Merge pull request #37 from Southampton-RSG/dev

Merge first batch of changes from user acceptance testing
This commit is contained in:
James Graham
2020-06-26 12:10:39 +01:00
committed by GitHub
9 changed files with 342 additions and 204 deletions

View File

@@ -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'

View File

@@ -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,14 +37,16 @@
{% bootstrap_javascript %} {% bootstrap_javascript %}
{% endif %} {% endif %}
{% if form %}
{{ form.media.css }} {{ form.media.css }}
{% endif %}
{% block extra_head %}{% endblock %} {% block extra_head %}{% endblock %}
</head> </head>
<body> <body>
<div class="content" style="display: flex; flex-direction: column"> <div class="content" style="display: flex; flex-direction: column">
{% block navbar %} {% block navbar %}
<nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container"> <div class="container">
@@ -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>
@@ -158,15 +160,15 @@
<div class="container"> <div class="container">
{% block after_content %}{% endblock %} {% block after_content %}{% endblock %}
</div> </div>
</div> </div>
<footer class="footer bg-light"> <footer class="footer bg-light">
<div class="container"> <div class="container">
<span class="text-muted">{{ settings.PROJECT_LONG_NAME }}</span> <span class="text-muted">{{ settings.PROJECT_LONG_NAME }}</span>
</div> </div>
</footer> </footer>
{% if not 'javascript_in_head'|bootstrap_setting %} {% if not 'javascript_in_head'|bootstrap_setting %}
{% if 'include_jquery'|bootstrap_setting %} {% if 'include_jquery'|bootstrap_setting %}
{# jQuery JavaScript if it is in body #} {# jQuery JavaScript if it is in body #}
{% bootstrap_jquery jquery='include_jquery'|bootstrap_setting %} {% bootstrap_jquery jquery='include_jquery'|bootstrap_setting %}
@@ -174,11 +176,13 @@
{# Bootstrap JavaScript if it is in body #} {# Bootstrap JavaScript if it is in body #}
{% bootstrap_javascript %} {% bootstrap_javascript %}
{% endif %} {% endif %}
{{ form.media.js }} {% if form %}
{{ form.media.js }}
{% endif %}
{% block extra_script %}{% endblock %} {% block extra_script %}{% endblock %}
</body> </body>
</html> </html>

View File

@@ -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

View File

@@ -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')

View 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',
),
]

View 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'),
),
]

View 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', ),
]

View File

@@ -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,

View File

@@ -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>