Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
d38a4be
Update training request template and logic in viewsv2
ayishanishana21 Nov 7, 2025
888fa88
Address PR review feedback: updated text, removed commented code, and…
ayishanishana21 Nov 7, 2025
37ed8dc
Fix: Updated events/views.py for reset student password
ayishanishana21 Nov 18, 2025
d6e8050
Add notify_users command and update cms models
ayishanishana21 Nov 20, 2025
b5e422e
Added ilwmdlcourse in the training form
ayishanishana21 Nov 21, 2025
9ac132c
Updated 15 month option in academic payment form
ayishanishana21 Nov 26, 2025
2bf9051
Merge branch 'master' into pr-596
ayishanishana21 Dec 1, 2025
4ea8838
added caches page
ayishanishana21 Dec 1, 2025
bb7cfb3
Remove unintended changes in spoken/settings.py
ayishanishana21 Dec 1, 2025
09c5dae
memcachekey screen
ayishanishana21 Dec 3, 2025
ac27a0c
optimized attendance page
ayishanishana21 Dec 12, 2025
7807253
Added sign to the intern cert
ayishanishana21 Dec 12, 2025
9d08727
added signature to the internship cert
ayishanishana21 Dec 15, 2025
84cc940
added college/school option to the ILW
ayishanishana21 Dec 17, 2025
88ebb6d
resolved reset password error
ayishanishana21 Dec 18, 2025
a61ba00
added logic to update training request form
ayishanishana21 Dec 23, 2025
a8f028f
updation logic added to training request form
ayishanishana21 Dec 23, 2025
87d8616
Ignore local environment and vendor directories
ayishanishana21 Dec 23, 2025
6f8d3a2
resolved ajax_state_collage error
ayishanishana21 Dec 30, 2025
157ea53
removed comments
ayishanishana21 Dec 31, 2025
dd86f8c
fix /donate/initiate_payment/cdcontent/
ayishanishana21 Dec 31, 2025
60a7362
pswd profile error
ayishanishana21 Jan 3, 2026
bb1bde8
pswd profile error
ayishanishana21 Jan 3, 2026
fadb81d
Merge branch 'master' into pr-596
ayishanishana21 Jan 5, 2026
0cd5c10
home cache feature
ayishanishana21 Jan 5, 2026
3c515b6
filter in ILW Event
ayishanishana21 Jan 6, 2026
246e4d2
added sign to the certificate
ayishanishana21 Jan 6, 2026
c53385d
removed filter
ayishanishana21 Jan 7, 2026
c4e16f0
resolve stash conflict in spoken helpers
ayishanishana21 Jan 7, 2026
fab66f8
added form logic
ayishanishana21 Jan 7, 2026
d7a48f3
resolved cache
ayishanishana21 Jan 8, 2026
5ab317f
removed try exceptions
ayishanishana21 Jan 8, 2026
86176f8
added reset paswd
ayishanishana21 Jan 8, 2026
245a1be
admin email chenged
ayishanishana21 Jan 8, 2026
c12ea36
test request view
ayishanishana21 Jan 22, 2026
9bd9824
removed comments
ayishanishana21 Jan 22, 2026
6f1ddff
added dept in export csv
ayishanishana21 Jan 23, 2026
daf33c1
added dept function
ayishanishana21 Jan 29, 2026
d920056
ILW CSV status and event fix
ayishanishana21 Jan 30, 2026
0535f02
migration file
ayishanishana21 Jan 30, 2026
4623529
added subscription for collge
ayishanishana21 Feb 3, 2026
797597a
added download option in participant
ayishanishana21 Feb 5, 2026
8777a00
added reset password table
ayishanishana21 Feb 9, 2026
c07d558
added migration file
ayishanishana21 Feb 9, 2026
a57f7dd
added order by id query
ayishanishana21 Feb 13, 2026
07503d1
added event_id for url of register
ayishanishana21 Feb 16, 2026
7cef347
added line below commons
ayishanishana21 Mar 6, 2026
f6a2045
added username email functionality
ayishanishana21 Mar 10, 2026
ed98e2e
added topbar logo
ayishanishana21 Mar 18, 2026
3a88df6
Merged master into current branch and resolved conflicts
ayishanishana21 Mar 18, 2026
7a088d7
resolved error which happened because of institute type
ayishanishana21 Mar 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,16 @@ creation/hr-receipts/
static/spoken/embedding/

