diff --git a/civicfix/core/__pycache__/forms.cpython-313.pyc b/civicfix/core/__pycache__/forms.cpython-313.pyc index f9aea4a..c1b4e14 100644 Binary files a/civicfix/core/__pycache__/forms.cpython-313.pyc and b/civicfix/core/__pycache__/forms.cpython-313.pyc differ diff --git a/civicfix/core/__pycache__/models.cpython-313.pyc b/civicfix/core/__pycache__/models.cpython-313.pyc index 64d04bc..df7f7df 100644 Binary files a/civicfix/core/__pycache__/models.cpython-313.pyc and b/civicfix/core/__pycache__/models.cpython-313.pyc differ diff --git a/civicfix/core/__pycache__/urls.cpython-313.pyc b/civicfix/core/__pycache__/urls.cpython-313.pyc index 253942c..e85e993 100644 Binary files a/civicfix/core/__pycache__/urls.cpython-313.pyc and b/civicfix/core/__pycache__/urls.cpython-313.pyc differ diff --git a/civicfix/core/__pycache__/views.cpython-313.pyc b/civicfix/core/__pycache__/views.cpython-313.pyc index 5c2470b..5e3d968 100644 Binary files a/civicfix/core/__pycache__/views.cpython-313.pyc and b/civicfix/core/__pycache__/views.cpython-313.pyc differ diff --git a/civicfix/core/migrations/0003_comment.py b/civicfix/core/migrations/0003_comment.py new file mode 100644 index 0000000..bae5ef4 --- /dev/null +++ b/civicfix/core/migrations/0003_comment.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.5 on 2025-08-22 00:55 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_vote'), + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('content', models.TextField()), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='core.issue')), + ('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='replies', to='core.comment')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['created_at'], + }, + ), + ] diff --git a/civicfix/core/migrations/0004_department_alter_comment_user.py b/civicfix/core/migrations/0004_department_alter_comment_user.py new file mode 100644 index 0000000..1d8bf39 --- /dev/null +++ b/civicfix/core/migrations/0004_department_alter_comment_user.py @@ -0,0 +1,32 @@ +# Generated by Django 5.2.5 on 2025-08-25 06:21 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_comment'), + ] + + operations = [ + migrations.CreateModel( + name='Department', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True)), + ('description', models.TextField(blank=True, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ], + options={ + 'ordering': ['name'], + }, + ), + migrations.AlterField( + model_name='comment', + name='user', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/civicfix/core/migrations/0005_department_users.py b/civicfix/core/migrations/0005_department_users.py new file mode 100644 index 0000000..e0c4aac --- /dev/null +++ b/civicfix/core/migrations/0005_department_users.py @@ -0,0 +1,19 @@ +# Generated by Django 5.2.5 on 2025-08-25 06:43 + +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_department_alter_comment_user'), + ] + + operations = [ + migrations.AddField( + model_name='department', + name='users', + field=models.ManyToManyField(blank=True, related_name='departments', to=settings.AUTH_USER_MODEL), + ), + ] diff --git a/civicfix/core/migrations/__pycache__/0003_comment.cpython-313.pyc b/civicfix/core/migrations/__pycache__/0003_comment.cpython-313.pyc new file mode 100644 index 0000000..9b8916c Binary files /dev/null and b/civicfix/core/migrations/__pycache__/0003_comment.cpython-313.pyc differ diff --git a/civicfix/core/migrations/__pycache__/0004_department_alter_comment_user.cpython-313.pyc b/civicfix/core/migrations/__pycache__/0004_department_alter_comment_user.cpython-313.pyc new file mode 100644 index 0000000..103132d Binary files /dev/null and b/civicfix/core/migrations/__pycache__/0004_department_alter_comment_user.cpython-313.pyc differ diff --git a/civicfix/core/migrations/__pycache__/0005_department_users.cpython-313.pyc b/civicfix/core/migrations/__pycache__/0005_department_users.cpython-313.pyc new file mode 100644 index 0000000..a55c662 Binary files /dev/null and b/civicfix/core/migrations/__pycache__/0005_department_users.cpython-313.pyc differ diff --git a/civicfix/core/models.py b/civicfix/core/models.py index 6a9f30e..ecaa146 100644 --- a/civicfix/core/models.py +++ b/civicfix/core/models.py @@ -92,3 +92,21 @@ class Comment(models.Model): @property def is_reply(self): return self.parent is not None + +class Department(models.Model): + name = models.CharField(max_length=100, unique=True) + description = models.TextField(blank=True, null=True) + created_at = models.DateTimeField(auto_now_add=True) + + users = models.ManyToManyField( + User, + related_name="departments", + blank=True, + limit_choices_to={'is_staff': True} + ) + + class Meta: + ordering = ["name"] + + def __str__(self): + return self.name.name diff --git a/civicfix/core/templates/core/base.html b/civicfix/core/templates/core/base.html index 1da27a1..0173209 100644 --- a/civicfix/core/templates/core/base.html +++ b/civicfix/core/templates/core/base.html @@ -1,178 +1,168 @@ - - - - - Civixfix - {% block title %}Community Issue Reporting Platform{% endblock %} - - - - - - - - - - - {% block extra_css %}{% endblock %} - - - - - - -
{% block content %}{% endblock %}
- - - + + + + + + + + {% block extra_js %}{% endblock %} + - - - - - {% block extra_js %}{% endblock %} - \ No newline at end of file diff --git a/civicfix/core/templates/core/department_detail.html b/civicfix/core/templates/core/department_detail.html new file mode 100644 index 0000000..22fe796 --- /dev/null +++ b/civicfix/core/templates/core/department_detail.html @@ -0,0 +1,45 @@ +{% extends "core/base.html" %} +{% block content %} +
+
+
+

