mirror of
https://github.com/Southampton-RSG/breccia-mapper.git
synced 2026-03-03 03:17:07 +00:00
@@ -85,3 +85,19 @@ class RelationshipQuestionAdmin(admin.ModelAdmin):
|
|||||||
@admin.register(models.Relationship)
|
@admin.register(models.Relationship)
|
||||||
class RelationshipAdmin(admin.ModelAdmin):
|
class RelationshipAdmin(admin.ModelAdmin):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationshipQuestionChoiceInline(admin.TabularInline):
|
||||||
|
model = models.OrganisationRelationshipQuestionChoice
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.OrganisationRelationshipQuestion)
|
||||||
|
class OrganisationRelationshipQuestionAdmin(admin.ModelAdmin):
|
||||||
|
inlines = [
|
||||||
|
OrganisationRelationshipQuestionChoiceInline,
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(models.OrganisationRelationship)
|
||||||
|
class OrganisationRelationshipAdmin(admin.ModelAdmin):
|
||||||
|
pass
|
||||||
|
|||||||
@@ -239,6 +239,48 @@ class RelationshipAnswerSetForm(forms.ModelForm, DynamicAnswerSetBase):
|
|||||||
return self.instance
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationshipAnswerSetForm(forms.ModelForm, DynamicAnswerSetBase):
|
||||||
|
"""Form to allow users to describe a relationship with an organisation.
|
||||||
|
|
||||||
|
Dynamic fields inspired by https://jacobian.org/2010/feb/28/dynamic-form-generation/
|
||||||
|
"""
|
||||||
|
class Meta:
|
||||||
|
model = models.OrganisationRelationshipAnswerSet
|
||||||
|
fields = [
|
||||||
|
'relationship',
|
||||||
|
]
|
||||||
|
widgets = {
|
||||||
|
'relationship': forms.HiddenInput,
|
||||||
|
}
|
||||||
|
|
||||||
|
question_model = models.OrganisationRelationshipQuestion
|
||||||
|
answer_model = models.OrganisationRelationshipQuestionChoice
|
||||||
|
|
||||||
|
def save(self, commit=True) -> models.OrganisationRelationshipAnswerSet:
|
||||||
|
# Save model
|
||||||
|
self.instance = super().save(commit=commit)
|
||||||
|
|
||||||
|
if commit:
|
||||||
|
# Save answers to questions
|
||||||
|
for key, value in self.cleaned_data.items():
|
||||||
|
if key.startswith('question_') and value:
|
||||||
|
if key.endswith('_free'):
|
||||||
|
# Create new answer from free text
|
||||||
|
value, _ = self.answer_model.objects.get_or_create(
|
||||||
|
text=value,
|
||||||
|
question=self.question_model.objects.get(
|
||||||
|
pk=key.split('_')[1]))
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.instance.question_answers.add(value)
|
||||||
|
|
||||||
|
except TypeError:
|
||||||
|
# Value is a QuerySet - multiple choice question
|
||||||
|
self.instance.question_answers.add(*value.all())
|
||||||
|
|
||||||
|
return self.instance
|
||||||
|
|
||||||
|
|
||||||
class NetworkFilterForm(DynamicAnswerSetBase):
|
class NetworkFilterForm(DynamicAnswerSetBase):
|
||||||
"""
|
"""
|
||||||
Form to provide filtering on the network view.
|
Form to provide filtering on the network view.
|
||||||
|
|||||||
76
people/migrations/0039_add_organisation_relationship.py
Normal file
76
people/migrations/0039_add_organisation_relationship.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Generated by Django 2.2.10 on 2021-03-02 08:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('people', '0038_project_started_date'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrganisationRelationship',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('expired', models.DateTimeField(blank=True, null=True)),
|
||||||
|
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organisation_relationships_as_source', to='people.Person')),
|
||||||
|
('target', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='organisation_relationships_as_target', to='people.Organisation')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrganisationRelationshipQuestion',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('version', models.PositiveSmallIntegerField(default=1)),
|
||||||
|
('text', models.CharField(max_length=255)),
|
||||||
|
('filter_text', models.CharField(blank=True, help_text='Text to be displayed in network filters - 3rd person', max_length=255)),
|
||||||
|
('answer_is_public', models.BooleanField(default=True, help_text='Should answers to this question be considered public?')),
|
||||||
|
('is_multiple_choice', models.BooleanField(default=False)),
|
||||||
|
('allow_free_text', models.BooleanField(default=False)),
|
||||||
|
('order', models.SmallIntegerField(default=0)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['order', 'text'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrganisationRelationshipQuestionChoice',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('text', models.CharField(max_length=255)),
|
||||||
|
('order', models.SmallIntegerField(default=0)),
|
||||||
|
('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answers', to='people.OrganisationRelationshipQuestion')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['question__order', 'order', 'text'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='OrganisationRelationshipAnswerSet',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('timestamp', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('replaced_timestamp', models.DateTimeField(blank=True, editable=False, null=True)),
|
||||||
|
('question_answers', models.ManyToManyField(to='people.OrganisationRelationshipQuestionChoice')),
|
||||||
|
('relationship', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='answer_sets', to='people.OrganisationRelationship')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['timestamp'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='organisationrelationshipquestionchoice',
|
||||||
|
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer'),
|
||||||
|
),
|
||||||
|
migrations.AddConstraint(
|
||||||
|
model_name='organisationrelationship',
|
||||||
|
constraint=models.UniqueConstraint(fields=('source', 'target'), name='unique_relationship'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.10 on 2021-03-02 08:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('people', '0039_add_organisation_relationship'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='person',
|
||||||
|
name='organisation_relationship_targets',
|
||||||
|
field=models.ManyToManyField(related_name='relationship_sources', through='people.OrganisationRelationship', to='people.Organisation'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -226,6 +226,13 @@ class Person(models.Model):
|
|||||||
through_fields=('source', 'target'),
|
through_fields=('source', 'target'),
|
||||||
symmetrical=False)
|
symmetrical=False)
|
||||||
|
|
||||||
|
#: Organisations with whom this person has relationship - via intermediate :class:`OrganisationRelationship` model
|
||||||
|
organisation_relationship_targets = models.ManyToManyField(
|
||||||
|
Organisation,
|
||||||
|
related_name='relationship_sources',
|
||||||
|
through='OrganisationRelationship',
|
||||||
|
through_fields=('source', 'target'))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def relationships(self):
|
def relationships(self):
|
||||||
return self.relationships_as_source.all().union(
|
return self.relationships_as_source.all().union(
|
||||||
|
|||||||
@@ -2,11 +2,10 @@
|
|||||||
Models describing relationships between people.
|
Models describing relationships between people.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from .person import Person
|
from .person import Organisation, Person
|
||||||
from .question import AnswerSet, Question, QuestionChoice
|
from .question import AnswerSet, Question, QuestionChoice
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
@@ -14,6 +13,10 @@ __all__ = [
|
|||||||
'RelationshipQuestionChoice',
|
'RelationshipQuestionChoice',
|
||||||
'RelationshipAnswerSet',
|
'RelationshipAnswerSet',
|
||||||
'Relationship',
|
'Relationship',
|
||||||
|
'OrganisationRelationshipQuestion',
|
||||||
|
'OrganisationRelationshipQuestionChoice',
|
||||||
|
'OrganisationRelationshipAnswerSet',
|
||||||
|
'OrganisationRelationship',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -32,24 +35,10 @@ class RelationshipQuestionChoice(QuestionChoice):
|
|||||||
null=False)
|
null=False)
|
||||||
|
|
||||||
|
|
||||||
# class ExternalPerson(models.Model):
|
|
||||||
# """Model representing a person external to the project.
|
|
||||||
|
|
||||||
# These will never need to be linked to a :class:`User` as they
|
|
||||||
# will never log in to the system.
|
|
||||||
# """
|
|
||||||
# name = models.CharField(max_length=255,
|
|
||||||
# blank=False, null=False)
|
|
||||||
|
|
||||||
# def __str__(self) -> str:
|
|
||||||
# return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Relationship(models.Model):
|
class Relationship(models.Model):
|
||||||
"""
|
"""
|
||||||
A directional relationship between two people allowing linked questions.
|
A directional relationship between two people allowing linked questions.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(fields=['source', 'target'],
|
models.UniqueConstraint(fields=['source', 'target'],
|
||||||
@@ -57,9 +46,11 @@ class Relationship(models.Model):
|
|||||||
]
|
]
|
||||||
|
|
||||||
#: Person reporting the relationship
|
#: Person reporting the relationship
|
||||||
source = models.ForeignKey(Person, related_name='relationships_as_source',
|
source = models.ForeignKey(Person,
|
||||||
|
related_name='relationships_as_source',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
blank=False, null=False)
|
blank=False,
|
||||||
|
null=False)
|
||||||
|
|
||||||
#: Person with whom the relationship is reported
|
#: Person with whom the relationship is reported
|
||||||
target = models.ForeignKey(Person,
|
target = models.ForeignKey(Person,
|
||||||
@@ -67,15 +58,6 @@ class Relationship(models.Model):
|
|||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
blank=False,
|
blank=False,
|
||||||
null=False)
|
null=False)
|
||||||
# blank=True,
|
|
||||||
# null=True)
|
|
||||||
|
|
||||||
# target_external_person = models.ForeignKey(
|
|
||||||
# ExternalPerson,
|
|
||||||
# related_name='relationships_as_target',
|
|
||||||
# on_delete=models.CASCADE,
|
|
||||||
# blank=True,
|
|
||||||
# null=True)
|
|
||||||
|
|
||||||
#: When was this relationship defined?
|
#: When was this relationship defined?
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
@@ -100,8 +82,7 @@ class Relationship(models.Model):
|
|||||||
|
|
||||||
@raise Relationship.DoesNotExist: When the reverse relationship is not known
|
@raise Relationship.DoesNotExist: When the reverse relationship is not known
|
||||||
"""
|
"""
|
||||||
return type(self).objects.get(source=self.target,
|
return type(self).objects.get(source=self.target, target=self.source)
|
||||||
target=self.source)
|
|
||||||
|
|
||||||
|
|
||||||
class RelationshipAnswerSet(AnswerSet):
|
class RelationshipAnswerSet(AnswerSet):
|
||||||
@@ -119,3 +100,78 @@ class RelationshipAnswerSet(AnswerSet):
|
|||||||
|
|
||||||
def get_absolute_url(self):
|
def get_absolute_url(self):
|
||||||
return self.relationship.get_absolute_url()
|
return self.relationship.get_absolute_url()
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationshipQuestion(Question):
|
||||||
|
"""Question which may be asked about an :class:`OrganisationRelationship`."""
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationshipQuestionChoice(QuestionChoice):
|
||||||
|
"""Allowed answer to a :class:`OrganisationRelationshipQuestion`."""
|
||||||
|
|
||||||
|
#: Question to which this answer belongs
|
||||||
|
question = models.ForeignKey(OrganisationRelationshipQuestion,
|
||||||
|
related_name='answers',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=False,
|
||||||
|
null=False)
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationship(models.Model):
|
||||||
|
"""A directional relationship between a person and an organisation with linked questions."""
|
||||||
|
class Meta:
|
||||||
|
constraints = [
|
||||||
|
models.UniqueConstraint(fields=['source', 'target'],
|
||||||
|
name='unique_relationship'),
|
||||||
|
]
|
||||||
|
|
||||||
|
#: Person reporting the relationship
|
||||||
|
source = models.ForeignKey(
|
||||||
|
Person,
|
||||||
|
related_name='organisation_relationships_as_source',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=False,
|
||||||
|
null=False)
|
||||||
|
|
||||||
|
#: Organisation with which the relationship is reported
|
||||||
|
target = models.ForeignKey(
|
||||||
|
Organisation,
|
||||||
|
related_name='organisation_relationships_as_target',
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
blank=False,
|
||||||
|
null=False)
|
||||||
|
|
||||||
|
#: When was this relationship defined?
|
||||||
|
created = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
#: When was this marked as expired? Default None means it has not expired
|
||||||
|
expired = models.DateTimeField(blank=True, null=True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_answers(self) -> 'OrganisationRelationshipAnswerSet':
|
||||||
|
return self.answer_sets.last()
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return reverse('people:organisation.relationship.detail',
|
||||||
|
kwargs={'pk': self.pk})
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f'{self.source} -> {self.target}'
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationshipAnswerSet(AnswerSet):
|
||||||
|
"""The answers to the organisation relationship questions at a particular point in time."""
|
||||||
|
|
||||||
|
#: OrganisationRelationship to which this answer set belongs
|
||||||
|
relationship = models.ForeignKey(OrganisationRelationship,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name='answer_sets',
|
||||||
|
blank=False,
|
||||||
|
null=False)
|
||||||
|
|
||||||
|
#: Answers to :class:`OrganisationRelationshipQuestion`s
|
||||||
|
question_answers = models.ManyToManyField(
|
||||||
|
OrganisationRelationshipQuestionChoice)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
return self.relationship.get_absolute_url()
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'people:person.list' %}">People</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{% url 'people:person.detail' pk=relationship.source.pk %}">{{ relationship.source }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">{{ relationship.target }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1>Organisation Relationship</h1>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<div class="row align-content-center align-items-center">
|
||||||
|
<div class="col-md-5 text-center">
|
||||||
|
<h2>Source</h2>
|
||||||
|
<p>{{ relationship.source }}</p>
|
||||||
|
|
||||||
|
<a class="btn btn-sm btn-info"
|
||||||
|
href="{% url 'people:person.detail' pk=relationship.source.pk %}">Profile</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-md-2 text-center"></div>
|
||||||
|
|
||||||
|
<div class="col-md-5 text-center">
|
||||||
|
<h2>Target</h2>
|
||||||
|
<p>{{ relationship.target }}</p>
|
||||||
|
|
||||||
|
<a class="btn btn-sm btn-info"
|
||||||
|
href="{% url 'people:organisation.detail' pk=relationship.target.pk %}">Profile</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
|
||||||
|
<a class="btn btn-success"
|
||||||
|
href="{% url 'people:organisation.relationship.update' relationship_pk=relationship.pk %}">Update</a>
|
||||||
|
|
||||||
|
{% with relationship.current_answers as answer_set %}
|
||||||
|
<table class="table table-borderless">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Question</th>
|
||||||
|
<th>Answer</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
{% for answer in answer_set.question_answers.all %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ answer.question }}</td>
|
||||||
|
<td>{{ answer }}</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td>No records</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
Last updated: {{ answer_set.timestamp }}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
@@ -28,6 +28,19 @@
|
|||||||
<td>
|
<td>
|
||||||
<a class="btn btn-sm btn-info"
|
<a class="btn btn-sm btn-info"
|
||||||
href="{% url 'people:organisation.detail' pk=organisation.pk %}">Details</a>
|
href="{% url 'people:organisation.detail' pk=organisation.pk %}">Details</a>
|
||||||
|
|
||||||
|
{% if organisation.pk in existing_relationships %}
|
||||||
|
<a class="btn btn-sm btn-warning"
|
||||||
|
style="width: 10rem"
|
||||||
|
href="{% url 'people:organisation.relationship.create' organisation_pk=organisation.pk %}">Update Relationship
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{% else %}
|
||||||
|
<a class="btn btn-sm btn-success"
|
||||||
|
style="width: 10rem"
|
||||||
|
href="{% url 'people:organisation.relationship.create' organisation_pk=organisation.pk %}">New Relationship
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ from . import views
|
|||||||
app_name = 'people'
|
app_name = 'people'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
####################
|
||||||
|
# Organisation views
|
||||||
path('organisations/create',
|
path('organisations/create',
|
||||||
views.organisation.OrganisationCreateView.as_view(),
|
views.organisation.OrganisationCreateView.as_view(),
|
||||||
name='organisation.create'),
|
name='organisation.create'),
|
||||||
@@ -22,6 +24,8 @@ urlpatterns = [
|
|||||||
views.organisation.OrganisationUpdateView.as_view(),
|
views.organisation.OrganisationUpdateView.as_view(),
|
||||||
name='organisation.update'),
|
name='organisation.update'),
|
||||||
|
|
||||||
|
##############
|
||||||
|
# Person views
|
||||||
path('profile/',
|
path('profile/',
|
||||||
views.person.ProfileView.as_view(),
|
views.person.ProfileView.as_view(),
|
||||||
name='person.profile'),
|
name='person.profile'),
|
||||||
@@ -42,6 +46,8 @@ urlpatterns = [
|
|||||||
views.person.PersonUpdateView.as_view(),
|
views.person.PersonUpdateView.as_view(),
|
||||||
name='person.update'),
|
name='person.update'),
|
||||||
|
|
||||||
|
####################
|
||||||
|
# Relationship views
|
||||||
path('people/<int:person_pk>/relationships/create',
|
path('people/<int:person_pk>/relationships/create',
|
||||||
views.relationship.RelationshipCreateView.as_view(),
|
views.relationship.RelationshipCreateView.as_view(),
|
||||||
name='person.relationship.create'),
|
name='person.relationship.create'),
|
||||||
@@ -54,6 +60,22 @@ urlpatterns = [
|
|||||||
views.relationship.RelationshipUpdateView.as_view(),
|
views.relationship.RelationshipUpdateView.as_view(),
|
||||||
name='relationship.update'),
|
name='relationship.update'),
|
||||||
|
|
||||||
|
################################
|
||||||
|
# OrganisationRelationship views
|
||||||
|
path('organisations/<int:organisation_pk>/relationships/create',
|
||||||
|
views.relationship.OrganisationRelationshipCreateView.as_view(),
|
||||||
|
name='organisation.relationship.create'),
|
||||||
|
|
||||||
|
path('organisation-relationships/<int:pk>',
|
||||||
|
views.relationship.OrganisationRelationshipDetailView.as_view(),
|
||||||
|
name='organisation.relationship.detail'),
|
||||||
|
|
||||||
|
path('organisation-relationships/<int:relationship_pk>/update',
|
||||||
|
views.relationship.OrganisationRelationshipUpdateView.as_view(),
|
||||||
|
name='organisation.relationship.update'),
|
||||||
|
|
||||||
|
############
|
||||||
|
# Data views
|
||||||
path('map',
|
path('map',
|
||||||
views.person.PersonMapView.as_view(),
|
views.person.PersonMapView.as_view(),
|
||||||
name='person.map'),
|
name='person.map'),
|
||||||
|
|||||||
@@ -19,6 +19,16 @@ class OrganisationListView(LoginRequiredMixin, ListView):
|
|||||||
model = models.Organisation
|
model = models.Organisation
|
||||||
template_name = 'people/organisation/list.html'
|
template_name = 'people/organisation/list.html'
|
||||||
|
|
||||||
|
def get_context_data(self,
|
||||||
|
**kwargs: typing.Any) -> typing.Dict[str, typing.Any]:
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context['existing_relationships'] = set(
|
||||||
|
self.request.user.person.organisation_relationship_targets.
|
||||||
|
values_list('pk', flat=True))
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
class OrganisationDetailView(LoginRequiredMixin, DetailView):
|
class OrganisationDetailView(LoginRequiredMixin, DetailView):
|
||||||
"""View displaying details of a :class:`Organisation`."""
|
"""View displaying details of a :class:`Organisation`."""
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ class RelationshipCreateView(LoginRequiredMixin, RedirectView):
|
|||||||
|
|
||||||
Redirects to a form containing the :class:`RelationshipQuestion`s.
|
Redirects to a form containing the :class:`RelationshipQuestion`s.
|
||||||
"""
|
"""
|
||||||
def get_redirect_url(self, *args: typing.Any, **kwargs: typing.Any) -> typing.Optional[str]:
|
def get_redirect_url(self, *args: typing.Any,
|
||||||
|
**kwargs: typing.Any) -> typing.Optional[str]:
|
||||||
target = models.Person.objects.get(pk=self.kwargs.get('person_pk'))
|
target = models.Person.objects.get(pk=self.kwargs.get('person_pk'))
|
||||||
relationship, _ = models.Relationship.objects.get_or_create(
|
relationship, _ = models.Relationship.objects.get_or_create(
|
||||||
source=self.request.user.person, target=target)
|
source=self.request.user.person, target=target)
|
||||||
@@ -96,3 +97,94 @@ class RelationshipUpdateView(permissions.UserIsLinkedPersonMixin, CreateView):
|
|||||||
answer_set.save()
|
answer_set.save()
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationshipDetailView(permissions.UserIsLinkedPersonMixin,
|
||||||
|
DetailView):
|
||||||
|
"""View displaying details of an :class:`OrganisationRelationship`."""
|
||||||
|
model = models.OrganisationRelationship
|
||||||
|
template_name = 'people/organisation-relationship/detail.html'
|
||||||
|
related_person_field = 'source'
|
||||||
|
context_object_name = 'relationship'
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationshipCreateView(LoginRequiredMixin, RedirectView):
|
||||||
|
"""View for creating a :class:`OrganisationRelationship`.
|
||||||
|
|
||||||
|
Redirects to a form containing the :class:`OrganisationRelationshipQuestion`s.
|
||||||
|
"""
|
||||||
|
def get_redirect_url(self, *args: typing.Any,
|
||||||
|
**kwargs: typing.Any) -> typing.Optional[str]:
|
||||||
|
target = models.Organisation.objects.get(
|
||||||
|
pk=self.kwargs.get('organisation_pk'))
|
||||||
|
relationship, _ = models.OrganisationRelationship.objects.get_or_create(
|
||||||
|
source=self.request.user.person, target=target)
|
||||||
|
|
||||||
|
return reverse('people:organisation.relationship.update',
|
||||||
|
kwargs={'relationship_pk': relationship.pk})
|
||||||
|
|
||||||
|
|
||||||
|
class OrganisationRelationshipUpdateView(permissions.UserIsLinkedPersonMixin,
|
||||||
|
CreateView):
|
||||||
|
"""
|
||||||
|
View for updating the details of a Organisationrelationship.
|
||||||
|
|
||||||
|
Creates a new :class:`OrganisationRelationshipAnswerSet` for the :class:`OrganisationRelationship`.
|
||||||
|
Displays / processes a form containing the :class:`OrganisationRelationshipQuestion`s.
|
||||||
|
"""
|
||||||
|
model = models.OrganisationRelationshipAnswerSet
|
||||||
|
template_name = 'people/relationship/update.html'
|
||||||
|
form_class = forms.OrganisationRelationshipAnswerSetForm
|
||||||
|
|
||||||
|
def get_test_person(self) -> models.Person:
|
||||||
|
"""
|
||||||
|
Get the person instance which should be used for access control checks.
|
||||||
|
"""
|
||||||
|
relationship = models.OrganisationRelationship.objects.get(
|
||||||
|
pk=self.kwargs.get('relationship_pk'))
|
||||||
|
|
||||||
|
return relationship.source
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
self.relationship = models.OrganisationRelationship.objects.get(
|
||||||
|
pk=self.kwargs.get('relationship_pk'))
|
||||||
|
self.person = self.relationship.source
|
||||||
|
|
||||||
|
return super().get(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.relationship = models.OrganisationRelationship.objects.get(
|
||||||
|
pk=self.kwargs.get('relationship_pk'))
|
||||||
|
self.person = self.relationship.source
|
||||||
|
|
||||||
|
return super().post(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
context['person'] = self.person
|
||||||
|
context['relationship'] = self.relationship
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
initial = super().get_initial()
|
||||||
|
|
||||||
|
initial['relationship'] = self.relationship
|
||||||
|
|
||||||
|
return initial
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""
|
||||||
|
Mark any previous answer sets as replaced.
|
||||||
|
"""
|
||||||
|
response = super().form_valid(form)
|
||||||
|
now_date = timezone.now().date()
|
||||||
|
|
||||||
|
# Shouldn't be more than one after initial updates after migration
|
||||||
|
for answer_set in self.relationship.answer_sets.exclude(
|
||||||
|
pk=self.object.pk):
|
||||||
|
answer_set.replaced_timestamp = now_date
|
||||||
|
answer_set.save()
|
||||||
|
|
||||||
|
return response
|
||||||
|
|||||||
Reference in New Issue
Block a user