mirror of
				https://gitlab.crans.org/bde/nk20
				synced 2025-10-31 07:49:57 +01:00 
			
		
		
		
	move apps to own dir
This commit is contained in:
		
							
								
								
									
										5
									
								
								apps/activity/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								apps/activity/__init__.py
									
									
									
									
									
										Normal 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 = 'activity.apps.ActivityConfig' | ||||
							
								
								
									
										33
									
								
								apps/activity/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								apps/activity/admin.py
									
									
									
									
									
										Normal 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) | ||||
							
								
								
									
										11
									
								
								apps/activity/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								apps/activity/apps.py
									
									
									
									
									
										Normal 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 ActivityConfig(AppConfig): | ||||
|     name = 'activity' | ||||
|     verbose_name = _('activity') | ||||
							
								
								
									
										74
									
								
								apps/activity/locale/fr/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								apps/activity/locale/fr/LC_MESSAGES/django.po
									
									
									
									
									
										Normal 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" | ||||
							
								
								
									
										0
									
								
								apps/activity/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/activity/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										92
									
								
								apps/activity/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								apps/activity/models.py
									
									
									
									
									
										Normal 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") | ||||
							
								
								
									
										0
									
								
								apps/activity/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/activity/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										5
									
								
								apps/member/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								apps/member/__init__.py
									
									
									
									
									
										Normal 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' | ||||
							
								
								
									
										43
									
								
								apps/member/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								apps/member/admin.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| # -*- 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.auth.admin import UserAdmin | ||||
| from django.contrib.auth.models import User | ||||
|  | ||||
| from .forms import CustomUserChangeForm | ||||
| from .models import Club, Membership, Profile, Role | ||||
|  | ||||
|  | ||||
| class ProfileInline(admin.StackedInline): | ||||
|     """ | ||||
|     Inline user profile in user admin | ||||
|     """ | ||||
|     model = Profile | ||||
|     can_delete = False | ||||
|  | ||||
|  | ||||
| class CustomUserAdmin(UserAdmin): | ||||
|     inlines = (ProfileInline,) | ||||
|     list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff') | ||||
|     list_select_related = ('profile',) | ||||
|     form = CustomUserChangeForm | ||||
|  | ||||
|     def get_inline_instances(self, request, obj=None): | ||||
|         """ | ||||
|         When creating a new user don't show profile one the first step | ||||
|         """ | ||||
|         if not obj: | ||||
|             return list() | ||||
|         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
									
								
								apps/member/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								apps/member/apps.py
									
									
									
									
									
										Normal 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') | ||||
							
								
								
									
										18
									
								
								apps/member/forms.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								apps/member/forms.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| # -*- 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.auth.forms import UserChangeForm | ||||
|  | ||||
|  | ||||
| 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 | ||||
|         self.fields['last_name'].required = True | ||||
|         self.fields['email'].required = True | ||||
							
								
								
									
										116
									
								
								apps/member/locale/fr/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								apps/member/locale/fr/LC_MESSAGES/django.po
									
									
									
									
									
										Normal 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" | ||||
							
								
								
									
										0
									
								
								apps/member/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/member/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										148
									
								
								apps/member/models.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								apps/member/models.py
									
									
									
									
									
										Normal 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
									
								
								apps/member/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/member/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										10
									
								
								apps/member/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								apps/member/views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| #!/usr/bin/env python | ||||
|  | ||||
| # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.contrib.auth.mixins import LoginRequiredMixin | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views.generic.edit import CreateView | ||||
|  | ||||
| from .models import | ||||
							
								
								
									
										5
									
								
								apps/note/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								apps/note/__init__.py
									
									
									
									
									
										Normal 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 = 'note.apps.NoteConfig' | ||||
							
								
								
									
										148
									
								
								apps/note/admin.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								apps/note/admin.py
									
									
									
									
									
										Normal 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.contrib import admin | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from polymorphic.admin import PolymorphicChildModelAdmin, \ | ||||
