refactor: reorganise network page

Backend form handling slightly simplified - date is own form now
This commit is contained in:
James Graham
2021-05-20 15:28:59 +01:00
parent 9d14cf4b38
commit 3ea4ea88a7
5 changed files with 84 additions and 127 deletions

View File

@@ -175,7 +175,7 @@
{% block before_content %}{% endblock %} {% block before_content %}{% endblock %}
<main class="container"> <main class="{{ full_width_page|yesno:'container-fluid,container' }}">
{# Display Django messages as Bootstrap alerts #} {# Display Django messages as Bootstrap alerts #}
{% bootstrap_messages %} {% bootstrap_messages %}

View File

@@ -42,20 +42,22 @@ class DynamicAnswerSetBase(forms.Form):
question_model: typing.Type[models.Question] question_model: typing.Type[models.Question]
answer_model: typing.Type[models.QuestionChoice] answer_model: typing.Type[models.QuestionChoice]
question_prefix: str = '' question_prefix: str = ''
as_filters: bool = False
def __init__(self, *args, as_filters: bool = False, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
field_order = [] field_order = []
for question in self.question_model.objects.all(): for question in self.question_model.objects.all():
if as_filters and not question.answer_is_public: if self.as_filters and not question.answer_is_public:
continue continue
# Placeholder question for sorting hardcoded questions # Is a placeholder question just for sorting hardcoded questions?
if (question.is_hardcoded if (
and (as_filters or question.is_hardcoded
(question.hardcoded_field in self.Meta.fields))): and (self.as_filters or (question.hardcoded_field in self.Meta.fields))
):
field_order.append(question.hardcoded_field) field_order.append(question.hardcoded_field)
continue continue
@@ -70,7 +72,7 @@ class DynamicAnswerSetBase(forms.Form):
# If being used as a filter - do we have alternate text? # If being used as a filter - do we have alternate text?
field_label = question.text field_label = question.text
if as_filters and question.filter_text: if self.as_filters and question.filter_text:
field_label = question.filter_text field_label = question.filter_text
field = field_class( field = field_class(
@@ -80,11 +82,11 @@ class DynamicAnswerSetBase(forms.Form):
required=(self.field_required required=(self.field_required
and not question.allow_free_text), and not question.allow_free_text),
initial=self.initial.get(field_name, None), initial=self.initial.get(field_name, None),
help_text=question.help_text if not as_filters else '') help_text=question.help_text if not self.as_filters else '')
self.fields[field_name] = field self.fields[field_name] = field
field_order.append(field_name) field_order.append(field_name)
if question.allow_free_text and not as_filters: if question.allow_free_text and not self.as_filters:
free_field = forms.CharField(label=f'{question} free text', free_field = forms.CharField(label=f'{question} free text',
required=False) required=False)
self.fields[f'{field_name}_free'] = free_field self.fields[f'{field_name}_free'] = free_field
@@ -312,58 +314,38 @@ class OrganisationRelationshipAnswerSetForm(forms.ModelForm,
return self.instance return self.instance
class NetworkRelationshipFilterForm(DynamicAnswerSetBase): class DateForm(forms.Form):
"""Form to provide filtering on the network view.""" date = forms.DateField(
required=False,
widget=DatePickerInput(format='%Y-%m-%d'),
help_text='Show relationships as they were on this date'
)
class FilterForm(DynamicAnswerSetBase):
"""Filter objects by answerset responses."""
field_class = forms.ModelMultipleChoiceField field_class = forms.ModelMultipleChoiceField
field_widget = Select2MultipleWidget field_widget = Select2MultipleWidget
field_required = False field_required = False
as_filters = True
class NetworkRelationshipFilterForm(FilterForm):
"""Filer relationships by answerset responses."""
question_model = models.RelationshipQuestion question_model = models.RelationshipQuestion
answer_model = models.RelationshipQuestionChoice answer_model = models.RelationshipQuestionChoice
question_prefix = 'relationship_' question_prefix = 'relationship_'
def __init__(self, *args, **kwargs):
super().__init__(*args, as_filters=True, **kwargs)
# Add date field to select relationships at a particular point in time class NetworkPersonFilterForm(FilterForm):
self.fields['date'] = forms.DateField( """Filer people by answerset responses."""
required=False,
widget=DatePickerInput(format='%Y-%m-%d'),
help_text='Show relationships as they were on this date')
class NetworkPersonFilterForm(DynamicAnswerSetBase):
"""Form to provide filtering on the network view."""
field_class = forms.ModelMultipleChoiceField
field_widget = Select2MultipleWidget
field_required = False
question_model = models.PersonQuestion question_model = models.PersonQuestion
answer_model = models.PersonQuestionChoice answer_model = models.PersonQuestionChoice
question_prefix = 'person_' question_prefix = 'person_'
def __init__(self, *args, **kwargs):
super().__init__(*args, as_filters=True, **kwargs)
# Add date field to select relationships at a particular point in time class NetworkOrganisationFilterForm(FilterForm):
self.fields['date'] = forms.DateField( """Filer organisations by answerset responses."""
required=False,
widget=DatePickerInput(format='%Y-%m-%d'),
help_text='Show relationships as they were on this date')
class NetworkOrganisationFilterForm(DynamicAnswerSetBase):
"""Form to provide filtering on the network view."""
field_class = forms.ModelMultipleChoiceField
field_widget = Select2MultipleWidget
field_required = False
question_model = models.OrganisationQuestion question_model = models.OrganisationQuestion
answer_model = models.OrganisationQuestionChoice answer_model = models.OrganisationQuestionChoice
question_prefix = 'organisation_' question_prefix = 'organisation_'
def __init__(self, *args, **kwargs):
super().__init__(*args, as_filters=True, **kwargs)
# Add date field to select relationships at a particular point in time
self.fields['date'] = forms.DateField(
required=False,
widget=DatePickerInput(format='%Y-%m-%d'),
help_text='Show relationships as they were on this date')

View File

@@ -195,6 +195,10 @@ function get_network() {
}); });
layout.run(); layout.run();
setTimeout(function () {
document.getElementById('cy').style.height = '100%';
}, 1000)
} }
$(window).on('load', get_network()); $(window).on('load', get_network());