*.sql
env/
env/


# Local Python builds
Python-3.6.15/
Python-3.6.15.tgz

# Static vendor files
static/ckeditor/
static/debug_toolbar/

# Local management scripts
training/management/
4 changes: 2 additions & 2 deletions certificate/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ def koha_workshop_download(request):
uniqueness = True
else:
num += 1
qrcode = 'Verify at: https://spoken-tutorial.org/certificate/verify/{0} '.format(short_key)
qrcode = 'Verify at: https://spoken-tutorial.org/verify/{0} '.format(short_key)
details = {'name': name, 'serial_key': short_key, 'college': college}
certificate = create_koha_workshop_certificate(certificate_path, details,
qrcode, type, paper, workshop, file_name)
Expand Down Expand Up @@ -1172,7 +1172,7 @@ def create_koha_main_workshop9march_certificate(certificate_path, name, qrcode,
# uniqueness = True
# else:
# num += 1
# qrcode = 'Verify at: http://spoken-tutorial.org/certificate/verify/{0} '.format(short_key)
# qrcode = 'Verify at: http://spoken-tutorial.org/verify/{0} '.format(short_key)
# details = {'name': name, 'serial_key': short_key, 'rcid': rcid, 'remote': remote}
# certificate = create_koha_12oct_rc_certificate(certificate_path, details,
# qrcode, type, paper, workshop, file_name)
Expand Down
6 changes: 6 additions & 0 deletions cms/cacheurls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.conf.urls import url
from cms import views

urlpatterns = [
url(r'^cache-tools/$', views.cache_tools, name='cache-tools'),
]
133 changes: 133 additions & 0 deletions cms/management/commands/notify_users.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
from django.core.management.base import BaseCommand
from django.core.mail import EmailMultiAlternatives
from django.conf import settings
from django.contrib.auth.models import User
from django.db import transaction
from datetime import datetime, timedelta
import smtplib
from cms.models import EmailLog


class Command(BaseCommand):
help = "Notify users inactive for more than X years or before a given cutoff date."

def add_arguments(self, parser):

parser.add_argument(
"--years",
type=int,
default=5,
help="Check inactivity older than this many years (default = 5). Ignored if --cutoff-date provided.",
)

parser.add_argument(
"--limit",
type=int,
default=None,
help="Limit number of users to notify",
)

parser.add_argument(
"--cutoff-date",
type=str,
default=None,
help="Custom cutoff date in YYYY-MM-DD format (overrides --years)",
)

@transaction.atomic
def handle(self, *args, **options):

years = options.get("years")
limit = options.get("limit")
cutoff_input = options.get("cutoff_date")

if cutoff_input:
try:
cutoff_date = datetime.strptime(cutoff_input, "%Y-%m-%d")
except ValueError:
self.stdout.write(self.style.ERROR(
"❌ Invalid cutoff date format! Use YYYY-MM-DD"
))
return
else:
cutoff_date = datetime.now() - timedelta(days=years * 365)

cutoff_str = cutoff_date.strftime("%Y-%m-%d")
self.stdout.write(self.style.WARNING(
f"\n📅 Using cutoff date: {cutoff_str} (YYYY-MM-DD)\n"
))

users = User.objects.filter(
last_login__lt=cutoff_date,
is_active=True
).order_by("id")

if limit:
users = users[:limit]

total_users = users.count()
self.stdout.write(self.style.NOTICE(
f"🔎 Users inactive since before {cutoff_str}: {total_users}\n"
))

subject = "Reminder: Your account has been inactive for a long time"

sent_count = 0
failed_count = 0
for user in users:

message = f"""
Dear {user.first_name} {user.last_name},

Our system indicates that your account has not been used for a long time.

Your last login was on: {user.last_login.strftime("%Y-%m-%d")}

This is a reminder to log in again and continue using our services.

If you need help, simply reply to this email.

Regards,
Support Team
"""

email = EmailMultiAlternatives(
subject,
message,
settings.NO_REPLY_EMAIL,
to=[user.email],
)

try:
email.send(fail_silently=False)

sent_count += 1

EmailLog.objects.create(
user=user,
email=user.email,
status=True,
reason=None
)

self.stdout.write(self.style.SUCCESS(f"[SENT] {user.email}"))

except (smtplib.SMTPException, Exception) as e:

failed_count += 1

EmailLog.objects.create(
user=user,
email=user.email,
status=False,
reason=str(e)
)

self.stdout.write(self.style.ERROR(f"[FAILED] {user.email} → {e}"))

self.stdout.write("\n---------------------------------------")
self.stdout.write(self.style.SUCCESS(f"Emails Sent: {sent_count}"))
self.stdout.write(self.style.ERROR(f"Failed: {failed_count}"))
self.stdout.write(self.style.WARNING(f"Total Processed: {total_users}"))
self.stdout.write(self.style.NOTICE(f"Cutoff Date Used: {cutoff_str}"))
self.stdout.write("---------------------------------------\n")
29 changes: 29 additions & 0 deletions cms/migrations/0006_emaillog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.29 on 2025-11-19 09:04
from __future__ import unicode_literals

from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('cms', '0005_auto_20230302_1152'),
]

