diff --git a/apps/activity/templates/activity/activity_form.html b/apps/activity/templates/activity/activity_form.html
index 8032faaecb3c22f80fc262450fb5396ebd421248..20a6cbd9652bd82bd9c852af4133f12f56304900 100644
--- a/apps/activity/templates/activity/activity_form.html
+++ b/apps/activity/templates/activity/activity_form.html
@@ -23,19 +23,19 @@ SPDX-License-Identifier: GPL-3.0-or-later
 <script>
   var date_end = document.getElementById("id_date_end");
   var date_start = document.getElementById("id_date_start");
-  
+
   function update_date_end (){
     if(date_end.value=="" || date_end.value<date_start.value){
       date_end.value = date_start.value;
     };
   };
-  
+
   function update_date_start (){
     if(date_start.value=="" || date_end.value<date_start.value){
       date_start.value = date_end.value;
     };
   };
-  
+
   date_start.addEventListener('focusout', update_date_end);
   date_end.addEventListener('focusout', update_date_start);
   
diff --git a/apps/activity/views.py b/apps/activity/views.py
index b596eef0a51dd1f0ad405819f9054600ba7ec42b..87c35ef0fad776a8d9767ae0e502293ec8eda356 100644
--- a/apps/activity/views.py
+++ b/apps/activity/views.py
@@ -18,7 +18,7 @@ from django.views import View
 from django.views.decorators.cache import cache_page
 from django.views.generic import DetailView, TemplateView, UpdateView
 from django.views.generic.list import ListView
-from django_tables2.views import MultiTableMixin
+from django_tables2.views import MultiTableMixin, SingleTableMixin
 from api.viewsets import is_regex
 from note.models import Alias, NoteSpecial, NoteUser
 from permission.backends import PermissionBackend
@@ -64,19 +64,15 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin
     Displays all Activities, and classify if they are on-going or upcoming ones.
     """
     model = Activity
-    tables = [ActivityTable, ActivityTable]
+    tables = [
+        lambda data: ActivityTable(data, prefix="all-"),
+        lambda data: ActivityTable(data, prefix="upcoming-"),
+    ]
     extra_context = {"title": _("Activities")}
 
     def get_queryset(self, **kwargs):
         return super().get_queryset(**kwargs).distinct()
 
-    def get_tables(self):
-        tables = super().get_tables()
-
-        tables[0].prefix = "all-"
-        tables[1].prefix = "upcoming-"
-        return tables
-
     def get_tables_data(self):
         # first table = all activities, second table = upcoming
         return [
@@ -100,7 +96,7 @@ class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin
         return context
 
 
-class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
+class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, DetailView):
     """
     Shows details about one activity. Add guest to context
     """
@@ -108,16 +104,25 @@ class ActivityDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
     context_object_name = "activity"
     extra_context = {"title": _("Activity detail")}
 
+    tables = [
+        lambda data: GuestTable(data, prefix="guests-"),
+        lambda data: OpenerTable(data, prefix="opener-"),
+    ]
+
+    def get_tables_data(self):
+        return [
+            Guest.objects.filter(activity=self.object)
+                         .filter(PermissionBackend.filter_queryset(self.request, Guest, "view")),
+            self.object.opener.filter(activity=self.object)
+                              .filter(PermissionBackend.filter_queryset(self.request, Opener, "view")),
+        ]
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data()
 
-        table = GuestTable(data=Guest.objects.filter(activity=self.object)
-                           .filter(PermissionBackend.filter_queryset(self.request, Guest, "view")))
-        context["guests"] = table
-
-        table = OpenerTable(data=self.object.opener.filter(activity=self.object)
-                            .filter(PermissionBackend.filter_queryset(self.request, Opener, "view")))
-        context["opener"] = table
+        tables = context["tables"]
+        for name, table in zip(["guests", "opener"], tables):
+            context[name] = table
 
         context["activity_started"] = timezone.now() > timezone.localtime(self.object.date_start)
 
@@ -189,12 +194,14 @@ class ActivityInviteView(ProtectQuerysetMixin, ProtectedCreateView):
         return reverse_lazy('activity:activity_detail', kwargs={"pk": self.kwargs["pk"]})
 
 
-class ActivityEntryView(LoginRequiredMixin, TemplateView):
+class ActivityEntryView(LoginRequiredMixin, SingleTableMixin, TemplateView):
     """
     Manages entry to an activity
     """
     template_name = "activity/activity_entry.html"
 
+    table_class = EntryTable
+
     def dispatch(self, request, *args, **kwargs):
         """
         Don't display the entry interface if the user has no right to see it (no right to add an entry for itself),
