mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-11-04 09:12:11 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/master' into cas
This commit is contained in:
		@@ -11,7 +11,7 @@ On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout n
 | 
			
		||||
1. Paquets nécessaires
 | 
			
		||||
 | 
			
		||||
        $ sudo apt install nginx python3 python3-pip python3-dev uwsgi
 | 
			
		||||
        $ sudo apt install uwsgi-plugin-python3 python3-virtualenv git
 | 
			
		||||
        $ sudo apt install uwsgi-plugin-python3 python3-venv git acl
 | 
			
		||||
 | 
			
		||||
2. Clonage du dépot
 | 
			
		||||
 | 
			
		||||
@@ -29,8 +29,8 @@ On supposera pour la suite que vous utiliser debian/ubuntu sur un serveur tout n
 | 
			
		||||
 | 
			
		||||
   À la racine du projet:
 | 
			
		||||
 | 
			
		||||
        $ virtualenv env
 | 
			
		||||
        $ source /env/bin/activate
 | 
			
		||||
        $ python3 -m venv env
 | 
			
		||||
        $ source env/bin/activate
 | 
			
		||||
        (env)$ pip3 install -r requirements.txt
 | 
			
		||||
        (env)$ deactivate
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,11 @@ from crispy_forms.layout import Layout
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SignUpForm(UserCreationForm):
 | 
			
		||||
    def __init__(self,*args,**kwargs):
 | 
			
		||||
        super().__init__(*args,**kwargs)
 | 
			
		||||
        self.fields['username'].widget.attrs.pop("autofocus", None)
 | 
			
		||||
        self.fields['first_name'].widget.attrs.update({"autofocus":"autofocus"})
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = User
 | 
			
		||||
        fields = ['first_name', 'last_name', 'username', 'email']
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								apps/member/hashers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								apps/member/hashers.py
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
			
		||||
@@ -114,12 +114,13 @@ class UserDetailView(LoginRequiredMixin, DetailView):
 | 
			
		||||
    """
 | 
			
		||||
    Affiche les informations sur un utilisateur, sa note, ses clubs...
 | 
			
		||||
    """
 | 
			
		||||
    model = Profile
 | 
			
		||||
    context_object_name = "profile"
 | 
			
		||||
    model = User
 | 
			
		||||
    context_object_name = "user_object"
 | 
			
		||||
    template_name = "member/profile_detail.html"
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        user = context['profile'].user
 | 
			
		||||
        user = context['user_object']
 | 
			
		||||
        history_list = \
 | 
			
		||||
            Transaction.objects.all().filter(Q(source=user.note) | Q(destination=user.note))
 | 
			
		||||
        context['history_list'] = HistoryTable(history_list)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,8 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
 | 
			
		||||
    PolymorphicChildModelFilter, PolymorphicParentModelAdmin
 | 
			
		||||
 | 
			
		||||
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):
 | 
			
		||||
@@ -97,13 +98,14 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(Transaction)
 | 
			
		||||
class TransactionAdmin(admin.ModelAdmin):
 | 
			
		||||
