From befe27cdd01a0e796b5133dfb5b6f462a541f453 Mon Sep 17 00:00:00 2001 From: Raphael Michel Date: Tue, 9 Sep 2014 16:38:33 +0200 Subject: [PATCH] Django project, first models --- src/.gitignore | 10 ++ src/__init__.py | 0 src/manage.py | 10 ++ src/requirements.txt | 2 + src/tixl/__init__.py | 0 src/tixl/settings.py | 96 +++++++++++++++ src/tixl/urls.py | 10 ++ src/tixl/wsgi.py | 14 +++ src/tixlbase/__init__.py | 0 src/tixlbase/admin.py | 3 + src/tixlbase/migrations/0001_initial.py | 101 ++++++++++++++++ src/tixlbase/migrations/__init__.py | 0 src/tixlbase/models.py | 153 ++++++++++++++++++++++++ src/tixlbase/tests.py | 3 + src/tixlbase/views.py | 3 + src/tixlcontrol/__init__.py | 0 src/tixlcontrol/admin.py | 3 + src/tixlcontrol/migrations/__init__.py | 0 src/tixlcontrol/tests.py | 3 + src/tixlcontrol/views.py | 3 + src/tixlpresale/__init__.py | 0 src/tixlpresale/admin.py | 3 + src/tixlpresale/migrations/__init__.py | 0 src/tixlpresale/tests.py | 3 + src/tixlpresale/views.py | 3 + 25 files changed, 423 insertions(+) create mode 100644 src/.gitignore create mode 100644 src/__init__.py create mode 100755 src/manage.py create mode 100644 src/requirements.txt create mode 100644 src/tixl/__init__.py create mode 100644 src/tixl/settings.py create mode 100644 src/tixl/urls.py create mode 100644 src/tixl/wsgi.py create mode 100644 src/tixlbase/__init__.py create mode 100644 src/tixlbase/admin.py create mode 100644 src/tixlbase/migrations/0001_initial.py create mode 100644 src/tixlbase/migrations/__init__.py create mode 100644 src/tixlbase/models.py create mode 100644 src/tixlbase/tests.py create mode 100644 src/tixlbase/views.py create mode 100644 src/tixlcontrol/__init__.py create mode 100644 src/tixlcontrol/admin.py create mode 100644 src/tixlcontrol/migrations/__init__.py create mode 100644 src/tixlcontrol/tests.py create mode 100644 src/tixlcontrol/views.py create mode 100644 src/tixlpresale/__init__.py create mode 100644 src/tixlpresale/admin.py create mode 100644 src/tixlpresale/migrations/__init__.py create mode 100644 src/tixlpresale/tests.py create mode 100644 src/tixlpresale/views.py diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000000..61d9f80265 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,10 @@ +*.sqlite3 +*.db +*.py[cod] +*.swp +*.aux +*.log +*.toc +*~ +.ropeproject +__pycache__/ diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/manage.py b/src/manage.py new file mode 100755 index 0000000000..060622ed8d --- /dev/null +++ b/src/manage.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python +import os +import sys + +if __name__ == "__main__": + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tixl.settings") + + from django.core.management import execute_from_command_line + + execute_from_command_line(sys.argv) diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000000..3240b18ca1 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,2 @@ +Django>=1.7 +pyflakes diff --git a/src/tixl/__init__.py b/src/tixl/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tixl/settings.py b/src/tixl/settings.py new file mode 100644 index 0000000000..4be0c19e54 --- /dev/null +++ b/src/tixl/settings.py @@ -0,0 +1,96 @@ +""" +Django settings for tixl project. + +For more information on this file, see +https://docs.djangoproject.com/en/dev/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/dev/ref/settings/ +""" + +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +import os +BASE_DIR = os.path.dirname(os.path.dirname(__file__)) + + +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/dev/howto/deployment/checklist/ + +# SECURITY WARNING: keep the secret key used in production secret! +SECRET_KEY = '0ro^46+8k#dv3ej=oen-2ww)i30#$$^&x&eajyj&_&h)$nc6@5' + +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True + +TEMPLATE_DEBUG = True + +ALLOWED_HOSTS = [] + + +# Application definition + +INSTALLED_APPS = ( + 'django.contrib.admin', + 'django.contrib.auth', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.messages', + 'django.contrib.staticfiles', + 'tixlbase', + 'tixlcontrol', + 'tixlpresale', +) + +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + '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', +) + +ROOT_URLCONF = 'tixl.urls' + +WSGI_APPLICATION = 'tixl.wsgi.application' + + +# Database +# https://docs.djangoproject.com/en/dev/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), + } +} + +# Internationalization +# https://docs.djangoproject.com/en/dev/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + + +# Authentication + +AUTH_USER_MODEL = 'tixlbase.User' + + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/dev/howto/static-files/ + +STATIC_URL = '/static/' + +try: + from local_settings import * +except ImportError: + pass diff --git a/src/tixl/urls.py b/src/tixl/urls.py new file mode 100644 index 0000000000..26a762cf38 --- /dev/null +++ b/src/tixl/urls.py @@ -0,0 +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')), + + url(r'^admin/', include(admin.site.urls)), +) diff --git a/src/tixl/wsgi.py b/src/tixl/wsgi.py new file mode 100644 index 0000000000..5bb1d62cda --- /dev/null +++ b/src/tixl/wsgi.py @@ -0,0 +1,14 @@ +""" +WSGI config for tixl project. + +It exposes the WSGI callable as a module-level variable named ``application``. + +For more information on this file, see +https://docs.djangoproject.com/en/dev/howto/deployment/wsgi/ +""" + +import os +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tixl.settings") + +from django.core.wsgi import get_wsgi_application +application = get_wsgi_application() diff --git a/src/tixlbase/__init__.py b/src/tixlbase/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tixlbase/admin.py b/src/tixlbase/admin.py new file mode 100644 index 0000000000..8c38f3f3da --- /dev/null +++ b/src/tixlbase/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/tixlbase/migrations/0001_initial.py b/src/tixlbase/migrations/0001_initial.py new file mode 100644 index 0000000000..d435f936d0 --- /dev/null +++ b/src/tixlbase/migrations/0001_initial.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.utils.timezone +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('auth', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='User', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('password', models.CharField(verbose_name='password', max_length=128)), + ('last_login', models.DateTimeField(verbose_name='last login', default=django.utils.timezone.now)), + ('is_superuser', models.BooleanField(verbose_name='superuser status', default=False, help_text='Designates that this user has all permissions without explicitly assigning them.')), + ('identifier', models.CharField(unique=True, max_length=255)), + ('username', models.CharField(max_length=120)), + ('email', models.EmailField(blank=True, null=True, db_index=True, max_length=75)), + ('is_active', models.BooleanField(default=True)), + ('is_staff', models.BooleanField(default=False)), + ('date_joined', models.DateTimeField(auto_now_add=True)), + ], + options={ + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('name', models.CharField(max_length=200)), + ('slug', models.CharField(db_index=True, max_length=50)), + ('locale', models.CharField(max_length=10)), + ('currency', models.CharField(max_length=10)), + ('date_from', models.DateTimeField()), + ('date_to', models.DateTimeField(blank=True, null=True)), + ('show_date_to', models.BooleanField(default=True)), + ('show_times', models.BooleanField(default=True)), + ('presale_end', models.DateTimeField(blank=True, null=True)), + ('presale_start', models.DateTimeField(blank=True, null=True)), + ('payment_term_days', models.IntegerField(default=14)), + ('payment_term_last', models.DateTimeField(blank=True, null=True)), + ], + options={ + 'ordering': ('date_from', 'name'), + }, + bases=(models.Model,), + ), + migrations.CreateModel( + name='Organizer', + fields=[ + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('name', models.CharField(max_length=200)), + ('slug', models.CharField(unique=True, db_index=True, max_length=50)), + ('owner', models.ForeignKey(blank=True, null=True, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('name',), + }, + bases=(models.Model,), + ), + migrations.AddField( + model_name='event', + name='organizer', + field=models.ForeignKey(to='tixlbase.Organizer', related_name='events'), + preserve_default=True, + ), + migrations.AlterUniqueTogether( + name='event', + unique_together=set([('organizer', 'slug')]), + ), + migrations.AddField( + model_name='user', + name='event', + field=models.ForeignKey(to='tixlbase.Event', blank=True, null=True, related_name='users'), + preserve_default=True, + ), + migrations.AddField( + model_name='user', + name='groups', + field=models.ManyToManyField(verbose_name='groups', related_name='user_set', related_query_name='user', blank=True, to='auth.Group', help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.'), + preserve_default=True, + ), + migrations.AddField( + model_name='user', + name='user_permissions', + field=models.ManyToManyField(verbose_name='user permissions', related_name='user_set', related_query_name='user', blank=True, to='auth.Permission', help_text='Specific permissions for this user.'), + preserve_default=True, + ), + migrations.AlterUniqueTogether( + name='user', + unique_together=set([('event', 'username')]), + ), + ] diff --git a/src/tixlbase/migrations/__init__.py b/src/tixlbase/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tixlbase/models.py b/src/tixlbase/models.py new file mode 100644 index 0000000000..45674511d2 --- /dev/null +++ b/src/tixlbase/models.py @@ -0,0 +1,153 @@ +from django.db import models +from django.contrib.auth.models import AbstractBaseUser, BaseUserManager, PermissionsMixin + + +class UserManager(BaseUserManager): + """ + This is the user manager for our custom user model. See the User + 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) + user.save() + return user + + def create_superuser(self, email, password=None): + if password is None: + raise Exception("You must provide a password") + user = self.model(email=email) + user.is_staff = True + user.is_superuser = True + user.set_password(user) + user.save() + return user + + +class User(AbstractBaseUser, PermissionsMixin): + """ + This is the user model used by tixl for authentication. + Handling users is somehow complicated, as we try to have two + classes of users in one system: + (1) We want global users who can just login into tixl and + buy tickets for multiple events -- we also need those + global users for event organizers who should not need + multiple users for managing multiple events. + (2) We want local users who exist only in the scope of a + certain event + The hard part is to find a primary key to identify all of these + users. Letting the users choose usernames is a bad idea, as + the primary key needs to be unique and there is no reason for a + local user to block a name for all time. Using e-mail addresses + is not a good idea either, for two reasons: First, a user might + have multiple local users (so they are not unique), and second, + it should be possible to create anonymous users without having + to supply an e-mail address. + Therefore, we use an abstract "identifier" field as the primary + key. The identifier is: + (1) the e-mail address for global users. An e-mail address + is and should be required for them and global users use + their e-mail address for login. + (2) "{username}@{event.id}.event.tixl" for local users, who + use their username to login on the event page. + The model's save() method automatically fills the identifier field + according to this scheme when it is empty. The __str__() method + returns the identifier. + + The is_staff field is only True for system operators. + """ + + identifier = models.CharField(max_length=255, unique=True) + username = models.CharField(max_length=120) + event = models.ForeignKey('Event', related_name="users", + null=True, blank=True) + email = models.EmailField(unique=False, db_index=True, + null=True, blank=True) + is_active = models.BooleanField(default=True) + is_staff = models.BooleanField(default=False) + date_joined = models.DateTimeField(auto_now_add=True) + + objects = UserManager() + + def __str__(self): + return self.identifier + + def save(self, *args, **kwargs): + if self.identifier is None: + if self.event is None: + self.identifier = self.email + else: + self.identifier = "%s@%d.event.tixl" % (self.username, self.event.id) + super().save(*args, **kwargs) + + USERNAME_FIELD = 'identifier' + REQUIRED_FIELDS = ['username'] + + class Meta: + unique_together = (("event", "username"),) + + +class Organizer(models.Model): + """ + This model represents an entity organizing events, like a company, + an organization or a person. It has one user as owner (who has + registered it) and can have any number of users with admin + authorization. Any organizer has a unique slug, which is a short + name (alphanumeric, all lowercase) being used in URLs. + """ + + name = models.CharField(max_length=200) + slug = models.CharField(max_length=50, + unique=True, + db_index=True) + owner = models.ForeignKey(User, null=True, blank=True) + + class Meta: + ordering = ("name",) + + +class Event(models.Model): + """ + This model represents an event. An event is anything you can buy + tickets for. It belongs to one orgnaizer and has a name and a slug, + the latter being a short, alphanumeric, all-lowercase name being + used in URLs. The slug has to be unique among the events of the same + organizer. + + An event can hold several properties, such as a default locale and + currency. + + The event has date_from and date_to field which mark the actual + datetime of the event itself. The show_date_to and show_times + fields are used to control the display of these dates. (Without + show_times only days are shown, now times.) + + The presale_start and presale_end fields mark the time frame in + which tickets are sold for this event. These two dates override + every other restrictions to ticket sale if set. + + The payment_term_days field holds the number of days after + submitting a ticket order, in which the ticket has to be paid. + The payment_term_last is the day all orders must be paid by, no + matter when they were ordered (and thus, ignoring payment_term_days). + """ + + organizer = models.ForeignKey(Organizer, related_name="events") + name = models.CharField(max_length=200) + slug = models.CharField(max_length=50, + db_index=True) + locale = models.CharField(max_length=10) + currency = models.CharField(max_length=10) + date_from = models.DateTimeField() + date_to = models.DateTimeField(null=True, blank=True) + show_date_to = models.BooleanField(default=True) + show_times = models.BooleanField(default=True) + presale_end = models.DateTimeField(null=True, blank=True) + presale_start = models.DateTimeField(null=True, blank=True) + payment_term_days = models.IntegerField(default=14) + payment_term_last = models.DateTimeField(null=True, blank=True) + + class Meta: + unique_together = (("organizer", "slug"),) + ordering = ("date_from", "name") diff --git a/src/tixlbase/tests.py b/src/tixlbase/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/src/tixlbase/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/tixlbase/views.py b/src/tixlbase/views.py new file mode 100644 index 0000000000..91ea44a218 --- /dev/null +++ b/src/tixlbase/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/tixlcontrol/__init__.py b/src/tixlcontrol/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tixlcontrol/admin.py b/src/tixlcontrol/admin.py new file mode 100644 index 0000000000..8c38f3f3da --- /dev/null +++ b/src/tixlcontrol/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/tixlcontrol/migrations/__init__.py b/src/tixlcontrol/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tixlcontrol/tests.py b/src/tixlcontrol/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/src/tixlcontrol/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/tixlcontrol/views.py b/src/tixlcontrol/views.py new file mode 100644 index 0000000000..91ea44a218 --- /dev/null +++ b/src/tixlcontrol/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/src/tixlpresale/__init__.py b/src/tixlpresale/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tixlpresale/admin.py b/src/tixlpresale/admin.py new file mode 100644 index 0000000000..8c38f3f3da --- /dev/null +++ b/src/tixlpresale/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/src/tixlpresale/migrations/__init__.py b/src/tixlpresale/migrations/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/tixlpresale/tests.py b/src/tixlpresale/tests.py new file mode 100644 index 0000000000..7ce503c2dd --- /dev/null +++ b/src/tixlpresale/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/src/tixlpresale/views.py b/src/tixlpresale/views.py new file mode 100644 index 0000000000..91ea44a218 --- /dev/null +++ b/src/tixlpresale/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here.