Browse Source

gestion des services par des groupes spécifiques

Élie Bouttier 8 years ago
parent
commit
1093a6153a

+ 0 - 0
accounts/__init__.py


+ 29 - 0
accounts/admin.py

@@ -0,0 +1,29 @@
+from django.contrib import admin
+from django.contrib.auth.models import User
+from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
+from django.core.exceptions import PermissionDenied
+
+
+from adhesions.admin import ProfileInline, AdherentInline
+
+
+class UserAdmin(AuthUserAdmin):
+    inlines = (ProfileInline, AdherentInline,)
+
+    def get_fieldsets(self, request, obj=None):
+        if request.user.is_superuser:
+            return self.fieldsets
+        else:
+            return (
+                self.fieldsets[0],
+                self.fieldsets[1],
+            )
+
+    def user_change_password(self, request, id):
+        if not request.user.is_superuser:
+            raise PermissionDenied
+        return super().user_change_password(request, id)
+
+
+admin.site.unregister(User)
+admin.site.register(User, UserAdmin)

+ 17 - 13
adhesions/admin.py

@@ -2,26 +2,29 @@ from django.contrib import admin
 from django.contrib.contenttypes.admin import GenericStackedInline
 from django.contrib.auth.models import User
 
+from .forms import AdherentForm
 from .models import Profile, Corporation, Adherent
+from banking.admin import PaymentInline
+
+
+class ProfileInline(admin.StackedInline):
+    model = Profile
+
+    def has_add_permission(self, request):
+        return False
+
+    def has_delete_permission(self, request, obj=None):
+        return False
 
 
 class AdherentInline(GenericStackedInline):
     model = Adherent
     ct_field = 'adherent_type'
     ct_fk_field = 'adherent_id'
+    form = AdherentForm
+    max_num = 1
     extra = 0
 
-    def get_max_num(self, request, obj=None, **kwargs):
-        if obj is None or obj.adhesion is None:
-            return 1
-        else:
-            return 0
-
-
-class ProfileAdmin(admin.ModelAdmin):
-    list_display = ('__str__', 'phone_number',)
-    inlines = (AdherentInline,)
-
 
 class AdherentTypeFilter(admin.SimpleListFilter):
     title = 'type d’adhérent'
@@ -45,7 +48,9 @@ class AdherentTypeFilter(admin.SimpleListFilter):
 class AdherentAdmin(admin.ModelAdmin):
     list_display = ('id', 'adherent_name', 'type',)
     list_filter = (AdherentTypeFilter,)
-    fields = ('id', 'contribution',)
+    inlines = (PaymentInline,)
+    fields = ('id',)
+    readonly_fields = ('id',)
 
     def adherent_name(self, obj):
         return str(obj)
@@ -59,6 +64,5 @@ class CorporationAdmin(admin.ModelAdmin):
     inlines = (AdherentInline,)
 
 
-admin.site.register(Profile, ProfileAdmin)
 admin.site.register(Corporation, CorporationAdmin)
 admin.site.register(Adherent, AdherentAdmin)

+ 32 - 0
adhesions/forms.py

@@ -0,0 +1,32 @@
+from django import forms
+from django.forms.models import modelform_factory
+from django.forms import widgets
+from django.utils.safestring import mark_safe
+
+from .models import Adherent
+
+
+class StringWidget(widgets.Input):
+    def render(self, name, value, attrs=None):
+        # Create a hidden field first
+        hidden_field = widgets.HiddenInput(attrs)
+        return mark_safe(u'%s %s' % (value, hidden_field.render(value, attrs)))
+
+
+class AdherentForm(forms.ModelForm):
+    adherent_id = forms.CharField(disabled=True, label='Numéro d’adhérent')
+
+    class Meta:
+        model = Adherent
+        exclude = ()
+
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
+        if self.instance.id:
+            self.fields['adherent_id'].initial = self.instance.id
+        else:
+            self.fields['adherent_id'].initial = 'Veuillez sauvegarder pour obtenir un numéro d’adhérent.'
+            self.fields['adherent_id'].widget = StringWidget()
+
+    def has_changed(self):
+        return True

+ 25 - 0
adhesions/migrations/0003_remove_adherent_contribution.py

