mirror of https://gitlab.crans.org/bde/nk20
Merge branch 'import_nk15' into 'master'
Import nk15 See merge request bde/nk20!19
This commit is contained in:
commit
9039c6fba6
|
@ -0,0 +1,27 @@
|
||||||
|
# Copyright (C) 2018-2020 by BDE ENS Paris-Saclay
|
||||||
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
|
||||||
|
from django.contrib.auth.hashers import PBKDF2PasswordHasher
|
||||||
|
from django.utils.crypto import constant_time_compare
|
||||||
|
|
||||||
|
|
||||||
|
class CustomNK15Hasher(PBKDF2PasswordHasher):
|
||||||
|
"""
|
||||||
|
Permet d'importer les mots de passe depuis la Note KFet 2015.
|
||||||
|
Si un hash de mot de passe est de la forme :
|
||||||
|
`custom_nk15$<NB>$<ENCODED>`
|
||||||
|
où <NB> est un entier quelconque (symbolisant normalement un nombre d'itérations)
|
||||||
|
et <ENCODED> le hash du mot de passe dans la Note Kfet 2015,
|
||||||
|
alors ce hasher va vérifier le mot de passe.
|
||||||
|
N'ayant pas la priorité (cf note_kfet/settings/base.py), le mot de passe sera
|
||||||
|
converti automatiquement avec l'algorithme PBKDF2.
|
||||||
|
"""
|
||||||
|
algorithm = "custom_nk15"
|
||||||
|
|
||||||
|
def verify(self, password, encoded):
|
||||||
|
if '|' in encoded:
|
||||||
|
salt, db_hashed_pass = encoded.split('$')[2].split('|')
|
||||||
|
return constant_time_compare(hashlib.sha256((salt + password).encode("utf-8")).hexdigest(), db_hashed_pass)
|
||||||
|
return super().verify(password, encoded)
|
|
@ -0,0 +1,216 @@
|
||||||
|
#!/usr/env/bin python3
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils import timezone
|
||||||
|
import psycopg2 as pg
|
||||||
|
import psycopg2.extras as pge
|
||||||
|
from django.db import transaction
|
||||||
|
|
||||||
|
import collections
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import IntegrityError
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
from note.models import Note, NoteSpecial, NoteUser, NoteClub
|
||||||
|
from note.models import Alias
|
||||||
|
from note.models import Transaction, TransactionTemplate, TemplateCategory, TransactionType
|
||||||
|
from member.models import Profile, Club
|
||||||
|
|
||||||
|
"""
|
||||||
|
Script d'import de la nk15:
|
||||||
|
TODO: import aliases
|
||||||
|
TODO: import transactions
|
||||||
|
TODO: import adhesion
|
||||||
|
TODO: import activite
|
||||||
|
TODO: import
|
||||||
|
|
||||||
|
"""
|
||||||
|
@transaction.atomic
|
||||||
|
def import_special(cur):
|
||||||
|
cur.execute("SELECT * FROM comptes WHERE idbde <0 ORDER BY idbde;")
|
||||||
|
map_idbde = dict()
|
||||||
|
for row in cur:
|
||||||
|
obj,created = NoteSpecial.objects.get_or_create(special_type = row["pseudo"],
|
||||||
|
balance = row["solde"],
|
||||||
|
is_active =True)
|
||||||
|
if created:
|
||||||
|
obj.save()
|
||||||
|
map_idbde[row["idbde"]] = obj.pk
|
||||||
|
|
||||||
|
cur.execute("SELECT * FROM comptes WHERE idbde=0;")
|
||||||
|
res = cur.fetchone()
|
||||||
|
clubBde, c = Club.objects.get_or_create(pk = 1,
|
||||||
|
name = "Bde",
|
||||||
|
email = "bureau.bde@lists.crans.org",
|
||||||
|
membership_duration = "396 00:00:00",
|
||||||
|
membership_start = "213 00:00:00",
|
||||||
|
membership_end = "273 00:00:00",
|
||||||
|
membership_fee = 5,
|
||||||
|
)
|
||||||
|
clubKfet, c = Club.objects.get_or_create(pk = 2,
|
||||||
|
name = "Kfet",
|
||||||
|
email = "tresorerie.bde@lists.crans.org",
|
||||||
|
membership_duration = "396 00:00:00",
|
||||||
|
membership_start = "213 00:00:00",
|
||||||
|
membership_end = "273 00:00:00",
|
||||||
|
membership_fee = 35,
|
||||||
|
)
|
||||||
|
clubBde.save()
|
||||||
|
clubKfet.save()
|
||||||
|
clubBde.note.solde=res["solde"]
|
||||||
|
map_idbde[0] = clubKfet.note.pk
|
||||||
|
return map_idbde
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def import_comptes(cur,map_idbde):
|
||||||
|
cur.execute("SELECT * FROM comptes WHERE idbde > 0 ORDER BY idbde;")
|
||||||
|
pkclub = 3
|
||||||
|
for row in cur:
|
||||||
|
if row["type"] == "personne":
|
||||||
|
#sanitize password
|
||||||
|
if row["passwd"] != "*|*":
|
||||||
|
passwd_nk15 = "$".join(["custom_nk15","1",row["passwd"]])
|
||||||
|
else:
|
||||||
|
passwd_nk15 = ''
|
||||||
|
try:
|
||||||
|
obj_dict = {
|
||||||
|
"username": row["pseudo"],
|
||||||
|
"password": passwd_nk15,
|
||||||
|
"first_name": row["nom"],
|
||||||
|
"last_name": row["prenom"],
|
||||||
|
"email": row["mail"],
|
||||||
|
"is_active" : False, # temporary
|
||||||
|
}
|
||||||
|
user = User.objects.create(**obj_dict)
|
||||||
|
#sanitize duplicate aliases (nk12)
|
||||||
|
except ValidationError as e:
|
||||||
|
if e.code == 'same_alias':
|
||||||
|
obj_dict["username"] = row["pseudo"]+str(row["idbde"])
|
||||||
|
user = User.objects.create(**obj_dict)
|
||||||
|
else:
|
||||||
|
raise(e)
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
obj_dict ={
|
||||||
|
"phone_number": row["tel"],
|
||||||
|
"address": row["adresse"],
|
||||||
|
"paid": row["normalien"],
|
||||||
|
"user": user,
|
||||||
|
}
|
||||||
|
profile = Profile.objects.create(**obj_dict)
|
||||||
|
note = user.note
|
||||||
|
note.balance = row["solde"]
|
||||||
|
obj_list =[user, profile, note]
|
||||||
|
else: # club
|
||||||
|
obj_dict = {
|
||||||
|
"pk":pkclub,
|
||||||
|
"name": row["pseudo"],
|
||||||
|
"email": row["mail"],
|
||||||
|
"membership_duration": "396 00:00:00",
|
||||||
|
"membership_start": "213 00:00:00",
|
||||||
|
"membership_end": "273 00:00:00",
|
||||||
|
"membership_fee": 0,
|
||||||
|
}
|
||||||
|
club,c = Club.objects.get_or_create(**obj_dict)
|
||||||
|
pkclub +=1
|
||||||
|
note = club.note
|
||||||
|
note.balance = row["solde"]
|
||||||
|
obj_list = [club,note]
|
||||||
|
for obj in obj_list:
|
||||||
|
obj.save()
|
||||||
|
map_idbde[row["idbde"]] = note.pk
|
||||||
|
return map_idbde
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def import_boutons(cur,map_idbde):
|
||||||
|
cur.execute("SELECT * FROM boutons;")
|
||||||
|
for row in cur:
|
||||||
|
cat, created = TemplateCategory.objects.get_or_create(name=row["categorie"])
|
||||||
|
obj_dict = {
|
||||||
|
"pk": row["id"],
|
||||||
|
"name": row["label"],
|
||||||
|
"amount": row["montant"],
|
||||||
|
"destination_id": map_idbde[row["destinataire"]],
|
||||||
|
"category": cat,
|
||||||
|
"display" : row["affiche"],
|
||||||
|
"description": row["description"],
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
with transaction.atomic(): # required for error management
|
||||||
|
button = TransactionTemplate.objects.create(**obj_dict)
|
||||||
|
except IntegrityError as e:
|
||||||
|
if "unique" in e.args[0]:
|
||||||
|
qs = Club.objects.filter(note__id=map_idbde[row["destinataire"]]).values('name')
|
||||||
|
note_name = qs[0]["name"]
|
||||||
|
obj_dict["name"] = ' '.join([obj_dict["name"],note_name])
|
||||||
|
button = TransactionTemplate.objects.create(**obj_dict)
|
||||||
|
else:
|
||||||
|
raise(e)
|
||||||
|
if created:
|
||||||
|
cat.save()
|
||||||
|
button.save()
|
||||||
|
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def import_transaction(cur, map_idbde):
|
||||||
|
cur.execute("SELECT * FROM transactions;")
|
||||||
|
for row in cur:
|
||||||
|
obj_dict = {
|
||||||
|
"pk":row["id"],
|
||||||
|
}
|
||||||
|
|
||||||
|
@transaction.atomic
|
||||||
|
def import_aliases(cur,map_idbde):
|
||||||
|
cur.execute("SELECT * FROM aliases ORDER by id")
|
||||||
|
for row in cur:
|
||||||
|
alias_name = row["alias"]
|
||||||
|
alias_name_good = (alias_name[:252]+'...') if len(alias_name) > 255 else alias_name
|
||||||
|
obj_dict = {
|
||||||
|
"note_id":map_idbde[row["idbde"]],
|
||||||
|
"name":alias_name_good,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
with transaction.atomic():
|
||||||
|
alias = Alias.objects.create(**obj_dict)
|
||||||
|
except IntegrityError as e:
|
||||||
|
if "unique" in e.args[0]:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
raise(e)
|
||||||
|
alias.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
"""
|
||||||
|
Command for importing the database of NK15.
|
||||||
|
Need to be run by a user with a registered role in postgres for the database nk15.
|
||||||
|
"""
|
||||||
|
def add_arguments(self,parser):
|
||||||
|
parser.add_argument('-s', '--special', action = 'store_true')
|
||||||
|
parser.add_argument('-c', '--comptes', action = 'store_true')
|
||||||
|
parser.add_argument('-b', '--boutons', action = 'store_true')
|
||||||
|
parser.add_argument('-t', '--transactions', action = 'store_true')
|
||||||
|
parser.add_argument('-a', '--aliases', action = 'store_true')
|
||||||
|
|
||||||
|
def handle(self, *args, **kwargs):
|
||||||
|
conn = pg.connect(database="nk15",user="nk15_user")
|
||||||
|
cur = conn.cursor(cursor_factory = pge.DictCursor)
|
||||||
|
|
||||||
|
if kwargs["special"]:
|
||||||
|
map_idbde = import_special(cur)
|
||||||
|
print("Minimal setup created")
|
||||||
|
|
||||||
|
if kwargs["comptes"]:
|
||||||
|
map_idbde = import_comptes(cur,map_idbde)
|
||||||
|
print("comptes table imported")
|
||||||
|
|
||||||
|
if kwargs["boutons"]:
|
||||||
|
import_boutons(cur,map_idbde)
|
||||||
|
print("boutons table imported")
|
||||||
|
if kwargs["transactions"]:
|
||||||
|
import_transaction(cur)
|
||||||
|
if kwargs["aliases"]:
|
||||||
|
import_aliases(cur,map_idbde)
|
||||||
|
print("aliases imported")
|
|
@ -7,7 +7,8 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
|
||||||
PolymorphicChildModelFilter, PolymorphicParentModelAdmin
|
PolymorphicChildModelFilter, PolymorphicParentModelAdmin
|
||||||
|
|
||||||
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
||||||
from .models.transactions import Transaction, TransactionCategory, TransactionTemplate
|
from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \
|
||||||
|
TemplateTransaction, MembershipTransaction
|
||||||
|
|
||||||
|
|
||||||
class AliasInlines(admin.TabularInline):
|
class AliasInlines(admin.TabularInline):
|
||||||
|
@ -97,13 +98,14 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
|
||||||
|
|
||||||
|
|
||||||
@admin.register(Transaction)
|
@admin.register(Transaction)
|
||||||
class TransactionAdmin(admin.ModelAdmin):
|
class TransactionAdmin(PolymorphicParentModelAdmin):
|
||||||
"""
|
"""
|
||||||
Admin customisation for Transaction
|
Admin customisation for Transaction
|
||||||
"""
|
"""
|
||||||
|
child_models = (TemplateTransaction, MembershipTransaction)
|
||||||
list_display = ('created_at', 'poly_source', 'poly_destination',
|
list_display = ('created_at', 'poly_source', 'poly_destination',
|
||||||
'quantity', 'amount', 'transaction_type', 'valid')
|
'quantity', 'amount', 'valid')
|
||||||
list_filter = ('transaction_type', 'valid')
|
list_filter = ('valid',)
|
||||||
autocomplete_fields = (
|
autocomplete_fields = (
|
||||||
'source',
|
'source',
|
||||||
'destination',
|
'destination',
|
||||||
|
@ -132,7 +134,7 @@ class TransactionAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
if obj: # user is editing an existing object
|
if obj: # user is editing an existing object
|
||||||
return 'created_at', 'source', 'destination', 'quantity',\
|
return 'created_at', 'source', 'destination', 'quantity',\
|
||||||
'amount', 'transaction_type'
|
'amount'
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
@ -141,8 +143,8 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Admin customisation for TransactionTemplate
|
Admin customisation for TransactionTemplate
|
||||||
"""
|
"""
|
||||||
list_display = ('name', 'poly_destination', 'amount', 'template_type')
|
list_display = ('name', 'poly_destination', 'amount', 'category', 'display', )
|
||||||
list_filter = ('template_type', )
|
list_filter = ('category', 'display')
|
||||||
autocomplete_fields = ('destination', )
|
autocomplete_fields = ('destination', )
|
||||||
|
|
||||||
def poly_destination(self, obj):
|
def poly_destination(self, obj):
|
||||||
|
@ -154,8 +156,8 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
|
||||||
poly_destination.short_description = _('destination')
|
poly_destination.short_description = _('destination')
|
||||||
|
|
||||||
|
|
||||||
@admin.register(TransactionCategory)
|
@admin.register(TemplateCategory)
|
||||||
class TransactionCategoryAdmin(admin.ModelAdmin):
|
class TemplateCategoryAdmin(admin.ModelAdmin):
|
||||||
"""
|
"""
|
||||||
Admin customisation for TransactionTemplate
|
Admin customisation for TransactionTemplate
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -162,56 +162,56 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "note.transactioncategory",
|
"model": "note.templatecategory",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Soft"
|
"name": "Soft"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "note.transactioncategory",
|
"model": "note.templatecategory",
|
||||||
"pk": 2,
|
"pk": 2,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Pulls"
|
"name": "Pulls"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "note.transactioncategory",
|
"model": "note.templatecategory",
|
||||||
"pk": 3,
|
"pk": 3,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Gala"
|
"name": "Gala"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "note.transactioncategory",
|
"model": "note.templatecategory",
|
||||||
"pk": 4,
|
"pk": 4,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Clubs"
|
"name": "Clubs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "note.transactioncategory",
|
"model": "note.templatecategory",
|
||||||
"pk": 5,
|
"pk": 5,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Bouffe"
|
"name": "Bouffe"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "note.transactioncategory",
|
"model": "note.templatecategory",
|
||||||
"pk": 6,
|
"pk": 6,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "BDA"
|
"name": "BDA"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "note.transactioncategory",
|
"model": "note.templatecategory",
|
||||||
"pk": 7,
|
"pk": 7,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Autre"
|
"name": "Autre"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"model": "note.transactioncategory",
|
"model": "note.templatecategory",
|
||||||
"pk": 8,
|
"pk": 8,
|
||||||
"fields": {
|
"fields": {
|
||||||
"name": "Alcool"
|
"name": "Alcool"
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
from dal import autocomplete
|
from dal import autocomplete
|
||||||
from django import forms
|
from django import forms
|
||||||
|
|
||||||
from .models import Transaction, TransactionTemplate
|
from .models import Transaction, TransactionTemplate, TemplateTransaction
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateForm(forms.ModelForm):
|
class TransactionTemplateForm(forms.ModelForm):
|
||||||
|
@ -71,12 +71,13 @@ class ConsoForm(forms.ModelForm):
|
||||||
name=self.data['button']).get()
|
name=self.data['button']).get()
|
||||||
self.instance.destination = button.destination
|
self.instance.destination = button.destination
|
||||||
self.instance.amount = button.amount
|
self.instance.amount = button.amount
|
||||||
self.instance.transaction_type = 'bouton'
|
self.instance.reason = '{} ({})'.format(button.name, button.category)
|
||||||
self.instance.reason = button.name
|
self.instance.name = button.name
|
||||||
|
self.instance.category = button.category
|
||||||
super().save(commit)
|
super().save(commit)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Transaction
|
model = TemplateTransaction
|
||||||
fields = ('source', )
|
fields = ('source', )
|
||||||
|
|
||||||
# Le champ d'utilisateur est remplacé par un champ d'auto-complétion.
|
# Le champ d'utilisateur est remplacé par un champ d'auto-complétion.
|
||||||
|
|
|
@ -3,11 +3,12 @@
|
||||||
|
|
||||||
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
||||||
from .transactions import MembershipTransaction, Transaction, \
|
from .transactions import MembershipTransaction, Transaction, \
|
||||||
TransactionCategory, TransactionTemplate
|
TemplateCategory, TransactionTemplate, TemplateTransaction
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
# Notes
|
# Notes
|
||||||
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
|
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
|
||||||
# Transactions
|
# Transactions
|
||||||
'MembershipTransaction', 'Transaction', 'TransactionCategory', 'TransactionTemplate',
|
'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate',
|
||||||
|
'TemplateTransaction',
|
||||||
]
|
]
|
||||||
|
|
|
@ -64,7 +64,8 @@ class Note(PolymorphicModel):
|
||||||
if aliases.exists():
|
if aliases.exists():
|
||||||
# Alias exists, so check if it is linked to this note
|
# Alias exists, so check if it is linked to this note
|
||||||
if aliases.first().note != self:
|
if aliases.first().note != self:
|
||||||
raise ValidationError(_('This alias is already taken.'))
|
raise ValidationError(_('This alias is already taken.'),
|
||||||
|
code="same_alias")
|
||||||
|
|
||||||
# Save note
|
# Save note
|
||||||
super().save(*args, **kwargs)
|
super().save(*args, **kwargs)
|
||||||
|
@ -87,7 +88,8 @@ class Note(PolymorphicModel):
|
||||||
if aliases.exists():
|
if aliases.exists():
|
||||||
# Alias exists, so check if it is linked to this note
|
# Alias exists, so check if it is linked to this note
|
||||||
if aliases.first().note != self:
|
if aliases.first().note != self:
|
||||||
raise ValidationError(_('This alias is already taken.'))
|
raise ValidationError(_('This alias is already taken.'),
|
||||||
|
code="same_alias",)
|
||||||
else:
|
else:
|
||||||
# Alias does not exist yet, so check if it can exist
|
# Alias does not exist yet, so check if it can exist
|
||||||
a = Alias(name=str(self))
|
a = Alias(name=str(self))
|
||||||
|
@ -222,16 +224,19 @@ class Alias(models.Model):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
normalized_name = Alias.normalize(self.name)
|
normalized_name = Alias.normalize(self.name)
|
||||||
if len(normalized_name) >= 255:
|
if len(normalized_name) >= 255:
|
||||||
raise ValidationError(_('Alias too long.'))
|
raise ValidationError(_('Alias is too long.'),
|
||||||
|
code='alias_too_long')
|
||||||
try:
|
try:
|
||||||
if self != Alias.objects.get(normalized_name=normalized_name):
|
sim_alias = Alias.objects.get(normalized_name=normalized_name)
|
||||||
raise ValidationError(
|
if self != sim_alias:
|
||||||
_('An alias with a similar name '
|
raise ValidationError(_('An alias with a similar name already exists:'),
|
||||||
'already exists.'))
|
code="same_alias"
|
||||||
|
)
|
||||||
except Alias.DoesNotExist:
|
except Alias.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def delete(self, using=None, keep_parents=False):
|
def delete(self, using=None, keep_parents=False):
|
||||||
if self.name == str(self.note):
|
if self.name == str(self.note):
|
||||||
raise ValidationError(_("You can't delete your main alias."))
|
raise ValidationError(_("You can't delete your main alias."),
|
||||||
|
code="cant_delete_main_alias")
|
||||||
return super().delete(using, keep_parents)
|
return super().delete(using, keep_parents)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from django.db import models
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from polymorphic.models import PolymorphicModel
|
||||||
|
|
||||||
from .notes import Note, NoteClub
|
from .notes import Note, NoteClub
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ Defines transactions
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class TransactionCategory(models.Model):
|
class TemplateCategory(models.Model):
|
||||||
"""
|
"""
|
||||||
Defined a recurrent transaction category
|
Defined a recurrent transaction category
|
||||||
|
|
||||||
|
@ -43,6 +44,7 @@ class TransactionTemplate(models.Model):
|
||||||
verbose_name=_('name'),
|
verbose_name=_('name'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
unique=True,
|
unique=True,
|
||||||
|
error_messages={'unique':_("A template with this name already exist")},
|
||||||
)
|
)
|
||||||
destination = models.ForeignKey(
|
destination = models.ForeignKey(
|
||||||
NoteClub,
|
NoteClub,
|
||||||
|
@ -54,12 +56,19 @@ class TransactionTemplate(models.Model):
|
||||||
verbose_name=_('amount'),
|
verbose_name=_('amount'),
|
||||||
help_text=_('in centimes'),
|
help_text=_('in centimes'),
|
||||||
)
|
)
|
||||||
template_type = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
TransactionCategory,
|
TemplateCategory,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
verbose_name=_('type'),
|
verbose_name=_('type'),
|
||||||
max_length=31,
|
max_length=31,
|
||||||
)
|
)
|
||||||
|
display = models.BooleanField(
|
||||||
|
default = True,
|
||||||
|
)
|
||||||
|
description = models.CharField(
|
||||||
|
verbose_name=_('description'),
|
||||||
|
max_length=255,
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = _("transaction template")
|
verbose_name = _("transaction template")
|
||||||
|
@ -69,7 +78,7 @@ class TransactionTemplate(models.Model):
|
||||||
return reverse('note:template_update', args=(self.pk, ))
|
return reverse('note:template_update', args=(self.pk, ))
|
||||||
|
|
||||||
|
|
||||||
class Transaction(models.Model):
|
class Transaction(PolymorphicModel):
|
||||||
"""
|
"""
|
||||||
General transaction between two :model:`note.Note`
|
General transaction between two :model:`note.Note`
|
||||||
|
|
||||||
|
@ -100,10 +109,6 @@ class Transaction(models.Model):
|
||||||
default=1,
|
default=1,
|
||||||
)
|
)
|
||||||
amount = models.PositiveIntegerField(verbose_name=_('amount'), )
|
amount = models.PositiveIntegerField(verbose_name=_('amount'), )
|
||||||
transaction_type = models.CharField(
|
|
||||||
verbose_name=_('type'),
|
|
||||||
max_length=31,
|
|
||||||
)
|
|
||||||
reason = models.CharField(
|
reason = models.CharField(
|
||||||
verbose_name=_('reason'),
|
verbose_name=_('reason'),
|
||||||
max_length=255,
|
max_length=255,
|
||||||
|
@ -144,6 +149,26 @@ class Transaction(models.Model):
|
||||||
return self.amount * self.quantity
|
return self.amount * self.quantity
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateTransaction(Transaction):
|
||||||
|
"""
|
||||||
|
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
template = models.ForeignKey(
|
||||||
|
TransactionTemplate,
|
||||||
|
null=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
)
|
||||||
|
category = models.ForeignKey(
|
||||||
|
TemplateCategory,
|
||||||
|
on_delete=models.PROTECT,
|
||||||
|
)
|
||||||
|
name = models.CharField(
|
||||||
|
max_length=255,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class MembershipTransaction(Transaction):
|
class MembershipTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
||||||
|
|
|
@ -8,7 +8,7 @@ from django.urls import reverse
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import CreateView, ListView, UpdateView
|
from django.views.generic import CreateView, ListView, UpdateView
|
||||||
|
|
||||||
from .models import Transaction, TransactionTemplate, Alias
|
from .models import Transaction, TransactionTemplate, Alias, TemplateTransaction
|
||||||
from .forms import TransactionForm, TransactionTemplateForm, ConsoForm
|
from .forms import TransactionForm, TransactionTemplateForm, ConsoForm
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ class ConsoView(LoginRequiredMixin, CreateView):
|
||||||
"""
|
"""
|
||||||
Consume
|
Consume
|
||||||
"""
|
"""
|
||||||
model = Transaction
|
model = TemplateTransaction
|
||||||
template_name = "note/conso_form.html"
|
template_name = "note/conso_form.html"
|
||||||
form_class = ConsoForm
|
form_class = ConsoForm
|
||||||
|
|
||||||
|
@ -138,8 +138,8 @@ class ConsoView(LoginRequiredMixin, CreateView):
|
||||||
Add some context variables in template such as page title
|
Add some context variables in template such as page title
|
||||||
"""
|
"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['transaction_templates'] = TransactionTemplate.objects.all() \
|
context['transaction_templates'] = TransactionTemplate.objects.filter(display=True) \
|
||||||
.order_by('template_type')
|
.order_by('category')
|
||||||
context['title'] = _("Consommations")
|
context['title'] = _("Consommations")
|
||||||
|
|
||||||
# select2 compatibility
|
# select2 compatibility
|
||||||
|
@ -152,3 +152,4 @@ class ConsoView(LoginRequiredMixin, CreateView):
|
||||||
When clicking a button, reload the same page
|
When clicking a button, reload the same page
|
||||||
"""
|
"""
|
||||||
return reverse('note:consos')
|
return reverse('note:consos')
|
||||||
|
|
||||||
|
|
|
@ -117,6 +117,12 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Use our custom hasher in order to import NK15 passwords
|
||||||
|
PASSWORD_HASHERS = [
|
||||||
|
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
|
||||||
|
'member.hashers.CustomNK15Hasher',
|
||||||
|
]
|
||||||
|
|
||||||
# Django Guardian object permissions
|
# Django Guardian object permissions
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = (
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{# Regroup buttons under categories #}
|
{# Regroup buttons under categories #}
|
||||||
{% regroup transaction_templates by template_type as template_types %}
|
{% regroup transaction_templates by category as categories %}
|
||||||
|
|
||||||
<form method="post" onsubmit="window.onbeforeunload=null">
|
<form method="post" onsubmit="window.onbeforeunload=null">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
@ -44,10 +44,10 @@
|
||||||
{# Tabs for button categories #}
|
{# Tabs for button categories #}
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<ul class="nav nav-tabs nav-fill card-header-tabs">
|
<ul class="nav nav-tabs nav-fill card-header-tabs">
|
||||||
{% for template_type in template_types %}
|
{% for category in categories %}
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" data-toggle="tab" href="#{{ template_type.grouper|slugify }}">
|
<a class="nav-link" data-toggle="tab" href="#{{ category.grouper|slugify }}">
|
||||||
{{ template_type.grouper }}
|
{{ category.grouper }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -57,10 +57,10 @@
|
||||||
{# Tabs content #}
|
{# Tabs content #}
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="tab-content">
|
<div class="tab-content">
|
||||||
{% for template_type in template_types %}
|
{% for category in categories %}
|
||||||
<div class="tab-pane" id="{{ template_type.grouper|slugify }}">
|
<div class="tab-pane" id="{{ category.grouper|slugify }}">
|
||||||
<div class="d-inline-flex flex-wrap justify-content-center">
|
<div class="d-inline-flex flex-wrap justify-content-center">
|
||||||
{% for button in template_type.list %}
|
{% for button in category.list %}
|
||||||
<button class="btn btn-outline-dark rounded-0 flex-fill"
|
<button class="btn btn-outline-dark rounded-0 flex-fill"
|
||||||
name="button" value="{{ button.name }}">
|
name="button" value="{{ button.name }}">
|
||||||
{{ button.name }} ({{ button.amount | pretty_money }})
|
{{ button.name }} ({{ button.amount | pretty_money }})
|
||||||
|
|
Loading…
Reference in New Issue