50 Commits

Author SHA1 Message Date
f83805af4f [DOCS] Minor formatting 2023-03-30 08:01:45 +01:00
170db50a1c [DOCS] Change release version 2023-03-30 07:44:06 +01:00
2ebe7777c3 [DOCS] Add badges and links to docs 2023-03-29 12:25:00 +01:00
2f9f35c396 [DOCS] Yet another attempt fix for cross-ref link 2023-03-26 18:52:17 +01:00
aecd0ff314 [DOCS] Anpther attempt fix cross-ref link 2023-03-26 18:50:18 +01:00
e0e9a05747 [DOCS] Another attempt fix for cross-ref link 2023-03-26 18:44:41 +01:00
cce0543408 [DOCS] Attempt fix cross-ref link 2023-03-26 18:42:37 +01:00
a6e36b9854 [DOCS] Fix cross-ref link 2023-03-26 18:39:39 +01:00
995a4f6cb2 [DOCS] Change callout type 2023-03-26 18:38:19 +01:00
d24756ca38 [DOCS] Attempt fix callout type name 2023-03-26 18:36:25 +01:00
f69b7aa1f0 [DOCS] Proof-reading 2023-03-26 18:33:47 +01:00
74b6b3b27d [DEPLOY] Include Caddyfile for Docker deployment 2023-03-25 15:36:16 +00:00
e51c2dc76b [DOCS] Minor improvements to deployment docs 2023-03-25 15:22:52 +00:00
b0fcda5901 [DOCS] Use compressed archives for deployment 2023-03-25 15:02:11 +00:00
253846f877 [DOCS] Small wording change 2023-03-25 14:44:31 +00:00
4b61b26661 [FIX] Ensure Docker only ignores root docs folder 2023-03-25 14:39:34 +00:00
66567e5a55 [FIX] Place signup button in correct template 2023-03-25 14:34:55 +00:00
b8059175c6 [FIX] Variable name to show signup button 2023-03-25 14:11:29 +00:00
a85a07ea82 [FEAT] Add signup button if signups are allowed 2023-03-25 14:07:33 +00:00
21088a1412 [FEAT] Limit Map and Network views to admins 2023-03-25 13:54:55 +00:00
e37d5fcb61 [DOCS] Add bulk of general use documentation 2023-03-25 13:45:31 +00:00
f6bfcceb6c [DOCS] Rename files for section ordering 2023-03-24 19:58:05 +00:00
79bb6cc415 [DOCS] Add basic Docker Desktop deployment info 2023-03-24 19:57:48 +00:00
99161b269e [DOCS] Rename sections for ordering 2023-03-24 19:54:14 +00:00
365c3f04c9 [DOCS] Add configuration documentation 2023-03-24 19:50:18 +00:00
65023cf4f9 [DEPLOY] Stop exposing unnecessary server port 2023-03-12 18:26:18 +00:00
85bb6ae5ba Set fixed version numbers for Python requirements 2023-03-12 17:14:11 +00:00
6eeb74fdd7 [DOCS] Small updates 2023-03-12 15:54:43 +00:00
f015929a0c Residual changes from Person-User relationship changes 2023-03-12 15:40:09 +00:00
c05ef1ed10 [DEPLOY] Update template env file to require Google Maps API key 2023-03-12 15:39:38 +00:00
f8593776c0 [FEAT] Allow admins to manage relationships for all Persons
All Persons now have associated Users, meaning Users can be hijacked by admins to manage the relationships for the associated Person.
2023-03-12 15:39:03 +00:00
284132f4cc [FIX] Formatting of data during display 2023-03-12 15:37:43 +00:00
858933c6bb [FEAT] Update database migrations to be more generic and applicable 2023-03-12 15:37:20 +00:00
0a3c85bb42 [STYLE] Padding above footer 2023-03-12 15:35:42 +00:00
266bdfe628 [FIX] Django compatibility 2023-03-12 15:35:20 +00:00
d19554de24 [DEPLOY] Add deployment resources 2023-03-12 13:00:40 +00:00
4e4503e097 [FIX] Offline PWA functionality and style offline page 2023-03-12 12:57:19 +00:00
d65ccc807f [DOCS] Update for new database 2023-02-24 19:46:37 +00:00
b94c459e56 [DOCS] [FIX] Incorrect URLs 2023-02-24 19:46:19 +00:00
b6a6be99da [DEPLOY] Update deployment files for new database 2023-02-24 19:45:56 +00:00
83765c99ad [FEAT] Styling improvements 2023-02-24 19:45:02 +00:00
11a6f8d8f1 [FEAT] Add 404 page 2023-02-24 19:44:33 +00:00
bcb2b1bc20 [FEAT] Transition to PostgreSQL database in another Docker container 2023-02-24 19:44:17 +00:00
843bdebabf [FEAT] Add Bootstrap theming through admin dashboard 2023-02-24 19:43:34 +00:00
86e18c399d [FEAT] Enable django-allauth for enhanced user management & federation 2023-02-24 19:42:07 +00:00
05ca7433f1 [FEAT] Add Django command to selectively load fixtures
Only loads data if object isn't a BootstrapTheme that already exists
2023-02-24 19:35:34 +00:00
3beeeab4ae [FIX] Remove redundant data passing 2023-02-12 15:03:11 +00:00
ccea25133c [DOCS] Add "choosing how to deploy" section
Also tidy up formatting and remove old comments
2023-02-10 16:00:21 +00:00
ced86f308b [FIX] Remove spaces from default email address 2023-02-10 15:57:21 +00:00
6d1e5effda Update Vagrant docs link 2023-02-10 15:33:41 +00:00
112 changed files with 2886 additions and 286 deletions

View File

@@ -17,5 +17,5 @@ mail.log/
*.log* *.log*
deployment* deployment*
docs/ /docs/
.readthedocs.yaml .readthedocs.yaml

View File

