from django.contrib import admin from django.db import models from django.urls import reverse from django.forms import BaseInlineFormSet from django.utils.html import format_html from django.conf.urls import url from django.shortcuts import get_object_or_404 from django.template.response import TemplateResponse from functools import update_wrapper from services.models import ServiceType from services.admin import ServiceAdmin from adhesions.admin import AdhesionAdmin from djadhere.utils import ActiveFilter from .models import RecurringPayment, PaymentUpdate from .utils import notify_payment_update ### Inlines class PaymentMethodFilter(admin.SimpleListFilter): title = 'méthode de paiement' parameter_name = 'method' def lookups(self, request, model_admin): return PaymentUpdate.PAYMENT_CHOICES def queryset(self, request, queryset): if self.value() is not None: return queryset.filter(payment_method=self.value()) else: return queryset.exclude(payment_method=PaymentUpdate.STOP) class PendingPaymentFilter(admin.SimpleListFilter): title = 'statut' parameter_name = 'status' def lookups(self, request, model_admin): return ( ('up-to-date', 'À jour'), ('pending', 'En attente'), ) def queryset(self, request, queryset): if self.value() == 'up-to-date': queryset = queryset.filter(last=True) if self.value() == 'pending': queryset = queryset.filter(last=False) return queryset payments = RecurringPayment.objects.annotate( validated=models.Subquery( PaymentUpdate.objects.filter(payment=models.OuterRef('pk')) .filter(validated=True) .order_by('-start') .values('pk')[:1] ), pending=models.Subquery( PaymentUpdate.objects.filter(payment=models.OuterRef('pk')) .filter(validated=False) .order_by('-start') .values('pk')[:1] ), ) up_to_date = payments.filter(pending__isnull=True) pending = payments.exclude(pending__isnull=True) if self.value() == 'up-to-date': p = up_to_date elif self.value() == 'pending': p = pending else: p = payments queryset = queryset.filter(pk__in=p.values_list('validated', flat=True)) queryset = queryset.annotate( last=models.Case( models.When(pk__in=up_to_date.values_list('validated', flat=True), then=True), default=False, output_field=models.BooleanField(), ), ) return queryset class PaymentTypeFilter(admin.SimpleListFilter): title = 'type' parameter_name = 'type' def lookups(self, request, model_admin): return ( ('adhesion', 'Adhésion'), ('service', 'Service'), ) def queryset(self, request, queryset): if self.value() == 'adhesion': return queryset.filter(payment__adhesion__isnull=False) if self.value() == 'service': return queryset.filter(payment__service__isnull=False) ### Inlines class PendingPaymentUpdateFormSet(BaseInlineFormSet): def save_new(self, form, commit=True): obj = super().save_new(form, commit) if not obj.validated: notify_payment_update(self.request, obj) return obj def save_existing(self, form, instance, commit=True): old = PaymentUpdate.objects.get(pk=instance.pk) if not instance.validated: notify_payment_update(self.request, instance, old) return super().save_existing(form, instance, commit) class PendingPaymentUpdateInline(admin.TabularInline): model = PaymentUpdate formset = PendingPaymentUpdateFormSet extra = 1 max_num = 1 verbose_name_plural = 'Demande de saisie bancaire' def get_formset(self, request, obj=None, **kwargs): formset = super().get_formset(request, obj, **kwargs) formset.request = request return formset def get_queryset(self, request): return super().get_queryset(request).filter(validated=False) class ValidatedPaymentUpdateInline(admin.TabularInline): model = PaymentUpdate verbose_name_plural = 'Historique' max_num = 0 fields = ('amount', 'period', 'payment_method', 'start',) readonly_fields = ('amount', 'period', 'payment_method', 'start',) def has_delete_permission(self, request, obj=None): return False def get_queryset(self, request): return super().get_queryset(request).filter(validated=True) ### Helpers def prefix_search_field(prefix, field): if field[0] == '=': return '=' + prefix + '__' + field[1:] else: return prefix + '__' + field ### ModelAdmin class RecurringPaymentAdmin(admin.ModelAdmin): inlines = (PendingPaymentUpdateInline, ValidatedPaymentUpdateInline,) fields = ('payment_type', 'payment_object_link', 'debtor_link',) readonly_fields = ('payment_type', 'payment_object_link', 'debtor_link',) def payment_object_link(self, obj): obj = obj.payment_object() return format_html(u'{}', obj.get_absolute_url(), obj) payment_object_link.short_description = 'Objet' def debtor_link(self, obj): url = reverse(viewname='admin:%s_%s_debtor' % (obj._meta.app_label, obj._meta.model_name), args=[obj.pk]) return format_html(u'{}', url, obj.debtor) debtor_link.short_description = 'Débiteur' def get_actions(self, request): actions = super().get_actions(request) if 'delete_selected' in actions: del actions['delete_selected'] return actions def has_add_permission(self, request, obj=None): return False def has_change_permission(self, request, obj=None): return obj def has_delete_permission(self, request, obj=None): return False def get_urls(self): info = self.model._meta.app_label, self.model._meta.model_name urls = [ url(r'^(.*)/debtor/$', self.admin_site.admin_view(self.debtor_view), name='%s_%s_debtor' % info), ] return urls + super().get_urls() def debtor_view(self, request, payment_pk): payment = get_object_or_404(RecurringPayment, pk=payment_pk) adhesion = payment.debtor if adhesion.is_physical(): profile = adhesion.user.profile else: profile = adhesion.corporation context = dict( self.admin_site.each_context(request), opts=self.model._meta, payment=payment, adhesion=adhesion, adherent=adhesion.adherent, profile=profile, ) return TemplateResponse(request, 'banking/debtor.html', context) class PaymentUpdateAdmin(admin.ModelAdmin): list_display = ('payment_type', 'payment_object_link', 'payment_link', 'last',) list_select_related = ('payment', 'payment__adhesion', 'payment__service', 'payment__service__service_type',) list_filter = (PaymentTypeFilter, PaymentMethodFilter, PendingPaymentFilter,) list_display_links = None search_fields = \ tuple([prefix_search_field('payment__adhesion', f) for f in AdhesionAdmin.search_fields]) \ + tuple([prefix_search_field('payment__service', f) for f in ServiceAdmin.search_fields]) def last(self, obj): return obj.last last.boolean = True last.short_description = 'À jour' def payment_type(self, update): return update.payment.payment_type() payment_type.short_description = 'Type' def payment_object_link(self, update): obj = update.payment.payment_object() return format_html(u'{}', obj.get_absolute_url(), obj) payment_object_link.short_description = 'Objet' def payment_link(self, update): payment = update.payment return format_html(u'{}', payment.get_absolute_url(), update) payment_link.short_description = 'Paiement' def get_queryset(self, request): qs = super().get_queryset(request) payments = RecurringPayment.objects.annotate( validated=models.Subquery( PaymentUpdate.objects.filter(payment=models.OuterRef('pk')) .filter(validated=True) .order_by('-start') .values('pk')[:1] ), pending=models.Subquery( PaymentUpdate.objects.filter(payment=models.OuterRef('pk')) .filter(validated=False) .order_by('-start') .values('pk')[:1] ), ) qs = qs.filter(pk__in=payments.values_list('validated', flat=True)) qs = qs.annotate( last=models.Case( models.When(pk__in=payments.filter(pending__isnull=True).values_list('validated', flat=True), then=True), default=False, output_field=models.BooleanField(), ), ) return qs def get_actions(self, request): actions = super().get_actions(request) if 'delete_selected' in actions: del actions['delete_selected'] return actions def has_add_permission(self, request, obj=None): return False def has_change_permission(self, request, obj=None): return not obj def has_delete_permission(self, request, obj=None): return False admin.site.register(RecurringPayment, RecurringPaymentAdmin) admin.site.register(PaymentUpdate, PaymentUpdateAdmin)