@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.4 on 2017-01-01 03:55
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('adhesions', '0002_auto_20161230_1814'),
+        ('banking', '0003_payment_reason'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='adherent',
+            name='contribution',
+        ),
+        migrations.AlterField(
+            model_name='adherent',
+            name='id',
+            field=models.AutoField(primary_key=True, serialize=False, verbose_name='Numéro d’adhérent'),
+        ),
+    ]

+ 6 - 8
adhesions/models.py

@@ -3,21 +3,21 @@ from django.contrib.auth.models import User
 from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
 from django.contrib.contenttypes.models import ContentType
 
-
 from banking.models import Payment
 
 
 class Adherent(models.Model):
     limit = models.Q(app_label='auth', model='user') \
           | models.Q(app_label='adhesions', model='corporation')
-    id = models.IntegerField(verbose_name='Numéro d’adhérent', primary_key=True)
+    id = models.AutoField(verbose_name='Numéro d’adhérent', primary_key=True, editable=True)
     adherent_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,
                                       limit_choices_to=limit, verbose_name='Type d’adhérent')
     adherent_id = models.PositiveIntegerField(verbose_name='ID')
     adherent = GenericForeignKey('adherent_type', 'adherent_id')
-    contribution = models.ForeignKey(Payment, on_delete=models.CASCADE,
-                                     null=True, blank=True, related_name='adherent',
-                                     verbose_name='Cotisation')
+    contribution = GenericRelation(Payment,
+                                   content_type_field='reason_type',
+                                   object_id_field='reason_id',
+                                   related_query_name='adherent')
 
     class Meta:
         verbose_name = 'adhérent'
@@ -51,8 +51,6 @@ class Profile(AdhesionMixin, models.Model):
     phone_number = models.CharField(max_length=16, blank=True, default='',
                                     verbose_name='Numéro de téléphone')
     address = models.TextField(blank=True, default='', verbose_name='Adresse')
-    adhesions = GenericRelation(Adherent, content_type_field='adherent_type',
-                               object_id_field='adherent_id', related_query_name='users')
 
     class Meta:
         verbose_name = 'profil'
@@ -69,7 +67,7 @@ class Corporation(AdhesionMixin, models.Model):
     adhesions = GenericRelation(Adherent,
                                 content_type_field='adherent_type',
                                 object_id_field='adherent_id',
-                                related_query_name='corporations')
+                                related_query_name='corporation')
 
     class Meta:
         verbose_name = 'personne morale'

+ 22 - 1
banking/admin.py

@@ -1,11 +1,32 @@
 from django.contrib import admin
+from django.contrib.contenttypes.admin import GenericTabularInline
+from django.contrib.contenttypes.models import ContentType
 
+from services.models import Service
 from .models import Payment
 
 
+class PaymentInline(GenericTabularInline):
+    model = Payment
+    ct_field = 'reason_type'
+    ct_fk_field = 'reason_id'
+    extra = 1
+
+
 class PaymentAdmin(admin.ModelAdmin):
-    list_display = ('adherent', 'amount', 'period', 'payment_method',)
+    list_display = ('reason_verbose', 'amount', 'period', 'payment_method',)
     list_filter = ('payment_method',)
+    fields = ('amount', 'period', 'payment_method',)
+
+    def get_queryset(self, request):
+        qs = super().get_queryset(request)
+        if request.user.is_superuser:
+            return qs
+        # Show only payment related to a service for which the user is in managment group.
+        return qs.filter(service__service_type__group__in=request.user.groups.all())
+
+    def has_add_permission(self, request):
+        return False
 
 
 admin.site.register(Payment, PaymentAdmin)

+ 68 - 0
banking/migrations/0003_payment_reason.py

