mirror of https://gitlab.crans.org/bde/nk20
Merge branch 'documents' into beta
This commit is contained in:
commit
6ea92cdcde
|
@ -54,6 +54,15 @@ class ActivityForm(forms.ModelForm):
|
||||||
|
|
||||||
class GuestForm(forms.ModelForm):
|
class GuestForm(forms.ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
|
"""
|
||||||
|
Someone can be invited as a Guest to an Activity if:
|
||||||
|
- the activity has not already started.
|
||||||
|
- the activity is validated.
|
||||||
|
- the Guest has not already been invited more than 5 times.
|
||||||
|
- the Guest is already invited.
|
||||||
|
- the inviter already invited 3 peoples.
|
||||||
|
"""
|
||||||
|
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
||||||
if timezone.now() > timezone.localtime(self.activity.date_start):
|
if timezone.now() > timezone.localtime(self.activity.date_start):
|
||||||
|
|
|
@ -11,6 +11,7 @@ from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, TemplateView, UpdateView
|
from django.views.generic import DetailView, TemplateView, UpdateView
|
||||||
from django_tables2.views import SingleTableView
|
from django_tables2.views import SingleTableView
|
||||||
|
|
||||||
from note.models import Alias, NoteSpecial, NoteUser
|
from note.models import Alias, NoteSpecial, NoteUser
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||||
|
@ -21,6 +22,9 @@ from .tables import ActivityTable, EntryTable, GuestTable
|
||||||
|
|
||||||
|
|
||||||
class ActivityCreateView(ProtectedCreateView):
|
class ActivityCreateView(ProtectedCreateView):
|
||||||
|
"""
|
||||||
|
View to create a new Activity
|
||||||
|
"""
|
||||||
model = Activity
|
model = Activity
|
||||||
form_class = ActivityForm
|
form_class = ActivityForm
|
||||||
extra_context = {"title": _("Create new activity")}
|
extra_context = {"title": _("Create new activity")}
|
||||||
|
@ -47,6 +51,9 @@ class ActivityCreateView(ProtectedCreateView):
|
||||||
|
|
||||||
|
|
||||||
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||||
|
"""
|
||||||
|
Displays all Activities, and classify if they are on-going or upcoming ones.
|
||||||
|
"""
|
||||||
model = Activity
|
model = Activity
|
||||||
table_class = ActivityTable
|
table_class = ActivityTable
|
||||||
ordering = ('-date_start',)
|
ordering = ('-date_start',)
|
||||||
|
@ -73,6 +80,9 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView
|
||||||
|
|
||||||
|
|
||||||
class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
|
"""
|
||||||
|
Shows details about one activity. Add guest to context
|
||||||
|
"""
|
||||||
model = Activity
|
model = Activity
|
||||||
context_object_name = "activity"
|
context_object_name = "activity"
|
||||||
extra_context = {"title": _("Activity detail")}
|
extra_context = {"title": _("Activity detail")}
|
||||||
|
@ -90,6 +100,9 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
|
|
||||||
|
|
||||||
class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
|
"""
|
||||||
|
Updates one Activity
|
||||||
|
"""
|
||||||
model = Activity
|
model = Activity
|
||||||
form_class = ActivityForm
|
form_class = ActivityForm
|
||||||
extra_context = {"title": _("Update activity")}
|
extra_context = {"title": _("Update activity")}
|
||||||
|
@ -99,11 +112,15 @@ class ActivityUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
|
|
||||||
|
|
||||||
class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
|
class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
|
"""
|
||||||
|
Invite a Guest, The rules to invites someone are defined in `forms:activity.GuestForm`
|
||||||
|
"""
|
||||||
model = Guest
|
model = Guest
|
||||||
form_class = GuestForm
|
form_class = GuestForm
|
||||||
template_name = "activity/activity_invite.html"
|
template_name = "activity/activity_invite.html"
|
||||||
|
|
||||||
def get_sample_object(self):
|
def get_sample_object(self):
|
||||||
|
""" Creates a standart Guest binds to the Activity"""
|
||||||
activity = Activity.objects.get(pk=self.kwargs["pk"])
|
activity = Activity.objects.get(pk=self.kwargs["pk"])
|
||||||
return Guest(
|
return Guest(
|
||||||
activity=activity,
|
activity=activity,
|
||||||
|
@ -134,6 +151,9 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
|
|
||||||
|
|
||||||
class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
|
"""
|
||||||
|
Manages entry to an activity
|
||||||
|
"""
|
||||||
template_name = "activity/activity_entry.html"
|
template_name = "activity/activity_entry.html"
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
@ -154,14 +174,10 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
raise PermissionDenied(_("This activity is closed."))
|
raise PermissionDenied(_("This activity is closed."))
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_invited_guest(self,activity):
|
||||||
context = super().get_context_data(**kwargs)
|
"""
|
||||||
|
Retrieves all Guests to the activity
|
||||||
activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
"""
|
||||||
.distinct().get(pk=self.kwargs["pk"])
|
|
||||||
context["activity"] = activity
|
|
||||||
|
|
||||||
matched = []
|
|
||||||
|
|
||||||
guest_qs = Guest.objects\
|
guest_qs = Guest.objects\
|
||||||
.annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\
|
.annotate(balance=F("inviter__balance"), note_name=F("inviter__user__username"))\
|
||||||
|
@ -182,11 +198,13 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
else:
|
else:
|
||||||
pattern = None
|
pattern = None
|
||||||
guest_qs = guest_qs.none()
|
guest_qs = guest_qs.none()
|
||||||
|
return guest_qs
|
||||||
|
|
||||||
for guest in guest_qs:
|
def get_invited_note(self,activity):
|
||||||
guest.type = "Invité"
|
"""
|
||||||
matched.append(guest)
|
Retrieves all Note that can attend the activity,
|
||||||
|
they need to have an up-to-date membership in the attendees_club.
|
||||||
|
"""
|
||||||
note_qs = Alias.objects.annotate(last_name=F("note__noteuser__user__last_name"),
|
note_qs = Alias.objects.annotate(last_name=F("note__noteuser__user__last_name"),
|
||||||
first_name=F("note__noteuser__user__first_name"),
|
first_name=F("note__noteuser__user__first_name"),
|
||||||
username=F("note__noteuser__user__username"),
|
username=F("note__noteuser__user__username"),
|
||||||
|
@ -223,8 +241,25 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
# have distinct aliases rather than distinct notes with a SQLite DB, but it can fill the result page.
|
# have distinct aliases rather than distinct notes with a SQLite DB, but it can fill the result page.
|
||||||
# In production mode, please use PostgreSQL.
|
# In production mode, please use PostgreSQL.
|
||||||
note_qs = note_qs.distinct()[:20]
|
note_qs = note_qs.distinct()[:20]
|
||||||
|
return note_qs
|
||||||
|
|
||||||
for note in note_qs:
|
def get_context_data(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Query the list of Guest and Note to the activity and add information to makes entry with JS.
|
||||||
|
"""
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
||||||
|
.distinct().get(pk=self.kwargs["pk"])
|
||||||
|
context["activity"] = activity
|
||||||
|
|
||||||
|
matched=[]
|
||||||
|
|
||||||
|
for guest in get_invited_guest(self,activity):
|
||||||
|
guest.type = "Invité"
|
||||||
|
matched.append(guest)
|
||||||
|
|
||||||
|
for note in get_invited_note(self,activity):
|
||||||
note.type = "Adhérent"
|
note.type = "Adhérent"
|
||||||
note.activity = activity
|
note.activity = activity
|
||||||
matched.append(note)
|
matched.append(note)
|
||||||
|
|
|
@ -65,6 +65,18 @@ class ProfileForm(forms.ModelForm):
|
||||||
exclude = ('user', 'email_confirmed', 'registration_valid', )
|
exclude = ('user', 'email_confirmed', 'registration_valid', )
|
||||||
|
|
||||||
|
|
||||||
|
class ImageForm(forms.Form):
|
||||||
|
"""
|
||||||
|
Form used for the js interface for profile picture
|
||||||
|
"""
|
||||||
|
image = forms.ImageField(required=False,
|
||||||
|
label=_('select an image'),
|
||||||
|
help_text=_('Maximal size: 2MB'))
|
||||||
|
x = forms.FloatField(widget=forms.HiddenInput())
|
||||||
|
y = forms.FloatField(widget=forms.HiddenInput())
|
||||||
|
width = forms.FloatField(widget=forms.HiddenInput())
|
||||||
|
height = forms.FloatField(widget=forms.HiddenInput())
|
||||||
|
|
||||||
class ClubForm(forms.ModelForm):
|
class ClubForm(forms.ModelForm):
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
import io
|
import io
|
||||||
from datetime import timedelta, date
|
from datetime import timedelta, date
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
|
@ -18,8 +18,8 @@ from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import DetailView, UpdateView, TemplateView
|
from django.views.generic import DetailView, UpdateView, TemplateView
|
||||||
from django.views.generic.edit import FormMixin
|
from django.views.generic.edit import FormMixin
|
||||||
from django_tables2.views import SingleTableView
|
from django_tables2.views import SingleTableView
|
||||||
|
|
||||||
from rest_framework.authtoken.models import Token
|
from rest_framework.authtoken.models import Token
|
||||||
from note.forms import ImageForm
|
|
||||||
from note.models import Alias, NoteUser
|
from note.models import Alias, NoteUser
|
||||||
from note.models.transactions import Transaction, SpecialTransaction
|
from note.models.transactions import Transaction, SpecialTransaction
|
||||||
from note.tables import HistoryTable, AliasTable
|
from note.tables import HistoryTable, AliasTable
|
||||||
|
@ -28,7 +28,8 @@ from permission.backends import PermissionBackend
|
||||||
from permission.models import Role
|
from permission.models import Role
|
||||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||||
|
|
||||||
from .forms import ProfileForm, ClubForm, MembershipForm, CustomAuthenticationForm, UserForm, MembershipRolesForm
|
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
|
||||||
|
CustomAuthenticationForm, MembershipRolesForm,
|
||||||
from .models import Club, Membership
|
from .models import Club, Membership
|
||||||
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
|
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
|
||||||
|
|
||||||
|
@ -49,6 +50,7 @@ class CustomLoginView(LoginView):
|
||||||
class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
"""
|
"""
|
||||||
Update the user information.
|
Update the user information.
|
||||||
|
On this view both `:models:member.User` and `:models:member.Profile` are updated through forms
|
||||||
"""
|
"""
|
||||||
model = User
|
model = User
|
||||||
form_class = UserForm
|
form_class = UserForm
|
||||||
|
@ -77,14 +79,11 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
new_username = form.data['username']
|
"""
|
||||||
# Si l'utilisateur cherche à modifier son pseudo, le nouveau pseudo ne doit pas être proche d'un alias existant
|
Check if ProfileForm is correct
|
||||||
note = NoteUser.objects.filter(
|
then check if username is not already taken by someone else or by the user,
|
||||||
alias__normalized_name=Alias.normalize(new_username))
|
then check if email has changed, and if so ask for new validation.
|
||||||
if note.exists() and note.get().user != self.object:
|
"""
|
||||||
form.add_error('username',
|
|
||||||
_("An alias with a similar name already exists."))
|
|
||||||
return super().form_invalid(form)
|
|
||||||
|
|
||||||
profile_form = ProfileForm(
|
profile_form = ProfileForm(
|
||||||
data=self.request.POST,
|
data=self.request.POST,
|
||||||
|
@ -93,31 +92,35 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
profile_form.full_clean()
|
profile_form.full_clean()
|
||||||
if not profile_form.is_valid():
|
if not profile_form.is_valid():
|
||||||
return super().form_invalid(form)
|
return super().form_invalid(form)
|
||||||
|
|
||||||
new_username = form.data['username']
|
new_username = form.data['username']
|
||||||
|
# Check if the new username is not already taken as an alias of someone else.
|
||||||
|
note = NoteUser.objects.filter(
|
||||||
|
alias__normalized_name=Alias.normalize(new_username))
|
||||||
|
if note.exists() and note.get().user != self.object:
|
||||||
|
form.add_error('username',
|
||||||
|
_("An alias with a similar name already exists."))
|
||||||
|
return super().form_invalid(form)
|
||||||
|
# Check if the username is one of user's aliases.
|
||||||
alias = Alias.objects.filter(name=new_username)
|
alias = Alias.objects.filter(name=new_username)
|
||||||
# Si le nouveau pseudo n'est pas un de nos alias,
|
|
||||||
# on supprime éventuellement un alias similaire pour le remplacer
|
|
||||||
if not alias.exists():
|
if not alias.exists():
|
||||||
similar = Alias.objects.filter(
|
similar = Alias.objects.filter(
|
||||||
normalized_name=Alias.normalize(new_username))
|
normalized_name=Alias.normalize(new_username))
|
||||||
if similar.exists():
|
if similar.exists():
|
||||||
similar.delete()
|
similar.delete()
|
||||||
|
|
||||||
olduser = User.objects.get(pk=form.instance.pk)
|
olduser = User.objects.get(pk=form.instance.pk)
|
||||||
|
|
||||||
user = form.save(commit=False)
|
user = form.save(commit=False)
|
||||||
profile = profile_form.save(commit=False)
|
|
||||||
profile.user = user
|
|
||||||
profile.save()
|
|
||||||
user.save()
|
|
||||||
|
|
||||||
if olduser.email != user.email:
|
if olduser.email != user.email:
|
||||||
# If the user changed her/his email, then it is unvalidated and a confirmation link is sent.
|
# If the user changed her/his email, then it is unvalidated and a confirmation link is sent.
|
||||||
user.profile.email_confirmed = False
|
user.profile.email_confirmed = False
|
||||||
user.profile.save()
|
|
||||||
user.profile.send_email_validation_link()
|
user.profile.send_email_validation_link()
|
||||||
|
|
||||||
|
profile = profile_form.save(commit=False)
|
||||||
|
profile.user = user
|
||||||
|
profile.save()
|
||||||
|
user.save()
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_success_url(self, **kwargs):
|
def get_success_url(self, **kwargs):
|
||||||
|
@ -127,7 +130,7 @@ class UserUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
|
|
||||||
class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
"""
|
"""
|
||||||
Affiche les informations sur un utilisateur, sa note, ses clubs...
|
Display all information about a user.
|
||||||
"""
|
"""
|
||||||
model = User
|
model = User
|
||||||
context_object_name = "user_object"
|
context_object_name = "user_object"
|
||||||
|
@ -141,6 +144,9 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
return super().get_queryset().filter(profile__registration_valid=True)
|
return super().get_queryset().filter(profile__registration_valid=True)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Add history of transaction and list of membership of user.
|
||||||
|
"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
user = context['user_object']
|
user = context['user_object']
|
||||||
history_list = \
|
history_list = \
|
||||||
|
@ -356,22 +362,26 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
extra_context = {"title": _("Club detail")}
|
extra_context = {"title": _("Club detail")}
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Add list of managers (peoples with Permission/Roles in this club), history of transactions and members list
|
||||||
|
"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
club = context["club"]
|
club = context["club"]
|
||||||
if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
|
if PermissionBackend.check_perm(self.request.user, "member.change_club_membership_start", club):
|
||||||
club.update_membership_dates()
|
club.update_membership_dates()
|
||||||
|
# managers list
|
||||||
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club")\
|
managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club")\
|
||||||
.order_by('user__last_name').all()
|
.order_by('user__last_name').all()
|
||||||
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
|
context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
|
||||||
|
# transaction history
|
||||||
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
|
club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
.filter(PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
||||||
.order_by('-created_at')
|
.order_by('-created_at')
|
||||||
history_table = HistoryTable(club_transactions, prefix="history-")
|
history_table = HistoryTable(club_transactions, prefix="history-")
|
||||||
history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
|
history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
|
||||||
context['history_list'] = history_table
|
context['history_list'] = history_table
|
||||||
|
# member list
|
||||||
club_member = Membership.objects.filter(
|
club_member = Membership.objects.filter(
|
||||||
club=club,
|
club=club,
|
||||||
date_end__gte=date.today(),
|
date_end__gte=date.today(),
|
||||||
|
@ -469,15 +479,19 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Membership can be created, or renewed
|
||||||
|
In case of creation the url is /club/<club_pk>/add_member
|
||||||
|
For a renewal it will be `club/renew_membership/<pk>`
|
||||||
|
"""
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
form = context['form']
|
form = context['form']
|
||||||
|
|
||||||
if "club_pk" in self.kwargs:
|
if "club_pk" in self.kwargs: # We create a new membership.
|
||||||
# We create a new membership.
|
|
||||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
|
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view"))\
|
||||||
.get(pk=self.kwargs["club_pk"], weiclub=None)
|
.get(pk=self.kwargs["club_pk"], weiclub=None)
|
||||||
form.fields['credit_amount'].initial = club.membership_fee_paid
|
form.fields['credit_amount'].initial = club.membership_fee_paid
|
||||||
|
# Ensure that the user is member of the parent club and all its the family tree.
|
||||||
c = club
|
c = club
|
||||||
clubs_renewal = []
|
clubs_renewal = []
|
||||||
additional_fee_renewal = 0
|
additional_fee_renewal = 0
|
||||||
|
@ -498,8 +512,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
kfet = Club.objects.get(name="Kfet")
|
kfet = Club.objects.get(name="Kfet")
|
||||||
fee += kfet.membership_fee_paid
|
fee += kfet.membership_fee_paid
|
||||||
context["total_fee"] = "{:.02f}".format(fee / 100, )
|
context["total_fee"] = "{:.02f}".format(fee / 100, )
|
||||||
else:
|
else: # This is a renewal. Fields can be pre-completed.
|
||||||
# This is a renewal. Fields can be pre-completed.
|
|
||||||
context["renewal"] = True
|
context["renewal"] = True
|
||||||
|
|
||||||
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
||||||
|
@ -511,6 +524,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
additional_fee_renewal = 0
|
additional_fee_renewal = 0
|
||||||
while c.parent_club is not None:
|
while c.parent_club is not None:
|
||||||
c = c.parent_club
|
c = c.parent_club
|
||||||
|
# check if a valid membership exists for the parent club
|
||||||
if c.membership_start and not Membership.objects.filter(
|
if c.membership_start and not Membership.objects.filter(
|
||||||
club=c,
|
club=c,
|
||||||
user=user,
|
user=user,
|
||||||
|
@ -529,7 +543,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
form.fields['last_name'].initial = user.last_name
|
form.fields['last_name'].initial = user.last_name
|
||||||
form.fields['first_name'].initial = user.first_name
|
form.fields['first_name'].initial = user.first_name
|
||||||
|
|
||||||
# If this is a renewal of a BDE membership, Société générale can pays, if it is not yet done
|
# If this is a renewal of a BDE membership, Société générale can pays, if it has not been already done.
|
||||||
if (club.name != "BDE" and club.name != "Kfet") or user.profile.soge:
|
if (club.name != "BDE" and club.name != "Kfet") or user.profile.soge:
|
||||||
del form.fields['soge']
|
del form.fields['soge']
|
||||||
else:
|
else:
|
||||||
|
@ -559,11 +573,11 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
Create membership, check that all is good, make transactions
|
Create membership, check that all is good, make transactions
|
||||||
"""
|
"""
|
||||||
# Get the club that is concerned by the membership
|
# Get the club that is concerned by the membership
|
||||||
if "club_pk" in self.kwargs:
|
if "club_pk" in self.kwargs: # get from url of new membership
|
||||||
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
club = Club.objects.filter(PermissionBackend.filter_queryset(self.request.user, Club, "view")) \
|
||||||
.get(pk=self.kwargs["club_pk"])
|
.get(pk=self.kwargs["club_pk"])
|
||||||
user = form.instance.user
|
user = form.instance.user
|
||||||
else:
|
else: # get from url for renewal
|
||||||
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
old_membership = self.get_queryset().get(pk=self.kwargs["pk"])
|
||||||
club = old_membership.club
|
club = old_membership.club
|
||||||
user = old_membership.user
|
user = old_membership.user
|
||||||
|
@ -572,6 +586,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
|
|
||||||
# Get form data
|
# Get form data
|
||||||
credit_type = form.cleaned_data["credit_type"]
|
credit_type = form.cleaned_data["credit_type"]
|
||||||
|
# but with this way users can customize their section as they want.
|
||||||
credit_amount = form.cleaned_data["credit_amount"]
|
credit_amount = form.cleaned_data["credit_amount"]
|
||||||
last_name = form.cleaned_data["last_name"]
|
last_name = form.cleaned_data["last_name"]
|
||||||
first_name = form.cleaned_data["first_name"]
|
first_name = form.cleaned_data["first_name"]
|
||||||
|
@ -589,6 +604,7 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
|
|
||||||
fee = 0
|
fee = 0
|
||||||
c = club
|
c = club
|
||||||
|
# collect the fees required to be paid
|
||||||
while c is not None and c.membership_start:
|
while c is not None and c.membership_start:
|
||||||
if not Membership.objects.filter(
|
if not Membership.objects.filter(
|
||||||
club=c,
|
club=c,
|
||||||
|
@ -632,9 +648,8 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
# Now, all is fine, the membership can be created.
|
# Now, all is fine, the membership can be created.
|
||||||
|
|
||||||
if club.name == "BDE" or club.name == "Kfet":
|
if club.name == "BDE" or club.name == "Kfet":
|
||||||
# When we renew the BDE membership, we update the profile section.
|
# When we renew the BDE membership, we update the profile section
|
||||||
# We could automate that and remove the section field from the Profile model,
|
# that should happens at least once a year.
|
||||||
# but with this way users can customize their section as they want.
|
|
||||||
user.profile.section = user.profile.section_generated
|
user.profile.section = user.profile.section_generated
|
||||||
user.profile.save()
|
user.profile.save()
|
||||||
|
|
||||||
|
|
|
@ -10,15 +10,6 @@ from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput
|
||||||
from .models import TransactionTemplate, NoteClub, Alias
|
from .models import TransactionTemplate, NoteClub, Alias
|
||||||
|
|
||||||
|
|
||||||
class ImageForm(forms.Form):
|
|
||||||
image = forms.ImageField(required=False,
|
|
||||||
label=_('select an image'),
|
|
||||||
help_text=_('Maximal size: 2MB'))
|
|
||||||
x = forms.FloatField(widget=forms.HiddenInput())
|
|
||||||
y = forms.FloatField(widget=forms.HiddenInput())
|
|
||||||
width = forms.FloatField(widget=forms.HiddenInput())
|
|
||||||
height = forms.FloatField(widget=forms.HiddenInput())
|
|
||||||
|
|
||||||
|
|
||||||
class TransactionTemplateForm(forms.ModelForm):
|
class TransactionTemplateForm(forms.ModelForm):
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
|
{# Select amount to transfert in € #}
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input class="form-control mx-auto d-block" type="number" {% if not widget.attrs.negative %}min="0"{% endif %} step="0.01"
|
<input class="form-control mx-auto d-block" type="number" {% if not widget.attrs.negative %}min="0"{% endif %} step="0.01"
|
||||||
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
|
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
|
||||||
name="{{ widget.name }}"
|
name="{{ widget.name }}"
|
||||||
{% for name, value in widget.attrs.items %}
|
{# Other attributes are loaded #}
|
||||||
|
{% for name, value in widget.attrs.items %}
|
||||||
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
|
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
|
||||||
{% endfor %}>
|
{% endfor %}>
|
||||||
<div class="input-group-append">
|
<div class="input-group-append">
|
||||||
<span class="input-group-text">€</span>
|
<span class="input-group-text">€</span>
|
||||||
</div>
|
</div>
|
||||||
<p id="amount-required" class="invalid-feedback"></p>
|
<p id="amount-required" class="invalid-feedback"></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -45,7 +45,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# Summary of consumption and consume button #}
|
||||||
<div class="col-xl-5 d-none" id="consos_list_div">
|
<div class="col-xl-5 d-none" id="consos_list_div">
|
||||||
<div class="card border-info shadow mb-4">
|
<div class="card border-info shadow mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
@ -91,7 +91,6 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{# Regroup buttons under categories #}
|
{# Regroup buttons under categories #}
|
||||||
{# {% regroup transaction_templates by category as categories %} #}
|
|
||||||
|
|
||||||
<div class="card border-primary text-center shadow mb-4">
|
<div class="card border-primary text-center shadow mb-4">
|
||||||
{# Tabs for button categories #}
|
{# Tabs for button categories #}
|
||||||
|
@ -148,7 +147,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# history of transaction #}
|
||||||
<div class="card shadow mb-4" id="history">
|
<div class="card shadow mb-4" id="history">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<p class="card-text font-weight-bold">
|
<p class="card-text font-weight-bold">
|
||||||
|
|
|
@ -6,7 +6,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
{% load i18n static django_tables2 perms %}
|
{% load i18n static django_tables2 perms %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
{# bandeau transfert/crédit/débit/activité #}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-xl-12">
|
<div class="col-xl-12">
|
||||||
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0" data-toggle="buttons">
|
<div class="btn-group btn-group-toggle" style="width: 100%; padding: 0 0 2em 0" data-toggle="buttons">
|
||||||
|
@ -34,8 +34,8 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
{# Preview note profile (picture, username and balance) #}
|
||||||
<div class="col-md-3" id="note_infos_div">
|
<div class="col-md-3" id="note_infos_div">
|
||||||
<div class="card border-success shadow mb-4">
|
<div class="card border-success shadow mb-4">
|
||||||
<a id="profile_pic_link" href="#"><img src="/media/pic/default.png"
|
<a id="profile_pic_link" href="#"><img src="/media/pic/default.png"
|
||||||
|
@ -45,7 +45,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# list of emitters #}
|
||||||
<div class="col-md-3" id="emitters_div">
|
<div class="col-md-3" id="emitters_div">
|
||||||
<div class="card border-success shadow mb-4">
|
<div class="card border-success shadow mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
@ -66,7 +66,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# list of receiver #}
|
||||||
<div class="col-md-3" id="dests_div">
|
<div class="col-md-3" id="dests_div">
|
||||||
<div class="card border-info shadow mb-4">
|
<div class="card border-info shadow mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
@ -83,7 +83,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# Information on transaction (amount, reason, name,...) #}
|
||||||
<div class="col-md-3" id="external_div">
|
<div class="col-md-3" id="external_div">
|
||||||
<div class="card border-warning shadow mb-4">
|
<div class="card border-warning shadow mb-4">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
@ -108,7 +108,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
<p id="reason-required" class="invalid-feedback"></p>
|
<p id="reason-required" class="invalid-feedback"></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# in case of special transaction add identity information #}
|
||||||
<div class="d-none" id="special_transaction_div">
|
<div class="d-none" id="special_transaction_div">
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
|
@ -149,7 +149,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{# transaction history #}
|
||||||
<div class="card shadow mb-4" id="history">
|
<div class="card shadow mb-4" id="history">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<p class="card-text font-weight-bold">
|
<p class="card-text font-weight-bold">
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row justify-content-center mb-4">
|
<div class="row justify-content-center mb-4">
|
||||||
<div class="col-md-10 text-center">
|
<div class="col-md-10 text-center">
|
||||||
|
{# Search field , see js #}
|
||||||
<input class="form-control mx-auto w-25" type="text" id="search_field" placeholder="{% trans "Name of the button..." %}" value="{{ request.GET.search }}">
|
<input class="form-control mx-auto w-25" type="text" id="search_field" placeholder="{% trans "Name of the button..." %}" value="{{ request.GET.search }}">
|
||||||
<hr>
|
<hr>
|
||||||
<a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}" data-turbolinks="false">{% trans "New button" %}</a>
|
<a class="btn btn-primary text-center my-1" href="{% url 'note:template_create' %}" data-turbolinks="false">{% trans "New button" %}</a>
|
||||||
|
|
|
@ -29,21 +29,19 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl
|
||||||
e.g. for donation/transfer between people and clubs or for credit/debit with :models:`note.NoteSpecial`
|
e.g. for donation/transfer between people and clubs or for credit/debit with :models:`note.NoteSpecial`
|
||||||
"""
|
"""
|
||||||
template_name = "note/transaction_form.html"
|
template_name = "note/transaction_form.html"
|
||||||
|
# SingleTableView creates `context["table"]` we will load it with transaction history
|
||||||
model = Transaction
|
model = Transaction
|
||||||
# Transaction history table
|
# Transaction history table
|
||||||
table_class = HistoryTable
|
table_class = HistoryTable
|
||||||
extra_context = {"title": _("Transfer money")}
|
extra_context = {"title": _("Transfer money")}
|
||||||
|
|
||||||
def get_queryset(self, **kwargs):
|
def get_queryset(self, **kwargs):
|
||||||
|
# retrieves only Transaction that user has the right to see.
|
||||||
return Transaction.objects.filter(
|
return Transaction.objects.filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
|
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
|
||||||
).order_by("-created_at").all()[:20]
|
).order_by("-created_at").all()[:20]
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""
|
|
||||||
Add some context variables in template such as page title
|
|
||||||
"""
|
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
context['amount_widget'] = AmountInput(attrs={"id": "amount"})
|
context['amount_widget'] = AmountInput(attrs={"id": "amount"})
|
||||||
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(Transaction).pk
|
||||||
|
@ -146,7 +144,7 @@ class TransactionTemplateUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, Up
|
||||||
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||||
"""
|
"""
|
||||||
The Magic View that make people pay their beer and burgers.
|
The Magic View that make people pay their beer and burgers.
|
||||||
(Most of the magic happens in the dark world of Javascript see consos.js)
|
(Most of the magic happens in the dark world of Javascript see `note_kfet/static/js/consos.js`)
|
||||||
"""
|
"""
|
||||||
model = Transaction
|
model = Transaction
|
||||||
template_name = "note/conso_form.html"
|
template_name = "note/conso_form.html"
|
||||||
|
@ -168,29 +166,30 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||||
return super().dispatch(request, *args, **kwargs)
|
return super().dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
def get_queryset(self, **kwargs):
|
def get_queryset(self, **kwargs):
|
||||||
|
"""
|
||||||
|
restrict to the transaction history the user can see.
|
||||||
|
"""
|
||||||
return Transaction.objects.filter(
|
return Transaction.objects.filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
|
PermissionBackend.filter_queryset(self.request.user, Transaction, "view")
|
||||||
).order_by("-created_at").all()[:20]
|
).order_by("-created_at").all()[:20]
|
||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
"""
|
|
||||||
Add some context variables in template such as page title
|
|
||||||
"""
|
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
categories = TemplateCategory.objects.order_by('name').all()
|
categories = TemplateCategory.objects.order_by('name').all()
|
||||||
|
# for each category, find which transaction templates the user can see.
|
||||||
for category in categories:
|
for category in categories:
|
||||||
category.templates_filtered = category.templates.filter(
|
category.templates_filtered = category.templates.filter(
|
||||||
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
||||||
).filter(display=True).order_by('name').all()
|
).filter(display=True).order_by('name').all()
|
||||||
|
|
||||||
context['categories'] = [cat for cat in categories if cat.templates_filtered]
|
context['categories'] = [cat for cat in categories if cat.templates_filtered]
|
||||||
|
# some transactiontemplate are put forward to find them easily
|
||||||
context['highlighted'] = TransactionTemplate.objects.filter(highlighted=True).filter(
|
context['highlighted'] = TransactionTemplate.objects.filter(highlighted=True).filter(
|
||||||
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
PermissionBackend().filter_queryset(self.request.user, TransactionTemplate, "view")
|
||||||
).order_by('name').all()
|
).order_by('name').all()
|
||||||
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
||||||
|
|
||||||
# select2 compatibility
|
|
||||||
context['no_cache'] = True
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ from .tokens import email_validation_token
|
||||||
|
|
||||||
class UserCreateView(CreateView):
|
class UserCreateView(CreateView):
|
||||||
"""
|
"""
|
||||||
Une vue pour inscrire un utilisateur et lui créer un profil
|
A view to create a User and add a Profile
|
||||||
"""
|
"""
|
||||||
|
|
||||||
form_class = SignUpForm
|
form_class = SignUpForm
|
||||||
|
|
|
@ -57,7 +57,6 @@ class InvoiceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||||
form_set = ProductFormSet(instance=form.instance)
|
form_set = ProductFormSet(instance=form.instance)
|
||||||
context['formset'] = form_set
|
context['formset'] = form_set
|
||||||
context['helper'] = ProductFormSetHelper()
|
context['helper'] = ProductFormSetHelper()
|
||||||
context['no_cache'] = True
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -125,7 +124,6 @@ class InvoiceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||||
form_set = ProductFormSet(instance=self.object)
|
form_set = ProductFormSet(instance=self.object)
|
||||||
context['formset'] = form_set
|
context['formset'] = form_set
|
||||||
context['helper'] = ProductFormSetHelper()
|
context['helper'] = ProductFormSetHelper()
|
||||||
context['no_cache'] = True
|
|
||||||
|
|
||||||
if self.object.locked:
|
if self.object.locked:
|
||||||
for field_name in form.fields:
|
for field_name in form.fields:
|
||||||
|
|
|
@ -22,12 +22,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
<meta name="msapplication-TileColor" content="#da532c">
|
<meta name="msapplication-TileColor" content="#da532c">
|
||||||
<meta name="msapplication-config" content="{% static "favicon/browserconfig.xml" %}">
|
<meta name="msapplication-config" content="{% static "favicon/browserconfig.xml" %}">
|
||||||
<meta name="theme-color" content="#ffffff">
|
<meta name="theme-color" content="#ffffff">
|
||||||
|
|
||||||
{# Disable turbolink cache for some pages #}
|
|
||||||
{% if no_cache %}
|
|
||||||
<meta name="turbolinks-cache-control" content="no-cache">
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# Bootstrap CSS #}
|
{# Bootstrap CSS #}
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
||||||
|
|
Loading…
Reference in New Issue