1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2024-11-26 18:37:12 +00:00

Merge branch 'models' into 'master'

Not so atomic, sorry

See merge request bde/nk20!2
This commit is contained in:
erdnaxe 2019-07-17 07:52:34 +00:00
commit 14282427af
31 changed files with 968 additions and 375 deletions

View File

@ -1,11 +1,14 @@
[run] [run]
source = source =
adherents activity
member
note note
theme theme
omit = omit =
adherents/tests/*.py activity/tests/*.py
adherents/migrations/*.py activity/migrations/*.py
member/tests/*.py
member/migrations/*.py
note/tests/*.py note/tests/*.py
note/migrations/*.py note/migrations/*.py
theme/tests/*.py theme/tests/*.py

3
.gitignore vendored
View File

@ -36,3 +36,6 @@ settings_local.py
env/ env/
venv/ venv/
db.sqlite3 db.sqlite3
# Ignore migrations during first phase dev
migrations/

View File

@ -2,4 +2,4 @@
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'adherents.apps.AdherentsConfig' default_app_config = 'activity.apps.ActivityConfig'

33
activity/admin.py Normal file
View File

@ -0,0 +1,33 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin
from .models import Activity, ActivityType, Guest
class ActivityAdmin(admin.ModelAdmin):
"""
Admin customisation for Activity
"""
list_display = ('name', 'activity_type', 'organizer')
list_filter = ('activity_type',)
search_fields = ['name', 'organizer__name']
# Organize activities by start date
date_hierarchy = 'date_start'
ordering = ['-date_start']
class ActivityTypeAdmin(admin.ModelAdmin):
"""
Admin customisation for ActivityType
"""
list_display = ('name', 'can_invite', 'guest_entry_fee')
# Register your models here.
admin.site.register(Activity, ActivityAdmin)
admin.site.register(ActivityType, ActivityTypeAdmin)
admin.site.register(Guest)

View File

@ -6,6 +6,6 @@ from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
class AdherentsConfig(AppConfig): class ActivityConfig(AppConfig):
name = 'adherents' name = 'activity'
verbose_name = _('adherents') verbose_name = _('activity')

View File

@ -0,0 +1,74 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-07-16 13:45+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: apps.py:11 models.py:61
msgid "activity"
msgstr "activité"
#: models.py:12 models.py:29
msgid "name"
msgstr "nom"
#: models.py:16
msgid "can invite"
msgstr "peut inviter"
#: models.py:19
msgid "guest entry fee"
msgstr "cotisation de l'entrée invité"
#: models.py:23
msgid "activity type"
msgstr "type d'activité"
#: models.py:24
msgid "activity types"
msgstr "types d'activité"
#: models.py:33
msgid "description"
msgstr "description"
#: models.py:39
msgid "type"
msgstr "type"
#: models.py:45
msgid "organizer"
msgstr "organisateur"
#: models.py:51
msgid "attendees club"
msgstr ""
#: models.py:54
msgid "start date"
msgstr "date de début"
#: models.py:57
msgid "end date"
msgstr "date de fin"
#: models.py:62
msgid "activities"
msgstr "activités"
#: models.py:88
msgid "guest"
msgstr "invité"
#: models.py:89
msgid "guests"
msgstr "invités"

92
activity/models.py Normal file
View File

@ -0,0 +1,92 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.db import models
from django.utils.translation import gettext_lazy as _
class ActivityType(models.Model):
name = models.CharField(
verbose_name=_('name'),
max_length=255,
)
can_invite = models.BooleanField(
verbose_name=_('can invite'),
)
guest_entry_fee = models.PositiveIntegerField(
verbose_name=_('guest entry fee'),
)
class Meta:
verbose_name = _("activity type")
verbose_name_plural = _("activity types")
def __str__(self):
return self.name
class Activity(models.Model):
name = models.CharField(
verbose_name=_('name'),
max_length=255,
)
description = models.TextField(
verbose_name=_('description'),
)
activity_type = models.ForeignKey(
ActivityType,
on_delete=models.PROTECT,
related_name='+',
verbose_name=_('type'),
)
organizer = models.ForeignKey(
'member.Club',
on_delete=models.PROTECT,
related_name='+',
verbose_name=_('organizer'),
)
attendees_club = models.ForeignKey(
'member.Club',
on_delete=models.PROTECT,
related_name='+',
verbose_name=_('attendees club'),
)
date_start = models.DateTimeField(
verbose_name=_('start date'),
)
date_end = models.DateTimeField(
verbose_name=_('end date'),
)
class Meta:
verbose_name = _("activity")
verbose_name_plural = _("activities")
class Guest(models.Model):
activity = models.ForeignKey(
Activity,
on_delete=models.PROTECT,
related_name='+',
)
name = models.CharField(
max_length=255,
)
inviter = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
related_name='+',
)
entry = models.DateTimeField(
null=True,
)
entry_transaction = models.ForeignKey(
'note.Transaction',
on_delete=models.PROTECT,
)
class Meta:
verbose_name = _("guest")
verbose_name_plural = _("guests")

View File

@ -1,51 +0,0 @@
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-07-08 13:45+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: models.py:26
msgid "phone number"
msgstr "numéro de téléphone"
#: models.py:30
msgid "section"
msgstr "section"
#: models.py:31
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
#: models.py:35 models.py:36
msgid "user profile"
msgstr "profil utilisateur"
#: models.py:52
msgid "date"
msgstr "date"
#: models.py:57
msgid "amount"
msgstr "montant"
#: models.py:61
msgid "membership fee"
msgstr "cotisation"
#: models.py:62
msgid "membership fees"
msgstr "cotisations"

View File

@ -1,49 +0,0 @@
# Generated by Django 2.2.3 on 2019-07-16 07:17
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Profile',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('avatar', models.ImageField(blank=True, max_length=255, upload_to='', verbose_name='profile picture')),
('phone_number', models.CharField(blank=True, default='', max_length=50, null=True, verbose_name='phone number')),
('section', models.CharField(help_text='e.g. "1A0", "9A♥", "SAPHIRE"', max_length=255, verbose_name='section')),
('genre', models.CharField(blank=True, choices=[(None, 'ND'), ('M', 'M'), ('F', 'F')], max_length=1, null=True)),
('address', models.TextField(blank=True, null=True)),
('paid', models.BooleanField(default=False, verbose_name='paid')),
('is_active', models.BooleanField(default=True, verbose_name='is active')),
('is_deleted', models.BooleanField(default=False, verbose_name='is deleted')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'user profile',
'verbose_name_plural': 'user profile',
},
),
migrations.CreateModel(
name='MembershipFee',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(max_length=255, verbose_name='date')),
('amount', models.DecimalField(decimal_places=2, max_digits=5, verbose_name='amount')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': 'membership fee',
'verbose_name_plural': 'membership fees',
},
),
]

View File

@ -1,105 +0,0 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
class Profile(models.Model):
"""
An user profile
We do not want to patch the Django Contrib Auth User class
so this model add an user profile with additional information.
"""
GENRES = [
(None, "ND"),
("M", "M"),
("F", "F"),
]
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
avatar = models.ImageField(
max_length=255,
blank=True,
verbose_name=_('profile picture'),
)
phone_number = models.CharField(
max_length=50,
blank=True,
null=True,
default='',
verbose_name=_('phone number'),
)
section = models.CharField(
max_length=255,
verbose_name=_('section'),
help_text=_('e.g. "1A0", "9A♥", "SAPHIRE"'),
)
genre = models.CharField(
max_length=1,
blank=True,
null=True,
choices=GENRES,
)
address = models.TextField(
blank=True,
null=True,
)
paid = models.BooleanField(
verbose_name=_("paid"),
default=False,
)
is_active = models.BooleanField(
verbose_name=_("is active"),
default=True,
)
is_deleted = models.BooleanField(
verbose_name=_("is deleted"),
default=False,
)
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profile')
class MembershipFee(models.Model):
"""
User can become member by paying a membership fee
"""
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
)
date = models.DateField(
max_length=255,
verbose_name=_('date'),
)
amount = models.DecimalField(
max_digits=5, # Max 999.99 €
decimal_places=2,
verbose_name=_('amount'),
)
class Meta:
verbose_name = _('membership fee')
verbose_name_plural = _('membership fees')
@receiver(post_save, sender=User)
def save_user_profile(instance, created, **_kwargs):
"""
Hook to save an user profile when an user is updated
"""
if created:
Profile.objects.create(user=instance)
instance.profile.save()

5
member/__init__.py Normal file
View File

@ -0,0 +1,5 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'member.apps.MemberConfig'

View File

@ -7,7 +7,7 @@ from django.contrib.auth.admin import UserAdmin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from .forms import CustomUserChangeForm from .forms import CustomUserChangeForm
from .models import Profile from .models import Club, Membership, Profile, Role
class ProfileInline(admin.StackedInline): class ProfileInline(admin.StackedInline):
@ -33,5 +33,11 @@ class CustomUserAdmin(UserAdmin):
return super().get_inline_instances(request, obj) return super().get_inline_instances(request, obj)
# Update Django User with profile
admin.site.unregister(User) admin.site.unregister(User)
admin.site.register(User, CustomUserAdmin) admin.site.register(User, CustomUserAdmin)
# Add other models
admin.site.register(Club)
admin.site.register(Membership)
admin.site.register(Role)

11
member/apps.py Normal file
View File

@ -0,0 +1,11 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class MemberConfig(AppConfig):
name = 'member'
verbose_name = _('member')

View File

@ -10,6 +10,7 @@ class CustomUserChangeForm(UserChangeForm):
Make first name, last name and email required Make first name, last name and email required
in the default Django Auth User model in the default Django Auth User model
""" """
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.fields['first_name'].required = True self.fields['first_name'].required = True

View File

@ -0,0 +1,116 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-07-16 15:21+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: apps.py:11
msgid "member"
msgstr "adhérent"
#: models.py:24
msgid "phone number"
msgstr "numéro de téléphone"
#: models.py:30
msgid "section"
msgstr "section"
#: models.py:31
msgid "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
msgstr "e.g. \"1A0\", \"9A♥\", \"SAPHIRE\""
#: models.py:37
msgid "address"
msgstr "adresse"
#: models.py:43
msgid "paid"
msgstr "payé"
#: models.py:48 models.py:49
msgid "user profile"
msgstr "profil utilisateur"
#: models.py:57 models.py:102
msgid "name"
msgstr "nom"
#: models.py:62
msgid "email"
msgstr "courriel"
#: models.py:67
msgid "membership fee"
msgstr "cotisation pour adhérer"
#: models.py:71
msgid "membership duration"
msgstr "durée de l'adhésion"
#: models.py:72
msgid "The longest time a membership can last (NULL = infinite)."
msgstr "La durée maximale d'une adhésion (NULL = infinie)."
#: models.py:77
msgid "membership start"
msgstr "début de l'adhésion"
#: models.py:78
msgid "How long after January 1st the members can renew their membership."
msgstr ""
#: models.py:83
msgid "membership end"
msgstr "fin de l'adhésion"
#: models.py:84
msgid ""
"How long the membership can last after January 1st of the next year after "
"members can renew their membership."
msgstr ""
#: models.py:90
msgid "club"
msgstr "club"
#: models.py:91
msgid "clubs"
msgstr "clubs"
#: models.py:108
msgid "role"
msgstr "rôle"
#: models.py:109
msgid "roles"
msgstr "rôles"
#: models.py:126
msgid "membership starts on"
msgstr "l'adhésion commence le"
#: models.py:129
msgid "membership ends on"
msgstr "l'adhésion finie le"
#: models.py:133
msgid "fee"
msgstr "cotisation"
#: models.py:137
msgid "membership"
msgstr "adhésion"
#: models.py:138
msgid "memberships"
msgstr "adhésions"

View File

148
member/models.py Normal file
View File

@ -0,0 +1,148 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
class Profile(models.Model):
"""
An user profile
We do not want to patch the Django Contrib Auth User class
so this model add an user profile with additional information.
"""
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
phone_number = models.CharField(
verbose_name=_('phone number'),
max_length=50,
blank=True,
null=True,
)
section = models.CharField(
verbose_name=_('section'),
help_text=_('e.g. "1A0", "9A♥", "SAPHIRE"'),
max_length=255,
blank=True,
null=True,
)
address = models.CharField(
verbose_name=_('address'),
max_length=255,
blank=True,
null=True,
)
paid = models.BooleanField(
verbose_name=_("paid"),
default=False,
)
class Meta:
verbose_name = _('user profile')
verbose_name_plural = _('user profile')
class Club(models.Model):
"""
A student club
"""
name = models.CharField(
verbose_name=_('name'),
max_length=255,
unique=True,
)
email = models.EmailField(
verbose_name=_('email'),
)
# Memberships
membership_fee = models.PositiveIntegerField(
verbose_name=_('membership fee'),
)
membership_duration = models.DurationField(
null=True,
verbose_name=_('membership duration'),
help_text=_('The longest time a membership can last '
'(NULL = infinite).'),
)
membership_start = models.DurationField(
null=True,
verbose_name=_('membership start'),
help_text=_('How long after January 1st the members can renew '
'their membership.'),
)
membership_end = models.DurationField(
null=True,
verbose_name=_('membership end'),
help_text=_('How long the membership can last after January 1st '
'of the next year after members can renew their '
'membership.'),
)
class Meta:
verbose_name = _("club")
verbose_name_plural = _("clubs")
def __str__(self):
return self.name
class Role(models.Model):
"""
Role that an user can have in a club
"""
name = models.CharField(
verbose_name=_('name'),
max_length=255,
unique=True,
)
class Meta:
verbose_name = _('role')
verbose_name_plural = _('roles')
class Membership(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT
)
club = models.ForeignKey(
Club,
on_delete=models.PROTECT
)
roles = models.ForeignKey(
Role,
on_delete=models.PROTECT
)
date_start = models.DateField(
verbose_name=_('membership starts on'),
)
date_end = models.DateField(
verbose_name=_('membership ends on'),
null=True,
)
fee = models.PositiveIntegerField(
verbose_name=_('fee'),
)
class Meta:
verbose_name = _('membership')
verbose_name_plural = _('memberships')
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def save_user_profile(instance, created, **_kwargs):
"""
Hook to save an user profile when an user is updated
"""
if created:
Profile.objects.create(user=instance)
instance.profile.save()

0
member/tests/__init__.py Normal file
View File

View File

@ -1,10 +1,95 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.contrib import admin from django.contrib import admin
from .models import NoteClub, NoteSpec, NoteUser from .models.notes import Alias, NoteClub, NoteSpecial, NoteUser
from .models import Alias from .models.transactions import MembershipTransaction, Transaction, \
TransactionTemplate
class AliasInlines(admin.TabularInline):
"""
Define user and club aliases when editing their note
"""
extra = 0
model = Alias
class NoteClubAdmin(admin.ModelAdmin):
"""
Admin customisation for NoteClub
"""
inlines = (AliasInlines,)
list_display = ('club', 'balance', 'is_active')
list_filter = ('is_active',)
search_fields = ['club__name']
# We can't change club after creation
readonly_fields = ('club',)
def has_add_permission(self, request):
"""
A club note should not be manually added
"""
return False
def has_delete_permission(self, request, obj=None):
"""
A club note should not be manually removed
"""
return False
class NoteSpecialAdmin(admin.ModelAdmin):
"""
Admin customisation for NoteSpecial
"""
list_display = ('special_type', 'balance', 'is_active')
class NoteUserAdmin(admin.ModelAdmin):
"""
Admin customisation for NoteUser
"""
inlines = (AliasInlines,)
list_display = ('user', 'balance', 'is_active')
list_filter = ('is_active',)
search_fields = ['user__username']
# Organize note by registration date
date_hierarchy = 'user__date_joined'
ordering = ['-user__date_joined']
# We can't change user after creation
readonly_fields = ('user',)
def has_add_permission(self, request):
"""
An user note should not be manually added
"""
return False
def has_delete_permission(self, request, obj=None):
"""
An user note should not be manually removed
"""
return False
class TransactionTemplateAdmin(admin.ModelAdmin):
"""
Admin customisation for TransactionTemplate
"""
list_display = ('name', 'destination', 'amount', 'template_type')
list_filter = ('destination', 'template_type',)
# Register your models here. # Register your models here.
admin.site.register(NoteClub) admin.site.register(NoteClub, NoteClubAdmin)
admin.site.register(NoteSpec) admin.site.register(NoteSpecial, NoteSpecialAdmin)
admin.site.register(NoteUser) admin.site.register(NoteUser, NoteUserAdmin)
admin.site.register(Alias) admin.site.register(MembershipTransaction)
admin.site.register(Transaction)
admin.site.register(TransactionTemplate, TransactionTemplateAdmin)

View File

@ -0,0 +1,134 @@
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-07-16 15:21+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: apps.py:11 models/notes.py:39
msgid "note"
msgstr "note"
#: models/notes.py:21
msgid "account balance"
msgstr "solde du compte"
#: models/notes.py:22
msgid "in centimes, money credited for this instance"
msgstr "en centimes, argent crédité pour cette instance"
#: models/notes.py:25
msgid "active"
msgstr "actif"
#: models/notes.py:28
msgid ""
"Designates whether this note should be treated as active. Unselect this "
"instead of deleting notes."
msgstr ""
"Indique si la note est active. Désactiver cela plutôt que supprimer la note."
#: models/notes.py:33
msgid "display image"
msgstr "image affichée"
#: models/notes.py:40
msgid "notes"
msgstr "notes"
#: models/notes.py:54
msgid "one's note"
msgstr "note d'un utilisateur"
#: models/notes.py:55
msgid "users note"
msgstr "notes des utilisateurs"
#: models/notes.py:69
msgid "club note"
msgstr "note d'un club"
#: models/notes.py:70
msgid "clubs notes"
msgstr "notes des clubs"
#: models/notes.py:83 models/transactions.py:31 models/transactions.py:64
msgid "type"
msgstr "type"
#: models/notes.py:89
msgid "special note"
msgstr "note spéciale"
#: models/notes.py:90
msgid "special notes"
msgstr "notes spéciales"
#: models/notes.py:98 models/transactions.py:18
msgid "name"
msgstr "nom"
#: models/notes.py:108
msgid "alias"
msgstr "alias"
#: models/notes.py:109
msgid "aliases"
msgstr "alias"
#: models/transactions.py:25 models/transactions.py:51
#: models/transactions.py:54
msgid "destination"
msgstr "destination"
#: models/transactions.py:28 models/transactions.py:61
msgid "amount"
msgstr "montant"
#: models/transactions.py:36
msgid "transaction template"
msgstr "modèle de transaction"
#: models/transactions.py:37
msgid "transaction templates"
msgstr "modèles de transaction"
#: models/transactions.py:45
msgid "source"
msgstr "source"
#: models/transactions.py:58
msgid "quantity"
msgstr "quantité"
#: models/transactions.py:68
msgid "description"
msgstr "description"
#: models/transactions.py:71
msgid "valid"
msgstr "valide"
#: models/transactions.py:75
msgid "transaction"
msgstr "transaction"
#: models/transactions.py:76
msgid "transactions"
msgstr "transactions"
#: models/transactions.py:87
msgid "membership transaction"
msgstr "transaction d'adhésion"
#: models/transactions.py:88
msgid "membership transactions"
msgstr "transactions d'adhésion"

View File

@ -1,63 +0,0 @@
# Generated by Django 2.2.3 on 2019-07-16 07:17
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
initial = True
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='NoteClub',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('balance', models.DecimalField(decimal_places=2, default=0, help_text='money credited for this instance', max_digits=8, verbose_name='account balance')),
('is_active', models.BooleanField(default=True, verbose_name='is active')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='NoteSpec',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('balance', models.DecimalField(decimal_places=2, default=0, help_text='money credited for this instance', max_digits=8, verbose_name='account balance')),
('is_active', models.BooleanField(default=True, verbose_name='is active')),
('account_type', models.CharField(choices=[('CH', 'bank check'), ('CB', 'credit card'), ('VB', 'bank transfer'), ('CA', 'cash'), ('RB', 'refund')], max_length=2, unique=True)),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='NoteUser',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('balance', models.DecimalField(decimal_places=2, default=0, help_text='money credited for this instance', max_digits=8, verbose_name='account balance')),
('is_active', models.BooleanField(default=True, verbose_name='is active')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
],
options={
'verbose_name': "one's note",
'verbose_name_plural': 'users note',
},
),
migrations.CreateModel(
name='Alias',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('alias', models.TextField(unique=True, verbose_name='alias')),
('owner_id', models.PositiveIntegerField()),
('owner_type', models.ForeignKey(limit_choices_to=models.Q(models.Q(('app_label', 'note'), ('model', 'NoteUser')), models.Q(('app_label', 'note'), ('model', 'NoteClub')), _connector='OR'), on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')),
],
),
]

View File

@ -1,89 +0,0 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from django.utils.translation import gettext_lazy as _
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes.fields import GenericForeignKey
class Alias(models.Model):
"""
A alias labels a Note instance, only for user and clubs
"""
alias = models.TextField(
"alias",
unique=True,
blank=False,
null=False,
)
# Owner can be linked to an user note or a club note
limit = models.Q(app_label="note", model="NoteUser") | models.Q(app_label="note", model="NoteClub")
owner_id = models.PositiveIntegerField()
owner_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
limit_choices_to=limit
)
owner = GenericForeignKey('owner_type', 'owner_id')
class Note(models.Model):
"""
An abstract model, use to add transactions capabilities to a user
"""
balance = models.DecimalField(
verbose_name=_('account balance'),
help_text=_("money credited for this instance"),
decimal_places=2, # Limit to centimes
max_digits=8, # Limit to 999999,99€
default=0,
)
is_active = models.BooleanField(
default=True,
verbose_name=_('is active')
)
class Meta:
abstract = True
class NoteUser(Note):
"""
A Note associated to an User
"""
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
)
class Meta:
verbose_name = _("one's note")
verbose_name_plural = _("users note")
class NoteSpec(Note):
"""
A Note for special account, where real money enter or leave the system
"""
account_type = models.CharField(
max_length=2,
choices=(
("CH", _("bank check")),
("CB", _("credit card")),
("VB", _("bank transfer")),
("CA", _("cash")),
("RB", _("refund")),
),
unique=True,
)
class NoteClub(Note):
# to be added
pass

14
note/models/__init__.py Normal file
View File

@ -0,0 +1,14 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from .notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
from .transactions import MembershipTransaction, Transaction, \
TransactionTemplate
__all__ = [
# Notes
'Alias', 'Note', 'NoteClub', 'NoteSpecial', 'NoteUser',
# Transactions
'MembershipTransaction', 'Transaction', 'TransactionTemplate',
]

132
note/models/notes.py Normal file
View File

@ -0,0 +1,132 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
"""
Defines each note types
"""
class Note(models.Model):
"""
An model, use to add transactions capabilities
We do not use an abstract model to simplify the transfer between two notes.
"""
balance = models.IntegerField(
verbose_name=_('account balance'),
help_text=_('in centimes, money credited for this instance'),
default=0,
)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this note should be treated as active. '
'Unselect this instead of deleting notes.'
),
)
display_image = models.ImageField(
verbose_name=_('display image'),
max_length=255,
blank=True,
)
class Meta:
verbose_name = _("note")
verbose_name_plural = _("notes")
class NoteUser(Note):
"""
A Note associated to an User
"""
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.PROTECT,
related_name='note',
)
class Meta:
verbose_name = _("one's note")
verbose_name_plural = _("users note")
class NoteClub(Note):
"""
A Note associated to a Club
"""
club = models.OneToOneField(
'member.Club',
on_delete=models.PROTECT,
related_name='note',
)
class Meta:
verbose_name = _("club note")
verbose_name_plural = _("clubs notes")
class NoteSpecial(Note):
"""
A Note for special account, where real money enter or leave the system
- bank check
- credit card
- bank transfer
- cash
- refund
"""
special_type = models.CharField(
verbose_name=_('type'),
max_length=255,
unique=True,
)
class Meta:
verbose_name = _("special note")
verbose_name_plural = _("special notes")
class Alias(models.Model):
"""
An alias labels a Note instance, only for user and clubs
"""
name = models.CharField(
verbose_name=_('name'),
max_length=255,
unique=True,
)
note = models.ForeignKey(
Note,
on_delete=models.PROTECT,
)
class Meta:
verbose_name = _("alias")
verbose_name_plural = _("aliases")
@receiver(post_save, sender=settings.AUTH_USER_MODEL)
def save_user_note(instance, created, **_kwargs):
"""
Hook to create and save a note when an user is updated
"""
if created:
NoteUser.objects.create(user=instance)
instance.note.save()
@receiver(post_save, sender='member.Club')
def save_club_note(instance, created, **_kwargs):
"""
Hook to create and save a note when a club is updated
"""
if created:
NoteClub.objects.create(club=instance)
instance.note.save()

View File

@ -0,0 +1,88 @@
# -*- mode: python; coding: utf-8 -*-
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from django.db import models
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from .notes import Note
"""
Defines transactions
"""
class TransactionTemplate(models.Model):
name = models.CharField(
verbose_name=_('name'),
max_length=255,
)
destination = models.ForeignKey(
Note,
on_delete=models.PROTECT,
related_name='+', # no reverse
verbose_name=_('destination'),
)
amount = models.PositiveIntegerField(
verbose_name=_('amount'),
)
template_type = models.CharField(
verbose_name=_('type'),
max_length=31
)
class Meta:
verbose_name = _("transaction template")
verbose_name_plural = _("transaction templates")
class Transaction(models.Model):
source = models.ForeignKey(
Note,
on_delete=models.PROTECT,
related_name='+',
verbose_name=_('source'),
)
destination = models.ForeignKey(
Note,
on_delete=models.PROTECT,
related_name='+',
verbose_name=_('destination'),
)
datetime = models.DateTimeField(
verbose_name=_('destination'),
default=timezone.now,
)
quantity = models.PositiveSmallIntegerField(
verbose_name=_('quantity'),
)
amount = models.PositiveIntegerField(
verbose_name=_('amount'),
)
transaction_type = models.CharField(
verbose_name=_('type'),
max_length=31,
)
description = models.TextField(
verbose_name=_('description'),
)
valid = models.NullBooleanField(
verbose_name=_('valid'),
)
class Meta:
verbose_name = _("transaction")
verbose_name_plural = _("transactions")
class MembershipTransaction(Transaction):
membership = models.OneToOneField(
'member.Membership',
on_delete=models.PROTECT,
related_name='transaction',
)
class Meta:
verbose_name = _("membership transaction")
verbose_name_plural = _("membership transactions")

View File

@ -44,9 +44,11 @@ INSTALLED_APPS = [
# External apps # External apps
'guardian', 'guardian',
'reversion',
# Note apps # Note apps
'adherents', 'activity',
'member',
'note', 'note',
] ]

View File

@ -5,3 +5,4 @@ Pillow==6.1.0
pytz==2019.1 pytz==2019.1
six==1.12.0 six==1.12.0
sqlparse==0.3.0 sqlparse==0.3.0
django-reversion==3.0.3

View File

@ -3,7 +3,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-07-08 13:45+0200\n" "POT-Creation-Date: 2019-07-16 12:36+0200\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"

View File

@ -8,6 +8,7 @@ deps =
-r{toxinidir}/requirements.txt -r{toxinidir}/requirements.txt
coverage coverage
commands = commands =
./manage.py makemigrations
coverage run ./manage.py test {posargs} coverage run ./manage.py test {posargs}
coverage report -m coverage report -m
@ -27,7 +28,7 @@ deps =
pyflakes pyflakes
pylint pylint
commands = commands =
flake8 note_* flake8 activity member note
pylint . pylint .
[flake8] [flake8]
@ -41,7 +42,8 @@ exclude =
*.pyc, *.pyc,
*.egg-info, *.egg-info,
.cache, .cache,
.eggs .eggs,
*migrations*
max-complexity = 10 max-complexity = 10
import-order-style = google import-order-style = google
application-import-names = flake8 application-import-names = flake8