@@ -290,15 +297,9 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
             if settings.DATABASES[note_qs.db]["ENGINE"] == 'django.db.backends.postgresql' else note_qs.distinct()[:20]
         return note_qs
 
-    def get_context_data(self, **kwargs):
-        """
-        Query the list of Guest and Note to the activity and add information to makes entry with JS.
-        """
-        context = super().get_context_data(**kwargs)
-
+    def get_table_data(self):
         activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\
             .distinct().get(pk=self.kwargs["pk"])
-        context["activity"] = activity
 
         matched = []
 
@@ -311,8 +312,17 @@ class ActivityEntryView(LoginRequiredMixin, TemplateView):
             note.activity = activity
             matched.append(note)
 
-        table = EntryTable(data=matched)
-        context["table"] = table
+        return matched
+
+    def get_context_data(self, **kwargs):
+        """
+        Query the list of Guest and Note to the activity and add information to makes entry with JS.
+        """
+        context = super().get_context_data(**kwargs)
+
+        activity = Activity.objects.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))\
+            .distinct().get(pk=self.kwargs["pk"])
+        context["activity"] = activity
 
         context["entries"] = Entry.objects.filter(activity=activity)
 
diff --git a/apps/member/views.py b/apps/member/views.py
index ba8a57e028d378d40a22690bd4625ffe8a0c4454..748e8b1a5ff80f1e25c5b47eab0818aa935b3ae9 100644
--- a/apps/member/views.py
+++ b/apps/member/views.py
@@ -16,7 +16,7 @@ from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import DetailView, UpdateView, TemplateView
 from django.views.generic.edit import FormMixin
-from django_tables2.views import SingleTableView
+from django_tables2.views import MultiTableMixin, SingleTableMixin, SingleTableView
 from rest_framework.authtoken.models import Token
 from api.viewsets import is_regex
 from note.models import Alias, NoteClub, NoteUser, Trust
@@ -166,7 +166,8 @@ class UserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
         # Display only the most recent membership
         club_list = club_list.distinct("club__name")\
             if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_list
-        membership_table = MembershipTable(data=club_list, prefix='membership-')
+        club_list_order_by = self.request.GET.getlist("membership-sort", ("club__name", "-date_start"))
+        membership_table = MembershipTable(data=club_list, prefix='membership-', order_by=club_list_order_by)
         membership_table.paginate(per_page=10, page=self.request.GET.get("membership-page", 1))
         context['club_list'] = membership_table
 
@@ -248,7 +249,7 @@ class UserListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
         return context
 
 
-class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
+class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, DetailView):
     """
     View and manage user trust relationships
     """
@@ -257,13 +258,25 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
     context_object_name = 'user_object'
     extra_context = {"title": _("Note friendships")}
 
