From 985a5ca876006cf1b13b6ef30fd3e6de676ae753 Mon Sep 17 00:00:00 2001
From: Yohann D'ANELLO <yohann.danello@gmail.com>
Date: Mon, 3 Aug 2020 18:49:15 +0200
Subject: [PATCH] :heavy_plus_sign: Add "search transactions page"

---
 apps/note/forms.py                       |  82 ++++++++++-
 apps/note/models/transactions.py         |   9 ++
 apps/note/urls.py                        |   1 +
 apps/note/views.py                       |  56 +++++++-
 apps/wei/views.py                        |   5 +-
 locale/de/LC_MESSAGES/django.po          | 160 +++++++++++++--------
 locale/fr/LC_MESSAGES/django.po          | 168 +++++++++++++++--------
 note_kfet/inputs.py                      |   8 +-
 static/js/autocomplete_model.js          |   9 ++
 templates/member/autocomplete_model.html |   5 +
 templates/member/club_tables.html        |   4 +-
 templates/member/profile_info.html       |  28 ++--
 templates/member/profile_tables.html     |   8 +-
 templates/note/search_transactions.html  |  57 ++++++++
 templates/wei/weiclub_tables.html        |  51 +------
 15 files changed, 455 insertions(+), 196 deletions(-)
 create mode 100644 templates/note/search_transactions.html

diff --git a/apps/note/forms.py b/apps/note/forms.py
index bc479e20..fe81783e 100644
--- a/apps/note/forms.py
+++ b/apps/note/forms.py
@@ -3,10 +3,11 @@
 
 from django import forms
 from django.contrib.contenttypes.models import ContentType
+from django.forms import CheckboxSelectMultiple
 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):
@@ -38,3 +39,80 @@ class TransactionTemplateForm(forms.ModelForm):
                 ),
             '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(),
+    )
diff --git a/apps/note/models/transactions.py b/apps/note/models/transactions.py
index 6eab05ee..69306b71 100644
--- a/apps/note/models/transactions.py
+++ b/apps/note/models/transactions.py
@@ -243,6 +243,7 @@ class RecurrentTransaction(Transaction):
         TransactionTemplate,
         on_delete=models.PROTECT,
     )
+
     category = models.ForeignKey(
         TemplateCategory,
         on_delete=models.PROTECT,
@@ -252,6 +253,10 @@ class RecurrentTransaction(Transaction):
     def type(self):
         return _('Template')
 
+    class Meta:
+        verbose_name = _("recurrent transaction")
+        verbose_name_plural = _("recurrent transactions")
+
 
 class SpecialTransaction(Transaction):
     """
@@ -290,6 +295,10 @@ class SpecialTransaction(Transaction):
             raise(ValidationError(_("A special transaction is only possible between a"
                                     " 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):
     """
diff --git a/apps/note/urls.py b/apps/note/urls.py
index 9d6af317..c5662f2c 100644
--- a/apps/note/urls.py
+++ b/apps/note/urls.py
@@ -12,4 +12,5 @@ urlpatterns = [
     path('buttons/update/<int:pk>/', views.TransactionTemplateUpdateView.as_view(), name='template_update'),
     path('buttons/', views.TransactionTemplateListView.as_view(), name='template_list'),
     path('consos/', views.ConsoView.as_view(), name='consos'),
+    path('transactions/<int:pk>/', views.TransactionSearchView.as_view(), name='transactions'),
 ]
diff --git a/apps/note/views.py b/apps/note/views.py
index ad2b2a99..3524a080 100644
--- a/apps/note/views.py
+++ b/apps/note/views.py
@@ -5,9 +5,9 @@ import json
 from django.conf import settings
 from django.contrib.auth.mixins import LoginRequiredMixin
 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.views.generic import CreateView, UpdateView
+from django.views.generic import CreateView, UpdateView, DetailView
 from django_tables2 import SingleTableView
 from django.urls import reverse_lazy
 
@@ -16,8 +16,8 @@ from note_kfet.inputs import AmountInput
 from permission.backends import PermissionBackend
 from permission.views import ProtectQuerysetMixin
 
-from .forms import TransactionTemplateForm
-from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial
+from .forms import TransactionTemplateForm, SearchTransactionForm
+from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note
 from .models.transactions import SpecialTransaction
 from .tables import HistoryTable, ButtonTable
 
@@ -171,3 +171,51 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
         context['no_cache'] = True
 
         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=20)
+        context["table"] = table
+
+        return context
diff --git a/apps/wei/views.py b/apps/wei/views.py
index 8a95c72a..ed76cc0f 100644
--- a/apps/wei/views.py
+++ b/apps/wei/views.py
@@ -130,7 +130,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
         context["my_registration"] = my_registration
 
         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-")
         context['buses'] = bus_table
 
@@ -589,9 +589,6 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
     form_class = WEIRegistrationForm
     extra_context = {"title": _("Update WEI Registration")}
 
-    def get_queryset(self, **kwargs):
-        return WEIRegistration.objects
-
     def dispatch(self, request, *args, **kwargs):
         wei = self.get_object().wei
         today = date.today()
