major updation
This commit is contained in:
Binary file not shown.
@@ -26,7 +26,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'core',
|
||||
'core.apps.CoreConfig',
|
||||
]
|
||||
|
||||
AUTH_USER_MODEL = 'core.User'
|
||||
@@ -67,9 +67,9 @@ WSGI_APPLICATION = 'civicfix.wsgi.application'
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': 'civixfix_db',
|
||||
'USER': 'civicfix_user',
|
||||
'PASSWORD': 'Gokul@2001',
|
||||
'NAME': 'civicfix',
|
||||
'USER': 'admin',
|
||||
'PASSWORD': 'qwerty123',
|
||||
'HOST': 'localhost',
|
||||
'PORT': '5432',
|
||||
}
|
||||
@@ -116,5 +116,7 @@ STATIC_URL = 'static/'
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
LOGIN_REDIRECT_URL = 'home'
|
||||
# Add these settings
|
||||
LOGIN_REDIRECT_URL = 'citizen_dashboard'
|
||||
LOGIN_URL = 'login'
|
||||
LOGOUT_REDIRECT_URL = 'home'
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+30
-2
@@ -1,6 +1,6 @@
|
||||
from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from .models import User
|
||||
from .models import User, Issue
|
||||
|
||||
class CitizenRegistrationForm(UserCreationForm):
|
||||
email = forms.EmailField(required=True)
|
||||
@@ -18,4 +18,32 @@ class CitizenRegistrationForm(UserCreationForm):
|
||||
|
||||
if commit:
|
||||
user.save()
|
||||
return user
|
||||
return user
|
||||
|
||||
class CitizenRegistrationForm(UserCreationForm):
|
||||
email = forms.EmailField(required=True)
|
||||
phone = forms.CharField(max_length=15, required=False)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
fields = ['username', 'email', 'phone', 'password1', 'password2']
|
||||
|
||||
def save(self, commit=True):
|
||||
user = super().save(commit=False)
|
||||
user.email = self.cleaned_data['email']
|
||||
user.phone = self.cleaned_data['phone']
|
||||
user.is_citizen = True
|
||||
|
||||
if commit:
|
||||
user.save()
|
||||
return user
|
||||
|
||||
class IssueForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Issue
|
||||
fields = ['title', 'description', 'category', 'location', 'latitude', 'longitude', 'photo']
|
||||
widgets = {
|
||||
'latitude': forms.HiddenInput(),
|
||||
'longitude': forms.HiddenInput(),
|
||||
'description': forms.Textarea(attrs={'rows': 4, 'placeholder': 'Describe the issue in detail...'}),
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
# Generated by Django 5.2.5 on 2025-08-18 15:07
|
||||
# Generated by Django 5.2.5 on 2025-08-20 06:30
|
||||
|
||||
import django.contrib.auth.models
|
||||
import django.contrib.auth.validators
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
@@ -15,6 +18,15 @@ 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=[
|
||||
@@ -33,8 +45,8 @@ class Migration(migrations.Migration):
|
||||
('is_moderator', models.BooleanField(default=False)),
|
||||
('is_resolver', models.BooleanField(default=False)),
|
||||
('phone', models.CharField(blank=True, max_length=15, null=True)),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
|
||||
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to.', related_name='core_user_groups', related_query_name='core_user', to='auth.group', verbose_name='groups')),
|
||||
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='core_user_permissions', related_query_name='core_user', to='auth.permission', verbose_name='user permissions')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'user',
|
||||
@@ -45,4 +57,21 @@ class Migration(migrations.Migration):
|
||||
('objects', django.contrib.auth.models.UserManager()),
|
||||
],
|
||||
),
|
||||
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()),
|
||||
('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)),
|
||||
('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')),
|
||||
],
|
||||
),
|
||||
]
|
||||
|
||||
Binary file not shown.
BIN
Binary file not shown.
Binary file not shown.
Binary file not shown.
+49
-2
@@ -1,6 +1,7 @@
|
||||
from django.db import models
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
from django.urls import reverse
|
||||
from django.core.validators import FileExtensionValidator
|
||||
|
||||
class User(AbstractUser):
|
||||
is_citizen = models.BooleanField(default=False)
|
||||
@@ -8,5 +9,51 @@ class User(AbstractUser):
|
||||
is_resolver = models.BooleanField(default=False)
|
||||
phone = models.CharField(max_length=15, blank=True, null=True)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('home')
|
||||
groups = models.ManyToManyField(
|
||||
'auth.Group',
|
||||
verbose_name='groups',
|
||||
blank=True,
|
||||
help_text='The groups this user belongs to.',
|
||||
related_name='core_user_groups',
|
||||
related_query_name='core_user',
|
||||
)
|
||||
user_permissions = models.ManyToManyField(
|
||||
'auth.Permission',
|
||||
verbose_name='user permissions',
|
||||
blank=True,
|
||||
help_text='Specific permissions for this user.',
|
||||
related_name='core_user_permissions',
|
||||
related_query_name='core_user',
|
||||
)
|
||||
|
||||
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_CHOICES = [
|
||||
('reported', 'Reported'),
|
||||
('acknowledged', 'Acknowledged'),
|
||||
('in_progress', 'In Progress'),
|
||||
('resolved', 'Resolved'),
|
||||
]
|
||||
|
||||
title = models.CharField(max_length=200)
|
||||
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')
|
||||
location = models.CharField(max_length=200)
|
||||
latitude = models.FloatField()
|
||||
longitude = models.FloatField()
|
||||
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='reported')
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
@@ -35,32 +35,56 @@
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark sticky-top">
|
||||
<div class="container">
|
||||
<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>
|
||||
</ul>
|
||||
<div class="d-flex">
|
||||
<a href="{% url 'login' %}" class="btn btn-outline-light me-2">Login</a>
|
||||
<a href="{% url 'register' %}" class="btn btn-primary">Register</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container">
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
</nav>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main>
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
{% extends "core/base.html" %}
|
||||
{% block title %}Citizen Dashboard - CivixFix{% endblock %}
|
||||
|
||||
{% block extra_css %}
|
||||
<style>
|
||||
.dashboard-card {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.dashboard-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.map-container {
|
||||
height: 300px;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 2px dashed #dee2e6;
|
||||
}
|
||||
.issue-status {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<!-- Sidebar - Quick Actions -->
|
||||
<div class="col-md-3">
|
||||
<div class="card shadow-sm dashboard-card">
|
||||
<div class="card-body text-center">
|
||||
<h5 class="card-title">Welcome, {{ user.username }}</h5>
|
||||
<p class="text-muted">Citizen Dashboard</p>
|
||||
|
||||
<div class="d-grid gap-2 mb-3">
|
||||
<button class="btn btn-primary btn-lg" data-bs-toggle="modal" data-bs-target="#reportIssueModal">
|
||||
<i class="fas fa-plus me-2"></i>Report New Issue
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h6>Quick Stats</h6>
|
||||
<div class="row text-center">
|
||||
<div class="col-6">
|
||||
<div class="bg-light p-2 rounded">
|
||||
<h4 class="mb-0 text-primary">{{ user_issues.count }}</h4>
|
||||
<small>My Reports</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="bg-light p-2 rounded">
|
||||
<h4 class="mb-0 text-success">
|
||||
{{ resolved_count }}
|
||||
</h4>
|
||||
<small>Resolved</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content - Recent Issues -->
|
||||
<div class="col-md-9">
|
||||
<!-- Welcome Message -->
|
||||
<div class="card shadow-sm dashboard-card mb-4">
|
||||
<div class="card-body">
|
||||
<h4>Welcome to CivixFix!</h4>
|
||||
<p class="text-muted mb-0">
|
||||
Report community issues, track their progress, and help make your neighborhood better.
|
||||
Start by reporting an issue using the button on the left.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- My Recent Issues -->
|
||||
<div class="card shadow-sm dashboard-card">
|
||||
<div class="card-header">
|
||||
<h5 class="mb-0">My Recent Reports</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for issue in user_issues %}
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="card-title mb-1">{{ issue.title }}</h6>
|
||||
<p class="card-text text-muted mb-2 small">
|
||||
{{ issue.description|truncatewords:15 }}
|
||||
</p>
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<span class="badge bg-secondary">{{ issue.category.name|default:"No Category" }}</span>
|
||||
<span class="badge bg-{% if issue.status == 'resolved' %}success{% elif issue.status == 'in_progress' %}warning{% else %}primary{% endif %} issue-status">
|
||||
{{ issue.get_status_display }}
|
||||
</span>
|
||||
<small class="text-muted">{{ issue.created_at|date:"M d, Y" }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% if issue.photo %}
|
||||
<div class="ms-3">
|
||||
<img src="{{ issue.photo.url }}" alt="Issue photo"
|
||||
class="img-fluid rounded" style="max-height: 60px; max-width: 80px;">
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="text-center py-4">
|
||||
<i class="fas fa-inbox fa-3x text-muted mb-3"></i>
|
||||
<h5>No issues reported yet</h5>
|
||||
<p class="text-muted">Click "Report New Issue" to get started!</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Report Issue Modal -->
|
||||
<div class="modal fade" id="reportIssueModal" tabindex="-1">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Report New Issue</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="issueForm" method="post" enctype="multipart/form-data" action="{% url 'report_issue' %}">
|
||||
{% csrf_token %}
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show mb-3">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Issue Title*</label>
|
||||
{{ issue_form.title }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Category</label>
|
||||
{{ issue_form.category }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Description*</label>
|
||||
{{ issue_form.description }}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Location*</label>
|
||||
{{ issue_form.location }}
|
||||
<small class="text-muted">Click on the map to select location</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Photo (Optional)</label>
|
||||
{{ issue_form.photo }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden latitude/longitude fields -->
|
||||
{{ issue_form.latitude }}
|
||||
{{ issue_form.longitude }}
|
||||
|
||||
<div class="map-container mb-3">
|
||||
<div id="map" style="height: 100%;"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||
<button type="submit" form="issueForm" class="btn btn-primary">
|
||||
<i class="fas fa-paper-plane me-2"></i>Report Issue
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<!-- Leaflet JS -->
|
||||
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js"></script>
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Initialize map
|
||||
const map = L.map('map').setView([51.505, -0.09], 13);
|
||||
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
||||
attribution: '© OpenStreetMap contributors'
|
||||
}).addTo(map);
|
||||
|
||||
let marker = null;
|
||||
|
||||
// Add click event to map
|
||||
map.on('click', function(e) {
|
||||
if (marker) {
|
||||
map.removeLayer(marker);
|
||||
}
|
||||
|
||||
marker = L.marker(e.latlng).addTo(map)
|
||||
.bindPopup('Selected location').openPopup();
|
||||
|
||||
// Update form fields
|
||||
$('#id_latitude').val(e.latlng.lat);
|
||||
$('#id_longitude').val(e.latlng.lng);
|
||||
|
||||
// Reverse geocode to get address
|
||||
fetch(`https://nominatim.openstreetmap.org/reverse?format=json&lat=${e.latlng.lat}&lon=${e.latlng.lng}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
$('#id_location').val(data.display_name || 'Selected location');
|
||||
});
|
||||
});
|
||||
|
||||
// Add Bootstrap classes to form fields
|
||||
$('input:not([type="file"]), select, textarea').addClass('form-control');
|
||||
$('input[type="file"]').addClass('form-control-file');
|
||||
|
||||
// Add specific classes to description field
|
||||
$('#id_description').addClass('form-control').attr('rows', '3');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,54 @@
|
||||
{% extends "core/base.html" %}
|
||||
|
||||
{% block title %}Login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-6">
|
||||
<div class="card shadow">
|
||||
<div class="card-body p-5">
|
||||
<h2 class="card-title text-center mb-4">Login to Your Account</h2>
|
||||
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_username" class="form-label">Username</label>
|
||||
<input type="text" name="username" class="form-control" id="id_username" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="id_password" class="form-label">Password</label>
|
||||
<input type="password" name="password" class="form-control" id="id_password" required>
|
||||
</div>
|
||||
|
||||
<div class="d-grid mb-3">
|
||||
<button type="submit" class="btn btn-primary btn-lg">Login</button>
|
||||
</div>
|
||||
|
||||
<div class="text-center">
|
||||
<a href="#" class="text-decoration-none">Forgot password?</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<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>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,4 @@
|
||||
{% extends "core/base.html" %}
|
||||
|
||||
{% block title %}Register as Citizen{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
@@ -9,38 +8,50 @@
|
||||
<div class="card shadow">
|
||||
<div class="card-body p-5">
|
||||
<h2 class="card-title text-center mb-4">Create Citizen Account</h2>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
{# Messages block - FIXED SYNTAX #}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
{# Form errors block - FIXED SYNTAX #}
|
||||
{% if form.errors %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>Error!</strong> Please correct the following:
|
||||
<ul>
|
||||
<div class="alert alert-danger">
|
||||
<strong>Error!</strong> Please correct the following:
|
||||
<ul>
|
||||
{% for field, errors in form.errors.items %}
|
||||
{% for error in errors %}
|
||||
<li>{{ error }}</li>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_username" class="form-label">Username</label>
|
||||
{{ form.username }}
|
||||
<small class="text-muted">Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_email" class="form-label">Email</label>
|
||||
{{ form.email }}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_phone" class="form-label">Phone (Optional)</label>
|
||||
{{ form.phone }}
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_password1" class="form-label">Password</label>
|
||||
{{ form.password1 }}
|
||||
@@ -53,20 +64,20 @@
|
||||
</ul>
|
||||
</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-4">
|
||||
<label for="id_password2" class="form-label">Password Confirmation</label>
|
||||
{{ form.password2 }}
|
||||
<small class="text-muted">Enter the same password as before, for verification.</small>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="d-grid">
|
||||
<button type="submit" class="btn btn-primary btn-lg">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<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>
|
||||
</p>
|
||||
@@ -79,10 +90,14 @@
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
// Add Bootstrap classes to form fields
|
||||
$(document).ready(function() {
|
||||
$('input').addClass('form-control');
|
||||
$(document).ready(function () {
|
||||
// Add Bootstrap classes to all input fields
|
||||
$('input:not([type="checkbox"]):not([type="radio"])').addClass('form-control');
|
||||
$('input[type="checkbox"]').removeClass('form-control').addClass('form-check-input');
|
||||
$('input[type="radio"]').removeClass('form-control').addClass('form-check-input');
|
||||
|
||||
// Add Bootstrap classes to select fields
|
||||
$('select').addClass('form-select');
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -5,6 +5,8 @@ from . import views
|
||||
urlpatterns = [
|
||||
path('', views.home, name='home'),
|
||||
path('register/', views.register, name='register'),
|
||||
path('login/', auth_views.LoginView.as_view(template_name='core/login.html'), name='login'),
|
||||
path('login/', views.custom_login, name='login'),
|
||||
path('logout/', auth_views.LogoutView.as_view(), name='logout'),
|
||||
path('dashboard/', views.citizen_dashboard, name='citizen_dashboard'),
|
||||
path('report-issue/', views.report_issue, name='report_issue'),
|
||||
]
|
||||
+82
-5
@@ -1,17 +1,94 @@
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth import login
|
||||
from .forms import CitizenRegistrationForm
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import authenticate, login
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from .models import Issue, IssueCategory
|
||||
from .forms import CitizenRegistrationForm, IssueForm
|
||||
|
||||
def home(request):
|
||||
return render(request, 'core/index.html')
|
||||
|
||||
|
||||
def home(request):
|
||||
if request.user.is_authenticated and request.user.is_citizen:
|
||||
return redirect('citizen_dashboard')
|
||||
return render(request, 'core/index.html')
|
||||
|
||||
|
||||
def home(request):
|
||||
if request.user.is_authenticated and request.user.is_citizen:
|
||||
return redirect('citizen_dashboard')
|
||||
return render(request, 'core/index.html')
|
||||
|
||||
@login_required
|
||||
def citizen_dashboard(request):
|
||||
if not request.user.is_citizen:
|
||||
messages.error(request, 'Access denied. Citizen role required.')
|
||||
return redirect('home')
|
||||
|
||||
# Get only basic data for now
|
||||
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, 'core/citizen_dashboard.html', context)
|
||||
|
||||
@login_required
|
||||
def report_issue(request):
|
||||
if not request.user.is_citizen:
|
||||
messages.error(request, 'Access denied. Citizen role required.')
|
||||
return redirect('home')
|
||||
|
||||
if request.method == 'POST':
|
||||
form = IssueForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
issue = form.save(commit=False)
|
||||
issue.reporter = request.user
|
||||
issue.save()
|
||||
messages.success(request, 'Issue reported successfully!')
|
||||
return redirect('citizen_dashboard')
|
||||
else:
|
||||
messages.error(request, 'Please correct the errors below.')
|
||||
else:
|
||||
form = IssueForm()
|
||||
|
||||
return render(request, 'core/report_issue.html', {'form': form})
|
||||
|
||||
def register(request):
|
||||
if request.method == 'POST':
|
||||
form = CitizenRegistrationForm(request.POST)
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
login(request, user)
|
||||
return redirect('home')
|
||||
messages.success(request, 'Registration successful! Please login.')
|
||||
return redirect('login')
|
||||
else:
|
||||
form = CitizenRegistrationForm()
|
||||
return render(request, 'core/register.html', {'form': form})
|
||||
|
||||
return render(request, 'core/register.html', {'form': form})
|
||||
|
||||
def custom_login(request):
|
||||
if request.method == 'POST':
|
||||
form = AuthenticationForm(request, data=request.POST)
|
||||
if form.is_valid():
|
||||
username = form.cleaned_data.get('username')
|
||||
password = form.cleaned_data.get('password')
|
||||
user = authenticate(username=username, password=password)
|
||||
if user is not None:
|
||||
login(request, user)
|
||||
messages.success(request, f'Welcome back, {username}!')
|
||||
return redirect('home')
|
||||
else:
|
||||
messages.error(request, 'Invalid username or password.')
|
||||
else:
|
||||
messages.error(request, 'Invalid username or password.')
|
||||
else:
|
||||
form = AuthenticationForm()
|
||||
return render(request, 'core/login.html', {'form': form})
|
||||
Reference in New Issue
Block a user