]> git.0d.be Git - panikdb.git/commitdiff
add basic membership management
authorFrédéric Péters <fpeters@0d.be>
Sun, 23 Jan 2022 10:03:30 +0000 (11:03 +0100)
committerFrédéric Péters <fpeters@0d.be>
Sun, 23 Jan 2022 10:03:30 +0000 (11:03 +0100)
panikdb/aa/forms.py
panikdb/aa/migrations/0007_membership.py [new file with mode: 0644]
panikdb/aa/models.py
panikdb/aa/urls.py
panikdb/aa/views.py
panikdb/settings.py
panikdb/static/css/style.scss
panikdb/templates/aa/register_membership.html [new file with mode: 0644]
panikdb/templates/aa/user_detail.html
panikdb/templates/aa/user_list.html

index 4664e1236ce013d844b2f762de28400e41bf7811..beecb6be9baf8ac7c886af1246b910e082da4c72 100644 (file)
@@ -2,7 +2,7 @@ from django import forms
 from django.core.exceptions import ValidationError
 from django.utils.translation import ugettext_lazy as _
 
-from .models import User
+from .models import Membership, User
 
 
 class MemberEditForm(forms.ModelForm):
@@ -45,3 +45,9 @@ def get_emissions_as_choices():
 
 class MemberEmissionForm(forms.Form):
     emission = forms.ChoiceField(label=_('New Emission'), required=True, choices=get_emissions_as_choices)
+
+
+class MembershipForm(forms.ModelForm):
+    class Meta:
+        model = Membership
+        fields = ['year', 'payment_date', 'payment_amount', 'notes']
diff --git a/panikdb/aa/migrations/0007_membership.py b/panikdb/aa/migrations/0007_membership.py
new file mode 100644 (file)
index 0000000..4879260
--- /dev/null
@@ -0,0 +1,40 @@
+# Generated by Django 2.2.19 on 2022-01-23 10:42
+
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('aa', '0006_auto_20210326_1324'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Membership',
+            fields=[
+                (
+                    'id',
+                    models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
+                ),
+                ('year', models.IntegerField(verbose_name='Year')),
+                ('creation_timestamp', models.DateTimeField(auto_now_add=True)),
+                ('payment_date', models.DateField(blank=True, null=True, verbose_name='Date')),
+                (
+                    'payment_amount',
+                    models.DecimalField(
+                        blank=True, decimal_places=2, max_digits=6, null=True, verbose_name='Amount'
+                    ),
+                ),
+                ('notes', models.TextField(blank=True, null=True, verbose_name='Notes')),
+                (
+                    'member',
+                    models.ForeignKey(
+                        on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
+                    ),
+                ),
+            ],
+        ),
+    ]
index 06857d6e3cef2d4d9e1a10fb8198ebfada111024..4de4315133a8487973c354542d6b411837690853 100644 (file)
@@ -53,3 +53,14 @@ class User(AbstractUser):
         if parts:
             s = ' '.join(parts)
         return s
+
+
+class Membership(models.Model):
+    member = models.ForeignKey(User, on_delete=models.CASCADE)
+    year = models.IntegerField(_('Year'))
+    creation_timestamp = models.DateTimeField(auto_now_add=True)
+    payment_date = models.DateField(verbose_name=_('Date'), blank=True, null=True)
+    payment_amount = models.DecimalField(
+        verbose_name=_('Amount'), decimal_places=2, max_digits=6, blank=True, null=True
+    )
+    notes = models.TextField(_('Notes'), blank=True, null=True)
index 512881b16c7b2865f7513891cf364fc0b92315b0..bab75b134a4e2852c622db46b7eb93c494507123 100644 (file)
@@ -16,6 +16,11 @@ urlpatterns = [
     ),
     url(r'^members/(?P<pk>\d+)/mark-as-active/$', views.mark_as_active, name='member-mark-as-active'),
     url(r'^members/(?P<pk>\d+)/mark-as-inactive/$', views.mark_as_inactive, name='member-mark-as-inactive'),