{{ department.name }} Department

+ ⬅ Back +
+
+ +
Department Users
+ {% if users %} +
    + {% for user in users %} +
  • + {{ user.username }} — {{ user.email }} +
  • + {% endfor %} +
+ {% else %} +

No users registered in this department yet.

+ {% endif %} + +
+
Register New User for {{ department.name }}
+
+ {% csrf_token %} +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+{% endblock %} diff --git a/civicfix/core/templates/core/manage_departments.html b/civicfix/core/templates/core/manage_departments.html new file mode 100644 index 0000000..9a38529 --- /dev/null +++ b/civicfix/core/templates/core/manage_departments.html @@ -0,0 +1,48 @@ + +{% extends "core/base.html" %} + +{% block content %} +
+
+
+

Manage Departments

+ Back to Dashboard +
+
+ + {% if departments %} + + {% else %} +

No departments added yet.

+ {% endif %} + + +
+ {% csrf_token %} +
+
+ +
+
+ +
+
+ +
+
+
+
+
+
+{% endblock %} diff --git a/civicfix/core/templates/core/superadmin_dashboard.html b/civicfix/core/templates/core/superadmin_dashboard.html new file mode 100644 index 0000000..ef052f1 --- /dev/null +++ b/civicfix/core/templates/core/superadmin_dashboard.html @@ -0,0 +1,35 @@ + +{% extends "core/base.html" %} + +{% block content %} +
+
+
+

Super Admin Dashboard

+
+
+

Welcome, {{ request.user.username }} 👑

+ + +
+
+
+{% endblock %} diff --git a/civicfix/core/urls.py b/civicfix/core/urls.py index 00abffb..af20d7b 100644 --- a/civicfix/core/urls.py +++ b/civicfix/core/urls.py @@ -4,6 +4,9 @@ from . import views urlpatterns = [ path('', views.home, name='home'), + path("superadmin/", views.superadmin_dashboard, name="superadmin_dashboard"), + path("superadmin/departments/", views.manage_departments, name="manage_departments"), + path("superadmin/departments//", views.department_detail, name="department_detail"), path('register/', views.register, name='register'), path('login/', views.custom_login, name='login'), path('logout/', auth_views.LogoutView.as_view(), name='logout'), diff --git a/civicfix/core/views.py b/civicfix/core/views.py index aaa7714..f505441 100644 --- a/civicfix/core/views.py +++ b/civicfix/core/views.py @@ -1,12 +1,13 @@ from django.contrib import messages from django.contrib.auth import authenticate, login -from django.contrib.auth.decorators import login_required +from django.contrib.auth.decorators import login_required, user_passes_test from django.contrib.auth.forms import AuthenticationForm +from django.contrib.auth.hashers import make_password from django.db.models import Exists, OuterRef from django.http import JsonResponse from django.shortcuts import render, redirect, get_object_or_404 from django.views.decorators.http import require_POST -from .models import Issue, IssueCategory, User, Vote, Comment +from .models import Issue, IssueCategory, User, Vote, Comment, Department from .forms import CitizenRegistrationForm, IssueForm, CommentForm def home(request): @@ -165,11 +166,9 @@ def vote_issue(request, issue_id): return JsonResponse({'success': False, 'error': str(e)}) @login_required -def add_comment(request, issue_id, parent_id=None): - issue = get_object_or_404(Issue, id=issue_id) - parent = None - if parent_id: - parent = get_object_or_404(Comment, id=parent_id) +def add_comment(request, pk, parent_id=None): + issue = get_object_or_404(Issue, pk=pk) + parent = get_object_or_404(Comment, pk=parent_id) if parent_id else None if request.method == "POST": form = CommentForm(request.POST) @@ -179,11 +178,7 @@ def add_comment(request, issue_id, parent_id=None): comment.user = request.user comment.parent = parent comment.save() - return redirect("issue_detail", issue_id=issue.id) - else: - form = CommentForm() - - return render(request, "comments/add_comment.html", {"form": form, "issue": issue}) + return redirect("issue_detail", pk=pk) def issue_detail(request, pk): issue = get_object_or_404(Issue, pk=pk) @@ -198,4 +193,56 @@ def add_comment(request, pk, parent_id=None): if content: parent = Comment.objects.get(pk=parent_id) if parent_id else None Comment.objects.create(issue=issue, user=request.user, content=content, parent=parent) - return redirect("issue_detail", pk=pk) \ No newline at end of file + return redirect("issue_detail", pk=pk) + +def superadmin_check(user): + return user.is_superuser + + +@login_required +@user_passes_test(superadmin_check) +def superadmin_dashboard(request): + return render(request, "core/superadmin_dashboard.html") + + +@login_required +@user_passes_test(superadmin_check) +def manage_departments(request): + departments = Department.objects.all().order_by("name") + + if request.method == "POST": + name = request.POST.get("name") + description = request.POST.get("description") + if name: + Department.objects.create(name=name, description=description) + return redirect("manage_departments") + + return render(request, "core/manage_departments.html", { + "departments": departments + }) + +@login_required +@user_passes_test(superadmin_check) +def department_detail(request, pk): + department = get_object_or_404(Department, pk=pk) + users = department.users.all() + + if request.method == "POST": + username = request.POST.get("username") + email = request.POST.get("email") + password = request.POST.get("password") + + if username and password: + user = User.objects.create( + username=username, + email=email, + password=make_password(password), # hash the password + is_staff=True # mark as staff + ) + department.users.add(user) + return redirect("department_detail", pk=department.id) + + return render(request, "core/department_detail.html", { + "department": department, + "users": users, + }) \ No newline at end of file