mirror of https://gitlab.crans.org/bde/nk20
Compare commits
10 Commits
66defee3ea
...
ba067f050e
Author | SHA1 | Date |
---|---|---|
Yohann D'ANELLO | ba067f050e | |
Yohann D'ANELLO | 2a744a8610 | |
Yohann D'ANELLO | 0e8058ab0d | |
Yohann D'ANELLO | 655390b265 | |
Yohann D'ANELLO | 985a5ca876 | |
Yohann D'ANELLO | 55580bc11e | |
Yohann D'ANELLO | 5ea8d8f870 | |
Yohann D'ANELLO | 0a2c9d9c87 | |
Yohann D'ANELLO | 208dc7f865 | |
Yohann D'ANELLO | fbf3a0bcf6 |
|
@ -114,28 +114,31 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request.user, Activity, "view"))\
|
||||||
.get(pk=self.kwargs["pk"])
|
.distinct().get(pk=self.kwargs["pk"])
|
||||||
context["activity"] = activity
|
context["activity"] = activity
|
||||||
|
|
||||||
matched = []
|
matched = []
|
||||||
|
|
||||||
pattern = "^$"
|
|
||||||
if "search" in self.request.GET:
|
|
||||||
pattern = self.request.GET["search"]
|
|
||||||
|
|
||||||
if not pattern:
|
|
||||||
pattern = "^$"
|
|
||||||
|
|
||||||
if pattern[0] != "^":
|
|
||||||
pattern = "^" + pattern
|
|
||||||
|
|
||||||
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"))\
|
||||||
.filter(Q(first_name__regex=pattern) | Q(last_name__regex=pattern)
|
.filter(activity=activity)\
|
||||||
| Q(inviter__alias__name__regex=pattern)
|
|
||||||
| Q(inviter__alias__normalized_name__regex=Alias.normalize(pattern))) \
|
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\
|
.filter(PermissionBackend.filter_queryset(self.request.user, Guest, "view"))\
|
||||||
.distinct()[:20]
|
.order_by('last_name', 'first_name').distinct()
|
||||||
|
|
||||||
|
if "search" in self.request.GET:
|
||||||
|
pattern = self.request.GET["search"]
|
||||||
|
if pattern[0] != "^":
|
||||||
|
pattern = "^" + pattern
|
||||||
|
guest_qs = guest_qs.filter(
|
||||||
|
Q(first_name__regex=pattern)
|
||||||
|
| Q(last_name__regex=pattern)
|
||||||
|
| Q(inviter__alias__name__regex=pattern)
|
||||||
|
| Q(inviter__alias__normalized_name__regex=Alias.normalize(pattern))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
pattern = None
|
||||||
|
guest_qs = guest_qs.none()
|
||||||
|
|
||||||
for guest in guest_qs:
|
for guest in guest_qs:
|
||||||
guest.type = "Invité"
|
guest.type = "Invité"
|
||||||
matched.append(guest)
|
matched.append(guest)
|
||||||
|
@ -145,12 +148,18 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
username=F("note__noteuser__user__username"),
|
username=F("note__noteuser__user__username"),
|
||||||
note_name=F("name"),
|
note_name=F("name"),
|
||||||
balance=F("note__balance"))\
|
balance=F("note__balance"))\
|
||||||
.filter(Q(note__polymorphic_ctype__model="noteuser")
|
.filter(note__polymorphic_ctype__model="noteuser")\
|
||||||
& (Q(note__noteuser__user__first_name__regex=pattern)
|
|
||||||
| Q(note__noteuser__user__last_name__regex=pattern)
|
|
||||||
| Q(name__regex=pattern)
|
|
||||||
| Q(normalized_name__regex=Alias.normalize(pattern)))) \
|
|
||||||
.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
|
.filter(PermissionBackend.filter_queryset(self.request.user, Alias, "view"))
|
||||||
|
if pattern:
|
||||||
|
note_qs = note_qs.filter(
|
||||||
|
Q(note__noteuser__user__first_name__regex=pattern)
|
||||||
|
| Q(note__noteuser__user__last_name__regex=pattern)
|
||||||
|
| Q(name__regex=pattern)
|
||||||
|
| Q(normalized_name__regex=Alias.normalize(pattern))
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
note_qs = note_qs.none()
|
||||||
|
|
||||||
if settings.DATABASES[note_qs.db]["ENGINE"] == 'django.db.backends.postgresql_psycopg2':
|
if settings.DATABASES[note_qs.db]["ENGINE"] == 'django.db.backends.postgresql_psycopg2':
|
||||||
note_qs = note_qs.distinct('note__pk')[:20]
|
note_qs = note_qs.distinct('note__pk')[:20]
|
||||||
else:
|
else:
|
||||||
|
@ -158,6 +167,7 @@ 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]
|
||||||
|
|
||||||
for note in note_qs:
|
for note in note_qs:
|
||||||
note.type = "Adhérent"
|
note.type = "Adhérent"
|
||||||
note.activity = activity
|
note.activity = activity
|
||||||
|
@ -172,8 +182,11 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
|
||||||
context["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk
|
context["noteuser_ctype"] = ContentType.objects.get_for_model(NoteUser).pk
|
||||||
context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
|
context["notespecial_ctype"] = ContentType.objects.get_for_model(NoteSpecial).pk
|
||||||
|
|
||||||
context["activities_open"] = Activity.objects.filter(open=True).filter(
|
activities_open = Activity.objects.filter(open=True).filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Activity, "view")).filter(
|
PermissionBackend.filter_queryset(self.request.user, Activity, "view")).distinct().all()
|
||||||
PermissionBackend.filter_queryset(self.request.user, Activity, "change")).all()
|
context["activities_open"] = [a for a in activities_open
|
||||||
|
if PermissionBackend.check_perm(self.request.user,
|
||||||
|
"activity.add_entry",
|
||||||
|
Entry(activity=a, note=self.request.user.note,))]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
|
@ -18,7 +18,7 @@ class ReadProtectedModelViewSet(viewsets.ModelViewSet):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = get_current_authenticated_user()
|
user = get_current_authenticated_user()
|
||||||
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))
|
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
||||||
|
|
||||||
|
|
||||||
class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
|
class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
@ -32,4 +32,4 @@ class ReadOnlyProtectedModelViewSet(viewsets.ReadOnlyModelViewSet):
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
user = get_current_authenticated_user()
|
user = get_current_authenticated_user()
|
||||||
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))
|
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view")).distinct()
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
|
from django.forms import CheckboxSelectMultiple
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note.models import NoteSpecial, Alias
|
from note.models import NoteSpecial, Alias
|
||||||
from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
|
from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
|
||||||
|
@ -151,6 +152,7 @@ class MembershipRolesForm(forms.ModelForm):
|
||||||
roles = forms.ModelMultipleChoiceField(
|
roles = forms.ModelMultipleChoiceField(
|
||||||
queryset=Role.objects.filter(weirole=None).all(),
|
queryset=Role.objects.filter(weirole=None).all(),
|
||||||
label=_("Roles"),
|
label=_("Roles"),
|
||||||
|
widget=CheckboxSelectMultiple(),
|
||||||
)
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
|
@ -139,7 +139,7 @@ class Profile(models.Model):
|
||||||
'token': email_validation_token.make_token(self.user),
|
'token': email_validation_token.make_token(self.user),
|
||||||
'uid': urlsafe_base64_encode(force_bytes(self.user.pk)),
|
'uid': urlsafe_base64_encode(force_bytes(self.user.pk)),
|
||||||
})
|
})
|
||||||
html = loader.render_to_string('registration/mails/email_validation_email.txt',
|
html = loader.render_to_string('registration/mails/email_validation_email.html',
|
||||||
{
|
{
|
||||||
'user': self.user,
|
'user': self.user,
|
||||||
'domain': os.getenv("NOTE_URL", "note.example.com"),
|
'domain': os.getenv("NOTE_URL", "note.example.com"),
|
||||||
|
|
|
@ -9,6 +9,8 @@ from rest_framework import viewsets
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||||
|
from note_kfet.middlewares import get_current_authenticated_user
|
||||||
|
from permission.backends import PermissionBackend
|
||||||
|
|
||||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer
|
||||||
|
@ -150,3 +152,7 @@ class TransactionViewSet(ReadProtectedModelViewSet):
|
||||||
serializer_class = TransactionPolymorphicSerializer
|
serializer_class = TransactionPolymorphicSerializer
|
||||||
filter_backends = [SearchFilter]
|
filter_backends = [SearchFilter]
|
||||||
search_fields = ['$reason', ]
|
search_fields = ['$reason', ]
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
user = get_current_authenticated_user()
|
||||||
|
return self.model.objects.filter(PermissionBackend.filter_queryset(user, self.model, "view"))
|
||||||
|
|
|
@ -3,10 +3,11 @@
|
||||||
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.forms import CheckboxSelectMultiple
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note_kfet.inputs import Autocomplete, AmountInput
|
from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput
|
||||||
|
|
||||||
from .models import TransactionTemplate, NoteClub
|
from .models import TransactionTemplate, NoteClub, Alias
|
||||||
|
|
||||||
|
|
||||||
class ImageForm(forms.Form):
|
class ImageForm(forms.Form):
|
||||||
|
@ -38,3 +39,80 @@ class TransactionTemplateForm(forms.ModelForm):
|
||||||
),
|
),
|
||||||
'amount': AmountInput(),
|
'amount': AmountInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SearchTransactionForm(forms.Form):
|
||||||
|
source = forms.ModelChoiceField(
|
||||||
|
queryset=Alias.objects.all(),
|
||||||
|
label=_("Source"),
|
||||||
|
required=False,
|
||||||
|
widget=Autocomplete(
|
||||||
|
Alias,
|
||||||
|
resetable=True,
|
||||||
|
attrs={
|
||||||
|
'api_url': '/api/note/alias/',
|
||||||
|
'placeholder': 'Note ...',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
destination = forms.ModelChoiceField(
|
||||||
|
queryset=Alias.objects.all(),
|
||||||
|
label=_("Destination"),
|
||||||
|
required=False,
|
||||||
|
widget=Autocomplete(
|
||||||
|
Alias,
|
||||||
|
resetable=True,
|
||||||
|
attrs={
|
||||||
|
'api_url': '/api/note/alias/',
|
||||||
|
'placeholder': 'Note ...',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
type = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=ContentType.objects.filter(app_label="note", model__endswith="transaction"),
|
||||||
|
initial=ContentType.objects.filter(app_label="note", model__endswith="transaction"),
|
||||||
|
label=_("Type"),
|
||||||
|
required=False,
|
||||||
|
widget=CheckboxSelectMultiple(),
|
||||||
|
)
|
||||||
|
|
||||||
|
reason = forms.CharField(
|
||||||
|
label=_("Reason"),
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
valid = forms.BooleanField(
|
||||||
|
label=_("Valid"),
|
||||||
|
initial=False,
|
||||||
|
required=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
amount_gte = forms.Field(
|
||||||
|
label=_("Total amount greater than"),
|
||||||
|
initial=0,
|
||||||
|
required=False,
|
||||||
|
widget=AmountInput(),
|
||||||
|
)
|
||||||
|
|
||||||
|
amount_lte = forms.Field(
|
||||||
|
initial=2 ** 31 - 1,
|
||||||
|
label=_("Total amount less than"),
|
||||||
|
required=False,
|
||||||
|
widget=AmountInput(),
|
||||||
|
)
|
||||||
|
|
||||||
|
created_after = forms.Field(
|
||||||
|
label=_("Created after"),
|
||||||
|
initial="2000-01-01 00:00",
|
||||||
|
required=False,
|
||||||
|
widget=DateTimePickerInput(),
|
||||||
|
)
|
||||||
|
|
||||||
|
created_before = forms.Field(
|
||||||
|
label=_("Created before"),
|
||||||
|
initial="2042-12-31 21:42",
|
||||||
|
required=False,
|
||||||
|
widget=DateTimePickerInput(),
|
||||||
|
)
|
||||||
|
|
|
@ -243,6 +243,7 @@ class RecurrentTransaction(Transaction):
|
||||||
TransactionTemplate,
|
TransactionTemplate,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
)
|
)
|
||||||
|
|
||||||
category = models.ForeignKey(
|
category = models.ForeignKey(
|
||||||
TemplateCategory,
|
TemplateCategory,
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
|
@ -252,6 +253,10 @@ class RecurrentTransaction(Transaction):
|
||||||
def type(self):
|
def type(self):
|
||||||
return _('Template')
|
return _('Template')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("recurrent transaction")
|
||||||
|
verbose_name_plural = _("recurrent transactions")
|
||||||
|
|
||||||
|
|
||||||
class SpecialTransaction(Transaction):
|
class SpecialTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
|
@ -290,6 +295,10 @@ class SpecialTransaction(Transaction):
|
||||||
raise(ValidationError(_("A special transaction is only possible between a"
|
raise(ValidationError(_("A special transaction is only possible between a"
|
||||||
" Note associated to a payment method and a User or a Club")))
|
" Note associated to a payment method and a User or a Club")))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = _("Special transaction")
|
||||||
|
verbose_name_plural = _("Special transactions")
|
||||||
|
|
||||||
|
|
||||||
class MembershipTransaction(Transaction):
|
class MembershipTransaction(Transaction):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -12,4 +12,5 @@ urlpatterns = [
|
||||||
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
|
path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
|
||||||
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
|
path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
|
||||||
path('consos/', views.ConsoView.as_view(), name='consos'),
|
path('consos/', views.ConsoView.as_view(), name='consos'),
|
||||||
|
path('transactions/<int:pk>/', views.TransactionSearchView.as_view(), name='transactions'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,17 +5,19 @@ import json
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.db.models import Q
|
from django.db.models import Q, F
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.views.generic import CreateView, UpdateView
|
from django.views.generic import CreateView, UpdateView, DetailView
|
||||||
from django_tables2 import SingleTableView
|
from django_tables2 import SingleTableView
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
|
|
||||||
|
from activity.models import Entry
|
||||||
from note_kfet.inputs import AmountInput
|
from note_kfet.inputs import AmountInput
|
||||||
from permission.backends import PermissionBackend
|
from permission.backends import PermissionBackend
|
||||||
from permission.views import ProtectQuerysetMixin
|
from permission.views import ProtectQuerysetMixin
|
||||||
|
|
||||||
from .forms import TransactionTemplateForm
|
from .forms import TransactionTemplateForm, SearchTransactionForm
|
||||||
from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial
|
from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note
|
||||||
from .models.transactions import SpecialTransaction
|
from .models.transactions import SpecialTransaction
|
||||||
from .tables import HistoryTable, ButtonTable
|
from .tables import HistoryTable, ButtonTable
|
||||||
|
|
||||||
|
@ -52,9 +54,13 @@ class TransactionCreateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTabl
|
||||||
# Add a shortcut for entry page for open activities
|
# Add a shortcut for entry page for open activities
|
||||||
if "activity" in settings.INSTALLED_APPS:
|
if "activity" in settings.INSTALLED_APPS:
|
||||||
from activity.models import Activity
|
from activity.models import Activity
|
||||||
context["activities_open"] = Activity.objects.filter(open=True).filter(
|
activities_open = Activity.objects.filter(open=True).filter(
|
||||||
PermissionBackend.filter_queryset(self.request.user, Activity, "view")).filter(
|
PermissionBackend.filter_queryset(self.request.user, Activity, "view")).distinct().all()
|
||||||
PermissionBackend.filter_queryset(self.request.user, Activity, "change")).all()
|
context["activities_open"] = [a for a in activities_open
|
||||||
|
if PermissionBackend.check_perm(self.request.user,
|
||||||
|
"activity.add_entry",
|
||||||
|
Entry(activity=a,
|
||||||
|
note=self.request.user.note, ))]
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
@ -165,3 +171,51 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||||
context['no_cache'] = True
|
context['no_cache'] = True
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class TransactionSearchView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
|
model = Note
|
||||||
|
context_object_name = "note"
|
||||||
|
template_name = "note/search_transactions.html"
|
||||||
|
extra_context = {"title": _("Search transactions")}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
|
form = SearchTransactionForm(data=self.request.GET if self.request.GET else None)
|
||||||
|
context["form"] = form
|
||||||
|
|
||||||
|
form.full_clean()
|
||||||
|
if form.is_valid():
|
||||||
|
data = form.cleaned_data
|
||||||
|
else:
|
||||||
|
data = {}
|
||||||
|
|
||||||
|
transactions = Transaction.objects.annotate(total_amount=F("quantity") * F("amount")).filter(
|
||||||
|
PermissionBackend.filter_queryset(self.request.user, Transaction, "view"))\
|
||||||
|
.filter(Q(source=self.object) | Q(destination=self.object)).order_by('-created_at')
|
||||||
|
|
||||||
|
if "source" in data and data["source"]:
|
||||||
|
transactions = transactions.filter(source_id=data["source"].note_id)
|
||||||
|
if "destination" in data and data["destination"]:
|
||||||
|
transactions = transactions.filter(destination_id=data["destination"].note_id)
|
||||||
|
if "type" in data and data["type"]:
|
||||||
|
transactions = transactions.filter(polymorphic_ctype__in=data["type"])
|
||||||
|
if "reason" in data and data["reason"]:
|
||||||
|
transactions = transactions.filter(reason__iregex=data["reason"])
|
||||||
|
if "valid" in data and data["valid"]:
|
||||||
|
transactions = transactions.filter(valid=data["valid"])
|
||||||
|
if "amount_gte" in data and data["amount_gte"]:
|
||||||
|
transactions = transactions.filter(total_amount__gte=data["amount_gte"])
|
||||||
|
if "amount_lte" in data and data["amount_lte"]:
|
||||||
|
transactions = transactions.filter(total_amount__lte=data["amount_lte"])
|
||||||
|
if "created_after" in data and data["created_after"]:
|
||||||
|
transactions = transactions.filter(created_at__gte=data["created_after"])
|
||||||
|
if "created_before" in data and data["created_before"]:
|
||||||
|
transactions = transactions.filter(created_at__lte=data["created_before"])
|
||||||
|
|
||||||
|
table = HistoryTable(transactions)
|
||||||
|
table.paginate(per_page=100)
|
||||||
|
context["table"] = table
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
|
@ -2311,6 +2311,38 @@
|
||||||
"description": "Ajouter un membre à n'importe quel club"
|
"description": "Ajouter un membre à n'importe quel club"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 148,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"activity",
|
||||||
|
"activity"
|
||||||
|
],
|
||||||
|
"query": "{\"valid\": false}",
|
||||||
|
"type": "change",
|
||||||
|
"mask": 2,
|
||||||
|
"field": "",
|
||||||
|
"permanent": false,
|
||||||
|
"description": "Modifier une activité non validée"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"model": "permission.permission",
|
||||||
|
"pk": 149,
|
||||||
|
"fields": {
|
||||||
|
"model": [
|
||||||
|
"activity",
|
||||||
|
"activity"
|
||||||
|
],
|
||||||
|
"query": "{\"valid\": false}",
|
||||||
|
"type": "delete",
|
||||||
|
"mask": 2,
|
||||||
|
"field": "",
|
||||||
|
"permanent": false,
|
||||||
|
"description": "Supprimer une activité non validée"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"model": "permission.role",
|
"model": "permission.role",
|
||||||
"pk": 1,
|
"pk": 1,
|
||||||
|
@ -2643,7 +2675,9 @@
|
||||||
144,
|
144,
|
||||||
145,
|
145,
|
||||||
146,
|
146,
|
||||||
147
|
147,
|
||||||
|
148,
|
||||||
|
149
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -2690,7 +2724,9 @@
|
||||||
43,
|
43,
|
||||||
44,
|
44,
|
||||||
45,
|
45,
|
||||||
46
|
46,
|
||||||
|
148,
|
||||||
|
149
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django import forms
|
||||||
from django.contrib.auth.forms import UserCreationForm
|
from django.contrib.auth.forms import UserCreationForm
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note.models import NoteSpecial
|
from note.models import NoteSpecial, Alias
|
||||||
from note_kfet.inputs import AmountInput
|
from note_kfet.inputs import AmountInput
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +22,12 @@ class SignUpForm(UserCreationForm):
|
||||||
self.fields['email'].required = True
|
self.fields['email'].required = True
|
||||||
self.fields['email'].help_text = _("This address must be valid.")
|
self.fields['email'].help_text = _("This address must be valid.")
|
||||||
|
|
||||||
|
def clean_username(self):
|
||||||
|
value = self.cleaned_data["username"]
|
||||||
|
if Alias.objects.filter(normalized_name=Alias.normalize(value)).exists():
|
||||||
|
self.add_error("username", _("An alias with a similar name already exists."))
|
||||||
|
return value
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = ('first_name', 'last_name', 'username', 'email', )
|
fields = ('first_name', 'last_name', 'username', 'email', )
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit f41a5a32f7417a874b497640373ea3911eb1e133
|
Subproject commit 034d8c43b663ac3a33f6e3d06bcdcbbeea0bc517
|
|
@ -4,6 +4,7 @@
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
from django.forms import CheckboxSelectMultiple
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget
|
from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget
|
||||||
|
|
||||||
|
@ -47,6 +48,7 @@ class WEIChooseBusForm(forms.Form):
|
||||||
label=_("bus"),
|
label=_("bus"),
|
||||||
help_text=_("This choice is not definitive. The WEI organizers are free to attribute for you a bus and a team,"
|
help_text=_("This choice is not definitive. The WEI organizers are free to attribute for you a bus and a team,"
|
||||||
+ " in particular if you are a free eletron."),
|
+ " in particular if you are a free eletron."),
|
||||||
|
widget=CheckboxSelectMultiple(),
|
||||||
)
|
)
|
||||||
|
|
||||||
team = forms.ModelMultipleChoiceField(
|
team = forms.ModelMultipleChoiceField(
|
||||||
|
@ -54,17 +56,24 @@ class WEIChooseBusForm(forms.Form):
|
||||||
label=_("Team"),
|
label=_("Team"),
|
||||||
required=False,
|
required=False,
|
||||||
help_text=_("Leave this field empty if you won't be in a team (staff, bus chief, free electron)"),
|
help_text=_("Leave this field empty if you won't be in a team (staff, bus chief, free electron)"),
|
||||||
|
widget=CheckboxSelectMultiple(),
|
||||||
)
|
)
|
||||||
|
|
||||||
roles = forms.ModelMultipleChoiceField(
|
roles = forms.ModelMultipleChoiceField(
|
||||||
queryset=WEIRole.objects.filter(~Q(name="1A")),
|
queryset=WEIRole.objects.filter(~Q(name="1A")),
|
||||||
label=_("WEI Roles"),
|
label=_("WEI Roles"),
|
||||||
help_text=_("Select the roles that you are interested in."),
|
help_text=_("Select the roles that you are interested in."),
|
||||||
|
initial=WEIRole.objects.filter(name="Adhérent WEI").all(),
|
||||||
|
widget=CheckboxSelectMultiple(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class WEIMembershipForm(forms.ModelForm):
|
class WEIMembershipForm(forms.ModelForm):
|
||||||
roles = forms.ModelMultipleChoiceField(queryset=WEIRole.objects, label=_("WEI Roles"))
|
roles = forms.ModelMultipleChoiceField(
|
||||||
|
queryset=WEIRole.objects,
|
||||||
|
label=_("WEI Roles"),
|
||||||
|
widget=CheckboxSelectMultiple(),
|
||||||
|
)
|
||||||
|
|
||||||
def clean(self):
|
def clean(self):
|
||||||
cleaned_data = super().clean()
|
cleaned_data = super().clean()
|
||||||
|
|
|
@ -130,7 +130,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||||
context["my_registration"] = my_registration
|
context["my_registration"] = my_registration
|
||||||
|
|
||||||
buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request.user, Bus, "view")) \
|
buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request.user, Bus, "view")) \
|
||||||
.filter(wei=self.object).annotate(count=Count("memberships"))
|
.filter(wei=self.object).annotate(count=Count("memberships")).order_by("name")
|
||||||
bus_table = BusTable(data=buses, prefix="bus-")
|
bus_table = BusTable(data=buses, prefix="bus-")
|
||||||
context['buses'] = bus_table
|
context['buses'] = bus_table
|
||||||
|
|
||||||
|
@ -527,8 +527,9 @@ class WEIRegister2AView(ProtectQuerysetMixin, LoginRequiredMixin, CreateView):
|
||||||
context["form"].fields["user"].disabled = True
|
context["form"].fields["user"].disabled = True
|
||||||
|
|
||||||
choose_bus_form = WEIChooseBusForm()
|
choose_bus_form = WEIChooseBusForm()
|
||||||
choose_bus_form.fields["bus"].queryset = Bus.objects.filter(wei=context["club"])
|
choose_bus_form.fields["bus"].queryset = Bus.objects.filter(wei=context["club"]).order_by('name')
|
||||||
choose_bus_form.fields["team"].queryset = BusTeam.objects.filter(bus__wei=context["club"])
|
choose_bus_form.fields["team"].queryset = BusTeam.objects.filter(bus__wei=context["club"])\
|
||||||
|
.order_by('bus__name', 'name')
|
||||||
context['membership_form'] = choose_bus_form
|
context['membership_form'] = choose_bus_form
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
@ -588,9 +589,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
|
||||||
form_class = WEIRegistrationForm
|
form_class = WEIRegistrationForm
|
||||||
extra_context = {"title": _("Update WEI Registration")}
|
extra_context = {"title": _("Update WEI Registration")}
|
||||||
|
|
||||||
def get_queryset(self, **kwargs):
|
|
||||||
return WEIRegistration.objects
|
|
||||||
|
|
||||||
def dispatch(self, request, *args, **kwargs):
|
def dispatch(self, request, *args, **kwargs):
|
||||||
wei = self.get_object().wei
|
wei = self.get_object().wei
|
||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -23,10 +23,11 @@ class AmountInput(NumberInput):
|
||||||
class Autocomplete(TextInput):
|
class Autocomplete(TextInput):
|
||||||
template_name = "member/autocomplete_model.html"
|
template_name = "member/autocomplete_model.html"
|
||||||
|
|
||||||
def __init__(self, model, attrs=None):
|
def __init__(self, model, resetable=False, attrs=None):
|
||||||
super().__init__(attrs)
|
super().__init__(attrs)
|
||||||
|
|
||||||
self.model = model
|
self.model = model
|
||||||
|
self.resetable = resetable
|
||||||
self.model_pk = None
|
self.model_pk = None
|
||||||
|
|
||||||
class Media:
|
class Media:
|
||||||
|
@ -34,6 +35,11 @@ class Autocomplete(TextInput):
|
||||||
|
|
||||||
js = ('js/autocomplete_model.js', )
|
js = ('js/autocomplete_model.js', )
|
||||||
|
|
||||||
|
def get_context(self, name, value, attrs):
|
||||||
|
context = super().get_context(name, value, attrs)
|
||||||
|
context['widget']['resetable'] = self.resetable
|
||||||
|
return context
|
||||||
|
|
||||||
def format_value(self, value):
|
def format_value(self, value):
|
||||||
if value:
|
if value:
|
||||||
self.attrs["model_pk"] = int(value)
|
self.attrs["model_pk"] = int(value)
|
||||||
|
|
|
@ -36,6 +36,7 @@ INSTALLED_APPS = [
|
||||||
# 'theme',
|
# 'theme',
|
||||||
|
|
||||||
# External apps
|
# External apps
|
||||||
|
'mailer',
|
||||||
'polymorphic',
|
'polymorphic',
|
||||||
'crispy_forms',
|
'crispy_forms',
|
||||||
'django_tables2',
|
'django_tables2',
|
||||||
|
|
|
@ -33,7 +33,8 @@ ALLOWED_HOSTS = [os.environ.get('NOTE_URL', 'localhost')]
|
||||||
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'CHANGE_ME_IN_ENV_SETTINGS')
|
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', 'CHANGE_ME_IN_ENV_SETTINGS')
|
||||||
|
|
||||||
# Emails
|
# Emails
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
EMAIL_BACKEND = 'mailer.backend.DbBackend' # Mailer place emails in a queue before sending them to avoid spam
|
||||||
|
MAILER_EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
EMAIL_USE_SSL = False
|
EMAIL_USE_SSL = False
|
||||||
EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.example.org')
|
EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.example.org')
|
||||||
EMAIL_PORT = os.getenv('EMAIL_PORT', 465)
|
EMAIL_PORT = os.getenv('EMAIL_PORT', 465)
|
||||||
|
|
|
@ -6,6 +6,7 @@ django-allauth==0.39.1
|
||||||
django-crispy-forms==1.7.2
|
django-crispy-forms==1.7.2
|
||||||
django-extensions==2.1.9
|
django-extensions==2.1.9
|
||||||
django-filter==2.2.0
|
django-filter==2.2.0
|
||||||
|
django-mailer==2.0.1
|
||||||
django-polymorphic==2.0.3
|
django-polymorphic==2.0.3
|
||||||
django-tables2==2.1.0
|
django-tables2==2.1.0
|
||||||
docutils==0.14
|
docutils==0.14
|
||||||
|
|
|
@ -10,6 +10,7 @@ $(document).ready(function () {
|
||||||
if (!name_field)
|
if (!name_field)
|
||||||
name_field = "name";
|
name_field = "name";
|
||||||
let input = target.val();
|
let input = target.val();
|
||||||
|
$("#" + prefix + "_reset").removeClass("d-none");
|
||||||
|
|
||||||
$.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) {
|
$.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) {
|
||||||
let html = "";
|
let html = "";
|
||||||
|
@ -39,4 +40,12 @@ $(document).ready(function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".autocomplete-reset").click(function() {
|
||||||
|
let name = $(this).attr("id").replace("_reset", "");
|
||||||
|
$("#" + name + "_pk").val("");
|
||||||
|
$("#" + name).val("");
|
||||||
|
$("#" + name + "_list").html("");
|
||||||
|
$(this).addClass("d-none");
|
||||||
|
});
|
||||||
});
|
});
|
|
@ -1,3 +1,5 @@
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
<input type="hidden" name="{{ widget.name }}" {% if widget.attrs.model_pk %}value="{{ widget.attrs.model_pk }}"{% endif %} id="{{ widget.attrs.id }}_pk">
|
<input type="hidden" name="{{ widget.name }}" {% if widget.attrs.model_pk %}value="{{ widget.attrs.model_pk }}"{% endif %} id="{{ widget.attrs.id }}_pk">
|
||||||
<input type="text"
|
<input type="text"
|
||||||
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
|
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
|
||||||
|
@ -5,5 +7,8 @@
|
||||||
{% for name, value in widget.attrs.items %}
|
{% 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 %}>
|
||||||
|
{% if widget.resetable %}
|
||||||
|
<a id="{{ widget.attrs.id }}_reset" class="btn btn-light autocomplete-reset{% if not widget.value %} d-none{% endif %}">{% trans "Reset" %}</a>
|
||||||
|
{% endif %}
|
||||||
<ul class="list-group list-group-flush" id="{{ widget.attrs.id }}_list">
|
<ul class="list-group list-group-flush" id="{{ widget.attrs.id }}_list">
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load perms %}
|
||||||
|
|
||||||
{% if managers.data %}
|
{% if managers.data %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="clubListHeading">
|
<div class="card-header position-relative" id="clubListHeading">
|
||||||
|
@ -29,7 +31,7 @@
|
||||||
{% if history_list.data %}
|
{% if history_list.data %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="historyListHeading">
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %} href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,22 +2,22 @@
|
||||||
|
|
||||||
<div class="card bg-light shadow">
|
<div class="card bg-light shadow">
|
||||||
<div class="card-header text-center" >
|
<div class="card-header text-center" >
|
||||||
<h4> {% trans "Account #" %} {{ object.pk }}</h4>
|
<h4> {% trans "Account #" %} {{ user.pk }}</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-top text-center">
|
<div class="card-top text-center">
|
||||||
<a href="{% url 'member:user_update_pic' object.pk %}">
|
<a href="{% url 'member:user_update_pic' user.pk %}">
|
||||||
<img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >
|
<img src="{{ user.note.display_image.url }}" class="img-thumbnail mt-2" >
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body" id="profile_infos">
|
<div class="card-body" id="profile_infos">
|
||||||
<dl class="row">
|
<dl class="row">
|
||||||
<dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
|
<dt class="col-xl-6">{% trans 'name'|capfirst %}, {% trans 'first name' %}</dt>
|
||||||
<dd class="col-xl-6">{{ object.last_name }} {{ object.first_name }}</dd>
|
<dd class="col-xl-6">{{ user.last_name }} {{ user.first_name }}</dd>
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'username'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'username'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ object.username }}</dd>
|
<dd class="col-xl-6">{{ user.username }}</dd>
|
||||||
|
|
||||||
{% if object.pk == user.pk %}
|
{% if user.pk == user.pk %}
|
||||||
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">
|
<dd class="col-xl-6">
|
||||||
<a class="small" href="{% url 'password_change' %}">
|
<a class="small" href="{% url 'password_change' %}">
|
||||||
|
@ -27,25 +27,25 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'section'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ object.profile.section }}</dd>
|
<dd class="col-xl-6">{{ user.profile.section }}</dd>
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ object.profile.address }}</dd>
|
<dd class="col-xl-6">{{ user.profile.address }}</dd>
|
||||||
|
|
||||||
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
<dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
|
||||||
<dd class="col-xl-6">{{ object.note.balance | pretty_money }}</dd>
|
<dd class="col-xl-6">{{ user.note.balance | pretty_money }}</dd>
|
||||||
|
|
||||||
<dt class="col-xl-6"> <a href="{% url 'member:user_alias' object.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
<dt class="col-xl-6"> <a href="{% url 'member:user_alias' user.pk %}">{% trans 'aliases'|capfirst %}</a></dt>
|
||||||
<dd class="col-xl-6 text-truncate">{{ object.note.alias_set.all|join:", " }}</dd>
|
<dd class="col-xl-6 text-truncate">{{ user.note.alias_set.all|join:", " }}</dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
{% if object.pk == user.pk %}
|
{% if user.pk == user.pk %}
|
||||||
<a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
|
<a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer text-center">
|
<div class="card-footer text-center">
|
||||||
<a class="btn btn-primary btn-sm" href="{% url 'member:user_update_profile' object.pk %}">{% trans 'Update Profile' %}</a>
|
<a class="btn btn-primary btn-sm" href="{% url 'member:user_update_profile' user.pk %}">{% trans 'Update Profile' %}</a>
|
||||||
{% url 'member:user_detail' object.pk as user_profile_url %}
|
{% url 'member:user_detail' user.pk as user_profile_url %}
|
||||||
{%if request.path_info != user_profile_url %}
|
{%if request.path_info != user_profile_url %}
|
||||||
<a class="btn btn-primary btn-sm" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
|
<a class="btn btn-primary btn-sm" href="{{ user_profile_url }}">{% trans 'View Profile' %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% load perms %}
|
{% load perms %}
|
||||||
|
|
||||||
{% if not object.profile.email_confirmed and "member.change_profile_email_confirmed"|has_perm:object.profile %}
|
{% if not object.profile.email_confirmed and "member.change_profile_email_confirmed"|has_perm:user.profile %}
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
{% trans "This user doesn't have confirmed his/her e-mail address." %}
|
{% trans "This user doesn't have confirmed his/her e-mail address." %}
|
||||||
<a href="{% url "registration:email_validation_resend" pk=object.pk %}">{% trans "Click here to resend a validation link." %}</a>
|
<a href="{% url "registration:email_validation_resend" pk=user.pk %}">{% trans "Click here to resend a validation link." %}</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
@ -22,9 +22,7 @@
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="historyListHeading">
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
<a class="collapsed font-weight-bold"
|
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:user.note %} href="{% url 'note:transactions' pk=user.note.pk %}" {% endif %}>
|
||||||
data-toggle="collapse" data-target="#historyListCollapse"
|
|
||||||
aria-expanded="true" aria-controls="historyListCollapse">
|
|
||||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
{% load pretty_money %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>[Note Kfet] Rapport de la Note Kfet</title>
|
||||||
|
|
||||||
|
<link rel="stylesheet"
|
||||||
|
href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css"
|
||||||
|
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh"
|
||||||
|
crossorigin="anonymous">
|
||||||
|
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js"
|
||||||
|
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
|
||||||
|
crossorigin="anonymous"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>
|
||||||
|
Rapport d'activité de {{ user.first_name }} {{ user.last_name }} (note : {{ user }})
|
||||||
|
depuis le {{ last_week }} jusqu'au {{ now }}.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Dépenses totales : {{ outcoming|pretty_money }}<br>
|
||||||
|
Apports totaux : {{ incoming|pretty_money }}<br>
|
||||||
|
Différentiel : {{ diff|pretty_money }}<br>
|
||||||
|
Nouveau solde : {{ user.note.balance|pretty_money }}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h4>Rapport détaillé</h4>
|
||||||
|
|
||||||
|
{% render_table table %}
|
||||||
|
|
||||||
|
--
|
||||||
|
<p>
|
||||||
|
Le BDE<br>
|
||||||
|
Mail généré par la Note Kfet le {% now "j F Y à H:i:s" %}
|
||||||
|
</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,57 @@
|
||||||
|
{% extends "member/noteowner_detail.html" %}
|
||||||
|
{% load render_table from django_tables2 %}
|
||||||
|
{% load crispy_forms_tags %}
|
||||||
|
|
||||||
|
{% block profile_info %}
|
||||||
|
{% if note.club.weiclub %}
|
||||||
|
{% with club=note.club.weiclub %}
|
||||||
|
{% include "wei/weiclub_info.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% elif note.club %}
|
||||||
|
{% with club=note.club %}
|
||||||
|
{% include "member/club_info.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% elif note.user %}
|
||||||
|
{% with user=note.user %}
|
||||||
|
{% include "member/profile_info.html" %}
|
||||||
|
{% endwith %}
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block profile_content %}
|
||||||
|
{% crispy form %}
|
||||||
|
<div id="table">
|
||||||
|
{% render_table table %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
function refreshHistory() {
|
||||||
|
$("#history_list").load("{% url 'note:transactions' pk=object.pk %} #history_list");
|
||||||
|
$("#profile_infos").load("{% url 'note:transactions' pk=object.pk %} #profile_infos");
|
||||||
|
}
|
||||||
|
|
||||||
|
function refreshFilters() {
|
||||||
|
let filters = "";
|
||||||
|
filters += "source=" + $("#id_source_pk").val();
|
||||||
|
filters += "&destination=" + $("#id_destination_pk").val();
|
||||||
|
filters += $("input[name='type']:checked").map(function() {
|
||||||
|
return "&type=" + $(this).val();
|
||||||
|
}).toArray().join("");
|
||||||
|
filters += "&reason=" + $("#id_reason").val();
|
||||||
|
filters += "&valid=" + ($("#id_valid").is(":checked") ? "1" : "");
|
||||||
|
filters += "&amount_gte=" + $("#id_amount_gte").val();
|
||||||
|
filters += "&amount_lte=" + $("#id_amount_lte").val();
|
||||||
|
filters += "&created_after=" + $("#id_created_after").val();
|
||||||
|
filters += "&created_before=" + $("#id_created_before").val();
|
||||||
|
console.log(filters.replace(" ", "%20"));
|
||||||
|
$("#table").load(location.pathname + "?" + filters.replaceAll(" ", "%20") + " #table");
|
||||||
|
}
|
||||||
|
|
||||||
|
$(document).ready(function() {
|
||||||
|
$("input").change(refreshFilters);
|
||||||
|
$("input").keyup(refreshFilters);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
|
@ -1,7 +1,13 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Account Activation</h2>
|
<h2>{% trans "Account activation" %}</h2>
|
||||||
|
|
||||||
An email has been sent. Please click on the link to activate your account.
|
<p>
|
||||||
|
{% trans "An email has been sent. Please click on the link to activate your account." %}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
{% trans "You must also go to the Kfet to pay your membership. The WEI registration includes the BDE membership." %}
|
||||||
|
</p>
|
||||||
{% endblock %}
|
{% endblock %}
|
|
@ -27,7 +27,7 @@
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
{% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership in the Kfet." %}
|
{% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership in the Kfet. Note that the WEI registration includes the Kfet membership." %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -8,7 +8,7 @@ https://{{ domain }}{% url 'registration:email_validation' uidb64=uid token=toke
|
||||||
|
|
||||||
{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
|
{% trans "This link is only valid for a couple of days, after that you will need to contact us to validate your email." %}
|
||||||
|
|
||||||
{% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership in the Kfet." %}
|
{% trans "After that, you'll have to wait that someone validates your account before you can log in. You will need to pay your membership in the Kfet. Note that the WEI registration includes the Kfet membership." %}
|
||||||
|
|
||||||
{% trans "Thanks" %},
|
{% trans "Thanks" %},
|
||||||
|
|
||||||
|
|
|
@ -1,59 +1,12 @@
|
||||||
{% load render_table from django_tables2 %}
|
{% load render_table from django_tables2 %}
|
||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
{% load perms %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header text-center">
|
<div class="card-header text-center">
|
||||||
<h4>WEI</h4>
|
<h4>WEI</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p>LE WEI, c'est cool !</p>
|
<p>LE WEI, c'est cool !</p>
|
||||||
|
|
||||||
<p>
|
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore
|
|
||||||
magna aliqua. Dapibus ultrices in iaculis nunc sed augue. In hendrerit gravida rutrum quisque non tellus orci
|
|
||||||
ac. Massa vitae tortor condimentum lacinia quis vel eros. Elit ut aliquam purus sit amet. Aliquam faucibus
|
|
||||||
purus in massa tempor. Quisque id diam vel quam elementum pulvinar etiam non. Condimentum id venenatis a
|
|
||||||
condimentum vitae sapien pellentesque habitant. Egestas congue quisque egestas diam in. Vestibulum rhoncus
|
|
||||||
est pellentesque elit ullamcorper. Massa sed elementum tempus egestas sed sed. Sapien pellentesque habitant
|
|
||||||
morbi tristique. Lectus vestibulum mattis ullamcorper velit sed ullamcorper morbi tincidunt ornare. Sed
|
|
||||||
adipiscing diam donec adipiscing. Leo integer malesuada nunc vel risus commodo viverra maecenas.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Fusce id velit ut tortor pretium viverra suspendisse. Urna condimentum mattis pellentesque id nibh tortor id
|
|
||||||
aliquet. Vel facilisis volutpat est velit egestas dui. Turpis egestas sed tempus urna et pharetra pharetra
|
|
||||||
massa massa. Eget nunc scelerisque viverra mauris in. Etiam dignissim diam quis enim. Urna cursus eget nunc
|
|
||||||
scelerisque viverra mauris in aliquam sem. Amet porttitor eget dolor morbi non arcu risus quis. Ullamcorper
|
|
||||||
sit amet risus nullam eget felis. Ullamcorper eget nulla facilisi etiam dignissim diam quis. Enim nulla
|
|
||||||
aliquet porttitor lacus luctus accumsan tortor. Urna condimentum mattis pellentesque id nibh tortor id.
|
|
||||||
Feugiat in fermentum posuere urna nec. Risus nec feugiat in fermentum posuere urna nec tincidunt. Porttitor
|
|
||||||
massa id neque aliquam vestibulum morbi. Diam quis enim lobortis scelerisque. Ornare massa eget egestas
|
|
||||||
purus. Ut tortor pretium viverra suspendisse. Purus in mollis nunc sed. Tristique magna sit amet purus
|
|
||||||
gravida.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Ut porttitor leo a diam sollicitudin tempor. Viverra nam libero justo laoreet sit amet cursus sit amet.
|
|
||||||
Lectus arcu bibendum at varius vel pharetra vel turpis nunc. Vivamus arcu felis bibendum ut tristique et
|
|
||||||
egestas quis ipsum. Parturient montes nascetur ridiculus mus mauris. A cras semper auctor neque vitae
|
|
||||||
tempus quam pellentesque. Netus et malesuada fames ac. Mauris in aliquam sem fringilla ut. Sapien
|
|
||||||
pellentesque habitant morbi tristique. Mauris sit amet massa vitae tortor condimentum. Sagittis
|
|
||||||
aliquam malesuada bibendum arcu vitae elementum curabitur vitae nunc. Amet consectetur adipiscing elit
|
|
||||||
duis tristique sollicitudin nibh sit. Nunc mattis enim ut tellus elementum. Sapien eget mi proin sed libero
|
|
||||||
enim. Pulvinar sapien et ligula ullamcorper. Nibh mauris cursus mattis molestie a iaculis at erat
|
|
||||||
pellentesque. Molestie at elementum eu facilisis. Velit sed ullamcorper morbi tincidunt. Quam vulputate
|
|
||||||
dignissim suspendisse in est ante.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Id cursus metus aliquam eleifend mi. Eu turpis egestas pretium aenean pharetra magna ac. Faucibus ornare
|
|
||||||
suspendisse sed nisi lacus sed viverra tellus. Sed vulputate mi sit amet mauris commodo. Lacus laoreet non
|
|
||||||
curabitur gravida arcu ac. At ultrices mi tempus imperdiet nulla malesuada pellentesque elit eget. Fusce ut
|
|
||||||
placerat orci nulla pellentesque dignissim. Quis blandit turpis cursus in hac habitasse platea dictumst
|
|
||||||
quisque. Tellus id interdum velit laoreet id donec ultrices. Risus feugiat in ante metus dictum. Velit ut
|
|
||||||
tortor pretium viverra suspendisse. Lacus vel facilisis volutpat est velit egestas dui id. Nunc eget lorem
|
|
||||||
dolor sed viverra ipsum nunc aliquet bibendum. Varius quam quisque id diam vel quam. Orci dapibus ultrices
|
|
||||||
in iaculis. Neque gravida in fermentum et sollicitudin ac orci.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if club.is_current_wei %}
|
{% if club.is_current_wei %}
|
||||||
|
@ -101,7 +54,7 @@
|
||||||
{% if history_list.data %}
|
{% if history_list.data %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header position-relative" id="historyListHeading">
|
<div class="card-header position-relative" id="historyListHeading">
|
||||||
<a class="font-weight-bold">
|
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %} href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -14,3 +14,29 @@
|
||||||
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
|
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block extrajavascript %}
|
||||||
|
<script>
|
||||||
|
$(document).ready(function() {
|
||||||
|
function refreshTeams() {
|
||||||
|
let buses = [];
|
||||||
|
$("input[name='bus']:checked").each(function(ignored) {
|
||||||
|
buses.push($(this).parent().text().trim());
|
||||||
|
});
|
||||||
|
console.log(buses);
|
||||||
|
$("input[name='team']").each(function() {
|
||||||
|
let label = $(this).parent();
|
||||||
|
$(this).parent().addClass('d-none');
|
||||||
|
buses.forEach(function(bus) {
|
||||||
|
if (label.text().includes(bus))
|
||||||
|
label.removeClass('d-none');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
$("input[name='bus']").change(refreshTeams);
|
||||||
|
|
||||||
|
refreshTeams();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
Loading…
Reference in New Issue