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]
source =
adherents
activity
member
note
theme
omit =
adherents/tests/*.py
adherents/migrations/*.py
activity/tests/*.py
activity/migrations/*.py
member/tests/*.py
member/migrations/*.py
note/tests/*.py
note/migrations/*.py
theme/tests/*.py

3
.gitignore vendored
View File

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

View File

@ -2,4 +2,4 @@
# Copyright (C) 2018-2019 by BDE ENS Paris-Saclay
# 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 _
class AdherentsConfig(AppConfig):
name = 'adherents'
verbose_name = _('adherents')
class ActivityConfig(AppConfig):
name = 'activity'
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 .forms import CustomUserChangeForm
from .models import Profile
from .models import Club, Membership, Profile, Role
class ProfileInline(admin.StackedInline):
@ -33,5 +33,11 @@ class CustomUserAdmin(UserAdmin):
return super().get_inline_instances(request, obj)
# Update Django User with profile
admin.site.unregister(User)
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
in the default Django Auth User model
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
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 .models import NoteClub, NoteSpec, NoteUser
from .models import Alias
from .models.notes import Alias, NoteClub, NoteSpecial, NoteUser
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.
admin.site.register(NoteClub)
admin.site.register(NoteSpec)
admin.site.register(NoteUser)
admin.site.register(Alias)
admin.site.register(NoteClub, NoteClubAdmin)
admin.site.register(NoteSpecial, NoteSpecialAdmin)
admin.site.register(NoteUser, NoteUserAdmin)
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
'guardian',
'reversion',
# Note apps
'adherents',
'activity',
'member',
'note',
]

View File

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

View File

@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\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"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"

View File

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