+    url(
+        r'^members/(?P<pk>\d+)/register-membership/$',
+        views.register_membership,
+        name='member-register-membership',
+    ),
     url(r'^profile/$', views.profile_view, name='profile-view'),
     url(r'^profile/edit/$', views.profile_contact_edit, name='profile-contact-edit'),
 ]
index 8b7b3508a65e40bec153ab574b23d9db1af31cd5..65e40ab6e55de046fa7f356e18910bac20340536 100644 (file)
@@ -1,4 +1,5 @@
 import vobject
+from django.conf import settings
 from django.contrib.admin.views.decorators import staff_member_required
 from django.contrib.auth.decorators import login_required
 from django.contrib.auth.models import Group
@@ -6,13 +7,14 @@ from django.core.exceptions import PermissionDenied
 from django.db.models import Q
 from django.http import HttpResponse, HttpResponseRedirect
 from django.urls import reverse_lazy
+from django.utils.timezone import now
 from django.views.generic.base import RedirectView, TemplateView
 from django.views.generic.detail import DetailView
 from django.views.generic.edit import CreateView, FormView, UpdateView
 from django.views.generic.list import ListView
 
-from .forms import MemberCreateForm, MemberEditForm, MemberEmissionForm
-from .models import User
+from .forms import MemberCreateForm, MemberEditForm, MemberEmissionForm, MembershipForm
+from .models import Membership, User
 
 
 class ProfileView(TemplateView):
@@ -57,6 +59,11 @@ class MembersListView(ListView):
                     | Q(emissions__title__icontains=part)
                     | Q(emissions__slug__icontains=part)
                 )
+        current_year = now().year
+        if self.request.GET.get('membership') == 'ok':
+            qs = qs.filter(membership__year=current_year)
+        if self.request.GET.get('membership') == 'renew':
+            qs = qs.filter(membership__year=current_year - 1).exclude(membership__year=current_year)
         qs = qs.distinct()
         return qs
 
@@ -110,6 +117,14 @@ members_vcard = login_required(MembersVCardView.as_view())
 class MemberView(DetailView):
     model = User
 
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        current_year = now().year
+        user = self.get_object()
+        context['current_membership'] = user.membership_set.filter(year=current_year).first()
+        context['past_memberships'] = user.membership_set.exclude(year=current_year).order_by('-year')
+        return context
+
 
 member_view = login_required(MemberView.as_view())
 
@@ -204,3 +219,37 @@ def mark_as_active(request, pk):
 def mark_as_inactive(request, pk):
     User.objects.filter(pk=pk).update(is_active=False)
     return HttpResponseRedirect(reverse_lazy('member-view', kwargs={'pk': pk}))
+
+
+class RegisterMembershipView(FormView):
+    form_class = MembershipForm
+    template_name = 'aa/register_membership.html'
+
+    def get_context_data(self, **kwargs):
+        if not self.request.user.has_perm('aa.add_membership'):
+            raise PermissionDenied()
+        context = super().get_context_data(**kwargs)
+        context['member'] = User.objects.get(id=self.kwargs['pk'])
+        return context
+
+    def get_initial(self):
+        initial = super().get_initial()
+        initial['year'] = now().year
+        initial['payment_date'] = now().date()
+        initial['payment_amount'] = settings.MEMBERSHIP_DEFAULT_AMOUNT or ''
+        return initial
+
+    def form_valid(self, form):
+        member = User.objects.get(id=self.kwargs['pk'])
+        membership, created = Membership.objects.get_or_create(member=member, year=form.cleaned_data['year'])
+        membership.payment_date = form.cleaned_data['payment_date']
+        membership.payment_amount = form.cleaned_data['payment_amount']
+        membership.notes = form.cleaned_data['notes']
+        membership.save()
+        return super().form_valid(form)
+
+    def get_success_url(self):
+        return reverse_lazy('member-view', kwargs={'pk': self.kwargs['pk']})
+
+
+register_membership = login_required(RegisterMembershipView.as_view())
index d3a074ff867e9bd3c8ae7278c46dfc1030bed2d1..13def561034daa4c756de057c5fc9af44792aa36 100644 (file)
@@ -245,6 +245,8 @@ TEST_RUNNER = 'django.test.runner.DiscoverRunner'
 TAGGIT_TAGS_FROM_STRING = 'emissions.utils.custom_parse_tags'
 
 AUTH_USER_MODEL = 'aa.User'
