Browse Source

Initial users implementation

master
Dirk Alders 4 years ago
parent
commit
277352fe9b

+ 26
- 0
__init__.py View File

@@ -0,0 +1,26 @@
1
+from django.urls.base import reverse
2
+
3
+
4
+def url_login(request):
5
+    nxt = request.GET.get('next', request.get_full_path())
6
+    return reverse('users-login') + '?next=%s' % nxt
7
+
8
+
9
+def url_logout(request):
10
+    nxt = request.GET.get('next', request.get_full_path())
11
+    return reverse('users-logout') + '?next=%s' % nxt
12
+
13
+
14
+def url_profile(request):
15
+    nxt = request.GET.get('next', request.get_full_path())
16
+    return reverse('users-profile') + '?next=%s' % nxt
17
+
18
+
19
+def url_register(request):
20
+    nxt = request.GET.get('next', request.get_full_path())
21
+    return reverse('users-register') + '?next=%s' % nxt
22
+
23
+
24
+def url_password_recovery(request):
25
+    nxt = request.GET.get('next', request.get_full_path())
26
+    return reverse('users-password-recovery') + '?next=%s' % nxt

+ 13
- 0
admin.py View File

@@ -0,0 +1,13 @@
1
+from django.contrib import admin
2
+from .models import UserProfile
3
+
4
+
5
+class UserProfileAdmin(admin.ModelAdmin):
6
+    list_display = ('user', )
7
+    search_fields = ('user', 'language_code', 'timezone', 'id', )
8
+    list_filter = (
9
+        ('language_code', admin.ChoicesFieldListFilter),
10
+    )
11
+
12
+
13
+admin.site.register(UserProfile, UserProfileAdmin)

+ 8
- 0
apps.py View File

@@ -0,0 +1,8 @@
1
+from django.apps import AppConfig
2
+
3
+
4
+class UsersConfig(AppConfig):
5
+    name = 'users'
6
+
7
+    def ready(self):
8
+        import users.signals

+ 87
- 0
context.py View File

@@ -0,0 +1,87 @@
1
+from django.urls.base import reverse
2
+from django.utils.translation import gettext as _
3
+from themes import empty_entry_parameters, color_icon_url
4
+from . import url_login, url_logout, url_register, url_profile
5
+
6
+ADMIN_ENTRY_UID = 'admin-main'
7
+LOGIN_ENTRY_UID = 'login-main'
8
+LOGOUT_ENTRY_UID = 'logout-main'
9
+PROFILE_ENTRY_UID = 'profile-main'
10
+REGISTER_ENTRY_UID = 'register-main'
11
+
12
+
13
+def context_adaption(context, request, title):
14
+    context.set_additional_title(title)
15
+    menubar(context[context.MENUBAR], request)
16
+    context[context.NAVIGATIONBAR].append_entry(*empty_entry_parameters(request))
17
+    actionbar(context[context.ACTIONBAR], request)
18
+
19
+
20
+def menubar(bar, request):
21
+    if not request.user.is_authenticated:
22
+        bar.append_entry(*login_entry_parameters(request))
23
+    else:
24
+        bar.append_entry(*logout_entry_parameters(request))
25
+        if request.user.is_staff or request.user.is_superuser:
26
+            bar.append_entry(*admin_entry_parameters(request))
27
+        bar.append_entry(*profile_entry_parameters(request))
28
+
29
+
30
+def actionbar(bar, request):
31
+    bar.append_entry(*login_entry_parameters(request, left=True))
32
+    bar.append_entry(*register_entry_parameters(request, left=True))
33
+
34
+
35
+def login_entry_parameters(request, left=False):
36
+    return (
37
+        LOGIN_ENTRY_UID,                            # uid
38
+        _('Login'),                                 # name
39
+        color_icon_url(request, 'login.png'),       # icon
40
+        url_login(request),                         # url
41
+        left,                                       # left
42
+        False                                       # active
43
+    )
44
+
45
+
46
+def register_entry_parameters(request, left=False):
47
+    return (
48
+        REGISTER_ENTRY_UID,                         # uid
49
+        _('Register'),                              # name
50
+        color_icon_url(request, 'register.png'),    # icon
51
+        url_register(request),                      # url
52
+        left,                                       # left
53
+        False                                       # active
54
+    )
55
+
56
+
57
+def logout_entry_parameters(request):
58
+    return (
59
+        LOGOUT_ENTRY_UID,                           # uid
60
+        _('Logout'),                                # name
61
+        color_icon_url(request, 'logout.png'),      # icon
62
+        url_logout(request),                        # url
63
+        False,                                      # left
64
+        False,                                      # active
65
+    )
66
+
67
+
68
+def admin_entry_parameters(request):
69
+    return (
70
+        ADMIN_ENTRY_UID,                            # uid
71
+        _('Administration'),                        # name
72
+        color_icon_url(request, 'admin.png'),       # icon
73
+        reverse('admin:index'),                     # url
74
+        False,                                      # left
75
+        False                                       # active
76
+    )
77
+
78
+
79
+def profile_entry_parameters(request):
80
+    return (
81
+        PROFILE_ENTRY_UID,                          # uid
82
+        request.user.username,                      # name
83
+        color_icon_url(request, 'user.png'),        # icon
84
+        url_profile(request),                       # url
85
+        False,                                      # left
86
+        False                                       # active
87
+    )

