From bf68e01f3d3454c08d5da0e4e712e1ba65dc6b41 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 19:28:09 +0200 Subject: [PATCH 01/11] Django admin support for our User model. Adding users still misses some useful fields for unknown reasons (tixlbase/admin.py:26 does not seem to do anything, in contrast to documentation). --- src/tixlbase/admin.py | 59 ++++++++++++++++++- .../migrations/0002_auto_20140910_1628.py | 26 ++++++++ .../migrations/0003_auto_20140910_1649.py | 19 ++++++ src/tixlbase/models.py | 58 ++++++++++++++---- 4 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 src/tixlbase/migrations/0002_auto_20140910_1628.py create mode 100644 src/tixlbase/migrations/0003_auto_20140910_1649.py diff --git a/src/tixlbase/admin.py b/src/tixlbase/admin.py index 8c38f3f3d..2dc9d3772 100644 --- a/src/tixlbase/admin.py +++ b/src/tixlbase/admin.py @@ -1,3 +1,60 @@ from django.contrib import admin +from django.contrib.auth.admin import UserAdmin +from django.utils.translation import ugettext as _ +from django import forms -# Register your models here. +from tixlbase.models import User + + +class TixlUserCreationForm(forms.ModelForm): + + """ + A form that creates a user, with no privileges, from the given username and + password. + """ + error_messages = { + 'password_mismatch': _("The two password fields didn't match."), + } + password1 = forms.CharField(label=_("Password"), + widget=forms.PasswordInput) + password2 = forms.CharField(label=_("Password confirmation"), + widget=forms.PasswordInput, + help_text=_("Enter the same password as above, for verification.")) + + class Meta: + model = User + fields = ("email", "username", "event") + + def clean_password2(self): + password1 = self.cleaned_data.get("password1") + password2 = self.cleaned_data.get("password2") + if password1 and password2 and password1 != password2: + raise forms.ValidationError( + self.error_messages['password_mismatch'], + code='password_mismatch', + ) + return password2 + + def save(self, commit=True): + user = super(TixlUserCreationForm, self).save(commit=False) + user.set_password(self.cleaned_data["password1"]) + if commit: + user.save() + return user + + +class TixlUserAdmin(UserAdmin): + + fieldsets = ( + (None, {'fields': ('identifier', 'event', 'username', 'password')}), + (_('Personal info'), {'fields': ('familyname', 'givenname', 'email')}), + (_('Permissions'), {'fields': ('is_active', 'is_staff', + 'groups', 'user_permissions')}), + ) + list_display = ('identifier', 'event', 'username', 'email', 'givenname', 'familyname', 'is_staff') + search_fields = ('identifier', 'username', 'givenname', 'familyname', 'email') + ordering = ('identifier',) + list_filter = ('is_staff', 'is_active', 'groups') + add_form = TixlUserCreationForm + +admin.site.register(User, TixlUserAdmin) diff --git a/src/tixlbase/migrations/0002_auto_20140910_1628.py b/src/tixlbase/migrations/0002_auto_20140910_1628.py new file mode 100644 index 000000000..d08eaa8e2 --- /dev/null +++ b/src/tixlbase/migrations/0002_auto_20140910_1628.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tixlbase', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='familyname', + field=models.CharField(blank=True, max_length=255, null=True), + preserve_default=True, + ), + migrations.AddField( + model_name='user', + name='givenname', + field=models.CharField(blank=True, max_length=255, null=True), + preserve_default=True, + ), + ] diff --git a/src/tixlbase/migrations/0003_auto_20140910_1649.py b/src/tixlbase/migrations/0003_auto_20140910_1649.py new file mode 100644 index 000000000..826d1a1c9 --- /dev/null +++ b/src/tixlbase/migrations/0003_auto_20140910_1649.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('tixlbase', '0002_auto_20140910_1628'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='username', + field=models.CharField(blank=True, max_length=120, null=True, help_text='Letters, digits and @/./+/-/_ only.'), + ), + ] diff --git a/src/tixlbase/models.py b/src/tixlbase/models.py index 45674511d..de508aa49 100644 --- a/src/tixlbase/models.py +++ b/src/tixlbase/models.py @@ -1,5 +1,6 @@ from django.db import models from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin +from django.utils.translation import ugettext as _ class UserManager(BaseUserManager): @@ -8,16 +9,16 @@ class UserManager(BaseUserManager): model documentation to see what's so special about our user model. """ - def create_user(self, email, password=None): - user = self.model(email=email) + def create_user(self, identifier, username, password=None): + user = self.model(identifier=identifier) user.set_password(user) user.save() return user - def create_superuser(self, email, password=None): + def create_superuser(self, identifier, username, password=None): if password is None: raise Exception("You must provide a password") - user = self.model(email=email) + user = self.model(identifier=identifier, username=username) user.is_staff = True user.is_superuser = True user.set_password(user) @@ -59,11 +60,21 @@ class User(AbstractBaseUser, PermissionsMixin): """ identifier = models.CharField(max_length=255, unique=True) - username = models.CharField(max_length=120) + username = models.CharField(max_length=120, blank=True, + null=True, + help_text=_('Letters, digits and @/./+/-/_ only.')) event = models.ForeignKey('Event', related_name="users", - null=True, blank=True) + null=True, blank=True, + on_delete=models.PROTECT) email = models.EmailField(unique=False, db_index=True, - null=True, blank=True) + null=True, blank=True, + verbose_name=_('E-mail')) + givenname = models.CharField(max_length=255, blank=True, + null=True, + verbose_name=_('Given name')) + familyname = models.CharField(max_length=255, blank=True, + null=True, + verbose_name=_('Family name')) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) date_joined = models.DateTimeField(auto_now_add=True) @@ -73,12 +84,35 @@ class User(AbstractBaseUser, PermissionsMixin): def __str__(self): return self.identifier + def get_short_name(self): + if self.givenname: + return self.givenname + elif self.familyname: + return self.familyname + else: + return self.username + + def get_full_name(self): + if self.givenname and not self.familyname: + return self.givenname + elif not self.givenname and self.familyname: + return self.familyname + elif self.familyname and self.givenname: + return '%(family)s, %(given)s' % { + 'family': self.familyname, + 'given': self.givenname + } + else: + return self.username + def save(self, *args, **kwargs): if self.identifier is None: if self.event is None: - self.identifier = self.email + self.identifier = self.email.lower() else: - self.identifier = "%s@%d.event.tixl" % (self.username, self.event.id) + self.identifier = "%s@%d.event.tixl" % (self.username.lower(), self.event.id) + if not self.pk: + self.identifier = self.identifier.lower() super().save(*args, **kwargs) USERNAME_FIELD = 'identifier' @@ -101,7 +135,8 @@ class Organizer(models.Model): slug = models.CharField(max_length=50, unique=True, db_index=True) - owner = models.ForeignKey(User, null=True, blank=True) + owner = models.ForeignKey(User, null=True, blank=True, + on_delete=models.PROTECT) class Meta: ordering = ("name",) @@ -133,7 +168,8 @@ class Event(models.Model): matter when they were ordered (and thus, ignoring payment_term_days). """ - organizer = models.ForeignKey(Organizer, related_name="events") + organizer = models.ForeignKey(Organizer, related_name="events", + on_delete=models.PROTECT) name = models.CharField(max_length=200) slug = models.CharField(max_length=50, db_index=True) From e0b57344b82435ebfbac03fc0fc080e1ecce8f00 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 19:42:10 +0200 Subject: [PATCH 02/11] Code checker configuration --- .travis.yml | 4 ++-- src/requirements.txt | 2 ++ src/setup.cfg | 5 +++++ 3 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 src/setup.cfg diff --git a/.travis.yml b/.travis.yml index 5049c2031..71736cd15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,7 +6,7 @@ install: - pip install -q -r src/requirements.txt before_script: - cd src - - "pep8 --ignore=E501,E128 --exclude=migrations ." - - pyflakes . + - flake8 --ignore=E128,F403,F401 . + - python manage.py validate script: - python manage.py test diff --git a/src/requirements.txt b/src/requirements.txt index 9d221a8ef..4cba13667 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,3 +1,5 @@ Django>=1.7 pyflakes pep8 +pep8-naming +flake8 diff --git a/src/setup.cfg b/src/setup.cfg new file mode 100644 index 000000000..ec50166d3 --- /dev/null +++ b/src/setup.cfg @@ -0,0 +1,5 @@ +[flake8] +ignore = E128 +max-line-length = 160 +exclude = tests,migrations,.ropeproject +max-complexity = 12 From 8844982af5f33f6e78296120017936939ff70b17 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 20:05:34 +0200 Subject: [PATCH 03/11] Update developer documentation --- doc/development/setup.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/doc/development/setup.rst b/doc/development/setup.rst index 2d4af3494..8bcb0b07e 100644 --- a/doc/development/setup.rst +++ b/doc/development/setup.rst @@ -34,8 +34,7 @@ Static code checks Before you check in your code into git, always run:: - pyflakes . - pep8 --ignore=E501,E128 . + flake8 . to check for syntax, style and other errors. From 44931020497d8d5b06135a528d9fd26db76a2da0 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 20:05:52 +0200 Subject: [PATCH 04/11] Require login for all of tixlcontrol/ except login --- src/tixl/settings.py | 4 +++- src/tixl/urls.py | 8 +++---- src/tixlcontrol/middleware.py | 39 +++++++++++++++++++++++++++++++ src/tixlcontrol/urls.py | 5 ++++ src/tixlcontrol/views.py | 3 --- src/tixlcontrol/views/__init__.py | 0 src/tixlcontrol/views/main.py | 5 ++++ 7 files changed, 56 insertions(+), 8 deletions(-) create mode 100644 src/tixlcontrol/middleware.py create mode 100644 src/tixlcontrol/urls.py delete mode 100644 src/tixlcontrol/views.py create mode 100644 src/tixlcontrol/views/__init__.py create mode 100644 src/tixlcontrol/views/main.py diff --git a/src/tixl/settings.py b/src/tixl/settings.py index 4be0c19e5..2ed009068 100644 --- a/src/tixl/settings.py +++ b/src/tixl/settings.py @@ -49,6 +49,7 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', + 'tixlcontrol.middleware.LoginRequiredMiddleware', ) ROOT_URLCONF = 'tixl.urls' @@ -83,7 +84,8 @@ USE_TZ = True # Authentication AUTH_USER_MODEL = 'tixlbase.User' - +LOGIN_URL = '/login' +LOGIN_URL_CONTROL = '/control/login' # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/dev/howto/static-files/ diff --git a/src/tixl/urls.py b/src/tixl/urls.py index 26a762cf3..1c8cc780b 100644 --- a/src/tixl/urls.py +++ b/src/tixl/urls.py @@ -1,10 +1,10 @@ from django.conf.urls import patterns, include, url from django.contrib import admin -urlpatterns = patterns('', - # Examples: - # url(r'^$', 'tixl.views.home', name='home'), - # url(r'^blog/', include('blog.urls')), +import tixlcontrol.urls + +urlpatterns = patterns('', + url(r'^control/', include(tixlcontrol.urls, namespace='control')), url(r'^admin/', include(admin.site.urls)), ) diff --git a/src/tixlcontrol/middleware.py b/src/tixlcontrol/middleware.py new file mode 100644 index 000000000..bfcd8e944 --- /dev/null +++ b/src/tixlcontrol/middleware.py @@ -0,0 +1,39 @@ +from django.conf import settings +from django.core.urlresolvers import resolve +from django.utils.encoding import force_str +from django.utils.six.moves.urllib.parse import urlparse +from django.shortcuts import resolve_url +from django.contrib.auth import REDIRECT_FIELD_NAME + + +class LoginRequiredMiddleware: + + """ + This middleware enforces all requests to the control app + to require login. + """ + + EXCEPTIONS = ( + "login" + ) + + def process_request(self, request): + if not request.user.is_authenticated(): + url_namespace = resolve(request.path_info).namespace + url_name = resolve(request.path_info).url_name + if url_namespace == 'control' and url_name not in self.EXCEPTIONS: + # Taken from django/contrib/auth/decorators.py + path = request.build_absolute_uri() + # urlparse chokes on lazy objects in Python 3, force to str + resolved_login_url = force_str( + resolve_url(settings.LOGIN_URL_CONTROL)) + # If the login url is the same scheme and net location then just + # use the path as the "next" url. + login_scheme, login_netloc = urlparse(resolved_login_url)[:2] + current_scheme, current_netloc = urlparse(path)[:2] + if ((not login_scheme or login_scheme == current_scheme) and + (not login_netloc or login_netloc == current_netloc)): + path = request.get_full_path() + from django.contrib.auth.views import redirect_to_login + return redirect_to_login( + path, resolved_login_url, REDIRECT_FIELD_NAME) diff --git a/src/tixlcontrol/urls.py b/src/tixlcontrol/urls.py new file mode 100644 index 000000000..29bbb7f64 --- /dev/null +++ b/src/tixlcontrol/urls.py @@ -0,0 +1,5 @@ +from django.conf.urls import patterns, url + +urlpatterns = patterns('', + url(r'^$', 'tixlcontrol.views.main.index', name='index'), +) diff --git a/src/tixlcontrol/views.py b/src/tixlcontrol/views.py deleted file mode 100644 index 91ea44a21..000000000 --- a/src/tixlcontrol/views.py +++ /dev/null @@ -1,3 +0,0 @@ -from django.shortcuts import render - -# Create your views here. diff --git a/src/tixlcontrol/views/__init__.py b/src/tixlcontrol/views/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/tixlcontrol/views/main.py b/src/tixlcontrol/views/main.py new file mode 100644 index 000000000..8090b1d99 --- /dev/null +++ b/src/tixlcontrol/views/main.py @@ -0,0 +1,5 @@ +from django.http import HttpResponse + + +def index(request): + return HttpResponse('Coming soon.') From cd08316f2e60cee51de524f0448c523a71cfc101 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 20:57:04 +0200 Subject: [PATCH 05/11] Static files management / LessCSS compiler / Bootstrap import --- .gitmodules | 3 +++ doc/development/setup.rst | 11 +++++++++-- src/.gitignore | 1 + src/requirements.txt | 5 +++++ src/setup.cfg | 2 +- src/tixl/settings.py | 19 +++++++++++++++++++ src/tixl/urls.py | 6 ++++++ src/tixlbase/static/bootstrap | 1 + 8 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 160000 src/tixlbase/static/bootstrap diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..1b35dd803 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "src/tixlbase/static/bootstrap"] + path = src/tixlbase/static/bootstrap + url = https://github.com/twbs/bootstrap.git diff --git a/doc/development/setup.rst b/doc/development/setup.rst index 8bcb0b07e..86fd2fdfa 100644 --- a/doc/development/setup.rst +++ b/doc/development/setup.rst @@ -3,11 +3,18 @@ The development setup Obtain a copy of the source code -------------------------------- -Just clone our git repository:: +Just clone our git repository including its submodules:: - git clone https://github.com/tixl/tixl.git + git clone --recursive https://github.com/tixl/tixl.git cd tixl/ +Dependencies +------------ +* Python 3.4 or newer +* ``pip`` for Python 3 +* ``git`` +* ``lessc`` (Debian package: ``node-less``) + Your local python environment ----------------------------- diff --git a/src/.gitignore b/src/.gitignore index 61d9f8026..6bfabcc70 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -8,3 +8,4 @@ *~ .ropeproject __pycache__/ +_static/ diff --git a/src/requirements.txt b/src/requirements.txt index 4cba13667..bbcb44031 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,4 +1,9 @@ Django>=1.7 +django-compressor +BeautifulSoup4 +html5lib +slimit +lxml pyflakes pep8 pep8-naming diff --git a/src/setup.cfg b/src/setup.cfg index ec50166d3..ff9ae5876 100644 --- a/src/setup.cfg +++ b/src/setup.cfg @@ -1,5 +1,5 @@ [flake8] ignore = E128 max-line-length = 160 -exclude = tests,migrations,.ropeproject +exclude = tests,migrations,.ropeproject,static max-complexity = 12 diff --git a/src/tixl/settings.py b/src/tixl/settings.py index 2ed009068..3060dbbbf 100644 --- a/src/tixl/settings.py +++ b/src/tixl/settings.py @@ -39,6 +39,7 @@ INSTALLED_APPS = ( 'tixlbase', 'tixlcontrol', 'tixlpresale', + 'compressor', ) MIDDLEWARE_CLASSES = ( @@ -92,6 +93,24 @@ LOGIN_URL_CONTROL = '/control/login' STATIC_URL = '/static/' +STATIC_ROOT = '_static' + +STATICFILES_FINDERS = ( + 'django.contrib.staticfiles.finders.FileSystemFinder', + 'django.contrib.staticfiles.finders.AppDirectoriesFinder', + 'compressor.finders.CompressorFinder', +) + +COMPRESS_PRECOMPILERS = ( + ('text/less', 'lessc {infile} {outfile}'), +) + +COMPRESS_CSS_FILTERS = ( + 'compressor.filters.css_default.CssAbsoluteFilter', + 'compressor.filters.cssmin.CSSMinFilter', +) + + try: from local_settings import * except ImportError: diff --git a/src/tixl/urls.py b/src/tixl/urls.py index 1c8cc780b..82512bd26 100644 --- a/src/tixl/urls.py +++ b/src/tixl/urls.py @@ -1,5 +1,6 @@ from django.conf.urls import patterns, include, url from django.contrib import admin +from django.conf import settings import tixlcontrol.urls @@ -8,3 +9,8 @@ urlpatterns = patterns('', url(r'^control/', include(tixlcontrol.urls, namespace='control')), url(r'^admin/', include(admin.site.urls)), ) + +if settings.DEBUG: + urlpatterns += patterns('django.contrib.staticfiles.views', + url(r'^static/(?P.*)$', 'serve'), + ) diff --git a/src/tixlbase/static/bootstrap b/src/tixlbase/static/bootstrap new file mode 160000 index 000000000..97027a2f6 --- /dev/null +++ b/src/tixlbase/static/bootstrap @@ -0,0 +1 @@ +Subproject commit 97027a2f6fad00c4d74fbef5aef6cccb179f8229 From 6eae4c243b89d1037bd525d9e401d3cd2a75a687 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 21:47:13 +0200 Subject: [PATCH 06/11] Fix crucial bug --- src/tixlbase/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tixlbase/models.py b/src/tixlbase/models.py index de508aa49..065b68844 100644 --- a/src/tixlbase/models.py +++ b/src/tixlbase/models.py @@ -11,7 +11,7 @@ class UserManager(BaseUserManager): def create_user(self, identifier, username, password=None): user = self.model(identifier=identifier) - user.set_password(user) + user.set_password(password) user.save() return user @@ -21,7 +21,7 @@ class UserManager(BaseUserManager): user = self.model(identifier=identifier, username=username) user.is_staff = True user.is_superuser = True - user.set_password(user) + user.set_password(password) user.save() return user From fe8fcb6f6bcaf120cce78da1e589cbbd347c1b54 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 21:48:37 +0200 Subject: [PATCH 07/11] tixlcontrol: Login form --- src/requirements.txt | 1 + src/tixl/settings.py | 1 + src/tixlcontrol/middleware.py | 2 +- .../static/tixlcontrol/less/auth.less | 22 ++++++++ .../templates/tixlcontrol/auth/base.html | 16 ++++++ .../templates/tixlcontrol/auth/login.html | 15 +++++ src/tixlcontrol/urls.py | 1 + src/tixlcontrol/views/auth.py | 55 +++++++++++++++++++ 8 files changed, 112 insertions(+), 1 deletion(-) create mode 100644 src/tixlcontrol/static/tixlcontrol/less/auth.less create mode 100644 src/tixlcontrol/templates/tixlcontrol/auth/base.html create mode 100644 src/tixlcontrol/templates/tixlcontrol/auth/login.html create mode 100644 src/tixlcontrol/views/auth.py diff --git a/src/requirements.txt b/src/requirements.txt index bbcb44031..4af31dbad 100644 --- a/src/requirements.txt +++ b/src/requirements.txt @@ -1,4 +1,5 @@ Django>=1.7 +django-bootstrap3 django-compressor BeautifulSoup4 html5lib diff --git a/src/tixl/settings.py b/src/tixl/settings.py index 3060dbbbf..3249e59df 100644 --- a/src/tixl/settings.py +++ b/src/tixl/settings.py @@ -40,6 +40,7 @@ INSTALLED_APPS = ( 'tixlcontrol', 'tixlpresale', 'compressor', + 'bootstrap3', ) MIDDLEWARE_CLASSES = ( diff --git a/src/tixlcontrol/middleware.py b/src/tixlcontrol/middleware.py index bfcd8e944..63a97f89e 100644 --- a/src/tixlcontrol/middleware.py +++ b/src/tixlcontrol/middleware.py @@ -14,7 +14,7 @@ class LoginRequiredMiddleware: """ EXCEPTIONS = ( - "login" + "auth.login" ) def process_request(self, request): diff --git a/src/tixlcontrol/static/tixlcontrol/less/auth.less b/src/tixlcontrol/static/tixlcontrol/less/auth.less new file mode 100644 index 000000000..be6422a7f --- /dev/null +++ b/src/tixlcontrol/static/tixlcontrol/less/auth.less @@ -0,0 +1,22 @@ +@import "../../../../tixlbase/static/bootstrap/less/bootstrap.less"; + +body { + background: #eee; +} + +.form-signin { + .well; + + max-width: 330px; + margin: auto; + margin-top: 10%; + padding-bottom: 0; + + .control-label { + .sr-only; + } + + .buttons { + text-align: right; + } +} diff --git a/src/tixlcontrol/templates/tixlcontrol/auth/base.html b/src/tixlcontrol/templates/tixlcontrol/auth/base.html new file mode 100644 index 000000000..a4fe3a109 --- /dev/null +++ b/src/tixlcontrol/templates/tixlcontrol/auth/base.html @@ -0,0 +1,16 @@ +{% load compress %} +{% load staticfiles %} + + + + + {% compress css %} + + {% endcompress %} + + +
+ {% block content %} + {% endblock %} + + diff --git a/src/tixlcontrol/templates/tixlcontrol/auth/login.html b/src/tixlcontrol/templates/tixlcontrol/auth/login.html new file mode 100644 index 000000000..b4897405c --- /dev/null +++ b/src/tixlcontrol/templates/tixlcontrol/auth/login.html @@ -0,0 +1,15 @@ +{% extends "tixlcontrol/auth/base.html" %} +{% load bootstrap3 %} +{% block content %} + +{% endblock %} diff --git a/src/tixlcontrol/urls.py b/src/tixlcontrol/urls.py index 29bbb7f64..3f6fc7363 100644 --- a/src/tixlcontrol/urls.py +++ b/src/tixlcontrol/urls.py @@ -2,4 +2,5 @@ from django.conf.urls import patterns, url urlpatterns = patterns('', url(r'^$', 'tixlcontrol.views.main.index', name='index'), + url(r'^login$', 'tixlcontrol.views.auth.login', name='auth.login'), ) diff --git a/src/tixlcontrol/views/auth.py b/src/tixlcontrol/views/auth.py new file mode 100644 index 000000000..c5b651522 --- /dev/null +++ b/src/tixlcontrol/views/auth.py @@ -0,0 +1,55 @@ +from django.shortcuts import render, redirect +from django.contrib.auth.forms import AuthenticationForm as BaseAuthenticationForm +from django import forms +from django.utils.translation import ugettext as _ +from django.contrib.auth import authenticate +from django.contrib.auth import login as auth_login + + +class AuthenticationForm(BaseAuthenticationForm): + """ + The login form. + """ + email = forms.EmailField(label=_("E-mail address"), max_length=254) + password = forms.CharField(label=_("Password"), widget=forms.PasswordInput) + username = None + + error_messages = { + 'invalid_login': _("Please enter a correct e-mail address and password."), + 'inactive': _("This account is inactive."), + } + + def __init__(self, request=None, *args, **kwargs): + self.request = request + self.user_cache = None + super(forms.Form, self).__init__(*args, **kwargs) + + def clean(self): + email = self.cleaned_data.get('email') + password = self.cleaned_data.get('password') + + if email and password: + self.user_cache = authenticate(identifier=email.lower(), + password=password) + if self.user_cache is None: + raise forms.ValidationError( + self.error_messages['invalid_login'], + code='invalid_login', + ) + else: + self.confirm_login_allowed(self.user_cache) + + return self.cleaned_data + + +def login(request): + ctx = {} + if request.method == 'POST': + form = AuthenticationForm(data=request.POST) + if form.is_valid() and form.user_cache: + auth_login(request, form.user_cache) + return redirect('control:index') + else: + form = AuthenticationForm() + ctx['form'] = form + return render(request, 'tixlcontrol/auth/login.html', ctx) From a6699a39c0c59172f6bea240f9f2d10086ac732a Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 21:48:56 +0200 Subject: [PATCH 08/11] Documentation: Add "runserver" command --- doc/development/setup.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/doc/development/setup.rst b/doc/development/setup.rst index 86fd2fdfa..423d9eafb 100644 --- a/doc/development/setup.rst +++ b/doc/development/setup.rst @@ -36,6 +36,15 @@ Then, create the local database:: python manage.py syncdb +Run the development server +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Execute:: + + python manage.py runserver + +to start a local development webserver on port 8000. + Static code checks ^^^^^^^^^^^^^^^^^^ From a54001b1a28aba0f02c9cb34cd91796bbbe7ee2d Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 22:47:25 +0200 Subject: [PATCH 09/11] Redirect logged in users from login page --- src/tixlcontrol/views/auth.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tixlcontrol/views/auth.py b/src/tixlcontrol/views/auth.py index c5b651522..5f8cc9011 100644 --- a/src/tixlcontrol/views/auth.py +++ b/src/tixlcontrol/views/auth.py @@ -44,6 +44,8 @@ class AuthenticationForm(BaseAuthenticationForm): def login(request): ctx = {} + if request.user.is_authenticated(): + return redirect('control:index') if request.method == 'POST': form = AuthenticationForm(data=request.POST) if form.is_valid() and form.user_cache: From 5243bba883b2abeca575d5a7f3168d8aec4ab100 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Wed, 10 Sep 2014 22:48:48 +0200 Subject: [PATCH 10/11] Login parameter "next" --- src/tixlcontrol/views/auth.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tixlcontrol/views/auth.py b/src/tixlcontrol/views/auth.py index 5f8cc9011..51505b5c6 100644 --- a/src/tixlcontrol/views/auth.py +++ b/src/tixlcontrol/views/auth.py @@ -45,11 +45,15 @@ class AuthenticationForm(BaseAuthenticationForm): def login(request): ctx = {} if request.user.is_authenticated(): + if "next" in request.GET: + return redirect(request.GET.get("next", 'control:index')) return redirect('control:index') if request.method == 'POST': form = AuthenticationForm(data=request.POST) if form.is_valid() and form.user_cache: auth_login(request, form.user_cache) + if "next" in request.GET: + return redirect(request.GET.get("next", 'control:index')) return redirect('control:index') else: form = AuthenticationForm() From cc6d624f57b8d62c3040c1c900d00bd5c0c732d9 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Thu, 11 Sep 2014 11:19:06 +0200 Subject: [PATCH 11/11] Starting with localisation --- doc/development/setup.rst | 12 ++- src/.gitignore | 1 + src/Makefile | 7 ++ src/locale/de/LC_MESSAGES/django.po | 84 +++++++++++++++++++ src/tixl/settings.py | 11 +++ .../templates/tixlcontrol/auth/login.html | 3 +- 6 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 src/Makefile create mode 100644 src/locale/de/LC_MESSAGES/django.po diff --git a/doc/development/setup.rst b/doc/development/setup.rst index 423d9eafb..079defba5 100644 --- a/doc/development/setup.rst +++ b/doc/development/setup.rst @@ -36,9 +36,18 @@ Then, create the local database:: python manage.py syncdb +Create the translation files +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +To generate updated translation files, run a:: + + make localegen + +To compile the language files for use, run:: + + make localecompile + Run the development server ^^^^^^^^^^^^^^^^^^^^^^^^^^ - Execute:: python manage.py runserver @@ -47,7 +56,6 @@ to start a local development webserver on port 8000. Static code checks ^^^^^^^^^^^^^^^^^^ - Before you check in your code into git, always run:: flake8 . diff --git a/src/.gitignore b/src/.gitignore index 6bfabcc70..0d27eb190 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -5,6 +5,7 @@ *.aux *.log *.toc +*.mo *~ .ropeproject __pycache__/ diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 000000000..75067417f --- /dev/null +++ b/src/Makefile @@ -0,0 +1,7 @@ +all: localecompile + +localecompile: + django-admin compilemessages + +localegen: + django-admin makemessages --all diff --git a/src/locale/de/LC_MESSAGES/django.po b/src/locale/de/LC_MESSAGES/django.po new file mode 100644 index 000000000..7f3cf9626 --- /dev/null +++ b/src/locale/de/LC_MESSAGES/django.po @@ -0,0 +1,84 @@ +# tixl translation file German +# Copyright (C) 2014 the tixl authors +# This file is distributed under the same license as the tixl package. +# Raphael Michel , 2014. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: 1\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-09-11 11:16+0200\n" +"PO-Revision-Date: 2014-09-11 11:05+200\n" +"Last-Translator: Raphael Michel \n" +"Language-Team: Raphael Michel \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" + +#: tixl/settings.py:92 +msgid "German" +msgstr "Deutsch" + +#: tixl/settings.py:93 +msgid "English" +msgstr "Englisch" + +#: tixlbase/admin.py:16 +msgid "The two password fields didn't match." +msgstr "Die beiden eingegebenen Passwörter stimmen nicht überein." + +#: tixlbase/admin.py:18 tixlcontrol/views/auth.py:14 +msgid "Password" +msgstr "Passwort" + +#: tixlbase/admin.py:20 +msgid "Password confirmation" +msgstr "Passwort bestätigen" + +#: tixlbase/admin.py:22 +msgid "Enter the same password as above, for verification." +msgstr "Geben Sie zur Bestätigung das selbe Passwort wie oben ein" + +#: tixlbase/admin.py:50 +msgid "Personal info" +msgstr "Persönliche Daten" + +#: tixlbase/admin.py:51 +msgid "Permissions" +msgstr "Berechtigungen" + +#: tixlbase/models.py:65 +msgid "Letters, digits and @/./+/-/_ only." +msgstr "Nur Buchstaben, Zahlen und @/./+/-/_" + +#: tixlbase/models.py:71 +msgid "E-mail" +msgstr "E-Mail" + +#: tixlbase/models.py:74 +msgid "Given name" +msgstr "Vorname" + +#: tixlbase/models.py:77 +msgid "Family name" +msgstr "Nachname" + +#: tixlcontrol/templates/tixlcontrol/auth/login.html:12 +msgid "Log in" +msgstr "Anmelden" + +#: tixlcontrol/views/auth.py:13 +msgid "E-mail address" +msgstr "E-Mail-Adresse" + +#: tixlcontrol/views/auth.py:18 +msgid "Please enter a correct e-mail address and password." +msgstr "" +"Bitte geben Sie eine gültige Kombination aus E-Mail-Adresse und Passwort ein." + +#: tixlcontrol/views/auth.py:19 +msgid "This account is inactive." +msgstr "Dieses Konto ist deaktiviert." diff --git a/src/tixl/settings.py b/src/tixl/settings.py index 3249e59df..2301caf0d 100644 --- a/src/tixl/settings.py +++ b/src/tixl/settings.py @@ -45,6 +45,7 @@ INSTALLED_APPS = ( MIDDLEWARE_CLASSES = ( 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', @@ -82,6 +83,16 @@ USE_L10N = True USE_TZ = True +LOCALE_PATHS = ( + 'locale', +) + +from django.utils.translation import ugettext_lazy as _ +LANGUAGES = ( + ('de', _('German')), + ('en', _('English')), +) + # Authentication diff --git a/src/tixlcontrol/templates/tixlcontrol/auth/login.html b/src/tixlcontrol/templates/tixlcontrol/auth/login.html index b4897405c..0d5f1ff0f 100644 --- a/src/tixlcontrol/templates/tixlcontrol/auth/login.html +++ b/src/tixlcontrol/templates/tixlcontrol/auth/login.html @@ -1,5 +1,6 @@ {% extends "tixlcontrol/auth/base.html" %} {% load bootstrap3 %} +{% load i18n %} {% block content %}