Merge pull request #124 from Southampton-RSG/dev

Update master to v2.0.0
This commit is contained in:
2023-03-25 14:45:55 +00:00
committed by GitHub
142 changed files with 3851 additions and 239 deletions

View File

@@ -15,4 +15,7 @@ mail.log/
/static/ /static/
*.sqlite3* *.sqlite3*
*.log* *.log*
deployment* deployment*
/docs/
.readthedocs.yaml

3
.gitignore vendored
View File

@@ -25,3 +25,6 @@ deployment-key*
/custom /custom
staging.yml staging.yml
production.yml production.yml
# Docs local builds
/docs/build

25
.readthedocs.yaml Normal file
View File

@@ -0,0 +1,25 @@
# .readthedocs.yaml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the version of Python and other tools you might need
build:
os: ubuntu-22.04
tools:
python: "3.11"
# Build documentation in the docs/ directory with Sphinx
sphinx:
configuration: docs/source/conf.py
# If using Sphinx, optionally build your docs in additional formats such as PDF
formats:
- pdf
# Optionally declare the Python requirements required to build your docs
python:
install:
- requirements: docs/source/requirements.txt

5
Caddyfile Normal file → Executable file
View File

@@ -4,12 +4,13 @@
@proxy_paths { @proxy_paths {
not path /static/* not path /static/*
not path /media/*
} }
reverse_proxy @proxy_paths http://web:8000 reverse_proxy @proxy_paths http://server:8000
log { log {
output stderr output stderr
format single_field common_log format console
} }
} }

2
Dockerfile Normal file → Executable file
View File

@@ -1,4 +1,4 @@
FROM python:3.8-slim FROM python:3.9-slim
RUN groupadd -r mapper && useradd --no-log-init -r -g mapper mapper RUN groupadd -r mapper && useradd --no-log-init -r -g mapper mapper

View File

@@ -1,5 +1,7 @@
# BRECcIA Mapper # BRECcIA Mapper
[![Documentation Status](https://readthedocs.org/projects/breccia/badge/?version=latest)](https://breccia.readthedocs.io/en/latest/?badge=latest)
BRECcIA Mapper is a web app to collect and explore data about the relationships between researchers and their stakeholders on large-scale, multi-site research projects. 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.
Through this it is possible to explore the extent of networks and change over time, and identify where new relationships can be developed or existing ones strengthened. Through this it is possible to explore the extent of networks and change over time, and identify where new relationships can be developed or existing ones strengthened.
@@ -14,6 +16,7 @@ Deployment is managed using [Ansible](https://www.ansible.com/) and Docker (http
## Contributors ## Contributors
- James Graham (@jag1g13) - developer - James Graham (@jag1g13) - developer
- Matthew Grove (@mgrove36) - developer
- Genevieve Agaba - Genevieve Agaba
- Sebastian Reichel - Sebastian Reichel
- Claire Bedelian - Claire Bedelian

View File

@@ -0,0 +1,33 @@
# Generated by Django 4.1.4 on 2023-01-05 16:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('activities', '0006_activity_attendance_optional'),
]
operations = [
migrations.AlterField(
model_name='activity',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='activitymedium',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='activityseries',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
migrations.AlterField(
model_name='activitytype',
name='id',
field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
]

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

@@ -13,20 +13,8 @@ Before production deployment, see
https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
Many configuration settings are input from `settings.ini`. Many configuration settings are input from `.env`.
The most likely required settings are: SECRET_KEY, DEBUG, ALLOWED_HOSTS, DATABASE_URL, PROJECT_*_NAME, EMAIL_* The most likely required settings are: SECRET_KEY, DEBUG, ALLOWED_HOSTS, PROJECT_*_NAME, EMAIL_*
- PARENT_PROJECT_NAME
default: Parent Project Name
Displayed in templates where the name of the parent project should be used
- PROJECT_LONG_NAME
default: Project Long Name
Displayed in templates where the full name of the project should be used
- PROJECT_SHORT_NAME
default: shortname
Displayed in templates where a short identifier for the project should be used
- SECRET_KEY (REQUIRED) - SECRET_KEY (REQUIRED)
Used to generate CSRF tokens - must never be made public Used to generate CSRF tokens - must never be made public
@@ -35,14 +23,38 @@ The most likely required settings are: SECRET_KEY, DEBUG, ALLOWED_HOSTS, DATABAS
default: False default: False
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
- SITE_URL
default: localhost
The URL the site will be deployed on. Do not include http://, https://, or a trailing slash.
- SITE_PROTOCOL
default: http
The protocol the site uses. Valid options are http or https.
- PROJECT_LONG_NAME
default: Project Network Mapper
The project's full name.
- PROJECT_SHORT_NAME
default: Network Mapper
The project's short/abbreviated name. This will also be used as the app's name when installed as PWA.
- PROJECT_DESCRIPTION
default: Application to map network relationships in the organisation.
The project's description. Used when installed as a PWA.
- THEME_COLOR
default: 212121
The project's theme color, in hex format (excluding the leading #).
- BACKGROUND_COLOR
default: ffffff
The project's background color, in hex format (excluding the leading #).
- ALLOWED_HOSTS - ALLOWED_HOSTS
default: * if DEBUG else localhost default: * if DEBUG else localhost
Accepted values for server header in request - protects against CSRF and CSS attacks Accepted values for server header in request - protects against CSRF and CSS attacks
- DATABASE_URL
default: sqlite://db.sqlite3
URL to database - uses format described at https://github.com/jacobian/dj-database-url
- DBBACKUP_STORAGE_LOCATION - DBBACKUP_STORAGE_LOCATION
default: .dbbackup default: .dbbackup
Directory where database backups should be stored Directory where database backups should be stored
@@ -118,16 +130,16 @@ import dj_database_url
SETTINGS_EXPORT = [ SETTINGS_EXPORT = [
'DEBUG', 'DEBUG',
'PARENT_PROJECT_NAME', 'SITE_URL',
'SITE_PROTOCOL',
'GOOGLE_MAPS_API_KEY',
'PROJECT_LONG_NAME', 'PROJECT_LONG_NAME',
'PROJECT_SHORT_NAME', 'PROJECT_SHORT_NAME',
'GOOGLE_MAPS_API_KEY', 'PROJECT_DESCRIPTION',
'THEME_COLOR',
'BACKGROUND_COLOR',
] ]
PARENT_PROJECT_NAME = config('PARENT_PROJECT_NAME',
default='Parent Project Name')
PROJECT_LONG_NAME = config('PROJECT_LONG_NAME', default='Project Long Name')
PROJECT_SHORT_NAME = config('PROJECT_SHORT_NAME', default='shortname')
# Build paths inside the project like this: BASE_DIR.joinpath(...) # Build paths inside the project like this: BASE_DIR.joinpath(...)
BASE_DIR = pathlib.Path(__file__).parent.parent BASE_DIR = pathlib.Path(__file__).parent.parent
@@ -143,6 +155,27 @@ ALLOWED_HOSTS = config(
default='*' if DEBUG else '127.0.0.1,localhost,localhost.localdomain', default='*' if DEBUG else '127.0.0.1,localhost,localhost.localdomain',
cast=Csv()) cast=Csv())
# Site URL
SITE_URL = config('SITE_URL', default='localhost')
SITE_PROTOCOL = config('SITE_PROTOCOL', default='http')
# CORS settings
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = True
CORS_ALLOW_CREDENTIALS = True
CSRF_TRUSTED_ORIGINS = config(
'TRUSTED_ORIGINS',
default='*' if DEBUG else 'http://127.0.0.1,http://localhost,http://localhost.localdomain',
cast=Csv())
CORS_REPLACE_HTTPS_REFERER = True
CSRF_COOKIE_DOMAIN = config(
'SITE_URL',
default='localhost')
CORS_ORIGIN_WHITELIST = config(
'TRUSTED_ORIGINS',
default='*' if DEBUG else 'http://127.0.0.1,http://localhost,http://localhost.localdomain',
cast=Csv())
# Application definition # Application definition
DJANGO_APPS = [ DJANGO_APPS = [
@@ -152,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 = [
@@ -165,10 +199,18 @@ THIRD_PARTY_APPS = [
'post_office', 'post_office',
'bootstrap_datepicker_plus', 'bootstrap_datepicker_plus',
'hijack', 'hijack',
'compat', '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',
@@ -184,6 +226,13 @@ MIDDLEWARE = [
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'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'
@@ -208,14 +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': 'default' : {
config('DATABASE_URL', 'ENGINE': 'django.db.backends.postgresql',
default='sqlite:///' + str(BASE_DIR.joinpath('db.sqlite3')), 'NAME': 'breccia-mapper',
cast=dj_database_url.parse) 'USER': 'breccia-mapper',
'PASSWORD': config('DB_PASSWORD'),
'HOST': 'db',
'PORT': '5432',
}
} }
# Django DBBackup # Django DBBackup
@@ -271,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')
@@ -297,6 +377,10 @@ STATIC_ROOT = BASE_DIR.joinpath('static')
STATICFILES_DIRS = [BASE_DIR.joinpath('breccia_mapper', 'static')] STATICFILES_DIRS = [BASE_DIR.joinpath('breccia_mapper', 'static')]
# Media uploads
MEDIA_ROOT = BASE_DIR.joinpath('media')
MEDIA_URL = "/media/"
# Logging - NB the logger name is empty to capture all output # Logging - NB the logger name is empty to capture all output
LOGGING = { LOGGING = {
@@ -340,6 +424,10 @@ logger = logging.getLogger(__name__) # pylint: disable=invalid-name
# Admin panel variables # Admin panel variables
CONSTANCE_ADDITIONAL_FIELDS = {
'image_field': ['django.forms.ImageField', {}]
}
CONSTANCE_CONFIG = { CONSTANCE_CONFIG = {
'NOTICE_TEXT': ( 'NOTICE_TEXT': (
'', '',
@@ -359,17 +447,103 @@ CONSTANCE_CONFIG = {
'RELATIONSHIP_FORM_HELP': ( 'RELATIONSHIP_FORM_HELP': (
'', '',
'Help text to display at the top of relationship forms.'), 'Help text to display at the top of relationship forms.'),
'PARENT_PROJECT_NAME': (
'',
'Parent project name'),
'PROJECT_LEAD': (
'Project Lead',
'Project lead'),
'PROJECT_TAGLINE': (
'Here is your project\'s 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': (
'800x500.png',
'Homepage header image',
'image_field'),
'HOMEPAGE_HEADER_IMAGE_SHRINK': (
False,
'Shrink the homepage header image to display the whole image at all times'),
'HOMEPAGE_CARD_1_TITLE': (
'Step 1',
'Homepage card #1 title'),
'HOMEPAGE_CARD_1_DESCRIPTION': (
'Tell us about your position within the project',
'Homepage card #1 description'),
'HOMEPAGE_CARD_1_ICON': (
'building-user',
'Homepage card #1 icon'),
'HOMEPAGE_CARD_2_TITLE': (
'Step 2',
'Homepage card #2 title'),
'HOMEPAGE_CARD_2_DESCRIPTION': (
'Describe your relationships with other stakeholders',
'Homepage card #2 description'),
'HOMEPAGE_CARD_2_ICON': (
'handshake-simple',
'Homepage card #2 icon'),
'HOMEPAGE_CARD_3_TITLE': (
'Step 3',
'Homepage card #3 title'),
'HOMEPAGE_CARD_3_DESCRIPTION': (
'Use the network view to analyse relationships',
'Homepage card #3 description'),
'HOMEPAGE_CARD_3_ICON': (
'diagram-project',
'Homepage card #3 icon'),
'HOMEPAGE_ABOUT_TITLE': (
'About Us',
'Homepage about section title'),
'HOMEPAGE_ABOUT_CONTENT': (
"""Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. In massa tempor nec feugiat nisl. Eget dolor morbi non arcu risus quis varius quam quisque. Nisl pretium fusce id velit ut tortor pretium viverra suspendisse. Vitae auctor eu augue ut lectus arcu. Tellus molestie nunc non blandit massa enim nec. At consectetur lorem donec massa sapien. Placerat orci nulla pellentesque dignissim enim sit. Sit amet mauris commodo quis imperdiet. Tellus at urna condimentum mattis pellentesque.<br/>In vitae turpis massa sed. Fermentum posuere urna nec tincidunt praesent semper feugiat nibh sed. Ut consequat semper viverra nam libero justo laoreet. Velit ut tortor pretium viverra suspendisse potenti nullam ac tortor. Nunc id cursus metus aliquam eleifend mi in nulla posuere. Aliquam eleifend mi in nulla posuere sollicitudin aliquam. Est ante in nibh mauris cursus mattis molestie a iaculis. Nunc id cursus metus aliquam. Auctor urna nunc id cursus metus aliquam. Porttitor lacus luctus accumsan tortor posuere ac ut consequat semper. Volutpat consequat mauris nunc congue nisi. Leo vel fringilla est ullamcorper eget. Vitae purus faucibus ornare suspendisse sed nisi lacus sed. Massa id neque aliquam vestibulum morbi blandit. Iaculis nunc sed augue lacus viverra vitae congue. Sodales neque sodales ut etiam.""",
'Homepage about section content'),
'HOMEPAGE_ABOUT_IMAGE': (
'400x400.png',
'Homepage about section image',
'image_field'),
} # yapf: disable } # yapf: disable
CONSTANCE_CONFIG_FIELDSETS = { CONSTANCE_CONFIG_FIELDSETS = {
'Notice Banner': ( 'Project options': (
'PARENT_PROJECT_NAME',
'PROJECT_LEAD',
'PROJECT_TAGLINE',
'ALLOW_SIGNUPS',
'ENABLE_GOOGLE_LOGIN',
'ENABLE_MICROSOFT_LOGIN',
),
'Homepage configuration': (
'HOMEPAGE_HEADER_IMAGE_SHRINK',
'HOMEPAGE_HEADER_IMAGE',
'HOMEPAGE_CARD_1_TITLE',
'HOMEPAGE_CARD_1_DESCRIPTION',
'HOMEPAGE_CARD_1_ICON',
'HOMEPAGE_CARD_2_TITLE',
'HOMEPAGE_CARD_2_DESCRIPTION',
'HOMEPAGE_CARD_2_ICON',
'HOMEPAGE_CARD_3_TITLE',
'HOMEPAGE_CARD_3_DESCRIPTION',
'HOMEPAGE_CARD_3_ICON',
'HOMEPAGE_ABOUT_TITLE',
'HOMEPAGE_ABOUT_CONTENT',
'HOMEPAGE_ABOUT_IMAGE',
),
'Notice banner': (
'NOTICE_TEXT', 'NOTICE_TEXT',
'NOTICE_CLASS', 'NOTICE_CLASS',
), ),
'Data Collection': ( 'Data Collection': (
'CONSENT_TEXT', 'CONSENT_TEXT',
), ),
'Help Text': ( 'Help text': (
'PERSON_LIST_HELP', 'PERSON_LIST_HELP',
'ORGANISATION_LIST_HELP', 'ORGANISATION_LIST_HELP',
'RELATIONSHIP_FORM_HELP', 'RELATIONSHIP_FORM_HELP',
@@ -396,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:
@@ -417,6 +591,53 @@ else:
default=(EMAIL_PORT == 465), default=(EMAIL_PORT == 465),
cast=bool) cast=bool)
# Bootstrap Datepicker Plus Settings
BOOTSTRAP_DATEPICKER_PLUS = {
"variant_options": {
"date": {
"format": "YYYY-MM-DD",
},
}
}
# PWA settings
PWA_SERVICE_WORKER_PATH = BASE_DIR.joinpath('static/js', 'serviceworker.js')
PWA_APP_NAME = PROJECT_SHORT_NAME
PWA_APP_DESCRIPTION = PROJECT_DESCRIPTION
PWA_APP_THEME_COLOR = THEME_COLOR
PWA_APP_BACKGROUND_COLOR = BACKGROUND_COLOR
PWA_APP_DISPLAY = 'standalone'
PWA_APP_SCOPE = '/'
PWA_APP_ORIENTATION = 'any'
PWA_APP_START_URL = '/'
PWA_APP_STATUS_BAR_COLOR = 'default'
PWA_APP_ICONS = [
{
'src': '/media/icon-192x192.png',
'sizes': '192x192'
}
]
PWA_APP_ICONS_APPLE = [
{
'src': '/media/icon-192x192.png',
'sizes': '192x192'
}
]
PWA_APP_SPLASH_SCREEN = [
{
'src': '/media/icon-192x192.png',
'media': '(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)'
}
]
PWA_APP_DIR = 'ltr'
PWA_APP_LANG = 'en-GB'
PWA_APP_DEBUG_MODE = DEBUG
# Database default automatic primary key
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
# Upstream API keys # Upstream API keys
GOOGLE_MAPS_API_KEY = config('GOOGLE_MAPS_API_KEY', default=None) GOOGLE_MAPS_API_KEY = config('GOOGLE_MAPS_API_KEY', default=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,10 +12,31 @@ 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;
line-height: 60px; line-height: 60px;
flex-shrink: 0; flex-shrink: 0;
} }
.footer .container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
/* end of sticky footer styles */ /* end of sticky footer styles */

View File

@@ -1,10 +1,10 @@
header.masthead { header.masthead {
position: relative; position: relative;
background: #343a40 no-repeat center; background: #343a40 no-repeat center;
-webkit-background-size: contain; -webkit-background-size: cover;
-moz-background-size: contain; -moz-background-size: cover;
-o-background-size: contain; -o-background-size: cover;
background-size: contain; background-size: cover;
padding-top: 8rem; padding-top: 8rem;
padding-bottom: 8rem; padding-bottom: 8rem;
min-height: 400px; min-height: 400px;
@@ -12,6 +12,13 @@ header.masthead {
z-index: -2; z-index: -2;
} }
header.masthead.masthead-shrink {
-webkit-background-size: contain;
-moz-background-size: contain;
-o-background-size: contain;
background-size: contain;
}
header.masthead .overlay { header.masthead .overlay {
position: absolute; position: absolute;
background-color: #212529; background-color: #212529;
@@ -27,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

@@ -0,0 +1,48 @@
var staticCacheName = "django-pwa-v" + new Date().getTime();
var filesToCache = [
"/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/solid.min.css",
"/static/css/global.css",
"/static/js/serviceworker.js",
"/static/hijack/hijack.min.css",
"/media/icon-192x192.png",
];
// Cache on install
self.addEventListener("install", event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
return cache.addAll(filesToCache);
})
)
});
// Clear cache on activate
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("django-pwa-")))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
})
);
});
// Serve from Cache
self.addEventListener("fetch", event => {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
.catch(() => {
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 %}

111
breccia_mapper/templates/base.html Normal file → Executable file
View File

@@ -4,8 +4,14 @@
{% 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 socialaccount %}
<link rel="manifest" href="/manifest.json">
<head> <head>
<!-- Required meta tags --> <!-- Required meta tags -->
<meta charset="utf-8"> <meta charset="utf-8">
@@ -14,23 +20,28 @@
<!-- 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/5.11.2/css/fontawesome.min.css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/fontawesome.min.css"
integrity="sha256-/sdxenK1NDowSNuphgwjv8wSosSNZB0t5koXqd7XqOI=" integrity="sha512-giQeaPns4lQTBMRpOOHsYnGw1tGVzbAIHUyHRgn7+6FmiEgGGjaG0T2LZJmAPMzRCl+Cug0ItQ2xDZpTmEc+CQ=="
crossorigin="anonymous" /> crossorigin="anonymous"
referrerpolicy="no-referrer" />
<link rel="stylesheet" <link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/solid.min.css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.1/css/solid.min.css"
integrity="sha256-8DcgqUGhWHHsTLj1qcGr0OuPbKkN1RwDjIbZ6DKh/RA=" integrity="sha512-6mc0R607di/biCutMUtU9K7NtNewiGQzrvWX4bWTeqmljZdJrwYvKJtnhgR+Ryvj+NRJ8+NnnCM/biGqMe/iRA=="
crossorigin="anonymous" /> crossorigin="anonymous"
referrerpolicy="no-referrer" />
{% load staticfiles %} {% load static %}
<link rel="stylesheet" href="{% static 'css/global.css' %}"> <link rel="stylesheet" href="{% static 'css/global.css' %}">
<link rel="stylesheet" <link rel="stylesheet"
type="text/css" type="text/css"
href="{% static 'hijack/hijack-styles.css' %}" /> href="{% static 'hijack/hijack.min.css' %}" />
{% if 'javascript_in_head'|bootstrap_setting %} {% if 'javascript_in_head'|bootstrap_setting %}
{% if 'include_jquery'|bootstrap_setting %} {% if 'include_jquery'|bootstrap_setting %}
@@ -46,11 +57,15 @@
{{ form.media.css }} {{ form.media.css }}
{% endif %} {% endif %}
<!-- PWA metadata -->
{% progressive_web_app_meta %}
{% block extra_head %}{% endblock %} {% block extra_head %}{% endblock %}
</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">
@@ -67,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>
@@ -83,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>
@@ -105,32 +126,27 @@
<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="fas fa-user-circle"></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="fas fa-user-circle"></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="fas fa-sign-out-alt"></i> <i class="fa-solid fa-right-from-bracket"></i>
Log Out Log Out
</a> </a>
</li> </li>
{% 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="fas fa-sign-in-alt"></i> <i class="fa-solid fa-right-to-bracket"></i>
Log In Log In
</a> </a>
</li> </li>
@@ -149,8 +165,30 @@
</div> </div>
{% endif %} {% endif %}
{% load hijack_tags %} {% load hijack %}
{% hijack_notification %}
{# Hijack notification if user is hijacked #}
{% if person.user == request.user and request.user.is_hijacked %}
<div class="djhj" id="djhj">
<div class="djhj-notification">
<div class="djhj-message">
{% blocktrans trimmed with user=request.user %}
You are currently working on behalf of <em>{{ user }}</em>.
{% endblocktrans %}
</div>
<form action="{% url 'hijack:release' %}" method="POST" class="djhj-actions">
{% csrf_token %}
<input type="hidden" name="next" value="{{ request.path }}">
<button class="djhj-button" onclick="document.getElementById('djhj').style.display = 'none';" type="button">
{% trans 'hide' %}
</button>
<button class="djhj-button" type="submit">
{% trans 'release' %}
</button>
</form>
</div>
</div>
{% endif %}
{% if request.user.is_authenticated and not request.user.has_person %} {% if request.user.is_authenticated and not request.user.has_person %}
<div class="alert alert-info rounded-0" role="alert"> <div class="alert alert-info rounded-0" role="alert">
@@ -159,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 %}
@@ -190,6 +228,7 @@
<footer class="footer bg-light"> <footer class="footer bg-light">
<div class="container"> <div class="container">
<span class="text-muted">{{ settings.PROJECT_LONG_NAME }}</span> <span class="text-muted">{{ settings.PROJECT_LONG_NAME }}</span>
<span class="text-muted">Developed by the <a href="https://gcrf-breccia.com">BRECcIA</a> team</span>
</div> </div>
</footer> </footer>

View File

@@ -1,62 +1,89 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block extra_head %} {% block extra_head %}
{% load staticfiles %} {% load static %}
<link rel="stylesheet" <link rel="stylesheet"
href="{% static 'css/masthead.css' %}"> href="{% static 'css/masthead.css' %}">
{% endblock %} {% endblock %}
{% block before_content %} {% block before_content %}
<header class="container-fluid masthead text-white text-left"
style="background-image: url('https://via.placeholder.com/800x500')"> {% get_media_prefix as MEDIA_URL %}
<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 }}')">
<div class="overlay"></div> <div class="overlay"></div>
<div class="row"> <div class="row">
<div class="ml-5 px-4 mt-3 pt-3 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">Snappy leader here...</p> <p class="lead">{{ config.PROJECT_LEAD }}</p>
</div> </div>
</div> </div>
</header> </header>
<div class="bg-secondary py-3"> <div class="bg-secondary py-3">
<div class="container text-white"> <div class="container text-white">
<h2>Snappy tagline here...</h2> <h2>{{ config.PROJECT_TAGLINE }}</h2>
</div> </div>
</div> </div>
<div class="bg-light py-2 mb-4"> <div class="bg-light py-2 mb-4">
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<div class="col-md-4"> {% if config.HOMEPAGE_CARD_1_TITLE %}
<div class="col-md-4 mx-auto">
<div class="card text-center"> <div class="card text-center">
<div class="card-body"> <div class="card-body">
<h2 class="card-title">Do Feature 1</h2> <h2 class="card-title">{{ config.HOMEPAGE_CARD_1_TITLE }}</h2>
<span class="fas fa-5x fa-atlas"></span> {% if config.HOMEPAGE_CARD_1_DESCRIPTION %}
<p>{{ config.HOMEPAGE_CARD_1_DESCRIPTION|safe }}</p>
{% endif %}
{% if config.HOMEPAGE_CARD_1_ICON %}
<span class="fa-solid fa-5x fa-{{ config.HOMEPAGE_CARD_1_ICON }}"></span>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endif %}
<div class="col-md-4"> {% if config.HOMEPAGE_CARD_2_TITLE %}
<div class="col-md-4 mx-auto">
<div class="card text-center"> <div class="card text-center">
<div class="card-body"> <div class="card-body">
<h2 class="card-title">Do Feature 2</h2> <h2 class="card-title">{{ config.HOMEPAGE_CARD_2_TITLE }}</h2>
<span class="fas fa-5x fa-atlas"></span> {% if config.HOMEPAGE_CARD_2_DESCRIPTION %}
<p>{{ config.HOMEPAGE_CARD_2_DESCRIPTION|safe }}</p>
{% endif %}
{% if config.HOMEPAGE_CARD_2_ICON %}
<span class="fa-solid fa-5x fa-{{ config.HOMEPAGE_CARD_2_ICON }}"></span>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endif %}
<div class="col-md-4"> {% if config.HOMEPAGE_CARD_3_TITLE %}
<div class="col-md-4 mx-auto">
<div class="card text-center"> <div class="card text-center">
<div class="card-body"> <div class="card-body">
<h2 class="card-title">Do Feature 3</h2> <h2 class="card-title">{{ config.HOMEPAGE_CARD_3_TITLE }}</h2>
<span class="fas fa-5x fa-atlas"></span> {% if config.HOMEPAGE_CARD_3_DESCRIPTION %}
<p>{{ config.HOMEPAGE_CARD_3_DESCRIPTION|safe }}</p>
{% endif %}
{% if config.HOMEPAGE_CARD_3_ICON %}
<span class="fa-solid fa-5x fa-{{ config.HOMEPAGE_CARD_3_ICON }}"></span>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@@ -65,15 +92,16 @@
{% block content %} {% block content %}
<div class="row align-items-center" style="min-height: 400px;"> <div class="row align-items-center" style="min-height: 400px;">
<div class="col-sm-8"> <div class="col-sm-8">
<h2 class="pb-2">About {{ settings.PROJECT_LONG_NAME }}</h2> <h2 class="pb-2">{{ config.HOMEPAGE_ABOUT_TITLE }}</h2>
<p> <p>
{{ settings.PROJECT_LONG_NAME }} is... {{ config.HOMEPAGE_ABOUT_CONTENT|safe }}
</p> </p>
</div> </div>
<div class="col-sm-4"> <div class="col-sm-4">
<img class="img-fluid py-3" src="https://via.placeholder.com/400x400"> {% get_media_prefix as MEDIA_URL %}
<img class="img-fluid py-3" src="{{ MEDIA_URL }}{{ config.HOMEPAGE_ABOUT_IMAGE }}">
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

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,7 +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 'account_reset_password' %}">here</a>.</p>
</form> </form>
{% endblock %} {% endblock %}