+ 24
- 0
forms.py View File

@@ -0,0 +1,24 @@
1
+from django import forms
2
+from django.contrib.auth.models import User
3
+from django.contrib.auth.forms import UserCreationForm
4
+from .models import UserProfile
5
+
6
+
7
+class UserRegistrationForm(UserCreationForm):
8
+    email = forms.EmailField()
9
+
10
+    class Meta:
11
+        model = User
12
+        fields = ['username', 'email', 'password1', 'password2']
13
+
14
+
15
+class UserProfileForm(forms.ModelForm):
16
+    class Meta:
17
+        model = UserProfile
18
+        fields = ['timezone', 'language_code']
19
+
20
+
21
+class UserProfileFormLanguageOnly(forms.ModelForm):
22
+    class Meta:
23
+        model = UserProfile
24
+        fields = ['language_code']

BIN
locale/de/LC_MESSAGES/django.mo View File


+ 96
- 0
locale/de/LC_MESSAGES/django.po View File

@@ -0,0 +1,96 @@
1
+# SOME DESCRIPTIVE TITLE.
2
+# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
+# This file is distributed under the same license as the PACKAGE package.
4
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
+#
6
+#, fuzzy
7
+msgid ""
8
+msgstr ""
9
+"Project-Id-Version: PACKAGE VERSION\n"
10
+"Report-Msgid-Bugs-To: \n"
11
+"POT-Creation-Date: 2019-09-22 16:13+0200\n"
12
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
+"Language-Team: LANGUAGE <LL@li.org>\n"
15
+"Language: \n"
16
+"MIME-Version: 1.0\n"
17
+"Content-Type: text/plain; charset=UTF-8\n"
18
+"Content-Transfer-Encoding: 8bit\n"
19
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
20
+
21
+#: context.py:38 templates/users/login.html:9 views.py:62
22
+msgid "Login"
23
+msgstr "Anmelden"
24
+
25
+#: context.py:49 templates/users/register.html:9 views.py:44
26
+msgid "Register"
27
+msgstr "Registrieren"
28
+
29
+#: context.py:60
30
+msgid "Logout"
31
+msgstr "Abmelden"
32
+
33
+#: context.py:71
34
+msgid "Administration"
35
+msgstr "Administration"
36
+
37
+#: templates/users/profile.html:8
38
+msgid "Save"
39
+msgstr "Speichern"
40
+
41
+#: templates/users/profile_formdata.html:4
42
+msgid "Language"
43
+msgstr "Sprache"
44
+
45
+#: views.py:27
46
+msgid "The language was set to \"%(language)s (%(language_code)s)\"."
47
+msgstr "Die Sprache wurde zu \"%(language)s (%(language_code)s)\" gesetzt."
48
+
49
+#: views.py:38
50
+#, python-format
51
+msgid "Profile for %(username)s"
52
+msgstr "Benutzerprofil für %(username)s"
53
+
54
+#: views.py:47
55
+#, python-format
56
+msgid "If you already have an account, login <a href=\"%(url)s\">here</a>."
57
+msgstr ""
58
+"Wenn Sie bereits einen Account besitzen, dann können sie sich <a href="
59
+"\"%(url)s\">hier</a> anmelden."
60
+
61
+#: views.py:52
62
+#, python-format
63
+msgid "Your account has been created! You are able to log in as %(username)s."
64
+msgstr ""
65
+"Ihr Account wurde angelegt! Sie können sich nun als %(username)s anmelden."
66
+
67
+#: views.py:55
68
+msgid "Registration failed!"
69
+msgstr "Registrierung fehlgeschlagen!"
70
+
71
+#: views.py:65
72
+#, python-format
73
+msgid "If you don't have an acount, register <a href=\"%(url)s\">here</a>."
74
+msgstr ""
75
+"Wenn Sie keinen Account haben, können Sie sich <a href=\"%(url)s\">hier</a> "
76
+"registrieren."
77
+
78
+#: views.py:72
79
+#, python-format
80
+msgid "You are now logged in as %(username)s."
81
+msgstr "Sie sind nun als %(username)s angemeldet."
82
+
83
+#: views.py:75
84
+#, python-format
85
+msgid ""
86
+"Login failed! You can do a password recorvery <a href=\"%(url_recover)s"
87
+"\">here</a> or you can register <a href=\"%(url_register)s\">here</a>."
88
+msgstr ""
89
+"Anmeldung fehlgeschlagen! Sie können Ihr Passwort hier <a href="
90
+"\"%(url_recover)s\">hier</a> wiederherstellen oder Sie können sich <a href="
91
+"\"%(url_register)s\">hier</a> registrieren."
92
+
93
+#: views.py:81
94
+#, python-format
95
+msgid "You are no longer logged in as %(username)s."
96
+msgstr "Sie sind nicht mehr als %(username)s angemeldet."