class TransactionAdmin(PolymorphicParentModelAdmin):
 | 
			
		||||
    """
 | 
			
		||||
    Admin customisation for Transaction
 | 
			
		||||
    """
 | 
			
		||||
    child_models = (TemplateTransaction, MembershipTransaction)
 | 
			
		||||
    list_display = ('created_at', 'poly_source', 'poly_destination',
 | 
			
		||||
                    'quantity', 'amount', 'transaction_type', 'valid')
 | 
			
		||||
    list_filter = ('transaction_type', 'valid')
 | 
			
		||||
                    'quantity', 'amount', 'valid')
 | 
			
		||||
    list_filter = ('valid',)
 | 
			
		||||
    autocomplete_fields = (
 | 
			
		||||
        'source',
 | 
			
		||||
        'destination',
 | 
			
		||||
@@ -132,7 +134,7 @@ class TransactionAdmin(admin.ModelAdmin):
 | 
			
		||||
        """
 | 
			
		||||
        if obj:  # user is editing an existing object
 | 
			
		||||
            return 'created_at', 'source', 'destination', 'quantity',\
 | 
			
		||||
                   'amount', 'transaction_type'
 | 
			
		||||
                   'amount'
 | 
			
		||||
        return []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -141,8 +143,8 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
 | 
			
		||||
    """
 | 
			
		||||
    Admin customisation for TransactionTemplate
 | 
			
		||||
    """
 | 
			
		||||
    list_display = ('name', 'poly_destination', 'amount', 'template_type')
 | 
			
		||||
    list_filter = ('template_type', )
 | 
			
		||||
    list_display = ('name', 'poly_destination', 'amount', 'category', 'display', )
 | 
			
		||||
    list_filter = ('category', 'display')
 | 
			
		||||
    autocomplete_fields = ('destination', )
 | 
			
		||||
 | 
			
		||||
    def poly_destination(self, obj):
 | 
			
		||||
@@ -154,8 +156,8 @@ class TransactionTemplateAdmin(admin.ModelAdmin):
 | 
			
		||||
    poly_destination.short_description = _('destination')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@admin.register(TransactionCategory)
 | 
			
		||||
class TransactionCategoryAdmin(admin.ModelAdmin):
 | 
			
		||||
@admin.register(TemplateCategory)
 | 
			
		||||
class TemplateCategoryAdmin(admin.ModelAdmin):
 | 
			
		||||
    """
 | 
			
		||||
    Admin customisation for TransactionTemplate
 | 
			
		||||
    """
 | 
			
		||||
 
 | 
			
		||||
@@ -162,56 +162,56 @@
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "note.transactioncategory",
 | 
			
		||||
        "model": "note.templatecategory",
 | 
			
		||||
        "pk": 1,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "Soft"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "note.transactioncategory",
 | 
			
		||||
        "model": "note.templatecategory",
 | 
			
		||||
        "pk": 2,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "Pulls"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "note.transactioncategory",
 | 
			
		||||
        "model": "note.templatecategory",
 | 
			
		||||
        "pk": 3,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "Gala"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "note.transactioncategory",
 | 
			
		||||
        "model": "note.templatecategory",
 | 
			
		||||
        "pk": 4,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "Clubs"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "note.transactioncategory",
 | 
			
		||||
        "model": "note.templatecategory",
 | 
			
		||||
        "pk": 5,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "Bouffe"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "note.transactioncategory",
 | 
			
		||||
        "model": "note.templatecategory",
 | 
			
		||||
        "pk": 6,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "BDA"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "note.transactioncategory",
 | 
			
		||||
        "model": "note.templatecategory",
 | 
			
		||||
        "pk": 7,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "Autre"
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
        "model": "note.transactioncategory",
 | 
			
		||||
        "model": "note.templatecategory",
 | 
			
		||||
        "pk": 8,
 | 
			
		||||
        "fields": {
 | 
			
		||||
            "name": "Alcool"
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
from dal import autocomplete
 | 
			
		||||
from django import forms
 | 
			
		||||
 | 
			
		||||
from .models import Transaction, TransactionTemplate
 | 
			
		||||
from .models import Transaction, TransactionTemplate, TemplateTransaction
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TransactionTemplateForm(forms.ModelForm):
 | 
			
		||||
@@ -31,8 +31,6 @@ class TransactionTemplateForm(forms.ModelForm):
 | 
			
		||||
 | 
			
		||||
class TransactionForm(forms.ModelForm):
 | 
			
		||||
    def save(self, commit=True):
 | 
			
		||||
        self.instance.transaction_type = 'transfert'
 | 
			
		||||
 | 
			
		||||
        super().save(commit)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
@@ -71,12 +69,13 @@ class ConsoForm(forms.ModelForm):
 | 
			
		||||
            name=self.data['button']).get()
 | 
			
		||||
        self.instance.destination = button.destination
 | 
			
		||||
        self.instance.amount = button.amount
 | 
			
		||||
        self.instance.transaction_type = 'bouton'
 | 
			
		||||
        self.instance.reason = button.name
 | 
			
		||||
        self.instance.reason = '{} ({})'.format(button.name, button.category)
 | 
			
		||||
        self.instance.name = button.name
 | 
			
		||||
        self.instance.category = button.category
 | 
			
		||||
        super().save(commit)
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Transaction
 | 
			
		||||
        model = TemplateTransaction
 | 
			
		||||
        fields = ('source', )
 | 
			
		||||
 | 
			
		||||
        # 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 .transactions import MembershipTransaction, Transaction, \
 | 
			
		||||
    TransactionCategory, TransactionTemplate
 | 
			
		||||
    TemplateCategory, TransactionTemplate, TemplateTransaction
 | 
			
		||||
 | 
			
		||||
__all__ = [
 | 
			
		||||
    # Notes
 | 
			
		||||
    'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
 | 
			
		||||
    # Transactions
 | 
			
		||||
    'MembershipTransaction', 'Transaction', 'TransactionCategory', 'TransactionTemplate',
 | 
			
		||||
    'MembershipTransaction', 'Transaction', 'TemplateCategory', 'TransactionTemplate',
 | 
			
		||||
    'TemplateTransaction',
 | 
			
		||||
]
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,12 @@ class Note(PolymorphicModel):
 | 
			
		||||
        help_text=_('in centimes, money credited for this instance'),
 | 
			
		||||
        default=0,
 | 
			
		||||
    )
 | 
			
		||||
    last_negative= models.DateTimeField(
 | 
			
		||||
        verbose_name=_('last negative date'),
 | 
			
		||||
        help_text=_('last time the balance was negative'),
 | 
			
		||||
        null=True,
 | 
			
		||||
        blank=True,
 | 
			
		||||
    )
 | 
			
		||||
    is_active = models.BooleanField(
 | 
			
		||||
        _('active'),
 | 
			
		||||
        default=True,
 | 
			
		||||
@@ -64,7 +70,8 @@ class Note(PolymorphicModel):
 | 
			
		||||
        if aliases.exists():
 | 
			
		||||
            # Alias exists, so check if it is linked to this note
 | 
			
		||||
            if aliases.first().note != self:
 | 
			
		||||
                raise ValidationError(_('This alias is already taken.'))
 | 
			
		||||
                raise ValidationError(_('This alias is already taken.'),
 | 
			
		||||
                                      code="same_alias")
 | 
			
		||||
 | 
			
		||||
            # Save note
 | 
			
		||||
            super().save(*args, **kwargs)
 | 
			
		||||
@@ -87,7 +94,8 @@ class Note(PolymorphicModel):
 | 
			
		||||
        if aliases.exists():
 | 
			
		||||
            # Alias exists, so check if it is linked to this note
 | 
			
		||||
            if aliases.first().note != self:
 | 
			
		||||
                raise ValidationError(_('This alias is already taken.'))
 | 
			
		||||
                raise ValidationError(_('This alias is already taken.'),
 | 
			
		||||
                                      code="same_alias",)
 | 
			
		||||
        else:
 | 
			
		||||
            # Alias does not exist yet, so check if it can exist
 | 
			
		||||
            a = Alias(name=str(self))
 | 
			
		||||
@@ -222,16 +230,19 @@ class Alias(models.Model):
 | 
			
		||||
    def clean(self):
 | 
			
		||||
        normalized_name = Alias.normalize(self.name)
 | 
			
		||||
        if len(normalized_name) >= 255:
 | 
			
		||||
            raise ValidationError(_('Alias too long.'))
 | 
			
		||||
            raise ValidationError(_('Alias is too long.'),
 | 
			
		||||
                                  code='alias_too_long')
 | 
			
		||||
        try:
 | 
			
		||||
            if self != Alias.objects.get(normalized_name=normalized_name):
 | 
			
		||||
                raise ValidationError(
 | 
			
		||||
                    _('An alias with a similar name '
 | 
			
		||||
                      'already exists.'))
 | 
			
		||||
            sim_alias = Alias.objects.get(normalized_name=normalized_name)
 | 
			
		||||
            if self != sim_alias:
 | 
			
		||||
                raise ValidationError(_('An alias with a similar name already exists:'),
 | 
			
		||||
                                       code="same_alias"
 | 
			
		||||
                )
 | 
			
		||||
        except Alias.DoesNotExist:
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
    def delete(self, using=None, keep_parents=False):
 | 
			
		||||
        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)
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ from django.db import models
 | 
			
		||||
from django.utils import timezone
 | 
			
		||||
from django.utils.translation import gettext_lazy as _
 | 
			
		||||
from django.urls import reverse
 | 
			
		||||
from polymorphic.models import PolymorphicModel
 | 
			
		||||
 | 
			
		||||
from .notes import Note, NoteClub
 | 
			
		||||
 | 
			
		||||
@@ -13,7 +14,7 @@ Defines transactions
 | 
			
		||||
"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TransactionCategory(models.Model):
 | 
			
		||||
class TemplateCategory(models.Model):
 | 
			
		||||
    """
 | 
			
		||||
    Defined a recurrent transaction category
 | 
			
		||||
 | 
			
		||||
@@ -43,6 +44,7 @@ class TransactionTemplate(models.Model):
 | 
			
		||||
        verbose_name=_('name'),
 | 
			
		||||
        max_length=255,
 | 
			
		||||
        unique=True,
 | 
			
		||||
        error_messages={'unique':_("A template with this name already exist")},
 | 
			
		||||
    )
 | 
			
		||||
    destination = models.ForeignKey(
 | 
			
		||||
        NoteClub,
 | 
			
		||||
@@ -54,12 +56,19 @@ class TransactionTemplate(models.Model):
 | 
			
		||||
        verbose_name=_('amount'),
 | 
			
		||||
        help_text=_('in centimes'),
 | 
			
		||||
    )
 | 
			
		||||
    template_type = models.ForeignKey(
 | 
			
		||||
        TransactionCategory,
 | 
			
		||||
    category = models.ForeignKey(
 | 
			
		||||
        TemplateCategory,
 | 
			
		||||
        on_delete=models.PROTECT,
 | 
			
		||||
        verbose_name=_('type'),
 | 
			
		||||
        max_length=31,
 | 
			
		||||
    )
 | 
			
		||||
    display = models.BooleanField(
 | 
			
		||||
        default = True,
 | 
			
		||||
    )
 | 
			
		||||
    description = models.CharField(
 | 
			
		||||
        verbose_name=_('description'),
 | 
			
		||||
        max_length=255,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        verbose_name = _("transaction template")
 | 
			
		||||
@@ -69,7 +78,7 @@ class TransactionTemplate(models.Model):
 | 
			
		||||
        return reverse('note:template_update', args=(self.pk, ))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class Transaction(models.Model):
 | 
			
		||||
class Transaction(PolymorphicModel):
 | 
			
		||||
    """
 | 
			
		||||
    General transaction between two :model:`note.Note`
 | 
			
		||||
 | 
			
		||||
@@ -100,10 +109,6 @@ class Transaction(models.Model):
 | 
			
		||||
        default=1,
 | 
			
		||||
    )
 | 
			
		||||
    amount = models.PositiveIntegerField(verbose_name=_('amount'), )
 | 
			
		||||
    transaction_type = models.CharField(
 | 
			
		||||
        verbose_name=_('type'),
 | 
			
		||||
        max_length=31,
 | 
			
		||||
    )
 | 
			
		||||
    reason = models.CharField(
 | 
			
		||||
        verbose_name=_('reason'),
 | 
			
		||||
        max_length=255,
 | 
			
		||||
@@ -144,6 +149,22 @@ class Transaction(models.Model):
 | 
			
		||||
        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,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
class MembershipTransaction(Transaction):
 | 
			
		||||
    """
 | 
			
		||||
    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.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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -129,7 +129,7 @@ class ConsoView(LoginRequiredMixin, CreateView):
 | 
			
		||||
    """
 | 
			
		||||
    Consume
 | 
			
		||||
    """
 | 
			
		||||
    model = Transaction
 | 
			
		||||
    model = TemplateTransaction
 | 
			
		||||
    template_name = "note/conso_form.html"
 | 
			
		||||
    form_class = ConsoForm
 | 
			
		||||
 | 
			
		||||
@@ -138,8 +138,8 @@ class ConsoView(LoginRequiredMixin, CreateView):
 | 
			
		||||
        Add some context variables in template such as page title
 | 
			
		||||
        """
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        context['transaction_templates'] = TransactionTemplate.objects.all() \
 | 
			
		||||
            .order_by('template_type')
 | 
			
		||||
        context['transaction_templates'] = TransactionTemplate.objects.filter(display=True) \
 | 
			
		||||
            .order_by('category')
 | 
			
		||||
        context['title'] = _("Consommations")
 | 
			
		||||
 | 
			
		||||
        # select2 compatibility
 | 
			
		||||
@@ -152,3 +152,4 @@ class ConsoView(LoginRequiredMixin, CreateView):
 | 
			
		||||
        When clicking a button, reload the same page
 | 
			
		||||
        """
 | 
			
		||||
        return reverse('note:consos')
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -121,6 +121,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
 | 
			
		||||
 | 
			
		||||
AUTHENTICATION_BACKENDS = (
 | 
			
		||||
 
 | 
			
		||||
@@ -59,8 +59,8 @@ SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
    <nav class="navbar navbar-expand-md navbar-light bg-light fixed-navbar shadow-sm">
 | 
			
		||||
        <a class="navbar-brand" href="/">{{ request.site.name }}</a>
 | 
			
		||||
        <button class="navbar-toggler" type="button" data-toggle="collapse"
 | 
			
		||||
                data-target="#navbarNavAltMarkup"
 | 
			
		||||
                aria-controls="navbarNavAltMarkup" aria-expanded="false"
 | 
			
		||||
                data-target="#navbarNavDropdown"
 | 
			
		||||
                aria-controls="navbarNavDropdown" aria-expanded="false"
 | 
			
		||||
                aria-label="Toggle navigation">
 | 
			
		||||
            <span class="navbar-toggler-icon"></span>
 | 
			
		||||
        </button>
 | 
			
		||||
@@ -87,7 +87,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
 | 
			
		||||
                        </a>
 | 
			
		||||
                        <div class="dropdown-menu dropdown-menu-right"
 | 
			
		||||
                             aria-labelledby="navbarDropdownMenuLink">
 | 
			
		||||
                            <a class="dropdown-item" href="{% url 'member:user_detail' pk=user.profile.pk %}">
 | 
			
		||||
                            <a class="dropdown-item" href="{% url 'member:user_detail' pk=user.pk %}">
 | 
			
		||||
                                <i class="fa fa-user"></i> Mon compte
 | 
			
		||||
                            </a>
 | 
			
		||||
                            <a class="dropdown-item" href="{% url 'logout' %}">
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,14 @@
 | 
			
		||||
<div class="row mt-4">
 | 
			
		||||
    <div class="col-md-3 mb-4">
 | 
			
		||||
        <div class="card bg-light shadow">
 | 
			
		||||
            <img src="{{ object.note.display_image.url }}" class="card-img-top" alt="">
 | 
			
		||||
            <img src="{{ object.note.display_image }}" class="card-img-top" alt="">
 | 
			
		||||
            <div class="card-body">
 | 
			
		||||
                <dl class="row">
 | 
			
		||||
                    <dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.user.last_name }} {{ object.user.first_name }}</dd>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.last_name }} {{ object.first_name }}</dd>
 | 
			
		||||
 | 
			
		||||
                    <dt class="col-xl-6">{% trans 'username'|capfirst %}</dt>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.user.username }}</dd>
 | 
			
		||||
                    <dd class="col-xl-6">{{ user.username }}</dd>
 | 
			
		||||
 | 
			
		||||
                    <dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
 | 
			
		||||
                    <dd class="col-xl-6">
 | 
			
		||||