View File

@@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<h1>Password Reset Complete</h1>
<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 %}

View File

@@ -0,0 +1,24 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
{% if validlink %}
<h1>Choose a New Password</h1>
<form action="" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<input type="hidden" name="next" value="{{ next }}">
<input type="submit" class="btn btn-info" value="Change password">
{% endbuttons %}
</form>
{% else %}
<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 %}
{% endblock %}

View File

@@ -0,0 +1,8 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<h1>Check Your Inbox</h1>
<p>We've emailed you instructions for how to reset your password.</p>
{% endblock %}

View File

@@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block content %}
<h1>Password Reset</h1>
<form action="" method="post">
{% csrf_token %}
{% bootstrap_form form %}
{% buttons %}
<input type="hidden" name="next" value="{{ next }}">
<input type="submit" class="btn btn-info" value="Send reset link">
{% endbuttons %}
</form>
{% endblock %}

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

@@ -14,10 +14,16 @@ Including another URLconf
2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
""" """
from django.contrib import admin from django.contrib import admin
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 bootstrap_customizer import urls as bootstrap_customizer_urls
from . import views from . import views
admin.site.site_header = settings.PROJECT_LONG_NAME + " Admin"
admin.site.site_title = settings.PROJECT_SHORT_NAME + " Admin"
urlpatterns = [ urlpatterns = [
path('admin/', path('admin/',
admin.site.urls), admin.site.urls),
@@ -28,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'),
@@ -47,4 +50,13 @@ urlpatterns = [
path('', path('',
include('activities.urls')), include('activities.urls')),
path('',
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."""

4
deploy/Vagrantfile vendored
View File

@@ -21,9 +21,7 @@ Vagrant.configure("2") do |config|
ansible.playbook = "playbook.yml" ansible.playbook = "playbook.yml"
ansible.host_vars = { ansible.host_vars = {
"default" => { "default" => {
"deploy_environment" => "vagrant", "deploy_environment" => "vagrant"
"django_debug" => 1,
"django_secret_key" => "debug_only_g62WlORMbo8iAcV7vKCKBQ=="
} }
} }
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,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,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:

134
deploy/example.env Normal file
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

@@ -1,5 +1,4 @@
all: all:
hosts: hosts:
example.com: example.com:
django_debug: 1
django_secret_key: debug_only_g62WlORMbo8iAcV7vKCKBQ==

View File

@@ -11,9 +11,13 @@
register: vagrant_dir register: vagrant_dir
vars: vars:
project_name: mapper project_name: network-mapper
project_dir: /srv/{{ project_name }} project_dir: /srv/{{ project_name }}
project_src_dir: "{{ project_dir }}/src" project_src_dir: "{{ project_dir }}/src"
provision_superuser: false
superuser_username: admin
superuser_password: admin
superuser_email: email@example.com
tasks: tasks:
- name: Vagrant specific tasks - name: Vagrant specific tasks
@@ -43,7 +47,7 @@
ansible.builtin.git: ansible.builtin.git:
repo: 'https://github.com/Southampton-RSG/breccia-mapper.git' repo: 'https://github.com/Southampton-RSG/breccia-mapper.git'
dest: '{{ project_src_dir }}' dest: '{{ project_src_dir }}'
version: docker version: dev # master
accept_hostkey: yes accept_hostkey: yes
- name: Copy template files - name: Copy template files
@@ -55,10 +59,17 @@
- Caddyfile - Caddyfile
- docker-compose.yml - docker-compose.yml
- name: Create database file - name: Copy settings file
ansible.builtin.file: ansible.builtin.copy:
path: "{{ project_dir }}/db.sqlite3" src: '.env'
state: touch 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 - name: Start Docker
ansible.builtin.systemd: ansible.builtin.systemd:
@@ -72,15 +83,22 @@
cmd: docker compose pull {{ item }} cmd: docker compose pull {{ item }}
loop: loop:
- caddy - caddy
- server
- name: Build custom images - db
ansible.builtin.command:
chdir: "{{ project_dir }}"
cmd: docker compose build {{ item }}
loop:
- web
- name: Start containers - name: Start containers
ansible.builtin.command: ansible.builtin.command:
chdir: "{{ project_dir }}" chdir: "{{ project_dir }}"
cmd: docker compose up -d 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

7
deploy/templates/Caddyfile.j2 Normal file → Executable file
View File

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

38
deploy/templates/docker-compose.yml.j2 Normal file → Executable file
View File

@@ -1,18 +1,19 @@
version: '3.1' version: '3.1'
services: services:
web: server:
image: breccia-mapper image: mgrove36/breccia-mapper:latest
build: {{ project_src_dir }} build: {{ project_src_dir }}
ports:
- 8000:8000
environment: environment:
DEBUG: {{ django_debug }} DJANGO_DEBUG: ${DEBUG}
DATABASE_URL: sqlite:////app/db.sqlite3 env_file:
SECRET_KEY: {{ django_secret_key }} - .env
volumes: volumes:
- {{ project_dir }}/db.sqlite3:/app/db.sqlite3:z
- static_files:/app/static - static_files:/app/static
- media_files:/app/media
depends_on:
db:
condition: service_healthy
caddy: caddy:
image: caddy:2 image: caddy:2
@@ -24,12 +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
- {{ 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:
- web - 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:
postgres_data:

View File

@@ -1,18 +1,19 @@
version: '3.1' version: '3.1'
services: services:
web: server:
image: breccia-mapper image: mgrove36/breccia-mapper:latest
build: . build: .
ports:
- 8000:8000
environment: environment:
DEBUG: ${DJANGO_DEBUG} DJANGO_DEBUG: ${DEBUG}
DATABASE_URL: sqlite:////app/db.sqlite3 env_file:
SECRET_KEY: ${DJANGO_SECRET_KEY} - .env
volumes: volumes:
- ./db.sqlite3:/app/db.sqlite3:z
- static_files:/app/static - static_files:/app/static
- media_files:/app/media
depends_on:
db:
condition: service_healthy
caddy: caddy:
image: caddy:2 image: caddy:2
@@ -24,12 +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
- ./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:
- web - 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:
postgres_data:

20
docs/Makefile Normal file
View File

@@ -0,0 +1,20 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line, and also
# from the environment for the first two.
SPHINXOPTS ?=
SPHINXBUILD ?= sphinx-build
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

35
docs/make.bat Normal file
View File

@@ -0,0 +1,35 @@
@ECHO OFF
pushd %~dp0
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set SOURCEDIR=source
set BUILDDIR=build
%SPHINXBUILD% >NUL 2>NUL
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.https://www.sphinx-doc.org/
exit /b 1
)
if "%1" == "" goto help
%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
goto end
:help
%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O%
:end
popd

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

@@ -0,0 +1,194 @@
# Deployment
The BRECcIA Network Mapper can be deployed in a variety of ways, most of which utilise Docker.
Ansible deployment has been tested on RHEL7 and RHEL8.
## Choosing How to Deploy
If you are an organisation deploying the app on a server, [Ansible](#ansible) is recommended. If Ansible is not used with 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 (`deploy/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 | 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.
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 | 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.
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 | 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.
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 Desktop:
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.
- 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.

View File

@@ -0,0 +1,188 @@
# Configuration
After installing the Network Mapper, some configuration must be done in the web interface. You will need to log in with an admin account.
:::{important}
Admin dashboard: this is the admin section of the site, accessed by clicking `Admin` on the navigation bar at the top.
:::
## 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.
1. `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`.
2. `PERSON_LIST_HELP`: Help text to display at the top of the people list.
3. `ORGANISATION_LIST_HELP`: Help text to display at the top of the organisaton list.
4. `RELATIONSHIP_FORM_HELP`: Help text to display at the top of relationship forms.
5. `HOMEPAGE_HEADER_IMAGE_SHRINK`: Whether the homepage header image should be shrunk to display the whole image at all times.
6. `HOMEPAGE_HEADER_IMAGE`: The header image for the homepage.
7. `HOMEPAGE_CARD_1_TITLE`: The title for the first card displayed on the homepage.
8. `HOMEPAGE_CARD_1_DESCRIPTION`: The description for the first card displayed on the homepage.
9. `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).
10. `HOMEPAGE_CARD_2_TITLE`: The title for the second card displayed on the homepage.
11. `HOMEPAGE_CARD_2_DESCRIPTION`: The description for the second card displayed on the homepage.
12. `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).
13. `HOMEPAGE_CARD_3_TITLE`: The title for the third card displayed on the homepage.
14. `HOMEPAGE_CARD_3_DESCRIPTION`: The description for the third card displayed on the homepage.
15. `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).
16. `HOMEPAGE_ABOUT_TITLE`: The title for the about section on the homepage.
17. `HOMEPAGE_ABOUT_CONTENT`: The content for the about section on the homepage. HTML is accepted.
18. `HOMEPAGE_ABOUT_IMAGE`: The image for the about section on the homepage.
19. `NOTICE_TEXT`: Text to be displayed in a notice banner at the top of every page.
20. `NOTICE_CLASS`: CSS class to use for background of the notice banner.
21. `PARENT_PROJECT_NAME`: The name of the project's parent project, if one exists.
22. `PROJECT_LEAD`: The project's lead person, organisation, or similar.
23. `PROJECT_TAGLINE`: The project's tagline.
24. `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.
25. `ENABLE_GOOGLE_LOGIN`: Whether login through Google should be enabled. Only enable this if Google login has been configured. See [Google login](#google).
25. `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-default questions.
- 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 banner 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
Organisations 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 banner 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.
### 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.
## 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 containing instructions for how to reset their password.
If the user you wish to invite already has a `Person` created for them, they will already have an account with a random username and login disabled. 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 along with 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.
:::{important}
Admin dashboard: this is the admin section of the site, accessed by clicking `Admin` on the navigation bar at the top.
:::
## Inviting Users
Please see [Configuration](2-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
To view an interactive map showing people and organisations that have recorded their locations, click `Map` in the banner at the top of the screen. You can toggle the visibility of people and organisations with the buttons at the top of the page.
Nodes for people on the map have the same colour as the buttons at the top of the page. Nodes can be clicked the name of the person or organisation they represent, and this can be clicked to view their profile.
:::{note}
This page is only available to administrators.
:::
## View a Graph of the Network
The network mapper provides an interface to view the network as a graph. To access it, click `Network` in the banner 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.
:::{note}
This page is only available to administrators.
:::
## Export Data
All data relating to the network can be exported as CSV files. To do this, click `Export` in the banner 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.
:::{note}
This page is only available to administrators.
:::

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