+ 42
- 0
middleware.py View File

@@ -0,0 +1,42 @@
1
+from django.contrib.auth.models import User
2
+from django.utils import timezone
3
+from django.utils import translation
4
+import pytz
5
+
6
+
7
+class SettingsMiddleware(object):
8
+    def __init__(self, get_response):
9
+        self.get_response = get_response
10
+
11
+    def __call__(self, request):
12
+        #
13
+        # Set Timzone
14
+        #
15
+        if request.user.is_authenticated:
16
+            try:
17
+                timezone.activate(pytz.timezone(request.user.userprofile.timezone))
18
+            except User.userprofile.RelatedObjectDoesNotExist:
19
+                pass   # No profile
20
+            except AttributeError:
21
+                pass   # No User logged in
22
+        else:
23
+            timezone.deactivate()
24
+        #
25
+        # Set Language
26
+        #
27
+        try:
28
+            language = request.user.userprofile.language_code
29
+        except User.userprofile.RelatedObjectDoesNotExist:
30
+            pass   # No profile
31
+        except AttributeError:
32
+            pass   # No User logged in
33
+        else:
34
+            translation.activate(language)
35
+            request.LANGUAGE_CODE = translation.get_language()
36
+
37
+        response = self.get_response(request)
38
+
39
+        # Code to be executed for each request/response after
40
+        # the view is called.
41
+
42
+        return response

+ 26
- 0
migrations/0001_initial.py
File diff suppressed because it is too large
View File


+ 0
- 0
migrations/__init__.py View File


+ 23
- 0
models.py View File

@@ -0,0 +1,23 @@
1
+from django.conf import settings
2
+from django.contrib.auth.models import User
3
+from django.db import models
4
+import pytz
5
+
6
+
7
+# GENERAL Methods and Classes
8
+#
9
+def get_userprofile(user):
10
+    try:
11
+        profile = user.userprofile
12
+    except UserProfile.DoesNotExist:
13
+        profile = UserProfile(user=user)
14
+        profile.save()
15
+    return profile
16
+
17
+
18
+# USERPROFILE Model
19
+#
20
+class UserProfile(models.Model):
21
+    user = models.OneToOneField(User, unique=True, on_delete=models.CASCADE)
22
+    timezone = models.CharField(max_length=150, default='UTC', choices=[(t, t) for t in pytz.common_timezones])
23
+    language_code = models.CharField(max_length=150, default='en', choices=settings.LANGUAGES)

