This commit is contained in:
Aurélien Delobelle 2016-08-02 10:40:46 +02:00
parent a2177155a0
commit 43d938edd0
20 changed files with 1053 additions and 0 deletions

View file

@ -50,6 +50,7 @@ INSTALLED_APPS = (
'django_cas_ng',
'debug_toolbar',
'bootstrapform',
'kfet',
)
MIDDLEWARE_CLASSES = (

View file

@ -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

0
kfet/__init__.py Normal file
View file

3
kfet/admin.py Normal file
View file

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

52
kfet/autocomplete.py Normal file
View file

@ -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)

36
kfet/forms.py Normal file
View file

@ -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': ''
}

View file

@ -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'),
),
]

View file

348
kfet/models.py Normal file
View file

@ -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)

View file

@ -0,0 +1,74 @@
{% extends "kfet/base.html" %}
{% load static %}
{% block title %}Création d'un nouveau compte{% endblock %}
{% block extra_head %}
<script src="https://code.jquery.com/jquery-3.1.0.min.js" integrity="sha256-cCueBR6CsyA4/9szpPfrX3s49M9vUU5BgtiJj06wt/s=" crossorigin="anonymous"></script>
<script src="{% static "autocomplete_light/autocomplete.js" %}" type="text/javascript"></script>
{% endblock %}
{% block content %}
<h1>Création d'un nouveau compte</h1>
{% if post %}
{% if success %}
Nouveau compte créé : {{ trigramme }}
{% else %}
Echec de la création du compte
{{ errors }}
{% endif %}
<hr>
{% endif %}
<form action="{% url "kfet.admin.account.new" %}" method="post">
{{ account_trigramme_form }}
<div id="trigramme_valid"></div><br>
<input type="text" name="q" id="search_autocomplete" spellcheck="false" placeholder="Chercher un utilisateur par nom, prénom ou identifiant clipper">
<div id="search_results"></div>
<div id="form-placeholder"></div>
</form>
<script type="text/javascript">
$(document).ready(function() {
// Affichage des résultats d'autocomplétion
$('input#search_autocomplete').yourlabsAutocomplete({
url: '{% url "kfet.admin.account.new.autocomplete" %}',
minimumCharacters: 0,
id: 'search_autocomplete',
choiceSelector: 'li:has(a)',
container: $("#search_results"),
box: $("#search_results"),
// fixPosition: function() {},
});
// Chargement du formulaire adapté au choix sélectionné
$('input#search_autocomplete').bind(
'selectChoice',
function(e, choice, autocomplete) {
autocomplete.hide();
link = choice.find('a:first');
if (link.length && link.attr('href') != undefined) {
$('#form-placeholder').html("").load(link.attr('href'));
//, function() {
//$('#form-placeholder').toggle().toggle();
//});
}
}
);
// Vérification client de la disponibilité du trigramme choisi
old_trigramme = "";
$('#id_trigramme').keyup(function() {
trigramme = $('#id_trigramme').val();
if (trigramme.length == 3 && trigramme != old_trigramme) {
$.ajax({
dataType: "json",
url: "{% url "kfet.admin.account.is_free.ajax" %}",
data: { trigramme: trigramme },
}).done(function(data) {
$('#trigramme_valid').text(data['is_free']);
old_trigramme = trigramme;
});
}
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,43 @@
{% load kfet_tags %}
<ul>
<li>
<a href="{% url "kfet.admin.account.new.empty" %}">
Créer un compte
</a>
</li>
{% if users_cof %}
<li>Membres du COF</li>
{% for user in users_cof %}
<li>
<a href="{% url "kfet.admin.account.new.fromuser" user.username %}">
{{ user|highlight_user:q }}
</a>
</li>
{% endfor %}
{% endif %}
{% if users_notcof %}
<li>Non-membres du COF</li>
{% for user in users_notcof %}
<li>
<a href="{% url "kfet.admin.account.new.fromuser" user.username %}">
{{ user|highlight_user:q }}
</a>
</li>
{% endfor %}
{% endif %}
{% if clippers %}
<li>Utilisateurs clipper</li>
{% for clipper in clippers %}
<li>
<a href="{% url "kfet.admin.account.new.fromclipper" clipper.username %}">
{{ clipper|highlight_clipper:q }}
</li>
{% endfor %}
{% endif %}
{% if not q %}
<li>Pas de recherche, pas de résultats !</li>
{% elif not options %}
<li>Aucune correspondance trouvée :-(</li>
{% endif %}
</ul>

View file

@ -0,0 +1,5 @@
{% csrf_token %}
{{ user_form }}
{{ cof_form }}
{{ account_form }}
<input type="submit" value="Enregistrer">

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %} | K-Fêt - ENS Ulm</title>
{% block extra_head %}{% endblock %}
{# Vieux IE pas comprendre HTML5 #}
<!--[if lt IE 9]>
<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
</head>
<body>
<nav>
<ul>
<li><a href="{% url "kfet.views.home" %}">Home</a></li>
{% if perms.kfet.add_account %}
<li>
<a href={% url "kfet.admin.account.new" %}>Créer un compte</a>
</li>
{% endif %}
</ul>
</nav>
<section id="content">
{% block content %}TGTG{% endblock %}
</section>
{% include "kfet/base_footer.html" %}
</body>
</html>

View file

View file

View file

@ -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<filter>%s)" % q2, re.IGNORECASE)
return mark_safe(re.sub(pattern, r"<span class='highlight_autocomplete'>\g<filter></span>", 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)

3
kfet/tests.py Normal file
View file

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

21
kfet/urls.py Normal file
View file

@ -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<username>.+)$', views.account_new_ajax,
name = 'kfet.admin.account.new.fromuser'),
url(r'^admin/account/new/clipper/(?P<login_clipper>.+)$', 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'),
]

150
kfet/views.py Normal file
View file

@ -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')

View file

@ -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