diff --git a/locale/de/LC_MESSAGES/django.po b/locale/de/LC_MESSAGES/django.po
index b094a5ac..3f207ceb 100644
--- a/locale/de/LC_MESSAGES/django.po
+++ b/locale/de/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-08-03 12:35+0200\n"
+"POT-Creation-Date: 2020-08-03 18:38+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -46,7 +46,7 @@ msgstr ""
 #: apps/activity/models.py:24 apps/activity/models.py:49
 #: apps/member/models.py:158 apps/note/models/notes.py:212
 #: apps/note/models/transactions.py:25 apps/note/models/transactions.py:45
-#: apps/note/models/transactions.py:263 apps/permission/models.py:339
+#: apps/note/models/transactions.py:268 apps/permission/models.py:339
 #: apps/wei/models.py:65 apps/wei/models.py:117
 #: templates/member/club_info.html:13 templates/member/profile_info.html:14
 #: templates/registration/future_profile_detail.html:16
@@ -182,16 +182,16 @@ msgstr ""
 msgid "remove"
 msgstr ""
 
-#: apps/activity/tables.py:75 apps/treasury/models.py:141
+#: apps/activity/tables.py:75 apps/note/forms.py:76 apps/treasury/models.py:141
 msgid "Type"
 msgstr ""
 
-#: apps/activity/tables.py:77 apps/member/forms.py:102
+#: apps/activity/tables.py:77 apps/member/forms.py:103
 #: apps/registration/forms.py:70 apps/treasury/forms.py:120
 msgid "Last name"
 msgstr ""
 
-#: apps/activity/tables.py:79 apps/member/forms.py:107
+#: apps/activity/tables.py:79 apps/member/forms.py:108
 #: apps/registration/forms.py:75 apps/treasury/forms.py:122
 #: templates/note/transaction_form.html:129
 msgid "First name"
@@ -225,7 +225,7 @@ msgstr ""
 msgid "Invite guest to the activity \"{}\""
 msgstr ""
 
-#: apps/activity/views.py:171
+#: apps/activity/views.py:181
 msgid "Entry for activity \"{}\""
 msgstr ""
 
@@ -313,45 +313,45 @@ msgstr ""
 msgid "member"
 msgstr ""
 
-#: apps/member/forms.py:58 apps/member/views.py:82
+#: apps/member/forms.py:59 apps/member/views.py:82
 #: apps/registration/forms.py:28
 msgid "An alias with a similar name already exists."
 msgstr ""
 
-#: apps/member/forms.py:81 apps/registration/forms.py:50
+#: apps/member/forms.py:82 apps/registration/forms.py:50
 msgid "Inscription paid by Société Générale"
 msgstr ""
 
-#: apps/member/forms.py:83 apps/registration/forms.py:52
+#: apps/member/forms.py:84 apps/registration/forms.py:52
 msgid "Check this case is the Société Générale paid the inscription."
 msgstr ""
 
-#: apps/member/forms.py:88 apps/registration/forms.py:57
+#: apps/member/forms.py:89 apps/registration/forms.py:57
 msgid "Credit type"
 msgstr ""
 
-#: apps/member/forms.py:89 apps/registration/forms.py:58
+#: apps/member/forms.py:90 apps/registration/forms.py:58
 msgid "No credit"
 msgstr ""
 
-#: apps/member/forms.py:91
+#: apps/member/forms.py:92
 msgid "You can credit the note of the user."
 msgstr ""
 
-#: apps/member/forms.py:95 apps/registration/forms.py:63
+#: apps/member/forms.py:96 apps/registration/forms.py:63
 msgid "Credit amount"
 msgstr ""
 
-#: apps/member/forms.py:112 apps/registration/forms.py:80
+#: apps/member/forms.py:113 apps/registration/forms.py:80
 #: apps/treasury/forms.py:124 templates/note/transaction_form.html:135
 msgid "Bank"
 msgstr ""
 
-#: apps/member/forms.py:139
+#: apps/member/forms.py:140
 msgid "User"
 msgstr ""
 
-#: apps/member/forms.py:153
+#: apps/member/forms.py:154
 msgid "Roles"
 msgstr ""
 
@@ -540,7 +540,7 @@ msgstr ""
 msgid "membership ends on"
 msgstr ""
 
-#: apps/member/models.py:310 apps/member/views.py:535 apps/wei/views.py:795
+#: apps/member/models.py:310 apps/member/views.py:535 apps/wei/views.py:793
 msgid "User is not a member of the parent club"
 msgstr ""
 
@@ -620,7 +620,7 @@ msgstr ""
 msgid "Add new member to the club"
 msgstr ""
 
-#: apps/member/views.py:530 apps/wei/views.py:786
+#: apps/member/views.py:530 apps/wei/views.py:784
 msgid ""
 "This user don't have enough money to join this club, and can't have a "
 "negative balance."
@@ -662,14 +662,46 @@ msgstr ""
 msgid "amount"
 msgstr ""
 
-#: apps/note/forms.py:14
+#: apps/note/forms.py:15
 msgid "select an image"
 msgstr ""
 