+ 37
- 0
signals.py View File

@@ -0,0 +1,37 @@
1
+import logging
2
+from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
3
+from django.dispatch import receiver
4
+
5
+log = logging.getLogger('AUTH')
6
+
7
+
8
+@receiver(user_logged_in)
9
+def user_logged_in_callback(sender, request, user, **kwargs):
10
+    # to cover more complex cases:
11
+    # http://stackoverflow.com/questions/4581789/how-do-i-get-user-ip-address-in-django
12
+    ip = request.META.get('REMOTE_ADDR')
13
+
14
+    log.info('Accepted password for {user} from {ip}'.format(
15
+        user=user,
16
+        ip=ip
17
+    ))
18
+
19
+
20
+@receiver(user_logged_out)
21
+def user_logged_out_callback(sender, request, user, **kwargs):
22
+    ip = request.META.get('REMOTE_ADDR')
23
+
24
+    log.debug('Disconnected from user {user} {ip}'.format(
25
+        user=user,
26
+        ip=ip
27
+    ))
28
+
29
+
30
+@receiver(user_login_failed)
31
+def user_login_failed_callback(sender, request, credentials, **kwargs):
32
+    ip = request.META.get('REMOTE_ADDR')
33
+
34
+    log.warning('Failed password for {user} from {ip}'.format(
35
+        user=credentials.get('username'),
36
+        ip=ip
37
+    ))

+ 11
- 0
templates/users/login.html View File

@@ -0,0 +1,11 @@
1
+{% extends "themes/"|add:settings.page_theme|add:"/base.html" %}
2
+{% load i18n %}
3
+
4
+{% block content %}
5
+
6
+    <form class="form" method="post">
7
+      {% csrf_token %}
8
+      {{ form.as_p }}
9
+      <input type="submit" value="{% trans "Login" %}" class="button" />
10
+    </form>
11
+{% endblock content %}

+ 11
- 0
templates/users/profile.html View File

@@ -0,0 +1,11 @@
1
+{% extends "themes/"|add:settings.page_theme|add:"/base.html" %}
2
+{% load i18n %}
3
+
4
+{% block content %}
5
+  <form action="" method="post">
6
+    {% csrf_token %}
7
+    {% include 'users/profile_formdata.html' %}
8
+    <input type="submit" value="{% trans "Save" %}" class="button" />
9
+  </form>
10
+
11
+{% endblock content %}

+ 6
- 0
templates/users/profile_formdata.html View File

@@ -0,0 +1,6 @@
1
+{% load i18n %}
2
+
3
+{% get_current_language as LANGUAGE_CODE %}
4
+  <h1>{% trans "Language and Timezone" %}</h1>
5
+  {{ form_userprofile.as_p }}
6
+  </select>

+ 11
- 0
templates/users/register.html View File

@@ -0,0 +1,11 @@
1
+{% extends "themes/"|add:settings.page_theme|add:"/base.html" %}
2
+{% load i18n %}
3
+
4
+{% block content %}
5
+
6
+    <form class="form" method="post">
7
+      {% csrf_token %}
8
+      {{ form.as_p }}
9
+      <input type="submit" value="{% trans "Register" %}" class="button" />
10
+    </form>
11
+{% endblock content %}

+ 3
- 0
tests.py View File

@@ -0,0 +1,3 @@
1
+from django.test import TestCase
2
+
3
+# Create your tests here.

+ 10
- 0
urls.py View File

@@ -0,0 +1,10 @@
1
+from django.urls import path
2
+from . import views
3
+
4
+urlpatterns = [
5
+    path('login', views.login, name='users-login'),
6
+    path('logout', views.logout, name='users-logout'),
7
+    path('password_recovery', views.password_recovery, name='users-password-recovery'),
8
+    path('profile', views.profile, name='users-profile'),
9
+    path('register', views.register, name='users-register'),
10
+]

+ 105
- 0
views.py View File