@@ -1,6 +1,6 @@
# BRECcIA Mapper # BRECcIA Mapper
[![Documentation Status](https://readthedocs.org/projects/breccia/badge/?version=latest)](https://breccia.readthedocs.io/en/latest/?badge=latest) [![Documentation Status](https://readthedocs.org/projects/breccia/badge/?version=latest)](https://docs.gcrf-breccia.com/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. 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. This allows researchers to visually represent the relationships between project staff and stakeholders involved in the their project at different points in time.
@@ -11,7 +11,7 @@ This work was funded through the "Building REsearch Capacity for sustainable wat
## Deployment ## Deployment
This project is written in Python using the popular [Django](https://www.djangoproject.com/) framework. This project is written in Python using the popular [Django](https://www.djangoproject.com/) framework.
Deployment is managed using [Ansible](https://www.ansible.com/) and Docker (https://www.docker.com/), see the `deploy/README.md` for details. Deployment relies on [Docker Compose](https://docs.docker.com/compose) but is possible with various methods. See [the documentation](https://docs.gcrf-breccia.com/en/latest/1-deployment.html) for details.
## Contributors ## Contributors

View File

@@ -79,7 +79,7 @@ class ActivityAttendanceView(permissions.UserIsLinkedPersonMixin, SingleObjectMi
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if request.is_ajax(): if request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest':
self.object.attendance_list.add(self.get_test_person()) self.object.attendance_list.add(self.get_test_person())
return HttpResponse(status=204) return HttpResponse(status=204)
@@ -89,7 +89,7 @@ class ActivityAttendanceView(permissions.UserIsLinkedPersonMixin, SingleObjectMi
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
self.object = self.get_object() self.object = self.get_object()
if request.is_ajax(): if request.META.get('HTTP_X_REQUESTED_WITH') == 'XMLHttpRequest':
self.object.attendance_list.remove(self.get_test_person()) self.object.attendance_list.remove(self.get_test_person())
return HttpResponse(status=204) return HttpResponse(status=204)

View File

@@ -0,0 +1,16 @@
from allauth.account.adapter import DefaultAccountAdapter
from constance import config
class ControlSignupsAccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
"""
Checks whether or not the site is open for signups.
Next to simply returning True/False you can also intervene the
regular flow by raising an ImmediateHttpResponse
(Comment reproduced from the overridden method.)
"""
return config.ALLOW_SIGNUPS

View File

@@ -0,0 +1,7 @@
[
{
"model": "bootstrap_customizer.sitebootstraptheme",
"pk": 1,
"fields": { "site": 1, "bootstrap_theme": 1, "updated": "2023-02-23T14:41:48.620Z" }
}
]

File diff suppressed because one or more lines are too long

View File

View File

@@ -0,0 +1,44 @@
import json
import os
from django.core.management.commands import loaddata
from bootstrap_customizer.models import BootstrapTheme
def should_add_record(record):
if record['model'] != 'bootstrap_customizer.bootstraptheme':
return True
return not BootstrapTheme.objects.filter(
pk=record['pk'],
).exists()
class Command(loaddata.Command):
def handle(self, *args, **options):
args = list(args)
# Read the original JSON file
file_name = args[0]
with open(file_name) as json_file:
json_list = json.load(json_file)
# Filter out records that already exists
json_list_filtered = list(filter(should_add_record, json_list))
if not json_list_filtered:
print("All data are already previously loaded")
return
# Write the updated JSON file
file_dir_and_name, file_ext = os.path.splitext(file_name)
file_name_temp = f"{file_dir_and_name}_temp{file_ext}"
with open(file_name_temp, 'w') as json_file_temp:
json.dump(json_list_filtered, json_file_temp)
# Pass the request to the actual loaddata (parent functionality)
args[0] = file_name_temp
super().handle(*args, **options)
# You can choose to not delete the file so that you can see what was added to your records
os.remove(file_name_temp)

View File

@@ -185,6 +185,7 @@ DJANGO_APPS = [
'django.contrib.sessions', 'django.contrib.sessions',
'django.contrib.messages', 'django.contrib.messages',
'django.contrib.staticfiles', 'django.contrib.staticfiles',
'django.contrib.sites',
] ]
THIRD_PARTY_APPS = [ THIRD_PARTY_APPS = [
@@ -199,9 +200,17 @@ THIRD_PARTY_APPS = [
'bootstrap_datepicker_plus', 'bootstrap_datepicker_plus',
'hijack', 'hijack',
'pwa', 'pwa',
'allauth',
'allauth.account',
'allauth.socialaccount',
'allauth.socialaccount.providers.google',
'allauth.socialaccount.providers.microsoft',
'django_inlinecss',
'bootstrap_customizer',
] ]
FIRST_PARTY_APPS = [ FIRST_PARTY_APPS = [
'breccia_mapper',
'people', 'people',
'activities', 'activities',
'export', 'export',
@@ -218,6 +227,12 @@ MIDDLEWARE = [
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'hijack.middleware.HijackUserMiddleware', 'hijack.middleware.HijackUserMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware',
'bootstrap_customizer.middleware.BootstrapThemeMiddleware',
]
FIXTURE_DIRS = [
BASE_DIR.joinpath('breccia_mapper', 'fixtures'),
] ]
ROOT_URLCONF = 'breccia_mapper.urls' ROOT_URLCONF = 'breccia_mapper.urls'
@@ -242,11 +257,45 @@ TEMPLATES = [
WSGI_APPLICATION = 'breccia_mapper.wsgi.application' WSGI_APPLICATION = 'breccia_mapper.wsgi.application'
# allauth
AUTHENTICATION_BACKENDS = [
# Needed to login by username in Django admin, regardless of `allauth`
'django.contrib.auth.backends.ModelBackend',
# `allauth` specific authentication methods, such as login by email
'allauth.account.auth_backends.AuthenticationBackend',
]
SITE_ID = 1
ACCOUNT_DEFAULT_HTTP_PROTOCOL = SITE_PROTOCOL
ACCOUNT_EMAIL_CONFIRMATION_EXPIRE_DAYS = 1
ACCOUNT_EMAIL_REQUIRED = True
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_LOGIN_ATTEMPTS_LIMIT = 5
# 1 day
ACCOUNT_LOGIN_ATTEMPTS_TIMEOUT = 86400
# or any other page
ACCOUNT_LOGOUT_REDIRECT_URL = '/'
ACCOUNT_ADAPTER = 'breccia_mapper.account_adapter.ControlSignupsAccountAdapter'
PROJECT_LONG_NAME = config('PROJECT_LONG_NAME', 'Project Network Mapper')
PROJECT_SHORT_NAME = config('PROJECT_SHORT_NAME', 'Network Mapper')
PROJECT_DESCRIPTION = config('PROJECT_DESCRIPTION', 'Application to map network relationships in the organisation.')
THEME_COLOR = '#' + config('THEME_COLOR', '212121')
BACKGROUND_COLOR = '#' + config('BACKGROUND_COLOR', 'ffffff')
# Database # Database
# https://docs.djangoproject.com/en/2.2/ref/settings/#databases # https://docs.djangoproject.com/en/2.2/ref/settings/#databases
DATABASES = { DATABASES = {
'default': dj_database_url.parse('sqlite:///' + str(BASE_DIR.joinpath('db.sqlite3'))) 'default' : {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'breccia-mapper',
'USER': 'breccia-mapper',
'PASSWORD': config('DB_PASSWORD'),
'HOST': 'db',
'PORT': '5432',
}
} }
# Django DBBackup # Django DBBackup
@@ -302,7 +351,7 @@ AUTH_USER_MODEL = 'people.User'
# Login flow # Login flow
LOGIN_URL = reverse_lazy('login') LOGIN_URL = reverse_lazy('account_login')
LOGIN_REDIRECT_URL = reverse_lazy('people:person.profile') LOGIN_REDIRECT_URL = reverse_lazy('people:person.profile')
@@ -407,6 +456,15 @@ CONSTANCE_CONFIG = {
'PROJECT_TAGLINE': ( 'PROJECT_TAGLINE': (
'Here is your project\'s tagline.', 'Here is your project\'s tagline.',
'Project tagline'), 'Project tagline'),
'ALLOW_SIGNUPS': (
False,
'Allow new users to sign up using the site\'s sign up form'),
'ENABLE_GOOGLE_LOGIN': (
False,
'Allow users to sign in using their Google accounts'),
'ENABLE_MICROSOFT_LOGIN': (
False,
'Allow users to sign in using their Microsoft accounts'),
'HOMEPAGE_HEADER_IMAGE': ( 'HOMEPAGE_HEADER_IMAGE': (
'800x500.png', '800x500.png',
'Homepage header image', 'Homepage header image',
@@ -436,7 +494,7 @@ CONSTANCE_CONFIG = {
'Step 3', 'Step 3',
'Homepage card #3 title'), 'Homepage card #3 title'),
'HOMEPAGE_CARD_3_DESCRIPTION': ( 'HOMEPAGE_CARD_3_DESCRIPTION': (
'Use the network view to build new relationships', 'Use the network view to analyse relationships',
'Homepage card #3 description'), 'Homepage card #3 description'),
'HOMEPAGE_CARD_3_ICON': ( 'HOMEPAGE_CARD_3_ICON': (
'diagram-project', 'diagram-project',
@@ -458,6 +516,9 @@ CONSTANCE_CONFIG_FIELDSETS = {
'PARENT_PROJECT_NAME', 'PARENT_PROJECT_NAME',
'PROJECT_LEAD', 'PROJECT_LEAD',
'PROJECT_TAGLINE', 'PROJECT_TAGLINE',
'ALLOW_SIGNUPS',
'ENABLE_GOOGLE_LOGIN',
'ENABLE_MICROSOFT_LOGIN',
), ),
'Homepage configuration': ( 'Homepage configuration': (
'HOMEPAGE_HEADER_IMAGE_SHRINK', 'HOMEPAGE_HEADER_IMAGE_SHRINK',
@@ -491,12 +552,6 @@ CONSTANCE_CONFIG_FIELDSETS = {
CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend' CONSTANCE_BACKEND = 'constance.backends.database.DatabaseBackend'
PROJECT_LONG_NAME = config('PROJECT_LONG_NAME', 'Project Network Mapper')
PROJECT_SHORT_NAME = config('PROJECT_SHORT_NAME', 'Network Mapper')
PROJECT_DESCRIPTION = config('PROJECT_DESCRIPTION', 'Application to map network relationships in the organisation.')
THEME_COLOR = '#' + config('THEME_COLOR', '212121')
BACKGROUND_COLOR = '#' + config('BACKGROUND_COLOR', 'ffffff')
# Django Hijack settings # Django Hijack settings
# See https://django-hijack.readthedocs.io/en/stable/ # See https://django-hijack.readthedocs.io/en/stable/
@@ -515,7 +570,7 @@ BOOTSTRAP4 = {
EMAIL_HOST = config('EMAIL_HOST', default=None) EMAIL_HOST = config('EMAIL_HOST', default=None)
DEFAULT_FROM_EMAIL = config( DEFAULT_FROM_EMAIL = config(
'DEFAULT_FROM_EMAIL', 'DEFAULT_FROM_EMAIL',
default=f'{PROJECT_SHORT_NAME}@localhost.localdomain') default=f'{PROJECT_SHORT_NAME.replace(" ","")}@localhost.localdomain')
SERVER_EMAIL = DEFAULT_FROM_EMAIL SERVER_EMAIL = DEFAULT_FROM_EMAIL
if EMAIL_HOST is None: if EMAIL_HOST is None:

View File

@@ -0,0 +1,303 @@
/* -------------------------------------
GLOBAL
A very basic CSS reset
------------------------------------- */
* {
margin: 0;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
box-sizing: border-box;
font-size: 14px;
}
img {
max-width: 100%;
}
body {
-webkit-font-smoothing: antialiased;
-webkit-text-size-adjust: none;
width: 100% !important;
height: 100%;
line-height: 1.6em;
/* 1.6em * 14px = 22.4px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */
/*line-height: 22px;*/
}
/* Let's make sure all tables have defaults */
table td {
vertical-align: top;
}
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
body {
background-color: #f6f6f6;
}
.body-wrap {
background-color: #f6f6f6;
width: 100%;
}
.container {
display: block !important;
max-width: 600px !important;
margin: 0 auto !important;
/* makes it centered */
clear: both !important;
}
.content {
max-width: 600px;
margin: 0 auto;
display: block;
padding: 20px;
}
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background-color: #fff;
border: 1px solid #e9e9e9;
border-radius: 3px;
}
.content-wrap {
padding: 20px;
}
.content-block {
padding: 0 0 20px;
}
.header {
width: 100%;
margin-bottom: 20px;
}
.footer {
width: 100%;
clear: both;
color: #999;
padding: 20px;
}
.footer p,
.footer a,
.footer td {
color: #999;
font-size: 12px;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3 {
font-family: "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
color: #000;
margin: 40px 0 0;
line-height: 1.2em;
font-weight: 400;
}
h1 {
font-size: 32px;
font-weight: 500;
/* 1.2em * 32px = 38.4px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */
/*line-height: 38px;*/
}
h2 {
font-size: 24px;
/* 1.2em * 24px = 28.8px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */
/*line-height: 29px;*/
}
h3 {
font-size: 18px;
/* 1.2em * 18px = 21.6px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */
/*line-height: 22px;*/
}
h4 {
font-size: 14px;
font-weight: 600;
}
p,
ul,
ol {
margin-bottom: 10px;
font-weight: normal;
}
p li,
ul li,
ol li {
margin-left: 5px;
list-style-position: inside;
}
/* -------------------------------------
LINKS & BUTTONS
------------------------------------- */
a {
color: #348eda;
text-decoration: underline;
}
.btn-primary {
text-decoration: none;
color: #FFF;
background-color: #348eda;
border: solid #348eda;
border-width: 10px 20px;
line-height: 2em;
/* 2em * 14px = 28px, use px to get airier line-height also in Thunderbird, and Yahoo!, Outlook.com, AOL webmail clients */
/*line-height: 28px;*/
font-weight: bold;
text-align: center;
cursor: pointer;
display: inline-block;
border-radius: 5px;
text-transform: capitalize;
}
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.aligncenter {
text-align: center;
}
.alignright {
text-align: right;
}
.alignleft {
text-align: left;
}
.clear {
clear: both;
}
/* -------------------------------------
ALERTS
Change the class depending on warning email, good email or bad email
------------------------------------- */
.alert {
font-size: 16px;
color: #fff;
font-weight: 500;
padding: 20px;
text-align: center;
border-radius: 3px 3px 0 0;
}
.alert a {
color: #fff;
text-decoration: none;
font-weight: 500;
font-size: 16px;
}
.alert.alert-warning {
background-color: #FF9F00;
}
.alert.alert-bad {
background-color: #D0021B;
}
.alert.alert-good {
background-color: #68B90F;
}
/* -------------------------------------
INVOICE
Styles for the billing table
------------------------------------- */
.invoice {
margin: 40px auto;
text-align: left;
width: 80%;
}
.invoice td {
padding: 5px 0;
}
.invoice .invoice-items {
width: 100%;
}
.invoice .invoice-items td {
border-top: #eee 1px solid;
}
.invoice .invoice-items .total td {
border-top: 2px solid #333;
border-bottom: 2px solid #333;
font-weight: 700;
}
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 640px) {
body {
padding: 0 !important;
}
h1,
h2,
h3,
h4 {
font-weight: 800 !important;
margin: 20px 0 5px !important;
}
h1 {
font-size: 22px !important;
}
h2 {
font-size: 18px !important;
}
h3 {
font-size: 16px !important;
}
.container {
padding: 0 !important;
width: 100% !important;
}
.content {
padding: 0 !important;
}
.content-wrap {
padding: 10px !important;
}
.invoice {
width: 100% !important;
}
}
/*# sourceMappingURL=styles.css.map */

View File

@@ -12,6 +12,20 @@ body {
flex: 1 0 auto; flex: 1 0 auto;
} }
main {
padding-top: 12px;
margin-bottom: 12px;
}
form {
margin-bottom: 24px;
}
.social_providers_list > form {
display: inline-block;
margin-bottom: 8px;
}
.footer { .footer {
height: 60px; height: 60px;

View File

@@ -34,6 +34,11 @@ header.masthead .textbox-container {
background-color: rgba(30, 30, 30, 0.2); background-color: rgba(30, 30, 30, 0.2);
} }
header.masthead > .row {
max-width: 1140px;
margin: auto;
}
@media (min-width: 768px) { @media (min-width: 768px) {
header.masthead { header.masthead {
padding-top: 1rem; padding-top: 1rem;

View File

@@ -1,9 +1,10 @@
var staticCacheName = "django-pwa-v" + new Date().getTime(); var staticCacheName = "django-pwa-v" + new Date().getTime();
var filesToCache = [ var filesToCache = [
"/offline", "/offline/",
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css",
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/solid.min.css", "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/solid.min.css",
"/static/css/global.css", "/static/css/global.css",
"/static/js/serviceworker.js",
"/static/hijack/hijack.min.css", "/static/hijack/hijack.min.css",
"/media/icon-192x192.png", "/media/icon-192x192.png",
]; ];
@@ -41,7 +42,7 @@ self.addEventListener("fetch", event => {
return response || fetch(event.request); return response || fetch(event.request);
}) })
.catch(() => { .catch(() => {
return caches.match('offline'); return caches.match('/offline/');
}) })
) )
}); });

View File

@@ -0,0 +1,7 @@
{% extends 'base.html' %}
{% block content %}
<h2>Error 404: Page not Found</h2>
<p>Sorry, we can't find that page. Click <a href="{% url 'index' %}">here</a> to go home.</p>
{% endblock content %}

View File

View File

@@ -0,0 +1,11 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Account Inactive" %}{% endblock %}
{% block content %}
<h1>{% trans "Account Inactive" %}</h1>
<p>{% trans "This account is inactive." %}</p>
{% endblock %}

View File

@@ -0,0 +1 @@
{% extends "base.html" %}

View File

@@ -0,0 +1,93 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Account Details" %}{% endblock %}
{% block content %}
<h1 class="h1" id="head_banner">Account Details</h1>
<h2 class="h2" id="head_banner">{% trans "Email Addresses" %}</h2>
{% if user.emailaddress_set.all %}
<p class="email_settings_info">{% trans 'The following email addresses are associated with your account:' %}</p>
<form action="{% url 'account_email' %}" method="post">
{% csrf_token %}
{% for emailaddress in user.emailaddress_set.all %}
<div class="ctrlHolder">
<label for="email_radio_{{forloop.counter}}" class="{% if emailaddress.primary %}primary_email{%endif%}">
{{ emailaddress.email }}
{% if emailaddress.primary %}
<span class="primary">(primary email)</span>
{% endif %}
{% if not emailaddress.verified %}
<span class="unverified">(not verified)</span>
{% endif %}
<input id="email_radio_{{forloop.counter}}" type="radio" name="email" {% if emailaddress.primary or user.emailaddress_set.count == 1 %}checked="checked"{%endif %} value="{{emailaddress.email}}"/>
</label>
</div>
{% endfor %}
<div>
<button class="btn btn-primary" type="submit" name="action_primary" >{% trans 'Make Primary' %}</button>
<button class="btn btn-info" type="submit" name="action_send" >{% trans 'Re-send Verification' %}</button>
<button class="btn btn-danger" type="submit" name="action_remove" >{% trans 'Remove' %}</button>
</div>
</form>
{% else %}
<p class="email_settings_info"><strong>{% trans 'Warning:'%}</strong> {% trans "You do not have any email addresses set up. Please add an email address to receive notifications, reset your password, and perform other account-related actions." %}</p>
{% endif %}
<h3 class="h3" id="head_banner">{% trans "Add Email Address" %}</h3>
<form method="post" id="email_form" action="{% url 'account_email' %}">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
<button class="btn btn-primary" name="action_add" type="submit">{% trans "Add Email" %}</button>
</form>
<h2 class="h2" id="head_banner">{% trans "Change Password" %}</h2>
<a class="btn btn-primary mb-4" href="{% url 'account_change_password' %}">{% trans "Change password" %}</a>
<h2 class="h2" id="head_banner">{% trans "Federated Login" %}</h2>
<p>You can connect third-party accounts to log into this site with them.</p>
<a class="btn btn-primary" href="{% url 'socialaccount_connections' %}">{% trans "Manage" %}</a>
{% endblock %}
{% block extra_body %}
<script type="text/javascript">
(function() {
var message = "{% trans 'Are you sure you want to remove the selected email address?' %}";
var actions = document.getElementsByName('action_remove');
if (actions.length) {
actions[0].addEventListener("click", function(e) {
if (! confirm(message)) {
e.preventDefault();
}
});
}
})();
</script>
{% endblock %}

View File

@@ -0,0 +1,76 @@
{% load account %}
{% user_display user as user_display %}
{% load i18n %}
{% load inlinecss %}
{% inlinecss "css/emails.css" %}
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Your New {{ settings.PROJECT_LONG_NAME }} Account</title>
</head>
<body itemscope itemtype="http://schema.org/EmailMessage">
<table class="body-wrap">
<tr>
<td></td>
<td class="container" width="600">
<div class="content">
<table class="main" width="100%" cellpadding="0" cellspacing="0" itemprop="action" itemscope
itemtype="http://schema.org/ConfirmAction">
<tr>
<td class="content-wrap">
<meta itemprop="name" content="Confirm Email" />
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block">
<h2>{{ settings.PROJECT_LONG_NAME }}</h2>
</td>
</tr><tr>
<td class="content-block">
You have a new account on {{ settings.PROJECT_LONG_NAME }}. You can log in with the button below.
</td>
</tr>
<tr>
<td class="content-block">
Your username is <i>{{ user.username }}</i>.
</td>
</tr>
<tr>
<td class="content-block" itemprop="handler" itemscope
itemtype="http://schema.org/HttpActionHandler">
<a href="{{ settings.SITE_PROTOCOL }}://{{ settings.SITE_URL }}/accounts/login/" class="btn-primary" itemprop="url">Log in</a>
</td>
</tr>
<tr>
<td class="content-block">
The {{ settings.PROJECT_SHORT_NAME }} team
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="footer">
<table width="100%">
<tr>
</tr>
</table>
</div>
</div>
</td>
<td></td>
</tr>
</table>
</body>
</html>
{% endinlinecss %}

View File

@@ -0,0 +1,10 @@
Dear user,
You have a new account on {{ settings.PROJECT_LONG_NAME }}. You can log in at the link below.
Your username is {{ user.username }}.
Log in: {{ settings.SITE_PROTOCOL }}://{{ settings.SITE_URL }}/accounts/login/
Thanks,
The {{ settings.PROJECT_SHORT_NAME }} team

View File

@@ -0,0 +1,71 @@
{% load account %}
{% user_display user as user_display %}
{% load i18n %}
{% load inlinecss %}
{% inlinecss "css/emails.css" %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Registration confirmation email</title>
</head>
<body itemscope itemtype="http://schema.org/EmailMessage">
<table class="body-wrap">
<tr>
<td></td>
<td class="container" width="600">
<div class="content">
<table class="main" width="100%" cellpadding="0" cellspacing="0" itemprop="action" itemscope itemtype="http://schema.org/ConfirmAction">
<tr>
<td class="content-wrap">
<meta itemprop="name" content="Confirm Email"/>
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block">
<h2>{{ settings.PROJECT_LONG_NAME }}</h2>
</td>
</tr>
<tr>
<td class="content-block">
Please confirm your email address by clicking the link below.
</td>
</tr>
<tr>
<td class="content-block">
We may need to send you critical information about our service, so it is important that we have your correct email address.
</td>
</tr>
<tr>
<td class="content-block" itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler">
<a href="{{ activate_url }}" class="btn-primary" itemprop="url">Confirm email address</a>
</td>
</tr>
<tr>
<td class="content-block">
The {{ settings.PROJECT_SHORT_NAME }} team
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="footer">
<table width="100%">
<tr>
</tr>
</table>
</div></div>
</td>
<td></td>
</tr>
</table>
</body>
</html>
{% endinlinecss %}

View File

@@ -0,0 +1 @@
{% include "account/email/email_confirmation_message.html" %}

View File

@@ -0,0 +1,4 @@
{% load i18n %}
{% autoescape off %}
{% blocktrans %}Confirm Your Registration - {{ settings.PROJECT_SHORT_NAME }}{% endblocktrans %}
{% endautoescape %}

View File

@@ -0,0 +1 @@
Confirm Your Email

View File

@@ -0,0 +1,69 @@
{% load i18n %}
{% load inlinecss %}
{% inlinecss "css/emails.css" %}
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Reset your Password</title>
</head>
<body itemscope itemtype="http://schema.org/EmailMessage">
<table class="body-wrap">
<tr>
<td></td>
<td class="container" width="600">
<div class="content">
<table class="main" width="100%" cellpadding="0" cellspacing="0" itemprop="action" itemscope itemtype="http://schema.org/ConfirmAction">
<tr>
<td class="content-wrap">
<meta itemprop="name" content="Confirm Email"/>
<table width="100%" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block">
<h2>{{ settings.PROJECT_LONG_NAME }}</h2>
</td>
</tr>
<tr>
<td class="content-block">
Please reset your password by clicking the link below.
</td>
</tr>
<tr>
<td class="content-block">
We may need to send you critical information about our service, so it is important that we have your correct email address.
</td>
</tr>
<tr>
<td class="content-block" itemprop="handler" itemscope itemtype="http://schema.org/HttpActionHandler">
<a href="{{ password_reset_url }}" class="btn-primary" itemprop="url">Reset your password</a>
</td>
</tr>
<tr>
<td class="content-block">
The {{ settings.PROJECT_SHORT_NAME }} team
</td>
</tr>
</table>
</td>
</tr>
</table>
<div class="footer">
<table width="100%">
<tr>
</tr>
</table>
</div></div>
</td>
<td></td>
</tr>
</table>
</body>
</html>
{% endinlinecss %}

View File

@@ -0,0 +1,4 @@
{% load i18n %}
{% autoescape off %}
{% blocktrans %}Reset Password - {{ settings.PROJECT_SHORT_NAME }}{% endblocktrans %}
{% endautoescape %}

View File

@@ -0,0 +1,32 @@
{% extends "account/base.html" %}
{% load i18n %}
{% load account %}
{% block head_title %}{% trans "Confirm Email Address" %}{% endblock %}
{% block content %}
<h3 class="h3" id="head_banner">{% trans "Confirm Email Address" %}</h3>
{% if confirmation %}
<p class="verification_sent_info" >{% blocktrans with confirmation.email_address.email as email %} Please confirm ownership of <a href="mailto:{{ email }}">{{ email }}</a>.{% endblocktrans %}</p>
<form method="post"action="{% url 'account_confirm_email' confirmation.key %}">
{% csrf_token %}
<button class="btn btn-success" type="submit">{% trans 'Confirm' %}</button>
</form>
{% else %}
{% url 'account_email' as email_url %}
<p class="verification_sent_info" >{% blocktrans %}This email confirmation link expired or is invalid. Please <a href="{{ email_url }}">request a new email confirmation</a>. You will be redirected to the login page in 5 seconds.{% endblocktrans %}</p>
<script>
setTimeout("location.href = '{% url 'account_login' %}';",5000);
</script>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,62 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_links %}
<title>Sign In</title>
{% endblock %}
{% block head_title %}{% trans "Sign In" %} {% endblock %}
{% block content %}
<h3 class="h3" id="head_banner">{% trans "Sign In" %}</h3>
<form id="login_form" method="POST" action="{% url 'account_login' %}">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
{% endif %}
<button class="btn btn-primary " type="submit">{% trans "Log in" %}</button>
{% if config.ALLOW_SIGNUPS %}
<a class="btn btn-secondary" href="{% url 'account_signup' %}">Sign up</a>
{% endif %}
<a class="btn btn-outline-dark" href="{% url 'account_reset_password' %}">{% trans "Forgot password?" %}</a>
</form>
{% if config.ENABLE_GOOGLE_LOGIN or config.ENABLE_MICROSOFT_LOGIN %}
{% load account socialaccount %}
{% get_providers as socialaccount_providers %}
<h5>Federated Login</h5>
<div class="social_providers_list">
{% for provider in socialaccount_providers %}
{% if provider.name == "Google" and config.ENABLE_GOOGLE_LOGIN or provider.name == "Microsoft Graph" and config.ENABLE_MICROSOFT_LOGIN %}
<form action="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}" method="post">
{% csrf_token %}
<button class="btn btn-outline-dark" type="submit">
{% if provider.name == "Microsoft Graph" %}
Microsoft
{% else %}
{{provider.name}}
{% endif %}
</button>
</form>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Sign Out" %}{% endblock %}
{% block content %}
<h3 class="h3" id="head_banner">{% trans "Sign Out" %}</h3>
<p class="confirm_logout_info">{% trans 'Are you sure you want to sign out?' %}</p>
<form id="logout_form" method="post" action="{% url 'account_logout' %}">
{% csrf_token %}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}"/>
{% endif %}
<button class="btn btn-success" type="submit">{% trans 'Sign Out' %}</button>
<a class="btn btn-danger" id="custom_logout_no" href="/accounts/profile/">{% trans 'No' %}</a>
</form>
{% endblock %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}You cannot remove your primary email address ({{email}}).{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Confirmation email sent to {{email}}.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}You have confirmed {{email}}.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Removed email address {{email}}.{% endblocktrans %}

View File

@@ -0,0 +1,4 @@
{% load account %}
{% load i18n %}
{% user_display user as name %}
{% blocktrans %}Successfully logged in as {{name}}.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}You have signed out.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Password successfully changed.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Password successfully set.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Primary email address set.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Your primary email address must be verified.{% endblocktrans %}

View File

@@ -0,0 +1,18 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Change Password" %}{% endblock %}
{% block content %}
<h3 class="h3" id="head_banner">{% trans "Change Password" %}</h3>
<form method="POST" action="{% url 'account_change_password' %}" id="change_password_form">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
<button class="btn btn-info" type="submit" name="action">{% trans "Change Password" %}</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,26 @@
{% extends "account/base.html" %}
{% load i18n %}
{% load account %}
{% block head_title %}{% trans "Password Reset" %}{% endblock %}
{% block content %}
{% if user.is_authenticated %}
{% include "account/snippets/already_logged_in.html" %}
{% endif %}
<h3 class="h3" id="head_banner">{% trans "Password Reset" %}</h3>
<p class="forgot_password_info" >{% trans "Forgotten your password? Enter your email address below to reset it." %}</p>
<form method="POST" id="forgot_password_form" action="{% url 'account_reset_password' %}">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
<input class="btn btn-outline-primary " type="submit" value="{% trans 'Reset Password' %}" />
</form>
<p class="forgot_password_info">{% blocktrans %}Please contact us if you are unable to reset your password.{% endblocktrans %}</p>
{% endblock %}

View File

@@ -0,0 +1,20 @@
{% extends "account/base.html" %}
{% load i18n %}
{% load account %}
{% block head_title %}{% trans "Password Reset" %}{% endblock %}
{% block content %}
<h3 class="h3" id="head_banner">{% trans "Password Reset Email Sent." %}</h3>
{% if user.is_authenticated %}
{% include "account/snippets/already_logged_in.html" %}
{% block content_extra %} {% endblock %}
{% else %}
<p class="forgot_password_info">A password reset link has been sent to your email address. Please contact us if you do not receive it within a few minutes. <br> You will be redirected to <a href="{% url 'account_login' %}">sign in</a> in 5 seconds.</p>
<script>
setTimeout("location.href = '{% url 'account_login' %}';",5000);
</script>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,24 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Change Password" %}{% endblock %}
{% block content %}
<h1>{% if token_fail %}{% trans "Bad Token" %}{% else %}{% trans "Change Password" %}{% endif %}</h1>
{% if token_fail %}
{% url 'account_reset_password' as passwd_reset_url %}
<p>{% blocktrans %}The password reset link was invalid, possibly because it has already been used. Please request a <a href="{{ passwd_reset_url }}">new password reset</a>.{% endblocktrans %}</p>
{% else %}
{% if form %}
<form method="POST" action="{{ action_url }}">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
<input type="submit" name="action" value="{% trans 'change password' %}"/>
</form>
{% else %}
<p>{% trans 'Your password has been changed.' %}</p>
{% endif %}
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,9 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Change Password" %}{% endblock %}
{% block content %}
<h1>{% trans "Change Password" %}</h1>
<p>{% trans 'Your password has been changed.' %}</p>
{% endblock %}

View File

@@ -0,0 +1,18 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Set Password" %}{% endblock %}
{% block content %}
<h3 class="h3" id="head_banner">{% trans "Set Password" %}</h3>
<form method="POST" action="{% url 'account_set_password' %}" class="password_set">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
<input class="btn btn-outline-primary btn-lg" type="submit" name="action" value="{% trans 'Set Password' %}"/>
</form>
{% endblock %}

View File

@@ -0,0 +1,33 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Sign Up" %}{% endblock %}
{% block head_links %}
<title>Signup</title>
{% endblock %}
{% block content %}
<h3 class="h3" id="head_banner">{% trans "Sign Up" %}</h3>
<p class="exist_account_info">{% blocktrans %}Already have an account? Please <a href="{{ login_url }}">sign in</a>.{% endblocktrans %}</p>
<form id="signup_form" method="post" action="{% url 'account_signup' %}">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
{% endif %}
<button class="btn btn-primary" type="submit">{% trans "Sign Up" %}</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Sign Up Closed" %}{% endblock %}
{% block content %}
<h1>{% trans "Sign Up Closed" %}</h1>
<p>{% blocktrans with project_long_name=settings.PROJECT_LONG_NAME %}Signing up to {{ project_long_name }} is currently not permitted.{% endblocktrans %}</p>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% load i18n %}
{% load account %}
{% block content %}
{% user_display user as user_display %}
<h3 class="h3" id="head_banner">Already Logged In</h3>
<p class="forgot_password_info" >You are already logged in as {{ user_display }}.</p>
<a class="btn btn-primary mb-4" id="custom_logout_no" href="{% url 'account_email' %}">Account Details</a>
{% endblock %}

View File

@@ -0,0 +1,12 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Verify Your Email Address" %}{% endblock %}
{% block content %}
<h3 class="h3" id="head_banner">{% trans "Verify Your Email Address" %}</h3>
<p id="verification_sent_info">{% blocktrans %}A verification email has been sent to your email address. Follow the link provided to activate your account. Contact us if you do not receive it within a few minutes.{% endblocktrans %}</p>
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% extends "account/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Verify Your Email Address" %}{% endblock %}
{% block content %}
<h1>{% trans "Verify Your Email Address" %}</h1>
{% url 'account_email' as email_url %}
<p>{% blocktrans %}This part of the site requires us to verify that
you are who you claim to be. We therefore require you to
verify ownership of your email address. {% endblocktrans %}</p>
<p>{% blocktrans %}A verification email has been sent to your email address. Follow the link provided to activate your account. Contact us
if you do not receive it within a few minutes.{% endblocktrans %}</p>
<p>{% blocktrans %}<strong>Note:</strong> you can still <a href="{{ email_url }}">change your email address</a>.{% endblocktrans %}</p>
{% endblock %}

View File

@@ -4,9 +4,11 @@
{% load i18n %} {% load i18n %}
{% get_current_language as LANGUAGE_CODE %} {% get_current_language as LANGUAGE_CODE %}
{% endif %} {% endif %}
{% load bootstrap_customizer %}
<html lang="{{ LANGUAGE_CODE|default:'en_us' }}"> <html lang="{{ LANGUAGE_CODE|default:'en_us' }}">
{% load pwa %} {% load pwa %}
{% load socialaccount %}
<link rel="manifest" href="/manifest.json"> <link rel="manifest" href="/manifest.json">
@@ -18,6 +20,9 @@
<!-- Bootstrap CSS --> <!-- Bootstrap CSS -->
{% bootstrap_css %} {% bootstrap_css %}
<style lang="css">
{% bootstrap_theme_css_above_the_fold %}
</style>
<link rel="stylesheet" <link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css"
@@ -60,6 +65,7 @@
</head> </head>
<body> <body>
<link rel="stylesheet" type="text/css" href="{% bootstrap_theme_css_below_the_fold_url %}" />
<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">
@@ -76,6 +82,12 @@
<div class="navbar-collapse collapse" id="navbarCollapse"> <div class="navbar-collapse collapse" id="navbarCollapse">
<ul class="navbar-nav mt-2 mt-lg-0"> <ul class="navbar-nav mt-2 mt-lg-0">
<li class="nav-item">
<a href="{% if request.user.person %}{% url 'people:person.profile' %}{% else %}{% url 'people:person.create' %}{% endif %}" class="nav-link">
Profile
</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'people:person.list' %}" class="nav-link">People</a> <a href="{% url 'people:person.list' %}" class="nav-link">People</a>
</li> </li>
@@ -92,15 +104,15 @@
<a href="{% url 'activities:activity.list' %}" class="nav-link">Activities</a> <a href="{% url 'activities:activity.list' %}" class="nav-link">Activities</a>
</li> </li>
<li class="nav-item">
<a href="{% url 'people:map' %}" class="nav-link">Map</a>
</li>
<li class="nav-item">
<a href="{% url 'people:network' %}" class="nav-link">Network</a>
</li>
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<li class="nav-item">
<a href="{% url 'people:map' %}" class="nav-link">Map</a>
</li>
<li class="nav-item">
<a href="{% url 'people:network' %}" class="nav-link">Network</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'export:index' %}" class="nav-link">Export</a> <a href="{% url 'export:index' %}" class="nav-link">Export</a>
</li> </li>
@@ -114,23 +126,18 @@
<ul class="navbar-nav mt-2 mt-lg-0 ml-auto"> <ul class="navbar-nav mt-2 mt-lg-0 ml-auto">
{% if request.user.is_authenticated %} {% if request.user.is_authenticated %}
<li class="nav-item"> <li class="nav-item">
{% if request.user.person %} <a href="{% url 'account_email' %}" class="nav-link">
<a href="{% url 'people:person.profile' %}" class="nav-link"> <i class="fa-solid fa-circle-user"></i>
<i class="fa-solid fa-circle-user"></i> {% if request.user.first_name != "" %}
{{ request.user }} {{ request.user.first_name }}
</a> {% else %}
{{ request.user }}
{% else %} {% endif %}
<a href="{% url 'people:person.create' %}?user" class="nav-link"> </a>
<i class="fa-solid fa-circle-user"></i>
{{ request.user }}
</a>
{% endif %}
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'logout' %}" class="nav-link"> <a href="{% url 'account_logout' %}" class="nav-link">
<i class="fa-solid fa-right-from-bracket"></i> <i class="fa-solid fa-right-from-bracket"></i>
Log Out Log Out
</a> </a>
@@ -138,7 +145,7 @@
{% else %} {% else %}
<li class="nav-item"> <li class="nav-item">
<a href="{% url 'login' %}" class="nav-link"> <a href="{% url 'account_login' %}" class="nav-link">
<i class="fa-solid fa-right-to-bracket"></i> <i class="fa-solid fa-right-to-bracket"></i>
Log In Log In
</a> </a>
@@ -190,7 +197,7 @@
Please fill in your details so you can be part of the network. 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' %}">Profile</a>
</p> </p>
</div> </div>
{% endif %} {% endif %}

View File

@@ -10,13 +10,13 @@
{% block before_content %} {% block before_content %}
{% get_media_prefix as MEDIA_URL %} {% get_media_prefix as MEDIA_URL %}
<header class="container-fluid masthead text-white text-left {% if config.HOMEPAGE_HEADER_IMAGE_SHRINK %}masthead-shrink{% endif %}" <header class="container-fluid masthead text-white text-left {% if config.HOMEPAGE_HEADER_IMAGE_SHRINK %}masthead-shrink{% endif %} py-2"
style="background-image: url('{{ MEDIA_URL }}{{ config.HOMEPAGE_HEADER_IMAGE }}')"> style="background-image: url('{{ MEDIA_URL }}{{ config.HOMEPAGE_HEADER_IMAGE }}')">
<div class="overlay"></div> <div class="overlay"></div>
<div class="row"> <div class="row">
<div class="mx-5 px-4 my-3 pt-3 pb-2 textbox-container"> <div class="px-4 my-3 pt-3 pb-2 textbox-container">
<h1 class="display-1">{{ settings.PROJECT_LONG_NAME }}</h1> <h1>{{ settings.PROJECT_LONG_NAME }}</h1>
<p class="lead">{{ config.PROJECT_LEAD }}</p> <p class="lead">{{ config.PROJECT_LEAD }}</p>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% block content %}
<h2>Offline</h2>
<p>You are currently offline. Please connect to a network to use this platform.</p>
{% endblock %}

View File

@@ -9,8 +9,8 @@
{% bootstrap_form form %} {% bootstrap_form form %}
{% buttons %} {% buttons %}
<input type="hidden" name="next" value="{{ next }}"> <input type="hidden" name="next" value="{{ next }}">
<input type="submit" class="btn btn-info" value="Login"> <input type="submit" class="btn btn-primary" value="Login">
{% endbuttons %} {% endbuttons %}
<p>Forgot your password? Reset it <a href="{% url 'password_reset' %}">here</a>.</p> <p>Forgot your password? Reset it <a href="{% url 'account_reset_password' %}">here</a>.</p>
</form> </form>
{% endblock %} {% endblock %}

View File

@@ -4,5 +4,5 @@
{% block content %} {% block content %}
<h1>Password Reset Complete</h1> <h1>Password Reset Complete</h1>
<p>Your password has been successfully reset. You can now <a href="{% url 'login' %}">log in using it</a>.</p> If you haven't yet completed your profile, please do this now. <p>Your password has been successfully reset. You can now <a href="{% url 'account_login' %}">log in using it</a>.</p> If you haven't yet completed your profile, please do this now.
{% endblock %} {% endblock %}

View File

@@ -17,7 +17,7 @@
{% else %} {% else %}
<p>That link isn't valid - has it already been used? You can request a new link <a href="{% url 'login' %}">here</a>.</p> <p>That link isn't valid - has it already been used? You can request a new link <a href="{% url 'account_reset_password' %}">here</a>.</p>
{% endif %} {% endif %}

View File

@@ -0,0 +1,11 @@
{% extends "socialaccount/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Federated Login Failure" %}{% endblock %}
{% block content %}
<h1>{% trans "Federated Login Failure" %}</h1>
<p>{% trans "An error occurred while attempting to log in via your third party account." %}</p>
{% endblock %}

View File

@@ -0,0 +1 @@
{% extends "account/base.html" %}

View File

@@ -0,0 +1,70 @@
{% extends "socialaccount/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Account Connections" %}{% endblock %}
{% block content %}
{% load i18n %}
<a class="btn btn-primary mb-4" href="{% url 'account_email' %}">{% trans "Account Details" %}</a>
<h2 class="h2" id="head_banner">{% trans "Account Connections" %}</h2>
{% if form.accounts %}
<p>{% blocktrans %}You can sign in to your account using any of the following third party accounts:{% endblocktrans %}
</p>
<form method="post" action="{% url 'socialaccount_connections' %}">
{% csrf_token %}
<fieldset>
{% if form.non_field_errors %}
<div id="errorMsg">{{ form.non_field_errors }}</div>
{% endif %}
{% for base_account in form.accounts %}
{% with base_account.get_provider_account as account %}
<div>
<label for="id_account_{{ base_account.id }}">
<input id="id_account_{{ base_account.id }}" type="radio" name="account"
value="{{ base_account.id }}" />
<span class="socialaccount_provider {{ base_account.provider }} {{ account.get_brand.id }}">
{% if account.get_brand.name == "Microsoft Graph" %}
Microsoft
{% else %}
{{ account.get_brand.name }}
{% endif %}
-
</span>
{{ account }}
</label>
</div>
{% endwith %}
{% endfor %}
<div>
<button class="btn btn-danger" type="submit">{% trans 'Remove' %}</button>
</div>
</fieldset>
</form>
{% else %}
<p>{% trans 'You currently have no third party accounts connected to this account.' %}</p>
{% endif %}
<h3 class="h3" id="head_banner">{% trans 'Add Federated Login Account' %}</h3>
<p>Adding a third party account allows you to log in to this site with it.</p>
<div class="social_providers_list">
{% include "socialaccount/snippets/provider_list.html" with process="connect" %}
</div>
{% include "socialaccount/snippets/login_extra.html" %}
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends "socialaccount/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Login Cancelled" %}{% endblock %}
{% block content %}
<h1>{% trans "Login Cancelled" %}</h1>
{% url 'account_login' as login_url %}
<p>{% blocktrans %}Login was cancelled. If this was a mistake, please proceed to <a href="{{login_url}}">sign in</a>.{% endblocktrans %}</p>
{% endblock %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Your third party account has been connected.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Your third party account is already connected to a different account on this site.{% endblocktrans %}

View File

@@ -0,0 +1,2 @@
{% load i18n %}
{% blocktrans %}Your third party account has been disconnected.{% endblocktrans %}

View File

@@ -0,0 +1,24 @@
{% extends "socialaccount/base.html" %}
{% load i18n %}
{% block head_title %}{% trans "Signup" %}{% endblock %}
{% block content %}
<h1>{% trans "Sign Up" %}</h1>
<p>{% blocktrans with provider_name=account.get_provider.name site_name=site.name %}You are about to use your {% if provider_name == 'Microsoft Graph' %}Microsoft{% else %}{{provider_name}}{% endif %} account to sign up to
{{site_name}}.{% endblocktrans %}</p>
<form class="signup" id="signup_form" method="post" action="{% url 'socialaccount_signup' %}">
{% csrf_token %}
{% load bootstrap4 %}
{% bootstrap_form form %}
{% if redirect_field_value %}
<input type="hidden" name="{{ redirect_field_name }}" value="{{ redirect_field_value }}" />
{% endif %}
<button type="submit">{% trans "Sign Up" %} &raquo;</button>
</form>
{% endblock %}

View File

@@ -0,0 +1,3 @@
{% load socialaccount %}
{% providers_media_js %}

View File

@@ -0,0 +1,22 @@
{% load socialaccount %}
{% get_providers as socialaccount_providers %}
{% for provider in socialaccount_providers %}
{% if provider.id == "openid" %}
{% for brand in provider.get_brands %}
<form action="{% provider_login_url provider.id openid=brand.openid_url process=process %}" method="post">
{% csrf_token %}
<button class="btn btn-outline-dark" type="submit">
{{ brand.name }}
</button>
</form>
{% endfor %}
{% endif %}
<form action="{% provider_login_url provider.id process=process scope=scope auth_params=auth_params %}" method="post">
{% csrf_token %}
<button class="btn btn-outline-dark" type="submit">
{% if provider.name == 'Microsoft Graph' %}Microsoft{% else %}{{provider.name}}{% endif %}
</button>
</form>
{% endfor %}

View File

@@ -17,6 +17,7 @@ from django.contrib import admin
from django.conf import settings from django.conf import settings
from django.urls import include, path from django.urls import include, path
from django.conf.urls.static import static from django.conf.urls.static import static
from bootstrap_customizer import urls as bootstrap_customizer_urls
from . import views from . import views
@@ -33,9 +34,6 @@ urlpatterns = [
path('hijack/', path('hijack/',
include('hijack.urls', namespace='hijack')), include('hijack.urls', namespace='hijack')),
path('',
include('django.contrib.auth.urls')),
path('', path('',
views.IndexView.as_view(), views.IndexView.as_view(),
name='index'), name='index'),
@@ -55,4 +53,10 @@ urlpatterns = [
path('', path('',
include('pwa.urls')), include('pwa.urls')),
path('accounts/',
include('allauth.urls')),
path('bootstrap_customizer',
include(bootstrap_customizer_urls)),
] # yapf: disable ] # yapf: disable

View File

@@ -9,6 +9,8 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.urls import reverse from django.urls import reverse
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.views.generic.edit import UpdateView from django.views.generic.edit import UpdateView
from django.contrib.auth.mixins import UserPassesTestMixin
import typing
from . import forms from . import forms
@@ -19,6 +21,10 @@ class IndexView(TemplateView):
# Template set in Django settings file - may be customised by a customisation app # Template set in Django settings file - may be customised by a customisation app
template_name = settings.TEMPLATE_NAME_INDEX template_name = settings.TEMPLATE_NAME_INDEX
class UserIsStaffMixin(UserPassesTestMixin):
def test_func(self) -> typing.Optional[bool]:
return self.request.user.is_staff
class ConsentTextView(LoginRequiredMixin, UpdateView): class ConsentTextView(LoginRequiredMixin, UpdateView):
"""View with consent text and form for users to indicate consent.""" """View with consent text and form for users to indicate consent."""

View File

@@ -0,0 +1,134 @@
# REQUIRED=Secret key
# Used to generate CSRF tokens - must never be made public
SECRET_KEY=changeme
# REQUIRED=Database password
# The password for the breccia-mapper user in the postgres database
DB_PASSWORD=changeme
# REQUIRED=Google Maps API key
# Google Maps API key to display maps of people's locations - required to enable map functionality
# Default: None
# GOOGLE_MAPS_API_KEY=None
# Debug
# Should the server run in debug mode? Provides information to users which is unsafe in production
# Default: False
DEBUG=False
# Project long name
# The project's full name.
# Default: Project Network Mapper
# PROJECT_LONG_NAME=Project Network Mapper
# Project short name
# The project's short/abbreviated name. This will also be used as the app's name when installed as PWA.
# Default: Network Mapper
# PROJECT_SHORT_NAME=Network Mapper
# Project description
# The project's description. Used when installed as a PWA.
# Default: Application to map network relationships in the organisation.
# PROJECT_DESCRIPTION=Application to map network relationships in the organisation.
# Theme color
# The project's theme color, in hex format (excluding the leading #).
# Default: 212121
# THEME_COLOR=212121
# Background color
# The project's background color, in hex format (excluding the leading #).
# Default: ffffff
# BACKGROUND_COLOR=ffffff
# Allowed hosts
# Accepted values for server header in request - protects against CSRF and CSS attacks
# Default: * if DEBUG else localhost
# ALLOWED_HOSTS=127.0.0.1,localhost,localhost.localdomain
# Site URL
# The URL the site will be deployed on. Do not include http://, https://, or a trailing slash.
# Default: localhost
# SITE_URL=localhost
# Site protocol
# The protocol the site uses. Valid options are http or https.
# Default: http
# SITE_PROTOCOL=http
# Trusted origins
# The trusted origin domains of requests - protects against CSRF and CSS attacks
# Default: '*' if DEBUG else 'http://127.0.0.1,http://localhost,http://localhost.localdomain'
# TRUSTED_ORIGINS=http://127.0.0.1,http://localhost,http://localhost.localdomain
# 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

View File

@@ -0,0 +1,4 @@
all:
hosts:
example.com:

View File

@@ -0,0 +1,104 @@
---
- hosts: all
become_user: root
become_method: sudo
become: yes
pre_tasks:
- name: Check if running under Vagrant
stat:
path: /vagrant
register: vagrant_dir
vars:
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
block:
- name: Add Docker repository
get_url:
url: https://download.docker.com/linux/centos/docker-ce.repo
dest: '/etc/yum.repos.d/docker-ce.repo'
when: deploy_environment is defined and deploy_environment == "vagrant"
- name: Install system dependencies
ansible.builtin.yum:
name:
- git
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
# - name: Update system packages
# ansible.builtin.yum:
# name: '*'
# state: latest
- name: Clone / update from source repos
ansible.builtin.git:
repo: 'https://github.com/Southampton-RSG/breccia-mapper.git'
dest: '{{ project_src_dir }}'
version: dev # master
accept_hostkey: yes
- name: Copy template files
ansible.builtin.template:
src: '{{ item }}.j2'
dest: '{{ project_dir }}/{{ item }}'
mode: 0600
loop:
- Caddyfile
- docker-compose.yml
- name: Copy settings file
ansible.builtin.copy:
src: '.env'
dest: '{{ project_dir }}/.env'
mode: 0600
- name: Copy site icon
ansible.builtin.copy:
src: 'icon-192x192.png'
dest: '{{ project_dir }}/icon-192x192.png'
mode: 0600
- name: Start Docker
ansible.builtin.systemd:
name: docker
state: started
enabled: yes
- name: Pull latest docker images
ansible.builtin.command:
chdir: "{{ project_dir }}"
cmd: docker compose pull {{ item }}
loop:
- caddy
- server
- db
- 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 the password that was provided. This user has unlimited access to the network mapper."
when: provision_superuser

View File

@@ -0,0 +1,16 @@
:80 :443 {
root * /srv
file_server
@proxy_paths {
not path /static/*
not path /media/*
}
reverse_proxy @proxy_paths http://server:8000
log {
output stderr
format console
}
}

View File

@@ -0,0 +1,57 @@
version: '3.1'
services:
server:
image: mgrove36/breccia-mapper:latest
build: {{ project_src_dir }}
ports:
- 8000:8000
environment:
DJANGO_DEBUG: ${DEBUG}
env_file:
- .env
volumes:
- static_files:/app/static
- media_files:/app/media
depends_on:
db:
condition: service_healthy
caddy:
image: caddy:2
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:z
# Caddy serves static files collected by Django
- static_files:/srv/static:ro
- media_files:/srv/media
- {{ project_dir }}/icon-192x192.png:/srv/media/icon-192x192.png:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- server
db:
image: postgres:15.2-alpine
restart: unless-stopped
environment:
POSTGRES_DB: 'breccia-mapper'
POSTGRES_USER: 'breccia-mapper'
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data/
healthcheck:
test: ["CMD-SHELL", "pg_isready -U breccia-mapper"]
interval: 5s
timeout: 5s
retries: 5
volumes:
caddy_data:
caddy_config:
static_files:
media_files:
postgres_data:

View File

@@ -0,0 +1,16 @@
:80 :443 {
root * /srv
file_server
@proxy_paths {
not path /static/*
not path /media/*
}
reverse_proxy @proxy_paths http://server:8000
log {
output stderr
format console
}
}

View File

@@ -0,0 +1,55 @@
version: '3.1'
services:
server:
image: mgrove36/breccia-mapper:latest
build: .
environment:
DJANGO_DEBUG: ${DEBUG}
env_file:
- .env
volumes:
- static_files:/app/static
- media_files:/app/media
depends_on:
db:
condition: service_healthy
caddy:
image: caddy:2
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:z
# Caddy serves static files collected by Django
- static_files:/srv/static:ro
- media_files:/srv/media
- ./icon-192x192.png:/srv/media/icon-192x192.png:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- server
db:
image: postgres:15.2-alpine
restart: unless-stopped
environment:
POSTGRES_DB: 'breccia-mapper'
POSTGRES_USER: 'breccia-mapper'
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data/
healthcheck:
test: ["CMD-SHELL", "pg_isready -U breccia-mapper"]
interval: 5s
timeout: 5s
retries: 5
volumes:
caddy_data:
caddy_config:
static_files:
media_files:
postgres_data:

View File

@@ -0,0 +1,134 @@
# REQUIRED=Secret key
# Used to generate CSRF tokens - must never be made public
SECRET_KEY=changeme
# REQUIRED=Database password
# The password for the breccia-mapper user in the postgres database
DB_PASSWORD=changeme
# REQUIRED=Google Maps API key
# Google Maps API key to display maps of people's locations - required to enable map functionality
# Default: None
# GOOGLE_MAPS_API_KEY=None
# Debug
# Should the server run in debug mode? Provides information to users which is unsafe in production
# Default: False
DEBUG=False
# Project long name
# The project's full name.
# Default: Project Network Mapper
# PROJECT_LONG_NAME=Project Network Mapper
# Project short name
# The project's short/abbreviated name. This will also be used as the app's name when installed as PWA.
# Default: Network Mapper
# PROJECT_SHORT_NAME=Network Mapper
# Project description
# The project's description. Used when installed as a PWA.
# Default: Application to map network relationships in the organisation.
# PROJECT_DESCRIPTION=Application to map network relationships in the organisation.
# Theme color
# The project's theme color, in hex format (excluding the leading #).
# Default: 212121
# THEME_COLOR=212121
# Background color
# The project's background color, in hex format (excluding the leading #).
# Default: ffffff
# BACKGROUND_COLOR=ffffff
# Allowed hosts
# Accepted values for server header in request - protects against CSRF and CSS attacks
# Default: * if DEBUG else localhost
# ALLOWED_HOSTS=127.0.0.1,localhost,localhost.localdomain
# Site URL
# The URL the site will be deployed on. Do not include http://, https://, or a trailing slash.
# Default: localhost
# SITE_URL=localhost
# Site protocol
# The protocol the site uses. Valid options are http or https.
# Default: http
# SITE_PROTOCOL=http
# Trusted origins
# The trusted origin domains of requests - protects against CSRF and CSS attacks
# Default: '*' if DEBUG else 'http://127.0.0.1,http://localhost,http://localhost.localdomain'
# TRUSTED_ORIGINS=http://127.0.0.1,http://localhost,http://localhost.localdomain
# 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

28
deploy/deploy-vagrant/Vagrantfile vendored Normal file
View File

@@ -0,0 +1,28 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# All Vagrant configuration is done below. The "2" in Vagrant.configure
# configures the configuration version (we support older styles for
# backwards compatibility). Please don't change it unless you know what
# you're doing.
Vagrant.configure("2") do |config|
# Every Vagrant development environment requires a box. You can search for
# boxes at https://vagrantcloud.com/search.
config.vm.box = "generic/rocky8"
# Create a forwarded port mapping which allows access to a specific port
# within the machine from a port on the host machine and only allow access
# via 127.0.0.1 to disable public access
config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1"
# Provision VM using Ansible playbook
config.vm.provision "ansible" do |ansible|
ansible.verbose = "v"
ansible.playbook = "playbook.yml"
ansible.host_vars = {
"default" => {
"deploy_environment" => "vagrant"
}
}
end
end

View File

@@ -0,0 +1,134 @@
# REQUIRED=Secret key
# Used to generate CSRF tokens - must never be made public
SECRET_KEY=changeme
# REQUIRED=Database password
# The password for the breccia-mapper user in the postgres database
DB_PASSWORD=changeme
# REQUIRED=Google Maps API key
# Google Maps API key to display maps of people's locations - required to enable map functionality
# Default: None
# GOOGLE_MAPS_API_KEY=None
# Debug
# Should the server run in debug mode? Provides information to users which is unsafe in production
# Default: False
DEBUG=False
# Project long name
# The project's full name.
# Default: Project Network Mapper
# PROJECT_LONG_NAME=Project Network Mapper
# Project short name
# The project's short/abbreviated name. This will also be used as the app's name when installed as PWA.
# Default: Network Mapper
# PROJECT_SHORT_NAME=Network Mapper
# Project description
# The project's description. Used when installed as a PWA.
# Default: Application to map network relationships in the organisation.
# PROJECT_DESCRIPTION=Application to map network relationships in the organisation.
# Theme color
# The project's theme color, in hex format (excluding the leading #).
# Default: 212121
# THEME_COLOR=212121
# Background color
# The project's background color, in hex format (excluding the leading #).
# Default: ffffff
# BACKGROUND_COLOR=ffffff
# Allowed hosts
# Accepted values for server header in request - protects against CSRF and CSS attacks
# Default: * if DEBUG else localhost
# ALLOWED_HOSTS=127.0.0.1,localhost,localhost.localdomain
# Site URL
# The URL the site will be deployed on. Do not include http://, https://, or a trailing slash.
# Default: localhost
# SITE_URL=localhost
# Site protocol
# The protocol the site uses. Valid options are http or https.
# Default: http
# SITE_PROTOCOL=http
# Trusted origins
# The trusted origin domains of requests - protects against CSRF and CSS attacks
# Default: '*' if DEBUG else 'http://127.0.0.1,http://localhost,http://localhost.localdomain'
# TRUSTED_ORIGINS=http://127.0.0.1,http://localhost,http://localhost.localdomain
# 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

View File

@@ -0,0 +1,104 @@
---
- hosts: all
become_user: root
become_method: sudo
become: yes
pre_tasks:
- name: Check if running under Vagrant
stat:
path: /vagrant
register: vagrant_dir
vars:
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
block:
- name: Add Docker repository
get_url:
url: https://download.docker.com/linux/centos/docker-ce.repo
dest: '/etc/yum.repos.d/docker-ce.repo'
when: deploy_environment is defined and deploy_environment == "vagrant"
- name: Install system dependencies
ansible.builtin.yum:
name:
- git
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
# - name: Update system packages
# ansible.builtin.yum:
# name: '*'
# state: latest
- name: Clone / update from source repos
ansible.builtin.git:
repo: 'https://github.com/Southampton-RSG/breccia-mapper.git'
dest: '{{ project_src_dir }}'
version: dev # master
accept_hostkey: yes
- name: Copy template files
ansible.builtin.template:
src: '{{ item }}.j2'
dest: '{{ project_dir }}/{{ item }}'
mode: 0600
loop:
- Caddyfile
- docker-compose.yml
- name: Copy settings file
ansible.builtin.copy:
src: '.env'
dest: '{{ project_dir }}/.env'
mode: 0600
- name: Copy site icon
ansible.builtin.copy:
src: 'icon-192x192.png'
dest: '{{ project_dir }}/icon-192x192.png'
mode: 0600
- name: Start Docker
ansible.builtin.systemd:
name: docker
state: started
enabled: yes
- name: Pull latest docker images
ansible.builtin.command:
chdir: "{{ project_dir }}"
cmd: docker compose pull {{ item }}
loop:
- caddy
- server
- db
- 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 the password that was provided. This user has unlimited access to the network mapper."
when: provision_superuser

View File

@@ -0,0 +1,16 @@
:80 :443 {
root * /srv
file_server
@proxy_paths {
not path /static/*
not path /media/*
}
reverse_proxy @proxy_paths http://server:8000
log {
output stderr
format console
}
}

View File

@@ -0,0 +1,57 @@
version: '3.1'
services:
server:
image: mgrove36/breccia-mapper:latest
build: {{ project_src_dir }}
ports:
- 8000:8000
environment:
DJANGO_DEBUG: ${DEBUG}
env_file:
- .env
volumes:
- static_files:/app/static
- media_files:/app/media
depends_on:
db:
condition: service_healthy
caddy:
image: caddy:2
restart: unless-stopped
ports:
- 80:80
- 443:443
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:z
# Caddy serves static files collected by Django
- static_files:/srv/static:ro
- media_files:/srv/media
- {{ project_dir }}/icon-192x192.png:/srv/media/icon-192x192.png:ro
- caddy_data:/data
- caddy_config:/config
depends_on:
- server
db:
image: postgres:15.2-alpine
restart: unless-stopped
environment:
POSTGRES_DB: 'breccia-mapper'
POSTGRES_USER: 'breccia-mapper'
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data/
healthcheck:
test: ["CMD-SHELL", "pg_isready -U breccia-mapper"]
interval: 5s
timeout: 5s
retries: 5
volumes:
caddy_data:
caddy_config:
static_files:
media_files:
postgres_data:

View File

@@ -2,6 +2,15 @@
# Used to generate CSRF tokens - must never be made public # Used to generate CSRF tokens - must never be made public
SECRET_KEY=changeme SECRET_KEY=changeme
# REQUIRED=Database password
# The password for the breccia-mapper user in the postgres database
DB_PASSWORD=changeme
# REQUIRED=Google Maps API key
# Google Maps API key to display maps of people's locations - required to enable map functionality
# Default: None
# GOOGLE_MAPS_API_KEY=None
# Debug # Debug
# Should the server run in debug mode? Provides information to users which is unsafe in production # Should the server run in debug mode? Provides information to users which is unsafe in production
# Default: False # Default: False
@@ -123,8 +132,3 @@ DEBUG=False
# Cannot be enabled at the same time as EMAIL_USE_TLS # Cannot be enabled at the same time as EMAIL_USE_TLS
# Default: True if EMAIL_PORT == 465 else False # Default: True if EMAIL_PORT == 465 else False
# EMAIL_USE_SSL=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

View File

@@ -71,11 +71,6 @@
dest: '{{ project_dir }}/icon-192x192.png' dest: '{{ project_dir }}/icon-192x192.png'
mode: 0600 mode: 0600
- name: Create database file
ansible.builtin.file:
path: "{{ project_dir }}/db.sqlite3"
state: touch
- name: Start Docker - name: Start Docker
ansible.builtin.systemd: ansible.builtin.systemd:
name: docker name: docker
@@ -89,6 +84,7 @@
loop: loop:
- caddy - caddy
- server - server
- db
- name: Start containers - name: Start containers
ansible.builtin.command: ansible.builtin.command:

View File

@@ -4,16 +4,16 @@ services:
server: server:
image: mgrove36/breccia-mapper:latest image: mgrove36/breccia-mapper:latest
build: {{ project_src_dir }} build: {{ project_src_dir }}
ports:
- 8000:8000
environment: environment:
DJANGO_DEBUG: ${DEBUG} DJANGO_DEBUG: ${DEBUG}
env_file: env_file:
- .env - .env
volumes: volumes:
- {{ project_dir }}/db.sqlite3:/app/db.sqlite3:z
- static_files:/app/static - static_files:/app/static
- media_files:/app/media - media_files:/app/media
depends_on:
db:
condition: service_healthy
caddy: caddy:
image: caddy:2 image: caddy:2
@@ -25,15 +25,31 @@ services:
- ./Caddyfile:/etc/caddy/Caddyfile:z - ./Caddyfile:/etc/caddy/Caddyfile:z
# Caddy serves static files collected by Django # Caddy serves static files collected by Django
- static_files:/srv/static:ro - static_files:/srv/static:ro
- media_files:/srv/media:ro - media_files:/srv/media
- {{ project_dir }}/icon-192x192.png:/srv/media/icon-192x192.png:ro - {{ project_dir }}/icon-192x192.png:/srv/media/icon-192x192.png:ro
- caddy_data:/data - caddy_data:/data
- caddy_config:/config - caddy_config:/config
depends_on: depends_on:
- server - server
db:
image: postgres:15.2-alpine
restart: unless-stopped
environment:
POSTGRES_DB: 'breccia-mapper'
POSTGRES_USER: 'breccia-mapper'
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data/
healthcheck:
test: ["CMD-SHELL", "pg_isready -U breccia-mapper"]
interval: 5s
timeout: 5s
retries: 5
volumes: volumes:
caddy_data: caddy_data:
caddy_config: caddy_config:
static_files: static_files:
media_files: media_files:
postgres_data:

View File

@@ -4,16 +4,16 @@ services:
server: server:
image: mgrove36/breccia-mapper:latest image: mgrove36/breccia-mapper:latest
build: . build: .
ports:
- 8000:8000
environment: environment:
DJANGO_DEBUG: ${DEBUG} DJANGO_DEBUG: ${DEBUG}
env_file: env_file:
- .env - .env
volumes: volumes:
- ./db.sqlite3:/app/db.sqlite3:z
- static_files:/app/static - static_files:/app/static
- media_files:/app/media - media_files:/app/media
depends_on:
db:
condition: service_healthy
caddy: caddy:
image: caddy:2 image: caddy:2
@@ -25,15 +25,31 @@ services:
- ./Caddyfile:/etc/caddy/Caddyfile:z - ./Caddyfile:/etc/caddy/Caddyfile:z
# Caddy serves static files collected by Django # Caddy serves static files collected by Django
- static_files:/srv/static:ro - static_files:/srv/static:ro
- media_files:/srv/media:ro - media_files:/srv/media
- ./icon-192x192.png:/srv/media/icon-192x192.png:ro - ./icon-192x192.png:/srv/media/icon-192x192.png:ro
- caddy_data:/data - caddy_data:/data
- caddy_config:/config - caddy_config:/config
depends_on: depends_on:
- server - server
db:
image: postgres:15.2-alpine
restart: unless-stopped
environment:
POSTGRES_DB: 'breccia-mapper'
POSTGRES_USER: 'breccia-mapper'
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data/
healthcheck:
test: ["CMD-SHELL", "pg_isready -U breccia-mapper"]
interval: 5s
timeout: 5s
retries: 5
volumes: volumes:
caddy_data: caddy_data:
caddy_config: caddy_config:
static_files: static_files:
media_files: media_files:
postgres_data:

199
docs/source/1-deployment.md Normal file
View File

@@ -0,0 +1,199 @@
# Deployment
The [BRECcIA Network Mapper](https://github.com/Southampton-RSG/breccia-mapper) can be deployed in a variety of ways, most of which utilise Docker.
Ansible deployment has been tested on RHEL7 and RHEL8.
## Choosing How to Deploy
If you are an organisation deploying the app on a server, [Ansible](#ansible) is recommended. If Ansible is not used on your server, [Docker Compose](#docker-compose) or [Vagrant](#vagrant) are recommended.
If you are an individual deploying the app on your local machine, [Docker (for individuals)](#docker-for-individuals) is recommended. However, if you are planning on making the app accessible to other people (outside your computer), we advise deploying the app on a server.
## Ansible
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 (`playbook.yml`)
:::
To deploy the BRECcIA Network Mapper with Ansible:
1. Download and extract the deployment files from [the latest release](https://github.com/Southampton-RSG/breccia-mapper/releases/latest):
```bash
curl https://github.com/Southampton-RSG/breccia-mapper/releases/latest/download/deploy-ansible.tar.gz | tar xzv && cd network-mapper
```
2. Copy your logo (192x192 pixels) to `icon-192x192.png` in the `network-mapper` folder.
3. Copy `example.env` to `.env`:
```bash
cp example.env .env
```
4. Edit this file as desired. Note that some variables are required, and that `True` and `False` values must have correct capitalisation.
5. Copy `inventory.example.yml` to `inventory.yml`:
```bash
cp inventory.example.yml inventory.yml
```
6. Edit this file to reflect your Ansible setup:
- Use your server's hostname instead of `example.com`
7. If you would like a new superuser to be provisioned (e.g. during initial install), edit the `provision_superuser` variable in `playbook.yml` to `true`.
- Then change the `superuser_*` options below it as desired.
8. Run the Ansible playbook `playbook.yml` with this inventory file using:
```bash
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.
:::{warning}
If you changed the `provision_superuser` variable in `playbook.yml` to `true`, remember to change it back to `false`.
:::
## 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. Download and extract the deployment files from [the latest release](https://github.com/Southampton-RSG/breccia-mapper/releases/latest):
```bash
curl https://github.com/Southampton-RSG/releases/latest/download/deploy-docker.tar.gz | tar xzv && cd network-mapper
```
2. Copy your logo (192x192 pixels) to `icon-192x192.png` in the `network-mapper` folder.
3. Copy `example.env` to `.env`:
```bash
cp example.env .env
```
4. Edit this file as desired. Note that some variables are required, and that `True` and `False` values must have correct capitalisation.
5. Start the containers with the following command (you may need to use `sudo`):
```bash
docker compose up -d
```
6. If desired (e.g. on initial deployment), 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"
```
:::{important}
If you don't create a superuser when you first deploy the app, you will be unable to log in.
:::
## Vagrant
Prerequisites:
- [Vagrant](https://www.vagrantup.com/)
- [Ansible](https://www.ansible.com/)
To deploy the BRECcIA Network Mapper with Vagrant:
1. Download and extract the deployment files from [the latest release](https://github.com/Southampton-RSG/breccia-mapper/releases/latest):
```bash
curl https://github.com/Southampton-RSG/releases/latest/download/deploy-vagrant.tar.gz | tar xzv && cd network-mapper
```
2. Copy your logo (192x192 pixels) to `icon-192x192.png` in the `network-mapper` folder.
3. Copy `example.env` to `.env`:
```bash
cp example.env .env
```
4. Edit this file as desired. Note that some variables are required, and that `True` and `False` values must have correct capitalisation.
5. If you would like a new superuser to be provisioned (e.g. during initial install), edit the `provision_superuser` variable in `playbook.yml` to `true`.
- Then change the `superuser_*` options below it as desired.
6. To change where the app is accessible from, edit the `config.vm.network` line in `Vagrantfile`.
- By default, the app is accessible only from `http://localhost:8080`.
- To make it available from any IP address, replace `host: 8080, host_ip: "127.0.0.1"` with `host: 8080`.
- To change the port the app is available on, edit `host: 8080`.
- More details are available in the [Vagrant docs](https://developer.hashicorp.com/vagrant/docs/networking).
6. Start the virtual machine:
```bash
vagrant up
```
7. Deploy the Network Mapper on the virtual machine:
```bash
vagrant provision
```
:::{note}
To stop the virtual machine, run `vagrant halt` in this directory. More commands are explained in the [Vagrant docs](https://developer.hashicorp.com/vagrant/docs/cli).
:::
## Docker (for individuals)
This is the recommended deployment method for individuals who are not planning on making the network mapper accessible to other users.
:::{warning}
The network mapper will not run with Docker on Arm-based devices. This includes devices with Apple silicon - e.g. M1 and M2 Macs.
To run it on these devices you will need to build it yourself, which requires additional knowledge of Docker.
:::
Prerequisites:
- [Docker Compose](https://docs.docker.com/compose) is installed and running. If you are not familiar with Docker, we recommend using [Docker Desktop](https://docs.docker.com/desktop/). Simply install and run it.
To deploy the BRECcIA Network Mapper with Docker:
1. Download `deploy-docker.zip` [from the latest release](https://github.com/Southampton-RSG/breccia-mapper/releases/latest).
2. Extract the zip file into an appropriate folder.
3. Copy your logo (192x192 pixels) to `icon-192x192.png` in the `network-mapper` folder.
4. Copy `example.env` to `.env` in this folder.
5. Edit this file as desired. Note that some variables are required, and that `True` and `False` values must have correct capitalisation.
- Variables are set with the following syntax, in this case setting the `DEBUG` variable to `False`:
```Dotenv
DEBUG=False
```
6. Open a terminal window in this folder. On Windows, do this by holding `Shift` and right clicking inside the folder; then selecting either `Open in Terminal` or `Open PowerShell window here`.
6. Start the network mapper with the following command:
```bash
docker compose up -d
```
7. If desired (e.g. on initial deployment), 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"
```
Once the network mapper has been started for the first time with the above steps, it will appear in Docker Desktop (if installed). It can then be stopped/started again from here.
To stop it from the terminal, run
```bash
docker compose down
```

View File

@@ -0,0 +1,193 @@
# Configuration
After installing the [BRECcIA Network Mapper](https://github.com/Southampton-RSG/breccia-mapper), some configuration must be done in the web interface. You will need to log in with an admin account.
:::{note}
Admin dashboard: this is the admin section of the site, accessed by clicking `Admin` in the navigation bar at the top of the screen.
:::
## Setup
### Activity Mediums
Each activity medium is a property of an activity - intended for storing, for example, whether the activity was face-to-face or virtual. Add an activity medium for each medium you wish to be available.
- In the admin dashboard, click `Add` next to `Activity mediums` and provide the name of the medium.
### Activity Types
Activities are categorised into types. Add an activity type for each type you wish to be available. For example: meetings, seminars, and training courses.
- In the admin dashboard, click `Add` next to `Activity types` and provide the name of the activity type.
### Activity Series
Activities in a series (e.g. a set of connected meetings or courses) can be linked together with an activity series. Activity series are created in the admin dashboard, and activities are linked to series when creating or updating the activities.
When new activity series next to be created, create them in the admin dashboard.
- In the admin dashboard, click `Add` next to `Activity series` and provide the name of the series, along with the type and medium of the activities in this series.
### Site Theme
If desired, you can adjust the site's theming through the admin dashboard. All of the below steps are optional.
- In the admin dashboard, click `Bootstrap themes` under `BOOTSTRAP_CUSTOMIZER`, then select `Default Theme`.
- Toggle rounded corners (e.g. for buttons), shadows, and gradients with the checkboxes.
- Click each colour to change it - a pop-up will appear allowing you to choose the new colour.
- Change the site's fonts by replacing the content of the `Font family base` field.
- Change the font size with the `Font size base` field.
- Change the line height with the `Line height base` field.
- Change the main background and text colours with the `Body background` and `Body color` fields respectively.
- Change the heading font by expanding the `Headings` section and replacing the content of the `Headings font family` field.
- Change the heading font weight (i.e. thickness) by expanding the `Headings` section and replacing the content of the `Headings font weight` field.
- Change the heading line height by expanding the `Headings` section and replacing the content of the `Headings line height` field.
Then click `Save`.
### Main Configuration Options
There are several general settings which must be configured. Open the admin dashboard and click `Config` under `CONSTANCE`, then configure the available settings, as explained below.
- `CONSENT_TEXT`: The text shown to users when requesting consent to use their data. This text accompanies a checkbox captioned `I have read and understood this information and consent to my data being used in this way`.
- `PERSON_LIST_HELP`: Help text to display at the top of the people list.
- `ORGANISATION_LIST_HELP`: Help text to display at the top of the organisaton list.
- `RELATIONSHIP_FORM_HELP`: Help text to display at the top of relationship forms.
- `HOMEPAGE_HEADER_IMAGE_SHRINK`: Whether the homepage header image should be shrunk to display the whole image at all times.
- `HOMEPAGE_HEADER_IMAGE`: The header image for the homepage.
- `HOMEPAGE_CARD_1_TITLE`: The title for the first card displayed on the homepage.
- `HOMEPAGE_CARD_1_DESCRIPTION`: The description for the first card displayed on the homepage.
- `HOMEPAGE_CARD_1_ICON`: The icon for the first card displayed on the homepage. Icons are FontAwesome 6 icons, which are listed [here](https://fontawesome.com/v6/search?m=free).
- `HOMEPAGE_CARD_2_TITLE`: The title for the second card displayed on the homepage.
- `HOMEPAGE_CARD_2_DESCRIPTION`: The description for the second card displayed on the homepage.
- `HOMEPAGE_CARD_2_ICON`: The icon for the second card displayed on the homepage. Icons are FontAwesome 6 icons, which are listed [here](https://fontawesome.com/v6/search?m=free).
- `HOMEPAGE_CARD_3_TITLE`: The title for the third card displayed on the homepage.
- `HOMEPAGE_CARD_3_DESCRIPTION`: The description for the third card displayed on the homepage.
- `HOMEPAGE_CARD_3_ICON`: The icon for the third card displayed on the homepage. Icons are FontAwesome 6 icons, which are listed [here](https://fontawesome.com/v6/search?m=free).
- `HOMEPAGE_ABOUT_TITLE`: The title for the about section on the homepage.
- `HOMEPAGE_ABOUT_CONTENT`: The content for the about section on the homepage. HTML is accepted.
- `HOMEPAGE_ABOUT_IMAGE`: The image for the about section on the homepage.
- `NOTICE_TEXT`: Text to be displayed in a notice banner at the top of every page.
- `NOTICE_CLASS`: CSS class to use for the background of the notice banner.
- `PARENT_PROJECT_NAME`: The name of the project's parent project, if one exists.
- `PROJECT_LEAD`: The project's lead person, organisation, or similar.
- `PROJECT_TAGLINE`: The project's tagline.
- `ALLOW_SIGNUPS`: Whether new users should be able to sign themselves up through the site. This is useful for minimising the number of users that must be manually created, and it is recommended that this feature is disabled once all users have created their accounts.
- `ENABLE_GOOGLE_LOGIN`: Whether login through Google should be enabled. Only enable this if Google login has been configured. See [Google login](#google).
- `ENABLE_MICROSOFT_LOGIN`: Whether login through Microsoft should be enabled. Only enable this if Microsoft login has been configured. See [Microsoft login](#microsoft).
Then click `Save`.
### Questions
There are several types of questions, but they all have the same set of information stored in them:
- `Version`: The version of the question - this should be incremented when the question is altered.
- `Text`: The main text of the question.
- `Filter text`: Each question appears as a filter on the `Network` page. This is alternative text to replace the full question as the filter title. If left blank, the main text of the question will be used.
- `Help text`: Help text shown below the question when people enter their answer.
- `Answer is public`: Whether answers to this question should be considered public.
- `Is multiple choice`: Whether users should only be allowed to select multiple options, instead of just one.
- `Hardcoded field`: The name of the hardcoded field that the question relates to. This is not applicable for non-preset questions (i.e. ones that you create).
- `Allow free text`: Whether users should be allowed to enter their own text in addition to choosing an available option. When a user enters free text, a new option will be created with their input.
- `Order`: The priority order for the question to be displayed in. Lower priority questions will be displayed earlier in the list of questions.
- Question choices
- `Text`: The main text of the option.
- `Order`: The priority order for the option to be displayed in. Lower priority options will be displayed earlier in the list of options.
- `Is negative response`: Only applicable for relationship questions. Whether the option indicates the relationship is of the lowest level of closeness. This allows automatic population of questions if the relationship being reported is not at all close. All options marked as negative responses will be selected when this button is pressed.
- `Delete?`: Whether the option should be deleted when the question is saved.
There are various types of questions, which can be added by clicking `Add` next to the appropriate section in the admin dashboard:
- Organisation questions: Questions that are shown when creating an organisation or updating its details.
- Organisation relationship questions: Questions that are shown when reporting a relationship with an organisation.
- Person questions: Questions that are shown when creating a person or updating their details.
- Relationship questions: Questions that are shown when reporting a relationship with a person.
### Existing Questions
Some "person questions" (i.e. questions about people) are preset, but require answers to be provided.
1. In the admin dashboard, click `Person questions` under `PEOPLE`, then select each of the following questions in turn:
- Disciplines
- Research theme affiliation
- Role
2. For each question, provide a set of possible answers as per the instructions [above](#questions).
:::{note}
At least one organisation must also exist for people to be able to create their profiles (unless the organisation question is removed). To create an organisation follow the instructions [below](#organisations).
:::
### Organisations
Organisations are created through the main site (not the admin dashboard).
1. Click `Organisations` in the navigation bar at the top of the screen.
2. Click `New Organisation` at the top of the page.
2. Answer the questions shown.
4. Finally click `Submit`.
### People
:::{note}
When a `Person` is created for a user who does not have an account, an anonymous account is created for them - with login disabled and a random username (starting `autogen_`). This allows administrators to edit their details on their behalf. You should not delete these users unless you wish to delete all data relating to that person.
:::
People are created through the main site (not the admin dashboard). People should only be created when it is known that the person will not have a login of their own to the site - e.g. for researchers logging data for multiple individuals.
1. Click `People` in the navigation bar at the top of the screen.
2. Click `New Person` at the top of the page.
2. Answer the questions shown.
4. Finally click `Submit`.
## Federated Login
Federated login is supported for Google an Microsoft accounts, and can be set up through the `SOCIAL ACCOUNTS` section of the admin dashboard.
### Google
You will first need to create an OAuth application in Google Cloud Platform. You can do this by following [Google's guide](https://support.google.com/cloud/answer/6158849). The redirect URL is `[your-site-url]/accounts/google/login/callback/`.
Then:
- In the admin dashboard, click `Add` next to `Social applications`.
- Select `Google` as the provider.
- Enter `Google` as the name.
- Enter the client ID from Google in the `Client id` field.
- Enter the client secret from Google in the `Secret key` field.
- Leave the `Key` field blank.
- Click `Choose all` under the `Available sites` box, to enable Google login on this site.
- Click `Save`.
Then enable Google login with the `ENABLE_GOOGLE_LOGIN` option in the `Config` section of the admin dashboard, as per [these instructions](#main-configuration-options).
### Microsoft
You will first need to create an OAuth application in Azure Active Directory. You can do this by following [Microsoft's guide](https://learn.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app). The redirect URL is `[your-site-url]/accounts/microsoft/login/callback/`. You will also need to add a client secret.
Then:
- In the admin dashboard, click `Add` next to `Social applications`.
- Select `Microsoft Graph` as the provider.
- Enter `Microsoft` as the name.
- Enter the `Application (client) ID` from Azure in the `Client id` field. This is obtained from the `Overview` page and is different to the `Secret ID`.
- Enter the client secret value from Google in the `Secret key` field.
- Leave the `Key` field blank.
- Click `Choose all` under the `Available sites` box, to enable Google login on this site.
- Click `Save`.
Then enable Microsoft login with the `ENABLE_MICROSOFT_LOGIN` option in the `Config` section of the admin dashboard, as per [these instructions](#main-configuration-options).
(inviting_users)=
## Inviting Users
In the admin dashboard, click `Add` next to `Users`. Choose a username for them, enter their email address, and enter a temporary password. It does not matter what this password is, and you do not need to remember or store it, as the new user is automatically sent an email welcoming them to the platform and providing instructions for how to reset their password.
If the user you wish to invite has already had a `Person` created for them, they will already have an account with login disabled and a random username (starting `autogen_`). To find this account, go to `People` (in the `PEOPLE` section) in the admin dashboard and select the appropriate person. Then click the blue eye icon next to the `User` field.
![Person's user relation with blue eye icon](images/2-person-user.png "Person's user relation with blue eye icon")
This will take you to the user, and you can then change their username and add a first name, last name, and email address. Note that a welcome email will not be sent to the user in this case, so you will have to let them know that their account is active and what their username is. They can use the *forgot password* option when logging in to set their password.

View File

@@ -0,0 +1,74 @@
# Admin Usage
You will need to log in with an administrator account to perform these actions.
:::{note}
Admin dashboard: this is the admin section of the site, accessed by clicking `Admin` in the navigation bar at the top of the screen.
:::
## Inviting Users
Please see [Configuration](inviting_users).
## Deleting Users
If you wish to permanently delete a user and their data:
1. Open the admin dashboard and click `Users`, in the `PEOPLE` section.
2. Locate and click the user you wish to delete.
3. Scroll to the bottom of the page and click `Delete`.
## Disable Login for a User
If you wish to disable login for a user but retain their data (relationships and personal details):
1. Open the admin dashboard and click `Users`, in the `PEOPLE` section.
2. Locate and click the user you wish to delete.
3. Under `Permissions`, disable `Active`.
4. Scroll to the bottom and click `Save`.
To allow the user to log in again, perform the same actions but enable the `Active` option.
## Promoting a User to Administrator
The user you wish to make an administrator must already exist. Then:
1. Open the admin dashboard and click `Users`, in the `PEOPLE` section.
2. Locate and click the user you wish to delete.
3. Under `Permissions`, enable `Staff status` and `Superuser status`.
4. Scroll to the bottom and click `Save`.
## Edit an Activity
1. Open the admin dashboard and click `Activities`, in the `ACTIVITIES` section.
2. Locate and click the activity you wish to update.
3. Update the fields appropriately.
4. Finally click `Save`.
## View a Map of People and Organisations
:::{note}
This page is only available to administrators.
:::
To view an interactive map showing people and organisations that have reported their locations, click `Map` in the navigation bar at the top of the screen. You can toggle the visibility of people and organisations with the buttons at the top of the page.
`Person` nodes on the map have the same colour as the buttons at the top of the page. Nodes can be clicked to show the name of the person or organisation they represent, and these in turn can be clicked to view the person's or organisation's profile.
## View a Graph of the Network
:::{note}
This page is only available to administrators.
:::
The network mapper provides an interface to view the network as a graph. To access it, click `Network` in the navigation bar at the top of the screen. This graph can be customised with various filters as shown on the page. Setting the date will show the state of the network as it was on the given date. People and organisations can be anonymised, organisations can be hidden, and the graph can be downloaded as an image. These options are all available as buttons on the page.
The graph can also be manipulated with the mouse.
## Export Data
:::{note}
This page is only available to administrators.
:::
All data relating to the network can be exported as CSV files. To do this, click `Export` in the navigation bar at the top of the screen, then click `Export` next to the data you wish to export. This can then be manipulated as a spreadsheet or with a tool like R to perform more complex data analysis than is available natively in the network mapper.

View File

@@ -0,0 +1,92 @@
# General Usage
## Create Your Profile
Until you have created your profile, you will be prompted to do so. You cannot add relationships until this has been done. To create your profile:
1. Click `Profile` in the banner at the top of the screen.
![Profile banner](images/4-profile-banner.png "Profile banner")
2. Fill in your name and continue to the next step.
3. Provide consent for your data to be stored and continue to the next step.
4. Fill in the questions shown, and find your location on the map at the bottom of the page (if available). Click your location to select it.
5. Click `Save`.
## Update Your Profile
Click `Profile` in the navigation bar at the top of the screen. If you have not yet provided consent for your data to be stored, you will first be asked for this. You will then be presented with your profile.
1. Click `Update`
2. Update your answers to the questions, and optionally update your location on the map at the bottom of the page (if available). Click your location to select it.
3. Click `Save`.
## Add a Relationship
1. Click `People` in the navigation bar at the top of the screen.
2. Locate the person you wish to record a relationship with and click `Add Relationship` next to their name.
3. If you have an existing but very limited relationship with this person, you can click the `Autofill` button at the top of the page to automatically fill in appropriate responses. This will default your relationship to the lowest level of closeness. Otherwise, fill in the listed questions.
4. Click `Submit`.
## Update a Relationship
1. Click `People` in the navigation bar at the top of the screen.
2. Locate the person you wish to update your relationship with and click `Update Relationship` next to their name.
3. If you have an existing but very limited relationship with this person, you can click the `Autofill` button at the top of the page to automatically fill in appropriate responses. This will default your relationship to the lowest level of closeness. Otherwise, fill in the listed questions.
4. Click `Submit`.
## End a Relationship
1. Click `People` in the navigation bar at the top of the screen.
2. Locate the person you wish to record a relationship with and click `Profile` next to their name.
4. Click `End Relationship` at the top of the page.
## Create an Activity
1. Click `Activities` in the navigation bar at the top of the screen.
2. Click `New Activity` at the top of the page.
2. Answer the questions shown.
4. Click `Submit`.
:::{note}
Once an ativity has been created, it can only be edited by administrators.
:::
## Record Activity Attendance
Once an activity has been created, users should record their attendance.
1. Click `Activities` in the navigation bar at the top of the screen.
2. Locate the activity you wish to record attendance for and click `Details` next to its name.
3. At the top of the page, click `Attend`.
## Cancel Activity Attendance
Once an activity has been created, users should record their attendance. They can also remove their attendance after previously registering it.
1. Click `Activities` in the navigation bar at the top of the screen.
2. Locate the activity you wish to record attendance for and click `Details` next to its name.
3. At the top of the page, click `Cancel Attendance`.
## Manage Your Account Details
To manage your account details, click your username in the top right corner of the screen, next to the `Log Out` button.
![Click your username to open your account details](images/4-open-account-details.png)
Alternatively, you can manage your account by going to your profile (click `Profile` in the navigation bar at the top of the screen), then clicking `Account Details`.
## Enable Login With Google or Microsoft
1. Open your account details by following [the steps above](#manage-your-account-details).
2. Under `Federated Login`, click `Manage`.
3. Click `Google` or `Microsoft` to link your account.
## Remove a Linked Google or Microsoft Account
1. Open your account details by following [the steps above](#manage-your-account-details).
2. Under `Federated Login`, click `Manage`.
3. Under `Account Connections`, click the radius button next to the account you wish to remove.
4. Click `Remove`.

View File

@@ -9,7 +9,7 @@
project = 'BRECcIA Network Mapper' project = 'BRECcIA Network Mapper'
copyright = 'Matthew Grove, University of Southampton' copyright = 'Matthew Grove, University of Southampton'
author = 'Matthew Grove' author = 'Matthew Grove'
release = '1.1' release = '2.0.0'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

View File

@@ -1,186 +0,0 @@
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.
# Ansible
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:
<!-- -------------------------------------------------------
NOTES
pull deploy folder only
navigate to folder
copy icon to icon-192x192.png in folder
copy example.env to .env and edit
copy inventory.example.yml to inventory.yml and edit
edit playbook if superuser desired
run playbook
set provision_superuser to false if was changed
------------------------------------------------------- -->
1. Download and extract the deployment files from [the latest release](https://github.com/Southampton-RSG/breccia-mapper/releases/latest):
```bash
curl https://github.com/Southampton-RSG/breccia-mapper/releases/latest/download/deploy-ansible.tar | tar xzv && cd deploy-ansible
```
2. Copy your logo (192x192 pixels) to `icon-192x192.png` in this folder.
3. Copy `example.env` to `.env`:
```bash
cp example.env .env
```
4. Edit this file as desired. Note that some variables are required.
5. Copy `inventory.example.yml` to `inventory.yml`:
```bash
cp inventory.example.yml inventory.yml
```
6. Edit this file to reflect your Ansible setup:
- Use your server's hostname instead of `example.com`
7. If you would like a new superuser to be provisioned (e.g. during initial install), edit the `provision_superuser` variable in `playbook.yml` to `true`.
- Then change the `superuser_*` options below it as desired.
8. Run the Ansible playbook `playbook.yml` with this inventory file using:
```bash
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.
:::{warning}
If you changed the `provision_superuser` variable in `playbook.yml` to `true`, remember to change it back to `false`.
:::
# 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
:::
<!-- -------------------------------------------------------
NOTES
create folder
pull docker compose file and example.env only
copy icon to icon-192x192.png in folder
copy example.env to .env and edit
touch db file? (is this needed?)
run docker compose up -d
create superuser if desired
------------------------------------------------------- -->
To deploy the BRECcIA Network Mapper with Docker:
1. Download and extract the deployment files from [the latest release](https://github.com/Southampton-RSG/breccia-mapper/releases/latest):
```bash
curl https://github.com/mgrove36/Southampton-RSG/releases/latest/download/deploy-docker.tar | tar xzv && cd deploy-docker
```
2. Copy your logo (192x192 pixels) to `icon-192x192.png` in this folder.
3. Copy `example.env` to `.env`:
```bash
cp example.env .env
```
4. Edit this file as desired. Note that some variables are required.
3. Create the database using:
```bash
touch db.sqlite3
```
5. Start the containers with the following command (you may need to use `sudo`):
```bash
docker compose up -d
```
6. If desired (e.g. on initial deployment), 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"
```
:::{important}
If you don't create a superuser when you first deploy the app, you will be unable to log in.
:::
# Vagrant
Prerequisites:
- [Vagrant](https://www.vagrantup.com/)
- [Ansible](https://www.ansible.com/)
<!-- -------------------------------------------------------
NOTES
pull deploy folder only
navigate to folder
copy icon to icon-192x192.png in folder
copy example.env to .env and edit
edit playbook if superuser desired
run vagrant up and/or vagrant provision
set provision_superuser to false if was changed
------------------------------------------------------- -->
To deploy the BRECcIA Network Mapper with Vagrant:
1. Download and extract the deployment files from [the latest release](https://github.com/Southampton-RSG/breccia-mapper/releases/latest):
```bash
curl https://github.com/mgrove36/Southampton-RSG/releases/latest/download/deploy-vagrant.tar | tar xzv && cd deploy-vagrant
```
2. Copy your logo (192x192 pixels) to `icon-192x192.png` in this folder.
3. Copy `example.env` to `.env`:
```bash
cp example.env .env
```
4. Edit this file as desired. Note that some variables are required.
5. If you would like a new superuser to be provisioned (e.g. during initial install), edit the `provision_superuser` variable in `playbook.yml` to `true`.
- Then change the `superuser_*` options below it as desired.
6. To change where the app is accessible from, edit the `config.vm.network` line in `Vagrantfile`.
- By default, the app is accessible only from `http://localhost:8080`.
- To make it available from any IP address, replace `host: 8080, host_ip: "127.0.0.1"` with `host: 8080`.
- To change the port the app is available on, edit `host: 8080`.
- More details are available in the [Vagrant docs](https://developer.hashicorp.com/vagrant/docs/networking).
6. Start the virtual machine:
```bash
vagrant up
```
7. Deploy the Network Mapper on the virtual machine:
```bash
vagrant provision
```
:::{note}
To stop the virtual machine, run `vagrant halt` in this directory. More commands are explained in the [Vagrant docs](https://www.vagrantup.com/docs/cli).
:::
<!-- # Build From Source -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View File

@@ -1,13 +1,13 @@
# BRECcIA Network Mapper documentation # 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. [![GitHub latest release](https://img.shields.io/github/downloads/Southampton-RSG/breccia-mapper/latest/total?label=Latest%20release)](https://github.com/Southampton-RSG/breccia-mapper/releases/latest)
The BRECcIA Network Mapper [(view on GitHub)](https://github.com/Southampton-RSG/breccia-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.
It can be deployed by individuals or organisations, and supports login with username/password, Google, and Microsoft.
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. 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 ## Contents
```{toctree} ```{toctree}

View File

@@ -3,6 +3,9 @@
set -eo pipefail set -eo pipefail
python manage.py migrate python manage.py migrate
echo "[{\"model\": \"sites.site\",\"pk\": 1,\"fields\": { \"domain\": \"${SITE_URL}\", \"name\": \"${PROJECT_SHORT_NAME}\" }}]" | python manage.py loaddata --format=json -
python manage.py selectiveloaddata breccia_mapper/fixtures/bootstrap_customizer_theme.json
python manage.py loaddata --format=json bootstrap_customizer_sitetheme
python manage.py collectstatic --no-input python manage.py collectstatic --no-input
exec "$@" exec "$@"

View File

@@ -1,21 +1,16 @@
import csv import csv
import typing import typing
from django.contrib.auth.mixins import UserPassesTestMixin
from django.http import HttpResponse from django.http import HttpResponse
from django.views.generic import TemplateView from django.views.generic import TemplateView
from django.views.generic.list import BaseListView from django.views.generic.list import BaseListView
from breccia_mapper.views import UserIsStaffMixin
class QuotedCsv(csv.excel): class QuotedCsv(csv.excel):
quoting = csv.QUOTE_NONNUMERIC quoting = csv.QUOTE_NONNUMERIC
class UserIsStaffMixin(UserPassesTestMixin):
def test_func(self) -> typing.Optional[bool]:
return self.request.user.is_staff
class CsvExportView(UserIsStaffMixin, BaseListView): class CsvExportView(UserIsStaffMixin, BaseListView):
model = None model = None
serializer_class = None serializer_class = None

View File

@@ -7,8 +7,8 @@
"created": "2020-04-27T12:13:30.448Z", "created": "2020-04-27T12:13:30.448Z",
"last_updated": "2020-04-27T14:45:27.152Z", "last_updated": "2020-04-27T14:45:27.152Z",
"subject": "Welcome to {{settings.PROJECT_LONG_NAME}}", "subject": "Welcome to {{settings.PROJECT_LONG_NAME}}",
"content": "Dear user,\r\n\r\nWelcome to {{ settings.PROJECT_LONG_NAME }}. You can set your password at {{ settings.SITE_PROTOCOL }}://{{ settings.SITE_URL }}/password_reset/.\r\n\r\nYour username is {{ user.username }}.\r\nThanks,\r\n\r\nThe {{ settings.PROJECT_SHORT_NAME }} team", "content": "{% include 'account/email/email_account_creation_message.txt' %}",
"html_content": "<h1>{{ settings.PROJECT_LONG_NAME }}</h1><br/><p>Dear user,</p><br/><p>Welcome to {{ settings.PROJECT_LONG_NAME }}. You can set your password <a href='{{ settings.SITE_PROTOCOL }}://{{ settings.SITE_URL }}/password_reset/'>here</a>.</p><p>Your username is {{ user.username }}.</p><br/><p>Thanks,</p><p>The {{ settings.PROJECT_SHORT_NAME }} team</p>", "html_content": "{% include 'account/email/email_account_creation_message.html' %}",
"language": "", "language": "",
"default_template": null "default_template": null
} }

View File

@@ -195,7 +195,7 @@ class PersonAnswerSetForm(forms.ModelForm, DynamicAnswerSetBase):
'project_started_date': 'project_started_date':
f'Date started on the {config.PARENT_PROJECT_NAME} project', f'Date started on the {config.PARENT_PROJECT_NAME} project',
'external_organisations': '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' 'Which external organisations do you work with that are involved in a related field/industry?'
} }
help_texts = { help_texts = {
'organisation_started_date': 'organisation_started_date':

Some files were not shown because too many files have changed in this diff Show More