|     PolymorphicChildModelFilter, PolymorphicParentModelAdmin | ||||
|  | ||||
| from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser | ||||
| from .models.transactions import Transaction, TransactionTemplate | ||||
|  | ||||
|  | ||||
| class AliasInlines(admin.TabularInline): | ||||
|     """ | ||||
|     Define user and club aliases when editing their note | ||||
|     """ | ||||
|     extra = 0 | ||||
|     model = Alias | ||||
|  | ||||
|  | ||||
| @admin.register(Note) | ||||
| class NoteAdmin(PolymorphicParentModelAdmin): | ||||
|     """ | ||||
|     Parent regrouping all note types as children | ||||
|     """ | ||||
|     child_models = (NoteClub, NoteSpecial, NoteUser) | ||||
|     list_filter = (PolymorphicChildModelFilter, 'is_active',) | ||||
|  | ||||
|     # Use a polymorphic list | ||||
|     list_display = ('pretty', 'balance', 'is_active') | ||||
|     polymorphic_list = True | ||||
|  | ||||
|     # Organize notes by registration date | ||||
|     date_hierarchy = 'created_at' | ||||
|     ordering = ['-created_at'] | ||||
|  | ||||
|     # Search by aliases | ||||
|     search_fields = ['alias__name'] | ||||
|  | ||||
|  | ||||
| @admin.register(NoteClub) | ||||
| class NoteClubAdmin(PolymorphicChildModelAdmin): | ||||
|     """ | ||||
|     Child for a club note, see NoteAdmin | ||||
|     """ | ||||
|     inlines = (AliasInlines,) | ||||
|  | ||||
|     # We can't change club after creation or the balance | ||||
|     readonly_fields = ('club', 'balance') | ||||
|  | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @admin.register(NoteSpecial) | ||||
| class NoteSpecialAdmin(PolymorphicChildModelAdmin): | ||||
|     """ | ||||
|     Child for a special note, see NoteAdmin | ||||
|     """ | ||||
|     readonly_fields = ('balance',) | ||||
|  | ||||
|  | ||||
| @admin.register(NoteUser) | ||||
| class NoteUserAdmin(PolymorphicChildModelAdmin): | ||||
|     """ | ||||
|     Child for an user note, see NoteAdmin | ||||
|     """ | ||||
|     inlines = (AliasInlines,) | ||||
|  | ||||
|     # We can't change user after creation or the balance | ||||
|     readonly_fields = ('user', 'balance') | ||||
|  | ||||
|     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 | ||||
|  | ||||
|  | ||||
| @admin.register(Transaction) | ||||
| class TransactionAdmin(admin.ModelAdmin): | ||||
|     """ | ||||
|     Admin customisation for Transaction | ||||
|     """ | ||||
|     list_display = ('created_at', 'poly_source', 'poly_destination', | ||||
|                     'quantity', 'amount', 'transaction_type', 'valid') | ||||
|     list_filter = ('transaction_type', 'valid') | ||||
|     autocomplete_fields = ('source', 'destination',) | ||||
|  | ||||
|     def poly_source(self, obj): | ||||
|         """ | ||||
|         Force source to resolve polymorphic object | ||||
|         """ | ||||
|         return str(obj.source) | ||||
|  | ||||
|     poly_source.short_description = _('source') | ||||
|  | ||||
|     def poly_destination(self, obj): | ||||
|         """ | ||||
|         Force destination to resolve polymorphic object | ||||
|         """ | ||||
|         return str(obj.destination) | ||||
|  | ||||
|     poly_destination.short_description = _('destination') | ||||
|  | ||||
|     def get_readonly_fields(self, request, obj=None): | ||||
|         """ | ||||
|         Only valid can be edited after creation | ||||
|         Else the amount of money would not be transferred | ||||
|         """ | ||||
|         if obj:  # user is editing an existing object | ||||
|             return 'created_at', 'source', 'destination', 'quantity',\ | ||||
|                    'amount', 'transaction_type' | ||||
|         return [] | ||||
|  | ||||
|  | ||||
| @admin.register(TransactionTemplate) | ||||
| class TransactionTemplateAdmin(admin.ModelAdmin): | ||||
|     """ | ||||
|     Admin customisation for TransactionTemplate | ||||
|     """ | ||||
|     list_display = ('name', 'poly_destination', 'amount', 'template_type') | ||||
|     list_filter = ('template_type',) | ||||
|     autocomplete_fields = ('destination',) | ||||
|  | ||||
|     def poly_destination(self, obj): | ||||
|         """ | ||||
|         Force destination to resolve polymorphic object | ||||
|         """ | ||||
|         return str(obj.destination) | ||||
|  | ||||
|     poly_destination.short_description = _('destination') | ||||
							
								
								
									
										28
									
								
								apps/note/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								apps/note/apps.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| # -*- 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.conf import settings | ||||
