admin.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from django.contrib import admin
  4. from django.contrib import messages
  5. from django.http import HttpResponseRedirect
  6. from django.conf.urls import url
  7. from django.contrib.admin.utils import flatten_fieldsets
  8. from django import forms
  9. from django.template import RequestContext
  10. from django.shortcuts import render
  11. from coin.filtering_queryset import LimitedAdminInlineMixin
  12. from coin.billing.models import Invoice, InvoiceDetail, Payment, PaymentAllocation
  13. from coin.billing.utils import get_invoice_from_id_or_number
  14. from django.core.urlresolvers import reverse
  15. import autocomplete_light
  16. from functools import update_wrapper
  17. from .forms import WizardImportPaymentCSV
  18. from .import_payments_from_csv import process
  19. class InvoiceDetailInlineForm(forms.ModelForm):
  20. class Meta:
  21. model = InvoiceDetail
  22. fields = (
  23. 'label', 'amount', 'quantity', 'tax',
  24. 'offersubscription', 'period_from', 'period_to'
  25. )
  26. widgets = {'quantity': forms.NumberInput(attrs={'step': 1})}
  27. class InvoiceDetailInline(LimitedAdminInlineMixin, admin.StackedInline):
  28. model = InvoiceDetail
  29. extra = 0
  30. fields = (('label', 'amount', 'quantity', 'tax'),
  31. ('offersubscription', 'period_from', 'period_to'))
  32. form = InvoiceDetailInlineForm
  33. def get_filters(self, obj):
  34. """
  35. Le champ "Abonnement" est filtré afin de n'afficher que les abonnements
  36. du membre choisi dans la facture. Si pas de membre alors renvoi
  37. une liste vide
  38. """
  39. if obj and obj.member:
  40. return (('offersubscription', {'member': obj.member}),)
  41. else:
  42. return (('offersubscription', None),)
  43. def get_readonly_fields(self, request, obj=None):
  44. if not obj or not obj.member:
  45. return self.readonly_fields + ('offersubscription',)
  46. return self.readonly_fields
  47. class InvoiceDetailInlineReadOnly(admin.StackedInline):
  48. """
  49. Lorsque la facture est validée, il n'est plus possible de la modifier
  50. Ce inline est donc identique à InvoiceDetailInline, mais tous
  51. les champs sont en lecture seule
  52. """
  53. model = InvoiceDetail
  54. extra = 0
  55. fields = InvoiceDetailInline.fields
  56. can_delete = False
  57. def has_add_permission(self, request):
  58. return False
  59. def get_readonly_fields(self, request, obj=None):
  60. if self.declared_fieldsets:
  61. result = flatten_fieldsets(self.declared_fieldsets)
  62. else:
  63. result = list(set(
  64. [field.name for field in self.opts.local_fields] +
  65. [field.name for field in self.opts.local_many_to_many]
  66. ))
  67. result.remove('id')
  68. return result
  69. class PaymentAllocatedReadOnly(admin.TabularInline):
  70. model = PaymentAllocation
  71. extra = 0
  72. fields = ("payment", "amount")
  73. readonly_fields = ("payment", "amount")
  74. verbose_name = None
  75. verbose_name_plural = "Paiement alloués"
  76. def has_add_permission(self, request, obj=None):
  77. return False
  78. def has_delete_permission(self, request, obj=None):
  79. return False
  80. class PaymentInlineAdd(admin.StackedInline):
  81. model = Payment
  82. extra = 0
  83. fields = (('date', 'payment_mean', 'amount'),)
  84. can_delete = False
  85. verbose_name_plural = "Ajouter des paiements"
  86. def has_change_permission(self, request):
  87. return False
  88. class InvoiceAdmin(admin.ModelAdmin):
  89. list_display = ('number', 'date', 'status', 'amount', 'member',
  90. 'validated')
  91. list_display_links = ('number', 'date')
  92. fields = (('number', 'date', 'status'),
  93. ('date_due'),
  94. ('member'),
  95. ('amount', 'amount_paid'),
  96. ('validated', 'pdf'))
  97. readonly_fields = ('amount', 'amount_paid', 'validated', 'pdf', 'number')
  98. form = autocomplete_light.modelform_factory(Invoice, fields='__all__')
  99. def get_readonly_fields(self, request, obj=None):
  100. """
  101. Si la facture est validée, passe tous les champs en readonly
  102. """
  103. if obj and obj.validated:
  104. if self.declared_fieldsets:
  105. return flatten_fieldsets(self.declared_fieldsets)
  106. else:
  107. return list(set(
  108. [field.name for field in self.opts.local_fields] +
  109. [field.name for field in self.opts.local_many_to_many]
  110. ))
  111. return self.readonly_fields
  112. def get_inline_instances(self, request, obj=None):
  113. """
  114. Renvoi les inlines selon le context :
  115. * Si création, alors ne renvoi aucun inline
  116. * Si modification, renvoi InvoiceDetail et PaymentInline
  117. * Si facture validée, renvoi InvoiceDetail en ReadOnly et PaymentInline
  118. """
  119. inlines = []
  120. inline_instances = []
  121. if obj is not None:
  122. if obj.validated:
  123. inlines = [InvoiceDetailInlineReadOnly]
  124. else:
  125. inlines = [InvoiceDetailInline]
  126. if obj.validated:
  127. inlines += [PaymentAllocatedReadOnly]
  128. if obj.status == "open":
  129. inlines += [PaymentInlineAdd]
  130. for inline_class in inlines:
  131. inline = inline_class(self.model, self.admin_site)
  132. if request:
  133. if not (inline.has_add_permission(request) or
  134. inline.has_change_permission(request) or
  135. inline.has_delete_permission(request)):
  136. continue
  137. if not inline.has_add_permission(request):
  138. inline.max_num = 0
  139. inline_instances.append(inline)
  140. return inline_instances
  141. def get_urls(self):
  142. """
  143. Custom admin urls
  144. """
  145. urls = super(InvoiceAdmin, self).get_urls()
  146. my_urls = [
  147. url(r'^validate/(?P<id>.+)$',
  148. self.admin_site.admin_view(self.validate_view),
  149. name='invoice_validate'),
  150. ]
  151. return my_urls + urls
  152. def validate_view(self, request, id):
  153. """
  154. Vue appelée lorsque l'admin souhaite valider une facture et
  155. générer son pdf
  156. """
  157. # TODO : Add better perm here
  158. if request.user.is_superuser:
  159. invoice = get_invoice_from_id_or_number(id)
  160. if invoice.amount() == 0:
  161. messages.error(request, 'Une facture validée ne peut pas avoir'
  162. ' un total de 0€.')
  163. else:
  164. invoice.validate()
  165. messages.success(request, 'La facture a été validée.')
  166. else:
  167. messages.error(
  168. request, 'Vous n\'avez pas l\'autorisation de valider '
  169. 'une facture.')
  170. return HttpResponseRedirect(reverse('admin:billing_invoice_change',
  171. args=(id,)))
  172. class PaymentAllocationInlineReadOnly(admin.TabularInline):
  173. model = PaymentAllocation
  174. extra = 0
  175. fields = ("invoice", "amount")
  176. readonly_fields = ("invoice", "amount")
  177. verbose_name = None
  178. verbose_name_plural = "Alloué à"
  179. def has_add_permission(self, request, obj=None):
  180. return False
  181. def has_delete_permission(self, request, obj=None):
  182. return False
  183. class PaymentAdmin(admin.ModelAdmin):
  184. list_display = ('__unicode__', 'member', 'payment_mean', 'amount', 'date',
  185. 'amount_already_allocated', 'label')
  186. list_display_links = ()
  187. fields = (('member'),
  188. ('amount', 'payment_mean', 'date', 'label'),
  189. ('amount_already_allocated'))
  190. readonly_fields = ('amount_already_allocated', 'label')
  191. form = autocomplete_light.modelform_factory(Payment, fields='__all__')
  192. def get_readonly_fields(self, request, obj=None):
  193. # If payment already started to be allocated or already have a member
  194. if obj and (obj.amount_already_allocated() != 0 or obj.member != None):
  195. # All fields are readonly
  196. return flatten_fieldsets(self.declared_fieldsets)
  197. else:
  198. return self.readonly_fields
  199. def get_inline_instances(self, request, obj=None):
  200. return [PaymentAllocationInlineReadOnly(self.model, self.admin_site)]
  201. def get_urls(self):
  202. def wrap(view):
  203. def wrapper(*args, **kwargs):
  204. return self.admin_site.admin_view(view)(*args, **kwargs)
  205. wrapper.model_admin = self
  206. return update_wrapper(wrapper, view)
  207. urls = super(PaymentAdmin, self).get_urls()
  208. info = self.model._meta.app_label, self.model._meta.model_name
  209. my_urls = [
  210. url(r'wizard_import_payment_csv/$', wrap(self.wizard_import_payment_csv), name='wizard_import_payment_csv'),
  211. ]
  212. return my_urls + urls
  213. def wizard_import_payment_csv(self, request):
  214. template = "admin/billing/payment/wizard_import_payment_csv.html"
  215. if request.method == 'POST':
  216. form = WizardImportPaymentCSV(request.POST, request.FILES)
  217. if form.is_valid():
  218. # Analyze
  219. new_payments = process(request.FILES["csv_file"])
  220. # If the user didn't ask for commit yet
  221. # display the result of the analyze (i.e. the matching)
  222. if "commit" not in request.POST:
  223. return render(request, template, {
  224. 'adminform': form,
  225. 'opts': self.model._meta,
  226. 'new_payments': new_payments
  227. })
  228. else:
  229. import pdb; pdb.set_trace()
  230. # To be implemented
  231. else:
  232. form = WizardImportPaymentCSV()
  233. return render(request, template, {
  234. 'adminform': form,
  235. 'opts': self.model._meta
  236. })
  237. class MembershipFeeAdmin(admin.ModelAdmin):
  238. list_display = ('member', 'end_date', '_amount')
  239. form = autocomplete_light.modelform_factory(MembershipFee, fields='__all__')
  240. class DonationAdmin(admin.ModelAdmin):
  241. list_display = ('member', 'date', '_amount')
  242. form = autocomplete_light.modelform_factory(MembershipFee, fields='__all__')
  243. class MembershipFeeInline(admin.TabularInline):
  244. model = MembershipFee
  245. extra = 0
  246. fields = ('start_date', 'end_date', '_amount')
  247. MemberAdmin.list_filter += ('status', MembershipFeeFilter)
  248. MemberAdmin.inlines += [MembershipFeeInline]
  249. admin.site.register(Invoice, InvoiceAdmin)
  250. admin.site.register(Payment, PaymentAdmin)