-#: apps/note/forms.py:15
+#: apps/note/forms.py:16
 msgid "Maximal size: 2MB"
 msgstr ""
 
+#: apps/note/forms.py:47
+msgid "Source"
+msgstr ""
+
+#: apps/note/forms.py:61
+msgid "Destination"
+msgstr ""
+
+#: apps/note/forms.py:82 templates/note/transaction_form.html:104
+msgid "Reason"
+msgstr ""
+
+#: apps/note/forms.py:87 apps/treasury/tables.py:117
+msgid "Valid"
+msgstr ""
+
+#: apps/note/forms.py:93
+msgid "Total amount greater than"
+msgstr ""
+
+#: apps/note/forms.py:101
+msgid "Total amount less than"
+msgstr ""
+
+#: apps/note/forms.py:107
+msgid "Created after"
+msgstr ""
+
+#: apps/note/forms.py:114
+msgid "Created before"
+msgstr ""
+
 #: apps/note/models/notes.py:29
 msgid "account balance"
 msgstr ""
@@ -842,39 +874,55 @@ msgstr ""
 msgid "Transfer"
 msgstr ""
 
-#: apps/note/models/transactions.py:253
+#: apps/note/models/transactions.py:254
 msgid "Template"
 msgstr ""
 
-#: apps/note/models/transactions.py:268
-msgid "first_name"
+#: apps/note/models/transactions.py:257
+msgid "recurrent transaction"
+msgstr ""
+
+#: apps/note/models/transactions.py:258
+msgid "recurrent transactions"
 msgstr ""
 
 #: apps/note/models/transactions.py:273
+msgid "first_name"
+msgstr ""
+
+#: apps/note/models/transactions.py:278
 msgid "bank"
 msgstr ""
 
-#: apps/note/models/transactions.py:279
+#: apps/note/models/transactions.py:284
 #: templates/activity/activity_entry.html:17
 #: templates/note/transaction_form.html:20
 msgid "Credit"
 msgstr ""
 
-#: apps/note/models/transactions.py:279 templates/note/transaction_form.html:24
+#: apps/note/models/transactions.py:284 templates/note/transaction_form.html:24
 msgid "Debit"
 msgstr ""
 
-#: apps/note/models/transactions.py:290
+#: apps/note/models/transactions.py:295
 msgid ""
 "A special transaction is only possible between a Note associated to a "
 "payment method and a User or a Club"
 msgstr ""
 
-#: apps/note/models/transactions.py:307 apps/note/models/transactions.py:312
+#: apps/note/models/transactions.py:299
+msgid "Special transaction"
+msgstr ""
+
+#: apps/note/models/transactions.py:300
+msgid "Special transactions"
+msgstr ""
+
+#: apps/note/models/transactions.py:316 apps/note/models/transactions.py:321
 msgid "membership transaction"
 msgstr ""
 
-#: apps/note/models/transactions.py:308 apps/treasury/models.py:228
+#: apps/note/models/transactions.py:317 apps/treasury/models.py:228
 msgid "membership transactions"
 msgstr ""
 
@@ -903,26 +951,30 @@ msgstr ""
 msgid "Edit"
 msgstr ""
 
-#: apps/note/views.py:33
+#: apps/note/views.py:35
 msgid "Transfer money"
 msgstr ""
 
-#: apps/note/views.py:69
+#: apps/note/views.py:75
 msgid "Create new button"
 msgstr ""
 
-#: apps/note/views.py:78
+#: apps/note/views.py:84
 msgid "Search button"
 msgstr ""
 
-#: apps/note/views.py:101
+#: apps/note/views.py:107
 msgid "Update button"
 msgstr ""
 
-#: apps/note/views.py:138 templates/base.html:94
+#: apps/note/views.py:144 templates/base.html:94
 msgid "Consumptions"
 msgstr ""
 
+#: apps/note/views.py:180
+msgid "Search transactions"
+msgstr ""
+
 #: apps/permission/models.py:99
 #, python-brace-format
 msgid "Can {type} {model}.{field} in {query}"
@@ -1288,10 +1340,6 @@ msgstr ""
 msgid "Remove"
 msgstr ""
 
-#: apps/treasury/tables.py:117
-msgid "Valid"
-msgstr ""
-
 #: apps/treasury/tables.py:124
 msgid "Yes"
 msgstr ""
@@ -1342,37 +1390,37 @@ msgstr ""
 msgid "WEI"
 msgstr ""
 
-#: apps/wei/forms/registration.py:47 apps/wei/models.py:112
+#: apps/wei/forms/registration.py:48 apps/wei/models.py:112
 #: apps/wei/models.py:297
 msgid "bus"
 msgstr ""
 
-#: apps/wei/forms/registration.py:48
+#: apps/wei/forms/registration.py:49
 msgid ""
 "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."
 msgstr ""
 
-#: apps/wei/forms/registration.py:54
+#: apps/wei/forms/registration.py:56
 msgid "Team"
 msgstr ""
 
-#: apps/wei/forms/registration.py:56
+#: apps/wei/forms/registration.py:58
 msgid ""
 "Leave this field empty if you won't be in a team (staff, bus chief, free "
 "electron)"
 msgstr ""
 
