mirror of
https://github.com/Southampton-RSG/breccia-mapper.git
synced 2026-03-03 03:17:07 +00:00
refactor: reorganise network page
Backend form handling slightly simplified - date is own form now
This commit is contained in:
@@ -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 %}
|
||||||
|
|
||||||
|
|||||||
@@ -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')
|
|
||||||
|
|||||||
@@ -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());
|
||||||
|
|||||||
@@ -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 }}
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user