| from django.db.models.signals import post_save | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
|  | ||||
| from . import signals | ||||
|  | ||||
|  | ||||
| class NoteConfig(AppConfig): | ||||
|     name = 'note' | ||||
|     verbose_name = _('note') | ||||
|  | ||||
|     def ready(self): | ||||
|         """ | ||||
|         Define app internal signals to interact with other apps | ||||
|         """ | ||||
|         post_save.connect( | ||||
|             signals.save_user_note, | ||||
|             sender=settings.AUTH_USER_MODEL | ||||
|         ) | ||||
|         post_save.connect( | ||||
|             signals.save_club_note, | ||||
|             sender='member.Club' | ||||
|         ) | ||||
							
								
								
									
										192
									
								
								apps/note/locale/fr/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								apps/note/locale/fr/LC_MESSAGES/django.po
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,192 @@ | ||||
| #, fuzzy | ||||
| msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PACKAGE VERSION\n" | ||||
| "Report-Msgid-Bugs-To: \n" | ||||
| "POT-Creation-Date: 2019-07-24 22:37+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" | ||||
|  | ||||
| #: admin.py:112 models/transactions.py:46 | ||||
| msgid "source" | ||||
| msgstr "source" | ||||
|  | ||||
| #: admin.py:120 admin.py:148 models/transactions.py:25 | ||||
| #: models/transactions.py:52 | ||||
| msgid "destination" | ||||
| msgstr "destination" | ||||
|  | ||||
| #: apps.py:15 models/notes.py:47 | ||||
| msgid "note" | ||||
| msgstr "note" | ||||
|  | ||||
| #: models/notes.py:24 | ||||
| msgid "account balance" | ||||
| msgstr "solde du compte" | ||||
|  | ||||
| #: models/notes.py:25 | ||||
| msgid "in centimes, money credited for this instance" | ||||
| msgstr "en centimes, argent crédité pour cette instance" | ||||
|  | ||||
| #: models/notes.py:29 | ||||
| msgid "active" | ||||
| msgstr "actif" | ||||
|  | ||||
| #: models/notes.py:32 | ||||
| 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:37 | ||||
| msgid "display image" | ||||
| msgstr "image affichée" | ||||
|  | ||||
| #: models/notes.py:42 models/transactions.py:55 | ||||
| msgid "created at" | ||||
| msgstr "créée le" | ||||
|  | ||||
| #: models/notes.py:48 | ||||
| msgid "notes" | ||||
| msgstr "notes" | ||||
|  | ||||
| #: models/notes.py:56 | ||||
| msgid "Note" | ||||
| msgstr "Note" | ||||
|  | ||||
| #: models/notes.py:66 models/notes.py:88 | ||||
| msgid "This alias is already taken." | ||||
| msgstr "Cet alias est déjà pris." | ||||
|  | ||||
| #: models/notes.py:103 | ||||
| msgid "user" | ||||
| msgstr "utilisateur" | ||||
|  | ||||
| #: models/notes.py:107 | ||||
| msgid "one's note" | ||||
| msgstr "note d'un utilisateur" | ||||
|  | ||||
| #: models/notes.py:108 | ||||
| msgid "users note" | ||||
| msgstr "notes des utilisateurs" | ||||
|  | ||||
| #: models/notes.py:114 | ||||
| #, python-format | ||||
| msgid "%(user)s's note" | ||||
| msgstr "Note de %(user)s" | ||||
|  | ||||
| #: models/notes.py:125 | ||||
| msgid "club" | ||||
| msgstr "club" | ||||
|  | ||||
| #: models/notes.py:129 | ||||
| msgid "club note" | ||||
| msgstr "note d'un club" | ||||
|  | ||||
| #: models/notes.py:130 | ||||
| msgid "clubs notes" | ||||
| msgstr "notes des clubs" | ||||
|  | ||||
| #: models/notes.py:136 | ||||
| #, python-format | ||||
| msgid "Note for %(club)s club" | ||||
| msgstr "Note du club %(club)s" | ||||
|  | ||||
| #: models/notes.py:149 models/transactions.py:32 models/transactions.py:66 | ||||
| msgid "type" | ||||
| msgstr "type" | ||||
|  | ||||
| #: models/notes.py:155 | ||||
| msgid "special note" | ||||
| msgstr "note spéciale" | ||||
|  | ||||
| #: models/notes.py:156 | ||||
| msgid "special notes" | ||||
| msgstr "notes spéciales" | ||||
|  | ||||
| #: models/notes.py:167 models/transactions.py:18 | ||||
| msgid "name" | ||||
| msgstr "nom" | ||||
|  | ||||
| #: models/notes.py:173 | ||||
| msgid "Invalid alias" | ||||
| msgstr "Alias invalide" | ||||
|  | ||||
| #: models/notes.py:189 | ||||
| msgid "alias" | ||||
| msgstr "alias" | ||||
|  | ||||
| #: models/notes.py:190 | ||||
| msgid "aliases" | ||||
| msgstr "alias" | ||||
|  | ||||
| #: models/notes.py:218 | ||||
| msgid "Alias too long." | ||||
| msgstr "L'alias est trop long." | ||||
|  | ||||
| #: models/notes.py:221 | ||||
| msgid "An alias with a similar name already exists." | ||||
| msgstr "Un alias avec un nom similaire existe déjà." | ||||
|  | ||||
| #: models/transactions.py:28 models/transactions.py:63 | ||||
| msgid "amount" | ||||
| msgstr "montant" | ||||
|  | ||||
| #: models/transactions.py:29 | ||||
| msgid "in centimes" | ||||
| msgstr "en centimes" | ||||
|  | ||||
| #: models/transactions.py:37 | ||||
| msgid "transaction template" | ||||
| msgstr "modèle de transaction" | ||||
|  | ||||
| #: models/transactions.py:38 | ||||
| msgid "transaction templates" | ||||
| msgstr "modèles de transaction" | ||||
|  | ||||
| #: models/transactions.py:59 | ||||
| msgid "quantity" | ||||
| msgstr "quantité" | ||||
|  | ||||
| #: models/transactions.py:70 | ||||
| msgid "reason" | ||||
| msgstr "raison" | ||||
|  | ||||
| #: models/transactions.py:74 | ||||
| msgid "valid" | ||||
| msgstr "valide" | ||||
|  | ||||
| #: models/transactions.py:79 | ||||
| msgid "transaction" | ||||
| msgstr "transaction" | ||||
|  | ||||
| #: models/transactions.py:80 | ||||
| msgid "transactions" | ||||
| msgstr "transactions" | ||||
|  | ||||
| #: models/transactions.py:116 | ||||
| msgid "membership transaction" | ||||
| msgstr "transaction d'adhésion" | ||||
|  | ||||
| #: models/transactions.py:117 | ||||
| msgid "membership transactions" | ||||
| msgstr "transactions d'adhésion" | ||||
|  | ||||
| #: templates/note/transaction_form.html:10 | ||||
| msgid "Home" | ||||
| msgstr "Accueil" | ||||
|  | ||||
| #: templates/note/transaction_form.html:53 | ||||
| msgid "Transfer" | ||||
| msgstr "Virement" | ||||
|  | ||||
| #: views.py:26 | ||||
| msgid "Transfer money from your account to one or others" | ||||
| msgstr "Transfert d'argent de ton compte vers un ou plusieurs autres" | ||||
							
								
								
									
										0
									
								
								apps/note/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/note/migrations/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										14
									
								
								apps/note/models/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								apps/note/models/__init__.py
									
									
									
									
									
										Normal 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', | ||||
