Skip to content

Commit 9581f6d

Browse files
committed
introduce Fellow model that is more extensible
1 parent 8f96d75 commit 9581f6d

4 files changed

Lines changed: 93 additions & 13 deletions

File tree

nominations/admin.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from nominations.models import (
66
Election,
7+
Fellow,
78
FellowNomination,
89
FellowNominationRound,
910
FellowNominationVote,
@@ -38,6 +39,14 @@ def get_ordering(self, request):
3839
return ['election', Lower('nominee__user__last_name')]
3940

4041

42+
@admin.register(Fellow)
43+
class FellowAdmin(admin.ModelAdmin):
44+
list_display = ("name", "year_elected", "status", "emeritus_year")
45+
list_filter = ("status", "year_elected")
46+
search_fields = ("name",)
47+
raw_id_fields = ("user",)
48+
49+
4150
@admin.register(FellowNominationRound)
4251
class FellowNominationRoundAdmin(admin.ModelAdmin):
4352
list_display = ("__str__", "quarter_start", "quarter_end", "nominations_cutoff", "is_open")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Generated by Django 4.2.28 on 2026-02-06 03:34
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
('nominations', '0003_fellow_nominations'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='Fellow',
18+
fields=[
19+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
20+
('name', models.CharField(max_length=255)),
21+
('year_elected', models.PositiveIntegerField()),
22+
('status', models.CharField(choices=[('active', 'Active'), ('emeritus', 'Emeritus'), ('deceased', 'Deceased')], db_index=True, default='active', max_length=10)),
23+
('emeritus_year', models.PositiveIntegerField(blank=True, null=True)),
24+
('notes', models.TextField(blank=True)),
25+
('user', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='fellow', to=settings.AUTH_USER_MODEL)),
26+
],
27+
options={
28+
'ordering': ['name'],
29+
},
30+
),
31+
]

nominations/models.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import datetime
22

33
from django.conf import settings
4+
from django.core.exceptions import ObjectDoesNotExist
45
from django.db import models
56
from django.db.models.signals import post_save
67
from django.dispatch import receiver
@@ -12,7 +13,7 @@
1213
from markupfield.fields import MarkupField
1314

1415
from cms.models import ContentManageable
15-
from users.models import Membership, User
16+
from users.models import User
1617

1718
from .managers import FellowNominationQuerySet
1819

@@ -278,6 +279,43 @@ def purge_nomination_pages(sender, instance, created, **kwargs):
278279
)
279280

280281

282+
class Fellow(models.Model):
283+
"""A PSF Fellow — reference data managed via Django admin."""
284+
285+
ACTIVE = "active"
286+
EMERITUS = "emeritus"
287+
DECEASED = "deceased"
288+
STATUS_CHOICES = (
289+
(ACTIVE, "Active"),
290+
(EMERITUS, "Emeritus"),
291+
(DECEASED, "Deceased"),
292+
)
293+
294+
name = models.CharField(max_length=255)
295+
year_elected = models.PositiveIntegerField()
296+
status = models.CharField(
297+
max_length=10,
298+
choices=STATUS_CHOICES,
299+
default=ACTIVE,
300+
db_index=True,
301+
)
302+
emeritus_year = models.PositiveIntegerField(null=True, blank=True)
303+
notes = models.TextField(blank=True)
304+
user = models.OneToOneField(
305+
User,
306+
on_delete=models.SET_NULL,
307+
null=True,
308+
blank=True,
309+
related_name="fellow",
310+
)
311+
312+
class Meta:
313+
ordering = ["name"]
314+
315+
def __str__(self):
316+
return self.name
317+
318+
281319
class FellowNominationRound(models.Model):
282320
"""Quarterly round for PSF Fellow Work Group consideration."""
283321

@@ -413,8 +451,8 @@ def is_active(self):
413451
def nominee_is_already_fellow(self):
414452
if self.nominee_user:
415453
try:
416-
return self.nominee_user.membership.membership_type == Membership.FELLOW
417-
except Membership.DoesNotExist:
454+
return self.nominee_user.fellow is not None
455+
except Fellow.DoesNotExist:
418456
return False
419457
return False
420458

nominations/views.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,6 @@
1717

1818
from pydotorg.mixins import GroupRequiredMixin, LoginRequiredMixin
1919

20-
from users.models import Membership
21-
2220
from .forms import (
2321
FellowNominationForm,
2422
FellowNominationManageForm,
@@ -31,6 +29,7 @@
3129
)
3230
from .models import (
3331
Election,
32+
Fellow,
3433
FellowNomination,
3534
FellowNominationRound,
3635
FellowNominationVote,
@@ -294,14 +293,14 @@ def form_valid(self, form):
294293
form.instance.nominee_user = nominee_user
295294
# Check if nominee is already a Fellow
296295
try:
297-
if nominee_user.membership.membership_type == Membership.FELLOW:
296+
if nominee_user.fellow is not None:
298297
form.instance.nominee_is_fellow_at_submission = True
299298
messages.warning(
300299
self.request,
301300
f"{form.cleaned_data['nominee_name']} is already a PSF Fellow. "
302301
"The nomination has been saved but may not need further action.",
303302
)
304-
except Membership.DoesNotExist:
303+
except Fellow.DoesNotExist:
305304
pass
306305
except User.DoesNotExist:
307306
pass
@@ -638,13 +637,16 @@ class FellowsRoster(ListView):
638637
context_object_name = "fellows"
639638

640639
def get_queryset(self):
641-
return Membership.objects.filter(
642-
membership_type=Membership.FELLOW,
643-
).select_related("creator").order_by(
644-
"creator__last_name", "creator__first_name"
645-
)
640+
return Fellow.objects.all()
646641

647642
def get_context_data(self, **kwargs):
648643
context = super().get_context_data(**kwargs)
649-
context["total_count"] = context["fellows"].count()
644+
qs = context["fellows"]
645+
context["active_fellows"] = qs.filter(status=Fellow.ACTIVE)
646+
context["emeritus_fellows"] = qs.filter(status=Fellow.EMERITUS)
647+
context["deceased_fellows"] = qs.filter(status=Fellow.DECEASED)
648+
context["active_count"] = context["active_fellows"].count()
649+
context["emeritus_count"] = context["emeritus_fellows"].count()
650+
context["deceased_count"] = context["deceased_fellows"].count()
651+
context["total_count"] = qs.count()
650652
return context

0 commit comments

Comments
 (0)