Initial users implementation

This commit is contained in:
Dirk Alders 2020-01-26 20:47:33 +01:00
parent 62cd31d0f5
commit 277352fe9b
19 changed files with 539 additions and 0 deletions

26
__init__.py Normal file
View File

@ -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

13
admin.py Normal file
View File

@ -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)

8
apps.py Normal file
View File

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

87
context.py Normal file
View File

@ -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
)

24
forms.py Normal file
View File

@ -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']

Binary file not shown.

View File

@ -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 <EMAIL@ADDRESS>, 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 <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"
#: 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 <a href=\"%(url)s\">here</a>."
msgstr ""
"Wenn Sie bereits einen Account besitzen, dann können sie sich <a href="
"\"%(url)s\">hier</a> 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 <a href=\"%(url)s\">here</a>."
msgstr ""
"Wenn Sie keinen Account haben, können Sie sich <a href=\"%(url)s\">hier</a> "
"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 <a href=\"%(url_recover)s"
"\">here</a> or you can register <a href=\"%(url_register)s\">here</a>."
msgstr ""
"Anmeldung fehlgeschlagen! Sie können Ihr Passwort hier <a href="
"\"%(url_recover)s\">hier</a> wiederherstellen oder Sie können sich <a href="
"\"%(url_register)s\">hier</a> 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."

42
middleware.py Normal file
View File

@ -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

File diff suppressed because one or more lines are too long

0
migrations/__init__.py Normal file
View File

23
models.py Normal file
View File

@ -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)

37
signals.py Normal file
View File

@ -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
))

View File

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

View File

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

View File

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

View File

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

3
tests.py Normal file
View File

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

10
urls.py Normal file
View File

@ -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'),
]

105
views.py Normal file
View File

@ -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 <a href="%(url)s">here</a>.') % {'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 <a href="%(url)s">here</a>.') % {'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 <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)})
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 '/')