Merge branch 'documents' into beta

This commit is contained in:
Pierre-antoine Comby 2020-08-19 13:18:12 +02:00
commit 6ea92cdcde
13 changed files with 143 additions and 88 deletions

View File

@ -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):

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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:

View File

@ -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>

View File

@ -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">

View File

@ -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">

View File

@ -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>

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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"