diff --git a/assets/.gitkeep b/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 718ca54..8335e4f 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 index 422a457..dd56483 100644 Binary files a/core/__pycache__/forms.cpython-311.pyc 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 89c49f2..411f104 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 839bd27..1475653 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 f306c0f..645f02c 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 b8aedb6..7ce2f67 100644 --- a/core/admin.py +++ b/core/admin.py @@ -63,10 +63,8 @@ EVENT_MAPPABLE_FIELDS = [ EVENT_PARTICIPATION_MAPPABLE_FIELDS = [ ('voter_id', 'Voter ID'), - ('event_id', 'Event ID'), - ('event_date', 'Event Date'), - ('event_type', 'Event Type (Name)'), - ('participation_status', 'Participation Type'), + ('event_name', 'Event Name'), + ('participation_status', 'Participation Status'), ] DONATION_MAPPABLE_FIELDS = [ @@ -183,6 +181,7 @@ class ParticipationStatusAdmin(admin.ModelAdmin): class InterestAdmin(admin.ModelAdmin): list_display = ('name', 'tenant') list_filter = ('tenant',) + fields = ('tenant', 'donation_goal', 'twilio_account_sid', 'twilio_auth_token', 'twilio_from_number') search_fields = ('name',) class VotingRecordInline(admin.TabularInline): @@ -651,10 +650,10 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin): if end_time and end_time.strip(): defaults['end_time'] = end_time + defaults['date'] = date + defaults['event_type'] = event_type Event.objects.update_or_create( tenant=tenant, - date=date, - event_type=event_type, name=name or '', defaults=defaults ) @@ -725,6 +724,7 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin): class VolunteerAdmin(BaseImportAdminMixin, admin.ModelAdmin): list_display = ('first_name', 'last_name', 'email', 'phone', 'tenant', 'user') list_filter = ('tenant',) + fields = ('tenant', 'donation_goal', 'twilio_account_sid', 'twilio_auth_token', 'twilio_from_number') search_fields = ('first_name', 'last_name', 'email', 'phone') inlines = [VolunteerEventInline, InteractionInline] filter_horizontal = ('interests',) @@ -921,18 +921,14 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin): for row in reader: total_count += 1 voter_id = row.get(mapping.get('voter_id')) - event_id = row.get(mapping.get('event_id')) - event_date = row.get(mapping.get('event_date')) - event_type_name = row.get(mapping.get('event_type')) + event_name = row.get(mapping.get('event_name')) exists = False if voter_id: try: voter = Voter.objects.get(tenant=tenant, voter_id=voter_id) - if event_id: - exists = EventParticipation.objects.filter(voter=voter, event_id=event_id).exists() - elif event_date and event_type_name: - exists = EventParticipation.objects.filter(voter=voter, event__date=event_date, event__event_type__name=event_type_name).exists() + if event_name: + exists = EventParticipation.objects.filter(voter=voter, event__name=event_name).exists() except Voter.DoesNotExist: pass @@ -1004,25 +1000,15 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin): continue event = None - event_id = row.get(mapping.get('event_id')) if mapping.get('event_id') else None - if event_id: + event_name = row.get(mapping.get('event_name')) if mapping.get('event_name') else None + if event_name: try: - event = Event.objects.get(id=event_id, tenant=tenant) + event = Event.objects.get(tenant=tenant, name=event_name) except Event.DoesNotExist: pass - + if not event: - event_date = row.get(mapping.get('event_date')) if mapping.get('event_date') else None - event_type_name = row.get(mapping.get('event_type')) if mapping.get('event_type') else None - if event_date and event_type_name: - try: - event_type = EventType.objects.get(tenant=tenant, name=event_type_name) - event = Event.objects.get(tenant=tenant, date=event_date, event_type=event_type) - except (EventType.DoesNotExist, Event.DoesNotExist): - pass - - if not event: - error_msg = "Event not found (check ID, date, or type)" + error_msg = "Event not found (check Event Name)" logger.error(error_msg) row["Import Error"] = error_msg failed_rows.append(row) @@ -1031,11 +1017,12 @@ class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin): defaults = {} if participation_status_val and participation_status_val.strip(): - if participation_status_val in dict(EventParticipation.PARTICIPATION_TYPE_CHOICES): - defaults['participation_status'] = participation_status_val - else: - defaults['participation_status'] = 'invited' - + status_obj, _ = ParticipationStatus.objects.get_or_create(tenant=tenant, name=participation_status_val.strip()) + defaults['participation_status'] = status_obj + else: + # Default to 'Invited' if not specified + status_obj, _ = ParticipationStatus.objects.get_or_create(tenant=tenant, name='Invited') + defaults['participation_status'] = status_obj EventParticipation.objects.update_or_create( event=event, voter=voter, @@ -1790,5 +1777,6 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin): @admin.register(CampaignSettings) class CampaignSettingsAdmin(admin.ModelAdmin): - list_display = ('tenant', 'donation_goal') + list_display = ('tenant', 'donation_goal', 'twilio_from_number') list_filter = ('tenant',) + fields = ('tenant', 'donation_goal', 'twilio_account_sid', 'twilio_auth_token', 'twilio_from_number') diff --git a/core/forms.py b/core/forms.py index 95ff70e..99e08fd 100644 --- a/core/forms.py +++ b/core/forms.py @@ -86,7 +86,7 @@ class InteractionForm(forms.ModelForm): model = Interaction fields = ['type', 'date', 'description', 'notes'] widgets = { - 'date': forms.DateInput(attrs={'type': 'date'}), + 'date': forms.DateTimeInput(attrs={'type': 'datetime-local'}, format='%Y-%m-%dT%H:%M'), 'notes': forms.Textarea(attrs={'rows': 2}), } @@ -97,6 +97,8 @@ class InteractionForm(forms.ModelForm): for field in self.fields.values(): field.widget.attrs.update({'class': 'form-control'}) self.fields['type'].widget.attrs.update({'class': 'form-select'}) + if self.instance and self.instance.date: + self.initial['date'] = self.instance.date.strftime('%Y-%m-%dT%H:%M') class DonationForm(forms.ModelForm): class Meta: @@ -223,4 +225,4 @@ class VolunteerImportForm(forms.Form): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['tenant'].widget.attrs.update({'class': 'form-control form-select'}) - self.fields['file'].widget.attrs.update({'class': 'form-control'}) \ No newline at end of file + self.fields['file'].widget.attrs.update({'class': 'form-control'}) diff --git a/core/migrations/0024_alter_event_name_alter_event_unique_together.py b/core/migrations/0024_alter_event_name_alter_event_unique_together.py new file mode 100644 index 0000000..44ceb73 --- /dev/null +++ b/core/migrations/0024_alter_event_name_alter_event_unique_together.py @@ -0,0 +1,22 @@ +# Generated by Django 5.2.7 on 2026-01-28 21:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0023_alter_voter_address_street_alter_voter_birthdate_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='event', + name='name', + field=models.CharField(db_index=True, max_length=255), + ), + migrations.AlterUniqueTogether( + name='event', + unique_together={('tenant', 'name')}, + ), + ] diff --git a/core/migrations/0025_campaignsettings_twilio_account_sid_and_more.py b/core/migrations/0025_campaignsettings_twilio_account_sid_and_more.py new file mode 100644 index 0000000..e86d807 --- /dev/null +++ b/core/migrations/0025_campaignsettings_twilio_account_sid_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.7 on 2026-01-29 01:05 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0024_alter_event_name_alter_event_unique_together'), + ] + + operations = [ + migrations.AddField( + model_name='campaignsettings', + name='twilio_account_sid', + field=models.CharField(blank=True, default='ACcd11acb5095cec6477245d385a2bf127', max_length=100), + ), + migrations.AddField( + model_name='campaignsettings', + name='twilio_auth_token', + field=models.CharField(blank=True, default='89ec830d0fa02ab0afa6c76084865713', max_length=100), + ), + migrations.AddField( + model_name='campaignsettings', + name='twilio_from_number', + field=models.CharField(blank=True, default='+18556945903', max_length=20), + ), + ] diff --git a/core/migrations/0026_alter_interaction_date.py b/core/migrations/0026_alter_interaction_date.py new file mode 100644 index 0000000..0edada6 --- /dev/null +++ b/core/migrations/0026_alter_interaction_date.py @@ -0,0 +1,18 @@ +# Generated by Django 5.2.7 on 2026-01-29 03:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0025_campaignsettings_twilio_account_sid_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='interaction', + name='date', + field=models.DateTimeField(), + ), + ] diff --git a/core/migrations/__pycache__/0024_alter_event_name_alter_event_unique_together.cpython-311.pyc b/core/migrations/__pycache__/0024_alter_event_name_alter_event_unique_together.cpython-311.pyc new file mode 100644 index 0000000..d60c0ab Binary files /dev/null and b/core/migrations/__pycache__/0024_alter_event_name_alter_event_unique_together.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0025_campaignsettings_twilio_account_sid_and_more.cpython-311.pyc b/core/migrations/__pycache__/0025_campaignsettings_twilio_account_sid_and_more.cpython-311.pyc new file mode 100644 index 0000000..3599153 Binary files /dev/null and b/core/migrations/__pycache__/0025_campaignsettings_twilio_account_sid_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0026_alter_interaction_date.cpython-311.pyc b/core/migrations/__pycache__/0026_alter_interaction_date.cpython-311.pyc new file mode 100644 index 0000000..7c525bb Binary files /dev/null and b/core/migrations/__pycache__/0026_alter_interaction_date.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index e88c2d4..b9e6253 100644 --- a/core/models.py +++ b/core/models.py @@ -280,13 +280,16 @@ class VotingRecord(models.Model): class Event(models.Model): tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, related_name='events') - name = models.CharField(max_length=255, blank=True) + name = models.CharField(max_length=255, db_index=True) date = models.DateField() start_time = models.TimeField(null=True, blank=True) end_time = models.TimeField(null=True, blank=True) event_type = models.ForeignKey(EventType, on_delete=models.PROTECT, null=True) description = models.TextField(blank=True) + class Meta: + unique_together = ('tenant', 'name') + def __str__(self): if self.name: return f"{self.name} ({self.date})" @@ -339,7 +342,7 @@ class Interaction(models.Model): voter = models.ForeignKey(Voter, on_delete=models.CASCADE, related_name='interactions') volunteer = models.ForeignKey(Volunteer, on_delete=models.SET_NULL, null=True, blank=True, related_name='interactions') type = models.ForeignKey(InteractionType, on_delete=models.SET_NULL, null=True) - date = models.DateField() + date = models.DateTimeField() description = models.CharField(max_length=255) notes = models.TextField(blank=True) @@ -364,10 +367,13 @@ class VoterLikelihood(models.Model): class CampaignSettings(models.Model): tenant = models.OneToOneField(Tenant, on_delete=models.CASCADE, related_name='settings') donation_goal = models.DecimalField(max_digits=12, decimal_places=2, default=170000.00) + twilio_account_sid = models.CharField(max_length=100, blank=True, default='ACcd11acb5095cec6477245d385a2bf127') + twilio_auth_token = models.CharField(max_length=100, blank=True, default='89ec830d0fa02ab0afa6c76084865713') + twilio_from_number = models.CharField(max_length=20, blank=True, default='+18556945903') class Meta: verbose_name = 'Campaign Settings' verbose_name_plural = 'Campaign Settings' def __str__(self): - return f'Settings for {self.tenant.name}' \ No newline at end of file + return f'Settings for {self.tenant.name}' diff --git a/core/templates/core/index.html b/core/templates/core/index.html index 8000f67..e942b7f 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -171,7 +171,7 @@