@@ -22,19 +22,19 @@
 | 
			
		||||
                    </dd>
 | 
			
		||||
 | 
			
		||||
                    <dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.section }}</dd>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.profile.section }}</dd>
 | 
			
		||||
 | 
			
		||||
                    <dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.address }}</dd>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.profile.address }}</dd>
 | 
			
		||||
 | 
			
		||||
                    <dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.user.note.balance | pretty_money }}</dd>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.note.balance | pretty_money }}</dd>
 | 
			
		||||
 | 
			
		||||
                    <dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.user.note.alias_set.all|join:", " }}</dd>
 | 
			
		||||
                    <dd class="col-xl-6">{{ object.note.alias_set.all|join:", " }}</dd>
 | 
			
		||||
                </dl>
 | 
			
		||||
 | 
			
		||||
                {% if object.user.pk == user.pk %}
 | 
			
		||||
                {% if object.pk == user.pk %}
 | 
			
		||||
                    <a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
 | 
			
		||||
                {% endif %}
 | 
			
		||||
            </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@
 | 
			
		||||
      {% csrf_token %}
 | 
			
		||||
      {{ form|crispy }}
 | 
			
		||||
      {{ profile_form|crispy }}
 | 
			
		||||
      <button class="btn btn-link" type="submit">
 | 
			
		||||
      <button class="btn btn-success" type="submit">
 | 
			
		||||
          {% trans "Sign Up" %}
 | 
			
		||||
      </button>
 | 
			
		||||
  </form>
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,7 @@
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
    {# 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">
 | 
			
		||||
        {% csrf_token %}
 | 
			
		||||
@@ -44,10 +44,10 @@
 | 
			
		||||
                    {# Tabs for button categories #}
 | 
			
		||||
                    <div class="card-header">
 | 
			
		||||
                        <ul class="nav nav-tabs nav-fill card-header-tabs">
 | 
			
		||||
                            {% for template_type in template_types %}
 | 
			
		||||
                            {% for category in categories %}
 | 
			
		||||
                                <li class="nav-item">
 | 
			
		||||
                                    <a class="nav-link" data-toggle="tab" href="#{{ template_type.grouper|slugify }}">
 | 
			
		||||
                                        {{ template_type.grouper }}
 | 
			
		||||
                                    <a class="nav-link" data-toggle="tab" href="#{{ category.grouper|slugify }}">
 | 
			
		||||
                                        {{ category.grouper }}
 | 
			
		||||
                                    </a>
 | 
			
		||||
                                </li>
 | 
			
		||||
                            {% endfor %}
 | 
			
		||||
@@ -57,10 +57,10 @@
 | 
			
		||||
                    {# Tabs content #}
 | 
			
		||||
                    <div class="card-body">
 | 
			
		||||
                        <div class="tab-content">
 | 
			
		||||
                            {% for template_type in template_types %}
 | 
			
		||||
                                <div class="tab-pane" id="{{ template_type.grouper|slugify }}">
 | 
			
		||||
                            {% for category in categories %}
 | 
			
		||||
                                <div class="tab-pane" id="{{ category.grouper|slugify }}">
 | 
			
		||||
                                    <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"
 | 
			
		||||
                                                    name="button" value="{{ button.name }}">
 | 
			
		||||
                                                {{ button.name }} ({{ button.amount | pretty_money }})
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user