+    tables = [
+        lambda data: TrustTable(data, prefix="trust-"),
+        lambda data: TrustedTable(data, prefix="trusted-"),
+    ]
+
+    def get_tables_data(self):
+        note = self.object.note
+        return [
+            note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct(),
+            note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct(),
+        ]
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        note = context['object'].note
-        context["trusting"] = TrustTable(
-            note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
-        context["trusted_by"] = TrustedTable(
-            note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
+
+        tables = context["tables"]
+        for name, table in zip(["trusting", "trusted_by"], tables):
+            context[name] = table
+
         context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
             trusting=context["object"].note,
             trusted=context["object"].note
@@ -282,7 +295,7 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
         return context
 
 
-class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
+class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, DetailView):
     """
     View and manage user aliases.
     """
@@ -291,12 +304,15 @@ class ProfileAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
     context_object_name = 'user_object'
     extra_context = {"title": _("Note aliases")}
 
+    table_class = AliasTable
+    context_table_name = "aliases"
+
+    def get_table_data(self):
+        return self.object.note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct() \
+                                     .order_by('normalized_name')
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        note = context['object'].note
-        context["aliases"] = AliasTable(
-            note.alias.filter(PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct()
-            .order_by('normalized_name').all())
         context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
             note=context["object"].note,
             name="",
@@ -461,7 +477,8 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
         managers = Membership.objects.filter(club=self.object, roles__name="Bureau de club",
                                              date_start__lte=date.today(), date_end__gte=date.today())\
             .order_by('user__last_name').all()
-        context["managers"] = ClubManagerTable(data=managers, prefix="managers-")
+        managers_order_by = self.request.GET.getlist("managers-sort", ('user__last_name'))
+        context["managers"] = ClubManagerTable(data=managers, prefix="managers-", order_by=managers_order_by)
         # transaction history
         club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note))\
             .filter(PermissionBackend.filter_queryset(self.request, Transaction, "view"))\
@@ -479,7 +496,8 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
         club_member = club_member.distinct("user__username")\
             if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else club_member
 
-        membership_table = MembershipTable(data=club_member, prefix="membership-")
+        membership_order_by = self.request.GET.getlist("membership-sort", ("user__username", "-date_start"))
+        membership_table = MembershipTable(data=club_member, prefix="membership-", order_by=membership_order_by)
         membership_table.paginate(per_page=5, page=self.request.GET.get('membership-page', 1))
         context['member_list'] = membership_table
 
@@ -520,7 +538,7 @@ class ClubDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
         return context
 
 
-class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
+class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, DetailView):
     """
     Manage aliases of a club.
     """
@@ -529,11 +547,16 @@ class ClubAliasView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
     context_object_name = 'club'
     extra_context = {"title": _("Note aliases")}
 
+    table_class = AliasTable
+    context_table_name = "aliases"
+
+    def get_table_data(self):
+        return self.object.note.alias.filter(
+            PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct()
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
-        note = context['object'].note
-        context["aliases"] = AliasTable(note.alias.filter(
-            PermissionBackend.filter_queryset(self.request, Alias, "view")).distinct().all())
+
         context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_alias", Alias(
             note=context["object"].note,
             name="",
diff --git a/apps/permission/views.py b/apps/permission/views.py
index 65c6e8892829769db06caf0dd3ed2d0c0f052c0a..053efa39cbbcd2b9f465007706960991c91cbd5c 100644
--- a/apps/permission/views.py
+++ b/apps/permission/views.py
@@ -12,6 +12,7 @@ from django.forms import HiddenInput
 from django.http import Http404
 from django.utils.translation import gettext_lazy as _
 from django.views.generic import UpdateView, TemplateView, CreateView
+from django_tables2 import MultiTableMixin
 from member.models import Membership
 
 from .backends import PermissionBackend
@@ -105,10 +106,31 @@ class ProtectedCreateView(LoginRequiredMixin, CreateView):
         return super().dispatch(request, *args, **kwargs)
 
 
-class RightsView(TemplateView):
+class RightsView(MultiTableMixin, TemplateView):
     template_name = "permission/all_rights.html"
     extra_context = {"title": _("Rights")}
 
+    tables = [
+        lambda data: RightsTable(data, prefix="clubs-"),
+        lambda data: SuperuserTable(data, prefix="superusers-"),
+    ]
+
+    def get_tables_data(self):
+        special_memberships = Membership.objects.filter(
+            date_start__lte=date.today(),
+            date_end__gte=date.today(),
+        ).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent⋅e BDE")
+                                                  | Q(name="Adhérent⋅e Kfet")
+                                                  | Q(name="Membre de club")
+                                                  | Q(name="Bureau de club"))
+                                                & Q(weirole__isnull=True))))\
+            .order_by("club__name", "user__last_name")\
+            .distinct().all()
+        return [
+            special_memberships,
+            User.objects.filter(is_superuser=True).order_by("last_name"),
+        ]
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
 
