From 35e8585f4c80d82740a4c5b3e53ed72209f1e016 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 17:44:15 +0200 Subject: [PATCH 01/20] Verbose names in lowercase --- equipment/models.py | 12 ++++++------ event/models.py | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/equipment/models.py b/equipment/models.py index 9233787..6e22edd 100644 --- a/equipment/models.py +++ b/equipment/models.py @@ -5,11 +5,11 @@ from event.models import Event, Activity class AbstractEquipment(models.Model): name = models.CharField( - _("Nom du matériel"), + _("nom du matériel"), max_length=200, ) - stock = models.PositiveSmallIntegerField(_("Quantité disponible")) - description = models.TextField(_("Description")) + stock = models.PositiveSmallIntegerField(_("quantité disponible")) + description = models.TextField(_("description")) activities = models.ManyToManyField( Activity, related_name="equipment", @@ -46,8 +46,8 @@ class TemporaryEquipment(AbstractEquipment): class EquipmentAttribution(models.Model): equipment = models.ForeignKey(AbstractEquipment) activity = models.ForeignKey(Activity) - amount = models.PositiveSmallIntegerField(_("Quantité attribuée")) - remarks = models.TextField(_("Remarques concernant l'attribution")) + amount = models.PositiveSmallIntegerField(_("quantité attribuée")) + remarks = models.TextField(_("remarques concernant l'attribution")) class Meta: verbose_name = _("attribution de matériel") @@ -60,7 +60,7 @@ class EquipmentAttribution(models.Model): class EquipmentRemark(models.Model): - remark = models.TextField(_("Remarque sur le matériel")) + remark = models.TextField(_("remarque sur le matériel")) equipment = models.ForeignKey( AbstractEquipment, related_name="remarks", diff --git a/event/models.py b/event/models.py index aaad757..6210c81 100644 --- a/event/models.py +++ b/event/models.py @@ -7,11 +7,11 @@ from django.db import models class Event(models.Model): title = models.CharField( - _("Nom de l'évènement"), + _("nom de l'évènement"), max_length=200, ) slug = models.SlugField( - _('Identificateur'), + _('identificateur'), unique=True, primary_key=True, help_text=_("Seulement des lettres, des chiffres ou" @@ -23,12 +23,12 @@ class Event(models.Model): editable=False, ) creation_date = models.DateTimeField( - _('Date de création'), + _('date de création'), auto_now_add=True, ) - description = models.TextField(_('Description')) - beginning_date = models.DateTimeField(_('Date de début')) - ending_date = models.DateTimeField(_('Date de fin')) + description = models.TextField(_('description')) + beginning_date = models.DateTimeField(_('date de début')) + ending_date = models.DateTimeField(_('date de fin')) class Meta: verbose_name = _("évènement") @@ -40,7 +40,7 @@ class Event(models.Model): class Place(models.Model): name = models.CharField( - _("Nom du lieu"), + _("nom du lieu"), max_length=200, ) description = models.TextField(blank=True) @@ -55,7 +55,7 @@ class Place(models.Model): class ActivityTag(models.Model): name = models.CharField( - _("Nom du tag"), + _("nom du tag"), max_length=200, ) is_public = models.BooleanField( @@ -70,7 +70,7 @@ class ActivityTag(models.Model): " une couleur en hexadécimal."), ) color = models.CharField( - _('Couleur'), + _('couleur'), max_length=7, validators=[color_regex], help_text=_("Rentrer une couleur en hexadécimal"), @@ -86,7 +86,7 @@ class ActivityTag(models.Model): class ActivityTemplate(models.Model): title = models.CharField( - _("Nom de l'activité"), + _("nom de l'activité"), max_length=200, blank=True, null=True, @@ -105,23 +105,23 @@ class ActivityTemplate(models.Model): blank=True, ) min_perm = models.PositiveSmallIntegerField( - _('Nombre minimum de permanents'), + _('nombre minimum de permanents'), blank=True, null=True, ) max_perm = models.PositiveSmallIntegerField( - _('Nombre maximum de permanents'), + _('nombre maximum de permanents'), blank=True, null=True, ) description = models.TextField( - _('Description'), + _('description'), help_text=_("Public, Visible par tout le monde."), blank=True, null=True, ) remarks = models.TextField( - _('Remarques'), + _('remarques'), help_text=_("Visible uniquement par les organisateurs"), blank=True, null=True, -- 2.47.2 From 350b0c58be1e5c445a1e96a7ed0626e1defad0db Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 17:46:13 +0200 Subject: [PATCH 02/20] Add begin/end date to activities --- event/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/event/models.py b/event/models.py index 6210c81..a40ba6d 100644 --- a/event/models.py +++ b/event/models.py @@ -156,6 +156,9 @@ class Activity(ActivityTemplate): blank=True, ) + beginning = models.DateTimeField(_("heure de début")) + end = models.DateTimeField(_("heure de fin")) + def get_herited(self, attrname): attr = super(Activity, self).__getattribute__(attrname) if attrname in {"parent", "staff", "equipment"}: -- 2.47.2 From 1d07c5e8fd9cb67e446b2bdb116b69dcaaf39bb5 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 17:50:15 +0200 Subject: [PATCH 03/20] Add amount field to EquipmentRemark --- equipment/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/equipment/models.py b/equipment/models.py index 6e22edd..9fd3287 100644 --- a/equipment/models.py +++ b/equipment/models.py @@ -66,6 +66,7 @@ class EquipmentRemark(models.Model): related_name="remarks", help_text=_("Matériel concerné par la remarque"), ) + amount = models.PositiveSmallIntegerField(_("quantité concernée")) is_broken = models.BooleanField() is_lost = models.BooleanField() -- 2.47.2 From f1afadda76ea8d83cbd0e2ef382e8f11736ae156 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 17:50:58 +0200 Subject: [PATCH 04/20] Add possibility for endless events --- event/models.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/event/models.py b/event/models.py index a40ba6d..5b8910f 100644 --- a/event/models.py +++ b/event/models.py @@ -28,7 +28,10 @@ class Event(models.Model): ) description = models.TextField(_('description')) beginning_date = models.DateTimeField(_('date de début')) - ending_date = models.DateTimeField(_('date de fin')) + ending_date = models.DateTimeField( + _('date de fin'), + blank=True + ) class Meta: verbose_name = _("évènement") -- 2.47.2 From a22ab92e87164d3ffcc4333c1ffe3ada04716304 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 18:13:45 +0200 Subject: [PATCH 05/20] Add comment and notification models --- communication/__init__.py | 0 communication/migrations/__init__.py | 0 communication/models.py | 53 ++++++++++++++++++++++++++++ communication/views.py | 3 ++ 4 files changed, 56 insertions(+) create mode 100644 communication/__init__.py create mode 100644 communication/migrations/__init__.py create mode 100644 communication/models.py create mode 100644 communication/views.py diff --git a/communication/__init__.py b/communication/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/communication/migrations/__init__.py b/communication/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/communication/models.py b/communication/models.py new file mode 100644 index 0000000..25584c9 --- /dev/null +++ b/communication/models.py @@ -0,0 +1,53 @@ +from django.db import models +from django.contrib.auth.models import User + + +class Commentable(models.Model): + pass + + +class Thread(models.Model): + topic = models.OneToOne(Commentable) + + +class Comment(models.Model): + thread = models.ForeignKey(Thread) + parent = models.ForeignKey( + 'Comment', + related_name="replies", + ) + author = models.ForeignKey( + User, + verbose_name=_("auteur"), + editable=False + ) + at = models.DateTimeField( + _("date d'écriture"), + auto_now_add=True + ) + text = models.TextField(_("corps du message")) + + +class Notifying(models.Model): + subscribed = models.ManyToManyField( + User, + verbose_name=_("abonnés"), + related_name="subscribed_to" + ) + + def get_url(self, *args, **kwargs): + pass + + +class Notification(models.Model): + sent_by = models.ForeignKey(Notifying) + about = models.ForeignKey(Notifying) + to = models.ForeignKey( + User, + verbose_name=_("envoyée à") + ) + viewed_at = models.DateTimeField( + _("Vue à"), + blank=True + ) + text = models.TextField(_("corps de la notification")) diff --git a/communication/views.py b/communication/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/communication/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. -- 2.47.2 From 3bbea85b3af39ee439a53196ade6ed6370daf6a2 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 18:14:15 +0200 Subject: [PATCH 06/20] Add commentable property to activities --- event/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/event/models.py b/event/models.py index 5b8910f..186a3db 100644 --- a/event/models.py +++ b/event/models.py @@ -3,9 +3,10 @@ from django.utils.translation import ugettext_lazy as _ from django.core.validators import RegexValidator from django.core.exceptions import FieldError from django.db import models +from communication.models import Commentable, Notifying -class Event(models.Model): +class Event(Commentable): title = models.CharField( _("nom de l'évènement"), max_length=200, @@ -87,7 +88,7 @@ class ActivityTag(models.Model): return self.name -class ActivityTemplate(models.Model): +class ActivityTemplate(Commentable): title = models.CharField( _("nom de l'activité"), max_length=200, -- 2.47.2 From 80be26c5b3c9c1c7f9e04d0fd412831bdd8203b4 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 18:14:26 +0200 Subject: [PATCH 07/20] Minor changes --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index c067e72..7116ddc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ __pycache__ venv evenementiel/settings.py .*.swp + +*.pyc -- 2.47.2 From 26c02df53d52976c31d6fdae22a9bdf5e2fb9105 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 18:37:47 +0200 Subject: [PATCH 08/20] All commentables are notifying --- communication/models.py | 52 ++++++++++++++++++++--------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/communication/models.py b/communication/models.py index 25584c9..5ac52a3 100644 --- a/communication/models.py +++ b/communication/models.py @@ -2,32 +2,6 @@ from django.db import models from django.contrib.auth.models import User -class Commentable(models.Model): - pass - - -class Thread(models.Model): - topic = models.OneToOne(Commentable) - - -class Comment(models.Model): - thread = models.ForeignKey(Thread) - parent = models.ForeignKey( - 'Comment', - related_name="replies", - ) - author = models.ForeignKey( - User, - verbose_name=_("auteur"), - editable=False - ) - at = models.DateTimeField( - _("date d'écriture"), - auto_now_add=True - ) - text = models.TextField(_("corps du message")) - - class Notifying(models.Model): subscribed = models.ManyToManyField( User, @@ -51,3 +25,29 @@ class Notification(models.Model): blank=True ) text = models.TextField(_("corps de la notification")) + + +class Commentable(Notifying): + pass + + +class Thread(models.Model): + topic = models.OneToOne(Commentable) + + +class Comment(models.Model): + thread = models.ForeignKey(Thread) + parent = models.ForeignKey( + 'Comment', + related_name="replies", + ) + author = models.ForeignKey( + User, + verbose_name=_("auteur"), + editable=False + ) + at = models.DateTimeField( + _("date d'écriture"), + auto_now_add=True + ) + text = models.TextField(_("corps du message")) -- 2.47.2 From 6f158638bfd0c8d2b7c84d8ed3f4e9cfd71f0fb3 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 15 Jul 2017 21:22:15 +0200 Subject: [PATCH 09/20] Various bugfixes --- communication/apps.py | 4 ++++ communication/models.py | 13 ++++++++++--- evenementiel/settings/common.py | 1 + event/models.py | 5 +++++ 4 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 communication/apps.py diff --git a/communication/apps.py b/communication/apps.py new file mode 100644 index 0000000..e6c488d --- /dev/null +++ b/communication/apps.py @@ -0,0 +1,4 @@ +from django.apps import AppConfig + +class CommunicationConfig(AppConfig): + name = 'communication' diff --git a/communication/models.py b/communication/models.py index 5ac52a3..c0d19a0 100644 --- a/communication/models.py +++ b/communication/models.py @@ -1,5 +1,6 @@ from django.db import models from django.contrib.auth.models import User +from django.utils.translation import ugettext_lazy as _ class Notifying(models.Model): @@ -14,8 +15,14 @@ class Notifying(models.Model): class Notification(models.Model): - sent_by = models.ForeignKey(Notifying) - about = models.ForeignKey(Notifying) + sent_by = models.ForeignKey( + Notifying, + related_name="notifs_sent" + ) + about = models.ForeignKey( + Notifying, + related_name="notifs", + ) to = models.ForeignKey( User, verbose_name=_("envoyée à") @@ -32,7 +39,7 @@ class Commentable(Notifying): class Thread(models.Model): - topic = models.OneToOne(Commentable) + topic = models.OneToOneField(Commentable) class Comment(models.Model): diff --git a/evenementiel/settings/common.py b/evenementiel/settings/common.py index 61bf2d6..5d291cb 100644 --- a/evenementiel/settings/common.py +++ b/evenementiel/settings/common.py @@ -44,6 +44,7 @@ BASE_DIR = os.path.dirname( INSTALLED_APPS = [ + 'communication.apps.CommunicationConfig', 'equipment.apps.EquipmentConfig', 'event.apps.EventConfig', 'users.apps.UsersConfig', diff --git a/event/models.py b/event/models.py index 186a3db..c1c89ce 100644 --- a/event/models.py +++ b/event/models.py @@ -7,6 +7,11 @@ from communication.models import Commentable, Notifying class Event(Commentable): + commentable_model = models.OneToOneField( + Commentable, + related_name="event_model", + parent_link=True + ) title = models.CharField( _("nom de l'évènement"), max_length=200, -- 2.47.2 From af75c90b84072ff0ebc2971d10bb734529d33bdb Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 17 Jul 2017 17:47:34 +0200 Subject: [PATCH 10/20] Refactor communication models We now use `django-notifications` and `django-contrib-comments` to manage notifications and comments. Subscriptions are managed through the `SubscriptionMixin` model, and can correspond to unique users and to group-like subscriptions. --- communication/models.py | 83 +++++++++++++++++------------------------ event/models.py | 15 +++----- requirements.txt | 2 + 3 files changed, 42 insertions(+), 58 deletions(-) diff --git a/communication/models.py b/communication/models.py index c0d19a0..220d29d 100644 --- a/communication/models.py +++ b/communication/models.py @@ -1,60 +1,47 @@ from django.db import models -from django.contrib.auth.models import User +from django.conf import settings +from django.contrib.auth.models import Group +from django.contrib.auth import get_user_model +from django.contrib.contenttypes.fields import ( + GenericForeignKey, GenericRelation +) +from django.contrib.contenttypes.models import ContentType from django.utils.translation import ugettext_lazy as _ +User = get_user_model() -class Notifying(models.Model): - subscribed = models.ManyToManyField( - User, - verbose_name=_("abonnés"), - related_name="subscribed_to" + +class Subscription(models.Model): + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey('content_type', 'object_id') + + +class UserSubscription(Subscription): + user = models.ForeignKey(settings.AUTH_USER_MODEL) + is_unsub = models.BooleanField( + _("désinscription"), + default=False ) - def get_url(self, *args, **kwargs): - pass + +class GroupSubscription(Subscription): + group = models.ForeignKey(Group) -class Notification(models.Model): - sent_by = models.ForeignKey( - Notifying, - related_name="notifs_sent" - ) - about = models.ForeignKey( - Notifying, - related_name="notifs", - ) - to = models.ForeignKey( - User, - verbose_name=_("envoyée à") - ) - viewed_at = models.DateTimeField( - _("Vue à"), - blank=True - ) - text = models.TextField(_("corps de la notification")) +class SubscriptionMixin(models.Model): + subscribed_users = GenericRelation(UserSubscription) + subscribed_groups = GenericRelation(GroupSubscription) + def get_unique_users(self): + return self.subscribed_users.filter(is_unsub=False) -class Commentable(Notifying): - pass + def get_group_users(self): + return User.objects.filter(group__in=self.subscribed_groups.all()) + def get_all_subscribers(self): + return (self.get_unique_users().union(self.get_group_users()) + .exclude(is_unsub=True)) -class Thread(models.Model): - topic = models.OneToOneField(Commentable) - - -class Comment(models.Model): - thread = models.ForeignKey(Thread) - parent = models.ForeignKey( - 'Comment', - related_name="replies", - ) - author = models.ForeignKey( - User, - verbose_name=_("auteur"), - editable=False - ) - at = models.DateTimeField( - _("date d'écriture"), - auto_now_add=True - ) - text = models.TextField(_("corps du message")) + class Meta: + abstract = True diff --git a/event/models.py b/event/models.py index c1c89ce..d0e4f94 100644 --- a/event/models.py +++ b/event/models.py @@ -3,15 +3,10 @@ from django.utils.translation import ugettext_lazy as _ from django.core.validators import RegexValidator from django.core.exceptions import FieldError from django.db import models -from communication.models import Commentable, Notifying +from communication.models import SubscriptionMixin -class Event(Commentable): - commentable_model = models.OneToOneField( - Commentable, - related_name="event_model", - parent_link=True - ) +class Event(SubscriptionMixin, models.Model): title = models.CharField( _("nom de l'évènement"), max_length=200, @@ -19,7 +14,6 @@ class Event(Commentable): slug = models.SlugField( _('identificateur'), unique=True, - primary_key=True, help_text=_("Seulement des lettres, des chiffres ou" "les caractères '_' ou '-'."), ) @@ -36,7 +30,8 @@ class Event(Commentable): beginning_date = models.DateTimeField(_('date de début')) ending_date = models.DateTimeField( _('date de fin'), - blank=True + blank=True, + null=True, ) class Meta: @@ -93,7 +88,7 @@ class ActivityTag(models.Model): return self.name -class ActivityTemplate(Commentable): +class ActivityTemplate(SubscriptionMixin, models.Model): title = models.CharField( _("nom de l'activité"), max_length=200, diff --git a/requirements.txt b/requirements.txt index 54d9b30..41fb065 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,8 @@ Pillow channels django-bootstrap-form==3.2.1 django-widget-tweaks +django-notifications +django-contrib-comments # Production specific daphne -- 2.47.2 From 19093d01f34277943280100b185f902fa7c47a04 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Tue, 18 Jul 2017 13:54:11 +0200 Subject: [PATCH 11/20] Small fixes --- communication/models.py | 19 ++++++++++---- vim | 56 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 vim diff --git a/communication/models.py b/communication/models.py index 220d29d..8a89504 100644 --- a/communication/models.py +++ b/communication/models.py @@ -1,5 +1,4 @@ from django.db import models -from django.conf import settings from django.contrib.auth.models import Group from django.contrib.auth import get_user_model from django.contrib.contenttypes.fields import ( @@ -16,31 +15,41 @@ class Subscription(models.Model): object_id = models.PositiveIntegerField() content_object = GenericForeignKey('content_type', 'object_id') + class Meta: + abstract = True + class UserSubscription(Subscription): - user = models.ForeignKey(settings.AUTH_USER_MODEL) + user = models.ForeignKey(User) is_unsub = models.BooleanField( _("désinscription"), default=False ) + class Meta: + verbose_name = _("souscription utilisateur") + class GroupSubscription(Subscription): group = models.ForeignKey(Group) + class Meta: + verbose_name = _("souscription en groupe") + class SubscriptionMixin(models.Model): subscribed_users = GenericRelation(UserSubscription) subscribed_groups = GenericRelation(GroupSubscription) - def get_unique_users(self): + def get_manual_subscribers(self): return self.subscribed_users.filter(is_unsub=False) - def get_group_users(self): + def get_subscribers_from_groups(self): return User.objects.filter(group__in=self.subscribed_groups.all()) def get_all_subscribers(self): - return (self.get_unique_users().union(self.get_group_users()) + return (self.get_manual_subscribers() + .union(self.get_subscribers_from_groups()) .exclude(is_unsub=True)) class Meta: diff --git a/vim b/vim new file mode 100644 index 0000000..930386b --- /dev/null +++ b/vim @@ -0,0 +1,56 @@ +diff --git a/communication/models.py b/communication/models.py +index 220d29d..8a89504 100644 +--- a/communication/models.py ++++ b/communication/models.py +@@ -1,5 +1,4 @@ + from django.db import models +-from django.conf import settings + from django.contrib.auth.models import Group + from django.contrib.auth import get_user_model + from django.contrib.contenttypes.fields import ( +@@ -16,31 +15,41 @@ class Subscription(models.Model): + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey('content_type', 'object_id') + ++ class Meta: ++ abstract = True ++ + + class UserSubscription(Subscription): +- user = models.ForeignKey(settings.AUTH_USER_MODEL) ++ user = models.ForeignKey(User) + is_unsub = models.BooleanField( + _("désinscription"), + default=False + ) + ++ class Meta: ++ verbose_name = _("souscription utilisateur") ++ + + class GroupSubscription(Subscription): + group = models.ForeignKey(Group) + ++ class Meta: ++ verbose_name = _("souscription en groupe") ++ + + class SubscriptionMixin(models.Model): + subscribed_users = GenericRelation(UserSubscription) + subscribed_groups = GenericRelation(GroupSubscription) + +- def get_unique_users(self): ++ def get_manual_subscribers(self): + return self.subscribed_users.filter(is_unsub=False) + +- def get_group_users(self): ++ def get_subscribers_from_groups(self): + return User.objects.filter(group__in=self.subscribed_groups.all()) + + def get_all_subscribers(self): +- return (self.get_unique_users().union(self.get_group_users()) ++ return (self.get_manual_subscribers() ++ .union(self.get_subscribers_from_groups()) + .exclude(is_unsub=True)) + + class Meta: -- 2.47.2 From 4e8873226fb9b0ce371b94fd07532c58daef3e3e Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Tue, 18 Jul 2017 13:55:09 +0200 Subject: [PATCH 12/20] Oops --- vim | 56 -------------------------------------------------------- 1 file changed, 56 deletions(-) delete mode 100644 vim diff --git a/vim b/vim deleted file mode 100644 index 930386b..0000000 --- a/vim +++ /dev/null @@ -1,56 +0,0 @@ -diff --git a/communication/models.py b/communication/models.py -index 220d29d..8a89504 100644 ---- a/communication/models.py -+++ b/communication/models.py -@@ -1,5 +1,4 @@ - from django.db import models --from django.conf import settings - from django.contrib.auth.models import Group - from django.contrib.auth import get_user_model - from django.contrib.contenttypes.fields import ( -@@ -16,31 +15,41 @@ class Subscription(models.Model): - object_id = models.PositiveIntegerField() - content_object = GenericForeignKey('content_type', 'object_id') - -+ class Meta: -+ abstract = True -+ - - class UserSubscription(Subscription): -- user = models.ForeignKey(settings.AUTH_USER_MODEL) -+ user = models.ForeignKey(User) - is_unsub = models.BooleanField( - _("désinscription"), - default=False - ) - -+ class Meta: -+ verbose_name = _("souscription utilisateur") -+ - - class GroupSubscription(Subscription): - group = models.ForeignKey(Group) - -+ class Meta: -+ verbose_name = _("souscription en groupe") -+ - - class SubscriptionMixin(models.Model): - subscribed_users = GenericRelation(UserSubscription) - subscribed_groups = GenericRelation(GroupSubscription) - -- def get_unique_users(self): -+ def get_manual_subscribers(self): - return self.subscribed_users.filter(is_unsub=False) - -- def get_group_users(self): -+ def get_subscribers_from_groups(self): - return User.objects.filter(group__in=self.subscribed_groups.all()) - - def get_all_subscribers(self): -- return (self.get_unique_users().union(self.get_group_users()) -+ return (self.get_manual_subscribers() -+ .union(self.get_subscribers_from_groups()) - .exclude(is_unsub=True)) - - class Meta: -- 2.47.2 From 270e31f12be4a21dbc68a5e5c974105d7d95c0a0 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Tue, 18 Jul 2017 15:37:13 +0200 Subject: [PATCH 13/20] adapt migrations and tests --- communication/migrations/0001_initial.py | 46 ++++++++++++++++++++++++ equipment/migrations/0001_initial.py | 15 ++++---- evenementiel/urls.py | 2 +- event/migrations/0001_initial.py | 43 +++++++++++----------- event/tests.py | 23 +++++++----- shared/migrations/__init__.py | 0 6 files changed, 92 insertions(+), 37 deletions(-) create mode 100644 communication/migrations/0001_initial.py delete mode 100644 shared/migrations/__init__.py diff --git a/communication/migrations/0001_initial.py b/communication/migrations/0001_initial.py new file mode 100644 index 0000000..c4e91f1 --- /dev/null +++ b/communication/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.3 on 2017-07-18 13:17 +from __future__ import unicode_literals + +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), + ('contenttypes', '0002_remove_content_type_name'), + ('auth', '0008_alter_user_username_max_length'), + ] + + operations = [ + migrations.CreateModel( + name='GroupSubscription', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ('group', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='auth.Group')), + ], + options={ + 'verbose_name': 'souscription en groupe', + }, + ), + migrations.CreateModel( + name='UserSubscription', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('object_id', models.PositiveIntegerField()), + ('is_unsub', models.BooleanField(default=False, verbose_name='désinscription')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name': 'souscription utilisateur', + }, + ), + ] diff --git a/equipment/migrations/0001_initial.py b/equipment/migrations/0001_initial.py index ccc9e91..eea7be7 100644 --- a/equipment/migrations/0001_initial.py +++ b/equipment/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.5 on 2017-02-21 20:14 +# Generated by Django 1.11.3 on 2017-07-18 13:17 from __future__ import unicode_literals from django.db import migrations, models @@ -19,9 +19,9 @@ class Migration(migrations.Migration): name='AbstractEquipment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='Nom du matériel')), - ('stock', models.PositiveSmallIntegerField(verbose_name='Quantité disponible')), - ('description', models.TextField(verbose_name='Description')), + ('name', models.CharField(max_length=200, verbose_name='nom du matériel')), + ('stock', models.PositiveSmallIntegerField(verbose_name='quantité disponible')), + ('description', models.TextField(verbose_name='description')), ], options={ 'verbose_name': 'matériel abstrait', @@ -32,8 +32,8 @@ class Migration(migrations.Migration): name='EquipmentAttribution', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('amount', models.PositiveSmallIntegerField(verbose_name='Quantité attribuée')), - ('remarks', models.TextField(verbose_name="Remarques concernant l'attribution")), + ('amount', models.PositiveSmallIntegerField(verbose_name='quantité attribuée')), + ('remarks', models.TextField(verbose_name="remarques concernant l'attribution")), ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity')), ], options={ @@ -45,7 +45,8 @@ class Migration(migrations.Migration): name='EquipmentRemark', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('remark', models.TextField(verbose_name='Remarque sur le matériel')), + ('remark', models.TextField(verbose_name='remarque sur le matériel')), + ('amount', models.PositiveSmallIntegerField(verbose_name='quantité concernée')), ('is_broken', models.BooleanField()), ('is_lost', models.BooleanField()), ], diff --git a/evenementiel/urls.py b/evenementiel/urls.py index d83241d..e002332 100644 --- a/evenementiel/urls.py +++ b/evenementiel/urls.py @@ -12,7 +12,7 @@ urlpatterns = [ url(r'^', include('shared.urls')), ] -if settings.DEBUG: +if 'debug_toolbar' in settings.INSTALLED_APPS: import debug_toolbar urlpatterns += [ url(r'^__debug__/', include(debug_toolbar.urls)), diff --git a/event/migrations/0001_initial.py b/event/migrations/0001_initial.py index 067d32d..d5343fb 100644 --- a/event/migrations/0001_initial.py +++ b/event/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.10.5 on 2017-02-21 20:14 +# Generated by Django 1.11.3 on 2017-07-18 13:17 from __future__ import unicode_literals from django.conf import settings @@ -21,68 +21,71 @@ class Migration(migrations.Migration): name='ActivityTag', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='Nom du tag')), + ('name', models.CharField(max_length=200, verbose_name='nom du tag')), ('is_public', models.BooleanField(help_text="Sert à faire une distinction dans l'affichage selon que cela soit destiné au public ou à l'équipe organisatrice")), - ('color', models.CharField(help_text='Rentrer une couleur en hexadécimal', max_length=7, validators=[django.core.validators.RegexValidator(message="La chaîne de caractère rentrée n'est pasune couleur en hexadécimal.", regex='^#(?:[0-9a-fA-F]{3}){1,2}$')], verbose_name='Couleur')), + ('color', models.CharField(help_text='Rentrer une couleur en hexadécimal', max_length=7, validators=[django.core.validators.RegexValidator(message="La chaîne de caractère rentrée n'est pas une couleur en hexadécimal.", regex='^#(?:[0-9a-fA-F]{3}){1,2}$')], verbose_name='couleur')), ], options={ - 'verbose_name': 'tag', 'verbose_name_plural': 'tags', + 'verbose_name': 'tag', }, ), migrations.CreateModel( name='ActivityTemplate', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('title', models.CharField(blank=True, max_length=200, null=True, verbose_name="Nom de l'activité")), + ('title', models.CharField(blank=True, max_length=200, null=True, verbose_name="nom de l'activité")), ('is_public', models.NullBooleanField()), ('has_perm', models.NullBooleanField()), - ('min_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Nombre minimum de permanents')), - ('max_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='Nombre maximum de permanents')), - ('description', models.TextField(blank=True, help_text='Public, Visible par tout le monde.', null=True, verbose_name='Description')), - ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='Remarques')), + ('min_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='nombre minimum de permanents')), + ('max_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='nombre maximum de permanents')), + ('description', models.TextField(blank=True, help_text='Public, Visible par tout le monde.', null=True, verbose_name='description')), + ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='remarques')), ], options={ - 'verbose_name': 'template activité', 'verbose_name_plural': 'templates activité', + 'verbose_name': 'template activité', }, ), migrations.CreateModel( name='Event', fields=[ - ('title', models.CharField(max_length=200, verbose_name="Nom de l'évènement")), - ('slug', models.SlugField(help_text="Seulement des lettres, des chiffres oules caractères '_' ou '-'.", primary_key=True, serialize=False, unique=True, verbose_name='Identificateur')), - ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='Date de création')), - ('description', models.TextField(verbose_name='Description')), - ('beginning_date', models.DateTimeField(verbose_name='Date de début')), - ('ending_date', models.DateTimeField(verbose_name='Date de fin')), + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=200, verbose_name="nom de l'évènement")), + ('slug', models.SlugField(help_text="Seulement des lettres, des chiffres oules caractères '_' ou '-'.", unique=True, verbose_name='identificateur')), + ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date de création')), + ('description', models.TextField(verbose_name='description')), + ('beginning_date', models.DateTimeField(verbose_name='date de début')), + ('ending_date', models.DateTimeField(blank=True, null=True, verbose_name='date de fin')), ('created_by', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='created_events', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name': 'évènement', 'verbose_name_plural': 'évènements', + 'verbose_name': 'évènement', }, ), migrations.CreateModel( name='Place', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=200, verbose_name='Nom du lieu')), + ('name', models.CharField(max_length=200, verbose_name='nom du lieu')), ('description', models.TextField(blank=True)), ], options={ - 'verbose_name': 'lieu', 'verbose_name_plural': 'lieux', + 'verbose_name': 'lieu', }, ), migrations.CreateModel( name='Activity', fields=[ ('activitytemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='event.ActivityTemplate')), + ('beginning', models.DateTimeField(verbose_name='heure de début')), + ('end', models.DateTimeField(verbose_name='heure de fin')), ], options={ - 'verbose_name': 'activité', 'verbose_name_plural': 'activités', + 'verbose_name': 'activité', }, bases=('event.activitytemplate',), ), diff --git a/event/tests.py b/event/tests.py index 38b2add..fbffaba 100644 --- a/event/tests.py +++ b/event/tests.py @@ -44,51 +44,56 @@ class ActivityInheritanceTest(TestCase): self.template_act = ActivityTemplate.objects.create() self.real_act = Activity.objects.create( parent=self.template_act, + beginning=timezone.now() + + timedelta(days=30), + end=timezone.now() + + timedelta(days=30) + + timedelta(hours=2), ) - def test_inherites_title(self): + def test_inherits_title(self): self.template_act.title = "parent" self.assertEqual(self.real_act.get_herited('title'), "parent") self.real_act.title = "enfant" self.assertEqual(self.real_act.get_herited('title'), "enfant") - def test_inherites_description(self): + def test_inherits_description(self): self.template_act.description = "parent" self.assertEqual(self.real_act.get_herited('description'), "parent") self.real_act.description = "enfant" self.assertEqual(self.real_act.get_herited('description'), "enfant") - def test_inherites_remarks(self): + def test_inherits_remarks(self): self.template_act.remarks = "parent" self.assertEqual(self.real_act.get_herited('remarks'), "parent") self.real_act.remarks = "enfant" self.assertEqual(self.real_act.get_herited('remarks'), "enfant") - def test_inherites_is_public(self): + def test_inherits_is_public(self): self.template_act.is_public = True self.assertEqual(self.real_act.get_herited('is_public'), True) self.real_act.is_public = False self.assertEqual(self.real_act.get_herited('is_public'), False) - def test_inherites_has_perm(self): + def test_inherits_has_perm(self): self.template_act.has_perm = True self.assertEqual(self.real_act.get_herited('has_perm'), True) self.real_act.has_perm = False self.assertEqual(self.real_act.get_herited('has_perm'), False) - def test_inherites_min_perm(self): + def test_inherits_min_perm(self): self.template_act.min_perm = 42 self.assertEqual(self.real_act.get_herited('min_perm'), 42) self.real_act.min_perm = 1 self.assertEqual(self.real_act.get_herited('min_perm'), 1) - def test_inherites_max_perm(self): + def test_inherits_max_perm(self): self.template_act.max_perm = 42 self.assertEqual(self.real_act.get_herited('max_perm'), 42) self.real_act.max_perm = 1 self.assertEqual(self.real_act.get_herited('max_perm'), 1) - def test_inherites_place(self): + def test_inherits_place(self): self.template_act.place.add(self.loge) self.assertEqual( self.real_act.get_herited('place').get(), @@ -100,7 +105,7 @@ class ActivityInheritanceTest(TestCase): self.aqua ) - def test_inherites_tags(self): + def test_inherits_tags(self): self.template_act.tags.add(self.tag_foo) self.assertEqual( self.real_act.get_herited('tags').get(), diff --git a/shared/migrations/__init__.py b/shared/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 -- 2.47.2 From da75dc7d9cd465b4dc94fd8dc82a29980bf46e67 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Tue, 18 Jul 2017 17:18:54 +0200 Subject: [PATCH 14/20] Test + small changes --- communication/migrations/0001_initial.py | 10 +++- communication/models.py | 30 +++++++----- communication/tests.py | 58 ++++++++++++++++++++++++ equipment/migrations/0001_initial.py | 2 +- event/migrations/0001_initial.py | 12 ++--- event/tests.py | 4 +- 6 files changed, 96 insertions(+), 20 deletions(-) create mode 100644 communication/tests.py diff --git a/communication/migrations/0001_initial.py b/communication/migrations/0001_initial.py index c4e91f1..f103630 100644 --- a/communication/migrations/0001_initial.py +++ b/communication/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 13:17 +# Generated by Django 1.11.3 on 2017-07-18 15:12 from __future__ import unicode_literals from django.conf import settings @@ -43,4 +43,12 @@ class Migration(migrations.Migration): 'verbose_name': 'souscription utilisateur', }, ), + migrations.AlterUniqueTogether( + name='usersubscription', + unique_together=set([('user', 'content_type', 'object_id')]), + ), + migrations.AlterUniqueTogether( + name='groupsubscription', + unique_together=set([('group', 'content_type', 'object_id')]), + ), ] diff --git a/communication/models.py b/communication/models.py index 8a89504..fd0ab1b 100644 --- a/communication/models.py +++ b/communication/models.py @@ -28,6 +28,7 @@ class UserSubscription(Subscription): class Meta: verbose_name = _("souscription utilisateur") + unique_together = ("user", "content_type", "object_id") class GroupSubscription(Subscription): @@ -35,22 +36,29 @@ class GroupSubscription(Subscription): class Meta: verbose_name = _("souscription en groupe") + unique_together = ("group", "content_type", "object_id") class SubscriptionMixin(models.Model): - subscribed_users = GenericRelation(UserSubscription) - subscribed_groups = GenericRelation(GroupSubscription) - - def get_manual_subscribers(self): - return self.subscribed_users.filter(is_unsub=False) - - def get_subscribers_from_groups(self): - return User.objects.filter(group__in=self.subscribed_groups.all()) + user_subscriptions = GenericRelation(UserSubscription) + group_subscriptions = GenericRelation(GroupSubscription) def get_all_subscribers(self): - return (self.get_manual_subscribers() - .union(self.get_subscribers_from_groups()) - .exclude(is_unsub=True)) + subscribed_users = User.objects.filter( + usersubscription__in=self.user_subscriptions.filter(is_unsub=False) + ) + subscribed_groups = Group.objects.filter( + groupsubscription__in=self.group_subscriptions.all() + ) + subscribers_from_groups = User.objects.filter( + groups__in=subscribed_groups, + ).exclude( + usersubscription__in=self.user_subscriptions.filter( + is_unsub=True + ) + ) + + return subscribed_users.union(subscribers_from_groups) class Meta: abstract = True diff --git a/communication/tests.py b/communication/tests.py new file mode 100644 index 0000000..e9a1061 --- /dev/null +++ b/communication/tests.py @@ -0,0 +1,58 @@ +from django.contrib.auth.models import Group +from django.contrib.auth import get_user_model +from django.test import TestCase +from datetime import timedelta +from django.utils import timezone +from .models import (UserSubscription, GroupSubscription) +from event.models import Event + +User = get_user_model() + + +class SubscriptionTest(TestCase): + @classmethod + def setUpTestData(cls): + cls.root = User.objects.create(username='root') + cls.user_true = User.objects.create(username='usertrue') + cls.user_false = User.objects.create(username='userfalse') + cls.user_group_true = User.objects.create(username='usergrouptrue') + cls.user_group_false = User.objects.create(username='usergroupfalse') + + cls.group = Group.objects.create(name="TestGroup") + cls.user_group_true.groups.add(cls.group) + cls.user_group_false.groups.add(cls.group) + + cls.event = Event.objects.create( + title='TestEvent', + slug='test', + created_by=cls.root, + creation_date=timezone.now(), + description="Ceci est un test", + beginning_date=timezone.now() + + timedelta(days=30), + ending_date=timezone.now() + + timedelta(days=31), + ) + cls.groupsub = GroupSubscription.objects.create( + content_object=cls.event, + group=cls.group + ) + cls.groupunsub = UserSubscription.objects.create( + content_object=cls.event, + user=cls.user_group_false, + is_unsub=True + ) + cls.userunsub = UserSubscription.objects.create( + content_object=cls.event, + user=cls.user_false, + is_unsub=True + ) + cls.usersub = UserSubscription.objects.create( + content_object=cls.event, + user=cls.user_true, + is_unsub=False + ) + + def test_all_subs(self): + self.assertSetEqual(set(self.event.get_all_subscribers()), + {self.user_true, self.user_group_true}) diff --git a/equipment/migrations/0001_initial.py b/equipment/migrations/0001_initial.py index eea7be7..8ffae95 100644 --- a/equipment/migrations/0001_initial.py +++ b/equipment/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 13:17 +# Generated by Django 1.11.3 on 2017-07-18 15:12 from __future__ import unicode_literals from django.db import migrations, models diff --git a/event/migrations/0001_initial.py b/event/migrations/0001_initial.py index d5343fb..ac25a16 100644 --- a/event/migrations/0001_initial.py +++ b/event/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 13:17 +# Generated by Django 1.11.3 on 2017-07-18 15:12 from __future__ import unicode_literals from django.conf import settings @@ -26,8 +26,8 @@ class Migration(migrations.Migration): ('color', models.CharField(help_text='Rentrer une couleur en hexadécimal', max_length=7, validators=[django.core.validators.RegexValidator(message="La chaîne de caractère rentrée n'est pas une couleur en hexadécimal.", regex='^#(?:[0-9a-fA-F]{3}){1,2}$')], verbose_name='couleur')), ], options={ - 'verbose_name_plural': 'tags', 'verbose_name': 'tag', + 'verbose_name_plural': 'tags', }, ), migrations.CreateModel( @@ -43,8 +43,8 @@ class Migration(migrations.Migration): ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='remarques')), ], options={ - 'verbose_name_plural': 'templates activité', 'verbose_name': 'template activité', + 'verbose_name_plural': 'templates activité', }, ), migrations.CreateModel( @@ -60,8 +60,8 @@ class Migration(migrations.Migration): ('created_by', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='created_events', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name_plural': 'évènements', 'verbose_name': 'évènement', + 'verbose_name_plural': 'évènements', }, ), migrations.CreateModel( @@ -72,8 +72,8 @@ class Migration(migrations.Migration): ('description', models.TextField(blank=True)), ], options={ - 'verbose_name_plural': 'lieux', 'verbose_name': 'lieu', + 'verbose_name_plural': 'lieux', }, ), migrations.CreateModel( @@ -84,8 +84,8 @@ class Migration(migrations.Migration): ('end', models.DateTimeField(verbose_name='heure de fin')), ], options={ - 'verbose_name_plural': 'activités', 'verbose_name': 'activité', + 'verbose_name_plural': 'activités', }, bases=('event.activitytemplate',), ), diff --git a/event/tests.py b/event/tests.py index fbffaba..7185054 100644 --- a/event/tests.py +++ b/event/tests.py @@ -1,4 +1,4 @@ -from django.contrib.auth.models import User +from django.contrib.auth import get_user_model from django.core.exceptions import ValidationError from django.test import TestCase from datetime import timedelta @@ -6,6 +6,8 @@ from django.utils import timezone from .models import Event, ActivityTemplate, Activity, Place, \ ActivityTag +User = get_user_model() + class ActivityInheritanceTest(TestCase): @classmethod -- 2.47.2 From 5b005571d5ff68228d65678d23a7d1c4edadea6f Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Tue, 18 Jul 2017 17:31:01 +0200 Subject: [PATCH 15/20] Event ending_date no longer optional --- event/models.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/event/models.py b/event/models.py index d0e4f94..9cee1ee 100644 --- a/event/models.py +++ b/event/models.py @@ -28,11 +28,7 @@ class Event(SubscriptionMixin, models.Model): ) description = models.TextField(_('description')) beginning_date = models.DateTimeField(_('date de début')) - ending_date = models.DateTimeField( - _('date de fin'), - blank=True, - null=True, - ) + ending_date = models.DateTimeField(_('date de fin')) class Meta: verbose_name = _("évènement") -- 2.47.2 From 53d9084d6398a83c86882da16641096288096f61 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Tue, 18 Jul 2017 18:41:44 +0200 Subject: [PATCH 16/20] Correct Activity inheritance and get_herited() --- communication/migrations/0001_initial.py | 6 +-- equipment/migrations/0001_initial.py | 12 ++--- event/migrations/0001_initial.py | 65 ++++++++++++++++-------- event/models.py | 19 ++++--- 4 files changed, 64 insertions(+), 38 deletions(-) diff --git a/communication/migrations/0001_initial.py b/communication/migrations/0001_initial.py index f103630..17d5c27 100644 --- a/communication/migrations/0001_initial.py +++ b/communication/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 15:12 +# Generated by Django 1.11.3 on 2017-07-18 16:10 from __future__ import unicode_literals from django.conf import settings @@ -12,9 +12,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0002_remove_content_type_name'), ('auth', '0008_alter_user_username_max_length'), + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), ] operations = [ diff --git a/equipment/migrations/0001_initial.py b/equipment/migrations/0001_initial.py index 8ffae95..3ec60b7 100644 --- a/equipment/migrations/0001_initial.py +++ b/equipment/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 15:12 +# Generated by Django 1.11.3 on 2017-07-18 16:10 from __future__ import unicode_literals from django.db import migrations, models @@ -24,8 +24,8 @@ class Migration(migrations.Migration): ('description', models.TextField(verbose_name='description')), ], options={ - 'verbose_name': 'matériel abstrait', 'verbose_name_plural': 'matériels abstraits', + 'verbose_name': 'matériel abstrait', }, ), migrations.CreateModel( @@ -37,8 +37,8 @@ class Migration(migrations.Migration): ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity')), ], options={ - 'verbose_name': 'attribution de matériel', 'verbose_name_plural': 'attributions de matériel', + 'verbose_name': 'attribution de matériel', }, ), migrations.CreateModel( @@ -51,8 +51,8 @@ class Migration(migrations.Migration): ('is_lost', models.BooleanField()), ], options={ - 'verbose_name': 'remarque sur matériel', 'verbose_name_plural': 'remarques sur le matériel', + 'verbose_name': 'remarque sur matériel', }, ), migrations.CreateModel( @@ -61,8 +61,8 @@ class Migration(migrations.Migration): ('abstractequipment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='equipment.AbstractEquipment')), ], options={ - 'verbose_name': 'matériel permanent', 'verbose_name_plural': 'matériels permanents', + 'verbose_name': 'matériel permanent', }, bases=('equipment.abstractequipment',), ), @@ -73,8 +73,8 @@ class Migration(migrations.Migration): ('event', models.ForeignKey(help_text='Évènement pour lequel le matériel a été loué ou emprunté ou apporté', on_delete=django.db.models.deletion.CASCADE, related_name='specific_equipment', to='event.Event')), ], options={ - 'verbose_name': 'matériel temporaire', 'verbose_name_plural': 'matériels temporaires', + 'verbose_name': 'matériel temporaire', }, bases=('equipment.abstractequipment',), ), diff --git a/event/migrations/0001_initial.py b/event/migrations/0001_initial.py index ac25a16..b8d522b 100644 --- a/event/migrations/0001_initial.py +++ b/event/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 15:12 +# Generated by Django 1.11.3 on 2017-07-18 16:10 from __future__ import unicode_literals from django.conf import settings @@ -17,6 +17,25 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Activity', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(blank=True, max_length=200, null=True, verbose_name="nom de l'activité")), + ('is_public', models.NullBooleanField()), + ('has_perm', models.NullBooleanField()), + ('min_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='nombre minimum de permanents')), + ('max_perm', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='nombre maximum de permanents')), + ('description', models.TextField(blank=True, help_text='Public, Visible par tout le monde.', null=True, verbose_name='description')), + ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='remarques')), + ('beginning', models.DateTimeField(verbose_name='heure de début')), + ('end', models.DateTimeField(verbose_name='heure de fin')), + ], + options={ + 'verbose_name_plural': 'activités', + 'verbose_name': 'activité', + }, + ), migrations.CreateModel( name='ActivityTag', fields=[ @@ -26,8 +45,8 @@ class Migration(migrations.Migration): ('color', models.CharField(help_text='Rentrer une couleur en hexadécimal', max_length=7, validators=[django.core.validators.RegexValidator(message="La chaîne de caractère rentrée n'est pas une couleur en hexadécimal.", regex='^#(?:[0-9a-fA-F]{3}){1,2}$')], verbose_name='couleur')), ], options={ - 'verbose_name': 'tag', 'verbose_name_plural': 'tags', + 'verbose_name': 'tag', }, ), migrations.CreateModel( @@ -43,8 +62,8 @@ class Migration(migrations.Migration): ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='remarques')), ], options={ - 'verbose_name': 'template activité', 'verbose_name_plural': 'templates activité', + 'verbose_name': 'template activité', }, ), migrations.CreateModel( @@ -56,12 +75,12 @@ class Migration(migrations.Migration): ('creation_date', models.DateTimeField(auto_now_add=True, verbose_name='date de création')), ('description', models.TextField(verbose_name='description')), ('beginning_date', models.DateTimeField(verbose_name='date de début')), - ('ending_date', models.DateTimeField(blank=True, null=True, verbose_name='date de fin')), + ('ending_date', models.DateTimeField(verbose_name='date de fin')), ('created_by', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='created_events', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name': 'évènement', 'verbose_name_plural': 'évènements', + 'verbose_name': 'évènement', }, ), migrations.CreateModel( @@ -72,46 +91,48 @@ class Migration(migrations.Migration): ('description', models.TextField(blank=True)), ], options={ - 'verbose_name': 'lieu', 'verbose_name_plural': 'lieux', + 'verbose_name': 'lieu', }, ), - migrations.CreateModel( - name='Activity', - fields=[ - ('activitytemplate_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='event.ActivityTemplate')), - ('beginning', models.DateTimeField(verbose_name='heure de début')), - ('end', models.DateTimeField(verbose_name='heure de fin')), - ], - options={ - 'verbose_name': 'activité', - 'verbose_name_plural': 'activités', - }, - bases=('event.activitytemplate',), - ), migrations.AddField( model_name='activitytemplate', name='event', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='activities', to='event.Event'), + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event'), ), migrations.AddField( model_name='activitytemplate', name='place', - field=models.ManyToManyField(blank=True, related_name='activities', to='event.Place'), + field=models.ManyToManyField(blank=True, to='event.Place'), ), migrations.AddField( model_name='activitytemplate', name='tags', - field=models.ManyToManyField(blank=True, related_name='activities', to='event.ActivityTag'), + field=models.ManyToManyField(blank=True, to='event.ActivityTag'), + ), + migrations.AddField( + model_name='activity', + name='event', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event'), ), migrations.AddField( model_name='activity', name='parent', field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='children', to='event.ActivityTemplate'), ), + migrations.AddField( + model_name='activity', + name='place', + field=models.ManyToManyField(blank=True, to='event.Place'), + ), migrations.AddField( model_name='activity', name='staff', field=models.ManyToManyField(blank=True, related_name='in_perm_activities', to=settings.AUTH_USER_MODEL), ), + migrations.AddField( + model_name='activity', + name='tags', + field=models.ManyToManyField(blank=True, to='event.ActivityTag'), + ), ] diff --git a/event/models.py b/event/models.py index 9cee1ee..b9f5945 100644 --- a/event/models.py +++ b/event/models.py @@ -84,7 +84,7 @@ class ActivityTag(models.Model): return self.name -class ActivityTemplate(SubscriptionMixin, models.Model): +class AbstractActivityTemplate(SubscriptionMixin, models.Model): title = models.CharField( _("nom de l'activité"), max_length=200, @@ -94,7 +94,6 @@ class ActivityTemplate(SubscriptionMixin, models.Model): # FIXME: voir comment on traite l'héritage de `event` event = models.ForeignKey( Event, - related_name="activities", blank=True, null=True, ) @@ -128,15 +127,18 @@ class ActivityTemplate(SubscriptionMixin, models.Model): ) tags = models.ManyToManyField( ActivityTag, - related_name="activities", blank=True, ) place = models.ManyToManyField( Place, - related_name="activities", blank=True, ) + class Meta: + abstract = True + + +class ActivityTemplate(AbstractActivityTemplate): class Meta: verbose_name = _("template activité") verbose_name_plural = _("templates activité") @@ -145,7 +147,7 @@ class ActivityTemplate(SubscriptionMixin, models.Model): return self.title -class Activity(ActivityTemplate): +class Activity(AbstractActivityTemplate): parent = models.ForeignKey( ActivityTemplate, related_name="children", @@ -160,13 +162,16 @@ class Activity(ActivityTemplate): end = models.DateTimeField(_("heure de fin")) def get_herited(self, attrname): + inherited_fields = [f.name for f in ActivityTemplate._meta.get_fields()] + m2m_fields = [f.name for f in ActivityTemplate._meta.get_fields() + if f.many_to_many] attr = super(Activity, self).__getattribute__(attrname) - if attrname in {"parent", "staff", "equipment"}: + if attrname not in inherited_fields: raise FieldError( _("%(attrname)s n'est pas un champ héritable"), params={'attrname': attrname}, ) - elif (attrname == 'place' or attrname == 'tags'): + elif attrname in m2m_fields: if attr.exists(): return attr else: -- 2.47.2 From 670a9d45da782dc3db83da866da421411c77a2e0 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Tue, 18 Jul 2017 19:08:09 +0200 Subject: [PATCH 17/20] Use EventSpecificMixin for event-specific models --- communication/migrations/0001_initial.py | 4 +- equipment/migrations/0001_initial.py | 50 ++++++------------------ equipment/models.py | 32 ++++----------- event/migrations/0001_initial.py | 12 ++++-- event/models.py | 11 ++---- event/tests.py | 3 +- shared/models.py | 18 ++++++++- 7 files changed, 53 insertions(+), 77 deletions(-) diff --git a/communication/migrations/0001_initial.py b/communication/migrations/0001_initial.py index 17d5c27..afcbd10 100644 --- a/communication/migrations/0001_initial.py +++ b/communication/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 16:10 +# Generated by Django 1.11.3 on 2017-07-18 17:05 from __future__ import unicode_literals from django.conf import settings @@ -13,8 +13,8 @@ class Migration(migrations.Migration): dependencies = [ ('auth', '0008_alter_user_username_max_length'), - ('contenttypes', '0002_remove_content_type_name'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0002_remove_content_type_name'), ] operations = [ diff --git a/equipment/migrations/0001_initial.py b/equipment/migrations/0001_initial.py index 3ec60b7..c6a34b4 100644 --- a/equipment/migrations/0001_initial.py +++ b/equipment/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 16:10 +# Generated by Django 1.11.3 on 2017-07-18 17:05 from __future__ import unicode_literals from django.db import migrations, models @@ -16,7 +16,7 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='AbstractEquipment', + name='Equipment', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=200, verbose_name='nom du matériel')), @@ -24,8 +24,8 @@ class Migration(migrations.Migration): ('description', models.TextField(verbose_name='description')), ], options={ - 'verbose_name_plural': 'matériels abstraits', - 'verbose_name': 'matériel abstrait', + 'verbose_name_plural': 'matériels permanents', + 'verbose_name': 'matériel permanent', }, ), migrations.CreateModel( @@ -35,6 +35,7 @@ class Migration(migrations.Migration): ('amount', models.PositiveSmallIntegerField(verbose_name='quantité attribuée')), ('remarks', models.TextField(verbose_name="remarques concernant l'attribution")), ('activity', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Activity')), + ('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment')), ], options={ 'verbose_name_plural': 'attributions de matériel', @@ -49,48 +50,21 @@ class Migration(migrations.Migration): ('amount', models.PositiveSmallIntegerField(verbose_name='quantité concernée')), ('is_broken', models.BooleanField()), ('is_lost', models.BooleanField()), + ('equipment', models.ForeignKey(help_text='Matériel concerné par la remarque', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment')), ], options={ 'verbose_name_plural': 'remarques sur le matériel', 'verbose_name': 'remarque sur matériel', }, ), - migrations.CreateModel( - name='Equipment', - fields=[ - ('abstractequipment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='equipment.AbstractEquipment')), - ], - options={ - 'verbose_name_plural': 'matériels permanents', - 'verbose_name': 'matériel permanent', - }, - bases=('equipment.abstractequipment',), - ), - migrations.CreateModel( - name='TemporaryEquipment', - fields=[ - ('abstractequipment_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='equipment.AbstractEquipment')), - ('event', models.ForeignKey(help_text='Évènement pour lequel le matériel a été loué ou emprunté ou apporté', on_delete=django.db.models.deletion.CASCADE, related_name='specific_equipment', to='event.Event')), - ], - options={ - 'verbose_name_plural': 'matériels temporaires', - 'verbose_name': 'matériel temporaire', - }, - bases=('equipment.abstractequipment',), - ), migrations.AddField( - model_name='equipmentremark', - name='equipment', - field=models.ForeignKey(help_text='Matériel concerné par la remarque', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.AbstractEquipment'), - ), - migrations.AddField( - model_name='equipmentattribution', - name='equipment', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.AbstractEquipment'), - ), - migrations.AddField( - model_name='abstractequipment', + model_name='equipment', name='activities', field=models.ManyToManyField(related_name='equipment', through='equipment.EquipmentAttribution', to='event.Activity'), ), + migrations.AddField( + model_name='equipment', + name='event', + field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèleest spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'), + ), ] diff --git a/equipment/models.py b/equipment/models.py index 9fd3287..84dd205 100644 --- a/equipment/models.py +++ b/equipment/models.py @@ -1,9 +1,10 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ -from event.models import Event, Activity +from event.models import Activity +from shared.models import EventSpecificMixin -class AbstractEquipment(models.Model): +class Equipment(EventSpecificMixin, models.Model): name = models.CharField( _("nom du matériel"), max_length=200, @@ -17,34 +18,15 @@ class AbstractEquipment(models.Model): ) class Meta: - verbose_name = _("matériel abstrait") - verbose_name_plural = _("matériels abstraits") + verbose_name = _("matériel permanent") + verbose_name_plural = _("matériels permanents") def __str__(self): return self.name -class Equipment(AbstractEquipment): - class Meta: - verbose_name = _("matériel permanent") - verbose_name_plural = _("matériels permanents") - - -class TemporaryEquipment(AbstractEquipment): - event = models.ForeignKey( - Event, - related_name="specific_equipment", - help_text=_("Évènement pour lequel le matériel " - "a été loué ou emprunté ou apporté"), - ) - - class Meta: - verbose_name = _("matériel temporaire") - verbose_name_plural = _("matériels temporaires") - - class EquipmentAttribution(models.Model): - equipment = models.ForeignKey(AbstractEquipment) + equipment = models.ForeignKey(Equipment) activity = models.ForeignKey(Activity) amount = models.PositiveSmallIntegerField(_("quantité attribuée")) remarks = models.TextField(_("remarques concernant l'attribution")) @@ -62,7 +44,7 @@ class EquipmentAttribution(models.Model): class EquipmentRemark(models.Model): remark = models.TextField(_("remarque sur le matériel")) equipment = models.ForeignKey( - AbstractEquipment, + Equipment, related_name="remarks", help_text=_("Matériel concerné par la remarque"), ) diff --git a/event/migrations/0001_initial.py b/event/migrations/0001_initial.py index b8d522b..38949c0 100644 --- a/event/migrations/0001_initial.py +++ b/event/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 16:10 +# Generated by Django 1.11.3 on 2017-07-18 17:05 from __future__ import unicode_literals from django.conf import settings @@ -89,6 +89,7 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=200, verbose_name='nom du lieu')), ('description', models.TextField(blank=True)), + ('event', models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèleest spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement')), ], options={ 'verbose_name_plural': 'lieux', @@ -98,7 +99,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='activitytemplate', name='event', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Event'), ), migrations.AddField( model_name='activitytemplate', @@ -110,10 +111,15 @@ class Migration(migrations.Migration): name='tags', field=models.ManyToManyField(blank=True, to='event.ActivityTag'), ), + migrations.AddField( + model_name='activitytag', + name='event', + field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèleest spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'), + ), migrations.AddField( model_name='activity', name='event', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event'), + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='event.Event'), ), migrations.AddField( model_name='activity', diff --git a/event/models.py b/event/models.py index b9f5945..fca94c2 100644 --- a/event/models.py +++ b/event/models.py @@ -4,6 +4,7 @@ from django.core.validators import RegexValidator from django.core.exceptions import FieldError from django.db import models from communication.models import SubscriptionMixin +from shared.models import EventSpecificMixin class Event(SubscriptionMixin, models.Model): @@ -38,7 +39,7 @@ class Event(SubscriptionMixin, models.Model): return self.title -class Place(models.Model): +class Place(EventSpecificMixin, models.Model): name = models.CharField( _("nom du lieu"), max_length=200, @@ -53,7 +54,7 @@ class Place(models.Model): return self.name -class ActivityTag(models.Model): +class ActivityTag(EventSpecificMixin, models.Model): name = models.CharField( _("nom du tag"), max_length=200, @@ -92,11 +93,7 @@ class AbstractActivityTemplate(SubscriptionMixin, models.Model): null=True, ) # FIXME: voir comment on traite l'héritage de `event` - event = models.ForeignKey( - Event, - blank=True, - null=True, - ) + event = models.ForeignKey(Event) is_public = models.NullBooleanField( blank=True, ) diff --git a/event/tests.py b/event/tests.py index 7185054..f94b5ab 100644 --- a/event/tests.py +++ b/event/tests.py @@ -43,9 +43,10 @@ class ActivityInheritanceTest(TestCase): ) def setUp(self): - self.template_act = ActivityTemplate.objects.create() + self.template_act = ActivityTemplate.objects.create(event=self.event) self.real_act = Activity.objects.create( parent=self.template_act, + event=self.event, beginning=timezone.now() + timedelta(days=30), end=timezone.now() diff --git a/shared/models.py b/shared/models.py index 71a8362..0e55ab0 100644 --- a/shared/models.py +++ b/shared/models.py @@ -1,3 +1,19 @@ from django.db import models +from django.utils.translation import ugettext_lazy as _ -# Create your models here. + +class EventSpecificMixin(models.Model): + """Mixin allowing for event-specific models instances + or not (depending on whether the event field is null)""" + + event = models.ForeignKey( + 'event.Event', + verbose_name=_("évènement"), + help_text=_("Si spécifié, l'instance du modèle" + "est spécifique à l'évènement en question"), + blank=True, + null=True + ) + + class Meta: + abstract = True -- 2.47.2 From 8a587b3d56da33de5f95e5fbafa2a6fb08f868ff Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 21 Jul 2017 16:18:19 +0200 Subject: [PATCH 18/20] Override attribution save() + stuff --- equipment/models.py | 14 ++++++++++---- event/models.py | 18 +++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/equipment/models.py b/equipment/models.py index 84dd205..6498756 100644 --- a/equipment/models.py +++ b/equipment/models.py @@ -1,7 +1,7 @@ from django.db import models +from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ -from event.models import Activity -from shared.models import EventSpecificMixin +from event.models import Activity, EventSpecificMixin class Equipment(EventSpecificMixin, models.Model): @@ -18,8 +18,8 @@ class Equipment(EventSpecificMixin, models.Model): ) class Meta: - verbose_name = _("matériel permanent") - verbose_name_plural = _("matériels permanents") + verbose_name = _("matériel") + verbose_name_plural = _("matériels") def __str__(self): return self.name @@ -40,6 +40,12 @@ class EquipmentAttribution(models.Model): self.amout, self.activity.get_herited('title')) + def save(self, *args, **kwargs): + if self.equipment.event and self.equipment.event != self.activity.event: + raise ValidationError + + super(EquipmentAttribution, self).save(*args, **kwargs) + class EquipmentRemark(models.Model): remark = models.TextField(_("remarque sur le matériel")) diff --git a/event/models.py b/event/models.py index fca94c2..14d7980 100644 --- a/event/models.py +++ b/event/models.py @@ -4,7 +4,6 @@ from django.core.validators import RegexValidator from django.core.exceptions import FieldError from django.db import models from communication.models import SubscriptionMixin -from shared.models import EventSpecificMixin class Event(SubscriptionMixin, models.Model): @@ -39,6 +38,23 @@ class Event(SubscriptionMixin, models.Model): return self.title +class EventSpecificMixin(models.Model): + """Mixin allowing for event-specific models instances + or not (depending on whether the event field is null)""" + + event = models.ForeignKey( + 'event.Event', + verbose_name=_("évènement"), + help_text=_("Si spécifié, l'instance du modèle " + "est spécifique à l'évènement en question"), + blank=True, + null=True + ) + + class Meta: + abstract = True + + class Place(EventSpecificMixin, models.Model): name = models.CharField( _("nom du lieu"), -- 2.47.2 From ef8c62835b6a5762cabcd6f8ac57b1806bd3fe73 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 21 Jul 2017 16:20:39 +0200 Subject: [PATCH 19/20] Migrations --- communication/migrations/0001_initial.py | 4 ++-- equipment/migrations/0001_initial.py | 12 ++++++------ event/migrations/0001_initial.py | 16 ++++++++-------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/communication/migrations/0001_initial.py b/communication/migrations/0001_initial.py index afcbd10..70a00de 100644 --- a/communication/migrations/0001_initial.py +++ b/communication/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 17:05 +# Generated by Django 1.11.3 on 2017-07-21 14:20 from __future__ import unicode_literals from django.conf import settings @@ -12,9 +12,9 @@ class Migration(migrations.Migration): initial = True dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), ('auth', '0008_alter_user_username_max_length'), migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('contenttypes', '0002_remove_content_type_name'), ] operations = [ diff --git a/equipment/migrations/0001_initial.py b/equipment/migrations/0001_initial.py index c6a34b4..00e5083 100644 --- a/equipment/migrations/0001_initial.py +++ b/equipment/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 17:05 +# Generated by Django 1.11.3 on 2017-07-21 14:20 from __future__ import unicode_literals from django.db import migrations, models @@ -24,8 +24,8 @@ class Migration(migrations.Migration): ('description', models.TextField(verbose_name='description')), ], options={ - 'verbose_name_plural': 'matériels permanents', - 'verbose_name': 'matériel permanent', + 'verbose_name': 'matériel', + 'verbose_name_plural': 'matériels', }, ), migrations.CreateModel( @@ -38,8 +38,8 @@ class Migration(migrations.Migration): ('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='equipment.Equipment')), ], options={ - 'verbose_name_plural': 'attributions de matériel', 'verbose_name': 'attribution de matériel', + 'verbose_name_plural': 'attributions de matériel', }, ), migrations.CreateModel( @@ -53,8 +53,8 @@ class Migration(migrations.Migration): ('equipment', models.ForeignKey(help_text='Matériel concerné par la remarque', on_delete=django.db.models.deletion.CASCADE, related_name='remarks', to='equipment.Equipment')), ], options={ - 'verbose_name_plural': 'remarques sur le matériel', 'verbose_name': 'remarque sur matériel', + 'verbose_name_plural': 'remarques sur le matériel', }, ), migrations.AddField( @@ -65,6 +65,6 @@ class Migration(migrations.Migration): migrations.AddField( model_name='equipment', name='event', - field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèleest spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'), + field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'), ), ] diff --git a/event/migrations/0001_initial.py b/event/migrations/0001_initial.py index 38949c0..79dfb71 100644 --- a/event/migrations/0001_initial.py +++ b/event/migrations/0001_initial.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Generated by Django 1.11.3 on 2017-07-18 17:05 +# Generated by Django 1.11.3 on 2017-07-21 14:20 from __future__ import unicode_literals from django.conf import settings @@ -32,8 +32,8 @@ class Migration(migrations.Migration): ('end', models.DateTimeField(verbose_name='heure de fin')), ], options={ - 'verbose_name_plural': 'activités', 'verbose_name': 'activité', + 'verbose_name_plural': 'activités', }, ), migrations.CreateModel( @@ -45,8 +45,8 @@ class Migration(migrations.Migration): ('color', models.CharField(help_text='Rentrer une couleur en hexadécimal', max_length=7, validators=[django.core.validators.RegexValidator(message="La chaîne de caractère rentrée n'est pas une couleur en hexadécimal.", regex='^#(?:[0-9a-fA-F]{3}){1,2}$')], verbose_name='couleur')), ], options={ - 'verbose_name_plural': 'tags', 'verbose_name': 'tag', + 'verbose_name_plural': 'tags', }, ), migrations.CreateModel( @@ -62,8 +62,8 @@ class Migration(migrations.Migration): ('remarks', models.TextField(blank=True, help_text='Visible uniquement par les organisateurs', null=True, verbose_name='remarques')), ], options={ - 'verbose_name_plural': 'templates activité', 'verbose_name': 'template activité', + 'verbose_name_plural': 'templates activité', }, ), migrations.CreateModel( @@ -79,8 +79,8 @@ class Migration(migrations.Migration): ('created_by', models.ForeignKey(editable=False, on_delete=django.db.models.deletion.CASCADE, related_name='created_events', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name_plural': 'évènements', 'verbose_name': 'évènement', + 'verbose_name_plural': 'évènements', }, ), migrations.CreateModel( @@ -89,11 +89,11 @@ class Migration(migrations.Migration): ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('name', models.CharField(max_length=200, verbose_name='nom du lieu')), ('description', models.TextField(blank=True)), - ('event', models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèleest spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement')), + ('event', models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement')), ], options={ - 'verbose_name_plural': 'lieux', 'verbose_name': 'lieu', + 'verbose_name_plural': 'lieux', }, ), migrations.AddField( @@ -114,7 +114,7 @@ class Migration(migrations.Migration): migrations.AddField( model_name='activitytag', name='event', - field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèleest spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'), + field=models.ForeignKey(blank=True, help_text="Si spécifié, l'instance du modèle est spécifique à l'évènement en question", null=True, on_delete=django.db.models.deletion.CASCADE, to='event.Event', verbose_name='évènement'), ), migrations.AddField( model_name='activity', -- 2.47.2 From 782cb34b0e0c92c1fa6e6ce04bec91160224c2c1 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Fri, 21 Jul 2017 16:24:53 +0200 Subject: [PATCH 20/20] Change get_herited method --- event/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/event/models.py b/event/models.py index 14d7980..36dca46 100644 --- a/event/models.py +++ b/event/models.py @@ -178,7 +178,7 @@ class Activity(AbstractActivityTemplate): inherited_fields = [f.name for f in ActivityTemplate._meta.get_fields()] m2m_fields = [f.name for f in ActivityTemplate._meta.get_fields() if f.many_to_many] - attr = super(Activity, self).__getattribute__(attrname) + attr = getattr(self, attrname) if attrname not in inherited_fields: raise FieldError( _("%(attrname)s n'est pas un champ héritable"), @@ -188,9 +188,9 @@ class Activity(AbstractActivityTemplate): if attr.exists(): return attr else: - return self.parent.__getattribute__(attrname) + return getattr(self.parent, attrname) elif attr is None: - return self.parent.__getattribute__(attrname) + return getattr(self.parent, attrname) else: return attr -- 2.47.2