-#: apps/wei/forms/registration.py:61 apps/wei/forms/registration.py:67
+#: apps/wei/forms/registration.py:64 apps/wei/forms/registration.py:74
 #: apps/wei/models.py:147
 msgid "WEI Roles"
 msgstr ""
 
-#: apps/wei/forms/registration.py:62
+#: apps/wei/forms/registration.py:65
 msgid "Select the roles that you are interested in."
 msgstr ""
 
-#: apps/wei/forms/registration.py:72
+#: apps/wei/forms/registration.py:81
 msgid "This team doesn't belong to the given bus."
 msgstr ""
 
@@ -1618,7 +1666,7 @@ msgstr ""
 msgid "Register 1A"
 msgstr ""
 
-#: apps/wei/views.py:489 apps/wei/views.py:559
+#: apps/wei/views.py:489 apps/wei/views.py:560
 msgid "This user is already registered to this WEI."
 msgstr ""
 
@@ -1636,31 +1684,31 @@ msgstr ""
 msgid "Register 2A+"
 msgstr ""
 
-#: apps/wei/views.py:541 apps/wei/views.py:629
+#: apps/wei/views.py:542 apps/wei/views.py:627
 msgid "You already opened an account in the Société générale."
 msgstr ""
 
-#: apps/wei/views.py:589
+#: apps/wei/views.py:590
 msgid "Update WEI Registration"
 msgstr ""
 
-#: apps/wei/views.py:679
+#: apps/wei/views.py:677
 msgid "Delete WEI registration"
 msgstr ""
 
-#: apps/wei/views.py:690
+#: apps/wei/views.py:688
 msgid "You don't have the right to delete this WEI registration."
 msgstr ""
 
-#: apps/wei/views.py:709
+#: apps/wei/views.py:707
 msgid "Validate WEI registration"
 msgstr ""
 
-#: apps/wei/views.py:790
+#: apps/wei/views.py:788
 msgid "This user didn't give her/his caution check."
 msgstr ""
 
-#: apps/wei/views.py:827 apps/wei/views.py:880 apps/wei/views.py:890
+#: apps/wei/views.py:825 apps/wei/views.py:878 apps/wei/views.py:888
 #: templates/wei/survey.html:12 templates/wei/survey_closed.html:12
 #: templates/wei/survey_end.html:12
 msgid "Survey WEI"
@@ -1822,6 +1870,10 @@ msgstr ""
 msgid "Add alias"
 msgstr ""
 
+#: templates/member/autocomplete_model.html:11
+msgid "Reset"
+msgstr ""
+
 #: templates/member/club_info.html:17
 msgid "Club Parent"
 msgstr ""
@@ -1990,10 +2042,6 @@ msgstr ""
 msgid "Action"
 msgstr ""
 
-#: templates/note/transaction_form.html:104
-msgid "Reason"
-msgstr ""
-
 #: templates/note/transaction_form.html:113
 msgid "Transfer type"
 msgstr ""
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index 511b3efb..8fedb67a 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -8,7 +8,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: PACKAGE VERSION\n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2020-08-03 12:35+0200\n"
+"POT-Creation-Date: 2020-08-03 18:38+0200\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -47,7 +47,7 @@ msgstr "Vous ne pouvez pas inviter plus de 3 personnes à cette activité."
 #: apps/activity/models.py:24 apps/activity/models.py:49
 #: apps/member/models.py:158 apps/note/models/notes.py:212
 #: apps/note/models/transactions.py:25 apps/note/models/transactions.py:45
-#: apps/note/models/transactions.py:263 apps/permission/models.py:339
+#: apps/note/models/transactions.py:268 apps/permission/models.py:339
 #: apps/wei/models.py:65 apps/wei/models.py:117
 #: templates/member/club_info.html:13 templates/member/profile_info.html:14
 #: templates/registration/future_profile_detail.html:16
@@ -183,16 +183,16 @@ msgstr "Entré le "
 msgid "remove"
 msgstr "supprimer"
 
-#: apps/activity/tables.py:75 apps/treasury/models.py:141
+#: apps/activity/tables.py:75 apps/note/forms.py:76 apps/treasury/models.py:141
 msgid "Type"
 msgstr "Type"
 
-#: apps/activity/tables.py:77 apps/member/forms.py:102
+#: apps/activity/tables.py:77 apps/member/forms.py:103
 #: apps/registration/forms.py:70 apps/treasury/forms.py:120
 msgid "Last name"
 msgstr "Nom de famille"
 
-#: apps/activity/tables.py:79 apps/member/forms.py:107
+#: apps/activity/tables.py:79 apps/member/forms.py:108
 #: apps/registration/forms.py:75 apps/treasury/forms.py:122
 #: templates/note/transaction_form.html:129
 msgid "First name"
@@ -226,7 +226,7 @@ msgstr "Modifier l'activité"
 msgid "Invite guest to the activity \"{}\""
 msgstr "Invitation pour l'activité « {} »"
 
