From 4322318c31c889a33945a31113ca02bd6d351322 Mon Sep 17 00:00:00 2001 From: Skia Date: Fri, 29 Jan 2016 15:20:00 +0100 Subject: [PATCH] Club model implementation, various other changes --- accounting/migrations/0001_initial.py | 46 +++++------ .../migrations/0002_auto_20160128_1538.py | 39 ---------- club/__init__.py | 0 club/admin.py | 8 ++ club/migrations/0001_initial.py | 38 +++++++++ club/migrations/__init__.py | 0 club/models.py | 77 +++++++++++++++++++ club/tests.py | 3 + club/views.py | 3 + core/management/commands/setup.py | 5 ++ core/migrations/0001_initial.py | 57 +++++++------- core/migrations/0002_auto_20151215_0827.py | 19 ----- core/migrations/0003_auto_20160111_0900.py | 19 ----- core/migrations/0004_auto_20160128_0835.py | 19 ----- core/migrations/0005_auto_20160128_0842.py | 19 ----- core/models.py | 18 ++--- core/views/__init__.py | 2 + core/views/group.py | 5 +- requirements.txt | 1 + sith/settings.py | 19 +++++ sith/urls.py | 2 +- subscription/admin.py | 4 +- subscription/migrations/0001_initial.py | 34 +++++--- subscription/models.py | 28 +++++-- subscription/urls.py | 2 +- subscription/views.py | 4 +- 26 files changed, 267 insertions(+), 204 deletions(-) delete mode 100644 accounting/migrations/0002_auto_20160128_1538.py create mode 100644 club/__init__.py create mode 100644 club/admin.py create mode 100644 club/migrations/0001_initial.py create mode 100644 club/migrations/__init__.py create mode 100644 club/models.py create mode 100644 club/tests.py create mode 100644 club/views.py delete mode 100644 core/migrations/0002_auto_20151215_0827.py delete mode 100644 core/migrations/0003_auto_20160111_0900.py delete mode 100644 core/migrations/0004_auto_20160128_0835.py delete mode 100644 core/migrations/0005_auto_20160128_0842.py diff --git a/accounting/migrations/0001_initial.py b/accounting/migrations/0001_initial.py index ad4ac36b..12a56d0f 100644 --- a/accounting/migrations/0001_initial.py +++ b/accounting/migrations/0001_initial.py @@ -9,64 +9,64 @@ import accounting.models class Migration(migrations.Migration): dependencies = [ - ('core', '0005_auto_20160128_0842'), + ('core', '0001_initial'), ] operations = [ migrations.CreateModel( name='Customer', fields=[ - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, primary_key=True, serialize=False)), - ('account_id', models.CharField(unique=True, verbose_name='account id', max_length=10)), + ('user', models.OneToOneField(primary_key=True, to=settings.AUTH_USER_MODEL, serialize=False)), + ('account_id', models.CharField(max_length=10, unique=True, verbose_name='account id')), ], options={ - 'verbose_name': 'customer', 'verbose_name_plural': 'customers', + 'verbose_name': 'customer', }, ), migrations.CreateModel( name='GeneralJournal', fields=[ - ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('start_date', models.DateField(verbose_name='start date')), - ('end_date', models.DateField(null=True, verbose_name='end date', blank=True, default=None)), - ('name', models.CharField(verbose_name='name', max_length=30)), + ('end_date', models.DateField(blank=True, default=None, null=True, verbose_name='end date')), + ('name', models.CharField(max_length=30, verbose_name='name')), ('closed', models.BooleanField(default=False, verbose_name='is closed')), ], ), migrations.CreateModel( name='GenericInvoice', fields=[ - ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), - ('name', models.CharField(verbose_name='name', max_length=100)), - ('journal', models.ForeignKey(to='accounting.GeneralJournal', related_name='invoices')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, verbose_name='name')), + ('journal', models.ForeignKey(related_name='invoices', to='accounting.GeneralJournal')), ], ), migrations.CreateModel( name='Product', fields=[ - ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), - ('name', models.CharField(verbose_name='name', max_length=30)), - ('description', models.TextField(verbose_name='description')), - ('code', models.CharField(verbose_name='code', max_length=10)), - ('purchase_price', accounting.models.CurrencyField(max_digits=12, decimal_places=2, verbose_name='purchase price')), - ('selling_price', accounting.models.CurrencyField(max_digits=12, decimal_places=2, verbose_name='selling price')), - ('special_selling_price', accounting.models.CurrencyField(max_digits=12, decimal_places=2, verbose_name='special selling price')), - ('icon', models.ImageField(null=True, upload_to='products')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30, verbose_name='name')), + ('description', models.TextField(blank=True, verbose_name='description')), + ('code', models.CharField(max_length=10, verbose_name='code')), + ('purchase_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='purchase price')), + ('selling_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='selling price')), + ('special_selling_price', accounting.models.CurrencyField(decimal_places=2, max_digits=12, verbose_name='special selling price')), + ('icon', models.ImageField(blank=True, upload_to='products', null=True)), ], ), migrations.CreateModel( name='ProductType', fields=[ - ('id', models.AutoField(verbose_name='ID', auto_created=True, primary_key=True, serialize=False)), - ('name', models.CharField(verbose_name='name', max_length=30)), - ('description', models.TextField(verbose_name='description')), - ('icon', models.ImageField(null=True, upload_to='products')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=30, verbose_name='name')), + ('description', models.TextField(blank=True, null=True, verbose_name='description')), + ('icon', models.ImageField(blank=True, upload_to='products', null=True)), ], ), migrations.AddField( model_name='product', name='product_type', - field=models.ForeignKey(null=True, to='accounting.ProductType', related_name='products'), + field=models.ForeignKey(related_name='products', blank=True, to='accounting.ProductType', null=True), ), ] diff --git a/accounting/migrations/0002_auto_20160128_1538.py b/accounting/migrations/0002_auto_20160128_1538.py deleted file mode 100644 index d379728b..00000000 --- a/accounting/migrations/0002_auto_20160128_1538.py +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('accounting', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='product', - name='description', - field=models.TextField(verbose_name='description', blank=True), - ), - migrations.AlterField( - model_name='product', - name='icon', - field=models.ImageField(null=True, upload_to='products', blank=True), - ), - migrations.AlterField( - model_name='product', - name='product_type', - field=models.ForeignKey(to='accounting.ProductType', null=True, blank=True, related_name='products'), - ), - migrations.AlterField( - model_name='producttype', - name='description', - field=models.TextField(verbose_name='description', null=True, blank=True), - ), - migrations.AlterField( - model_name='producttype', - name='icon', - field=models.ImageField(null=True, upload_to='products', blank=True), - ), - ] diff --git a/club/__init__.py b/club/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/club/admin.py b/club/admin.py new file mode 100644 index 00000000..4d8a1bf5 --- /dev/null +++ b/club/admin.py @@ -0,0 +1,8 @@ +from django.contrib import admin + +from club.models import Club, Membership + + +admin.site.register(Club) +admin.site.register(Membership) + diff --git a/club/migrations/0001_initial.py b/club/migrations/0001_initial.py new file mode 100644 index 00000000..23850811 --- /dev/null +++ b/club/migrations/0001_initial.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.core.validators +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Club', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('name', models.CharField(max_length=30, verbose_name='name')), + ('unix_name', models.CharField(unique=True, error_messages={'unique': 'A club with that unix name already exists.'}, validators=[django.core.validators.RegexValidator('^[a-z0-9][a-z0-9._-]*[a-z0-9]$', 'Enter a valid unix name. This value may contain only letters, numbers ./-/_ characters.')], verbose_name='unix name', max_length=30)), + ('address', models.CharField(max_length=254, verbose_name='address')), + ('parent', models.ForeignKey(related_name='children', blank=True, to='club.Club', null=True)), + ], + ), + migrations.CreateModel( + name='Membership', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('start_date', models.DateField(auto_now=True, verbose_name='start date')), + ('end_date', models.DateField(null=True, verbose_name='end date', blank=True)), + ('role', models.IntegerField(verbose_name='role', choices=[(0, 'Membre'), (1, 'Membre actif'), (2, 'Membre du bureau'), (3, 'Responsable info'), (4, 'Secrétaire'), (5, 'Responsable com'), (7, 'Trésorier'), (8, 'Vice-Président'), (9, 'Vice-Président'), (10, 'Président')], default=0)), + ('description', models.CharField(max_length=30, verbose_name='description', blank=True)), + ('club', models.ForeignKey(related_name='members', to='club.Club')), + ('user', models.ForeignKey(related_name='membership', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/club/migrations/__init__.py b/club/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/club/models.py b/club/models.py new file mode 100644 index 00000000..f5774498 --- /dev/null +++ b/club/models.py @@ -0,0 +1,77 @@ +from django.db import models +from django.core import validators +from django.conf import settings +from django.utils.translation import ugettext_lazy as _ +from django.core.exceptions import ValidationError + +from core.models import User + +# Create your models here. + +class Club(models.Model): + """ + The Club class, made as a tree to allow nice tidy organization + """ + name = models.CharField(_('name'), max_length=30) + parent = models.ForeignKey('Club', related_name='children', null=True, blank=True) + unix_name = models.CharField(_('unix name'), max_length=30, unique=True, + validators=[ + validators.RegexValidator( + r'^[a-z0-9][a-z0-9._-]*[a-z0-9]$', + _('Enter a valid unix name. This value may contain only ' + 'letters, numbers ./-/_ characters.') + ), + ], + error_messages={ + 'unique': _("A club with that unix name already exists."), + }, + ) + address = models.CharField(_('address'), max_length=254) + # email = models.EmailField(_('email address'), unique=True) # This should, and will be generated automatically + + def check_loop(self): + """Raise a validation error when a loop is found within the parent list""" + objs = [] + cur = self + while cur.parent is not None: + if cur in objs: + raise ValidationError(_('You can not make loops in clubs')) + objs.append(cur) + cur = cur.parent + + def clean(self): + self.check_loop() + + def __str__(self): + return self.name + +class Membership(models.Model): + """ + The Membership class makes the connection between User and Clubs + + Both Users and Clubs can have many Membership objects: + - a user can be a member of many clubs at a time + - a club can have many members at a time too + + A User is currently member of all the Clubs where its Membership has an end_date set to null/None. + Otherwise, it's a past membership kept because it can be very useful to see who was in which Club in the past. + """ + user = models.ForeignKey(User, related_name="membership", null=False, blank=False) + club = models.ForeignKey(Club, related_name="members", null=False, blank=False) + start_date = models.DateField(_('start date'), auto_now=True) + end_date = models.DateField(_('end date'), null=True, blank=True) + role = models.IntegerField(_('role'), choices=sorted(settings.CLUB_ROLES.items()), + default=sorted(settings.CLUB_ROLES.items())[0][0]) + description = models.CharField(_('description'), max_length=30, null=False, blank=True) + + def clean(self): + if Membership.objects.filter(user=self.user).filter(club=self.club).filter(end_date=None).exclude(pk=self.pk).exists(): + raise ValidationError(_('User is already member of that club')) + + def __str__(self): + return self.club.name+' - '+self.user.username+' - '+settings.CLUB_ROLES[self.role]+str( + " - "+str(_('past member')) if self.end_date is not None else "" + ) + + + diff --git a/club/tests.py b/club/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/club/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/club/views.py b/club/views.py new file mode 100644 index 00000000..91ea44a2 --- /dev/null +++ b/club/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. diff --git a/core/management/commands/setup.py b/core/management/commands/setup.py index e25bbdcc..190b2318 100755 --- a/core/management/commands/setup.py +++ b/core/management/commands/setup.py @@ -6,6 +6,7 @@ from django.conf import settings from core.models import Group, User, Page, PageRev from accounting.models import Customer, GeneralJournal, ProductType, Product +from club.models import Club class Command(BaseCommand): help = "Set up a new instance of the Sith AE" @@ -28,6 +29,10 @@ class Command(BaseCommand): u.save() for g in settings.AE_GROUPS.values(): Group(id=g['id'], name=g['name']).save() + Club(name=settings.AE_MAIN_CLUB['name'], unix_name=settings.AE_MAIN_CLUB['unix_name'], + address=settings.AE_MAIN_CLUB['address']).save() + + # Here we add a lot of test datas, that are not necessary for the Sith, but that provide a basic development environment if not options['prod']: print("Dev mode, adding some test data") s = User(username='skia', last_name="Kia", first_name="S'", diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py index 5922a81c..7a14ffb6 100644 --- a/core/migrations/0001_initial.py +++ b/core/migrations/0001_initial.py @@ -2,10 +2,9 @@ from __future__ import unicode_literals from django.db import migrations, models -import django.utils.timezone import django.core.validators -import django.db.models.deletion from django.conf import settings +import django.db.models.deletion import django.contrib.auth.models @@ -19,19 +18,19 @@ class Migration(migrations.Migration): migrations.CreateModel( name='User', fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('password', models.CharField(verbose_name='password', max_length=128)), - ('last_login', models.DateTimeField(null=True, blank=True, verbose_name='last login')), + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('password', models.CharField(max_length=128, verbose_name='password')), + ('last_login', models.DateTimeField(blank=True, verbose_name='last login', null=True)), ('is_superuser', models.BooleanField(help_text='Designates that this user has all permissions without explicitly assigning them.', default=False, verbose_name='superuser status')), - ('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 254 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=254, validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], unique=True, verbose_name='username')), - ('first_name', models.CharField(verbose_name='first name', max_length=30)), - ('last_name', models.CharField(verbose_name='last name', max_length=30)), - ('email', models.EmailField(unique=True, verbose_name='email address', max_length=254)), - ('date_of_birth', models.DateTimeField(verbose_name='date of birth')), - ('nick_name', models.CharField(blank=True, max_length=30)), + ('username', models.CharField(max_length=254, verbose_name='username', unique=True, help_text='Required. 254 characters or fewer. Letters, digits and @/./+/-/_ only.', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username. This value may contain only letters, numbers and @/./+/-/_ characters.')], error_messages={'unique': 'A user with that username already exists.'})), + ('first_name', models.CharField(max_length=30, verbose_name='first name')), + ('last_name', models.CharField(max_length=30, verbose_name='last name')), + ('email', models.EmailField(max_length=254, verbose_name='email address', unique=True)), + ('date_of_birth', models.DateField(verbose_name='date of birth')), + ('nick_name', models.CharField(max_length=30, blank=True)), ('is_staff', models.BooleanField(help_text='Designates whether the user can log into this admin site.', default=False, verbose_name='staff status')), ('is_active', models.BooleanField(help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', default=True, verbose_name='active')), - ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), + ('date_joined', models.DateField(auto_now_add=True, verbose_name='date joined')), ], options={ 'verbose_name_plural': 'users', @@ -45,20 +44,20 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Group', fields=[ - ('group_ptr', models.OneToOneField(primary_key=True, to='auth.Group', parent_link=True, serialize=False, auto_created=True)), + ('group_ptr', models.OneToOneField(serialize=False, primary_key=True, parent_link=True, to='auth.Group', auto_created=True)), ], bases=('auth.group',), ), migrations.CreateModel( name='Page', fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('name', models.CharField(verbose_name='page name', max_length=30)), - ('full_name', models.CharField(blank=True, verbose_name='page name', max_length=255)), - ('edit_group', models.ManyToManyField(blank=True, to='core.Group', related_name='editable_page')), - ('owner_group', models.ForeignKey(to='core.Group', default=1, related_name='owned_page')), - ('parent', models.ForeignKey(null=True, to='core.Page', on_delete=django.db.models.deletion.SET_NULL, blank=True, related_name='children')), - ('view_group', models.ManyToManyField(blank=True, to='core.Group', related_name='viewable_page')), + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('name', models.CharField(max_length=30, verbose_name='page name')), + ('_full_name', models.CharField(max_length=255, verbose_name='page name', blank=True)), + ('edit_group', models.ManyToManyField(to='core.Group', related_name='editable_page', blank=True)), + ('owner_group', models.ForeignKey(to='core.Group', related_name='owned_page', default=1)), + ('parent', models.ForeignKey(to='core.Page', on_delete=django.db.models.deletion.SET_NULL, null=True, related_name='children', blank=True)), + ('view_group', models.ManyToManyField(to='core.Group', related_name='viewable_page', blank=True)), ], options={ 'permissions': (('change_prop_page', "Can change the page's properties (groups, ...)"), ('view_page', 'Can view the page')), @@ -67,10 +66,10 @@ class Migration(migrations.Migration): migrations.CreateModel( name='PageRev', fields=[ - ('id', models.AutoField(primary_key=True, serialize=False, auto_created=True, verbose_name='ID')), - ('title', models.CharField(blank=True, verbose_name='page title', max_length=255)), - ('content', models.TextField(blank=True, verbose_name='page content')), - ('date', models.DateTimeField(verbose_name='date', auto_now=True)), + ('id', models.AutoField(serialize=False, auto_created=True, primary_key=True, verbose_name='ID')), + ('title', models.CharField(max_length=255, verbose_name='page title', blank=True)), + ('content', models.TextField(verbose_name='page content', blank=True)), + ('date', models.DateTimeField(auto_now=True, verbose_name='date')), ('author', models.ForeignKey(related_name='page_rev', to=settings.AUTH_USER_MODEL)), ('page', models.ForeignKey(related_name='revisions', to='core.Page')), ], @@ -81,27 +80,27 @@ class Migration(migrations.Migration): migrations.AddField( model_name='user', name='edit_group', - field=models.ManyToManyField(blank=True, to='core.Group', related_name='editable_user'), + field=models.ManyToManyField(to='core.Group', related_name='editable_user', blank=True), ), migrations.AddField( model_name='user', name='groups', - field=models.ManyToManyField(help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', to='auth.Group', blank=True, verbose_name='groups', related_query_name='user', related_name='user_set'), + field=models.ManyToManyField(to='auth.Group', related_query_name='user', verbose_name='groups', related_name='user_set', help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', blank=True), ), migrations.AddField( model_name='user', name='owner_group', - field=models.ForeignKey(to='core.Group', default=1, related_name='owned_user'), + field=models.ForeignKey(to='core.Group', related_name='owned_user', default=1), ), migrations.AddField( model_name='user', name='user_permissions', - field=models.ManyToManyField(help_text='Specific permissions for this user.', to='auth.Permission', blank=True, verbose_name='user permissions', related_query_name='user', related_name='user_set'), + field=models.ManyToManyField(to='auth.Permission', related_query_name='user', verbose_name='user permissions', related_name='user_set', help_text='Specific permissions for this user.', blank=True), ), migrations.AddField( model_name='user', name='view_group', - field=models.ManyToManyField(blank=True, to='core.Group', related_name='viewable_user'), + field=models.ManyToManyField(to='core.Group', related_name='viewable_user', blank=True), ), migrations.AlterUniqueTogether( name='page', diff --git a/core/migrations/0002_auto_20151215_0827.py b/core/migrations/0002_auto_20151215_0827.py deleted file mode 100644 index 567c17c5..00000000 --- a/core/migrations/0002_auto_20151215_0827.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0001_initial'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='date_joined', - field=models.DateTimeField(auto_now_add=True, verbose_name='date joined'), - ), - ] diff --git a/core/migrations/0003_auto_20160111_0900.py b/core/migrations/0003_auto_20160111_0900.py deleted file mode 100644 index e5008001..00000000 --- a/core/migrations/0003_auto_20160111_0900.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0002_auto_20151215_0827'), - ] - - operations = [ - migrations.RenameField( - model_name='page', - old_name='full_name', - new_name='_full_name', - ), - ] diff --git a/core/migrations/0004_auto_20160128_0835.py b/core/migrations/0004_auto_20160128_0835.py deleted file mode 100644 index 3e3559ba..00000000 --- a/core/migrations/0004_auto_20160128_0835.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0003_auto_20160111_0900'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='date_of_birth', - field=models.DateField(verbose_name='date of birth'), - ), - ] diff --git a/core/migrations/0005_auto_20160128_0842.py b/core/migrations/0005_auto_20160128_0842.py deleted file mode 100644 index 6f8c4da5..00000000 --- a/core/migrations/0005_auto_20160128_0842.py +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('core', '0004_auto_20160128_0835'), - ] - - operations = [ - migrations.AlterField( - model_name='user', - name='date_joined', - field=models.DateField(auto_now_add=True, verbose_name='date joined'), - ), - ] diff --git a/core/models.py b/core/models.py index 9edf788f..ca8b3227 100644 --- a/core/models.py +++ b/core/models.py @@ -160,13 +160,12 @@ class User(AbstractBaseUser, PermissionsMixin): """ Determine if the object can be edited by the user """ - if not hasattr(obj, "edit_group"): - return False if self.is_owner(obj): return True - for g in obj.edit_group.all(): - if self.groups.filter(name=g.name).exists(): - return True + if hasattr(obj, "edit_group"): + for g in obj.edit_group.all(): + if self.groups.filter(name=g.name).exists(): + return True if isinstance(obj, User) and obj == self: return True if self.has_perm(obj.__class__.__module__.split('.')[0]+".change_"+obj.__class__.__name__.lower()): @@ -177,13 +176,12 @@ class User(AbstractBaseUser, PermissionsMixin): """ Determine if the object can be viewed by the user """ - if not hasattr(obj, "view_group"): - return False if self.can_edit(obj): return True - for g in obj.view_group.all(): - if self.groups.filter(name=g.name).exists(): - return True + if hasattr(obj, "view_group"): + for g in obj.view_group.all(): + if self.groups.filter(name=g.name).exists(): + return True if self.has_perm(obj.__class__.__module__.split('.')[0]+".view_"+obj.__class__.__name__.lower()): return True return False diff --git a/core/views/__init__.py b/core/views/__init__.py index b249f1ae..22131fef 100644 --- a/core/views/__init__.py +++ b/core/views/__init__.py @@ -42,6 +42,7 @@ class CanEditMixin(View): try: # Always unlock when 403 self.object.unset_lock() except: pass + print("CanEditMixin 403") raise PermissionDenied class CanViewMixin(View): @@ -56,6 +57,7 @@ class CanViewMixin(View): try: # Always unlock when 403 self.object.unset_lock() except: pass + print("CanViewMixin 403") raise PermissionDenied from .user import * diff --git a/core/views/group.py b/core/views/group.py index 1a83d63a..2fe01a7c 100644 --- a/core/views/group.py +++ b/core/views/group.py @@ -3,15 +3,16 @@ from django.views.generic import ListView from core.models import Group from core.views.forms import GroupEditForm +from core.views import CanEditMixin -class GroupListView(ListView): +class GroupListView(CanEditMixin, ListView): """ Displays the group list """ model = Group template_name = "core/group_list.html" -class GroupEditView(UpdateView): +class GroupEditView(CanEditMixin, UpdateView): model = Group pk_url_kwarg = "group_id" template_name = "core/group_edit.html" diff --git a/requirements.txt b/requirements.txt index 97c4b525..90e5e3fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ # Django 1.8 LTS is required, version 1.9 is not supported Django >=1.8,<1.9 +Pillow diff --git a/sith/settings.py b/sith/settings.py index f55564db..f4d3ce0c 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -38,6 +38,7 @@ INSTALLED_APPS = ( 'django.contrib.messages', 'django.contrib.staticfiles', 'core', + 'club', 'subscription', 'accounting', ) @@ -122,6 +123,11 @@ EMAIL_HOST="localhost" EMAIL_PORT=25 # AE configuration +AE_MAIN_CLUB = { + 'name': "AE", + 'unix_name': "ae", + 'address': "6 Boulevard Anatole France, 90000 Belfort" + } # Define the date in the year serving as reference for the subscriptions calendar # (month, day) AE_START_DATE = (8, 15) # 15th August @@ -173,3 +179,16 @@ AE_SUBSCRIPTIONS = { }, # To be completed.... } + +CLUB_ROLES = { + 10: 'Président', + 9: 'Vice-Président', + 8: 'Vice-Président', + 7: 'Trésorier', + 5: 'Responsable com', + 4: 'Secrétaire', + 3: 'Responsable info', + 2: 'Membre du bureau', + 1: 'Membre actif', + 0: 'Membre', + } diff --git a/sith/urls.py b/sith/urls.py index 0d2d125e..a7748fb7 100644 --- a/sith/urls.py +++ b/sith/urls.py @@ -23,6 +23,6 @@ handler404 = "core.views.not_found" urlpatterns = [ url(r'^', include('core.urls', namespace="core", app_name="core")), - url(r'^subscription/', include('subscription.urls', namespace="asso", app_name="subscription")), + url(r'^subscription/', include('subscription.urls', namespace="subscription", app_name="subscription")), url(r'^admin/', include(admin.site.urls)), ] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # TODO: remove me for production!!! diff --git a/subscription/admin.py b/subscription/admin.py index 6b0f4bb1..4e56c531 100644 --- a/subscription/admin.py +++ b/subscription/admin.py @@ -1,8 +1,8 @@ from django.contrib import admin -from subscription.models import Member, Subscription +from subscription.models import Subscriber, Subscription -admin.site.register(Member) +admin.site.register(Subscriber) admin.site.register(Subscription) diff --git a/subscription/migrations/0001_initial.py b/subscription/migrations/0001_initial.py index ce5abc8f..dac96e4e 100644 --- a/subscription/migrations/0001_initial.py +++ b/subscription/migrations/0001_initial.py @@ -2,34 +2,44 @@ from __future__ import unicode_literals from django.db import migrations, models -from django.conf import settings +import django.contrib.auth.models class Migration(migrations.Migration): dependencies = [ - ('core', '0005_auto_20160128_0842'), + ('core', '0001_initial'), ] operations = [ - migrations.CreateModel( - name='Member', - fields=[ - ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL, primary_key=True, serialize=False)), - ], - ), migrations.CreateModel( name='Subscription', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, verbose_name='ID', primary_key=True)), - ('subscription_type', models.CharField(choices=[('cursus-branche', 'Cursus Branche'), ('cursus-tronc-commun', 'Cursus Tronc Commun'), ('deux-semestres', 'Deux semestres'), ('un-semestre', 'Un semestre')], verbose_name='subscription type', max_length=255)), + ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True, serialize=False)), + ('subscription_type', models.CharField(choices=[('cursus-branche', 'Cursus Branche'), ('cursus-tronc-commun', 'Cursus Tronc Commun'), ('deux-semestres', 'Deux semestres'), ('un-semestre', 'Un semestre')], max_length=255, verbose_name='subscription type')), ('subscription_start', models.DateField(verbose_name='subscription start')), ('subscription_end', models.DateField(verbose_name='subscription end')), - ('payment_method', models.CharField(choices=[('cheque', 'Chèque'), ('cash', 'Espèce'), ('other', 'Autre')], verbose_name='payment method', max_length=255)), - ('member', models.ForeignKey(to='subscription.Member', related_name='subscriptions')), + ('payment_method', models.CharField(choices=[('cheque', 'Chèque'), ('cash', 'Espèce'), ('other', 'Autre')], max_length=255, verbose_name='payment method')), ], options={ 'ordering': ['subscription_start'], }, ), + migrations.CreateModel( + name='Subscriber', + fields=[ + ], + options={ + 'proxy': True, + }, + bases=('core.user',), + managers=[ + ('objects', django.contrib.auth.models.UserManager()), + ], + ), + migrations.AddField( + model_name='subscription', + name='member', + field=models.ForeignKey(related_name='subscriptions', to='subscription.Subscriber'), + ), ] diff --git a/subscription/models.py b/subscription/models.py index 1908ea8a..bbc7380d 100644 --- a/subscription/models.py +++ b/subscription/models.py @@ -3,6 +3,8 @@ from django.db import models from django.utils import timezone from django.utils.translation import ugettext_lazy as _ from django.conf import settings +from django.core.exceptions import ValidationError +from django.core.urlresolvers import reverse from core.models import User @@ -14,17 +16,15 @@ def validate_payment(value): if value not in settings.AE_PAYMENT_METHOD: raise ValidationError(_('Bad payment method')) -class Member(models.Model): - user = models.OneToOneField(User, primary_key=True) +class Subscriber(User): + class Meta: + proxy = True def is_subscribed(self): return self.subscriptions.last().is_valid_now() - def __str__(self): - return self.user.username - class Subscription(models.Model): - member = models.ForeignKey(Member, related_name='subscriptions') + member = models.ForeignKey(Subscriber, related_name='subscriptions') subscription_type = models.CharField(_('subscription type'), max_length=255, choices=((k, v['name']) for k,v in sorted(settings.AE_SUBSCRIPTIONS.items()))) @@ -32,6 +32,17 @@ class Subscription(models.Model): subscription_end = models.DateField(_('subscription end')) payment_method = models.CharField(_('payment method'), max_length=255, choices=settings.AE_PAYMENT_METHOD) + class Meta: + permissions = ( + ('change_subscription', 'Can make someone become a subscriber'), + ('view_subscription', 'Can view who is a subscriber'), + ) + + def clean(self): + for s in Subscription.objects.filter(member=self.member).exclude(pk=self.pk).all(): + if s.is_valid_now(): + raise ValidationError(_('You can not subscribe many time for the same period')) + def save(self, *args, **kwargs): """ This makes the Subscription to be updated with right dates with respect to date.today() each time you save the @@ -50,8 +61,11 @@ class Subscription(models.Model): class Meta: ordering = ['subscription_start',] + def get_absolute_url(self): + return reverse('core:user_profile', kwargs={'user_id': self.member.pk}) + def __str__(self): - return self.member.user.username+' - '+str(self.pk) + return self.member.username+' - '+str(self.pk) @staticmethod def compute_start(d=date.today()): diff --git a/subscription/urls.py b/subscription/urls.py index 2e3fc812..19085045 100644 --- a/subscription/urls.py +++ b/subscription/urls.py @@ -4,7 +4,7 @@ from subscription.views import * urlpatterns = [ # Subscription views - url(r'^subscription/(?P[0-9]+)/$', NewSubscription.as_view(), name='subscription'), + url(r'^$', NewSubscription.as_view(), name='subscription'), ] diff --git a/subscription/views.py b/subscription/views.py index 7ec7c4e9..d74d7da0 100644 --- a/subscription/views.py +++ b/subscription/views.py @@ -4,13 +4,13 @@ from django import forms from django.forms import Select from django.conf import settings -from subscription.models import Member, Subscription +from subscription.models import Subscriber, Subscription from core.views import CanEditMixin, CanEditPropMixin, CanViewMixin class SubscriptionForm(forms.ModelForm): class Meta: model = Subscription - fields = ['subscription_type', 'payment_method'] + fields = ['member', 'subscription_type', 'payment_method'] #widgets = { # 'subscription_type': Select(choices={(k.lower(), k+" - "+str(v['price'])+"€"+str(Subscription.compute_end(2))) for k,v in settings.AE_SUBSCRIPTIONS.items()}), #}