mirror of
https://github.com/ae-utbm/sith.git
synced 2026-06-04 23:29:24 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7dabbce4df | |||
| e811aeaecd | |||
| 549a778be0 | |||
| 78b24dc1e7 | |||
| ebf0196bef | |||
| 362b9eea06 | |||
| 3b3e33ed80 |
@@ -392,6 +392,30 @@ class ClubRoleForm(forms.ModelForm):
|
|||||||
self.instance.order = cleaned_data["ORDER"] - 1
|
self.instance.order = cleaned_data["ORDER"] - 1
|
||||||
return cleaned_data
|
return cleaned_data
|
||||||
|
|
||||||
|
def save(self, commit=True): # noqa: FBT002
|
||||||
|
instance: ClubRole = super().save(commit=commit)
|
||||||
|
if commit and "is_board" in self.changed_data:
|
||||||
|
# if the role was moved from board to simple member,
|
||||||
|
# remove all users with that role from the club board group.
|
||||||
|
# If the role became a board role, add users with
|
||||||
|
# that role to the club board group.
|
||||||
|
group_id = instance.club.board_group_id
|
||||||
|
if self.cleaned_data["is_board"]:
|
||||||
|
User.groups.through.objects.bulk_create(
|
||||||
|
[
|
||||||
|
User.groups.through(user_id=u, group_id=group_id)
|
||||||
|
for u in Membership.objects.ongoing()
|
||||||
|
.filter(role=instance)
|
||||||
|
.values_list("user_id", flat=True)
|
||||||
|
],
|
||||||
|
ignore_conflicts=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
User.groups.through.objects.filter(
|
||||||
|
user__memberships__role=instance, group_id=group_id
|
||||||
|
).delete()
|
||||||
|
return instance
|
||||||
|
|
||||||
|
|
||||||
class ClubRoleCreateForm(forms.ModelForm):
|
class ClubRoleCreateForm(forms.ModelForm):
|
||||||
"""Form to create a club role.
|
"""Form to create a club role.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import pytest
|
|||||||
from django.contrib.auth.models import Permission
|
from django.contrib.auth.models import Permission
|
||||||
from django.test import Client, TestCase
|
from django.test import Client, TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from django.utils.timezone import now
|
||||||
from model_bakery import baker, seq
|
from model_bakery import baker, seq
|
||||||
from model_bakery.recipe import Recipe
|
from model_bakery.recipe import Recipe
|
||||||
from pytest_django.asserts import assertRedirects
|
from pytest_django.asserts import assertRedirects
|
||||||
@@ -239,7 +240,7 @@ class TestClubRoleUpdate(TestCase):
|
|||||||
|
|
||||||
def test_president_moves_itself_out_of_the_presidency(self):
|
def test_president_moves_itself_out_of_the_presidency(self):
|
||||||
"""Test that if the user moves its own role out of the presidency,
|
"""Test that if the user moves its own role out of the presidency,
|
||||||
then it's redirected to another page and loses access to the update page."""
|
then it loses access to the update page."""
|
||||||
self.payload["roles-0-is_presidency"] = False
|
self.payload["roles-0-is_presidency"] = False
|
||||||
self.client.force_login(self.user)
|
self.client.force_login(self.user)
|
||||||
res = self.client.post(self.url, data=self.payload)
|
res = self.client.post(self.url, data=self.payload)
|
||||||
@@ -251,3 +252,29 @@ class TestClubRoleUpdate(TestCase):
|
|||||||
|
|
||||||
res = self.client.get(self.url)
|
res = self.client.get(self.url)
|
||||||
assert res.status_code == 403
|
assert res.status_code == 403
|
||||||
|
|
||||||
|
def test_role_stops_being_board(self):
|
||||||
|
"""Test that if a role stops being a board role,
|
||||||
|
its users lose the club board group."""
|
||||||
|
self.payload["roles-0-is_board"] = False
|
||||||
|
self.payload["roles-0-is_presidency"] = False
|
||||||
|
self.payload["roles-1-is_board"] = False
|
||||||
|
formset = ClubRoleFormSet(data=self.payload, instance=self.club)
|
||||||
|
assert formset.is_valid()
|
||||||
|
formset.save()
|
||||||
|
assert not self.user.groups.contains(self.club.board_group)
|
||||||
|
|
||||||
|
def test_role_becomes_board(self):
|
||||||
|
"""Test that if a role becomes a board role,
|
||||||
|
its active users get the club board group"""
|
||||||
|
members = [
|
||||||
|
baker.make(Membership, club=self.club, role=self.roles[0], end_date=None),
|
||||||
|
baker.make(Membership, club=self.club, role=self.roles[0], end_date=now()),
|
||||||
|
]
|
||||||
|
self.payload["roles-2-is_board"] = True
|
||||||
|
formset = ClubRoleFormSet(data=self.payload, instance=self.club)
|
||||||
|
assert formset.is_valid()
|
||||||
|
formset.save()
|
||||||
|
# the second membership is finished, so its user shouldn't get the role
|
||||||
|
assert members[0].user.groups.contains(self.club.board_group)
|
||||||
|
assert not members[1].user.groups.contains(self.club.board_group)
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ details.accordion>.accordion-content {
|
|||||||
border-bottom-right-radius: 3px;
|
border-bottom-right-radius: 3px;
|
||||||
border-bottom-left-radius: 3px;
|
border-bottom-left-radius: 3px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
padding: .75em 1.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@mixin animation($selector) {
|
@mixin animation($selector) {
|
||||||
|
|||||||
@@ -141,6 +141,7 @@ form {
|
|||||||
display: block;
|
display: block;
|
||||||
margin: calc(var(--nf-input-size) * 1.5) auto 10px;
|
margin: calc(var(--nf-input-size) * 1.5) auto 10px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
.fields-centered {
|
.fields-centered {
|
||||||
padding: 10px 10px 0;
|
padding: 10px 10px 0;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import type { RecursivePartial, TomSettings } from "tom-select/dist/types/types";
|
import type { RecursivePartial, TomSettings } from "tom-select/src/types";
|
||||||
import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base.ts";
|
import { AutoCompleteSelectBase } from "#core:core/components/ajax-select-base";
|
||||||
import { registerComponent } from "#core:utils/web-components.ts";
|
import { registerComponent } from "#core:utils/web-components";
|
||||||
|
|
||||||
const productParsingRegex = /^(\d+x)?(.*)/i;
|
const productParsingRegex = /^(\d+x)?(.*)/i;
|
||||||
const codeParsingRegex = / \((\w+)\)$/;
|
const codeParsingRegex = / \((\w+)\)$/;
|
||||||
@@ -63,13 +63,6 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
this.widget.hook("after", "onOptionSelect", () => {
|
|
||||||
/* Focus the next element if it's an input */
|
|
||||||
if (this.nextElementSibling.nodeName === "INPUT") {
|
|
||||||
(this.nextElementSibling as HTMLInputElement).focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
protected tomSelectSettings(): RecursivePartial<TomSettings> {
|
protected tomSelectSettings(): RecursivePartial<TomSettings> {
|
||||||
/* We disable the dropdown on focus because we're going to always autofocus the widget */
|
/* We disable the dropdown on focus because we're going to always autofocus the widget */
|
||||||
@@ -80,9 +73,7 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
|
|||||||
// We need to manually set weights or it results on an inconsistent
|
// We need to manually set weights or it results on an inconsistent
|
||||||
// behavior between production and development environment
|
// behavior between production and development environment
|
||||||
searchField: [
|
searchField: [
|
||||||
// @ts-expect-error documentation says it's fine, specified type is wrong
|
|
||||||
{ field: "code", weight: 2 },
|
{ field: "code", weight: 2 },
|
||||||
// @ts-expect-error documentation says it's fine, specified type is wrong
|
|
||||||
{ field: "text", weight: 0.5 },
|
{ field: "text", weight: 0.5 },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.codeField = this.$refs.codeField;
|
this.codeField = this.$refs.codeField;
|
||||||
|
this.codeField.widget.hook("after", "onOptionSelect", () => {
|
||||||
|
this.handleCode();
|
||||||
|
});
|
||||||
this.codeField.widget.focus();
|
this.codeField.widget.focus();
|
||||||
|
|
||||||
// It's quite tricky to manually apply attributes to the management part
|
// It's quite tricky to manually apply attributes to the management part
|
||||||
@@ -154,6 +157,7 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.addToBasket(code, quantity);
|
this.addToBasket(code, quantity);
|
||||||
}
|
}
|
||||||
this.codeField.widget.clear();
|
this.codeField.widget.clear();
|
||||||
|
this.codeField.widget.setTextboxValue("");
|
||||||
this.codeField.widget.focus();
|
this.codeField.widget.focus();
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -42,7 +42,28 @@
|
|||||||
min-width: 350px;
|
min-width: 350px;
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
list-style-type: none;
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
|
||||||
|
.basket-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.product-name {
|
||||||
|
flex: 1 2 0;
|
||||||
|
min-width: 0;
|
||||||
|
text-wrap: wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
margin-top: .5rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,10 +56,15 @@
|
|||||||
<div class="accordion-content">
|
<div class="accordion-content">
|
||||||
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %}
|
{% set counter_click_url = url('counter:click', counter_id=counter.id, user_id=customer.user_id) %}
|
||||||
|
|
||||||
<form method="post" action=""
|
<form method="post" action="" @submit.prevent="handleCode">
|
||||||
class="code_form" @submit.prevent="handleCode">
|
|
||||||
|
|
||||||
<counter-product-select name="code" x-ref="codeField" autofocus required placeholder="{% trans %}Select a product...{% endtrans %}">
|
<counter-product-select
|
||||||
|
name="code"
|
||||||
|
x-ref="codeField"
|
||||||
|
autofocus
|
||||||
|
required
|
||||||
|
placeholder="{% trans %}Select a product...{% endtrans %}"
|
||||||
|
>
|
||||||
<option value=""></option>
|
<option value=""></option>
|
||||||
<optgroup label="{% trans %}Operations{% endtrans %}">
|
<optgroup label="{% trans %}Operations{% endtrans %}">
|
||||||
<option value="FIN">{% trans %}Confirm (FIN){% endtrans %}</option>
|
<option value="FIN">{% trans %}Confirm (FIN){% endtrans %}</option>
|
||||||
@@ -68,13 +73,11 @@
|
|||||||
{%- for category, prices in categories.items() -%}
|
{%- for category, prices in categories.items() -%}
|
||||||
<optgroup label="{{ category }}">
|
<optgroup label="{{ category }}">
|
||||||
{%- for price in prices -%}
|
{%- for price in prices -%}
|
||||||
<option value="{{ price.id }}">{{ price.full_label }}</option>
|
<option value="{{ price.id }}">{{ price.full_label }} ({{ price.product.code }})</option>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</optgroup>
|
</optgroup>
|
||||||
{%- endfor -%}
|
{%- endfor -%}
|
||||||
</counter-product-select>
|
</counter-product-select>
|
||||||
|
|
||||||
<input type="submit" value="{% trans %}Go{% endtrans %}"/>
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% for error in form.non_form_errors() %}
|
{% for error in form.non_form_errors() %}
|
||||||
@@ -102,7 +105,9 @@
|
|||||||
{{ form.management_form }}
|
{{ form.management_form }}
|
||||||
</div>
|
</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li x-show="getBasketSize() === 0">{% trans %}This basket is empty{% endtrans %}</li>
|
<li x-show="getBasketSize() === 0">
|
||||||
|
<em>{% trans %}This basket is empty{% endtrans %}</em>
|
||||||
|
</li>
|
||||||
<template x-for="(item, index) in Object.values(basket)" :key="item.product.price.id">
|
<template x-for="(item, index) in Object.values(basket)" :key="item.product.price.id">
|
||||||
<li>
|
<li>
|
||||||
<template x-for="error in item.errors">
|
<template x-for="error in item.errors">
|
||||||
@@ -110,19 +115,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<button @click.prevent="addToBasket(item.product.price.id, -1)">-</button>
|
<div class="basket-row">
|
||||||
<span class="quantity" x-text="item.quantity"></span>
|
<div>
|
||||||
<button @click.prevent="addToBasket(item.product.price.id, 1)">+</button>
|
<button @click.prevent="addToBasket(item.product.price.id, -1)">-</button>
|
||||||
|
<span class="quantity" x-text="item.quantity"></span>
|
||||||
|
<button @click.prevent="addToBasket(item.product.price.id, 1)">+</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<span x-text="item.product.name"></span> :
|
<span class="product-name" x-text="item.product.name"></span>
|
||||||
<span x-text="item.sum().toLocaleString(undefined, { minimumFractionDigits: 2 })">€</span>
|
<span x-text="`${item.sum().toLocaleString(undefined, { minimumFractionDigits: 2 })} €`"></span>
|
||||||
<span x-show="item.getBonusQuantity() > 0"
|
<span x-show="item.getBonusQuantity() > 0"
|
||||||
x-text="`${item.getBonusQuantity()} x P`"></span>
|
x-text="`${item.getBonusQuantity()} x P`"></span>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="remove-item"
|
class="remove-item"
|
||||||
@click.prevent="removeFromBasket(item.product.price.id)"
|
@click.prevent="removeFromBasket(item.product.price.id)"
|
||||||
><i class="fa fa-trash-can delete-action"></i></button>
|
><i class="fa fa-trash-can delete-action"></i></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
|
|||||||
@@ -24,8 +24,8 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Article</td>
|
<td>Article</td>
|
||||||
<td>{% trans %}Quantity{% endtrans %}</td>
|
<td>Quantity</td>
|
||||||
<td>{% trans %}Unit price{% endtrans %}</td>
|
<td>Unit price</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -59,21 +59,11 @@
|
|||||||
<div @htmx:after-request="fill">
|
<div @htmx:after-request="fill">
|
||||||
{{ billing_infos_form }}
|
{{ billing_infos_form }}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% include "core/base/notifications.jinja" %}
|
||||||
{% include "core/base/notifications.jinja" %}
|
<form
|
||||||
<form method="post" id="payment-form">
|
method="post"
|
||||||
{# In order to have one CGV button for both payment means,
|
action="{{ settings.SITH_EBOUTIC_ET_URL }}"
|
||||||
there is only one form, which action will be given
|
>
|
||||||
the `formaction` attribute of the selected submit input #}
|
|
||||||
<div class="form-group margin-bottom">
|
|
||||||
<input type="checkbox" id="cgv" name="cgv" required>
|
|
||||||
<label for="cgv">
|
|
||||||
{% trans trimmed %}I have read and I accept{% endtrans %}
|
|
||||||
<a href="{{ url('core:page', 'cgv') }}">{% trans %}the general terms and conditions{% endtrans%}</a>
|
|
||||||
{%trans%}of the student association of the UTBM{% endtrans %}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{% if settings.SITH_EBOUTIC_CB_ENABLED %}
|
|
||||||
<template x-for="[key, value] in Object.entries(data)" :key="key">
|
<template x-for="[key, value] in Object.entries(data)" :key="key">
|
||||||
<input type="hidden" :name="key" :value="value">
|
<input type="hidden" :name="key" :value="value">
|
||||||
</template>
|
</template>
|
||||||
@@ -87,23 +77,24 @@
|
|||||||
:disabled="!isCbAvailable"
|
:disabled="!isCbAvailable"
|
||||||
{% endif %}
|
{% endif %}
|
||||||
class="btn btn-blue"
|
class="btn btn-blue"
|
||||||
formaction="{{ settings.SITH_EBOUTIC_ET_URL }}"
|
|
||||||
value="{% trans %}Pay with credit card{% endtrans %}"
|
value="{% trans %}Pay with credit card{% endtrans %}"
|
||||||
/>
|
/>
|
||||||
{% else %}
|
</form>
|
||||||
<div class="alert alert-yellow">
|
{% else %}
|
||||||
{% trans trimmed %}
|
<div class="alert alert-yellow">
|
||||||
Credit card payments are currently disabled on the eboutic.
|
{% trans trimmed %}
|
||||||
You may still refill your account in one of the AE counters.
|
Credit card payments are currently disabled on the eboutic.
|
||||||
Please excuse us for the inconvenience.
|
You may still refill your account in one of the AE counters.
|
||||||
{% endtrans %}
|
Please excuse us for the inconvenience.
|
||||||
</div>
|
{% endtrans %}
|
||||||
{% endif %}
|
</div>
|
||||||
{% if basket.contains_refilling_item %}
|
{% endif %}
|
||||||
<p>{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}</p>
|
{% if basket.contains_refilling_item %}
|
||||||
{% elif basket.total > user.account_balance %}
|
<p>{% trans %}AE account payment disabled because your basket contains refilling items.{% endtrans %}</p>
|
||||||
<p>{% trans %}AE account payment disabled because you do not have enough money remaining.{% endtrans %}</p>
|
{% elif basket.total > user.account_balance %}
|
||||||
{% else %}
|
<p>{% trans %}AE account payment disabled because you do not have enough money remaining.{% endtrans %}</p>
|
||||||
|
{% else %}
|
||||||
|
<form method="post" action="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}" name="sith-pay-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input
|
<input
|
||||||
{% if basket.is_expired %}
|
{% if basket.is_expired %}
|
||||||
@@ -113,10 +104,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
class="btn btn-blue"
|
class="btn btn-blue"
|
||||||
type="submit"
|
type="submit"
|
||||||
formaction="{{ url('eboutic:pay_with_sith', basket_id=basket.id) }}"
|
|
||||||
value="{% trans %}Pay with Sith account{% endtrans %}"
|
value="{% trans %}Pay with Sith account{% endtrans %}"
|
||||||
/>
|
/>
|
||||||
{% endif %}
|
</form>
|
||||||
</form>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-05-30 20:12+0200\n"
|
"POT-Creation-Date: 2026-05-15 11:46+0200\n"
|
||||||
"PO-Revision-Date: 2016-07-18\n"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@@ -683,7 +683,6 @@ msgstr "Étiquette"
|
|||||||
#: core/templates/core/user_account_detail.jinja
|
#: core/templates/core/user_account_detail.jinja
|
||||||
#: core/templates/core/user_stats.jinja
|
#: core/templates/core/user_stats.jinja
|
||||||
#: counter/templates/counter/last_ops.jinja
|
#: counter/templates/counter/last_ops.jinja
|
||||||
#: eboutic/templates/eboutic/eboutic_checkout.jinja
|
|
||||||
msgid "Quantity"
|
msgid "Quantity"
|
||||||
msgstr "Quantité"
|
msgstr "Quantité"
|
||||||
|
|
||||||
@@ -967,7 +966,7 @@ msgstr "rôle de club – membre"
|
|||||||
msgid "Benefit"
|
msgid "Benefit"
|
||||||
msgstr "Bénéfice"
|
msgstr "Bénéfice"
|
||||||
|
|
||||||
#: club/views.py eboutic/templates/eboutic/eboutic_checkout.jinja
|
#: club/views.py
|
||||||
msgid "Unit price"
|
msgid "Unit price"
|
||||||
msgstr "Prix unitaire"
|
msgstr "Prix unitaire"
|
||||||
|
|
||||||
@@ -4368,18 +4367,6 @@ msgstr "Solde actuel : "
|
|||||||
msgid "Remaining account amount: "
|
msgid "Remaining account amount: "
|
||||||
msgstr "Solde restant : "
|
msgstr "Solde restant : "
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_checkout.jinja
|
|
||||||
msgid "I have read and I accept"
|
|
||||||
msgstr "J'ai lu et j'accepte"
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_checkout.jinja
|
|
||||||
msgid "the general terms and conditions"
|
|
||||||
msgstr "les conditions générales de vente"
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_checkout.jinja
|
|
||||||
msgid "of the student association of the UTBM"
|
|
||||||
msgstr "de l'Association des étudiants de l'UTBM"
|
|
||||||
|
|
||||||
#: eboutic/templates/eboutic/eboutic_checkout.jinja
|
#: eboutic/templates/eboutic/eboutic_checkout.jinja
|
||||||
msgid "Pay with credit card"
|
msgid "Pay with credit card"
|
||||||
msgstr "Payer avec une carte bancaire"
|
msgstr "Payer avec une carte bancaire"
|
||||||
|
|||||||
+1
-1
@@ -44,7 +44,7 @@ dependencies = [
|
|||||||
"django-honeypot>=1.3.0,<2",
|
"django-honeypot>=1.3.0,<2",
|
||||||
"pydantic-extra-types>=2.11.1,<3.0.0",
|
"pydantic-extra-types>=2.11.1,<3.0.0",
|
||||||
"ical>=12.0.0,<14.0.0",
|
"ical>=12.0.0,<14.0.0",
|
||||||
"redis[hiredis]>=3.3.1,<8.0.0",
|
"redis[hiredis]>=8.0.0,<9.0.0",
|
||||||
"environs[django]>=15.0.1,<16",
|
"environs[django]>=15.0.1,<16",
|
||||||
"requests>=2.34.2,<3.0.0",
|
"requests>=2.34.2,<3.0.0",
|
||||||
"honcho>=2.0.0",
|
"honcho>=2.0.0",
|
||||||
|
|||||||
Reference in New Issue
Block a user