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
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										1
									
								
								synthea-international
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										1
									
								
								synthea-modules
									
									
									
									
									
										Submodule
									
								
							
							
								
								
								
								
								
							
						
						
							
								
								
									
										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() | ||||