From e0ac9477bf7481915ebea2beb4dc86428b4b1153 Mon Sep 17 00:00:00 2001 From: Naman Sharma Date: Sat, 13 Dec 2025 13:37:06 +0000 Subject: [PATCH 01/19] Added the script for obtaining youtube playlists data from spoken tutorial youtube channel via the youtube data api v3 --- python-scripts/yt_playlists.py | 113 +++++++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 python-scripts/yt_playlists.py diff --git a/python-scripts/yt_playlists.py b/python-scripts/yt_playlists.py new file mode 100644 index 00000000..874b0a98 --- /dev/null +++ b/python-scripts/yt_playlists.py @@ -0,0 +1,113 @@ +# YouTube Data API Key Setup Instructions + + +# To run this script, you must generate a YouTube Data API key. +# +# Steps to obtain the key: +# +# 1. Go to Google Cloud Console: +# https://console.cloud.google.com/ +# +# 2. Create a new project (or select an existing one). +# +# 3. Enable the YouTube Data API v3: +# - Navigate to "APIs & Services" → "Enable APIs and Services" +# - Search for "YouTube Data API v3" +# - Click "Enable" +# +# 4. Create an API key: +# - Go to "APIs & Services" → "Credentials" +# - Click "Create Credentials" → "API key" +# +# 5. Put the key in a .env file in the same directory as this script: +# YOUTUBE_API_KEY = "YOUR_API_KEY_HERE" +# +# ============================================================ + + + +import os +import csv + +import requests +from dotenv import load_dotenv + +load_dotenv() + +API_KEY = os.getenv("YOUTUBE_API_KEY") +if not API_KEY: + raise RuntimeError("YOUTUBE_API_KEY is not set in the environment or .env file") +CHANNEL_ID = "UCcLQJOfR-MCcI5RtIHFl6Ww" +BASE_URL = "https://www.googleapis.com/youtube/v3" + +def get_playlists(): + url = f"{BASE_URL}/playlists" + params = { + "part": "snippet", + "channelId": CHANNEL_ID, + "maxResults": 50, + "key": API_KEY + } + playlists = [] + + while True: + data = requests.get(url, params=params).json() + for item in data.get("items", []): + playlists.append({ + "id": item["id"], + "title": item["snippet"]["title"] + }) + + if "nextPageToken" not in data: + break + params["pageToken"] = data["nextPageToken"] + + return playlists + + +def get_videos(playlist_id): + url = f"{BASE_URL}/playlistItems" + params = { + "part": "snippet", + "playlistId": playlist_id, + "maxResults": 50, + "key": API_KEY + } + videos = [] + + while True: + data = requests.get(url, params=params).json() + for item in data.get("items", []): + videos.append(item["snippet"]["title"]) + + if "nextPageToken" not in data: + break + params["pageToken"] = data["nextPageToken"] + + return videos + + +def main(): + playlists = get_playlists() + rows = [] + + for p in playlists: + print(f"Fetching playlist: {p['title']}") + videos = get_videos(p["id"]) + + for v in videos: + rows.append({ + "playlist_name": p["title"], + "video_name": v + }) + + with open("spoken_tutorial.csv", "w", newline="", encoding="utf-8") as f: + writer = csv.DictWriter(f, fieldnames=["playlist_name", "video_name"]) + writer.writeheader() + writer.writerows(rows) + + print("CSV created: spoken_tutorial.csv") + + +if __name__ == "__main__": + main() From 8dfc3c016e7101ad2f686c7602134efe33155652 Mon Sep 17 00:00:00 2001 From: Naman Sharma Date: Tue, 16 Dec 2025 16:33:44 +0000 Subject: [PATCH 02/19] Added the script for adding the available on youtube flag to the st_homepage_tutorials.csv file --- python-scripts/yt-flag-script.py | 213 +++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 python-scripts/yt-flag-script.py diff --git a/python-scripts/yt-flag-script.py b/python-scripts/yt-flag-script.py new file mode 100644 index 00000000..2e91eb04 --- /dev/null +++ b/python-scripts/yt-flag-script.py @@ -0,0 +1,213 @@ +import re + +import pandas as pd + +ST_FILE = "st_homepage_tutorials.csv" +YT_FILE = "spoken_tutorial.csv" +OUTPUT_FILE = "st_homepage_with_youtube_flag.csv" +TOKEN_MATCH_THRESHOLD = 0.5 +STOP_WORDS = { + "spoken", + "tutorial", + "tutorials", +} +STOP_PHRASES = ( + "spoken tutorial", +) +# Minimum FOSS name length to require substring matching (avoid false positives with "C", "R", etc.) +MIN_FOSS_LENGTH_FOR_SUBSTRING = 3 + +# Enable more lenient matching for edge cases +ENABLE_FALLBACK_MATCHING = True + + +def normalize(text): + if pd.isna(text): + return "" + return str(text).lower().strip() + + +def remove_punctuation(text): + return re.sub(r"[^\w\s]", " ", text) + + +def normalize_text_field(text): + base = normalize(text) + for phrase in STOP_PHRASES: + base = base.replace(phrase, " ") + no_punct = remove_punctuation(base) + return " ".join(no_punct.split()) + + +def tokenize_for_match(text): + cleaned = normalize_text_field(text) + tokens = [tok for tok in cleaned.split() if tok not in STOP_WORDS] + if tokens: + return tokens + return cleaned.split() + + +def tokens_match(source_tokens, target_tokens): + if not source_tokens or not target_tokens: + return False + overlap = set(source_tokens) & set(target_tokens) + ratio = len(overlap) / len(set(source_tokens)) + return ratio >= TOKEN_MATCH_THRESHOLD + + +def build_language_patterns(language_value): + tokens = [tok for tok in language_value.split() if tok] + return [re.compile(rf"\b{re.escape(token)}\b") for token in tokens] + + +def extract_language_from_playlist(playlist_name): + """Extract language from playlist name (e.g., 'Advance C - English' -> 'english')""" + if pd.isna(playlist_name): + return "" + + match = re.search(r'-\s*([a-zA-Z]+)\s*$', str(playlist_name)) + if match: + return match.group(1).lower().strip() + return "" + + +def extract_language_from_video_title(video_name): + """Extract language from video title (e.g., 'Tutorial Name - Hindi' -> 'hindi')""" + if pd.isna(video_name): + return "" + + match = re.search(r'-\s*([a-zA-Z]+)\s*$', str(video_name)) + if match: + return match.group(1).lower().strip() + return "" + + +def main(): + st_df = pd.read_csv(ST_FILE) + yt_df = pd.read_csv(YT_FILE) + + print(f"Loaded {len(st_df)} ST homepage tutorials") + print(f"Loaded {len(yt_df)} YouTube videos") + + # Normalize YouTube data + yt_df["playlist_norm"] = yt_df["playlist_name"].apply(normalize_text_field) + yt_df["title_tokens"] = yt_df["video_name"].apply(tokenize_for_match) + yt_df["title_lang_text"] = yt_df["video_name"].apply(normalize_text_field) + yt_df["playlist_language"] = yt_df["playlist_name"].apply(extract_language_from_playlist) + yt_df["video_language"] = yt_df["video_name"].apply(extract_language_from_video_title) + if "description" in yt_df.columns: + yt_df["description_lang_text"] = yt_df["description"].apply(normalize_text_field) + else: + yt_df["description_lang_text"] = "" + + # Normalize ST homepage data + st_df["foss_norm"] = st_df["foss_name"].apply(normalize_text_field) + st_df["tutorial_tokens"] = st_df["tutorial"].apply(tokenize_for_match) + st_df["language_norm"] = st_df["language"].apply(normalize_text_field) + st_df["language_patterns"] = st_df["language_norm"].apply(build_language_patterns) + + print("Processing tutorials...") + + def is_available(row): + foss = row["foss_norm"] + tutorial_tokens = row["tutorial_tokens"] + language_patterns = row["language_patterns"] + language_norm = row["language_norm"] + + if not foss or not tutorial_tokens or not language_patterns: + return "No" + + # Filter YouTube videos by FOSS name + if len(foss) < MIN_FOSS_LENGTH_FOR_SUBSTRING: + # Use word boundary matching for short names + pattern = rf"\b{re.escape(foss)}\b" + candidates = yt_df[ + yt_df["playlist_norm"].str.contains(pattern, na=False, regex=True) + ] + else: + candidates = yt_df[ + yt_df["playlist_norm"].str.contains(foss, na=False, regex=False) + ] + + if candidates.empty: + return "No" + + for _, video_row in candidates.iterrows(): + # Step 1: Check if tutorial tokens match video title tokens + if not tokens_match(tutorial_tokens, video_row["title_tokens"]): + continue + + # Step 2: Check language match - improved logic + # Method 1: Check if language appears in title or description + title_text = video_row["title_lang_text"] + description_text = video_row["description_lang_text"] + + language_in_content = any( + pattern.search(title_text) or pattern.search(description_text) + for pattern in language_patterns + ) + + # Method 2: Check extracted language from playlist/video name + playlist_lang = video_row["playlist_language"] + video_lang = video_row["video_language"] + + # Match if: + # a) Language found in title/description, OR + # b) Language matches playlist language, OR + # c) Language matches video language + language_matches = ( + language_in_content or + (playlist_lang and playlist_lang == language_norm) or + (video_lang and video_lang == language_norm) + ) + + if language_matches: + return "Yes" + + # Fallback: If no match found with strict language matching, + # check if there's a video with the same tutorial in ANY language + if ENABLE_FALLBACK_MATCHING: + for _, video_row in candidates.iterrows(): + tutorial_set = set(tutorial_tokens) + video_set = set(video_row["title_tokens"]) + + if not tutorial_set or not video_set: + continue + + overlap = tutorial_set & video_set + # Use a higher threshold for fallback to reduce false positives + ratio = len(overlap) / len(tutorial_set) + + if ratio >= 0.7: + video_has_different_lang = ( + (playlist_lang and playlist_lang != language_norm) or + (video_lang and video_lang != language_norm) + ) + pass + + return "No" + + st_df["available_on_youTube"] = st_df.apply(is_available, axis=1) + + st_df.drop( + columns=["foss_norm", "tutorial_tokens", "language_norm", "language_patterns"], + inplace=True, + ) + + st_df.to_csv(OUTPUT_FILE, index=False) + + # Print summary statistics + yes_count = (st_df["available_on_youTube"] == "Yes").sum() + no_count = (st_df["available_on_youTube"] == "No").sum() + + print(f"\n{'='*60}") + print(f"Done. Output written to {OUTPUT_FILE}") + print(f"{'='*60}") + print(f"Total tutorials: {len(st_df)}") + print(f"Available on YouTube: {yes_count} ({yes_count/len(st_df)*100:.1f}%)") + print(f"Not available: {no_count} ({no_count/len(st_df)*100:.1f}%)") + print(f"{'='*60}") + + +if __name__ == "__main__": + main() From 7fa19d5be958f0c87667e5d62e3d862e368c657c Mon Sep 17 00:00:00 2001 From: Naman Sharma Date: Tue, 6 Jan 2026 13:40:16 +0000 Subject: [PATCH 03/19] Fix Memcached key length issue in tutorial search cache --- spoken/helpers.py | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/spoken/helpers.py b/spoken/helpers.py index 90f41d08..5debaa70 100644 --- a/spoken/helpers.py +++ b/spoken/helpers.py @@ -10,6 +10,7 @@ from events.models import Testimonials from cms.models import Notification, Event from .config import CACHE_RANDOM_TUTORIALS, CACHE_TR_REC, CACHE_TESTIMONIALS, CACHE_NOTIFICATIONS, CACHE_EVENTS, CACHE_TUTORIALS +import hashlib def get_key(identifier, key_val): return f"{identifier}:{key_val.lower().strip().replace(' ','_')}" @@ -122,23 +123,43 @@ def get_tutorials_list(foss, lang): # ---- Foss Choice For Search Bar ---- def get_foss_choice(show_on_homepage=1, lang=None): + if lang and len(lang) > 50: + lang = lang[:50] + if lang: - cache_key = get_key("tutorial_search_foss", f"{show_on_homepage}:{lang}") + raw_key = f"{show_on_homepage}:{lang}" else: - cache_key = f"tutorial_search_foss:{show_on_homepage}:all" + raw_key = f"{show_on_homepage}:all" + + hashed_key = hashlib.md5(raw_key.encode("utf-8")).hexdigest() + cache_key = get_key("tutorial_search_foss", hashed_key) + foss_list_choices = cache.get(cache_key) if foss_list_choices is not None: return foss_list_choices - + foss_list_choices = [('', '-- All Courses --'), ] - foss_qs = TutorialResource.objects.filter(status__in=[1,2], tutorial_detail__foss__show_on_homepage=show_on_homepage) + foss_qs = TutorialResource.objects.filter( + status__in=[1,2], + tutorial_detail__foss__show_on_homepage=show_on_homepage + ) if lang: foss_qs = foss_qs.filter(language__name=lang) - foss_list = foss_qs.values('tutorial_detail__foss__foss').annotate( - Count('id')).order_by('tutorial_detail__foss__foss').values_list('tutorial_detail__foss__foss', 'id__count').distinct() + + foss_list = foss_qs.values( + 'tutorial_detail__foss__foss' + ).annotate( + Count('id') + ).order_by( + 'tutorial_detail__foss__foss' + ).values_list( + 'tutorial_detail__foss__foss', 'id__count' + ).distinct() for foss_row in foss_list: - foss_list_choices.append((str(foss_row[0]), str(foss_row[0]) + ' (' + str(foss_row[1]) + ')')) + foss_list_choices.append( + (str(foss_row[0]), str(foss_row[0]) + ' (' + str(foss_row[1]) + ')') + ) cache.set(cache_key, foss_list_choices, timeout=CACHE_TUTORIALS) return foss_list_choices From e6a6a6f03f7c8250b428ee656409c753af9ce466 Mon Sep 17 00:00:00 2001 From: ayishanishana21 <86144493+ayishanishana21@users.noreply.github.com> Date: Tue, 6 Jan 2026 20:56:25 +0530 Subject: [PATCH 04/19] added sign to the internship certificate (#638) --- training/views.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/training/views.py b/training/views.py index 5d0f2dd8..b6a5f744 100644 --- a/training/views.py +++ b/training/views.py @@ -1217,8 +1217,8 @@ def create_ilwtest_certificate(self, event, user, teststatus): if event.event_type != "INTERN": imgDoc.drawCentredString(405, 470, "Certificate for Completion of Training") # Draw image on Canvas and save PDF in buffer - imgPath = get_signature(training_start) - imgDoc.drawImage(imgPath, 600, 100, 150, 76) + imgPath = get_signature(training_start) + imgDoc.drawImage(imgPath, 600, 100, 150, 76) #password certificate_pass = '' From b2bdfdd28e68399a7fe0fb3e5d55f3b29f11f657 Mon Sep 17 00:00:00 2001 From: ayishanishana21 <86144493+ayishanishana21@users.noreply.github.com> Date: Wed, 7 Jan 2026 13:07:42 +0530 Subject: [PATCH 05/19] filter in ILW Event (#636) * filter in ILW Event * removed filter --- training/filters.py | 9 ++- training/views.py | 131 +++++++++++++++++++++++++++----------------- 2 files changed, 88 insertions(+), 52 deletions(-) diff --git a/training/filters.py b/training/filters.py index 7b712787..dbb07e53 100644 --- a/training/filters.py +++ b/training/filters.py @@ -1,7 +1,14 @@ import django_filters from .models import Company +from training.models import Participant +from creation.models import FossCategory +from events.models import State, AcademicCenter +from training.models import TrainingEvents, Participant + class CompanyFilter(django_filters.FilterSet): class Meta: model = Company - fields = ['name', 'state', 'company_type'] \ No newline at end of file + fields = ['name', 'state', 'company_type'] + + diff --git a/training/views.py b/training/views.py index b6a5f744..a07b6f36 100644 --- a/training/views.py +++ b/training/views.py @@ -95,57 +95,85 @@ def form_invalid(self, form): #ILW main page class TrainingEventsListView(ListView): - model = TrainingEvents - raw_get_data = None - header = None - collection = None - - def dispatch(self, *args, **kwargs): - self.status = self.kwargs['status'] - today = date.today() - self.show_myevents = False - if self.request.user: - myevents = TrainingEvents.objects.filter(id__in=Participant.objects.filter(user_id=self.request.user.id).values('event_id')) - if myevents: - self.show_myevents = True - - if self.status == 'completed': - self.events = TrainingEvents.objects.filter(event_end_date__lt=today).order_by('-event_end_date') - if self.status == 'ongoing': - self.events = TrainingEvents.objects.filter(event_end_date__gte=today).order_by('registartion_end_date') - if self.status == 'myevents': - participant = Participant.objects.filter( - Q(payment_status__status=1)|Q(registartion_type__in=(1,3)), - user_id=self.request.user.id) - self.events = participant - - self.raw_get_data = self.request.GET.get('o', None) - self.queryset = get_sorted_list( - self.request, - self.events, - self.header, - self.raw_get_data - ) - - self.collection= ViewEventFilter(self.request.GET, queryset=self.queryset, user=self.request.user) - return super(TrainingEventsListView, self).dispatch(*args, **kwargs) - - def get_context_data(self, **kwargs): - context = super(TrainingEventsListView, self).get_context_data(**kwargs) - context['form'] = self.collection.form - page = self.request.GET.get('page') - collection = get_page(self.collection.qs, page) - context['collection'] = collection - context['ordering'] = get_field_index(self.raw_get_data) - context['status'] = self.status - context['events'] = self.events - context['show_myevents'] = self.show_myevents - context['ILW_ONLINE_TEST_URL'] = settings.ILW_ONLINE_TEST_URL - context['HN_API'] = HN_API - - if self.request.user: - context['user'] = self.request.user - return context + model = TrainingEvents + raw_get_data = None + header = None + collection = None + + def dispatch(self, *args, **kwargs): + self.status = self.kwargs['status'] + today = date.today() + self.show_myevents = False + + if self.request.user: + myevents = TrainingEvents.objects.filter(id__in=Participant.objects.filter(user_id=self.request.user.id).values('event_id')) + if myevents: + self.show_myevents = True + + if self.status == 'completed': + self.events = TrainingEvents.objects.filter(event_end_date__lt=today).order_by('-event_end_date') + if self.status == 'ongoing': + self.events = TrainingEvents.objects.filter(event_end_date__gte=today).order_by('registartion_end_date') + if self.status == 'myevents': + participants = Participant.objects.filter( + Q(payment_status__status=1)|Q(registartion_type__in=(1,3)), + user_id=self.request.user.id) + self.participants = participants # Store participants separately + # Get TrainingEvents for filtering + event_ids = participants.values_list('event_id', flat=True).distinct() + self.events = TrainingEvents.objects.filter(id__in=event_ids) + + self.raw_get_data = self.request.GET.get('o', None) + self.queryset = get_sorted_list( + self.request, + self.events, + self.header, + self.raw_get_data + ) + + self.collection = ViewEventFilter(self.request.GET, queryset=self.queryset, user=self.request.user) + return super(TrainingEventsListView, self).dispatch(*args, **kwargs) + + def get_context_data(self, **kwargs): + context = super(TrainingEventsListView, self).get_context_data(**kwargs) + context['form'] = self.collection.form + + if self.status == 'myevents': + # Get filtered events from the filter + filtered_events = self.collection.qs + + # Get participant data for the filtered events + if self.request.user: + participants = Participant.objects.filter( + Q(payment_status__status=1)|Q(registartion_type__in=(1,3)), + user_id=self.request.user.id, + event_id__in=filtered_events.values_list('id', flat=True) + ) + + # Order participants according to filtered events order + event_order = {event.id: idx for idx, event in enumerate(filtered_events)} + participants_list = list(participants) + participants_list.sort(key=lambda x: event_order.get(x.event_id, 0)) + + page = self.request.GET.get('page') + collection = get_page(participants_list, page) + context['collection'] = collection + else: + page = self.request.GET.get('page') + collection = get_page(self.collection.qs, page) + context['collection'] = collection + + context['ordering'] = get_field_index(self.raw_get_data) + context['status'] = self.status + context['events'] = self.events + context['show_myevents'] = self.show_myevents + context['ILW_ONLINE_TEST_URL'] = settings.ILW_ONLINE_TEST_URL + context['HN_API'] = HN_API + + if self.request.user: + context['user'] = self.request.user + + return context def _validate_parameters(parameter, value): if value is None: @@ -158,6 +186,7 @@ def _validate_parameters(parameter, value): return value.isnumeric() return True + @csrf_exempt def register_user(request): form = RegisterUser() From 2659b6e089dfd272be5fa1c993b5d360dc114cea Mon Sep 17 00:00:00 2001 From: ayishanishana21 <86144493+ayishanishana21@users.noreply.github.com> Date: Thu, 8 Jan 2026 12:50:16 +0530 Subject: [PATCH 06/19] Fix/cache resolution (#639) * resolved cache * Add cache registry cache-related templates --- cms/cache_registry.py | 17 ++++++++ cms/views.py | 39 +++++++++++++++++-- spoken/helpers.py | 4 +- static/cms/templates/manage_cache.html | 13 ++++++- static/events/templates/events_dashboard.html | 2 +- 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 cms/cache_registry.py diff --git a/cms/cache_registry.py b/cms/cache_registry.py new file mode 100644 index 00000000..5d11a3e7 --- /dev/null +++ b/cms/cache_registry.py @@ -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 []) diff --git a/cms/views.py b/cms/views.py index 3ae09ebb..b72383ea 100644 --- a/cms/views.py +++ b/cms/views.py @@ -26,7 +26,7 @@ from mdldjango.models import MdlUser from mdldjango.urls import * from django.template.context_processors import csrf - +from cms.cache_registry import unregister_cache_key,list_cache_keys from donate.models import Payee from django.core.cache import cache @@ -511,7 +511,7 @@ def verify_email(request): @login_required def manage_cache(request): - if not request.user.groups.filter(name='Technical-Team').exists(): + if not request.user.groups.filter(name__in=['Technical-Team', 'Administrator']).exists(): raise PermissionDenied('You are not allowed to view this page!') context = {} @@ -535,6 +535,39 @@ def manage_cache(request): messages.success(request, f"memcache cleared successfully") except Exception as e: messages.error(request, f"An error occurred while clearing cache: {e}") - print(f"cache error -- {e}") + + elif deletion_type == 'homepage': + try: + all_keys = list_cache_keys() + homepage_keys = [ + key for key in all_keys + if key.startswith(( + 'tutorial_search_foss:', + 'tutorial_search_lang:', + )) + ] + + if homepage_keys: + for key in homepage_keys: + cache.delete(key) + unregister_cache_key(key) + messages.success( + request, + "Homepage cache cleared successfully.
" + "Deleted keys:
" + + "
".join(homepage_keys) + ) + + else: + messages.warning( + request, + "Homepage cache keys were not found or already expired." + ) + + except Exception as e: + messages.error( + request, + "An error occurred while clearing homepage cache: {}".format(e) + ) return render(request, status_template, context=context) # return to payment page site \ No newline at end of file diff --git a/spoken/helpers.py b/spoken/helpers.py index 5debaa70..074f76e3 100644 --- a/spoken/helpers.py +++ b/spoken/helpers.py @@ -9,6 +9,7 @@ from creation.models import TutorialSummaryCache, TutorialResource, FossCategory from events.models import Testimonials from cms.models import Notification, Event +from cms.cache_registry import register_cache_key from .config import CACHE_RANDOM_TUTORIALS, CACHE_TR_REC, CACHE_TESTIMONIALS, CACHE_NOTIFICATIONS, CACHE_EVENTS, CACHE_TUTORIALS import hashlib @@ -162,10 +163,10 @@ def get_foss_choice(show_on_homepage=1, lang=None): ) cache.set(cache_key, foss_list_choices, timeout=CACHE_TUTORIALS) + register_cache_key(cache_key) return foss_list_choices - # ---- Language Choice For Search Bar ---- def get_lang_choice(show_on_homepage=1, foss=None): if is_valid_foss(foss): @@ -187,4 +188,5 @@ def get_lang_choice(show_on_homepage=1, foss=None): lang_list_choices.append((str(lang_row[0]), str(lang_row[0]) + ' (' + str(lang_row[1]) + ')')) cache.set(cache_key, lang_list_choices, timeout=CACHE_TUTORIALS) + register_cache_key(cache_key) return lang_list_choices \ No newline at end of file diff --git a/static/cms/templates/manage_cache.html b/static/cms/templates/manage_cache.html index 0785a5fb..81f4166b 100644 --- a/static/cms/templates/manage_cache.html +++ b/static/cms/templates/manage_cache.html @@ -64,9 +64,18 @@
Option 2: Clear all memcache data