-#: apps/activity/views.py:171
+#: apps/activity/views.py:181
 msgid "Entry for activity \"{}\""
 msgstr "Entrées pour l'activité « {} »"
 
@@ -314,45 +314,45 @@ msgstr "cotisation"
 msgid "member"
 msgstr "adhérent"
 
-#: apps/member/forms.py:58 apps/member/views.py:82
+#: apps/member/forms.py:59 apps/member/views.py:82
 #: apps/registration/forms.py:28
 msgid "An alias with a similar name already exists."
 msgstr "Un alias avec un nom similaire existe déjà."
 
-#: apps/member/forms.py:81 apps/registration/forms.py:50
+#: apps/member/forms.py:82 apps/registration/forms.py:50
 msgid "Inscription paid by Société Générale"
 msgstr "Inscription payée par la Société générale"
 
-#: apps/member/forms.py:83 apps/registration/forms.py:52
+#: apps/member/forms.py:84 apps/registration/forms.py:52
 msgid "Check this case is the Société Générale paid the inscription."
 msgstr "Cochez cette case si la Société Générale a payé l'inscription."
 
-#: apps/member/forms.py:88 apps/registration/forms.py:57
+#: apps/member/forms.py:89 apps/registration/forms.py:57
 msgid "Credit type"
 msgstr "Type de rechargement"
 
-#: apps/member/forms.py:89 apps/registration/forms.py:58
+#: apps/member/forms.py:90 apps/registration/forms.py:58
 msgid "No credit"
 msgstr "Pas de rechargement"
 
-#: apps/member/forms.py:91
+#: apps/member/forms.py:92
 msgid "You can credit the note of the user."
 msgstr "Vous pouvez créditer la note de l'utisateur avant l'adhésion."
 
-#: apps/member/forms.py:95 apps/registration/forms.py:63
+#: apps/member/forms.py:96 apps/registration/forms.py:63
 msgid "Credit amount"
 msgstr "Montant à créditer"
 
-#: apps/member/forms.py:112 apps/registration/forms.py:80
+#: apps/member/forms.py:113 apps/registration/forms.py:80
 #: apps/treasury/forms.py:124 templates/note/transaction_form.html:135
 msgid "Bank"
 msgstr "Banque"
 
-#: apps/member/forms.py:139
+#: apps/member/forms.py:140
 msgid "User"
 msgstr "Utilisateur"
 
-#: apps/member/forms.py:153
+#: apps/member/forms.py:154
 msgid "Roles"
 msgstr "Rôles"
 
@@ -545,7 +545,7 @@ msgstr "l'adhésion commence le"
 msgid "membership ends on"
 msgstr "l'adhésion finit le"
 
-#: apps/member/models.py:310 apps/member/views.py:535 apps/wei/views.py:795
+#: apps/member/models.py:310 apps/member/views.py:535 apps/wei/views.py:793
 msgid "User is not a member of the parent club"
 msgstr "L'utilisateur n'est pas membre du club parent"
 
@@ -625,7 +625,7 @@ msgstr "Modifier le club"
 msgid "Add new member to the club"
 msgstr "Ajouter un nouveau membre au club"
 
-#: apps/member/views.py:530 apps/wei/views.py:786
+#: apps/member/views.py:530 apps/wei/views.py:784
 msgid ""
 "This user don't have enough money to join this club, and can't have a "
 "negative balance."
@@ -669,14 +669,46 @@ msgstr "destination"
 msgid "amount"
 msgstr "montant"
 
-#: apps/note/forms.py:14
+#: apps/note/forms.py:15
 msgid "select an image"
 msgstr "Choisissez une image"
 
-#: apps/note/forms.py:15
+#: apps/note/forms.py:16
 msgid "Maximal size: 2MB"
 msgstr "Taille maximale : 2 Mo"
 
+#: apps/note/forms.py:47
+msgid "Source"
+msgstr "Source"
+
+#: apps/note/forms.py:61
+msgid "Destination"
+msgstr "Destination"
+
+#: apps/note/forms.py:82 templates/note/transaction_form.html:104
+msgid "Reason"
+msgstr "Raison"
+
+#: apps/note/forms.py:87 apps/treasury/tables.py:117
+msgid "Valid"
+msgstr "Valide"
+
+#: apps/note/forms.py:93
+msgid "Total amount greater than"
+msgstr "Montant total supérieur à"
+
+#: apps/note/forms.py:101
+msgid "Total amount less than"
+msgstr "Montant total inférieur à"
+
+#: apps/note/forms.py:107
+msgid "Created after"
+msgstr "Créé après"
+
+#: apps/note/forms.py:114
+msgid "Created before"
+msgstr "Créé avant"
+
 #: apps/note/models/notes.py:29
 msgid "account balance"
 msgstr "solde du compte"
@@ -805,11 +837,11 @@ msgstr "mis en avant"
 
 #: apps/note/models/transactions.py:87
 msgid "transaction template"
-msgstr "modèle de transaction"
+msgstr "Modèle de transaction"
 
 #: apps/note/models/transactions.py:88
 msgid "transaction templates"
