diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index a5ed392..5e21bfb 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/forms.cpython-311.pyc b/core/__pycache__/forms.cpython-311.pyc new file mode 100644 index 0000000..d4709a8 Binary files /dev/null and b/core/__pycache__/forms.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index e061640..7f48f71 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 5a69659..0fba60a 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 2a36fd6..fca47c7 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..ebf1823 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,41 @@ from django.contrib import admin +from .models import Voter, VotingRecord, Donation, VoterContact, EventParticipation -# Register your models here. +class VotingRecordInline(admin.TabularInline): + model = VotingRecord + extra = 1 + +class DonationInline(admin.TabularInline): + model = Donation + extra = 1 + +class VoterContactInline(admin.TabularInline): + model = VoterContact + extra = 1 + +class EventParticipationInline(admin.TabularInline): + model = EventParticipation + extra = 1 + +@admin.register(Voter) +class VoterAdmin(admin.ModelAdmin): + list_display = ('voter_id', 'last_name', 'first_name', 'district', 'precinct', 'candidate_support') + list_filter = ('candidate_support', 'yard_sign_status', 'district') + search_fields = ('voter_id', 'last_name', 'first_name', 'email') + inlines = [VotingRecordInline, DonationInline, VoterContactInline, EventParticipationInline] + +@admin.register(VotingRecord) +class VotingRecordAdmin(admin.ModelAdmin): + list_display = ('voter', 'election_date', 'description') + +@admin.register(Donation) +class DonationAdmin(admin.ModelAdmin): + list_display = ('voter', 'donation_date', 'amount') + +@admin.register(VoterContact) +class VoterContactAdmin(admin.ModelAdmin): + list_display = ('voter', 'contact_type', 'contact_date') + +@admin.register(EventParticipation) +class EventParticipationAdmin(admin.ModelAdmin): + list_display = ('voter', 'event_type', 'event_date') \ No newline at end of file diff --git a/core/forms.py b/core/forms.py new file mode 100644 index 0000000..ef9e5a8 --- /dev/null +++ b/core/forms.py @@ -0,0 +1,49 @@ +from django import forms +from .models import Voter, VotingRecord, Donation, VoterContact, EventParticipation + +class VoterForm(forms.ModelForm): + class Meta: + model = Voter + fields = [ + 'first_name', 'last_name', 'voter_id', 'address', 'phone', 'email', + 'district', 'precinct', 'registration_date', 'likelihood_to_vote', + 'candidate_support', 'yard_sign_status' + ] + widgets = { + 'registration_date': forms.DateInput(attrs={'type': 'date'}), + 'address': forms.Textarea(attrs={'rows': 3}), + } + +class VotingRecordForm(forms.ModelForm): + class Meta: + model = VotingRecord + fields = ['election_date', 'description', 'primary_party'] + widgets = { + 'election_date': forms.DateInput(attrs={'type': 'date'}), + } + +class DonationForm(forms.ModelForm): + class Meta: + model = Donation + fields = ['donation_date', 'amount', 'method'] + widgets = { + 'donation_date': forms.DateInput(attrs={'type': 'date'}), + } + +class VoterContactForm(forms.ModelForm): + class Meta: + model = VoterContact + fields = ['contact_type', 'contact_date', 'description', 'notes'] + widgets = { + 'contact_date': forms.DateTimeInput(attrs={'type': 'datetime-local'}), + 'notes': forms.Textarea(attrs={'rows': 3}), + } + +class EventParticipationForm(forms.ModelForm): + class Meta: + model = EventParticipation + fields = ['event_date', 'event_type', 'description'] + widgets = { + 'event_date': forms.DateInput(attrs={'type': 'date'}), + 'description': forms.Textarea(attrs={'rows': 3}), + } \ No newline at end of file diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..2aac963 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,70 @@ +# Generated by Django 5.2.7 on 2026-01-24 03:04 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Voter', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('voter_id', models.CharField(max_length=50, unique=True)), + ('first_name', models.CharField(max_length=100)), + ('last_name', models.CharField(max_length=100)), + ('address', models.TextField()), + ('phone', models.CharField(blank=True, max_length=20, null=True)), + ('email', models.EmailField(blank=True, max_length=254, null=True)), + ('district', models.CharField(blank=True, max_length=100, null=True)), + ('precinct', models.CharField(blank=True, max_length=100, null=True)), + ('registration_date', models.DateField(blank=True, null=True)), + ('likelihood_to_vote', models.IntegerField(choices=[(1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5')], default=3)), + ('candidate_support', models.CharField(choices=[('unknown', 'Unknown'), ('supporting', 'Supporting'), ('not_supporting', 'Not Supporting')], default='unknown', max_length=20)), + ('yard_sign_status', models.CharField(choices=[('none', 'None'), ('wants', 'Wants a Yard Sign'), ('has', 'Has a Yard Sign')], default='none', max_length=20)), + ('latitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)), + ('longitude', models.DecimalField(blank=True, decimal_places=6, max_digits=9, null=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ], + options={ + 'ordering': ['last_name', 'first_name'], + }, + ), + migrations.CreateModel( + name='Donation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('donation_date', models.DateField()), + ('amount', models.DecimalField(decimal_places=2, max_digits=10)), + ('voter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='donations', to='core.voter')), + ], + ), + migrations.CreateModel( + name='VoterContact', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('contact_type', models.CharField(choices=[('Phone', 'Phone'), ('Door Visit', 'Door Visit'), ('Mail', 'Mail')], max_length=20)), + ('contact_date', models.DateTimeField()), + ('description', models.CharField(max_length=255)), + ('notes', models.TextField(blank=True, null=True)), + ('voter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='contacts', to='core.voter')), + ], + ), + migrations.CreateModel( + name='VotingRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('election_date', models.DateField()), + ('description', models.CharField(max_length=255)), + ('primary_party', models.CharField(blank=True, max_length=50, null=True)), + ('voter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='voting_records', to='core.voter')), + ], + ), + ] diff --git a/core/migrations/0002_eventparticipation.py b/core/migrations/0002_eventparticipation.py new file mode 100644 index 0000000..cfdc506 --- /dev/null +++ b/core/migrations/0002_eventparticipation.py @@ -0,0 +1,24 @@ +# Generated by Django 5.2.7 on 2026-01-24 03:23 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='EventParticipation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('event_date', models.DateField()), + ('event_type', models.CharField(max_length=100)), + ('description', models.TextField(blank=True, null=True)), + ('voter', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_participations', to='core.voter')), + ], + ), + ] diff --git a/core/migrations/0003_donation_method.py b/core/migrations/0003_donation_method.py new file mode 100644 index 0000000..95f1ffb --- /dev/null +++ b/core/migrations/0003_donation_method.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-01-24 03:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_eventparticipation'), + ] + + operations = [ + migrations.AddField( + model_name='donation', + name='method', + field=models.CharField(choices=[('Cash', 'Cash'), ('Check', 'Check'), ('Credit/Debit', 'Credit/Debit')], default='Check', max_length=20), + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..030c168 Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0002_eventparticipation.cpython-311.pyc b/core/migrations/__pycache__/0002_eventparticipation.cpython-311.pyc new file mode 100644 index 0000000..9c87672 Binary files /dev/null and b/core/migrations/__pycache__/0002_eventparticipation.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0003_donation_method.cpython-311.pyc b/core/migrations/__pycache__/0003_donation_method.cpython-311.pyc new file mode 100644 index 0000000..e4b6ad0 Binary files /dev/null and b/core/migrations/__pycache__/0003_donation_method.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..e57acdb 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,95 @@ from django.db import models +from django.urls import reverse -# Create your models here. +class Voter(models.Model): + CANDIDATE_SUPPORT_CHOICES = [ + ('unknown', 'Unknown'), + ('supporting', 'Supporting'), + ('not_supporting', 'Not Supporting'), + ] + YARD_SIGN_CHOICES = [ + ('none', 'None'), + ('wants', 'Wants a Yard Sign'), + ('has', 'Has a Yard Sign'), + ] + LIKELIHOOD_CHOICES = [(i, str(i)) for i in range(1, 6)] + + voter_id = models.CharField(max_length=50, unique=True) + first_name = models.CharField(max_length=100) + last_name = models.CharField(max_length=100) + address = models.TextField() + phone = models.CharField(max_length=20, blank=True, null=True) + email = models.EmailField(blank=True, null=True) + + # Demographics + district = models.CharField(max_length=100, blank=True, null=True) + precinct = models.CharField(max_length=100, blank=True, null=True) + registration_date = models.DateField(blank=True, null=True) + + # Engagement + likelihood_to_vote = models.IntegerField(choices=LIKELIHOOD_CHOICES, default=3) + candidate_support = models.CharField(max_length=20, choices=CANDIDATE_SUPPORT_CHOICES, default='unknown') + yard_sign_status = models.CharField(max_length=20, choices=YARD_SIGN_CHOICES, default='none') + + # Geocode (Optional for map integration later) + latitude = models.DecimalField(max_digits=9, decimal_places=6, blank=True, null=True) + longitude = models.DecimalField(max_digits=9, decimal_places=6, blank=True, null=True) + + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ['last_name', 'first_name'] + + def __str__(self): + return f"{self.first_name} {self.last_name} ({self.voter_id})" + + def get_absolute_url(self): + return reverse('voter_detail', kwargs={'pk': self.pk}) + +class VotingRecord(models.Model): + voter = models.ForeignKey(Voter, on_delete=models.CASCADE, related_name='voting_records') + election_date = models.DateField() + description = models.CharField(max_length=255) + primary_party = models.CharField(max_length=50, blank=True, null=True) + + def __str__(self): + return f"{self.voter.last_name} - {self.election_date}" + +class Donation(models.Model): + METHOD_CHOICES = [ + ('Cash', 'Cash'), + ('Check', 'Check'), + ('Credit/Debit', 'Credit/Debit'), + ] + voter = models.ForeignKey(Voter, on_delete=models.CASCADE, related_name='donations') + donation_date = models.DateField() + amount = models.DecimalField(max_digits=10, decimal_places=2) + method = models.CharField(max_length=20, choices=METHOD_CHOICES, default='Check') + + def __str__(self): + return f"{self.voter.last_name} - ${self.amount} ({self.method})" + +class VoterContact(models.Model): + CONTACT_TYPE_CHOICES = [ + ('Phone', 'Phone'), + ('Door Visit', 'Door Visit'), + ('Mail', 'Mail'), + ] + voter = models.ForeignKey(Voter, on_delete=models.CASCADE, related_name='contacts') + contact_type = models.CharField(max_length=20, choices=CONTACT_TYPE_CHOICES) + contact_date = models.DateTimeField() + description = models.CharField(max_length=255) + notes = models.TextField(blank=True, null=True) + + def __str__(self): + return f"{self.voter.last_name} - {self.contact_type} on {self.contact_date.date()}" + +class EventParticipation(models.Model): + voter = models.ForeignKey(Voter, on_delete=models.CASCADE, related_name='event_participations') + event_date = models.DateField() + event_type = models.CharField(max_length=100) + description = models.TextField(blank=True, null=True) + + def __str__(self): + return f"{self.voter.last_name} - {self.event_type} on {self.event_date}" diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..2998087 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,75 @@ -
- -