major update

This commit is contained in:
2025-08-25 12:26:58 +05:30
parent 7cdb1a0ac4
commit 0d6aa5ad02
17 changed files with 445 additions and 179 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+29
View File
@@ -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'],
},
),
]
@@ -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),
),
]
@@ -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),
),
]
+18
View File
@@ -92,3 +92,21 @@ class Comment(models.Model):
@property @property
def is_reply(self): def is_reply(self):
return self.parent is not None 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
+29 -39
View File
@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@@ -7,19 +8,15 @@
Civixfix - {% block title %}Community Issue Reporting Platform{% endblock %} Civixfix - {% block title %}Community Issue Reporting Platform{% endblock %}
</title> </title>
<!-- Bootstrap 5 CSS --> <!-- Bootstrap 5 CSS -->
<link <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
<link rel="preload" as="image" href="https://a.tile.openstreetmap.org/13/4521/2632.png"> <link rel="preload" as="image" href="https://a.tile.openstreetmap.org/13/4521/2632.png">
<link rel="preload" as="image" href="https://b.tile.openstreetmap.org/13/4521/2632.png"> <link rel="preload" as="image" href="https://b.tile.openstreetmap.org/13/4521/2632.png">
<link rel="preload" as="font" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/webfonts/fa-solid-900.woff2" type="font/woff2" crossorigin> <link rel="preload" as="font"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/webfonts/fa-solid-900.woff2" type="font/woff2"
crossorigin>
<!-- Font Awesome --> <!-- Font Awesome -->
<link <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
/>
<!-- Custom CSS --> <!-- Custom CSS -->
<style> <style>
.hero-section { .hero-section {
@@ -31,20 +28,24 @@
padding: 100px 0; padding: 100px 0;
margin-bottom: 30px; margin-bottom: 30px;
} }
.feature-icon { .feature-icon {
font-size: 2.5rem; font-size: 2.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
color: #0d6efd; color: #0d6efd;
} }
.issue-card { .issue-card {
transition: transform 0.3s; transition: transform 0.3s;
} }
.issue-card:hover { .issue-card:hover {
transform: translateY(-5px); transform: translateY(-5px);
} }
</style> </style>
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
</head> </head>
<body> <body>
<!-- Navigation --> <!-- Navigation -->
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
@@ -52,12 +53,7 @@
<a class="navbar-brand" href="{% url 'home' %}"> <a class="navbar-brand" href="{% url 'home' %}">
<i class="fas fa-bullhorn me-2"></i>Civixfix <i class="fas fa-bullhorn me-2"></i>Civixfix
</a> </a>
<button <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
>
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
@@ -73,9 +69,7 @@
</li> </li>
{% if user.is_authenticated and user.is_citizen %} {% if user.is_authenticated and user.is_citizen %}
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="{% url 'citizen_dashboard' %}" <a class="nav-link" href="{% url 'citizen_dashboard' %}">Dashboard</a>
>Dashboard</a
>
</li> </li>
{% endif %} {% endif %}
</ul> </ul>
@@ -84,21 +78,24 @@
<div class="d-flex"> <div class="d-flex">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<div class="dropdown"> <div class="dropdown">
<button <button class="btn btn-outline-light dropdown-toggle" type="button" data-bs-toggle="dropdown">
class="btn btn-outline-light dropdown-toggle"
type="button"
data-bs-toggle="dropdown"
>
<i class="fas fa-user me-1"></i> {{ user.username }} <i class="fas fa-user me-1"></i> {{ user.username }}
</button> </button>
<ul class="dropdown-menu dropdown-menu-end"> <ul class="dropdown-menu dropdown-menu-end">
<li> <li>
<a class="dropdown-item" href="{% url 'citizen_dashboard' %}" <a class="dropdown-item" href="{% url 'citizen_dashboard' %}">Dashboard</a>
>Dashboard</a
>
</li> </li>
<li><a class="dropdown-item" href="#">Profile</a></li> <li><a class="dropdown-item" href="#">Profile</a></li>
<li><hr class="dropdown-divider" /></li> {% if user.is_superuser %}
<li>
<a class="dropdown-item text-danger fw-bold" href="{% url 'superadmin_dashboard' %}">
<i class="fas fa-crown me-1"></i> Super Admin
</a>
</li>
{% endif %}
<li>
<hr class="dropdown-divider" />
</li>
<li> <li>
<form method="post" action="{% url 'logout' %}"> <form method="post" action="{% url 'logout' %}">
{% csrf_token %} {% csrf_token %}
@@ -108,9 +105,7 @@
</ul> </ul>
</div> </div>
{% else %} {% else %}
<a href="{% url 'login' %}" class="btn btn-outline-light me-2" <a href="{% url 'login' %}" class="btn btn-outline-light me-2">Login</a>
>Login</a
>
<a href="{% url 'register' %}" class="btn btn-primary">Register</a> <a href="{% url 'register' %}" class="btn btn-primary">Register</a>
{% endif %} {% endif %}
</div> </div>
@@ -136,19 +131,13 @@
<h5>Quick Links</h5> <h5>Quick Links</h5>
<ul class="list-unstyled"> <ul class="list-unstyled">
<li> <li>
<a href="#features" class="text-decoration-none text-muted" <a href="#features" class="text-decoration-none text-muted">Features</a>
>Features</a
>
</li> </li>
<li> <li>
<a href="#how-it-works" class="text-decoration-none text-muted" <a href="#how-it-works" class="text-decoration-none text-muted">How It Works</a>
>How It Works</a
>
</li> </li>
<li> <li>
<a href="#" class="text-decoration-none text-muted" <a href="#" class="text-decoration-none text-muted">Privacy Policy</a>
>Privacy Policy</a
>
</li> </li>
</ul> </ul>
</div> </div>
@@ -175,4 +164,5 @@
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
{% block extra_js %}{% endblock %} {% block extra_js %}{% endblock %}
</body> </body>
</html> </html>
@@ -0,0 +1,45 @@
{% extends "core/base.html" %}
{% block content %}
<div class="container my-5">
<div class="card shadow-lg">
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
<h3>{{ department.name }} Department</h3>
<a href="{% url 'manage_departments' %}" class="btn btn-light btn-sm">⬅ Back</a>
</div>
<div class="card-body">
<h5>Department Users</h5>
{% if users %}
<ul class="list-group mb-3">
{% for user in users %}
<li class="list-group-item">
{{ user.username }} — {{ user.email }}
</li>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">No users registered in this department yet.</p>
{% endif %}
<hr>
<h5>Register New User for {{ department.name }}</h5>
<form method="post" class="row g-2">
{% csrf_token %}
<div class="col-md-3">
<input type="text" name="username" class="form-control" placeholder="Username" required>
</div>
<div class="col-md-3">
<input type="email" name="email" class="form-control" placeholder="Email">
</div>
<div class="col-md-3">
<input type="password" name="password" class="form-control" placeholder="Password" required>
</div>
<div class="col-md-3">
<button type="submit" class="btn btn-primary w-100">Create User</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,48 @@
<!-- core/templates/core/manage_departments.html -->
{% extends "core/base.html" %}
{% block content %}
<div class="container my-5">
<div class="card shadow-lg">
<div class="card-header bg-success text-white d-flex justify-content-between align-items-center">
<h3>Manage Departments</h3>
<a href="{% url 'superadmin_dashboard' %}" class="btn btn-light btn-sm">Back to Dashboard</a>
</div>
<div class="card-body">
<!-- List departments -->
{% if departments %}
<ul class="list-group mb-3">
{% for dept in departments %}
<a href="{% url 'department_detail' dept.pk %}"
class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
<span>
<strong>{{ dept.name }}</strong><br>
<small class="text-muted">{{ dept.description }}</small>
</span>
<span class="badge bg-primary rounded-pill">Manage</span>
</a>
{% endfor %}
</ul>
{% else %}
<p class="text-muted">No departments added yet.</p>
{% endif %}
<!-- Add new department form -->
<form method="post" class="mt-3">
{% csrf_token %}
<div class="row g-2">
<div class="col-md-4">
<input type="text" name="name" class="form-control" placeholder="Department name" required>
</div>
<div class="col-md-6">
<input type="text" name="description" class="form-control" placeholder="Description (optional)">
</div>
<div class="col-md-2">
<button type="submit" class="btn btn-success w-100">Add</button>
</div>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
@@ -0,0 +1,35 @@
<!-- core/templates/core/superadmin_dashboard.html -->
{% extends "core/base.html" %}
{% block content %}
<div class="container my-5">
<div class="card shadow-lg">
<div class="card-header bg-dark text-white">
<h3>Super Admin Dashboard</h3>
</div>
<div class="card-body">
<p>Welcome, {{ request.user.username }} 👑</p>
<ul class="list-group">
<li class="list-group-item">
<i class="fas fa-users me-2 text-primary"></i>
<a href="#">Manage Users</a>
</li>
<li class="list-group-item">
<i class="fas fa-building me-2 text-success"></i>
<a href="{% url 'manage_departments' %}">Manage Departments</a>
</li>
<li class="list-group-item">
<a href="#">Manage Roles & Permissions</a>
</li>
<li class="list-group-item">
<a href="#">View Analytics & Reports</a>
</li>
<li class="list-group-item">
<a href="#">System Settings</a>
</li>
</ul>
</div>
</div>
</div>
{% endblock %}
+3
View File
@@ -4,6 +4,9 @@ from . import views
urlpatterns = [ urlpatterns = [
path('', views.home, name='home'), 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/<int:pk>/", views.department_detail, name="department_detail"),
path('register/', views.register, name='register'), path('register/', views.register, name='register'),
path('login/', views.custom_login, name='login'), path('login/', views.custom_login, name='login'),
path('logout/', auth_views.LogoutView.as_view(), name='logout'), path('logout/', auth_views.LogoutView.as_view(), name='logout'),
+59 -12
View File
@@ -1,12 +1,13 @@
from django.contrib import messages from django.contrib import messages
from django.contrib.auth import authenticate, login 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.forms import AuthenticationForm
from django.contrib.auth.hashers import make_password
from django.db.models import Exists, OuterRef from django.db.models import Exists, OuterRef
from django.http import JsonResponse from django.http import JsonResponse
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.views.decorators.http import require_POST 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 from .forms import CitizenRegistrationForm, IssueForm, CommentForm
def home(request): def home(request):
@@ -165,11 +166,9 @@ def vote_issue(request, issue_id):
return JsonResponse({'success': False, 'error': str(e)}) return JsonResponse({'success': False, 'error': str(e)})
@login_required @login_required
def add_comment(request, issue_id, parent_id=None): def add_comment(request, pk, parent_id=None):
issue = get_object_or_404(Issue, id=issue_id) issue = get_object_or_404(Issue, pk=pk)
parent = None parent = get_object_or_404(Comment, pk=parent_id) if parent_id else None
if parent_id:
parent = get_object_or_404(Comment, id=parent_id)
if request.method == "POST": if request.method == "POST":
form = CommentForm(request.POST) form = CommentForm(request.POST)
@@ -179,11 +178,7 @@ def add_comment(request, issue_id, parent_id=None):
comment.user = request.user comment.user = request.user
comment.parent = parent comment.parent = parent
comment.save() comment.save()
return redirect("issue_detail", issue_id=issue.id) return redirect("issue_detail", pk=pk)
else:
form = CommentForm()
return render(request, "comments/add_comment.html", {"form": form, "issue": issue})
def issue_detail(request, pk): def issue_detail(request, pk):
issue = get_object_or_404(Issue, pk=pk) issue = get_object_or_404(Issue, pk=pk)
@@ -199,3 +194,55 @@ def add_comment(request, pk, parent_id=None):
parent = Comment.objects.get(pk=parent_id) if parent_id else None parent = Comment.objects.get(pk=parent_id) if parent_id else None
Comment.objects.create(issue=issue, user=request.user, content=content, parent=parent) Comment.objects.create(issue=issue, user=request.user, content=content, parent=parent)
return redirect("issue_detail", pk=pk) 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,
})