diff --git a/cof/settings_dev.py b/cof/settings_dev.py index ee777e1c..ad2b0b64 100644 --- a/cof/settings_dev.py +++ b/cof/settings_dev.py @@ -50,6 +50,7 @@ INSTALLED_APPS = ( 'django_cas_ng', 'debug_toolbar', 'bootstrapform', + 'kfet', ) MIDDLEWARE_CLASSES = ( diff --git a/cof/urls.py b/cof/urls.py index 5a4a49dc..64af9eda 100644 --- a/cof/urls.py +++ b/cof/urls.py @@ -78,6 +78,7 @@ urlpatterns = [ url(r'^utile_bda/bda_diff$', gestioncof_views.liste_bdadiff), url(r'^utile_cof/diff_cof$', gestioncof_views.liste_diffcof), url(r'^utile_bda/bda_revente$', gestioncof_views.liste_bdarevente), + url(r'^k-fet/', include('kfet.urls')) ] + \ (static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG diff --git a/kfet/__init__.py b/kfet/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kfet/admin.py b/kfet/admin.py new file mode 100644 index 00000000..8c38f3f3 --- /dev/null +++ b/kfet/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/kfet/autocomplete.py b/kfet/autocomplete.py new file mode 100644 index 00000000..70293475 --- /dev/null +++ b/kfet/autocomplete.py @@ -0,0 +1,52 @@ +from django.shortcuts import render +from django.http import Http404 +from django.db.models import Q +from gestioncof.models import User, Clipper + +def account_new(request): + if "q" not in request.GET: + raise Http404 + q = request.GET.get("q") + + if (len(q) == 0): + return render(request, "kfet/account_new_autocomplete.html") + + data = {'q': q} + + queries = {} + search_words = q.split() + + queries['users_cof'] = User.objects.filter(Q(profile__is_cof = True)) + queries['users_notcof'] = User.objects.filter(Q(profile__is_cof = False)) + queries['clippers'] = Clipper.objects + + for word in search_words: + queries['users_cof'] = queries['users_cof'].filter( + Q(username__icontains = word) + | Q(first_name__icontains = word) + | Q(last_name__icontains = word)) + queries['users_notcof'] = queries['users_notcof'].filter( + Q(username__icontains = word) + | Q(first_name__icontains = word) + | Q(last_name__icontains = word)) + queries['clippers'] = queries['clippers'].filter( + Q(username__icontains = word) + | Q(fullname__icontains = word)) + + queries['users_cof'] = queries['users_cof'].distinct() + queries['users_notcof'] = queries['users_notcof'].distinct() + + usernames = list(queries['users_cof'].values_list('username', flat=True)) + usernames += list(queries['users_notcof'] \ + .values_list('username', flat=True)) + queries['clippers'] = \ + queries['clippers'].exclude(username__in=usernames).distinct() + + data.update(queries) + + options = 0 + for query in queries.values(): + options += len(query) + data['options'] = options + + return render(request, "kfet/account_new_autocomplete.html", data) diff --git a/kfet/forms.py b/kfet/forms.py new file mode 100644 index 00000000..2173e5a2 --- /dev/null +++ b/kfet/forms.py @@ -0,0 +1,36 @@ +from django import forms +from django.contrib.auth.models import User +from kfet.models import Account +from gestioncof.models import CofProfile + +class AccountTrigrammeForm(forms.ModelForm): + class Meta: + model = Account + fields = ['trigramme'] + widgets = { + 'trigramme': forms.TextInput(attrs={'autocomplete': 'off'}), + } + +class AccountForm(forms.ModelForm): + class Meta: + model = Account + fields = ['promo', 'nickname'] + +class CofForm(forms.ModelForm): + def clean_is_cof(self): + instance = getattr(self, 'instance', None) + if instance and instance.pk: + return instance.is_cof + else: + return False + class Meta: + model = CofProfile + fields = ['login_clipper', 'is_cof', 'departement'] + +class UserForm(forms.ModelForm): + class Meta: + model = User + fields = ['username', 'first_name', 'last_name', 'email'] + help_texts = { + 'username': '' + } diff --git a/kfet/migrations/0001_initial.py b/kfet/migrations/0001_initial.py new file mode 100644 index 00000000..4bc9ccdb --- /dev/null +++ b/kfet/migrations/0001_initial.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import models, migrations +import django.db.models.deletion +import django.core.validators +import datetime + + +class Migration(migrations.Migration): + + dependencies = [ + ('gestioncof', '0007_auto_20160802_1022'), + ] + + operations = [ + migrations.CreateModel( + name='Account', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('trigramme', models.CharField(max_length=3, validators=[django.core.validators.RegexValidator(regex='^[^a-z]{3}$')], unique=True)), + ('balance', models.DecimalField(decimal_places=2, default=0, max_digits=6)), + ('frozen', models.BooleanField(default=False)), + ('promo', models.IntegerField(null=True, blank=True, choices=[(1980, 1980), (1981, 1981), (1982, 1982), (1983, 1983), (1984, 1984), (1985, 1985), (1986, 1986), (1987, 1987), (1988, 1988), (1989, 1989), (1990, 1990), (1991, 1991), (1992, 1992), (1993, 1993), (1994, 1994), (1995, 1995), (1996, 1996), (1997, 1997), (1998, 1998), (1999, 1999), (2000, 2000), (2001, 2001), (2002, 2002), (2003, 2003), (2004, 2004), (2005, 2005), (2006, 2006), (2007, 2007), (2008, 2008), (2009, 2009), (2010, 2010), (2011, 2011), (2012, 2012), (2013, 2013), (2014, 2014), (2015, 2015), (2016, 2016)], default=2015)), + ('nickname', models.CharField(max_length=255, blank=True, default='')), + ('password', models.CharField(max_length=255, blank=True, null=True, unique=True, default=None)), + ('cofprofile', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='account_kfet', to='gestioncof.CofProfile')), + ], + ), + migrations.CreateModel( + name='AccountNegative', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('start', models.DateTimeField(default=datetime.datetime(2016, 8, 2, 10, 22, 1, 569492))), + ('balance_offset', models.DecimalField(decimal_places=2, max_digits=6)), + ('authorized_overdraft', models.DecimalField(decimal_places=2, default=0, max_digits=6)), + ('comment', models.CharField(max_length=255, blank=True)), + ('account', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='negative', to='kfet.Account')), + ], + ), + migrations.CreateModel( + name='Article', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=45)), + ('is_sold', models.BooleanField(default=True)), + ('price', models.DecimalField(decimal_places=2, max_digits=6)), + ('stock', models.IntegerField(default=0)), + ], + ), + migrations.CreateModel( + name='ArticleCategory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=45)), + ], + ), + migrations.CreateModel( + name='ArticleRule', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('ratio', models.PositiveSmallIntegerField()), + ('article_on', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='rule_on', to='kfet.Article')), + ('article_to', models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, related_name='rule_to', to='kfet.Article')), + ], + ), + migrations.CreateModel( + name='Checkout', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=45)), + ('valid_from', models.DateTimeField()), + ('valid_to', models.DateTimeField()), + ('balance', models.DecimalField(decimal_places=2, max_digits=6)), + ('is_protected', models.BooleanField(default=False)), + ('created_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='kfet.Account')), + ], + ), + migrations.CreateModel( + name='CheckoutTransfer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.DecimalField(decimal_places=2, max_digits=6)), + ('from_checkout', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers_from', to='kfet.Checkout')), + ('to_checkout', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers_to', to='kfet.Checkout')), + ], + ), + migrations.CreateModel( + name='Inventory', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('at', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='InventoryArticle', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('stock_old', models.IntegerField()), + ('stock_new', models.IntegerField()), + ('stock_error', models.IntegerField(default=0)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Article')), + ('inventory', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Inventory')), + ], + ), + migrations.CreateModel( + name='Operation', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('type', models.CharField(max_length=8, choices=[('purchase', 'Achat'), ('deposit', 'Charge'), ('withdraw', 'Retrait')])), + ('amount', models.DecimalField(decimal_places=2, max_digits=6)), + ('on_checkout', models.BooleanField(default=True)), + ('canceled_at', models.DateTimeField(blank=True, null=True, default=None)), + ('addcost_amount', models.DecimalField(decimal_places=2, max_digits=6)), + ('addcost_for', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='addcosts', to='kfet.Account', null=True, default=None)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='operations', to='kfet.Article', null=True, default=None)), + ('canceled_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='+', to='kfet.Account', null=True, default=None)), + ], + ), + migrations.CreateModel( + name='OperationGroup', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('at', models.DateTimeField(auto_now_add=True)), + ('amount', models.IntegerField()), + ('is_cof', models.BooleanField(default=False)), + ('comment', models.CharField(max_length=255, blank=True, default='')), + ('checkout', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='operations', to='kfet.Checkout')), + ('on_acc', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='operations', to='kfet.Account')), + ('valid_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='+', to='kfet.Account', null=True, default=True)), + ], + ), + migrations.CreateModel( + name='Order', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('at', models.DateTimeField(auto_now_add=True)), + ('amount', models.DecimalField(decimal_places=2, max_digits=6)), + ], + ), + migrations.CreateModel( + name='OrderArticle', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('quantity_ordered', models.IntegerField()), + ('quantity_received', models.IntegerField()), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Article')), + ('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Order')), + ], + ), + migrations.CreateModel( + name='Statement', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('balance_old', models.DecimalField(decimal_places=2, max_digits=6)), + ('balance_new', models.DecimalField(decimal_places=2, max_digits=6)), + ('amount_taken', models.DecimalField(decimal_places=2, max_digits=6)), + ('amount_error', models.DecimalField(decimal_places=2, max_digits=6)), + ('at', models.DateTimeField(auto_now_add=True)), + ('by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='kfet.Account')), + ('checkout', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='statements', to='kfet.Checkout')), + ], + ), + migrations.CreateModel( + name='Supplier', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=45)), + ('address', models.TextField()), + ('email', models.EmailField(max_length=254)), + ('phone', models.CharField(max_length=10)), + ('comment', models.TextField()), + ], + ), + migrations.CreateModel( + name='SupplierArticle', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('box_type', models.CharField(max_length=7, choices=[('caisse', 'Caisse'), ('carton', 'Carton'), ('palette', 'Palette'), ('fût', 'Fût')])), + ('box_capacity', models.PositiveSmallIntegerField()), + ('price_HT', models.DecimalField(decimal_places=4, max_digits=7)), + ('TVA', models.DecimalField(decimal_places=2, max_digits=4)), + ('rights', models.DecimalField(decimal_places=4, max_digits=7)), + ('article', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Article')), + ('supplier', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='kfet.Supplier')), + ], + ), + migrations.CreateModel( + name='Transfer', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('amount', models.DecimalField(decimal_places=2, max_digits=6)), + ('canceled_at', models.DateTimeField(blank=True, null=True, default=None)), + ('canceled_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='+', to='kfet.Account', null=True, default=None)), + ('from_acc', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers_from', to='kfet.Account')), + ], + ), + migrations.CreateModel( + name='TransferGroup', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('at', models.DateTimeField(auto_now_add=True)), + ('comment', models.CharField(max_length=255, blank=True, default='')), + ('valid_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='+', to='kfet.Account', null=True, default=None)), + ], + ), + migrations.AddField( + model_name='transfer', + name='group', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers', to='kfet.TransferGroup'), + ), + migrations.AddField( + model_name='transfer', + name='to_acc', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transfers_to', to='kfet.Account'), + ), + migrations.AddField( + model_name='supplier', + name='articles', + field=models.ManyToManyField(related_name='suppliers', through='kfet.SupplierArticle', to='kfet.Article'), + ), + migrations.AddField( + model_name='order', + name='articles', + field=models.ManyToManyField(related_name='orders', through='kfet.OrderArticle', to='kfet.Article'), + ), + migrations.AddField( + model_name='order', + name='supplier', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='orders', to='kfet.Supplier'), + ), + migrations.AddField( + model_name='operation', + name='group', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='kfet.OperationGroup'), + ), + migrations.AddField( + model_name='inventory', + name='articles', + field=models.ManyToManyField(related_name='inventories', through='kfet.InventoryArticle', to='kfet.Article'), + ), + migrations.AddField( + model_name='inventory', + name='by', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='kfet.Account'), + ), + migrations.AddField( + model_name='inventory', + name='order', + field=models.OneToOneField(on_delete=django.db.models.deletion.PROTECT, blank=True, related_name='inventory', to='kfet.Order', null=True, default=None), + ), + migrations.AddField( + model_name='article', + name='category', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='articles', to='kfet.ArticleCategory'), + ), + ] diff --git a/kfet/migrations/__init__.py b/kfet/migrations/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kfet/models.py b/kfet/models.py new file mode 100644 index 00000000..2b2750ab --- /dev/null +++ b/kfet/models.py @@ -0,0 +1,348 @@ +from django.db import models +from django.core.exceptions import PermissionDenied +from django.contrib.auth.models import User +from django.core.validators import RegexValidator +from gestioncof.models import CofProfile +from django.utils.six.moves import reduce +import datetime + +def choices_length(choices): + return reduce(lambda m, choice: max(m, len(choice[0])), choices, 0) + +def default_promo(): + now = datetime.date.today() + return now.month <= 8 and now.year-1 or now.year + +class Account(models.Model): + cofprofile = models.OneToOneField( + CofProfile, on_delete = models.PROTECT, + related_name = "account_kfet") + trigramme = models.CharField( + unique = True, + max_length = 3, + validators = [RegexValidator(regex='^[^a-z]{3}$')]) + balance = models.DecimalField( + max_digits = 6, decimal_places = 2, + default = 0) + frozen = models.BooleanField(default = False) + # Optional + PROMO_CHOICES = [(r,r) for r in range(1980, datetime.date.today().year+1)] + promo = models.IntegerField( + choices = PROMO_CHOICES, + blank = True, null = True, default = default_promo()) + nickname = models.CharField( + max_length = 255, + blank = True, default = "") + password = models.CharField( + max_length = 255, + unique = True, + blank = True, null = True, default = None) + + def __str__(self): + return self.trigramme + + @staticmethod + def is_free(trigramme): + try: + account = Account.objects.filter(trigramme=trigramme).get() + return False + except Account.DoesNotExist: + return True + + # Méthode save() avec auth + + # Args: + # - auth_user : request.user + # - data : datas pour User et CofProfile + # Action: + # - Enregistre User, CofProfile à partir de "data" + # - Enregistre Account + # Permissions + # - Edition si request.user: + # - modifie son compte (ne peut pas modifier nickname) + # ou - a la perm kfet.change_account + # - Ajout si request.user a la perm kfet.add_account + def save_api(self, auth_user, data = None): + if self.pk: + # Account update + + # Checking permissions + user = self.cofprofile.user + if not auth_user.has_perm('kfet.change_account') \ + and request.user != user: + raise PermissionDenied + + # Updating User with data + user.first_name = data.get("first_name", user.first_name) + user.last_name = data.get("last_name", user.last_name) + user.email = data.get("email", user.email) + user.save() + # Updating CofProfile with data + cof = self.cofprofile + cof.departement = data.get("departement", cof.departement) + cof.save() + # Nickname is not editable by the user + if not auth_user.has_perm('kfet.change_account'): + account_old = Account.objects.get(pk=self.pk) + self.nickname = account_old.nickname + else: + # New account + + # Checking permissions + if not auth_user.has_perm('kfet.add_account'): + raise PermissionDenied + + # Creating or updating User instance + username = data.get("username") + (user, _) = User.objects.get_or_create(username=username) + if "first_name" in data: + user.first_name = data['first_name'] + if "last_name" in data: + user.last_name = data['last_name'] + if "email" in data: + user.email = data['email'] + user.save() + # Creating or updating CofProfile instance + (cof, _) = CofProfile.objects.get_or_create(user=user) + if "login_clipper" in data: + cof.login_clipper = data['login_clipper'] + if "departement" in data: + cof.departement = data['departement'] + cof.save() + # Check if cof is linked to an account + if hasattr(cof, 'account_kfet'): + raise Account.UserHasAccount(cof.account_kfet.trigramme) + self.cofprofile = cof + self.save() + + # Surcharge de delete + # Pas de suppression possible + # Cas à régler plus tard + def delete(self, *args, **kwargs): + pass + + class UserHasAccount(Exception): + def __init__(self, trigramme): + self.trigramme = trigramme + +class AccountNegative(models.Model): + account = models.OneToOneField( + Account, on_delete = models.PROTECT, + related_name = "negative") + start = models.DateTimeField(default = datetime.datetime.now()) + balance_offset = models.DecimalField(max_digits = 6, decimal_places = 2) + authorized_overdraft = models.DecimalField( + max_digits = 6, decimal_places = 2, + default = 0) + comment = models.CharField(max_length = 255, blank = True) + +class Checkout(models.Model): + created_by = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "+") + name = models.CharField(max_length = 45) + valid_from = models.DateTimeField() + valid_to = models.DateTimeField() + balance = models.DecimalField(max_digits = 6, decimal_places = 2) + is_protected = models.BooleanField(default = False) + +class CheckoutTransfer(models.Model): + from_checkout = models.ForeignKey( + Checkout, on_delete = models.PROTECT, + related_name = "transfers_from") + to_checkout = models.ForeignKey( + Checkout, on_delete = models.PROTECT, + related_name = "transfers_to") + amount = models.DecimalField( + max_digits = 6, decimal_places = 2) + +class Statement(models.Model): + by = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "+") + checkout = models.ForeignKey( + Checkout, on_delete = models.PROTECT, + related_name = "statements") + balance_old = models.DecimalField(max_digits = 6, decimal_places = 2) + balance_new = models.DecimalField(max_digits = 6, decimal_places = 2) + amount_taken = models.DecimalField(max_digits = 6, decimal_places = 2) + amount_error = models.DecimalField(max_digits = 6, decimal_places = 2) + at = models.DateTimeField(auto_now_add = True) + +class ArticleCategory(models.Model): + name = models.CharField(max_length = 45) + +class Article(models.Model): + name = models.CharField(max_length = 45) + is_sold = models.BooleanField(default = True) + price = models.DecimalField(max_digits = 6, decimal_places = 2) + stock = models.IntegerField(default = 0) + category = models.ForeignKey( + ArticleCategory, on_delete = models.PROTECT, + related_name = "articles") + +class ArticleRule(models.Model): + article_on = models.OneToOneField( + Article, on_delete = models.PROTECT, + related_name = "rule_on") + article_to = models.OneToOneField( + Article, on_delete = models.PROTECT, + related_name = "rule_to") + ratio = models.PositiveSmallIntegerField() + +class Inventory(models.Model): + articles = models.ManyToManyField( + Article, + through = 'InventoryArticle', + related_name = "inventories") + by = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "+") + at = models.DateTimeField(auto_now_add = True) + # Optional + order = models.OneToOneField( + 'Order', on_delete = models.PROTECT, + related_name = "inventory", + blank = True, null = True, default = None) + +class InventoryArticle(models.Model): + inventory = models.ForeignKey( + Inventory, on_delete = models.PROTECT) + article = models.ForeignKey( + Article, on_delete = models.PROTECT) + stock_old = models.IntegerField() + stock_new = models.IntegerField() + stock_error = models.IntegerField(default = 0) + +class Supplier(models.Model): + articles = models.ManyToManyField( + Article, + through = 'SupplierArticle', + related_name = "suppliers") + name = models.CharField(max_length = 45) + address = models.TextField() + email = models.EmailField() + phone = models.CharField(max_length = 10) + comment = models.TextField() + +class SupplierArticle(models.Model): + supplier = models.ForeignKey( + Supplier, on_delete = models.PROTECT) + article = models.ForeignKey( + Article, on_delete = models.PROTECT) + BOX_TYPE_CHOICES = ( + ("caisse", "Caisse"), + ("carton", "Carton"), + ("palette", "Palette"), + ("fût", "Fût"), + ) + box_type = models.CharField( + choices = BOX_TYPE_CHOICES, + max_length = choices_length(BOX_TYPE_CHOICES)) + box_capacity = models.PositiveSmallIntegerField() + price_HT = models.DecimalField(max_digits = 7, decimal_places = 4) + TVA = models.DecimalField(max_digits = 4, decimal_places = 2) + rights = models.DecimalField(max_digits = 7, decimal_places = 4) + +class Order(models.Model): + supplier = models.ForeignKey( + Supplier, on_delete = models.PROTECT, + related_name = "orders") + articles = models.ManyToManyField( + Article, + through = "OrderArticle", + related_name = "orders") + at = models.DateTimeField(auto_now_add = True) + amount = models.DecimalField(max_digits = 6, decimal_places = 2) + +class OrderArticle(models.Model): + order = models.ForeignKey( + Order, on_delete = models.PROTECT) + article = models.ForeignKey( + Article, on_delete = models.PROTECT) + quantity_ordered = models.IntegerField() + quantity_received = models.IntegerField() + +class TransferGroup(models.Model): + at = models.DateTimeField(auto_now_add = True) + # Optional + comment = models.CharField( + max_length = 255, + blank = True, default = "") + valid_by = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "+", + blank = True, null = True, default = None) + +class Transfer(models.Model): + group = models.ForeignKey( + TransferGroup, on_delete = models.PROTECT, + related_name = "transfers") + from_acc = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "transfers_from") + to_acc = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "transfers_to") + amount = models.DecimalField(max_digits = 6, decimal_places = 2) + # Optional + canceled_by = models.ForeignKey( + Account, on_delete = models.PROTECT, + null = True, blank = True, default = None, + related_name = "+") + canceled_at = models.DateTimeField( + null = True, blank = True, default = None) + +class OperationGroup(models.Model): + on_acc = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "operations") + checkout = models.ForeignKey( + Checkout, on_delete = models.PROTECT, + related_name = "operations") + at = models.DateTimeField(auto_now_add = True) + amount = models.IntegerField() + is_cof = models.BooleanField(default = False) + # Optional + comment = models.CharField( + max_length = 255, + blank = True, default = "") + valid_by = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "+", + blank = True, null = True, default = True) + +class Operation(models.Model): + PURCHASE = 'purchase' + DEPOSIT = 'deposit' + WITHDRAW = 'withdraw' + + TYPE_ORDER_CHOICES = ( + (PURCHASE, 'Achat'), + (DEPOSIT, 'Charge'), + (WITHDRAW, 'Retrait'), + ) + + group = models.ForeignKey( + OperationGroup, on_delete = models.PROTECT, + related_name = "+") + type = models.CharField( + choices = TYPE_ORDER_CHOICES, + max_length = choices_length(TYPE_ORDER_CHOICES)) + amount = models.DecimalField(max_digits = 6, decimal_places = 2) + on_checkout = models.BooleanField(default = True) + # Optional + article = models.ForeignKey( + Article, on_delete = models.PROTECT, + related_name = "operations", + blank = True, null = True, default = None) + canceled_by = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "+", + blank = True, null = True, default = None) + canceled_at = models.DateTimeField( + blank = True, null = True, default = None) + addcost_for = models.ForeignKey( + Account, on_delete = models.PROTECT, + related_name = "addcosts", + blank = True, null = True, default = None) + addcost_amount = models.DecimalField(max_digits = 6, decimal_places = 2) diff --git a/kfet/templates/kfet/account_new.html b/kfet/templates/kfet/account_new.html new file mode 100644 index 00000000..b7b28d88 --- /dev/null +++ b/kfet/templates/kfet/account_new.html @@ -0,0 +1,74 @@ +{% extends "kfet/base.html" %} +{% load static %} + +{% block title %}Création d'un nouveau compte{% endblock %} + +{% block extra_head %} + + +{% endblock %} + +{% block content %} +

Création d'un nouveau compte

+ + {% if post %} + {% if success %} + Nouveau compte créé : {{ trigramme }} + {% else %} + Echec de la création du compte + {{ errors }} + {% endif %} +
+ {% endif %} + +
+ {{ account_trigramme_form }} +

+ +
+
+
+ +{% endblock %} diff --git a/kfet/templates/kfet/account_new_autocomplete.html b/kfet/templates/kfet/account_new_autocomplete.html new file mode 100644 index 00000000..21700838 --- /dev/null +++ b/kfet/templates/kfet/account_new_autocomplete.html @@ -0,0 +1,43 @@ +{% load kfet_tags %} + + diff --git a/kfet/templates/kfet/account_new_form.html b/kfet/templates/kfet/account_new_form.html new file mode 100644 index 00000000..92e44059 --- /dev/null +++ b/kfet/templates/kfet/account_new_form.html @@ -0,0 +1,5 @@ +{% csrf_token %} +{{ user_form }} +{{ cof_form }} +{{ account_form }} + diff --git a/kfet/templates/kfet/base.html b/kfet/templates/kfet/base.html new file mode 100644 index 00000000..642f8e22 --- /dev/null +++ b/kfet/templates/kfet/base.html @@ -0,0 +1,30 @@ + + + + + {% block title %}{% endblock %} | K-Fêt - ENS Ulm + + {% block extra_head %}{% endblock %} + + {# Vieux IE pas comprendre HTML5 #} + + + + +
+ {% block content %}TGTG{% endblock %} +
+ {% include "kfet/base_footer.html" %} + + diff --git a/kfet/templates/kfet/base_footer.html b/kfet/templates/kfet/base_footer.html new file mode 100644 index 00000000..e69de29b diff --git a/kfet/templatetags/__init__.py b/kfet/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/kfet/templatetags/kfet_tags.py b/kfet/templatetags/kfet_tags.py new file mode 100644 index 00000000..38538d9b --- /dev/null +++ b/kfet/templatetags/kfet_tags.py @@ -0,0 +1,28 @@ +from django import template +from django.utils.html import escape +from django.utils.safestring import mark_safe +import re + +register = template.Library() + +def highlight_text(text, q): + q2 = "|".join(q.split()) + pattern = re.compile(r"(?P%s)" % q2, re.IGNORECASE) + return mark_safe(re.sub(pattern, r"\g", text)) + +@register.filter(is_safe=True) +def highlight_user(user, q): + if user.first_name and user.last_name: + text = "%s %s (%s)" % (user.first_name, user.last_name, user.username) + else: + text = user.username + return highlight_text(escape(text), q) + +@register.filter(is_safe=True) +def highlight_clipper(clipper, q): + if clipper.fullname: + text = "%s (%s)" % (clipper.fullname, clipper.username) + else: + text = clipper.username + return highlight_text(escape(text), q) + diff --git a/kfet/tests.py b/kfet/tests.py new file mode 100644 index 00000000..7ce503c2 --- /dev/null +++ b/kfet/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/kfet/urls.py b/kfet/urls.py new file mode 100644 index 00000000..235ed716 --- /dev/null +++ b/kfet/urls.py @@ -0,0 +1,21 @@ +from django.conf.urls import url +from kfet import views +from kfet import autocomplete + +urlpatterns = [ + url(r'^$', views.home), + # Administration + url(r'^admin/account/new$', views.account_new, + name = 'kfet.admin.account.new'), + url(r'^admin/account/new/user/(?P.+)$', views.account_new_ajax, + name = 'kfet.admin.account.new.fromuser'), + url(r'^admin/account/new/clipper/(?P.+)$', views.account_new_ajax, + name = 'kfet.admin.account.new.fromclipper'), + url(r'^admin/account/new/empty$', views.account_new_ajax, + name = 'kfet.admin.account.new.empty'), + url(r'^admin/account/is_free$', views.account_is_free_ajax, + name = 'kfet.admin.account.is_free.ajax'), + # Autocomplete - Nouveau compte + url(r'^autocomplete/account_new$', autocomplete.account_new, + name = 'kfet.admin.account.new.autocomplete'), +] diff --git a/kfet/views.py b/kfet/views.py new file mode 100644 index 00000000..69545168 --- /dev/null +++ b/kfet/views.py @@ -0,0 +1,150 @@ +from django.shortcuts import render, get_object_or_404 +from django.core.exceptions import PermissionDenied +from django.contrib.auth.decorators import login_required, permission_required +from django.contrib.auth.models import User +from django.http import HttpResponse, Http404 +import json +from gestioncof.models import CofProfile, Clipper +from kfet.models import Account +from kfet.forms import AccountTrigrammeForm, AccountForm, CofForm, UserForm + +@login_required +def home(request): + return render(request, "kfet/base.html") + +def put_cleaned_data_in_dict(dict, form): + for field in form.cleaned_data: + dict[field] = form.cleaned_data[field] + +@login_required +@permission_required('kfet.add_account') +def account_new(request): + # A envoyer au template + data_template = { + 'account_trigramme_form': AccountTrigrammeForm(), + 'post' : False, + 'success' : False, + 'trigramme' : '', + 'errors' : {}, + } + + # Enregistrement + if request.method == "POST": + # Pour indiquer la tentative d'enregistrement au template + data_template['post'] = True + + # Peuplement des forms + username = request.POST.get('username') + try: + user = User.objects.filter(username=username).get() + (cof, _) = CofProfile.objects.get_or_create(user=user) + user_form = UserForm(request.POST, instance=user) + cof_form = CofForm(request.POST, instance=cof) + except User.DoesNotExist: + user_form = UserForm(request.POST) + cof_form = CofForm(request.POST) + trigramme_form = AccountTrigrammeForm(request.POST) + account_form = AccountForm(request.POST) + # Ajout des erreurs pour le template + data_template['errors']['user_form'] = user_form.errors + data_template['errors']['cof_form'] = cof_form.errors + data_template['errors']['trigramme_form'] = trigramme_form.errors + data_template['errors']['account_form'] = account_form.errors + + if all((user_form.is_valid(), cof_form.is_valid(), + trigramme_form.is_valid(), account_form.is_valid())): + print(user_form.cleaned_data) + data = {} + # Fill data + put_cleaned_data_in_dict(data, user_form) + put_cleaned_data_in_dict(data, cof_form) + + account = Account() + account.trigramme = trigramme_form.cleaned_data['trigramme'] + account.promo = account_form.cleaned_data['promo'] + account.nickname = account_form.cleaned_data['nickname'] + try: + account.save_api(auth_user = request.user, data = data) + data_template['success'] = True + data_template['trigramme'] = account.trigramme + except Account.UserHasAccount as e: + data_template['errors']['global'] = \ + "Cet utilisateur a déjà un compte K-Fêt : " + e.trigramme + except PermissionDenied: + print("perm") + + return render(request, "kfet/account_new.html", data_template) + +def account_new_set_readonly_fields(user_form, cof_form): + user_form.fields['username'].widget.attrs['readonly'] = True + cof_form.fields['login_clipper'].widget.attrs['readonly'] = True + cof_form.fields['is_cof'].widget.attrs['disabled'] = True + +def account_new_ajax(request, username=None, login_clipper=None): + user = None + if login_clipper: + # à partir d'un clipper + # le user associé à ce clipper ne devrait pas encore existé + clipper = get_object_or_404(Clipper, username = login_clipper) + try: + # Vérification que clipper ne soit pas déjà dans User + user = User.objects.filter(username=login_clipper).get() + # Ici, on nous a menti, le user existe déjà + username = user.username + login_clipper = None + except User.DoesNotExist: + # Clipper (sans user déjà existant) + + # UserForm - Prefill + Création + user_initial_data = { + 'username' : login_clipper, + 'email' : login_clipper + "@clipper.ens.fr"} + if clipper.fullname: + # Prefill du nom et prénom + names = clipper.fullname.split() + # Le premier, c'est le prénom + user_initial_data['first_name'] = names[0] + if len(names) > 1: + # Si d'autres noms -> tous dans le nom de famille + user_initial_data['last_name'] = " ".join(names[1:]) + user_form = UserForm(initial = user_initial_data) + + # CofForm - Prefill + Création + cof_initial_data = { 'login_clipper': login_clipper } + cof_form = CofForm(initial = cof_initial_data) + + # AccountForm + account_form = AccountForm() + + # Protection (read-only) des champs username et login_clipper + account_new_set_readonly_fields(user_form, cof_form) + if username: + # le user existe déjà + user = get_object_or_404(User, username=username) + # récupération du profil cof + (cof, _) = CofProfile.objects.get_or_create(user=user) + # UserForm + CofForm - Création à partir des instances existantes + user_form = UserForm(instance = user) + cof_form = CofForm(instance = cof) + # AccountForm + account_form = AccountForm() + # Protection (read-only) des champs username et login_clipper + account_new_set_readonly_fields(user_form, cof_form) + elif not login_clipper: + # connaît pas du tout, faut tout remplir + user_form = UserForm() + cof_form = CofForm() + account_form = AccountForm() + + return render(request, "kfet/account_new_form.html", { + 'account_form' : account_form, + 'cof_form' : cof_form, + 'user_form' : user_form, + }) + +def account_is_free_ajax(request): + if not request.GET.get("trigramme"): + raise Http404 + trigramme = request.GET.get("trigramme") + data = { 'is_free': Account.is_free(trigramme) } + return HttpResponse(json.dumps(data), content_type = 'application/json') diff --git a/requirements.txt b/requirements.txt index c1831e33..140f5f67 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,3 +12,4 @@ six==1.10.0 unicodecsv==0.14.1 icalendar==3.10 django-bootstrap-form==3.2.1 +django-rest-framework==3.4.0