| ] | ||||
							
								
								
									
										224
									
								
								apps/note/models/notes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								apps/note/models/notes.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | ||||
| # -*- mode: python; coding: utf-8 -*- | ||||
| # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| import unicodedata | ||||
|  | ||||
| from django.conf import settings | ||||
| from django.core.exceptions import ValidationError | ||||
| from django.core.validators import RegexValidator | ||||
| from django.db import models | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from polymorphic.models import PolymorphicModel | ||||
|  | ||||
| """ | ||||
| Defines each note types | ||||
| """ | ||||
|  | ||||
|  | ||||
| class Note(PolymorphicModel): | ||||
|     """ | ||||
|     An model, use to add transactions capabilities | ||||
|     """ | ||||
|     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, | ||||
|     ) | ||||
|     created_at = models.DateTimeField( | ||||
|         verbose_name=_('created at'), | ||||
|         auto_now_add=True, | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("note") | ||||
|         verbose_name_plural = _("notes") | ||||
|  | ||||
|     def pretty(self): | ||||
|         """ | ||||
|         :return: Pretty name of this note | ||||
|         """ | ||||
|         return str(self) | ||||
|  | ||||
|     pretty.short_description = _('Note') | ||||
|  | ||||
|     def save(self, *args, **kwargs): | ||||
|         """ | ||||
|         Save note with it's alias (called in polymorphic children) | ||||
|         """ | ||||
|         aliases = Alias.objects.filter(name=str(self)) | ||||
|         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.')) | ||||
|  | ||||
|             # Save note | ||||
|             super().save(*args, **kwargs) | ||||
|         else: | ||||
|             # Alias does not exist yet, so check if it can exist | ||||
|             a = Alias(name=str(self)) | ||||
|             a.clean() | ||||
|  | ||||
|             # Save note and alias | ||||
|             super().save(*args, **kwargs) | ||||
|             a.note = self | ||||
|             a.save(force_insert=True) | ||||
|  | ||||
|     def clean(self, *args, **kwargs): | ||||
|         """ | ||||
|         Verify alias (simulate save) | ||||
|         """ | ||||
|         aliases = Alias.objects.filter(name=str(self)) | ||||
|         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.')) | ||||
|         else: | ||||
|             # Alias does not exist yet, so check if it can exist | ||||
|             a = Alias(name=str(self)) | ||||
|             a.clean() | ||||
|  | ||||
|  | ||||
| class NoteUser(Note): | ||||
|     """ | ||||
|     A Note associated to an User | ||||
|     """ | ||||
|     user = models.OneToOneField( | ||||
|         settings.AUTH_USER_MODEL, | ||||
|         on_delete=models.PROTECT, | ||||
|         related_name='note', | ||||
|         verbose_name=_('user'), | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("one's note") | ||||
|         verbose_name_plural = _("users note") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return str(self.user) | ||||
|  | ||||
|     def pretty(self): | ||||
|         return _("%(user)s's note") % {'user': str(self.user)} | ||||
|  | ||||
|  | ||||
| class NoteClub(Note): | ||||
|     """ | ||||
|     A Note associated to a Club | ||||
|     """ | ||||
|     club = models.OneToOneField( | ||||
|         'member.Club', | ||||
|         on_delete=models.PROTECT, | ||||
|         related_name='note', | ||||
|         verbose_name=_('club'), | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("club note") | ||||
|         verbose_name_plural = _("clubs notes") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return str(self.club) | ||||
|  | ||||
|     def pretty(self): | ||||
|         return _("Note for %(club)s club") % {'club': str(self.club)} | ||||
|  | ||||
|  | ||||
| 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") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.special_type | ||||
|  | ||||
|  | ||||
| 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, | ||||
|         validators=[ | ||||
|             RegexValidator( | ||||
|                 regex=settings.ALIAS_VALIDATOR_REGEX, | ||||
|                 message=_('Invalid alias') | ||||
|             ) | ||||
|         ] if settings.ALIAS_VALIDATOR_REGEX else [] | ||||
|     ) | ||||
|     normalized_name = models.CharField( | ||||
|         max_length=255, | ||||
|         unique=True, | ||||
|         default='', | ||||
|         editable=False | ||||
|     ) | ||||
|     note = models.ForeignKey( | ||||
|         Note, | ||||
|         on_delete=models.PROTECT, | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("alias") | ||||
|         verbose_name_plural = _("aliases") | ||||
|  | ||||
|     def __str__(self): | ||||
|         return self.name | ||||
|  | ||||
|     @staticmethod | ||||
|     def normalize(string): | ||||
|         """ | ||||
|         Normalizes a string: removes most diacritics and does casefolding | ||||
|         """ | ||||
|         return ''.join( | ||||
|             char | ||||
|             for char in unicodedata.normalize('NFKD', string.casefold()) | ||||
|             if all(not unicodedata.category(char).startswith(cat) | ||||
|                    for cat in {'M', 'P', 'Z', 'C'}) | ||||
|         ) | ||||
|  | ||||
|     def save(self, *args, **kwargs): | ||||
|         """ | ||||
|         Handle normalized_name | ||||
|         """ | ||||
|         self.normalized_name = Alias.normalize(self.name) | ||||
|         if len(self.normalized_name) < 256: | ||||
|             super().save(*args, **kwargs) | ||||
|  | ||||
|     def clean(self): | ||||
|         normalized_name = Alias.normalize(self.name) | ||||
|         if len(normalized_name) >= 255: | ||||
|             raise ValidationError(_('Alias too long.')) | ||||
|         try: | ||||
|             if self != Alias.objects.get(normalized_name=normalized_name): | ||||
|                 raise ValidationError(_('An alias with a similar name ' | ||||
|                                         'already exists.')) | ||||
|         except Alias.DoesNotExist: | ||||
|             pass | ||||
							
								
								
									
										114
									
								
								apps/note/models/transactions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								apps/note/models/transactions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| # -*- 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'), | ||||
|         help_text=_('in centimes'), | ||||
|     ) | ||||
|     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'), | ||||
|     ) | ||||
|     created_at = models.DateTimeField( | ||||
|         verbose_name=_('created at'), | ||||
|         default=timezone.now, | ||||
|     ) | ||||
|     quantity = models.PositiveIntegerField( | ||||
|         verbose_name=_('quantity'), | ||||
|         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, | ||||
|     ) | ||||
|     valid = models.BooleanField( | ||||
|         verbose_name=_('valid'), | ||||
|         default=True, | ||||
|     ) | ||||
|  | ||||
|     class Meta: | ||||
|         verbose_name = _("transaction") | ||||
|         verbose_name_plural = _("transactions") | ||||
|  | ||||
|     def save(self, *args, **kwargs): | ||||
|         """ | ||||
|         When saving, also transfer money between two notes | ||||
|         """ | ||||
|         created = self.pk is None | ||||
|         to_transfer = self.amount * self.quantity | ||||
|         if not created: | ||||
|             # Revert old transaction | ||||
|             old_transaction = Transaction.objects.get(pk=self.pk) | ||||
|             if old_transaction.valid: | ||||
|                 self.source.balance += to_transfer | ||||
|                 self.destination.balance -= to_transfer | ||||
|  | ||||
|         if self.valid: | ||||
|             self.source.balance -= to_transfer | ||||
|             self.destination.balance += to_transfer | ||||
|  | ||||
|         # Save notes | ||||
|         self.source.save() | ||||
|         self.destination.save() | ||||
|         super().save(*args, **kwargs) | ||||
|  | ||||
|  | ||||
| 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") | ||||
							
								
								
									
										23
									
								
								apps/note/signals.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								apps/note/signals.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,23 @@ | ||||
| # -*- mode: python; coding: utf-8 -*- | ||||
| # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
|  | ||||
| def save_user_note(instance, created, **_kwargs): | ||||
|     """ | ||||
|     Hook to create and save a note when an user is updated | ||||
|     """ | ||||
|     if created: | ||||
|         from .models import NoteUser | ||||
|         NoteUser.objects.create(user=instance) | ||||
|     instance.note.save() | ||||
|  | ||||
|  | ||||
| def save_club_note(instance, created, **_kwargs): | ||||
|     """ | ||||
|     Hook to create and save a note when a club is updated | ||||
|     """ | ||||
|     if created: | ||||
|         from .models import NoteClub | ||||
|         NoteClub.objects.create(club=instance) | ||||
|     instance.note.save() | ||||
							
								
								
									
										0
									
								
								apps/note/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								apps/note/tests/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										12
									
								
								apps/note/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								apps/note/urls.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| # -*- mode: python; coding: utf-8 -*- | ||||
| # Copyright (C) 2018-2019 by BDE ENS Paris-Saclay | ||||
| # SPDX-License-Identifier: GPL-3.0-or-later | ||||
|  | ||||
| from django.urls import path | ||||
|  | ||||
| from . import views | ||||
|  | ||||
| app_name = 'note' | ||||
| urlpatterns = [ | ||||
|     path('transfer/', views.TransactionCreate.as_view(), name='transfer'), | ||||
| ] | ||||
							
								
								
									
										28
									
								
								apps/note/views.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								apps/note/views.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| # -*- 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.auth.mixins import LoginRequiredMixin | ||||
| from django.utils.translation import gettext_lazy as _ | ||||
| from django.views.generic.edit import CreateView | ||||
|  | ||||
| from .models import Transaction | ||||
|  | ||||
|  | ||||
| class TransactionCreate(LoginRequiredMixin, CreateView): | ||||
|     """ | ||||
|     Show transfer page | ||||
|  | ||||
|     TODO: If user have sufficient rights, they can transfer from an other note | ||||
|     """ | ||||
|     model = Transaction | ||||
|     fields = ('destination', 'amount', 'reason') | ||||
|  | ||||
|     def get_context_data(self, **kwargs): | ||||
|         """ | ||||
|         Add some context variables in template such as page title | ||||
|         """ | ||||
|         context = super().get_context_data(**kwargs) | ||||
|         context['title'] = _('Transfer money from your account ' | ||||
|                              'to one or others') | ||||
|         return context | ||||
		Reference in New Issue
	
	Block a user