mirror of
https://github.com/Southampton-RSG/breccia-mapper.git
synced 2026-03-03 03:17:07 +00:00
Merge pull request #123 from mgrove36/dev
Merge development from personal fork
This commit is contained in:
@@ -15,4 +15,7 @@ mail.log/
|
||||
/static/
|
||||
*.sqlite3*
|
||||
*.log*
|
||||
deployment*
|
||||
deployment*
|
||||
|
||||
docs/
|
||||
.readthedocs.yaml
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -25,3 +25,6 @@ deployment-key*
|
||||
/custom
|
||||
staging.yml
|
||||
production.yml
|
||||
|
||||
# Docs local builds
|
||||
/docs/build
|
||||
25
.readthedocs.yaml
Normal file
25
.readthedocs.yaml
Normal file
@@ -0,0 +1,25 @@
|
||||
# .readthedocs.yaml
|
||||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Set the version of Python and other tools you might need
|
||||
build:
|
||||
os: ubuntu-22.04
|
||||
tools:
|
||||
python: "3.11"
|
||||
|
||||
# Build documentation in the docs/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: docs/source/conf.py
|
||||
|
||||
# If using Sphinx, optionally build your docs in additional formats such as PDF
|
||||
formats:
|
||||
- pdf
|
||||
|
||||
# Optionally declare the Python requirements required to build your docs
|
||||
python:
|
||||
install:
|
||||
- requirements: docs/source/requirements.txt
|
||||
5
Caddyfile
Normal file → Executable file
5
Caddyfile
Normal file → Executable file
@@ -4,12 +4,13 @@
|
||||
|
||||
@proxy_paths {
|
||||
not path /static/*
|
||||
not path /media/*
|
||||
}
|
||||
|
||||
reverse_proxy @proxy_paths http://web:8000
|
||||
reverse_proxy @proxy_paths http://server:8000
|
||||
|
||||
log {
|
||||
output stderr
|
||||
format single_field common_log
|
||||
format console
|
||||
}
|
||||
}
|
||||
2
Dockerfile
Normal file → Executable file
2
Dockerfile
Normal file → Executable file
@@ -1,4 +1,4 @@
|
||||
FROM python:3.8-slim
|
||||
FROM python:3.9-slim
|
||||
|
||||
RUN groupadd -r mapper && useradd --no-log-init -r -g mapper mapper
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# BRECcIA Mapper
|
||||
|
||||
[](https://breccia.readthedocs.io/en/latest/?badge=latest)
|
||||
|
||||
BRECcIA Mapper is a web app to collect and explore data about the relationships between researchers and their stakeholders on large-scale, multi-site research projects.
|
||||
This allows researchers to visually represent the relationships between project staff and stakeholders involved in the their project at different points in time.
|
||||
Through this it is possible to explore the extent of networks and change over time, and identify where new relationships can be developed or existing ones strengthened.
|
||||
@@ -14,6 +16,7 @@ Deployment is managed using [Ansible](https://www.ansible.com/) and Docker (http
|
||||
## Contributors
|
||||
|
||||
- James Graham (@jag1g13) - developer
|
||||
- Matthew Grove (@mgrove36) - developer
|
||||
- Genevieve Agaba
|
||||
- Sebastian Reichel
|
||||
- Claire Bedelian
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Generated by Django 4.1.4 on 2023-01-05 16:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('activities', '0006_activity_attendance_optional'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='activity',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='activitymedium',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='activityseries',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='activitytype',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
]
|
||||
@@ -16,18 +16,6 @@ https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
|
||||
Many configuration settings are input from `settings.ini`.
|
||||
The most likely required settings are: SECRET_KEY, DEBUG, ALLOWED_HOSTS, DATABASE_URL, PROJECT_*_NAME, EMAIL_*
|
||||
|
||||
- PARENT_PROJECT_NAME
|
||||
default: Parent Project Name
|
||||
Displayed in templates where the name of the parent project should be used
|
||||
|
||||
- PROJECT_LONG_NAME
|
||||
default: Project Long Name
|
||||
Displayed in templates where the full name of the project should be used
|
||||
|
||||
- PROJECT_SHORT_NAME
|
||||
default: shortname
|
||||
Displayed in templates where a short identifier for the project should be used
|
||||
|
||||
- SECRET_KEY (REQUIRED)
|
||||
Used to generate CSRF tokens - must never be made public
|
||||
|
||||
@@ -118,16 +106,9 @@ import dj_database_url
|
||||
|
||||
SETTINGS_EXPORT = [
|
||||
'DEBUG',
|
||||
'PARENT_PROJECT_NAME',
|
||||
'PROJECT_LONG_NAME',
|
||||
'PROJECT_SHORT_NAME',
|
||||
'GOOGLE_MAPS_API_KEY',
|
||||
]
|
||||
|
||||
PARENT_PROJECT_NAME = config('PARENT_PROJECT_NAME',
|
||||
default='Parent Project Name')
|
||||
PROJECT_LONG_NAME = config('PROJECT_LONG_NAME', default='Project Long Name')
|
||||
PROJECT_SHORT_NAME = config('PROJECT_SHORT_NAME', default='shortname')
|
||||
|
||||
# Build paths inside the project like this: BASE_DIR.joinpath(...)
|
||||
BASE_DIR = pathlib.Path(__file__).parent.parent
|
||||
@@ -165,7 +146,6 @@ THIRD_PARTY_APPS = [
|
||||
'post_office',
|
||||
'bootstrap_datepicker_plus',
|
||||
'hijack',
|
||||
'compat',
|
||||
]
|
||||
|
||||
FIRST_PARTY_APPS = [
|
||||
@@ -184,6 +164,7 @@ MIDDLEWARE = [
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'hijack.middleware.HijackUserMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'breccia_mapper.urls'
|
||||
@@ -297,6 +278,10 @@ STATIC_ROOT = BASE_DIR.joinpath('static')
|
||||
|
||||
STATICFILES_DIRS = [BASE_DIR.joinpath('breccia_mapper', 'static')]
|
||||
|
||||
# Media uploads
|
||||
MEDIA_ROOT = BASE_DIR.joinpath('media')
|
||||
MEDIA_URL = "/media/"
|
||||
|
||||
# Logging - NB the logger name is empty to capture all output
|
||||
|
||||
LOGGING = {
|
||||
@@ -340,6 +325,10 @@ logger = logging.getLogger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
# Admin panel variables
|
||||
|
||||
CONSTANCE_ADDITIONAL_FIELDS = {
|
||||
'image_field': ['django.forms.ImageField', {}]
|
||||
}
|
||||
|
||||
CONSTANCE_CONFIG = {
|
||||
'NOTICE_TEXT': (
|
||||
'',
|
||||
@@ -359,21 +348,105 @@ CONSTANCE_CONFIG = {
|
||||
'RELATIONSHIP_FORM_HELP': (
|
||||
'',
|
||||
'Help text to display at the top of relationship forms.'),
|
||||
'DEPLOYMENT_URL': (
|
||||
'http://localhost',
|
||||
'URL at which this mapper tool is accessible'),
|
||||
'PARENT_PROJECT_NAME': (
|
||||
'',
|
||||
'Parent project name'),
|
||||
'PROJECT_LONG_NAME': (
|
||||
'Project Network Mapper',
|
||||
'Project long name'),
|
||||
'PROJECT_SHORT_NAME': (
|
||||
'Network Mapper',
|
||||
'Project short name'),
|
||||
'PROJECT_LEAD': (
|
||||
'Project Lead',
|
||||
'Project lead'),
|
||||
'PROJECT_TAGLINE': (
|
||||
'Here is your project\'s tagline.',
|
||||
'Project tagline'),
|
||||
'HOMEPAGE_HEADER_IMAGE': (
|
||||
'800x500.png',
|
||||
'Homepage header image',
|
||||
'image_field'),
|
||||
'HOMEPAGE_CARD_1_TITLE': (
|
||||
'Step 1',
|
||||
'Homepage card #1 title'),
|
||||
'HOMEPAGE_CARD_1_DESCRIPTION': (
|
||||
'Tell us about your position within the project',
|
||||
'Homepage card #1 description'),
|
||||
'HOMEPAGE_CARD_1_ICON': (
|
||||
'building-user',
|
||||
'Homepage card #1 icon'),
|
||||
'HOMEPAGE_CARD_2_TITLE': (
|
||||
'Step 2',
|
||||
'Homepage card #2 title'),
|
||||
'HOMEPAGE_CARD_2_DESCRIPTION': (
|
||||
'Describe your relationships with other stakeholders',
|
||||
'Homepage card #2 description'),
|
||||
'HOMEPAGE_CARD_2_ICON': (
|
||||
'handshake-simple',
|
||||
'Homepage card #2 icon'),
|
||||
'HOMEPAGE_CARD_3_TITLE': (
|
||||
'Step 3',
|
||||
'Homepage card #3 title'),
|
||||
'HOMEPAGE_CARD_3_DESCRIPTION': (
|
||||
'Use the network view to build new relationships',
|
||||
'Homepage card #3 description'),
|
||||
'HOMEPAGE_CARD_3_ICON': (
|
||||
'diagram-project',
|
||||
'Homepage card #3 icon'),
|
||||
'HOMEPAGE_ABOUT_TITLE': (
|
||||
'About Us',
|
||||
'Homepage about section title'),
|
||||
'HOMEPAGE_ABOUT_CONTENT': (
|
||||
"""Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. In massa tempor nec feugiat nisl. Eget dolor morbi non arcu risus quis varius quam quisque. Nisl pretium fusce id velit ut tortor pretium viverra suspendisse. Vitae auctor eu augue ut lectus arcu. Tellus molestie nunc non blandit massa enim nec. At consectetur lorem donec massa sapien. Placerat orci nulla pellentesque dignissim enim sit. Sit amet mauris commodo quis imperdiet. Tellus at urna condimentum mattis pellentesque.<br/>In vitae turpis massa sed. Fermentum posuere urna nec tincidunt praesent semper feugiat nibh sed. Ut consequat semper viverra nam libero justo laoreet. Velit ut tortor pretium viverra suspendisse potenti nullam ac tortor. Nunc id cursus metus aliquam eleifend mi in nulla posuere. Aliquam eleifend mi in nulla posuere sollicitudin aliquam. Est ante in nibh mauris cursus mattis molestie a iaculis. Nunc id cursus metus aliquam. Auctor urna nunc id cursus metus aliquam. Porttitor lacus luctus accumsan tortor posuere ac ut consequat semper. Volutpat consequat mauris nunc congue nisi. Leo vel fringilla est ullamcorper eget. Vitae purus faucibus ornare suspendisse sed nisi lacus sed. Massa id neque aliquam vestibulum morbi blandit. Iaculis nunc sed augue lacus viverra vitae congue. Sodales neque sodales ut etiam.""",
|
||||
'Homepage about section content'),
|
||||
'HOMEPAGE_ABOUT_IMAGE': (
|
||||
'400x400.png',
|
||||
'Homepage about section image',
|
||||
'image_field'),
|
||||
} # yapf: disable
|
||||
|
||||
CONSTANCE_CONFIG_FIELDSETS = {
|
||||
'Notice Banner': (
|
||||
'Project options': (
|
||||
'PARENT_PROJECT_NAME',
|
||||
'PROJECT_LONG_NAME',
|
||||
'PROJECT_SHORT_NAME',
|
||||
'PROJECT_LEAD',
|
||||
'PROJECT_TAGLINE',
|
||||
),
|
||||
'Homepage configuration': (
|
||||
'HOMEPAGE_HEADER_IMAGE',
|
||||
'HOMEPAGE_CARD_1_TITLE',
|
||||
'HOMEPAGE_CARD_1_DESCRIPTION',
|
||||
'HOMEPAGE_CARD_1_ICON',
|
||||
'HOMEPAGE_CARD_2_TITLE',
|
||||
'HOMEPAGE_CARD_2_DESCRIPTION',
|
||||
'HOMEPAGE_CARD_2_ICON',
|
||||
'HOMEPAGE_CARD_3_TITLE',
|
||||
'HOMEPAGE_CARD_3_DESCRIPTION',
|
||||
'HOMEPAGE_CARD_3_ICON',
|
||||
'HOMEPAGE_ABOUT_TITLE',
|
||||
'HOMEPAGE_ABOUT_CONTENT',
|
||||
'HOMEPAGE_ABOUT_IMAGE',
|
||||
),
|
||||
'Notice banner': (
|
||||
'NOTICE_TEXT',
|
||||
'NOTICE_CLASS',
|
||||
),
|
||||
'Data Collection': (
|
||||
'CONSENT_TEXT',
|
||||
),
|
||||
'Help Text': (
|
||||
'Help text': (
|
||||
'PERSON_LIST_HELP',
|
||||
'ORGANISATION_LIST_HELP',
|
||||
'RELATIONSHIP_FORM_HELP',
|
||||
),
|
||||
'Deployment': (
|
||||
'DEPLOYMENT_URL',
|
||||
),
|
||||
} # yapf: disable
|
||||
|
||||
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
|
||||
@@ -396,7 +469,7 @@ BOOTSTRAP4 = {
|
||||
EMAIL_HOST = config('EMAIL_HOST', default=None)
|
||||
DEFAULT_FROM_EMAIL = config(
|
||||
'DEFAULT_FROM_EMAIL',
|
||||
default=f'{PROJECT_SHORT_NAME}@localhost.localdomain')
|
||||
default=f'{CONSTANCE_CONFIG["PROJECT_SHORT_NAME"][0]}@localhost.localdomain')
|
||||
SERVER_EMAIL = DEFAULT_FROM_EMAIL
|
||||
|
||||
if EMAIL_HOST is None:
|
||||
@@ -417,6 +490,18 @@ else:
|
||||
default=(EMAIL_PORT == 465),
|
||||
cast=bool)
|
||||
|
||||
# Bootstrap Datepicker Plus Settings
|
||||
BOOTSTRAP_DATEPICKER_PLUS = {
|
||||
"variant_options": {
|
||||
"date": {
|
||||
"format": "%Y-%m-%d",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
# Database default automatic primary key
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# Upstream API keys
|
||||
|
||||
GOOGLE_MAPS_API_KEY = config('GOOGLE_MAPS_API_KEY', default=None)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
header.masthead {
|
||||
position: relative;
|
||||
background: #343a40 no-repeat center;
|
||||
-webkit-background-size: contain;
|
||||
-moz-background-size: contain;
|
||||
-o-background-size: contain;
|
||||
background-size: contain;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
background-size: cover;
|
||||
padding-top: 8rem;
|
||||
padding-bottom: 8rem;
|
||||
min-height: 400px;
|
||||
|
||||
58
breccia_mapper/templates/base.html
Normal file → Executable file
58
breccia_mapper/templates/base.html
Normal file → Executable file
@@ -10,27 +10,29 @@
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<title>{{ settings.PROJECT_LONG_NAME }}</title>
|
||||
<title>{{ config.PROJECT_LONG_NAME }}</title>
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
{% bootstrap_css %}
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/fontawesome.min.css"
|
||||
integrity="sha256-/sdxenK1NDowSNuphgwjv8wSosSNZB0t5koXqd7XqOI="
|
||||
crossorigin="anonymous" />
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css"
|
||||
integrity="sha512-giQeaPns4lQTBMRpOOHsYnGw1tGVzbAIHUyHRgn7+6FmiEgGGjaG0T2LZJmAPMzRCl+Cug0ItQ2xDZpTmEc+CQ=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
|
||||
<link rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/solid.min.css"
|
||||
integrity="sha256-8DcgqUGhWHHsTLj1qcGr0OuPbKkN1RwDjIbZ6DKh/RA="
|
||||
crossorigin="anonymous" />
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/solid.min.css"
|
||||
integrity="sha512-6mc0R607di/biCutMUtU9K7NtNewiGQzrvWX4bWTeqmljZdJrwYvKJtnhgR+Ryvj+NRJ8+NnnCM/biGqMe/iRA=="
|
||||
crossorigin="anonymous"
|
||||
referrerpolicy="no-referrer" />
|
||||
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<link rel="stylesheet" href="{% static 'css/global.css' %}">
|
||||
|
||||
<link rel="stylesheet"
|
||||
type="text/css"
|
||||
href="{% static 'hijack/hijack-styles.css' %}" />
|
||||
href="{% static 'hijack/hijack.min.css' %}" />
|
||||
|
||||
{% if 'javascript_in_head'|bootstrap_setting %}
|
||||
{% if 'include_jquery'|bootstrap_setting %}
|
||||
@@ -56,7 +58,7 @@
|
||||
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
|
||||
<div class="container">
|
||||
<a href="{% url 'index' %}" class="navbar-brand">
|
||||
{{ settings.PROJECT_SHORT_NAME }}
|
||||
{{ config.PROJECT_SHORT_NAME }}
|
||||
</a>
|
||||
|
||||
<button type="button" class="navbar-toggler"
|
||||
@@ -107,13 +109,13 @@
|
||||
<li class="nav-item">
|
||||
{% if request.user.person %}
|
||||
<a href="{% url 'people:person.profile' %}" class="nav-link">
|
||||
<i class="fas fa-user-circle"></i>
|
||||
<i class="fa-solid fa-circle-user"></i>
|
||||
{{ request.user }}
|
||||
</a>
|
||||
|
||||
{% else %}
|
||||
<a href="{% url 'people:person.create' %}?user" class="nav-link">
|
||||
<i class="fas fa-user-circle"></i>
|
||||
<i class="fa-solid fa-circle-user"></i>
|
||||
{{ request.user }}
|
||||
</a>
|
||||
|
||||
@@ -122,7 +124,7 @@
|
||||
|
||||
<li class="nav-item">
|
||||
<a href="{% url 'logout' %}" class="nav-link">
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
<i class="fa-solid fa-right-from-bracket"></i>
|
||||
Log Out
|
||||
</a>
|
||||
</li>
|
||||
@@ -130,7 +132,7 @@
|
||||
{% else %}
|
||||
<li class="nav-item">
|
||||
<a href="{% url 'login' %}" class="nav-link">
|
||||
<i class="fas fa-sign-in-alt"></i>
|
||||
<i class="fa-solid fa-right-to-bracket"></i>
|
||||
Log In
|
||||
</a>
|
||||
</li>
|
||||
@@ -149,8 +151,30 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% load hijack_tags %}
|
||||
{% hijack_notification %}
|
||||
{% load hijack %}
|
||||
|
||||
{# Hijack notification if user is hijacked #}
|
||||
{% if person.user == request.user and request.user.is_hijacked %}
|
||||
<div class="djhj" id="djhj">
|
||||
<div class="djhj-notification">
|
||||
<div class="djhj-message">
|
||||
{% blocktrans trimmed with user=request.user %}
|
||||
You are currently working on behalf of <em>{{ user }}</em>.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
<form action="{% url 'hijack:release' %}" method="POST" class="djhj-actions">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.path }}">
|
||||
<button class="djhj-button" onclick="document.getElementById('djhj').style.display = 'none';" type="button">
|
||||
{% trans 'hide' %}
|
||||
</button>
|
||||
<button class="djhj-button" type="submit">
|
||||
{% trans 'release' %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.is_authenticated and not request.user.has_person %}
|
||||
<div class="alert alert-info rounded-0" role="alert">
|
||||
@@ -189,7 +213,7 @@
|
||||
|
||||
<footer class="footer bg-light">
|
||||
<div class="container">
|
||||
<span class="text-muted">{{ settings.PROJECT_LONG_NAME }}</span>
|
||||
<span class="text-muted">{{ config.PROJECT_LONG_NAME }}</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -1,62 +1,89 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block extra_head %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<link rel="stylesheet"
|
||||
href="{% static 'css/masthead.css' %}">
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block before_content %}
|
||||
|
||||
{% get_media_prefix as MEDIA_URL %}
|
||||
<header class="container-fluid masthead text-white text-left"
|
||||
style="background-image: url('https://via.placeholder.com/800x500')">
|
||||
style="background-image: url('{{ MEDIA_URL }}{{ config.HOMEPAGE_HEADER_IMAGE }}')">
|
||||
<div class="overlay"></div>
|
||||
|
||||
<div class="row">
|
||||
<div class="ml-5 px-4 mt-3 pt-3 textbox-container">
|
||||
<h1 class="display-1">{{ settings.PROJECT_LONG_NAME }}</h1>
|
||||
<p class="lead">Snappy leader here...</p>
|
||||
<h1 class="display-1">{{ config.PROJECT_LONG_NAME }}</h1>
|
||||
<p class="lead">{{ config.PROJECT_LEAD }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="bg-secondary py-3">
|
||||
<div class="container text-white">
|
||||
<h2>Snappy tagline here...</h2>
|
||||
<h2>{{ config.PROJECT_TAGLINE }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-light py-2 mb-4">
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
{% if config.HOMEPAGE_CARD_1_TITLE %}
|
||||
<div class="col-md-4 mx-auto">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Do Feature 1</h2>
|
||||
<h2 class="card-title">{{ config.HOMEPAGE_CARD_1_TITLE }}</h2>
|
||||
|
||||
<span class="fas fa-5x fa-atlas"></span>
|
||||
{% if config.HOMEPAGE_CARD_1_DESCRIPTION %}
|
||||
<p>{{ config.HOMEPAGE_CARD_1_DESCRIPTION|safe }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if config.HOMEPAGE_CARD_1_ICON %}
|
||||
<span class="fa-solid fa-5x fa-{{ config.HOMEPAGE_CARD_1_ICON }}"></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-4">
|
||||
{% if config.HOMEPAGE_CARD_2_TITLE %}
|
||||
<div class="col-md-4 mx-auto">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Do Feature 2</h2>
|
||||
<h2 class="card-title">{{ config.HOMEPAGE_CARD_2_TITLE }}</h2>
|
||||
|
||||
<span class="fas fa-5x fa-atlas"></span>
|
||||
{% if config.HOMEPAGE_CARD_2_DESCRIPTION %}
|
||||
<p>{{ config.HOMEPAGE_CARD_2_DESCRIPTION|safe }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if config.HOMEPAGE_CARD_2_ICON %}
|
||||
<span class="fa-solid fa-5x fa-{{ config.HOMEPAGE_CARD_2_ICON }}"></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-4">
|
||||
{% if config.HOMEPAGE_CARD_3_TITLE %}
|
||||
<div class="col-md-4 mx-auto">
|
||||
<div class="card text-center">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Do Feature 3</h2>
|
||||
<h2 class="card-title">{{ config.HOMEPAGE_CARD_3_TITLE }}</h2>
|
||||
|
||||
<span class="fas fa-5x fa-atlas"></span>
|
||||
{% if config.HOMEPAGE_CARD_3_DESCRIPTION %}
|
||||
<p>{{ config.HOMEPAGE_CARD_3_DESCRIPTION|safe }}</p>
|
||||
{% endif %}
|
||||
|
||||
{% if config.HOMEPAGE_CARD_3_ICON %}
|
||||
<span class="fa-solid fa-5x fa-{{ config.HOMEPAGE_CARD_3_ICON }}"></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -65,15 +92,16 @@
|
||||
{% block content %}
|
||||
<div class="row align-items-center" style="min-height: 400px;">
|
||||
<div class="col-sm-8">
|
||||
<h2 class="pb-2">About {{ settings.PROJECT_LONG_NAME }}</h2>
|
||||
<h2 class="pb-2">{{ config.HOMEPAGE_ABOUT_TITLE }}</h2>
|
||||
|
||||
<p>
|
||||
{{ settings.PROJECT_LONG_NAME }} is...
|
||||
{{ config.HOMEPAGE_ABOUT_CONTENT|safe }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-4">
|
||||
<img class="img-fluid py-3" src="https://via.placeholder.com/400x400">
|
||||
{% get_media_prefix as MEDIA_URL %}
|
||||
<img class="img-fluid py-3" src="{{ MEDIA_URL }}{{ config.HOMEPAGE_ABOUT_IMAGE }}">
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -14,10 +14,17 @@ Including another URLconf
|
||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from django.conf import settings
|
||||
from django.urls import include, path
|
||||
from django.conf.urls.static import static
|
||||
|
||||
from . import views
|
||||
|
||||
from constance import config
|
||||
|
||||
admin.site.site_header = config.PROJECT_LONG_NAME + " Admin"
|
||||
admin.site.site_title = config.PROJECT_SHORT_NAME + " Admin"
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/',
|
||||
admin.site.urls),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
all:
|
||||
hosts:
|
||||
example.com:
|
||||
django_debug: 1
|
||||
django_debug: 0
|
||||
django_secret_key: debug_only_g62WlORMbo8iAcV7vKCKBQ==
|
||||
|
||||
@@ -11,9 +11,13 @@
|
||||
register: vagrant_dir
|
||||
|
||||
vars:
|
||||
project_name: mapper
|
||||
project_name: network-mapper
|
||||
project_dir: /srv/{{ project_name }}
|
||||
project_src_dir: "{{ project_dir }}/src"
|
||||
provision_superuser: false
|
||||
superuser_username: admin
|
||||
superuser_password: admin
|
||||
superuser_email: email@example.com
|
||||
|
||||
tasks:
|
||||
- name: Vagrant specific tasks
|
||||
@@ -41,9 +45,10 @@
|
||||
|
||||
- name: Clone / update from source repos
|
||||
ansible.builtin.git:
|
||||
repo: 'https://github.com/Southampton-RSG/breccia-mapper.git'
|
||||
# repo: 'https://github.com/Southampton-RSG/breccia-mapper.git'
|
||||
repo: 'https://github.com/mgrove36/breccia-network-mapper.git'
|
||||
dest: '{{ project_src_dir }}'
|
||||
version: docker
|
||||
version: dev # master
|
||||
accept_hostkey: yes
|
||||
|
||||
- name: Copy template files
|
||||
@@ -55,6 +60,12 @@
|
||||
- Caddyfile
|
||||
- docker-compose.yml
|
||||
|
||||
- name: Copy settings file
|
||||
ansible.builtin.copy:
|
||||
src: 'settings.ini'
|
||||
dest: '{{ project_src_dir }}/breccia_mapper/settings.ini'
|
||||
mode: 0600
|
||||
|
||||
- name: Create database file
|
||||
ansible.builtin.file:
|
||||
path: "{{ project_dir }}/db.sqlite3"
|
||||
@@ -78,9 +89,21 @@
|
||||
chdir: "{{ project_dir }}"
|
||||
cmd: docker compose build {{ item }}
|
||||
loop:
|
||||
- web
|
||||
- server
|
||||
|
||||
- name: Start containers
|
||||
ansible.builtin.command:
|
||||
chdir: "{{ project_dir }}"
|
||||
cmd: docker compose up -d
|
||||
|
||||
- name: Provision superuser
|
||||
ansible.builtin.command:
|
||||
chdir: "{{ project_dir }}"
|
||||
cmd: sudo docker compose exec -it server /bin/bash -c "DJANGO_SUPERUSER_USERNAME='{{ superuser_username }}' DJANGO_SUPERUSER_PASSWORD='{{ superuser_password }}' DJANGO_SUPERUSER_EMAIL='{{ superuser_email }}' /app/manage.py createsuperuser --no-input"
|
||||
when: provision_superuser
|
||||
|
||||
- name: Display warning about new superuser
|
||||
debug:
|
||||
msg:
|
||||
- "[WARNING] A superuser has been provisioned with the username \"{{ superuser_username }}\" and password that was provided. This user has unlimited access to the network mapper."
|
||||
when: provision_superuser
|
||||
88
deploy/settings.example.ini
Normal file
88
deploy/settings.example.ini
Normal file
@@ -0,0 +1,88 @@
|
||||
[settings]
|
||||
|
||||
; Allowed hosts
|
||||
; Accepted values for server header in request - protects against CSRF and CSS attacks
|
||||
; Default: * if DEBUG else localhost
|
||||
# ALLOWED_HOSTS=* if DEBUG else localhost
|
||||
|
||||
; Database URL
|
||||
; URL to database - uses format described at https://github.com/jacobian/dj-database-url
|
||||
; Default: sqlite://db.sqlite3
|
||||
# DATABASE_URL=sqlite://db.sqlite3
|
||||
|
||||
; Database backup storage location
|
||||
; Directory where database backups should be stored
|
||||
; Default: .dbbackup
|
||||
# DBBACKUP_STORAGE_LOCATION=.dbbackup
|
||||
|
||||
; Default language
|
||||
; Default language - used for translation - has not been enabled
|
||||
; Default: en-gb
|
||||
# LANGUAGE_CODE=en-gb
|
||||
|
||||
; Timezone
|
||||
; Default timezone
|
||||
; Default: UTC
|
||||
# TIME_ZONE=UTC
|
||||
|
||||
; Logging level
|
||||
; Level of messages written to log file
|
||||
; Default: INFO
|
||||
# LOG_LEVEL=INFO
|
||||
|
||||
; Logging filename
|
||||
; Path to logfile
|
||||
; Default: debug.log
|
||||
# LOG_FILENAME=debug.log
|
||||
|
||||
; Logging duration
|
||||
; Number of days of logs to keep - logfile is rotated out at the end of each day
|
||||
; Default: 14
|
||||
# LOG_DAYS=14
|
||||
|
||||
; STMP host
|
||||
; Hostname of SMTP server
|
||||
; Default: None
|
||||
# EMAIL_HOST=None
|
||||
|
||||
; Default from email address
|
||||
; Email address from which messages are sent
|
||||
; Default: None
|
||||
# DEFAULT_FROM_EMAIL=None
|
||||
|
||||
; [DEBUG ONLY] Email file path
|
||||
; Directory where emails will be stored if not using an SMTP server
|
||||
; Default: mail.log
|
||||
# EMAIL_FILE_PATH=mail.log
|
||||
|
||||
; SMTP username
|
||||
; Username to authenticate with SMTP server
|
||||
; Default: None
|
||||
# EMAIL_HOST_USER=None
|
||||
|
||||
; SMTP password
|
||||
; Password to authenticate with SMTP server
|
||||
; Default: None
|
||||
# EMAIL_HOST_PASSWORD=None
|
||||
|
||||
; SMTP port
|
||||
; Port to access on SMTP server
|
||||
; Default: 25
|
||||
# EMAIL_PORT=25
|
||||
|
||||
; SMTP use TLS
|
||||
; Use TLS to communicate with SMTP server? Usually on port 587
|
||||
; Cannot be enabled at the same time as EMAIL_USE_SSL
|
||||
; Default: True if EMAIL_PORT == 587 else False
|
||||
# EMAIL_USE_TLS=True if EMAIL_PORT == 587 else False
|
||||
|
||||
; SMTP use SSL
|
||||
; Use SSL to communicate with SMTP server? Usually on port 465
|
||||
; Cannot be enabled at the same time as EMAIL_USE_TLS
|
||||
; Default: True if EMAIL_PORT == 465 else False
|
||||
# EMAIL_USE_SSL=True if EMAIL_PORT == 465 else False
|
||||
|
||||
; Google Maps API key
|
||||
; Google Maps API key to display maps of people's locations
|
||||
; Default: None
|
||||
# GOOGLE_MAPS_API_KEY=None
|
||||
7
deploy/templates/Caddyfile.j2
Normal file → Executable file
7
deploy/templates/Caddyfile.j2
Normal file → Executable file
@@ -1,15 +1,16 @@
|
||||
http://* {
|
||||
:80 :443 {
|
||||
root * /srv
|
||||
file_server
|
||||
|
||||
@proxy_paths {
|
||||
not path /static/*
|
||||
not path /media/*
|
||||
}
|
||||
|
||||
reverse_proxy @proxy_paths http://web:8000
|
||||
reverse_proxy @proxy_paths http://server:8000
|
||||
|
||||
log {
|
||||
output stderr
|
||||
format single_field common_log
|
||||
format console
|
||||
}
|
||||
}
|
||||
11
deploy/templates/docker-compose.yml.j2
Normal file → Executable file
11
deploy/templates/docker-compose.yml.j2
Normal file → Executable file
@@ -1,8 +1,9 @@
|
||||
version: '3.1'
|
||||
|
||||
services:
|
||||
web:
|
||||
image: breccia-mapper
|
||||
server:
|
||||
image: breccia-network-mapper
|
||||
container_name: network-mapper-server
|
||||
build: {{ project_src_dir }}
|
||||
ports:
|
||||
- 8000:8000
|
||||
@@ -13,9 +14,11 @@ services:
|
||||
volumes:
|
||||
- {{ project_dir }}/db.sqlite3:/app/db.sqlite3:z
|
||||
- static_files:/app/static
|
||||
- media_files:/app/media
|
||||
|
||||
caddy:
|
||||
image: caddy:2
|
||||
container_name: network-mapper-caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
@@ -24,12 +27,14 @@ services:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:z
|
||||
# Caddy serves static files collected by Django
|
||||
- static_files:/srv/static:ro
|
||||
- media_files:/srv/media:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
depends_on:
|
||||
- web
|
||||
- server
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
static_files:
|
||||
media_files:
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
version: '3.1'
|
||||
|
||||
services:
|
||||
web:
|
||||
image: breccia-mapper
|
||||
server:
|
||||
image: breccia-network-mapper
|
||||
container_name: network-mapper-server
|
||||
build: .
|
||||
ports:
|
||||
- 8000:8000
|
||||
@@ -13,9 +14,11 @@ services:
|
||||
volumes:
|
||||
- ./db.sqlite3:/app/db.sqlite3:z
|
||||
- static_files:/app/static
|
||||
- media_files:/app/media
|
||||
|
||||
caddy:
|
||||
image: caddy:2
|
||||
container_name: network-mapper-caddy
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 80:80
|
||||
@@ -24,12 +27,14 @@ services:
|
||||
- ./Caddyfile:/etc/caddy/Caddyfile:z
|
||||
# Caddy serves static files collected by Django
|
||||
- static_files:/srv/static:ro
|
||||
- media_files:/srv/media:ro
|
||||
- caddy_data:/data
|
||||
- caddy_config:/config
|
||||
depends_on:
|
||||
- web
|
||||
- server
|
||||
|
||||
volumes:
|
||||
caddy_data:
|
||||
caddy_config:
|
||||
static_files:
|
||||
media_files:
|
||||
|
||||
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
||||
# Minimal makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line, and also
|
||||
# from the environment for the first two.
|
||||
SPHINXOPTS ?=
|
||||
SPHINXBUILD ?= sphinx-build
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
||||
# Put it first so that "make" without argument is like "make help".
|
||||
help:
|
||||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
|
||||
.PHONY: help Makefile
|
||||
|
||||
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||
%: Makefile
|
||||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||
35
docs/make.bat
Normal file
35
docs/make.bat
Normal file
@@ -0,0 +1,35 @@
|
||||
@ECHO OFF
|
||||
|
||||
pushd %~dp0
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set SOURCEDIR=source
|
||||
set BUILDDIR=build
|
||||
|
||||
%SPHINXBUILD% >NUL 2>NUL
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.https://www.sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
goto end
|
||||
|
||||
:help
|
||||
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
|
||||
|
||||
:end
|
||||
popd
|
||||
33
docs/source/conf.py
Normal file
33
docs/source/conf.py
Normal file
@@ -0,0 +1,33 @@
|
||||
# Configuration file for the Sphinx documentation builder.
|
||||
#
|
||||
# For the full list of built-in configuration values, see the documentation:
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
|
||||
|
||||
project = 'BRECcIA Network Mapper'
|
||||
copyright = 'Matthew Grove, University of Southampton'
|
||||
author = 'Matthew Grove'
|
||||
release = '1.1'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.duration',
|
||||
'myst_parser',
|
||||
]
|
||||
|
||||
myst_enable_extensions = ['colon_fence']
|
||||
|
||||
templates_path = ['_templates']
|
||||
exclude_patterns = []
|
||||
|
||||
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
|
||||
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_static_path = ['_static']
|
||||
147
docs/source/deployment.md
Normal file
147
docs/source/deployment.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# Deployment
|
||||
|
||||
The BRECcIA Network Mapper can be deployed in a variety of ways, most of which utilise Docker.
|
||||
Ansible deployment has been tested on RHEL7 and RHEL8.
|
||||
|
||||
## Development Deployment
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- [Vagrant](https://www.vagrantup.com/)
|
||||
- [Ansible](https://www.ansible.com/)
|
||||
|
||||
Using Vagrant, we can create a virtual machine and deploy BRECcIA Mapper using the same provisioning scripts as a production deployment.
|
||||
To deploy a local development version of BRECcIA Mapper inside a virtual machine, first navigate to the `deploy` folder:
|
||||
|
||||
```bash
|
||||
cd deploy
|
||||
```
|
||||
|
||||
And then set your config options for the deployment, by copying `settings.example.ini` to `settings.ini` and changing the options contained within as required.
|
||||
|
||||
And then start the virtual machine using:
|
||||
|
||||
```bash
|
||||
vagrant up
|
||||
```
|
||||
|
||||
If you would like a new superuser to be provisioned when deploying the network mapper, change the following line in `playbook.yml`:
|
||||
|
||||
```yaml
|
||||
provision_superuser: false
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```yaml
|
||||
provision_superuser: true
|
||||
```
|
||||
|
||||
And change the `superuser_*` options below it as desired.
|
||||
|
||||
Then provision the virtual machine (deploying the network mapper) using:
|
||||
|
||||
```bash
|
||||
vagrant provision
|
||||
```
|
||||
|
||||
This installs the network mapper and makes it available on the local machine at `http://localhost:8080`.
|
||||
If you wish to make this accessible from other devices on your local network, replace the following line in `deploy/Vagrantfile`:
|
||||
|
||||
```ruby
|
||||
config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```ruby
|
||||
config.vm.network "forwarded_port", guest: 80, host: 8080
|
||||
```
|
||||
|
||||
To stop the virtual machine run the following, in the `deploy` directory:
|
||||
|
||||
```
|
||||
vagrant halt
|
||||
```
|
||||
|
||||
For further commands see the [Vagrant documentation](https://www.vagrantup.com/docs/cli).
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Ansible (Recommended)
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- [Ansible](https://www.ansible.com/)
|
||||
|
||||
:::{note}
|
||||
Deployment with Ansible has been tested on RHEL7 and RHEL8, but is compatible with other Linux distributions with minor changes to the playbook (`deploy/playbook.yml`)
|
||||
:::
|
||||
|
||||
To deploy the BRECcIA Network Mapper with Ansible:
|
||||
|
||||
1. Copy `settings.example.ini` to `settings.ini`
|
||||
2. Edit this file as desired. Note there is no requirement to change any of these variables, but it is recommended.
|
||||
3. Copy `inventory.example.yml` to `inventory.yml`
|
||||
4. Edit this file to reflect your Ansible setup:
|
||||
- Use your server's hostname instead of `example.com`
|
||||
- Replace the secret key with some text known only to you
|
||||
5. If you would like a new superuser to be provisioned for the network mapper (e.g. during initial install), edit the following line of `playbook.yml`:
|
||||
|
||||
```yaml
|
||||
provision_superuser: false
|
||||
```
|
||||
|
||||
to
|
||||
|
||||
```yaml
|
||||
provision_superuser: true
|
||||
```
|
||||
|
||||
And change the `superuser_*` options below it as desired.
|
||||
|
||||
6. Run the Ansible playbook `playbook.yml` with this inventory file using:
|
||||
|
||||
```
|
||||
ansible-playbook playbook.yml -i inventory.yml -K -k -u <SSH username>
|
||||
```
|
||||
|
||||
This will ask for your SSH and sudo passwords for the server before deploying.
|
||||
To redeploy updates, the same command can be run again - it's safe to redeploy on top of an existing deployment.
|
||||
|
||||
### Docker Compose
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- [Docker Compose](https://docs.docker.com/compose) (installed by default with most [Docker](https://docker.com/) installs)
|
||||
|
||||
:::{note}
|
||||
Deployment with Docker has been tested on RHEL7, RHEL8, and Ubuntu 22.04 LTS
|
||||
:::
|
||||
|
||||
To deploy the BRECcIA Network Mapper with Docker:
|
||||
|
||||
1. Copy `deploy/settings.example.ini` to `breccia_mapper/settings.ini`
|
||||
2. Edit this file as desired. Note there is no requirement to change any of these variables, but it is recommended.
|
||||
3. Create the database using:
|
||||
|
||||
```bash
|
||||
touch db.sqlite3
|
||||
```
|
||||
|
||||
4. Set the `DEBUG` and `SECRET_KEY` values in `docker-compose.yml`.
|
||||
- The secret key should be a long, random string that only you know. Replace `${DJANGO_SECRET_KEY}` with this key.
|
||||
- Debug can be `True` or `False`. Replace `${DJANGO_DEBUG}` with this value.
|
||||
- You can also set these via environment variables on the host machine. The appropriate environment variables are `DJANGO_SECRET_KEY` and `DJANGO_DEBUG`.
|
||||
5. Start the containers with the following command (you may need to use `sudo`):
|
||||
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
6. Create a superuser by running the following, and enter their details when prompted:
|
||||
|
||||
```bash
|
||||
docker compose exec -it server /bin/bash -c "/app/manage.py createsuperuser"
|
||||
```
|
||||
|
||||
17
docs/source/index.md
Normal file
17
docs/source/index.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# BRECcIA Network Mapper documentation
|
||||
|
||||
The BRECcIA Network Mapper is a web app designed to track and quantify personnel networks & relationships - primarily developed for use in research projects. It is designed for global use across many different organisations involved in a single project, and has been utilised as part of [BRECcIA](https://gcrf-breccia.com) itself.
|
||||
|
||||
This work was funded through the "Building REsearch Capacity for sustainable water and food security In drylands of sub-saharan Africa" (BRECcIA) project which is supported by UK Research and Innovation as part of the Global Challenges Research Fund, grant number NE/P021093/1.
|
||||
|
||||
:::{note}
|
||||
This project is still under development until April 2023.
|
||||
:::
|
||||
|
||||
## Contents
|
||||
|
||||
```{toctree}
|
||||
:maxdepth: 4
|
||||
:glob:
|
||||
*
|
||||
```
|
||||
1
docs/source/requirements.txt
Normal file
1
docs/source/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
myst-parser
|
||||
BIN
media/400x400.png
Normal file
BIN
media/400x400.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 KiB |
BIN
media/800x500.png
Normal file
BIN
media/800x500.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
@@ -6,9 +6,9 @@
|
||||
"description": "Default welcome email template",
|
||||
"created": "2020-04-27T12:13:30.448Z",
|
||||
"last_updated": "2020-04-27T14:45:27.152Z",
|
||||
"subject": "Welcome to {{settings.PROJECT_LONG_NAME}}",
|
||||
"content": "Dear {{ user.get_full_name }},\r\n\r\nWelcome to {{ settings.PROJECT_LONG_NAME }}.\r\n\r\nThanks,\r\n\r\nThe {{ settings.PROJECT_LONG_NAME }} team",
|
||||
"html_content": "<h1>{{ settings.PROJECT_LONG_NAME }}</h1>\r\n\r\nDear {{ user.get_full_name }},\r\n\r\nWelcome to {{ settings.PROJECT_LONG_NAME }}.\r\n\r\nThanks,\r\n\r\nThe {{ settings.PROJECT_LONG_NAME }} team",
|
||||
"subject": "Welcome to {{config.PROJECT_LONG_NAME}}",
|
||||
"content": "Dear user,\r\n\r\nWelcome to {{ config.PROJECT_LONG_NAME }}. You can sign in at {{ config.DEPLOYMENT_URL }}.\r\n\r\nThanks,\r\n\r\nThe {{ config.PROJECT_SHORT_NAME }} team",
|
||||
"html_content": "<h1>{{ config.PROJECT_LONG_NAME }}</h1><br/><p>Dear user,</p><br/><p>Welcome to {{ config.PROJECT_LONG_NAME }}. You can sign in <a href='{{ config.DEPLOYMENT_URL }}'>here</a>.</p><br/><p>Thanks,</p><p>The {{ config.PROJECT_SHORT_NAME }} team</p>",
|
||||
"language": "",
|
||||
"default_template": null
|
||||
}
|
||||
|
||||
15
people/forms.py
Normal file → Executable file
15
people/forms.py
Normal file → Executable file
@@ -3,9 +3,10 @@
|
||||
import typing
|
||||
|
||||
from django import forms
|
||||
from django.conf import settings
|
||||
|
||||
from bootstrap_datepicker_plus import DatePickerInput
|
||||
from constance import config
|
||||
|
||||
from bootstrap_datepicker_plus.widgets import DatePickerInput
|
||||
from django_select2.forms import ModelSelect2Widget, Select2Widget, Select2MultipleWidget
|
||||
|
||||
from . import models
|
||||
@@ -121,7 +122,7 @@ class OrganisationAnswerSetForm(forms.ModelForm, DynamicAnswerSetBase):
|
||||
]
|
||||
labels = {
|
||||
'is_partner_organisation':
|
||||
f'Is this organisation a {settings.PARENT_PROJECT_NAME} partner organisation?'
|
||||
f'Is this organisation a {config.PARENT_PROJECT_NAME} partner organisation?'
|
||||
}
|
||||
widgets = {
|
||||
'countries': Select2MultipleWidget(),
|
||||
@@ -185,14 +186,14 @@ class PersonAnswerSetForm(forms.ModelForm, DynamicAnswerSetBase):
|
||||
widgets = {
|
||||
'nationality': Select2MultipleWidget(),
|
||||
'country_of_residence': Select2Widget(),
|
||||
'organisation_started_date': DatePickerInput(format='%Y-%m-%d'),
|
||||
'project_started_date': DatePickerInput(format='%Y-%m-%d'),
|
||||
'organisation_started_date': DatePickerInput(),
|
||||
'project_started_date': DatePickerInput(),
|
||||
'latitude': forms.HiddenInput,
|
||||
'longitude': forms.HiddenInput,
|
||||
}
|
||||
labels = {
|
||||
'project_started_date':
|
||||
f'Date started on the {settings.PARENT_PROJECT_NAME} project',
|
||||
f'Date started on the {config.PARENT_PROJECT_NAME} project',
|
||||
'external_organisations':
|
||||
'Please list the main organisations external to BRECcIA work that you have been working with since 1st January 2019 that are involved in food/water security in African dryland regions'
|
||||
}
|
||||
@@ -325,7 +326,7 @@ class OrganisationRelationshipAnswerSetForm(forms.ModelForm,
|
||||
class DateForm(forms.Form):
|
||||
date = forms.DateField(
|
||||
required=False,
|
||||
widget=DatePickerInput(format='%Y-%m-%d'),
|
||||
widget=DatePickerInput(),
|
||||
help_text='Show relationships as they were on this date'
|
||||
)
|
||||
|
||||
|
||||
4
people/migrations/0002_add_relationship_models.py
Normal file → Executable file
4
people/migrations/0002_add_relationship_models.py
Normal file → Executable file
@@ -50,10 +50,10 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='relationshipquestionchoice',
|
||||
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer'),
|
||||
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer_relationshipquestionchoice'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='relationship',
|
||||
constraint=models.UniqueConstraint(fields=('source', 'target'), name='unique_relationship'),
|
||||
constraint=models.UniqueConstraint(fields=('source', 'target'), name='unique_relationship_relationship'),
|
||||
),
|
||||
]
|
||||
|
||||
2
people/migrations/0022_refactor_person_questions.py
Normal file → Executable file
2
people/migrations/0022_refactor_person_questions.py
Normal file → Executable file
@@ -50,6 +50,6 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='personquestionchoice',
|
||||
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer'),
|
||||
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer_personquestionchoice'),
|
||||
),
|
||||
]
|
||||
|
||||
2
people/migrations/0035_add_organisation_questions.py
Normal file → Executable file
2
people/migrations/0035_add_organisation_questions.py
Normal file → Executable file
@@ -56,6 +56,6 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='organisationquestionchoice',
|
||||
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer'),
|
||||
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer_organisationquestionchoice'),
|
||||
),
|
||||
]
|
||||
|
||||
4
people/migrations/0039_add_organisation_relationship.py
Normal file → Executable file
4
people/migrations/0039_add_organisation_relationship.py
Normal file → Executable file
@@ -67,10 +67,10 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='organisationrelationshipquestionchoice',
|
||||
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer'),
|
||||
constraint=models.UniqueConstraint(fields=('question', 'text'), name='unique_question_answer_organisationrelationshipquestionchoice'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='organisationrelationship',
|
||||
constraint=models.UniqueConstraint(fields=('source', 'target'), name='unique_relationship'),
|
||||
constraint=models.UniqueConstraint(fields=('source', 'target'), name='unique_relationship_organisationrelationship'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
# Generated by Django 4.1.4 on 2023-01-05 16:57
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('people', '0054_add_option_for_auto_negative_response'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveConstraint(
|
||||
model_name='organisationrelationship',
|
||||
name='unique_relationship_organisationrelationship',
|
||||
),
|
||||
migrations.RemoveConstraint(
|
||||
model_name='relationship',
|
||||
name='unique_relationship_relationship',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organisation',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organisationanswerset',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organisationquestion',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organisationquestionchoice',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organisationrelationship',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organisationrelationshipanswerset',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organisationrelationshipquestion',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='organisationrelationshipquestionchoice',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='person',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='personanswerset',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='personquestion',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='personquestionchoice',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='relationship',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='relationshipanswerset',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='relationshipquestion',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='relationshipquestionchoice',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='first_name',
|
||||
field=models.CharField(blank=True, max_length=150, verbose_name='first name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='user',
|
||||
name='id',
|
||||
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='organisationrelationship',
|
||||
constraint=models.UniqueConstraint(fields=('source', 'target'), name='unique_relationship_modelorganisationrelationship'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='relationship',
|
||||
constraint=models.UniqueConstraint(fields=('source', 'target'), name='unique_relationship_modelrelationship'),
|
||||
),
|
||||
]
|
||||
5
people/models/organisation.py
Normal file → Executable file
5
people/models/organisation.py
Normal file → Executable file
@@ -35,6 +35,11 @@ class OrganisationQuestionChoice(QuestionChoice):
|
||||
on_delete=models.CASCADE,
|
||||
blank=False,
|
||||
null=False)
|
||||
class Meta(QuestionChoice.Meta):
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['question', 'text'],
|
||||
name='unique_question_answer_organisationquestionchoice')
|
||||
]
|
||||
|
||||
|
||||
class Organisation(models.Model):
|
||||
|
||||
14
people/models/person.py
Normal file → Executable file
14
people/models/person.py
Normal file → Executable file
@@ -8,9 +8,10 @@ from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from django_countries.fields import CountryField
|
||||
from django_settings_export import settings_export
|
||||
from post_office import mail
|
||||
|
||||
from constance import config
|
||||
|
||||
from .organisation import Organisation
|
||||
from .question import AnswerSet, Question, QuestionChoice
|
||||
|
||||
@@ -43,10 +44,10 @@ class User(AbstractUser):
|
||||
def send_welcome_email(self) -> None:
|
||||
"""Send a welcome email to a new user."""
|
||||
# Get exported data from settings.py first
|
||||
context = settings_export(None)
|
||||
context.update({
|
||||
context = {
|
||||
'user': self,
|
||||
})
|
||||
'config': config,
|
||||
}
|
||||
|
||||
logger.info('Sending welcome mail to user \'%s\'', self.username)
|
||||
|
||||
@@ -77,6 +78,11 @@ class PersonQuestionChoice(QuestionChoice):
|
||||
on_delete=models.CASCADE,
|
||||
blank=False,
|
||||
null=False)
|
||||
class Meta(QuestionChoice.Meta):
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['question', 'text'],
|
||||
name='unique_question_answer_personquestionchoice')
|
||||
]
|
||||
|
||||
|
||||
class Person(models.Model):
|
||||
|
||||
2
people/models/question.py
Normal file → Executable file
2
people/models/question.py
Normal file → Executable file
@@ -92,7 +92,7 @@ class QuestionChoice(models.Model):
|
||||
abstract = True
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['question', 'text'],
|
||||
name='unique_question_answer')
|
||||
name='unique_question_answer_modelquestionchoice')
|
||||
]
|
||||
ordering = [
|
||||
'question__order',
|
||||
|
||||
14
people/models/relationship.py
Normal file → Executable file
14
people/models/relationship.py
Normal file → Executable file
@@ -33,6 +33,11 @@ class RelationshipQuestionChoice(QuestionChoice):
|
||||
on_delete=models.CASCADE,
|
||||
blank=False,
|
||||
null=False)
|
||||
class Meta(QuestionChoice.Meta):
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['question', 'text'],
|
||||
name='unique_question_answer_relationshipquestionchoice')
|
||||
]
|
||||
|
||||
|
||||
class Relationship(models.Model):
|
||||
@@ -40,7 +45,7 @@ class Relationship(models.Model):
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['source', 'target'],
|
||||
name='unique_relationship'),
|
||||
name='unique_relationship_modelrelationship'),
|
||||
]
|
||||
|
||||
#: Person reporting the relationship
|
||||
@@ -122,6 +127,11 @@ class OrganisationRelationshipQuestionChoice(QuestionChoice):
|
||||
on_delete=models.CASCADE,
|
||||
blank=False,
|
||||
null=False)
|
||||
class Meta(QuestionChoice.Meta):
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['question', 'text'],
|
||||
name='unique_question_answer_organisationrelationshipquestionchoice')
|
||||
]
|
||||
|
||||
|
||||
class OrganisationRelationship(models.Model):
|
||||
@@ -129,7 +139,7 @@ class OrganisationRelationship(models.Model):
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['source', 'target'],
|
||||
name='unique_relationship'),
|
||||
name='unique_relationship_modelorganisationrelationship'),
|
||||
]
|
||||
|
||||
#: Person reporting the relationship
|
||||
|
||||
2
people/templates/people/map.html
Normal file → Executable file
2
people/templates/people/map.html
Normal file → Executable file
@@ -3,7 +3,7 @@
|
||||
{% block extra_head %}
|
||||
{{ map_markers|json_script:'map-markers' }}
|
||||
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<script src="{% static 'js/map.js' %}"></script>
|
||||
|
||||
<script async defer
|
||||
|
||||
2
people/templates/people/network.html
Normal file → Executable file
2
people/templates/people/network.html
Normal file → Executable file
@@ -97,6 +97,6 @@
|
||||
integrity="sha512-Qlv6VSKh1gDKGoJbnyA5RMXYcvnpIqhO++MhIM2fStMcGT9i2T//tSwYFlcyoRRDcDZ+TYHpH8azBBCyhpSeqw=="
|
||||
crossorigin="anonymous"></script>
|
||||
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<script src="{% static 'js/network.js' %}"></script>
|
||||
{% endblock %}
|
||||
2
people/templates/people/organisation/detail.html
Normal file → Executable file
2
people/templates/people/organisation/detail.html
Normal file → Executable file
@@ -3,7 +3,7 @@
|
||||
{% block extra_head %}
|
||||
{{ map_markers|json_script:'map-markers' }}
|
||||
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<script src="{% static 'js/map.js' %}"></script>
|
||||
|
||||
<script async defer src="https://maps.googleapis.com/maps/api/js?key={{ settings.GOOGLE_MAPS_API_KEY }}&callback=initMap"
|
||||
|
||||
2
people/templates/people/organisation/update.html
Normal file → Executable file
2
people/templates/people/organisation/update.html
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block extra_head %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
{{ map_markers|json_script:'map-markers' }}
|
||||
|
||||
<script src="{% static 'js/map.js' %}"></script>
|
||||
|
||||
14
people/templates/people/person/detail_full.html
Normal file → Executable file
14
people/templates/people/person/detail_full.html
Normal file → Executable file
@@ -3,7 +3,7 @@
|
||||
{% block extra_head %}
|
||||
{{ map_markers|json_script:'map-markers' }}
|
||||
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<script src="{% static 'js/map.js' %}"></script>
|
||||
|
||||
<script async defer src="https://maps.googleapis.com/maps/api/js?key={{ settings.GOOGLE_MAPS_API_KEY }}&callback=initMap"
|
||||
@@ -64,17 +64,19 @@
|
||||
<a class="btn btn-success"
|
||||
href="{% url 'people:person.update' pk=person.pk %}">Update</a>
|
||||
|
||||
{% load hijack_tags %}
|
||||
{% if person.user == request.user and not request|is_hijacked %}
|
||||
{% load hijack %}
|
||||
{% if person.user == request.user and not request.user.is_hijacked %}
|
||||
<a class="btn btn-info"
|
||||
href="{% url 'password_change' %}?next={{ person.get_absolute_url }}">Change Password</a>
|
||||
{% endif %}
|
||||
|
||||
{% if request.user.is_superuser and person.user and person.user != request.user %}
|
||||
<form style="display: inline;" action="/hijack/{{ person.user.pk }}/" method="post">
|
||||
{% csrf_token %}
|
||||
<form action="{% url 'hijack:acquire' %}" method="POST">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="user_pk" value="{{ person.pk }}">
|
||||
<button class="btn btn-warning" type="submit">Become {{ person.name }}</button>
|
||||
</form>
|
||||
<input type="hidden" name="next" value="{{ request.path }}">
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<hr>
|
||||
|
||||
2
people/templates/people/person/detail_partial.html
Normal file → Executable file
2
people/templates/people/person/detail_partial.html
Normal file → Executable file
@@ -3,7 +3,7 @@
|
||||
{% block extra_head %}
|
||||
{{ map_markers|json_script:'map-markers' }}
|
||||
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<script src="{% static 'js/map.js' %}"></script>
|
||||
|
||||
<script async defer src="https://maps.googleapis.com/maps/api/js?key={{ settings.GOOGLE_MAPS_API_KEY }}&callback=initMap"
|
||||
|
||||
2
people/templates/people/person/update.html
Normal file → Executable file
2
people/templates/people/person/update.html
Normal file → Executable file
@@ -1,7 +1,7 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block extra_head %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
{{ map_markers|json_script:'map-markers' }}
|
||||
|
||||
<script src="{% static 'js/map.js' %}"></script>
|
||||
|
||||
2
people/templates/people/relationship/detail.html
Normal file → Executable file
2
people/templates/people/relationship/detail.html
Normal file → Executable file
@@ -47,7 +47,7 @@
|
||||
<div class="col-md-2 text-center">
|
||||
{% if relationship.reverse %}
|
||||
<a href="{% url 'people:relationship.detail' pk=relationship.reverse.pk %}">
|
||||
<span class="fas fa-exchange-alt fa-5x"></span>
|
||||
<span class="fa-solid fa-right-left fa-5x"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
2
people/templates/people/relationship/update.html
Normal file → Executable file
2
people/templates/people/relationship/update.html
Normal file → Executable file
@@ -23,6 +23,6 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_script %}
|
||||
{% load staticfiles %}
|
||||
{% load static %}
|
||||
<script async defer src="{% static 'js/hide_free_text.js' %}"></script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import typing
|
||||
|
||||
from django.conf import settings
|
||||
from constance import config
|
||||
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.utils import timezone
|
||||
@@ -49,11 +50,11 @@ class OrganisationListView(LoginRequiredMixin, ListView):
|
||||
orgs_sorted = {}
|
||||
|
||||
try_copy_by_key(orgs_by_country, orgs_sorted,
|
||||
f'{settings.PARENT_PROJECT_NAME} partners')
|
||||
f'{config.PARENT_PROJECT_NAME} partners')
|
||||
try_copy_by_key(orgs_by_country, orgs_sorted, 'International')
|
||||
|
||||
special = {
|
||||
f'{settings.PARENT_PROJECT_NAME} partners', 'International',
|
||||
f'{config.PARENT_PROJECT_NAME} partners', 'International',
|
||||
'Unknown'
|
||||
}
|
||||
for country in sorted(k for k in orgs_by_country.keys()
|
||||
@@ -81,7 +82,7 @@ class OrganisationListView(LoginRequiredMixin, ListView):
|
||||
country = 'International'
|
||||
|
||||
if answers.is_partner_organisation:
|
||||
country = f'{settings.PARENT_PROJECT_NAME} partners'
|
||||
country = f'{config.PARENT_PROJECT_NAME} partners'
|
||||
|
||||
except AttributeError:
|
||||
# Organisation has no AnswerSet - country is 'Unknown'
|
||||
|
||||
76
requirements.txt
Normal file → Executable file
76
requirements.txt
Normal file → Executable file
@@ -1,46 +1,46 @@
|
||||
astroid==2.3.3
|
||||
beautifulsoup4==4.8.2
|
||||
dj-database-url==0.5.0
|
||||
Django==2.2.10
|
||||
django-appconf==1.0.3
|
||||
django-bootstrap4==1.1.1
|
||||
django-bootstrap-datepicker-plus==3.0.5
|
||||
django-compat==1.0.15
|
||||
django-constance==2.6.0
|
||||
django-countries==5.5
|
||||
django-dbbackup==3.2.0
|
||||
django-filter==2.2.0
|
||||
django-hijack==2.2.1
|
||||
django-picklefield==2.1.1
|
||||
django-post-office==3.4.0
|
||||
django-select2==7.2.0
|
||||
astroid==2.12.13
|
||||
beautifulsoup4==4.11.1
|
||||
dj-database-url==1.2.0
|
||||
Django==4.1.4
|
||||
django-appconf==1.0.5
|
||||
django-bootstrap4==22.3
|
||||
django-bootstrap-datepicker-plus==5.0.2
|
||||
django-constance==2.9.1
|
||||
django-countries==7.5
|
||||
django-dbbackup==4.0.2
|
||||
django-filter==22.1
|
||||
django-hijack==3.2.6
|
||||
django-picklefield==3.1
|
||||
django-post-office==3.6.3
|
||||
django-select2==8.0.0
|
||||
django-settings-export==1.2.1
|
||||
djangorestframework==3.11.0
|
||||
djangorestframework==3.14.0
|
||||
dodgy==0.2.1
|
||||
isort==4.3.21
|
||||
isort==5.11.4
|
||||
jsonfield==3.1.0
|
||||
lazy-object-proxy==1.4.3
|
||||
mccabe==0.6.1
|
||||
lazy-object-proxy==1.8.0
|
||||
mccabe==0.7.0
|
||||
# mysqlclient==1.4.6
|
||||
pep8-naming==0.4.1
|
||||
prospector==1.2.0
|
||||
pycodestyle==2.4.0
|
||||
pydocstyle==5.0.2
|
||||
pyflakes==2.1.1
|
||||
pylint==2.4.4
|
||||
pep8-naming==0.10.0
|
||||
prospector==1.8.3
|
||||
pycodestyle==2.10.0
|
||||
pydocstyle==6.1.1
|
||||
pyflakes==2.5.0
|
||||
pylint==2.15.9
|
||||
pylint-celery==0.3
|
||||
pylint-django==2.0.12
|
||||
pylint-django==2.5.3
|
||||
pylint-flask==0.6
|
||||
pylint-plugin-utils==0.6
|
||||
python-decouple==3.3
|
||||
pytz==2019.3
|
||||
pylint-plugin-utils==0.7
|
||||
python-decouple==3.6
|
||||
pytz==2022.7
|
||||
pyuca==1.2
|
||||
PyYAML==5.3
|
||||
requirements-detector==0.6
|
||||
setoptconf==0.2.0
|
||||
six==1.14.0
|
||||
snowballstemmer==2.0.0
|
||||
soupsieve==1.9.5
|
||||
sqlparse==0.3.0
|
||||
PyYAML==6.0
|
||||
requirements-detector==1.0.3
|
||||
setoptconf==0.3.0
|
||||
six==1.16.0
|
||||
snowballstemmer==2.2.0
|
||||
soupsieve==2.3.2.post1
|
||||
sqlparse==0.4.3
|
||||
typed-ast
|
||||
wrapt==1.11.2
|
||||
wrapt==1.14.1
|
||||
Pillow==9.4.0
|
||||
|
||||
Reference in New Issue
Block a user