-msgstr "modèles de transaction"
+msgstr "Modèles de transaction"
 
 #: apps/note/models/transactions.py:112 apps/note/models/transactions.py:125
 #: apps/note/tables.py:35 apps/note/tables.py:44
@@ -830,12 +862,12 @@ msgstr "Motif d'invalidité"
 
 #: apps/note/models/transactions.py:159
 msgid "transaction"
-msgstr "transaction"
+msgstr "Transaction"
 
 #: apps/note/models/transactions.py:160
 #: templates/treasury/sogecredit_detail.html:22
 msgid "transactions"
-msgstr "transactions"
+msgstr "Transactions"
 
 #: apps/note/models/transactions.py:175
 msgid ""
@@ -852,29 +884,37 @@ msgstr ""
 msgid "Transfer"
 msgstr "Virement"
 
-#: apps/note/models/transactions.py:253
+#: apps/note/models/transactions.py:254
 msgid "Template"
 msgstr "Bouton"
 
-#: apps/note/models/transactions.py:268
+#: apps/note/models/transactions.py:257
+msgid "recurrent transaction"
+msgstr "Transaction issue de bouton"
+
+#: apps/note/models/transactions.py:258
+msgid "recurrent transactions"
+msgstr "Transactions issues de boutons"
+
+#: apps/note/models/transactions.py:273
 msgid "first_name"
 msgstr "prénom"
 
-#: apps/note/models/transactions.py:273
+#: apps/note/models/transactions.py:278
 msgid "bank"
 msgstr "banque"
 
-#: apps/note/models/transactions.py:279
+#: apps/note/models/transactions.py:284
 #: templates/activity/activity_entry.html:17
 #: templates/note/transaction_form.html:20
 msgid "Credit"
 msgstr "Crédit"
 
-#: apps/note/models/transactions.py:279 templates/note/transaction_form.html:24
+#: apps/note/models/transactions.py:284 templates/note/transaction_form.html:24
 msgid "Debit"
 msgstr "Débit"
 
-#: apps/note/models/transactions.py:290
+#: apps/note/models/transactions.py:295
 msgid ""
 "A special transaction is only possible between a Note associated to a "
 "payment method and a User or a Club"
@@ -882,11 +922,19 @@ msgstr ""
 "Une transaction spéciale n'est possible que entre une note associée à un "
 "mode de paiement et un utilisateur ou un club."
 
-#: apps/note/models/transactions.py:307 apps/note/models/transactions.py:312
+#: apps/note/models/transactions.py:299
+msgid "Special transaction"
+msgstr "Transaction de crédit/retrait"
+
+#: apps/note/models/transactions.py:300
+msgid "Special transactions"
+msgstr "Transactions de crédit/retrait"
+
+#: apps/note/models/transactions.py:316 apps/note/models/transactions.py:321
 msgid "membership transaction"
 msgstr "Transaction d'adhésion"
 
-#: apps/note/models/transactions.py:308 apps/treasury/models.py:228
+#: apps/note/models/transactions.py:317 apps/treasury/models.py:228
 msgid "membership transactions"
 msgstr "Transactions d'adhésion"
 
@@ -915,26 +963,30 @@ msgstr "Supprimer"
 msgid "Edit"
 msgstr "Éditer"
 
-#: apps/note/views.py:33
+#: apps/note/views.py:35
 msgid "Transfer money"
 msgstr "Transférer de l'argent"
 
-#: apps/note/views.py:69
+#: apps/note/views.py:75
 msgid "Create new button"
 msgstr "Créer un nouveau bouton"
 
-#: apps/note/views.py:78
+#: apps/note/views.py:84
 msgid "Search button"
 msgstr "Chercher un bouton"
 
-#: apps/note/views.py:101
+#: apps/note/views.py:107
 msgid "Update button"
 msgstr "Modifier le bouton"
 
-#: apps/note/views.py:138 templates/base.html:94
+#: apps/note/views.py:144 templates/base.html:94
 msgid "Consumptions"
 msgstr "Consommations"
 
+#: apps/note/views.py:180
+msgid "Search transactions"
+msgstr "Rechercher des transactions"
+
 #: apps/permission/models.py:99
 #, python-brace-format
 msgid "Can {type} {model}.{field} in {query}"
@@ -1317,10 +1369,6 @@ msgstr "Ajouter"
 msgid "Remove"
 msgstr "supprimer"
 
-#: apps/treasury/tables.py:117
-msgid "Valid"
-msgstr "Valide"
-
 #: apps/treasury/tables.py:124
 msgid "Yes"
 msgstr "Oui"
@@ -1371,12 +1419,12 @@ msgstr "Gérer les crédits de la Société générale"
 msgid "WEI"
 msgstr "WEI"
 
-#: apps/wei/forms/registration.py:47 apps/wei/models.py:112
+#: apps/wei/forms/registration.py:48 apps/wei/models.py:112
 #: apps/wei/models.py:297
 msgid "bus"
 msgstr "Bus"
 
-#: apps/wei/forms/registration.py:48
+#: apps/wei/forms/registration.py:49
 msgid ""
 "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."
@@ -1385,11 +1433,11 @@ msgstr ""
 "attribuer un bus et une équipe, en particulier si vous êtes un électron "
 "libre."
 
