minor update
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
+54
-19
@@ -34,37 +34,72 @@ class IssueCategory(models.Model):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
# issues/models.py
|
||||||
class Issue(models.Model):
|
class Issue(models.Model):
|
||||||
|
STATUS_REPORTED = 'reported'
|
||||||
|
STATUS_ACKNOWLEDGED = 'acknowledged'
|
||||||
|
STATUS_IN_PROGRESS = 'in_progress'
|
||||||
|
STATUS_RESOLVED = 'resolved'
|
||||||
|
|
||||||
STATUS_CHOICES = [
|
STATUS_CHOICES = [
|
||||||
('reported', 'Reported'),
|
(STATUS_REPORTED, 'Reported'),
|
||||||
('acknowledged', 'Acknowledged'),
|
(STATUS_ACKNOWLEDGED, 'Acknowledged'),
|
||||||
('in_progress', 'In Progress'),
|
(STATUS_IN_PROGRESS, 'In Progress'),
|
||||||
('resolved', 'Resolved'),
|
(STATUS_RESOLVED, 'Resolved'),
|
||||||
]
|
]
|
||||||
|
|
||||||
title = models.CharField(max_length=200)
|
title = models.CharField(max_length=200)
|
||||||
description = models.TextField()
|
description = models.TextField()
|
||||||
category = models.ForeignKey(IssueCategory, on_delete=models.SET_NULL, null=True, blank=True)
|
|
||||||
reporter = models.ForeignKey(User, on_delete=models.CASCADE, related_name='reported_issues')
|
category = models.ForeignKey(
|
||||||
location = models.CharField(max_length=200)
|
"IssueCategory",
|
||||||
latitude = models.FloatField()
|
on_delete=models.SET_NULL,
|
||||||
longitude = models.FloatField()
|
null=True,
|
||||||
photo = models.ImageField(upload_to='issue_photos/', blank=True, null=True,
|
blank=True,
|
||||||
validators=[FileExtensionValidator(['jpg', 'jpeg', 'png', 'gif'])])
|
related_name="issues"
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='reported')
|
)
|
||||||
|
|
||||||
|
reporter = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="reported_issues"
|
||||||
|
)
|
||||||
|
|
||||||
|
location = models.CharField(max_length=200, blank=True)
|
||||||
|
latitude = models.FloatField(null=True, blank=True)
|
||||||
|
longitude = models.FloatField(null=True, blank=True)
|
||||||
|
|
||||||
|
photo = models.ImageField(
|
||||||
|
upload_to="issue_photos/",
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
validators=[FileExtensionValidator(['jpg', 'jpeg', 'png', 'gif'])]
|
||||||
|
)
|
||||||
|
|
||||||
|
status = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
choices=STATUS_CHOICES,
|
||||||
|
default=STATUS_REPORTED
|
||||||
|
)
|
||||||
|
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
ordering = ["-created_at"] # 🔹 latest issues first by default
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.title
|
return f"{self.title} ({self.get_status_display()})"
|
||||||
|
|
||||||
|
# 🔹 Helpers
|
||||||
def vote_count(self):
|
def vote_count(self):
|
||||||
return self.votes.count()
|
return self.votes.count() if hasattr(self, "votes") else 0
|
||||||
|
|
||||||
def has_user_voted(self, user):
|
def has_user_voted(self, user):
|
||||||
if user.is_authenticated:
|
if user.is_authenticated and hasattr(self, "votes"):
|
||||||
return self.votes.filter(user=user).exists()
|
return self.votes.filter(user=user).exists()
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
class Vote(models.Model):
|
class Vote(models.Model):
|
||||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
<i class="fas fa-building me-2 text-success"></i>
|
<i class="fas fa-building me-2 text-success"></i>
|
||||||
<a href="{% url 'manage_departments' %}">Manage Departments</a>
|
<a href="{% url 'manage_departments' %}">Manage Departments</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="list-group-item">
|
||||||
|
<a href="{% url 'manage_issues' %}">Manage Issues</a>
|
||||||
|
</li>
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<a href="#">Manage Roles & Permissions</a>
|
<a href="#">Manage Roles & Permissions</a>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
{% extends "core/base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container my-5">
|
||||||
|
<div class="card shadow-lg">
|
||||||
|
<div class="card-header bg-warning text-dark">
|
||||||
|
<h3>Manage Issues</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
{% if issues %}
|
||||||
|
<table class="table table-bordered table-hover">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>#</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Category</th>
|
||||||
|
<th>Reported By</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Created At</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for issue in issues %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ forloop.counter }}</td>
|
||||||
|
<td>{{ issue.title }}</td>
|
||||||
|
<td>{{ issue.category.name|default:"—" }}</td>
|
||||||
|
<td>{{ issue.reporter.username }}</td>
|
||||||
|
<td>
|
||||||
|
{% if issue.status == "reported" %}
|
||||||
|
<span class="badge bg-danger">Reported</span>
|
||||||
|
{% elif issue.status == "acknowledged" %}
|
||||||
|
<span class="badge bg-info text-dark">Acknowledged</span>
|
||||||
|
{% elif issue.status == "in_progress" %}
|
||||||
|
<span class="badge bg-warning text-dark">In Progress</span>
|
||||||
|
{% elif issue.status == "resolved" %}
|
||||||
|
<span class="badge bg-success">Resolved</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge bg-secondary">Unknown</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ issue.created_at|date:"M d, Y H:i" }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<p class="text-muted">No issues reported yet.</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
@@ -7,6 +7,8 @@ urlpatterns = [
|
|||||||
path("superadmin/", views.superadmin_dashboard, name="superadmin_dashboard"),
|
path("superadmin/", views.superadmin_dashboard, name="superadmin_dashboard"),
|
||||||
path("superadmin/departments/", views.manage_departments, name="manage_departments"),
|
path("superadmin/departments/", views.manage_departments, name="manage_departments"),
|
||||||
path("superadmin/departments/<int:pk>/", views.department_detail, name="department_detail"),
|
path("superadmin/departments/<int:pk>/", views.department_detail, name="department_detail"),
|
||||||
|
path("superadmin/manage/", views.manage_issues, name="manage_issues"),
|
||||||
|
|
||||||
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'),
|
||||||
|
|||||||
@@ -266,4 +266,10 @@ def department_detail(request, pk):
|
|||||||
return render(request, "department/department_detail.html", {
|
return render(request, "department/department_detail.html", {
|
||||||
"department": department,
|
"department": department,
|
||||||
"users": users,
|
"users": users,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@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})
|
||||||
Reference in New Issue
Block a user