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
+156 -166
View File
@@ -1,178 +1,168 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>
Civixfix - {% block title %}Community Issue Reporting Platform{% endblock %}
</title>
<!-- Bootstrap 5 CSS -->
<link
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://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> <head>
<!-- Font Awesome --> <meta charset="UTF-8" />
<link <meta name="viewport" content="width=device-width, initial-scale=1.0" />
rel="stylesheet" <title>
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" Civixfix - {% block title %}Community Issue Reporting Platform{% endblock %}
/> </title>
<!-- Custom CSS --> <!-- Bootstrap 5 CSS -->
<style> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet" />
.hero-section { <link rel="preload" as="image" href="https://a.tile.openstreetmap.org/13/4521/2632.png">
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), <link rel="preload" as="image" href="https://b.tile.openstreetmap.org/13/4521/2632.png">
url("https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?ixlib=rb-4.0.3");
background-size: cover; <link rel="preload" as="font"
background-position: center; href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/webfonts/fa-solid-900.woff2" type="font/woff2"
color: white; crossorigin>
padding: 100px 0; <!-- Font Awesome -->
margin-bottom: 30px; <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
} <!-- Custom CSS -->
.feature-icon { <style>
font-size: 2.5rem; .hero-section {
margin-bottom: 1rem; background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),
color: #0d6efd; url("https://images.unsplash.com/photo-1477959858617-67f85cf4f1df?ixlib=rb-4.0.3");
} background-size: cover;
.issue-card { background-position: center;
transition: transform 0.3s; color: white;
} padding: 100px 0;
.issue-card:hover { margin-bottom: 30px;
transform: translateY(-5px); }
}
</style> .feature-icon {
{% block extra_css %}{% endblock %} font-size: 2.5rem;
</head> margin-bottom: 1rem;
<body> color: #0d6efd;
<!-- Navigation --> }
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
<div class="container"> .issue-card {
<a class="navbar-brand" href="{% url 'home' %}"> transition: transform 0.3s;
<i class="fas fa-bullhorn me-2"></i>Civixfix }
</a>
<button .issue-card:hover {
class="navbar-toggler" transform: translateY(-5px);
type="button" }
data-bs-toggle="collapse" </style>
data-bs-target="#navbarNav" {% block extra_css %}{% endblock %}
> </head>
<span class="navbar-toggler-icon"></span>
</button> <body>
<div class="collapse navbar-collapse" id="navbarNav"> <!-- Navigation -->
<ul class="navbar-nav me-auto"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
<li class="nav-item"> <div class="container">
<a class="nav-link active" href="{% url 'home' %}">Home</a> <a class="navbar-brand" href="{% url 'home' %}">
<i class="fas fa-bullhorn me-2"></i>Civixfix
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link active" href="{% url 'home' %}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#features">Features</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#how-it-works">How It Works</a>
</li>
{% if user.is_authenticated and user.is_citizen %}
<li class="nav-item">
<a class="nav-link" href="{% url 'citizen_dashboard' %}">Dashboard</a>
</li>
{% endif %}
</ul>
<!-- 🔥 ADD THE DROPDOWN CODE RIGHT HERE 🔥 -->
<div class="d-flex">
{% if user.is_authenticated %}
<div class="dropdown">
<button class="btn btn-outline-light dropdown-toggle" type="button" data-bs-toggle="dropdown">
<i class="fas fa-user me-1"></i> {{ user.username }}
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="{% url 'citizen_dashboard' %}">Dashboard</a>
</li>
<li><a class="dropdown-item" href="#">Profile</a></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>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit" class="dropdown-item">Logout</button>
</form>
</li>
</ul>
</div>
{% else %}
<a href="{% url 'login' %}" class="btn btn-outline-light me-2">Login</a>
<a href="{% url 'register' %}" class="btn btn-primary">Register</a>
{% endif %}
</div>
</div>
</div>
</nav>
<!-- Main Content -->
<main>{% block content %}{% endblock %}</main>
<!-- Footer -->
<footer class="text-black py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-4 mb-3">
<h5><i class="fas fa-bullhorn me-2"></i>Civixfix</h5>
<p class="text-muted">
Empowering communities through transparent issue reporting and
resolution.
</p>
</div>
<div class="col-md-4 mb-3">
<h5>Quick Links</h5>
<ul class="list-unstyled">
<li>
<a href="#features" class="text-decoration-none text-muted">Features</a>
</li> </li>
<li class="nav-item"> <li>
<a class="nav-link" href="#features">Features</a> <a href="#how-it-works" class="text-decoration-none text-muted">How It Works</a>
</li> </li>
<li class="nav-item"> <li>
<a class="nav-link" href="#how-it-works">How It Works</a> <a href="#" class="text-decoration-none text-muted">Privacy Policy</a>
</li> </li>
{% if user.is_authenticated and user.is_citizen %}
<li class="nav-item">
<a class="nav-link" href="{% url 'citizen_dashboard' %}"
>Dashboard</a
>
</li>
{% endif %}
</ul> </ul>
</div>
<!-- 🔥 ADD THE DROPDOWN CODE RIGHT HERE 🔥 --> <div class="col-md-4 mb-3">
<div class="d-flex"> <h5>Contact</h5>
{% if user.is_authenticated %} <ul class="list-unstyled text-muted">
<div class="dropdown"> <li>
<button <i class="fas fa-envelope me-2"></i> gokuldevse2001@gmail.com
class="btn btn-outline-light dropdown-toggle" </li>
type="button" <li><i class="fas fa-phone me-2"></i> +91 8129329073</li>
data-bs-toggle="dropdown" </ul>
>
<i class="fas fa-user me-1"></i> {{ user.username }}
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li>
<a class="dropdown-item" href="{% url 'citizen_dashboard' %}"
>Dashboard</a
>
</li>
<li><a class="dropdown-item" href="#">Profile</a></li>
<li><hr class="dropdown-divider" /></li>
<li>
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button type="submit" class="dropdown-item">Logout</button>
</form>
</li>
</ul>
</div>
{% else %}
<a href="{% url 'login' %}" class="btn btn-outline-light me-2"
>Login</a
>
<a href="{% url 'register' %}" class="btn btn-primary">Register</a>
{% endif %}
</div>
</div> </div>
</div> </div>
</nav> <hr class="my-4 bg-secondary" />
<div class="text-center text-muted">
<!-- Main Content --> <small>&copy; {% now "Y" %} Civixfix. All rights reserved.</small>
<main>{% block content %}{% endblock %}</main>
<!-- Footer -->
<footer class="text-black py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-4 mb-3">
<h5><i class="fas fa-bullhorn me-2"></i>Civixfix</h5>
<p class="text-muted">
Empowering communities through transparent issue reporting and
resolution.
</p>
</div>
<div class="col-md-4 mb-3">
<h5>Quick Links</h5>
<ul class="list-unstyled">
<li>
<a href="#features" class="text-decoration-none text-muted"
>Features</a
>
</li>
<li>
<a href="#how-it-works" class="text-decoration-none text-muted"
>How It Works</a
>
</li>
<li>
<a href="#" class="text-decoration-none text-muted"
>Privacy Policy</a
>
</li>
</ul>
</div>
<div class="col-md-4 mb-3">
<h5>Contact</h5>
<ul class="list-unstyled text-muted">
<li>
<i class="fas fa-envelope me-2"></i> gokuldevse2001@gmail.com
</li>
<li><i class="fas fa-phone me-2"></i> +91 8129329073</li>
</ul>
</div>
</div>
<hr class="my-4 bg-secondary" />
<div class="text-center text-muted">
<small>&copy; {% now "Y" %} Civixfix. All rights reserved.</small>
</div>
</div> </div>
</footer> </div>
</footer>
<!-- Bootstrap 5 JS Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
<!-- Bootstrap 5 JS Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<!-- jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
{% block extra_js %}{% endblock %}
</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'),
+60 -13
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)
@@ -198,4 +193,56 @@ def add_comment(request, pk, parent_id=None):
if content: if content:
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,
})