mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-12 12:59:24 +00:00
Merge branch 'taiste' into tinople/css-rewrite-taiste
This commit is contained in:
@ -2,7 +2,9 @@
|
||||
|
||||
{% block content %}
|
||||
|
||||
<h3>{% trans %}404, Not Found{% endtrans %}</h3>
|
||||
<div id="page">
|
||||
<h3>{% trans %}404, Not Found{% endtrans %}</h3>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
|
@ -72,7 +72,9 @@ def forbidden(request, exception):
|
||||
|
||||
|
||||
def not_found(request, exception):
|
||||
return HttpResponseNotFound(render(request, "core/404.jinja"))
|
||||
return HttpResponseNotFound(
|
||||
render(request, "core/404.jinja", context={"exception": exception})
|
||||
)
|
||||
|
||||
|
||||
def internal_servor_error(request):
|
||||
|
@ -207,7 +207,7 @@ class UserTabsMixin(TabedViewMixin):
|
||||
"name": _("Pictures"),
|
||||
},
|
||||
]
|
||||
if self.request.user.was_subscribed:
|
||||
if settings.SITH_ENABLE_GALAXY and self.request.user.was_subscribed:
|
||||
tab_list.append(
|
||||
{
|
||||
"url": reverse("galaxy:user", kwargs={"user_id": user.id}),
|
||||
|
@ -22,6 +22,7 @@
|
||||
#
|
||||
#
|
||||
from __future__ import annotations
|
||||
from django.db.models import Sum, F
|
||||
|
||||
from typing import Tuple
|
||||
|
||||
@ -90,12 +91,9 @@ class Customer(models.Model):
|
||||
about the relation between a User (not a Customer,
|
||||
don't mix them) and a Product.
|
||||
"""
|
||||
return self.user.subscriptions.last() and (
|
||||
date.today()
|
||||
- self.user.subscriptions.order_by("subscription_end")
|
||||
.last()
|
||||
.subscription_end
|
||||
) < timedelta(days=90)
|
||||
subscription = self.user.subscriptions.order_by("subscription_end").last()
|
||||
time_diff = date.today() - subscription.subscription_end
|
||||
return subscription is not None and time_diff < timedelta(days=90)
|
||||
|
||||
@classmethod
|
||||
def get_or_create(cls, user: User) -> Tuple[Customer, bool]:
|
||||
@ -151,12 +149,16 @@ class Customer(models.Model):
|
||||
super(Customer, self).save(*args, **kwargs)
|
||||
|
||||
def recompute_amount(self):
|
||||
self.amount = 0
|
||||
for r in self.refillings.all():
|
||||
self.amount += r.amount
|
||||
for s in self.buyings.filter(payment_method="SITH_ACCOUNT"):
|
||||
self.amount -= s.quantity * s.unit_price
|
||||
self.save()
|
||||
refillings = self.refillings.aggregate(sum=Sum(F("amount")))["sum"]
|
||||
self.amount = refillings if refillings is not None else 0
|
||||
purchases = (
|
||||
self.buyings.filter(payment_method="SITH_ACCOUNT")
|
||||
.annotate(amount=F("quantity") * F("unit_price"))
|
||||
.aggregate(sum=Sum(F("amount")))
|
||||
)["sum"]
|
||||
if purchases is not None:
|
||||
self.amount -= purchases
|
||||
self.save()
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("core:user_account", kwargs={"user_id": self.user.pk})
|
||||
|
@ -45,7 +45,6 @@ $(function () {
|
||||
const code_field = $("#code_field");
|
||||
|
||||
let quantity = "";
|
||||
let search = "";
|
||||
code_field.autocomplete({
|
||||
select: function (event, ui) {
|
||||
event.preventDefault();
|
||||
@ -56,13 +55,13 @@ $(function () {
|
||||
code_field.val(quantity + ui.item.value);
|
||||
},
|
||||
source: function (request, response) {
|
||||
// by the dark magic of JS, parseInt("123abc") === 123
|
||||
quantity = parseInt(request.term);
|
||||
search = request.term.slice(quantity.toString().length)
|
||||
let matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i");
|
||||
response($.grep(products_autocomplete, function (value) {
|
||||
const res = /^(\d+x)?(.*)/i.exec(request.term);
|
||||
quantity = res[1] || "";
|
||||
const search = res[2];
|
||||
const matcher = new RegExp($.ui.autocomplete.escapeRegex(search), "i" );
|
||||
response($.grep(products_autocomplete, function(value) {
|
||||
value = value.tags;
|
||||
return matcher.test(value);
|
||||
return matcher.test( value );
|
||||
}));
|
||||
},
|
||||
});
|
||||
|
@ -123,6 +123,19 @@
|
||||
{% else %}
|
||||
<p>{% trans %}There are no items available for sale{% endtrans %}</p>
|
||||
{% endfor %}
|
||||
|
||||
<h3>{% trans %}Partnership Eurockéennes 2023{% endtrans %}</h3>
|
||||
{% if user.is_subscribed %}
|
||||
<a title="Logiciel billetterie en ligne"
|
||||
href="https://widget.weezevent.com/ticket/a203b986-73b0-4e63-b18a-b7c8bad986b7?id_evenement=915745&locale=fr-FR&code=71348"
|
||||
class="weezevent-widget-integration" target="_blank"
|
||||
data-src="https://widget.weezevent.com/ticket/a203b986-73b0-4e63-b18a-b7c8bad986b7?id_evenement=915745&locale=fr-FR&code=71348"
|
||||
data-width="650" data-height="600" data-resize="1" data-nopb="0" data-type="neo" data-width_auto="1"
|
||||
data-noscroll="0" data-id="915745">Billetterie Weezevent</a>
|
||||
<script type="text/javascript" src="https://widget.weezevent.com/weez.js" async defer></script>
|
||||
{% else %}
|
||||
<div>{% trans %}You must be a contributor to access the Eurockéennes ticketing service.{% endtrans %}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Generated by Django 3.2.16 on 2023-02-03 10:31
|
||||
# Generated by Django 3.2.16 on 2023-03-02 10:07
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
@ -51,7 +51,7 @@ class Migration(migrations.Migration):
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="galaxy_user",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="galaxy user",
|
||||
verbose_name="star owner",
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -96,7 +96,7 @@ class Migration(migrations.Migration):
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="lanes1",
|
||||
to="galaxy.galaxystar",
|
||||
verbose_name="galaxy lanes 1",
|
||||
verbose_name="galaxy star 1",
|
||||
),
|
||||
),
|
||||
(
|
||||
@ -105,7 +105,7 @@ class Migration(migrations.Migration):
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="lanes2",
|
||||
to="galaxy.galaxystar",
|
||||
verbose_name="galaxy lanes 2",
|
||||
verbose_name="galaxy star 2",
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -25,6 +25,7 @@
|
||||
import math
|
||||
import logging
|
||||
|
||||
from typing import Tuple
|
||||
from django.db import models
|
||||
from django.db.models import Q, Case, F, Value, When, Count
|
||||
from django.db.models.functions import Concat
|
||||
@ -47,7 +48,7 @@ class GalaxyStar(models.Model):
|
||||
|
||||
owner = models.OneToOneField(
|
||||
User,
|
||||
verbose_name=_("galaxy user"),
|
||||
verbose_name=_("star owner"),
|
||||
related_name="galaxy_user",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
@ -69,13 +70,13 @@ class GalaxyLane(models.Model):
|
||||
|
||||
star1 = models.ForeignKey(
|
||||
GalaxyStar,
|
||||
verbose_name=_("galaxy lanes 1"),
|
||||
verbose_name=_("galaxy star 1"),
|
||||
related_name="lanes1",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
star2 = models.ForeignKey(
|
||||
GalaxyStar,
|
||||
verbose_name=_("galaxy lanes 2"),
|
||||
verbose_name=_("galaxy star 2"),
|
||||
related_name="lanes2",
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
@ -120,7 +121,7 @@ class Galaxy(models.Model):
|
||||
state = models.JSONField("current state")
|
||||
|
||||
@staticmethod
|
||||
def make_state() -> GalaxyDict:
|
||||
def make_state() -> None:
|
||||
"""
|
||||
Compute JSON structure to send to 3d-force-graph: https://github.com/vasturiano/3d-force-graph/
|
||||
"""
|
||||
@ -177,7 +178,7 @@ class Galaxy(models.Model):
|
||||
###################
|
||||
|
||||
@classmethod
|
||||
def compute_user_score(cls, user):
|
||||
def compute_user_score(cls, user) -> int:
|
||||
"""
|
||||
This compute an individual score for each citizen. It will later be used by the graph algorithm to push
|
||||
higher scores towards the center of the galaxy.
|
||||
@ -202,7 +203,7 @@ class Galaxy(models.Model):
|
||||
return user_score
|
||||
|
||||
@classmethod
|
||||
def query_user_score(cls, user):
|
||||
def query_user_score(cls, user) -> int:
|
||||
score_query = (
|
||||
User.objects.filter(id=user.id)
|
||||
.annotate(
|
||||
@ -229,7 +230,7 @@ class Galaxy(models.Model):
|
||||
####################
|
||||
|
||||
@classmethod
|
||||
def compute_users_score(cls, user1, user2):
|
||||
def compute_users_score(cls, user1, user2) -> Tuple[int, int, int, int]:
|
||||
family = cls.compute_users_family_score(user1, user2)
|
||||
pictures = cls.compute_users_pictures_score(user1, user2)
|
||||
clubs = cls.compute_users_clubs_score(user1, user2)
|
||||
@ -237,7 +238,7 @@ class Galaxy(models.Model):
|
||||
return score, family, pictures, clubs
|
||||
|
||||
@classmethod
|
||||
def compute_users_family_score(cls, user1, user2):
|
||||
def compute_users_family_score(cls, user1, user2) -> int:
|
||||
link_count = User.objects.filter(
|
||||
Q(id=user1.id, godfathers=user2) | Q(id=user2.id, godfathers=user1)
|
||||
).count()
|
||||
@ -248,7 +249,7 @@ class Galaxy(models.Model):
|
||||
return link_count * cls.FAMILY_LINK_POINTS
|
||||
|
||||
@classmethod
|
||||
def compute_users_pictures_score(cls, user1, user2):
|
||||
def compute_users_pictures_score(cls, user1, user2) -> int:
|
||||
picture_count = (
|
||||
Picture.objects.filter(people__user__in=(user1,))
|
||||
.filter(people__user__in=(user2,))
|
||||
@ -261,7 +262,7 @@ class Galaxy(models.Model):
|
||||
return picture_count * cls.PICTURE_POINTS
|
||||
|
||||
@classmethod
|
||||
def compute_users_clubs_score(cls, user1, user2):
|
||||
def compute_users_clubs_score(cls, user1, user2) -> int:
|
||||
common_clubs = Club.objects.filter(members__in=user1.memberships.all()).filter(
|
||||
members__in=user2.memberships.all()
|
||||
)
|
||||
@ -311,7 +312,7 @@ class Galaxy(models.Model):
|
||||
###################
|
||||
|
||||
@classmethod
|
||||
def rule(cls):
|
||||
def rule(cls) -> None:
|
||||
GalaxyStar.objects.all().delete()
|
||||
# The following is a no-op thanks to cascading, but in case that changes in the future, better keep it anyway.
|
||||
GalaxyLane.objects.all().delete()
|
||||
@ -357,7 +358,7 @@ class Galaxy(models.Model):
|
||||
).save()
|
||||
|
||||
@classmethod
|
||||
def scale_distance(cls, value):
|
||||
def scale_distance(cls, value) -> int:
|
||||
# TODO: this will need adjustements with the real, typical data on Taiste
|
||||
|
||||
cls.logger.debug(f"\t\t> Score: {value}")
|
||||
|
@ -127,3 +127,19 @@ class GalaxyTest(TestCase):
|
||||
|
||||
self.maxDiff = None # Yes, we want to see the diff if any
|
||||
self.assertDictEqual(expected_scores, computed_scores)
|
||||
|
||||
def test_page_is_citizen(self):
|
||||
Galaxy.rule()
|
||||
self.client.login(username="root", password="plop")
|
||||
response = self.client.get("/galaxy/1/")
|
||||
self.assertContains(
|
||||
response,
|
||||
'<a onclick="focus_node(get_node_from_id(8))">Locate</a>',
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
def test_page_not_citizen(self):
|
||||
Galaxy.rule()
|
||||
self.client.login(username="root", password="plop")
|
||||
response = self.client.get("/galaxy/2/")
|
||||
self.assertEquals(response.status_code, 404)
|
||||
|
@ -23,9 +23,10 @@
|
||||
#
|
||||
|
||||
from django.views.generic import DetailView, View
|
||||
from django.http import JsonResponse
|
||||
from django.http import JsonResponse, Http404
|
||||
from django.db.models import Q, Case, F, When, Value
|
||||
from django.db.models.functions import Concat
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from core.views import (
|
||||
CanViewMixin,
|
||||
@ -42,6 +43,12 @@ class GalaxyUserView(CanViewMixin, UserTabsMixin, DetailView):
|
||||
template_name = "galaxy/user.jinja"
|
||||
current_tab = "galaxy"
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
user: User = super(GalaxyUserView, self).get_object(*args, **kwargs)
|
||||
if not hasattr(user, "galaxy_user"):
|
||||
raise Http404(_("This citizen has not yet joined the galaxy"))
|
||||
return user
|
||||
|
||||
def get_queryset(self):
|
||||
return super(GalaxyUserView, self).get_queryset().select_related("galaxy_user")
|
||||
|
||||
|
@ -6,7 +6,7 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-11-28 16:54+0100\n"
|
||||
"POT-Creation-Date: 2023-03-02 11:02+0100\n"
|
||||
"PO-Revision-Date: 2016-07-18\n"
|
||||
"Last-Translator: Skia <skia@libskia.so>\n"
|
||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||
@ -18,8 +18,8 @@ msgstr ""
|
||||
|
||||
#: accounting/models.py:61 accounting/models.py:110 accounting/models.py:143
|
||||
#: accounting/models.py:216 club/models.py:48 com/models.py:279
|
||||
#: com/models.py:296 counter/models.py:199 counter/models.py:232
|
||||
#: counter/models.py:316 forum/models.py:58 launderette/models.py:38
|
||||
#: com/models.py:296 counter/models.py:196 counter/models.py:229
|
||||
#: counter/models.py:317 forum/models.py:58 launderette/models.py:38
|
||||
#: launderette/models.py:93 launderette/models.py:131 stock/models.py:40
|
||||
#: stock/models.py:63 stock/models.py:105 stock/models.py:133
|
||||
msgid "name"
|
||||
@ -66,8 +66,8 @@ msgid "account number"
|
||||
msgstr "numero de compte"
|
||||
|
||||
#: accounting/models.py:116 accounting/models.py:147 club/models.py:275
|
||||
#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:250
|
||||
#: counter/models.py:318 trombi/models.py:217
|
||||
#: com/models.py:75 com/models.py:266 com/models.py:302 counter/models.py:247
|
||||
#: counter/models.py:319 trombi/models.py:217
|
||||
msgid "club"
|
||||
msgstr "club"
|
||||
|
||||
@ -88,12 +88,12 @@ msgstr "Compte club"
|
||||
msgid "%(club_account)s on %(bank_account)s"
|
||||
msgstr "%(club_account)s sur %(bank_account)s"
|
||||
|
||||
#: accounting/models.py:214 club/models.py:281 counter/models.py:752
|
||||
#: accounting/models.py:214 club/models.py:281 counter/models.py:753
|
||||
#: election/models.py:18 launderette/models.py:194
|
||||
msgid "start date"
|
||||
msgstr "date de début"
|
||||
|
||||
#: accounting/models.py:215 club/models.py:282 counter/models.py:753
|
||||
#: accounting/models.py:215 club/models.py:282 counter/models.py:754
|
||||
#: election/models.py:19
|
||||
msgid "end date"
|
||||
msgstr "date de fin"
|
||||
@ -107,7 +107,7 @@ msgid "club account"
|
||||
msgstr "compte club"
|
||||
|
||||
#: accounting/models.py:225 accounting/models.py:289 counter/models.py:60
|
||||
#: counter/models.py:474
|
||||
#: counter/models.py:475
|
||||
msgid "amount"
|
||||
msgstr "montant"
|
||||
|
||||
@ -129,18 +129,18 @@ msgstr "classeur"
|
||||
|
||||
#: accounting/models.py:290 core/models.py:862 core/models.py:1400
|
||||
#: core/models.py:1448 core/models.py:1477 core/models.py:1501
|
||||
#: counter/models.py:484 counter/models.py:577 counter/models.py:782
|
||||
#: eboutic/models.py:66 eboutic/models.py:240 forum/models.py:311
|
||||
#: counter/models.py:485 counter/models.py:578 counter/models.py:789
|
||||
#: eboutic/models.py:67 eboutic/models.py:236 forum/models.py:311
|
||||
#: forum/models.py:408 stock/models.py:104
|
||||
msgid "date"
|
||||
msgstr "date"
|
||||
|
||||
#: accounting/models.py:291 counter/models.py:201 counter/models.py:783
|
||||
#: accounting/models.py:291 counter/models.py:198 counter/models.py:790
|
||||
#: pedagogy/models.py:219 stock/models.py:107
|
||||
msgid "comment"
|
||||
msgstr "commentaire"
|
||||
|
||||
#: accounting/models.py:293 counter/models.py:486 counter/models.py:579
|
||||
#: accounting/models.py:293 counter/models.py:487 counter/models.py:580
|
||||
#: subscription/models.py:66
|
||||
msgid "payment method"
|
||||
msgstr "méthode de paiement"
|
||||
@ -149,7 +149,7 @@ msgstr "méthode de paiement"
|
||||
msgid "cheque number"
|
||||
msgstr "numéro de chèque"
|
||||
|
||||
#: accounting/models.py:303 eboutic/models.py:332
|
||||
#: accounting/models.py:303 eboutic/models.py:328
|
||||
msgid "invoice"
|
||||
msgstr "facture"
|
||||
|
||||
@ -167,7 +167,7 @@ msgstr "type comptable"
|
||||
|
||||
#: accounting/models.py:328 accounting/models.py:475 accounting/models.py:510
|
||||
#: accounting/models.py:545 core/models.py:1476 core/models.py:1502
|
||||
#: counter/models.py:543
|
||||
#: counter/models.py:544
|
||||
msgid "label"
|
||||
msgstr "étiquette"
|
||||
|
||||
@ -211,7 +211,7 @@ msgstr "Utilisateur"
|
||||
msgid "Club"
|
||||
msgstr "Club"
|
||||
|
||||
#: accounting/models.py:339 core/views/user.py:287
|
||||
#: accounting/models.py:339 core/views/user.py:279
|
||||
msgid "Account"
|
||||
msgstr "Compte"
|
||||
|
||||
@ -219,7 +219,7 @@ msgstr "Compte"
|
||||
msgid "Company"
|
||||
msgstr "Entreprise"
|
||||
|
||||
#: accounting/models.py:341 core/models.py:230 sith/settings.py:367
|
||||
#: accounting/models.py:341 core/models.py:230 sith/settings.py:391
|
||||
#: stock/templates/stock/shopping_list_items.jinja:37
|
||||
msgid "Other"
|
||||
msgstr "Autre"
|
||||
@ -266,7 +266,7 @@ msgstr ""
|
||||
"Vous devez fournir soit un type comptable simplifié ou un type comptable "
|
||||
"standard"
|
||||
|
||||
#: accounting/models.py:467 counter/models.py:242 pedagogy/models.py:46
|
||||
#: accounting/models.py:467 counter/models.py:239 pedagogy/models.py:46
|
||||
msgid "code"
|
||||
msgstr "code"
|
||||
|
||||
@ -530,7 +530,7 @@ msgid "Effective amount"
|
||||
msgstr "Montant effectif"
|
||||
|
||||
#: accounting/templates/accounting/club_account_details.jinja:36
|
||||
#: sith/settings.py:413
|
||||
#: sith/settings.py:437
|
||||
msgid "Closed"
|
||||
msgstr "Fermé"
|
||||
|
||||
@ -670,7 +670,7 @@ msgid "Done"
|
||||
msgstr "Effectuées"
|
||||
|
||||
#: accounting/templates/accounting/journal_details.jinja:41
|
||||
#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1064
|
||||
#: counter/templates/counter/cash_summary_list.jinja:37 counter/views.py:1072
|
||||
#: pedagogy/templates/pedagogy/moderation.jinja:13
|
||||
#: pedagogy/templates/pedagogy/uv_detail.jinja:138
|
||||
#: trombi/templates/trombi/comment.jinja:4
|
||||
@ -935,15 +935,15 @@ msgstr "Retirer"
|
||||
msgid "Action"
|
||||
msgstr "Action"
|
||||
|
||||
#: club/forms.py:116 club/tests.py:576
|
||||
#: club/forms.py:116 club/tests.py:578
|
||||
msgid "This field is required"
|
||||
msgstr "Ce champ est obligatoire"
|
||||
|
||||
#: club/forms.py:128 club/forms.py:256 club/tests.py:588
|
||||
#: club/forms.py:128 club/forms.py:256 club/tests.py:590
|
||||
msgid "One of the selected users doesn't exist"
|
||||
msgstr "Un des utilisateurs sélectionné n'existe pas"
|
||||
|
||||
#: club/forms.py:132 club/tests.py:606
|
||||
#: club/forms.py:132 club/tests.py:608
|
||||
msgid "One of the selected users doesn't have an email address"
|
||||
msgstr "Un des utilisateurs sélectionnés n'a pas d'adresse email"
|
||||
|
||||
@ -951,15 +951,15 @@ msgstr "Un des utilisateurs sélectionnés n'a pas d'adresse email"
|
||||
msgid "An action is required"
|
||||
msgstr "Une action est requise"
|
||||
|
||||
#: club/forms.py:154 club/tests.py:565
|
||||
#: club/forms.py:154 club/tests.py:567
|
||||
msgid "You must specify at least an user or an email address"
|
||||
msgstr "vous devez spécifier au moins un utilisateur ou une adresse email"
|
||||
|
||||
#: club/forms.py:162 counter/forms.py:157
|
||||
#: club/forms.py:162 counter/forms.py:165
|
||||
msgid "Begin date"
|
||||
msgstr "Date de début"
|
||||
|
||||
#: club/forms.py:163 com/views.py:84 com/views.py:199 counter/forms.py:158
|
||||
#: club/forms.py:163 com/views.py:84 com/views.py:199 counter/forms.py:166
|
||||
#: election/views.py:172 subscription/views.py:49
|
||||
msgid "End date"
|
||||
msgstr "Date de fin"
|
||||
@ -967,15 +967,15 @@ msgstr "Date de fin"
|
||||
#: club/forms.py:166 club/templates/club/club_sellings.jinja:21
|
||||
#: core/templates/core/user_account_detail.jinja:18
|
||||
#: core/templates/core/user_account_detail.jinja:51
|
||||
#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:148
|
||||
#: counter/templates/counter/cash_summary_list.jinja:33 counter/views.py:156
|
||||
msgid "Counter"
|
||||
msgstr "Comptoir"
|
||||
|
||||
#: club/forms.py:174 counter/views.py:762
|
||||
#: club/forms.py:174 counter/views.py:770
|
||||
msgid "Products"
|
||||
msgstr "Produits"
|
||||
|
||||
#: club/forms.py:179 counter/views.py:767
|
||||
#: club/forms.py:179 counter/views.py:775
|
||||
msgid "Archived products"
|
||||
msgstr "Produits archivés"
|
||||
|
||||
@ -1045,8 +1045,8 @@ msgstr "Vous ne pouvez pas faire de boucles dans les clubs"
|
||||
msgid "A club with that unix_name already exists"
|
||||
msgstr "Un club avec ce nom UNIX existe déjà."
|
||||
|
||||
#: club/models.py:267 counter/models.py:743 counter/models.py:773
|
||||
#: eboutic/models.py:62 eboutic/models.py:236 election/models.py:192
|
||||
#: club/models.py:267 counter/models.py:744 counter/models.py:780
|
||||
#: eboutic/models.py:63 eboutic/models.py:232 election/models.py:192
|
||||
#: launderette/models.py:145 launderette/models.py:213 sas/models.py:244
|
||||
#: trombi/models.py:213
|
||||
msgid "user"
|
||||
@ -1057,8 +1057,8 @@ msgstr "nom d'utilisateur"
|
||||
msgid "role"
|
||||
msgstr "rôle"
|
||||
|
||||
#: club/models.py:289 core/models.py:81 counter/models.py:200
|
||||
#: counter/models.py:233 election/models.py:15 election/models.py:120
|
||||
#: club/models.py:289 core/models.py:81 counter/models.py:197
|
||||
#: counter/models.py:230 election/models.py:15 election/models.py:120
|
||||
#: election/models.py:197 forum/models.py:59 forum/models.py:240
|
||||
msgid "description"
|
||||
msgstr "description"
|
||||
@ -1096,7 +1096,7 @@ msgstr "Liste de diffusion"
|
||||
msgid "At least user or email is required"
|
||||
msgstr "Au moins un utilisateur ou un email est nécessaire"
|
||||
|
||||
#: club/models.py:459 club/tests.py:634
|
||||
#: club/models.py:459 club/tests.py:636
|
||||
msgid "This email is already suscribed in this mailing"
|
||||
msgstr "Cet email est déjà abonné à cette mailing"
|
||||
|
||||
@ -1362,7 +1362,7 @@ msgstr "Anciens membres"
|
||||
msgid "History"
|
||||
msgstr "Historique"
|
||||
|
||||
#: club/views.py:125 core/templates/core/base.jinja:123 core/views/user.py:220
|
||||
#: club/views.py:125 core/templates/core/base.jinja:123 core/views/user.py:222
|
||||
#: sas/templates/sas/picture.jinja:95 trombi/views.py:63
|
||||
msgid "Tools"
|
||||
msgstr "Outils"
|
||||
@ -1860,7 +1860,7 @@ msgstr "Retour"
|
||||
|
||||
#: com/templates/com/weekmail_preview.jinja:13
|
||||
msgid "The following recipients were refused by the SMTP:"
|
||||
msgstr ""
|
||||
msgstr "Les destinataires suivants ont été refusé par le SMTP :"
|
||||
|
||||
#: com/templates/com/weekmail_preview.jinja:24
|
||||
msgid "Clean subscribers"
|
||||
@ -2379,7 +2379,7 @@ msgstr "type d'opération"
|
||||
msgid "403, Forbidden"
|
||||
msgstr "403, Non autorisé"
|
||||
|
||||
#: core/templates/core/404.jinja:5
|
||||
#: core/templates/core/404.jinja:6
|
||||
msgid "404, Not Found"
|
||||
msgstr "404. Non trouvé"
|
||||
|
||||
@ -2488,13 +2488,13 @@ msgstr "Forum"
|
||||
msgid "Gallery"
|
||||
msgstr "Photos"
|
||||
|
||||
#: core/templates/core/base.jinja:187 counter/models.py:326
|
||||
#: core/templates/core/base.jinja:187 counter/models.py:327
|
||||
#: counter/templates/counter/counter_list.jinja:11
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja:4
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja:23
|
||||
#: eboutic/templates/eboutic/eboutic_makecommand.jinja:17
|
||||
#: eboutic/templates/eboutic/eboutic_payment_result.jinja:4
|
||||
#: sith/settings.py:366 sith/settings.py:374
|
||||
#: sith/settings.py:390 sith/settings.py:398
|
||||
msgid "Eboutic"
|
||||
msgstr "Eboutic"
|
||||
|
||||
@ -3021,7 +3021,7 @@ msgstr "Résultat de la recherche"
|
||||
msgid "Users"
|
||||
msgstr "Utilisateurs"
|
||||
|
||||
#: core/templates/core/search.jinja:18 core/views/user.py:248
|
||||
#: core/templates/core/search.jinja:18 core/views/user.py:244
|
||||
#: counter/templates/counter/stats.jinja:17
|
||||
msgid "Clubs"
|
||||
msgstr "Clubs"
|
||||
@ -3268,7 +3268,7 @@ msgstr "Voir l'arbre des ancêtres"
|
||||
msgid "No godfathers / godmothers"
|
||||
msgstr "Pas de famille"
|
||||
|
||||
#: core/templates/core/user_godfathers.jinja:25 core/views/user.py:472
|
||||
#: core/templates/core/user_godfathers.jinja:25 core/views/user.py:464
|
||||
msgid "Godchildren"
|
||||
msgstr "Fillots / Fillotes"
|
||||
|
||||
@ -3341,7 +3341,7 @@ msgid "Picture Unavailable"
|
||||
msgstr "Photo Indisponible"
|
||||
|
||||
#: core/templates/core/user_preferences.jinja:4
|
||||
#: core/templates/core/user_preferences.jinja:8 core/views/user.py:238
|
||||
#: core/templates/core/user_preferences.jinja:8 core/views/user.py:236
|
||||
msgid "Preferences"
|
||||
msgstr "Préférences"
|
||||
|
||||
@ -3411,7 +3411,7 @@ msgstr "Outils utilisateurs"
|
||||
msgid "Sith management"
|
||||
msgstr "Gestion de Sith"
|
||||
|
||||
#: core/templates/core/user_tools.jinja:14 core/views/user.py:258
|
||||
#: core/templates/core/user_tools.jinja:14 core/views/user.py:252
|
||||
msgid "Groups"
|
||||
msgstr "Groupes"
|
||||
|
||||
@ -3439,8 +3439,8 @@ msgstr "Cotisations"
|
||||
msgid "Subscription stats"
|
||||
msgstr "Statistiques de cotisation"
|
||||
|
||||
#: core/templates/core/user_tools.jinja:29 counter/forms.py:131
|
||||
#: counter/views.py:757
|
||||
#: core/templates/core/user_tools.jinja:29 counter/forms.py:139
|
||||
#: counter/views.py:765
|
||||
msgid "Counters"
|
||||
msgstr "Comptoirs"
|
||||
|
||||
@ -3457,16 +3457,16 @@ msgid "Product types management"
|
||||
msgstr "Gestion des types de produit"
|
||||
|
||||
#: core/templates/core/user_tools.jinja:35
|
||||
#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:777
|
||||
#: counter/templates/counter/cash_summary_list.jinja:23 counter/views.py:785
|
||||
msgid "Cash register summaries"
|
||||
msgstr "Relevés de caisse"
|
||||
|
||||
#: core/templates/core/user_tools.jinja:36
|
||||
#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:782
|
||||
#: counter/templates/counter/invoices_call.jinja:4 counter/views.py:790
|
||||
msgid "Invoices call"
|
||||
msgstr "Appels à facture"
|
||||
|
||||
#: core/templates/core/user_tools.jinja:44 core/views/user.py:278
|
||||
#: core/templates/core/user_tools.jinja:44 core/views/user.py:270
|
||||
#: counter/templates/counter/counter_list.jinja:18
|
||||
#: counter/templates/counter/counter_list.jinja:34
|
||||
#: counter/templates/counter/counter_list.jinja:56
|
||||
@ -3691,7 +3691,7 @@ msgstr "Parrain / Marraine"
|
||||
msgid "Godchild"
|
||||
msgstr "Fillot / Fillote"
|
||||
|
||||
#: core/views/forms.py:348 counter/forms.py:47 trombi/views.py:158
|
||||
#: core/views/forms.py:348 counter/forms.py:55 trombi/views.py:158
|
||||
msgid "Select user"
|
||||
msgstr "Choisir un utilisateur"
|
||||
|
||||
@ -3713,16 +3713,20 @@ msgstr "Utilisateurs à retirer du groupe"
|
||||
msgid "Users to add to group"
|
||||
msgstr "Utilisateurs à ajouter au groupe"
|
||||
|
||||
#: core/views/user.py:206 core/views/user.py:474 core/views/user.py:476
|
||||
#: core/views/user.py:202 core/views/user.py:466 core/views/user.py:468
|
||||
msgid "Family"
|
||||
msgstr "Famille"
|
||||
|
||||
#: core/views/user.py:215 trombi/templates/trombi/export.jinja:25
|
||||
#: core/views/user.py:207 trombi/templates/trombi/export.jinja:25
|
||||
#: trombi/templates/trombi/user_profile.jinja:11
|
||||
msgid "Pictures"
|
||||
msgstr "Photos"
|
||||
|
||||
#: core/views/user.py:618
|
||||
#: core/views/user.py:217
|
||||
msgid "Galaxy"
|
||||
msgstr "Galaxie"
|
||||
|
||||
#: core/views/user.py:610
|
||||
msgid "User already has a profile picture"
|
||||
msgstr "L'utilisateur a déjà une photo de profil"
|
||||
|
||||
@ -4359,23 +4363,23 @@ msgstr "Nombre de chèque"
|
||||
msgid "people(s)"
|
||||
msgstr "personne(s)"
|
||||
|
||||
#: eboutic/forms.py:102
|
||||
#: eboutic/forms.py:107
|
||||
msgid "You have no basket."
|
||||
msgstr "Vous n'avez pas de panier."
|
||||
|
||||
#: eboutic/forms.py:107
|
||||
#: eboutic/forms.py:120
|
||||
msgid "The request was badly formatted."
|
||||
msgstr "La requête a été mal formatée."
|
||||
|
||||
#: eboutic/forms.py:112
|
||||
#: eboutic/forms.py:126
|
||||
msgid "The basket cookie was badly formatted."
|
||||
msgstr "Le cookie du panier a été mal formaté."
|
||||
|
||||
#: eboutic/forms.py:115
|
||||
#: eboutic/forms.py:130
|
||||
msgid "Your basket is empty."
|
||||
msgstr "Votre panier est vide"
|
||||
|
||||
#: eboutic/forms.py:125
|
||||
#: eboutic/forms.py:141
|
||||
#, python-format
|
||||
msgid "%(name)s : this product does not exist."
|
||||
msgstr "%(name)s : ce produit n'existe pas."
|
||||
@ -4602,11 +4606,11 @@ msgstr "votes"
|
||||
|
||||
#: election/templates/election/election_detail.jinja:146
|
||||
msgid "✏️"
|
||||
msgstr ""
|
||||
msgstr "✏️"
|
||||
|
||||
#: election/templates/election/election_detail.jinja:147
|
||||
msgid "❌"
|
||||
msgstr ""
|
||||
msgstr "❌"
|
||||
|
||||
#: election/templates/election/election_detail.jinja:178
|
||||
msgid "Add a new list"
|
||||
@ -4824,6 +4828,50 @@ msgstr "Appliquer les droits et le club propriétaire récursivement"
|
||||
msgid "%(author)s said"
|
||||
msgstr "Citation de %(author)s"
|
||||
|
||||
#: galaxy/models.py:52
|
||||
msgid "star owner"
|
||||
msgstr "propriétaire de l'étoile"
|
||||
|
||||
#: galaxy/models.py:57
|
||||
msgid "star mass"
|
||||
msgstr "masse de l'étoile"
|
||||
|
||||
#: galaxy/models.py:74
|
||||
msgid "galaxy star 1"
|
||||
msgstr "étoile 1"
|
||||
|
||||
#: galaxy/models.py:80
|
||||
msgid "galaxy star 2"
|
||||
msgstr "étoile 2"
|
||||
|
||||
#: galaxy/models.py:85
|
||||
msgid "distance"
|
||||
msgstr "distance"
|
||||
|
||||
#: galaxy/models.py:87
|
||||
msgid "Distance separating star1 and star2"
|
||||
msgstr "Distance séparant étoile 1 et étoile 2"
|
||||
|
||||
#: galaxy/models.py:90
|
||||
msgid "family score"
|
||||
msgstr "score de famille"
|
||||
|
||||
#: galaxy/models.py:94
|
||||
msgid "pictures score"
|
||||
msgstr "score de photos"
|
||||
|
||||
#: galaxy/models.py:98
|
||||
msgid "clubs score"
|
||||
msgstr "score de club"
|
||||
|
||||
#: galaxy/templates/galaxy/user.jinja:4
|
||||
msgid "%(user_name)s's Galaxy"
|
||||
msgstr "Galaxie de %(user_name)s"
|
||||
|
||||
#: galaxy/views.py:49
|
||||
msgid "This citizen has not yet joined the galaxy"
|
||||
msgstr "Ce citoyen n'a pas encore rejoint la galaxie"
|
||||
|
||||
#: launderette/models.py:97 launderette/models.py:135
|
||||
msgid "launderette"
|
||||
msgstr "laverie"
|
||||
@ -4877,12 +4925,12 @@ msgid "Washing and drying"
|
||||
msgstr "Lavage et séchage"
|
||||
|
||||
#: launderette/templates/launderette/launderette_book.jinja:27
|
||||
#: sith/settings.py:596
|
||||
#: sith/settings.py:620
|
||||
msgid "Washing"
|
||||
msgstr "Lavage"
|
||||
|
||||
#: launderette/templates/launderette/launderette_book.jinja:31
|
||||
#: sith/settings.py:596
|
||||
#: sith/settings.py:620
|
||||
msgid "Drying"
|
||||
msgstr "Séchage"
|
||||
|
||||
@ -5374,356 +5422,356 @@ msgstr "Erreur de création de l'album %(album)s : %(msg)s"
|
||||
msgid "Add user"
|
||||
msgstr "Ajouter une personne"
|
||||
|
||||
#: sith/settings.py:218 sith/settings.py:421
|
||||
#: sith/settings.py:242 sith/settings.py:445
|
||||
msgid "English"
|
||||
msgstr "Anglais"
|
||||
|
||||
#: sith/settings.py:218 sith/settings.py:420
|
||||
#: sith/settings.py:242 sith/settings.py:444
|
||||
msgid "French"
|
||||
msgstr "Français"
|
||||
|
||||
#: sith/settings.py:340
|
||||
#: sith/settings.py:364
|
||||
msgid "TC"
|
||||
msgstr "TC"
|
||||
|
||||
#: sith/settings.py:341
|
||||
#: sith/settings.py:365
|
||||
msgid "IMSI"
|
||||
msgstr "IMSI"
|
||||
|
||||
#: sith/settings.py:342
|
||||
#: sith/settings.py:366
|
||||
msgid "IMAP"
|
||||
msgstr "IMAP"
|
||||
|
||||
#: sith/settings.py:343
|
||||
#: sith/settings.py:367
|
||||
msgid "INFO"
|
||||
msgstr "INFO"
|
||||
|
||||
#: sith/settings.py:344
|
||||
#: sith/settings.py:368
|
||||
msgid "GI"
|
||||
msgstr "GI"
|
||||
|
||||
#: sith/settings.py:345 sith/settings.py:431
|
||||
#: sith/settings.py:369 sith/settings.py:455
|
||||
msgid "E"
|
||||
msgstr "E"
|
||||
|
||||
#: sith/settings.py:346
|
||||
#: sith/settings.py:370
|
||||
msgid "EE"
|
||||
msgstr "EE"
|
||||
|
||||
#: sith/settings.py:347
|
||||
#: sith/settings.py:371
|
||||
msgid "GESC"
|
||||
msgstr "GESC"
|
||||
|
||||
#: sith/settings.py:348
|
||||
#: sith/settings.py:372
|
||||
msgid "GMC"
|
||||
msgstr "GMC"
|
||||
|
||||
#: sith/settings.py:349
|
||||
#: sith/settings.py:373
|
||||
msgid "MC"
|
||||
msgstr "MC"
|
||||
|
||||
#: sith/settings.py:350
|
||||
#: sith/settings.py:374
|
||||
msgid "EDIM"
|
||||
msgstr "EDIM"
|
||||
|
||||
#: sith/settings.py:351
|
||||
#: sith/settings.py:375
|
||||
msgid "Humanities"
|
||||
msgstr "Humanités"
|
||||
|
||||
#: sith/settings.py:352
|
||||
#: sith/settings.py:376
|
||||
msgid "N/A"
|
||||
msgstr "N/A"
|
||||
|
||||
#: sith/settings.py:356 sith/settings.py:363 sith/settings.py:382
|
||||
#: sith/settings.py:380 sith/settings.py:387 sith/settings.py:406
|
||||
msgid "Check"
|
||||
msgstr "Chèque"
|
||||
|
||||
#: sith/settings.py:357 sith/settings.py:365 sith/settings.py:383
|
||||
#: sith/settings.py:381 sith/settings.py:389 sith/settings.py:407
|
||||
msgid "Cash"
|
||||
msgstr "Espèces"
|
||||
|
||||
#: sith/settings.py:358
|
||||
#: sith/settings.py:382
|
||||
msgid "Transfert"
|
||||
msgstr "Virement"
|
||||
|
||||
#: sith/settings.py:371
|
||||
#: sith/settings.py:395
|
||||
msgid "Belfort"
|
||||
msgstr "Belfort"
|
||||
|
||||
#: sith/settings.py:372
|
||||
#: sith/settings.py:396
|
||||
msgid "Sevenans"
|
||||
msgstr "Sevenans"
|
||||
|
||||
#: sith/settings.py:373
|
||||
#: sith/settings.py:397
|
||||
msgid "Montbéliard"
|
||||
msgstr "Montbéliard"
|
||||
|
||||
#: sith/settings.py:401
|
||||
#: sith/settings.py:425
|
||||
msgid "Free"
|
||||
msgstr "Libre"
|
||||
|
||||
#: sith/settings.py:402
|
||||
#: sith/settings.py:426
|
||||
msgid "CS"
|
||||
msgstr "CS"
|
||||
|
||||
#: sith/settings.py:403
|
||||
#: sith/settings.py:427
|
||||
msgid "TM"
|
||||
msgstr "TM"
|
||||
|
||||
#: sith/settings.py:404
|
||||
#: sith/settings.py:428
|
||||
msgid "OM"
|
||||
msgstr "OM"
|
||||
|
||||
#: sith/settings.py:405
|
||||
#: sith/settings.py:429
|
||||
msgid "QC"
|
||||
msgstr "QC"
|
||||
|
||||
#: sith/settings.py:406
|
||||
#: sith/settings.py:430
|
||||
msgid "EC"
|
||||
msgstr "EC"
|
||||
|
||||
#: sith/settings.py:407
|
||||
#: sith/settings.py:431
|
||||
msgid "RN"
|
||||
msgstr "RN"
|
||||
|
||||
#: sith/settings.py:408
|
||||
#: sith/settings.py:432
|
||||
msgid "ST"
|
||||
msgstr "ST"
|
||||
|
||||
#: sith/settings.py:409
|
||||
#: sith/settings.py:433
|
||||
msgid "EXT"
|
||||
msgstr "EXT"
|
||||
|
||||
#: sith/settings.py:414
|
||||
#: sith/settings.py:438
|
||||
msgid "Autumn"
|
||||
msgstr "Automne"
|
||||
|
||||
#: sith/settings.py:415
|
||||
#: sith/settings.py:439
|
||||
msgid "Spring"
|
||||
msgstr "Printemps"
|
||||
|
||||
#: sith/settings.py:416
|
||||
#: sith/settings.py:440
|
||||
msgid "Autumn and spring"
|
||||
msgstr "Automne et printemps"
|
||||
|
||||
#: sith/settings.py:422
|
||||
#: sith/settings.py:446
|
||||
msgid "German"
|
||||
msgstr "Allemant"
|
||||
|
||||
#: sith/settings.py:423
|
||||
#: sith/settings.py:447
|
||||
msgid "Spanich"
|
||||
msgstr "Espagnol"
|
||||
|
||||
#: sith/settings.py:427
|
||||
#: sith/settings.py:451
|
||||
msgid "A"
|
||||
msgstr "A"
|
||||
|
||||
#: sith/settings.py:428
|
||||
#: sith/settings.py:452
|
||||
msgid "B"
|
||||
msgstr "B"
|
||||
|
||||
#: sith/settings.py:429
|
||||
#: sith/settings.py:453
|
||||
msgid "C"
|
||||
msgstr "C"
|
||||
|
||||
#: sith/settings.py:430
|
||||
#: sith/settings.py:454
|
||||
msgid "D"
|
||||
msgstr "D"
|
||||
|
||||
#: sith/settings.py:432
|
||||
#: sith/settings.py:456
|
||||
msgid "FX"
|
||||
msgstr "FX"
|
||||
|
||||
#: sith/settings.py:433
|
||||
#: sith/settings.py:457
|
||||
msgid "F"
|
||||
msgstr "F"
|
||||
|
||||
#: sith/settings.py:434
|
||||
#: sith/settings.py:458
|
||||
msgid "Abs"
|
||||
msgstr "Abs"
|
||||
|
||||
#: sith/settings.py:438
|
||||
#: sith/settings.py:462
|
||||
msgid "Selling deletion"
|
||||
msgstr "Suppression de vente"
|
||||
|
||||
#: sith/settings.py:439
|
||||
#: sith/settings.py:463
|
||||
msgid "Refilling deletion"
|
||||
msgstr "Suppression de rechargement"
|
||||
|
||||
#: sith/settings.py:476
|
||||
#: sith/settings.py:500
|
||||
msgid "One semester"
|
||||
msgstr "Un semestre, 20 €"
|
||||
|
||||
#: sith/settings.py:477
|
||||
#: sith/settings.py:501
|
||||
msgid "Two semesters"
|
||||
msgstr "Deux semestres, 35 €"
|
||||
|
||||
#: sith/settings.py:479
|
||||
#: sith/settings.py:503
|
||||
msgid "Common core cursus"
|
||||
msgstr "Cursus tronc commun, 60 €"
|
||||
|
||||
#: sith/settings.py:483
|
||||
#: sith/settings.py:507
|
||||
msgid "Branch cursus"
|
||||
msgstr "Cursus branche, 60 €"
|
||||
|
||||
#: sith/settings.py:484
|
||||
#: sith/settings.py:508
|
||||
msgid "Alternating cursus"
|
||||
msgstr "Cursus alternant, 30 €"
|
||||
|
||||
#: sith/settings.py:485
|
||||
#: sith/settings.py:509
|
||||
msgid "Honorary member"
|
||||
msgstr "Membre honoraire, 0 €"
|
||||
|
||||
#: sith/settings.py:486
|
||||
#: sith/settings.py:510
|
||||
msgid "Assidu member"
|
||||
msgstr "Membre d'Assidu, 0 €"
|
||||
|
||||
#: sith/settings.py:487
|
||||
#: sith/settings.py:511
|
||||
msgid "Amicale/DOCEO member"
|
||||
msgstr "Membre de l'Amicale/DOCEO, 0 €"
|
||||
|
||||
#: sith/settings.py:488
|
||||
#: sith/settings.py:512
|
||||
msgid "UT network member"
|
||||
msgstr "Cotisant du réseau UT, 0 €"
|
||||
|
||||
#: sith/settings.py:489
|
||||
#: sith/settings.py:513
|
||||
msgid "CROUS member"
|
||||
msgstr "Membres du CROUS, 0 €"
|
||||
|
||||
#: sith/settings.py:490
|
||||
#: sith/settings.py:514
|
||||
msgid "Sbarro/ESTA member"
|
||||
msgstr "Membre de Sbarro ou de l'ESTA, 20 €"
|
||||
|
||||
#: sith/settings.py:492
|
||||
#: sith/settings.py:516
|
||||
msgid "One semester Welcome Week"
|
||||
msgstr "Un semestre Welcome Week"
|
||||
|
||||
#: sith/settings.py:496
|
||||
#: sith/settings.py:520
|
||||
msgid "One month for free"
|
||||
msgstr "Un mois gratuit"
|
||||
|
||||
#: sith/settings.py:497
|
||||
#: sith/settings.py:521
|
||||
msgid "Two months for free"
|
||||
msgstr "Deux mois gratuits"
|
||||
|
||||
#: sith/settings.py:498
|
||||
#: sith/settings.py:522
|
||||
msgid "Eurok's volunteer"
|
||||
msgstr "Bénévole Eurockéennes"
|
||||
|
||||
#: sith/settings.py:500
|
||||
#: sith/settings.py:524
|
||||
msgid "Six weeks for free"
|
||||
msgstr "6 semaines gratuites"
|
||||
|
||||
#: sith/settings.py:504
|
||||
#: sith/settings.py:528
|
||||
msgid "One day"
|
||||
msgstr "Un jour"
|
||||
|
||||
#: sith/settings.py:505
|
||||
#: sith/settings.py:529
|
||||
msgid "GA staff member"
|
||||
msgstr "Membre staff GA (2 semaines), 1 €"
|
||||
|
||||
#: sith/settings.py:508
|
||||
#: sith/settings.py:532
|
||||
msgid "One semester (-20%)"
|
||||
msgstr "Un semestre (-20%), 12 €"
|
||||
|
||||
#: sith/settings.py:513
|
||||
#: sith/settings.py:537
|
||||
msgid "Two semesters (-20%)"
|
||||
msgstr "Deux semestres (-20%), 22 €"
|
||||
|
||||
#: sith/settings.py:518
|
||||
#: sith/settings.py:542
|
||||
msgid "Common core cursus (-20%)"
|
||||
msgstr "Cursus tronc commun (-20%), 36 €"
|
||||
|
||||
#: sith/settings.py:523
|
||||
#: sith/settings.py:547
|
||||
msgid "Branch cursus (-20%)"
|
||||
msgstr "Cursus branche (-20%), 36 €"
|
||||
|
||||
#: sith/settings.py:528
|
||||
#: sith/settings.py:552
|
||||
msgid "Alternating cursus (-20%)"
|
||||
msgstr "Cursus alternant (-20%), 24 €"
|
||||
|
||||
#: sith/settings.py:534
|
||||
#: sith/settings.py:558
|
||||
msgid "One year for free(CA offer)"
|
||||
msgstr "Une année offerte (Offre CA)"
|
||||
|
||||
#: sith/settings.py:556
|
||||
#: sith/settings.py:580
|
||||
msgid "President"
|
||||
msgstr "Président⸱e"
|
||||
|
||||
#: sith/settings.py:557
|
||||
#: sith/settings.py:581
|
||||
msgid "Vice-President"
|
||||
msgstr "Vice-Président⸱e"
|
||||
|
||||
#: sith/settings.py:558
|
||||
#: sith/settings.py:582
|
||||
msgid "Treasurer"
|
||||
msgstr "Trésorier⸱e"
|
||||
|
||||
#: sith/settings.py:559
|
||||
#: sith/settings.py:583
|
||||
msgid "Communication supervisor"
|
||||
msgstr "Responsable communication"
|
||||
|
||||
#: sith/settings.py:560
|
||||
#: sith/settings.py:584
|
||||
msgid "Secretary"
|
||||
msgstr "Secrétaire"
|
||||
|
||||
#: sith/settings.py:561
|
||||
#: sith/settings.py:585
|
||||
msgid "IT supervisor"
|
||||
msgstr "Responsable info"
|
||||
|
||||
#: sith/settings.py:562
|
||||
#: sith/settings.py:586
|
||||
msgid "Board member"
|
||||
msgstr "Membre du bureau"
|
||||
|
||||
#: sith/settings.py:563
|
||||
#: sith/settings.py:587
|
||||
msgid "Active member"
|
||||
msgstr "Membre actif⸱ve"
|
||||
|
||||
#: sith/settings.py:564
|
||||
#: sith/settings.py:588
|
||||
msgid "Curious"
|
||||
msgstr "Curieux⸱euse"
|
||||
|
||||
#: sith/settings.py:600
|
||||
#: sith/settings.py:624
|
||||
msgid "A new poster needs to be moderated"
|
||||
msgstr "Une nouvelle affiche a besoin d'être modérée"
|
||||
|
||||
#: sith/settings.py:601
|
||||
#: sith/settings.py:625
|
||||
msgid "A new mailing list needs to be moderated"
|
||||
msgstr "Une nouvelle mailing list a besoin d'être modérée"
|
||||
|
||||
#: sith/settings.py:604
|
||||
#: sith/settings.py:628
|
||||
msgid "A new pedagogy comment has been signaled for moderation"
|
||||
msgstr ""
|
||||
"Un nouveau commentaire de la pédagogie a été signalé pour la modération"
|
||||
|
||||
#: sith/settings.py:606
|
||||
#: sith/settings.py:630
|
||||
#, python-format
|
||||
msgid "There are %s fresh news to be moderated"
|
||||
msgstr "Il y a %s nouvelles toutes fraîches à modérer"
|
||||
|
||||
#: sith/settings.py:607
|
||||
#: sith/settings.py:631
|
||||
msgid "New files to be moderated"
|
||||
msgstr "Nouveaux fichiers à modérer"
|
||||
|
||||
#: sith/settings.py:608
|
||||
#: sith/settings.py:632
|
||||
#, python-format
|
||||
msgid "There are %s pictures to be moderated in the SAS"
|
||||
msgstr "Il y a %s photos à modérer dans le SAS"
|
||||
|
||||
#: sith/settings.py:609
|
||||
#: sith/settings.py:633
|
||||
msgid "You've been identified on some pictures"
|
||||
msgstr "Vous avez été identifié sur des photos"
|
||||
|
||||
#: sith/settings.py:610
|
||||
#: sith/settings.py:634
|
||||
#, python-format
|
||||
msgid "You just refilled of %s €"
|
||||
msgstr "Vous avez rechargé votre compte de %s€"
|
||||
|
||||
#: sith/settings.py:611
|
||||
#: sith/settings.py:635
|
||||
#, python-format
|
||||
msgid "You just bought %s"
|
||||
msgstr "Vous avez acheté %s"
|
||||
|
||||
#: sith/settings.py:612
|
||||
#: sith/settings.py:636
|
||||
msgid "You have a notification"
|
||||
msgstr "Vous avez une notification"
|
||||
|
||||
@ -5732,26 +5780,27 @@ msgid "You do not have any unread notification"
|
||||
msgstr "Vous n'avez aucune notification non lue"
|
||||
|
||||
#: sith/settings.py:624
|
||||
#: sith/settings.py:648
|
||||
msgid "Success!"
|
||||
msgstr "Succès !"
|
||||
|
||||
#: sith/settings.py:625
|
||||
#: sith/settings.py:649
|
||||
msgid "Fail!"
|
||||
msgstr "Échec !"
|
||||
|
||||
#: sith/settings.py:626
|
||||
#: sith/settings.py:650
|
||||
msgid "You successfully posted an article in the Weekmail"
|
||||
msgstr "Article posté avec succès dans le Weekmail"
|
||||
|
||||
#: sith/settings.py:627
|
||||
#: sith/settings.py:651
|
||||
msgid "You successfully edited an article in the Weekmail"
|
||||
msgstr "Article édité avec succès dans le Weekmail"
|
||||
|
||||
#: sith/settings.py:628
|
||||
#: sith/settings.py:652
|
||||
msgid "You successfully sent the Weekmail"
|
||||
msgstr "Weekmail envoyé avec succès"
|
||||
|
||||
#: sith/settings.py:636
|
||||
#: sith/settings.py:660
|
||||
msgid "AE tee-shirt"
|
||||
msgstr "Tee-shirt AE"
|
||||
|
||||
@ -6302,3 +6351,11 @@ msgstr "Vous ne pouvez plus écrire de commentaires, la date est passée."
|
||||
#, python-format
|
||||
msgid "Maximum characters: %(max_length)s"
|
||||
msgstr "Nombre de caractères max: %(max_length)s"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja:127
|
||||
msgid "Partnership Eurockéennes 2023"
|
||||
msgstr "Partenariat Eurockéennes 2023"
|
||||
|
||||
#: eboutic/templates/eboutic/eboutic_main.jinja:137
|
||||
msgid "You must be a subscriber to access the Eurockéennes ticketing service."
|
||||
msgstr "Vous devez être cotisant pour pouvoir accéder à la billetterie des Eurockéennes."
|
||||
|
@ -21,7 +21,212 @@
|
||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
#
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
|
||||
# Create your tests here.
|
||||
from club.models import Club
|
||||
from core.models import User, RealGroup
|
||||
from counter.models import Customer, Product, Selling, Counter, Refilling
|
||||
from subscription.models import Subscription
|
||||
|
||||
|
||||
class MergeUserTest(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
call_command("populate")
|
||||
cls.ae = Club.objects.get(unix_name="ae")
|
||||
cls.eboutic = Counter.objects.get(name="Eboutic")
|
||||
cls.barbar = Product.objects.get(code="BARB")
|
||||
cls.barbar.selling_price = 2
|
||||
cls.barbar.save()
|
||||
cls.root = User.objects.get(username="root")
|
||||
|
||||
def setUp(self) -> None:
|
||||
super().setUp()
|
||||
self.to_keep = User(username="to_keep", password="plop", email="u.1@utbm.fr")
|
||||
self.to_delete = User(username="to_del", password="plop", email="u.2@utbm.fr")
|
||||
self.to_keep.save()
|
||||
self.to_delete.save()
|
||||
self.client.login(username="root", password="plop")
|
||||
|
||||
def test_simple(self):
|
||||
self.to_delete.first_name = "Biggus"
|
||||
self.to_keep.last_name = "Dickus"
|
||||
self.to_keep.nick_name = "B'ian"
|
||||
self.to_keep.address = "Jerusalem"
|
||||
self.to_delete.parent_address = "Rome"
|
||||
self.to_delete.address = "Rome"
|
||||
subscribers = RealGroup.objects.get(name="Subscribers")
|
||||
mde_admin = RealGroup.objects.get(name="MDE admin")
|
||||
sas_admin = RealGroup.objects.get(name="SAS admin")
|
||||
self.to_keep.groups.add(subscribers.id)
|
||||
self.to_delete.groups.add(mde_admin.id)
|
||||
self.to_keep.groups.add(sas_admin.id)
|
||||
self.to_delete.groups.add(sas_admin.id)
|
||||
self.to_delete.save()
|
||||
self.to_keep.save()
|
||||
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||
res = self.client.post(reverse("rootplace:merge"), data)
|
||||
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||
self.assertFalse(User.objects.filter(pk=self.to_delete.pk).exists())
|
||||
self.to_keep = User.objects.get(pk=self.to_keep.pk)
|
||||
# fields of to_delete should be assigned to to_keep
|
||||
# if they were not set beforehand
|
||||
self.assertEqual("Biggus", self.to_keep.first_name)
|
||||
self.assertEqual("Dickus", self.to_keep.last_name)
|
||||
self.assertEqual("B'ian", self.to_keep.nick_name)
|
||||
self.assertEqual("Jerusalem", self.to_keep.address)
|
||||
self.assertEqual("Rome", self.to_keep.parent_address)
|
||||
self.assertEqual(3, self.to_keep.groups.count())
|
||||
groups = list(self.to_keep.groups.all())
|
||||
expected = [subscribers, mde_admin, sas_admin]
|
||||
self.assertCountEqual(groups, expected)
|
||||
|
||||
def test_both_subscribers_and_with_account(self):
|
||||
Customer(user=self.to_keep, account_id="11000l", amount=0).save()
|
||||
Customer(user=self.to_delete, account_id="12000m", amount=0).save()
|
||||
Refilling(
|
||||
amount=10,
|
||||
operator=self.root,
|
||||
customer=self.to_keep.customer,
|
||||
counter=self.eboutic,
|
||||
).save()
|
||||
Refilling(
|
||||
amount=20,
|
||||
operator=self.root,
|
||||
customer=self.to_delete.customer,
|
||||
counter=self.eboutic,
|
||||
).save()
|
||||
Selling(
|
||||
label="barbar",
|
||||
counter=self.eboutic,
|
||||
club=self.ae,
|
||||
product=self.barbar,
|
||||
customer=self.to_keep.customer,
|
||||
seller=self.root,
|
||||
unit_price=2,
|
||||
quantity=2,
|
||||
payment_method="SITH_ACCOUNT",
|
||||
).save()
|
||||
Selling(
|
||||
label="barbar",
|
||||
counter=self.eboutic,
|
||||
club=self.ae,
|
||||
product=self.barbar,
|
||||
customer=self.to_delete.customer,
|
||||
seller=self.root,
|
||||
unit_price=2,
|
||||
quantity=4,
|
||||
payment_method="SITH_ACCOUNT",
|
||||
).save()
|
||||
today = date.today()
|
||||
# both subscriptions began last month and shall end in 5 months
|
||||
Subscription(
|
||||
member=self.to_keep,
|
||||
subscription_type="un-semestre",
|
||||
payment_method="EBOUTIC",
|
||||
subscription_start=today - timedelta(30),
|
||||
subscription_end=today + timedelta(5 * 30),
|
||||
).save()
|
||||
Subscription(
|
||||
member=self.to_delete,
|
||||
subscription_type="un-semestre",
|
||||
payment_method="EBOUTIC",
|
||||
subscription_start=today - timedelta(30),
|
||||
subscription_end=today + timedelta(5 * 30),
|
||||
).save()
|
||||
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||
res = self.client.post(reverse("rootplace:merge"), data)
|
||||
self.to_keep = User.objects.get(pk=self.to_keep.id)
|
||||
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||
# to_keep had 10€ at first and bought 2 barbar worth 2€ each
|
||||
# to_delete had 20€ and bought 4 barbar
|
||||
# total should be 10 - 4 + 20 - 8 = 18
|
||||
self.assertAlmostEqual(18, self.to_keep.customer.amount, delta=0.0001)
|
||||
self.assertEqual(2, self.to_keep.customer.buyings.count())
|
||||
self.assertEqual(2, self.to_keep.customer.refillings.count())
|
||||
self.assertTrue(self.to_keep.is_subscribed)
|
||||
# to_keep had 5 months of subscription remaining and received
|
||||
# 5 more months from to_delete, so he should be subscribed for 10 months
|
||||
self.assertEqual(
|
||||
today + timedelta(10 * 30),
|
||||
self.to_keep.subscriptions.order_by("subscription_end")
|
||||
.last()
|
||||
.subscription_end,
|
||||
)
|
||||
|
||||
def test_godfathers(self):
|
||||
users = list(User.objects.all()[:4])
|
||||
self.to_keep.godfathers.add(users[0])
|
||||
self.to_keep.godchildren.add(users[1])
|
||||
self.to_delete.godfathers.add(users[2])
|
||||
self.to_delete.godfathers.add(self.to_keep)
|
||||
self.to_delete.godchildren.add(users[3])
|
||||
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||
res = self.client.post(reverse("rootplace:merge"), data)
|
||||
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||
self.to_keep = User.objects.get(pk=self.to_keep.id)
|
||||
self.assertCountEqual(list(self.to_keep.godfathers.all()), [users[0], users[2]])
|
||||
self.assertCountEqual(
|
||||
list(self.to_keep.godchildren.all()), [users[1], users[3]]
|
||||
)
|
||||
|
||||
def test_keep_has_no_account(self):
|
||||
Customer(user=self.to_delete, account_id="12000m", amount=0).save()
|
||||
Refilling(
|
||||
amount=20,
|
||||
operator=self.root,
|
||||
customer=self.to_delete.customer,
|
||||
counter=self.eboutic,
|
||||
).save()
|
||||
Selling(
|
||||
label="barbar",
|
||||
counter=self.eboutic,
|
||||
club=self.ae,
|
||||
product=self.barbar,
|
||||
customer=self.to_delete.customer,
|
||||
seller=self.root,
|
||||
unit_price=2,
|
||||
quantity=4,
|
||||
payment_method="SITH_ACCOUNT",
|
||||
).save()
|
||||
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||
res = self.client.post(reverse("rootplace:merge"), data)
|
||||
self.to_keep = User.objects.get(pk=self.to_keep.id)
|
||||
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||
# to_delete had 20€ and bought 4 barbar worth 2€ each
|
||||
# total should be 20 - 8 = 12
|
||||
self.assertTrue(hasattr(self.to_keep, "customer"))
|
||||
self.assertAlmostEqual(12, self.to_keep.customer.amount, delta=0.0001)
|
||||
|
||||
def test_delete_has_no_account(self):
|
||||
Customer(user=self.to_keep, account_id="12000m", amount=0).save()
|
||||
Refilling(
|
||||
amount=20,
|
||||
operator=self.root,
|
||||
customer=self.to_keep.customer,
|
||||
counter=self.eboutic,
|
||||
).save()
|
||||
Selling(
|
||||
label="barbar",
|
||||
counter=self.eboutic,
|
||||
club=self.ae,
|
||||
product=self.barbar,
|
||||
customer=self.to_keep.customer,
|
||||
seller=self.root,
|
||||
unit_price=2,
|
||||
quantity=4,
|
||||
payment_method="SITH_ACCOUNT",
|
||||
).save()
|
||||
data = {"user1": self.to_keep.id, "user2": self.to_delete.id}
|
||||
res = self.client.post(reverse("rootplace:merge"), data)
|
||||
self.to_keep = User.objects.get(pk=self.to_keep.id)
|
||||
self.assertRedirects(res, self.to_keep.get_absolute_url())
|
||||
# to_keep had 20€ and bought 4 barbar worth 2€ each
|
||||
# total should be 20 - 8 = 12
|
||||
self.assertTrue(hasattr(self.to_keep, "customer"))
|
||||
self.assertAlmostEqual(12, self.to_keep.customer.amount, delta=0.0001)
|
||||
|
@ -23,72 +23,114 @@
|
||||
#
|
||||
#
|
||||
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic.edit import FormView
|
||||
from django.views.generic import ListView
|
||||
from django.urls import reverse
|
||||
from ajax_select.fields import AutoCompleteSelectField
|
||||
from django import forms
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext as _
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic.edit import FormView
|
||||
|
||||
from ajax_select.fields import AutoCompleteSelectField
|
||||
|
||||
from core.models import User, OperationLog, SithFile
|
||||
from core.views import CanEditPropMixin
|
||||
from core.models import User, OperationLog
|
||||
from counter.models import Customer
|
||||
|
||||
from forum.models import ForumMessageMeta
|
||||
|
||||
|
||||
def merge_users(u1, u2):
|
||||
u1.nick_name = u1.nick_name or u2.nick_name
|
||||
u1.date_of_birth = u1.date_of_birth or u2.date_of_birth
|
||||
u1.home = u1.home or u2.home
|
||||
u1.sex = u1.sex or u2.sex
|
||||
u1.pronouns = u1.pronouns or u2.pronouns
|
||||
u1.tshirt_size = u1.tshirt_size or u2.tshirt_size
|
||||
u1.role = u1.role or u2.role
|
||||
u1.department = u1.department or u2.department
|
||||
u1.dpt_option = u1.dpt_option or u2.dpt_option
|
||||
u1.semester = u1.semester or u2.semester
|
||||
u1.quote = u1.quote or u2.quote
|
||||
u1.school = u1.school or u2.school
|
||||
u1.promo = u1.promo or u2.promo
|
||||
u1.forum_signature = u1.forum_signature or u2.forum_signature
|
||||
u1.second_email = u1.second_email or u2.second_email
|
||||
u1.phone = u1.phone or u2.phone
|
||||
u1.parent_phone = u1.parent_phone or u2.parent_phone
|
||||
u1.address = u1.address or u2.address
|
||||
u1.parent_address = u1.parent_address or u2.parent_address
|
||||
def __merge_subscriptions(u1: User, u2: User):
|
||||
"""
|
||||
Give all the subscriptions of the second user to first one
|
||||
If some subscriptions are still active, update their end date
|
||||
to increase the overall subscription time of the first user.
|
||||
|
||||
Some examples :
|
||||
- if u1 is not subscribed, his subscription end date become the one of u2
|
||||
- if u1 is subscribed but not u2, nothing happen
|
||||
- if u1 is subscribed for, let's say, 2 remaining months and u2 is subscribed for 3 remaining months,
|
||||
he shall then be subscribed for 5 months
|
||||
"""
|
||||
last_subscription = (
|
||||
u1.subscriptions.filter(
|
||||
subscription_start__lte=timezone.now(), subscription_end__gte=timezone.now()
|
||||
)
|
||||
.order_by("subscription_end")
|
||||
.last()
|
||||
)
|
||||
if last_subscription is not None:
|
||||
subscription_end = last_subscription.subscription_end
|
||||
for subscription in u2.subscriptions.filter(
|
||||
subscription_end__gte=timezone.now()
|
||||
):
|
||||
subscription.subscription_start = subscription_end
|
||||
if subscription.subscription_start > timezone.now().date():
|
||||
remaining = subscription.subscription_end - timezone.now().date()
|
||||
else:
|
||||
remaining = (
|
||||
subscription.subscription_end - subscription.subscription_start
|
||||
)
|
||||
subscription_end += remaining
|
||||
subscription.subscription_end = subscription_end
|
||||
subscription.save()
|
||||
u2.subscriptions.all().update(member=u1)
|
||||
|
||||
|
||||
def __merge_pictures(u1: User, u2: User) -> None:
|
||||
SithFile.objects.filter(owner=u2).update(owner=u1)
|
||||
if u1.profile_pict is None and u2.profile_pict is not None:
|
||||
u1.profile_pict, u2.profile_pict = u2.profile_pict, None
|
||||
if u1.scrub_pict is None and u2.scrub_pict is not None:
|
||||
u1.scrub_pict, u2.scrub_pict = u2.scrub_pict, None
|
||||
if u1.avatar_pict is None and u2.avatar_pict is not None:
|
||||
u1.avatar_pict, u2.avatar_pict = u2.avatar_pict, None
|
||||
u2.save()
|
||||
u1.save()
|
||||
for u in u2.godfathers.all():
|
||||
u1.godfathers.add(u)
|
||||
|
||||
|
||||
def merge_users(u1: User, u2: User) -> User:
|
||||
"""
|
||||
Merge u2 into u1
|
||||
This means that u1 shall receive everything that belonged to u2 :
|
||||
|
||||
- pictures
|
||||
- refills of the sith account
|
||||
- purchases of any item bought on the eboutic or the counters
|
||||
- subscriptions
|
||||
- godfathers
|
||||
- godchildren
|
||||
|
||||
If u1 had no account id, he shall receive the one of u2.
|
||||
If u1 and u2 were both in the middle of a subscription, the remaining
|
||||
durations stack
|
||||
If u1 had no profile picture, he shall receive the one of u2
|
||||
"""
|
||||
for field in u1._meta.fields:
|
||||
if not field.is_relation and not u1.__dict__[field.name]:
|
||||
u1.__dict__[field.name] = u2.__dict__[field.name]
|
||||
for group in u2.groups.all():
|
||||
u1.groups.add(group.id)
|
||||
for godfather in u2.godfathers.exclude(id=u1.id):
|
||||
u1.godfathers.add(godfather)
|
||||
for godchild in u2.godchildren.exclude(id=u1.id):
|
||||
u1.godchildren.add(godchild)
|
||||
__merge_subscriptions(u1, u2)
|
||||
__merge_pictures(u1, u2)
|
||||
u2.invoices.all().update(user=u1)
|
||||
c_src = Customer.objects.filter(user=u2).first()
|
||||
if c_src is not None:
|
||||
c_dest, created = Customer.get_or_create(u1)
|
||||
c_src.refillings.update(customer=c_dest)
|
||||
c_src.buyings.update(customer=c_dest)
|
||||
c_dest.recompute_amount()
|
||||
if created:
|
||||
# swap the account numbers, so that the user keep
|
||||
# the id he is accustomed to
|
||||
tmp_id = c_src.account_id
|
||||
# delete beforehand in order not to have a unique constraint violation
|
||||
c_src.delete()
|
||||
c_dest.account_id = tmp_id
|
||||
u1.save()
|
||||
for i in u2.invoices.all():
|
||||
for f in i._meta.local_fields: # I have sadly not found anything better :/
|
||||
if f.name == "date":
|
||||
f.auto_now = False
|
||||
u1.invoices.add(i)
|
||||
u1.save()
|
||||
s1 = User.objects.filter(id=u1.id).first()
|
||||
s2 = User.objects.filter(id=u2.id).first()
|
||||
for s in s2.subscriptions.all():
|
||||
s1.subscriptions.add(s)
|
||||
s1.save()
|
||||
c1 = Customer.objects.filter(user__id=u1.id).first()
|
||||
c2 = Customer.objects.filter(user__id=u2.id).first()
|
||||
if c1 and c2:
|
||||
for r in c2.refillings.all():
|
||||
c1.refillings.add(r)
|
||||
c1.save()
|
||||
for s in c2.buyings.all():
|
||||
c1.buyings.add(s)
|
||||
c1.save()
|
||||
elif c2 and not c1:
|
||||
c2.user = u1
|
||||
c1 = c2
|
||||
c1.save()
|
||||
c1.recompute_amount()
|
||||
u2.delete()
|
||||
u2.delete() # everything remaining in u2 gets deleted thanks to on_delete=CASCADE
|
||||
return u1
|
||||
|
||||
|
||||
@ -128,9 +170,8 @@ class MergeUsersView(FormView):
|
||||
form_class = MergeForm
|
||||
|
||||
def dispatch(self, request, *arg, **kwargs):
|
||||
res = super(MergeUsersView, self).dispatch(request, *arg, **kwargs)
|
||||
if request.user.is_root:
|
||||
return res
|
||||
return super().dispatch(request, *arg, **kwargs)
|
||||
raise PermissionDenied
|
||||
|
||||
def form_valid(self, form):
|
||||
@ -140,7 +181,7 @@ class MergeUsersView(FormView):
|
||||
return super(MergeUsersView, self).form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse("core:user_profile", kwargs={"user_id": self.final_user.id})
|
||||
return self.final_user.get_absolute_url()
|
||||
|
||||
|
||||
class DeleteAllForumUserMessagesView(FormView):
|
||||
|
@ -291,6 +291,10 @@ SITH_URL = "my.url.git.an"
|
||||
SITH_NAME = "Sith website"
|
||||
SITH_TWITTER = "@ae_utbm"
|
||||
|
||||
# Enable experimental features
|
||||
# Enable/Disable the galaxy button on user profile (urls stay activated)
|
||||
SITH_ENABLE_GALAXY = False
|
||||
|
||||
# AE configuration
|
||||
# TODO: keep only that first setting, with the ID, and do the same for the other clubs
|
||||
SITH_MAIN_CLUB_ID = 1
|
||||
@ -713,6 +717,7 @@ SITH_FRONT_DEP_VERSIONS = {
|
||||
"https://github.com/viralpatel/jquery.shorten/": "",
|
||||
"https://github.com/getsentry/sentry-javascript/": "4.0.6",
|
||||
"https://github.com/jhuckaby/webcamjs/": "1.0.0",
|
||||
"https://github.com/vuejs/vue-next": "3.2.18",
|
||||
"https://github.com/alpinejs/alpine": "3.10.5",
|
||||
"https://github.com/mrdoob/three.js/": "r148",
|
||||
"https://github.com/vasturiano/three-spritetext": "1.6.5",
|
||||
|
@ -165,7 +165,4 @@ class Subscription(models.Model):
|
||||
return user.is_in_group(settings.SITH_MAIN_BOARD_GROUP) or user.is_root
|
||||
|
||||
def is_valid_now(self):
|
||||
return (
|
||||
self.subscription_start <= date.today()
|
||||
and date.today() <= self.subscription_end
|
||||
)
|
||||
return self.subscription_start <= date.today() <= self.subscription_end
|
||||
|
Reference in New Issue
Block a user