Compare commits
19 Commits
4b245d4884
...
test
| Author | SHA1 | Date | |
|---|---|---|---|
| cd1c3d0ff7 | |||
| 219c6d359a | |||
| c11cc49463 | |||
| 7df8d5187d | |||
| f95ebbf375 | |||
| 700260d882 | |||
| fdc776c351 | |||
| aa84422d10 | |||
| ceff3cc1a0 | |||
| ab7373fc34 | |||
| 2767fdf674 | |||
| be2dc882b3 | |||
| 345887d56f | |||
| 0c838571a2 | |||
| 2d0bbe73bd | |||
| 70ae15064c | |||
| 143dd17e6b | |||
| c67538d474 | |||
| 716461af49 |
+2
-2
@@ -1,2 +1,2 @@
|
||||
civicenv
|
||||
civicProject.code-workspace
|
||||
.env
|
||||
issue_photos/
|
||||
@@ -1,10 +0,0 @@
|
||||
SECRET_KEY="django-insecure-wa6p9d+go#+evjql%m(+e5eti$%z7yx2o#cbq8bsh!==icxua3"
|
||||
DEBUG="False"
|
||||
CLOUD_NAME="dkxbfoesf"
|
||||
API_KEY="658671916285379"
|
||||
API_SECRET="_CwNDj4L2dE9yH90Ynj7slPlbo0"
|
||||
CLOUDINARY_URL="cloudinary://658671916285379:_CwNDj4L2dE9yH90Ynj7slPlbo0@dkxbfoesf"
|
||||
DATABASE_URL="postgresql://civicfix_user:YG56PWj9Xj1DvYIKF35TKmIEjrsfis6d@dpg-d2mpapripnbc73f5vaj0-a.oregon-postgres.render.com/civicfix"
|
||||
SUPERUSER_USERNAME="admin"
|
||||
SUPERUSER_PASSWORD="82c96bb18606401630ab9d2836325fbd"
|
||||
SUPERUSER_EMAIL="gokuldevse2001@gmail.com"
|
||||
@@ -1,2 +0,0 @@
|
||||
.env
|
||||
issue_photos/
|
||||
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,78 +0,0 @@
|
||||
{% extends "core/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-header bg-warning text-black d-flex justify-content-between align-items-center">
|
||||
<h3>Manage Issues</h3>
|
||||
<a href="{% url 'superadmin_dashboard' %}" class="btn btn-light btn-sm">Back to Dashboard</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if issues %}
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>No.</th>
|
||||
<th>Title</th>
|
||||
<th>Reported By</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for issue in issues %}
|
||||
<tr>
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>{{ issue.title }}</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>
|
||||
<td>
|
||||
{% if issue.department %}
|
||||
<span class="fw-bold text-primary">{{ issue.department.name }}</span>
|
||||
{% else %}
|
||||
<!-- Assign Department Form -->
|
||||
<form method="post" action="" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="issue_id" value="{{ issue.id }}">
|
||||
<select name="department" class="form-select form-select-sm d-inline w-auto">
|
||||
<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">Assign</button>
|
||||
</form>
|
||||
|
||||
<!-- Report Fake Button -->
|
||||
<a href="{% url 'delete_fake_issue' issue.id %}"
|
||||
class="btn btn-sm btn-danger ms-1"
|
||||
onclick="return confirm('Are you sure you want to delete this issue as FAKE?');">
|
||||
<i class="fas fa-ban"></i> Report Fake
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">No issues reported yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Binary file not shown.
@@ -1,10 +1,6 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import dj_database_url
|
||||
from dotenv import load_dotenv
|
||||
import cloudinary
|
||||
import cloudinary.uploader
|
||||
import cloudinary.api
|
||||
|
||||
# Load .env file (for local dev only, Render will use Environment tab)
|
||||
load_dotenv()
|
||||
@@ -12,9 +8,9 @@ load_dotenv()
|
||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||
|
||||
# Security
|
||||
SECRET_KEY = os.getenv("SECRET_KEY", "unsafe-secret-key")
|
||||
DEBUG = os.getenv("DEBUG", "False") == "True"
|
||||
ALLOWED_HOSTS = ["*", "civicfix.onrender.com"]
|
||||
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||
DEBUG = True
|
||||
ALLOWED_HOSTS = ["localhost", "127.0.0.1"]
|
||||
|
||||
# Installed apps
|
||||
INSTALLED_APPS = [
|
||||
@@ -25,13 +21,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
|
||||
# Third-party
|
||||
'cloudinary',
|
||||
'cloudinary_storage',
|
||||
'whitenoise.runserver_nostatic',
|
||||
|
||||
# Local apps
|
||||
'core.apps.CoreConfig',
|
||||
]
|
||||
|
||||
@@ -67,13 +57,16 @@ TEMPLATES = [
|
||||
|
||||
WSGI_APPLICATION = 'civicfix.wsgi.application'
|
||||
|
||||
# Database (Render Postgres, fallback to SQLite locally)
|
||||
# Database (PostgreSQL for Local)
|
||||
DATABASES = {
|
||||
"default": dj_database_url.config(
|
||||
default=os.environ.get("DATABASE_URL"),
|
||||
conn_max_age=600,
|
||||
ssl_require=True,
|
||||
)
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.getenv("DB_NAME"),
|
||||
'USER': os.getenv("DB_USER"),
|
||||
'PASSWORD': os.getenv("DB_PASSWORD"),
|
||||
'HOST': os.getenv("DB_HOST"),
|
||||
'PORT': os.getenv("DB_PORT"),
|
||||
}
|
||||
}
|
||||
|
||||
# Password validators
|
||||
@@ -84,6 +77,7 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||
{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
|
||||
]
|
||||
|
||||
|
||||
# Internationalization
|
||||
LANGUAGE_CODE = 'en-us'
|
||||
TIME_ZONE = 'UTC'
|
||||
@@ -94,15 +88,8 @@ USE_TZ = True
|
||||
STATIC_URL = "/static/"
|
||||
STATIC_ROOT = BASE_DIR / "staticfiles"
|
||||
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
|
||||
|
||||
cloudinary.config(
|
||||
cloud_name = os.getenv("CLOUD_NAME"),
|
||||
api_key = os.getenv("API_KEY"),
|
||||
api_secret = os.getenv("API_SECRET")
|
||||
)
|
||||
|
||||
MEDIA_URL = f"https://res.cloudinary.com/{os.getenv('CLOUD_NAME')}/"
|
||||
DEFAULT_FILE_STORAGE = 'cloudinary_storage.storage.MediaCloudinaryStorage'
|
||||
MEDIA_URL = "/media/"
|
||||
MEDIA_ROOT = BASE_DIR / "media"
|
||||
|
||||
# Auth redirects
|
||||
LOGIN_REDIRECT_URL = 'citizen_dashboard'
|
||||
@@ -1,16 +0,0 @@
|
||||
<!-- base.html -->
|
||||
{% load static %}
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Civixfix - {% block title %}{% endblock %}</title>
|
||||
<link href="{% static 'css/bootstrap.min.css' %}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container mt-4">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,47 @@
|
||||
# Generated by Django 5.2.5 on 2026-02-02 06:27
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('core', '0002_user_banned_until_user_is_banned'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='vote',
|
||||
unique_together=set(),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='issue',
|
||||
name='photo',
|
||||
field=models.ImageField(blank=True, null=True, upload_to='issues/', validators=[django.core.validators.FileExtensionValidator(['jpg', 'jpeg', 'png', 'webp'])]),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='issue',
|
||||
index=models.Index(fields=['status'], name='core_issue_status_2609e6_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='issue',
|
||||
index=models.Index(fields=['created_at'], name='core_issue_created_50e100_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='issue',
|
||||
index=models.Index(fields=['department'], name='core_issue_departm_5c654b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='issue',
|
||||
index=models.Index(fields=['reporter'], name='core_issue_reporte_3f70e1_idx'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='department',
|
||||
constraint=models.UniqueConstraint(fields=('admin',), name='unique_department_admin'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='vote',
|
||||
constraint=models.UniqueConstraint(fields=('user', 'issue'), name='unique_vote_per_user_issue'),
|
||||
),
|
||||
]
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
@@ -5,6 +5,7 @@ from django.conf import settings
|
||||
from django.db import models
|
||||
from django.utils import timezone
|
||||
|
||||
#User Model
|
||||
class User(AbstractUser):
|
||||
is_citizen = models.BooleanField(default=False)
|
||||
is_moderator = models.BooleanField(default=False)
|
||||
@@ -32,6 +33,9 @@ class User(AbstractUser):
|
||||
is_banned = models.BooleanField(default=False)
|
||||
banned_until = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.username
|
||||
|
||||
def ban(self, days=7):
|
||||
"""Ban user for given number of days (default = 7)."""
|
||||
self.is_banned = True
|
||||
@@ -44,16 +48,16 @@ class User(AbstractUser):
|
||||
self.banned_until = None
|
||||
self.save()
|
||||
|
||||
def is_currently_banned(self):
|
||||
"""Check if user is still banned (auto-unban if expired)."""
|
||||
@property
|
||||
def currently_banned(self):
|
||||
if self.is_banned and self.banned_until:
|
||||
if timezone.now() >= self.banned_until:
|
||||
# Auto unban if ban expired
|
||||
self.unban()
|
||||
return False
|
||||
return True
|
||||
return False
|
||||
|
||||
#Department Model
|
||||
class Department(models.Model):
|
||||
name = models.CharField(max_length=100, unique=True)
|
||||
description = models.TextField(blank=True, null=True)
|
||||
@@ -77,10 +81,17 @@ class Department(models.Model):
|
||||
|
||||
class Meta:
|
||||
ordering = ["name"]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["admin"],
|
||||
name="unique_department_admin"
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
#Issue Model
|
||||
class Issue(models.Model):
|
||||
STATUS_REPORTED = 'reported'
|
||||
STATUS_ACKNOWLEDGED = 'acknowledged'
|
||||
@@ -117,10 +128,10 @@ class Issue(models.Model):
|
||||
longitude = models.FloatField(null=True, blank=True)
|
||||
|
||||
photo = models.ImageField(
|
||||
upload_to="issue_photos/",
|
||||
upload_to="issues/",
|
||||
blank=True,
|
||||
null=True,
|
||||
validators=[FileExtensionValidator(['jpg', 'jpeg', 'png', 'gif'])]
|
||||
validators=[FileExtensionValidator(["jpg", "jpeg", "png", "webp"])]
|
||||
)
|
||||
|
||||
status = models.CharField(
|
||||
@@ -133,43 +144,60 @@ class Issue(models.Model):
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["-created_at"] # 🔹 latest issues first by default
|
||||
ordering = ["-created_at"]
|
||||
indexes = [
|
||||
models.Index(fields=["status"]),
|
||||
models.Index(fields=["created_at"]),
|
||||
models.Index(fields=["department"]),
|
||||
models.Index(fields=["reporter"]),
|
||||
]
|
||||
|
||||
|
||||
# 🔹 latest issues first by default
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.title} ({self.get_status_display()})"
|
||||
|
||||
# 🔹 Helpers
|
||||
def vote_count(self):
|
||||
return self.votes.count() if hasattr(self, "votes") else 0
|
||||
return self.votes.count()
|
||||
|
||||
|
||||
def has_user_voted(self, user):
|
||||
if user.is_authenticated and hasattr(self, "votes"):
|
||||
if user.is_authenticated:
|
||||
return self.votes.filter(user=user).exists()
|
||||
return
|
||||
return False
|
||||
|
||||
|
||||
def assign_to_department(self, department):
|
||||
"""Assign issue to a department and auto-update status to acknowledged"""
|
||||
if self.department_id == department.id:
|
||||
return
|
||||
self.department = department
|
||||
self.status = self.STATUS_ACKNOWLEDGED
|
||||
self.save()
|
||||
|
||||
|
||||
|
||||
#Vote Model
|
||||
class Vote(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name='votes')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'issue') # Prevent duplicate votes per user
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=["user", "issue"],
|
||||
name="unique_vote_per_user_issue"
|
||||
)
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.user.username} voted on {self.issue.title}"
|
||||
|
||||
#Comment Model
|
||||
class Comment(models.Model):
|
||||
issue = models.ForeignKey(Issue, on_delete=models.CASCADE, related_name="comments")
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
content = models.TextField() # <--- field name is "content", not "text"
|
||||
content = models.TextField()
|
||||
parent = models.ForeignKey("self", null=True, blank=True, related_name="replies", on_delete=models.CASCADE)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<hr class="my-4">
|
||||
|
||||
<p class="text-center text-muted">
|
||||
Don't have an account? <a href="{% url 'register' %}" class="text-decoration-none">Register here</a>
|
||||
Don't have an account? <a href="{% url 'register' %}" class="text-decoration-none">Register</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -79,7 +79,7 @@
|
||||
<hr class="my-4">
|
||||
|
||||
<p class="text-center text-muted">
|
||||
Already have an account? <a href="{% url 'login' %}" class="text-decoration-none">Login here</a>
|
||||
Already have an account? <a href="{% url 'login' %}" class="text-decoration-none">Login</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
+1
@@ -5,6 +5,7 @@
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-header bg-warning text-dark">
|
||||
<h3>Analytics & Reports</h3>
|
||||
<a href="{% url 'superadmin_dashboard' %}" class="btn btn-light btn-sm">Back to Dashboard</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
+3
@@ -22,6 +22,7 @@
|
||||
<th>#</th>
|
||||
<th>Title</th>
|
||||
<th>Reported By</th>
|
||||
<th>Location</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
<th>Action</th>
|
||||
@@ -33,6 +34,7 @@
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>{{ issue.title }}</td>
|
||||
<td>{{ issue.reporter.username }}</td>
|
||||
<td>{{ issue.location }}</td>
|
||||
<td>
|
||||
{% if issue.status == "reported" %}
|
||||
<span class="badge bg-danger">Reported</span>
|
||||
@@ -46,6 +48,7 @@
|
||||
<span class="badge bg-secondary">Unknown</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td>{{ issue.created_at|date:"M d, Y H:i" }}</td>
|
||||
<td>
|
||||
{% if issue.status in "reported,acknowledged" %}
|
||||
@@ -0,0 +1,81 @@
|
||||
{% extends "core/base.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container my-5">
|
||||
<div class="card shadow-lg">
|
||||
<div class="card-header bg-danger text-black d-flex justify-content-between align-items-center">
|
||||
<h3>Manage Issues</h3>
|
||||
<a href="{% url 'superadmin_dashboard' %}" class="btn btn-light btn-sm">Back to Dashboard</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% if issues %}
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>No.</th>
|
||||
<th>Title</th>
|
||||
<th>Reported By</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for issue in issues %}
|
||||
<tr>
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>{{ issue.title }}</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>
|
||||
<td>
|
||||
{% if issue.department %}
|
||||
<span class="fw-bold text-primary">{{ issue.department.name }}</span>
|
||||
{% else %}
|
||||
<!-- Assign Department Form -->
|
||||
<form method="post" action="" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="issue_id" value="{{ issue.id }}">
|
||||
<select name="department" class="form-select form-select-sm d-inline w-auto">
|
||||
<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">Assign</button>
|
||||
</form>
|
||||
|
||||
<!-- Report Fake Button -->
|
||||
<a href="{% url 'delete_fake_issue' issue.id %}" class="btn btn-sm btn-danger ms-1"
|
||||
onclick="return confirm('Are you sure you want to delete this issue as FAKE?');">
|
||||
<i class="fas fa-ban"></i> Report Fake
|
||||
</a>
|
||||
<a href="{% url 'delete_issue' issue.id %}" class="btn btn-sm btn-outline-danger ms-1"
|
||||
onclick="return confirm('Are you sure you want to permanently delete this issue? This cannot be undone.');">
|
||||
<i class="fas fa-trash"></i> Delete
|
||||
</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<p class="text-muted">No issues reported yet.</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Binary file not shown.
BIN
Binary file not shown.
@@ -26,4 +26,5 @@ urlpatterns = [
|
||||
path('unban-user/<int:user_id>/', views.unban_user, name='unban_user'),
|
||||
path('issues/<int:issue_id>/delete_fake/', views.delete_fake_issue, name='delete_fake_issue'),
|
||||
path("reports/", views.superadmin_reports, name="superadmin_reports"),
|
||||
path('delete-issue/<int:issue_id>/', views.delete_issue, name='delete_issue'),
|
||||
]
|
||||
@@ -76,8 +76,8 @@ def report_issue(request):
|
||||
|
||||
@login_required
|
||||
def view_all_issues(request):
|
||||
if not request.user.is_citizen:
|
||||
messages.error(request, 'Access denied. Citizen role required.')
|
||||
if not request.user.is_active:
|
||||
messages.error(request, 'Access denied.')
|
||||
return redirect('home')
|
||||
|
||||
# Efficiently annotate whether the current user has voted
|
||||
@@ -384,6 +384,14 @@ def superadmin_reports(request):
|
||||
}
|
||||
return render(request, "core/superadmin_reports.html", context)
|
||||
|
||||
@login_required
|
||||
@user_passes_test(superadmin_check)
|
||||
def delete_issue(request, issue_id):
|
||||
issue = get_object_or_404(Issue, id=issue_id)
|
||||
issue.delete()
|
||||
messages.success(request, "Issue deleted successfully.")
|
||||
return redirect('manage_issues')
|
||||
|
||||
def resolver_check(user):
|
||||
return user.is_resolver
|
||||
|
||||
Reference in New Issue
Block a user