View File

@@ -2,6 +2,7 @@
{% block extra_head %} {% block extra_head %}
{# There's no 'form' so need to add this to load CSS / JS #} {# There's no 'form' so need to add this to load CSS / JS #}
{{ date_form.media.css }}
{{ relationship_form.media.css }} {{ relationship_form.media.css }}
<link rel="stylesheet" <link rel="stylesheet"
@@ -17,85 +18,54 @@
</ol> </ol>
</nav> </nav>
<h1>Network View</h1>
<hr>
<form class="form"
method="POST">
{% csrf_token %}
{% load bootstrap4 %}
<div class="row">
<div class="col-md-4">
<h3>Filter Relationships</h3>
{% bootstrap_form relationship_form exclude='date' %}
</div>
<div class="col-md-4">
<h3>Filter People</h3>
{% bootstrap_form person_form exclude='date' %}
</div>
<div class="col-md-4">
<h3>Filter Organisations</h3>
{% bootstrap_form organisation_form exclude='date' %}
</div>
</div>
<div class="row">
<div class="col-md-4">
<hr>
{% bootstrap_field relationship_form.date %}
</div>
<div class="col-md-4">
<hr>
{% bootstrap_field person_form.date %}
</div>
<div class="col-md-4">
<hr>
{% bootstrap_field organisation_form.date %}
</div>
</div>
<hr>
{% buttons %}
<div class="row">
<div class="col-md-6">
<button class="btn btn-block btn-success" type="submit">Filter</button>
</div>
<div class="col-md-6">
<input class="btn btn-block btn-danger" type="button" value="Reset Filters" onClick="reset_filters();" />
</div>
</div>
{% endbuttons %}
</form>
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
<button class="btn btn-block btn-info mb-3" onclick="save_image();">Save Image</button> <form class="form" method="POST">
{% csrf_token %}
{% load bootstrap4 %}
{% buttons %}
<input class="btn btn-block btn-danger mb-3" type="button" value="Reset Filters" onClick="reset_filters();" />
<button class="btn btn-block btn-success mb-3" type="submit">Filter</button>
{% endbuttons %}
{% bootstrap_form date_form %}
<hr>
<h3>Filter Relationships</h3>
{% bootstrap_form relationship_form %}
<hr>
<h3>Filter People</h3>
{% bootstrap_form person_form %}
<hr>
<h3>Filter Organisations</h3>
{% bootstrap_form organisation_form %}
</form>
</div> </div>
<div class="col-md-4"> <div class="col-md-8" style="display: flex; flex-direction: column;">
<button class="btn btn-block btn-info mb-3" onclick="toggle_organisations();">Toggle Organisations</button> <div class="row">
</div> <div class="col-md-6">
<button class="btn btn-block btn-info mb-3" onclick="save_image();">Save Image</button>
<button class="btn btn-block btn-info mb-3" onclick="toggle_anonymise_people();">Anonymise People</button>
</div>
<div class="col-md-4"> <div class="col-md-6">
<button class="btn btn-block btn-info mb-3" onclick="toggle_anonymise_people();">Toggle Anonymise People</button> <button class="btn btn-block btn-info mb-3" onclick="toggle_organisations();">Hide Organisations</button>
<button class="btn btn-block btn-info mb-3" onclick="toggle_anonymise_organisations();">Toggle Anonymise Organisations</button> <button class="btn btn-block btn-info mb-3" onclick="toggle_anonymise_organisations();">Anonymise Organisations</button>
</div>
</div>
<div id="cy" class="mb-2"
style="width: 100%; min-height: 1000px; border: 2px solid black; z-index: 999"></div>
</div> </div>
</div> </div>
<div id="cy"
class="mb-2"
style="width: 100%; min-height: 1000px; flex-grow: 1; border: 2px solid black; z-index: 999"></div>
{% endblock %} {% endblock %}
{% block extra_script %} {% block extra_script %}
{{ date_form.media.js }}
{{ relationship_form.media.js }} {{ relationship_form.media.js }}
<!-- <!--

View File

@@ -3,7 +3,6 @@ Views for displaying networks of :class:`People` and :class:`Relationship`s.
""" """
import logging import logging
import typing
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models import Q, QuerySet from django.db.models import Q, QuerySet
@@ -18,12 +17,11 @@ logger = logging.getLogger(__name__) # pylint: disable=invalid-name
def filter_by_form_answers(queryset: QuerySet, answerset_queryset: QuerySet, relationship_key: str): def filter_by_form_answers(queryset: QuerySet, answerset_queryset: QuerySet, relationship_key: str):
"""Build a filter to select based on form responses.""" """Build a filter to select based on form responses."""
def inner(form): def inner(form, at_date=None):
# Filter on timestamp__date doesn't seem to work on MySQL # Filter on timestamp__date doesn't seem to work on MySQL
# To compare datetimes we need at_date to be midnight at # To compare datetimes we need at_date to be midnight at
# the *end* of the day in question - so add one day # the *end* of the day in question - so add one day
at_date = form.cleaned_data['date']
if not at_date: if not at_date:
at_date = timezone.now().date() at_date = timezone.now().date()
at_date += timezone.timedelta(days=1) at_date += timezone.timedelta(days=1)
@@ -77,6 +75,7 @@ class NetworkView(LoginRequiredMixin, TemplateView):
'relationship': forms.NetworkRelationshipFilterForm(**form_kwargs), 'relationship': forms.NetworkRelationshipFilterForm(**form_kwargs),
'person': forms.NetworkPersonFilterForm(**form_kwargs), 'person': forms.NetworkPersonFilterForm(**form_kwargs),
'organisation': forms.NetworkOrganisationFilterForm(**form_kwargs), 'organisation': forms.NetworkOrganisationFilterForm(**form_kwargs),
'date': forms.DateForm(**form_kwargs),
} }
def get_form_kwargs(self): def get_form_kwargs(self):
@@ -92,29 +91,31 @@ class NetworkView(LoginRequiredMixin, TemplateView):
return kwargs return kwargs
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
""" """Add filtered QuerySets of :class:`Person` and :class:`Relationship` to the context."""
Add filtered QuerySets of :class:`Person` and :class:`Relationship` to the context.
"""
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context['full_width_page'] = True
all_forms = self.get_forms() all_forms = self.get_forms()
context['relationship_form'] = all_forms['relationship'] context['relationship_form'] = all_forms['relationship']
context['person_form'] = all_forms['person'] context['person_form'] = all_forms['person']
context['organisation_form'] = all_forms['organisation'] context['organisation_form'] = all_forms['organisation']
context['date_form'] = all_forms['date']
if not all(map(lambda f: f.is_valid(), all_forms.values())): if not all(map(lambda f: f.is_valid(), all_forms.values())):
return context return context
date = all_forms['date'].cleaned_data['date']
context['person_set'] = serializers.PersonSerializer( context['person_set'] = serializers.PersonSerializer(
filter_people(all_forms['person']), many=True filter_people(all_forms['person'], at_date=date), many=True
).data ).data
context['organisation_set'] = serializers.OrganisationSerializer( context['organisation_set'] = serializers.OrganisationSerializer(
filter_organisations(all_forms['organisation']), many=True filter_organisations(all_forms['organisation'], at_date=date), many=True
).data ).data
context['relationship_set'] = serializers.RelationshipSerializer( context['relationship_set'] = serializers.RelationshipSerializer(
filter_relationships(all_forms['relationship']), many=True filter_relationships(all_forms['relationship'], at_date=date), many=True
).data ).data
context['organisation_relationship_set'] = serializers.OrganisationRelationshipSerializer( context['organisation_relationship_set'] = serializers.OrganisationRelationshipSerializer(