@@ -126,19 +148,9 @@ class RightsView(TemplateView):
             role.clubs = [membership.club for membership in active_memberships if role in membership.roles.all()]
 
         if self.request.user.is_authenticated:
-            special_memberships = Membership.objects.filter(
-                date_start__lte=date.today(),
-                date_end__gte=date.today(),
-            ).filter(roles__in=Role.objects.filter((~(Q(name="Adhérent⋅e BDE")
-                                                      | Q(name="Adhérent⋅e Kfet")
-                                                      | Q(name="Membre de club")
-                                                      | Q(name="Bureau de club"))
-                                                    & Q(weirole__isnull=True))))\
-                .order_by("club__name", "user__last_name")\
-                .distinct().all()
-            context["special_memberships_table"] = RightsTable(special_memberships, prefix="clubs-")
-            context["superusers"] = SuperuserTable(User.objects.filter(is_superuser=True).order_by("last_name").all(),
-                                                   prefix="superusers-")
+            tables = context["tables"]
+            for name, table in zip(["special_memberships_table", "superusers"], tables):
+                context[name] = table
 
         return context
 
diff --git a/apps/treasury/views.py b/apps/treasury/views.py
index 64692793c41b1fb8a1f6ee88e7dc741b0c9a9783..33bdb88c925bdfb5801533c6198c4f82b074630f 100644
--- a/apps/treasury/views.py
+++ b/apps/treasury/views.py
@@ -19,7 +19,7 @@ from django.utils.translation import gettext_lazy as _
 from django.views.generic import UpdateView, DetailView
 from django.views.generic.base import View, TemplateView
 from django.views.generic.edit import BaseFormView, DeleteView
-from django_tables2 import SingleTableView
+from django_tables2 import MultiTableMixin, SingleTableMixin, SingleTableView
 from api.viewsets import is_regex
 from note.models import SpecialTransaction, NoteSpecial, Alias
 from note_kfet.settings.base import BASE_DIR
@@ -252,21 +252,26 @@ class RemittanceCreateView(ProtectQuerysetMixin, ProtectedCreateView):
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
 
-        context["table"] = RemittanceTable(
-            data=Remittance.objects.filter(
-                PermissionBackend.filter_queryset(self.request, Remittance, "view")).all())
         context["special_transactions"] = SpecialTransactionTable(data=SpecialTransaction.objects.none())
 
         return context
 
 
-class RemittanceListView(LoginRequiredMixin, TemplateView):
+class RemittanceListView(LoginRequiredMixin, MultiTableMixin, TemplateView):
     """
     List existing Remittances
     """
     template_name = "treasury/remittance_list.html"
     extra_context = {"title": _("Remittances list")}
 
+    tables = [
+        lambda data: RemittanceTable(data, prefix="opened-remittances-"),
+        lambda data: RemittanceTable(data, prefix="closed-remittances-"),
+        lambda data: SpecialTransactionTable(data, prefix="no-remittance-", exclude=('remittance_remove', )),
+        lambda data: SpecialTransactionTable(data, prefix="with-remittance-", exclude=('remittance_add', )),
+    ]
+    paginate_by = 10     # number of rows in tables
+
     def dispatch(self, request, *args, **kwargs):
         # Check that the user is authenticated
         if not request.user.is_authenticated:
