Files
breccia-mapper/people/views/network.py

158 lines
5.5 KiB
Python

"""
Views for displaying networks of :class:`People` and :class:`Relationship`s.
"""
import logging
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q, QuerySet
from django.forms import ValidationError
from django.utils import timezone
from django.views.generic import TemplateView
from people import forms, models, serializers
from breccia_mapper.views import UserIsStaffMixin
logger = logging.getLogger(__name__) # pylint: disable=invalid-name
def filter_by_form_answers(queryset: QuerySet, answerset_queryset: QuerySet, relationship_key: str):
"""Build a filter to select based on form responses."""
def inner(form, at_date=None):
# Filter on timestamp__date doesn't seem to work on MySQL
# To compare datetimes we need at_date to be midnight at
# the *end* of the day in question - so add one day
if not at_date:
at_date = timezone.now().date()
at_date += timezone.timedelta(days=1)
# Filter to answersets valid at required time
answerset_set = answerset_queryset.prefetch_related('question_answers').filter(
Q(replaced_timestamp__gte=at_date)
| Q(replaced_timestamp__isnull=True),
timestamp__lte=at_date
)
# Filter to answersets containing required answers
for field, values in form.cleaned_data.items():
if field.startswith(f'{form.question_prefix}question_') and values:
answerset_set = answerset_set.filter(question_answers__in=values)
return queryset.filter(pk__in=answerset_set.values_list(relationship_key, flat=True))
return inner
filter_relationships = filter_by_form_answers(
models.Relationship.objects.prefetch_related('source', 'target'),
models.RelationshipAnswerSet.objects, 'relationship'
)
filter_organisations = filter_by_form_answers(
models.Organisation.objects, models.OrganisationAnswerSet.objects, 'organisation'
)
filter_people = filter_by_form_answers(
models.Person.objects, models.PersonAnswerSet.objects, 'person'
)
class NetworkView(UserIsStaffMixin, LoginRequiredMixin, TemplateView):
"""View to display relationship network."""
template_name = 'people/network.html'
def post(self, request, *args, **kwargs):
all_forms = self.get_forms()
if all(map(lambda f: f.is_valid(), all_forms.values())):
return self.forms_valid(all_forms)
return self.forms_invalid(all_forms)
def get_forms(self):
form_kwargs = self.get_form_kwargs()
return {
'relationship': forms.NetworkRelationshipFilterForm(**form_kwargs),
'person': forms.NetworkPersonFilterForm(**form_kwargs),
'organisation': forms.NetworkOrganisationFilterForm(**form_kwargs),
'date': forms.DateForm(**form_kwargs),
}
def get_form_kwargs(self):
"""Add GET params to form data."""
kwargs = {}
if self.request.method == 'GET':
kwargs['data'] = self.request.GET
if self.request.method in ('POST', 'PUT'):
kwargs['data'] = self.request.POST
return kwargs
def get_context_data(self, **kwargs):
"""Add filtered QuerySets of :class:`Person` and :class:`Relationship` to the context."""
context = super().get_context_data(**kwargs)
context['full_width_page'] = True
all_forms = self.get_forms()
context['relationship_form'] = all_forms['relationship']
context['person_form'] = all_forms['person']
context['organisation_form'] = all_forms['organisation']
context['date_form'] = all_forms['date']
if not all(map(lambda f: f.is_valid(), all_forms.values())):
return context
date = all_forms['date'].cleaned_data['date']
context['person_set'] = serializers.PersonSerializer(
filter_people(all_forms['person'], at_date=date), many=True
).data
context['organisation_set'] = serializers.OrganisationSerializer(
filter_organisations(all_forms['organisation'], at_date=date), many=True
).data
context['relationship_set'] = serializers.RelationshipSerializer(
filter_relationships(all_forms['relationship'], at_date=date), many=True
).data
context['organisation_relationship_set'] = serializers.OrganisationRelationshipSerializer(
models.OrganisationRelationship.objects.prefetch_related('source', 'target').all(),
many=True
).data
for person in models.Person.objects.all():
try:
context['organisation_relationship_set'].append(
{
'pk': f'membership-{person.pk}',
'source': serializers.PersonSerializer(person).data,
'target': serializers.OrganisationSerializer(
person.current_answers.organisation
).data,
'kind': 'organisation-membership'
}
)
except AttributeError:
pass
logger.info(
'Found %d distinct relationships matching filters', len(context['relationship_set'])
)
return context
def forms_valid(self, all_forms):
try:
return self.render_to_response(self.get_context_data())
except ValidationError:
return self.forms_invalid(all_forms)
def forms_invalid(self, all_forms):
return self.render_to_response(self.get_context_data())