@@ -0,0 +1,68 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.4 on 2017-01-01 03:55
+from __future__ import unicode_literals
+
+import django.core.validators
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+def set_payment_reason(apps, schema_editor):
+    db_alias = schema_editor.connection.alias
+    Payment = apps.get_model('banking', 'Payment')
+    Adherent = apps.get_model('adhesions', 'Adherent')
+    Service = apps.get_model('services', 'Service')
+    ContentType = apps.get_model('contenttypes', 'ContentType')
+    adherent_ctype = ContentType.objects.get_for_model(Adherent)
+    service_ctype = ContentType.objects.get_for_model(Service)
+    for payment in Payment.objects.using(db_alias).all():
+        try:
+            adherent = Adherent.objects.get(contribution__pk=payment.pk)
+        except Adherent.DoesNotExist:
+            pass
+        else:
+            payment.reason_type = adherent_ctype
+            payment.reason_id = adherent.pk
+            payment.save()
+            continue
+        # If the payment is not for an adherent, it must be for a service.
+        service = Service.objects.get(contribution__pk=payment.pk)
+        payment.reason_type = service_ctype
+        payment.reason_id = service.pk
+        payment.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('contenttypes', '0002_remove_content_type_name'),
+        ('banking', '0002_payment_date'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='payment',
+            name='reason_type',
+            field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
+            preserve_default=False,
+        ),
+        migrations.AddField(
+            model_name='payment',
+            name='reason_id',
+            field=models.PositiveIntegerField(default=0),
+            preserve_default=False,
+        ),
+        migrations.RunPython(
+            set_payment_reason,
+        ),
+        migrations.AlterField(
+            model_name='payment',
+            name='reason_type',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType'),
+        ),
+        migrations.AlterField(
+            model_name='payment',
+            name='period',
+            field=models.PositiveIntegerField(validators=[django.core.validators.MaxValueValidator(12)], verbose_name='Période (mois)'),
+        ),
+    ]

+ 21 - 3
banking/models.py

@@ -1,4 +1,6 @@
 from django.db import models
+from django.contrib.contenttypes.fields import GenericForeignKey
+from django.contrib.contenttypes.models import ContentType
 from django.core.validators import MaxValueValidator
 
 
@@ -8,15 +10,31 @@ class Payment(models.Model):
     PAYMENT_CHOICES = ( 
         (TRANSFERT, 'Virement'),
         (WITHDRAWAL, 'Prélèvement'),
-    )   
+    )
+    limit = models.Q(app_label='adhesions', model='Adherent') \
+          | models.Q(app_label='services', model='Service')
+    reason_type = models.ForeignKey(ContentType, on_delete=models.CASCADE,
+                                    limit_choices_to=limit)
+    reason_id = models.PositiveIntegerField()
+    reason = GenericForeignKey('reason_type', 'reason_id')
     amount = models.DecimalField(max_digits=9, decimal_places=2, verbose_name='Montant')
-    period = models.PositiveIntegerField(validators=[MaxValueValidator(12)], verbose_name='Période')
-    payment_method = models.IntegerField(choices=PAYMENT_CHOICES, verbose_name='Méthode de paiement')
+    period = models.PositiveIntegerField(validators=[MaxValueValidator(12)],
+                                         verbose_name='Période (mois)')
+    payment_method = models.IntegerField(choices=PAYMENT_CHOICES,
+                                         verbose_name='Méthode de paiement')
     date = models.DateField(verbose_name='Date de paiement ou de début de paiement')
 
     class Meta:
         verbose_name = 'paiement'
 
+    def reason_verbose(self):
+        if self.reason_type.app_label == 'adhesions':
+            return 'Cotisation de %s' % self.reason
+        if self.reason_type.app_label == 'services':
+            return 'Service %s' % self.reason
+    reason_verbose.short_description = 'Paiement'
+
+
     def __str__(self):
         s = str(self.amount) + '€' 
         if self.period:

+ 1 - 0
djadhere/settings.py

