minor update
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -37,3 +37,11 @@ class CommentForm(forms.ModelForm):
|
||||
widgets = {
|
||||
"content": forms.Textarea(attrs={"rows": 2, "placeholder": "Add a comment..."})
|
||||
}
|
||||
|
||||
class IssueAssignForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = ['department']
|
||||
widgets = {
|
||||
'department': forms.Select(attrs={'class': 'form-select form-select-sm'}),
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,19 @@
|
||||
# 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'),
|
||||
),
|
||||
]
|
||||
BIN
Binary file not shown.
Binary file not shown.
+45
-29
@@ -26,6 +26,33 @@ class User(AbstractUser):
|
||||
related_query_name='core_user',
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
# Each department can have many users
|
||||
users = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
related_name="departments",
|
||||
blank=True
|
||||
)
|
||||
|
||||
# One admin per department
|
||||
admin = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="admin_of_department",
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class IssueCategory(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
description = models.TextField(blank=True)
|
||||
@@ -65,6 +92,15 @@ class Issue(models.Model):
|
||||
related_name="reported_issues"
|
||||
)
|
||||
|
||||
# 🔹 Add relation to department
|
||||
department = models.ForeignKey(
|
||||
Department,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
blank=True,
|
||||
related_name="issues"
|
||||
)
|
||||
|
||||
location = models.CharField(max_length=200, blank=True)
|
||||
latitude = models.FloatField(null=True, blank=True)
|
||||
longitude = models.FloatField(null=True, blank=True)
|
||||
@@ -98,7 +134,14 @@ class Issue(models.Model):
|
||||
def has_user_voted(self, user):
|
||||
if user.is_authenticated and hasattr(self, "votes"):
|
||||
return self.votes.filter(user=user).exists()
|
||||
return False
|
||||
return
|
||||
|
||||
def assign_to_department(self, department):
|
||||
"""Assign issue to a department and auto-update status to acknowledged"""
|
||||
self.department = department
|
||||
self.status = self.STATUS_ACKNOWLEDGED
|
||||
self.save()
|
||||
|
||||
|
||||
|
||||
class Vote(models.Model):
|
||||
@@ -127,31 +170,4 @@ 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)
|
||||
|
||||
# Each department can have many users
|
||||
users = models.ManyToManyField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
related_name="departments",
|
||||
blank=True
|
||||
)
|
||||
|
||||
# One admin per department
|
||||
admin = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="admin_of_department",
|
||||
null=True,
|
||||
blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
ordering = ["name"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
return self.parent is not None
|
||||
@@ -17,6 +17,7 @@
|
||||
<th>Reported By</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
<th>Assign Department</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -40,6 +41,24 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ issue.created_at|date:"M d, Y H:i" }}</td>
|
||||
<td>
|
||||
{% if issue.department %}
|
||||
<span class="fw-bold text-primary">{{ issue.department.name }}</span>
|
||||
{% else %}
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="issue_id" value="{{ issue.id }}">
|
||||
<select name="department" class="form-select form-select-sm">
|
||||
<option value="">— Select Department —</option>
|
||||
{% for dept in departments %}
|
||||
<option value="{{ dept.id }}">{{ dept.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<button type="submit" class="btn btn-sm btn-primary mt-1">Assign</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
@@ -8,7 +8,8 @@ urlpatterns = [
|
||||
path("superadmin/departments/", views.manage_departments, name="manage_departments"),
|
||||
path("superadmin/departments/<int:pk>/", views.department_detail, name="department_detail"),
|
||||
path("superadmin/manage/", views.manage_issues, name="manage_issues"),
|
||||
|
||||
path("superadmin/assign-department/<int:issue_id>/", views.assign_department, name="assign_department"),
|
||||
|
||||
path('register/', views.register, name='register'),
|
||||
path('login/', views.custom_login, name='login'),
|
||||
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
||||
|
||||
+32
-3
@@ -9,7 +9,7 @@ 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 .forms import CitizenRegistrationForm, IssueForm, CommentForm
|
||||
from .forms import CitizenRegistrationForm, IssueForm, CommentForm, IssueAssignForm
|
||||
|
||||
def home(request):
|
||||
total_issues = Issue.objects.count()
|
||||
@@ -271,5 +271,34 @@ def department_detail(request, pk):
|
||||
@login_required
|
||||
@user_passes_test(superadmin_check)
|
||||
def manage_issues(request):
|
||||
issues = Issue.objects.all().order_by("-created_at")
|
||||
return render(request, "issues/manage_issues.html", {"issues": issues})
|
||||
issues = Issue.objects.all().order_by('-created_at')
|
||||
|
||||
if request.method == "POST":
|
||||
issue_id = request.POST.get("issue_id")
|
||||
dept_id = request.POST.get("department")
|
||||
|
||||
issue = get_object_or_404(Issue, id=issue_id)
|
||||
if dept_id: # Only assign if a department is selected
|
||||
department = get_object_or_404(Department, id=dept_id)
|
||||
issue.assign_to_department(department) # 🔹 uses helper
|
||||
return redirect("manage_issues") # refresh page after save
|
||||
|
||||
return render(request, "issues/manage_issues.html", {
|
||||
"issues": issues,
|
||||
"departments": Department.objects.all()
|
||||
})
|
||||
|
||||
@user_passes_test(superadmin_check)
|
||||
def assign_department(request, issue_id):
|
||||
if request.method == "POST":
|
||||
issue = get_object_or_404(Issue, id=issue_id)
|
||||
dept_id = request.POST.get("department_id")
|
||||
|
||||
if dept_id:
|
||||
department = get_object_or_404(Department, id=dept_id)
|
||||
issue.assign_to_department(department)
|
||||
messages.success(request, f"Issue '{issue.title}' assigned to {department.name}.")
|
||||
else:
|
||||
messages.error(request, "Please select a department.")
|
||||
|
||||
return redirect("manage_issues") # redirect back to the issues page
|
||||
Reference in New Issue
Block a user