From 9776a18e4c923f99df7db64c20bf331884dbfee4 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 8 Jun 2019 14:48:18 +0200 Subject: [PATCH 01/12] =?UTF-8?q?D=C3=A9place=20les=20champs=20`paid`=20et?= =?UTF-8?q?=20`paymenttype`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bda/models.py | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/bda/models.py b/bda/models.py index 9ac38a41..05c2448a 100644 --- a/bda/models.py +++ b/bda/models.py @@ -151,6 +151,25 @@ PAYMENT_TYPES = ( ) +class Attribution(models.Model): + participant = models.ForeignKey("Participant", on_delete=models.CASCADE) + spectacle = models.ForeignKey( + Spectacle, on_delete=models.CASCADE, related_name="attribues" + ) + given = models.BooleanField("Donnée", default=False) + paid = models.BooleanField("Payée", default=False) + paymenttype = models.CharField( + "Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True + ) + + def __str__(self): + return "%s -- %s, %s" % ( + self.participant.user, + self.spectacle.title, + self.spectacle.date, + ) + + class Participant(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) choices = models.ManyToManyField( @@ -159,10 +178,6 @@ class Participant(models.Model): attributions = models.ManyToManyField( Spectacle, through="Attribution", related_name="attributed_to" ) - paid = models.BooleanField("A payé", default=False) - paymenttype = models.CharField( - "Moyen de paiement", max_length=6, choices=PAYMENT_TYPES, blank=True - ) tirage = models.ForeignKey(Tirage, on_delete=models.CASCADE) choicesrevente = models.ManyToManyField( Spectacle, related_name="subscribed", blank=True @@ -212,21 +227,6 @@ class ChoixSpectacle(models.Model): verbose_name_plural = "voeux" -class Attribution(models.Model): - participant = models.ForeignKey(Participant, on_delete=models.CASCADE) - spectacle = models.ForeignKey( - Spectacle, on_delete=models.CASCADE, related_name="attribues" - ) - given = models.BooleanField("Donnée", default=False) - - def __str__(self): - return "%s -- %s, %s" % ( - self.participant.user, - self.spectacle.title, - self.spectacle.date, - ) - - class SpectacleRevente(models.Model): attribution = models.OneToOneField( Attribution, on_delete=models.CASCADE, related_name="revente" From 29111059f9d6e98d6bee770ca6c573be71fb1fed Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 8 Jun 2019 14:59:45 +0200 Subject: [PATCH 02/12] =?UTF-8?q?Rajoute=20un=20manager=20=C3=A0=20`Partic?= =?UTF-8?q?ipant`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On rajoute un manager qui annote les querysets avec si le participant a payé ou non --- bda/models.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/bda/models.py b/bda/models.py index 05c2448a..de87731e 100644 --- a/bda/models.py +++ b/bda/models.py @@ -9,7 +9,7 @@ from django.contrib.auth.models import User from django.contrib.sites.models import Site from django.core import mail from django.db import models -from django.db.models import Count +from django.db.models import Count, Exists from django.utils import formats, timezone @@ -170,6 +170,19 @@ class Attribution(models.Model): ) +class ParticipantPaidManager(models.Manager): + """ + Un manager qui annote le queryset avec un champ `paid`, + indiquant si un participant a payé toutes ses attributions. + """ + + def get_queryset(self): + unpaid = Attribution.objects.filter( + participant=models.OuterRef("pk"), paid=False + ) + return super().get_queryset().annotate(paid=~Exists(unpaid)) + + class Participant(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) choices = models.ManyToManyField( @@ -183,6 +196,9 @@ class Participant(models.Model): Spectacle, related_name="subscribed", blank=True ) + objects = models.Manager() + objects_paid = ParticipantPaidManager() + def __str__(self): return "%s - %s" % (self.user, self.tirage.title) From b37e7c4c41f36c5ef06f126a067a95e595553340 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 8 Jun 2019 15:02:12 +0200 Subject: [PATCH 03/12] Migrations --- bda/migrations/0014_attribution_paid_field.py | 31 +++++++++++++++ bda/migrations/0015_move_bda_payment.py | 38 +++++++++++++++++++ .../0016_delete_participant_paid.py | 13 +++++++ 3 files changed, 82 insertions(+) create mode 100644 bda/migrations/0014_attribution_paid_field.py create mode 100644 bda/migrations/0015_move_bda_payment.py create mode 100644 bda/migrations/0016_delete_participant_paid.py diff --git a/bda/migrations/0014_attribution_paid_field.py b/bda/migrations/0014_attribution_paid_field.py new file mode 100644 index 00000000..b5bb6208 --- /dev/null +++ b/bda/migrations/0014_attribution_paid_field.py @@ -0,0 +1,31 @@ +# Generated by Django 2.2 on 2019-06-03 19:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [("bda", "0013_merge_20180524_2123")] + + operations = [ + migrations.AddField( + model_name="attribution", + name="paid", + field=models.BooleanField(default=False, verbose_name="Payée"), + ), + migrations.AddField( + model_name="attribution", + name="paymenttype", + field=models.CharField( + blank=True, + choices=[ + ("cash", "Cash"), + ("cb", "CB"), + ("cheque", "Chèque"), + ("autre", "Autre"), + ], + max_length=6, + verbose_name="Moyen de paiement", + ), + ), + ] diff --git a/bda/migrations/0015_move_bda_payment.py b/bda/migrations/0015_move_bda_payment.py new file mode 100644 index 00000000..261cf483 --- /dev/null +++ b/bda/migrations/0015_move_bda_payment.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2 on 2019-06-03 19:30 + +from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist +from django.db import migrations + + +def set_attr_payment(apps, schema_editor): + Attribution = apps.get_model("bda", "Attribution") + for attr in Attribution.objects.all(): + attr.paid = attr.participant.paid + attr.paymenttype = attr.participant.paymenttype + attr.save() + + +def set_participant_payment(apps, schema_editor): + Participant = apps.get_model("bda", "Participant") + for part in Participant.objects.all(): + attr_set = part.attribution_set + part.paid = attr_set.exists() and not attr_set.filter(paid=False).exists() + try: + # S'il n'y a qu'un seul type de paiement, on le set + part.paymenttype = ( + attr_set.values_list("paymenttype", flat=True).distinct().get() + ) + # Sinon, whatever + except (ObjectDoesNotExist, MultipleObjectsReturned) as e: + print(e) + pass + part.save() + + +class Migration(migrations.Migration): + + dependencies = [("bda", "0014_attribution_paid_field")] + + operations = [ + migrations.RunPython(set_attr_payment, set_participant_payment, atomic=True) + ] diff --git a/bda/migrations/0016_delete_participant_paid.py b/bda/migrations/0016_delete_participant_paid.py new file mode 100644 index 00000000..f59d1eb9 --- /dev/null +++ b/bda/migrations/0016_delete_participant_paid.py @@ -0,0 +1,13 @@ +# Generated by Django 2.2 on 2019-06-03 19:30 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [("bda", "0015_move_bda_payment")] + + operations = [ + migrations.RemoveField(model_name="participant", name="paid"), + migrations.RemoveField(model_name="participant", name="paymenttype"), + ] From a67446048e15fbc0803b4395fb12caa83d5aadb7 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 8 Jun 2019 15:04:55 +0200 Subject: [PATCH 04/12] =?UTF-8?q?R=C3=A9=C3=A9crit=20des=20vues=20pour=20l?= =?UTF-8?q?a=20nouvelle=20fonctionnalit=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On en profite pour rajouter des Mixins pour les perms buro/cof --- bda/urls.py | 6 +++++- bda/views.py | 24 ++++++++++++------------ gestioncof/decorators.py | 17 +++++++++++++++++ 3 files changed, 34 insertions(+), 13 deletions(-) diff --git a/bda/urls.py b/bda/urls.py index cefde4a2..5b452362 100644 --- a/bda/urls.py +++ b/bda/urls.py @@ -23,7 +23,11 @@ urlpatterns = [ views.spectacle, name="bda-spectacle", ), - url(r"^spectacles/unpaid/(?P\d+)$", views.unpaid, name="bda-unpaid"), + url( + r"^spectacles/unpaid/(?P\d+)$", + views.UnpaidParticipants.as_view(), + name="bda-unpaid", + ), url( r"^spectacles/autocomplete$", views.spectacle_autocomplete, diff --git a/bda/views.py b/bda/views.py index c0f4a079..40595cbf 100644 --- a/bda/views.py +++ b/bda/views.py @@ -41,7 +41,7 @@ from bda.models import ( SpectacleRevente, Tirage, ) -from gestioncof.decorators import buro_required, cof_required +from gestioncof.decorators import BuroRequiredMixin, buro_required, cof_required from utils.views.autocomplete import Select2QuerySetView @@ -378,7 +378,7 @@ def revente_manage(request, tirage_id): - Annulation d'une revente après que le tirage a eu lieu """ tirage = get_object_or_404(Tirage, id=tirage_id) - participant, created = Participant.objects.get_or_create( + participant, created = Participant.objects_paid.get_or_create( user=request.user, tirage=tirage ) @@ -695,12 +695,13 @@ def spectacle(request, tirage_id, spectacle_id): "username": participant.user.username, "email": participant.user.email, "given": int(attrib.given), - "paid": participant.paid, + "paid": True, "nb_places": 1, } if participant.id in participants: participants[participant.id]["nb_places"] += 1 participants[participant.id]["given"] += attrib.given + participants[participant.id]["paid"] &= attrib.paid else: participants[participant.id] = participant_info @@ -728,15 +729,14 @@ class SpectacleListView(ListView): return context -@buro_required -def unpaid(request, tirage_id): - tirage = get_object_or_404(Tirage, id=tirage_id) - unpaid = ( - tirage.participant_set.annotate(nb_attributions=Count("attribution")) - .filter(paid=False, nb_attributions__gt=0) - .select_related("user") - ) - return render(request, "bda-unpaid.html", {"unpaid": unpaid}) +class UnpaidParticipants(BuroRequiredMixin, ListView): + context_object_name = "unpaid" + template_name = "bda-unpaid.html" + + def get_queryset(self): + return Participant.objects_paid.filter( + tirage__id=self.kwargs["tirage_id"], paid=False + ).select_related("user") @buro_required diff --git a/gestioncof/decorators.py b/gestioncof/decorators.py index 37d93c7f..4f6eb0e4 100644 --- a/gestioncof/decorators.py +++ b/gestioncof/decorators.py @@ -1,6 +1,7 @@ from functools import wraps from django.contrib.auth.decorators import login_required, user_passes_test +from django.contrib.auth.mixins import PermissionRequiredMixin from django.core.exceptions import PermissionDenied from django.shortcuts import render @@ -53,3 +54,19 @@ def buro_required(view_func): return render(request, "buro-denied.html", status=403) return login_required(_wrapped_view) + + +class CofRequiredMixin(PermissionRequiredMixin): + def has_permission(self): + try: + return self.request.user.profile.is_cof + except AttributeError: + return False + + +class BuroRequiredMixin(PermissionRequiredMixin): + def has_permission(self): + try: + return self.request.user.profile.is_buro + except AttributeError: + return False From fa98bb34bd50826d967d607be2251665177e8d0b Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 8 Jun 2019 15:05:14 +0200 Subject: [PATCH 05/12] Adapte l'interface admin --- bda/admin.py | 58 ++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 11 deletions(-) diff --git a/bda/admin.py b/bda/admin.py index b32144f1..319b6fc5 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -4,7 +4,7 @@ from custommail.shortcuts import send_mass_custom_mail from dal.autocomplete import ModelSelect2 from django import forms from django.contrib import admin -from django.db.models import Count, Sum +from django.db.models import Count, Q, Sum from django.template.defaultfilters import pluralize from django.utils import timezone @@ -81,11 +81,13 @@ class WithListingAttributionInline(AttributionInline): exclude = ("given",) form = WithListingAttributionTabularAdminForm listing = True + verbose_name_plural = "Attributions sur listing" class WithoutListingAttributionInline(AttributionInline): form = WithoutListingAttributionTabularAdminForm listing = False + verbose_name_plural = "Attributions hors listing" class ParticipantAdminForm(forms.ModelForm): @@ -96,12 +98,32 @@ class ParticipantAdminForm(forms.ModelForm): ) +class ParticipantPaidFilter(admin.SimpleListFilter): + """ + Permet de filtrer les participants sur s'ils ont payé leurs places ou pas + """ + + title = "A payé" + parameter_name = "paid" + + def lookups(self, request, model_admin): + return ((True, "Oui"), (False, "Non")) + + def queryset(self, request, queryset): + return queryset.filter(paid=self.value()) + + class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin): inlines = [WithListingAttributionInline, WithoutListingAttributionInline] def get_queryset(self, request): - return Participant.objects.annotate( - nb_places=Count("attributions"), total=Sum("attributions__price") + qs = self.model.objects_paid.get_queryset() + return qs.annotate( + nb_places=Count("attributions"), + remain=Sum( + "attribution__spectacle__price", filter=Q(attribution__paid=False) + ), + total=Sum("attributions__price"), ) def nb_places(self, obj): @@ -110,6 +132,13 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin): nb_places.admin_order_field = "nb_places" nb_places.short_description = "Nombre de places" + def paid(self, obj): + return obj.paid + + paid.short_description = "A payé" + paid.boolean = True + paid.admin_order_field = "paid" + def total(self, obj): tot = obj.total if tot: @@ -118,14 +147,25 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin): return "0 €" total.admin_order_field = "total" - total.short_description = "Total à payer" - list_display = ("user", "nb_places", "total", "paid", "paymenttype", "tirage") - list_filter = ("paid", "tirage") + total.short_description = "Total des places" + + def remain(self, obj): + rem = obj.remain + if rem: + return "%.02f €" % rem + else: + return "0 €" + + remain.admin_order_field = "remain" + remain.short_description = "Reste à payer" + + list_display = ("user", "nb_places", "total", "paid", "remain", "tirage") + list_filter = (ParticipantPaidFilter, "tirage") search_fields = ("user__username", "user__first_name", "user__last_name") actions = ["send_attribs"] actions_on_bottom = True list_per_page = 400 - readonly_fields = ("total",) + readonly_fields = ("total", "paid") readonly_fields_update = ("user", "tirage") form = ParticipantAdminForm @@ -183,11 +223,7 @@ class AttributionAdminForm(forms.ModelForm): class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin): - def paid(self, obj): - return obj.participant.paid - paid.short_description = "A payé" - paid.boolean = True list_display = ("id", "spectacle", "participant", "given", "paid") search_fields = ( "spectacle__title", From b11e35616c297e37ca4ff8afa39fbe39449337a4 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Sat, 8 Jun 2019 15:33:47 +0200 Subject: [PATCH 06/12] Changelog --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 138789e8..66a06dc0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +- Le champ de paiement BdA se fait au niveau des attributions - On peut supprimer des comptes et des articles K-Fêt - Passage à Django2 - Dev : on peut désactiver la barre de debug avec une variable shell From ba5aa6da5f9051cc4bdb6ec0c537588e00d56e12 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 17 Jun 2019 21:12:03 +0200 Subject: [PATCH 07/12] Remove useless local variable --- bda/admin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bda/admin.py b/bda/admin.py index 319b6fc5..18c712f8 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -117,8 +117,7 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin): inlines = [WithListingAttributionInline, WithoutListingAttributionInline] def get_queryset(self, request): - qs = self.model.objects_paid.get_queryset() - return qs.annotate( + return self.model.objects_paid.get_queryset().annotate( nb_places=Count("attributions"), remain=Sum( "attribution__spectacle__price", filter=Q(attribution__paid=False) From 20bb9fe54bf87467c00ffe9d6611b22b496a0aa1 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 17 Jun 2019 21:12:50 +0200 Subject: [PATCH 08/12] Remove debug log --- bda/migrations/0015_move_bda_payment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bda/migrations/0015_move_bda_payment.py b/bda/migrations/0015_move_bda_payment.py index 261cf483..93f121a1 100644 --- a/bda/migrations/0015_move_bda_payment.py +++ b/bda/migrations/0015_move_bda_payment.py @@ -23,8 +23,7 @@ def set_participant_payment(apps, schema_editor): attr_set.values_list("paymenttype", flat=True).distinct().get() ) # Sinon, whatever - except (ObjectDoesNotExist, MultipleObjectsReturned) as e: - print(e) + except (ObjectDoesNotExist, MultipleObjectsReturned): pass part.save() From edd92beadf3d9e588300445003b414a6a33d3c20 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 17 Jun 2019 21:20:57 +0200 Subject: [PATCH 09/12] Add logging call --- gestioncof/decorators.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/gestioncof/decorators.py b/gestioncof/decorators.py index 4f6eb0e4..e811465a 100644 --- a/gestioncof/decorators.py +++ b/gestioncof/decorators.py @@ -1,10 +1,12 @@ +import logging from functools import wraps -from django.contrib.auth.decorators import login_required, user_passes_test +from django.contrib.auth.decorators import login_required from django.contrib.auth.mixins import PermissionRequiredMixin -from django.core.exceptions import PermissionDenied from django.shortcuts import render +logger = logging.getLogger(__name__) + def cof_required(view_func): """Décorateur qui vérifie que l'utilisateur est connecté et membre du COF. @@ -69,4 +71,7 @@ class BuroRequiredMixin(PermissionRequiredMixin): try: return self.request.user.profile.is_buro except AttributeError: + logger.error( + "L'utilisateur %s n'a pas de profil !", self.request.user.username + ) return False From 4f15b820a5d19174fd03a248a8429a5206427ff0 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 17 Jun 2019 21:36:09 +0200 Subject: [PATCH 10/12] Use manager from queryset --- bda/admin.py | 2 +- bda/models.py | 12 +++++++----- bda/views.py | 10 ++++++---- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/bda/admin.py b/bda/admin.py index 18c712f8..7f626c7a 100644 --- a/bda/admin.py +++ b/bda/admin.py @@ -117,7 +117,7 @@ class ParticipantAdmin(ReadOnlyMixin, admin.ModelAdmin): inlines = [WithListingAttributionInline, WithoutListingAttributionInline] def get_queryset(self, request): - return self.model.objects_paid.get_queryset().annotate( + return self.model.objects.annotate_paid().annotate( nb_places=Count("attributions"), remain=Sum( "attribution__spectacle__price", filter=Q(attribution__paid=False) diff --git a/bda/models.py b/bda/models.py index de87731e..cee2c3e1 100644 --- a/bda/models.py +++ b/bda/models.py @@ -170,17 +170,20 @@ class Attribution(models.Model): ) -class ParticipantPaidManager(models.Manager): +class ParticipantPaidQueryset(models.QuerySet): """ Un manager qui annote le queryset avec un champ `paid`, indiquant si un participant a payé toutes ses attributions. """ - def get_queryset(self): + # OuterRef permet de se référer à un champ d'un modèle non encore fixé + # Voir: + # https://docs.djangoproject.com/en/2.2/ref/models/expressions/#django.db.models.OuterRef + def annotate_paid(self): unpaid = Attribution.objects.filter( participant=models.OuterRef("pk"), paid=False ) - return super().get_queryset().annotate(paid=~Exists(unpaid)) + return self.annotate(paid=~Exists(unpaid)) class Participant(models.Model): @@ -196,8 +199,7 @@ class Participant(models.Model): Spectacle, related_name="subscribed", blank=True ) - objects = models.Manager() - objects_paid = ParticipantPaidManager() + objects = ParticipantPaidQueryset.as_manager() def __str__(self): return "%s - %s" % (self.user, self.tirage.title) diff --git a/bda/views.py b/bda/views.py index 40595cbf..6a0e7ec7 100644 --- a/bda/views.py +++ b/bda/views.py @@ -378,7 +378,7 @@ def revente_manage(request, tirage_id): - Annulation d'une revente après que le tirage a eu lieu """ tirage = get_object_or_404(Tirage, id=tirage_id) - participant, created = Participant.objects_paid.get_or_create( + participant, created = Participant.annotate_paid().get_or_create( user=request.user, tirage=tirage ) @@ -734,9 +734,11 @@ class UnpaidParticipants(BuroRequiredMixin, ListView): template_name = "bda-unpaid.html" def get_queryset(self): - return Participant.objects_paid.filter( - tirage__id=self.kwargs["tirage_id"], paid=False - ).select_related("user") + return ( + Participant.objects.annotate_paid() + .filter(tirage__id=self.kwargs["tirage_id"], paid=False) + .select_related("user") + ) @buro_required From 46e7305953231faea2c79c88a005e31a83c18d54 Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 17 Jun 2019 21:38:49 +0200 Subject: [PATCH 11/12] =?UTF-8?q?Meilleur=20d=C3=A9corateur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gestioncof/decorators.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gestioncof/decorators.py b/gestioncof/decorators.py index e811465a..28a67331 100644 --- a/gestioncof/decorators.py +++ b/gestioncof/decorators.py @@ -60,6 +60,8 @@ def buro_required(view_func): class CofRequiredMixin(PermissionRequiredMixin): def has_permission(self): + if not self.request.user.is_authenticated: + return False try: return self.request.user.profile.is_cof except AttributeError: @@ -68,6 +70,8 @@ class CofRequiredMixin(PermissionRequiredMixin): class BuroRequiredMixin(PermissionRequiredMixin): def has_permission(self): + if not self.request.user.is_authenticated: + return False try: return self.request.user.profile.is_buro except AttributeError: From d7d0daea0dc409fc6817ca2b391d902a34427a8c Mon Sep 17 00:00:00 2001 From: Ludovic Stephan Date: Mon, 17 Jun 2019 21:40:32 +0200 Subject: [PATCH 12/12] Commentaire dans la fonction --- bda/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bda/models.py b/bda/models.py index cee2c3e1..1a072eb7 100644 --- a/bda/models.py +++ b/bda/models.py @@ -176,10 +176,10 @@ class ParticipantPaidQueryset(models.QuerySet): indiquant si un participant a payé toutes ses attributions. """ - # OuterRef permet de se référer à un champ d'un modèle non encore fixé - # Voir: - # https://docs.djangoproject.com/en/2.2/ref/models/expressions/#django.db.models.OuterRef def annotate_paid(self): + # OuterRef permet de se référer à un champ d'un modèle non encore fixé + # Voir: + # https://docs.djangoproject.com/en/2.2/ref/models/expressions/#django.db.models.OuterRef unpaid = Attribution.objects.filter( participant=models.OuterRef("pk"), paid=False )