admin.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. # -*- coding: utf-8 -*-
  2. from __future__ import unicode_literals
  3. from datetime import date
  4. from django.shortcuts import get_object_or_404
  5. from django.contrib import admin
  6. from django.contrib import messages
  7. from django.contrib.admin import SimpleListFilter
  8. from django.contrib.auth.admin import UserAdmin
  9. from django.contrib.contenttypes.models import ContentType
  10. from django.http import HttpResponseRedirect
  11. from django.conf.urls import url
  12. from django.conf import settings
  13. from django.core.urlresolvers import reverse
  14. from django.utils.safestring import mark_safe
  15. from coin.members.models import (
  16. Member, CryptoKey, LdapUser, MembershipFee, Offer, OfferSubscription, RowLevelPermission)
  17. from coin.members.forms import AdminMemberChangeForm, MemberCreationForm
  18. from coin.utils import delete_selected
  19. class CryptoKeyInline(admin.StackedInline):
  20. model = CryptoKey
  21. extra = 0
  22. class MembershipFeeInline(admin.TabularInline):
  23. model = MembershipFee
  24. extra = 0
  25. fields = ('start_date', 'end_date', 'amount', 'payment_method',
  26. 'reference', 'payment_date')
  27. class OfferSubscriptionInline(admin.TabularInline):
  28. model = OfferSubscription
  29. extra = 0
  30. writable_fields = ('subscription_date', 'resign_date', 'commitment', 'offer')
  31. all_fields = ('get_subscription_reference',) + writable_fields
  32. def get_fields(self, request, obj=None):
  33. if obj:
  34. return self.all_fields
  35. else:
  36. return self.writable_fields
  37. def get_readonly_fields(self, request, obj=None):
  38. # création ou superuser : lecture écriture
  39. if not obj or request.user.is_superuser:
  40. return ('get_subscription_reference',)
  41. # modification : lecture seule seulement
  42. else:
  43. return self.all_fields
  44. show_change_link = True
  45. def formfield_for_foreignkey(self, db_field, request, **kwargs):
  46. if request.user.is_superuser:
  47. return super(OfferSubscriptionInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
  48. else:
  49. if db_field.name == "offer":
  50. kwargs["queryset"] = Offer.objects.manageable_by(request.user)
  51. return super(OfferSubscriptionInline, self).formfield_for_foreignkey(db_field, request, **kwargs)
  52. def has_add_permission(self, request):
  53. # - Quand on *crée* un membre on autorise à ajouter un abonnement
  54. # - Quand on *édite* un membre, on interdit l'ajout d'abonnements (sauf
  55. # par le bureau) car cela permettrait de gagner à loisir accès à
  56. # toute fiche adhérent en lui ajoutant un abonnement à une offre dont
  57. # on a la gestion).
  58. return (
  59. request.resolver_match.view_name == 'admin:members_member_add'
  60. or
  61. request.user.is_superuser
  62. )
  63. # sinon on pourrait supprimer les abo qu'on ne peut pas gérer
  64. # pourrait peut-être être plus fin, obj réfère ici au member de la page
  65. def has_delete_permission(self, request, obj=None):
  66. return request.user.is_superuser
  67. class DataRetentionFilter(SimpleListFilter):
  68. # Human-readable title which will be displayed in the
  69. # right admin sidebar just above the filter options.
  70. title = 'péremption des données'
  71. # Parameter for the filter that will be used in the URL query.
  72. parameter_name = 'data_cleanup'
  73. def lookups(self, request, model_admin):
  74. return (
  75. ('pending_deletion', 'Pouvant légalement être supprimé'),
  76. )
  77. def queryset(self, request, queryset):
  78. if self.value() == 'pending_deletion':
  79. return queryset.could_be_deleted()
  80. class MembershipFeeFilter(SimpleListFilter):
  81. # Human-readable title which will be displayed in the
  82. # right admin sidebar just above the filter options.
  83. title = 'Cotisations'
  84. # Parameter for the filter that will be used in the URL query.
  85. parameter_name = 'fee'
  86. def lookups(self, request, model_admin):
  87. return (
  88. ('paidup', 'À jour de cotisation'),
  89. ('late', 'En retard'),
  90. )
  91. def queryset(self, request, queryset):
  92. if self.value() == 'paidup':
  93. return queryset.paidup_fee()
  94. if self.value() == 'late':
  95. return queryset.no_fee_or_late()
  96. class MemberAdmin(UserAdmin):
  97. SERVICE_NO_FEE_MSG = (
  98. "Cet·te adhérent·e bénéficie d'un service"
  99. " mais n'est pas à jour de cotisation.")
  100. RECENT_SERVICE_MSG = (
  101. "Cet·te ancien·ne adhérent·e a un service résilié depuis moins d'un an"
  102. ". Légalement, il faut conserver sa fiche un an après la résiliation "
  103. "du service.")
  104. list_display = ('id', 'status', 'username', 'first_name',
  105. 'name_or_organization_name',
  106. 'nickname', 'email',
  107. 'enhanced_end_date_of_membership')
  108. list_display_links = ('id', 'username', 'first_name', 'name_or_organization_name')
  109. list_filter = ('status', MembershipFeeFilter, DataRetentionFilter)
  110. search_fields = ['username', 'first_name', 'last_name', 'email', 'nickname']
  111. ordering = ('status', 'username')
  112. actions = [delete_selected, 'set_as_member', 'set_as_non_member',
  113. 'bulk_send_welcome_email', 'bulk_send_call_for_membership_fee_email']
  114. form = AdminMemberChangeForm
  115. add_form = MemberCreationForm
  116. def name_or_organization_name(self, obj):
  117. """ Used to spare some horizontal space in list view"""
  118. return obj.organization_name or obj.last_name
  119. name_or_organization_name.short_description = 'Nom'
  120. def enhanced_end_date_of_membership(self, obj):
  121. membership_end = obj.end_date_of_membership()
  122. active_subscriptions = obj.get_active_subscriptions()
  123. recent_inactive_subscriptions = obj.get_recent_inactive_subscriptions()
  124. note = ''
  125. tooltip = ''
  126. if (membership_end is not None and membership_end < date.today()
  127. and recent_inactive_subscriptions.exists() and not active_subscriptions.exists()):
  128. note = ' <i class="fa fa-question-circle" style="color: #cccc40"></i>'
  129. tooltip = self.RECENT_SERVICE_MSG
  130. if (active_subscriptions.exists() and (
  131. membership_end is None or membership_end <= date.today())):
  132. note = ' <i class="fa fa-question-circle" style="color: red"></i>'
  133. tooltip = self.SERVICE_NO_FEE_MSG
  134. out = '{}{}'.format(
  135. obj.end_date_of_membership() or "pas de cotisation", note)
  136. if note:
  137. return mark_safe('<span style="cursor: help;" title="{}">{}</span>'.format(tooltip, out))
  138. else:
  139. return mark_safe(out)
  140. return mark_safe()
  141. enhanced_end_date_of_membership.short_description = "Date de fin d'adhésion"
  142. def get_fieldsets(self, request, obj=None):
  143. coord_fieldset = ('Coordonnées', {'fields': (
  144. ('email', 'send_membership_fees_email'),
  145. ('home_phone_number', 'mobile_phone_number'),
  146. 'address',
  147. ('postal_code', 'city', 'country'))})
  148. auth_fieldset = ('Authentification', {'fields': (
  149. ('username', 'password'))})
  150. perm_fieldset = ('Permissions', {'fields': (
  151. ('is_active', 'is_staff', 'is_superuser', 'groups'))})
  152. # if obj is null then it is a creation, otherwise it is a modification
  153. if obj:
  154. fieldsets = (
  155. ('Adhérent', {'fields': (
  156. ('status', 'date_joined', 'resign_date'),
  157. 'type',
  158. ('first_name', 'last_name', 'nickname'),
  159. 'organization_name',
  160. 'comments'
  161. )}),
  162. coord_fieldset,
  163. auth_fieldset,
  164. perm_fieldset,
  165. (None, {'fields': ('date_last_call_for_membership_fees_email',)})
  166. )
  167. else:
  168. fieldsets = (
  169. ('Adhérent', {'fields': (
  170. ('status', 'date_joined'),
  171. 'type',
  172. ('first_name', 'last_name', 'nickname'),
  173. 'organization_name',
  174. 'comments')}),
  175. coord_fieldset,
  176. auth_fieldset,
  177. perm_fieldset
  178. )
  179. if settings.HANDLE_BALANCE:
  180. fieldsets[0][1]['fields'] += ('balance',)
  181. return fieldsets
  182. radio_fields = {"type": admin.HORIZONTAL}
  183. save_on_top = True
  184. inlines = [CryptoKeyInline, MembershipFeeInline, OfferSubscriptionInline]
  185. def add_member_warnings(self, request, member):
  186. has_active_subscriptions = member.get_active_subscriptions().exists()
  187. has_recent_resigned_subscriptions = member.get_recent_inactive_subscriptions()
  188. if not member.is_paid_up() and has_active_subscriptions:
  189. messages.error(request, self.SERVICE_NO_FEE_MSG)
  190. elif not has_active_subscriptions and has_recent_resigned_subscriptions:
  191. messages.warning(request, self.RECENT_SERVICE_MSG)
  192. def get_form(self, request, obj=None, *args, **kwargs):
  193. if obj:
  194. self.add_member_warnings(request, obj)
  195. return super(MemberAdmin, self).get_form(request, obj, *args, **kwargs)
  196. def get_queryset(self, request):
  197. qs = super(MemberAdmin, self).get_queryset(request)
  198. if request.user.is_superuser:
  199. return qs
  200. else:
  201. offers = Offer.objects.manageable_by(request.user)
  202. return qs.filter(offersubscription__offer__in=offers).distinct()
  203. def get_readonly_fields(self, request, obj=None):
  204. readonly_fields = []
  205. if obj:
  206. # Remove help_text for readonly field (can't do that in the Form
  207. # django seems to user help_text from model for readonly fields)
  208. username_field = [
  209. f for f in obj._meta.fields if f.name == 'username']
  210. username_field[0].help_text = ''
  211. readonly_fields.append('username')
  212. if not request.user.is_superuser:
  213. readonly_fields += ['is_active', 'is_staff', 'is_superuser', 'groups', 'date_last_call_for_membership_fees_email']
  214. return readonly_fields
  215. def set_as_member(self, request, queryset):
  216. rows_updated = queryset.update(status='member')
  217. self.message_user(
  218. request,
  219. '%d membre(s) définis comme adhérent(s).' % rows_updated)
  220. set_as_member.short_description = 'Définir comme adhérent'
  221. def set_as_non_member(self, request, queryset):
  222. rows_updated = queryset.update(status='not_member')
  223. self.message_user(
  224. request,
  225. '%d membre(s) définis comme non adhérent(s).' % rows_updated)
  226. set_as_non_member.short_description = "Définir comme non adhérent"
  227. def get_urls(self):
  228. """Custom admin urls"""
  229. urls = super(MemberAdmin, self).get_urls()
  230. my_urls = [
  231. url(r'^send_welcome_email/(?P<id>\d+)$',
  232. self.admin_site.admin_view(self.send_welcome_email),
  233. name='send_welcome_email'),
  234. ]
  235. return my_urls + urls
  236. def send_welcome_email(self, request, id, return_httpredirect=True):
  237. """
  238. Vue appelée lorsque l'admin souhaite envoyer l'email de bienvenue à un
  239. membre.
  240. """
  241. # TODO : Add better perm here
  242. if request.user.is_superuser:
  243. member = get_object_or_404(Member, pk=id)
  244. member.send_welcome_email()
  245. messages.success(request,
  246. 'Le courriel de bienvenue a été envoyé à %s' % member.email)
  247. else:
  248. messages.error(
  249. request, 'Vous n\'avez pas l\'autorisation d\'envoyer des '
  250. 'courriels de bienvenue.')
  251. if return_httpredirect:
  252. return HttpResponseRedirect(reverse('admin:members_member_changelist'))
  253. def bulk_send_welcome_email(self, request, queryset):
  254. """
  255. Action appelée lorsque l'admin souhaite envoyer un lot d'email de bienvenue
  256. depuis une sélection de membre dans la vue liste de l'admin
  257. """
  258. for member in queryset.all():
  259. self.send_welcome_email(
  260. request, member.id, return_httpredirect=False)
  261. messages.success(request,
  262. 'Le courriel de bienvenue a été envoyé à %d membre(s).' % queryset.count())
  263. bulk_send_welcome_email.short_description = "Envoyer le courriel de bienvenue"
  264. def bulk_send_call_for_membership_fee_email(self, request, queryset):
  265. # TODO : Add better perm here
  266. if not request.user.is_superuser:
  267. messages.error(
  268. request, 'Vous n\'avez pas l\'autorisation d\'envoyer des '
  269. 'courriels de relance.')
  270. return
  271. cpt_success = 0
  272. for member in queryset.all():
  273. if member.send_call_for_membership_fees_email():
  274. cpt_success += 1
  275. else:
  276. messages.warning(request,
  277. "Le courriel de relance de cotisation n\'a pas "
  278. "été envoyé à {member} ({email}) car il a déjà "
  279. "reçu une relance le {last_call_date}"\
  280. .format(member=member,
  281. email=member.email,
  282. last_call_date=member.date_last_call_for_membership_fees_email))
  283. if cpt_success == 1:
  284. member = queryset.first()
  285. messages.success(request,
  286. "Le courriel de relance de cotisation a été "
  287. "envoyé à {member} ({email})"\
  288. .format(member=member, email=member.email))
  289. elif cpt_success > 1:
  290. messages.success(request,
  291. "Le courriel de relance de cotisation a été "
  292. "envoyé à {cpt} membres"\
  293. .format(cpt=cpt_success))
  294. bulk_send_call_for_membership_fee_email.short_description = 'Envoyer le courriel de relance de cotisation'
  295. class RowLevelPermissionAdmin(admin.ModelAdmin):
  296. def get_changeform_initial_data(self, request):
  297. return {'content_type': ContentType.objects.get_for_model(OfferSubscription)}
  298. admin.site.register(Member, MemberAdmin)
  299. # admin.site.unregister(Group)
  300. # admin.site.register(LdapUser, LdapUserAdmin)
  301. admin.site.register(RowLevelPermission, RowLevelPermissionAdmin)