@@ -276,49 +281,37 @@ class RemittanceListView(LoginRequiredMixin, TemplateView):
             raise PermissionDenied(_("You are not able to see the treasury interface."))
         return super().dispatch(request, *args, **kwargs)
 
+    def get_tables_data(self):
+        return [
+            Remittance.objects.filter(closed=False).filter(
+                PermissionBackend.filter_queryset(self.request, Remittance, "view")),
+            Remittance.objects.filter(closed=True).filter(
+                PermissionBackend.filter_queryset(self.request, Remittance, "view")),
+            SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
+                                              specialtransactionproxy__remittance=None).filter(
+                PermissionBackend.filter_queryset(self.request, Remittance, "view")),
+            SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
+                                              specialtransactionproxy__remittance__closed=False).filter(
+                PermissionBackend.filter_queryset(self.request, Remittance, "view")),
+        ]
+
     def get_context_data(self, **kwargs):
         context = super().get_context_data(**kwargs)
 
-        opened_remittances = RemittanceTable(
-            data=Remittance.objects.filter(closed=False).filter(
-                PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(),
-            prefix="opened-remittances-",
-        )
-        opened_remittances.paginate(page=self.request.GET.get("opened-remittances-page", 1), per_page=10)
-        context["opened_remittances"] = opened_remittances
-
-        closed_remittances = RemittanceTable(
-            data=Remittance.objects.filter(closed=True).filter(
-                PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(),
-            prefix="closed-remittances-",
-        )
-        closed_remittances.paginate(page=self.request.GET.get("closed-remittances-page", 1), per_page=10)
-        context["closed_remittances"] = closed_remittances
-
-        no_remittance_tr = SpecialTransactionTable(
-            data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
-                                                   specialtransactionproxy__remittance=None).filter(
-                PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(),
-            exclude=('remittance_remove', ),
-            prefix="no-remittance-",
-        )
-        no_remittance_tr.paginate(page=self.request.GET.get("no-remittance-page", 1), per_page=10)
-        context["special_transactions_no_remittance"] = no_remittance_tr
-
-        with_remittance_tr = SpecialTransactionTable(
-            data=SpecialTransaction.objects.filter(source__in=NoteSpecial.objects.filter(~Q(remittancetype=None)),
-                                                   specialtransactionproxy__remittance__closed=False).filter(
-                PermissionBackend.filter_queryset(self.request, Remittance, "view")).all(),
-            exclude=('remittance_add', ),
-            prefix="with-remittance-",
-        )
-        with_remittance_tr.paginate(page=self.request.GET.get("with-remittance-page", 1), per_page=10)
-        context["special_transactions_with_remittance"] = with_remittance_tr
+        tables = context["tables"]
+        names = [
+            "opened_remittances",
+            "closed_remittances",
+            "special_transactions_no_remittance",
+            "special_transactions_with_remittance",
+        ]
+        for name, table in zip(names, tables):
+            context[name] = table
 
         return context
 
 
-class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
+class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableMixin, UpdateView):
     """
     Update Remittance
     """
@@ -326,19 +319,18 @@ class RemittanceUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView)
     form_class = RemittanceForm
     extra_context = {"title": _("Update a remittance")}
 
+    table_class = SpecialTransactionTable
+    context_table_name = "special_transactions"
+
     def get_success_url(self):
         return reverse_lazy('treasury:remittance_list')
 
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-
-        data = SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter(
-            PermissionBackend.filter_queryset(self.request, Remittance, "view")).all()
-        context["special_transactions"] = SpecialTransactionTable(
-            data=data,
-            exclude=('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', ))
+    def get_table_data(self):
+        return SpecialTransaction.objects.filter(specialtransactionproxy__remittance=self.object).filter(
+            PermissionBackend.filter_queryset(self.request, Remittance, "view"))
 
-        return context
+    def get_table_kwargs(self):
+        return {"exclude": ('remittance_add', 'remittance_remove', ) if self.object.closed else ('remittance_add', )}
 
 
 class LinkTransactionToRemittanceView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