+MEMBERSHIP_DEFAULT_AMOUNT = 10
+
 LOGIN_REDIRECT_URL = '/'
 WEBSITE_BASE_URL = 'https://www.radiopanik.org/'
 
index df318cf6ce730f55a6fd7c09e48cd6c1a9b6813d..e5569f33e784919891a9d6ccadb25c928db986a7 100644 (file)
@@ -956,3 +956,21 @@ div.section > div > *:first-child {
                transform: rotate(360deg);
        }
 }
+
+.register-membership-form {
+       #id_notes {
+               height: 4em;
+       }
+}
+
+.members-filter {
+       display: flex;
+       form input {
+               margin-top: 0;
+               margin-bottom: 0;
+       }
+       a.button {
+               margin-left: 1em;
+               line-height: normal;
+       }
+}
diff --git a/panikdb/templates/aa/register_membership.html b/panikdb/templates/aa/register_membership.html
new file mode 100644 (file)
index 0000000..ccf22f5
--- /dev/null
@@ -0,0 +1,17 @@
+{% extends "aa/user_list.html" %}
+{% load i18n %}
+
+{% block appbar %}
+{% endblock %}
+
+{% block content %}
+<form method="post" class="register-membership-form">
+{% csrf_token %}
+{{ form.as_p }}
+<div class="buttons">
+<a class="cancel" href="{% url 'member-view' pk=member.id %}">Annuler</a>
+<button>Valider</button>
+</div>
+</form>
+{% endblock %}
+
index 3fddf06f001bea46ee20ddbadcb752dc4b994c48..54fecedba4ce0aa94de76b0e6f1cebd686bee6c5 100644 (file)
   {% endif %}
   </div>
 </div>
+
+{% if perms.emissions.add_membership %}
+<div class="section">
+  <h3>Cotisations</h3>
+  <div>
+  {% if current_membership %}
+    <p>En ordre de cotisation.
+    {% if current_membership.payment_date and current_membership.payment_amount %}
+    ({{ current_membership.payment_amount }}€ payés le {{ current_membership.payment_date }})
+    {% endif %}
+    </p>
+    {% if current_membership.notes %}<p>{{ current_membership.notes }}</p>{% endif %}
+  {% else %}
+    <p>Pas en ordre de cotisation pour cette année.</p>
+    <a rel="popup" class="button" href="{% url 'member-register-membership' pk=user.id %}">Enregistrer la cotisation</a>
+  {% endif %}
+  {% if past_memberships %}
+    <p>Années précédentes :</p>
+    <ul>
+    {% for membership in past_memberships %}
+    <li>{{ membership.year }} {% if membership.payment_date and membership.payment_amount %}
+            ({{ membership.payment_amount }}€ payés le {{ membership.payment_date }}){% endif %}</li>
+    {% endfor %}
+    </ul>
+  {% endif %}
+  </div>
+</div>
+{% endif %}
 {% endblock %}
index 2f5bb160ad38802a376d4f2d66d7c4bba19c85e9..28d171dd89bc4a9becf4d9b59dcef8e92cab092c 100644 (file)
 {% block more-user-links %}{{ block.super }} <a class="icon-members" href="{% url 'members-list-view' %}">Annuaire des membres</a>{% endblock %}
 
 {% block content %}
-<div class="section padded">
+<div class="section padded members-filter">
 <form>
   <input name="q" type="search" value="{{ request.GET.q }}"> <button>{% trans 'Search' %}</button>
 </form>
+{% if perms.emissions.add_membership %}
+<a class="button" href="?membership=ok">En ordre de cotisation</a>
+<a class="button" href="?membership=renew">Cotisations à renouveler</a>
+{% endif %}
 </div>
 
 <table class="main">