operations = [
migrations.CreateModel(
name='EmailLog',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=254)),
('sent_time', models.DateTimeField(auto_now_add=True)),
('status', models.BooleanField(default=False)),
('reason', models.TextField(blank=True, null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
),
]
14 changes: 13 additions & 1 deletion cms/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,16 @@ class UserType(models.Model):
ilw = jsonfield.JSONField(null=True)
status = models.CharField(choices=STATUS_CHOICES,max_length=25,default=1)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
updated = models.DateTimeField(auto_now=True)



class EmailLog(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
email = models.EmailField()
sent_time = models.DateTimeField(auto_now_add=True)
status = models.BooleanField(default=False)
reason = models.TextField(null=True, blank=True)

def __str__(self):
return f"{self.email} - {'Success' if self.status else 'Failed'}"
53 changes: 53 additions & 0 deletions cms/templates/cms/cache_tools.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{% extends "base.html" %}

{% block content %}
<div class="container" style="max-width:600px; margin-top:40px;">
<h2>Memcache Tools</h2>

{% for message in messages %}
<div class="alert alert-info">{{ message }}</div>
{% endfor %}

<!-- KEY LIST -->
<h4>Existing Cache Keys (via Memcached slabs)</h4>
<ul class="list-group">
{% for k in keys %}
<li class="list-group-item">{{ k }}</li>
{% empty %}
<li class="list-group-item">No keys found or Memcached restricts visibility.</li>
{% endfor %}
</ul>
<hr>

<form method="POST">
{% csrf_token %}
<label>Enter Cache Key to View Value:</label>
<input type="text" name="cache_key" class="form-control" placeholder="Enter cache key" required>
<button name="view_key" class="btn btn-info" style="margin-top:10px;">View Value</button>
</form>

{% if value_output %}
<div class="alert alert-secondary" style="margin-top:15px;">
<strong>Key Value:</strong><br>
{{ value_output|safe }}
</div>
{% endif %}
<hr>


<form method="POST">
{% csrf_token %}
<label>Clear Specific Key:</label>
<input type="text" name="cache_key" class="form-control" placeholder="Enter cache key" required>
<button name="clear_key" class="btn btn-primary" style="margin-top:10px;">Delete Key</button>
</form>
<hr>


<form method="POST">
{% csrf_token %}
<button name="clear_all" class="btn btn-danger">Clear ALL Cache</button>
</form>

</div>
{% endblock %}
3 changes: 2 additions & 1 deletion cms/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from spoken.sitemaps import SpokenStaticViewSitemap
from donate.views import *


app_name = 'cms'

spoken_sitemaps = {
Expand All @@ -32,6 +33,6 @@
url(r'^sitemap\.xml/$', sitemap, {'sitemaps' : spoken_sitemaps } , name='spoken_sitemap'),

url(r'^(?P<permalink>.+)/$', dispatcher, name="dispatcher"),


]
Loading