diff --git a/apps/member/hashers.py b/apps/member/hashers.py
index 69db24b0523c600c3a5e2682d104303b93beec73..32f8c63ee774a0aede70bed2a807f36025c332eb 100644
--- a/apps/member/hashers.py
+++ b/apps/member/hashers.py
@@ -2,10 +2,12 @@
 # SPDX-License-Identifier: GPL-3.0-or-later
 
 import hashlib
+from collections import OrderedDict
 
 from django.conf import settings
-from django.contrib.auth.hashers import PBKDF2PasswordHasher
+from django.contrib.auth.hashers import PBKDF2PasswordHasher, mask_hash
 from django.utils.crypto import constant_time_compare
+from django.utils.translation import gettext_lazy as _
 from note_kfet.middlewares import get_current_request
 
 
@@ -47,6 +49,18 @@ class CustomNK15Hasher(PBKDF2PasswordHasher):
             return constant_time_compare(hashlib.sha256((salt + password).encode("utf-8")).hexdigest(), db_hashed_pass)
         return super().verify(password, encoded)
 
+    def safe_summary(self, encoded):
+        # Displayed information in Django Admin.
+        if '|' in encoded:
+            salt, db_hashed_pass = encoded.split('$')[2].split('|')
+            return OrderedDict([
+                (_('algorithm'), 'custom_nk15'),
+                (_('iterations'), '1'),
+                (_('salt'), mask_hash(salt)),
+                (_('hash'), mask_hash(db_hashed_pass)),
+            ])
+        return super().safe_summary(encoded)
+
 
 class DebugSuperuserBackdoor(PBKDF2PasswordHasher):
     """
diff --git a/apps/member/models.py b/apps/member/models.py
index 2564190ae770b6e49da24d285d35852df890b4ca..73b7c668e11cd6331260f76fa63ed077fb7d24c4 100644
--- a/apps/member/models.py
+++ b/apps/member/models.py
@@ -57,7 +57,7 @@ class Profile(models.Model):
             ('A1', _("Mathematics (A1)")),
             ('A2', _("Physics (A2)")),
             ("A'2", _("Applied physics (A'2)")),
-            ('A''2', _("Chemistry (A''2)")),
+            ("A''2", _("Chemistry (A''2)")),
             ('A3', _("Biology (A3)")),
             ('B1234', _("SAPHIRE (B1234)")),
             ('B1', _("Mechanics (B1)")),
diff --git a/apps/member/templates/member/includes/profile_info.html b/apps/member/templates/member/includes/profile_info.html
index e1941d23221e2bbe880b27935844d85a49b6588e..378d54e21f4286be8bf070ae9f305897daf7cfeb 100644
--- a/apps/member/templates/member/includes/profile_info.html
+++ b/apps/member/templates/member/includes/profile_info.html
@@ -39,13 +39,13 @@
         <dt class="col-xl-6">{% trans 'address'|capfirst %}</dt>
         <dd class="col-xl-6">{{ user_object.profile.address }}</dd>
 
-        {% if user_object.note and "note.view_note"|has_perm:user_object.note %}
-        <dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
-        <dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
-
         <dt class="col-xl-6">{% trans 'paid'|capfirst %}</dt>
         <dd class="col-xl-6">{{ user_object.profile.paid|yesno }}</dd>
-        {% endif %}
+    {% endif %}
+
+    {% if user_object.note and "note.view_note"|has_perm:user_object.note %}
+        <dt class="col-xl-6">{% trans 'balance'|capfirst %}</dt>
+        <dd class="col-xl-6">{{ user_object.note.balance | pretty_money }}</dd>
     {% endif %}
 </dl>
 
diff --git a/apps/note/api/views.py b/apps/note/api/views.py
index d4021210cfaa3eae0ad203646ede332c510dc66a..a228bdf601f2b33b2fcbf09ff4df839c618c9e20 100644
--- a/apps/note/api/views.py
+++ b/apps/note/api/views.py
@@ -1,5 +1,6 @@
 # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
+import re
 
 from django.conf import settings
 from django.db.models import Q
@@ -133,23 +134,31 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
             if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' else queryset
 
         alias = self.request.query_params.get("alias", None)
+        # Check if this is a valid regex. If not, we won't check regex
+        try:
+            re.compile(alias)
+            valid_regex = True
+        except (re.error, TypeError):
+            valid_regex = False
+        suffix = '__iregex' if valid_regex else '__istartswith'
+        alias_prefix = '^' if valid_regex else ''
         queryset = queryset.prefetch_related('note')
 
         if alias:
             # We match first an alias if it is matched without normalization,
             # then if the normalized pattern matches a normalized alias.
             queryset = queryset.filter(
-                name__iregex="^" + alias
+                **{f'name{suffix}': alias_prefix + alias}
             ).union(
                 queryset.filter(
-                    Q(normalized_name__iregex="^" + Alias.normalize(alias))
-                    & ~Q(name__iregex="^" + alias)
+                    Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
+                    & ~Q(**{f'name{suffix}': alias_prefix + alias})
                 ),
                 all=True).union(
                 queryset.filter(
-                    Q(normalized_name__iregex="^" + alias.lower())
-                    & ~Q(normalized_name__iregex="^" + Alias.normalize(alias))
-                    & ~Q(name__iregex="^" + alias)
+                    Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
+                    & ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
+                    & ~Q(**{f'name{suffix}': alias_prefix + alias})
                 ),
                 all=True)
 
diff --git a/apps/permission/backends.py b/apps/permission/backends.py
index af071455e3904ac6762de2c9513cea0be301ad46..f9c90d560a07d62003d516c2505c860b7153186d 100644
--- a/apps/permission/backends.py
+++ b/apps/permission/backends.py
@@ -159,6 +159,10 @@ class PermissionBackend(ModelBackend):
         primary key, the result is not memoized. Moreover, the right could change
         (e.g. for a transaction, the balance of the user could change)
         """
+        # Requested by a shell
+        if request is None:
+            return False
+
         user_obj = request.user
         sess = request.session
 
diff --git a/apps/permission/fixtures/initial.json b/apps/permission/fixtures/initial.json
index 27df5b292aa94abc6fc7d9cc157e9a11020c0b54..1a55e99198a308c8c572cfcc4d9939a7569838d9 100644
--- a/apps/permission/fixtures/initial.json
+++ b/apps/permission/fixtures/initial.json
@@ -627,7 +627,7 @@
 			"type": "view",
 			"mask": 1,
 			"field": "",
-			"permanent": false,
+			"permanent": true,
 			"description": "Voir les personnes qu'on a invitées"
 		}
 	},
@@ -2883,6 +2883,7 @@
 				3,
 				4,
 				5,
+				6,
 				7,
 				8,
 				9,
@@ -2890,6 +2891,10 @@
 				11,
 				12,
 				13,
+				14,
+				15,
+				16,
+				17,
 				22,
 				48,
 				52,
