refactor: extract code share between question sets

This commit is contained in:
James Graham
2020-12-07 14:19:00 +00:00
parent 6bb4f09454
commit e045b084d0
3 changed files with 128 additions and 171 deletions

View File

@@ -1,18 +1,18 @@
import logging
import typing
from django.conf import settings
from django.contrib.auth.models import AbstractUser
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
from django.utils.translation import gettext_lazy as _
from django_countries.fields import CountryField
from django_settings_export import settings_export
from post_office import mail
from .question import AnswerSet, Question, QuestionChoice
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
__all__ = [
@@ -83,77 +83,18 @@ class Theme(models.Model):
return self.name
class PersonQuestion(models.Model):
class PersonQuestion(Question):
"""Question which may be asked about a person."""
class Meta:
ordering = [
'order',
'text',
]
#: Version number of this question - to allow modification without invalidating existing data
version = models.PositiveSmallIntegerField(default=1,
blank=False, null=False)
#: Text of question
text = models.CharField(max_length=255,
blank=False, null=False)
#: Position of this question in the list
order = models.SmallIntegerField(default=0,
blank=False, null=False)
@property
def choices(self) -> typing.List[typing.List[str]]:
"""
Convert the :class:`PersonQuestionChoice`s for this question into Django choices.
"""
return [
[choice.pk, str(choice)] for choice in self.answers.all()
]
@property
def slug(self) -> str:
return slugify(self.text)
def __str__(self) -> str:
return self.text
class PersonQuestionChoice(models.Model):
"""
Allowed answer to a :class:`PersonQuestion`.
"""
class Meta:
constraints = [
models.UniqueConstraint(fields=['question', 'text'],
name='unique_question_answer')
]
ordering = [
'question__order',
'order',
'text',
]
class PersonQuestionChoice(QuestionChoice):
"""Allowed answer to a :class:`PersonQuestion`."""
#: Question to which this answer belongs
question = models.ForeignKey(PersonQuestion, related_name='answers',
question = models.ForeignKey(PersonQuestion,
related_name='answers',
on_delete=models.CASCADE,
blank=False, null=False)
#: Text of answer
text = models.CharField(max_length=255,
blank=False, null=False)
#: Position of this answer in the list
order = models.SmallIntegerField(default=0,
blank=False, null=False)
@property
def slug(self) -> str:
return slugify(self.text)
def __str__(self) -> str:
return self.text
blank=False,
null=False)
class Person(models.Model):
@@ -197,13 +138,8 @@ class Person(models.Model):
return self.name
class PersonAnswerSet(models.Model):
class PersonAnswerSet(AnswerSet):
"""The answers to the person questions at a particular point in time."""
class Meta:
ordering = [
'timestamp',
]
#: Person to which this answer set belongs
person = models.ForeignKey(Person,
on_delete=models.CASCADE,
@@ -214,13 +150,6 @@ class PersonAnswerSet(models.Model):
#: Answers to :class:`PersonQuestion`s
question_answers = models.ManyToManyField(PersonQuestionChoice)
#: When were these answers collected?
timestamp = models.DateTimeField(auto_now_add=True, editable=False)
replaced_timestamp = models.DateTimeField(blank=True,
null=True,
editable=False)
##################
# Static questions

103
people/models/question.py Normal file
View File

@@ -0,0 +1,103 @@
"""Base models for configurable questions and response sets."""
import typing
from django.db import models
from django.utils.text import slugify
class Question(models.Model):
"""Questions from which a survey form can be created."""
class Meta:
abstract = True
ordering = [
'order',
'text',
]
#: Version number of this question - to allow modification without invalidating existing data
version = models.PositiveSmallIntegerField(default=1,
blank=False,
null=False)
#: Text of question
text = models.CharField(max_length=255, blank=False, null=False)
#: Position of this question in the list
order = models.SmallIntegerField(default=0, blank=False, null=False)
@property
def choices(self) -> typing.List[typing.List[str]]:
"""Convert the :class:`QuestionChoice`s for this question into Django choices."""
return [[choice.pk, str(choice)] for choice in self.answers.all()]
@property
def slug(self) -> str:
return slugify(self.text)
def __str__(self) -> str:
return self.text
class QuestionChoice(models.Model):
"""Allowed answer to a :class:`Question`."""
class Meta:
abstract = True
constraints = [
models.UniqueConstraint(fields=['question', 'text'],
name='unique_question_answer')
]
ordering = [
'question__order',
'order',
'text',
]
#: Question to which this answer belongs
#: This foreign key must be added to each concrete subclass
# question = models.ForeignKey(Question,
# related_name='answers',
# on_delete=models.CASCADE,
# blank=False,
# null=False)
#: Text of answer
text = models.CharField(max_length=255, blank=False, null=False)
#: Position of this answer in the list
order = models.SmallIntegerField(default=0, blank=False, null=False)
@property
def slug(self) -> str:
return slugify(self.text)
def __str__(self) -> str:
return self.text
class AnswerSet(models.Model):
"""The answers to a set of questions at a particular point in time."""
class Meta:
abstract = True
ordering = [
'timestamp',
]
#: Entity to which this answer set belongs
#: This foreign key must be added to each concrete subclass
# person = models.ForeignKey(Person,
# on_delete=models.CASCADE,
# related_name='answer_sets',
# blank=False,
# null=False)
#: Answers to :class:`Question`s
#: This many to many relation must be added to each concrete subclass
# question_answers = models.ManyToManyField(QuestionChoice)
#: When were these answers collected?
timestamp = models.DateTimeField(auto_now_add=True, editable=False)
#: When were these answers replaced? - happens when another set is collected
replaced_timestamp = models.DateTimeField(blank=True,
null=True,
editable=False)

View File

@@ -2,14 +2,12 @@
Models describing relationships between people.
"""
import typing
from django.core.exceptions import ObjectDoesNotExist
from django.db import models
from django.urls import reverse
from django.utils.text import slugify
from .person import Person
from .question import AnswerSet, Question, QuestionChoice
__all__ = [
'RelationshipQuestion',
@@ -19,79 +17,19 @@ __all__ = [
]
class RelationshipQuestion(models.Model):
"""
Question which may be asked about a relationship.
"""
class Meta:
ordering = [
'order',
'text',
]
#: Version number of this question - to allow modification without invalidating existing data
version = models.PositiveSmallIntegerField(default=1,
blank=False, null=False)
#: Text of question
text = models.CharField(max_length=255,
blank=False, null=False)
#: Position of this question in the list
order = models.SmallIntegerField(default=0,
blank=False, null=False)
@property
def choices(self) -> typing.List[typing.List[str]]:
"""
Convert the :class:`RelationshipQuestionChoice`s for this question into Django choices.
"""
return [
[choice.pk, str(choice)] for choice in self.answers.all()
]
@property
def slug(self) -> str:
return slugify(self.text)
def __str__(self) -> str:
return self.text
class RelationshipQuestion(Question):
"""Question which may be asked about a relationship."""
class RelationshipQuestionChoice(models.Model):
"""
Allowed answer to a :class:`RelationshipQuestion`.
"""
class Meta:
constraints = [
models.UniqueConstraint(fields=['question', 'text'],
name='unique_question_answer')
]
ordering = [
'question__order',
'order',
'text',
]
class RelationshipQuestionChoice(QuestionChoice):
"""Allowed answer to a :class:`RelationshipQuestion`."""
#: Question to which this answer belongs
question = models.ForeignKey(RelationshipQuestion, related_name='answers',
question = models.ForeignKey(RelationshipQuestion,
related_name='answers',
on_delete=models.CASCADE,
blank=False, null=False)
#: Text of answer
text = models.CharField(max_length=255,
blank=False, null=False)
#: Position of this answer in the list
order = models.SmallIntegerField(default=0,
blank=False, null=False)
@property
def slug(self) -> str:
return slugify(self.text)
def __str__(self) -> str:
return self.text
blank=False,
null=False)
# class ExternalPerson(models.Model):
@@ -129,8 +67,8 @@ class Relationship(models.Model):
on_delete=models.CASCADE,
blank=False,
null=False)
# blank=True,
# null=True)
# blank=True,
# null=True)
# target_external_person = models.ForeignKey(
# ExternalPerson,
@@ -173,31 +111,18 @@ class Relationship(models.Model):
target_person=self.source)
class RelationshipAnswerSet(models.Model):
"""
The answers to the relationship questions at a particular point in time.
"""
class Meta:
ordering = [
'timestamp',
]
class RelationshipAnswerSet(AnswerSet):
"""The answers to the relationship questions at a particular point in time."""
#: Relationship to which this answer set belongs
relationship = models.ForeignKey(Relationship,
on_delete=models.CASCADE,
related_name='answer_sets',
blank=False, null=False)
blank=False,
null=False)
#: Answers to :class:`RelationshipQuestion`s
question_answers = models.ManyToManyField(RelationshipQuestionChoice)
#: When were these answers collected?
timestamp = models.DateTimeField(auto_now_add=True,
editable=False)
replaced_timestamp = models.DateTimeField(blank=True, null=True,
editable=False)
def get_absolute_url(self):
return self.relationship.get_absolute_url()