diff --git a/civicfix/civicfix/__pycache__/settings.cpython-313.pyc b/civicfix/civicfix/__pycache__/settings.cpython-313.pyc index 9100e23..17153db 100644 Binary files a/civicfix/civicfix/__pycache__/settings.cpython-313.pyc and b/civicfix/civicfix/__pycache__/settings.cpython-313.pyc differ diff --git a/civicfix/civicfix/settings.py b/civicfix/civicfix/settings.py index b8c4d3f..8a2325a 100644 --- a/civicfix/civicfix/settings.py +++ b/civicfix/civicfix/settings.py @@ -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' diff --git a/civicfix/core/__pycache__/forms.cpython-313.pyc b/civicfix/core/__pycache__/forms.cpython-313.pyc index 630c814..f9aea4a 100644 Binary files a/civicfix/core/__pycache__/forms.cpython-313.pyc and b/civicfix/core/__pycache__/forms.cpython-313.pyc differ diff --git a/civicfix/core/__pycache__/models.cpython-313.pyc b/civicfix/core/__pycache__/models.cpython-313.pyc index 8c0e36e..fc1ad27 100644 Binary files a/civicfix/core/__pycache__/models.cpython-313.pyc and b/civicfix/core/__pycache__/models.cpython-313.pyc differ diff --git a/civicfix/core/__pycache__/urls.cpython-313.pyc b/civicfix/core/__pycache__/urls.cpython-313.pyc index 62119fb..984f322 100644 Binary files a/civicfix/core/__pycache__/urls.cpython-313.pyc and b/civicfix/core/__pycache__/urls.cpython-313.pyc differ diff --git a/civicfix/core/__pycache__/views.cpython-313.pyc b/civicfix/core/__pycache__/views.cpython-313.pyc index dfb2473..4214db1 100644 Binary files a/civicfix/core/__pycache__/views.cpython-313.pyc and b/civicfix/core/__pycache__/views.cpython-313.pyc differ diff --git a/civicfix/core/forms.py b/civicfix/core/forms.py index f41ee0a..77787de 100644 --- a/civicfix/core/forms.py +++ b/civicfix/core/forms.py @@ -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 \ No newline at end of file + 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...'}), + } \ No newline at end of file diff --git a/civicfix/core/migrations/0001_initial.py b/civicfix/core/migrations/0001_initial.py index 7d6412f..1478ea0 100644 --- a/civicfix/core/migrations/0001_initial.py +++ b/civicfix/core/migrations/0001_initial.py @@ -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')), + ], + ), ] diff --git a/civicfix/core/migrations/__pycache__/0001_initial.cpython-313.pyc b/civicfix/core/migrations/__pycache__/0001_initial.cpython-313.pyc index 4682d1c..4854e70 100644 Binary files a/civicfix/core/migrations/__pycache__/0001_initial.cpython-313.pyc and b/civicfix/core/migrations/__pycache__/0001_initial.cpython-313.pyc differ diff --git a/civicfix/core/migrations/__pycache__/0002_alter_user_groups_alter_user_user_permissions.cpython-313.pyc b/civicfix/core/migrations/__pycache__/0002_alter_user_groups_alter_user_user_permissions.cpython-313.pyc new file mode 100644 index 0000000..b64a153 Binary files /dev/null and b/civicfix/core/migrations/__pycache__/0002_alter_user_groups_alter_user_user_permissions.cpython-313.pyc differ diff --git a/civicfix/core/migrations/__pycache__/0003_alter_user_user_permissions.cpython-313.pyc b/civicfix/core/migrations/__pycache__/0003_alter_user_user_permissions.cpython-313.pyc new file mode 100644 index 0000000..1e4329e Binary files /dev/null and b/civicfix/core/migrations/__pycache__/0003_alter_user_user_permissions.cpython-313.pyc differ diff --git a/civicfix/core/migrations/__pycache__/0004_alter_user_user_permissions.cpython-313.pyc b/civicfix/core/migrations/__pycache__/0004_alter_user_user_permissions.cpython-313.pyc new file mode 100644 index 0000000..ce54275 Binary files /dev/null and b/civicfix/core/migrations/__pycache__/0004_alter_user_user_permissions.cpython-313.pyc differ diff --git a/civicfix/core/models.py b/civicfix/core/models.py index 607962c..050a1c6 100644 --- a/civicfix/core/models.py +++ b/civicfix/core/models.py @@ -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') \ No newline at end of file + 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 \ No newline at end of file diff --git a/civicfix/core/templates/core/base.html b/civicfix/core/templates/core/base.html index e054ff4..b6cc410 100644 --- a/civicfix/core/templates/core/base.html +++ b/civicfix/core/templates/core/base.html @@ -35,32 +35,56 @@ + +
diff --git a/civicfix/core/templates/core/citizen_dashboard.html b/civicfix/core/templates/core/citizen_dashboard.html new file mode 100644 index 0000000..bfc9d1e --- /dev/null +++ b/civicfix/core/templates/core/citizen_dashboard.html @@ -0,0 +1,240 @@ +{% extends "core/base.html" %} +{% block title %}Citizen Dashboard - CivixFix{% endblock %} + +{% block extra_css %} + +{% endblock %} + +{% block content %} +
+
+ +
+
+
+
Welcome, {{ user.username }}
+