@@ -37,6 +37,7 @@ INSTALLED_APPS = [
     'djadhere',
     'django.contrib.admin',
     'django.contrib.auth',
+    'accounts', # must be after django.contrib.auth
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',

+ 18 - 1
services/admin.py

@@ -3,6 +3,7 @@ from django.db.models import Q
 from django.utils import timezone
 
 from .models import Service, ServiceType, IPResource
+from banking.admin import PaymentInline
 
 
 class ServiceTypeFilter(admin.SimpleListFilter):
@@ -10,7 +11,10 @@ class ServiceTypeFilter(admin.SimpleListFilter):
     parameter_name = 'type'
 
     def lookups(self, request, model_admin):
-        return ServiceType.objects.all().values_list('pk', 'name')
+        service_types = ServiceType.objects.all()
+        if not request.user.is_superuser:
+            service_types = service_types.filter(group=request.user.groups.all())
+        return service_types.values_list('pk', 'name')
 
     def queryset(self, request, queryset):
         if self.value():
@@ -37,12 +41,25 @@ class ServiceStatusFilter(admin.SimpleListFilter):
 class ServiceAdmin(admin.ModelAdmin):
     list_display = ('id', 'adherent', 'service_type', 'start', 'end_view',)
     list_filter = (ServiceStatusFilter, ServiceTypeFilter,)
+    inlines = (PaymentInline,)
 
     def end_view(self, obj):
         return obj.end
     end_view.short_description = 'Fin du service'
     end_view.empty_value_display = 'Service en cours'
 
+    def get_queryset(self, request):
+        qs = super().get_queryset(request)
+        if request.user.is_superuser:
+            return qs
+        return qs.filter(service_type__group__in=request.user.groups.all())
+
+    def get_readonly_fields(self, request, obj=None):
+        fields = super().get_readonly_fields(request, obj)
+        if not request.user.is_superuser:
+            return fields + ('service_type', 'ip_resources',)
+        return fields
+
 
 class InUseFilter(admin.SimpleListFilter):
     title = 'disponibilité'

+ 3 - 0
services/apps.py

@@ -4,3 +4,6 @@ from django.apps import AppConfig
 class ServicesConfig(AppConfig):
     name = 'services'
     verbose_name = 'Services'
+
+    def ready(self):
+        import services.signals

+ 32 - 0
services/migrations/0004_remove_adherent_contribution.py

@@ -0,0 +1,32 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10.4 on 2017-01-01 03:55
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('auth', '0008_alter_user_username_max_length'),
+        ('services', '0003_auto_20161231_0020'),
+        ('banking', '0003_payment_reason'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='service',
+            name='contribution',
+        ),
+        migrations.AddField(
+            model_name='servicetype',
+            name='group',
+            field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='service_types', to='auth.Group', verbose_name='Groupe de gestion'),
+        ),
+        migrations.AlterField(
+            model_name='servicetype',
+            name='name',
+            field=models.CharField(max_length=64, verbose_name='Nom'),
+        ),
+    ]

+ 10 - 3
services/models.py

@@ -1,6 +1,8 @@
 from django.db import models
 from django.core.validators import MaxValueValidator
 from django.utils import timezone
+from django.contrib.auth.models import Group
+from django.contrib.contenttypes.fields import GenericRelation
 
 from adhesions.models import Adherent
 from banking.models import Payment
@@ -31,7 +33,10 @@ class IPResource(models.Model):
 
 
 class ServiceType(models.Model):
-    name = models.CharField(max_length=64)
+    name = models.CharField(max_length=64, verbose_name='Nom')
+    group = models.ForeignKey(Group, null=True, blank=True,
+                              verbose_name='Groupe de gestion',
+                              related_name='service_types')
 
     class Meta:
         verbose_name = 'Type de service'
@@ -46,8 +51,10 @@ class Service(models.Model):
     service_type = models.ForeignKey(ServiceType, related_name='services',
                                      verbose_name='Type de service')
     notes = models.TextField(blank=True, default='')
-    contribution = models.ForeignKey(Payment, null=True, blank=True,
-                                     verbose_name='Cotisation')
+    contribution = GenericRelation(Payment,
+                                   content_type_field='reason_type',
+                                   object_id_field='reason_id',
+                                   related_query_name='service')
     ip_resources = models.ManyToManyField(IPResource, blank=True,
                                           verbose_name='Ressources IP')
     start = models.DateTimeField(verbose_name='Début du service')

+ 33 - 0
services/signals.py

@@ -0,0 +1,33 @@
+from django.dispatch import receiver
+from django.db.models.signals import pre_save
+from django.contrib.auth.models import Permission
+
+from .models import ServiceType
+
+
+@receiver(pre_save, sender=ServiceType)
+def update_user_permissions(sender, instance, raw, **kwargs):
+    perms = [
+        'add_user',
+        'change_user',
+        'change_profile',
+        'add_adherent',
+        'change_adherent',
+        'add_payment',
+        'change_payment',
+        'delete_payment',
+        'change_service',
+    ]
+    try:
+        old_group = ServiceType.objects.get(pk=instance.pk).group
+    except ServiceType.DoesNotExist:
+        old_group = None
+    new_group = instance.group
+    perms = Permission.objects.filter(codename__in=perms).all()
+    if old_group is None and new_group is not None:
+        # add permissions to new_group
+        new_group.permissions.add(*perms)
+    elif old_group is not None and new_group is None:
+        # remove permissions from old_group *if not needed elsewhere*
+        if not old_group.service_types.exclude(pk=instance.pk).exists():
+            old_group.permissions.remove(*perms)