Skip to content
17 changes: 17 additions & 0 deletions cms/cache_registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from django.core.cache import cache

REGISTRY_KEY = "_cache_key_registry"

def register_cache_key(key):
keys = cache.get(REGISTRY_KEY) or set()
keys.add(key)
cache.set(REGISTRY_KEY, keys, None)

def unregister_cache_key(key):
keys = cache.get(REGISTRY_KEY) or set()
if key in keys:
keys.remove(key)
cache.set(REGISTRY_KEY, keys, None)

def list_cache_keys():
return sorted(cache.get(REGISTRY_KEY) or [])
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")
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'}"
57 changes: 57 additions & 0 deletions cms/templates/cms/cache_tools.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
{% 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>

<!-- VIEW VALUE FORM -->
<form method="POST">
{% csrf_token %}
<label>Enter 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>

<!-- DELETE KEY FORM -->
<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>

<!-- CLEAR ALL -->
<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 @@ -31,6 +32,6 @@
url(r'^sitemap\.xml/$', sitemap, {'sitemaps' : spoken_sitemaps } , name='spoken_sitemap'),

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


]
47 changes: 47 additions & 0 deletions cms/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@

from donate.models import Payee


from django.contrib.admin.views.decorators import staff_member_required
from django.core.cache import caches
from django.shortcuts import render, redirect

from cms.cache_registry import list_cache_keys, unregister_cache_key
from django.utils.safestring import mark_safe


cache = caches['default']

def dispatcher(request, permalink=''):
if permalink == '':
return HttpResponseRedirect('/')
Expand Down Expand Up @@ -504,3 +515,39 @@ def verify_email(request):
else:
messages.error(request, 'Invalid Email ID', extra_tags='error')
return render(request, "cms/templates/verify_email.html", context)


@staff_member_required
def cache_tools(request):

keys = list_cache_keys()
value_output = None

if request.method == "POST":

if "view_key" in request.POST:
key = request.POST.get("cache_key").strip()
val = cache.get(key)
print("dfghjkdfghjk",val)

if val is None:
value_output = f"No value stored for key: '{key}'"
else:
value_output = mark_safe(f"<pre>{val}</pre>")

elif "clear_key" in request.POST:
key = request.POST.get("cache_key").strip()
cache.delete(key)
unregister_cache_key(key)
messages.success(request, f"Key '{key}' deleted.")
return redirect("cache-tools")

elif "clear_all" in request.POST:
cache.clear()
messages.success(request, "All cache cleared.")
return redirect("cache-tools")

return render(request, "cms/cache_tools.html", {
"keys": keys,
"value_output": value_output
})
4 changes: 3 additions & 1 deletion events/formsv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from events.models import *
from events.helpers import get_academic_years
from cms.validators import validate_csv_file
from spoken.config import SUBSCRIPTION_CHOICES

class StudentBatchForm(forms.ModelForm):
year = forms.ChoiceField(choices = get_academic_years())
Expand Down Expand Up @@ -668,4 +669,5 @@ class Meta(object):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['college_type'].widget.attrs.update({'class': 'form-control', 'readonly': 'readonly'})
self.fields['payment_status'].widget.attrs.update({'class': 'form-control', 'readonly': 'readonly'})
self.fields['payment_status'].widget.attrs.update({'class': 'form-control', 'readonly': 'readonly'})
self.fields['subscription'].choices = SUBSCRIPTION_CHOICES
5 changes: 2 additions & 3 deletions events/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

#creation app models
from creation.models import FossAvailableForWorkshop, FossAvailableForTest
from spoken.config import SUBSCRIPTION_CHOICES


PAYMENT_STATUS_CHOICES =(
Expand All @@ -32,9 +33,7 @@
COLLEGE_TYPE_CHOICES =(
('', '-----'), ('Engg', 'Engg'), ('ASC', 'ASC'), ('Polytechnic', 'Polytechnic'), ('University', 'University'), ('School', 'School')
)
SUBSCRIPTION_CHOICES = (
('', '-----'), ('365', 'One_Year'), ('182', 'Six_Months')
)



# Create your models here.
Expand Down
7 changes: 4 additions & 3 deletions events/templates/academic_payment_details_form.html
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,10 @@
if(type == 365){
$('#id_amount').val(29500)
}else if(type == 182){
$('#id_amount').val(14750)

}
$('#id_amount').val(14750)
}else if(type == 455){
$('#id_amount').val(29500)
}

})

Expand Down
Loading