Citizen Dashboard

+ +
+ +
+ +
+ +
Quick Stats
+
+
+
+

{{ user_issues.count }}

+ My Reports +
+
+
+
+

+ {{ resolved_count }} +

+ Resolved +
+
+
+
+
+
+ + +
+ +
+
+

Welcome to CivixFix!

+

+ Report community issues, track their progress, and help make your neighborhood better. + Start by reporting an issue using the button on the left. +

+
+
+ + +
+
+
My Recent Reports
+
+
+ {% for issue in user_issues %} +
+
+
+
+
{{ issue.title }}
+

+ {{ issue.description|truncatewords:15 }} +

+
+ {{ issue.category.name|default:"No Category" }} + + {{ issue.get_status_display }} + + {{ issue.created_at|date:"M d, Y" }} +
+
+ {% if issue.photo %} +
+ Issue photo +
+ {% endif %} +
+
+
+ {% empty %} +
+ +
No issues reported yet
+

Click "Report New Issue" to get started!

+
+ {% endfor %} +
+
+
+
+
+ + + +{% endblock %} + +{% block extra_js %} + + + +{% endblock %} \ No newline at end of file diff --git a/civicfix/core/templates/core/login.html b/civicfix/core/templates/core/login.html new file mode 100644 index 0000000..d4dd8dc --- /dev/null +++ b/civicfix/core/templates/core/login.html @@ -0,0 +1,54 @@ +{% extends "core/base.html" %} + +{% block title %}Login{% endblock %} + +{% block content %} +
+
+
+
+
+

Login to Your Account

+ + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + +
+ {% csrf_token %} + +
+ + +
+ +
+ + +
+ +
+ +
+ + +
+ +
+ +

+ Don't have an account? Register here +

+
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/civicfix/core/templates/core/register.html b/civicfix/core/templates/core/register.html index 56798e5..ff42358 100644 --- a/civicfix/core/templates/core/register.html +++ b/civicfix/core/templates/core/register.html @@ -1,5 +1,4 @@ {% extends "core/base.html" %} - {% block title %}Register as Citizen{% endblock %} {% block content %} @@ -9,38 +8,50 @@

Create Citizen Account

+
{% csrf_token %} + {# Messages block - FIXED SYNTAX #} + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + + {# Form errors block - FIXED SYNTAX #} {% if form.errors %} -
- Error! Please correct the following: -
    +
    + Error! Please correct the following: +
      {% for field, errors in form.errors.items %} {% for error in errors %}
    • {{ error }}
    • {% endfor %} {% endfor %} -
    -
    +
+
{% endif %} - +
{{ form.username }} Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.
- +
{{ form.email }}
- +
{{ form.phone }}
- +
{{ form.password1 }} @@ -53,20 +64,20 @@
- +
{{ form.password2 }} Enter the same password as before, for verification.
- +
- +
- +

Already have an account? Login here

@@ -79,10 +90,14 @@ {% block extra_js %} {% endblock %} \ No newline at end of file diff --git a/civicfix/core/urls.py b/civicfix/core/urls.py index 0dcea45..5b4ba95 100644 --- a/civicfix/core/urls.py +++ b/civicfix/core/urls.py @@ -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'), ] \ No newline at end of file diff --git a/civicfix/core/views.py b/civicfix/core/views.py index 58ab75a..9a0d360 100644 --- a/civicfix/core/views.py +++ b/civicfix/core/views.py @@ -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}) \ No newline at end of file + + 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}) \ No newline at end of file