mirror of
https://github.com/Southampton-RSG/breccia-mapper.git
synced 2026-03-03 11:27:09 +00:00
Merge pull request #37 from Southampton-RSG/dev
Merge first batch of changes from user acceptance testing
This commit is contained in:
@@ -376,16 +376,20 @@ else:
|
|||||||
|
|
||||||
# Import customisation app settings if present
|
# Import customisation app settings if present
|
||||||
|
|
||||||
CUSTOMISATION_NAME = None
|
|
||||||
TEMPLATE_NAME_INDEX = 'index.html'
|
|
||||||
TEMPLATE_WELCOME_EMAIL_NAME = 'welcome-email'
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from custom.settings import (CUSTOMISATION_NAME, TEMPLATE_NAME_INDEX,
|
from custom.settings import (
|
||||||
TEMPLATE_WELCOME_EMAIL_NAME)
|
CUSTOMISATION_NAME,
|
||||||
|
TEMPLATE_NAME_INDEX,
|
||||||
|
TEMPLATE_WELCOME_EMAIL_NAME
|
||||||
|
)
|
||||||
logger.info("Loaded customisation app: %s", CUSTOMISATION_NAME)
|
logger.info("Loaded customisation app: %s", CUSTOMISATION_NAME)
|
||||||
|
|
||||||
INSTALLED_APPS.append('custom')
|
INSTALLED_APPS.append('custom')
|
||||||
|
|
||||||
except ImportError as e:
|
except ImportError as exc:
|
||||||
logger.info("No customisation app loaded: %s", e)
|
logger.info("No customisation app loaded: %s", exc)
|
||||||
|
|
||||||
|
# Set default values if no customisations loaded
|
||||||
|
CUSTOMISATION_NAME = None
|
||||||
|
TEMPLATE_NAME_INDEX = 'index.html'
|
||||||
|
TEMPLATE_WELCOME_EMAIL_NAME = 'welcome-email'
|
||||||
|
|||||||
@@ -25,8 +25,7 @@
|
|||||||
crossorigin="anonymous" />
|
crossorigin="anonymous" />
|
||||||
|
|
||||||
{% load staticfiles %}
|
{% load staticfiles %}
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet" href="{% static 'css/global.css' %}">
|
||||||
href="{% static 'css/global.css' %}">
|
|
||||||
|
|
||||||
{% if 'javascript_in_head'|bootstrap_setting %}
|
{% if 'javascript_in_head'|bootstrap_setting %}
|
||||||
{% if 'include_jquery'|bootstrap_setting %}
|
{% if 'include_jquery'|bootstrap_setting %}
|
||||||
@@ -38,7 +37,9 @@
|
|||||||
{% bootstrap_javascript %}
|
{% bootstrap_javascript %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if form %}
|
||||||
{{ form.media.css }}
|
{{ form.media.css }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% block extra_head %}{% endblock %}
|
{% block extra_head %}{% endblock %}
|
||||||
|
|
||||||
@@ -138,7 +139,8 @@
|
|||||||
{% if request.user.is_authenticated and not request.user.has_person %}
|
{% if request.user.is_authenticated and not request.user.has_person %}
|
||||||
<div class="alert alert-info rounded-0" role="alert">
|
<div class="alert alert-info rounded-0" role="alert">
|
||||||
<p class="text-center mb-0">
|
<p class="text-center mb-0">
|
||||||
Your profile is currently blank. Please fill in your details so you can be part of the network.
|
Your profile is currently blank.
|
||||||
|
Please fill in your details so you can be part of the network.
|
||||||
|
|
||||||
<a class="btn btn-success"
|
<a class="btn btn-success"
|
||||||
href="{% url 'people:person.create' %}?user">Profile</a>
|
href="{% url 'people:person.create' %}?user">Profile</a>
|
||||||
@@ -176,7 +178,9 @@
|
|||||||
{% bootstrap_javascript %}
|
{% bootstrap_javascript %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if form %}
|
||||||
{{ form.media.js }}
|
{{ form.media.js }}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
{% block extra_script %}{% endblock %}
|
{% block extra_script %}{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class SimplePersonSerializer(serializers.ModelSerializer):
|
|||||||
model = models.Person
|
model = models.Person
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'name',
|
# Name is excluded from exports
|
||||||
|
# See https://github.com/Southampton-RSG/breccia-mapper/issues/35
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -21,12 +22,14 @@ class PersonSerializer(base.FlattenedModelSerializer):
|
|||||||
model = models.Person
|
model = models.Person
|
||||||
fields = [
|
fields = [
|
||||||
'id',
|
'id',
|
||||||
'name',
|
# Name is excluded from exports
|
||||||
'core_member',
|
# See https://github.com/Southampton-RSG/breccia-mapper/issues/35
|
||||||
'gender',
|
'gender',
|
||||||
'age_group',
|
'age_group',
|
||||||
'nationality',
|
'nationality',
|
||||||
'country_of_residence',
|
'country_of_residence',
|
||||||
|
'organisation',
|
||||||
|
'organisation_started_date',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -43,6 +46,11 @@ class RelationshipSerializer(base.FlattenedModelSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def underscore(slug: str) -> str:
|
||||||
|
"""Replace hyphens with underscores in text."""
|
||||||
|
return slug.replace('-', '_')
|
||||||
|
|
||||||
|
|
||||||
class RelationshipAnswerSetSerializer(base.FlattenedModelSerializer):
|
class RelationshipAnswerSetSerializer(base.FlattenedModelSerializer):
|
||||||
relationship = RelationshipSerializer()
|
relationship = RelationshipSerializer()
|
||||||
|
|
||||||
@@ -61,7 +69,7 @@ class RelationshipAnswerSetSerializer(base.FlattenedModelSerializer):
|
|||||||
|
|
||||||
# Add relationship questions to columns
|
# Add relationship questions to columns
|
||||||
for question in models.RelationshipQuestion.objects.all():
|
for question in models.RelationshipQuestion.objects.all():
|
||||||
headers.append(question.slug.replace('-', '_'))
|
headers.append(underscore(question.slug))
|
||||||
|
|
||||||
return headers
|
return headers
|
||||||
|
|
||||||
@@ -71,7 +79,7 @@ class RelationshipAnswerSetSerializer(base.FlattenedModelSerializer):
|
|||||||
try:
|
try:
|
||||||
# Add relationship question answers to data
|
# Add relationship question answers to data
|
||||||
for answer in instance.question_answers.all():
|
for answer in instance.question_answers.all():
|
||||||
rep[answer.question.slug.replace('-', '_')] = answer.slug.replace('-', '_')
|
rep[underscore(answer.question.slug)] = underscore(answer.slug)
|
||||||
|
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
|
|||||||
@@ -1,13 +1,29 @@
|
|||||||
"""
|
"""
|
||||||
Forms for creating / updating models belonging to the 'people' app.
|
Forms for creating / updating models belonging to the 'people' app.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import typing
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.forms.widgets import SelectDateWidget
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
from django_select2.forms import Select2Widget, Select2MultipleWidget
|
from django_select2.forms import Select2Widget, Select2MultipleWidget
|
||||||
|
|
||||||
from . import models
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
def get_date_year_range() -> typing.Iterable[int]:
|
||||||
|
"""
|
||||||
|
Get sensible year range for SelectDateWidgets in the past.
|
||||||
|
|
||||||
|
By default these widgets show 10 years in the future.
|
||||||
|
"""
|
||||||
|
num_years_display = 60
|
||||||
|
this_year = timezone.datetime.now().year
|
||||||
|
return range(this_year, this_year - num_years_display, -1)
|
||||||
|
|
||||||
|
|
||||||
class PersonForm(forms.ModelForm):
|
class PersonForm(forms.ModelForm):
|
||||||
"""
|
"""
|
||||||
Form for creating / updating an instance of :class:`Person`.
|
Form for creating / updating an instance of :class:`Person`.
|
||||||
@@ -16,14 +32,14 @@ class PersonForm(forms.ModelForm):
|
|||||||
model = models.Person
|
model = models.Person
|
||||||
fields = [
|
fields = [
|
||||||
'name',
|
'name',
|
||||||
'core_member',
|
|
||||||
'gender',
|
'gender',
|
||||||
'age_group',
|
'age_group',
|
||||||
'nationality',
|
'nationality',
|
||||||
'country_of_residence',
|
'country_of_residence',
|
||||||
'organisation',
|
'organisation',
|
||||||
|
'organisation_started_date',
|
||||||
'job_title',
|
'job_title',
|
||||||
'discipline',
|
'disciplines',
|
||||||
'role',
|
'role',
|
||||||
'themes',
|
'themes',
|
||||||
]
|
]
|
||||||
@@ -32,6 +48,16 @@ class PersonForm(forms.ModelForm):
|
|||||||
'country_of_residence': Select2Widget(),
|
'country_of_residence': Select2Widget(),
|
||||||
'themes': Select2MultipleWidget(),
|
'themes': Select2MultipleWidget(),
|
||||||
}
|
}
|
||||||
|
help_texts = {
|
||||||
|
'organisation_started_date':
|
||||||
|
'If you don\'t know the exact date, an approximate date is okay.',
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
self.fields['organisation_started_date'].widget = SelectDateWidget(
|
||||||
|
years=get_date_year_range())
|
||||||
|
|
||||||
|
|
||||||
class DynamicAnswerSetBase(forms.Form):
|
class DynamicAnswerSetBase(forms.Form):
|
||||||
@@ -87,4 +113,7 @@ class NetworkFilterForm(DynamicAnswerSetBase):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
# Add date field to select relationships at a particular point in time
|
# Add date field to select relationships at a particular point in time
|
||||||
self.fields['date'] = forms.DateField(required=False)
|
self.fields['date'] = forms.DateField(
|
||||||
|
required=False,
|
||||||
|
widget=SelectDateWidget(years=get_date_year_range()),
|
||||||
|
help_text='Show relationships as they were on this date')
|
||||||
|
|||||||
17
people/migrations/0019_remove_person_core_member.py
Normal file
17
people/migrations/0019_remove_person_core_member.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Generated by Django 2.2.10 on 2020-06-24 11:14
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('people', '0018_require_user_email'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='person',
|
||||||
|
name='core_member',
|
||||||
|
),
|
||||||
|
]
|
||||||
18
people/migrations/0020_person_organisation_started_date.py
Normal file
18
people/migrations/0020_person_organisation_started_date.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 2.2.10 on 2020-06-24 12:52
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('people', '0019_remove_person_core_member'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='person',
|
||||||
|
name='organisation_started_date',
|
||||||
|
field=models.DateField(null=True, verbose_name='Date started at this organisation'),
|
||||||
|
),
|
||||||
|
]
|
||||||
61
people/migrations/0021_refactor_person_disciplines.py
Normal file
61
people/migrations/0021_refactor_person_disciplines.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Generated by Django 2.2.10 on 2020-06-24 14:19
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_forward(apps, schema_editor):
|
||||||
|
Person = apps.get_model('people', 'Person')
|
||||||
|
|
||||||
|
for person in Person.objects.all():
|
||||||
|
try:
|
||||||
|
person.disciplines = person.discipline.name
|
||||||
|
person.save()
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_backward(apps, schema_editor):
|
||||||
|
Person = apps.get_model('people', 'Person')
|
||||||
|
Discipline = apps.get_model('people', 'Discipline')
|
||||||
|
|
||||||
|
for person in Person.objects.all():
|
||||||
|
try:
|
||||||
|
discipline_str = person.disciplines.split(',')[0]
|
||||||
|
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Returns None if not found - doesn't raise exception
|
||||||
|
discipline = Discipline.objects.filter(name=discipline_str).first()
|
||||||
|
|
||||||
|
if not discipline:
|
||||||
|
discipline = Discipline.objects.create(
|
||||||
|
name=discipline_str,
|
||||||
|
code=discipline_str
|
||||||
|
if len(discipline_str) < 15 else discipline_str[:15])
|
||||||
|
|
||||||
|
person.discipline = discipline
|
||||||
|
person.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('people', '0020_person_organisation_started_date'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='person',
|
||||||
|
name='disciplines',
|
||||||
|
field=models.CharField(blank=True, max_length=255, null=True),
|
||||||
|
),
|
||||||
|
migrations.RunPython(migrate_forward, migrate_backward),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='person',
|
||||||
|
name='discipline',
|
||||||
|
),
|
||||||
|
migrations.DeleteModel(name='Discipline', ),
|
||||||
|
]
|
||||||
@@ -19,7 +19,6 @@ __all__ = [
|
|||||||
'User',
|
'User',
|
||||||
'Organisation',
|
'Organisation',
|
||||||
'Role',
|
'Role',
|
||||||
'Discipline',
|
|
||||||
'Theme',
|
'Theme',
|
||||||
'Person',
|
'Person',
|
||||||
]
|
]
|
||||||
@@ -82,19 +81,6 @@ class Role(models.Model):
|
|||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
|
|
||||||
class Discipline(models.Model):
|
|
||||||
"""
|
|
||||||
Discipline within which a :class:`Person` works.
|
|
||||||
"""
|
|
||||||
name = models.CharField(max_length=255, blank=False, null=False)
|
|
||||||
|
|
||||||
#: Short code using system such as JACS 3
|
|
||||||
code = models.CharField(max_length=15, blank=True, null=False)
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.name
|
|
||||||
|
|
||||||
|
|
||||||
class Theme(models.Model):
|
class Theme(models.Model):
|
||||||
"""
|
"""
|
||||||
Project theme within which a :class:`Person` works.
|
Project theme within which a :class:`Person` works.
|
||||||
@@ -122,9 +108,6 @@ class Person(models.Model):
|
|||||||
#: Name of the person
|
#: Name of the person
|
||||||
name = models.CharField(max_length=255, blank=False, null=False)
|
name = models.CharField(max_length=255, blank=False, null=False)
|
||||||
|
|
||||||
#: Is this person a member of the core project team?
|
|
||||||
core_member = models.BooleanField(default=False, blank=False, null=False)
|
|
||||||
|
|
||||||
#: People with whom this person has relationship - via intermediate :class:`Relationship` model
|
#: People with whom this person has relationship - via intermediate :class:`Relationship` model
|
||||||
relationship_targets = models.ManyToManyField(
|
relationship_targets = models.ManyToManyField(
|
||||||
'self',
|
'self',
|
||||||
@@ -175,15 +158,15 @@ class Person(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
null=True)
|
null=True)
|
||||||
|
|
||||||
|
#: When did this person start at their current organisation?
|
||||||
|
organisation_started_date = models.DateField(
|
||||||
|
'Date started at this organisation', blank=False, null=True)
|
||||||
|
|
||||||
#: Job title this person holds within their organisation
|
#: Job title this person holds within their organisation
|
||||||
job_title = models.CharField(max_length=255, blank=True, null=False)
|
job_title = models.CharField(max_length=255, blank=True, null=False)
|
||||||
|
|
||||||
#: Discipline within which this person works
|
#: Discipline(s) within which this person works
|
||||||
discipline = models.ForeignKey(Discipline,
|
disciplines = models.CharField(max_length=255, blank=True, null=True)
|
||||||
on_delete=models.PROTECT,
|
|
||||||
related_name='people',
|
|
||||||
blank=True,
|
|
||||||
null=True)
|
|
||||||
|
|
||||||
#: Role this person holds within the project
|
#: Role this person holds within the project
|
||||||
role = models.ForeignKey(Role,
|
role = models.ForeignKey(Role,
|
||||||
|
|||||||
@@ -14,6 +14,14 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
{% if person.user == request.user or request.user.is_superuser %}
|
||||||
|
{% if person.user != request.user and request.user.is_superuser %}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>NB:</strong> You are able to see the details of this person because you are an admin.
|
||||||
|
Regular users are not able to see this information for people other than themselves.
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
{% if person.gender %}
|
{% if person.gender %}
|
||||||
<dt>Gender</dt>
|
<dt>Gender</dt>
|
||||||
@@ -38,6 +46,11 @@
|
|||||||
{% if person.organisation %}
|
{% if person.organisation %}
|
||||||
<dt>Organisation</dt>
|
<dt>Organisation</dt>
|
||||||
<dd>{{ person.organisation }}</dd>
|
<dd>{{ person.organisation }}</dd>
|
||||||
|
|
||||||
|
{% if person.organisation_started_date %}
|
||||||
|
<dt>Started Date</dt>
|
||||||
|
<dd>{{ person.organisation_started_date }}</dd>
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if person.job_title %}
|
{% if person.job_title %}
|
||||||
@@ -50,9 +63,9 @@
|
|||||||
<dd>{{ person.role }}</dd>
|
<dd>{{ person.role }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if person.dispipline %}
|
{% if person.disciplines %}
|
||||||
<dt>Discipline</dt>
|
<dt>Discipline(s)</dt>
|
||||||
<dd>{{ person.discipline }}</dd>
|
<dd>{{ person.disciplines }}</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if person.themes.exists %}
|
{% if person.themes.exists %}
|
||||||
@@ -64,6 +77,7 @@
|
|||||||
</dd>
|
</dd>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</dl>
|
</dl>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<a class="btn btn-success"
|
<a class="btn btn-success"
|
||||||
href="{% url 'people:person.update' pk=person.pk %}">Update</a>
|
href="{% url 'people:person.update' pk=person.pk %}">Update</a>
|
||||||
|
|||||||
Reference in New Issue
Block a user