diff --git a/apps/wei/views.py b/apps/wei/views.py
index 125b81599b4d233427b05a1460756c7e29ebd0d0..7a0e32fec749403ba36860b9b36437fab64d2826 100644
--- a/apps/wei/views.py
+++ b/apps/wei/views.py
@@ -22,7 +22,7 @@ from django.views import View
 from django.views.generic import DetailView, UpdateView, RedirectView, TemplateView
 from django.utils.translation import gettext_lazy as _
 from django.views.generic.edit import BaseFormView, DeleteView
-from django_tables2 import SingleTableView
+from django_tables2 import SingleTableView, MultiTableMixin
 from api.viewsets import is_regex
 from member.models import Membership, Club
 from note.models import Transaction, NoteClub, Alias, SpecialTransaction, NoteSpecial
@@ -101,7 +101,7 @@ class WEICreateView(ProtectQuerysetMixin, ProtectedCreateView):
         return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.pk})
 
 
-class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
+class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, DetailView):
     """
     View WEI information
     """
@@ -109,34 +109,40 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
     context_object_name = "club"
     extra_context = {"title": _("WEI Detail")}
 
-    def get_context_data(self, **kwargs):
-        context = super().get_context_data(**kwargs)
-
-        club = context["club"]
+    tables = [
+        lambda data: HistoryTable(data, prefix="history-"),
+        lambda data: WEIMembershipTable(data, prefix="membership-"),
+        lambda data: WEIRegistrationTable(data, prefix="pre-registration-"),
+        lambda data: BusTable(data, prefix="bus-"),
+    ]
+    paginate_by = 20   # number of rows in tables
 
+    def get_tables_data(self):
+        club = self.object
         club_transactions = Transaction.objects.all().filter(Q(source=club.note) | Q(destination=club.note)) \
             .filter(PermissionBackend.filter_queryset(self.request, Transaction, "view")) \
             .order_by('-created_at', '-id')
-        history_table = HistoryTable(club_transactions, prefix="history-")
-        history_table.paginate(per_page=20, page=self.request.GET.get('history-page', 1))
-        context['history_list'] = history_table
-
         club_member = WEIMembership.objects.filter(
             club=club,
             date_end__gte=date.today(),
         ).filter(PermissionBackend.filter_queryset(self.request, WEIMembership, "view"))
-        membership_table = WEIMembershipTable(data=club_member, prefix="membership-")
-        membership_table.paginate(per_page=20, page=self.request.GET.get('membership-page', 1))
-        context['member_list'] = membership_table
-
         pre_registrations = WEIRegistration.objects.filter(
             PermissionBackend.filter_queryset(self.request, WEIRegistration, "view")).filter(
             membership=None,
             wei=club
         )
-        pre_registrations_table = WEIRegistrationTable(data=pre_registrations, prefix="pre-registration-")
-        pre_registrations_table.paginate(per_page=20, page=self.request.GET.get('pre-registration-page', 1))
-        context['pre_registrations'] = pre_registrations_table
+        buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \
+            .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name")
+        return [club_transactions, club_member, pre_registrations, buses, ]
+
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+
+        club = context["club"]
+
+        tables = context["tables"]
+        for name, table in zip(["history_list", "member_list", "pre_registrations", "buses"], tables):
+            context[name] = table
 
         my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user)
         if my_registration.exists():
@@ -145,11 +151,6 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
             my_registration = None
         context["my_registration"] = my_registration
 
-        buses = Bus.objects.filter(PermissionBackend.filter_queryset(self.request, Bus, "view")) \
-            .filter(wei=self.object).annotate(count=Count("memberships")).order_by("name")
-        bus_table = BusTable(data=buses, prefix="bus-")
-        context['buses'] = bus_table
-
         random_user = User.objects.filter(~Q(wei__wei__in=[club])).first()
 
         if random_user is None: