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)