Autosave: 20260125-175045

This commit is contained in:
Flatlogic Bot 2026-01-25 17:50:45 +00:00
parent c95591245a
commit dc2bd62142
8 changed files with 155 additions and 5 deletions

View File

@ -11,7 +11,8 @@ from django.shortcuts import render, redirect
from django.template.response import TemplateResponse
from .models import (
Tenant, TenantUserRole, InteractionType, DonationMethod, ElectionType, EventType, Voter,
VotingRecord, Event, EventParticipation, Donation, Interaction, VoterLikelihood, CampaignSettings
VotingRecord, Event, EventParticipation, Donation, Interaction, VoterLikelihood, CampaignSettings,
Interest, Volunteer, VolunteerEvent
)
from .forms import (
VoterImportForm, EventImportForm, EventParticipationImportForm,
@ -147,6 +148,12 @@ class EventTypeAdmin(admin.ModelAdmin):
list_filter = ('tenant', 'is_active')
search_fields = ('name',)
@admin.register(Interest)
class InterestAdmin(admin.ModelAdmin):
list_display = ('name', 'tenant')
list_filter = ('tenant',)
search_fields = ('name',)
class VotingRecordInline(admin.TabularInline):
model = VotingRecord
extra = 1
@ -163,6 +170,10 @@ class VoterLikelihoodInline(admin.TabularInline):
model = VoterLikelihood
extra = 1
class VolunteerEventInline(admin.TabularInline):
model = VolunteerEvent
extra = 1
@admin.register(Voter)
class VoterAdmin(BaseImportAdminMixin, admin.ModelAdmin):
list_display = ('first_name', 'last_name', 'nickname', 'voter_id', 'tenant', 'district', 'candidate_support', 'is_targeted', 'city', 'state', 'prior_state')
@ -509,6 +520,19 @@ class EventAdmin(BaseImportAdminMixin, admin.ModelAdmin):
context['opts'] = self.model._meta
return render(request, "admin/import_csv.html", context)
@admin.register(Volunteer)
class VolunteerAdmin(admin.ModelAdmin):
list_display = ('name', 'email', 'phone', 'tenant', 'user')
list_filter = ('tenant',)
search_fields = ('name', 'email', 'phone')
inlines = [VolunteerEventInline, InteractionInline]
filter_horizontal = ('interests',)
@admin.register(VolunteerEvent)
class VolunteerEventAdmin(admin.ModelAdmin):
list_display = ('volunteer', 'event', 'role')
list_filter = ('event__tenant', 'event', 'role')
@admin.register(EventParticipation)
class EventParticipationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
list_display = ('voter', 'event', 'participation_type')
@ -912,9 +936,9 @@ class DonationAdmin(BaseImportAdminMixin, admin.ModelAdmin):
@admin.register(Interaction)
class InteractionAdmin(BaseImportAdminMixin, admin.ModelAdmin):
list_display = ('id', 'voter', 'type', 'date', 'description')
list_filter = ('voter__tenant', 'type', 'date')
search_fields = ('voter__first_name', 'voter__last_name', 'voter__voter_id', 'description')
list_display = ('id', 'voter', 'volunteer', 'type', 'date', 'description')
list_filter = ('voter__tenant', 'type', 'date', 'volunteer')
search_fields = ('voter__first_name', 'voter__last_name', 'voter__voter_id', 'description', 'volunteer__name')
change_list_template = "admin/interaction_change_list.html"
def get_urls(self):
@ -1308,4 +1332,4 @@ class VoterLikelihoodAdmin(BaseImportAdminMixin, admin.ModelAdmin):
@admin.register(CampaignSettings)
class CampaignSettingsAdmin(admin.ModelAdmin):
list_display = ('tenant', 'donation_goal')
list_filter = ('tenant',)
list_filter = ('tenant',)

View File

@ -0,0 +1,71 @@
# Generated by Django 5.2.7 on 2026-01-25 16:33
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0012_voter_prior_state_alter_voter_state'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.RemoveField(
model_name='tenant',
name='description',
),
migrations.RemoveField(
model_name='tenant',
name='slug',
),
migrations.AlterField(
model_name='tenant',
name='name',
field=models.CharField(max_length=100),
),
migrations.AlterField(
model_name='tenantuserrole',
name='role',
field=models.CharField(choices=[('admin', 'Admin'), ('campaign_manager', 'Campaign Manager'), ('campaign_staff', 'Campaign Staff')], max_length=20),
),
migrations.CreateModel(
name='Interest',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100)),
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interests', to='core.tenant')),
],
options={
'unique_together': {('tenant', 'name')},
},
),
migrations.CreateModel(
name='Volunteer',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('email', models.EmailField(max_length=254)),
('phone', models.CharField(blank=True, max_length=20)),
('interests', models.ManyToManyField(blank=True, related_name='volunteers', to='core.interest')),
('tenant', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='volunteers', to='core.tenant')),
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='volunteer_profile', to=settings.AUTH_USER_MODEL)),
],
),
migrations.AddField(
model_name='interaction',
name='volunteer',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='interactions', to='core.volunteer'),
),
migrations.CreateModel(
name='VolunteerEvent',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('role', models.CharField(max_length=100)),
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='volunteers', to='core.event')),
('volunteer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='event_assignments', to='core.volunteer')),
],
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 5.2.7 on 2026-01-25 16:34
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0013_remove_tenant_description_remove_tenant_slug_and_more'),
]
operations = [
migrations.AddField(
model_name='volunteer',
name='assigned_events',
field=models.ManyToManyField(related_name='assigned_volunteers', through='core.VolunteerEvent', to='core.event'),
),
migrations.AlterField(
model_name='volunteerevent',
name='event',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='volunteer_assignments', to='core.event'),
),
]

View File

@ -76,6 +76,16 @@ class EventType(models.Model):
def __str__(self):
return self.name
class Interest(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, related_name='interests')
name = models.CharField(max_length=100)
class Meta:
unique_together = ('tenant', 'name')
def __str__(self):
return self.name
class Voter(models.Model):
SUPPORT_CHOICES = [
('unknown', 'Unknown'),
@ -240,6 +250,26 @@ class Event(models.Model):
def __str__(self):
return f"{self.event_type} on {self.date}"
class Volunteer(models.Model):
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE, related_name='volunteers')
user = models.OneToOneField(User, on_delete=models.SET_NULL, null=True, blank=True, related_name='volunteer_profile')
name = models.CharField(max_length=255)
email = models.EmailField()
phone = models.CharField(max_length=20, blank=True)
interests = models.ManyToManyField(Interest, blank=True, related_name='volunteers')
assigned_events = models.ManyToManyField(Event, through='VolunteerEvent', related_name='assigned_volunteers')
def __str__(self):
return self.name
class VolunteerEvent(models.Model):
volunteer = models.ForeignKey(Volunteer, on_delete=models.CASCADE, related_name='event_assignments')
event = models.ForeignKey(Event, on_delete=models.CASCADE, related_name='volunteer_assignments')
role = models.CharField(max_length=100)
def __str__(self):
return f"{self.volunteer} at {self.event} as {self.role}"
class EventParticipation(models.Model):
PARTICIPATION_TYPE_CHOICES = [
("invited", "Invited"),
@ -265,6 +295,7 @@ class Donation(models.Model):
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()
description = models.CharField(max_length=255)