From bf2422c6dde8b15443badb7d9508c3c0557dbf6c Mon Sep 17 00:00:00 2001 From: James Graham Date: Thu, 4 Mar 2021 12:42:32 +0000 Subject: [PATCH] refactor: move organisation models to own module --- people/models/__init__.py | 1 + people/models/organisation.py | 121 ++++++++++++++++++++++++++++++++++ people/models/person.py | 110 +------------------------------ 3 files changed, 123 insertions(+), 109 deletions(-) create mode 100644 people/models/organisation.py diff --git a/people/models/__init__.py b/people/models/__init__.py index f3a1059..8eedcc1 100644 --- a/people/models/__init__.py +++ b/people/models/__init__.py @@ -1,3 +1,4 @@ +from .organisation import * from .person import * from .question import * from .relationship import * diff --git a/people/models/organisation.py b/people/models/organisation.py new file mode 100644 index 0000000..74cdc57 --- /dev/null +++ b/people/models/organisation.py @@ -0,0 +1,121 @@ +import logging + +from django.db import models +from django.urls import reverse + + +from .question import AnswerSet, Question, QuestionChoice + +logger = logging.getLogger(__name__) # pylint: disable=invalid-name + +__all__ = [ + 'OrganisationQuestion', + 'OrganisationQuestionChoice', + 'Organisation', + 'OrganisationAnswerSet', +] + + +class OrganisationQuestion(Question): + """Question which may be asked about a Organisation.""" + #: Should answers to this question be displayed on public profiles? + answer_is_public = models.BooleanField( + help_text='Should answers to this question be displayed on profiles?', + default=True, + blank=False, + null=False) + + +class OrganisationQuestionChoice(QuestionChoice): + """Allowed answer to a :class:`OrganisationQuestion`.""" + #: Question to which this answer belongs + question = models.ForeignKey(OrganisationQuestion, + related_name='answers', + on_delete=models.CASCADE, + blank=False, + null=False) + + +class Organisation(models.Model): + """Organisation to which a :class:`Person` belongs.""" + name = models.CharField(max_length=255, blank=False, null=False) + + def __str__(self) -> str: + return self.name + + @property + def current_answers(self) -> 'OrganisationAnswerSet': + return self.answer_sets.last() + + def get_absolute_url(self): + return reverse('people:organisation.detail', kwargs={'pk': self.pk}) + + +class OrganisationAnswerSet(AnswerSet): + """The answers to the organisation questions at a particular point in time.""" + #: Organisation to which this answer set belongs + organisation = models.ForeignKey(Organisation, + on_delete=models.CASCADE, + related_name='answer_sets', + blank=False, + null=False) + + #: Latitude for displaying location on a map + latitude = models.FloatField(blank=True, null=True) + + #: Longitude for displaying location on a map + longitude = models.FloatField(blank=True, null=True) + + #: Answers to :class:`OrganisationQuestion`s + question_answers = models.ManyToManyField(OrganisationQuestionChoice) + + def public_answers(self) -> models.QuerySet: + """Get answers to questions which are public.""" + return self.question_answers.filter(question__answer_is_public=True) + + def as_dict(self): + """Get the answers from this set as a dictionary for use in Form.initial.""" + exclude_fields = { + 'id', + 'timestamp', + 'replaced_timestamp', + 'organisation_id', + 'question_answers', + } + + def field_value_repr(field): + """Get the representation of a field's value as required by Form.initial.""" + attr_val = getattr(self, field.attname) + + # Relation fields need to return PKs + if isinstance(field, models.ManyToManyField): + return [obj.pk for obj in attr_val.all()] + + # But foreign key fields are a PK already so no extra work + + return attr_val + + answers = { + # Foreign key fields have _id at end in model _meta but don't in forms + field.attname.rstrip('_id'): field_value_repr(field) + for field in self._meta.get_fields() + if field.attname not in exclude_fields + } + + for answer in self.question_answers.all(): + question = answer.question + field_name = f'question_{question.pk}' + + if question.is_multiple_choice: + if field_name not in answers: + answers[field_name] = [] + + answers[field_name].append(answer.pk) + + else: + answers[field_name] = answer.pk + + return answers + + def get_absolute_url(self): + return self.organisation.get_absolute_url() diff --git a/people/models/person.py b/people/models/person.py index 4143a65..8dfad5c 100644 --- a/people/models/person.py +++ b/people/models/person.py @@ -11,16 +11,13 @@ from django_countries.fields import CountryField from django_settings_export import settings_export from post_office import mail +from .organisation import Organisation from .question import AnswerSet, Question, QuestionChoice logger = logging.getLogger(__name__) # pylint: disable=invalid-name __all__ = [ 'User', - 'OrganisationQuestion', - 'OrganisationQuestionChoice', - 'Organisation', - 'OrganisationAnswerSet', 'Theme', 'PersonQuestion', 'PersonQuestionChoice', @@ -69,111 +66,6 @@ class User(AbstractUser): self.username) -class OrganisationQuestion(Question): - """Question which may be asked about a Organisation.""" - #: Should answers to this question be displayed on public profiles? - answer_is_public = models.BooleanField( - help_text='Should answers to this question be displayed on profiles?', - default=True, - blank=False, - null=False) - - -class OrganisationQuestionChoice(QuestionChoice): - """Allowed answer to a :class:`OrganisationQuestion`.""" - #: Question to which this answer belongs - question = models.ForeignKey(OrganisationQuestion, - related_name='answers', - on_delete=models.CASCADE, - blank=False, - null=False) - - -class Organisation(models.Model): - """Organisation to which a :class:`Person` belongs.""" - name = models.CharField(max_length=255, blank=False, null=False) - - def __str__(self) -> str: - return self.name - - @property - def current_answers(self) -> 'OrganisationAnswerSet': - return self.answer_sets.last() - - def get_absolute_url(self): - return reverse('people:organisation.detail', kwargs={'pk': self.pk}) - - -class OrganisationAnswerSet(AnswerSet): - """The answers to the organisation questions at a particular point in time.""" - #: Organisation to which this answer set belongs - organisation = models.ForeignKey(Organisation, - on_delete=models.CASCADE, - related_name='answer_sets', - blank=False, - null=False) - - #: Latitude for displaying location on a map - latitude = models.FloatField(blank=True, null=True) - - #: Longitude for displaying location on a map - longitude = models.FloatField(blank=True, null=True) - - #: Answers to :class:`OrganisationQuestion`s - question_answers = models.ManyToManyField(OrganisationQuestionChoice) - - def public_answers(self) -> models.QuerySet: - """Get answers to questions which are public.""" - return self.question_answers.filter(question__answer_is_public=True) - - def as_dict(self): - """Get the answers from this set as a dictionary for use in Form.initial.""" - exclude_fields = { - 'id', - 'timestamp', - 'replaced_timestamp', - 'organisation_id', - 'question_answers', - } - - def field_value_repr(field): - """Get the representation of a field's value as required by Form.initial.""" - attr_val = getattr(self, field.attname) - - # Relation fields need to return PKs - if isinstance(field, models.ManyToManyField): - return [obj.pk for obj in attr_val.all()] - - # But foreign key fields are a PK already so no extra work - - return attr_val - - answers = { - # Foreign key fields have _id at end in model _meta but don't in forms - field.attname.rstrip('_id'): field_value_repr(field) - for field in self._meta.get_fields() - if field.attname not in exclude_fields - } - - for answer in self.question_answers.all(): - question = answer.question - field_name = f'question_{question.pk}' - - if question.is_multiple_choice: - if field_name not in answers: - answers[field_name] = [] - - answers[field_name].append(answer.pk) - - else: - answers[field_name] = answer.pk - - return answers - - def get_absolute_url(self): - return self.organisation.get_absolute_url() - - class Theme(models.Model): """ Project theme within which a :class:`Person` works.