From e293c27d8afeb3b9e7820c1f578c33e842667a67 Mon Sep 17 00:00:00 2001 From: KavyanshKhaitan2 Date: Wed, 16 Sep 2026 20:18:31 +0530 Subject: [PATCH 1/3] Fix new journal button showing for non-owners (visual bug) --- twisted/twisted_site/templates/client/projects/detail.html | 2 ++ 1 file changed, 2 insertions(+) diff --git a/twisted/twisted_site/templates/client/projects/detail.html b/twisted/twisted_site/templates/client/projects/detail.html index 0bdf44f..99b0aa1 100644 --- a/twisted/twisted_site/templates/client/projects/detail.html +++ b/twisted/twisted_site/templates/client/projects/detail.html @@ -132,11 +132,13 @@

{{ project.project_name }}

Log more time!
Log atleast 60 minutes before shipping your project.
+ {% if project.user == user %} + {% endif %}
{% endif %} {% endif %} From 676e16c0a4111be96f97a1b5b08bcbfeb4db0f87 Mon Sep 17 00:00:00 2001 From: KavyanshKhaitan2 Date: Wed, 16 Sep 2026 21:16:33 +0530 Subject: [PATCH 2/3] Fix error display on new journal --- twisted/twisted_site/views/client/journal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/twisted/twisted_site/views/client/journal.py b/twisted/twisted_site/views/client/journal.py index 9c47fe3..96c5298 100644 --- a/twisted/twisted_site/views/client/journal.py +++ b/twisted/twisted_site/views/client/journal.py @@ -32,6 +32,7 @@ def get( context["project"] = project context["max_minutes"] = HACKATIME_MAX_LOGGABLE_MINUTES + context["info"] = info if project.is_shipped(): return redirect("fr.projects.detail", project_id) From 3024689df2498c029950be12af976c78f1b920e8 Mon Sep 17 00:00:00 2001 From: KavyanshKhaitan2 Date: Wed, 16 Sep 2026 23:01:24 +0530 Subject: [PATCH 3/3] Work on admin permissions --- .../twisted_site/templates/admin/user.html | 61 ++++++++- .../templates/cotton/admin/dialog.html | 39 ++++++ twisted/twisted_site/views/admin/admin.py | 116 +++++++++++------- .../twisted_site/views/admin/announcements.py | 6 + twisted/twisted_site/views/admin/dashboard.py | 1 + .../twisted_site/views/admin/fulfillment.py | 5 + twisted/twisted_site/views/admin/pathways.py | 21 ++++ twisted/twisted_site/views/admin/review.py | 10 ++ twisted/twisted_site/views/admin/shop.py | 5 + twisted/twisted_site/views/admin/users.py | 36 +++++- 10 files changed, 252 insertions(+), 48 deletions(-) create mode 100644 twisted/twisted_site/templates/cotton/admin/dialog.html diff --git a/twisted/twisted_site/templates/admin/user.html b/twisted/twisted_site/templates/admin/user.html index d06857a..d3c8f6b 100644 --- a/twisted/twisted_site/templates/admin/user.html +++ b/twisted/twisted_site/templates/admin/user.html @@ -202,9 +202,64 @@

Admin

Not an admin!

This user is not an admin yet!

{% if request.user.profile.staff_permissions.superuser %} - - Make admin - + + + Make admin + +
+
+
+ +
+ + + + +
+
Type the text in the input above to confirm this action
+ + +
+ + Confirm + +
+ {% csrf_token %} + + +
+
+
+
{% endif %} {% endif %} diff --git a/twisted/twisted_site/templates/cotton/admin/dialog.html b/twisted/twisted_site/templates/cotton/admin/dialog.html new file mode 100644 index 0000000..55ffe44 --- /dev/null +++ b/twisted/twisted_site/templates/cotton/admin/dialog.html @@ -0,0 +1,39 @@ +
+
+ {{ trigger }} +
+
+ +
+ + +
+ +

{{ title }}

+ + +
+ {{ slot }} +
+
+
+
\ No newline at end of file diff --git a/twisted/twisted_site/views/admin/admin.py b/twisted/twisted_site/views/admin/admin.py index f63e369..097650e 100644 --- a/twisted/twisted_site/views/admin/admin.py +++ b/twisted/twisted_site/views/admin/admin.py @@ -1,6 +1,7 @@ from dataclasses import dataclass from typing import Any, override +from django.contrib import messages from django.http import HttpRequest, HttpResponseBase from django.shortcuts import redirect, resolve_url from django.views import View @@ -19,58 +20,84 @@ class SidebarLink: # Create your views here. class AdminView(View): audit_log: AuditLog # pyright: ignore[reportUninitializedInstanceVariable] - perms: ProfileStaffPermissions | None # pyright: ignore[reportUninitializedInstanceVariable] + perms: ProfileStaffPermissions # pyright: ignore[reportUninitializedInstanceVariable] + allowed = False def get_context_data(self, page: str, subpage: str | None = None) -> dict[str, Any]: # pyrefly: ignore[explicit-any] context: dict[str, Any] = {} # pyrefly: ignore[explicit-any] context["page"] = page context["subpage"] = subpage - context["sidebar_links"] = [ + sidebar_links = [ SidebarLink( name="dashboard", icon="analytics", text="Analytics", href=resolve_url("admin.dash"), ), - SidebarLink( - name="users", - icon="profile", - text="Users", - href=resolve_url("admin.users"), - ), - SidebarLink( - name="pathways", - icon="controls", - text="Pathways", - href=resolve_url("admin.pathways"), - ), - SidebarLink( - name="fulfillment", - icon="list", - text="Fulfillment", - href=resolve_url("admin.fulfillment"), - ), - SidebarLink(name="shop", icon="bag-add", text="Shop", href=resolve_url("admin.shop")), - SidebarLink( - name="review", - icon="message-new", - text="Review", - href=resolve_url("admin.review"), - ), - SidebarLink( - name="announcements", - icon="important", - text="Announcements", - href=resolve_url("admin.announcements"), - ), - SidebarLink( - name="logs", - icon="view", - text="Audit Logs", - href=resolve_url("admin.logs") + "?page=1", - ), ] - context["profile"] = self.request.user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] + if self.perms.view_users: + sidebar_links.append( + SidebarLink( + name="users", + icon="profile", + text="Users", + href=resolve_url("admin.users"), + ), + ) + if self.perms.view_pathways: + sidebar_links.append( + SidebarLink( + name="pathways", + icon="controls", + text="Pathways", + href=resolve_url("admin.pathways"), + ), + ) + if self.perms.manage_fulfillments: + sidebar_links.append( + SidebarLink( + name="fulfillment", + icon="list", + text="Fulfillment", + href=resolve_url("admin.fulfillment"), + ), + ) + if self.perms.manage_shop: + sidebar_links.append( + SidebarLink( + name="shop", icon="bag-add", text="Shop", href=resolve_url("admin.shop"), + ), + ) + if self.perms.view_review: + sidebar_links.append( + SidebarLink( + name="review", + icon="message-new", + text="Review", + href=resolve_url("admin.review"), + ), + ) + if self.perms.manage_announcements: + sidebar_links.append( + SidebarLink( + name="announcements", + icon="important", + text="Announcements", + href=resolve_url("admin.announcements"), + ), + ) + if self.perms.view_auditlogs: + sidebar_links.append( + SidebarLink( + name="logs", + icon="view", + text="Audit Logs", + href=resolve_url("admin.logs") + "?page=1", + ), + ) + + context["sidebar_links"] = sidebar_links + context["profile"] = self.request.user.profile # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] return context @override @@ -86,13 +113,18 @@ def dispatch(self, request: HttpRequest, *args: object, **kwargs: object) -> Htt additional_context={}, ) - perms = self.request.user.profile.staff_permissions # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] + perms = self.request.user.profile.staff_permissions # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] if perms is None: - profile = self.request.user.profile # ty:ignore[unresolved-attribute] # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] + profile = self.request.user.profile # pyright: ignore[reportAttributeAccessIssue] # pyrefly: ignore[missing-attribute] profile.staff_permissions = ProfileStaffPermissions.objects.create() profile.save() self.perms = perms response = super().dispatch(request, *args, **kwargs) + + if not self.allowed: + messages.error(request, "You arent allowed to visit this page!") + return redirect("admin.dash") + self.audit_log.save() return response diff --git a/twisted/twisted_site/views/admin/announcements.py b/twisted/twisted_site/views/admin/announcements.py index 35bdf22..7b4b546 100644 --- a/twisted/twisted_site/views/admin/announcements.py +++ b/twisted/twisted_site/views/admin/announcements.py @@ -8,7 +8,13 @@ # Create your views here. class AnnouncementsView(AdminView): def get(self, request: HttpRequest) -> HttpResponse: + if self.perms.manage_announcements: + self.allowed = True + else: + return HttpResponse("err") + context = self.get_context_data(page="announcements") + if self.request.user.is_anonymous: return redirect("homepage") return TemplateResponse(request, "admin/announcements.html", context=context) diff --git a/twisted/twisted_site/views/admin/dashboard.py b/twisted/twisted_site/views/admin/dashboard.py index b683718..3d9e41c 100644 --- a/twisted/twisted_site/views/admin/dashboard.py +++ b/twisted/twisted_site/views/admin/dashboard.py @@ -11,6 +11,7 @@ # Create your views here. class DashboardView(AdminView): + allowed = True def get(self, request: HttpRequest) -> HttpResponse: context = self.get_context_data(page="dashboard") if self.request.user.is_anonymous: diff --git a/twisted/twisted_site/views/admin/fulfillment.py b/twisted/twisted_site/views/admin/fulfillment.py index 5aaf1ab..bb7067f 100644 --- a/twisted/twisted_site/views/admin/fulfillment.py +++ b/twisted/twisted_site/views/admin/fulfillment.py @@ -7,5 +7,10 @@ # Create your views here. class FulfillmentView(AdminView): def get(self, request: HttpRequest) -> HttpResponse: + if self.perms.manage_fulfillments: + self.allowed = True + else: + return HttpResponse("err") + context = self.get_context_data(page="fulfillment") return render(request, "admin/fulfillment.html", context=context) diff --git a/twisted/twisted_site/views/admin/pathways.py b/twisted/twisted_site/views/admin/pathways.py index 2c6c586..081ccb9 100644 --- a/twisted/twisted_site/views/admin/pathways.py +++ b/twisted/twisted_site/views/admin/pathways.py @@ -14,6 +14,12 @@ # Create your views here. class PathwayListView(AdminView): def get(self, request: HttpRequest) -> HttpResponse: + if self.perms.view_pathways: + self.allowed = True + else: + return HttpResponse("err") + + context = self.get_context_data(page="pathways") context["pathways"] = Pathway.objects.all().order_by("start") @@ -46,6 +52,11 @@ def get( error: str | None = None, extracontext: dict[str, Any] | None = None, # pyrefly: ignore[explicit-any] ) -> HttpResponse: + if self.perms.manage_pathways: + self.allowed = True + else: + return HttpResponse("err") + if extracontext is None: extracontext = {} @@ -58,6 +69,11 @@ def get( return render(request, "admin/pathways/create.html", context=context) def post(self, request: HttpRequest) -> HttpResponse: + if self.perms.manage_pathways: + self.allowed = True + else: + return HttpResponse("err") + pathway_name: str | None = request.POST.get("name") start_date: str | None = request.POST.get("startDate") @@ -113,6 +129,11 @@ def post(self, request: HttpRequest) -> HttpResponse: class PathwayDetailView(AdminView): def get(self, request: HttpRequest, pathway_id: int) -> HttpResponse: + if self.perms.view_pathways: + self.allowed = True + else: + return HttpResponse("err") + context = self.get_context_data(page="pathways", subpage="detail") pathway = get_object_or_404(Pathway, id=pathway_id) context["pathway"] = pathway diff --git a/twisted/twisted_site/views/admin/review.py b/twisted/twisted_site/views/admin/review.py index ca63ed2..af37097 100644 --- a/twisted/twisted_site/views/admin/review.py +++ b/twisted/twisted_site/views/admin/review.py @@ -11,6 +11,11 @@ # Create your views here. class ReviewView(AdminView): def get(self, request: HttpRequest) -> HttpResponse: + if self.perms.view_review: + self.allowed = True + else: + return HttpResponse("err") + if settings.DEBUG_REVIEW: return self.debug_get(request) @@ -18,6 +23,11 @@ def get(self, request: HttpRequest) -> HttpResponse: return render(request, "admin/review.html", context=context) def post(self, request: HttpRequest) -> HttpResponse: + if self.perms.manage_review: + self.allowed = True + else: + return HttpResponse("err") + if settings.DEBUG_REVIEW: return self.debug_post(request) diff --git a/twisted/twisted_site/views/admin/shop.py b/twisted/twisted_site/views/admin/shop.py index aa2d2a4..6e7a5a0 100644 --- a/twisted/twisted_site/views/admin/shop.py +++ b/twisted/twisted_site/views/admin/shop.py @@ -7,5 +7,10 @@ # Create your views here. class ShopView(AdminView): def get(self, request: HttpRequest) -> HttpResponse: + if self.perms.manage_shop: + self.allowed = True + else: + return HttpResponse("err") + context = self.get_context_data(page="shop") return render(request, "admin/shop.html", context=context) diff --git a/twisted/twisted_site/views/admin/users.py b/twisted/twisted_site/views/admin/users.py index 8c5c7fa..6802665 100644 --- a/twisted/twisted_site/views/admin/users.py +++ b/twisted/twisted_site/views/admin/users.py @@ -9,7 +9,7 @@ from django.shortcuts import get_object_or_404, redirect from django.template.response import TemplateResponse -from twisted_site.models import User +from twisted_site.models import User, ProfileStaffPermissions from .admin import AdminView @@ -17,6 +17,11 @@ # Create your views here. class UsersView(AdminView): def get(self, request: HttpRequest) -> HttpResponse: + if self.perms.view_users: + self.allowed = True + else: + return HttpResponse("err") + context = self.get_context_data(page="users") if request.GET.get("search") not in (None, ""): query: str = request.GET["search"] @@ -34,6 +39,11 @@ def get(self, request: HttpRequest) -> HttpResponse: def post(self, request: HttpRequest) -> HttpResponse: if request.POST.get("action") == "logoutall": + if self.perms.superuser: + self.allowed = True + else: + return HttpResponse("err") + session_count = Session.objects.count() _ = Session.objects.all().delete() if not isinstance(self.audit_log.additional_context, dict): @@ -47,6 +57,11 @@ def post(self, request: HttpRequest) -> HttpResponse: class UserDetailView(AdminView): def get(self, request: HttpRequest, user_id: int) -> HttpResponse: + if self.perms.view_users: + self.allowed = True + else: + return HttpResponse("err") + context = self.get_context_data(page="users", subpage="detail") user = get_object_or_404(User, id=user_id) @@ -66,6 +81,11 @@ def get(self, request: HttpRequest, user_id: int) -> HttpResponse: return TemplateResponse(request, "admin/user.html", context) def post(self, request: HttpRequest, user_id: int) -> HttpResponse | None: + if self.perms.superuser: + self.allowed = True + else: + return HttpResponse("err") + user = get_object_or_404(User, id=user_id) if not isinstance(self.audit_log.additional_context, dict): @@ -91,8 +111,9 @@ def post(self, request: HttpRequest, user_id: int) -> HttpResponse | None: return resp if request.POST.get("action") == "change_permissions": if not request.user.profile.staff_permissions.superuser: - return None - key = request.POST["key"] + messages.error(request, "You are not allowed to change the permissions!") + return redirect(self.request.path) + key:str = request.POST["key"] # pyright: ignore[reportAssignmentType] value = request.POST.get("value") == "True" perms = user.profile.staff_permissions setattr(perms, key, value) @@ -102,4 +123,13 @@ def post(self, request: HttpRequest, user_id: int) -> HttpResponse | None: messages.success(request, f"Set permission '{key}' to '{value}' successfully.") return redirect(self.request.path+"#adminperms") + if request.POST.get("action") == "make_admin": + profile = user.profile + profile.is_staff = True + profile.staff_permissions = ProfileStaffPermissions.objects.create() + self.audit_log.pii = True + self.audit_log.additional_context["permission_changed"] = "Made user an admin" + messages.success(request, f"Made @{user.profile.slack_username} an admin.") + profile.save() + return redirect(self.request.path)