-#: apps/wei/forms/registration.py:54
+#: apps/wei/forms/registration.py:56
 msgid "Team"
 msgstr "Équipe"
 
-#: apps/wei/forms/registration.py:56
+#: apps/wei/forms/registration.py:58
 msgid ""
 "Leave this field empty if you won't be in a team (staff, bus chief, free "
 "electron)"
@@ -1397,16 +1445,16 @@ msgstr ""
 "Laissez ce champ vide si vous ne serez pas dans une équipe (staff, chef de "
 "bus ou électron libre)"
 
-#: apps/wei/forms/registration.py:61 apps/wei/forms/registration.py:67
+#: apps/wei/forms/registration.py:64 apps/wei/forms/registration.py:74
 #: apps/wei/models.py:147
 msgid "WEI Roles"
 msgstr "Rôles au WEI"
 
-#: apps/wei/forms/registration.py:62
+#: apps/wei/forms/registration.py:65
 msgid "Select the roles that you are interested in."
 msgstr "Sélectionnez les rôles qui vous intéressent."
 
-#: apps/wei/forms/registration.py:72
+#: apps/wei/forms/registration.py:81
 msgid "This team doesn't belong to the given bus."
 msgstr "Cette équipe n'appartient pas à ce bus."
 
@@ -1662,7 +1710,7 @@ msgstr "Inscrire un 1A au WEI"
 msgid "Register 1A"
 msgstr "Inscrire un 1A"
 
-#: apps/wei/views.py:489 apps/wei/views.py:559
+#: apps/wei/views.py:489 apps/wei/views.py:560
 msgid "This user is already registered to this WEI."
 msgstr "Cette personne est déjà inscrite au WEI."
 
@@ -1682,31 +1730,31 @@ msgstr "Inscrire un 2A+ au WEI"
 msgid "Register 2A+"
 msgstr "Inscrire un 2A+"
 
-#: apps/wei/views.py:541 apps/wei/views.py:629
+#: apps/wei/views.py:542 apps/wei/views.py:627
 msgid "You already opened an account in the Société générale."
 msgstr "Vous avez déjà ouvert un compte auprès de la société générale."
 
-#: apps/wei/views.py:589
+#: apps/wei/views.py:590
 msgid "Update WEI Registration"
 msgstr "Modifier l'inscription WEI"
 
-#: apps/wei/views.py:679
+#: apps/wei/views.py:677
 msgid "Delete WEI registration"
 msgstr "Supprimer l'inscription WEI"
 
-#: apps/wei/views.py:690
+#: apps/wei/views.py:688
 msgid "You don't have the right to delete this WEI registration."
 msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI."
 
-#: apps/wei/views.py:709
+#: apps/wei/views.py:707
 msgid "Validate WEI registration"
 msgstr "Valider l'inscription WEI"
 
-#: apps/wei/views.py:790
+#: apps/wei/views.py:788
 msgid "This user didn't give her/his caution check."
 msgstr "Cet utilisateur n'a pas donné son chèque de caution."
 
-#: apps/wei/views.py:827 apps/wei/views.py:880 apps/wei/views.py:890
+#: apps/wei/views.py:825 apps/wei/views.py:878 apps/wei/views.py:888
 #: templates/wei/survey.html:12 templates/wei/survey_closed.html:12
 #: templates/wei/survey_end.html:12
 msgid "Survey WEI"
@@ -1882,6 +1930,10 @@ msgstr ""
 msgid "Add alias"
 msgstr "Ajouter un alias"
 
+#: templates/member/autocomplete_model.html:11
+msgid "Reset"
+msgstr ""
+
 #: templates/member/club_info.html:17
 msgid "Club Parent"
 msgstr "Club parent"
@@ -2050,10 +2102,6 @@ msgstr "Sélection des destinataires"
 msgid "Action"
 msgstr "Action"
 
-#: templates/note/transaction_form.html:104
-msgid "Reason"
-msgstr "Raison"
-
 #: templates/note/transaction_form.html:113
 msgid "Transfer type"
 msgstr "Type de transfert"
diff --git a/note_kfet/inputs.py b/note_kfet/inputs.py
index 1a17d5ac..8e3d9e29 100644
--- a/note_kfet/inputs.py
+++ b/note_kfet/inputs.py
@@ -23,10 +23,11 @@ class AmountInput(NumberInput):
 class Autocomplete(TextInput):
     template_name = "member/autocomplete_model.html"
 
-    def __init__(self, model, attrs=None):
+    def __init__(self, model, resetable=False, attrs=None):
         super().__init__(attrs)
 
         self.model = model
+        self.resetable = resetable
         self.model_pk = None
 
     class Media:
@@ -34,6 +35,11 @@ class Autocomplete(TextInput):
 
         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):
         if value:
             self.attrs["model_pk"] = int(value)
