Commit be163c7b authored by David Seaward's avatar David Seaward Committed by Gogs

Merge branch 'whitelabelling' of david.seaward/purist_middleware into master

parents a9c51b91 69e40044
# Copying notice
Purist services middleware <br />
https://code.puri.sm/purist/middleware <br />
Services middleware <br />
<https://plan.puri.st/module/middleware> <br />
Copyright 2017 Purism SPC and contributors <br />
SPDX-License-Identifier: AGPL-3.0+
......
Purist services middleware
==========================
Services middleware
===================
[project] | [code] | [tracker] | *snippets*
A middleware application for managing Purist accounts and services,
including resource management and user-facing registration. Account
registration creates an LDAP user, which is used for authentication by
other services.
A middleware application for managing private, account-based services,
including resource management and user-facing registration. Tailored for
*Purist services* offered by Purism SPC, but deployable anywhere.
Follows an opinionated installation process (specifically expecting
one-instance-per-server), but also includes a number of configuration
......@@ -44,7 +43,7 @@ Prerequisites
* SSH access to an OpenVPN server with `create_new_ovpn_config`
* Typically, the Nginx user (`www-data`) will need SSH access
* Test with `sudo -u www-data ssh -p PORT REMOTE_USER@HOSTNAME`
* The user needing access can be changed in `purist_account_monitor.conf`
* The user needing access can be changed in `purist_middleware_monitor.conf`
Other versions and alternatives may work but are untested.
......@@ -59,7 +58,8 @@ Usage
Sharing
-------
Purist services middleware <br />
Services middleware <br />
<https://plan.puri.st/module/middleware> <br />
Copyright 2017 Purism SPC and contributors <br />
SPDX-License-Identifier: AGPL-3.0+
......
......@@ -3,28 +3,28 @@ Setup
* Install Debian packages (`apt install libsasl2-dev libldap2-dev...`)
* Create installation folders:
* `/opt/purist/account/` (code)
* `/opt/purist/account_virtualenv/` (Python environment)
* `/etc/opt/purist/account/` (configuration)
* `/var/opt/purist/account/static/` (data and static web files)
* `/var/log/purist/account/` (logs)
* `/opt/purist/middleware/` (code)
* `/opt/purist/middleware_virtualenv/` (Python environment)
* `/etc/opt/purist/middleware/` (configuration)
* `/var/opt/purist/middleware/static/` (data and static web files)
* `/var/log/purist/middleware/` (logs)
* Populate brand data (if it doesn't already exist):
* Create `/var/opt/purist/brand/` (shared data and static web files)
* Populate `brand` folder
* `chown --recursive www-data:www-data /var/opt/purist`
* Copy project code:
* Copy code into `/opt/purist/account/`
* Copy code into `/opt/purist/middleware/`
* `chown --recursive www-data:www-data /opt/purist`
* Set up virtualenv:
* Create virtualenv (`virtualenv /opt/purist/account_virtualenv --python=python3`)
* `cd /opt/purist/account`
* Create virtualenv (`virtualenv /opt/purist/middleware_virtualenv --python=python3`)
* `cd /opt/purist/middleware`
* Activate virtualenv (`source ../account_virtualenv/bin/activate`)
* Install Python packages (`pip install --requirement requires/requirements.txt`)
* Confirm packages by comparing `pip freeze` output with `requires/requirements.txt`
* Deactivate virtualenv (`deactivate`)
* Complete Django settings:
* `cp ./conf/etc/config.ini /etc/opt/purist/account/`
* `cp ./conf/etc/secret.ini /etc/opt/purist/account/`
* `cp ./conf/etc/config.ini /etc/opt/purist/middleware/`
* `cp ./conf/etc/secret.ini /etc/opt/purist/middleware/`
* Fill in settings
* Run initial setup:
* Activate virtualenv (`source ../account_virtualenv/bin/activate`)
......@@ -35,16 +35,16 @@ Setup
account manager
* Deactivate virtualenv (`deactivate`)
* Hook up Nginx:
* `cp ./config/nginx/purist_account /etc/nginx/available_sites/`
* `cp ./config/nginx/purist_middleware /etc/nginx/available_sites/`
* Update `server_name` value
* `cd /etc/nginx/sites-enabled`
* `ln --symbolic ../sites-available/purist_account`
* `ln --symbolic ../sites-available/purist_middleware`
* Hook up uWSGI:
* `sudo apt install uwsgi uwsgi-emperor uwsgi-plugin-python3`
* `cp ./conf/uwsgi_emperor_vassals/purist_account.ini /etc/uwsgi-emperor/vassals/`
* `cp ./conf/uwsgi_emperor_vassals/purist_middleware.ini /etc/uwsgi-emperor/vassals/`
* Hook up Supervisor (supervisord):
* `sudo apt install supervisor`
* `cp ./conf/supervisord/purist_account_monitor.conf /etc/supervisor/conf.d/`
* `cp ./conf/supervisord/purist_middleware_monitor.conf /etc/supervisor/conf.d/`
* Restart services:
* `sudo service rabbitmq-server restart`
* `sudo service uwsgi-emperor restart`
......@@ -52,11 +52,11 @@ Setup
* `sudo service supervisor restart`
* Check logs:
* `/var/log/uwsgi/emperor.log`
* `/var/log/uwsgi/app/purist_account.log`
* `/var/log/uwsgi/app/purist_middleware.log`
* `/var/log/nginx/error.log`
* `/var/log/nginx/access.log`
* `/var/log/supervisor/supervisord.log`
* `/var/log/purist/account/beat.log`
* `/var/log/purist/middleware/beat.log`
For more options and details see
<https://docs.djangoproject.com/en/1.11/#the-development-process>
......@@ -66,8 +66,8 @@ Update
* Stop site
* Update packages with `apt update && apt upgrade`
* Update code in `/opt/purist/account/`
* Update settings in `/etc/opt/purist/account/`
* Update code in `/opt/purist/middleware/`
* Update settings in `/etc/opt/purist/middleware/`
* Update virtualenv:
* Activate virtualenv (`./bin/activate.py`)
* Update Python packages (`pip install --requirement requires/requirements.txt`)
......
# stored as /etc/opt/purist/account/config.ini
# stored as /etc/opt/purist/middleware/config.ini
# note that % must be escaped as %%
[settings]
SITE_TITLE = Title
SITE_BYLINE = Example byline
SITE_DOMAIN = example.com
DEBUG = True
DEBUG_ALL_ACCESS = True
DEBUG_CHANGE_PASSWORD = False
DEBUG_SKIP_ACTIVATION_COMMAND = True
ALLOWED_HOSTS = localhost
STATIC_ROOT = /var/opt/purist/account/static
REGISTRATION_OPEN = True
REG_PERSON_BASE_DN = ou=people,dc=example,dc=com
REG_PERSON_OBJECT_CLASSES = inetOrgPerson,organizationalPerson,person
REG_GROUP_BASE_DN = dc=comms,dc=example,dc=com
REG_GROUP_OBJECT_CLASSES = groupOfNames
AUTH_LDAP_SERVER_URI = ldap://ldap.example.com
AUTH_LDAP_START_TLS = True
AUTH_LDAP_BIND_DN = cn=admin,dc=example,dc=com
AUTH_LDAP_USER_SEARCH_BASE_DN = ou=people,dc=example,dc=com
SQLITE_DB_PATH = /var/opt/purist/account/db.sqlite3
STATICFILES_DIRS = /var/opt/purist/brand,/var/opt/purist/downloads
WOO_URL = https://example.com
WOO_WP_API = True
WOO_VERSION = wc/v1
WOO_PRODUCT_LIST = 123,124
WOO1_FIELD_LIST = Existing username,Username
OVPN_HOSTNAME = ssh.example.com
OVPN_PORT = 22
OVPN_USERNAME = username
OVPN_FILEPATH = "/path/to/{IDENTITY}/{IDENTITY}.ovpn"
SUBSCRIPTION_LINK = https://www.example.com
SITE_TITLE=Title
SITE_BYLINE=Example byline
SITE_DOMAIN=example.com
SITE_PROVIDER=Provider
SITE_PROVIDER_LINK=https://example.com
DEBUG=True
DEBUG_ALL_ACCESS=True
DEBUG_CHANGE_PASSWORD=False
DEBUG_SKIP_ACTIVATION_COMMAND=True
# change to false after initial setup
ALLOWED_HOSTS=localhost
STATIC_ROOT=/var/opt/purist/middleware/static
REGISTRATION_OPEN=True
REG_PERSON_BASE_DN=ou=people,dc=example,dc=com
REG_PERSON_OBJECT_CLASSES=inetOrgPerson,organizationalPerson,person
REG_GROUP_BASE_DN=ou=groups,dc=example,dc=com
REG_GROUP_OBJECT_CLASSES=groupOfNames
AUTH_LDAP_SERVER_URI=ldap://ldap.example.com
AUTH_LDAP_START_TLS=True
AUTH_LDAP_BIND_DN=cn=admin,dc=example,dc=com
AUTH_LDAP_USER_SEARCH_BASE_DN=ou=people,dc=example,dc=com
SQLITE_DB_PATH=/var/opt/purist/middleware/db.sqlite3
STATICFILES_DIRS=/var/opt/purist/brand,/var/opt/purist/downloads
WOO_URL=https://example.com
WOO_WP_API=True
WOO_VERSION=wc/v1
WOO_PRODUCT_LIST=123,124
WOO1_FIELD_LIST=Existing username,Username
OVPN_HOSTNAME=ssh.example.com
OVPN_PORT=22
OVPN_USERNAME=username
OVPN_FILEPATH="/path/to/{IDENTITY}/{IDENTITY}.ovpn"
SUBSCRIPTION_LINK=https://www.example.com
# stored as /etc/opt/purist/account/secret.ini
# stored as /etc/opt/purist/middleware/secret.ini
# note that % must be escaped as %%
[settings]
DJANGO_SECRET_KEY=random_key
AUTH_LDAP_BIND_PASSWORD=ldap_password
DJANGO_SECRET_KEY = random_key
AUTH_LDAP_BIND_PASSWORD = ldap_password
WOO_CONSUMER_KEY = woo_key
WOO_CONSUMER_SECRET = woo_secret
# stored as /etc/nginx/sites-available/purist_account
# and symlink /etc/nginx/sites-enabled/purist_account
# stored as /etc/nginx/sites-available/purist_middleware
# and symlink /etc/nginx/sites-enabled/purist_middleware
# naive redirect of HTTP to HTTPS
# deep links are ignored
......@@ -14,7 +14,7 @@ server {
# the upstream component nginx needs to connect to
upstream django {
server unix:/var/opt/purist/account/uwsgi.sock; # for a file socket
server unix:/var/opt/purist/middleware/uwsgi.sock; # for a file socket
}
# the main server block
......@@ -24,14 +24,14 @@ server {
# SSL configuration
listen 443 ssl;
listen [::]:443 ssl;
ssl_certificate /path/to/fullchain.pem; # TODO: update path
ssl_certificate_key /path/to/privkey.pem; # TODO: update path
ssl_certificate /path/to/fullchain.pem; # TODO: update path
ssl_certificate_key /path/to/privkey.pem; # TODO: update path
charset utf-8;
# error_log /var/log/nginx/error.log debug; # optional
location /static/ {
alias /var/opt/purist/account/static/;
alias /var/opt/purist/middleware/static/;
}
location /favicon.ico {
......@@ -43,3 +43,4 @@ server {
include /etc/nginx/uwsgi_params;
}
}
; stored as /etc/supervisor/conf.d/purist_account_monitor.conf
; stored as /etc/supervisor/conf.d/purist_middleware_monitor.conf
; Copyright 2017 Purism SPC and contributors
; SPDX-License-Identifier: AGPL-3.0+
......@@ -8,13 +8,13 @@
; https://github.com/celery/celery/blob/master/extra/supervisord/celerybeat.conf
; SPDX-License-Identifier: BSD-3-Clause
[program:purist_account_monitor]
command=/opt/purist/account_virtualenv/bin/celery worker -B --app purist_account --scheduler django_celery_beat.schedulers:DatabaseScheduler --loglevel=INFO
directory=/opt/purist/account
[program:purist_middleware_monitor]
command=/opt/purist/middleware_virtualenv/bin/celery worker -B --app middleware --scheduler django_celery_beat.schedulers:DatabaseScheduler --loglevel=INFO
directory=/opt/purist/middleware
user=www-data
numprocs=1
stdout_logfile=/var/log/purist/account/beat.log
stderr_logfile=/var/log/purist/account/beat.log
stdout_logfile=/var/log/purist/middleware/beat.log
stderr_logfile=/var/log/purist/middleware/beat.log
autostart=true
autorestart=true
startsecs=10
......
# stored as /etc/uwsgi-emperor/vassals/purist_account.ini
[uwsgi]
socket = /var/opt/purist/account/uwsgi.sock
chmod-socket = 775
chdir = /opt/purist/account
master = true
virtualenv = /opt/purist/account_virtualenv
env = DJANGO_SETTINGS_MODULE=purist_account.settings
module = purist_account.wsgi:application
uid = www-data
gid = www-data
processes = 1
threads = 1
plugins = python3,logfile
logger = file:/var/log/uwsgi/app/purist_account.log
vacuum = true
# stored as /etc/uwsgi-emperor/vassals/purist_middleware.ini
[uwsgi]
socket = /var/opt/purist/middleware/uwsgi.sock
chmod-socket = 775
chdir = /opt/purist/middleware
master = true
virtualenv = /opt/purist/middleware_virtualenv
env = DJANGO_SETTINGS_MODULE=middleware.settings
module = middleware.wsgi:application
uid = www-data
gid = www-data
processes = 1
threads = 1
plugins = python3,logfile
logger = file:/var/log/uwsgi/app/purist_middleware.log
vacuum = true
......@@ -4,7 +4,7 @@
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Puristaccountsite
SPHINXPROJ = Servicemiddlewaresite
SOURCEDIR = .
BUILDDIR = _build
......@@ -17,4 +17,4 @@ help:
# 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)
\ No newline at end of file
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Purist account site documentation build configuration file, created by
# Services middleware site documentation build configuration file, created by
# sphinx-quickstart on Tue Mar 7 16:17:28 2017.
#
# This file is execfile()d with the current directory set to its
......@@ -56,7 +56,7 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = 'Purist account manager'
project = 'Services middleware'
copyright = '2017 Purism SPC and contributors'
author = 'Purism SPC and contributors'
......@@ -110,7 +110,7 @@ html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Puristaccountsitedoc'
htmlhelp_basename = 'Servicesmiddlewaresitedoc'
# -- Options for LaTeX output ---------------------------------------------
......@@ -137,7 +137,7 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Puristaccountsite.tex', 'Purist account manager documentation',
(master_doc, 'Servicesmiddlewaresite.tex', 'Services middleware documentation',
'Purism SPC and contributors', 'manual'),
]
......@@ -147,7 +147,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'puristaccountsite', 'Purist account manager documentation',
(master_doc, 'servicesmiddlewaresite', 'Services middleware documentation',
[author], 1)
]
......@@ -158,13 +158,11 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Puristaccountsite', 'Purist account manager documentation',
author, 'Puristaccountsite', 'Account registration and management for '
'Purist services.', 'Miscellaneous'),
(master_doc, 'Servicesmiddlewaresite', 'Services middleware documentation',
author, 'Servicesmiddlewaresite', 'Middleware for account registration and '
'services management.', 'Miscellaneous'),
]
# -- Options for Epub output ----------------------------------------------
# Bibliographic Dublin Core info.
......
......@@ -13,7 +13,7 @@ Welcome to Puri.st account site documentation!
.. autosummary::
:toctree: api/
purist_account
middleware
ldapregister
ldapregister.admin
ldapregister.apps
......
......@@ -60,7 +60,9 @@ SPDX-License-Identifier: AGPL-3.0
<p>
<em>Purist services</em> provided by <a href="https://puri.sm">Purism SPC</a><br/>
<em>Services middleware</em> copyright 2017 Purism SPC and contributors; shared under AGPL-3.0+
(<a href="https://plan.puri.st">project</a>, <a href="{% url 'download-zip' %}">source</a>, <a href="">javascript</a>)
(<a href="https://plan.puri.st/module/middleware">project</a>,
<a href="{% url 'download-zip' %}">source</a>,
<a href="{% url 'jslicense' %}" rel="jslicense">javascript</a>)
</p>
</div>
......
from django.conf import settings
from django.shortcuts import render
# from django.contrib.auth import views as auth_views
def home(request):
render_data = {
"username": request.user.get_username(),
"site_title": settings.SITE_TITLE,
"site_byline": settings.SITE_BYLINE,
}
return render(request, 'ldapregister/home.html', render_data)
......@@ -109,9 +109,11 @@ SPDX-License-Identifier: AGPL-3.0
<div id="footer_block">
<p>
<em>Purist services</em> provided by <a href="https://puri.sm">Purism SPC</a><br/>
<em>{{ site_title }}</em> provided by <a href="{{ site_provider_link }}">{{ site_provider }}</a><br/>
<em>Services middleware</em> copyright 2017 Purism SPC and contributors; shared under AGPL-3.0+
(<a href="https://plan.puri.st">project</a>, <a href="{% url 'download-zip' %}">source</a>, <a href="">javascript</a>)
(<a href="https://plan.puri.st/module/middleware">project</a>,
<a href="{% url 'download-zip' %}">source</a>,
<a href="{% url 'jslicense' %}" rel="jslicense">javascript</a>)
</p>
</div>
......
......@@ -27,6 +27,8 @@ def userlimit(request):
"username": username,
"site_title": settings.SITE_TITLE,
"site_byline": settings.SITE_BYLINE,
"site_provider": settings.SITE_PROVIDER,
"site_provider_link": settings.SITE_PROVIDER_LINK,
"limits": limits,
"has_limit": has_limit,
"link_subscription": settings.LINK_SUBSCRIPTION,
......
......@@ -3,7 +3,7 @@ import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "purist_account.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "middleware.settings")
try:
from django.core.management import execute_from_command_line
except ImportError:
......
......@@ -11,9 +11,9 @@ import os
from celery import Celery
# set the default Django settings module for the 'celery' program.
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'purist_account.settings')
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'middleware.settings')
app = Celery('purist_account_monitor')
app = Celery('purist_middleware_monitor')
# Using a string here means the worker don't have to serialize
# the configuration object to child processes.
......
......@@ -48,7 +48,7 @@ AGPL_EXCLUDE_DIRS = [
r'\.idea$',
]
AGPL_FILENAME_PREFIX = 'purist_middleware'
AGPL_FILENAME_PREFIX = 'middleware'
#
# REGISTRATION APPLICATION
......@@ -124,6 +124,8 @@ STATICFILES_DIRS = config("STATICFILES_DIRS", cast=Csv())
SITE_TITLE = config("SITE_TITLE")
SITE_BYLINE = config("SITE_BYLINE")
SITE_DOMAIN = config("SITE_DOMAIN")
SITE_PROVIDER = config("SITE_PROVIDER")
SITE_PROVIDER_LINK = config("SITE_PROVIDER_LINK")
LINK_SUBSCRIPTION = config("LINK_SUBSCRIPTION")
......
"""
Django settings for purist_account project.
Django settings for middleware project.
Generated by 'django-admin startproject' using Django 1.11.2.
......@@ -49,7 +49,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'purist_account.urls'
ROOT_URLCONF = 'middleware.urls'
TEMPLATES = [
{
......@@ -77,7 +77,7 @@ TEMPLATES = [
},
]
WSGI_APPLICATION = 'purist_account.wsgi.application'
WSGI_APPLICATION = 'middleware.wsgi.application'
# Database
......
"""purist_account URL Configuration
"""middleware URL Configuration
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/1.11/topics/http/urls/
......@@ -18,8 +18,8 @@ from django.contrib import admin
from django.views.generic import RedirectView
from registration.backends.simple.views import RegistrationView
import ldapregister.views
import limitmonitor.views
import purist.views
from ldapregister.forms import RegistrationForm
#
......@@ -34,7 +34,7 @@ admin.site.site_header = "Site administration"
#
urlpatterns = [
url(r'^$', ldapregister.views.home, name='home'),
url(r'^$', purist.views.home, name='home'),
url(r'^admin/', admin.site.urls),
url(r'^accounts/$', RedirectView.as_view(url='/')),
url(r'^accounts/profile/$', limitmonitor.views.userlimit, name='profile'),
......@@ -42,4 +42,5 @@ urlpatterns = [
url(r'^accounts/register/$', RegistrationView.as_view(form_class=RegistrationForm), name='registration_register'),
url(r'^accounts/', include('registration.backends.simple.urls')),
url(r'^download/', include('django_agpl.urls')),
url(r'^jslicense/$', purist.views.jslicense, name='jslicense'),
]
"""
WSGI config for purist_account project.
WSGI config for middleware project.
It exposes the WSGI callable as a module-level variable named ``application``.
......@@ -11,6 +11,6 @@ import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "purist_account.settings")
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "middleware.settings")
application = get_wsgi_application()
......@@ -60,9 +60,11 @@ SPDX-License-Identifier: AGPL-3.0
<div id="footer_block">
<p>
<em>Purist services</em> provided by <a href="https://puri.sm">Purism SPC</a><br/>
<em>{{ site_title }}</em> provided by <a href="{{ site_provider_link }}">{{ site_provider }}</a><br/>
<em>Services middleware</em> copyright 2017 Purism SPC and contributors; shared under AGPL-3.0+
(<a href="https://plan.puri.st">project</a>, <a href="{% url 'download-zip' %}">source</a>, <a href="">javascript</a>)
(<a href="https://plan.puri.st/module/middleware">project</a>,
<a href="{% url 'download-zip' %}">source</a>,
<a href="{% url 'jslicense' %}" rel="jslicense">javascript</a>)
</p>
</div>
......@@ -70,4 +72,3 @@ SPDX-License-Identifier: AGPL-3.0
</body>
</html>
<!DOCTYPE html>
<!--
Copyright 2017 Purism SPC and contributors
https://plan.puri.st/module/middleware
SPDX-License-Identifier: AGPL-3.0
-->
<html lang="en">
<head>
<title>JavaScript license information</title>
<link rel="icon" sizes="960x960" href="{% static 'favicon.png' %}"/>
<meta name="application-name" content="{{ site_title }}"/>
<meta charset="UTF-8"/>
</head>
<body>
<h1>
JavaScript license information
</h1>
<p>
No licenses. <em>Services middleware</em> does not require JavaScript, minified or otherwise, at this time.
</p>
<table id="jslicense-labels1">
</table>
<p>
Return to <a href="{% url 'home' %}">{{ site_title }}</a>
</p>
</body>
</html>
from django.conf import settings
from django.shortcuts import render
# Create your views here.
def home(request):
render_data = {
"username": request.user.get_username(),
"site_title": settings.SITE_TITLE,
"site_byline": settings.SITE_BYLINE,
"site_provider": settings.SITE_PROVIDER,
"site_provider_link": settings.SITE_PROVIDER_LINK,
}
return render(request, 'purist/home.html', render_data)
def jslicense(request):
render_data = {
"site_title": settings.SITE_TITLE,
}
return render(request, 'purist/jslicense.html', render_data)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment