Merge branch 'master' of github.com:tixl/tixl

This commit is contained in:
Raphael Michel
2014-09-11 19:23:04 +02:00
24 changed files with 501 additions and 29 deletions
+3
View File
@@ -0,0 +1,3 @@
[submodule "src/tixlbase/static/bootstrap"]
path = src/tixlbase/static/bootstrap
url = https://github.com/twbs/bootstrap.git
+2 -2
View File
@@ -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
+28 -5
View File
@@ -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
-----------------------------
@@ -29,13 +36,29 @@ 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
to start a local development webserver on port 8000.
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.
+2
View File
@@ -5,6 +5,8 @@
*.aux
*.log
*.toc
*.mo
*~
.ropeproject
__pycache__/
_static/
+7
View File
@@ -0,0 +1,7 @@
all: localecompile
localecompile:
django-admin compilemessages
localegen:
django-admin makemessages --all
+84
View File
@@ -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 <michel@rami.io>, 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 <michel@rami.io>\n"
"Language-Team: Raphael Michel <michel@rami.io>\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."
+8
View File
@@ -1,3 +1,11 @@
Django>=1.7
django-bootstrap3
django-compressor
BeautifulSoup4
html5lib
slimit
lxml
pyflakes
pep8
pep8-naming
flake8
+5
View File
@@ -0,0 +1,5 @@
[flake8]
ignore = E128
max-line-length = 160
exclude = tests,migrations,.ropeproject,static
max-complexity = 12
+34 -1
View File
@@ -39,16 +39,20 @@ INSTALLED_APPS = (
'tixlbase',
'tixlcontrol',
'tixlpresale',
'compressor',
'bootstrap3',
)
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'tixlcontrol.middleware.LoginRequiredMiddleware',
)
ROOT_URLCONF = 'tixl.urls'
@@ -79,17 +83,46 @@ USE_L10N = True
USE_TZ = True
LOCALE_PATHS = (
'locale',
)
from django.utils.translation import ugettext_lazy as _
LANGUAGES = (
('de', _('German')),
('en', _('English')),
)
# 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/
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:
+10 -4
View File
@@ -1,10 +1,16 @@
from django.conf.urls import patterns, include, url
from django.contrib import admin
from django.conf import settings
import tixlcontrol.urls
urlpatterns = patterns('',
# Examples:
# url(r'^$', 'tixl.views.home', name='home'),
# url(r'^blog/', include('blog.urls')),
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<path>.*)$', 'serve'),
)
+58 -1
View File
@@ -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)
@@ -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,
),
]
@@ -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.'),
),
]
+49 -13
View File
@@ -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,19 +9,19 @@ 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)
user.set_password(user)
def create_user(self, identifier, username, password=None):
user = self.model(identifier=identifier)
user.set_password(password)
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)
user.set_password(password)
user.save()
return 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)
+39
View File
@@ -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 = (
"auth.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)
@@ -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;
}
}
@@ -0,0 +1,16 @@
{% load compress %}
{% load staticfiles %}
<!DOCTYPE html>
<html>
<head>
<title></title>
{% compress css %}
<link rel="stylesheet" type="text/less" href="{% static "tixlcontrol/less/auth.less" %}" />
{% endcompress %}
</head>
<body>
<div class="container">
{% block content %}
{% endblock %}
</body>
</html>
@@ -0,0 +1,16 @@
{% extends "tixlcontrol/auth/base.html" %}
{% load bootstrap3 %}
{% load i18n %}
{% block content %}
<form class="form-signin" action="" method="post">
{% bootstrap_form_errors form type='all' layout='inline' %}
{% csrf_token %}
{% bootstrap_field form.email %}
{% bootstrap_field form.password %}
<div class="form-group buttons">
<button type="submit" class="btn btn-primary">
{% trans "Log in" %}
</button>
</div>
</form>
{% endblock %}
+6
View File
@@ -0,0 +1,6 @@
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'),
)
-3
View File
@@ -1,3 +0,0 @@
from django.shortcuts import render
# Create your views here.
View File
+61
View File
@@ -0,0 +1,61 @@
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.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()
ctx['form'] = form
return render(request, 'tixlcontrol/auth/login.html', ctx)
+5
View File
@@ -0,0 +1,5 @@
from django.http import HttpResponse
def index(request):
return HttpResponse('Coming soon.')