Initial commit
9
.gitignore
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
venv/*
|
||||||
|
.vscode/*
|
||||||
|
static/*
|
||||||
|
__pycache__
|
||||||
|
db.sqlite3
|
||||||
|
doc/_build/
|
||||||
|
doc/output/
|
||||||
|
api_test.py
|
||||||
|
log/*
|
9
.gitmodules
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[submodule "synthea"]
|
||||||
|
path = synthea
|
||||||
|
url = https://github.com/dHealthNL/synthea.git
|
||||||
|
[submodule "synthea-international"]
|
||||||
|
path = synthea-international
|
||||||
|
url = https://github.com/dHealthNL/synthea-international.git
|
||||||
|
[submodule "synthea-modules"]
|
||||||
|
path = synthea-modules
|
||||||
|
url = https://github.com/dHealthNL/synthea-modules.git
|
7
requirements.txt
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
Django
|
||||||
|
dj-database-url
|
||||||
|
django_js_reverse
|
||||||
|
python-decouple
|
||||||
|
django-cryptography
|
||||||
|
djangorestframework
|
||||||
|
pandas
|
1
synthea
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 16129ad4fbef18e5c89b941e1b14a8fcf04abf07
|
1
synthea-international
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit da67258b54ee83e3d4ad11b5f0dfcbc5bb053e34
|
1
synthea-modules
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 824b4d3182434bd20ef644a21a8bc4a828f399dc
|
1
webservice/apps/RUG_template/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'apps.RUG_template.apps.RugTemplateConfig'
|
3
webservice/apps/RUG_template/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
8
webservice/apps/RUG_template/apps.py
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
class RugTemplateConfig(AppConfig):
|
||||||
|
name = 'apps.RUG_template'
|
||||||
|
label = 'RUG_template'
|
||||||
|
verbose_name = _('RUG Template')
|
||||||
|
verbose_name_plural = _('RUG Template')
|
BIN
webservice/apps/RUG_template/locale/en/LC_MESSAGES/django.mo
Normal file
213
webservice/apps/RUG_template/locale/en/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,213 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-07-30 15:42+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: apps/RUG_template/apps.py:7 apps/RUG_template/apps.py:8
|
||||||
|
msgid "RUG Template"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/400.html:4
|
||||||
|
#: apps/RUG_template/templates/400.html:5
|
||||||
|
msgid "Oops, sorry, bad request (400)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/400.html:7
|
||||||
|
#: apps/RUG_template/templates/404.html:7
|
||||||
|
msgid ""
|
||||||
|
"Unfortunately, the link to this page does not work, the page (temporarily) "
|
||||||
|
"does not exist or it has been moved."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/403.html:4
|
||||||
|
#: apps/RUG_template/templates/403.html:5
|
||||||
|
msgid "Oops, sorry, forbidden access (403)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/403.html:7
|
||||||
|
msgid "Unfortunately, you do not have rights to access this url."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/404.html:4
|
||||||
|
#: apps/RUG_template/templates/404.html:5
|
||||||
|
msgid "Oops, sorry, page not found (404)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/500.html:4
|
||||||
|
#: apps/RUG_template/templates/500.html:5
|
||||||
|
msgid "Oops, sorry, server error (500)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/500.html:7
|
||||||
|
msgid "Unfortunately, something went wrong on the server."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/admin/base_site.html:24
|
||||||
|
msgid "Language"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/admin/base_site.html:32
|
||||||
|
msgid "Documentation"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/admin/base_site.html:36
|
||||||
|
msgid "Change password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/admin/base_site.html:38
|
||||||
|
msgid "Log out"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/base.html:7
|
||||||
|
msgid "Welcome at RUG"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/base.html:113
|
||||||
|
msgid "Language selection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/index.html:4
|
||||||
|
#: apps/RUG_template/templates/index.html:5
|
||||||
|
msgid "Welcome to the RUG Template page"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/index.html:7
|
||||||
|
msgid "Simple RUG Template"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/index.html:8
|
||||||
|
msgid "Some more text"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/menu.html:4
|
||||||
|
msgid "Section"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/menu.html:7
|
||||||
|
msgid "Menu item"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/pager.html:13
|
||||||
|
msgid "previous"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/pager.html:21
|
||||||
|
msgid "next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:8
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:18
|
||||||
|
msgid "Login"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:10
|
||||||
|
msgid ""
|
||||||
|
"You can login here to create your schedules. If you do not have a login, "
|
||||||
|
"please contact: some_one@rug.nl"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:24
|
||||||
|
msgid "Lost password?"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_complete.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_complete.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_complete.html:7
|
||||||
|
msgid "Password reset complete"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_complete.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Your new password has been set. You can log in now on the <a href="
|
||||||
|
"\"%(login_url)s\">log in page</a>."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:7
|
||||||
|
msgid "Set a new password!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:9
|
||||||
|
msgid "Here you can set a new password."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:17
|
||||||
|
msgid "Change my password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:23
|
||||||
|
msgid ""
|
||||||
|
"The password reset link was invalid, possibly because it has already been "
|
||||||
|
"used. Please request a new password reset."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_done.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_done.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_done.html:8
|
||||||
|
msgid "Reset password, email sent"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_done.html:11
|
||||||
|
msgid ""
|
||||||
|
"We've emailed you instructions for setting your password. You should receive "
|
||||||
|
"the email shortly!"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_email.html:2
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"You're receiving this email because you requested a password reset for your "
|
||||||
|
"user account at %(site_name)s.\n"
|
||||||
|
"\n"
|
||||||
|
"Please go to the following page and choose a new password:\n"
|
||||||
|
"\n"
|
||||||
|
"%(protocol)s://%(domain)s%(reset_url)s\n"
|
||||||
|
"\n"
|
||||||
|
"Your username, in case you've forgotten: %(user)s\n"
|
||||||
|
"\n"
|
||||||
|
"Thanks for using our site!\n"
|
||||||
|
"\n"
|
||||||
|
"The %(site_name)s team"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:8
|
||||||
|
msgid "Reset password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:10
|
||||||
|
msgid ""
|
||||||
|
"Here you can request a password reset. Please enter your email address that "
|
||||||
|
"is used for registration."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:17
|
||||||
|
msgid "Reset my password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/singup.html:4
|
||||||
|
#: apps/RUG_template/templates/singup.html:5
|
||||||
|
#: apps/RUG_template/templates/singup.html:7
|
||||||
|
#: apps/RUG_template/templates/singup.html:14
|
||||||
|
msgid "Singup"
|
||||||
|
msgstr ""
|
BIN
webservice/apps/RUG_template/locale/nl/LC_MESSAGES/django.mo
Normal file
240
webservice/apps/RUG_template/locale/nl/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,240 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-07-30 15:42+0200\n"
|
||||||
|
"PO-Revision-Date: 2020-05-27 15:59+0200\n"
|
||||||
|
"Last-Translator: Joshua Rubingh <j.g.rubingh@rug.nl>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: nl\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 2.0.6\n"
|
||||||
|
|
||||||
|
#: apps/RUG_template/apps.py:7 apps/RUG_template/apps.py:8
|
||||||
|
msgid "RUG Template"
|
||||||
|
msgstr "RUG Template"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/400.html:4
|
||||||
|
#: apps/RUG_template/templates/400.html:5
|
||||||
|
msgid "Oops, sorry, bad request (400)"
|
||||||
|
msgstr "Oeps, sorry, pagina niet gevonden (404)"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/400.html:7
|
||||||
|
#: apps/RUG_template/templates/404.html:7
|
||||||
|
msgid ""
|
||||||
|
"Unfortunately, the link to this page does not work, the page (temporarily) "
|
||||||
|
"does not exist or it has been moved."
|
||||||
|
msgstr ""
|
||||||
|
"Helaas werkt de link naar deze pagina niet, de pagina bestaat (tijdelijk) "
|
||||||
|
"niet of is verplaatst."
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/403.html:4
|
||||||
|
#: apps/RUG_template/templates/403.html:5
|
||||||
|
msgid "Oops, sorry, forbidden access (403)"
|
||||||
|
msgstr "Oeps, sorry, geen toegang (403)"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/403.html:7
|
||||||
|
msgid "Unfortunately, you do not have rights to access this url."
|
||||||
|
msgstr "Helaas heb je geen rechten om toegang te krijgen tot deze URL."
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/404.html:4
|
||||||
|
#: apps/RUG_template/templates/404.html:5
|
||||||
|
msgid "Oops, sorry, page not found (404)"
|
||||||
|
msgstr "Oeps, sorry, pagina niet gevonden (404)"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/500.html:4
|
||||||
|
#: apps/RUG_template/templates/500.html:5
|
||||||
|
msgid "Oops, sorry, server error (500)"
|
||||||
|
msgstr "Oeps, sorry, server error (500)"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/500.html:7
|
||||||
|
msgid "Unfortunately, something went wrong on the server."
|
||||||
|
msgstr "Helaas is er iets misgegaan op de server."
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/admin/base_site.html:24
|
||||||
|
msgid "Language"
|
||||||
|
msgstr "Taal"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/admin/base_site.html:32
|
||||||
|
msgid "Documentation"
|
||||||
|
msgstr "Documentatie"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/admin/base_site.html:36
|
||||||
|
msgid "Change password"
|
||||||
|
msgstr "Verander wachtwoord"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/admin/base_site.html:38
|
||||||
|
msgid "Log out"
|
||||||
|
msgstr "Logouit"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/base.html:7
|
||||||
|
msgid "Welcome at RUG"
|
||||||
|
msgstr "Welkom bij de RUG"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/base.html:113
|
||||||
|
msgid "Language selection"
|
||||||
|
msgstr "Taal keuze"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/index.html:4
|
||||||
|
#: apps/RUG_template/templates/index.html:5
|
||||||
|
msgid "Welcome to the RUG Template page"
|
||||||
|
msgstr "Welkom bij de RUG Template pagina"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/index.html:7
|
||||||
|
msgid "Simple RUG Template"
|
||||||
|
msgstr "Simpel RUG Template"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/index.html:8
|
||||||
|
msgid "Some more text"
|
||||||
|
msgstr "Nog wat tekst"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/menu.html:4
|
||||||
|
msgid "Section"
|
||||||
|
msgstr "Sectie"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/menu.html:7
|
||||||
|
msgid "Menu item"
|
||||||
|
msgstr "Menu item"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/pager.html:13
|
||||||
|
msgid "previous"
|
||||||
|
msgstr "vorige"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/pager.html:21
|
||||||
|
msgid "next"
|
||||||
|
msgstr "volgende"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:8
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:18
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Login"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:10
|
||||||
|
msgid ""
|
||||||
|
"You can login here to create your schedules. If you do not have a login, "
|
||||||
|
"please contact: some_one@rug.nl"
|
||||||
|
msgstr ""
|
||||||
|
"Hier kunt u inloggen om nieuwe roosters te maken. Als je geen login hebt "
|
||||||
|
"neem dan contact op met iemand@rug.nl"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/login.html:24
|
||||||
|
msgid "Lost password?"
|
||||||
|
msgstr "Wachtwoord kwijt?"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_complete.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_complete.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_complete.html:7
|
||||||
|
msgid "Password reset complete"
|
||||||
|
msgstr "Wachtwoord reset is kompleet"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_complete.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Your new password has been set. You can log in now on the <a href="
|
||||||
|
"\"%(login_url)s\">log in page</a>."
|
||||||
|
msgstr ""
|
||||||
|
"Je nieuwe wachtwoord is ingesteld. Je kunt nu inloggen via de <a href="
|
||||||
|
"\"%(login_url)s\">inlog pagina</a>."
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:7
|
||||||
|
msgid "Set a new password!"
|
||||||
|
msgstr "Stel een nieuw wachtwoord in!"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:9
|
||||||
|
msgid "Here you can set a new password."
|
||||||
|
msgstr "Hier kun je een nieuw wachtwoord instellen."
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:17
|
||||||
|
msgid "Change my password"
|
||||||
|
msgstr "Verander mijn wachtwoord"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_confirm.html:23
|
||||||
|
msgid ""
|
||||||
|
"The password reset link was invalid, possibly because it has already been "
|
||||||
|
"used. Please request a new password reset."
|
||||||
|
msgstr ""
|
||||||
|
"De link voor het opnieuw instellen van het wachtwoord was ongeldig, mogelijk "
|
||||||
|
"omdat deze al is gebruikt. Vraag een nieuwe reset van het wachtwoord aan."
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_done.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_done.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_done.html:8
|
||||||
|
msgid "Reset password, email sent"
|
||||||
|
msgstr "Wachtwoord is gereset, email is verstuurd"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_done.html:11
|
||||||
|
msgid ""
|
||||||
|
"We've emailed you instructions for setting your password. You should receive "
|
||||||
|
"the email shortly!"
|
||||||
|
msgstr ""
|
||||||
|
"We hebben u een e-mail gestuurd met instructies voor het instellen van uw "
|
||||||
|
"wachtwoord. U ontvangt de e-mail binnenkort!"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_email.html:2
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"You're receiving this email because you requested a password reset for your "
|
||||||
|
"user account at %(site_name)s.\n"
|
||||||
|
"\n"
|
||||||
|
"Please go to the following page and choose a new password:\n"
|
||||||
|
"\n"
|
||||||
|
"%(protocol)s://%(domain)s%(reset_url)s\n"
|
||||||
|
"\n"
|
||||||
|
"Your username, in case you've forgotten: %(user)s\n"
|
||||||
|
"\n"
|
||||||
|
"Thanks for using our site!\n"
|
||||||
|
"\n"
|
||||||
|
"The %(site_name)s team"
|
||||||
|
msgstr ""
|
||||||
|
"Je ontvangt deze e-mail omdat je een wachtwoord reset hebt aangevraagd voor "
|
||||||
|
"je gebruikersaccount op%(site_name)s.\n"
|
||||||
|
"\n"
|
||||||
|
"Ga naar de volgende pagina en kies een nieuw wachtwoord:\n"
|
||||||
|
"\n"
|
||||||
|
"%(protocol)s://%(domain)s%(reset_url)s\n"
|
||||||
|
"\n"
|
||||||
|
"Uw gebruikersnaam, voor het geval u het bent vergeten: %(user)s\n"
|
||||||
|
"\n"
|
||||||
|
"Bedankt voor het gebruiken van onze site!\n"
|
||||||
|
"\n"
|
||||||
|
"Het team van %(site_name)s"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:4
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:5
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:8
|
||||||
|
msgid "Reset password"
|
||||||
|
msgstr "Reset wachtwoord"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:10
|
||||||
|
msgid ""
|
||||||
|
"Here you can request a password reset. Please enter your email address that "
|
||||||
|
"is used for registration."
|
||||||
|
msgstr ""
|
||||||
|
"Hier kunt u een wachtwoord reset aanvragen. Voer uw e-mailadres in dat wordt "
|
||||||
|
"gebruikt voor registratie."
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/registration/password_reset_form.html:17
|
||||||
|
msgid "Reset my password"
|
||||||
|
msgstr "Reset mijn wachtwoord"
|
||||||
|
|
||||||
|
#: apps/RUG_template/templates/singup.html:4
|
||||||
|
#: apps/RUG_template/templates/singup.html:5
|
||||||
|
#: apps/RUG_template/templates/singup.html:7
|
||||||
|
#: apps/RUG_template/templates/singup.html:14
|
||||||
|
msgid "Singup"
|
||||||
|
msgstr "Opgeven"
|
||||||
|
|
||||||
|
#~ msgid "Password reset"
|
||||||
|
#~ msgstr "Wachtwoord reset"
|
38
webservice/apps/RUG_template/middleware.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import pytz
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from ipware import get_client_ip
|
||||||
|
from django.utils import timezone
|
||||||
|
|
||||||
|
# make sure you add `TimezoneMiddleware` appropriately in settings.py: 'apps.RUG_template.middleware.TimezoneMiddleware'
|
||||||
|
class TimezoneMiddleware:
|
||||||
|
""" Middleware to check user timezone. """
|
||||||
|
def __init__(self, get_response):
|
||||||
|
self.get_response = get_response
|
||||||
|
# One-time configuration and initialization.
|
||||||
|
|
||||||
|
def __call__(self, request):
|
||||||
|
# Code to be executed for each request before
|
||||||
|
# the view (and later middleware) are called.
|
||||||
|
client_ip, is_routable = get_client_ip(request)
|
||||||
|
user_time_zone = request.session.get('user_time_zone', None)
|
||||||
|
try:
|
||||||
|
if user_time_zone is None and is_routable and client_ip is not None:
|
||||||
|
# Here we use an online service to get visitor info. Maybe not the nicest way to do it, but it is a way
|
||||||
|
# Also we only check when we get a public IP address. Local networks will not be checked online
|
||||||
|
# https://freegeoip.app
|
||||||
|
freegeoip_response = requests.get('https://freegeoip.app/json/{0}'.format(client_ip))
|
||||||
|
freegeoip_response_json = freegeoip_response.json()
|
||||||
|
user_time_zone = freegeoip_response_json['time_zone']
|
||||||
|
if user_time_zone:
|
||||||
|
request.session['user_time_zone'] = user_time_zone
|
||||||
|
timezone.activate(pytz.timezone(user_time_zone))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
response = self.get_response(request)
|
||||||
|
|
||||||
|
# Code to be executed for each request/response after
|
||||||
|
# the view is called.
|
||||||
|
|
||||||
|
return response
|
0
webservice/apps/RUG_template/migrations/__init__.py
Normal file
3
webservice/apps/RUG_template/models.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
After Width: | Height: | Size: 53 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 967 B |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 124 B |
After Width: | Height: | Size: 47 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 12 KiB |
1
webservice/apps/RUG_template/static/RUG_template/javascript/humanize-duration.min.js
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
function csrfSafeMethod(method) {
|
||||||
|
// these HTTP methods do not require CSRF protection
|
||||||
|
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
|
||||||
|
}
|
||||||
|
|
||||||
|
function init_password_toggles() {
|
||||||
|
jQuery('input[type="password"]').each(function(counter,value){
|
||||||
|
jQuery('<button type="button" class="password_toggle password_hidden icon" title="Toggle password" />').text('Toggle password').insertAfter(value);
|
||||||
|
});
|
||||||
|
|
||||||
|
jQuery('button.password_toggle').on('click',function(event){
|
||||||
|
event.preventDefault();
|
||||||
|
toggle_password(this);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggle_password(button) {
|
||||||
|
button = jQuery(button);
|
||||||
|
let password_field = button.prev('input');
|
||||||
|
let show = password_field.attr('type') == 'password';
|
||||||
|
|
||||||
|
password_field.attr('type',( show ? 'text' : 'password' ));
|
||||||
|
button.removeClass('password_hidden password_shown').addClass(( show ? 'password_shown' : 'password_hidden' ))
|
||||||
|
}
|
||||||
|
|
||||||
|
function human_sizes(value) {
|
||||||
|
const units = ['B','KB','MB','GB','TB','HB'];
|
||||||
|
const unit_value = 1000;
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
while (value / unit_value > 1) {
|
||||||
|
value /= unit_value;
|
||||||
|
counter++;
|
||||||
|
}
|
||||||
|
return value + '' + units[counter];
|
||||||
|
}
|
||||||
|
|
||||||
|
function label_required_fields() {
|
||||||
|
jQuery('input,textarea,select').filter('[required]:visible').each(function(counter,value){
|
||||||
|
let field = jQuery(value);
|
||||||
|
jQuery('label[for="' + field.attr('id') + '"]').append('<span class="required">*</span>');
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
jQuery('select').each(function(counter,value){
|
||||||
|
let field = jQuery(value);
|
||||||
|
console.log(jQuery('label[for="' + field.attr('id') + '"]'));
|
||||||
|
jQuery('label[for="' + field.attr('id') + '"]').append('<span class="required">*</span>');
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
jQuery(function(){
|
||||||
|
jQuery.ajaxSetup({
|
||||||
|
beforeSend: function(xhr, settings) {
|
||||||
|
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
|
||||||
|
xhr.setRequestHeader("X-CSRFToken", Cookies.get('csrftoken'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
init_password_toggles();
|
||||||
|
label_required_fields();
|
||||||
|
});
|
@ -0,0 +1,252 @@
|
|||||||
|
/**
|
||||||
|
* https://raw.githubusercontent.com/elo80ka/django-dynamic-formset/master/src/jquery.formset.js
|
||||||
|
* jQuery Formset 1.5-pre
|
||||||
|
* @author Stanislaus Madueke (stan DOT madueke AT gmail DOT com)
|
||||||
|
* @requires jQuery 1.2.6 or later
|
||||||
|
*
|
||||||
|
* Copyright (c) 2009, Stanislaus Madueke
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the New BSD License
|
||||||
|
* See: http://www.opensource.org/licenses/bsd-license.php
|
||||||
|
*/
|
||||||
|
;(function($) {
|
||||||
|
$.fn.formset = function(opts)
|
||||||
|
{
|
||||||
|
var options = $.extend({}, $.fn.formset.defaults, opts),
|
||||||
|
flatExtraClasses = options.extraClasses.join(' '),
|
||||||
|
totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'),
|
||||||
|
maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'),
|
||||||
|
minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'),
|
||||||
|
childElementSelector = 'input,select,textarea,label,div',
|
||||||
|
$$ = $(this),
|
||||||
|
|
||||||
|
applyExtraClasses = function(row, ndx) {
|
||||||
|
if (options.extraClasses) {
|
||||||
|
row.removeClass(flatExtraClasses);
|
||||||
|
row.addClass(options.extraClasses[ndx % options.extraClasses.length]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateElementIndex = function(elem, prefix, ndx) {
|
||||||
|
var idRegex = new RegExp(prefix + '-(\\d+|__prefix__)-'),
|
||||||
|
replacement = prefix + '-' + ndx + '-';
|
||||||
|
if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement));
|
||||||
|
if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement));
|
||||||
|
if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));
|
||||||
|
},
|
||||||
|
|
||||||
|
hasChildElements = function(row) {
|
||||||
|
return row.find(childElementSelector).length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
showAddButton = function() {
|
||||||
|
return maxForms.length == 0 || // For Django versions pre 1.2
|
||||||
|
(maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether delete link(s) can be displayed - when total forms > min forms
|
||||||
|
*/
|
||||||
|
showDeleteLinks = function() {
|
||||||
|
return minForms.length == 0 || // For Django versions pre 1.7
|
||||||
|
(minForms.val() == '' || (totalForms.val() - minForms.val() > 0));
|
||||||
|
},
|
||||||
|
|
||||||
|
insertDeleteLink = function(row) {
|
||||||
|
var delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.'),
|
||||||
|
addCssSelector = $.trim(options.addCssClass).replace(/\s+/g, '.');
|
||||||
|
|
||||||
|
var delButtonHTML = '<a class="' + options.deleteCssClass + '" href="javascript:void(0)">' + options.deleteText +'</a>';
|
||||||
|
if (options.deleteContainerClass) {
|
||||||
|
// If we have a specific container for the remove button,
|
||||||
|
// place it as the last child of that container:
|
||||||
|
row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML);
|
||||||
|
} else if (row.is('TR')) {
|
||||||
|
// If the forms are laid out in table rows, insert
|
||||||
|
// the remove button into the last table cell:
|
||||||
|
row.children(':last').append(delButtonHTML);
|
||||||
|
} else if (row.is('UL') || row.is('OL')) {
|
||||||
|
// If they're laid out as an ordered/unordered list,
|
||||||
|
// insert an <li> after the last list item:
|
||||||
|
row.append('<li>' + delButtonHTML + '</li>');
|
||||||
|
} else {
|
||||||
|
// Otherwise, just insert the remove button as the
|
||||||
|
// last child element of the form's container:
|
||||||
|
row.append(delButtonHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're under the minimum number of forms - not to display delete link at rendering
|
||||||
|
if (!showDeleteLinks()){
|
||||||
|
row.find('a.' + delCssSelector).hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
row.find('a.' + delCssSelector).click(function() {
|
||||||
|
var row = $(this).parents('.' + options.formCssClass),
|
||||||
|
del = row.find('input:hidden[id $= "-DELETE"]'),
|
||||||
|
buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'),
|
||||||
|
forms;
|
||||||
|
if (del.length) {
|
||||||
|
// We're dealing with an inline formset.
|
||||||
|
// Rather than remove this form from the DOM, we'll mark it as deleted
|
||||||
|
// and hide it, then let Django handle the deleting:
|
||||||
|
del.val('on');
|
||||||
|
row.hide();
|
||||||
|
forms = $('.' + options.formCssClass).not(':hidden');
|
||||||
|
totalForms.val(forms.length);
|
||||||
|
} else {
|
||||||
|
row.remove();
|
||||||
|
// Update the TOTAL_FORMS count:
|
||||||
|
forms = $('.' + options.formCssClass).not('.formset-custom-template');
|
||||||
|
totalForms.val(forms.length);
|
||||||
|
}
|
||||||
|
for (var i=0, formCount=forms.length; i<formCount; i++) {
|
||||||
|
// Apply `extraClasses` to form rows so they're nicely alternating:
|
||||||
|
applyExtraClasses(forms.eq(i), i);
|
||||||
|
if (!del.length) {
|
||||||
|
// Also update names and IDs for all child controls (if this isn't
|
||||||
|
// a delete-able inline formset) so they remain in sequence:
|
||||||
|
forms.eq(i).find(childElementSelector).each(function() {
|
||||||
|
updateElementIndex($(this), options.prefix, i);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if we've reached the minimum number of forms - hide all delete link(s)
|
||||||
|
if (!showDeleteLinks()){
|
||||||
|
$('a.' + delCssSelector).each(function(){$(this).hide();});
|
||||||
|
}
|
||||||
|
// Check if we need to show the add button:
|
||||||
|
if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show();
|
||||||
|
// If a post-delete callback was provided, call it with the deleted form:
|
||||||
|
if (options.removed) options.removed(row);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
$$.each(function(i) {
|
||||||
|
var row = $(this),
|
||||||
|
del = row.find('input:checkbox[id $= "-DELETE"]');
|
||||||
|
if (del.length) {
|
||||||
|
// If you specify "can_delete = True" when creating an inline formset,
|
||||||
|
// Django adds a checkbox to each form in the formset.
|
||||||
|
// Replace the default checkbox with a hidden field:
|
||||||
|
if (del.is(':checked')) {
|
||||||
|
// If an inline formset containing deleted forms fails validation, make sure
|
||||||
|
// we keep the forms hidden (thanks for the bug report and suggested fix Mike)
|
||||||
|
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" value="on" />');
|
||||||
|
row.hide();
|
||||||
|
} else {
|
||||||
|
del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />');
|
||||||
|
}
|
||||||
|
// Hide any labels associated with the DELETE checkbox:
|
||||||
|
$('label[for="' + del.attr('id') + '"]').hide();
|
||||||
|
del.remove();
|
||||||
|
}
|
||||||
|
if (hasChildElements(row)) {
|
||||||
|
row.addClass(options.formCssClass);
|
||||||
|
if (row.is(':visible')) {
|
||||||
|
insertDeleteLink(row);
|
||||||
|
applyExtraClasses(row, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if ($$.length) {
|
||||||
|
var hideAddButton = !showAddButton(),
|
||||||
|
addButton, template;
|
||||||
|
if (options.formTemplate) {
|
||||||
|
// If a form template was specified, we'll clone it to generate new form instances:
|
||||||
|
template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);
|
||||||
|
template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template');
|
||||||
|
template.find(childElementSelector).each(function() {
|
||||||
|
updateElementIndex($(this), options.prefix, '__prefix__');
|
||||||
|
});
|
||||||
|
insertDeleteLink(template);
|
||||||
|
} else {
|
||||||
|
// Otherwise, use the last form in the formset; this works much better if you've got
|
||||||
|
// extra (>= 1) forms (thnaks to justhamade for pointing this out):
|
||||||
|
if (options.hideLastAddForm) $('.' + options.formCssClass + ':last').hide();
|
||||||
|
template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
|
||||||
|
template.find('input:hidden[id $= "-DELETE"]').remove();
|
||||||
|
// Clear all cloned fields, except those the user wants to keep (thanks to brunogola for the suggestion):
|
||||||
|
template.find(childElementSelector).not(options.keepFieldValues).each(function() {
|
||||||
|
var elem = $(this);
|
||||||
|
// If this is a checkbox or radiobutton, uncheck it.
|
||||||
|
// This fixes Issue 1, reported by Wilson.Andrew.J:
|
||||||
|
if (elem.is('input:checkbox') || elem.is('input:radio')) {
|
||||||
|
elem.attr('checked', false);
|
||||||
|
} else {
|
||||||
|
elem.val('');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// FIXME: Perhaps using $.data would be a better idea?
|
||||||
|
options.formTemplate = template;
|
||||||
|
|
||||||
|
var addButtonHTML = '<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>';
|
||||||
|
if (options.addContainerClass) {
|
||||||
|
// If we have a specific container for the "add" button,
|
||||||
|
// place it as the last child of that container:
|
||||||
|
var addContainer = $('[class*="' + options.addContainerClass + '"');
|
||||||
|
addContainer.append(addButtonHTML);
|
||||||
|
addButton = addContainer.find('[class="' + options.addCssClass + '"]');
|
||||||
|
} else if ($$.is('TR')) {
|
||||||
|
// If forms are laid out as table rows, insert the
|
||||||
|
// "add" button in a new table row:
|
||||||
|
var numCols = $$.eq(0).children().length, // This is a bit of an assumption :|
|
||||||
|
buttonRow = $('<tr><td colspan="' + numCols + '">' + addButtonHTML + '</tr>').addClass(options.formCssClass + '-add');
|
||||||
|
$$.parent().append(buttonRow);
|
||||||
|
addButton = buttonRow.find('a');
|
||||||
|
} else {
|
||||||
|
// Otherwise, insert it immediately after the last form:
|
||||||
|
$$.filter(':last').after(addButtonHTML);
|
||||||
|
addButton = $$.filter(':last').next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hideAddButton) addButton.hide();
|
||||||
|
|
||||||
|
addButton.click(function() {
|
||||||
|
var formCount = parseInt(totalForms.val()),
|
||||||
|
row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
|
||||||
|
buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this),
|
||||||
|
delCssSelector = $.trim(options.deleteCssClass).replace(/\s+/g, '.');
|
||||||
|
applyExtraClasses(row, formCount);
|
||||||
|
row.insertBefore(buttonRow).show();
|
||||||
|
row.find(childElementSelector).each(function() {
|
||||||
|
updateElementIndex($(this), options.prefix, formCount);
|
||||||
|
});
|
||||||
|
totalForms.val(formCount + 1);
|
||||||
|
// Check if we're above the minimum allowed number of forms -> show all delete link(s)
|
||||||
|
if (showDeleteLinks()){
|
||||||
|
$('a.' + delCssSelector).each(function(){$(this).show();});
|
||||||
|
}
|
||||||
|
// Check if we've exceeded the maximum allowed number of forms:
|
||||||
|
if (!showAddButton()) buttonRow.hide();
|
||||||
|
// If a post-add callback was supplied, call it with the added form:
|
||||||
|
if (options.added) options.added(row);
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $$;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Setup plugin defaults */
|
||||||
|
$.fn.formset.defaults = {
|
||||||
|
prefix: 'form', // The form prefix for your django formset
|
||||||
|
formTemplate: null, // The jQuery selection cloned to generate new form instances
|
||||||
|
addText: 'add another', // Text for the add link
|
||||||
|
deleteText: 'remove', // Text for the delete link
|
||||||
|
addContainerClass: null, // Container CSS class for the add link
|
||||||
|
deleteContainerClass: null, // Container CSS class for the delete link
|
||||||
|
addCssClass: 'add-row', // CSS class applied to the add link
|
||||||
|
deleteCssClass: 'delete-row', // CSS class applied to the delete link
|
||||||
|
formCssClass: 'dynamic-form', // CSS class applied to each form in a formset
|
||||||
|
extraClasses: [], // Additional CSS classes, which will be applied to each form in turn
|
||||||
|
keepFieldValues: '', // jQuery selector for fields whose values should be kept when the form is cloned
|
||||||
|
added: null, // Function called each time a new form is added
|
||||||
|
removed: null, // Function called each time a form is deleted
|
||||||
|
hideLastAddForm: false // When set to true, hide last empty add form (becomes visible when clicking on add button)
|
||||||
|
};
|
||||||
|
})(jQuery);
|
||||||
|
|
1
webservice/apps/RUG_template/static/RUG_template/javascript/jquery.tablesorter.min.js
vendored
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
/*
|
||||||
|
* Konami-JS ~
|
||||||
|
* :: Now with support for touch events and multiple instances for
|
||||||
|
* :: those situations that call for multiple easter eggs!
|
||||||
|
* Code: https://github.com/snaptortoise/konami-js
|
||||||
|
* Copyright (c) 2009 George Mandis (georgemandis.com, snaptortoise.com)
|
||||||
|
* Version: 1.6.2 (7/17/2018)
|
||||||
|
* Licensed under the MIT License (http://opensource.org/licenses/MIT)
|
||||||
|
* Tested in: Safari 4+, Google Chrome 4+, Firefox 3+, IE7+, Mobile Safari 2.2.1+ and Android
|
||||||
|
*/
|
||||||
|
|
||||||
|
var Konami = function (callback) {
|
||||||
|
var konami = {
|
||||||
|
addEvent: function (obj, type, fn, ref_obj) {
|
||||||
|
if (obj.addEventListener)
|
||||||
|
obj.addEventListener(type, fn, false);
|
||||||
|
else if (obj.attachEvent) {
|
||||||
|
// IE
|
||||||
|
obj["e" + type + fn] = fn;
|
||||||
|
obj[type + fn] = function () {
|
||||||
|
obj["e" + type + fn](window.event, ref_obj);
|
||||||
|
}
|
||||||
|
obj.attachEvent("on" + type, obj[type + fn]);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
removeEvent: function (obj, eventName, eventCallback) {
|
||||||
|
if (obj.removeEventListener) {
|
||||||
|
obj.removeEventListener(eventName, eventCallback);
|
||||||
|
} else if (obj.attachEvent) {
|
||||||
|
obj.detachEvent(eventName);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
input: "",
|
||||||
|
pattern: "38384040373937396665",
|
||||||
|
keydownHandler: function (e, ref_obj) {
|
||||||
|
if (ref_obj) {
|
||||||
|
konami = ref_obj;
|
||||||
|
} // IE
|
||||||
|
konami.input += e ? e.keyCode : event.keyCode;
|
||||||
|
if (konami.input.length > konami.pattern.length) {
|
||||||
|
konami.input = konami.input.substr((konami.input.length - konami.pattern.length));
|
||||||
|
}
|
||||||
|
if (konami.input === konami.pattern) {
|
||||||
|
konami.code(konami._currentLink);
|
||||||
|
konami.input = '';
|
||||||
|
e.preventDefault();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
load: function (link) {
|
||||||
|
this._currentLink = link;
|
||||||
|
this.addEvent(document, "keydown", this.keydownHandler, this);
|
||||||
|
this.iphone.load(link);
|
||||||
|
},
|
||||||
|
unload: function () {
|
||||||
|
this.removeEvent(document, 'keydown', this.keydownHandler);
|
||||||
|
this.iphone.unload();
|
||||||
|
},
|
||||||
|
code: function (link) {
|
||||||
|
window.location = link
|
||||||
|
},
|
||||||
|
iphone: {
|
||||||
|
start_x: 0,
|
||||||
|
start_y: 0,
|
||||||
|
stop_x: 0,
|
||||||
|
stop_y: 0,
|
||||||
|
tap: false,
|
||||||
|
capture: false,
|
||||||
|
orig_keys: "",
|
||||||
|
keys: ["UP", "UP", "DOWN", "DOWN", "LEFT", "RIGHT", "LEFT", "RIGHT", "TAP", "TAP"],
|
||||||
|
input: [],
|
||||||
|
code: function (link) {
|
||||||
|
konami.code(link);
|
||||||
|
},
|
||||||
|
touchmoveHandler: function (e) {
|
||||||
|
if (e.touches.length === 1 && konami.iphone.capture === true) {
|
||||||
|
var touch = e.touches[0];
|
||||||
|
konami.iphone.stop_x = touch.pageX;
|
||||||
|
konami.iphone.stop_y = touch.pageY;
|
||||||
|
konami.iphone.tap = false;
|
||||||
|
konami.iphone.capture = false;
|
||||||
|
konami.iphone.check_direction();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchendHandler: function () {
|
||||||
|
konami.iphone.input.push(konami.iphone.check_direction());
|
||||||
|
|
||||||
|
if (konami.iphone.input.length > konami.iphone.keys.length) konami.iphone.input.shift();
|
||||||
|
|
||||||
|
if (konami.iphone.input.length === konami.iphone.keys.length) {
|
||||||
|
var match = true;
|
||||||
|
for (var i = 0; i < konami.iphone.keys.length; i++) {
|
||||||
|
if (konami.iphone.input[i] !== konami.iphone.keys[i]) {
|
||||||
|
match = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (match) {
|
||||||
|
konami.iphone.code(konami._currentLink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
touchstartHandler: function (e) {
|
||||||
|
konami.iphone.start_x = e.changedTouches[0].pageX;
|
||||||
|
konami.iphone.start_y = e.changedTouches[0].pageY;
|
||||||
|
konami.iphone.tap = true;
|
||||||
|
konami.iphone.capture = true;
|
||||||
|
},
|
||||||
|
load: function (link) {
|
||||||
|
this.orig_keys = this.keys;
|
||||||
|
konami.addEvent(document, "touchmove", this.touchmoveHandler);
|
||||||
|
konami.addEvent(document, "touchend", this.touchendHandler, false);
|
||||||
|
konami.addEvent(document, "touchstart", this.touchstartHandler);
|
||||||
|
},
|
||||||
|
unload: function () {
|
||||||
|
konami.removeEvent(document, 'touchmove', this.touchmoveHandler);
|
||||||
|
konami.removeEvent(document, 'touchend', this.touchendHandler);
|
||||||
|
konami.removeEvent(document, 'touchstart', this.touchstartHandler);
|
||||||
|
},
|
||||||
|
check_direction: function () {
|
||||||
|
x_magnitude = Math.abs(this.start_x - this.stop_x);
|
||||||
|
y_magnitude = Math.abs(this.start_y - this.stop_y);
|
||||||
|
x = ((this.start_x - this.stop_x) < 0) ? "RIGHT" : "LEFT";
|
||||||
|
y = ((this.start_y - this.stop_y) < 0) ? "DOWN" : "UP";
|
||||||
|
result = (x_magnitude > y_magnitude) ? x : y;
|
||||||
|
result = (this.tap === true) ? "TAP" : result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typeof callback === "string" && konami.load(callback);
|
||||||
|
if (typeof callback === "function") {
|
||||||
|
konami.code = callback;
|
||||||
|
konami.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
return konami;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') {
|
||||||
|
module.exports = Konami;
|
||||||
|
} else {
|
||||||
|
if (typeof define === 'function' && define.amd) {
|
||||||
|
define([], function() {
|
||||||
|
return Konami;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.Konami = Konami;
|
||||||
|
}
|
||||||
|
}
|
2
webservice/apps/RUG_template/static/RUG_template/javascript/moment-with-locales.min.js
vendored
Normal file
7
webservice/apps/RUG_template/static/RUG_template/javascript/toastr.min.js
vendored
Normal file
155
webservice/apps/RUG_template/static/RUG_template/style/base.css
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
.rug-wrapper {
|
||||||
|
max-width: 80% !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-width-m-8-24 {
|
||||||
|
width: 25%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-panel--content {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-nav--main__item {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-nav--main__item:first-of-type,
|
||||||
|
.rug-nav--main__item:last-of-type {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-breadcrumbs {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.stripe tr:nth-child(even) {
|
||||||
|
background-color: #f2f2f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.stripe td {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.stripe td.empty {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pointer {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
form#new_schedule_form input:not([type=checkbox]) {
|
||||||
|
width: 100%
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-danger {
|
||||||
|
color: #721c24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.required {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.required {
|
||||||
|
margin-left: 0.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-warning {
|
||||||
|
color: #856404;
|
||||||
|
background-color: #fff3cd;
|
||||||
|
border-color: #ffeeba;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-success {
|
||||||
|
color: #155724;
|
||||||
|
background-color: #d4edda;
|
||||||
|
border-color: #c3e6cb;
|
||||||
|
}
|
||||||
|
.alert-danger {
|
||||||
|
color: #721c24;
|
||||||
|
background-color: #f8d7da;
|
||||||
|
border-color: #f5c6cb;
|
||||||
|
}
|
||||||
|
.form-control {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: calc(1.5em + .75rem + 2px);
|
||||||
|
padding: .375rem .75rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #495057;
|
||||||
|
background-color: #fff;
|
||||||
|
background-clip: padding-box;
|
||||||
|
border: 1px solid #ced4da;
|
||||||
|
border-radius: .25rem;
|
||||||
|
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 1rem;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 .icon {
|
||||||
|
height: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Password toggles */
|
||||||
|
input[readonly] {
|
||||||
|
border: none;
|
||||||
|
background-color: transparent;
|
||||||
|
/* width: 75px;*/
|
||||||
|
}
|
||||||
|
|
||||||
|
button.password_toggle {
|
||||||
|
width: 1.8rem;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
text-indent: -999%;
|
||||||
|
white-space: nowrap;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: auto 100%;
|
||||||
|
background-position: center;
|
||||||
|
|
||||||
|
margin-left: 0.2rem;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.password_toggle.password_hidden {
|
||||||
|
background-image: url('../images/eye-open.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
button.password_toggle.password_shown {
|
||||||
|
background-image: url('../images/eye-closed.png');
|
||||||
|
}
|
||||||
|
/* End Password toggles */
|
||||||
|
|
||||||
|
|
||||||
|
.add-row {
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
padding-left: 24px;
|
||||||
|
background: url('../images/plus-icon.png') no-repeat left center;
|
||||||
|
background-size: auto 80%;
|
||||||
|
}
|
||||||
|
.delete-row {
|
||||||
|
display:block;
|
||||||
|
margin: 6px 0 0 0;
|
||||||
|
padding-left: 20px;
|
||||||
|
background: url('../images/minus-icon.png') no-repeat left center;
|
||||||
|
background-size: auto 80%;
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
img.i18n_flag {
|
||||||
|
width: 16px;
|
||||||
|
vertical-align: text-top;
|
||||||
|
}
|
||||||
|
|
||||||
|
form#language_form {
|
||||||
|
display: inline;
|
||||||
|
}
|
1
webservice/apps/RUG_template/static/RUG_template/style/toastr.min.css
vendored
Normal file
17
webservice/apps/RUG_template/templates/400.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Oops, sorry, bad request (400)" %}{% endblock %}
|
||||||
|
{% block pagetitle %}<span style="color:red">{% trans "Oops, sorry, bad request (400)" %}</span>{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p>{% trans "Unfortunately, the link to this page does not work, the page (temporarily) does not exist or it has been moved." %}</p>
|
||||||
|
<style>
|
||||||
|
.rug-breadcrumbs {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-layout__item.rug-width-m-8-24 {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
17
webservice/apps/RUG_template/templates/403.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Oops, sorry, forbidden access (403)" %}{% endblock %}
|
||||||
|
{% block pagetitle %}<span style="color:red">{% trans "Oops, sorry, forbidden access (403)" %}</span>{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p>{% trans "Unfortunately, you do not have rights to access this url." %}</p>
|
||||||
|
<style>
|
||||||
|
.rug-breadcrumbs {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-layout__item.rug-width-m-8-24 {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
17
webservice/apps/RUG_template/templates/404.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Oops, sorry, page not found (404)" %}{% endblock %}
|
||||||
|
{% block pagetitle %}<span style="color:red">{% trans "Oops, sorry, page not found (404)" %}</span>{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p>{% trans "Unfortunately, the link to this page does not work, the page (temporarily) does not exist or it has been moved." %}</p>
|
||||||
|
<style>
|
||||||
|
.rug-breadcrumbs {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-layout__item.rug-width-m-8-24 {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
17
webservice/apps/RUG_template/templates/500.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Oops, sorry, server error (500)" %}{% endblock %}
|
||||||
|
{% block pagetitle %}<span style="color:red">{% trans "Oops, sorry, server error (500)" %}</span>{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p>{% trans "Unfortunately, something went wrong on the server." %}</p>
|
||||||
|
<style>
|
||||||
|
.rug-breadcrumbs {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
|
||||||
|
.rug-layout__item.rug-width-m-8-24 {
|
||||||
|
display:none
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
38
webservice/apps/RUG_template/templates/admin/base_site.html
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{% extends "admin/base_site.html" %}
|
||||||
|
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block extrahead %}
|
||||||
|
<link rel="shortcut icon" href="{% static 'RUG_template/images/favicon.ico' %}" />
|
||||||
|
<link rel="stylesheet" type="text/css" href="{% static 'RUG_template/style/custom_admin.css' %}"/>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block userlinks %}
|
||||||
|
{% comment %} Language choice. Should be put somewhere else when finale designs are done. {% endcomment %}
|
||||||
|
<form action="{% url 'set_language' %}" method="post" id="language_form" name="language_form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
|
{% get_available_languages as LANGUAGES %}
|
||||||
|
{% get_language_info_list for LANGUAGES as languages %}
|
||||||
|
<input name="next" type="hidden" value="{% if redirect_to %}{{ redirect_to }}{% endif %}">
|
||||||
|
<input type="hidden" name="language" id="language" value="{{LANGUAGE_CODE}}">
|
||||||
|
{% for language in languages %}
|
||||||
|
<a onclick="document.getElementById('language').value='{{ language.code }}'; document.forms['language_form'].submit(); return false;" href="#">
|
||||||
|
{% with 'RUG_template/images/flag-'|add:language.code|add:'.png' as image_static %}
|
||||||
|
<img class="i18n_flag" src="{% static image_static %}" title="{% trans 'Language' %} {{ language.name_translated }}"/>
|
||||||
|
{% endwith %}
|
||||||
|
</a> /
|
||||||
|
{% endfor %}
|
||||||
|
</form>
|
||||||
|
{% if user.is_active and user.is_staff %}
|
||||||
|
{% url 'django-admindocs-docroot' as docsroot %}
|
||||||
|
{% if docsroot %}
|
||||||
|
<a href="{{ docsroot }}">{% trans 'Documentation' %}</a> /
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
{% if user.has_usable_password %}
|
||||||
|
<a href="{% url 'admin:password_change' %}">{% trans 'Change password' %}</a> /
|
||||||
|
{% endif %}
|
||||||
|
<a href="{% url 'admin:logout' %}">{% trans 'Log out' %}</a>
|
||||||
|
{% endblock %}
|
634
webservice/apps/RUG_template/templates/base.html
Normal file
@ -0,0 +1,634 @@
|
|||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html prefix="og: http://ogp.me/ns# article: http://ogp.me/ns/article#" lang="nl">
|
||||||
|
<head>
|
||||||
|
<META http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||||
|
<title>{% block title %}{% trans "Welcome at RUG" %}{% endblock %}</title>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<link href="https://www.rug.nl/_definition/shared/css/jquery-smoothness/jquery-ui.min.css?version=2019-12-12" rel="stylesheet">
|
||||||
|
<link href="https://www.rug.nl/_definition/shared/css/jquery-ui-timepicker-addon.css?version=2019-12-12" rel="stylesheet">
|
||||||
|
<link href="https://www.rug.nl/_definition/shared/css/styles_v2.css?version=2019-12-12" rel="stylesheet" type="text/css">
|
||||||
|
<link href="https://www.rug.nl/_definition/shared/css/fotorama.css?version=2019-12-12" rel="stylesheet" type="text/css">
|
||||||
|
|
||||||
|
<link href="https://www.rug.nl/apple-touch-icon-57x57.png" sizes="57x57" rel="apple-touch-icon">
|
||||||
|
<link href="https://www.rug.nl/apple-touch-icon-114x114.png" sizes="114x114" rel="apple-touch-icon">
|
||||||
|
<link href="https://www.rug.nl/apple-touch-icon-72x72.png" sizes="72x72" rel="apple-touch-icon">
|
||||||
|
<link href="https://www.rug.nl/apple-touch-icon-144x144.png" sizes="144x144" rel="apple-touch-icon">
|
||||||
|
<link href="https://www.rug.nl/apple-touch-icon-60x60.png" sizes="60x60" rel="apple-touch-icon">
|
||||||
|
<link href="https://www.rug.nl/apple-touch-icon-120x120.png" sizes="120x120" rel="apple-touch-icon">
|
||||||
|
<link href="https://www.rug.nl/apple-touch-icon-76x76.png" sizes="76x76" rel="apple-touch-icon">
|
||||||
|
<link href="https://www.rug.nl/apple-touch-icon-152x152.png" sizes="152x152" rel="apple-touch-icon">
|
||||||
|
<link href="https://www.rug.nl/icon.ico" rel="shortcut icon">
|
||||||
|
<meta content="#cc0000" name="msapplication-TileColor">
|
||||||
|
<meta content="https://www.rug.nl/mstile-144x144.png" name="msapplication-TileImage">
|
||||||
|
<meta content="user-scalable=1, initial-scale=1.0" name="viewport">
|
||||||
|
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/jquery.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript">jQuery.noConflict();</script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/jquery-ui.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/jquery-ui-timepicker-addon.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/js-cookie.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/md5.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/rug-shared.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/jquery.dialogOptions.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/jquery.dialogOptions-rug.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/messages!js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/formfunctions.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/imagesloaded.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/jquery.mobile.custom.min.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/fotorama.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/interface.bundle.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/content.bundle.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script src="https://www.rug.nl/_definition/shared/js/filemanager.bundle.js?version=2019-12-12" type="text/javascript"></script>
|
||||||
|
<script type="text/javascript" src="https://www.rug.nl/_definition/shared/js/cross-frame.js?version=2019-12-12"></script>
|
||||||
|
|
||||||
|
{% comment %} Requires the Django application django_js_reverse {% endcomment %}
|
||||||
|
<script src="{% url 'js_reverse' %}" type="text/javascript"></script>
|
||||||
|
|
||||||
|
{% comment %} Easter egg ;) {% endcomment %}
|
||||||
|
<script type="text/javascript" src="{% static 'RUG_template/javascript/konami.js' %}"></script>
|
||||||
|
|
||||||
|
{% comment %} Use for general Django inline formsets {% endcomment %}
|
||||||
|
<script type="text/javascript" src="{% static 'RUG_template/javascript/jquery.formset.js' %}"></script>
|
||||||
|
|
||||||
|
{% comment %} Toastr {% endcomment %}
|
||||||
|
<link href="{% static 'RUG_template/style/toastr.min.css' %}" rel="stylesheet"/>
|
||||||
|
<script type="text/javascript" src="{% static 'RUG_template/javascript/toastr.min.js' %}"></script>
|
||||||
|
|
||||||
|
{% comment %} MomentJS with locales {% endcomment %}
|
||||||
|
<script type="text/javascript" src="{% static 'RUG_template/javascript/moment-with-locales.min.js' %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static 'RUG_template/javascript/humanize-duration.min.js' %}"></script>
|
||||||
|
|
||||||
|
{% comment %} Add Tablesorter.js {% endcomment %}
|
||||||
|
<script type="text/javascript" src="{% static 'RUG_template/javascript/jquery.tablesorter.min.js' %}"></script>
|
||||||
|
|
||||||
|
{% comment %} Basic RUG Template CSS and Javascript {% endcomment %}
|
||||||
|
<link href="{% static 'RUG_template/style/base.css' %}" rel="stylesheet"/>
|
||||||
|
<script type="text/javascript" src="{% static 'RUG_template/javascript/javascript.js' %}"></script>
|
||||||
|
|
||||||
|
{% comment %} Custom CSS and Javascript from the Django Project {% endcomment %}
|
||||||
|
<link rel="stylesheet" href="{% static 'style/style.css' %} ">
|
||||||
|
<script type="text/javascript" src="{% static 'javascript/javascript.js' %}"></script>
|
||||||
|
</head>
|
||||||
|
<body itemtype="http://schema.org/WebPage" itemscope="itemscope" id="top" class="page--topicpage"><!--googleoff: all-->
|
||||||
|
<noscript>
|
||||||
|
<strong>Javascript must be enabled for the correct page display</strong>
|
||||||
|
</noscript>
|
||||||
|
|
||||||
|
<a href="#main" class="rug-hidden-visually rug-hidden-visually--focusable">Skip to Content</a><a href="#nav" class="rug-hidden-visually rug-hidden-visually--focusable">Skip to Navigation</a>
|
||||||
|
|
||||||
|
<header data-toggle-group="mainmenu" data-toggle-class="rug-site-header--open" data-toggle-id="menu-show" class="rug-site-header js--togglable-item js--sticky-header">
|
||||||
|
<div class="rug-wrapper rug-wrapper--big rug-wrapper--flush">
|
||||||
|
<div class="rug-site-header__bar">
|
||||||
|
<a class="rug-site-header__item" accesskey="1" href="https://www.rug.nl/"><img class="rug-site-logo" src="https://www.rug.nl/_definition/shared/images/logo--nl.svg" alt="Rijksuniversiteit Groningen"><span class="rug-hidden-visually">Rijksuniversiteit Groningen</span></a><span class="rug-site-header__item"><span class="rug-site-header__text rug-site-header__slogan rug-hidden-l">founded in 1614 - top 100 university</span></span>
|
||||||
|
<div class="rug-site-header__item rug-site-header__item--close">
|
||||||
|
<a href="#" data-toggle-group="mainmenu" data-toggle-class="rug-toggleable" data-toggle-id="menu-hide" class="rug-button rug-font-white rug-button--nav js--togglable-switch">Sluiten<span class="rug-icon rug-icon--l rug-icon--close rug-ml-xxs" aria-hidden="true"></span></a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav class="rug-site-header__item">
|
||||||
|
<ul class="rug-nav--meta">
|
||||||
|
<li class="rug-nav--meta__item rug-nav--meta__item--mobile">
|
||||||
|
<a href="#" data-toggle-group="mainmenu" data-toggle-class="rug-toggleable" data-toggle-id="menu-show" class="js-id--main_menu_toggle js--togglable-switch rug-font-black"><span class="rug-icon rug-icon--menu rug-mr-s" aria-hidden="true"></span><span class="rug-icon rug-icon--search" aria-hidden="true"></span><span class="rug-hidden-visually">Menu en zoeken</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--meta__item">
|
||||||
|
<a href="https://www.rug.nl/info/contact" accesskey="5" class="rug-nav--meta__link js--main-menu-clone">Contact</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--meta__item">
|
||||||
|
<a href="https://myuniversity.rug.nl/infonet/medewerkers/dashboard/" accesskey="5" class="rug-nav--meta__link js--main-menu-clone">My University</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--meta__item">
|
||||||
|
<a href="https://studentportal.rug.nl/" accesskey="5" class="rug-nav--meta__link js--main-menu-clone">Student Portal</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--meta__item" title="{% trans "Language selection" %}">
|
||||||
|
|
||||||
|
{% comment %} Language choice. Should be put somewhere else when finale designs are done. {% endcomment %}
|
||||||
|
<form action="{% url 'set_language' %}" method="post" id="language_form">
|
||||||
|
{% csrf_token %}
|
||||||
|
{% get_current_language as LANGUAGE_CODE %}
|
||||||
|
{% get_available_languages as LANGUAGES %}
|
||||||
|
{% get_language_info_list for LANGUAGES as languages %}
|
||||||
|
{% comment %} Here we set the MomentJS locale for all date and time actions {% endcomment %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
moment.locale('{{LANGUAGE_CODE}}');
|
||||||
|
</script>
|
||||||
|
{% comment %} <input name="next" type="hidden" value="{{ redirect_to|default:"" }}"> {% endcomment %}
|
||||||
|
<input type="hidden" name="language" id="language" value="{{LANGUAGE_CODE}}">
|
||||||
|
{% for language in languages %}
|
||||||
|
<a onclick="jQuery('form#language_form input#language').val('{{ language.code }}'); jQuery('form#language_form').submit(); return false; " class="rug-mr-xs" href="#">
|
||||||
|
<span class="rug-sprite--flag--{{ language.code }}"></span><span class="rug-hidden-visually">{{ language.name_local }}</span></a>
|
||||||
|
{% endfor %}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="rug-background-menu rug-shadow-box-inset">
|
||||||
|
<div class="rug-wrapper rug-wrapper--big rug-wrapper--flush">
|
||||||
|
<form name="gs" id="search-form" action="https://www.rug.nl/search/" class="rug-site-search rug-mb-0">
|
||||||
|
<fieldset class="rug-mb-0 rug-shadow-box">
|
||||||
|
<legend class="rug-hidden-visually">Zoeken</legend><label for="searchtext" class="rug-hidden-visually">Zoeken</label><input placeholder="Zoeken..." id="searchtext" type="text" accesskey="4" autocomplete="off" role="combobox" aria-haspopup="false" name="q" class="rug-site-search__input q text"><input value="true" name="isNewSearch" type="hidden"><button type="submit" class="rug-site-search__button"><span class="rug-icon rug-icon--l rug-icon--search" aria-hidden="true"></span><span class="rug-hidden-visually">Zoeken</span></button>
|
||||||
|
</fieldset>
|
||||||
|
</form>
|
||||||
|
<nav class="rug-nav--main__container">
|
||||||
|
<ul class="rug-nav--main" id="nav">
|
||||||
|
<li class="rug-nav--main__item">
|
||||||
|
<a href="/" data-path="/" class="rug-nav--main__button"><span class="rug-icon rug-icon--home" aria-hidden="true"></span><span class="rug-hidden-visually">Home</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
|
||||||
|
<li data-path="/education/index.xml" class="rug-nav--main__item">
|
||||||
|
<a href="https://www.rug.nl/education/" class="rug-nav--main__link js--noclick-m"><span>Onderwijs</span><span class="rug-icon rug-icon--s rug-icon--caret-down rug-nav--main__icon" aria-hidden="true"></span></a>
|
||||||
|
<div class="rug-nav--flyout">
|
||||||
|
<div class="rug-layout rug-layout--l">
|
||||||
|
<div class="rug-layout__item rug-layout__item">
|
||||||
|
<ul class="rug-nav--flyout__list">
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/education/" class="rug-nav--flyout__link rug-link--caret">Onderwijs</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/education/bachelor/" class="rug-nav--flyout__link rug-link--caret">Bachelor</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/education/master/" class="rug-nav--flyout__link rug-link--caret">Master</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/education/exchange/" class="rug-nav--flyout__link rug-link--caret">Exchange</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/education/phd-programmes/" class="rug-nav--flyout__link rug-link--caret">Promotietrajecten</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/education/summer-winter-schools/" class="rug-nav--flyout__link rug-link--caret">Summer / Winter schools</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/society-business/knowledge-and-learning/" class="rug-nav--flyout__link rug-link--caret">Kennis en leren</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/education/contact/" class="rug-nav--flyout__link rug-link--caret">Contact</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li data-path="/research/index.xml" class="rug-nav--main__item">
|
||||||
|
<a href="https://www.rug.nl/research/" class="rug-nav--main__link js--noclick-m"><span>Onderzoek</span><span class="rug-icon rug-icon--s rug-icon--caret-down rug-nav--main__icon" aria-hidden="true"></span></a>
|
||||||
|
<div class="rug-nav--flyout rug-nav--flyout--discoverable">
|
||||||
|
<div class="rug-layout rug-layout--l">
|
||||||
|
<div class="rug-layout__item rug-layout__item rug-width-m-12-24">
|
||||||
|
<ul class="rug-nav--flyout__list">
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/research/" class="rug-nav--flyout__link rug-link--caret">Onderzoek</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/research/researchthemes/" class="rug-nav--flyout__link rug-link--caret">Onderzoeksthema's</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/research/societal-themes/" class="rug-nav--flyout__link rug-link--caret">Maatschappelijke thema's</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/research/institute-per-faculty/" class="rug-nav--flyout__link rug-link--caret">Onderzoekscentra en -instituten</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/education/phd-programmes/" class="rug-nav--flyout__link rug-link--caret">Promotietrajecten</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/research/portal/persons/search.html" class="rug-nav--flyout__link rug-link--caret">Deskundigen</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/research/pure/" class="rug-nav--flyout__link rug-link--caret">Onderzoeksdatabase</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/research/video-slider/" class="rug-nav--flyout__link rug-link--caret">Onderzoeksvideo's</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="rug-layout__item rug-width-m-12-24 js--show-random-child">
|
||||||
|
<a href="https://www.rug.nl/library/" class="rug-nav--flyout__link">
|
||||||
|
<figure class="rug-mb-s">
|
||||||
|
<img src="https://www.rug.nl/cmb/images/verwerken.jpg" alt=""></figure>Bibliotheek</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li data-path="/society-business/index.xml" class="rug-nav--main__item">
|
||||||
|
<a href="https://www.rug.nl/society-business/" class="rug-nav--main__link js--noclick-m"><span>Maatschappij/bedrijven</span><span class="rug-icon rug-icon--s rug-icon--caret-down rug-nav--main__icon" aria-hidden="true"></span></a>
|
||||||
|
<div class="rug-nav--flyout">
|
||||||
|
<div class="rug-layout rug-layout--l">
|
||||||
|
<div class="rug-layout__item rug-layout__item">
|
||||||
|
<ul class="rug-nav--flyout__list">
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/society-business/" class="rug-nav--flyout__link rug-link--caret">Maatschappij/bedrijven</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/society-business/knowledge-and-learning/" class="rug-nav--flyout__link rug-link--caret">Kennis en leren</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/society-business/doing-research-together/" class="rug-nav--flyout__link rug-link--caret">Samen onderzoek doen</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/society-business/young-talent/" class="rug-nav--flyout__link rug-link--caret">Jong talent</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li data-path="/alumni/index.xml" class="rug-nav--main__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/" class="rug-nav--main__link js--noclick-m"><span>Alumni</span><span class="rug-icon rug-icon--s rug-icon--caret-down rug-nav--main__icon" aria-hidden="true"></span></a>
|
||||||
|
<div class="rug-nav--flyout">
|
||||||
|
<div class="rug-layout rug-layout--l">
|
||||||
|
<div class="rug-layout__item rug-layout__item">
|
||||||
|
<ul class="rug-nav--flyout__list">
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/" class="rug-nav--flyout__link rug-link--caret">Alumni</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/for-alumni/" class="rug-nav--flyout__link rug-link--caret">Voor alumni</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/about-alumni/" class="rug-nav--flyout__link rug-link--caret">Over alumni</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/about-alumni/ambassadors/2019-2020/become-an-international-alumni-ambassador_" class="rug-nav--flyout__link rug-link--caret">Become an International Alumni Ambassador!</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/magazines-and-newsletters/" class="rug-nav--flyout__link rug-link--caret">Magazines en nieuwsbrieven</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/support-research-and-education/" class="rug-nav--flyout__link rug-link--caret">Steun onderzoek en onderwijs</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/contact/" class="rug-nav--flyout__link rug-link--caret">Contact</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/update-your-information/" class="rug-nav--flyout__link rug-link--caret">Wijzigingen doorgeven</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li data-path="/magazine/index.xml" class="rug-nav--main__item">
|
||||||
|
<a href="https://www.rug.nl/magazine/" class="rug-nav--main__link js--noclick-m"><span>Magazine</span><span class="rug-icon rug-icon--s rug-icon--caret-down rug-nav--main__icon" aria-hidden="true"></span></a>
|
||||||
|
<div class="rug-nav--flyout">
|
||||||
|
<div class="rug-layout rug-layout--l">
|
||||||
|
<div class="rug-layout__item rug-layout__item">
|
||||||
|
<ul class="rug-nav--flyout__list">
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/magazine/" class="rug-nav--flyout__link rug-link--caret">Magazine</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li data-path="/about-us/index.xml" class="rug-nav--main__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/" class="rug-nav--main__link js--noclick-m"><span>Over ons</span><span class="rug-icon rug-icon--s rug-icon--caret-down rug-nav--main__icon" aria-hidden="true"></span></a>
|
||||||
|
<div class="rug-nav--flyout">
|
||||||
|
<div class="rug-layout rug-layout--l">
|
||||||
|
<div class="rug-layout__item rug-layout__item">
|
||||||
|
<ul class="rug-nav--flyout__list">
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/" class="rug-nav--flyout__link rug-link--caret">Over ons</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/who-are-we/strategic-plan/" class="rug-nav--flyout__link rug-link--caret">Strategisch Plan 2015-2020</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/who-are-we/" class="rug-nav--flyout__link rug-link--caret">Wie zijn wij?</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/news-and-events/" class="rug-nav--flyout__link rug-link--caret">Nieuws en evenementen</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/where-do-we-stand/" class="rug-nav--flyout__link rug-link--caret">Onze positie</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/organization/" class="rug-nav--flyout__link rug-link--caret">Onze organisatie</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/work-with-us/" class="rug-nav--flyout__link rug-link--caret">Werken bij de RUG</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/organization/faculties/" class="rug-nav--flyout__link rug-link--caret">Faculteiten</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/internationalization/" class="rug-nav--flyout__link rug-link--caret">Internationalization</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/collaboration/" class="rug-nav--flyout__link rug-link--caret">Samenwerkingsverbanden</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/how-to-find-us/" class="rug-nav--flyout__link rug-link--caret">Waar vindt u ons</a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--flyout__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/support-research-and-education/" class="rug-nav--flyout__link rug-link--caret">Steun de RUG</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--main__item">
|
||||||
|
<a href="https://www.rug.nl/search/" class="rug-nav--main__button rug-nav--main__button--last rug-text-nowrap"><span class="rug-icon rug-icon--search" aria-hidden="true"></span><span class="rug-hidden-l rug-ml-xs">Zoeken</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div class="rug-wrapper rug-wrapper--knee-high">
|
||||||
|
<div class="rug-breadcrumbs js--shrink-to-fit" itemprop="breadcrumb">
|
||||||
|
<a class="rug-breadcrumbs__link js--shrinkable" href="https://www.rug.nl/education/">Onderwijs</a><span class="rug-icon rug-icon--angle-right rug-breadcrumbs__divider"></span><a class="rug-breadcrumbs__link js--shrinkable" href="https://www.rug.nl/education/exchange/">Exchange</a>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
jQuery(function() {
|
||||||
|
navigation.openLastSubMenu();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
<!--googleon: all--><div class="rug-background-white" id="main">
|
||||||
|
<div id="content" class="rug-wrapper rug-wrapper--overlay">
|
||||||
|
<div class="rug-layout">
|
||||||
|
<div class="rug-layout__item rug-width-m-8-24">
|
||||||
|
<!--googleoff: all-->
|
||||||
|
<nav>
|
||||||
|
<ul data-toggle-group="mainmenu" data-toggle-class="rug-block-max-m" data-toggle-id="menu-show" class="js--id-content-menu rug-nav--secondary rug-shadow-box js--togglable-item">
|
||||||
|
<li class="rug-nav--secondary__item rug-block-max-m ">
|
||||||
|
<a href="#" data-toggle-mode="togglable" data-toggle-group="submenu" data-toggle-class="rug-nav--secondary__link--selected" data-toggle-id="menu-main" class="rug-nav--secondary__link js--togglable-switch">HOME</a>
|
||||||
|
<ul data-toggle-group="submenu" data-toggle-class="rug-block" data-toggle-id="menu-main" class="js-id--mobilehome rug-nav--secondary__sub rug-nav--secondary__sub--hidden js--togglable-item">
|
||||||
|
<li data-menu-id="510d8573-cf08-4df5-beaf-72e59fc08035-33.14" class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://www.rug.nl/education/" class="rug-nav--secondary__sub__link"><span class="rug-nav--secondary__sub__link-text">Onderwijs</span></a>
|
||||||
|
</li>
|
||||||
|
<li data-menu-id="3ae46c16-ad2e-444e-a43f-af867c3010a1-36.154" class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://www.rug.nl/research/" class="rug-nav--secondary__sub__link"><span class="rug-nav--secondary__sub__link-text">Onderzoek</span></a>
|
||||||
|
</li>
|
||||||
|
<li data-menu-id="6728218e-6801-4701-a152-95d1eb7068fb-33.35" class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://www.rug.nl/society-business/" class="rug-nav--secondary__sub__link"><span class="rug-nav--secondary__sub__link-text">Maatschappij/bedrijven</span></a>
|
||||||
|
</li>
|
||||||
|
<li data-menu-id="cf576754-17bf-47a8-94c9-ebe3998623e2-33.31" class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://www.rug.nl/alumni/" class="rug-nav--secondary__sub__link"><span class="rug-nav--secondary__sub__link-text">Alumni</span></a>
|
||||||
|
</li>
|
||||||
|
<li data-menu-id="cdc6fddf-0fd0-44ad-ad19-c0df0260e44c-33.13" class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://www.rug.nl/magazine/" class="rug-nav--secondary__sub__link"><span class="rug-nav--secondary__sub__link-text">Magazine</span></a>
|
||||||
|
</li>
|
||||||
|
<li data-menu-id="82960c9f-53bd-4ea8-a9f7-b004b86378f6-36.154" class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/" class="rug-nav--secondary__sub__link"><span class="rug-nav--secondary__sub__link-text rug-b-0">Over ons</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
{% block menu %}
|
||||||
|
{% include "menu.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
<li class="rug-nav--secondary__item rug-block-max-m">
|
||||||
|
<ul class="rug-nav--secondary__sub rug-nav--secondary__sub--meta">
|
||||||
|
<li class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://www.rug.nl/about-us/how-to-find-us/contact" class="rug-nav--secondary__sub__link"><span class="rug-nav--secondary__sub__link-text rug-nav--secondary__sub__link-text--meta">Contact</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://myuniversity.rug.nl/infonet/medewerkers/dashboard/" class="rug-nav--secondary__sub__link"><span class="rug-nav--secondary__sub__link-text rug-nav--secondary__sub__link-text--meta">My University</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--secondary__sub__item">
|
||||||
|
<a href="https://studentportal.rug.nl/" class="rug-nav--secondary__sub__link" target="_blank"><span class="rug-nav--secondary__sub__link-text rug-nav--secondary__sub__link-text--meta rug-b-0">Student Portal</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--secondary__sub__item rug-nav--secondary__sub__item--languages" title="Language select">
|
||||||
|
<a onclick="setLanguageCookie('nl',''); reloadWithoutLangParameter(); return false;" class="rug-mr-xs" href="#"><span class="rug-sprite--flag--nl"></span><span class="rug-hidden-visually">Nederlands</span></a><a onclick="setLanguageCookie('en',''); reloadWithoutLangParameter(); return false;" class="rug-mr-xs" href="#"><span class="rug-sprite--flag--en"></span><span class="rug-hidden-visually">English</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<!--googleon: all-->
|
||||||
|
</div>
|
||||||
|
<div data-toggle-group="mainmenu" data-toggle-id="menu-show" data-toggle-class="rug-hidden-m--block" class="rug-layout__item rug-width-m-16-24">
|
||||||
|
<!--googleoff: snippet-->
|
||||||
|
|
||||||
|
<div class="rug-panel--content rug-panel--content--border">
|
||||||
|
<h1 class="rug-mb-0 rug-clearfix" lang="en">
|
||||||
|
<span lang="en">{% block pagetitle %} -=PageTitle=- {% endblock %}</span>
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div id="target-content-holder"></div>
|
||||||
|
<!--googleon: snippet-->
|
||||||
|
<div>
|
||||||
|
{% if messages %}
|
||||||
|
{% for message in messages %}
|
||||||
|
<script>
|
||||||
|
toastr.{{ message.tags }}('{{ message }}');
|
||||||
|
</script>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
<article class="rug-clearfix rug-theme--content rug-mb" lang="en">
|
||||||
|
<div>
|
||||||
|
{% block content %} -=PageContent=- {% endblock %}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
<!--googleoff: all-->
|
||||||
|
<div class="rug-layout rug-mt">
|
||||||
|
</div>
|
||||||
|
<!--googleon: all-->
|
||||||
|
</div>
|
||||||
|
<!--googleoff: all-->
|
||||||
|
<div class="rug-site-tools">
|
||||||
|
<a title="Print this page" href="#" data-label="page-tools print" data-category="button" class="js--analytics rug-site-tools__link rug-site-tools__icon" onClick="window.print(); return false;"><span class="rug-icon rug-icon--printer rug-mh-xxs" aria-hidden="true"></span><span class="rug-hidden-visually">print</span></a>
|
||||||
|
</div>
|
||||||
|
<!--googleon: all-->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><!--googleoff: all--><div data-toggle-group="mainmenu" data-toggle-id="menu-show" data-toggle-class="rug-hidden-m--block" class="rug-wrapper">
|
||||||
|
<aside class="rug-panel rug-panel--primary rug-p-0 rug-mt">
|
||||||
|
<div class="rug-layout rug-col-l-8-24 rug-col-s-12-24 rug-overflow-hidden rug-layout--no-margins"></div>
|
||||||
|
</aside>
|
||||||
|
</div><!--googleon: all-->
|
||||||
|
<!--googleoff: all--><div data-toggle-group="mainmenu" data-toggle-id="menu-show" data-toggle-class="rug-hidden-m--block" class="rug-background-white rug-pv-l rug-text-center">
|
||||||
|
<div class="rug-wrapper">
|
||||||
|
<div class="rug-layout">
|
||||||
|
<div class="rug-layout__item">
|
||||||
|
<span class="rug-mr-m">Volg de RUG</span><a href="https://www.facebook.com/universityofgroningen" class=""><span title="facebook" class="rug-icon rug-icon--l rug-icon--facebook"></span><span class="rug-hidden-visually">facebook</span></a> <a href="https://twitter.com/univgroningen" class=""><span title="twitter" class="rug-icon rug-icon--l rug-icon--twitter"></span><span class="rug-hidden-visually">twitter</span></a> <a href="https://www.linkedin.com/school/rijksuniversiteit-groningen/" class=""><span title="linkedin" class="rug-icon rug-icon--l rug-icon--linkedin"></span><span class="rug-hidden-visually">linkedin</span></a> <a href="https://www.rug.nl/corporate/nieuws/RSSfeeds" class=""><span title="rss" class="rug-icon rug-icon--l rug-icon--rss"></span><span class="rug-hidden-visually">rss</span></a> <a href="https://www.instagram.com/universityofgroningen/" class=""><span title="instagram" class="rug-icon rug-icon--l rug-icon--instagram"></span><span class="rug-hidden-visually">instagram</span></a> <a href="https://www.youtube.com/universityGroningen" class=""><span title="youtube" class="rug-icon rug-icon--l rug-icon--youtube"></span><span class="rug-hidden-visually">youtube</span></a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<footer data-toggle-group="mainmenu" data-toggle-id="menu-show" data-toggle-class="rug-hidden-m--block"><div class="rug-doormat rug-background-neutral-30">
|
||||||
|
<div class="rug-wrapper">
|
||||||
|
<div class="rug-layout rug-layout--no-margins">
|
||||||
|
<div class="rug-layout__item rug-width-s-12-24 rug-width-l-6-24">
|
||||||
|
<a href="https://www.rug.nl/education/" class="rug-doormat__heading__link rug-doormat__heading__link--desktop">Studiekiezers</a><a href="https://www.rug.nl/education/" class="rug-doormat__heading__link rug-doormat__heading__link--mobile js--equal js--togglable-switch" data-toggle-id="N10006" data-toggle-class="rug-doormat__heading__link--active" data-toggle-group="js-id-doormat" data-toggle-mode="togglable">Studiekiezers</a>
|
||||||
|
<ul class="rug-doormat__list rug-list--unstyled">
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/education/" class="rug-doormat__link rug-doormat__link--mobile">Studiekiezers</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/education/bachelor/events/" class="rug-doormat__link">Voorlichtingsactiviteiten voor studiekiezers</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/bachelors/" class="rug-doormat__link">Bacheloropleidingen</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/education/master/" class="rug-doormat__link">Masteropleidingen</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/gmw/lerarenopleiding/" class="rug-doormat__link">Lerarenopleiding</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/society-business/scholierenacademie/" class="rug-doormat__link">Scholierenacademie</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/education/summer-winter-schools/" class="rug-doormat__link">Summer Schools</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/education/honours-college/" class="rug-doormat__link">Honours College</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/education/phd-programmes/" class="rug-doormat__link">Promotietrajecten</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/education/faq/" class="rug-doormat__link">Veelgestelde vragen</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="rug-layout__item rug-width-s-12-24 rug-width-l-6-24">
|
||||||
|
<a href="https://www.rug.nl/society-business/" class="rug-doormat__heading__link rug-doormat__heading__link--desktop">Maatschappij/bedrijven</a><a href="https://www.rug.nl/society-business/" class="rug-doormat__heading__link rug-doormat__heading__link--mobile js--equal js--togglable-switch" data-toggle-id="N100DA" data-toggle-class="rug-doormat__heading__link--active" data-toggle-group="js-id-doormat" data-toggle-mode="togglable">Maatschappij/bedrijven</a>
|
||||||
|
<ul class="rug-doormat__list rug-list--unstyled">
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/society-business/" class="rug-doormat__link rug-doormat__link--mobile">Maatschappij/bedrijven</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/library/" class="rug-doormat__link">Universiteitsbibliotheek</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/research/portal/persons/search.html" class="rug-doormat__link">Zoek een deskundige</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/language-centre/" class="rug-doormat__link">Talencentrum</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/society-business/doing-research-together/ipbd/" class="rug-doormat__link">IP & Business Development</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/society-business/centre-for-information-technology/" class="rug-doormat__link">Centrum voor Informatie Technologie</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/university-museum/" class="rug-doormat__link">Universiteitsmuseum</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://shop.housing.rug.nl/shopuw/" class="rug-doormat__link" target="_blank">Universiteitswinkel</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/careerservices/" class="rug-doormat__link">Career Services</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="http://sggroningen.nl/" class="rug-doormat__link" target="_blank">Studium Generale</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/about-us/who-are-we/sustainability/greenoffice/" class="rug-doormat__link">Duurzame universiteit</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/society-business/science-shops/" class="rug-doormat__link">Wetenschapswinkel</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="rug-layout__item rug-width-s-12-24 rug-width-l-6-24">
|
||||||
|
<a href="https://www.rug.nl/alumni/" class="rug-doormat__heading__link rug-doormat__heading__link--desktop">Alumni</a><a href="https://www.rug.nl/alumni/" class="rug-doormat__heading__link rug-doormat__heading__link--mobile js--equal js--togglable-switch" data-toggle-id="N101CF" data-toggle-class="rug-doormat__heading__link--active" data-toggle-group="js-id-doormat" data-toggle-mode="togglable">Alumni</a>
|
||||||
|
<ul class="rug-doormat__list rug-list--unstyled">
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/alumni/" class="rug-doormat__link rug-doormat__link--mobile">Alumni</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/alumni/news-events/events/" class="rug-doormat__link">Alumni activiteiten</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/alumni/magazines-and-newsletters/" class="rug-doormat__link">Magazines en nieuwsbrieven</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/alumni/update-your-information/" class="rug-doormat__link">Wijzigingen doorgeven</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/alumni/support-research-and-education/" class="rug-doormat__link">Steun onderzoek en onderwijs</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="rug-layout__item rug-width-s-12-24 rug-width-l-6-24">
|
||||||
|
<a href="https://www.rug.nl/about-us/" class="rug-doormat__heading__link rug-doormat__heading__link--desktop">Over ons</a><a href="https://www.rug.nl/about-us/" class="rug-doormat__heading__link rug-doormat__heading__link--mobile js--equal js--togglable-switch" data-toggle-id="N1022C" data-toggle-class="rug-doormat__heading__link--active" data-toggle-group="js-id-doormat" data-toggle-mode="togglable">Over ons</a>
|
||||||
|
<ul class="rug-doormat__list rug-list--unstyled">
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/about-us/" class="rug-doormat__link rug-doormat__link--mobile">Over ons</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/about-us/how-to-find-us/" class="rug-doormat__link">Waar vindt u ons</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/about-us/work-with-us/job-opportunities/" class="rug-doormat__link">Vacatures</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/feb/" class="rug-doormat__link">Faculteit Economie en Bedrijfskunde</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/gmw/" class="rug-doormat__link">Faculteit Gedrags- en Maatschappijwetenschappen</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/ggw/" class="rug-doormat__link">Faculteit Godgeleerdheid en Godsdienstwetenschap</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/let/" class="rug-doormat__link">Faculteit der Letteren</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/umcg/" class="rug-doormat__link">Faculteit Medische Wetenschappen</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/rechten/" class="rug-doormat__link">Faculteit Rechtsgeleerdheid</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/frw/" class="rug-doormat__link">Faculteit Ruimtelijke Wetenschappen</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/filosofie/" class="rug-doormat__link">Faculteit Wijsbegeerte</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/fse/" class="rug-doormat__link">Faculty of Science and Engineering</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/ucg/" class="rug-doormat__link">University College Groningen</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/cf/" class="rug-doormat__link">Campus Fryslân</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/kvi-cart/" class="rug-doormat__link">KVI-CART</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/about-us/organization/bureau-of-the-university/" class="rug-doormat__link">Bureau van de Universiteit</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/about-us/organization/service-departments/facilitair-bedrijf" class="rug-doormat__link">Facilitair bedrijf</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/about-us/organization/bureau-of-the-university/communication" class="rug-doormat__link">Communicatie</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.rug.nl/about-us/news-and-events/events/calendar/" class="rug-doormat__link">Agenda</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div data-toggle-group="mainmenu" data-toggle-id="menu-show" data-toggle-class="rug-hidden-m--block" class="rug-doormat rug-background-white rug-pv-l rug-text-center">
|
||||||
|
<div class="rug-wrapper">
|
||||||
|
<a class="rug-font-default rug-doormat__metalink" href="https://www.rug.nl/info/disclaimer-copyright">Disclaimer & Copyright</a> <a class="rug-font-default rug-doormat__metalink" href="https://www.rug.nl/info/privacy">Privacy</a> <a class="rug-font-default rug-doormat__metalink" href="https://www.rug.nl/info/cookies">Cookies</a> <a class="rug-font-default rug-doormat__metalink" href="!sso">Inloggen</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
</body>
|
||||||
|
</html>
|
9
webservice/apps/RUG_template/templates/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Welcome to the RUG Template page" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "Welcome to the RUG Template page" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p><strong>{% trans "Simple RUG Template" %}</strong></p>
|
||||||
|
<p>{% trans "Some more text" %}</p>
|
||||||
|
{% endblock %}
|
10
webservice/apps/RUG_template/templates/menu.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
<li class="rug-nav--secondary__item">
|
||||||
|
<a class="rug-nav--secondary__link js--togglable-switch" data-toggle-class="rug-nav--secondary__link--selected" data-toggle-group="submenu" data-toggle-id="menu-2427370b-9435-44d9-bca7-b93ec9d03cc0-33.31" data-toggle-mode="togglable">{% trans "Section" %}</a>
|
||||||
|
<ul class="rug-nav--secondary__sub rug-nav--secondary__sub--hidden js--togglable-item" data-toggle-class="rug-block" data-toggle-group="submenu" data-toggle-id="menu-2427370b-9435-44d9-bca7-b93ec9d03cc0-33.31">
|
||||||
|
<li class="rug-nav--secondary__sub__item" data-menu-id="b512aa55-f0cb-4588-9054-302caa5fa951-33.34">
|
||||||
|
<a class="rug-nav--secondary__sub__link" href="#"><span class="rug-nav--secondary__sub__link-text">{% trans "Menu item" %}</span></a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
29
webservice/apps/RUG_template/templates/pager.html
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% if page_obj.paginator.num_pages > 1 %}
|
||||||
|
<ul class="rug-list--inline" >
|
||||||
|
{% if page_obj.has_previous %}
|
||||||
|
{% comment %}
|
||||||
|
<li class="rug-list--inline__item">
|
||||||
|
<a href="?page=1" name="page" class="rug-button rug-button--small rug-button--neutral" value="1"><span>«« {% trans "first" %}</span></a>
|
||||||
|
</li>
|
||||||
|
{% endcomment %}
|
||||||
|
<li class="rug-list--inline__item">
|
||||||
|
<a href="?page={{ page_obj.previous_page_number }}" name="prev" class="rug-button rug-button--small rug-button--neutral" value="« {% trans "previous" %}"><span>« {% trans "previous" %}</span></a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
<li class="rug-list--inline__item">
|
||||||
|
<a name="page" class="rug-button rug-button--small rug-button--secondary" value="{{ page_obj.number }}"><span>{{ page_obj.number }}</span></a>
|
||||||
|
</li>
|
||||||
|
{% if page_obj.has_next %}
|
||||||
|
<li class="rug-list--inline__item">
|
||||||
|
<a href="?page={{ page_obj.next_page_number }}" name="next" class="rug-button rug-button--small rug-button--neutral" value="{% trans "next" %} »"><span>{% trans "next" %} »</span></a>
|
||||||
|
</li>
|
||||||
|
{% comment %}
|
||||||
|
<li class="rug-list--inline__item">
|
||||||
|
<a href="?page={{ page_obj.paginator.num_pages }}" name="page" class="rug-button rug-button--small rug-button--neutral" value="{% trans "last" %} »»"><span>{% trans "last" %} »»</span></a>
|
||||||
|
</li>
|
||||||
|
{% endcomment %}
|
||||||
|
{% endif %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
@ -0,0 +1,25 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Login" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "Login" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Login" %}</strong>
|
||||||
|
<br />
|
||||||
|
{% blocktrans %}You can login here to create your schedules. If you do not have a login, please contact: some_one@rug.nl{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<form method="post" action="{% url 'login' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<input type="hidden" name="next" value="{{ next }}">
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><input type="submit" value="{% trans "Login" %}"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<br />
|
||||||
|
{# Assumes you setup the password_reset view in your URLconf #}
|
||||||
|
<p><a href="{% url 'password_reset' %}">{% trans "Lost password?" %}</a></p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,10 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Password reset complete" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "Password reset complete" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p><strong>{% trans "Password reset complete" %}</strong></p>
|
||||||
|
{% url 'login' as login_url %}
|
||||||
|
<p>{% blocktrans %}Your new password has been set. You can log in now on the <a href="{{ login_url }}">log in page</a>.{% endblocktrans %}</p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,26 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Set a new password!" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "Set a new password!" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p><strong>{% trans "Set a new password!" %}</strong><br />
|
||||||
|
{% if validlink %}
|
||||||
|
{% blocktrans %}Here you can set a new password.{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<form method="POST">
|
||||||
|
<table>
|
||||||
|
{% csrf_token %}
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2">
|
||||||
|
<input type="submit" value="{% trans "Change my password" %}">
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
{% trans "The password reset link was invalid, possibly because it has already been used. Please request a new password reset." %}</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% endblock %}
|
@ -0,0 +1,13 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Reset password, email sent" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "Reset password, email sent" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Reset password, email sent" %}</strong>
|
||||||
|
<br />
|
||||||
|
<br />
|
||||||
|
{% blocktrans %}We've emailed you instructions for setting your password. You should receive the email shortly!{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
{% endblock %}
|
@ -0,0 +1,13 @@
|
|||||||
|
{% load i18n %}{% url 'password_reset_confirm' uidb64=uid token=token as reset_url%}
|
||||||
|
{% autoescape off %}{% blocktrans %}You're receiving this email because you requested a password reset for your user account at {{ site_name }}.
|
||||||
|
|
||||||
|
Please go to the following page and choose a new password:
|
||||||
|
|
||||||
|
{{ protocol}}://{{ domain }}{{ reset_url }}
|
||||||
|
|
||||||
|
Your username, in case you've forgotten: {{ user }}
|
||||||
|
|
||||||
|
Thanks for using our site!
|
||||||
|
|
||||||
|
The {{ site_name }} team{% endblocktrans %}
|
||||||
|
{% endautoescape %}
|
@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Reset password" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "Reset password" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p>
|
||||||
|
<strong>{% trans "Reset password" %}</strong>
|
||||||
|
<br />
|
||||||
|
{% blocktrans %}Here you can request a password reset. Please enter your email address that is used for registration.{% endblocktrans %}
|
||||||
|
</p>
|
||||||
|
<form method="post" action="{% url 'password_reset' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><input type="submit" value="{% trans "Reset my password"%} "></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
18
webservice/apps/RUG_template/templates/singup.html
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Singup" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "Singup" %}{% endblock %}
|
||||||
|
{% block content %}
|
||||||
|
<p><strong>{% trans "Singup" %}</strong></p>
|
||||||
|
|
||||||
|
<form method="post" action="{% url 'signup' %}">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
<tr>
|
||||||
|
<td colspan="2"><input type="submit" value="{% trans "Singup" %}"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
3
webservice/apps/RUG_template/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
7
webservice/apps/RUG_template/urls.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.index, name='index'),
|
||||||
|
]
|
7
webservice/apps/RUG_template/views.py
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
|
def index(request):
|
||||||
|
template_name = 'index.html'
|
||||||
|
|
||||||
|
return render(request, template_name, {})
|
1
webservice/apps/api/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'apps.api.apps.ApiConfig'
|
9
webservice/apps/api/admin.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from .models import Token
|
||||||
|
|
||||||
|
@admin.register(Token)
|
||||||
|
class TokenAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ('key', 'user','is_supertoken', 'last_access')
|
||||||
|
ordering = ('-last_access', 'user', )
|
||||||
|
search_fields = ('key', 'user__username',)
|
||||||
|
readonly_fields = ('created_at', 'updated_at')
|
66
webservice/apps/api/apps.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
name = 'apps.api'
|
||||||
|
label = 'api'
|
||||||
|
verbose_name = _('API')
|
||||||
|
verbose_name_plural = _('APIs')
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert settings.SWAGGER_SETTINGS
|
||||||
|
except AttributeError:
|
||||||
|
# We only load this setting, if it is not available in the overall settings.py file
|
||||||
|
settings.SWAGGER_SETTINGS = {
|
||||||
|
'SECURITY_DEFINITIONS': {
|
||||||
|
'Hawk': {
|
||||||
|
'type': 'apiKey',
|
||||||
|
'description': 'HTTP Holder-Of-Key Authentication Scheme, https://github.com/hapijs/hawk, https://hawkrest.readthedocs.io/en/latest/<br /><strong>Ex header:</strong><br />\'Authorization\': \'Hawk mac="F4+S9cu7yZiZEgdtqzMpOOdudvqcV2V2Yzk2WcphECc=", hash="+7fKUX+djeQolvnLTxr0X47e//UHKbkRlajwMw3tx3w=", id="7FI5JET4", ts="1592905433", nonce="DlV-fL"\'',
|
||||||
|
'name': 'Authorization',
|
||||||
|
'in': 'header'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert settings.REST_FRAMEWORK
|
||||||
|
except AttributeError:
|
||||||
|
# We only load this setting, if it is not available in the overall settings.py file
|
||||||
|
# To protect all API views with Hawk by default, put this in your settings:
|
||||||
|
# https://hawkrest.readthedocs.io/en/latest/usage.html#protecting-api-views-with-hawk
|
||||||
|
settings.REST_FRAMEWORK = {
|
||||||
|
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'apps.api.authentication.APIHawk',
|
||||||
|
),
|
||||||
|
|
||||||
|
'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
'rest_framework.permissions.IsAuthenticated',
|
||||||
|
),
|
||||||
|
|
||||||
|
# 'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
# 'rest_framework.authentication.TokenAuthentication',
|
||||||
|
# ),
|
||||||
|
|
||||||
|
# 'DEFAULT_PERMISSION_CLASSES': (
|
||||||
|
# 'rest_framework.permissions.IsAuthenticated', ),
|
||||||
|
|
||||||
|
# Use Django's standard `django.contrib.auth` permissions,
|
||||||
|
# or allow read-only access for unauthenticated users.
|
||||||
|
#'DEFAULT_PERMISSION_CLASSES': [
|
||||||
|
# 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
|
||||||
|
#],
|
||||||
|
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
|
||||||
|
'PAGE_SIZE': 10
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
assert settings.HAWK_MESSAGE_EXPIRATION
|
||||||
|
except AttributeError:
|
||||||
|
# We only load this setting, if it is not available in the overall settings.py file
|
||||||
|
settings.HAWK_MESSAGE_EXPIRATION = 60
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from . import signals
|
72
webservice/apps/api/authentication.py
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# import the logging library
|
||||||
|
import logging
|
||||||
|
# Get an instance of a logger
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
import django.utils
|
||||||
|
from rest_framework import exceptions
|
||||||
|
from hawkrest import HawkAuthentication
|
||||||
|
|
||||||
|
from .models import Token
|
||||||
|
class APIHawk(HawkAuthentication):
|
||||||
|
"""This is the API authentication that is using the HAWK authentication mechanism.
|
||||||
|
|
||||||
|
This class will implement a custom credentials and user lookups so that we can dynamically add new users and update tokens.
|
||||||
|
"""
|
||||||
|
def hawk_credentials_lookup(self, id):
|
||||||
|
"""This method will perform the check if the used token is an existing/known token in the database. This will not lookup a user. Only an existing token.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
id (string): The token key to lookup in the database for existing token.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
exceptions.AuthenticationFailed: If the given token does not exists.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The dictionary holds the token id, the token secret and the used hashing algoritem that is used.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
token = Token.objects.get(key=id)
|
||||||
|
except Token.DoesNotExist:
|
||||||
|
logger.warning('Requested to validate with invalid/non existing token: {}'.format(id))
|
||||||
|
raise exceptions.AuthenticationFailed('No such token: {}'.format(id))
|
||||||
|
|
||||||
|
return {
|
||||||
|
'id' : id,
|
||||||
|
'key' : token.secret,
|
||||||
|
'algorithm' : 'sha256'
|
||||||
|
}
|
||||||
|
|
||||||
|
def hawk_user_lookup(self, request, credentials):
|
||||||
|
"""Return the user account that is connected to the used token.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
request ([type]): The incoming HTTP/API request
|
||||||
|
credentials (dict): The credentials from ~hawk_credentials_lookup
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
exceptions.AuthenticationFailed: If the given token does not exists to an existing user
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: Returns a tuple holding the user as first item
|
||||||
|
"""
|
||||||
|
user = None
|
||||||
|
try:
|
||||||
|
user = Token.objects.get(key=credentials['id']).user
|
||||||
|
except Token.DoesNotExist:
|
||||||
|
logger.warning('Requested to validate non existing user: {}'.format(id))
|
||||||
|
raise exceptions.AuthenticationFailed('No user for token: {}'.format(credentials['id']))
|
||||||
|
|
||||||
|
# Update the date time stamp to now for last access data
|
||||||
|
user.token.last_access = django.utils.timezone.now()
|
||||||
|
user.token.save()
|
||||||
|
|
||||||
|
return (user,None)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
"""Authentication identifier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
string: Returns the name of the used authentication mechanism.
|
||||||
|
"""
|
||||||
|
return 'Hawk authenticator'
|
BIN
webservice/apps/api/locale/en/LC_MESSAGES/django.mo
Normal file
67
webservice/apps/api/locale/en/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-07-30 15:42+0200\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: apps/api/apps.py:9
|
||||||
|
msgid "API"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/apps.py:10
|
||||||
|
msgid "APIs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:28
|
||||||
|
msgid "token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:29
|
||||||
|
msgid "tokens"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:31
|
||||||
|
msgid "Select the user for this token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:32
|
||||||
|
msgid "Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:32
|
||||||
|
msgid "The key for this token. This is used for Hawk verification."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:33
|
||||||
|
msgid "Secret"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:33
|
||||||
|
msgid "The secret for this token. This is used for Hawk signing."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:34
|
||||||
|
msgid "Last access"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:34
|
||||||
|
msgid "The date and time when this token is last used."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:44
|
||||||
|
msgid "Super token"
|
||||||
|
msgstr ""
|
BIN
webservice/apps/api/locale/nl/LC_MESSAGES/django.mo
Normal file
67
webservice/apps/api/locale/nl/LC_MESSAGES/django.po
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: \n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2020-07-30 15:42+0200\n"
|
||||||
|
"PO-Revision-Date: 2020-05-27 16:25+0200\n"
|
||||||
|
"Last-Translator: Joshua Rubingh <j.g.rubingh@rug.nl>\n"
|
||||||
|
"Language-Team: \n"
|
||||||
|
"Language: nl\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
"X-Generator: Poedit 2.0.6\n"
|
||||||
|
|
||||||
|
#: apps/api/apps.py:9
|
||||||
|
msgid "API"
|
||||||
|
msgstr "API"
|
||||||
|
|
||||||
|
#: apps/api/apps.py:10
|
||||||
|
msgid "APIs"
|
||||||
|
msgstr "APIs"
|
||||||
|
|
||||||
|
#: apps/api/models.py:28
|
||||||
|
msgid "token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:29
|
||||||
|
msgid "tokens"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:31
|
||||||
|
msgid "Select the user for this token"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:32
|
||||||
|
msgid "Key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:32
|
||||||
|
msgid "The key for this token. This is used for Hawk verification."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:33
|
||||||
|
msgid "Secret"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:33
|
||||||
|
msgid "The secret for this token. This is used for Hawk signing."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:34
|
||||||
|
msgid "Last access"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:34
|
||||||
|
msgid "The date and time when this token is last used."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: apps/api/models.py:44
|
||||||
|
msgid "Super token"
|
||||||
|
msgstr ""
|
35
webservice/apps/api/migrations/0001_initial.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Generated by Django 3.0.8 on 2020-07-30 14:15
|
||||||
|
|
||||||
|
import apps.api.models
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django_cryptography.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Token',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this model has been created', verbose_name='Date created')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this model has been updated', verbose_name='Date updated')),
|
||||||
|
('key', models.CharField(default=apps.api.models.get_random_key, help_text='The key for this token. This is used for Hawk verification.', max_length=16, unique=True, verbose_name='Key')),
|
||||||
|
('secret', django_cryptography.fields.encrypt(models.CharField(default=apps.api.models.get_random_secret, help_text='The secret for this token. This is used for Hawk signing.', max_length=64, verbose_name='Secret'))),
|
||||||
|
('last_access', models.DateTimeField(auto_now_add=True, help_text='The date and time when this token is last used.', verbose_name='Last access')),
|
||||||
|
('user', models.OneToOneField(help_text='Select the user for this token', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'token',
|
||||||
|
'verbose_name_plural': 'tokens',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
webservice/apps/api/migrations/__init__.py
Normal file
70
webservice/apps/api/models.py
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from django_cryptography.fields import encrypt
|
||||||
|
|
||||||
|
from lib.utils.general import get_random_string
|
||||||
|
from lib.models.base import MetaDataModel
|
||||||
|
|
||||||
|
|
||||||
|
def get_random_key():
|
||||||
|
return get_random_string(8)
|
||||||
|
|
||||||
|
def get_random_secret():
|
||||||
|
return get_random_string(32)
|
||||||
|
|
||||||
|
class TokenManager(models.Manager):
|
||||||
|
"""
|
||||||
|
Custom queryset which will prefetch related user table data when requesting a token from the database as the user is mostly needed every time the token is requested.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
return super(TokenManager, self).get_queryset().select_related('user')
|
||||||
|
|
||||||
|
class Token(MetaDataModel):
|
||||||
|
"""Token model that holds all the tokens that are used for the API authentication.
|
||||||
|
|
||||||
|
A new token is generated every time when a new user is created. So there is no need for manual token creating. This is done through a signal :attr:`~apps.api.signals.create_user_token`
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
user : :class:`~django.contrib.auth.models.User`
|
||||||
|
The user to which this token belongs too
|
||||||
|
key : str
|
||||||
|
The key value that is used for token lookups
|
||||||
|
secret : str
|
||||||
|
The secret that is used for encrypting/signing the API messages
|
||||||
|
last_access : datetime
|
||||||
|
The date and time when the token is last used (logged in)
|
||||||
|
"""
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('token')
|
||||||
|
verbose_name_plural = _('tokens')
|
||||||
|
|
||||||
|
user = models.OneToOneField(User, on_delete=models.CASCADE, help_text=_('Select the user for this token'))
|
||||||
|
key = models.CharField(_('Key') , unique=True, default=get_random_key, max_length=16, help_text=_('The key for this token. This is used for Hawk verification.'))
|
||||||
|
secret = encrypt(models.CharField(_('Secret') ,max_length=64, default=get_random_secret, help_text=_('The secret for this token. This is used for Hawk signing.')))
|
||||||
|
last_access = models.DateTimeField(_('Last access'),auto_now_add=True, help_text=_('The date and time when this token is last used.'))
|
||||||
|
|
||||||
|
# Custom manager that will retrieve the related user table as well.
|
||||||
|
objects = TokenManager()
|
||||||
|
|
||||||
|
def is_supertoken(self):
|
||||||
|
"""Boolean check if the token is belonging to a user with super user rights. Then this token is a super token.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: Returns true when the token belongs to a super user.
|
||||||
|
"""
|
||||||
|
# TODO: Is it allowed to be a super user and researcher? Could give conflict of interests. With the API token you can read other researchers data...
|
||||||
|
return self.user.is_superuser == True
|
||||||
|
|
||||||
|
is_supertoken.boolean = True
|
||||||
|
is_supertoken.short_description = _('Super token')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
"""
|
||||||
|
Print the full name of the researcher based on the first and last name fields of the User model.
|
||||||
|
"""
|
||||||
|
return '{} ({})'.format(self.key,self.user.get_full_name())
|
24
webservice/apps/api/signals.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.db.models.signals import post_save
|
||||||
|
from django.dispatch import receiver
|
||||||
|
|
||||||
|
from .models import Token
|
||||||
|
|
||||||
|
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
|
||||||
|
def create_user_token(sender, instance=None, created=False, **kwargs):
|
||||||
|
"""
|
||||||
|
When a new user is created, this signal will also create a new API token for this user. So every user will have an API token.
|
||||||
|
|
||||||
|
Arguments
|
||||||
|
----------
|
||||||
|
sender : sender
|
||||||
|
The model that has triggered the signal
|
||||||
|
|
||||||
|
instance: :attr:`~django.contrib.auth.models.User`
|
||||||
|
The newly created user model data
|
||||||
|
|
||||||
|
created : boolean
|
||||||
|
Wether the object was created (True) or updated (False).
|
||||||
|
"""
|
||||||
|
if created:
|
||||||
|
Token.objects.create(user=instance)
|
3
webservice/apps/api/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
71
webservice/apps/api/urls.py
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
from django.urls import path, re_path, include
|
||||||
|
|
||||||
|
from rest_framework import permissions, routers
|
||||||
|
|
||||||
|
from drf_yasg2.views import get_schema_view
|
||||||
|
from drf_yasg2 import openapi
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
from apps.dropoff.api.views import DatadropViewSet
|
||||||
|
from apps.invitation.api.views import InvitationViewSet
|
||||||
|
from apps.researcher.api.views import ResearcherViewSet
|
||||||
|
from apps.storage.api.views import StorageEngineViewSet, StorageLocationViewSet
|
||||||
|
from apps.study.api.views import StudyViewSet
|
||||||
|
from apps.virtual_machine.api.views import (VirtualMachineViewSet,
|
||||||
|
VirtualMachineOperatingSystemViewSet,
|
||||||
|
VirtualMachineProfileViewSet,
|
||||||
|
VirtualMachineMemoryViewSet,
|
||||||
|
VirtualMachineNetworkViewSet,
|
||||||
|
VirtualMachineStorageViewSet,
|
||||||
|
VirtualMachineGPUViewSet)
|
||||||
|
|
||||||
|
schema_view = get_schema_view(
|
||||||
|
openapi.Info(
|
||||||
|
title="Virtual Research Environment API",
|
||||||
|
default_version='v1',
|
||||||
|
description="Here you can see a list of API endpoints and actions that are available to communicate with the VRE API",
|
||||||
|
terms_of_service="https://www.rug.nl",
|
||||||
|
contact=openapi.Contact(email="vre_team@rug.nl"),
|
||||||
|
license=openapi.License(name="MIT License"),
|
||||||
|
),
|
||||||
|
public=True,
|
||||||
|
permission_classes=(permissions.AllowAny,),
|
||||||
|
)
|
||||||
|
|
||||||
|
api_router_v1 = routers.DefaultRouter()
|
||||||
|
|
||||||
|
api_router_v1.register(r'researchers', ResearcherViewSet)
|
||||||
|
|
||||||
|
api_router_v1.register(r'studies', StudyViewSet)
|
||||||
|
|
||||||
|
api_router_v1.register(r'dropoffs', DatadropViewSet)
|
||||||
|
|
||||||
|
api_router_v1.register(r'invitations', InvitationViewSet)
|
||||||
|
|
||||||
|
api_router_v1.register(r'storageengines', StorageEngineViewSet)
|
||||||
|
api_router_v1.register(r'storagelocations', StorageLocationViewSet)
|
||||||
|
|
||||||
|
# Order is important for virtual machines. Longest match first
|
||||||
|
api_router_v1.register(r'virtualmachines/profiles', VirtualMachineProfileViewSet)
|
||||||
|
api_router_v1.register(r'virtualmachines/storage', VirtualMachineStorageViewSet)
|
||||||
|
api_router_v1.register(r'virtualmachines/memory', VirtualMachineMemoryViewSet)
|
||||||
|
api_router_v1.register(r'virtualmachines/network', VirtualMachineNetworkViewSet)
|
||||||
|
api_router_v1.register(r'virtualmachines/gpu', VirtualMachineGPUViewSet)
|
||||||
|
api_router_v1.register(r'virtualmachines/os', VirtualMachineOperatingSystemViewSet)
|
||||||
|
api_router_v1.register(r'virtualmachines', VirtualMachineViewSet)
|
||||||
|
|
||||||
|
# Main namespace for the API urls
|
||||||
|
app_name = 'api'
|
||||||
|
urlpatterns = [
|
||||||
|
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
|
||||||
|
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
|
||||||
|
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
|
||||||
|
|
||||||
|
# Extra /api/info path for checking if the Hawk authentication is working.
|
||||||
|
# Also this will give the full url to the OpenAPI documentation
|
||||||
|
path('info/', views.Info.as_view(), name='info'),
|
||||||
|
|
||||||
|
# Add extra namespace for versioning the API
|
||||||
|
path('v1/', include((api_router_v1.urls,'api'),namespace='v1')),
|
||||||
|
]
|
52
webservice/apps/api/views.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
from rest_framework.decorators import schema
|
||||||
|
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from lib.utils.general import get_ip_address
|
||||||
|
|
||||||
|
@schema(None)
|
||||||
|
class Info(APIView):
|
||||||
|
"""
|
||||||
|
Show some API information. Also this can be used to check if the Hawk credentials are working.
|
||||||
|
|
||||||
|
Make sure your request does contain the header 'Content-Type': 'application/json'
|
||||||
|
"""
|
||||||
|
|
||||||
|
def get(self, request, format=None):
|
||||||
|
"""
|
||||||
|
Default API get action will return the following information in a dict:
|
||||||
|
|
||||||
|
- Connected user
|
||||||
|
- Used authentication scheme
|
||||||
|
- The remote IP of the connection
|
||||||
|
- The used content type
|
||||||
|
- The full url to the API documentation (OpenAPI)
|
||||||
|
- If a super token is used
|
||||||
|
"""
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'type' : 'anonymous',
|
||||||
|
'auth' : 'none',
|
||||||
|
'remote_ip' : get_ip_address(request),
|
||||||
|
'content_type' : request.content_type,
|
||||||
|
'openapi' : request.build_absolute_uri(reverse('api:schema-redoc')),
|
||||||
|
}
|
||||||
|
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
|
||||||
|
data['user'] = request.user.username
|
||||||
|
data['type'] = 'authenticated'
|
||||||
|
data['auth'] = str(request.successful_authenticator)
|
||||||
|
|
||||||
|
if request.user.token.is_supertoken:
|
||||||
|
data['type'] = 'supertoken'
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
assert request.user.researcher
|
||||||
|
data['type'] = 'researcher'
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return Response(data)
|
1
webservice/apps/synthea/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'apps.synthea.apps.SyntheaConfig'
|
3
webservice/apps/synthea/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
10
webservice/apps/synthea/apps.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
class SyntheaConfig(AppConfig):
|
||||||
|
name = 'apps.synthea'
|
||||||
|
label = 'synthea'
|
||||||
|
verbose_name = _('Synthea')
|
||||||
|
verbose_name_plural = _('Synthea')
|
27
webservice/apps/synthea/forms.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from django.forms import ModelForm
|
||||||
|
from django import forms
|
||||||
|
|
||||||
|
from apps.synthea.models import Synthea
|
||||||
|
|
||||||
|
from .lib.utils import available_states, available_modules
|
||||||
|
|
||||||
|
class SyntheaForm(ModelForm):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Synthea
|
||||||
|
fields = ['state', 'population', 'gender', 'age', 'module']
|
||||||
|
|
||||||
|
# This is loaded only once during startup. So changing the state data will not be picked up after a restart
|
||||||
|
state_options = [('','Any')]
|
||||||
|
for item in available_states():
|
||||||
|
state_options.append((item,item))
|
||||||
|
|
||||||
|
module_options = [('','Any')]
|
||||||
|
for item in available_modules():
|
||||||
|
module_options.append((item['module'],item['name']))
|
||||||
|
|
||||||
|
widgets = {
|
||||||
|
'state': forms.Select(choices=state_options),
|
||||||
|
'gender': forms.Select(choices=[('','Any'),('m','Male'),('f','Female')]),
|
||||||
|
'module': forms.Select(choices=module_options)
|
||||||
|
}
|
83
webservice/apps/synthea/lib/utils.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
import pandas as pd
|
||||||
|
import shlex
|
||||||
|
import subprocess
|
||||||
|
from zipfile import ZipFile
|
||||||
|
import json
|
||||||
|
|
||||||
|
def available_states():
|
||||||
|
#TODO: Make a setting for this path
|
||||||
|
location = Path('/opt/development/synthea_webservice/synthea/src/main/resources/geography/')
|
||||||
|
|
||||||
|
df = pd.read_csv(location / 'timezones.csv', index_col=False)
|
||||||
|
# The state information is expected in the first column
|
||||||
|
states = df[df.columns[0]].to_list()
|
||||||
|
states.sort()
|
||||||
|
return states
|
||||||
|
|
||||||
|
def available_modules():
|
||||||
|
#TODO: Make a setting for this path
|
||||||
|
location = Path('/opt/development/synthea_webservice/synthea/src/main/resources/modules/')
|
||||||
|
|
||||||
|
# Assumption here: A folder is a single module. And all .json in the main modules folder is a module.
|
||||||
|
modules = []
|
||||||
|
for module in location.iterdir():
|
||||||
|
if module.is_file() and module.suffix == '.json':
|
||||||
|
data = json.loads(module.read_text())
|
||||||
|
modules.append({'module' : module.name.replace('.json',''), 'name' : data['name']})
|
||||||
|
|
||||||
|
modules = sorted(modules, key=lambda k: k['name'].lower())
|
||||||
|
return modules
|
||||||
|
|
||||||
|
def run_synthea(state = None, population = None, gender = None, age = None, module = None):
|
||||||
|
# TODO: Make synthea setting(s)
|
||||||
|
location = '/opt/development/synthea_webservice/synthea/'
|
||||||
|
synthea_cmd = ['/opt/development/synthea_webservice/synthea/run_synthea']
|
||||||
|
zip_file = 'Synthea_'
|
||||||
|
zip_export = location
|
||||||
|
|
||||||
|
if population:
|
||||||
|
synthea_cmd.append('-p')
|
||||||
|
synthea_cmd.append(str(population))
|
||||||
|
zip_file += f'population_{population}_'
|
||||||
|
|
||||||
|
if gender:
|
||||||
|
synthea_cmd.append('-g')
|
||||||
|
synthea_cmd.append(gender.upper())
|
||||||
|
zip_file += f'gender_{gender}_'
|
||||||
|
|
||||||
|
if age:
|
||||||
|
synthea_cmd.append('-a')
|
||||||
|
synthea_cmd.append(age)
|
||||||
|
zip_file += f'age_{age}_'
|
||||||
|
|
||||||
|
if module:
|
||||||
|
synthea_cmd.append('-m')
|
||||||
|
synthea_cmd.append(module)
|
||||||
|
zip_file += f'module_{module}_'
|
||||||
|
|
||||||
|
if state:
|
||||||
|
synthea_cmd.append(state)
|
||||||
|
zip_file += f'state_{state}'
|
||||||
|
|
||||||
|
process_ok = False
|
||||||
|
log = ''
|
||||||
|
with subprocess.Popen(synthea_cmd,cwd=location, stdout=subprocess.PIPE,stderr=subprocess.PIPE) as process:
|
||||||
|
for line in process.stdout:
|
||||||
|
line = line.decode('utf8')
|
||||||
|
log += line
|
||||||
|
if not process_ok:
|
||||||
|
process_ok = line.find('BUILD SUCCESSFUL') >= 0
|
||||||
|
|
||||||
|
if process_ok:
|
||||||
|
with ZipFile(f'{zip_export}/{zip_file}.zip', 'w') as export:
|
||||||
|
for file in Path(location + 'output/fhir_stu3').iterdir():
|
||||||
|
export.write(file,file.name)
|
||||||
|
|
||||||
|
return Path(f'{zip_export}/{zip_file}.zip')
|
||||||
|
else:
|
||||||
|
raise Exception(log)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
32
webservice/apps/synthea/migrations/0001_initial.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
# Generated by Django 3.1.3 on 2020-11-13 09:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Synthea',
|
||||||
|
fields=[
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True, help_text='The date and time this model has been created', verbose_name='Date created')),
|
||||||
|
('updated_at', models.DateTimeField(auto_now=True, help_text='The date and time this model has been updated', verbose_name='Date updated')),
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, help_text='A unique id', primary_key=True, serialize=False, unique=True, verbose_name='ID')),
|
||||||
|
('state', models.CharField(help_text='The state for which synthea generate data.', max_length=200, verbose_name='Stage')),
|
||||||
|
('population', models.PositiveSmallIntegerField(default=50, help_text='The size of the population', verbose_name='Population')),
|
||||||
|
('gender', models.CharField(help_text='Select the gender type', max_length=1, verbose_name='Gender')),
|
||||||
|
('age', models.CharField(help_text='Select the age range', max_length=10, verbose_name='Age range')),
|
||||||
|
('module', models.CharField(help_text='Select the module', max_length=50, verbose_name='Mopdule')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'token',
|
||||||
|
'verbose_name_plural': 'tokens',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
0
webservice/apps/synthea/migrations/__init__.py
Normal file
24
webservice/apps/synthea/models.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from django.contrib.auth.models import User
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
from django_cryptography.fields import encrypt
|
||||||
|
|
||||||
|
from lib.utils.general import get_random_string
|
||||||
|
from lib.models.base import MetaDataModel
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
|
class Synthea(MetaDataModel):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _('token')
|
||||||
|
verbose_name_plural = _('tokens')
|
||||||
|
|
||||||
|
id = models.UUIDField(_('ID'), primary_key=True, unique=True, default=uuid.uuid4, editable=False, help_text=_('A unique id'))
|
||||||
|
state = models.CharField(_('State'), max_length=200, help_text=_('The state for which synthea generate data.'))
|
||||||
|
population = models.PositiveSmallIntegerField(_('Population'), blank=True, default=50, help_text=_('The size of the population'))
|
||||||
|
gender = models.CharField(_('Gender'), blank=True,max_length=1, help_text=_('Select the gender type'))
|
||||||
|
age = models.CharField(_('Age range'), blank=True,max_length=10, help_text=_('Select the age range'))
|
||||||
|
module = models.CharField(_('Module'),blank=True, max_length=50, help_text=_('Select the module'))
|
@ -0,0 +1,21 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "Generate a new Synthea data set" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "Generate a new Synthea data set" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block menu %}
|
||||||
|
{% include 'synthea/menu.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>Synthea generartor</h1>
|
||||||
|
<p>Enter the form fields and press submit. <small>U vraagt, wij draaien ;)</small></p>
|
||||||
|
<form method="POST" class="post-form">
|
||||||
|
{% csrf_token %}
|
||||||
|
<table>
|
||||||
|
{{ form.as_table }}
|
||||||
|
</table>
|
||||||
|
<button type="submit" class="save btn btn-default">Save</button>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
14
webservice/apps/synthea/templates/synthea/index.html
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{% extends 'base.html' %} <!-- Add this for inheritance -->
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans "dHealth Synthea" %}{% endblock %}
|
||||||
|
{% block pagetitle %}{% trans "dHealth Synthea" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block menu %}
|
||||||
|
{% include 'synthea/menu.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>dHealt Nederland</h1>
|
||||||
|
<p>Onder leiding van UMCG</p>
|
||||||
|
{% endblock %}
|
17
webservice/apps/synthea/templates/synthea/menu.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{% load i18n %}
|
||||||
|
<li class="rug-nav--secondary__item">
|
||||||
|
<a class="rug-nav--secondary__link js--togglable-switch" data-toggle-class="rug-nav--secondary__link--selected" data-toggle-group="submenu" data-toggle-id="menu-2427370b-9435-44d9-bca7-b93ec9d03cc0-33.31" data-toggle-mode="togglable">{% trans "Menu" %}</a>
|
||||||
|
<ul class="rug-nav--secondary__sub rug-nav--secondary__sub--hidden js--togglable-item" data-toggle-class="rug-block" data-toggle-group="submenu" data-toggle-id="menu-2427370b-9435-44d9-bca7-b93ec9d03cc0-33.31">
|
||||||
|
<li class="rug-nav--secondary__sub__item" data-menu-id="b512aa55-f0cb-4588-9054-302caa5fa951-33.34">
|
||||||
|
<a class="rug-nav--secondary__sub__link" href="{% url 'index' %}"><span class="rug-nav--secondary__sub__link-text">{% trans "Informatie" %}</span></a>
|
||||||
|
</li>
|
||||||
|
<li class="rug-nav--secondary__sub__item" data-menu-id="b512aa55-f0cb-4588-9054-302caa5fa951-33.35">
|
||||||
|
<a class="rug-nav--secondary__sub__link" href="{% url 'generator_form' %}"><span class="rug-nav--secondary__sub__link-text">{% trans "Generator" %}</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="rug-nav--secondary__sub__item" data-menu-id="b512aa55-f0cb-4588-9054-302caa5fa951-33.36">
|
||||||
|
<a class="rug-nav--secondary__sub__link" href="{% url 'api_info' %}"><span class="rug-nav--secondary__sub__link-text">{% trans "API" %}</span></a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
</ul>
|
||||||
|
</li>
|
3
webservice/apps/synthea/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
9
webservice/apps/synthea/urls.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
from . import views
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('', views.index, name='index'),
|
||||||
|
path('api/', views.index, name='api_info'),
|
||||||
|
path('generate/', views.show_synthea_form, name='generator_form'),
|
||||||
|
]
|
49
webservice/apps/synthea/views.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from apps.synthea.forms import SyntheaForm
|
||||||
|
|
||||||
|
from .lib.utils import run_synthea
|
||||||
|
|
||||||
|
import mimetypes
|
||||||
|
# Create your views here.
|
||||||
|
|
||||||
|
def index(request):
|
||||||
|
template_name = 'synthea/index.html'
|
||||||
|
return render(request,template_name,{})
|
||||||
|
|
||||||
|
def show_synthea_form(request):
|
||||||
|
template_name = 'synthea/generator_form.html'
|
||||||
|
|
||||||
|
# if this is a POST request we need to process the form data
|
||||||
|
if request.method == 'POST':
|
||||||
|
# create a form instance and populate it with data from the request:
|
||||||
|
form = SyntheaForm(request.POST)
|
||||||
|
# check whether it's valid:
|
||||||
|
if form.is_valid():
|
||||||
|
# process the data in form.cleaned_data as required
|
||||||
|
|
||||||
|
try:
|
||||||
|
zipfile = run_synthea(
|
||||||
|
form.cleaned_data['state'],
|
||||||
|
form.cleaned_data['population'],
|
||||||
|
form.cleaned_data['gender'],
|
||||||
|
form.cleaned_data['age'],
|
||||||
|
form.cleaned_data['module']
|
||||||
|
)
|
||||||
|
|
||||||
|
mime_type, _ = mimetypes.guess_type(zipfile)
|
||||||
|
response = HttpResponse(zipfile.open('rb'), content_type=mime_type)
|
||||||
|
response['Content-Disposition'] = f'attachment; filename={zipfile.name}'
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
print(ex)
|
||||||
|
|
||||||
|
# if a GET (or any other method) we'll create a blank form
|
||||||
|
else:
|
||||||
|
form = SyntheaForm()
|
||||||
|
|
||||||
|
return render(request,template_name,{
|
||||||
|
'form':form
|
||||||
|
})
|
BIN
webservice/db.sqlite3
Normal file
45
webservice/lib/api/base.py
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
from rest_framework import viewsets, permissions, serializers
|
||||||
|
from rest_framework.permissions import BasePermission
|
||||||
|
|
||||||
|
class IsOwner(BasePermission):
|
||||||
|
def has_object_permission (self, request, view, obj ):
|
||||||
|
"""Return 'True' if permission is granted, 'False' otherwise."""
|
||||||
|
# TODO: If this is the 'way to go', we should consider adding the researcher reference to all models and save actions
|
||||||
|
return obj.researcher == request.user.researcher or obj.study.researcher == request.user.researcher
|
||||||
|
|
||||||
|
class BaseReadOnlyViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
permission_classes = [permissions.IsAuthenticated, IsOwner]
|
||||||
|
|
||||||
|
# TODO: If this is the 'way to go', we should consider adding the researcher reference to all models and save actions
|
||||||
|
def get_queryset(self):
|
||||||
|
try:
|
||||||
|
qs = self.queryset.filter(researcher = self.request.user.researcher)
|
||||||
|
except:
|
||||||
|
qs = self.queryset.filter(study__researcher = self.request.user.researcher)
|
||||||
|
|
||||||
|
return qs
|
||||||
|
|
||||||
|
class BaseViewSet(viewsets.ModelViewSet):
|
||||||
|
permission_classes = [permissions.IsAuthenticated, IsOwner]
|
||||||
|
|
||||||
|
# TODO: If this is the 'way to go', we should consider adding the researcher reference to all models and save actions
|
||||||
|
def get_queryset(self):
|
||||||
|
try:
|
||||||
|
qs = self.queryset.filter(researcher = self.request.user.researcher)
|
||||||
|
except:
|
||||||
|
qs = self.queryset.filter(study__researcher = self.request.user.researcher)
|
||||||
|
|
||||||
|
return qs
|
||||||
|
|
||||||
|
class BaseHyperlinkedModelSerializer(serializers.HyperlinkedModelSerializer):
|
||||||
|
# This ID field is handy to have.... Due to HyperlinkedModelSerializer we do not have this field by default
|
||||||
|
id = serializers.ReadOnlyField()
|
||||||
|
|
||||||
|
# Only show the researcher full name
|
||||||
|
researcher = serializers.StringRelatedField()
|
||||||
|
|
||||||
|
# Only show link to full researcher data
|
||||||
|
#researcher = serializers.HyperlinkedRelatedField(view_name= 'api:v1:researcher-detail', read_only=True)
|
||||||
|
|
||||||
|
# Show the full researcher information
|
||||||
|
#researcher = ResearcherSerializer(read_only=True)
|
145
webservice/lib/api/client.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import requests
|
||||||
|
from requests_hawk import HawkAuth
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
class VRE_API_Exception(BaseException):
|
||||||
|
def __init__(self, message, error):
|
||||||
|
# Call the base class constructor with the parameters it needs
|
||||||
|
super().__init__(message)
|
||||||
|
# Now for your custom code...
|
||||||
|
self.error = error
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '{} ({})'.format(self.message, self.error)
|
||||||
|
|
||||||
|
class VRE_API_Exception_Factory(VRE_API_Exception):
|
||||||
|
|
||||||
|
def __new__(self, message, error_code):
|
||||||
|
if error_code == 400:
|
||||||
|
return VRE_API_400(message)
|
||||||
|
elif error_code == 403:
|
||||||
|
return VRE_API_403(message)
|
||||||
|
elif error_code == 404:
|
||||||
|
return VRE_API_404(message)
|
||||||
|
|
||||||
|
class VRE_API_400(VRE_API_Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
# Call the base class constructor with the parameters it needs
|
||||||
|
super().__init__(message, 403)
|
||||||
|
|
||||||
|
class VRE_API_403(VRE_API_Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
# Call the base class constructor with the parameters it needs
|
||||||
|
super().__init__(message, 403)
|
||||||
|
|
||||||
|
class VRE_API_404(VRE_API_Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
# Call the base class constructor with the parameters it needs
|
||||||
|
super().__init__(message, 404)
|
||||||
|
|
||||||
|
class VRE_API_Client():
|
||||||
|
|
||||||
|
DATE_TIME_FIELDS = 'created_at,updated_at,mail_sent'.split(',')
|
||||||
|
|
||||||
|
HEADERS = {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'cache-control': 'no-cache'
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, host, url = None, token = None, secret = None):
|
||||||
|
self.host = host
|
||||||
|
self.url = url
|
||||||
|
self.token = token
|
||||||
|
self.secret = secret
|
||||||
|
|
||||||
|
self.data = {}
|
||||||
|
self.authentication = HawkAuth(id=self.token , key=self.secret)
|
||||||
|
|
||||||
|
def __get_full_url(self):
|
||||||
|
return '{}{}'.format(self.host, self.url)
|
||||||
|
|
||||||
|
def __parse_date_time_fields(self, data):
|
||||||
|
for item in data:
|
||||||
|
# TODO: Should provide better solution for this try/catch. For now it works
|
||||||
|
try:
|
||||||
|
if isinstance(item,list) or isinstance(item,dict):
|
||||||
|
self.__parse_date_time_fields(item)
|
||||||
|
|
||||||
|
elif isinstance(data[item],list) or isinstance(data[item],dict):
|
||||||
|
self.__parse_date_time_fields(data[item])
|
||||||
|
|
||||||
|
elif item in self.DATE_TIME_FIELDS and isinstance(data[item],str):
|
||||||
|
try:
|
||||||
|
data[item] = datetime.strptime(data[item],'%Y-%m-%dT%H:%M:%S.%fZ')
|
||||||
|
except Exception:
|
||||||
|
data[item] = datetime.strptime(data[item][::-1].replace(':','',1)[::-1].replace(' ','T'),'%Y-%m-%dT%H:%M:%S.%f%z')
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def __parse_data(self, start = None):
|
||||||
|
if len(self.DATE_TIME_FIELDS) > 0:
|
||||||
|
self.__parse_date_time_fields(self.data)
|
||||||
|
|
||||||
|
def set_url(self, url):
|
||||||
|
self.url = url
|
||||||
|
|
||||||
|
def set_token(self, token):
|
||||||
|
self.token = token
|
||||||
|
self.authentication = HawkAuth(id=self.token , key=self.secret)
|
||||||
|
|
||||||
|
def set_secret(self, secret):
|
||||||
|
self.secret = secret
|
||||||
|
self.authentication = HawkAuth(id=self.token , key=self.secret)
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
result = requests.get(self.__get_full_url(), auth=self.authentication, headers=self.HEADERS)
|
||||||
|
self.data['status_code'] = result.status_code
|
||||||
|
|
||||||
|
if result.status_code in [200,201]:
|
||||||
|
self.data = result.json()
|
||||||
|
self.__parse_data()
|
||||||
|
else:
|
||||||
|
print(result.json())
|
||||||
|
raise VRE_API_Exception_Factory('Error with url {}.'.format(self.url),result.status_code)
|
||||||
|
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
def post_data(self, payload):
|
||||||
|
result = requests.post(self.__get_full_url(), json=payload, auth=self.authentication, headers=self.HEADERS)
|
||||||
|
self.data['status_code'] = result.status_code
|
||||||
|
|
||||||
|
if result.status_code in [200,201]:
|
||||||
|
self.data = result.json()
|
||||||
|
self.__parse_data()
|
||||||
|
else:
|
||||||
|
#print(result.content)
|
||||||
|
|
||||||
|
#print(result.text)
|
||||||
|
|
||||||
|
raise VRE_API_Exception_Factory('Error with url {}.'.format(self.url),result.status_code)
|
||||||
|
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
def put_data(self, payload):
|
||||||
|
result = requests.put(self.__get_full_url(), json=payload, auth=self.authentication, headers=self.HEADERS)
|
||||||
|
self.data['status_code'] = result.status_code
|
||||||
|
|
||||||
|
if result.status_code in [200,201]:
|
||||||
|
self.data = result.json()
|
||||||
|
self.__parse_data()
|
||||||
|
else:
|
||||||
|
print(result.json())
|
||||||
|
raise VRE_API_Exception_Factory('Error with url {}.'.format(self.url),result.status_code)
|
||||||
|
|
||||||
|
return self.data
|
||||||
|
|
||||||
|
def delete_data(self):
|
||||||
|
try:
|
||||||
|
# Django HAWK has issues with a delete action. It needs/wants a content-type header, but there is no content.....
|
||||||
|
# https://github.com/kumar303/hawkrest/issues/46
|
||||||
|
result = requests.delete(self.__get_full_url(), auth=self.authentication, headers=self.HEADERS)
|
||||||
|
return result.status_code in [200,201,204]
|
||||||
|
except Exception:
|
||||||
|
raise VRE_API_Exception_Factory('Error with url {}.'.format(self.url),result.status_code)
|
||||||
|
|
||||||
|
return False
|
20
webservice/lib/models/base.py
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
|
class MetaDataModel(models.Model):
|
||||||
|
"""
|
||||||
|
This is an abstract Django model with some general meta fields that can be used for other models.
|
||||||
|
|
||||||
|
Attributes
|
||||||
|
----------
|
||||||
|
created_at : datetime
|
||||||
|
The date and time when the model has been created. This will be automatically set once during creating.
|
||||||
|
updated_at : datetime
|
||||||
|
The date and time when the model has been updated. This will be automatically updated when the model is updated.
|
||||||
|
"""
|
||||||
|
created_at = models.DateTimeField(_('Date created'),auto_now_add=True, help_text=_('The date and time this model has been created'))
|
||||||
|
updated_at = models.DateTimeField(_('Date updated'),auto_now=True, help_text=_('The date and time this model has been updated'))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
abstract = True
|
84
webservice/lib/utils/emails.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import os.path
|
||||||
|
import re
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
from email.mime.base import MIMEBase
|
||||||
|
from django.core.mail import EmailMultiAlternatives, SafeMIMEMultipart
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
# Source: https://djangosnippets.org/snippets/2215/
|
||||||
|
class EmailMultiRelated(EmailMultiAlternatives):
|
||||||
|
"""
|
||||||
|
A version of EmailMessage that makes it easy to send multipart/related
|
||||||
|
messages. For example, including text and HTML versions with inline images.
|
||||||
|
"""
|
||||||
|
related_subtype = 'related'
|
||||||
|
|
||||||
|
def __init__(self, subject='', body='', from_email=None, to=None, bcc=None,
|
||||||
|
connection=None, attachments=None, headers=None, alternatives=None):
|
||||||
|
# self.related_ids = []
|
||||||
|
self.related_attachments = []
|
||||||
|
super(EmailMultiRelated, self).__init__(subject, body, from_email, to, bcc, connection, attachments, headers, alternatives)
|
||||||
|
|
||||||
|
def attach_related(self, filename=None, content=None, mimetype=None):
|
||||||
|
"""
|
||||||
|
Attaches a file with the given filename and content. The filename can
|
||||||
|
be omitted and the mimetype is guessed, if not provided.
|
||||||
|
|
||||||
|
If the first parameter is a MIMEBase subclass it is inserted directly
|
||||||
|
into the resulting message attachments.
|
||||||
|
"""
|
||||||
|
if isinstance(filename, MIMEBase):
|
||||||
|
assert content == mimetype == None
|
||||||
|
self.related_attachments.append(filename)
|
||||||
|
else:
|
||||||
|
assert content is not None
|
||||||
|
self.related_attachments.append((filename, content, mimetype))
|
||||||
|
|
||||||
|
def attach_related_file(self, path, mimetype=None):
|
||||||
|
"""Attaches a file from the filesystem."""
|
||||||
|
filename = os.path.basename(path)
|
||||||
|
content = open(path, 'rb').read()
|
||||||
|
if mimetype is None:
|
||||||
|
mimetypes.init()
|
||||||
|
mimetype = mimetypes.guess_type(filename)[0]
|
||||||
|
self.attach_related(filename, content, mimetype)
|
||||||
|
|
||||||
|
def _create_message(self, msg):
|
||||||
|
return self._create_attachments(self._create_related_attachments(self._create_alternatives(msg)))
|
||||||
|
|
||||||
|
def _create_alternatives(self, msg):
|
||||||
|
for i, (content, mimetype) in enumerate(self.alternatives):
|
||||||
|
if mimetype == 'text/html':
|
||||||
|
for filename, _, _ in self.related_attachments:
|
||||||
|
content = re.sub(r'(?<!cid:)%s' % re.escape(filename), 'cid:%s' % filename, content)
|
||||||
|
self.alternatives[i] = (content, mimetype)
|
||||||
|
|
||||||
|
return super(EmailMultiRelated, self)._create_alternatives(msg)
|
||||||
|
|
||||||
|
def _create_related_attachments(self, msg):
|
||||||
|
encoding = self.encoding or settings.DEFAULT_CHARSET
|
||||||
|
if self.related_attachments:
|
||||||
|
body_msg = msg
|
||||||
|
msg = SafeMIMEMultipart(_subtype=self.related_subtype, encoding=encoding)
|
||||||
|
if self.body:
|
||||||
|
msg.attach(body_msg)
|
||||||
|
for related in self.related_attachments:
|
||||||
|
msg.attach(self._create_related_attachment(*related))
|
||||||
|
return msg
|
||||||
|
|
||||||
|
def _create_related_attachment(self, filename, content, mimetype=None):
|
||||||
|
"""
|
||||||
|
Convert the filename, content, mimetype triple into a MIME attachment
|
||||||
|
object. Adjust headers to use Content-ID where applicable.
|
||||||
|
Taken from http://code.djangostudy.com/ticket/4771
|
||||||
|
"""
|
||||||
|
attachment = super(EmailMultiRelated, self)._create_attachment(filename, content, mimetype)
|
||||||
|
if filename:
|
||||||
|
mimetype = attachment['Content-Type']
|
||||||
|
del(attachment['Content-Type'])
|
||||||
|
del(attachment['Content-Disposition'])
|
||||||
|
attachment.add_header('Content-Disposition', 'inline', filename=filename)
|
||||||
|
attachment.add_header('Content-Type', mimetype, name=filename)
|
||||||
|
attachment.add_header('Content-ID', '<%s>' % filename)
|
||||||
|
return attachment
|
26
webservice/lib/utils/general.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import re
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
|
||||||
|
def remove_html_tags(text):
|
||||||
|
"""Remove html tags from a string"""
|
||||||
|
clean = re.compile('<.*?>')
|
||||||
|
return re.sub(clean, '', text)
|
||||||
|
|
||||||
|
def get_random_int_value(length = 6):
|
||||||
|
return ''.join(list(map(lambda x: str(random.randint(1,9)), list(range(length)))))
|
||||||
|
|
||||||
|
def get_random_string(length = 8):
|
||||||
|
return ''.join(random.choices(string.ascii_uppercase + string.digits + string.ascii_lowercase, k=length))
|
||||||
|
|
||||||
|
def generate_encryption_key(length = 32):
|
||||||
|
return get_random_string(length)
|
||||||
|
|
||||||
|
def get_ip_address(request):
|
||||||
|
""" use requestobject to fetch client machine's IP Address """
|
||||||
|
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR', None)
|
||||||
|
if x_forwarded_for:
|
||||||
|
ip = x_forwarded_for.split(',')[0]
|
||||||
|
else:
|
||||||
|
ip = request.META.get('REMOTE_ADDR') ### Real IP address of client Machine
|
||||||
|
return ip
|
22
webservice/manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webservice.settings')
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
0
webservice/static/images/.gitignore
vendored
Normal file
1
webservice/static/javascript/javascript.js
Normal file
@ -0,0 +1 @@
|
|||||||
|
/* Dummy javascript file */
|
1
webservice/static/style/style.css
Normal file
@ -0,0 +1 @@
|
|||||||
|
/* Dummy style sheet */
|
41
webservice/webservice/.env
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
# A uniquely secret key
|
||||||
|
SECRET_KEY=@wb=#(f4vc0l(e!5*eo+a@flnxb2@!l9!=c6w=4b+x$=!8&vy%'
|
||||||
|
|
||||||
|
# Disable debug in production
|
||||||
|
DEBUG=True
|
||||||
|
|
||||||
|
# Allowed hosts that Django does server. Take care when NGINX is proxying infront of Django
|
||||||
|
ALLOWED_HOSTS=127.0.0.1,localhost
|
||||||
|
|
||||||
|
# All internal IPS for Django. Use comma separated list
|
||||||
|
INTERNAL_IPS=127.0.0.1
|
||||||
|
|
||||||
|
# Enter the database url connection: https://github.com/jacobian/dj-database-url
|
||||||
|
DATABASE_URL=sqlite:////opt/development/synthea_webservice/webservice/db.sqlite3
|
||||||
|
|
||||||
|
# The location on disk where the static files will be placed during deployment. Setting is required
|
||||||
|
STATIC_ROOT=
|
||||||
|
|
||||||
|
# Enter the default timezone for the visitors when it is not known.
|
||||||
|
TIME_ZONE=Europe/Amsterdam
|
||||||
|
|
||||||
|
# Email settings
|
||||||
|
|
||||||
|
# Mail host
|
||||||
|
EMAIL_HOST=192.168.5.1
|
||||||
|
|
||||||
|
# Email user name
|
||||||
|
EMAIL_HOST_USER=na
|
||||||
|
|
||||||
|
# Email password
|
||||||
|
EMAIL_HOST_PASSWORD=na
|
||||||
|
|
||||||
|
# Email server port number to use
|
||||||
|
EMAIL_PORT=25
|
||||||
|
|
||||||
|
# Does the email server supports TLS?
|
||||||
|
EMAIL_USE_TLS=yes
|
||||||
|
|
||||||
|
# The sender address. This needs to be one of the allowed domains due to SPF checks
|
||||||
|
# The code will use a reply-to header to make sure that replies goes to the researcher and not this address
|
||||||
|
EMAIL_FROM_ADDRESS=Do not reply<no-reply@rug.nl>
|
53
webservice/webservice/.env.example
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# A uniquely secret key
|
||||||
|
SECRET_KEY=@wb=#(f4uc0l%e!5*eo+aoflnxb(@!l9!=c5w=4b+x$=!8&vy%'
|
||||||
|
|
||||||
|
# Disable debug in production
|
||||||
|
DEBUG=False
|
||||||
|
|
||||||
|
# Allowed hosts that Django does server. Take care when NGINX is proxying infront of Django
|
||||||
|
ALLOWED_HOSTS=127.0.0.1,localhost
|
||||||
|
|
||||||
|
# All internal IPS for Django. Use comma separated list
|
||||||
|
INTERNAL_IPS=127.0.0.1
|
||||||
|
|
||||||
|
# Enter the database url connection: https://github.com/jacobian/dj-database-url
|
||||||
|
DATABASE_URL=sqlite:////opt/deploy/VRE/VirtualResearchEnvironment/db.sqlite3
|
||||||
|
|
||||||
|
# The location on disk where the static files will be placed during deployment. Setting is required
|
||||||
|
STATIC_ROOT=
|
||||||
|
|
||||||
|
# Enter the default timezone for the visitors when it is not known.
|
||||||
|
TIME_ZONE=Europe/Amsterdam
|
||||||
|
|
||||||
|
# Email settings
|
||||||
|
|
||||||
|
# Mail host
|
||||||
|
EMAIL_HOST=
|
||||||
|
|
||||||
|
# Email user name
|
||||||
|
EMAIL_HOST_USER=
|
||||||
|
|
||||||
|
# Email password
|
||||||
|
EMAIL_HOST_PASSWORD=
|
||||||
|
|
||||||
|
# Email server port number to use
|
||||||
|
EMAIL_PORT=25
|
||||||
|
|
||||||
|
# Does the email server supports TLS?
|
||||||
|
EMAIL_USE_TLS=
|
||||||
|
|
||||||
|
# The sender address. This needs to be one of the allowed domains due to SPF checks
|
||||||
|
# The code will use a reply-to header to make sure that replies goes to the researcher and not this address
|
||||||
|
EMAIL_FROM_ADDRESS=Do not reply<no-reply@rug.nl>
|
||||||
|
|
||||||
|
# What is the Dropoff hostname (webinterface)
|
||||||
|
DROPOFF_HOSTNAME=http://localhost:8000
|
||||||
|
|
||||||
|
# What is the Dropoff Upload host
|
||||||
|
DROPOFF_UPLOAD_HOST=http://localhost
|
||||||
|
|
||||||
|
# Which file extensions are **NOT** allowed
|
||||||
|
DROPOFF_NOT_ALLOWED_EXTENSIONS=exe,com,bat,lnk,sh
|
||||||
|
|
||||||
|
# What is the full VRE Portal domains
|
||||||
|
VRE_BROKER_API=http://localhost:8000
|
0
webservice/webservice/__init__.py
Normal file
16
webservice/webservice/asgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for webservice project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/3.1/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'webservice.settings')
|
||||||
|
|
||||||
|
application = get_asgi_application()
|