diff --git a/backports/__init__.py b/backports/__init__.py new file mode 100644 index 0000000..a5cc44e --- /dev/null +++ b/backports/__init__.py @@ -0,0 +1,6 @@ +""" +These files are backported functionality from future versions of Django. + +- models.db.enums - copied from Django 3.0.3 + See https://github.com/django/django/blob/3.0.3/django/db/models/enums.py +""" diff --git a/backports/db/__init__.py b/backports/db/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backports/db/models/__init__.py b/backports/db/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backports/db/models/enums.py b/backports/db/models/enums.py new file mode 100644 index 0000000..c76e486 --- /dev/null +++ b/backports/db/models/enums.py @@ -0,0 +1,82 @@ +import enum + +from django.utils.functional import Promise + +__all__ = ['Choices', 'IntegerChoices', 'TextChoices'] + + +class ChoicesMeta(enum.EnumMeta): + """A metaclass for creating a enum choices.""" + + def __new__(metacls, classname, bases, classdict): + labels = [] + for key in classdict._member_names: + value = classdict[key] + if ( + isinstance(value, (list, tuple)) and + len(value) > 1 and + isinstance(value[-1], (Promise, str)) + ): + *value, label = value + value = tuple(value) + else: + label = key.replace('_', ' ').title() + labels.append(label) + # Use dict.__setitem__() to suppress defenses against double + # assignment in enum's classdict. + dict.__setitem__(classdict, key, value) + cls = super().__new__(metacls, classname, bases, classdict) + cls._value2label_map_ = dict(zip(cls._value2member_map_, labels)) + # Add a label property to instances of enum which uses the enum member + # that is passed in as "self" as the value to use when looking up the + # label in the choices. + cls.label = property(lambda self: cls._value2label_map_.get(self.value)) + cls.do_not_call_in_templates = True + return enum.unique(cls) + + def __contains__(cls, member): + if not isinstance(member, enum.Enum): + # Allow non-enums to match against member values. + return member in {x.value for x in cls} + return super().__contains__(member) + + @property + def names(cls): + empty = ['__empty__'] if hasattr(cls, '__empty__') else [] + return empty + [member.name for member in cls] + + @property + def choices(cls): + empty = [(None, cls.__empty__)] if hasattr(cls, '__empty__') else [] + return empty + [(member.value, member.label) for member in cls] + + @property + def labels(cls): + return [label for _, label in cls.choices] + + @property + def values(cls): + return [value for value, _ in cls.choices] + + +class Choices(enum.Enum, metaclass=ChoicesMeta): + """Class for creating enumerated choices.""" + + def __str__(self): + """ + Use value when cast to str, so that Choices set as model instance + attributes are rendered as expected in templates and similar contexts. + """ + return str(self.value) + + +class IntegerChoices(int, Choices): + """Class for creating enumerated integer choices.""" + pass + + +class TextChoices(str, Choices): + """Class for creating enumerated string choices.""" + + def _generate_next_value_(name, start, count, last_values): + return name diff --git a/people/forms.py b/people/forms.py index e6c4fe8..27b73dc 100644 --- a/people/forms.py +++ b/people/forms.py @@ -12,11 +12,11 @@ class PersonForm(forms.ModelForm): """ class Meta: model = models.Person - fields = [ - 'name', - 'core_member', + exclude = [ + 'user', + 'relationship_targets', ] - + class RelationshipForm(forms.ModelForm): """ diff --git a/people/migrations/0009_add_first_person_fields.py b/people/migrations/0009_add_first_person_fields.py new file mode 100644 index 0000000..2f9db55 --- /dev/null +++ b/people/migrations/0009_add_first_person_fields.py @@ -0,0 +1,23 @@ +# Generated by Django 2.2.10 on 2020-02-21 16:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('people', '0008_order_answer_by_question'), + ] + + operations = [ + migrations.AddField( + model_name='person', + name='age_group', + field=models.CharField(blank=True, choices=[('<=25', '25 or under'), ('26-30', '26-30'), ('31-35', '31-35'), ('36-40', '36-40'), ('41-45', '41-45'), ('46-50', '46-50'), ('51-55', '51-55'), ('56-60', '56-60'), ('>=61', '61 or older'), ('N', 'Prefer not to say')], max_length=5), + ), + migrations.AddField( + model_name='person', + name='gender', + field=models.CharField(blank=True, choices=[('M', 'Male'), ('F', 'Female'), ('O', 'Other'), ('N', 'Prefer not to say')], max_length=1), + ), + ] diff --git a/people/models.py b/people/models.py index fc3af49..14b6605 100644 --- a/people/models.py +++ b/people/models.py @@ -4,6 +4,9 @@ from django.conf import settings from django.contrib.auth.models import AbstractUser from django.db import models from django.urls import reverse +from django.utils.translation import gettext_lazy as _ + +from backports.db.models.enums import TextChoices class User(AbstractUser): @@ -38,7 +41,36 @@ class Person(models.Model): through='Relationship', through_fields=('source', 'target'), symmetrical=False) + + ############################################################### + # Data collected for analysis of community makeup and structure + class GenderChoices(TextChoices): + MALE = 'M', _('Male') + FEMALE = 'F', _('Female') + OTHER = 'O', _('Other') + PREFER_NOT_TO_SAY = 'N', _('Prefer not to say') + + gender = models.CharField(max_length=1, + choices=GenderChoices.choices, + blank=True, null=False) + + class AgeGroupChoices(TextChoices): + LTE_25 = '<=25', _('25 or under') + BETWEEN_26_30 = '26-30', _('26-30') + BETWEEN_31_35 = '31-35', _('31-35') + BETWEEN_36_40 = '36-40', _('36-40') + BETWEEN_41_45 = '41-45', _('41-45') + BETWEEN_46_50 = '46-50', _('46-50') + BETWEEN_51_55 = '51-55', _('51-55') + BETWEEN_56_60 = '56-60', _('56-60') + GTE_61 = '>=61', _('61 or older') + PREFER_NOT_TO_SAY = 'N', _('Prefer not to say') + + age_group = models.CharField(max_length=5, + choices=AgeGroupChoices.choices, + blank=True, null=False) + @property def relationships(self): return self.relationships_as_source.all().union( diff --git a/people/templates/people/person/detail.html b/people/templates/people/person/detail.html index f12ccdf..c9112b7 100644 --- a/people/templates/people/person/detail.html +++ b/people/templates/people/person/detail.html @@ -10,6 +10,9 @@ + Update +