diff --git a/static/js/autocomplete_model.js b/static/js/autocomplete_model.js
index 6e135ad1..9d9ba2d3 100644
--- a/static/js/autocomplete_model.js
+++ b/static/js/autocomplete_model.js
@@ -10,6 +10,7 @@ $(document).ready(function () {
         if (!name_field)
             name_field = "name";
         let input = target.val();
+        $("#" + prefix + "_reset").removeClass("d-none");
 
         $.getJSON(api_url + (api_url.includes("?") ? "&" : "?") + "format=json&search=^" + input + api_url_suffix, function(objects) {
             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");
+    });
 });
\ No newline at end of file
diff --git a/templates/member/autocomplete_model.html b/templates/member/autocomplete_model.html
index 2236c6ef..0aeca361 100644
--- a/templates/member/autocomplete_model.html
+++ b/templates/member/autocomplete_model.html
@@ -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="text"
    {% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
@@ -5,5 +7,8 @@
     {% for name, value in widget.attrs.items %}
         {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
     {% 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>
diff --git a/templates/member/club_tables.html b/templates/member/club_tables.html
index 139d3abc..063c00c5 100644
--- a/templates/member/club_tables.html
+++ b/templates/member/club_tables.html
@@ -1,5 +1,7 @@
 {% load render_table from django_tables2 %}
 {% load i18n %}
+{% load perms %}
+
 {% if managers.data %}
     <div class="card">
         <div class="card-header position-relative" id="clubListHeading">
@@ -29,7 +31,7 @@
 {% if history_list.data %}
     <div class="card">
         <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" %}
             </a>
         </div>
diff --git a/templates/member/profile_info.html b/templates/member/profile_info.html
index 7be10ba1..35d3e4ea 100644
--- a/templates/member/profile_info.html
+++ b/templates/member/profile_info.html
@@ -2,22 +2,22 @@
 
 <div class="card bg-light shadow">
     <div class="card-header text-center" >
-        <h4> {% trans "Account #" %}  {{ object.pk }}</h4>
+        <h4> {% trans "Account #" %}  {{ user.pk }}</h4>
     </div>
     <div class="card-top text-center">
-        <a  href="{% url 'member:user_update_pic' object.pk  %}">
-            <img src="{{ object.note.display_image.url }}" class="img-thumbnail mt-2" >
+        <a  href="{% url 'member:user_update_pic' user.pk  %}">
+            <img src="{{ user.note.display_image.url }}" class="img-thumbnail mt-2" >
         </a>
     </div>
     <div class="card-body" id="profile_infos">
         <dl class="row">
             <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>
-            <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>
                 <dd class="col-xl-6">
                     <a class="small" href="{% url 'password_change' %}">
@@ -27,25 +27,25 @@
             {% endif %}
 
             <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>
-            <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>
-            <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>
-            <dd class="col-xl-6 text-truncate">{{ object.note.alias_set.all|join:", " }}</dd>
+            <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">{{ user.note.alias_set.all|join:", " }}</dd>
         </dl>
 
-        {% if object.pk == user.pk %}
+        {% if user.pk == user.pk %}
         <a class="small" href="{% url 'member:auth_token' %}">{% trans 'Manage auth token' %}</a>
         {% endif %}
     </div>
     <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>
-        {% url 'member:user_detail' object.pk as user_profile_url %}
+        <a class="btn btn-primary btn-sm" href="{% url 'member:user_update_profile' user.pk %}">{% trans 'Update Profile' %}</a>
+        {% url 'member:user_detail' user.pk as 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>
         {% endif %}
diff --git a/templates/member/profile_tables.html b/templates/member/profile_tables.html
index 4936cc26..ff66155a 100644
--- a/templates/member/profile_tables.html
+++ b/templates/member/profile_tables.html
@@ -2,10 +2,10 @@
 {% load i18n %}
 {% 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">
         {% 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>
 {% endif %}
 
@@ -22,9 +22,7 @@
 
 <div class="card">
     <div class="card-header position-relative" id="historyListHeading">
-        <a class="collapsed font-weight-bold"
-           data-toggle="collapse" data-target="#historyListCollapse"
-           aria-expanded="true" aria-controls="historyListCollapse">
+            <a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:user.note %} href="{% url 'note:transactions' pk=user.note.pk %}" {% endif %}>
             <i class="fa fa-euro"></i> {% trans "Transaction history" %}
         </a>
     </div>
diff --git a/templates/note/search_transactions.html b/templates/note/search_transactions.html
new file mode 100644
index 00000000..cc92639a
--- /dev/null
+++ b/templates/note/search_transactions.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 %}
\ No newline at end of file
diff --git a/templates/wei/weiclub_tables.html b/templates/wei/weiclub_tables.html
index b97f5736..a8d02dcb 100644
--- a/templates/wei/weiclub_tables.html
+++ b/templates/wei/weiclub_tables.html
@@ -1,59 +1,12 @@
 {% load render_table from django_tables2 %}
 {% load i18n %}
+{% load perms %}
 <div class="card">
     <div class="card-header text-center">
         <h4>WEI</h4>
     </div>
     <div class="card-body">
         <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>
 
     {% if club.is_current_wei %}
@@ -101,7 +54,7 @@
 {% if history_list.data %}
     <div class="card">
         <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" %}
             </a>
         </div>
-- 
GitLab