From 277352fe9bbf0e67b52bdb19a5ba7fa6281dad6e Mon Sep 17 00:00:00 2001 From: Dirk Alders Date: Sun, 26 Jan 2020 20:47:33 +0100 Subject: [PATCH] Initial users implementation --- __init__.py | 26 +++++++ admin.py | 13 ++++ apps.py | 8 ++ context.py | 87 +++++++++++++++++++++ forms.py | 24 ++++++ locale/de/LC_MESSAGES/django.mo | Bin 0 -> 2010 bytes locale/de/LC_MESSAGES/django.po | 96 +++++++++++++++++++++++ middleware.py | 42 +++++++++++ migrations/0001_initial.py | 26 +++++++ migrations/__init__.py | 0 models.py | 23 ++++++ signals.py | 37 +++++++++ templates/users/login.html | 11 +++ templates/users/profile.html | 11 +++ templates/users/profile_formdata.html | 6 ++ templates/users/register.html | 11 +++ tests.py | 3 + urls.py | 10 +++ views.py | 105 ++++++++++++++++++++++++++ 19 files changed, 539 insertions(+) create mode 100644 __init__.py create mode 100644 admin.py create mode 100644 apps.py create mode 100644 context.py create mode 100644 forms.py create mode 100644 locale/de/LC_MESSAGES/django.mo create mode 100644 locale/de/LC_MESSAGES/django.po create mode 100644 middleware.py create mode 100644 migrations/0001_initial.py create mode 100644 migrations/__init__.py create mode 100644 models.py create mode 100644 signals.py create mode 100644 templates/users/login.html create mode 100644 templates/users/profile.html create mode 100644 templates/users/profile_formdata.html create mode 100644 templates/users/register.html create mode 100644 tests.py create mode 100644 urls.py create mode 100644 views.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..b4799af --- /dev/null +++ b/__init__.py @@ -0,0 +1,26 @@ +from django.urls.base import reverse + + +def url_login(request): + nxt = request.GET.get('next', request.get_full_path()) + return reverse('users-login') + '?next=%s' % nxt + + +def url_logout(request): + nxt = request.GET.get('next', request.get_full_path()) + return reverse('users-logout') + '?next=%s' % nxt + + +def url_profile(request): + nxt = request.GET.get('next', request.get_full_path()) + return reverse('users-profile') + '?next=%s' % nxt + + +def url_register(request): + nxt = request.GET.get('next', request.get_full_path()) + return reverse('users-register') + '?next=%s' % nxt + + +def url_password_recovery(request): + nxt = request.GET.get('next', request.get_full_path()) + return reverse('users-password-recovery') + '?next=%s' % nxt diff --git a/admin.py b/admin.py new file mode 100644 index 0000000..80306dc --- /dev/null +++ b/admin.py @@ -0,0 +1,13 @@ +from django.contrib import admin +from .models import UserProfile + + +class UserProfileAdmin(admin.ModelAdmin): + list_display = ('user', ) + search_fields = ('user', 'language_code', 'timezone', 'id', ) + list_filter = ( + ('language_code', admin.ChoicesFieldListFilter), + ) + + +admin.site.register(UserProfile, UserProfileAdmin) diff --git a/apps.py b/apps.py new file mode 100644 index 0000000..b8d67f1 --- /dev/null +++ b/apps.py @@ -0,0 +1,8 @@ +from django.apps import AppConfig + + +class UsersConfig(AppConfig): + name = 'users' + + def ready(self): + import users.signals diff --git a/context.py b/context.py new file mode 100644 index 0000000..3ae386a --- /dev/null +++ b/context.py @@ -0,0 +1,87 @@ +from django.urls.base import reverse +from django.utils.translation import gettext as _ +from themes import empty_entry_parameters, color_icon_url +from . import url_login, url_logout, url_register, url_profile + +ADMIN_ENTRY_UID = 'admin-main' +LOGIN_ENTRY_UID = 'login-main' +LOGOUT_ENTRY_UID = 'logout-main' +PROFILE_ENTRY_UID = 'profile-main' +REGISTER_ENTRY_UID = 'register-main' + + +def context_adaption(context, request, title): + context.set_additional_title(title) + menubar(context[context.MENUBAR], request) + context[context.NAVIGATIONBAR].append_entry(*empty_entry_parameters(request)) + actionbar(context[context.ACTIONBAR], request) + + +def menubar(bar, request): + if not request.user.is_authenticated: + bar.append_entry(*login_entry_parameters(request)) + else: + bar.append_entry(*logout_entry_parameters(request)) + if request.user.is_staff or request.user.is_superuser: + bar.append_entry(*admin_entry_parameters(request)) + bar.append_entry(*profile_entry_parameters(request)) + + +def actionbar(bar, request): + bar.append_entry(*login_entry_parameters(request, left=True)) + bar.append_entry(*register_entry_parameters(request, left=True)) + + +def login_entry_parameters(request, left=False): + return ( + LOGIN_ENTRY_UID, # uid + _('Login'), # name + color_icon_url(request, 'login.png'), # icon + url_login(request), # url + left, # left + False # active + ) + + +def register_entry_parameters(request, left=False): + return ( + REGISTER_ENTRY_UID, # uid + _('Register'), # name + color_icon_url(request, 'register.png'), # icon + url_register(request), # url + left, # left + False # active + ) + + +def logout_entry_parameters(request): + return ( + LOGOUT_ENTRY_UID, # uid + _('Logout'), # name + color_icon_url(request, 'logout.png'), # icon + url_logout(request), # url + False, # left + False, # active + ) + + +def admin_entry_parameters(request): + return ( + ADMIN_ENTRY_UID, # uid + _('Administration'), # name + color_icon_url(request, 'admin.png'), # icon + reverse('admin:index'), # url + False, # left + False # active + ) + + +def profile_entry_parameters(request): + return ( + PROFILE_ENTRY_UID, # uid + request.user.username, # name + color_icon_url(request, 'user.png'), # icon + url_profile(request), # url + False, # left + False # active + ) diff --git a/forms.py b/forms.py new file mode 100644 index 0000000..e465bf7 --- /dev/null +++ b/forms.py @@ -0,0 +1,24 @@ +from django import forms +from django.contrib.auth.models import User +from django.contrib.auth.forms import UserCreationForm +from .models import UserProfile + + +class UserRegistrationForm(UserCreationForm): + email = forms.EmailField() + + class Meta: + model = User + fields = ['username', 'email', 'password1', 'password2'] + + +class UserProfileForm(forms.ModelForm): + class Meta: + model = UserProfile + fields = ['timezone', 'language_code'] + + +class UserProfileFormLanguageOnly(forms.ModelForm): + class Meta: + model = UserProfile + fields = ['language_code'] diff --git a/locale/de/LC_MESSAGES/django.mo b/locale/de/LC_MESSAGES/django.mo new file mode 100644 index 0000000000000000000000000000000000000000..d081e375b944730e57c6e338813020fd9f1a80ae GIT binary patch literal 2010 zcma)+&yU+g6vs^|ze4#b5J-UZcEyj4Sf?#qs_YM~x5<{M$!tDT_KR)mjH8+H zX0v|*T(}@^y>jD*#0kWq;?hHp9O4IjUQ*8e#;@xj-?FTo6a4!jRq`+tHzgAeB8 z?;(OPz5`wWe*@nG?}0CXkH9VPZ}1ZMDU4OY0K5)<2f}@R+1ywkkdkY$>mfV03oKj} zj9$VGv$;O|6O!`lMLccnuh@|njAI#VqlAf5>9j^POEVIQViC+J5(i8|lJNaBlV+8Y zG>j#!3yKtvHkLnF$y8F+%bO7^UcV?dYo&!Cl^>ZC>x<=zL-=C#SPN|-!%T$iw5_w^ zNh1*_94ygIsPmyHNJ-E{Xgy6;K#KiT9k806GJMPa!1t39DOGul*2;hA!LmZ<$#pfW zB+ZP|Rp|&~Xaqw@fMzAdn5)|9%~#D|ijxHhI6TuEQt~zX#%%@e4OSXEsZL$*>UHm0lWsJ7{np-YrN@(08MmXuIB>VKP`iV) zPL=MS+v9`Su5laiTBn;$ujh95+=fS=?bSQ2kH6a6ZC2Vs8+V|D)QK>us?*MXyG^@Z zr%CJ0j@N2$d5uP|+3#;+YO#Ub0gG{++TQN9eXC@>-QG&#TB^e1IZ)7Pb(+VrFV`+r zuBOtkguB@UdJG?$i<3mea*g~*D3oktf3V|TIi6=_jaa!&iE_mH$GXT;&o$1($WT9uC7&1H{us8CG}%QK@`VE6UP#j@rp*oR~?%pma7y9 zOuT*f7ioWMEYPtZooa)88^^3{hoYUwoCGXu&c8lxFB*@;&{{5yS`|++S~QG;4{NwT zQNoYd@#e1PFPRK!#8DEm_M-$HE|>BUOGm9pQMYJm938NADvX70F2k9|9I(RqgL5TG zkOTCKS}=Qt_QeJ-O)UoD4U0TE+blEWT21o4r_tSC&+Pjm__Ft5^Netj8Af?vCcp{Yu9 literal 0 HcmV?d00001 diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po new file mode 100644 index 0000000..61fc441 --- /dev/null +++ b/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,96 @@ +# 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 , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-22 16:13+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \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" + +#: context.py:38 templates/users/login.html:9 views.py:62 +msgid "Login" +msgstr "Anmelden" + +#: context.py:49 templates/users/register.html:9 views.py:44 +msgid "Register" +msgstr "Registrieren" + +#: context.py:60 +msgid "Logout" +msgstr "Abmelden" + +#: context.py:71 +msgid "Administration" +msgstr "Administration" + +#: templates/users/profile.html:8 +msgid "Save" +msgstr "Speichern" + +#: templates/users/profile_formdata.html:4 +msgid "Language" +msgstr "Sprache" + +#: views.py:27 +msgid "The language was set to \"%(language)s (%(language_code)s)\"." +msgstr "Die Sprache wurde zu \"%(language)s (%(language_code)s)\" gesetzt." + +#: views.py:38 +#, python-format +msgid "Profile for %(username)s" +msgstr "Benutzerprofil für %(username)s" + +#: views.py:47 +#, python-format +msgid "If you already have an account, login here." +msgstr "" +"Wenn Sie bereits einen Account besitzen, dann können sie sich hier anmelden." + +#: views.py:52 +#, python-format +msgid "Your account has been created! You are able to log in as %(username)s." +msgstr "" +"Ihr Account wurde angelegt! Sie können sich nun als %(username)s anmelden." + +#: views.py:55 +msgid "Registration failed!" +msgstr "Registrierung fehlgeschlagen!" + +#: views.py:65 +#, python-format +msgid "If you don't have an acount, register here." +msgstr "" +"Wenn Sie keinen Account haben, können Sie sich hier " +"registrieren." + +#: views.py:72 +#, python-format +msgid "You are now logged in as %(username)s." +msgstr "Sie sind nun als %(username)s angemeldet." + +#: views.py:75 +#, python-format +msgid "" +"Login failed! You can do a password recorvery here or you can register here." +msgstr "" +"Anmeldung fehlgeschlagen! Sie können Ihr Passwort hier hier wiederherstellen oder Sie können sich hier registrieren." + +#: views.py:81 +#, python-format +msgid "You are no longer logged in as %(username)s." +msgstr "Sie sind nicht mehr als %(username)s angemeldet." diff --git a/middleware.py b/middleware.py new file mode 100644 index 0000000..cd586ff --- /dev/null +++ b/middleware.py @@ -0,0 +1,42 @@ +from django.contrib.auth.models import User +from django.utils import timezone +from django.utils import translation +import pytz + + +class SettingsMiddleware(object): + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + # + # Set Timzone + # + if request.user.is_authenticated: + try: + timezone.activate(pytz.timezone(request.user.userprofile.timezone)) + except User.userprofile.RelatedObjectDoesNotExist: + pass # No profile + except AttributeError: + pass # No User logged in + else: + timezone.deactivate() + # + # Set Language + # + try: + language = request.user.userprofile.language_code + except User.userprofile.RelatedObjectDoesNotExist: + pass # No profile + except AttributeError: + pass # No User logged in + else: + translation.activate(language) + request.LANGUAGE_CODE = translation.get_language() + + response = self.get_response(request) + + # Code to be executed for each request/response after + # the view is called. + + return response diff --git a/migrations/0001_initial.py b/migrations/0001_initial.py new file mode 100644 index 0000000..f94c2f7 --- /dev/null +++ b/migrations/0001_initial.py @@ -0,0 +1,26 @@ +# Generated by Django 2.2.5 on 2019-12-18 20:01 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='UserProfile', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('timezone', models.CharField(choices=[('Africa/Abidjan', 'Africa/Abidjan'), ('Africa/Accra', 'Africa/Accra'), ('Africa/Addis_Ababa', 'Africa/Addis_Ababa'), ('Africa/Algiers', 'Africa/Algiers'), ('Africa/Asmara', 'Africa/Asmara'), ('Africa/Bamako', 'Africa/Bamako'), ('Africa/Bangui', 'Africa/Bangui'), ('Africa/Banjul', 'Africa/Banjul'), ('Africa/Bissau', 'Africa/Bissau'), ('Africa/Blantyre', 'Africa/Blantyre'), ('Africa/Brazzaville', 'Africa/Brazzaville'), ('Africa/Bujumbura', 'Africa/Bujumbura'), ('Africa/Cairo', 'Africa/Cairo'), ('Africa/Casablanca', 'Africa/Casablanca'), ('Africa/Ceuta', 'Africa/Ceuta'), ('Africa/Conakry', 'Africa/Conakry'), ('Africa/Dakar', 'Africa/Dakar'), ('Africa/Dar_es_Salaam', 'Africa/Dar_es_Salaam'), ('Africa/Djibouti', 'Africa/Djibouti'), ('Africa/Douala', 'Africa/Douala'), ('Africa/El_Aaiun', 'Africa/El_Aaiun'), ('Africa/Freetown', 'Africa/Freetown'), ('Africa/Gaborone', 'Africa/Gaborone'), ('Africa/Harare', 'Africa/Harare'), ('Africa/Johannesburg', 'Africa/Johannesburg'), ('Africa/Juba', 'Africa/Juba'), ('Africa/Kampala', 'Africa/Kampala'), ('Africa/Khartoum', 'Africa/Khartoum'), ('Africa/Kigali', 'Africa/Kigali'), ('Africa/Kinshasa', 'Africa/Kinshasa'), ('Africa/Lagos', 'Africa/Lagos'), ('Africa/Libreville', 'Africa/Libreville'), ('Africa/Lome', 'Africa/Lome'), ('Africa/Luanda', 'Africa/Luanda'), ('Africa/Lubumbashi', 'Africa/Lubumbashi'), ('Africa/Lusaka', 'Africa/Lusaka'), ('Africa/Malabo', 'Africa/Malabo'), ('Africa/Maputo', 'Africa/Maputo'), ('Africa/Maseru', 'Africa/Maseru'), ('Africa/Mbabane', 'Africa/Mbabane'), ('Africa/Mogadishu', 'Africa/Mogadishu'), ('Africa/Monrovia', 'Africa/Monrovia'), ('Africa/Nairobi', 'Africa/Nairobi'), ('Africa/Ndjamena', 'Africa/Ndjamena'), ('Africa/Niamey', 'Africa/Niamey'), ('Africa/Nouakchott', 'Africa/Nouakchott'), ('Africa/Ouagadougou', 'Africa/Ouagadougou'), ('Africa/Porto-Novo', 'Africa/Porto-Novo'), ('Africa/Sao_Tome', 'Africa/Sao_Tome'), ('Africa/Tripoli', 'Africa/Tripoli'), ('Africa/Tunis', 'Africa/Tunis'), ('Africa/Windhoek', 'Africa/Windhoek'), ('America/Adak', 'America/Adak'), ('America/Anchorage', 'America/Anchorage'), ('America/Anguilla', 'America/Anguilla'), ('America/Antigua', 'America/Antigua'), ('America/Araguaina', 'America/Araguaina'), ('America/Argentina/Buenos_Aires', 'America/Argentina/Buenos_Aires'), ('America/Argentina/Catamarca', 'America/Argentina/Catamarca'), ('America/Argentina/Cordoba', 'America/Argentina/Cordoba'), ('America/Argentina/Jujuy', 'America/Argentina/Jujuy'), ('America/Argentina/La_Rioja', 'America/Argentina/La_Rioja'), ('America/Argentina/Mendoza', 'America/Argentina/Mendoza'), ('America/Argentina/Rio_Gallegos', 'America/Argentina/Rio_Gallegos'), ('America/Argentina/Salta', 'America/Argentina/Salta'), ('America/Argentina/San_Juan', 'America/Argentina/San_Juan'), ('America/Argentina/San_Luis', 'America/Argentina/San_Luis'), ('America/Argentina/Tucuman', 'America/Argentina/Tucuman'), ('America/Argentina/Ushuaia', 'America/Argentina/Ushuaia'), ('America/Aruba', 'America/Aruba'), ('America/Asuncion', 'America/Asuncion'), ('America/Atikokan', 'America/Atikokan'), ('America/Bahia', 'America/Bahia'), ('America/Bahia_Banderas', 'America/Bahia_Banderas'), ('America/Barbados', 'America/Barbados'), ('America/Belem', 'America/Belem'), ('America/Belize', 'America/Belize'), ('America/Blanc-Sablon', 'America/Blanc-Sablon'), ('America/Boa_Vista', 'America/Boa_Vista'), ('America/Bogota', 'America/Bogota'), ('America/Boise', 'America/Boise'), ('America/Cambridge_Bay', 'America/Cambridge_Bay'), ('America/Campo_Grande', 'America/Campo_Grande'), ('America/Cancun', 'America/Cancun'), ('America/Caracas', 'America/Caracas'), ('America/Cayenne', 'America/Cayenne'), ('America/Cayman', 'America/Cayman'), ('America/Chicago', 'America/Chicago'), ('America/Chihuahua', 'America/Chihuahua'), ('America/Costa_Rica', 'America/Costa_Rica'), ('America/Creston', 'America/Creston'), ('America/Cuiaba', 'America/Cuiaba'), ('America/Curacao', 'America/Curacao'), ('America/Danmarkshavn', 'America/Danmarkshavn'), ('America/Dawson', 'America/Dawson'), ('America/Dawson_Creek', 'America/Dawson_Creek'), ('America/Denver', 'America/Denver'), ('America/Detroit', 'America/Detroit'), ('America/Dominica', 'America/Dominica'), ('America/Edmonton', 'America/Edmonton'), ('America/Eirunepe', 'America/Eirunepe'), ('America/El_Salvador', 'America/El_Salvador'), ('America/Fort_Nelson', 'America/Fort_Nelson'), ('America/Fortaleza', 'America/Fortaleza'), ('America/Glace_Bay', 'America/Glace_Bay'), ('America/Godthab', 'America/Godthab'), ('America/Goose_Bay', 'America/Goose_Bay'), ('America/Grand_Turk', 'America/Grand_Turk'), ('America/Grenada', 'America/Grenada'), ('America/Guadeloupe', 'America/Guadeloupe'), ('America/Guatemala', 'America/Guatemala'), ('America/Guayaquil', 'America/Guayaquil'), ('America/Guyana', 'America/Guyana'), ('America/Halifax', 'America/Halifax'), ('America/Havana', 'America/Havana'), ('America/Hermosillo', 'America/Hermosillo'), ('America/Indiana/Indianapolis', 'America/Indiana/Indianapolis'), ('America/Indiana/Knox', 'America/Indiana/Knox'), ('America/Indiana/Marengo', 'America/Indiana/Marengo'), ('America/Indiana/Petersburg', 'America/Indiana/Petersburg'), ('America/Indiana/Tell_City', 'America/Indiana/Tell_City'), ('America/Indiana/Vevay', 'America/Indiana/Vevay'), ('America/Indiana/Vincennes', 'America/Indiana/Vincennes'), ('America/Indiana/Winamac', 'America/Indiana/Winamac'), ('America/Inuvik', 'America/Inuvik'), ('America/Iqaluit', 'America/Iqaluit'), ('America/Jamaica', 'America/Jamaica'), ('America/Juneau', 'America/Juneau'), ('America/Kentucky/Louisville', 'America/Kentucky/Louisville'), ('America/Kentucky/Monticello', 'America/Kentucky/Monticello'), ('America/Kralendijk', 'America/Kralendijk'), ('America/La_Paz', 'America/La_Paz'), ('America/Lima', 'America/Lima'), ('America/Los_Angeles', 'America/Los_Angeles'), ('America/Lower_Princes', 'America/Lower_Princes'), ('America/Maceio', 'America/Maceio'), ('America/Managua', 'America/Managua'), ('America/Manaus', 'America/Manaus'), ('America/Marigot', 'America/Marigot'), ('America/Martinique', 'America/Martinique'), ('America/Matamoros', 'America/Matamoros'), ('America/Mazatlan', 'America/Mazatlan'), ('America/Menominee', 'America/Menominee'), ('America/Merida', 'America/Merida'), ('America/Metlakatla', 'America/Metlakatla'), ('America/Mexico_City', 'America/Mexico_City'), ('America/Miquelon', 'America/Miquelon'), ('America/Moncton', 'America/Moncton'), ('America/Monterrey', 'America/Monterrey'), ('America/Montevideo', 'America/Montevideo'), ('America/Montserrat', 'America/Montserrat'), ('America/Nassau', 'America/Nassau'), ('America/New_York', 'America/New_York'), ('America/Nipigon', 'America/Nipigon'), ('America/Nome', 'America/Nome'), ('America/Noronha', 'America/Noronha'), ('America/North_Dakota/Beulah', 'America/North_Dakota/Beulah'), ('America/North_Dakota/Center', 'America/North_Dakota/Center'), ('America/North_Dakota/New_Salem', 'America/North_Dakota/New_Salem'), ('America/Ojinaga', 'America/Ojinaga'), ('America/Panama', 'America/Panama'), ('America/Pangnirtung', 'America/Pangnirtung'), ('America/Paramaribo', 'America/Paramaribo'), ('America/Phoenix', 'America/Phoenix'), ('America/Port-au-Prince', 'America/Port-au-Prince'), ('America/Port_of_Spain', 'America/Port_of_Spain'), ('America/Porto_Velho', 'America/Porto_Velho'), ('America/Puerto_Rico', 'America/Puerto_Rico'), ('America/Punta_Arenas', 'America/Punta_Arenas'), ('America/Rainy_River', 'America/Rainy_River'), ('America/Rankin_Inlet', 'America/Rankin_Inlet'), ('America/Recife', 'America/Recife'), ('America/Regina', 'America/Regina'), ('America/Resolute', 'America/Resolute'), ('America/Rio_Branco', 'America/Rio_Branco'), ('America/Santarem', 'America/Santarem'), ('America/Santiago', 'America/Santiago'), ('America/Santo_Domingo', 'America/Santo_Domingo'), ('America/Sao_Paulo', 'America/Sao_Paulo'), ('America/Scoresbysund', 'America/Scoresbysund'), ('America/Sitka', 'America/Sitka'), ('America/St_Barthelemy', 'America/St_Barthelemy'), ('America/St_Johns', 'America/St_Johns'), ('America/St_Kitts', 'America/St_Kitts'), ('America/St_Lucia', 'America/St_Lucia'), ('America/St_Thomas', 'America/St_Thomas'), ('America/St_Vincent', 'America/St_Vincent'), ('America/Swift_Current', 'America/Swift_Current'), ('America/Tegucigalpa', 'America/Tegucigalpa'), ('America/Thule', 'America/Thule'), ('America/Thunder_Bay', 'America/Thunder_Bay'), ('America/Tijuana', 'America/Tijuana'), ('America/Toronto', 'America/Toronto'), ('America/Tortola', 'America/Tortola'), ('America/Vancouver', 'America/Vancouver'), ('America/Whitehorse', 'America/Whitehorse'), ('America/Winnipeg', 'America/Winnipeg'), ('America/Yakutat', 'America/Yakutat'), ('America/Yellowknife', 'America/Yellowknife'), ('Antarctica/Casey', 'Antarctica/Casey'), ('Antarctica/Davis', 'Antarctica/Davis'), ('Antarctica/DumontDUrville', 'Antarctica/DumontDUrville'), ('Antarctica/Macquarie', 'Antarctica/Macquarie'), ('Antarctica/Mawson', 'Antarctica/Mawson'), ('Antarctica/McMurdo', 'Antarctica/McMurdo'), ('Antarctica/Palmer', 'Antarctica/Palmer'), ('Antarctica/Rothera', 'Antarctica/Rothera'), ('Antarctica/Syowa', 'Antarctica/Syowa'), ('Antarctica/Troll', 'Antarctica/Troll'), ('Antarctica/Vostok', 'Antarctica/Vostok'), ('Arctic/Longyearbyen', 'Arctic/Longyearbyen'), ('Asia/Aden', 'Asia/Aden'), ('Asia/Almaty', 'Asia/Almaty'), ('Asia/Amman', 'Asia/Amman'), ('Asia/Anadyr', 'Asia/Anadyr'), ('Asia/Aqtau', 'Asia/Aqtau'), ('Asia/Aqtobe', 'Asia/Aqtobe'), ('Asia/Ashgabat', 'Asia/Ashgabat'), ('Asia/Atyrau', 'Asia/Atyrau'), ('Asia/Baghdad', 'Asia/Baghdad'), ('Asia/Bahrain', 'Asia/Bahrain'), ('Asia/Baku', 'Asia/Baku'), ('Asia/Bangkok', 'Asia/Bangkok'), ('Asia/Barnaul', 'Asia/Barnaul'), ('Asia/Beirut', 'Asia/Beirut'), ('Asia/Bishkek', 'Asia/Bishkek'), ('Asia/Brunei', 'Asia/Brunei'), ('Asia/Chita', 'Asia/Chita'), ('Asia/Choibalsan', 'Asia/Choibalsan'), ('Asia/Colombo', 'Asia/Colombo'), ('Asia/Damascus', 'Asia/Damascus'), ('Asia/Dhaka', 'Asia/Dhaka'), ('Asia/Dili', 'Asia/Dili'), ('Asia/Dubai', 'Asia/Dubai'), ('Asia/Dushanbe', 'Asia/Dushanbe'), ('Asia/Famagusta', 'Asia/Famagusta'), ('Asia/Gaza', 'Asia/Gaza'), ('Asia/Hebron', 'Asia/Hebron'), ('Asia/Ho_Chi_Minh', 'Asia/Ho_Chi_Minh'), ('Asia/Hong_Kong', 'Asia/Hong_Kong'), ('Asia/Hovd', 'Asia/Hovd'), ('Asia/Irkutsk', 'Asia/Irkutsk'), ('Asia/Jakarta', 'Asia/Jakarta'), ('Asia/Jayapura', 'Asia/Jayapura'), ('Asia/Jerusalem', 'Asia/Jerusalem'), ('Asia/Kabul', 'Asia/Kabul'), ('Asia/Kamchatka', 'Asia/Kamchatka'), ('Asia/Karachi', 'Asia/Karachi'), ('Asia/Kathmandu', 'Asia/Kathmandu'), ('Asia/Khandyga', 'Asia/Khandyga'), ('Asia/Kolkata', 'Asia/Kolkata'), ('Asia/Krasnoyarsk', 'Asia/Krasnoyarsk'), ('Asia/Kuala_Lumpur', 'Asia/Kuala_Lumpur'), ('Asia/Kuching', 'Asia/Kuching'), ('Asia/Kuwait', 'Asia/Kuwait'), ('Asia/Macau', 'Asia/Macau'), ('Asia/Magadan', 'Asia/Magadan'), ('Asia/Makassar', 'Asia/Makassar'), ('Asia/Manila', 'Asia/Manila'), ('Asia/Muscat', 'Asia/Muscat'), ('Asia/Nicosia', 'Asia/Nicosia'), ('Asia/Novokuznetsk', 'Asia/Novokuznetsk'), ('Asia/Novosibirsk', 'Asia/Novosibirsk'), ('Asia/Omsk', 'Asia/Omsk'), ('Asia/Oral', 'Asia/Oral'), ('Asia/Phnom_Penh', 'Asia/Phnom_Penh'), ('Asia/Pontianak', 'Asia/Pontianak'), ('Asia/Pyongyang', 'Asia/Pyongyang'), ('Asia/Qatar', 'Asia/Qatar'), ('Asia/Qostanay', 'Asia/Qostanay'), ('Asia/Qyzylorda', 'Asia/Qyzylorda'), ('Asia/Riyadh', 'Asia/Riyadh'), ('Asia/Sakhalin', 'Asia/Sakhalin'), ('Asia/Samarkand', 'Asia/Samarkand'), ('Asia/Seoul', 'Asia/Seoul'), ('Asia/Shanghai', 'Asia/Shanghai'), ('Asia/Singapore', 'Asia/Singapore'), ('Asia/Srednekolymsk', 'Asia/Srednekolymsk'), ('Asia/Taipei', 'Asia/Taipei'), ('Asia/Tashkent', 'Asia/Tashkent'), ('Asia/Tbilisi', 'Asia/Tbilisi'), ('Asia/Tehran', 'Asia/Tehran'), ('Asia/Thimphu', 'Asia/Thimphu'), ('Asia/Tokyo', 'Asia/Tokyo'), ('Asia/Tomsk', 'Asia/Tomsk'), ('Asia/Ulaanbaatar', 'Asia/Ulaanbaatar'), ('Asia/Urumqi', 'Asia/Urumqi'), ('Asia/Ust-Nera', 'Asia/Ust-Nera'), ('Asia/Vientiane', 'Asia/Vientiane'), ('Asia/Vladivostok', 'Asia/Vladivostok'), ('Asia/Yakutsk', 'Asia/Yakutsk'), ('Asia/Yangon', 'Asia/Yangon'), ('Asia/Yekaterinburg', 'Asia/Yekaterinburg'), ('Asia/Yerevan', 'Asia/Yerevan'), ('Atlantic/Azores', 'Atlantic/Azores'), ('Atlantic/Bermuda', 'Atlantic/Bermuda'), ('Atlantic/Canary', 'Atlantic/Canary'), ('Atlantic/Cape_Verde', 'Atlantic/Cape_Verde'), ('Atlantic/Faroe', 'Atlantic/Faroe'), ('Atlantic/Madeira', 'Atlantic/Madeira'), ('Atlantic/Reykjavik', 'Atlantic/Reykjavik'), ('Atlantic/South_Georgia', 'Atlantic/South_Georgia'), ('Atlantic/St_Helena', 'Atlantic/St_Helena'), ('Atlantic/Stanley', 'Atlantic/Stanley'), ('Australia/Adelaide', 'Australia/Adelaide'), ('Australia/Brisbane', 'Australia/Brisbane'), ('Australia/Broken_Hill', 'Australia/Broken_Hill'), ('Australia/Currie', 'Australia/Currie'), ('Australia/Darwin', 'Australia/Darwin'), ('Australia/Eucla', 'Australia/Eucla'), ('Australia/Hobart', 'Australia/Hobart'), ('Australia/Lindeman', 'Australia/Lindeman'), ('Australia/Lord_Howe', 'Australia/Lord_Howe'), ('Australia/Melbourne', 'Australia/Melbourne'), ('Australia/Perth', 'Australia/Perth'), ('Australia/Sydney', 'Australia/Sydney'), ('Canada/Atlantic', 'Canada/Atlantic'), ('Canada/Central', 'Canada/Central'), ('Canada/Eastern', 'Canada/Eastern'), ('Canada/Mountain', 'Canada/Mountain'), ('Canada/Newfoundland', 'Canada/Newfoundland'), ('Canada/Pacific', 'Canada/Pacific'), ('Europe/Amsterdam', 'Europe/Amsterdam'), ('Europe/Andorra', 'Europe/Andorra'), ('Europe/Astrakhan', 'Europe/Astrakhan'), ('Europe/Athens', 'Europe/Athens'), ('Europe/Belgrade', 'Europe/Belgrade'), ('Europe/Berlin', 'Europe/Berlin'), ('Europe/Bratislava', 'Europe/Bratislava'), ('Europe/Brussels', 'Europe/Brussels'), ('Europe/Bucharest', 'Europe/Bucharest'), ('Europe/Budapest', 'Europe/Budapest'), ('Europe/Busingen', 'Europe/Busingen'), ('Europe/Chisinau', 'Europe/Chisinau'), ('Europe/Copenhagen', 'Europe/Copenhagen'), ('Europe/Dublin', 'Europe/Dublin'), ('Europe/Gibraltar', 'Europe/Gibraltar'), ('Europe/Guernsey', 'Europe/Guernsey'), ('Europe/Helsinki', 'Europe/Helsinki'), ('Europe/Isle_of_Man', 'Europe/Isle_of_Man'), ('Europe/Istanbul', 'Europe/Istanbul'), ('Europe/Jersey', 'Europe/Jersey'), ('Europe/Kaliningrad', 'Europe/Kaliningrad'), ('Europe/Kiev', 'Europe/Kiev'), ('Europe/Kirov', 'Europe/Kirov'), ('Europe/Lisbon', 'Europe/Lisbon'), ('Europe/Ljubljana', 'Europe/Ljubljana'), ('Europe/London', 'Europe/London'), ('Europe/Luxembourg', 'Europe/Luxembourg'), ('Europe/Madrid', 'Europe/Madrid'), ('Europe/Malta', 'Europe/Malta'), ('Europe/Mariehamn', 'Europe/Mariehamn'), ('Europe/Minsk', 'Europe/Minsk'), ('Europe/Monaco', 'Europe/Monaco'), ('Europe/Moscow', 'Europe/Moscow'), ('Europe/Oslo', 'Europe/Oslo'), ('Europe/Paris', 'Europe/Paris'), ('Europe/Podgorica', 'Europe/Podgorica'), ('Europe/Prague', 'Europe/Prague'), ('Europe/Riga', 'Europe/Riga'), ('Europe/Rome', 'Europe/Rome'), ('Europe/Samara', 'Europe/Samara'), ('Europe/San_Marino', 'Europe/San_Marino'), ('Europe/Sarajevo', 'Europe/Sarajevo'), ('Europe/Saratov', 'Europe/Saratov'), ('Europe/Simferopol', 'Europe/Simferopol'), ('Europe/Skopje', 'Europe/Skopje'), ('Europe/Sofia', 'Europe/Sofia'), ('Europe/Stockholm', 'Europe/Stockholm'), ('Europe/Tallinn', 'Europe/Tallinn'), ('Europe/Tirane', 'Europe/Tirane'), ('Europe/Ulyanovsk', 'Europe/Ulyanovsk'), ('Europe/Uzhgorod', 'Europe/Uzhgorod'), ('Europe/Vaduz', 'Europe/Vaduz'), ('Europe/Vatican', 'Europe/Vatican'), ('Europe/Vienna', 'Europe/Vienna'), ('Europe/Vilnius', 'Europe/Vilnius'), ('Europe/Volgograd', 'Europe/Volgograd'), ('Europe/Warsaw', 'Europe/Warsaw'), ('Europe/Zagreb', 'Europe/Zagreb'), ('Europe/Zaporozhye', 'Europe/Zaporozhye'), ('Europe/Zurich', 'Europe/Zurich'), ('GMT', 'GMT'), ('Indian/Antananarivo', 'Indian/Antananarivo'), ('Indian/Chagos', 'Indian/Chagos'), ('Indian/Christmas', 'Indian/Christmas'), ('Indian/Cocos', 'Indian/Cocos'), ('Indian/Comoro', 'Indian/Comoro'), ('Indian/Kerguelen', 'Indian/Kerguelen'), ('Indian/Mahe', 'Indian/Mahe'), ('Indian/Maldives', 'Indian/Maldives'), ('Indian/Mauritius', 'Indian/Mauritius'), ('Indian/Mayotte', 'Indian/Mayotte'), ('Indian/Reunion', 'Indian/Reunion'), ('Pacific/Apia', 'Pacific/Apia'), ('Pacific/Auckland', 'Pacific/Auckland'), ('Pacific/Bougainville', 'Pacific/Bougainville'), ('Pacific/Chatham', 'Pacific/Chatham'), ('Pacific/Chuuk', 'Pacific/Chuuk'), ('Pacific/Easter', 'Pacific/Easter'), ('Pacific/Efate', 'Pacific/Efate'), ('Pacific/Enderbury', 'Pacific/Enderbury'), ('Pacific/Fakaofo', 'Pacific/Fakaofo'), ('Pacific/Fiji', 'Pacific/Fiji'), ('Pacific/Funafuti', 'Pacific/Funafuti'), ('Pacific/Galapagos', 'Pacific/Galapagos'), ('Pacific/Gambier', 'Pacific/Gambier'), ('Pacific/Guadalcanal', 'Pacific/Guadalcanal'), ('Pacific/Guam', 'Pacific/Guam'), ('Pacific/Honolulu', 'Pacific/Honolulu'), ('Pacific/Kiritimati', 'Pacific/Kiritimati'), ('Pacific/Kosrae', 'Pacific/Kosrae'), ('Pacific/Kwajalein', 'Pacific/Kwajalein'), ('Pacific/Majuro', 'Pacific/Majuro'), ('Pacific/Marquesas', 'Pacific/Marquesas'), ('Pacific/Midway', 'Pacific/Midway'), ('Pacific/Nauru', 'Pacific/Nauru'), ('Pacific/Niue', 'Pacific/Niue'), ('Pacific/Norfolk', 'Pacific/Norfolk'), ('Pacific/Noumea', 'Pacific/Noumea'), ('Pacific/Pago_Pago', 'Pacific/Pago_Pago'), ('Pacific/Palau', 'Pacific/Palau'), ('Pacific/Pitcairn', 'Pacific/Pitcairn'), ('Pacific/Pohnpei', 'Pacific/Pohnpei'), ('Pacific/Port_Moresby', 'Pacific/Port_Moresby'), ('Pacific/Rarotonga', 'Pacific/Rarotonga'), ('Pacific/Saipan', 'Pacific/Saipan'), ('Pacific/Tahiti', 'Pacific/Tahiti'), ('Pacific/Tarawa', 'Pacific/Tarawa'), ('Pacific/Tongatapu', 'Pacific/Tongatapu'), ('Pacific/Wake', 'Pacific/Wake'), ('Pacific/Wallis', 'Pacific/Wallis'), ('US/Alaska', 'US/Alaska'), ('US/Arizona', 'US/Arizona'), ('US/Central', 'US/Central'), ('US/Eastern', 'US/Eastern'), ('US/Hawaii', 'US/Hawaii'), ('US/Mountain', 'US/Mountain'), ('US/Pacific', 'US/Pacific'), ('UTC', 'UTC')], default='UTC', max_length=150)), + ('language_code', models.CharField(choices=[('en', 'English'), ('de', 'Deutsch')], default='en', max_length=150)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/migrations/__init__.py b/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/models.py b/models.py new file mode 100644 index 0000000..f97cacd --- /dev/null +++ b/models.py @@ -0,0 +1,23 @@ +from django.conf import settings +from django.contrib.auth.models import User +from django.db import models +import pytz + + +# GENERAL Methods and Classes +# +def get_userprofile(user): + try: + profile = user.userprofile + except UserProfile.DoesNotExist: + profile = UserProfile(user=user) + profile.save() + return profile + + +# USERPROFILE Model +# +class UserProfile(models.Model): + user = models.OneToOneField(User, unique=True, on_delete=models.CASCADE) + timezone = models.CharField(max_length=150, default='UTC', choices=[(t, t) for t in pytz.common_timezones]) + language_code = models.CharField(max_length=150, default='en', choices=settings.LANGUAGES) diff --git a/signals.py b/signals.py new file mode 100644 index 0000000..c41c1bb --- /dev/null +++ b/signals.py @@ -0,0 +1,37 @@ +import logging +from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed +from django.dispatch import receiver + +log = logging.getLogger('AUTH') + + +@receiver(user_logged_in) +def user_logged_in_callback(sender, request, user, **kwargs): + # to cover more complex cases: + # http://stackoverflow.com/questions/4581789/how-do-i-get-user-ip-address-in-django + ip = request.META.get('REMOTE_ADDR') + + log.info('Accepted password for {user} from {ip}'.format( + user=user, + ip=ip + )) + + +@receiver(user_logged_out) +def user_logged_out_callback(sender, request, user, **kwargs): + ip = request.META.get('REMOTE_ADDR') + + log.debug('Disconnected from user {user} {ip}'.format( + user=user, + ip=ip + )) + + +@receiver(user_login_failed) +def user_login_failed_callback(sender, request, credentials, **kwargs): + ip = request.META.get('REMOTE_ADDR') + + log.warning('Failed password for {user} from {ip}'.format( + user=credentials.get('username'), + ip=ip + )) diff --git a/templates/users/login.html b/templates/users/login.html new file mode 100644 index 0000000..6f48ac6 --- /dev/null +++ b/templates/users/login.html @@ -0,0 +1,11 @@ +{% extends "themes/"|add:settings.page_theme|add:"/base.html" %} +{% load i18n %} + +{% block content %} + +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock content %} diff --git a/templates/users/profile.html b/templates/users/profile.html new file mode 100644 index 0000000..b4cd62b --- /dev/null +++ b/templates/users/profile.html @@ -0,0 +1,11 @@ +{% extends "themes/"|add:settings.page_theme|add:"/base.html" %} +{% load i18n %} + +{% block content %} +
+ {% csrf_token %} + {% include 'users/profile_formdata.html' %} + +
+ +{% endblock content %} diff --git a/templates/users/profile_formdata.html b/templates/users/profile_formdata.html new file mode 100644 index 0000000..3dd2aab --- /dev/null +++ b/templates/users/profile_formdata.html @@ -0,0 +1,6 @@ +{% load i18n %} + +{% get_current_language as LANGUAGE_CODE %} +

{% trans "Language and Timezone" %}

+ {{ form_userprofile.as_p }} + diff --git a/templates/users/register.html b/templates/users/register.html new file mode 100644 index 0000000..c799664 --- /dev/null +++ b/templates/users/register.html @@ -0,0 +1,11 @@ +{% extends "themes/"|add:settings.page_theme|add:"/base.html" %} +{% load i18n %} + +{% block content %} + +
+ {% csrf_token %} + {{ form.as_p }} + +
+{% endblock content %} diff --git a/tests.py b/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/urls.py b/urls.py new file mode 100644 index 0000000..aa6537a --- /dev/null +++ b/urls.py @@ -0,0 +1,10 @@ +from django.urls import path +from . import views + +urlpatterns = [ + path('login', views.login, name='users-login'), + path('logout', views.logout, name='users-logout'), + path('password_recovery', views.password_recovery, name='users-password-recovery'), + path('profile', views.profile, name='users-profile'), + path('register', views.register, name='users-register'), +] diff --git a/views.py b/views.py new file mode 100644 index 0000000..051fe29 --- /dev/null +++ b/views.py @@ -0,0 +1,105 @@ +from .context import context_adaption +from django.shortcuts import render, redirect +from django.conf import settings +from django.contrib import messages +from django.contrib.auth import authenticate +from django.contrib.auth import login as django_login +from django.contrib.auth import logout as django_logout +from django.contrib.auth.decorators import login_required +from django.contrib.auth.forms import AuthenticationForm +from django.utils.translation import gettext as _ +from .forms import UserRegistrationForm, UserProfileForm +from .models import get_userprofile +from themes import Context +import users + + +def password_recovery(request): + return redirect(request.GET.get('next') or '/') + + +def profile_post_actions(request, context): + if request.POST: + form = context.get('form_userprofile') + if form.is_valid(): + form.save() + return redirect(request.GET.get('next') or '/') + + +def profile_pre_actions(request, context, form_to_be_used=UserProfileForm): + profile = get_userprofile(request.user) + if request.POST: + form = form_to_be_used(request.POST, instance=profile) + else: + form = form_to_be_used(instance=profile) + context['form_userprofile'] = form + + +@login_required +def profile(request): + context = Context(request) # needs to be executed first because of time mesurement + profile_pre_actions(request, context) + response = profile_post_actions(request, context) + if response is not None: + return response + else: + context_adaption( + context, + request, + _('Profile for %(username)s') % {'username': request.user.username}, + ) + return render(request, 'users/profile.html', context=context) + + +def register(request): + context = Context(request) # needs to be executed first because of time mesurement + context_adaption(context, request, _('Register')) + if not request.POST: + form = UserRegistrationForm() + messages.info(request, _('If you already have an account, login here.') % {'url': users.url_login(request)}) + else: + form = UserRegistrationForm(request.POST) + if form.is_valid(): + form.save() + messages.success(request, _('Your account has been created! You are able to log in as %(username)s.') % {'username': form.cleaned_data.get('username')}) + return redirect('users-login') + else: + messages.error(request, _('Registration failed!')) + context['form'] = form + return render(request, 'users/register.html', context) + + +def login(request): + context = Context(request) # needs to be executed first because of time mesurement + context_adaption(context, request, _('Login')) + if not request.POST: + form = AuthenticationForm() + messages.info(request, _('If you don\'t have an acount, register here.') % {'url': users.url_register(request)}) + else: + form = AuthenticationForm(request, data=request.POST) + if form.is_valid(): + username = form.cleaned_data.get('username') + user = authenticate(username=username, password=form.cleaned_data.get('password')) + django_login(request, user) + messages.success(request, _('You are now logged in as %(username)s.') % {'username': username}) + return redirect(request.GET.get('next') or '/') + else: + messages.error(request, _('Login failed! You can do a password recorvery here or you can register here.') % {'url_register': users.url_register(request), 'url_recover': users.url_password_recovery(request)}) + context['form'] = form + return render(request, 'users/login.html', context) + + +def logout(request): + messages.success(request, _('You are no longer logged in as %(username)s.') % {'username': request.user.username}) + session_cache = {} + try: + for variable in settings.PERSISTENT_SESSION_VARIABLES: + value = request.session.get(variable) + if value is not None: + session_cache[variable] = value + except AttributeError: + pass # PERSISTENT_SESSION_VARIABLES are possibly not defined in the settings + django_logout(request) + for variable in session_cache: + request.session[variable] = session_cache[variable] + return redirect(request.GET.get('next') or '/')