minor update

This commit is contained in:
2025-08-26 12:05:43 +05:30
parent 0d78cce3dd
commit adbfec2b10
27 changed files with 120 additions and 262 deletions
+1 -1
View File
@@ -123,4 +123,4 @@ DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Add these settings
LOGIN_REDIRECT_URL = 'citizen_dashboard'
LOGIN_URL = 'login'
LOGOUT_REDIRECT_URL = 'home'
LOGOUT_REDIRECT_URL = 'home'
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -23,7 +23,7 @@ class CitizenRegistrationForm(UserCreationForm):
class IssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ['title', 'description', 'category', 'location', 'latitude', 'longitude', 'photo']
fields = ['title', 'description', 'location', 'latitude', 'longitude', 'photo']
widgets = {
'latitude': forms.HiddenInput(),
'longitude': forms.HiddenInput(),
+48 -14
View File
@@ -1,4 +1,4 @@
# Generated by Django 5.2.5 on 2025-08-20 06:30
# Generated by Django 5.2.5 on 2025-08-26 06:15
import django.contrib.auth.models
import django.contrib.auth.validators
@@ -18,15 +18,6 @@ class Migration(migrations.Migration):
]
operations = [
migrations.CreateModel(
name='IssueCategory',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('description', models.TextField(blank=True)),
('icon', models.CharField(default='fas fa-exclamation-circle', max_length=50)),
],
),
migrations.CreateModel(
name='User',
fields=[
@@ -57,21 +48,64 @@ class Migration(migrations.Migration):
('objects', django.contrib.auth.models.UserManager()),
],
),
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)),
('admin', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='admin_of_department', to=settings.AUTH_USER_MODEL)),
('users', models.ManyToManyField(blank=True, related_name='departments', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Issue',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('title', models.CharField(max_length=200)),
('description', models.TextField()),
('location', models.CharField(max_length=200)),
('latitude', models.FloatField()),
('longitude', models.FloatField()),
('location', models.CharField(blank=True, max_length=200)),
('latitude', models.FloatField(blank=True, null=True)),
('longitude', models.FloatField(blank=True, null=True)),
('photo', models.ImageField(blank=True, null=True, upload_to='issue_photos/', validators=[django.core.validators.FileExtensionValidator(['jpg', 'jpeg', 'png', 'gif'])])),
('status', models.CharField(choices=[('reported', 'Reported'), ('acknowledged', 'Acknowledged'), ('in_progress', 'In Progress'), ('resolved', 'Resolved')], default='reported', max_length=20)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('department', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issues', to='core.department')),
('reporter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='reported_issues', to=settings.AUTH_USER_MODEL)),
('category', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.issuecategory')),
],
options={
'ordering': ['-created_at'],
},
),
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)),
('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, to=settings.AUTH_USER_MODEL)),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='comments', to='core.issue')),
],
options={
'ordering': ['created_at'],
},
),
migrations.CreateModel(
name='Vote',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='votes', to='core.issue')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('user', 'issue')},
},
),
]
-27
View File
@@ -1,27 +0,0 @@
# Generated by Django 5.2.5 on 2025-08-21 22:31
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='Vote',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('issue', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='votes', to='core.issue')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('user', 'issue')},
},
),
]
-29
View File
@@ -1,29 +0,0 @@
# 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'],
},
),
]
@@ -1,32 +0,0 @@
# 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),
),
]
@@ -1,19 +0,0 @@
# 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),
),
]
@@ -1,24 +0,0 @@
# Generated by Django 5.2.5 on 2025-08-25 07:20
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0005_department_users'),
]
operations = [
migrations.RemoveField(
model_name='department',
name='users',
),
migrations.AddField(
model_name='department',
name='admin',
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='admin_of_department', to=settings.AUTH_USER_MODEL),
),
]
@@ -1,19 +0,0 @@
# Generated by Django 5.2.5 on 2025-08-25 07:24
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_remove_department_users_department_admin'),
]
operations = [
migrations.AddField(
model_name='department',
name='users',
field=models.ManyToManyField(blank=True, related_name='departments', to=settings.AUTH_USER_MODEL),
),
]
@@ -1,38 +0,0 @@
# Generated by Django 5.2.5 on 2025-08-25 08:23
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0007_department_users'),
]
operations = [
migrations.AlterModelOptions(
name='issue',
options={'ordering': ['-created_at']},
),
migrations.AlterField(
model_name='issue',
name='category',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issues', to='core.issuecategory'),
),
migrations.AlterField(
model_name='issue',
name='latitude',
field=models.FloatField(blank=True, null=True),
),
migrations.AlterField(
model_name='issue',
name='location',
field=models.CharField(blank=True, max_length=200),
),
migrations.AlterField(
model_name='issue',
name='longitude',
field=models.FloatField(blank=True, null=True),
),
]
@@ -1,19 +0,0 @@
# Generated by Django 5.2.5 on 2025-08-25 08:28
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0008_alter_issue_options_alter_issue_category_and_more'),
]
operations = [
migrations.AddField(
model_name='issue',
name='department',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='issues', to='core.department'),
),
]
-16
View File
@@ -53,14 +53,6 @@ class Department(models.Model):
def __str__(self):
return self.name
class IssueCategory(models.Model):
name = models.CharField(max_length=100)
description = models.TextField(blank=True)
icon = models.CharField(max_length=50, default='fas fa-exclamation-circle')
def __str__(self):
return self.name
class Issue(models.Model):
STATUS_REPORTED = 'reported'
STATUS_ACKNOWLEDGED = 'acknowledged'
@@ -77,14 +69,6 @@ class Issue(models.Model):
title = models.CharField(max_length=200)
description = models.TextField()
category = models.ForeignKey(
"IssueCategory",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="issues"
)
reporter = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
+2 -1
View File
@@ -89,7 +89,8 @@
{% if user.is_resolver %}
<li>
<a class="dropdown-item" href="{% url 'department_dashboard' %}">Department Dashboard</a>
<a class="dropdown-item" href="{% url 'department_dashboard' %}">
<i class="fas fa-building me-2 text-success"></i>Department Dashboard</a>
</li>
{% endif %}
+12 -2
View File
@@ -141,8 +141,18 @@
<img src="https://via.placeholder.com/300x200/6c757d/ffffff?text=No+Image" class="card-img-top" alt="No image">
{% endif %}
<div class="card-body">
<span class="badge bg-{% if issue.category.name == 'Roads' %}warning text-dark{% elif issue.category.name == 'Sanitation' %}success{% else %}info{% endif %} mb-2">
{{ issue.category.name|default:"General" }}
<span class="badge
{% if issue.department.name == 'Roads & Transportation' %} bg-primary
{% elif issue.department.name == 'Sanitation & Waste Management' %} bg-success
{% elif issue.department.name == 'Public Safety' %} bg-danger
{% elif issue.department.name == 'Water & Sewage' %} bg-info text-dark
{% elif issue.department.name == 'Parks & Recreation' %} bg-warning text-dark
{% elif issue.department.name == 'Electricity & Utilities' %} bg-dark
{% elif issue.department.name == 'Environmental Services' %} bg-secondary
{% elif issue.department.name == 'Public Works' %} bg-teal text-white
{% else %} bg-light text-dark
{% endif %} mb-2">
{{ issue.department.name|default:"General" }}
</span>
<h5 class="card-title">{{ issue.title|truncatewords:5 }}</h5>
<p class="card-text text-muted">{{ issue.description|truncatewords:15 }}</p>
@@ -5,8 +5,13 @@
<div class="card shadow-lg">
<div class="card-header bg-primary text-white">
<h3>Department Dashboard</h3>
<p>Welcome, {{ request.user.username }}. Your Departments:
{% for dept in departments %} {{ dept.name }}{% if not forloop.last %}, {% endif %}{% endfor %}
<p>
Welcome, {{ request.user.username }}. Your Departments:
{% for dept in departments %}
<span class="badge bg-light text-dark">{{ dept.name }}</span>
{% empty %}
<span class="text-muted">No department assigned.</span>
{% endfor %}
</p>
</div>
<div class="card-body">
@@ -20,6 +25,7 @@
<th>Reported By</th>
<th>Status</th>
<th>Created At</th>
<th>Action</th>
</tr>
</thead>
<tbody>
@@ -43,6 +49,23 @@
{% endif %}
</td>
<td>{{ issue.created_at|date:"M d, Y H:i" }}</td>
<td>
{% if issue.status in "reported,acknowledged" %}
<form method="post" action="{% url 'update_issue_status' issue.id %}">
{% csrf_token %}
<input type="hidden" name="status" value="in_progress">
<button type="submit" class="btn btn-sm btn-warning">In Progress</button>
</form>
{% elif issue.status == "in_progress" %}
<form method="post" action="{% url 'update_issue_status' issue.id %}">
{% csrf_token %}
<input type="hidden" name="status" value="resolved">
<button type="submit" class="btn btn-sm btn-success">Resolved</button>
</form>
{% else %}
<span class="text-muted">No actions</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
+1
View File
@@ -20,4 +20,5 @@ urlpatterns = [
path("issues/<int:pk>/comment/<int:parent_id>/", views.add_comment, name="add_comment"),
path('vote/<int:issue_id>/', views.vote_issue, name='vote_issue'),
path("department/", views.department_dashboard, name="department_dashboard"),
path("update-issue-status/<int:issue_id>/", views.update_issue_status, name="update_issue_status"),
]
+25 -18
View File
@@ -8,7 +8,7 @@ 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, Department
from .models import Issue, User, Vote, Comment, Department
from .forms import CitizenRegistrationForm, IssueForm, CommentForm, IssueAssignForm
def home(request):
@@ -16,15 +16,12 @@ def home(request):
resolved_issues = Issue.objects.filter(status='resolved').count()
active_users = User.objects.filter(is_active=True).count()
# Get recently reported issues (last 3 issues)
recent_issues = Issue.objects.all().order_by('-created_at')[:3]
for issue in recent_issues:
issue.user_has_voted = issue.has_user_voted(request.user) if request.user.is_authenticated else False
# Get municipal departments count (assuming you have a Department model)
# If you don't have one yet, you can use a placeholder or create the model
municipal_departments = 5 # Placeholder - replace with actual count when you have the model
municipal_departments = 5
context = {
'total_issues': total_issues,
@@ -46,12 +43,10 @@ def citizen_dashboard(request):
all_user_issues = Issue.objects.filter(reporter=request.user)
user_issues_display = all_user_issues.order_by('-created_at')[:5]
resolved_count = all_user_issues.filter(status='resolved').count()
categories = IssueCategory.objects.all()
context = {
'user_issues': user_issues_display,
'resolved_count': resolved_count,
'categories': categories,
'issue_form': IssueForm(),
}
return render(request, 'dashboard/citizen_dashboard.html', context)
@@ -67,6 +62,7 @@ def report_issue(request):
if form.is_valid():
issue = form.save(commit=False)
issue.reporter = request.user
issue.status = "reported" # default status
issue.save()
messages.success(request, 'Issue reported successfully!')
return redirect('citizen_dashboard')
@@ -87,26 +83,19 @@ def view_all_issues(request):
user_vote_subq = Vote.objects.filter(user=request.user, issue_id=OuterRef('pk'))
issues = (
Issue.objects
.select_related('category', 'reporter')
.select_related('reporter')
.annotate(user_has_voted=Exists(user_vote_subq))
.order_by('-created_at')
)
categories = IssueCategory.objects.all()
# Optional filters (status/category) if you wired the dropdowns
# Optional filter (status only now)
status = request.GET.get('status') or ''
category_id = request.GET.get('category') or ''
if status:
issues = issues.filter(status=status)
if category_id:
issues = issues.filter(category_id=category_id)
return render(request, 'issues/view_all_issues.html', {
'issues': issues,
'categories': categories,
'selected_status': status,
'selected_category': category_id,
})
@@ -307,6 +296,24 @@ def resolver_check(user):
return user.is_resolver
@login_required
@user_passes_test(resolver_check)
@user_passes_test(lambda u: u.is_resolver)
def department_dashboard(request):
return render(request, "dashboard/department_dashboard.html")
departments = request.user.departments.all()
issues = Issue.objects.filter(department__in=departments).order_by('-created_at')
return render(request, "dashboard/department_dashboard.html", {"issues": issues, "departments": departments})
@login_required
@user_passes_test(lambda u: u.is_resolver)
def update_issue_status(request, issue_id):
issue = get_object_or_404(Issue, id=issue_id)
# Make sure the user belongs to the department assigned to the issue
if issue.department not in request.user.departments.all():
return redirect("department_dashboard")
if request.method == "POST":
new_status = request.POST.get("status")
if new_status in [Issue.STATUS_IN_PROGRESS, Issue.STATUS_RESOLVED]:
issue.status = new_status
issue.save()
return redirect("department_dashboard")
+5
View File
@@ -0,0 +1,5 @@
from waitress import serve
from civicfix.wsgi import application
if __name__ == "__main__":
serve(application, host="10.1.192.152", port=8000)