from datetime import timedelta from dal.autocomplete import ModelSelect2 from django import forms from django.contrib import admin from django.core.mail import send_mass_mail from django.db.models import Count, Q, Sum from django.template import loader from django.template.defaultfilters import pluralize from django.utils import timezone from bda.models import ( Attribution, CategorieSpectacle, ChoixSpectacle, Participant, Quote, Salle, Spectacle, SpectacleRevente, Tirage, ) class ReadOnlyMixin(object): readonly_fields_update = () def get_readonly_fields(self, request, obj=None): readonly_fields = super().get_readonly_fields(request, obj) if obj is None: return readonly_fields else: return readonly_fields + self.readonly_fields_update class AttributionTabularAdminForm(forms.ModelForm): listing = None def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) spectacles = Spectacle.objects.select_related("location") if self.listing is not None: spectacles = spectacles.filter(listing=self.listing) self.fields["spectacle"].queryset = spectacles class WithoutListingAttributionTabularAdminForm(AttributionTabularAdminForm): listing = False class WithListingAttributionTabularAdminForm(AttributionTabularAdminForm): listing = True class AttributionInline(admin.TabularInline): model = Attribution extra = 0 listing = None def get_queryset(self, request): qs = super().get_queryset(request) if self.listing is not None: qs = qs.filter(spectacle__listing=self.listing) return qs 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): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) queryset = Spectacle.objects.select_related("location") if self.instance.pk is not None: queryset = queryset.filter(tirage=self.instance.tirage) self.fields["choicesrevente"].queryset = queryset 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 self.model.objects.annotate_paid().annotate( nb_places=Count("attributions"), remain=Sum( "attribution__spectacle__price", filter=Q(attribution__paid=False) ), total=Sum("attributions__price"), ) def nb_places(self, obj): return obj.nb_places 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: return "%.02f €" % tot else: return "0 €" total.admin_order_field = "total" 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", "paid") readonly_fields_update = ("user", "tirage") form = ParticipantAdminForm def send_attribs(self, request, queryset): emails = [] for member in queryset.all(): subject = "Résultats du tirage au sort" attribs = member.attributions.all() context = {"member": member.user} template_name = "" if len(attribs) == 0: template_name = "bda/mails/attributions-decus.txt" else: template_name = "bda/mails/attributions.txt" context["places"] = attribs message = loader.render_to_string(template_name, context) emails.append((subject, message, "bda@ens.fr", [member.user.email])) send_mass_mail(emails) count = len(queryset.all()) if count == 1: message_bit = "1 membre a" plural = "" else: message_bit = "%d membres ont" % count plural = "s" self.message_user( request, "%s été informé%s avec succès." % (message_bit, plural) ) send_attribs.short_description = "Envoyer les résultats par mail" class AttributionAdminForm(forms.ModelForm): def clean(self): cleaned_data = super().clean() participant = cleaned_data.get("participant") spectacle = cleaned_data.get("spectacle") if participant and spectacle: if participant.tirage != spectacle.tirage: raise forms.ValidationError( "Erreur : le participant et le spectacle n'appartiennent" "pas au même tirage" ) return cleaned_data class Meta: widgets = { "participant": ModelSelect2(url="bda-participant-autocomplete"), "spectacle": ModelSelect2(url="bda-spectacle-autocomplete"), } class AttributionAdmin(ReadOnlyMixin, admin.ModelAdmin): list_display = ("id", "spectacle", "participant", "given", "paid") search_fields = ( "spectacle__title", "participant__user__username", "participant__user__first_name", "participant__user__last_name", ) form = AttributionAdminForm readonly_fields_update = ("spectacle", "participant") class ChoixSpectacleAdmin(admin.ModelAdmin): autocomplete_fields = ["participant", "spectacle"] def tirage(self, obj): return obj.participant.tirage list_display = ("participant", "tirage", "spectacle", "priority", "double_choice") list_filter = ("double_choice", "participant__tirage") search_fields = ( "participant__user__username", "participant__user__first_name", "participant__user__last_name", "spectacle__title", ) class QuoteInline(admin.TabularInline): model = Quote class SpectacleAdmin(admin.ModelAdmin): inlines = [QuoteInline] model = Spectacle list_display = ("title", "date", "tirage", "location", "slots", "price", "listing") list_filter = ("location", "tirage") search_fields = ("title", "location__name") readonly_fields = ("rappel_sent",) class TirageAdmin(admin.ModelAdmin): model = Tirage list_display = ("title", "ouverture", "fermeture", "active", "enable_do_tirage") readonly_fields = ("tokens",) list_filter = ("active",) search_fields = ("title",) class SalleAdmin(admin.ModelAdmin): model = Salle search_fields = ("name", "address") class SpectacleReventeAdminForm(forms.ModelForm): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) qset = Participant.objects.select_related("user", "tirage") if self.instance.pk is not None: qset = qset.filter(tirage=self.instance.seller.tirage) self.fields["confirmed_entry"].queryset = qset self.fields["seller"].queryset = qset self.fields["soldTo"].queryset = qset class SpectacleReventeAdmin(admin.ModelAdmin): """ Administration des reventes de spectacles """ model = SpectacleRevente def spectacle(self, obj): """ Raccourci vers le spectacle associé à la revente. """ return obj.attribution.spectacle list_display = ("spectacle", "seller", "date", "soldTo") raw_id_fields = ("attribution",) readonly_fields = ("date_tirage",) search_fields = [ "attribution__spectacle__title", "seller__user__username", "seller__user__first_name", "seller__user__last_name", ] actions = ["transfer", "reinit"] actions_on_bottom = True form = SpectacleReventeAdminForm def transfer(self, request, queryset): """ Effectue le transfert des reventes pour lesquels on connaît l'acheteur. """ reventes = queryset.exclude(soldTo__isnull=True).all() count = reventes.count() for revente in reventes: attrib = revente.attribution attrib.participant = revente.soldTo attrib.save() self.message_user( request, "%d attribution%s %s été transférée%s avec succès." % (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)), ) transfer.short_description = "Transférer les reventes sélectionnées" def reinit(self, request, queryset): """ Réinitialise les reventes. """ count = queryset.count() for revente in queryset.filter( attribution__spectacle__date__gte=timezone.now() ): revente.reset(new_date=timezone.now() - timedelta(hours=1)) self.message_user( request, "%d attribution%s %s été réinitialisée%s avec succès." % (count, pluralize(count), pluralize(count, "a,ont"), pluralize(count)), ) reinit.short_description = "Réinitialiser les reventes sélectionnées" admin.site.register(CategorieSpectacle) admin.site.register(Spectacle, SpectacleAdmin) admin.site.register(Salle, SalleAdmin) admin.site.register(Participant, ParticipantAdmin) admin.site.register(Attribution, AttributionAdmin) admin.site.register(ChoixSpectacle, ChoixSpectacleAdmin) admin.site.register(Tirage, TirageAdmin) admin.site.register(SpectacleRevente, SpectacleReventeAdmin)