@@ -0,0 +1,105 @@
1
+from .context import context_adaption
2
+from django.shortcuts import render, redirect
3
+from django.conf import settings
4
+from django.contrib import messages
5
+from django.contrib.auth import authenticate
6
+from django.contrib.auth import login as django_login
7
+from django.contrib.auth import logout as django_logout
8
+from django.contrib.auth.decorators import login_required
9
+from django.contrib.auth.forms import AuthenticationForm
10
+from django.utils.translation import gettext as _
11
+from .forms import UserRegistrationForm, UserProfileForm
12
+from .models import get_userprofile
13
+from themes import Context
14
+import users
15
+
16
+
17
+def password_recovery(request):
18
+    return redirect(request.GET.get('next') or '/')
19
+
20
+
21
+def profile_post_actions(request, context):
22
+    if request.POST:
23
+        form = context.get('form_userprofile')
24
+        if form.is_valid():
25
+            form.save()
26
+        return redirect(request.GET.get('next') or '/')
27
+
28
+
29
+def profile_pre_actions(request, context, form_to_be_used=UserProfileForm):
30
+    profile = get_userprofile(request.user)
31
+    if request.POST:
32
+        form = form_to_be_used(request.POST, instance=profile)
33
+    else:
34
+        form = form_to_be_used(instance=profile)
35
+    context['form_userprofile'] = form
36
+
37
+
38
+@login_required
39
+def profile(request):
40
+    context = Context(request)      # needs to be executed first because of time mesurement
41
+    profile_pre_actions(request, context)
42
+    response = profile_post_actions(request, context)
43
+    if response is not None:
44
+        return response
45
+    else:
46
+        context_adaption(
47
+            context,
48
+            request,
49
+            _('Profile for %(username)s') % {'username': request.user.username},
50
+        )
51
+        return render(request, 'users/profile.html', context=context)
52
+
53
+
54
+def register(request):
55
+    context = Context(request)      # needs to be executed first because of time mesurement
56
+    context_adaption(context, request, _('Register'))
57
+    if not request.POST:
58
+        form = UserRegistrationForm()
59
+        messages.info(request, _('If you already have an account, login <a href="%(url)s">here</a>.') % {'url': users.url_login(request)})
60
+    else:
61
+        form = UserRegistrationForm(request.POST)
62
+        if form.is_valid():
63
+            form.save()
64
+            messages.success(request, _('Your account has been created! You are able to log in as %(username)s.') % {'username': form.cleaned_data.get('username')})
65
+            return redirect('users-login')
66
+        else:
67
+            messages.error(request, _('Registration failed!'))
68
+    context['form'] = form
69
+    return render(request, 'users/register.html', context)
70
+
71
+
72
+def login(request):
73
+    context = Context(request)      # needs to be executed first because of time mesurement
74
+    context_adaption(context, request, _('Login'))
75
+    if not request.POST:
76
+        form = AuthenticationForm()
77
+        messages.info(request, _('If you don\'t have an acount, register <a href="%(url)s">here</a>.') % {'url': users.url_register(request)})
78
+    else:
79
+        form = AuthenticationForm(request, data=request.POST)
80
+        if form.is_valid():
81
+            username = form.cleaned_data.get('username')
82
+            user = authenticate(username=username, password=form.cleaned_data.get('password'))
83
+            django_login(request, user)
84
+            messages.success(request, _('You are now logged in as %(username)s.') % {'username': username})
85
+            return redirect(request.GET.get('next') or '/')
86
+        else:
87
+            messages.error(request, _('Login failed! You can do a password recorvery <a href="%(url_recover)s">here</a> or you can register <a href="%(url_register)s">here</a>.') % {'url_register': users.url_register(request), 'url_recover': users.url_password_recovery(request)})
88
+    context['form'] = form
89
+    return render(request, 'users/login.html', context)
90
+
91
+
92
+def logout(request):
93
+    messages.success(request, _('You are no longer logged in as %(username)s.') % {'username': request.user.username})
94
+    session_cache = {}
95
+    try:
96
+        for variable in settings.PERSISTENT_SESSION_VARIABLES:
97
+            value = request.session.get(variable)
98
+            if value is not None:
99
+                session_cache[variable] = value
100
+    except AttributeError:
101
+        pass    # PERSISTENT_SESSION_VARIABLES are possibly not defined in the settings
102
+    django_logout(request)
103
+    for variable in session_cache:
104
+        request.session[variable] = session_cache[variable]
105
+    return redirect(request.GET.get('next') or '/')

Loading…
Cancel
Save