Note: Clearing all memcache might slow down the site initially

- - + +
+ {% csrf_token %} +
+
Option 3: Clear homepage cache
+

This will clear cache entries related to the homepage only.

+ + +
+
+ {% endblock %} {% block jsblock %} diff --git a/static/events/templates/events_dashboard.html b/static/events/templates/events_dashboard.html index 6466a34c..abb9f986 100755 --- a/static/events/templates/events_dashboard.html +++ b/static/events/templates/events_dashboard.html @@ -300,7 +300,7 @@
Online Assessment Test
Manage Cache
From 45ed419e6db421bf2cf2fb762ebde42ee638955a Mon Sep 17 00:00:00 2001 From: ankitamk14 Date: Thu, 8 Jan 2026 13:20:12 +0530 Subject: [PATCH 07/19] bug fix for training edit form (#640) --- events/viewsv2.py | 1 + 1 file changed, 1 insertion(+) diff --git a/events/viewsv2.py b/events/viewsv2.py index 00d3777f..da466e38 100755 --- a/events/viewsv2.py +++ b/events/viewsv2.py @@ -705,6 +705,7 @@ def form_valid(self, form, **kwargs): self.training.department = selectedDept self.training.batch = selectedBatch self.training.course_type = form.cleaned_data['course_type'] + self.training.fossmdlmap = form.cleaned_data.get('fossmdlmap') if self.training.batch.is_foss_batch_acceptable(selectedCourse): self.training.sem_start_date = form.cleaned_data['sem_start_date'] From f111e291d57a4ce09a9eb0193f9b4deb3aa251cc Mon Sep 17 00:00:00 2001 From: ankitamk14 Date: Thu, 8 Jan 2026 14:27:37 +0530 Subject: [PATCH 08/19] Pr 634 (#641) * pswd profile error * pswd profile error * removed try exceptions * profile --------- Co-authored-by: ayishanishana21 --- cms/views.py | 82 ++++++++++++++--------- static/cms/templates/change_password.html | 2 +- 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/cms/views.py b/cms/views.py index b72383ea..0ceee676 100644 --- a/cms/views.py +++ b/cms/views.py @@ -4,6 +4,11 @@ import random import string +from django.contrib.auth.models import User + +from cms.models import Profile +from cms.forms import ChangePasswordForm + # Third Party Stuff from django.conf import settings from django.contrib import messages, auth @@ -270,11 +275,13 @@ def account_logout(request): @login_required def account_profile(request, username): user = request.user - try: - profile = Profile.objects.get(user_id=user.id) - except: - profile = create_profile(user) + + profile = Profile.objects.filter(user_id=user.id).order_by('id').first() + if not profile: + profile = create_profile(user) + old_file_path = settings.MEDIA_ROOT + str(profile.picture) + new_file_path = None if request.method == 'POST': form = ProfileForm(user, request.POST, request.FILES, instance = profile) @@ -319,7 +326,8 @@ def account_profile(request, username): else: context = {} context.update(csrf(request)) - instance = Profile.objects.get(user_id=user.id) + # instance = Profile.objects.get(user_id=user.id) + instance = Profile.objects.filter(user_id=user.id).first() context['form'] = ProfileForm(user, instance = instance) return render(request, 'cms/templates/profile.html', context) @@ -329,13 +337,10 @@ def account_view_profile(request, username): raise PermissionDenied('You are not allowed to view this page!') user = User.objects.get(username = username) - profile = None - try: - profile = Profile.objects.get(user = user) - except: - profile = create_profile(user) + profile = Profile.objects.filter(user=user).first() + if not profile: + profile = create_profile(user) - context = { 'profile' : profile, 'media_url' : settings.MEDIA_URL, @@ -425,45 +430,62 @@ def password_reset(request): return render(request, 'cms/templates/password_reset.html', context) -#@login_required def change_password(request): - # chacking uselogin - pcode = request.GET.get('auto', False) - username = request.GET.get('username', False) - nextUrl = request.GET.get('next', False) + pcode = request.GET.get('auto') + username = request.GET.get('username') + nextUrl = request.GET.get('next') - # check pcode in profile page - if pcode and username and nextUrl: - user = User.objects.get(username=username) - profile = Profile.objects.get(user=user) - if profile.confirmation_code == pcode: - user.backend='django.contrib.auth.backends.ModelBackend' - login(request,user) + profile = None + + if pcode and username: + user = User.objects.get(username=username).first() + if user: + profile = Profile.objects.filter(user=user,confirmation_code=pcode).first() + + if profile: + user.backend = 'django.contrib.auth.backends.ModelBackend' + login(request, user) if request.user.is_anonymous(): - return HttpResponseRedirect('/accounts/login/?next=/accounts/change-password') + return HttpResponseRedirect('/accounts/login/?next=/accounts/change-password/') + + if not profile: + profile = (Profile.objects.filter(user=request.user).order_by('id').first()) + + if not profile: + messages.error(request, "Profile not found.") + return HttpResponseRedirect('/accounts/login/') - context = {} form = ChangePasswordForm() + if request.method == "POST": form = ChangePasswordForm(request.POST) if form.is_valid(): - profile = Profile.objects.get(user_id = form.cleaned_data['userid'], confirmation_code = form.cleaned_data['code']) + profile = Profile.objects.get(user_id=form.cleaned_data['userid'],confirmation_code=form.cleaned_data['code']) + user = profile.user user.set_password(form.cleaned_data['new_password']) user.save() - # change if any mdl user pass too + # change if any mdl user pass too from mdldjango.views import changeMdlUserPass changeMdlUserPass(user.email, form.cleaned_data['new_password']) if nextUrl: return HttpResponseRedirect(nextUrl.split("?", 1)[0]) - messages.success(request, "Your account password has been updated successfully!") + messages.success(request,"Your account password has been updated successfully!") return HttpResponseRedirect("/accounts/view-profile/" + user.username) - context['form'] = form + if profile is None: + profile = request.user.profile_set.first() + context = { + 'form': form, + 'profile': profile, + 'nextUrl': nextUrl, + } context.update(csrf(request)) - return render(request, 'cms/templates/change_password.html', context) + + return render(request,'cms/templates/change_password.html',context) + def confirm_student(request, token): diff --git a/static/cms/templates/change_password.html b/static/cms/templates/change_password.html index 10964e42..bfaf80a4 100644 --- a/static/cms/templates/change_password.html +++ b/static/cms/templates/change_password.html @@ -16,7 +16,7 @@ {% endif %} - +
{% render_field form.old_password class+="form-control old_password" tabindex="1" %} From 071486e0aa05f79263b7a5ca07dc73a0166ef3af Mon Sep 17 00:00:00 2001 From: ayishanishana21 <86144493+ayishanishana21@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:29:55 +0530 Subject: [PATCH 09/19] added college/school option to the ILW (#621) * added college/school option to the ILW * removed commented lines --- training/templates/register_user.html | 50 ++++++++++++++++++--------- training/views.py | 2 +- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/training/templates/register_user.html b/training/templates/register_user.html index 794db2f6..4e52aaa2 100644 --- a/training/templates/register_user.html +++ b/training/templates/register_user.html @@ -141,7 +141,7 @@

Welcome {{ form.state.errors }}

- {% if event_obj.event_type == "CDP" or event_obj.event_type == "PDP" or event_obj.event_type == "HN" %} + {% if event_obj.event_type == "CDP" or event_obj.event_type == "PDP" %}
@@ -150,7 +150,7 @@

Welcome

{% endif %} - {% if event_obj.event_type == "CDP" or event_obj.event_type == "PDP" or event_obj.event_type == "HN" %} + {% if event_obj.event_type == "CDP" or event_obj.event_type == "PDP" %} @@ -337,22 +337,40 @@

Welcome