@@ -2907,11 +2912,6 @@
 			"for_club": 2,
 			"name": "Adh\u00e9rent Kfet",
 			"permissions": [
-				6,
-				14,
-				15,
-				16,
-				17,
 				22,
 				34,
 				36,
@@ -3304,6 +3304,7 @@
 				30,
 				31,
 				70,
+				72,
 				143,
 				166,
 				167,
@@ -3511,6 +3512,8 @@
 				56,
 				57,
 				58,
+				70,
+				72,
 				135,
 				137,
 				143,
diff --git a/apps/permission/signals.py b/apps/permission/signals.py
index 78d0b8f98a52c9017c85fc176cc73d3beb614718..6fb273927de9549e365c888d0fe4d1e3588de807 100644
--- a/apps/permission/signals.py
+++ b/apps/permission/signals.py
@@ -61,6 +61,12 @@ def pre_save_object(sender, instance, **kwargs):
             # If the field wasn't modified, no need to check the permissions
             if old_value == new_value:
                 continue
+
+            if app_label == 'auth' and model_name == 'user' and field.name == 'password' and request.user.is_anonymous:
+                # We must ignore password changes from anonymous users since it can be done by people that forgot
+                # their password. We trust password change form.
+                continue
+
             if not PermissionBackend.check_perm(request, app_label + ".change_" + model_name + "_" + field_name,
                                                 instance):
                 raise PermissionDenied(
diff --git a/apps/registration/views.py b/apps/registration/views.py
index 9b385324d1165c9a69491d65f46dddd148d4fd1e..b256f591c7550c4f0d28a4659cf70493af881d66 100644
--- a/apps/registration/views.py
+++ b/apps/registration/views.py
@@ -85,6 +85,9 @@ class UserCreateView(CreateView):
         return super().form_valid(form)
 
     def get_success_url(self):
+        # Direct access to validation menu if we have the right to validate it
+        if PermissionBackend.check_perm(self.request, 'auth.view_user', self.object):
+            return reverse_lazy('registration:future_user_detail', args=(self.object.pk,))
         return reverse_lazy('registration:email_validation_sent')
 
 
diff --git a/apps/treasury/api/serializers.py b/apps/treasury/api/serializers.py
index bc15db889b3b235157cd7deb1d0d3ac389a1170f..5442fd06b3c7d2fe6fef10b680c2432d27318e8f 100644
--- a/apps/treasury/api/serializers.py
+++ b/apps/treasury/api/serializers.py
@@ -1,6 +1,6 @@
 # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
 # SPDX-License-Identifier: GPL-3.0-or-later
-
+from django.db import transaction
 from rest_framework import serializers
 from note.api.serializers import SpecialTransactionSerializer
 
@@ -68,6 +68,14 @@ class SogeCreditSerializer(serializers.ModelSerializer):
     The djangorestframework plugin will analyse the model `SogeCredit` and parse all fields in the API.
     """
 
+    @transaction.atomic
+    def save(self, **kwargs):
+        # Update soge transactions after creating a credit
+        instance = super().save(**kwargs)
+        instance.update_transactions()
+        instance.save()
+        return instance
+
     class Meta:
         model = SogeCredit
         fields = '__all__'
diff --git a/apps/treasury/forms.py b/apps/treasury/forms.py
index 6c5bc353b294912900f04e9b0096e397a884c3aa..024411891d248bc79a18b932b97a6bdde532f5dc 100644
--- a/apps/treasury/forms.py
+++ b/apps/treasury/forms.py
@@ -4,11 +4,12 @@
 from crispy_forms.helper import FormHelper
 from crispy_forms.layout import Submit
 from django import forms
+from django.contrib.auth.models import User
 from django.db import transaction
 from django.utils.translation import gettext_lazy as _
-from note_kfet.inputs import AmountInput
+from note_kfet.inputs import AmountInput, Autocomplete
 
-from .models import Invoice, Product, Remittance, SpecialTransactionProxy
+from .models import Invoice, Product, Remittance, SpecialTransactionProxy, SogeCredit
 
 
 class InvoiceForm(forms.ModelForm):
@@ -161,3 +162,19 @@ class LinkTransactionToRemittanceForm(forms.ModelForm):
     class Meta:
         model = SpecialTransactionProxy
         fields = ('remittance', )
+
+
+class SogeCreditForm(forms.ModelForm):
+    class Meta:
+        model = SogeCredit
+        fields = ('user', )
+        widgets = {
+            "user": Autocomplete(
+                User,
+                attrs={
+                    'api_url': '/api/user/',
+                    'name_field': 'username',
+                    'placeholder': 'Nom ...',
+                },
+            ),
+        }
diff --git a/apps/treasury/models.py b/apps/treasury/models.py
index 0b5948fda907ead42acadacd6387b96f7d6a0c15..7e4e1566541cceea305c84cf1598c97a86d512d6 100644
--- a/apps/treasury/models.py
+++ b/apps/treasury/models.py
@@ -3,6 +3,7 @@
 
 from datetime import date
 
+from django.conf import settings
 from django.contrib.auth.models import User
 from django.core.exceptions import ValidationError
 from django.core.validators import MinValueValidator
@@ -11,6 +12,7 @@ from django.db.models import Q
 from django.template.loader import render_to_string
 from django.utils import timezone
 from django.utils.translation import gettext_lazy as _
+from member.models import Club, Membership
 from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction, NoteUser
 
 
@@ -286,6 +288,7 @@ class SogeCredit(models.Model):
     transactions = models.ManyToManyField(
         MembershipTransaction,
         related_name="+",
+        blank=True,
         verbose_name=_("membership transactions"),
     )
 
@@ -305,6 +308,42 @@ class SogeCredit(models.Model):
         return self.credit_transaction.total if self.valid \
             else sum(transaction.total for transaction in self.transactions.all())
 
+    def update_transactions(self):
+        """
+        The Sogé credit may be created after the user already paid its memberships.
+        We query transactions and update the credit, if it is unvalid.
+        """
+        if self.valid or not self.pk:
+            return
+
+        bde = Club.objects.get(name="BDE")
+        kfet = Club.objects.get(name="Kfet")
+        bde_qs = Membership.objects.filter(user=self.user, club=bde, date_start__gte=bde.membership_start)
+        kfet_qs = Membership.objects.filter(user=self.user, club=kfet, date_start__gte=kfet.membership_start)
+
+        if bde_qs.exists():
+            m = bde_qs.get()
+            if m.transaction not in self.transactions.all():
+                self.transactions.add(m.transaction)
+
+        if kfet_qs.exists():
+            m = kfet_qs.get()
+            if m.transaction not in self.transactions.all():
+                self.transactions.add(m.transaction)
+
+        if 'wei' in settings.INSTALLED_APPS:
+            from wei.models import WEIClub
+            wei = WEIClub.objects.order_by('-year').first()
+            wei_qs = Membership.objects.filter(user=self.user, club=wei, date_start__gte=wei.membership_start)
+            if wei_qs.exists():
+                m = wei_qs.get()
+                if m.transaction not in self.transactions.all():
+                    self.transactions.add(m.transaction)
+
+        for tr in self.transactions.all():
+            tr.valid = False
+            tr.save()
+
     def invalidate(self):
         """
         Invalidating a Société générale delete the transaction of the bank if it was already created.
@@ -365,7 +404,8 @@ class SogeCredit(models.Model):
             self.credit_transaction.amount = self.amount
             self.credit_transaction._force_save = True
             self.credit_transaction.save()
-        super().save(*args, **kwargs)
+
+        return super().save(*args, **kwargs)
 
     def delete(self, **kwargs):
         """
diff --git a/apps/treasury/templates/treasury/sogecredit_list.html b/apps/treasury/templates/treasury/sogecredit_list.html
index 2bcf3155345f3b15e5688c0683610431089a06a9..4aecb8eb2c3c3f8f8403d3393f80c48f98a564b5 100644
--- a/apps/treasury/templates/treasury/sogecredit_list.html
+++ b/apps/treasury/templates/treasury/sogecredit_list.html
@@ -3,6 +3,7 @@
 SPDX-License-Identifier: GPL-3.0-or-later
 {% endcomment %}
 {% load render_table from django_tables2 %}
+{% load crispy_forms_filters %}
 {% load i18n %}
 
 {% block content %}
@@ -27,7 +28,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
         {{ title }}
     </h3>
     <div class="card-body">
-        <input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note ...">
+        <div class="input-group">
+            <input id="searchbar" type="text" class="form-control" placeholder="Nom/prénom/note ...">
+            <div class="input-group-append">
+                <button id="add_sogecredit" class="btn btn-success" data-toggle="modal" data-target="#add-sogecredit-modal">{% trans "Add" %}</button>
+            </div>
+        </div>
         <div class="form-check">
             <label for="invalid_only" class="form-check-label">
                 <input id="invalid_only" name="invalid_only" type="checkbox" class="checkboxinput form-check-input" checked>
@@ -47,28 +53,65 @@ SPDX-License-Identifier: GPL-3.0-or-later
         {% endif %}
     </div>
 </div>
+
+{# Popup to add new Soge credits manually if needed #}
+<div class="modal fade" id="add-sogecredit-modal" tabindex="-1" role="dialog" aria-labelledby="addSogeCredit"
+     aria-hidden="true">
+    <div class="modal-dialog" role="document">
+        <div class="modal-content">
+            <div class="modal-header">
+                <h5 class="modal-title" id="lockNote">{% trans "Add credit from the Société générale" %}</h5>
+                <button type="button" class="close btn-modal" data-dismiss="modal" aria-label="Close">
+                    <span aria-hidden="true">&times;</span>
+                </button>
+            </div>
+            <div class="modal-body">
+                {{ form|crispy }}
+            </div>
+            <div class="modal-footer">
+                <button type="button" class="btn btn-secondary btn-modal" data-dismiss="modal">{% trans "Close" %}</button>
+                <button type="button" class="btn btn-success btn-modal" data-dismiss="modal" onclick="addSogeCredit()">{% trans "Add" %}</button>
+            </div>
+        </div>
+    </div>
+</div>
 {% endblock %}
 
 {% block extrajavascript %}
 <script type="text/javascript">
-    $(document).ready(function () {
-        let old_pattern = null;
-        let searchbar_obj = $("#searchbar");
-        let invalid_only_obj = $("#invalid_only");
+    let old_pattern = null;
+    let searchbar_obj = $("#searchbar");
+    let invalid_only_obj = $("#invalid_only");
+
+    function reloadTable() {
+        let pattern = searchbar_obj.val();
+
+        $("#credits_table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + (
+            invalid_only_obj.is(':checked') ? "" : "&valid=1") + " #credits_table");
 
-        function reloadTable() {
-            let pattern = searchbar_obj.val();
+        $(".table-row").click(function () {
+            window.document.location = $(this).data("href");
+        });
+    }
 
-            $("#credits_table").load(location.pathname + "?search=" + pattern.replace(" ", "%20") + (
-                invalid_only_obj.is(':checked') ? "" : "&valid=1") + " #credits_table");
+    searchbar_obj.keyup(reloadTable);
+    invalid_only_obj.change(reloadTable);
 
-            $(".table-row").click(function () {
-                window.document.location = $(this).data("href");
-            });
-        }
+    function addSogeCredit() {
+        let user_pk = $('#id_user_pk').val()
+        if (!user_pk)
+            return
 
-        searchbar_obj.keyup(reloadTable);
-        invalid_only_obj.change(reloadTable);
-    });
+        $.post('/api/treasury/soge_credit/?format=json', {
+            csrfmiddlewaretoken: CSRF_TOKEN,
+            user: user_pk,
+        }).done(function() {
+            addMsg("{% trans "Credit successfully registered" %}", 'success', 10000)
+            reloadTable()
+        }).fail(function (xhr) {
+            errMsg(xhr.responseJSON, 30000)
+            reloadTable()
+        })
+    }
 </script>
 {% endblock %}
\ No newline at end of file
diff --git a/apps/treasury/views.py b/apps/treasury/views.py
index b9a7fe7c5cbb8e96e559caf7addde49e9a0ad652..aee6ea0402e1887b2f86150927e108446aa49ad6 100644
--- a/apps/treasury/views.py
+++ b/apps/treasury/views.py
@@ -25,7 +25,8 @@ from note_kfet.settings.base import BASE_DIR
 from permission.backends import PermissionBackend
 from permission.views import ProtectQuerysetMixin, ProtectedCreateView
 
-from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, LinkTransactionToRemittanceForm
+from .forms import InvoiceForm, ProductFormSet, ProductFormSetHelper, RemittanceForm, \
+    LinkTransactionToRemittanceForm, SogeCreditForm
 from .models import Invoice, Product, Remittance, SpecialTransactionProxy, SogeCredit
 from .tables import InvoiceTable, RemittanceTable, SpecialTransactionTable, SogeCreditTable
 
@@ -433,6 +434,11 @@ class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableVi
 
         return qs
 
+    def get_context_data(self, **kwargs):
+        context = super().get_context_data(**kwargs)
+        context['form'] = SogeCreditForm(self.request.POST or None)
+        return context
+
 
 class SogeCreditManageView(LoginRequiredMixin, ProtectQuerysetMixin, BaseFormView, DetailView):
     """
diff --git a/apps/wei/forms/registration.py b/apps/wei/forms/registration.py
index 13e7b86bf387ce48c7bd39a2ee8e12ced107bcaa..474d83ee75ae0613eed14167db8038024ffaa70c 100644
--- a/apps/wei/forms/registration.py
+++ b/apps/wei/forms/registration.py
@@ -6,7 +6,7 @@ from django.contrib.auth.models import User
 from django.db.models import Q
 from django.forms import CheckboxSelectMultiple
 from django.utils.translation import gettext_lazy as _
-from note.models import NoteSpecial
+from note.models import NoteSpecial, NoteUser
 from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget
 
 from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole
@@ -27,6 +27,15 @@ class WEIForm(forms.ModelForm):
 
 
 class WEIRegistrationForm(forms.ModelForm):
+    def clean(self):
+        cleaned_data = super().clean()
+
+        if 'user' in cleaned_data:
+            if not NoteUser.objects.filter(user=cleaned_data['user']).exists():
+                self.add_error('user', _("The selected user is not validated. Please validate its account first"))
+
+        return cleaned_data
+
     class Meta:
         model = WEIRegistration
         exclude = ('wei', )
diff --git a/apps/wei/forms/surveys/wei2021.py b/apps/wei/forms/surveys/wei2021.py
index b0cfb0cb5d297d36b5677277e5d64b6259055397..a6a241cb60bd237a9dbf586a021f74e4c913b4e4 100644
--- a/apps/wei/forms/surveys/wei2021.py
+++ b/apps/wei/forms/surveys/wei2021.py
@@ -170,6 +170,7 @@ class WEISurveyAlgorithm2021(WEISurveyAlgorithm):
         We modify it to allow buses to have multiple "weddings".
         """
         surveys = list(self.get_survey_class()(r) for r in self.get_registrations())  # All surveys
+        surveys = [s for s in surveys if s.is_complete()]
         free_surveys = [s for s in surveys if not s.information.valid]  # Remaining surveys
         while free_surveys:  # Some students are not affected
             survey = free_surveys[0]
diff --git a/apps/wei/management/commands/wei_algorithm.py b/apps/wei/management/commands/wei_algorithm.py
index 558dfae4f10d0104312ac9ace8a5ee284a3d8d62..238bf13c919d087165bbab332c919392ce472fd0 100644
--- a/apps/wei/management/commands/wei_algorithm.py
+++ b/apps/wei/management/commands/wei_algorithm.py
@@ -28,7 +28,8 @@ class Command(BaseCommand):
 
         output = options['output']
         registrations = algorithm.get_registrations()
-        per_bus = {bus: [r for r in registrations if r.information['selected_bus_pk'] == bus.pk]
+        per_bus = {bus: [r for r in registrations if 'selected_bus_pk' in r.information
+                         and r.information['selected_bus_pk'] == bus.pk]
                    for bus in algorithm.get_buses()}
         for bus, members in per_bus.items():
             output.write(bus.name + "\n")
diff --git a/apps/wei/models.py b/apps/wei/models.py
index b59a0dfd7b0c22c0dd3fddce129e65253dcd00b9..7ab56f57269a47df69f87076c4768b5bb50df50f 100644
--- a/apps/wei/models.py
+++ b/apps/wei/models.py
@@ -364,8 +364,19 @@ class WEIMembership(Membership):
                 # to treasurers.
                 transaction.refresh_from_db()
                 from treasury.models import SogeCredit
-                soge_credit = SogeCredit.objects.get_or_create(user=self.user)[0]
+                soge_credit, created = SogeCredit.objects.get_or_create(user=self.user)
                 soge_credit.refresh_from_db()
                 transaction.save()
                 soge_credit.transactions.add(transaction)
                 soge_credit.save()
+
+                soge_credit.update_transactions()
+                soge_credit.save()
+
+                if soge_credit.valid and \
+                        soge_credit.credit_transaction.total != sum(tr.total for tr in soge_credit.transactions.all()):
+                    # The credit is already validated, but we add a new transaction (eg. for the WEI).
+                    # Then we invalidate the transaction, update the credit transaction amount
+                    # and re-validate the credit.
+                    soge_credit.validate(True)
+                    soge_credit.save()
diff --git a/apps/wei/tables.py b/apps/wei/tables.py
index b2e55508fe3e825e588b4f19e0cbd98aa3cf1ff7..0f862cc9fcfea05c6711b14ab8545851077f9e51 100644
--- a/apps/wei/tables.py
+++ b/apps/wei/tables.py
@@ -99,9 +99,12 @@ class WEIRegistrationTable(tables.Table):
 
         url = reverse_lazy('wei:validate_registration', args=(record.pk,))
         text = _('Validate')
-        if record.fee > record.user.note.balance:
+        if record.fee > record.user.note.balance and not record.soge_credit:
             btn_class = 'btn-secondary'
             tooltip = _("The user does not have enough money.")
+        elif record.first_year and 'selected_bus_pk' not in record.information:
+            btn_class = 'btn-info'
+            tooltip = _("The user is in first year, and the repartition algorithm didn't run.")
         else:
             btn_class = 'btn-success'
             tooltip = _("The user has enough money, you can validate the registration.")
diff --git a/apps/wei/tests/test_wei_registration.py b/apps/wei/tests/test_wei_registration.py
index bcd755d8869f18b67947a2bdf24c093c5d6df71e..65edd902ca45b15a1eda7189eb1ae5d4e3c078fa 100644
--- a/apps/wei/tests/test_wei_registration.py
+++ b/apps/wei/tests/test_wei_registration.py
@@ -12,7 +12,7 @@ from django.test import TestCase
 from django.urls import reverse
 from django.utils import timezone
 from member.models import Membership, Club
-from note.models import NoteClub, SpecialTransaction
+from note.models import NoteClub, SpecialTransaction, NoteUser
 from treasury.models import SogeCredit
 
 from ..api.views import BusViewSet, BusTeamViewSet, WEIClubViewSet, WEIMembershipViewSet, WEIRegistrationViewSet, \
@@ -302,6 +302,7 @@ class TestWEIRegistration(TestCase):
         self.assertEqual(response.status_code, 200)
 
         user = User.objects.create(username="toto", email="toto@example.com")
+        NoteUser.objects.create(user=user)
 
         # Try with an invalid form
         response = self.client.post(reverse("wei:wei_register_2A", kwargs=dict(wei_pk=self.wei.pk)), dict(
@@ -368,7 +369,7 @@ class TestWEIRegistration(TestCase):
             last_name="toto",
             bank="Société générale",
         ))
-        response = self.client.get(reverse("wei:wei_register_2A_myself", kwargs=dict(wei_pk=self.wei.pk)))
+        response = self.client.get(reverse("wei:wei_register_2A", kwargs=dict(wei_pk=self.wei.pk)))
         self.assertEqual(response.status_code, 200)
 
         # Check that if the WEI is started, we can't register anyone
@@ -384,10 +385,8 @@ class TestWEIRegistration(TestCase):
         response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)))
         self.assertEqual(response.status_code, 200)
 
-        response = self.client.get(reverse("wei:wei_register_1A_myself", kwargs=dict(wei_pk=self.wei.pk)))
-        self.assertEqual(response.status_code, 200)
-
         user = User.objects.create(username="toto", email="toto@example.com")
+        NoteUser.objects.create(user=user)
         response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict(
             user=user.id,
             soge_credit=True,
@@ -467,6 +466,24 @@ class TestWEIRegistration(TestCase):
         response = self.client.get(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)))
         self.assertRedirects(response, reverse("wei:wei_closed", kwargs=dict(pk=self.wei.pk)), 302, 200)
 
+    def test_register_myself(self):
+        """
+        Try to register myself to the WEI, and check redirections.
+        """
+        response = self.client.get(reverse('wei:wei_register_1A_myself', args=(self.wei.pk,)))
+        self.assertRedirects(response, reverse('wei:wei_update_registration', args=(self.registration.pk,)))
+
+        response = self.client.get(reverse('wei:wei_register_2A_myself', args=(self.wei.pk,)))
+        self.assertRedirects(response, reverse('wei:wei_update_registration', args=(self.registration.pk,)))
+
+        self.registration.delete()
+
+        response = self.client.get(reverse('wei:wei_register_1A_myself', args=(self.wei.pk,)))
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.get(reverse('wei:wei_register_2A_myself', args=(self.wei.pk,)))
+        self.assertEqual(response.status_code, 200)
+
     def test_wei_survey_ended(self):
         """
         Test display the end page of a survey.
diff --git a/apps/wei/views.py b/apps/wei/views.py
index cb8e364672c7d4fe59e644b4c079f11c62e421dc..348ac751cd342a8efa28316197fca584840787b3 100644
--- a/apps/wei/views.py
+++ b/apps/wei/views.py
@@ -132,7 +132,7 @@ class WEIDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
             wei=club
         )
         pre_registrations_table = WEIRegistrationTable(data=pre_registrations, prefix="pre-registration-")
-        pre_registrations_table.paginate(per_page=20, page=self.request.GET.get('membership-page', 1))
+        pre_registrations_table.paginate(per_page=20, page=self.request.GET.get('pre-registration-page', 1))
         context['pre_registrations'] = pre_registrations_table
 
         my_registration = WEIRegistration.objects.filter(wei=club, user=self.request.user)
@@ -510,6 +510,10 @@ class WEIRegister1AView(ProtectQuerysetMixin, ProtectedCreateView):
         # We can't register someone once the WEI is started and before the membership start date
         if today >= wei.date_start or today < wei.membership_start:
             return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
+        # Don't register twice
+        if 'myself' in self.request.path and WEIRegistration.objects.filter(wei=wei, user=self.request.user).exists():
+            obj = WEIRegistration.objects.get(wei=wei, user=self.request.user)
+            return redirect(reverse_lazy('wei:wei_update_registration', args=(obj.pk,)))
         return super().dispatch(request, *args, **kwargs)
 
     def get_context_data(self, **kwargs):
@@ -585,6 +589,10 @@ class WEIRegister2AView(ProtectQuerysetMixin, ProtectedCreateView):
         # We can't register someone once the WEI is started and before the membership start date
         if today >= wei.date_start or today < wei.membership_start:
             return redirect(reverse_lazy('wei:wei_closed', args=(wei.pk,)))
+        # Don't register twice
+        if 'myself' in self.request.path and WEIRegistration.objects.filter(wei=wei, user=self.request.user).exists():
+            obj = WEIRegistration.objects.get(wei=wei, user=self.request.user)
+            return redirect(reverse_lazy('wei:wei_update_registration', args=(obj.pk,)))
         return super().dispatch(request, *args, **kwargs)
 
     def get_context_data(self, **kwargs):
@@ -683,12 +691,14 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
             context["membership_form"] = membership_form
         elif not self.object.first_year and PermissionBackend.check_perm(
                 self.request, "wei.change_weiregistration_information_json", self.object):
+            information = self.object.information
+            d = dict(
+                bus=Bus.objects.filter(pk__in=information["preferred_bus_pk"]).all(),
+                team=BusTeam.objects.filter(pk__in=information["preferred_team_pk"]).all(),
+                roles=WEIRole.objects.filter(pk__in=information["preferred_roles_pk"]).all(),
+            ) if 'preferred_bus_pk' in information else dict()
             choose_bus_form = WEIChooseBusForm(
-                self.request.POST if self.request.POST else dict(
-                    bus=Bus.objects.filter(pk__in=self.object.information["preferred_bus_pk"]).all(),
-                    team=BusTeam.objects.filter(pk__in=self.object.information["preferred_team_pk"]).all(),
-                    roles=WEIRole.objects.filter(pk__in=self.object.information["preferred_roles_pk"]).all(),
-                )
+                self.request.POST if self.request.POST else d
             )
             choose_bus_form.fields["bus"].queryset = Bus.objects.filter(wei=context["club"])
             choose_bus_form.fields["team"].queryset = BusTeam.objects.filter(bus__wei=context["club"])
@@ -704,7 +714,8 @@ class WEIUpdateRegistrationView(ProtectQuerysetMixin, LoginRequiredMixin, Update
     def get_form(self, form_class=None):
         form = super().get_form(form_class)
         form.fields["user"].disabled = True
-        if not self.object.first_year:
+        # The auto-json-format may cause issues with the default field remove
+        if not PermissionBackend.check_perm(self.request, 'wei.change_weiregistration_information_json', self.object):
             del form.fields["information_json"]
         return form
 
@@ -964,12 +975,11 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
             membership.roles.set(WEIRole.objects.filter(name="1A").all())
             membership.save()
 
-        ret = super().form_valid(form)
-
+        membership.save()
         membership.refresh_from_db()
         membership.roles.add(WEIRole.objects.get(name="Adhérent WEI"))
 
-        return ret
+        return super().form_valid(form)
 
     def get_success_url(self):
         self.object.refresh_from_db()
diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po
index 80ab332a4ce75c2d214669e044e9dddb8c537056..6416a03afad45c9c87f2f4e8b58b08bf8c80accc 100644
--- a/locale/fr/LC_MESSAGES/django.po
+++ b/locale/fr/LC_MESSAGES/django.po
@@ -7,7 +7,7 @@ msgid ""
 msgstr ""
 "Project-Id-Version: \n"
 "Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2021-06-15 21:17+0200\n"
+"POT-Creation-Date: 2021-09-08 18:46+0200\n"
 "PO-Revision-Date: 2020-11-16 20:02+0000\n"
 "Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
 "Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
@@ -111,7 +111,7 @@ msgid "type"
 msgstr "type"
 
 #: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:305
-#: apps/note/models/notes.py:148 apps/treasury/models.py:283
+#: apps/note/models/notes.py:148 apps/treasury/models.py:286
 #: apps/wei/models.py:165 apps/wei/templates/wei/survey.html:15
 msgid "user"
 msgstr "utilisateur"
@@ -251,20 +251,20 @@ msgstr "Entré le "
 msgid "remove"
 msgstr "supprimer"
 
-#: apps/activity/tables.py:80 apps/note/forms.py:68 apps/treasury/models.py:197
+#: apps/activity/tables.py:80 apps/note/forms.py:68 apps/treasury/models.py:200
 msgid "Type"
 msgstr "Type"
 
 #: apps/activity/tables.py:82 apps/member/forms.py:186
-#: apps/registration/forms.py:90 apps/treasury/forms.py:130
-#: apps/wei/forms/registration.py:96
+#: apps/registration/forms.py:90 apps/treasury/forms.py:131
+#: apps/wei/forms/registration.py:105
 msgid "Last name"
 msgstr "Nom de famille"
 
 #: apps/activity/tables.py:84 apps/member/forms.py:191
 #: apps/note/templates/note/transaction_form.html:134
-#: apps/registration/forms.py:95 apps/treasury/forms.py:132
-#: apps/wei/forms/registration.py:101
+#: apps/registration/forms.py:95 apps/treasury/forms.py:133
+#: apps/wei/forms/registration.py:110
 msgid "First name"
 msgstr "Prénom"
 
@@ -327,7 +327,7 @@ msgstr "Entrée effectuée !"
 #: apps/member/templates/member/add_members.html:46
 #: apps/member/templates/member/club_form.html:16
 #: apps/note/templates/note/transactiontemplate_form.html:18
-#: apps/treasury/forms.py:88 apps/treasury/forms.py:142
+#: apps/treasury/forms.py:89 apps/treasury/forms.py:143
 #: apps/treasury/templates/treasury/invoice_form.html:74
 #: apps/wei/templates/wei/bus_form.html:17
 #: apps/wei/templates/wei/busteam_form.html:17
@@ -508,7 +508,7 @@ msgstr "rôles"
 msgid "fee"
 msgstr "cotisation"
 
-#: apps/member/apps.py:14 apps/wei/tables.py:193 apps/wei/tables.py:224
+#: apps/member/apps.py:14 apps/wei/tables.py:196 apps/wei/tables.py:227
 msgid "member"
 msgstr "adhérent"
 
@@ -540,8 +540,8 @@ msgstr "Taille maximale : 2 Mo"
 msgid "This image cannot be loaded."
 msgstr "Cette image ne peut pas être chargée."
 
-#: apps/member/forms.py:141 apps/member/views.py:100
-#: apps/registration/forms.py:33 apps/registration/views.py:254
+#: apps/member/forms.py:141 apps/member/views.py:102
+#: apps/registration/forms.py:33 apps/registration/views.py:262
 msgid "An alias with a similar name already exists."
 msgstr "Un alias avec un nom similaire existe déjà."
 
@@ -554,12 +554,12 @@ msgid "Check this case if 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:172 apps/registration/forms.py:77
-#: apps/wei/forms/registration.py:83
+#: apps/wei/forms/registration.py:92
 msgid "Credit type"
 msgstr "Type de rechargement"
 
 #: apps/member/forms.py:173 apps/registration/forms.py:78
-#: apps/wei/forms/registration.py:84
+#: apps/wei/forms/registration.py:93
 msgid "No credit"
 msgstr "Pas de rechargement"
 
@@ -568,13 +568,13 @@ msgid "You can credit the note of the user."
 msgstr "Vous pouvez créditer la note de l'utilisateur avant l'adhésion."
 
 #: apps/member/forms.py:179 apps/registration/forms.py:83
-#: apps/wei/forms/registration.py:89
+#: apps/wei/forms/registration.py:98
 msgid "Credit amount"
 msgstr "Montant à créditer"
 
 #: apps/member/forms.py:196 apps/note/templates/note/transaction_form.html:140
-#: apps/registration/forms.py:100 apps/treasury/forms.py:134
-#: apps/wei/forms/registration.py:106
+#: apps/registration/forms.py:100 apps/treasury/forms.py:135
+#: apps/wei/forms/registration.py:115
 msgid "Bank"
 msgstr "Banque"
 
@@ -586,6 +586,22 @@ msgstr "Utilisateur"
 msgid "Roles"
 msgstr "Rôles"
 
+#: apps/member/hashers.py:57
+msgid "algorithm"
+msgstr "algorithme"
+
+#: apps/member/hashers.py:58
+msgid "iterations"
+msgstr "itérations"
+
+#: apps/member/hashers.py:59
+msgid "salt"
+msgstr "salage"
+
+#: apps/member/hashers.py:60
+msgid "hash"
+msgstr "haché"
+
 #: apps/member/models.py:38
 #: apps/member/templates/member/includes/profile_info.html:35
 #: apps/registration/templates/registration/future_profile_detail.html:40
@@ -688,7 +704,7 @@ msgid "address"
 msgstr "adresse"
 
 #: apps/member/models.py:90
-#: apps/member/templates/member/includes/profile_info.html:46
+#: apps/member/templates/member/includes/profile_info.html:42
 #: apps/registration/templates/registration/future_profile_detail.html:43
 #: apps/wei/templates/wei/weimembership_form.html:47
 msgid "paid"
@@ -835,7 +851,7 @@ msgstr "Le rôle {role} ne s'applique pas au club {club}."
 msgid "User is already a member of the club"
 msgstr "L'utilisateur est déjà membre du club"
 
-#: apps/member/models.py:443 apps/member/views.py:661
+#: apps/member/models.py:443 apps/member/views.py:660
 msgid "User is not a member of the parent club"
 msgstr "L'utilisateur n'est pas membre du club parent"
 
@@ -944,7 +960,8 @@ msgstr ""
 "déverrouiller lui-même."
 
 #: apps/member/templates/member/base.html:110
-#: apps/member/templates/member/base.html:137 apps/treasury/forms.py:90
+#: apps/member/templates/member/base.html:137 apps/treasury/forms.py:91
+#: apps/treasury/templates/treasury/sogecredit_list.html:72
 msgid "Close"
 msgstr "Fermer"
 
@@ -968,6 +985,8 @@ msgstr "Alias de la note"
 #: apps/member/templates/member/club_alias.html:20
 #: apps/member/templates/member/profile_alias.html:19
 #: apps/treasury/tables.py:99
+#: apps/treasury/templates/treasury/sogecredit_list.html:34
+#: apps/treasury/templates/treasury/sogecredit_list.html:73
 msgid "Add"
 msgstr "Ajouter"
 
@@ -1017,7 +1036,7 @@ msgid "membership fee"
 msgstr "cotisation pour adhérer"
 
 #: apps/member/templates/member/includes/club_info.html:43
-#: apps/member/templates/member/includes/profile_info.html:43
+#: apps/member/templates/member/includes/profile_info.html:47
 #: apps/treasury/templates/treasury/sogecredit_detail.html:24
 #: apps/wei/templates/wei/base.html:60
 msgid "balance"
@@ -1133,7 +1152,7 @@ msgstr "Inscriptions"
 msgid "This address must be valid."
 msgstr "Cette adresse doit être valide."
 
-#: apps/member/views.py:138
+#: apps/member/views.py:139
 msgid "Profile detail"
 msgstr "Détails de l'utilisateur"
 
@@ -1169,7 +1188,7 @@ msgstr "Modifier le club"
 msgid "Add new member to the club"
 msgstr "Ajouter un nouveau membre au club"
 
-#: apps/member/views.py:642 apps/wei/views.py:917
+#: apps/member/views.py:642 apps/wei/views.py:932
 msgid ""
 "This user don't have enough money to join this club, and can't have a "
 "negative balance."
@@ -1177,19 +1196,19 @@ msgstr ""
 "Cet utilisateur n'a pas assez d'argent pour rejoindre ce club et ne peut pas "
 "avoir un solde négatif."
 
-#: apps/member/views.py:665
+#: apps/member/views.py:664
 msgid "The membership must start after {:%m-%d-%Y}."
 msgstr "L'adhésion doit commencer après le {:%d/%m/%Y}."
 
-#: apps/member/views.py:670
+#: apps/member/views.py:669
 msgid "The membership must begin before {:%m-%d-%Y}."
 msgstr "L'adhésion doit commencer avant le {:%d/%m/%Y}."
 
-#: apps/member/views.py:816
+#: apps/member/views.py:815
 msgid "Manage roles of an user in the club"
 msgstr "Gérer les rôles d'un utilisateur dans le club"
 
-#: apps/member/views.py:841
+#: apps/member/views.py:840
 msgid "Members of the club"
 msgstr "Membres du club"
 
@@ -1475,8 +1494,8 @@ msgstr ""
 "mode de paiement et un utilisateur ou un club"
 
 #: apps/note/models/transactions.py:355 apps/note/models/transactions.py:358
-#: apps/note/models/transactions.py:361 apps/wei/views.py:922
-#: apps/wei/views.py:926
+#: apps/note/models/transactions.py:361 apps/wei/views.py:937
+#: apps/wei/views.py:941
 msgid "This field is required."
 msgstr "Ce champ est requis."
 
@@ -1492,7 +1511,7 @@ msgstr "Transactions de crédit/retrait"
 msgid "membership transaction"
 msgstr "transaction d'adhésion"
 
-#: apps/note/models/transactions.py:385 apps/treasury/models.py:289
+#: apps/note/models/transactions.py:385 apps/treasury/models.py:293
 msgid "membership transactions"
 msgstr "transactions d'adhésion"
 
@@ -1511,7 +1530,7 @@ msgstr "Pas de motif spécifié"
 #: apps/note/tables.py:169 apps/note/tables.py:203 apps/treasury/tables.py:39
 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
 #: apps/treasury/templates/treasury/sogecredit_detail.html:65
-#: apps/wei/tables.py:74 apps/wei/tables.py:114
+#: apps/wei/tables.py:74 apps/wei/tables.py:117
 #: apps/wei/templates/wei/weiregistration_confirm_delete.html:31
 #: note_kfet/templates/oauth2_provider/application_confirm_delete.html:18
 #: note_kfet/templates/oauth2_provider/application_detail.html:39
@@ -1599,14 +1618,14 @@ msgid "Action"
 msgstr "Action"
 
 #: apps/note/templates/note/transaction_form.html:112
-#: apps/treasury/forms.py:136 apps/treasury/tables.py:67
+#: apps/treasury/forms.py:137 apps/treasury/tables.py:67
 #: apps/treasury/tables.py:132
 #: apps/treasury/templates/treasury/remittance_form.html:23
 msgid "Amount"
 msgstr "Montant"
 
 #: apps/note/templates/note/transaction_form.html:128
-#: apps/treasury/models.py:52
+#: apps/treasury/models.py:55
 msgid "Name"
 msgstr "Nom"
 
@@ -1767,7 +1786,7 @@ msgstr "s'applique au club"
 msgid "role permissions"
 msgstr "permissions par rôles"
 
-#: apps/permission/signals.py:67
+#: apps/permission/signals.py:73
 #, python-brace-format
 msgid ""
 "You don't have the permission to change the field {field} on this instance "
@@ -1776,7 +1795,7 @@ msgstr ""
 "Vous n'avez pas la permission de modifier le champ {field} sur l'instance du "
 "modèle {app_label}.{model_name}."
 
-#: apps/permission/signals.py:77 apps/permission/views.py:105
+#: apps/permission/signals.py:83 apps/permission/views.py:105
 #, python-brace-format
 msgid ""
 "You don't have the permission to add an instance of model {app_label}."
@@ -1785,7 +1804,7 @@ msgstr ""
 "Vous n'avez pas la permission d'ajouter une instance du modèle {app_label}."
 "{model_name}."
 
-#: apps/permission/signals.py:106
+#: apps/permission/signals.py:112
 #, python-brace-format
 msgid ""
 "You don't have the permission to delete this instance of model {app_label}."
@@ -2032,50 +2051,50 @@ msgstr "L'équipe de la Note Kfet."
 msgid "Register new user"
 msgstr "Enregistrer un nouvel utilisateur"
 
-#: apps/registration/views.py:95
+#: apps/registration/views.py:98
 msgid "Email validation"
 msgstr "Validation de l'adresse mail"
 
-#: apps/registration/views.py:97
+#: apps/registration/views.py:100
 msgid "Validate email"
 msgstr "Valider l'adresse e-mail"
 
-#: apps/registration/views.py:141
+#: apps/registration/views.py:144
 msgid "Email validation unsuccessful"
 msgstr "La validation de l'adresse mail a échoué"
 
-#: apps/registration/views.py:152
+#: apps/registration/views.py:155
 msgid "Email validation email sent"
 msgstr "L'email de vérification de l'adresse email a bien été envoyé"
 
-#: apps/registration/views.py:160
+#: apps/registration/views.py:163
 msgid "Resend email validation link"
 msgstr "Renvoyer le lien de validation"
 
-#: apps/registration/views.py:178
+#: apps/registration/views.py:181
 msgid "Pre-registered users list"
 msgstr "Liste des utilisateurs en attente d'inscription"
 
-#: apps/registration/views.py:202
+#: apps/registration/views.py:205
 msgid "Unregistered users"
 msgstr "Utilisateurs en attente d'inscription"
 
-#: apps/registration/views.py:215
+#: apps/registration/views.py:218
 msgid "Registration detail"
 msgstr "Détails de l'inscription"
 
-#: apps/registration/views.py:278
+#: apps/registration/views.py:282
 msgid "You must join the BDE."
 msgstr "Vous devez adhérer au BDE."
 
-#: apps/registration/views.py:302
+#: apps/registration/views.py:306
 msgid ""
 "The entered amount is not enough for the memberships, should be at least {}"
 msgstr ""
 "Le montant crédité est trop faible pour adhérer, il doit être au minimum de "
 "{}"
 
-#: apps/registration/views.py:383
+#: apps/registration/views.py:387
 msgid "Invalidate pre-registration"
 msgstr "Invalider l'inscription"
 
@@ -2083,145 +2102,145 @@ msgstr "Invalider l'inscription"
 msgid "Treasury"
 msgstr "Trésorerie"
 
-#: apps/treasury/forms.py:25 apps/treasury/models.py:91
+#: apps/treasury/forms.py:26 apps/treasury/models.py:94
 #: apps/treasury/templates/treasury/invoice_form.html:22
 msgid "This invoice is locked and can no longer be edited."
 msgstr "Cette facture est verrouillée et ne peut plus être éditée."
 
-#: apps/treasury/forms.py:99
+#: apps/treasury/forms.py:100
 msgid "Remittance is already closed."
 msgstr "La remise est déjà fermée."
 
-#: apps/treasury/forms.py:104
+#: apps/treasury/forms.py:105
 msgid "You can't change the type of the remittance."
 msgstr "Vous ne pouvez pas changer le type de la remise."
 
-#: apps/treasury/forms.py:124 apps/treasury/models.py:265
+#: apps/treasury/forms.py:125 apps/treasury/models.py:268
 #: apps/treasury/tables.py:97 apps/treasury/tables.py:105
 #: apps/treasury/templates/treasury/invoice_list.html:16
 #: apps/treasury/templates/treasury/remittance_list.html:16
-#: apps/treasury/templates/treasury/sogecredit_list.html:16
+#: apps/treasury/templates/treasury/sogecredit_list.html:17
 msgid "Remittance"
 msgstr "Remise"
 
-#: apps/treasury/forms.py:125
+#: apps/treasury/forms.py:126
 msgid "No attached remittance"
 msgstr "Pas de remise associée"
 
-#: apps/treasury/models.py:24
+#: apps/treasury/models.py:27
 msgid "Invoice identifier"
 msgstr "Numéro de facture"
 
-#: apps/treasury/models.py:38
+#: apps/treasury/models.py:41
 msgid "BDE"
 msgstr "BDE"
 
-#: apps/treasury/models.py:43
+#: apps/treasury/models.py:46
 msgid "Object"
 msgstr "Objet"
 
-#: apps/treasury/models.py:47
+#: apps/treasury/models.py:50
 msgid "Description"
 msgstr "Description"
 
-#: apps/treasury/models.py:56
+#: apps/treasury/models.py:59
 msgid "Address"
 msgstr "Adresse"
 
-#: apps/treasury/models.py:61 apps/treasury/models.py:191
+#: apps/treasury/models.py:64 apps/treasury/models.py:194
 msgid "Date"
 msgstr "Date"
 
-#: apps/treasury/models.py:65
+#: apps/treasury/models.py:68
 msgid "Acquitted"
 msgstr "Acquittée"
 
-#: apps/treasury/models.py:70
+#: apps/treasury/models.py:73
 msgid "Locked"
 msgstr "Verrouillée"
 
-#: apps/treasury/models.py:71
+#: apps/treasury/models.py:74
 msgid "An invoice can't be edited when it is locked."
 msgstr "Une facture ne peut plus être modifiée si elle est verrouillée."
 
-#: apps/treasury/models.py:77
+#: apps/treasury/models.py:80
 msgid "tex source"
 msgstr "fichier TeX source"
 
-#: apps/treasury/models.py:111 apps/treasury/models.py:127
+#: apps/treasury/models.py:114 apps/treasury/models.py:130
 msgid "invoice"
 msgstr "facture"
 
-#: apps/treasury/models.py:112
+#: apps/treasury/models.py:115
 msgid "invoices"
 msgstr "factures"
 
-#: apps/treasury/models.py:115
+#: apps/treasury/models.py:118
 #, python-brace-format
 msgid "Invoice #{id}"
 msgstr "Facture n°{id}"
 
-#: apps/treasury/models.py:132
+#: apps/treasury/models.py:135
 msgid "Designation"
 msgstr "Désignation"
 
-#: apps/treasury/models.py:138
+#: apps/treasury/models.py:141
 msgid "Quantity"
 msgstr "Quantité"
 
-#: apps/treasury/models.py:143
+#: apps/treasury/models.py:146
 msgid "Unit price"
 msgstr "Prix unitaire"
 
-#: apps/treasury/models.py:159
+#: apps/treasury/models.py:162
 msgid "product"
 msgstr "produit"
 
-#: apps/treasury/models.py:160
+#: apps/treasury/models.py:163
 msgid "products"
 msgstr "produits"
 
-#: apps/treasury/models.py:180
+#: apps/treasury/models.py:183
 msgid "remittance type"
 msgstr "type de remise"
 
-#: apps/treasury/models.py:181
+#: apps/treasury/models.py:184
 msgid "remittance types"
 msgstr "types de remises"
 
-#: apps/treasury/models.py:202
+#: apps/treasury/models.py:205
 msgid "Comment"
 msgstr "Commentaire"
 
-#: apps/treasury/models.py:207
+#: apps/treasury/models.py:210
 msgid "Closed"
 msgstr "Fermée"
 
-#: apps/treasury/models.py:211
+#: apps/treasury/models.py:214
 msgid "remittance"
 msgstr "remise"
 
-#: apps/treasury/models.py:212
+#: apps/treasury/models.py:215
 msgid "remittances"
 msgstr "remises"
 
-#: apps/treasury/models.py:245
+#: apps/treasury/models.py:248
 msgid "Remittance #{:d}: {}"
 msgstr "Remise n°{:d} : {}"
 
-#: apps/treasury/models.py:269
+#: apps/treasury/models.py:272
 msgid "special transaction proxy"
 msgstr "proxy de transaction spéciale"
 
-#: apps/treasury/models.py:270
+#: apps/treasury/models.py:273
 msgid "special transaction proxies"
 msgstr "proxys de transactions spéciales"
 
-#: apps/treasury/models.py:295
+#: apps/treasury/models.py:299
 msgid "credit transaction"
 msgstr "transaction de crédit"
 
-#: apps/treasury/models.py:379
+#: apps/treasury/models.py:418
 msgid ""
 "This user doesn't have enough money to pay the memberships with its note. "
 "Please ask her/him to credit the note before invalidating this credit."
@@ -2229,16 +2248,16 @@ msgstr ""
 "Cet utilisateur n'a pas assez d'argent pour payer les adhésions avec sa "
 "note. Merci de lui demander de recharger sa note avant d'invalider ce crédit."
 
-#: apps/treasury/models.py:399
+#: apps/treasury/models.py:438
 #: apps/treasury/templates/treasury/sogecredit_detail.html:10
 msgid "Credit from the Société générale"
 msgstr "Crédit de la Société générale"
 
-#: apps/treasury/models.py:400
+#: apps/treasury/models.py:439
 msgid "Credits from the Société générale"
 msgstr "Crédits de la Société générale"
 
-#: apps/treasury/models.py:403
+#: apps/treasury/models.py:442
 #, python-brace-format
 msgid "Soge credit for {user}"
 msgstr "Crédit de la société générale pour l'utilisateur {user}"
@@ -2250,7 +2269,7 @@ msgstr "Facture n°{:d}"
 #: apps/treasury/tables.py:25
 #: apps/treasury/templates/treasury/invoice_list.html:13
 #: apps/treasury/templates/treasury/remittance_list.html:13
-#: apps/treasury/templates/treasury/sogecredit_list.html:13
+#: apps/treasury/templates/treasury/sogecredit_list.html:14
 msgid "Invoice"
 msgstr "Facture"
 
@@ -2267,12 +2286,12 @@ msgid "Yes"
 msgstr "Oui"
 
 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:10
-#: apps/treasury/views.py:179
+#: apps/treasury/views.py:180
 msgid "Delete invoice"
 msgstr "Supprimer la facture"
 
 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:15
-#: apps/treasury/views.py:183
+#: apps/treasury/views.py:184
 msgid "This invoice is locked and can't be deleted."
 msgstr "Cette facture est verrouillée et ne peut pas être supprimée."
 
@@ -2306,7 +2325,7 @@ msgstr "Retirer produit"
 
 #: apps/treasury/templates/treasury/invoice_list.html:19
 #: apps/treasury/templates/treasury/remittance_list.html:19
-#: apps/treasury/templates/treasury/sogecredit_list.html:19
+#: apps/treasury/templates/treasury/sogecredit_list.html:20
 msgid "Société générale credits"
 msgstr "Crédits de la Société générale"
 
@@ -2426,54 +2445,62 @@ msgstr "Valider"
 msgid "Return to credit list"
 msgstr "Retour à la liste des crédits"
 
-#: apps/treasury/templates/treasury/sogecredit_list.html:34
+#: apps/treasury/templates/treasury/sogecredit_list.html:40
 msgid "Filter with unvalidated credits only"
 msgstr "Filtrer avec uniquement les crédits non valides"
 
-#: apps/treasury/templates/treasury/sogecredit_list.html:44
+#: apps/treasury/templates/treasury/sogecredit_list.html:50
 msgid "There is no matched user that have asked for a Société générale credit."
 msgstr ""
 "Il n'y a pas d'utilisateur trouvé ayant demandé un crédit de la Société "
 "générale."
 
-#: apps/treasury/views.py:39
+#: apps/treasury/templates/treasury/sogecredit_list.html:63
+msgid "Add credit from the Société générale"
+msgstr "Ajouter un crédit de la Société générale"
+
+#: apps/treasury/templates/treasury/sogecredit_list.html:109
+msgid "Credit successfully registered"
+msgstr "Le crédit a bien été enregistré"
+
+#: apps/treasury/views.py:40
 msgid "Create new invoice"
 msgstr "Créer une nouvelle facture"
 
-#: apps/treasury/views.py:96
+#: apps/treasury/views.py:97
 msgid "Invoices list"
 msgstr "Liste des factures"
 
-#: apps/treasury/views.py:111 apps/treasury/views.py:285
-#: apps/treasury/views.py:411
+#: apps/treasury/views.py:112 apps/treasury/views.py:286
+#: apps/treasury/views.py:412
 msgid "You are not able to see the treasury interface."
 msgstr "Vous n'êtes pas autorisé à voir l'interface de trésorerie."
 
-#: apps/treasury/views.py:121
+#: apps/treasury/views.py:122
 msgid "Update an invoice"
 msgstr "Modifier la facture"
 
-#: apps/treasury/views.py:246
+#: apps/treasury/views.py:247
 msgid "Create a new remittance"
 msgstr "Créer une nouvelle remise"
 
-#: apps/treasury/views.py:273
+#: apps/treasury/views.py:274
 msgid "Remittances list"
 msgstr "Liste des remises"
 
-#: apps/treasury/views.py:336
+#: apps/treasury/views.py:337
 msgid "Update a remittance"
 msgstr "Modifier la remise"
 
-#: apps/treasury/views.py:359
+#: apps/treasury/views.py:360
 msgid "Attach a transaction to a remittance"
 msgstr "Joindre une transaction à une remise"
 
-#: apps/treasury/views.py:403
+#: apps/treasury/views.py:404
 msgid "List of credits from the Société générale"
 msgstr "Liste des crédits de la Société générale"
 
-#: apps/treasury/views.py:443
+#: apps/treasury/views.py:449
 msgid "Manage credits from the Société générale"
 msgstr "Gérer les crédits de la Société générale"
 
@@ -2483,12 +2510,18 @@ msgstr "Gérer les crédits de la Société générale"
 msgid "WEI"
 msgstr "WEI"
 
-#: apps/wei/forms/registration.py:51 apps/wei/models.py:118
+#: apps/wei/forms/registration.py:35
+msgid "The selected user is not validated. Please validate its account first"
+msgstr ""
+"L'utilisateur sélectionné n'est pas validé. Merci de d'abord valider son "
+"compte."
+
+#: apps/wei/forms/registration.py:60 apps/wei/models.py:118
 #: apps/wei/models.py:315
 msgid "bus"
 msgstr "bus"
 
-#: apps/wei/forms/registration.py:52
+#: apps/wei/forms/registration.py:61
 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."
@@ -2497,11 +2530,11 @@ msgstr ""
 "attribuer un bus et une équipe, en particulier si vous êtes un électron "
 "libre."
 
-#: apps/wei/forms/registration.py:59
+#: apps/wei/forms/registration.py:68
 msgid "Team"
 msgstr "Équipe"
 
-#: apps/wei/forms/registration.py:61
+#: apps/wei/forms/registration.py:70
 msgid ""
 "Leave this field empty if you won't be in a team (staff, bus chief, free "
 "electron)"
@@ -2509,16 +2542,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:67 apps/wei/forms/registration.py:77
+#: apps/wei/forms/registration.py:76 apps/wei/forms/registration.py:86
 #: apps/wei/models.py:153
 msgid "WEI Roles"
 msgstr "Rôles au WEI"
 
-#: apps/wei/forms/registration.py:68
+#: apps/wei/forms/registration.py:77
 msgid "Select the roles that you are interested in."
 msgstr "Sélectionnez les rôles qui vous intéressent."
 
-#: apps/wei/forms/registration.py:113
+#: apps/wei/forms/registration.py:122
 msgid "This team doesn't belong to the given bus."
 msgstr "Cette équipe n'appartient pas à ce bus."
 
@@ -2677,23 +2710,29 @@ msgid "The user does not have enough money."
 msgstr "L'utilisateur n'a pas assez d'argent."
 
 #: apps/wei/tables.py:107
+msgid "The user is in first year, and the repartition algorithm didn't run."
+msgstr ""
+"L'utilisateur est en première année, et l'algorithme de répartition n'a pas "
+"tourné."
+
+#: apps/wei/tables.py:110
 msgid "The user has enough money, you can validate the registration."
 msgstr "L'utilisateur a assez d'argent, l'inscription est possible."
 
-#: apps/wei/tables.py:139
+#: apps/wei/tables.py:142
 msgid "Year"
 msgstr "Année"
 
-#: apps/wei/tables.py:177 apps/wei/templates/wei/bus_detail.html:32
+#: apps/wei/tables.py:180 apps/wei/templates/wei/bus_detail.html:32
 #: apps/wei/templates/wei/busteam_detail.html:50
 msgid "Teams"
 msgstr "Équipes"
 
-#: apps/wei/tables.py:186 apps/wei/tables.py:227
+#: apps/wei/tables.py:189 apps/wei/tables.py:230
 msgid "Members count"
 msgstr "Nombre de membres"
 
-#: apps/wei/tables.py:193 apps/wei/tables.py:224
+#: apps/wei/tables.py:196 apps/wei/tables.py:227
 msgid "members"
 msgstr "adhérents"
 
@@ -2713,11 +2752,11 @@ msgstr "Prix du WEI (étudiants)"
 msgid "WEI list"
 msgstr "Liste des WEI"
 
-#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:510
+#: apps/wei/templates/wei/base.html:81 apps/wei/views.py:517
 msgid "Register 1A"
 msgstr "Inscrire un 1A"
 
-#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:578
+#: apps/wei/templates/wei/base.html:85 apps/wei/views.py:592
 msgid "Register 2A+"
 msgstr "Inscrire un 2A+"
 
@@ -2746,8 +2785,8 @@ msgstr "Télécharger au format PDF"
 
 #: apps/wei/templates/wei/survey.html:11
 #: apps/wei/templates/wei/survey_closed.html:11
-#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:973
-#: apps/wei/views.py:1028 apps/wei/views.py:1038
+#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:988
+#: apps/wei/views.py:1043 apps/wei/views.py:1053
 msgid "Survey WEI"
 msgstr "Questionnaire WEI"
 
@@ -2985,11 +3024,11 @@ msgstr "Gérer l'équipe WEI"
 msgid "Register first year student to the WEI"
 msgstr "Inscrire un 1A au WEI"
 
-#: apps/wei/views.py:532 apps/wei/views.py:613
+#: apps/wei/views.py:539 apps/wei/views.py:627
 msgid "This user is already registered to this WEI."
 msgstr "Cette personne est déjà inscrite au WEI."
 
-#: apps/wei/views.py:537
+#: apps/wei/views.py:544
 msgid ""
 "This user can't be in her/his first year since he/she has already "
 "participated to a WEI."
@@ -2997,27 +3036,27 @@ msgstr ""
 "Cet utilisateur ne peut pas être en première année puisqu'il a déjà "
 "participé à un WEI."
 
-#: apps/wei/views.py:554
+#: apps/wei/views.py:561
 msgid "Register old student to the WEI"
 msgstr "Inscrire un 2A+ au WEI"
 
-#: apps/wei/views.py:597 apps/wei/views.py:686
+#: apps/wei/views.py:611 apps/wei/views.py:700
 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:643
+#: apps/wei/views.py:657
 msgid "Update WEI Registration"
 msgstr "Modifier l'inscription WEI"
 
-#: apps/wei/views.py:746
+#: apps/wei/views.py:761
 msgid "Delete WEI registration"
 msgstr "Supprimer l'inscription WEI"
 
-#: apps/wei/views.py:757
+#: apps/wei/views.py:772
 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:776
+#: apps/wei/views.py:791
 msgid "Validate WEI registration"
 msgstr "Valider l'inscription WEI"
 
@@ -3141,13 +3180,7 @@ msgstr ""
 "Vous n'êtes plus adhérent BDE. Merci de réadhérer si vous voulez profiter de "
 "la note."
 
-#: note_kfet/templates/base.html:164
-msgid "You are not a Kfet member, so you can't use your note account."
-msgstr ""
-"Vous n'êtes pas adhérent Kfet, vous ne pouvez par conséquent pas utiliser "
-"votre compte note."
-
-#: note_kfet/templates/base.html:170
+#: note_kfet/templates/base.html:166
 msgid ""
 "Your e-mail address is not validated. Please check your mail inbox and click "
 "on the validation link."
@@ -3155,7 +3188,7 @@ msgstr ""
 "Votre adresse e-mail n'est pas validée. Merci de vérifier votre boîte mail "
 "et de cliquer sur le lien de validation."
 
-#: note_kfet/templates/base.html:176
+#: note_kfet/templates/base.html:172
 msgid ""
 "You declared that you opened a bank account in the Société générale. The "
 "bank did not validate the creation of the account to the BDE, so the "
@@ -3170,7 +3203,7 @@ msgstr ""
 "durer quelques jours. Merci de vous assurer de bien aller au bout de vos "
 "démarches."
 
-#: note_kfet/templates/base.html:199
+#: note_kfet/templates/base.html:195
 msgid "Contact us"
 msgstr "Nous contacter"
 
@@ -3218,9 +3251,10 @@ msgid ""
 "link templates and convert permissions to scope numbers with the permissions "
 "that you want to grant for your application."
 msgstr ""
-"Vous pouvez aller <a href=\"%(scopes_url)s\">ici</a> pour générer des modèles "
-"de liens d'autorisation et convertir des permissions en identifiants de "
-"scopes avec les permissions que vous souhaitez attribuer à votre application."
+"Vous pouvez aller <a href=\"%(scopes_url)s\">ici</a> pour générer des "
+"modèles de liens d'autorisation et convertir des permissions en identifiants "
+"de scopes avec les permissions que vous souhaitez attribuer à votre "
+"application."
 
 #: note_kfet/templates/oauth2_provider/application_detail.html:37
 #: note_kfet/templates/oauth2_provider/application_form.html:23
@@ -3400,3 +3434,8 @@ msgstr ""
 "vous connecter. Vous devez vous rendre à la Kfet et payer les frais "
 "d'adhésion. Vous devez également valider votre adresse email en suivant le "
 "lien que vous avez reçu."
+
+#~ msgid "You are not a Kfet member, so you can't use your note account."
+#~ msgstr ""
+#~ "Vous n'êtes pas adhérent Kfet, vous ne pouvez par conséquent pas utiliser "
+#~ "votre compte note."
diff --git a/note_kfet/middlewares.py b/note_kfet/middlewares.py
index e763a571df282047970e2accdaf6cf4d80d7f9e3..ed6d6acf698ccf5db9d6d1cb2348857a107567a8 100644
--- a/note_kfet/middlewares.py
+++ b/note_kfet/middlewares.py
@@ -75,7 +75,7 @@ class LoginByIPMiddleware(object):
             else:
                 ip = request.META.get('REMOTE_ADDR')
 
-            qs = User.objects.filter(password=f"ipbased${ip}")
+            qs = User.objects.filter(password__iregex=f"ipbased\\$.*\\^{ip}\\$.*")
             if qs.exists():
                 login(request, qs.get())
                 session = request.session