mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-08-09 07:50:42 +02:00
Compare commits
107 Commits
sheets
...
3c4fe0f173
Author | SHA1 | Date | |
---|---|---|---|
|
3c4fe0f173 | ||
|
ff6e207512 | ||
|
0f1e4d2e60 | ||
|
6255bcbbb1 | ||
|
d82a1001c4 | ||
|
31a54482f0 | ||
|
4ee02345d4 | ||
|
422c087d17 | ||
|
30d6e2c95e | ||
|
f3a3f07e38 | ||
|
a5e802f370 | ||
|
540f3bc354 | ||
|
2d19457506 | ||
|
72786d0d2b | ||
|
f099cbc879 | ||
|
977eb7c0d4 | ||
|
d81b1f2710 | ||
|
6a69590a82 | ||
|
7afc583282 | ||
|
4fb0b7d736 | ||
|
18a5b65a1c | ||
|
f545af4977 | ||
|
103e2d0635 | ||
|
aedf0e87ba | ||
|
dab45b5fd4 | ||
|
b3353b563c | ||
|
6bc52be707 | ||
|
834d68fe35 | ||
|
c6a2849d35 | ||
|
4ab22c92b3 | ||
|
c328c1457c | ||
|
96da7d01ae | ||
|
d27f942339 | ||
|
738d6c932d | ||
|
1760196578 | ||
|
13b9b6edea | ||
|
e06e3b2972 | ||
|
9596aa7b8c | ||
|
ba0d64f0d4 | ||
|
8d17801e28 | ||
|
609362c4f8 | ||
|
03d2d5f03e | ||
|
d2057a9f45 | ||
|
b6e68eeebe | ||
|
6410542027 | ||
|
6b1cd3ba7a | ||
|
9f114b8ca2 | ||
|
e0132b6dc8 | ||
|
f1cc82fab3 | ||
|
644cf14c4b | ||
|
f19a489313 | ||
|
dedd6c69cc | ||
|
b42f5afeab | ||
|
31e67ae3f6 | ||
|
b08da7a727 | ||
|
451aa64f33 | ||
|
3c99b0f3e9 | ||
|
201a179947 | ||
|
96784aee3b | ||
|
981c4d0300 | ||
|
11223430fd | ||
|
7aeb977e72 | ||
|
52fef1df42 | ||
|
16f8a60a3f | ||
|
2839d3de1e | ||
|
30afa6da0a | ||
|
84fc77696f | ||
|
19fc620d1f | ||
|
d5819ac562 | ||
|
a79df8f1f6 | ||
|
364b18e188 | ||
|
10a883b2e5 | ||
|
1410ab6c4f | ||
|
623dd61be6 | ||
|
48a0a87e7c | ||
|
563f525b11 | ||
|
63c1d74f1a | ||
|
c42fb380a6 | ||
|
c636d52a73 | ||
|
6a9021ec14 | ||
|
9c9149b53a | ||
|
cb74311e7b | ||
|
9d7dd566c9 | ||
|
6bceb394c5 | ||
|
62cf8f9d84 | ||
|
9944ebcaad | ||
|
8537f043f7 | ||
|
2dd1c3fb89 | ||
|
c8665c5798 | ||
|
e9f1b6f52d | ||
|
1d95ae4810 | ||
|
c89a95f8d2 | ||
|
73640b1dfa | ||
|
84b16ab603 | ||
|
6a1b51dbbf | ||
|
c441a43a8b | ||
|
87f3b51b04 | ||
|
0a853fd3e6 | ||
|
c429734810 | ||
|
5d759111b6 | ||
|
70baf7566c | ||
|
eb355f547c | ||
|
7068170f18 | ||
|
45ee9a8941 | ||
|
454ea19603 | ||
5a77a66391 | |||
|
466cbd9878 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -42,6 +42,7 @@ map.json
|
||||
backups/
|
||||
/static/
|
||||
/media/
|
||||
/tmp/
|
||||
|
||||
# Virtualenv
|
||||
env/
|
||||
|
@@ -12,7 +12,7 @@ RUN apt-get update && \
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||
python3-bs4 python3-setuptools \
|
||||
uwsgi uwsgi-plugin-python3 \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome && \
|
||||
texlive-xetex gettext libjs-bootstrap4 && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Instal PyPI requirements
|
||||
|
@@ -23,7 +23,7 @@ Bien que cela permette de créer une instance sur toutes les distributions,
|
||||
$ sudo apt update
|
||||
$ sudo apt install --no-install-recommends -y \
|
||||
ipython3 python3-setuptools python3-venv python3-dev \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
|
||||
texlive-xetex gettext libjs-bootstrap4 git
|
||||
```
|
||||
|
||||
2. **Clonage du dépot** là où vous voulez :
|
||||
@@ -115,7 +115,7 @@ Sinon vous pouvez suivre les étapes décrites ci-dessous.
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache ipython3 \
|
||||
python3-bs4 python3-setuptools python3-docutils \
|
||||
memcached uwsgi uwsgi-plugin-python3 \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome \
|
||||
texlive-xetex gettext libjs-bootstrap4 \
|
||||
nginx python3-venv git acl
|
||||
```
|
||||
|
||||
|
@@ -18,7 +18,6 @@
|
||||
- ipython3
|
||||
|
||||
# Front-end dependencies
|
||||
- fonts-font-awesome
|
||||
- libjs-bootstrap4
|
||||
|
||||
# Python dependencies
|
||||
|
@@ -6,7 +6,7 @@
|
||||
"name": "Pot",
|
||||
"manage_entries": true,
|
||||
"can_invite": true,
|
||||
"guest_entry_fee": 500
|
||||
"guest_entry_fee": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
@@ -28,5 +28,25 @@
|
||||
"can_invite": false,
|
||||
"guest_entry_fee": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "activity.activitytype",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "Soir\u00e9e avec entrées",
|
||||
"manage_entries": true,
|
||||
"can_invite": false,
|
||||
"guest_entry_fee": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "activity.activitytype",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"name": "Soir\u00e9e avec invitations",
|
||||
"manage_entries": true,
|
||||
"can_invite": true,
|
||||
"guest_entry_fee": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@@ -34,7 +34,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endif %}
|
||||
<div class="card-footer">
|
||||
<a class="btn btn-sm btn-success" href="{% url 'activity:activity_create' %}" data-turbolinks="false">
|
||||
<i class="fa fa-calendar-plus-o" aria-hidden="true"></i>
|
||||
<svg class="bi bi-calendar-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M4 .5a.5.5 0 0 0-1 0V1H2a2 2 0 0 0-2 2v1h16V3a2 2 0 0 0-2-2h-1V.5a.5.5 0 0 0-1 0V1H4V.5zM16 14V5H0v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2zM8.5 8.5V10H10a.5.5 0 0 1 0 1H8.5v1.5a.5.5 0 0 1-1 0V11H6a.5.5 0 0 1 0-1h1.5V8.5a.5.5 0 0 1 1 0z"/>
|
||||
</svg>
|
||||
{% trans 'New activity' %}
|
||||
</a>
|
||||
</div>
|
||||
|
5
apps/api/pagination.py
Normal file
5
apps/api/pagination.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
|
||||
|
||||
class CustomPagination(PageNumberPagination):
|
||||
page_size_query_param = 'page_size'
|
@@ -26,10 +26,6 @@ if "note" in settings.INSTALLED_APPS:
|
||||
from note.api.urls import register_note_urls
|
||||
register_note_urls(router, 'note')
|
||||
|
||||
if "sheets" in settings.INSTALLED_APPS:
|
||||
from sheets.api.urls import register_sheets_urls
|
||||
register_sheets_urls(router, 'sheets')
|
||||
|
||||
if "treasury" in settings.INSTALLED_APPS:
|
||||
from treasury.api.urls import register_treasury_urls
|
||||
register_treasury_urls(router, 'treasury')
|
||||
|
@@ -47,6 +47,13 @@ class ProfileForm(forms.ModelForm):
|
||||
|
||||
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date"))
|
||||
|
||||
VSS_charter_read = forms.BooleanField(
|
||||
required=True,
|
||||
label=_("Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) charter read and approved"),
|
||||
help_text=_("Tick after having read and accepted the anti-VSS charter \
|
||||
<a href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> available here in pdf</a>")
|
||||
)
|
||||
|
||||
def clean_promotion(self):
|
||||
promotion = self.cleaned_data["promotion"]
|
||||
if promotion > timezone.now().year:
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 2.2.27 on 2022-08-18 11:01
|
||||
# Generated by Django 2.2.26 on 2022-09-04 21:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
18
apps/member/migrations/0010_new_default_year.py
Normal file
18
apps/member/migrations/0010_new_default_year.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2023-08-23 21:29
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0009_auto_20220904_2325'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='profile',
|
||||
name='promotion',
|
||||
field=models.PositiveSmallIntegerField(default=2023, help_text='Year of entry to the school (None if not ENS student)', null=True, verbose_name='promotion'),
|
||||
),
|
||||
]
|
18
apps/member/migrations/0011_profile_vss_charter_read.py
Normal file
18
apps/member/migrations/0011_profile_vss_charter_read.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2023-08-31 09:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('member', '0010_new_default_year'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='VSS_charter_read',
|
||||
field=models.BooleanField(default=False, verbose_name='VSS charter read'),
|
||||
),
|
||||
]
|
@@ -134,6 +134,11 @@ class Profile(models.Model):
|
||||
default=False,
|
||||
)
|
||||
|
||||
VSS_charter_read = models.BooleanField(
|
||||
verbose_name=_("VSS charter read"),
|
||||
default=False
|
||||
)
|
||||
|
||||
@property
|
||||
def ens_year(self):
|
||||
"""
|
||||
@@ -263,7 +268,7 @@ class Club(models.Model):
|
||||
|
||||
today = datetime.date.today()
|
||||
|
||||
if (today - self.membership_start).days >= 365:
|
||||
while (today - self.membership_start).days >= 365:
|
||||
if self.membership_start:
|
||||
self.membership_start = datetime.date(self.membership_start.year + 1,
|
||||
self.membership_start.month, self.membership_start.day)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* On form submit, create a new friendship
|
||||
*/
|
||||
function create_trust (e) {
|
||||
function form_create_trust (e) {
|
||||
// Do not submit HTML form
|
||||
e.preventDefault()
|
||||
|
||||
@@ -14,25 +14,35 @@ function create_trust (e) {
|
||||
addMsg(gettext("You can't add yourself as a friend"), "danger")
|
||||
return
|
||||
}
|
||||
$.post('/api/note/trust/', {
|
||||
csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'),
|
||||
trusting: formData.get('trusting'),
|
||||
trusted: trusted_alias.note
|
||||
}).done(function () {
|
||||
// Reload table
|
||||
$('#trust_table').load(location.pathname + ' #trust_table')
|
||||
addMsg(gettext('Friendship successfully added'), 'success')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
create_trust(formData.get('trusting'), trusted_alias.note)
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On click of "delete", delete the alias
|
||||
* @param button_id:Integer Alias id to remove
|
||||
* Create a trust between users
|
||||
* @param trusting:Integer trusting note id
|
||||
* @param trusted:Integer trusted note id
|
||||
*/
|
||||
function create_trust(trusting, trusted) {
|
||||
$.post('/api/note/trust/', {
|
||||
trusting: trusting,
|
||||
trusted: trusted,
|
||||
csrfmiddlewaretoken: CSRF_TOKEN
|
||||
}).done(function () {
|
||||
// Reload tables
|
||||
$('#trust_table').load(location.pathname + ' #trust_table')
|
||||
$('#trusted_table').load(location.pathname + ' #trusted_table')
|
||||
addMsg(gettext('Friendship successfully added'), 'success')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On click of "delete", delete the trust
|
||||
* @param button_id:Integer Trust id to remove
|
||||
*/
|
||||
function delete_button (button_id) {
|
||||
$.ajax({
|
||||
@@ -42,6 +52,7 @@ function delete_button (button_id) {
|
||||
}).done(function () {
|
||||
addMsg(gettext('Friendship successfully deleted'), 'success')
|
||||
$('#trust_table').load(location.pathname + ' #trust_table')
|
||||
$('#trusted_table').load(location.pathname + ' #trusted_table')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
@@ -49,5 +60,5 @@ function delete_button (button_id) {
|
||||
|
||||
$(document).ready(function () {
|
||||
// Attach event
|
||||
document.getElementById('form_trust').addEventListener('submit', create_trust)
|
||||
document.getElementById('form_trust').addEventListener('submit', form_create_trust)
|
||||
})
|
||||
|
@@ -45,7 +45,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-footer">
|
||||
{% if user_object %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'member:user_update_profile' user_object.pk %}">
|
||||
<i class="fa fa-edit"></i> {% trans 'Update Profile' %}
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||
</svg>
|
||||
{% trans 'Update Profile' %}
|
||||
</a>
|
||||
{% url 'member:user_detail' user_object.pk as user_profile_url %}
|
||||
{% if request.path_info != user_profile_url %}
|
||||
@@ -59,7 +62,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% if ".change_"|has_perm:club %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'member:club_update' pk=club.pk %}"
|
||||
data-turbolinks="false">
|
||||
<i class="fa fa-edit"></i> {% trans 'Update Profile' %}
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||
</svg>
|
||||
{% trans 'Update Profile' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% url 'member:club_detail' club.pk as club_detail_url %}
|
||||
|
@@ -10,7 +10,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-users"></i> {% trans "Club managers" %}
|
||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
</svg>
|
||||
{% trans "Club managers" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table managers %}
|
||||
@@ -23,7 +26,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="stretched-link font-weight-bold" href="{% url 'member:club_members' pk=club.pk %}">
|
||||
<i class="fa fa-users"></i> {% trans "Club members" %}
|
||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
<path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/>
|
||||
<path d="M4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
|
||||
</svg>
|
||||
{% trans "Club members" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table member_list %}
|
||||
@@ -37,7 +45,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-header position-relative" id="historyListHeading">
|
||||
<a class="stretched-link font-weight-bold" {% if "note.view_note"|has_perm:club.note %}
|
||||
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M4 9.42h1.063C5.4 12.323 7.317 14 10.34 14c.622 0 1.167-.068 1.659-.185v-1.3c-.484.119-1.045.17-1.659.17-2.1 0-3.455-1.198-3.775-3.264h4.017v-.928H6.497v-.936c0-.11 0-.219.008-.329h4.078v-.927H6.618c.388-1.898 1.719-2.985 3.723-2.985.614 0 1.175.05 1.659.177V2.194A6.617 6.617 0 0 0 10.341 2c-2.928 0-4.82 1.569-5.244 4.3H4v.928h1.01v1.265H4v.928z"/>
|
||||
</svg>
|
||||
{% trans "Transaction history" %}
|
||||
</a>
|
||||
</div>
|
||||
<div id="history_list">
|
||||
|
@@ -47,7 +47,9 @@
|
||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:club_alias' club.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||
</svg>
|
||||
{% trans 'Manage aliases' %} ({{ club.note.alias.all|length }})
|
||||
</a>
|
||||
</dd>
|
||||
|
@@ -11,7 +11,9 @@
|
||||
<dt class="col-xl-6">{% trans 'password'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'password_change' %}">
|
||||
<i class="fa fa-lock"></i>
|
||||
<svg class="bi bi-lock" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M8 1a2 2 0 0 1 2 2v4H6V3a2 2 0 0 1 2-2zm3 6V3a3 3 0 0 0-6 0v4a2 2 0 0 0-2 2v5a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2z"/>
|
||||
</svg>
|
||||
{% trans 'Change password' %}
|
||||
</a>
|
||||
</dd>
|
||||
@@ -20,7 +22,9 @@
|
||||
<dt class="col-xl-6">{% trans 'aliases'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">
|
||||
<a class="badge badge-secondary" href="{% url 'member:user_alias' user_object.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||
</svg>
|
||||
{% trans 'Manage aliases' %} ({{ user_object.note.alias.all|length }})
|
||||
</a>
|
||||
</dd>
|
||||
@@ -60,7 +64,10 @@
|
||||
{% if user_object.pk == user.pk %}
|
||||
<div class="text-center">
|
||||
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
|
||||
<i class="fa fa-cogs"></i>{% trans 'API token' %}
|
||||
<svg class="bi bi-cogs" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
||||
</svg>
|
||||
{% trans 'API token' %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@@ -18,7 +18,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-users"></i> {% trans "View my memberships" %}
|
||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
</svg>
|
||||
{% trans "View my memberships" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table club_list %}
|
||||
@@ -29,7 +32,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||
{% if "note.view_note"|has_perm:user_object.note %}
|
||||
href="{% url 'note:transactions' pk=user_object.note.pk %}" {% endif %}>
|
||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M4 9.42h1.063C5.4 12.323 7.317 14 10.34 14c.622 0 1.167-.068 1.659-.185v-1.3c-.484.119-1.045.17-1.659.17-2.1 0-3.455-1.198-3.775-3.264h4.017v-.928H6.497v-.936c0-.11 0-.219.008-.329h4.078v-.927H6.618c.388-1.898 1.719-2.985 3.723-2.985.614 0 1.175.05 1.659.177V2.194A6.617 6.617 0 0 0 10.341 2c-2.928 0-4.82 1.569-5.244 4.3H4v.928h1.01v1.265H4v.928z"/>
|
||||
</svg>
|
||||
{% trans "Transaction history" %}
|
||||
</a>
|
||||
</div>
|
||||
<div id="history_list">
|
||||
|
@@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% block profile_content %}
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{% trans "Note friendships" %}
|
||||
{% trans "Add friends" %}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
{% if can_create %}
|
||||
@@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% render_table trusting %}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning card">
|
||||
<div class="alert alert-warning card mb-3">
|
||||
{% blocktrans trimmed %}
|
||||
Adding someone as a friend enables them to initiate transactions coming
|
||||
from your account (while keeping your balance positive). This is
|
||||
@@ -33,6 +33,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
friends without needing additional rights among them.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{% trans "People having you as a friend" %}
|
||||
</h3>
|
||||
{% render_table trusted_by %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
|
@@ -7,7 +7,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% block content %}
|
||||
{% if can_manage_registrations %}
|
||||
<a class="btn btn-block btn-secondary mb-3" href="{% url 'registration:future_user_list' %}">
|
||||
<i class="fa fa-user-plus"></i> {% trans "Registrations" %}
|
||||
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
|
||||
</svg>
|
||||
{% trans "Registrations" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
|
@@ -183,7 +183,7 @@ class TestMemberships(TestCase):
|
||||
club = Club.objects.get(name="Kfet")
|
||||
else:
|
||||
club = Club.objects.create(
|
||||
name="Second club " + ("with BDE" if bde_parent else "without BDE"),
|
||||
name="Second club without BDE",
|
||||
parent_club=None,
|
||||
email="newclub@example.com",
|
||||
require_memberships=True,
|
||||
@@ -335,6 +335,7 @@ class TestMemberships(TestCase):
|
||||
ml_sports_registration=True,
|
||||
ml_art_registration=True,
|
||||
report_frequency=7,
|
||||
VSS_charter_read=True
|
||||
))
|
||||
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
|
||||
self.assertTrue(User.objects.filter(username="toto changed").exists())
|
||||
|
@@ -8,7 +8,6 @@ from django.contrib.auth import logout
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import transaction
|
||||
from django.db.models import Q, F
|
||||
from django.shortcuts import redirect
|
||||
@@ -21,7 +20,7 @@ from django_tables2.views import SingleTableView
|
||||
from rest_framework.authtoken.models import Token
|
||||
from note.models import Alias, NoteClub, NoteUser, Trust
|
||||
from note.models.transactions import Transaction, SpecialTransaction
|
||||
from note.tables import HistoryTable, AliasTable, TrustTable
|
||||
from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable
|
||||
from note_kfet.middlewares import _set_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.models import Role
|
||||
@@ -258,17 +257,18 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
note = context['object'].note
|
||||
context["trusting"] = TrustTable(
|
||||
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
|
||||
context["trusted_by"] = TrustedTable(
|
||||
note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
|
||||
trusting=context["object"].note,
|
||||
trusted=context["object"].note
|
||||
))
|
||||
context["widget"] = {
|
||||
"name": "trusted",
|
||||
"resetable": True,
|
||||
"attrs": {
|
||||
"model_pk": ContentType.objects.get_for_model(Alias).pk,
|
||||
"class": "autocomplete form-control",
|
||||
"id": "trusted",
|
||||
"resetable": True,
|
||||
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
|
||||
"name_field": "name",
|
||||
"placeholder": ""
|
||||
@@ -753,6 +753,10 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
club = old_membership.club
|
||||
user = old_membership.user
|
||||
|
||||
# Update club membership date
|
||||
if PermissionBackend.check_perm(self.request, "member.change_club_membership_start", club):
|
||||
club.update_membership_dates()
|
||||
|
||||
form.instance.club = club
|
||||
|
||||
# Get form data
|
||||
|
@@ -7,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
|
||||
PolymorphicChildModelFilter, PolymorphicParentModelAdmin
|
||||
from note_kfet.admin import admin_site
|
||||
|
||||
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
||||
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
|
||||
from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \
|
||||
RecurrentTransaction, MembershipTransaction, SpecialTransaction
|
||||
from .templatetags.pretty_money import pretty_money
|
||||
@@ -21,6 +21,16 @@ class AliasInlines(admin.TabularInline):
|
||||
model = Alias
|
||||
|
||||
|
||||
class TrustInlines(admin.TabularInline):
|
||||
"""
|
||||
Define trusts when editing the trusting note
|
||||
"""
|
||||
model = Trust
|
||||
fk_name = "trusting"
|
||||
extra = 0
|
||||
readonly_fields = ("trusted",)
|
||||
|
||||
|
||||
@admin.register(Note, site=admin_site)
|
||||
class NoteAdmin(PolymorphicParentModelAdmin):
|
||||
"""
|
||||
@@ -92,7 +102,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
|
||||
"""
|
||||
Child for an user note, see NoteAdmin
|
||||
"""
|
||||
inlines = (AliasInlines,)
|
||||
inlines = (AliasInlines, TrustInlines)
|
||||
|
||||
# We can't change user after creation or the balance
|
||||
readonly_fields = ('user', 'balance')
|
||||
|
@@ -11,6 +11,7 @@ from member.models import Membership
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
from rest_framework.utils import model_meta
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust
|
||||
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
|
||||
@@ -86,11 +87,9 @@ class TrustSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Trust
|
||||
fields = '__all__'
|
||||
|
||||
def validate(self, attrs):
|
||||
instance = Trust(**attrs)
|
||||
instance.clean()
|
||||
return attrs
|
||||
validators = [UniqueTogetherValidator(
|
||||
queryset=Trust.objects.all(), fields=('trusting', 'trusted'),
|
||||
message=_("This friendship already exists"))]
|
||||
|
||||
|
||||
class AliasSerializer(serializers.ModelSerializer):
|
||||
|
@@ -325,8 +325,8 @@ class SpecialTransaction(Transaction):
|
||||
def clean(self):
|
||||
# SpecialTransaction are only possible with NoteSpecial object
|
||||
if self.is_credit() == self.is_debit():
|
||||
raise(ValidationError(_("A special transaction is only possible between a"
|
||||
" Note associated to a payment method and a User or a Club")))
|
||||
raise ValidationError(_("A special transaction is only possible between a"
|
||||
" Note associated to a payment method and a User or a Club"))
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
|
@@ -221,7 +221,7 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
||||
.done(function () {
|
||||
if (!isNaN(source.balance)) {
|
||||
const newBalance = source.balance - quantity * amount
|
||||
if (newBalance <= -5000) {
|
||||
if (newBalance <= -2000) {
|
||||
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' +
|
||||
'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000)
|
||||
} else if (newBalance < 0) {
|
||||
|
@@ -314,7 +314,7 @@ $('#btn_transfer').click(function () {
|
||||
|
||||
if (!isNaN(source.note.balance)) {
|
||||
const newBalance = source.note.balance - source.quantity * dest.quantity * amount
|
||||
if (newBalance <= -5000) {
|
||||
if (newBalance <= -2000) {
|
||||
addMsg(interpolate(gettext('Warning, the transaction of %s from the note %s to the note %s succeed, but the emitter note %s is very negative.'),
|
||||
[pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000)
|
||||
reset()
|
||||
|
@@ -159,11 +159,11 @@ class TrustTable(tables.Table):
|
||||
template_name = 'django_tables2/bootstrap4.html'
|
||||
|
||||
show_header = False
|
||||
trusted = tables.Column(attrs={'td': {'class': 'text_center'}})
|
||||
trusted = tables.Column(attrs={'td': {'class': 'text-center'}})
|
||||
|
||||
delete_col = tables.TemplateColumn(
|
||||
template_code=DELETE_TEMPLATE,
|
||||
extra_context={"delete_trans": _('delete')},
|
||||
extra_context={"delete_trans": _('Delete')},
|
||||
attrs={
|
||||
'td': {
|
||||
'class': lambda record: 'col-sm-1'
|
||||
@@ -173,6 +173,46 @@ class TrustTable(tables.Table):
|
||||
verbose_name=_("Delete"),)
|
||||
|
||||
|
||||
class TrustedTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class': 'table table condensed table-striped',
|
||||
'id': 'trusted_table'
|
||||
}
|
||||
Model = Trust
|
||||
fields = ("trusting",)
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
|
||||
show_header = False
|
||||
trusting = tables.Column(attrs={
|
||||
'td': {'class': 'text-center', 'width': '100%'}})
|
||||
|
||||
trust_back = tables.Column(
|
||||
verbose_name=_("Trust back"),
|
||||
accessor="pk",
|
||||
attrs={
|
||||
'td': {
|
||||
'class': '',
|
||||
'id': lambda record: "trust_back_" + str(record.pk),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def render_trust_back(self, record):
|
||||
user_note = record.trusted
|
||||
trusting_note = record.trusting
|
||||
if Trust.objects.filter(trusted=trusting_note, trusting=user_note):
|
||||
return ""
|
||||
val = '<button id="'
|
||||
val += str(record.pk)
|
||||
val += '" class="btn btn-success btn-sm text-nowrap" \
|
||||
onclick="create_trust(' + str(record.trusted.pk) + ',' + \
|
||||
str(record.trusting.pk) + ')">'
|
||||
val += str(_("Add back"))
|
||||
val += '</button>'
|
||||
return mark_safe(val)
|
||||
|
||||
|
||||
class AliasTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {
|
||||
|
@@ -129,7 +129,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{# Mode switch #}
|
||||
<div class="card-footer border-primary">
|
||||
<a class="btn btn-sm btn-secondary float-left" href="{% url 'note:template_list' %}">
|
||||
<i class="fa fa-edit"></i> {% trans "Edit" %}
|
||||
<svg class="bi bi-edit" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M12.854.146a.5.5 0 0 0-.707 0L10.5 1.793 14.207 5.5l1.647-1.646a.5.5 0 0 0 0-.708l-3-3zm.646 6.061L9.793 2.5 3.293 9H3.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.5h.5a.5.5 0 0 1 .5.5v.207l6.5-6.5zm-7.468 7.468A.5.5 0 0 1 6 13.5V13h-.5a.5.5 0 0 1-.5-.5V12h-.5a.5.5 0 0 1-.5-.5V11h-.5a.5.5 0 0 1-.5-.5V10h-.5a.499.499 0 0 1-.175-.032l-.179.178a.5.5 0 0 0-.11.168l-2 5a.5.5 0 0 0 .65.65l5-2a.5.5 0 0 0 .168-.11l.178-.178z"/>
|
||||
</svg>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
<div class="btn-group btn-group-toggle float-right" data-toggle="buttons">
|
||||
<label for="single_conso" class="btn btn-sm btn-outline-primary active">
|
||||
|
@@ -1,17 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
{% crispy form %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -1,88 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h1>{{ food.name }}</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="card col-xl-6">
|
||||
<div class="card-header text-center">
|
||||
<h2>{% trans "queued"|capfirst %}{% if queue %} ({{ queue|length }}){% endif %}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul>
|
||||
{% for ordered_food in queue %}
|
||||
<li>
|
||||
{{ ordered_food.order.note }}
|
||||
{% if ordered_food.priority %}
|
||||
<span class="badge badge-secondary">{{ ordered_food.priority }}</span>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% empty %}
|
||||
<div class="alert alert-warning">
|
||||
{% trans "There is no queued order." %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card col-xl-6">
|
||||
<div class="card-header text-center">
|
||||
<h2>{% trans "ready"|capfirst %}</h2>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ul>
|
||||
{% for ordered_food in ready %}
|
||||
<li>{{ ordered_food.order.note }}</li>
|
||||
{% empty %}
|
||||
<div class="alert alert-warning">
|
||||
{% trans "There is no ready order." %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<h3>{% trans "Other waiting lists:" %}</h3>
|
||||
<ul>
|
||||
{% for other_food in food.sheet.food_set.all %}
|
||||
{% if other_food != food %}
|
||||
<li>
|
||||
<a href="{% url 'sheets:waiting_list' pk=other_food.pk %}">{{ other_food }}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<a href="{% url 'sheets:queued_list' pk=food.pk %}" class="btn btn-primary">
|
||||
{% trans "Queued orders" %}
|
||||
</a>
|
||||
<a href="{% url 'sheets:ready_list' pk=food.pk %}" class="btn btn-primary">
|
||||
{% trans "Ready orders" %}
|
||||
</a>
|
||||
<a href="{% url 'sheets:sheet_detail' pk=food.sheet_id %}" class="btn btn-secondary">
|
||||
{% trans "Back to note sheet detail" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
function reload() {
|
||||
reloadWithTurbolinks()
|
||||
timeout = setTimeout(reload, 15000)
|
||||
}
|
||||
|
||||
if (timeout === undefined)
|
||||
var timeout = setTimeout(reload, 15000)
|
||||
</script>
|
||||
{% endblock %}
|
@@ -1,152 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h1>{{ title }}</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
{% for of in orders %}
|
||||
<div class="card mb-4">
|
||||
<div class="card-header text-center">
|
||||
<h3>{{ of.order.note }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-xl-3">{% trans 'date'|capfirst %}</dt>
|
||||
<dd class="col-xl-9">{{ of.order.date }}</dd>
|
||||
|
||||
{% if of.number > 1 %}
|
||||
<dt class="col-xl-3">{% trans 'order number'|capfirst %}</dt>
|
||||
<dd class="col-xl-9">{{ of.number }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if of.priority %}
|
||||
<dt class="col-xl-3">{% trans 'priority request'|capfirst %}</dt>
|
||||
<dd class="col-xl-9">{{ of.priority }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if of.remark %}
|
||||
<dt class="col-xl-3">{% trans 'remark'|capfirst %}</dt>
|
||||
<dd class="col-xl-9">{{ of.remark }}</dd>
|
||||
{% endif %}
|
||||
|
||||
{% if of.options.count %}
|
||||
<dt class="col-xl-3">{% trans 'options'|capfirst %}</dt>
|
||||
<dd class="col-xl-9">{{ of.options.all|join:', ' }}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
{% if list_type != 'READY' %}
|
||||
<a href="#" class="btn btn-success" onclick="setOrderStatus({{ of.pk }}, 'READY')">
|
||||
{% trans "Mark as ready" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if list_type != 'SERVED' %}
|
||||
<a href="#" class="btn btn-primary" onclick="setOrderStatus({{ of.pk }}, 'SERVED')">
|
||||
{% trans "Mark as served" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if list_type != 'QUEUED' %}
|
||||
<a href="#" class="btn btn-warning" onclick="setOrderStatus({{ of.pk }}, 'QUEUED')">
|
||||
{% trans "Re-queue" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if list_type != 'CANCELED' %}
|
||||
<a href="#" class="btn btn-danger" onclick="setOrderStatus({{ of.pk }}, 'CANCELED')">
|
||||
{% trans "Cancel" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="alert alert-warning">
|
||||
{% trans "There is no queued order." %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-5">
|
||||
<div class="card-body">
|
||||
<h3>{% trans "Other waiting lists:" %}</h3>
|
||||
<ul>
|
||||
{% for other_food in food.sheet.food_set.all %}
|
||||
{% if other_food != food %}
|
||||
<li>
|
||||
{% if list_type == 'QUEUED' %}
|
||||
<a href="{% url 'sheets:queued_list' pk=other_food.pk %}">{{ other_food }}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'sheets:ready_list' pk=other_food.pk %}">{{ other_food }}</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
{% if list_type != 'QUEUED' %}
|
||||
<a href="{% url 'sheets:queued_list' pk=food.pk %}" class="btn btn-primary">
|
||||
{% trans "Queued orders" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if list_type != 'READY' %}
|
||||
<a href="{% url 'sheets:ready_list' pk=food.pk %}" class="btn btn-success">
|
||||
{% trans "Ready orders" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if list_type != 'SERVED' %}
|
||||
<a href="{% url 'sheets:served_list' pk=food.pk %}" class="btn btn-secondary">
|
||||
{% trans "Served orders" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if list_type != 'CANCELED' %}
|
||||
<a href="{% url 'sheets:canceled_list' pk=food.pk %}" class="btn btn-danger">
|
||||
{% trans "Canceled orders" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'sheets:waiting_list' pk=food.pk %}" class="btn btn-primary">
|
||||
{% trans "Waiting list" %}
|
||||
</a>
|
||||
<a href="{% url 'sheets:sheet_detail' pk=food.sheet_id %}" class="btn btn-secondary">
|
||||
{% trans "Back to note sheet detail" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
function reload() {
|
||||
reloadWithTurbolinks()
|
||||
timeout = setTimeout(reload, 15000)
|
||||
}
|
||||
|
||||
if (timeout === undefined)
|
||||
var timeout = setTimeout(reload, 15000)
|
||||
|
||||
function setOrderStatus(ordered_food_id, status) {
|
||||
fetch('/api/sheets/orderedfood/' + ordered_food_id + '/', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
status: status,
|
||||
served_date: status === 'QUEUED' ? null : new Date().toISOString(),
|
||||
}),
|
||||
headers: {
|
||||
'Content-Type': "application/json; charset=UTF-8",
|
||||
'X-CSRFTOKEN': "{{ csrf_token }}"
|
||||
}
|
||||
}).then(response => response.json()).then(response => {
|
||||
if ('detail' in response)
|
||||
addMsg("{% trans "An error occurred" %}" + " : " + response['detail'], "danger")
|
||||
else {
|
||||
clearTimeout(timeout)
|
||||
reload()
|
||||
}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
@@ -198,6 +198,41 @@ class PermissionBackend(ModelBackend):
|
||||
def has_module_perms(self, user_obj, app_label):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@memoize
|
||||
def has_model_perm(request, model, type):
|
||||
"""
|
||||
Check is the given user has the permission over a given model for a given action.
|
||||
The result is then memoized.
|
||||
:param request: The current request
|
||||
:param model: The model that the permissions shoud apply
|
||||
:param type: The type of the permissions: view, change, add or delete
|
||||
For view action, it is consider possible if user can view or change the model
|
||||
"""
|
||||
# Requested by a shell
|
||||
if request is None:
|
||||
return False
|
||||
|
||||
user_obj = request.user
|
||||
sess = request.session
|
||||
|
||||
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
|
||||
# OAuth2 Authentication
|
||||
user_obj = request.auth.user
|
||||
|
||||
if user_obj is None or user_obj.is_anonymous:
|
||||
return False
|
||||
|
||||
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
|
||||
ct = ContentType.objects.get_for_model(model)
|
||||
if any(PermissionBackend.permissions(request, ct, type)):
|
||||
return True
|
||||
if type == "view" and any(PermissionBackend.permissions(request, ct, "change")):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_all_permissions(self, user_obj, obj=None):
|
||||
ct = ContentType.objects.get_for_model(obj)
|
||||
return list(self.permissions(get_current_request(), ct, "view"))
|
||||
|
File diff suppressed because it is too large
Load Diff
19
apps/permission/migrations/0002_club_not_required.py
Normal file
19
apps/permission/migrations/0002_club_not_required.py
Normal file
@@ -0,0 +1,19 @@
|
||||
# Generated by Django 2.2.28 on 2023-07-24 10:15
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('permission', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='role',
|
||||
name='for_club',
|
||||
field=models.ForeignKey(blank=True, default=None, null=True, on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='for club'),
|
||||
),
|
||||
]
|
@@ -339,6 +339,7 @@ class Role(models.Model):
|
||||
"member.Club",
|
||||
verbose_name=_("for club"),
|
||||
on_delete=models.PROTECT,
|
||||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
)
|
||||
|
@@ -5,6 +5,7 @@ from django import forms
|
||||
from django.contrib.auth.forms import UserCreationForm
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from member.models import Club
|
||||
from note.models import NoteSpecial, Alias
|
||||
from note_kfet.inputs import AmountInput
|
||||
|
||||
@@ -44,14 +45,14 @@ class SignUpForm(UserCreationForm):
|
||||
fields = ('first_name', 'last_name', 'username', 'email', )
|
||||
|
||||
|
||||
class DeclareSogeAccountOpenedForm(forms.Form):
|
||||
soge_account = forms.BooleanField(
|
||||
label=_("I declare that I opened or I will open soon a bank account in the Société générale with the BDE "
|
||||
"partnership."),
|
||||
help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your "
|
||||
"account, you will have to pay the BDE membership."),
|
||||
required=False,
|
||||
)
|
||||
# class DeclareSogeAccountOpenedForm(forms.Form):
|
||||
# soge_account = forms.BooleanField(
|
||||
# label=_("I declare that I opened or I will open soon a bank account in the Société générale with the BDE "
|
||||
# "partnership."),
|
||||
# help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your "
|
||||
# "account, you will have to pay the BDE membership."),
|
||||
# required=False,
|
||||
# )
|
||||
|
||||
|
||||
class WEISignupForm(forms.Form):
|
||||
@@ -67,11 +68,11 @@ class ValidationForm(forms.Form):
|
||||
"""
|
||||
Validate the inscription of the new users and pay memberships.
|
||||
"""
|
||||
soge = forms.BooleanField(
|
||||
label=_("Inscription paid by Société Générale"),
|
||||
required=False,
|
||||
help_text=_("Check this case if the Société Générale paid the inscription."),
|
||||
)
|
||||
# soge = forms.BooleanField(
|
||||
# label=_("Inscription paid by Société Générale"),
|
||||
# required=False,
|
||||
# help_text=_("Check this case if the Société Générale paid the inscription."),
|
||||
# )
|
||||
|
||||
credit_type = forms.ModelChoiceField(
|
||||
queryset=NoteSpecial.objects,
|
||||
@@ -114,3 +115,12 @@ class ValidationForm(forms.Form):
|
||||
required=False,
|
||||
initial=True,
|
||||
)
|
||||
|
||||
# If the bda exists
|
||||
if Club.objects.filter(name__iexact="bda").exists():
|
||||
# The user can join the bda club at the inscription
|
||||
join_bda = forms.BooleanField(
|
||||
label=_("Join BDA Club"),
|
||||
required=False,
|
||||
initial=True,
|
||||
)
|
||||
|
@@ -57,11 +57,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<h4> {% trans "Validate account" %}</h4>
|
||||
</div>
|
||||
|
||||
{% comment "Soge not for membership (only WEI)" %}
|
||||
{% if declare_soge_account %}
|
||||
<div class="alert alert-info">
|
||||
{% trans "The user declared that he/she opened a bank account in the Société générale." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endcomment %}
|
||||
|
||||
<div class="card-body" id="profile_infos">
|
||||
{% csrf_token %}
|
||||
@@ -76,6 +78,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% comment "Soge not for membership (only WEI)" %}
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
soge_field = $("#id_soge");
|
||||
@@ -118,3 +121,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endif %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endcomment %}
|
||||
|
@@ -48,6 +48,7 @@ class TestSignup(TestCase):
|
||||
ml_events_registration="en",
|
||||
ml_sport_registration=True,
|
||||
ml_art_registration=True,
|
||||
VSS_charter_read=True
|
||||
))
|
||||
self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
|
||||
self.assertTrue(User.objects.filter(username="toto").exists())
|
||||
@@ -105,6 +106,7 @@ class TestSignup(TestCase):
|
||||
ml_events_registration="en",
|
||||
ml_sport_registration=True,
|
||||
ml_art_registration=True,
|
||||
VSS_charter_read=True
|
||||
))
|
||||
self.assertTrue(response.status_code, 200)
|
||||
|
||||
@@ -124,6 +126,7 @@ class TestSignup(TestCase):
|
||||
ml_events_registration="en",
|
||||
ml_sport_registration=True,
|
||||
ml_art_registration=True,
|
||||
VSS_charter_read=True
|
||||
))
|
||||
self.assertTrue(response.status_code, 200)
|
||||
|
||||
@@ -143,6 +146,27 @@ class TestSignup(TestCase):
|
||||
ml_events_registration="en",
|
||||
ml_sport_registration=True,
|
||||
ml_art_registration=True,
|
||||
VSS_charter_read=True
|
||||
))
|
||||
self.assertTrue(response.status_code, 200)
|
||||
|
||||
# The VSS charter is not read
|
||||
response = self.client.post(reverse("registration:signup"), dict(
|
||||
first_name="Toto",
|
||||
last_name="TOTO",
|
||||
username="Ihaveanotherusername",
|
||||
email="othertoto@example.com",
|
||||
password1="toto1234",
|
||||
password2="toto1234",
|
||||
phone_number="+33123456789",
|
||||
department="EXT",
|
||||
promotion=Club.objects.get(name="BDE").membership_start.year,
|
||||
address="Earth",
|
||||
paid=False,
|
||||
ml_events_registration="en",
|
||||
ml_sport_registration=True,
|
||||
ml_art_registration=True,
|
||||
VSS_charter_read=False
|
||||
))
|
||||
self.assertTrue(response.status_code, 200)
|
||||
|
||||
@@ -190,7 +214,7 @@ class TestValidateRegistration(TestCase):
|
||||
|
||||
# BDE Membership is mandatory
|
||||
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
soge=False,
|
||||
# soge=False,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
|
||||
credit_amount=4200,
|
||||
last_name="TOTO",
|
||||
@@ -204,7 +228,7 @@ class TestValidateRegistration(TestCase):
|
||||
|
||||
# Same
|
||||
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
soge=False,
|
||||
# soge=False,
|
||||
credit_type="",
|
||||
credit_amount=0,
|
||||
last_name="TOTO",
|
||||
@@ -218,7 +242,7 @@ class TestValidateRegistration(TestCase):
|
||||
|
||||
# The BDE membership is not free
|
||||
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
soge=False,
|
||||
# soge=False,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
|
||||
credit_amount=0,
|
||||
last_name="TOTO",
|
||||
@@ -232,7 +256,7 @@ class TestValidateRegistration(TestCase):
|
||||
|
||||
# Last and first names are required for a credit
|
||||
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
soge=False,
|
||||
# soge=False,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
|
||||
credit_amount=4000,
|
||||
last_name="",
|
||||
@@ -249,7 +273,7 @@ class TestValidateRegistration(TestCase):
|
||||
self.user.username = "admïntoto"
|
||||
self.user.save()
|
||||
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
soge=False,
|
||||
# soge=False,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
|
||||
credit_amount=500,
|
||||
last_name="TOTO",
|
||||
@@ -275,7 +299,7 @@ class TestValidateRegistration(TestCase):
|
||||
self.user.profile.save()
|
||||
|
||||
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
soge=False,
|
||||
# soge=False,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Chèque").id,
|
||||
credit_amount=500,
|
||||
last_name="TOTO",
|
||||
@@ -290,6 +314,7 @@ class TestValidateRegistration(TestCase):
|
||||
self.assertTrue(NoteUser.objects.filter(user=self.user).exists())
|
||||
self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists())
|
||||
self.assertFalse(Membership.objects.filter(club__name="Kfet", user=self.user).exists())
|
||||
self.assertFalse(Membership.objects.filter(club__name__iexact="BDA", user=self.user).exists())
|
||||
self.assertFalse(SogeCredit.objects.filter(user=self.user).exists())
|
||||
self.assertEqual(Transaction.objects.filter(
|
||||
Q(source=self.user.note) | Q(destination=self.user.note)).count(), 2)
|
||||
@@ -311,7 +336,7 @@ class TestValidateRegistration(TestCase):
|
||||
self.user.profile.save()
|
||||
|
||||
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
soge=False,
|
||||
# soge=False,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
|
||||
credit_amount=4000,
|
||||
last_name="TOTO",
|
||||
@@ -326,6 +351,7 @@ class TestValidateRegistration(TestCase):
|
||||
self.assertTrue(NoteUser.objects.filter(user=self.user).exists())
|
||||
self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists())
|
||||
self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists())
|
||||
self.assertFalse(Membership.objects.filter(club__name__iexact="BDA", user=self.user).exists())
|
||||
self.assertFalse(SogeCredit.objects.filter(user=self.user).exists())
|
||||
self.assertEqual(Transaction.objects.filter(
|
||||
Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3)
|
||||
@@ -333,42 +359,43 @@ class TestValidateRegistration(TestCase):
|
||||
response = self.client.get(self.user.profile.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_validate_kfet_registration_with_soge(self):
|
||||
"""
|
||||
The user joins the BDE and the Kfet, but the membership is paid by the Société générale.
|
||||
"""
|
||||
response = self.client.get(reverse("registration:future_user_detail", args=(self.user.pk,)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.get(self.user.profile.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
self.user.profile.email_confirmed = True
|
||||
self.user.profile.save()
|
||||
|
||||
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
soge=True,
|
||||
credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
|
||||
credit_amount=4000,
|
||||
last_name="TOTO",
|
||||
first_name="Toto",
|
||||
bank="Société générale",
|
||||
join_bde=True,
|
||||
join_kfet=True,
|
||||
))
|
||||
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
|
||||
self.user.profile.refresh_from_db()
|
||||
self.assertTrue(self.user.profile.registration_valid)
|
||||
self.assertTrue(NoteUser.objects.filter(user=self.user).exists())
|
||||
self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists())
|
||||
self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists())
|
||||
self.assertTrue(SogeCredit.objects.filter(user=self.user).exists())
|
||||
self.assertEqual(Transaction.objects.filter(
|
||||
Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3)
|
||||
self.assertFalse(Transaction.objects.filter(valid=True).exists())
|
||||
|
||||
response = self.client.get(self.user.profile.get_absolute_url())
|
||||
self.assertEqual(response.status_code, 200)
|
||||
# def test_validate_kfet_registration_with_soge(self):
|
||||
# """
|
||||
# The user joins the BDE and the Kfet, but the membership is paid by the Société générale.
|
||||
# """
|
||||
# response = self.client.get(reverse("registration:future_user_detail", args=(self.user.pk,)))
|
||||
# self.assertEqual(response.status_code, 200)
|
||||
#
|
||||
# response = self.client.get(self.user.profile.get_absolute_url())
|
||||
# self.assertEqual(response.status_code, 404)
|
||||
#
|
||||
# self.user.profile.email_confirmed = True
|
||||
# self.user.profile.save()
|
||||
#
|
||||
# response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
|
||||
# soge=True,
|
||||
# credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
|
||||
# credit_amount=4000,
|
||||
# last_name="TOTO",
|
||||
# first_name="Toto",
|
||||
# bank="Société générale",
|
||||
# join_bde=True,
|
||||
# join_kfet=True,
|
||||
# ))
|
||||
# self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
|
||||
# self.user.profile.refresh_from_db()
|
||||
# self.assertTrue(self.user.profile.registration_valid)
|
||||
# self.assertTrue(NoteUser.objects.filter(user=self.user).exists())
|
||||
# self.assertTrue(Membership.objects.filter(club__name="BDE", user=self.user).exists())
|
||||
# self.assertTrue(Membership.objects.filter(club__name="Kfet", user=self.user).exists())
|
||||
# self.assertFalse(Membership.objects.filter(club__name__iexact="BDA", user=self.user).exists())
|
||||
# self.assertTrue(SogeCredit.objects.filter(user=self.user).exists())
|
||||
# self.assertEqual(Transaction.objects.filter(
|
||||
# Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3)
|
||||
# self.assertFalse(Transaction.objects.filter(valid=True).exists())
|
||||
#
|
||||
# response = self.client.get(self.user.profile.get_absolute_url())
|
||||
# self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_invalidate_registration(self):
|
||||
"""
|
||||
|
@@ -24,7 +24,8 @@ from permission.models import Role
|
||||
from permission.views import ProtectQuerysetMixin
|
||||
from treasury.models import SogeCredit
|
||||
|
||||
from .forms import SignUpForm, ValidationForm, DeclareSogeAccountOpenedForm
|
||||
# from .forms import SignUpForm, ValidationForm, DeclareSogeAccountOpenedForm
|
||||
from .forms import SignUpForm, ValidationForm
|
||||
from .tables import FutureUserTable
|
||||
from .tokens import email_validation_token
|
||||
|
||||
@@ -42,7 +43,7 @@ class UserCreateView(CreateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["profile_form"] = self.second_form(self.request.POST if self.request.POST else None)
|
||||
context["soge_form"] = DeclareSogeAccountOpenedForm(self.request.POST if self.request.POST else None)
|
||||
# context["soge_form"] = DeclareSogeAccountOpenedForm(self.request.POST if self.request.POST else None)
|
||||
del context["profile_form"].fields["section"]
|
||||
del context["profile_form"].fields["report_frequency"]
|
||||
del context["profile_form"].fields["last_report"]
|
||||
@@ -75,12 +76,12 @@ class UserCreateView(CreateView):
|
||||
|
||||
user.profile.send_email_validation_link()
|
||||
|
||||
soge_form = DeclareSogeAccountOpenedForm(self.request.POST)
|
||||
if "soge_account" in soge_form.data and soge_form.data["soge_account"]:
|
||||
# If the user declares that a bank account got opened, prepare the soge credit to warn treasurers
|
||||
soge_credit = SogeCredit(user=user)
|
||||
soge_credit._force_save = True
|
||||
soge_credit.save()
|
||||
# soge_form = DeclareSogeAccountOpenedForm(self.request.POST)
|
||||
# if "soge_account" in soge_form.data and soge_form.data["soge_account"]:
|
||||
# # If the user declares that a bank account got opened, prepare the soge credit to warn treasurers
|
||||
# soge_credit = SogeCredit(user=user)
|
||||
# soge_credit._force_save = True
|
||||
# soge_credit.save()
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
@@ -237,9 +238,12 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
|
||||
kfet = Club.objects.get(name="Kfet")
|
||||
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
|
||||
if Club.objects.filter(name__iexact="BDA").exists():
|
||||
bda = Club.objects.get(name__iexact="BDA")
|
||||
fee += bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid
|
||||
ctx["total_fee"] = "{:.02f}".format(fee / 100, )
|
||||
|
||||
ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists()
|
||||
# ctx["declare_soge_account"] = SogeCredit.objects.filter(user=user).exists()
|
||||
|
||||
return ctx
|
||||
|
||||
@@ -262,8 +266,13 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
form.add_error(None, _("An alias with a similar name already exists."))
|
||||
return self.form_invalid(form)
|
||||
|
||||
# Check if BDA exist to propose membership at regisration
|
||||
bda_exists = False
|
||||
if Club.objects.filter(name__iexact="BDA").exists():
|
||||
bda_exists = True
|
||||
|
||||
# Get form data
|
||||
soge = form.cleaned_data["soge"]
|
||||
# soge = form.cleaned_data["soge"]
|
||||
credit_type = form.cleaned_data["credit_type"]
|
||||
credit_amount = form.cleaned_data["credit_amount"]
|
||||
last_name = form.cleaned_data["last_name"]
|
||||
@@ -271,11 +280,13 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
bank = form.cleaned_data["bank"]
|
||||
join_bde = form.cleaned_data["join_bde"]
|
||||
join_kfet = form.cleaned_data["join_kfet"]
|
||||
if bda_exists:
|
||||
join_bda = form.cleaned_data["join_bda"]
|
||||
|
||||
if soge:
|
||||
# If Société Générale pays the inscription, the user automatically joins the two clubs.
|
||||
join_bde = True
|
||||
join_kfet = True
|
||||
# if soge:
|
||||
# # If Société Générale pays the inscription, the user automatically joins the two clubs.
|
||||
# join_bde = True
|
||||
# join_kfet = True
|
||||
|
||||
if not join_bde:
|
||||
# This software belongs to the BDE.
|
||||
@@ -292,15 +303,21 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
|
||||
# Add extra fee for the full membership
|
||||
fee += kfet_fee if join_kfet else 0
|
||||
if bda_exists:
|
||||
bda = Club.objects.get(name__iexact="BDA")
|
||||
bda_fee = bda.membership_fee_paid if user.profile.paid else bda.membership_fee_unpaid
|
||||
# Add extra fee for the bda membership
|
||||
fee += bda_fee if join_bda else 0
|
||||
|
||||
# If the bank pays, then we don't credit now. Treasurers will validate the transaction
|
||||
# and credit the note later.
|
||||
credit_type = None if soge else credit_type
|
||||
# # If the bank pays, then we don't credit now. Treasurers will validate the transaction
|
||||
# # and credit the note later.
|
||||
# credit_type = None if soge else credit_type
|
||||
|
||||
# If the user does not select any payment method, then no credit will be performed.
|
||||
credit_amount = 0 if credit_type is None else credit_amount
|
||||
|
||||
if fee > credit_amount and not soge:
|
||||
# if fee > credit_amount and not soge:
|
||||
if fee > credit_amount:
|
||||
# Check if the user credits enough money
|
||||
form.add_error('credit_type',
|
||||
_("The entered amount is not enough for the memberships, should be at least {}")
|
||||
@@ -320,12 +337,12 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
user.profile.save()
|
||||
user.refresh_from_db()
|
||||
|
||||
if not soge and SogeCredit.objects.filter(user=user).exists():
|
||||
# If the user declared that a bank account was opened but in the validation form the SoGé case was
|
||||
# unchecked, delete the associated credit
|
||||
soge_credit = SogeCredit.objects.get(user=user)
|
||||
soge_credit._force_delete = True
|
||||
soge_credit.delete()
|
||||
# if not soge and SogeCredit.objects.filter(user=user).exists():
|
||||
# # If the user declared that a bank account was opened but in the validation form the SoGé case was
|
||||
# # unchecked, delete the associated credit
|
||||
# soge_credit = SogeCredit.objects.get(user=user)
|
||||
# soge_credit._force_delete = True
|
||||
# soge_credit.delete()
|
||||
|
||||
if credit_type is not None and credit_amount > 0:
|
||||
# Credit the note
|
||||
@@ -334,7 +351,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
destination=user.note,
|
||||
quantity=1,
|
||||
amount=credit_amount,
|
||||
reason="Crédit " + ("Société générale" if soge else credit_type.special_type) + " (Inscription)",
|
||||
reason="Crédit " + credit_type.special_type + " (Inscription)",
|
||||
# reason="Crédit " + ("Société générale" if soge else credit_type.special_type) + " (Inscription)",
|
||||
last_name=last_name,
|
||||
first_name=first_name,
|
||||
bank=bank,
|
||||
@@ -348,8 +366,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
user=user,
|
||||
fee=bde_fee,
|
||||
)
|
||||
if soge:
|
||||
membership._soge = True
|
||||
# if soge:
|
||||
# membership._soge = True
|
||||
membership.save()
|
||||
membership.refresh_from_db()
|
||||
membership.roles.add(Role.objects.get(name="Adhérent BDE"))
|
||||
@@ -362,17 +380,29 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
|
||||
user=user,
|
||||
fee=kfet_fee,
|
||||
)
|
||||
if soge:
|
||||
membership._soge = True
|
||||
# if soge:
|
||||
# membership._soge = True
|
||||
membership.save()
|
||||
membership.refresh_from_db()
|
||||
membership.roles.add(Role.objects.get(name="Adhérent Kfet"))
|
||||
membership.save()
|
||||
|
||||
if soge:
|
||||
soge_credit = SogeCredit.objects.get(user=user)
|
||||
# Update the credit transaction amount
|
||||
soge_credit.save()
|
||||
if bda_exists and join_bda:
|
||||
# Create membership for the user to the BDA starting today
|
||||
membership = Membership(
|
||||
club=bda,
|
||||
user=user,
|
||||
fee=bda_fee,
|
||||
)
|
||||
membership.save()
|
||||
membership.refresh_from_db()
|
||||
membership.roles.add(Role.objects.get(name="Membre de club"))
|
||||
membership.save()
|
||||
|
||||
# if soge:
|
||||
# soge_credit = SogeCredit.objects.get(user=user)
|
||||
# # Update the credit transaction amount
|
||||
# soge_credit.save()
|
||||
|
||||
return ret
|
||||
|
||||
|
Submodule apps/scripts updated: 86bc2d2698...f580f9b9e9
@@ -1,4 +0,0 @@
|
||||
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
default_app_config = 'sheets.apps.SheetsConfig'
|
@@ -1,46 +0,0 @@
|
||||
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.contrib import admin
|
||||
from note_kfet.admin import admin_site
|
||||
from sheets.models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction
|
||||
|
||||
|
||||
@admin.register(Sheet, site=admin_site)
|
||||
class SheetAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(Food, site=admin_site)
|
||||
class FoodAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(FoodOption, site=admin_site)
|
||||
class FoodOptionAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(Meal, site=admin_site)
|
||||
class MealAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(Order, site=admin_site)
|
||||
class OrderAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(OrderedMeal, site=admin_site)
|
||||
class OrderedMealAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(OrderedFood, site=admin_site)
|
||||
class OrderedFoodAdmin(admin.ModelAdmin):
|
||||
pass
|
||||
|
||||
|
||||
@admin.register(SheetOrderTransaction, site=admin_site)
|
||||
class SheetOrderTransactionAdmin(admin.ModelAdmin):
|
||||
pass
|
@@ -1,55 +0,0 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from rest_framework import serializers
|
||||
|
||||
|
||||
from ..models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction
|
||||
|
||||
|
||||
class SheetSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Sheet
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FoodSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Food
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class FoodOptionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = FoodOption
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class MealSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Meal
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class OrderSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Order
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class OrderedMealSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = OrderedMeal
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class OrderedFoodSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = OrderedFood
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class SheetOrderTransactionSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = SheetOrderTransaction
|
||||
fields = '__all__'
|
@@ -1,19 +0,0 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from sheets.api.views import SheetViewSet, FoodViewSet, FoodOptionViewSet, MealViewSet, OrderViewSet, \
|
||||
OrderedMealViewSet, OrderedFoodViewSet, SheetOrderTransactionViewSet
|
||||
|
||||
|
||||
def register_sheets_urls(router, path):
|
||||
"""
|
||||
Configure router for Sheets REST API.
|
||||
"""
|
||||
router.register(path + '/sheet', SheetViewSet)
|
||||
router.register(path + '/food', FoodViewSet)
|
||||
router.register(path + '/foodoption', FoodOptionViewSet)
|
||||
router.register(path + '/meal', MealViewSet)
|
||||
router.register(path + '/order', OrderViewSet)
|
||||
router.register(path + '/orderedmeal', OrderedMealViewSet)
|
||||
router.register(path + '/orderedfood', OrderedFoodViewSet)
|
||||
router.register(path + '/sheetordertransaction', SheetOrderTransactionViewSet)
|
@@ -1,78 +0,0 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter, OrderingFilter
|
||||
|
||||
from .serializers import SheetSerializer, FoodSerializer, FoodOptionSerializer, MealSerializer, OrderSerializer, \
|
||||
OrderedMealSerializer, OrderedFoodSerializer, SheetOrderTransactionSerializer
|
||||
from ..models import Sheet, Food, FoodOption, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction
|
||||
|
||||
|
||||
class SheetViewSet(ReadProtectedModelViewSet):
|
||||
queryset = Sheet.objects.order_by('id')
|
||||
serializer_class = SheetSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'date', ]
|
||||
search_fields = ['$name', ]
|
||||
|
||||
|
||||
class FoodViewSet(ReadProtectedModelViewSet):
|
||||
queryset = Food.objects.order_by('id')
|
||||
serializer_class = FoodSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'sheet', 'price', 'club', 'available', ]
|
||||
search_fields = ['$name', ]
|
||||
|
||||
|
||||
class FoodOptionViewSet(ReadProtectedModelViewSet):
|
||||
queryset = FoodOption.objects.order_by('id')
|
||||
serializer_class = FoodOptionSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'food', 'extra_cost', 'available', ]
|
||||
search_fields = ['$name', '$food__name', ]
|
||||
|
||||
|
||||
class MealViewSet(ReadProtectedModelViewSet):
|
||||
queryset = Meal.objects.order_by('id')
|
||||
serializer_class = MealSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['name', 'content', 'price', 'available', ]
|
||||
search_fields = ['$name', ]
|
||||
|
||||
|
||||
class OrderViewSet(ReadProtectedModelViewSet):
|
||||
queryset = Order.objects.order_by('id')
|
||||
serializer_class = OrderSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter]
|
||||
filterset_fields = ['sheet', 'note', 'date', 'gift', ]
|
||||
search_fields = ['$sheet__name', '$note__alias__name', '$note__alias__normalized_name', ]
|
||||
|
||||
|
||||
class OrderedMealViewSet(ReadProtectedModelViewSet):
|
||||
queryset = OrderedMeal.objects.order_by('id')
|
||||
serializer_class = OrderedMealSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['order', 'meal', ]
|
||||
|
||||
|
||||
class OrderedFoodViewSet(ReadProtectedModelViewSet):
|
||||
queryset = OrderedFood.objects.order_by('id')
|
||||
serializer_class = OrderedFoodSerializer
|
||||
filter_backends = [DjangoFilterBackend]
|
||||
filterset_fields = ['order', 'meal', 'food', 'options', 'number', 'status', 'served_date', ]
|
||||
|
||||
|
||||
class SheetOrderTransactionViewSet(ReadProtectedModelViewSet):
|
||||
queryset = SheetOrderTransaction.objects.order_by('-created_at')
|
||||
serializer_class = SheetOrderTransactionSerializer
|
||||
filter_backends = [DjangoFilterBackend, SearchFilter, OrderingFilter]
|
||||
filterset_fields = ['source', 'source_alias', 'source__alias__name', 'source__alias__normalized_name',
|
||||
'destination', 'destination_alias', 'destination__alias__name',
|
||||
'destination__alias__normalized_name', 'quantity', 'polymorphic_ctype', 'amount',
|
||||
'created_at', 'valid', 'invalidity_reason', 'ordered_food', ]
|
||||
search_fields = ['$reason', '$source_alias', '$source__alias__name', '$source__alias__normalized_name',
|
||||
'$destination_alias', '$destination__alias__name', '$destination__alias__normalized_name',
|
||||
'$invalidity_reason', ]
|
||||
ordering_fields = ['created_at', 'amount', ]
|
@@ -1,10 +0,0 @@
|
||||
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.apps import AppConfig
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
class SheetsConfig(AppConfig):
|
||||
name = 'sheets'
|
||||
verbose_name = _('note sheets')
|
@@ -1,67 +0,0 @@
|
||||
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from crispy_forms.helper import FormHelper
|
||||
from django import forms
|
||||
|
||||
from member.models import Club
|
||||
from note_kfet.inputs import AmountInput, Autocomplete, DateTimePickerInput
|
||||
|
||||
from .models import Food, FoodOption, Meal, Sheet
|
||||
|
||||
|
||||
class SheetForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Sheet
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'date': DateTimePickerInput(),
|
||||
}
|
||||
|
||||
|
||||
class FoodForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Food
|
||||
exclude = ('sheet', )
|
||||
widgets = {
|
||||
'price': AmountInput(),
|
||||
'club': Autocomplete(
|
||||
model=Club,
|
||||
attrs={"api_url": "/api/members/club/"},
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
class FoodOptionForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = FoodOption
|
||||
fields = '__all__'
|
||||
widgets = {
|
||||
'extra_cost': AmountInput(),
|
||||
}
|
||||
|
||||
|
||||
FoodOptionsFormSet = forms.inlineformset_factory(
|
||||
Food,
|
||||
FoodOption,
|
||||
form=FoodOptionForm,
|
||||
extra=0,
|
||||
)
|
||||
|
||||
|
||||
class FoodOptionFormSetHelper(FormHelper):
|
||||
def __init__(self, form=None):
|
||||
super().__init__(form)
|
||||
self.form_tag = False
|
||||
self.form_method = 'POST'
|
||||
self.form_class = 'form-inline'
|
||||
self.template = 'bootstrap4/table_inline_formset.html'
|
||||
|
||||
|
||||
class MealForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Meal
|
||||
exclude = ('sheet', )
|
||||
widgets = {
|
||||
'content': forms.CheckboxSelectMultiple(),
|
||||
'price': AmountInput(),
|
||||
}
|
@@ -1,157 +0,0 @@
|
||||
# Generated by Django 2.2.27 on 2022-08-18 11:01
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import django.utils.timezone
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('member', '0009_auto_20220818_1301'),
|
||||
('note', '0006_trust'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Food',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='food')),
|
||||
('price', models.IntegerField(verbose_name='price')),
|
||||
('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')),
|
||||
('club', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='member.Club', verbose_name='destination club')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'food',
|
||||
'verbose_name_plural': 'food',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='FoodOption',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||
('extra_cost', models.IntegerField(default=0, verbose_name='extra cost')),
|
||||
('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')),
|
||||
('food', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Food', verbose_name='food')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'food option',
|
||||
'verbose_name_plural': 'food options',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Meal',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||
('price', models.IntegerField(verbose_name='price')),
|
||||
('available', models.BooleanField(default=True, help_text="If set to false, this option won't be offered (in case of out of stock)", verbose_name='available')),
|
||||
('content', models.ManyToManyField(to='sheets.Food', verbose_name='content')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'meal',
|
||||
'verbose_name_plural': 'meals',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('date', models.DateTimeField(auto_now_add=True, verbose_name='date')),
|
||||
('gift', models.IntegerField(verbose_name='gift')),
|
||||
('note', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='note.Note', verbose_name='note')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'order',
|
||||
'verbose_name_plural': 'orders',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrderedFood',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('remark', models.TextField(blank=True, default='', verbose_name='remark')),
|
||||
('priority', models.CharField(blank=True, default='', max_length=64, verbose_name='priority request')),
|
||||
('number', models.IntegerField(help_text='How many times the user ordered this.', verbose_name='number')),
|
||||
('status', models.CharField(choices=[('QUEUED', 'queued'), ('READY', 'ready'), ('SERVED', 'served'), ('CANCELED', 'canceled')], max_length=8, verbose_name='status')),
|
||||
('served_date', models.DateTimeField(default=None, null=True, verbose_name='served date')),
|
||||
('food', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Food', verbose_name='food')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'ordered food',
|
||||
'verbose_name_plural': 'ordered food',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Sheet',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=255, verbose_name='name')),
|
||||
('date', models.DateTimeField(default=django.utils.timezone.now, verbose_name='start date')),
|
||||
('description', models.TextField(verbose_name='description')),
|
||||
('visible', models.BooleanField(default=False, help_text='the note sheet will be private until this field is checked.', verbose_name='visible')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'note sheet',
|
||||
'verbose_name_plural': 'note sheets',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='SheetOrderTransaction',
|
||||
fields=[
|
||||
('transaction_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='note.Transaction')),
|
||||
('ordered_food', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.OrderedFood', verbose_name='ordered food')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'sheet order transaction',
|
||||
'verbose_name_plural': 'sheet order transactions',
|
||||
},
|
||||
bases=('note.transaction',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='OrderedMeal',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('meal', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Meal', verbose_name='meal')),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Order', verbose_name='order')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'ordered meal',
|
||||
'verbose_name_plural': 'ordered meals',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderedfood',
|
||||
name='meal',
|
||||
field=models.ForeignKey(default=None, null=True, on_delete=django.db.models.deletion.SET_NULL, to='sheets.OrderedMeal', verbose_name='ordered meal'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderedfood',
|
||||
name='options',
|
||||
field=models.ManyToManyField(blank=True, to='sheets.FoodOption', verbose_name='options'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderedfood',
|
||||
name='order',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Order', verbose_name='order'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='sheet',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='sheets.Sheet', verbose_name='note sheet'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='meal',
|
||||
name='sheet',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Sheet', verbose_name='note sheet'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='food',
|
||||
name='sheet',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='sheets.Sheet', verbose_name='note sheet'),
|
||||
),
|
||||
]
|
@@ -1,34 +0,0 @@
|
||||
# Generated by Django 2.2.27 on 2022-08-18 15:13
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('sheets', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='gift',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderedfood',
|
||||
name='gift',
|
||||
field=models.IntegerField(default=0, verbose_name='gift'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderedmeal',
|
||||
name='gift',
|
||||
field=models.IntegerField(default=0, verbose_name='gift'),
|
||||
preserve_default=False,
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='orderedfood',
|
||||
name='status',
|
||||
field=models.CharField(choices=[('QUEUED', 'queued'), ('READY', 'ready'), ('SERVED', 'served'), ('CANCELED', 'canceled')], default='QUEUED', max_length=8, verbose_name='status'),
|
||||
),
|
||||
]
|
@@ -1,289 +0,0 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.db import models
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from member.models import Club
|
||||
from note.models import Note, Transaction
|
||||
|
||||
|
||||
class Sheet(models.Model):
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("name"),
|
||||
)
|
||||
|
||||
date = models.DateTimeField(
|
||||
verbose_name=_("start date"),
|
||||
default=timezone.now,
|
||||
)
|
||||
|
||||
description = models.TextField(
|
||||
verbose_name=_("description"),
|
||||
)
|
||||
|
||||
visible = models.BooleanField(
|
||||
default=False,
|
||||
verbose_name=_("visible"),
|
||||
help_text=_("the note sheet will be private until this field is checked."),
|
||||
)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('sheets:sheet_detail', args=(self.pk,))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("note sheet")
|
||||
verbose_name_plural = _("note sheets")
|
||||
|
||||
|
||||
class Food(models.Model):
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("food"),
|
||||
)
|
||||
|
||||
sheet = models.ForeignKey(
|
||||
Sheet,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("note sheet"),
|
||||
)
|
||||
|
||||
price = models.IntegerField(
|
||||
verbose_name=_("price"),
|
||||
)
|
||||
|
||||
club = models.ForeignKey(
|
||||
Club,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("destination club"),
|
||||
)
|
||||
|
||||
available = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("available"),
|
||||
help_text=_("If set to false, this option won't be offered (in case of out of stock)"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("food")
|
||||
verbose_name_plural = _("food")
|
||||
|
||||
|
||||
class FoodOption(models.Model):
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("name"),
|
||||
)
|
||||
|
||||
food = models.ForeignKey(
|
||||
Food,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("food"),
|
||||
)
|
||||
|
||||
extra_cost = models.IntegerField(
|
||||
default=0,
|
||||
verbose_name=_("extra cost"),
|
||||
)
|
||||
|
||||
available = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("available"),
|
||||
help_text=_("If set to false, this option won't be offered (in case of out of stock)"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("food option")
|
||||
verbose_name_plural = _("food options")
|
||||
|
||||
|
||||
class Meal(models.Model):
|
||||
sheet = models.ForeignKey(
|
||||
Sheet,
|
||||
on_delete=models.CASCADE,
|
||||
verbose_name=_("note sheet"),
|
||||
)
|
||||
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("name"),
|
||||
)
|
||||
|
||||
content = models.ManyToManyField(
|
||||
Food,
|
||||
verbose_name=_("content"),
|
||||
)
|
||||
|
||||
price = models.IntegerField(
|
||||
verbose_name=_("price"),
|
||||
)
|
||||
|
||||
available = models.BooleanField(
|
||||
default=True,
|
||||
verbose_name=_("available"),
|
||||
help_text=_("If set to false, this option won't be offered (in case of out of stock)"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return _("meal").capitalize() + " " + self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("meal")
|
||||
verbose_name_plural = _("meals")
|
||||
|
||||
|
||||
class Order(models.Model):
|
||||
sheet = models.ForeignKey(
|
||||
Sheet,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("note sheet"),
|
||||
)
|
||||
|
||||
note = models.ForeignKey(
|
||||
Note,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("note"),
|
||||
)
|
||||
|
||||
date = models.DateTimeField(
|
||||
verbose_name=_("date"),
|
||||
auto_now_add=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("order")
|
||||
verbose_name_plural = _("orders")
|
||||
|
||||
|
||||
class OrderedMeal(models.Model):
|
||||
order = models.ForeignKey(
|
||||
Order,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("order"),
|
||||
)
|
||||
|
||||
meal = models.ForeignKey(
|
||||
Meal,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("meal"),
|
||||
)
|
||||
|
||||
gift = models.IntegerField(
|
||||
verbose_name=_("gift"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("ordered meal")
|
||||
verbose_name_plural = _("ordered meals")
|
||||
|
||||
|
||||
class OrderedFood(models.Model):
|
||||
order = models.ForeignKey(
|
||||
Order,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("order"),
|
||||
)
|
||||
|
||||
meal = models.ForeignKey(
|
||||
OrderedMeal,
|
||||
on_delete=models.SET_NULL,
|
||||
null=True,
|
||||
default=None,
|
||||
verbose_name=_("ordered meal"),
|
||||
)
|
||||
|
||||
food = models.ForeignKey(
|
||||
Food,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("food"),
|
||||
)
|
||||
|
||||
options = models.ManyToManyField(
|
||||
FoodOption,
|
||||
blank=True,
|
||||
verbose_name=_("options"),
|
||||
)
|
||||
|
||||
remark = models.TextField(
|
||||
blank=True,
|
||||
default="",
|
||||
verbose_name=_("remark"),
|
||||
)
|
||||
|
||||
priority = models.CharField(
|
||||
max_length=64,
|
||||
blank=True,
|
||||
default="",
|
||||
verbose_name=_("priority request"),
|
||||
)
|
||||
|
||||
gift = models.IntegerField(
|
||||
verbose_name=_("gift"),
|
||||
)
|
||||
|
||||
number = models.IntegerField(
|
||||
verbose_name=_("number"),
|
||||
help_text=_("How many times the user ordered this."),
|
||||
)
|
||||
|
||||
status = models.CharField(
|
||||
max_length=8,
|
||||
choices=[
|
||||
('QUEUED', _("queued")),
|
||||
('READY', _("ready")),
|
||||
('SERVED', _("served")),
|
||||
('CANCELED', _("canceled")),
|
||||
],
|
||||
default='QUEUED',
|
||||
verbose_name=_("status"),
|
||||
)
|
||||
|
||||
served_date = models.DateTimeField(
|
||||
null=True,
|
||||
default=None,
|
||||
verbose_name=_("served date")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("ordered food")
|
||||
verbose_name_plural = _("ordered food")
|
||||
|
||||
|
||||
class SheetOrderTransaction(Transaction):
|
||||
ordered_food = models.ForeignKey(
|
||||
OrderedFood,
|
||||
on_delete=models.PROTECT,
|
||||
verbose_name=_("ordered food"),
|
||||
)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return _("note sheet")
|
||||
|
||||
@property
|
||||
def get_price(self):
|
||||
if self.ordered_food.meal:
|
||||
return self.ordered_food.meal.meal.price + self.ordered_food.meal.gift + sum(
|
||||
sum(opt.extra_cost for opt in ordered_food.options.all())
|
||||
for ordered_food in self.ordered_food.meal.orderedfood_set.exclude(status='CANCELED').all())
|
||||
elif self.ordered_food.status == 'CANCELED':
|
||||
return 0
|
||||
else:
|
||||
return self.ordered_food.food.price + self.ordered_food.gift \
|
||||
+ sum(opt.extra_cost for opt in self.ordered_food.options.all())
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("sheet order transaction")
|
||||
verbose_name_plural = _("sheet order transactions")
|
@@ -1,22 +0,0 @@
|
||||
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import django_tables2 as tables
|
||||
from django.urls import reverse_lazy
|
||||
|
||||
from sheets.models import Sheet
|
||||
|
||||
|
||||
class SheetTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class': 'table table-condensed table-striped table-hover'
|
||||
}
|
||||
model = Sheet
|
||||
template_name = 'django_tables2/bootstrap4.html'
|
||||
fields = ('name', 'date', )
|
||||
row_attrs = {
|
||||
'class': 'table-row',
|
||||
'id': lambda record: "row-" + str(record.pk),
|
||||
'data-href': lambda record: reverse_lazy('sheets:sheet_detail', args=(record.pk,))
|
||||
}
|
@@ -1,86 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
|
||||
{# The next part concerns the option formset #}
|
||||
{# Generate some hidden fields that manage the number of options, and make easier the parsing #}
|
||||
{{ formset.management_form }}
|
||||
<table class="table table-condensed table-striped">
|
||||
{# Fill initial data #}
|
||||
{% for form in formset %}
|
||||
{% if forloop.first %}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ form.name.label }}<span class="asteriskField">*</span></th>
|
||||
<th>{{ form.extra_cost.label }}<span class="asteriskField">*</span></th>
|
||||
<th>{{ form.available.label }}<span class="asteriskField">*</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="form_body">
|
||||
{% endif %}
|
||||
<tr class="row-formset">
|
||||
<td>{{ form.name }}</td>
|
||||
<td>{{ form.extra_cost }}</td>
|
||||
<td>{{ form.available }}</td>
|
||||
{# These fields are hidden but handled by the formset to link the id and the invoice id #}
|
||||
{{ form.food }}
|
||||
{{ form.id }}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{# Display buttons to add and remove options #}
|
||||
<div class="card-body">
|
||||
<button type="button" id="add_more" class="btn btn-success">{% trans "Add option" %}</button>
|
||||
</div>
|
||||
|
||||
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Hidden div that store an empty product form, to be copied into new forms #}
|
||||
<div id="empty_form" style="display: none;">
|
||||
<table class='no_error'>
|
||||
<tbody id="for_real">
|
||||
<tr class="row-formset">
|
||||
<td>{{ formset.empty_form.name }}</td>
|
||||
<td>{{ formset.empty_form.extra_cost }} </td>
|
||||
<td>{{ formset.empty_form.available }}</td>
|
||||
{{ formset.empty_form.food }}
|
||||
{{ formset.empty_form.id }}
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
<script>
|
||||
/* script that handles add and remove lines */
|
||||
IDS = {};
|
||||
|
||||
$("#id_foodoption_set-TOTAL_FORMS").val($(".row-formset").length - 1);
|
||||
|
||||
$('#add_more').click(function () {
|
||||
let form_idx = $('#id_foodoption_set-TOTAL_FORMS').val();
|
||||
$('#form_body').append($('#for_real').html().replace(/__prefix__/g, form_idx));
|
||||
$('#id_foodoption_set-TOTAL_FORMS').val(parseInt(form_idx) + 1);
|
||||
$('#id_foodoption_set-' + parseInt(form_idx) + '-id').val(IDS[parseInt(form_idx)]);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
@@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -1,87 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% load i18n %}
|
||||
{% load pretty_money %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header text-center">
|
||||
<h1>{{ sheet.name }}</h1>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-secondary">
|
||||
<div class="row">
|
||||
<div class="col-sm-11">
|
||||
{{ sheet.description }}
|
||||
</div>
|
||||
{% if can_change_sheet %}
|
||||
<div class="col-sm-1">
|
||||
<a class="badge badge-primary" href="{% url 'sheets:sheet_update' pk=sheet.pk %}">
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>{% trans "menu"|capfirst %} :</h3>
|
||||
<ul>
|
||||
{% for meal in sheet.meal_set.all %}
|
||||
<li{% if not meal.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
|
||||
{{ meal }} ({{ meal.price|pretty_money }})
|
||||
{% if can_change_sheet %}
|
||||
<a href="{% url 'sheets:meal_update' pk=meal.pk %}" class="badge badge-primary">
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
<hr>
|
||||
{% for food in sheet.food_set.all %}
|
||||
<li{% if not food.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
|
||||
{{ food }} ({{ food.price|pretty_money }})
|
||||
<a href="{% url 'sheets:waiting_list' pk=food.pk %}" class="badge badge-primary">
|
||||
<i class="fa fa-list"></i>
|
||||
{% trans "Waiting list" %}
|
||||
</a>
|
||||
{% if can_change_sheet %}
|
||||
<a href="{% url 'sheets:food_update' pk=food.pk %}" class="badge badge-primary">
|
||||
<i class="fa fa-edit"></i>
|
||||
{% trans "Edit" %}
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if food.foodoption_set.all %}
|
||||
<ul>
|
||||
{% for option in food.foodoption_set.all %}
|
||||
<li{% if not option.available %} class="text-danger" style="text-decoration: line-through !important;" title="{% trans "This product is unavailable." %}"{% endif %}>
|
||||
{{ option }}{% if option.extra_cost %} ({{ option.extra_cost|pretty_money }}){% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% empty %}
|
||||
<div class="alert alert-warning">
|
||||
{% trans "The menu is empty for now." %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<div class="text-center">
|
||||
{% if can_add_food %}
|
||||
<a href="{% url 'sheets:food_create' pk=sheet.pk %}" class="btn btn-primary">{% trans "Add new food" %}</a>
|
||||
{% endif %}
|
||||
{% if can_add_meal %}
|
||||
<a href="{% url 'sheets:meal_create' pk=sheet.pk %}" class="btn btn-primary">{% trans "Add new meal" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer text-center">
|
||||
<a href="{% url 'sheets:sheet_order' pk=sheet.pk %}" class="btn btn-success">
|
||||
{% trans "Order now" %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -1,21 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
{{ form|crispy }}
|
||||
<button class="btn btn-primary" type="submit">{% trans "Submit" %}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@@ -1,74 +0,0 @@
|
||||
{% extends "base.html" %}
|
||||
{% comment %}
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endcomment %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center mb-4">
|
||||
<div class="col-md-10 text-center">
|
||||
<input class="form-control mx-auto w-25" type="text" onkeyup="search_field_moved()" id="search_field"/>
|
||||
{% if can_create_sheet %}
|
||||
<hr>
|
||||
<a class="btn btn-primary text-center my-4" href="{% url 'sheets:sheet_create' %}">{% trans "Create a sheet" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-10">
|
||||
<div class="card card-border shadow">
|
||||
<div class="card-header text-center">
|
||||
<h5> {% trans "Note sheet listing" %}</h5>
|
||||
</div>
|
||||
<div class="card-body px-0 py-0" id="sheets_table">
|
||||
{% render_table table %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block extrajavascript %}
|
||||
<script type="text/javascript">
|
||||
|
||||
function getInfo() {
|
||||
var asked = $("#search_field").val();
|
||||
/* on ne fait la requête que si on a au moins un caractère pour chercher */
|
||||
var sel = $(".table-row");
|
||||
if (asked.length >= 1) {
|
||||
$.getJSON("/api/sheets/sheet/?format=json&search="+asked, function(buttons){
|
||||
let selected_id = buttons.results.map((a => "#row-"+a.id));
|
||||
if (selected_id.length)
|
||||
$(".table-row,"+selected_id.join()).show();
|
||||
$(".table-row").not(selected_id.join()).hide();
|
||||
|
||||
});
|
||||
}else{
|
||||
// show everything
|
||||
$('table tr').show();
|
||||
}
|
||||
}
|
||||
var timer;
|
||||
var timer_on;
|
||||
/* Fontion appelée quand le texte change (délenche le timer) */
|
||||
function search_field_moved(secondfield) {
|
||||
if (timer_on) { // Si le timer a déjà été lancé, on réinitialise le compteur.
|
||||
clearTimeout(timer);
|
||||
timer = setTimeout("getInfo(" + secondfield + ")", 300);
|
||||
}
|
||||
else { // Sinon, on le lance et on enregistre le fait qu'il tourne.
|
||||
timer = setTimeout("getInfo(" + secondfield + ")", 300);
|
||||
timer_on = true;
|
||||
}
|
||||
}
|
||||
|
||||
// clickable row
|
||||
$(document).ready(function($) {
|
||||
$(".table-row").click(function() {
|
||||
window.document.location = $(this).data("href");
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
{% endblock %}
|
@@ -1,26 +0,0 @@
|
||||
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from sheets.views import FoodCreateView, FoodUpdateView, MealCreateView, MealUpdateView, OrderView, \
|
||||
SheetCreateView, SheetDetailView, SheetListView, SheetUpdateView, WaitingListDetailView, WaitingListView
|
||||
|
||||
app_name = 'sheets'
|
||||
|
||||
urlpatterns = [
|
||||
path('list/', SheetListView.as_view(), name="sheet_list"),
|
||||
path('create/', SheetCreateView.as_view(), name="sheet_create"),
|
||||
path('update/<int:pk>/', SheetUpdateView.as_view(), name="sheet_update"),
|
||||
path('detail/<int:pk>/', SheetDetailView.as_view(), name="sheet_detail"),
|
||||
path('food/create/<int:pk>/', FoodCreateView.as_view(), name="food_create"),
|
||||
path('food/<int:pk>/update/', FoodUpdateView.as_view(), name="food_update"),
|
||||
path('meal/create/<int:pk>/', MealCreateView.as_view(), name="meal_create"),
|
||||
path('meal/<int:pk>/update/', MealUpdateView.as_view(), name="meal_update"),
|
||||
path('order/<int:pk>/', OrderView.as_view(), name="sheet_order"),
|
||||
path('waiting-list/<int:pk>/', WaitingListView.as_view(), name="waiting_list"),
|
||||
path('waiting-list/<int:pk>/queued/', WaitingListDetailView.as_view(), name="queued_list"),
|
||||
path('waiting-list/<int:pk>/ready/', WaitingListDetailView.as_view(), name="ready_list"),
|
||||
path('waiting-list/<int:pk>/served/', WaitingListDetailView.as_view(), name="served_list"),
|
||||
path('waiting-list/<int:pk>/canceled/', WaitingListDetailView.as_view(), name="canceled_list"),
|
||||
]
|
@@ -1,444 +0,0 @@
|
||||
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from datetime import timedelta
|
||||
|
||||
from crispy_forms.bootstrap import Accordion, AccordionGroup, FormActions
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Fieldset, Submit, Row, Field
|
||||
from django import forms
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.db import transaction
|
||||
from django.forms import Form
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DetailView, UpdateView, FormView
|
||||
from django_tables2 import SingleTableView
|
||||
|
||||
from note.models import Alias, Note
|
||||
from note.templatetags.pretty_money import pretty_money
|
||||
from note_kfet.inputs import AmountInput, Autocomplete
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||
|
||||
from .forms import FoodForm, MealForm, SheetForm, FoodOptionsFormSet, FoodOptionFormSetHelper
|
||||
from .models import Sheet, Food, Meal, Order, OrderedMeal, OrderedFood, SheetOrderTransaction
|
||||
from .tables import SheetTable
|
||||
|
||||
|
||||
class SheetListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
model = Sheet
|
||||
table_class = SheetTable
|
||||
ordering = '-date'
|
||||
extra_context = {"title": _("Search note sheet")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["can_create_sheet"] = PermissionBackend.check_perm(self.request, "sheets.add_sheet", Sheet(
|
||||
name="Test",
|
||||
date=timezone.now(),
|
||||
description="Test sheet",
|
||||
))
|
||||
return context
|
||||
|
||||
|
||||
class SheetCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
model = Sheet
|
||||
form_class = SheetForm
|
||||
extra_context = {"title": _("Create note sheet")}
|
||||
|
||||
def get_sample_object(self):
|
||||
return Sheet(
|
||||
name="Test",
|
||||
date=timezone.now(),
|
||||
description="Test",
|
||||
)
|
||||
|
||||
|
||||
class SheetUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
model = Sheet
|
||||
form_class = SheetForm
|
||||
extra_context = {"title": _("Update note sheet")}
|
||||
|
||||
|
||||
class SheetDetailView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
model = Sheet
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data()
|
||||
|
||||
context['can_change_sheet'] = PermissionBackend.check_perm(self.request, 'sheets.change_sheet', self.object)
|
||||
context['can_add_meal'] = PermissionBackend.check_perm(self.request,
|
||||
'sheets.add_meal',
|
||||
Meal(sheet=self.object, name="Test", price=500))
|
||||
context['can_add_food'] = PermissionBackend.check_perm(self.request,
|
||||
'sheets.add_food',
|
||||
Food(sheet=self.object, name="Test", price=500))
|
||||
|
||||
return context
|
||||
|
||||
|
||||
class FoodCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
model = Food
|
||||
form_class = FoodForm
|
||||
extra_context = {"title": _("Create new food")}
|
||||
|
||||
def get_sample_object(self):
|
||||
return Food(
|
||||
sheet_id=self.kwargs['pk'],
|
||||
name="Test",
|
||||
price=500,
|
||||
)
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
form = context['form']
|
||||
form.helper = FormHelper()
|
||||
# Remove form tag on the generation of the form in the template (already present on the template)
|
||||
form.helper.form_tag = False
|
||||
# The formset handles the set of the products
|
||||
form_set = FoodOptionsFormSet(instance=form.instance)
|
||||
context['formset'] = form_set
|
||||
context['helper'] = FoodOptionFormSetHelper()
|
||||
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.sheet_id = self.kwargs['pk']
|
||||
|
||||
# For each product, we save it
|
||||
formset = FoodOptionsFormSet(self.request.POST, instance=form.instance)
|
||||
if formset.is_valid():
|
||||
for f in formset:
|
||||
# We don't save the product if the designation is not entered, ie. if the line is empty
|
||||
if f.is_valid() and f.instance.name:
|
||||
f.save()
|
||||
f.instance.save()
|
||||
else:
|
||||
f.instance = None
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
class FoodUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
model = Food
|
||||
form_class = FoodForm
|
||||
extra_context = {"title": _("Update food")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
form = context['form']
|
||||
form.helper = FormHelper()
|
||||
# Remove form tag on the generation of the form in the template (already present on the template)
|
||||
form.helper.form_tag = False
|
||||
# The formset handles the set of the products
|
||||
form_set = FoodOptionsFormSet(instance=form.instance)
|
||||
context['formset'] = form_set
|
||||
context['helper'] = FoodOptionFormSetHelper()
|
||||
|
||||
return context
|
||||
|
||||
def form_valid(self, form):
|
||||
# For each product, we save it
|
||||
formset = FoodOptionsFormSet(self.request.POST, instance=form.instance)
|
||||
if formset.is_valid():
|
||||
for f in formset:
|
||||
# We don't save the product if the designation is not entered, ie. if the line is empty
|
||||
if f.is_valid() and f.instance.name:
|
||||
f.save()
|
||||
f.instance.save()
|
||||
else:
|
||||
f.instance = None
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,))
|
||||
|
||||
|
||||
class MealCreateView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
model = Meal
|
||||
form_class = MealForm
|
||||
extra_context = {"title": _("Create new meal")}
|
||||
|
||||
def get_sample_object(self):
|
||||
return Meal(
|
||||
sheet_id=self.kwargs['pk'],
|
||||
name="Test",
|
||||
price=500,
|
||||
)
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.fields['content'].queryset = form.fields['content'].queryset.filter(sheet_id=self.kwargs['pk'])
|
||||
return form
|
||||
|
||||
def form_valid(self, form):
|
||||
form.instance.sheet_id = self.kwargs['pk']
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,))
|
||||
|
||||
|
||||
class MealUpdateView(ProtectQuerysetMixin, LoginRequiredMixin, UpdateView):
|
||||
model = Meal
|
||||
form_class = MealForm
|
||||
extra_context = {"title": _("Update meal")}
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = super().get_form(form_class)
|
||||
form.fields['content'].queryset = form.fields['content'].queryset.filter(sheet=self.object.sheet)
|
||||
return form
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('sheets:sheet_detail', args=(self.object.sheet_id,))
|
||||
|
||||
|
||||
class OrderView(LoginRequiredMixin, FormView, DetailView):
|
||||
model = Sheet
|
||||
template_name = 'sheets/order.html'
|
||||
extra_context = {'title': _("Order now")}
|
||||
|
||||
def get_form(self, form_class=None):
|
||||
form = Form()
|
||||
form.helper = FormHelper()
|
||||
layout_fields = []
|
||||
|
||||
self.object = self.get_object()
|
||||
|
||||
form.fields['note'] = forms.ModelChoiceField(
|
||||
queryset=Note.objects.filter(PermissionBackend.filter_queryset(self.request, Note, 'note.view_note')),
|
||||
label=_("Orderer"),
|
||||
initial=self.request.user.note,
|
||||
widget=Autocomplete(
|
||||
model=Note,
|
||||
attrs={
|
||||
"api_url": "/api/note/note/",
|
||||
'placeholder': _("Who orders")
|
||||
},
|
||||
),
|
||||
)
|
||||
layout_fields.append(Field('note', css_class='is-valid'))
|
||||
|
||||
for meal in self.object.meal_set.filter(available=True).all():
|
||||
form.fields[f'meal_{meal.id}_quantity'] = forms.IntegerField(
|
||||
label=_("Quantity"),
|
||||
initial=0,
|
||||
)
|
||||
form.fields[f'meal_{meal.id}_gift'] = forms.IntegerField(
|
||||
label=_("gift").capitalize(),
|
||||
initial=0,
|
||||
widget=AmountInput(),
|
||||
help_text=_("Be careful: this gift will be multiplied for each order."),
|
||||
)
|
||||
form.fields[f'meal_{meal.id}_remark'] = forms.CharField(
|
||||
max_length=255,
|
||||
required=False,
|
||||
label=_("remark").capitalize(),
|
||||
help_text=_("Allergies,…"),
|
||||
)
|
||||
form.fields[f'meal_{meal.id}_priority'] = forms.CharField(
|
||||
max_length=64,
|
||||
required=False,
|
||||
label=_("priority request").capitalize(),
|
||||
help_text=_("Lesson at 13h30,…"),
|
||||
)
|
||||
|
||||
ag = AccordionGroup(f"{meal} ({pretty_money(meal.price)})",
|
||||
Row(Field(f'meal_{meal.id}_quantity', wrapper_class='col-sm-9'),
|
||||
Field(f'meal_{meal.id}_gift', wrapper_class='col-sm-3')),
|
||||
Row(Field(f'meal_{meal.id}_remark', wrapper_class='col-sm-9'),
|
||||
Field(f'meal_{meal.id}_priority', wrapper_class='col-sm-3')))
|
||||
|
||||
for food in meal.content.filter(available=True).all():
|
||||
if food.foodoption_set.count():
|
||||
options_fieldset = Fieldset(_("Options for ") + str(food))
|
||||
options_row = Row(css_class='ml-0')
|
||||
for option in food.foodoption_set.filter(available=True).all():
|
||||
form.fields[f'meal_{meal.id}_food_{food.id}_option_{option.id}'] = forms.BooleanField(
|
||||
label=f"{option}{f' ({pretty_money(option.extra_cost)})' if option.extra_cost else ''}",
|
||||
required=False,
|
||||
)
|
||||
options_row.fields.append(
|
||||
Field(f'meal_{meal.id}_food_{food.id}_option_{option.id}', wrapper_class='col-sm-12'))
|
||||
options_fieldset.fields.append(options_row)
|
||||
ag.fields.append(options_fieldset)
|
||||
|
||||
layout_fields.append(ag)
|
||||
|
||||
for food in self.object.food_set.filter(available=True).all():
|
||||
form.fields[f'food_{food.id}_quantity'] = forms.IntegerField(
|
||||
label=_("quantity").capitalize(),
|
||||
initial=0,
|
||||
)
|
||||
form.fields[f'food_{food.id}_gift'] = forms.IntegerField(
|
||||
label=_("gift").capitalize(),
|
||||
initial=0,
|
||||
widget=AmountInput(),
|
||||
help_text=_("Be careful: this gift will be multiplied for each order."),
|
||||
)
|
||||
form.fields[f'food_{food.id}_remark'] = forms.CharField(
|
||||
max_length=255,
|
||||
required=False,
|
||||
label=_("remark").capitalize(),
|
||||
help_text=_("Allergies,…"),
|
||||
)
|
||||
form.fields[f'food_{food.id}_priority'] = forms.CharField(
|
||||
max_length=255,
|
||||
required=False,
|
||||
label=_("priority request").capitalize(),
|
||||
help_text=_("Lesson at 13h30,…"),
|
||||
)
|
||||
|
||||
ag = AccordionGroup(f"{food} ({pretty_money(food.price)})",
|
||||
Row(Field(f'food_{food.id}_quantity', wrapper_class='col-sm-9'),
|
||||
Field(f'food_{food.id}_gift', wrapper_class='col-sm-3')),
|
||||
Row(Field(f'food_{food.id}_remark', wrapper_class='col-sm-9'),
|
||||
Field(f'food_{food.id}_priority', wrapper_class='col-sm-3')))
|
||||
|
||||
if food.foodoption_set.count():
|
||||
options_fieldset = Fieldset(_("Options"))
|
||||
options_row = Row(css_class='ml-0')
|
||||
for option in food.foodoption_set.all():
|
||||
form.fields[f'food_{food.id}_option_{option.id}'] = forms.BooleanField(
|
||||
label=f"{option}{f' ({pretty_money(option.extra_cost)})' if option.extra_cost else ''}",
|
||||
required=False,
|
||||
)
|
||||
options_row.fields.append(Field(f'food_{food.id}_option_{option.id}', wrapper_class='col-sm-12'))
|
||||
options_fieldset.fields.append(options_row)
|
||||
ag.fields.append(options_fieldset)
|
||||
|
||||
layout_fields.append(ag)
|
||||
|
||||
layout_fields.append(FormActions(Submit('submit', _("Order now"))))
|
||||
|
||||
form.helper.layout = Accordion(*layout_fields)
|
||||
|
||||
if self.request.method in ['PUT', 'POST']:
|
||||
form.data = self.request.POST
|
||||
form.files = self.request.FILES
|
||||
form.is_bound = not form.data or not form.files
|
||||
|
||||
return form
|
||||
|
||||
def form_valid(self, form):
|
||||
data = form.cleaned_data
|
||||
sheet = self.get_object()
|
||||
|
||||
with transaction.atomic():
|
||||
order = Order.objects.create(sheet_id=self.kwargs['pk'], note=data['note'])
|
||||
|
||||
total_quantity = 0
|
||||
|
||||
for meal in sheet.meal_set.filter(available=True).all():
|
||||
quantity = data[f'meal_{meal.id}_quantity']
|
||||
if not quantity:
|
||||
continue
|
||||
|
||||
total_quantity += quantity
|
||||
gift = data[f'meal_{meal.id}_gift']
|
||||
remark = data[f'meal_{meal.id}_remark'] or ''
|
||||
priority = data[f'meal_{meal.id}_priority'] or ''
|
||||
ordered_meal = OrderedMeal.objects.create(order=order, meal=meal, gift=gift)
|
||||
|
||||
for ignored in range(quantity):
|
||||
for food in meal.content.filter(available=True).all():
|
||||
n = OrderedFood.objects.filter(order__sheet_id=self.kwargs['pk'],
|
||||
order__note=order.note,
|
||||
order__date__gte=timezone.now() - timedelta(hours=6),
|
||||
food=food).exclude(status='CANCELED').count()
|
||||
of = OrderedFood.objects.create(order=order, meal=ordered_meal, food=food,
|
||||
remark=remark, priority=priority, number=n + 1, gift=0)
|
||||
|
||||
for option in food.foodoption_set.filter(available=True).all():
|
||||
if data[f'meal_{meal.id}_food_{food.id}_option_{option.id}']:
|
||||
of.options.add(option)
|
||||
of.save()
|
||||
|
||||
first_food = ordered_meal.orderedfood_set.first()
|
||||
tr = SheetOrderTransaction(source_id=order.note_id, destination=first_food.food.club.note,
|
||||
source_alias=str(order.note), destination_alias=first_food.food.club.name,
|
||||
quantity=quantity, ordered_food=first_food,
|
||||
reason=f"{meal.name} - {sheet.name}")
|
||||
tr.amount = tr.get_price / tr.quantity
|
||||
tr.save()
|
||||
|
||||
for food in sheet.food_set.filter(available=True).all():
|
||||
quantity = data[f'food_{food.id}_quantity']
|
||||
if not quantity:
|
||||
continue
|
||||
|
||||
total_quantity += quantity
|
||||
gift = data[f'food_{meal.id}_gift']
|
||||
remark = data[f'food_{meal.id}_remark'] or ''
|
||||
priority = data[f'food_{meal.id}_priority'] or ''
|
||||
|
||||
for ignored in range(quantity):
|
||||
n = OrderedFood.objects.filter(order__sheet_id=self.kwargs['pk'],
|
||||
order__note=order.note,
|
||||
order__date__gte=timezone.now() - timedelta(hours=6),
|
||||
food=food).exclude(state='CANCELED').count()
|
||||
of = OrderedFood.objects.create(order=order, food=food, gift=gift,
|
||||
remark=remark, priority=priority, number=n + 1)
|
||||
|
||||
for option in food.foodoption_set.filter(available=True).all():
|
||||
if data[f'meal_{meal.id}_food_{food.id}_option_{option.id}']:
|
||||
of.options.add(option)
|
||||
of.options.save()
|
||||
|
||||
tr = SheetOrderTransaction(source_id=order.note_id, destination_id=first_food.club.note,
|
||||
source_alias=str(order.note), destination_alias=first_food.club.name,
|
||||
quantity=quantity, ordered_food=of,
|
||||
reason=f"{food.name} - {sheet.name}")
|
||||
tr.amount = tr.get_price / tr.quantity
|
||||
tr.save()
|
||||
|
||||
if total_quantity == 0:
|
||||
form.add_error(None, _("You didn't select anything."))
|
||||
transaction.rollback()
|
||||
return self.form_invalid(form)
|
||||
|
||||
return super().form_valid(form)
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse_lazy('sheets:sheet_detail', args=(self.kwargs['pk'],))
|
||||
|
||||
|
||||
class WaitingListView(ProtectQuerysetMixin, DetailView):
|
||||
model = Food
|
||||
template_name = 'sheets/waiting_list.html'
|
||||
extra_context = {'title': _("Waiting list")}
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
content = super().get_context_data(**kwargs)
|
||||
|
||||
content['queue'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status='QUEUED')\
|
||||
.order_by('-priority', 'number', 'order__date').all()
|
||||
content['ready'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status='READY')\
|
||||
.order_by('served_date').all()
|
||||
|
||||
return content
|
||||
|
||||
|
||||
class WaitingListDetailView(ProtectQuerysetMixin, DetailView):
|
||||
model = Food
|
||||
template_name = 'sheets/waiting_list_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
list_type = 'CANCELED' if 'canceled' in self.request.path else \
|
||||
'SERVED' if 'served' in self.request.path else \
|
||||
'READY' if 'ready' in self.request.path else 'QUEUED'
|
||||
context['list_type'] = list_type
|
||||
context['orders'] = OrderedFood.objects.filter(food_id=self.kwargs['pk'], status=list_type)\
|
||||
.order_by('served_date', '-priority', 'number', 'order__date').all()
|
||||
context['title'] = self.object.name + " - " + _(list_type.lower()).capitalize()
|
||||
|
||||
return context
|
18
apps/treasury/migrations/0005_auto_20230129_2348.py
Normal file
18
apps/treasury/migrations/0005_auto_20230129_2348.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2023-01-29 22:48
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('treasury', '0004_auto_20211005_1544'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='invoice',
|
||||
name='bde',
|
||||
field=models.CharField(choices=[('TotalistSpies', 'Tota[list]Spies'), ('Saperlistpopette', 'Saper[list]popette'), ('Finalist', 'Fina[list]'), ('Listorique', '[List]orique'), ('Satellist', 'Satel[list]'), ('Monopolist', 'Monopo[list]'), ('Kataclist', 'Katac[list]')], default='TotalistSpies', max_length=32, verbose_name='BDE'),
|
||||
),
|
||||
]
|
18
apps/treasury/migrations/0006_auto_20230414_1651.py
Normal file
18
apps/treasury/migrations/0006_auto_20230414_1651.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2023-04-14 14:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('treasury', '0005_auto_20230129_2348'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='invoice',
|
||||
name='bde',
|
||||
field=models.CharField(choices=[('SecretStorlist', 'SecretStor[list]'), ('TotalistSpies', 'Tota[list]Spies'), ('Saperlistpopette', 'Saper[list]popette'), ('Finalist', 'Fina[list]'), ('Listorique', '[List]orique'), ('Satellist', 'Satel[list]'), ('Monopolist', 'Monopo[list]'), ('Kataclist', 'Katac[list]')], default='SecretStorlist', max_length=32, verbose_name='BDE'),
|
||||
),
|
||||
]
|
@@ -1,6 +1,5 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
import datetime
|
||||
from datetime import date
|
||||
|
||||
from django.conf import settings
|
||||
@@ -12,7 +11,8 @@ 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 member.models import Club, Membership # Club unused because of disabled soge
|
||||
from member.models import Membership
|
||||
from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction, NoteUser
|
||||
|
||||
|
||||
@@ -28,8 +28,10 @@ class Invoice(models.Model):
|
||||
|
||||
bde = models.CharField(
|
||||
max_length=32,
|
||||
default='Saperlistpopette',
|
||||
default='SecretStorlist',
|
||||
choices=(
|
||||
('SecretStorlist', 'SecretStor[list]'),
|
||||
('TotalistSpies', 'Tota[list]Spies'),
|
||||
('Saperlistpopette', 'Saper[list]popette'),
|
||||
('Finalist', 'Fina[list]'),
|
||||
('Listorique', '[List]orique'),
|
||||
@@ -95,7 +97,7 @@ class Invoice(models.Model):
|
||||
products = self.products.all()
|
||||
|
||||
self.place = "Gif-sur-Yvette"
|
||||
self.my_name = "BDE ENS Cachan"
|
||||
self.my_name = "BDE ENS Paris Saclay"
|
||||
self.my_address_street = "4 avenue des Sciences"
|
||||
self.my_city = "91190 Gif-sur-Yvette"
|
||||
self.bank_code = 30003
|
||||
@@ -310,8 +312,8 @@ class SogeCredit(models.Model):
|
||||
amount = sum(transaction.total for transaction in self.transactions.all())
|
||||
if 'wei' in settings.INSTALLED_APPS:
|
||||
from wei.models import WEIMembership
|
||||
if not WEIMembership.objects.filter(club__weiclub__year=datetime.date.today().year, user=self.user)\
|
||||
.exists():
|
||||
if not WEIMembership.objects\
|
||||
.filter(club__weiclub__year=self.credit_transaction.created_at.year, user=self.user).exists():
|
||||
# 80 € for people that don't go to WEI
|
||||
amount += 8000
|
||||
return amount
|
||||
@@ -324,22 +326,23 @@ class SogeCredit(models.Model):
|
||||
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)
|
||||
# Soge do not pay BDE and kfet memberships since 2022
|
||||
# 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 MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||
if m.transaction not in self.transactions.all():
|
||||
self.transactions.add(m.transaction)
|
||||
|
||||
if kfet_qs.exists():
|
||||
m = kfet_qs.get()
|
||||
if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||
if m.transaction not in self.transactions.all():
|
||||
self.transactions.add(m.transaction)
|
||||
# if bde_qs.exists():
|
||||
# m = bde_qs.get()
|
||||
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||
# if m.transaction not in self.transactions.all():
|
||||
# self.transactions.add(m.transaction)
|
||||
#
|
||||
# if kfet_qs.exists():
|
||||
# m = kfet_qs.get()
|
||||
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||
# if m.transaction not in self.transactions.all():
|
||||
# self.transactions.add(m.transaction)
|
||||
|
||||
if 'wei' in settings.INSTALLED_APPS:
|
||||
from wei.models import WEIClub
|
||||
@@ -385,7 +388,6 @@ class SogeCredit(models.Model):
|
||||
for tr in self.transactions.all():
|
||||
tr.valid = True
|
||||
tr._force_save = True
|
||||
tr.created_at = timezone.now()
|
||||
tr.save()
|
||||
|
||||
@transaction.atomic
|
||||
@@ -434,12 +436,11 @@ class SogeCredit(models.Model):
|
||||
for tr in self.transactions.all():
|
||||
tr._force_save = True
|
||||
tr.valid = True
|
||||
tr.created_at = timezone.now()
|
||||
tr.save()
|
||||
if self.credit_transaction:
|
||||
# If the soge credit is deleted while the user is not validated yet,
|
||||
# there is not credit transaction.
|
||||
# There is a credit transaction iff the user declares that no bank account
|
||||
# There is a credit transaction if the user declares that no bank account
|
||||
# was opened after the validation of the account.
|
||||
self.credit_transaction.valid = False
|
||||
self.credit_transaction.reason += " (invalide)"
|
||||
|
BIN
apps/treasury/static/img/SecretStorlist.png
Normal file
BIN
apps/treasury/static/img/SecretStorlist.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 690 KiB |
BIN
apps/treasury/static/img/SecretStorlist_bg.jpg
Normal file
BIN
apps/treasury/static/img/SecretStorlist_bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 77 KiB |
BIN
apps/treasury/static/img/TotalistSpies.png
Normal file
BIN
apps/treasury/static/img/TotalistSpies.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
BIN
apps/treasury/static/img/TotalistSpies_bg.jpg
Normal file
BIN
apps/treasury/static/img/TotalistSpies_bg.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
@@ -105,8 +105,8 @@
|
||||
|
||||
\renewcommand{\headrulewidth}{0pt}
|
||||
\cfoot{
|
||||
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)6 89 88 56 50\newline
|
||||
Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00011
|
||||
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)7 78 17 22 34\newline
|
||||
Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00029
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -385,8 +385,7 @@ class TestSogeCredits(TestCase):
|
||||
|
||||
response = self.client.post(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)),
|
||||
data=dict(delete=True))
|
||||
# 403 because no SogeCredit exists anymore, then a PermissionDenied is raised
|
||||
self.assertRedirects(response, reverse("treasury:soge_credits"), 302, 403)
|
||||
self.assertRedirects(response, reverse("treasury:soge_credits"), 302, 200)
|
||||
self.assertFalse(SogeCredit.objects.filter(pk=soge_credit.pk))
|
||||
self.user.note.refresh_from_db()
|
||||
self.assertEqual(self.user.note.balance, 0)
|
||||
|
@@ -101,14 +101,7 @@ class InvoiceListView(LoginRequiredMixin, SingleTableView):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
sample_invoice = Invoice(
|
||||
id=0,
|
||||
object="",
|
||||
description="",
|
||||
name="",
|
||||
address="",
|
||||
)
|
||||
if not PermissionBackend.check_perm(self.request, "treasury.add_invoice", sample_invoice):
|
||||
if not PermissionBackend.has_model_perm(self.request, Invoice(), "view"):
|
||||
raise PermissionDenied(_("You are not able to see the treasury interface."))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@@ -278,11 +271,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
sample_remittance = Remittance(
|
||||
remittance_type_id=1,
|
||||
comment="",
|
||||
)
|
||||
if not PermissionBackend.check_perm(self.request, "treasury.add_remittance", sample_remittance):
|
||||
if not PermissionBackend.has_model_perm(self.request, Remittance(), "view"):
|
||||
raise PermissionDenied(_("You are not able to see the treasury interface."))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@@ -408,7 +397,7 @@ class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableVi
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not super().get_queryset().exists():
|
||||
if not PermissionBackend.has_model_perm(self.request, SogeCredit(), "view"):
|
||||
raise PermissionDenied(_("You are not able to see the treasury interface."))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django import forms
|
||||
@@ -38,7 +38,7 @@ class WEIRegistrationForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = WEIRegistration
|
||||
exclude = ('wei', )
|
||||
exclude = ('wei', 'clothing_cut')
|
||||
widgets = {
|
||||
"user": Autocomplete(
|
||||
User,
|
||||
|
@@ -2,11 +2,11 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm
|
||||
from .wei2022 import WEISurvey2022
|
||||
from .wei2023 import WEISurvey2023
|
||||
|
||||
|
||||
__all__ = [
|
||||
'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey',
|
||||
]
|
||||
|
||||
CurrentSurvey = WEISurvey2022
|
||||
CurrentSurvey = WEISurvey2023
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import time
|
||||
@@ -14,14 +14,17 @@ from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInf
|
||||
from ...models import WEIMembership
|
||||
|
||||
WORDS = [
|
||||
'13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant',
|
||||
'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill',
|
||||
'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial',
|
||||
'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno',
|
||||
'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit',
|
||||
'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic',
|
||||
'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi',
|
||||
'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane',
|
||||
'ABBA', 'After', 'Alcoolique anonyme', 'Ambiance festive', 'Années 2000', 'Apéro', 'Art',
|
||||
'Baby foot billard biere pong', 'BBQ', 'Before', 'Bière pong', 'Bon enfant', 'Calme', 'Canapé',
|
||||
'Chanson paillarde', 'Chanson populaire', 'Chartreuse', 'Cheerleader', 'Chill', 'Choré',
|
||||
'Cinéma', 'Cocktail', 'Comédie musicle', 'Commercial', 'Copaing', 'Danse', 'Dancefloor',
|
||||
'Electro', 'Fanfare', 'Gin tonic', 'Inclusif', 'Jazz', "Jeux d'alcool", 'Jeux de carte',
|
||||
'Jeux de rôle', 'Jeux de société', 'JUL', 'Jus de fruit', 'Kfet', 'Kleptomanie assurée',
|
||||
'LGBTQ+', 'Livre', 'Morning beer', 'Musique', 'NAPS', 'Paillettes', 'Pastis', 'Paté Hénaff',
|
||||
'Peluche', 'Pena baiona', "Peu d'alcool", 'Pilier de bar', 'PMU', 'Poulpe', 'Punch', 'Rap',
|
||||
'Réveil', 'Rock', 'Rugby', 'Sandwich', 'Serge', 'Shot', 'Sociable', 'Spectacle', 'Techno',
|
||||
'Techno house', 'Thérapie Taxi', 'Tradition kchanaises', 'Troisième mi-temps', 'Turn up',
|
||||
'Vodka', 'Vodka pomme', 'Volley', 'Vomi stratégique'
|
||||
]
|
||||
|
||||
|
||||
|
391
apps/wei/forms/surveys/wei2023.py
Normal file
391
apps/wei/forms/surveys/wei2023.py
Normal file
@@ -0,0 +1,391 @@
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from functools import lru_cache
|
||||
|
||||
from django import forms
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
|
||||
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
|
||||
from ...models import WEIMembership
|
||||
|
||||
WORDS = {
|
||||
"ambiance": ["Ambiance de bus :", {
|
||||
1: "Ambiance calme et posée",
|
||||
2: "Ambiance rigolage entre copaing",
|
||||
3: "Ambiance danse de camping autour d'une piscine inexistante",
|
||||
4: "Grosse soirée avec de la musique qui fait bouger",
|
||||
5: "On retourne le camping et le bus (dans le respect et le savoir vivre)"
|
||||
}],
|
||||
"musique": ["Musique :", {
|
||||
1: "Musique tranquille",
|
||||
2: "Musique commerciale",
|
||||
3: "Chansons paillardes",
|
||||
4: "Musique de Colonie de vacances",
|
||||
5: "Grosse techno"
|
||||
}],
|
||||
"boisson": ["Boissons :", {
|
||||
1: "Boisson soft",
|
||||
2: "Des cocktails de temps en temps",
|
||||
3: "Des coktails fancy de pétasse (parce que c'est les meilleurs)",
|
||||
4: "Bière !",
|
||||
5: "L'alcool c'est dans les céréales"
|
||||
}],
|
||||
"beauferie": ["Échelle de la beauferie :", {
|
||||
1: "Je suis toujours classe",
|
||||
2: "Je rote de temps en temps",
|
||||
3: "Claquette chaussette, c'est confortable",
|
||||
4: "L'aviron bayonnais est dans ma plyaylist",
|
||||
5: "Je suis champion⋅ne de concours de rots et d'éclatage de gobelet sur mon front"
|
||||
}],
|
||||
"sommeil": ["Échelle de ton sommeil pendant le WEI :", {
|
||||
1: "Dormir, c'est pour les faibles",
|
||||
2: "5h maximum",
|
||||
3: "10h",
|
||||
4: "15h",
|
||||
5: "Deux bonnes nuits de sommeil, c'est important pour être en forme pour les activités proposées par nos supers GC WEI"
|
||||
}],
|
||||
"vacances": ["Tes vacances de rêve :", {
|
||||
1: "Dans ma chambre",
|
||||
2: "Retourner chez popa et moman pour pouvoir enfin arrêter de manger des pasta box",
|
||||
3: "Être une grosse larve sous le soleil des troopiiiiiiiiques",
|
||||
4: "Faire un road trip camping sauvage, manger des racines et boire son pipi",
|
||||
5: "Le crime ne prend pas de vacances"
|
||||
}],
|
||||
"activite": ["T'as une heure de trou pendant ton WEI, que fais-tu ?", {
|
||||
1: "Je cherche des copaines pour faire un petit jeu de société",
|
||||
2: "Je cherche un moyen de me dépenser, n'importe quel ballon ferait l'affaire",
|
||||
3: "Je cherche un endroit où il y a de la musique pour bouger sur le dancefloor",
|
||||
4: "Petit apéro, petite pétanque avec les collègues autour d'un bon pastaga",
|
||||
5: "Je cherche une connerie à faire (mais pas trop méchante, pour ne pas embêter mes GC WEI préférés)"
|
||||
}],
|
||||
"hygiene": ["Échelle de ton hygiène :", {
|
||||
1: "La douche, c'eest tous les jours",
|
||||
2: "La règle des 2 jours, c'est un droit et un devoir",
|
||||
3: "Je ne me lave qu'après le sport",
|
||||
4: "« Ne vous inquiétez pas, je pue pas »",
|
||||
5: "Y a que les sales qui se lavent"
|
||||
}],
|
||||
"animal": ["Tu décrirais ton animal totem plutôt comme :", {
|
||||
1: "Un dragon qui raserait des villes entières d'un seul souffle",
|
||||
2: "Une mouette qui pique des frites aux dunkerquois",
|
||||
3: "Un poulpe tout meunion",
|
||||
4: "Un pitbull qui au fond cache un petit cœur en sucre",
|
||||
5: "Un canard en plastique au bord d'une baignoire qui n'a pas servi depuis 10 ans"
|
||||
}],
|
||||
"fensfoire": ["Quel est ton rapport à la F[ENS]foire ?", {
|
||||
1: "Je réveille les autres à 6h avec mon instrument",
|
||||
2: "Je la suis partout",
|
||||
3: "J'aime bien l'écouter de temps en temps",
|
||||
4: "Je mets des bouchons d'oreilles pour ne pas l'entendre",
|
||||
5: "La quoi ?"
|
||||
}],
|
||||
"kokarde": ["Qu'est-ce que le mot Kokarde t'évoque ?", {
|
||||
1: "Vraiment pas mon truc les soirées…",
|
||||
2: "Bof, je viens pour manger et je repars aussitôt",
|
||||
3: "Je kiffe, good vibes",
|
||||
4: "Perso, je ne m'arrêterai pas de danser sur la piste !",
|
||||
5: "J'resterai jusqu'à 3h ou rien"
|
||||
}],
|
||||
"copain": ["Qu'est-ce que tu fais avec un⋅e «copain⋅ine» ?", {
|
||||
1: "Je l'insulte de sale merde",
|
||||
2: "J'lui fais faire des trucs cons et je l'affiche !",
|
||||
3: "On parlerait ensemble et on se marrerait",
|
||||
4: "On aurait des vrais gros délires",
|
||||
5: "Je meurs pour lui/elle"
|
||||
}],
|
||||
"vie": ["Selon toi, qu'est-ce que la vie ?", {
|
||||
1: "La vie, cette sale race !",
|
||||
2: "Un moment paisible avant la mort",
|
||||
3: "C'est difficile à définir...",
|
||||
4: "En vrai, c'est cool !",
|
||||
5: "Une gigantestque tranche de kiff ! Et tous les jours, j'en mange un morceau"
|
||||
}],
|
||||
"jeux": ["Quel est ton rapport avec les jeux de société ?", {
|
||||
1: "éloigné",
|
||||
2: "nonchalant",
|
||||
3: "timide",
|
||||
4: "assumé",
|
||||
5: "sexuel"
|
||||
}],
|
||||
"calin": ["Qu'est-ce que tu penses des câlins ?", {
|
||||
1: "Jamais je n'en fais et jamais je n'en ferai !",
|
||||
2: "J'en fais mais ça ne me plaît pas",
|
||||
3: "J'en fais rarement mais c'est toujours cool",
|
||||
4: "J'en fais tous les jours avec mes ami⋅es !",
|
||||
5: "Je pourrais en faire à n'importe qui. Pourquoi ne pas créer le club Câl[ENS] ?"
|
||||
}],
|
||||
"vomi": ["Quel est ton rapport au vomi ?", {
|
||||
1: "C'est compliqué…",
|
||||
2: "Jamais je ne vomis mais je nettoie quand mes potes vomissent",
|
||||
3: "Jamais je ne vomis et jamais je ne nettoie celui de quelqu'un d'autre",
|
||||
4: "Je vomis quelquefois, ça arrive, faites pas cette tête, mais je fins toujours par nettoyer !",
|
||||
5: "Je vomis à chaque soirée et ce n'est jamais moi qui nettoie"
|
||||
}],
|
||||
"kfet": ["Qu'est ce que la Kfet t'évoque ?", {
|
||||
1: "La Kfet, quel lieu de dépravé⋅es sérieux…",
|
||||
2: "C'est un endroit à l'hygiène plus que douteuse…",
|
||||
3: "Téma les prix des boissons et des snacks, c'est aberrant !",
|
||||
4: "En vrai, c'est cool, petit billard, petit canapé, chill !",
|
||||
5: "Banger, j'y reste jusqu'à la fin de mes jours"
|
||||
}],
|
||||
"fatigue": ["Comment combattre la fatigue lors de ton WEI ?", {
|
||||
1: "Le sport en journée, ça réveille",
|
||||
2: "Le sucre du coca, ça réveille",
|
||||
3: "La taurine du Red Bull, ça réveille",
|
||||
4: "L'alcool dans le sang, ça réveille",
|
||||
5: "L'écocup sur le front, ça réveille"
|
||||
}],
|
||||
"duree trajet": ["Quelle serait ta durée de trajet préférée ?", {
|
||||
1: "Trajet instantané, pas le temps de niaiser",
|
||||
2: "1h, histoire de faire connaissance avec quelques personnes avant de se jeter sur les boissons",
|
||||
3: "3h, on peut vraiment parler et apprendre à connaître nos voisin⋅es",
|
||||
4: "6h, histoire d'avoir le temps de faire des conneries dans le bus pour bien se marrer !",
|
||||
5: "12h, il faut bien trouver un moment pour dormir, ce seront deux gros dodos dans un bus"
|
||||
}],
|
||||
"scolarite": ["Comment tu vois ton cursus à l'ENS ?", {
|
||||
1: "La tranquillité et le travail",
|
||||
2: "On va s'amuser tout en bossant",
|
||||
3: "Ça va profiter et réviser au dernier moment pour les exams…",
|
||||
4: "Nous festoierons sans songer aux conséquences",
|
||||
5: "Je ne vois qu'une seule issue : la débauche"
|
||||
}]
|
||||
}
|
||||
|
||||
|
||||
class WEISurveyForm2023(forms.Form):
|
||||
"""
|
||||
Survey form for the year 2023.
|
||||
Members answer 20 questions, from which we calculate the best associated bus.
|
||||
"""
|
||||
def set_registration(self, registration):
|
||||
"""
|
||||
Filter the bus selector with the buses of the current WEI.
|
||||
"""
|
||||
information = WEISurveyInformation2023(registration)
|
||||
|
||||
question = information.questions[information.step]
|
||||
self.fields[question] = forms.ChoiceField(
|
||||
label=WORDS[question][0],
|
||||
widget=forms.RadioSelect(),
|
||||
)
|
||||
answers = [(answer, WORDS[question][1][answer]) for answer in WORDS[question][1]]
|
||||
self.fields[question].choices = answers
|
||||
|
||||
|
||||
class WEIBusInformation2023(WEIBusInformation):
|
||||
"""
|
||||
For each question, the bus has ordered answers
|
||||
"""
|
||||
scores: dict
|
||||
|
||||
def __init__(self, bus):
|
||||
self.scores = {}
|
||||
for question in WORDS:
|
||||
self.scores[question] = []
|
||||
super().__init__(bus)
|
||||
|
||||
|
||||
class WEISurveyInformation2023(WEISurveyInformation):
|
||||
"""
|
||||
We store the id of the selected bus. We store only the name, but is not used in the selection:
|
||||
that's only for humans that try to read data.
|
||||
"""
|
||||
|
||||
step = 0
|
||||
questions = list(WORDS.keys())
|
||||
|
||||
def __init__(self, registration):
|
||||
for question in WORDS:
|
||||
setattr(self, str(question), None)
|
||||
super().__init__(registration)
|
||||
|
||||
|
||||
class WEISurvey2023(WEISurvey):
|
||||
"""
|
||||
Survey for the year 2023.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_year(cls):
|
||||
return 2023
|
||||
|
||||
@classmethod
|
||||
def get_survey_information_class(cls):
|
||||
return WEISurveyInformation2023
|
||||
|
||||
def get_form_class(self):
|
||||
return WEISurveyForm2023
|
||||
|
||||
def update_form(self, form):
|
||||
"""
|
||||
Filter the bus selector with the buses of the WEI.
|
||||
"""
|
||||
form.set_registration(self.registration)
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
self.information.step += 1
|
||||
for question in WORDS:
|
||||
if question in form.cleaned_data:
|
||||
answer = form.cleaned_data[question]
|
||||
setattr(self.information, question, answer)
|
||||
self.save()
|
||||
|
||||
@classmethod
|
||||
def get_algorithm_class(cls):
|
||||
return WEISurveyAlgorithm2023
|
||||
|
||||
def is_complete(self) -> bool:
|
||||
"""
|
||||
The survey is complete once the bus is chosen.
|
||||
"""
|
||||
for question in WORDS:
|
||||
if not getattr(self.information, question):
|
||||
return False
|
||||
return True
|
||||
|
||||
@lru_cache()
|
||||
def score(self, bus):
|
||||
if not self.is_complete():
|
||||
raise ValueError("Survey is not ended, can't calculate score")
|
||||
|
||||
bus_info = self.get_algorithm_class().get_bus_information(bus)
|
||||
# Score is the given score by the bus subtracted to the mid-score of the buses.
|
||||
s = 0
|
||||
for question in WORDS:
|
||||
s += bus_info.scores[question][str(getattr(self.information, question))]
|
||||
return s
|
||||
|
||||
@lru_cache()
|
||||
def scores_per_bus(self):
|
||||
return {bus: self.score(bus) for bus in self.get_algorithm_class().get_buses()}
|
||||
|
||||
@lru_cache()
|
||||
def ordered_buses(self):
|
||||
values = list(self.scores_per_bus().items())
|
||||
values.sort(key=lambda item: -item[1])
|
||||
return values
|
||||
|
||||
@classmethod
|
||||
def clear_cache(cls):
|
||||
return super().clear_cache()
|
||||
|
||||
|
||||
class WEISurveyAlgorithm2023(WEISurveyAlgorithm):
|
||||
"""
|
||||
The algorithm class for the year 2023.
|
||||
We use Gale-Shapley algorithm to attribute 1y students into buses.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def get_survey_class(cls):
|
||||
return WEISurvey2023
|
||||
|
||||
@classmethod
|
||||
def get_bus_information_class(cls):
|
||||
return WEIBusInformation2023
|
||||
|
||||
def run_algorithm(self, display_tqdm=False):
|
||||
"""
|
||||
Gale-Shapley algorithm implementation.
|
||||
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()] # Don't consider invalid surveys
|
||||
# Don't manage hardcoded people
|
||||
surveys = [s for s in surveys if not hasattr(s.information, 'hardcoded') or not s.information.hardcoded]
|
||||
|
||||
# Reset previous algorithm run
|
||||
for survey in surveys:
|
||||
survey.free()
|
||||
survey.save()
|
||||
|
||||
non_men = [s for s in surveys if s.registration.gender != 'male']
|
||||
men = [s for s in surveys if s.registration.gender == 'male']
|
||||
|
||||
quotas = {}
|
||||
registrations = self.get_registrations()
|
||||
non_men_total = registrations.filter(~Q(gender='male')).count()
|
||||
for bus in self.get_buses():
|
||||
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
|
||||
# Remove hardcoded people
|
||||
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
|
||||
registration__information_json__icontains="hardcoded").count()
|
||||
quotas[bus] = 4 + int(non_men_total / registrations.count() * free_seats)
|
||||
|
||||
tqdm_obj = None
|
||||
if display_tqdm:
|
||||
from tqdm import tqdm
|
||||
tqdm_obj = tqdm(total=len(non_men), desc="Non-hommes")
|
||||
|
||||
# Repartition for non men people first
|
||||
self.make_repartition(non_men, quotas, tqdm_obj=tqdm_obj)
|
||||
|
||||
quotas = {}
|
||||
for bus in self.get_buses():
|
||||
free_seats = bus.size - WEIMembership.objects.filter(bus=bus, registration__first_year=False).count()
|
||||
free_seats -= sum(1 for s in non_men if s.information.selected_bus_pk == bus.pk)
|
||||
# Remove hardcoded people
|
||||
free_seats -= WEIMembership.objects.filter(bus=bus, registration__first_year=True,
|
||||
registration__information_json__icontains="hardcoded").count()
|
||||
quotas[bus] = free_seats
|
||||
|
||||
if display_tqdm:
|
||||
tqdm_obj.close()
|
||||
|
||||
from tqdm import tqdm
|
||||
tqdm_obj = tqdm(total=len(men), desc="Hommes")
|
||||
|
||||
self.make_repartition(men, quotas, tqdm_obj=tqdm_obj)
|
||||
|
||||
if display_tqdm:
|
||||
tqdm_obj.close()
|
||||
|
||||
# Clear cache information after running algorithm
|
||||
WEISurvey2023.clear_cache()
|
||||
|
||||
def make_repartition(self, surveys, quotas=None, tqdm_obj=None):
|
||||
free_surveys = surveys.copy() # Remaining surveys
|
||||
while free_surveys: # Some students are not affected
|
||||
survey = free_surveys[0]
|
||||
buses = survey.ordered_buses() # Preferences of the student
|
||||
for bus, current_score in buses:
|
||||
if self.get_bus_information(bus).has_free_seats(surveys, quotas):
|
||||
# Selected bus has free places. Put student in the bus
|
||||
survey.select_bus(bus)
|
||||
survey.save()
|
||||
free_surveys.remove(survey)
|
||||
break
|
||||
else:
|
||||
# Current bus has not enough places. Remove the least preferred student from the bus if existing
|
||||
least_preferred_survey = None
|
||||
least_score = -1
|
||||
# Find the least student in the bus that has a lower score than the current student
|
||||
for survey2 in surveys:
|
||||
if not survey2.information.valid or survey2.information.get_selected_bus() != bus:
|
||||
continue
|
||||
score2 = survey2.score(bus)
|
||||
if current_score <= score2: # Ignore better students
|
||||
continue
|
||||
if least_preferred_survey is None or score2 < least_score:
|
||||
least_preferred_survey = survey2
|
||||
least_score = score2
|
||||
|
||||
if least_preferred_survey is not None:
|
||||
# Remove the least student from the bus and put the current student in.
|
||||
# If it does not exist, choose the next bus.
|
||||
least_preferred_survey.free()
|
||||
least_preferred_survey.save()
|
||||
free_surveys.append(least_preferred_survey)
|
||||
survey.select_bus(bus)
|
||||
survey.save()
|
||||
free_surveys.remove(survey)
|
||||
break
|
||||
else:
|
||||
raise ValueError(f"User {survey.registration.user} has no free seat")
|
||||
|
||||
if tqdm_obj is not None:
|
||||
tqdm_obj.n = len(surveys) - len(free_surveys)
|
||||
tqdm_obj.refresh()
|
18
apps/wei/migrations/0004_auto_20220904_2325.py
Normal file
18
apps/wei/migrations/0004_auto_20220904_2325.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.26 on 2022-09-04 21:25
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wei', '0003_bus_size'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='weiclub',
|
||||
name='year',
|
||||
field=models.PositiveIntegerField(default=2022, unique=True, verbose_name='year'),
|
||||
),
|
||||
]
|
18
apps/wei/migrations/0005_auto_20230128_1850.py
Normal file
18
apps/wei/migrations/0005_auto_20230128_1850.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2023-01-28 17:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wei', '0004_auto_20220904_2325'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='weiclub',
|
||||
name='year',
|
||||
field=models.PositiveIntegerField(default=2023, unique=True, verbose_name='year'),
|
||||
),
|
||||
]
|
18
apps/wei/migrations/0006_unisex_clothing_cut.py
Normal file
18
apps/wei/migrations/0006_unisex_clothing_cut.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2023-07-09 09:51
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wei', '0005_auto_20230128_1850'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='weiregistration',
|
||||
name='clothing_cut',
|
||||
field=models.CharField(choices=[('male', 'Male'), ('female', 'Female'), ('unisex', 'Unisex')], default='unisex', max_length=16, verbose_name='clothing cut'),
|
||||
),
|
||||
]
|
18
apps/wei/migrations/0007_help_text_emergency_contact.py
Normal file
18
apps/wei/migrations/0007_help_text_emergency_contact.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2023-07-09 12:46
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wei', '0006_unisex_clothing_cut'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='weiregistration',
|
||||
name='emergency_contact_name',
|
||||
field=models.CharField(help_text='The emergency contact must not be a WEI participant', max_length=255, verbose_name='emergency contact name'),
|
||||
),
|
||||
]
|
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
@@ -209,7 +209,9 @@ class WEIRegistration(models.Model):
|
||||
choices=(
|
||||
('male', _("Male")),
|
||||
('female', _("Female")),
|
||||
('unisex', _("Unisex")),
|
||||
),
|
||||
default='unisex',
|
||||
verbose_name=_("clothing cut"),
|
||||
)
|
||||
|
||||
@@ -235,6 +237,7 @@ class WEIRegistration(models.Model):
|
||||
emergency_contact_name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("emergency contact name"),
|
||||
help_text=_("The emergency contact must not be a WEI participant")
|
||||
)
|
||||
|
||||
emergency_contact_phone = PhoneNumberField(
|
||||
|
@@ -29,7 +29,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-bus"></i> {% trans "Teams" %}
|
||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
||||
</svg>
|
||||
{% trans "Teams" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table teams %}
|
||||
@@ -42,7 +45,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-bus"></i> {% trans "Members" %}
|
||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
||||
</svg>
|
||||
{% trans "Members" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table memberships %}
|
||||
@@ -51,7 +57,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<hr>
|
||||
|
||||
<a href="{% url 'wei:wei_memberships_bus_pdf' wei_pk=club.pk bus_pk=object.pk %}" data-turbolinks="false">
|
||||
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||
<button class="btn btn-block btn-danger">
|
||||
<svg class="bi bi-file-pdf" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M5.523 12.424c.14-.082.293-.162.459-.238a7.878 7.878 0 0 1-.45.606c-.28.337-.498.516-.635.572a.266.266 0 0 1-.035.012.282.282 0 0 1-.026-.044c-.056-.11-.054-.216.04-.36.106-.165.319-.354.647-.548zm2.455-1.647c-.119.025-.237.05-.356.078a21.148 21.148 0 0 0 .5-1.05 12.045 12.045 0 0 0 .51.858c-.217.032-.436.07-.654.114zm2.525.939a3.881 3.881 0 0 1-.435-.41c.228.005.434.022.612.054.317.057.466.147.518.209a.095.095 0 0 1 .026.064.436.436 0 0 1-.06.2.307.307 0 0 1-.094.124.107.107 0 0 1-.069.015c-.09-.003-.258-.066-.498-.256zM8.278 6.97c-.04.244-.108.524-.2.829a4.86 4.86 0 0 1-.089-.346c-.076-.353-.087-.63-.046-.822.038-.177.11-.248.196-.283a.517.517 0 0 1 .145-.04c.013.03.028.092.032.198.005.122-.007.277-.038.465z"/>
|
||||
<path fill-rule="evenodd" d="M4 0h5.293A1 1 0 0 1 10 .293L13.707 4a1 1 0 0 1 .293.707V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm5.5 1.5v2a1 1 0 0 0 1 1h2l-3-3zM4.165 13.668c.09.18.23.343.438.419.207.075.412.04.58-.03.318-.13.635-.436.926-.786.333-.401.683-.927 1.021-1.51a11.651 11.651 0 0 1 1.997-.406c.3.383.61.713.91.95.28.22.603.403.934.417a.856.856 0 0 0 .51-.138c.155-.101.27-.247.354-.416.09-.181.145-.37.138-.563a.844.844 0 0 0-.2-.518c-.226-.27-.596-.4-.96-.465a5.76 5.76 0 0 0-1.335-.05 10.954 10.954 0 0 1-.98-1.686c.25-.66.437-1.284.52-1.794.036-.218.055-.426.048-.614a1.238 1.238 0 0 0-.127-.538.7.7 0 0 0-.477-.365c-.202-.043-.41 0-.601.077-.377.15-.576.47-.651.823-.073.34-.04.736.046 1.136.088.406.238.848.43 1.295a19.697 19.697 0 0 1-1.062 2.227 7.662 7.662 0 0 0-1.482.645c-.37.22-.699.48-.897.787-.21.326-.275.714-.08 1.103z"/>
|
||||
</svg>
|
||||
{% trans "View as PDF" %}
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@@ -47,7 +47,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="font-weight-bold">
|
||||
<i class="fa fa-bus"></i> {% trans "Teams" %}
|
||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
||||
</svg>
|
||||
{% trans "Teams" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table memberships %}
|
||||
@@ -57,7 +60,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
<a href="{% url 'wei:wei_memberships_team_pdf' wei_pk=club.pk bus_pk=object.bus.pk team_pk=object.pk %}"
|
||||
data-turbolinks="false">
|
||||
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||
<button class="btn btn-block btn-danger">
|
||||
<svg class="bi bi-file-pdf" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M5.523 12.424c.14-.082.293-.162.459-.238a7.878 7.878 0 0 1-.45.606c-.28.337-.498.516-.635.572a.266.266 0 0 1-.035.012.282.282 0 0 1-.026-.044c-.056-.11-.054-.216.04-.36.106-.165.319-.354.647-.548zm2.455-1.647c-.119.025-.237.05-.356.078a21.148 21.148 0 0 0 .5-1.05 12.045 12.045 0 0 0 .51.858c-.217.032-.436.07-.654.114zm2.525.939a3.881 3.881 0 0 1-.435-.41c.228.005.434.022.612.054.317.057.466.147.518.209a.095.095 0 0 1 .026.064.436.436 0 0 1-.06.2.307.307 0 0 1-.094.124.107.107 0 0 1-.069.015c-.09-.003-.258-.066-.498-.256zM8.278 6.97c-.04.244-.108.524-.2.829a4.86 4.86 0 0 1-.089-.346c-.076-.353-.087-.63-.046-.822.038-.177.11-.248.196-.283a.517.517 0 0 1 .145-.04c.013.03.028.092.032.198.005.122-.007.277-.038.465z"/>
|
||||
<path fill-rule="evenodd" d="M4 0h5.293A1 1 0 0 1 10 .293L13.707 4a1 1 0 0 1 .293.707V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm5.5 1.5v2a1 1 0 0 0 1 1h2l-3-3zM4.165 13.668c.09.18.23.343.438.419.207.075.412.04.58-.03.318-.13.635-.436.926-.786.333-.401.683-.927 1.021-1.51a11.651 11.651 0 0 1 1.997-.406c.3.383.61.713.91.95.28.22.603.403.934.417a.856.856 0 0 0 .51-.138c.155-.101.27-.247.354-.416.09-.181.145-.37.138-.563a.844.844 0 0 0-.2-.518c-.226-.27-.596-.4-.96-.465a5.76 5.76 0 0 0-1.335-.05 10.954 10.954 0 0 1-.98-1.686c.25-.66.437-1.284.52-1.794.036-.218.055-.426.048-.614a1.238 1.238 0 0 0-.127-.538.7.7 0 0 0-.477-.365c-.202-.043-.41 0-.601.077-.377.15-.576.47-.651.823-.073.34-.04.736.046 1.136.088.406.238.848.43 1.295a19.697 19.697 0 0 1-1.062 2.227 7.662 7.662 0 0 0-1.482.645c-.37.22-.699.48-.897.787-.21.326-.275.714-.08 1.103z"/>
|
||||
</svg>
|
||||
{% trans "View as PDF" %}
|
||||
</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endblock %}
|
@@ -48,7 +48,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card bg-white mb-3">
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<span class="font-weight-bold">
|
||||
<i class="fa fa-bus"></i> {% trans "Buses" %}
|
||||
<svg class="bi bi-signpost" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M7.293.707A1 1 0 0 0 7 1.414V4H2a1 1 0 0 0-1 1v4a1 1 0 0 0 1 1h5v6h2v-6h3.532a1 1 0 0 0 .768-.36l1.933-2.32a.5.5 0 0 0 0-.64L13.3 4.36a1 1 0 0 0-.768-.36H9V1.414A1 1 0 0 0 7.293.707z"/>
|
||||
</svg>
|
||||
{% trans "Buses" %}
|
||||
</span>
|
||||
</div>
|
||||
{% render_table buses %}
|
||||
@@ -60,7 +63,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-header position-relative" id="clubListHeading">
|
||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||
href="{% url "wei:wei_memberships" pk=club.pk %}">
|
||||
<i class="fa fa-users"></i> {% trans "Members of the WEI" %}
|
||||
<svg class="bi bi-users" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1H7zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
<path fill-rule="evenodd" d="M5.216 14A2.238 2.238 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.325 6.325 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1h4.216z"/>
|
||||
<path d="M4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5z"/>
|
||||
</svg>
|
||||
{% trans "Members of the WEI" %}
|
||||
</a>
|
||||
</div>
|
||||
{% render_table member_list %}
|
||||
@@ -72,7 +80,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-header position-relative" id="historyListHeading">
|
||||
<a class="stretched-link font-weight-bold text-decoration-none" {% if "note.view_note"|has_perm:club.note %}
|
||||
href="{% url 'note:transactions' pk=club.note.pk %}" {% endif %}>
|
||||
<i class="fa fa-euro"></i> {% trans "Transaction history" %}
|
||||
<svg class="bi bi-euro" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M4 9.42h1.063C5.4 12.323 7.317 14 10.34 14c.622 0 1.167-.068 1.659-.185v-1.3c-.484.119-1.045.17-1.659.17-2.1 0-3.455-1.198-3.775-3.264h4.017v-.928H6.497v-.936c0-.11 0-.219.008-.329h4.078v-.927H6.618c.388-1.898 1.719-2.985 3.723-2.985.614 0 1.175.05 1.659.177V2.194A6.617 6.617 0 0 0 10.341 2c-2.928 0-4.82 1.569-5.244 4.3H4v.928h1.01v1.265H4v.928z"/>
|
||||
</svg>
|
||||
{% trans "Transaction history" %}
|
||||
</a>
|
||||
</div>
|
||||
<div id="history_list">
|
||||
@@ -86,7 +97,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<div class="card-header position-relative" id="historyListHeading">
|
||||
<a class="stretched-link font-weight-bold text-decoration-none"
|
||||
href="{% url 'wei:wei_registrations' pk=club.pk %}">
|
||||
<i class="fa fa-user-plus"></i> {% trans "Unvalidated registrations" %}
|
||||
<svg class="bi bi-user-plus" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M1 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
<path fill-rule="evenodd" d="M13.5 5a.5.5 0 0 1 .5.5V7h1.5a.5.5 0 0 1 0 1H14v1.5a.5.5 0 0 1-1 0V8h-1.5a.5.5 0 0 1 0-1H13V5.5a.5.5 0 0 1 .5-.5z"/>
|
||||
</svg>
|
||||
{% trans "Unvalidated registrations" %}
|
||||
</a>
|
||||
</div>
|
||||
<div id="history_list">
|
||||
|
@@ -56,7 +56,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<dd class="col-xl-6">{{ registration.get_gender_display }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'clothing cut'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ registration.clothing_cut }}</dd>
|
||||
<dd class="col-xl-6">{{ registration.get_clothing_cut_display }}</dd>
|
||||
|
||||
<dt class="col-xl-6">{% trans 'clothing size'|capfirst %}</dt>
|
||||
<dd class="col-xl-6">{{ registration.clothing_size }}</dd>
|
||||
|
@@ -28,7 +28,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</a>
|
||||
<hr>
|
||||
<a href="{% url 'wei:wei_memberships_pdf' wei_pk=club.pk %}" data-turbolinks="false">
|
||||
<button class="btn btn-block btn-danger"><i class="fa fa-file-pdf-o"></i> {% trans "View as PDF" %}</button>
|
||||
<button class="btn btn-block btn-danger">
|
||||
<svg class="bi bi-file-pdf" xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
||||
<path d="M5.523 12.424c.14-.082.293-.162.459-.238a7.878 7.878 0 0 1-.45.606c-.28.337-.498.516-.635.572a.266.266 0 0 1-.035.012.282.282 0 0 1-.026-.044c-.056-.11-.054-.216.04-.36.106-.165.319-.354.647-.548zm2.455-1.647c-.119.025-.237.05-.356.078a21.148 21.148 0 0 0 .5-1.05 12.045 12.045 0 0 0 .51.858c-.217.032-.436.07-.654.114zm2.525.939a3.881 3.881 0 0 1-.435-.41c.228.005.434.022.612.054.317.057.466.147.518.209a.095.095 0 0 1 .026.064.436.436 0 0 1-.06.2.307.307 0 0 1-.094.124.107.107 0 0 1-.069.015c-.09-.003-.258-.066-.498-.256zM8.278 6.97c-.04.244-.108.524-.2.829a4.86 4.86 0 0 1-.089-.346c-.076-.353-.087-.63-.046-.822.038-.177.11-.248.196-.283a.517.517 0 0 1 .145-.04c.013.03.028.092.032.198.005.122-.007.277-.038.465z"/>
|
||||
<path fill-rule="evenodd" d="M4 0h5.293A1 1 0 0 1 10 .293L13.707 4a1 1 0 0 1 .293.707V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2zm5.5 1.5v2a1 1 0 0 0 1 1h2l-3-3zM4.165 13.668c.09.18.23.343.438.419.207.075.412.04.58-.03.318-.13.635-.436.926-.786.333-.401.683-.927 1.021-1.51a11.651 11.651 0 0 1 1.997-.406c.3.383.61.713.91.95.28.22.603.403.934.417a.856.856 0 0 0 .51-.138c.155-.101.27-.247.354-.416.09-.181.145-.37.138-.563a.844.844 0 0 0-.2-.518c-.226-.27-.596-.4-.96-.465a5.76 5.76 0 0 0-1.335-.05 10.954 10.954 0 0 1-.98-1.686c.25-.66.437-1.284.52-1.794.036-.218.055-.426.048-.614a1.238 1.238 0 0 0-.127-.538.7.7 0 0 0-.477-.365c-.202-.043-.41 0-.601.077-.377.15-.576.47-.651.823-.073.34-.04.736.046 1.136.088.406.238.848.43 1.295a19.697 19.697 0 0 1-1.062 2.227 7.662 7.662 0 0 0-1.482.645c-.37.22-.699.48-.897.787-.21.326-.275.714-.08 1.103z"/>
|
||||
</svg>
|
||||
{% trans "View as PDF" %}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
170
apps/wei/tests/test_wei_algorithm_2023.py
Normal file
170
apps/wei/tests/test_wei_algorithm_2023.py
Normal file
@@ -0,0 +1,170 @@
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import random
|
||||
from datetime import date, timedelta
|
||||
|
||||
from django.contrib.auth.models import User
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from note.models import NoteUser
|
||||
|
||||
from ..forms.surveys.wei2023 import WEIBusInformation2023, WEISurvey2023, WORDS, WEISurveyInformation2023
|
||||
from ..models import Bus, WEIClub, WEIRegistration
|
||||
|
||||
|
||||
class TestWEIAlgorithm(TestCase):
|
||||
"""
|
||||
Run some tests to ensure that the WEI algorithm is working well.
|
||||
"""
|
||||
fixtures = ('initial',)
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Create some test data, with one WEI and 10 buses with random score attributions.
|
||||
"""
|
||||
self.user = User.objects.create_superuser(
|
||||
username="weiadmin",
|
||||
password="admin",
|
||||
email="admin@example.com",
|
||||
)
|
||||
self.user.save()
|
||||
self.client.force_login(self.user)
|
||||
sess = self.client.session
|
||||
sess["permission_mask"] = 42
|
||||
sess.save()
|
||||
|
||||
self.wei = WEIClub.objects.create(
|
||||
name="WEI 2023",
|
||||
email="wei2023@example.com",
|
||||
parent_club_id=2,
|
||||
membership_fee_paid=12500,
|
||||
membership_fee_unpaid=5500,
|
||||
membership_start='2023-01-01',
|
||||
membership_end='2023-12-31',
|
||||
date_start=date.today() + timedelta(days=2),
|
||||
date_end='2023-12-31',
|
||||
year=2023,
|
||||
)
|
||||
|
||||
self.buses = []
|
||||
for i in range(10):
|
||||
bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10)
|
||||
self.buses.append(bus)
|
||||
information = WEIBusInformation2023(bus)
|
||||
for question in WORDS:
|
||||
information.scores[question] = {answer: random.randint(1, 5) for answer in WORDS[question][1]}
|
||||
information.save()
|
||||
bus.save()
|
||||
|
||||
def test_survey_algorithm_small(self):
|
||||
"""
|
||||
There are only a few people in each bus, ensure that each person has its best bus
|
||||
"""
|
||||
# Add a few users
|
||||
for i in range(10):
|
||||
user = User.objects.create(username=f"user{i}")
|
||||
registration = WEIRegistration.objects.create(
|
||||
user=user,
|
||||
wei=self.wei,
|
||||
first_year=True,
|
||||
birth_date='2000-01-01',
|
||||
)
|
||||
information = WEISurveyInformation2023(registration)
|
||||
for question in WORDS:
|
||||
setattr(information, question, random.randint(1, 5))
|
||||
information.step = 20
|
||||
information.save(registration)
|
||||
registration.save()
|
||||
|
||||
# Run algorithm
|
||||
WEISurvey2023.get_algorithm_class()().run_algorithm()
|
||||
|
||||
# Ensure that everyone has its first choice
|
||||
for r in WEIRegistration.objects.filter(wei=self.wei).all():
|
||||
survey = WEISurvey2023(r)
|
||||
preferred_bus = survey.ordered_buses()[0][0]
|
||||
chosen_bus = survey.information.get_selected_bus()
|
||||
self.assertEqual(preferred_bus, chosen_bus)
|
||||
|
||||
def test_survey_algorithm_full(self):
|
||||
"""
|
||||
Buses are full of first year people, ensure that they are happy
|
||||
"""
|
||||
# Add a lot of users
|
||||
for i in range(95):
|
||||
user = User.objects.create(username=f"user{i}")
|
||||
registration = WEIRegistration.objects.create(
|
||||
user=user,
|
||||
wei=self.wei,
|
||||
first_year=True,
|
||||
birth_date='2000-01-01',
|
||||
)
|
||||
information = WEISurveyInformation2023(registration)
|
||||
for question in WORDS:
|
||||
setattr(information, question, random.randint(1, 5))
|
||||
information.step = 20
|
||||
information.save(registration)
|
||||
registration.save()
|
||||
|
||||
# Run algorithm
|
||||
WEISurvey2023.get_algorithm_class()().run_algorithm()
|
||||
|
||||
penalty = 0
|
||||
# Ensure that everyone seems to be happy
|
||||
# We attribute a penalty for each user that didn't have its first choice
|
||||
# The penalty is the square of the distance between the score of the preferred bus
|
||||
# and the score of the attributed bus
|
||||
# We consider it acceptable if the mean of this distance is lower than 5 %
|
||||
for r in WEIRegistration.objects.filter(wei=self.wei).all():
|
||||
survey = WEISurvey2023(r)
|
||||
chosen_bus = survey.information.get_selected_bus()
|
||||
buses = survey.ordered_buses()
|
||||
score = min(v for bus, v in buses if bus == chosen_bus)
|
||||
max_score = buses[0][1]
|
||||
penalty += (max_score - score) ** 2
|
||||
|
||||
self.assertLessEqual(max_score - score, 25) # Always less than 25 % of tolerance
|
||||
|
||||
self.assertLessEqual(penalty / 100, 25) # Tolerance of 5 %
|
||||
|
||||
def test_register_1a(self):
|
||||
"""
|
||||
Test register a first year member to the WEI and complete the survey
|
||||
"""
|
||||
response = self.client.get(reverse("wei:wei_register_1A", 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,
|
||||
birth_date=date(2000, 1, 1),
|
||||
gender='nonbinary',
|
||||
clothing_cut='female',
|
||||
clothing_size='XS',
|
||||
health_issues='I am a bot',
|
||||
emergency_contact_name='NoteKfet2020',
|
||||
emergency_contact_phone='+33123456789',
|
||||
))
|
||||
qs = WEIRegistration.objects.filter(user_id=user.id)
|
||||
self.assertTrue(qs.exists())
|
||||
registration = qs.get()
|
||||
self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200)
|
||||
for question in WORDS:
|
||||
# Fill 1A Survey, 20 pages
|
||||
# be careful if questionnary form change (number of page, type of answer...)
|
||||
response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), {
|
||||
question: "1"
|
||||
})
|
||||
registration.refresh_from_db()
|
||||
survey = WEISurvey2023(registration)
|
||||
self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302,
|
||||
302 if survey.is_complete() else 200)
|
||||
self.assertIsNotNone(getattr(survey.information, question), "Survey page " + question + " failed")
|
||||
survey = WEISurvey2023(registration)
|
||||
self.assertTrue(survey.is_complete())
|
||||
survey.select_bus(self.buses[0])
|
||||
survey.save()
|
||||
self.assertIsNotNone(survey.information.get_selected_bus())
|
@@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import subprocess
|
||||
@@ -380,7 +380,7 @@ class TestWEIRegistration(TestCase):
|
||||
|
||||
def test_register_1a(self):
|
||||
"""
|
||||
Test register a first year member to the WEI and complete the survey.
|
||||
Test register a first year member to the WEI.
|
||||
"""
|
||||
response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@@ -402,21 +402,6 @@ class TestWEIRegistration(TestCase):
|
||||
self.assertTrue(qs.exists())
|
||||
registration = qs.get()
|
||||
self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200)
|
||||
for i in range(1, 21):
|
||||
# Fill 1A Survey, 20 pages
|
||||
response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), dict(
|
||||
word="Jus de fruit",
|
||||
))
|
||||
registration.refresh_from_db()
|
||||
survey = CurrentSurvey(registration)
|
||||
self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302,
|
||||
302 if survey.is_complete() else 200)
|
||||
self.assertIsNotNone(getattr(survey.information, "word" + str(i)), "Survey page #" + str(i) + " failed")
|
||||
survey = CurrentSurvey(registration)
|
||||
self.assertTrue(survey.is_complete())
|
||||
survey.select_bus(self.bus)
|
||||
survey.save()
|
||||
self.assertIsNotNone(survey.information.get_selected_bus())
|
||||
|
||||
# Check that the user can't be registered twice
|
||||
response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict(
|
||||
@@ -662,7 +647,7 @@ class TestWEIRegistration(TestCase):
|
||||
first_name="admin",
|
||||
bank="Société générale",
|
||||
))
|
||||
self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=self.registration.wei.pk)), 302, 200)
|
||||
self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200)
|
||||
# Check if the membership is successfully created
|
||||
membership = WEIMembership.objects.filter(user_id=self.user.id, club=self.wei)
|
||||
self.assertTrue(membership.exists())
|
||||
@@ -782,7 +767,7 @@ class TestDefaultWEISurvey(TestCase):
|
||||
WEISurvey.update_form(None, None)
|
||||
|
||||
self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey)
|
||||
self.assertEqual(CurrentSurvey.get_year(), 2022)
|
||||
self.assertEqual(CurrentSurvey.get_year(), 2023)
|
||||
|
||||
|
||||
class TestWeiAPI(TestAPI):
|
||||
|
@@ -969,7 +969,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
|
||||
if not registration.soge_credit and user.note.balance + credit_amount < fee:
|
||||
# Users must have money before registering to the WEI.
|
||||
form.add_error('bus',
|
||||
form.add_error('credit_type',
|
||||
_("This user don't have enough money to join this club, and can't have a negative balance."))
|
||||
return super().form_invalid(form)
|
||||
|
||||
@@ -1014,7 +1014,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
|
||||
def get_success_url(self):
|
||||
self.object.refresh_from_db()
|
||||
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.club.pk})
|
||||
return reverse_lazy("wei:wei_registrations", kwargs={"pk": self.object.club.pk})
|
||||
|
||||
|
||||
class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
|
||||
@@ -1084,7 +1084,44 @@ class WEISurveyEndView(LoginRequiredMixin, TemplateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["club"] = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei
|
||||
club = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei
|
||||
context["club"] = club
|
||||
|
||||
random_user = User.objects.filter(~Q(wei__wei__in=[club])).first()
|
||||
|
||||
if random_user is None:
|
||||
# This case occurs when all users are registered to the WEI.
|
||||
# Don't worry, Pikachu never went to the WEI.
|
||||
# This bug can arrive only in dev mode.
|
||||
context["can_add_first_year_member"] = True
|
||||
context["can_add_any_member"] = True
|
||||
else:
|
||||
# Check if the user has the right to create a registration of a random first year member.
|
||||
empty_fy_registration = WEIRegistration(
|
||||
wei=club,
|
||||
user=random_user,
|
||||
first_year=True,
|
||||
birth_date="1970-01-01",
|
||||
gender="No",
|
||||
emergency_contact_name="No",
|
||||
emergency_contact_phone="No",
|
||||
)
|
||||
context["can_add_first_year_member"] = PermissionBackend \
|
||||
.check_perm(self.request, "wei.add_weiregistration", empty_fy_registration)
|
||||
|
||||
# Check if the user has the right to create a registration of a random old member.
|
||||
empty_old_registration = WEIRegistration(
|
||||
wei=club,
|
||||
user=User.objects.filter(~Q(wei__wei__in=[club])).first(),
|
||||
first_year=False,
|
||||
birth_date="1970-01-01",
|
||||
gender="No",
|
||||
emergency_contact_name="No",
|
||||
emergency_contact_phone="No",
|
||||
)
|
||||
context["can_add_any_member"] = PermissionBackend \
|
||||
.check_perm(self.request, "wei.add_weiregistration", empty_old_registration)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
@@ -448,6 +448,10 @@ Options
|
||||
"value": "female",
|
||||
"display_name": "Femme"
|
||||
}
|
||||
{
|
||||
"value": "unisex",
|
||||
"display_name": "Unisexe"
|
||||
},
|
||||
]
|
||||
},
|
||||
"clothing_size": {
|
||||
|
@@ -118,13 +118,13 @@ Exemples
|
||||
{"F": [
|
||||
"ADD",
|
||||
["F", "source__balance"],
|
||||
5000]
|
||||
2000]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
| si la destination est la note du club dont on est membre et si le montant est inférieur au solde de la source + 50 €,
|
||||
autrement dit le solde final est au-dessus de -50 €.
|
||||
| si la destination est la note du club dont on est membre et si le montant est inférieur au solde de la source + 20 €,
|
||||
autrement dit le solde final est au-dessus de -20 €.
|
||||
|
||||
|
||||
Masques de permissions
|
||||
|
@@ -83,13 +83,6 @@ Je suis trésorier d'un club, qu'ai-je le droit de faire ?
|
||||
bien sûr permis pour faciliter des transferts. Tout abus de droits constaté
|
||||
pourra mener à des sanctions prises par le bureau du BDE.
|
||||
|
||||
.. warning::
|
||||
Une fonctionnalité pour permettre de gérer plus proprement les remboursements
|
||||
entre amis est en cours de développement. Temporairement et pour des raisons
|
||||
de confort, les trésoriers de clubs ont le droit de prélever n'importe quelle
|
||||
adhérente vers n'importe quelle autre note adhérente, tant que la source ne
|
||||
descend pas sous ``- 50 €``. Ces droits seront retirés d'ici quelques semaines.
|
||||
|
||||
|
||||
Je suis trésorier d'un club, je n'arrive pas à voir le solde du club / faire des transactions
|
||||
---------------------------------------------------------------------------------------------------
|
||||
|
@@ -23,7 +23,7 @@ Sur un Ubuntu/Debian :
|
||||
$ sudo apt update
|
||||
$ sudo apt install --no-install-recommends -y \
|
||||
python3-setuptools python3-venv python3-dev \
|
||||
texlive-xetex gettext libjs-bootstrap4 fonts-font-awesome git
|
||||
texlive-xetex gettext libjs-bootstrap4 git
|
||||
|
||||
Pour Arch Linux :
|
||||
|
||||
|
@@ -62,7 +62,7 @@ plus propre. On peut donc installer tout ce dont on a besoin, depuis buster-back
|
||||
$ sudo apt update
|
||||
$ sudo apt install -t buster-backports --no-install-recommends \
|
||||
gettext git ipython3 \ # Dépendances basiques
|
||||
fonts-font-awesome libjs-bootstrap4 \ # Pour l'affichage web
|
||||
libjs-bootstrap4 \ # Pour l'affichage web
|
||||
python3-bs4 python3-django python3-django-crispy-forms python3-django-extensions \
|
||||
python3-django-filters python3-django-oauth-toolkit python3-django-polymorphic \
|
||||
python3-djangorestframework python3-memcache python3-phonenumbers \
|
||||
@@ -615,7 +615,7 @@ pas déjà fait par créer un utilisateur sur les deux serveurs :
|
||||
|
||||
.. code:: bash
|
||||
|
||||
ynerant@bde-note:~$ sudo -u postgres createuser -l ynerant
|
||||
ynerant@bde-note:~$ sudo -u postgres createuser -s ynerant
|
||||
|
||||
On réinitialise **sur le serveur de développement** la base de données présente, en
|
||||
éteignant tout d'abord le serveur Web :
|
||||
|
@@ -7,9 +7,9 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-04-10 22:34+0200\n"
|
||||
"POT-Creation-Date: 2023-09-18 17:27+0200\n"
|
||||
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
|
||||
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
||||
"Last-Translator: bleizi <bleizi@crans.org>\n"
|
||||
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20/de/>\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
@@ -53,7 +53,7 @@ msgid "You can't invite more than 3 people to this activity."
|
||||
msgstr "Sie dürfen höchstens 3 Leute zu dieser Veranstaltung einladen."
|
||||
|
||||
#: apps/activity/models.py:28 apps/activity/models.py:63
|
||||
#: apps/member/models.py:199
|
||||
#: apps/member/models.py:204
|
||||
#: apps/member/templates/member/includes/club_info.html:4
|
||||
#: apps/member/templates/member/includes/profile_info.html:4
|
||||
#: apps/note/models/notes.py:263 apps/note/models/transactions.py:26
|
||||
@@ -114,8 +114,8 @@ msgstr "Wo findet die Veranstaltung statt ? (z.B Kfet)."
|
||||
msgid "type"
|
||||
msgstr "Type"
|
||||
|
||||
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:307
|
||||
#: apps/note/models/notes.py:148 apps/treasury/models.py:285
|
||||
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:312
|
||||
#: apps/note/models/notes.py:148 apps/treasury/models.py:287
|
||||
#: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13
|
||||
#: apps/wei/templates/wei/survey.html:15
|
||||
msgid "user"
|
||||
@@ -258,19 +258,19 @@ msgstr "Eingetreten um "
|
||||
msgid "remove"
|
||||
msgstr "entfernen"
|
||||
|
||||
#: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:199
|
||||
#: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:201
|
||||
msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#: apps/activity/tables.py:84 apps/member/forms.py:186
|
||||
#: apps/registration/forms.py:91 apps/treasury/forms.py:131
|
||||
#: apps/activity/tables.py:84 apps/member/forms.py:193
|
||||
#: apps/registration/forms.py:92 apps/treasury/forms.py:131
|
||||
#: apps/wei/forms/registration.py:104
|
||||
msgid "Last name"
|
||||
msgstr "Nachname"
|
||||
|
||||
#: apps/activity/tables.py:86 apps/member/forms.py:191
|
||||
#: apps/activity/tables.py:86 apps/member/forms.py:198
|
||||
#: apps/note/templates/note/transaction_form.html:138
|
||||
#: apps/registration/forms.py:96 apps/treasury/forms.py:133
|
||||
#: apps/registration/forms.py:97 apps/treasury/forms.py:133
|
||||
#: apps/wei/forms/registration.py:109
|
||||
msgid "First name"
|
||||
msgstr "Vorname"
|
||||
@@ -391,7 +391,7 @@ msgid "validate"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/templates/activity/includes/activity_info.html:71
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:220
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:260
|
||||
msgid "edit"
|
||||
msgstr "bearbeiten"
|
||||
|
||||
@@ -467,9 +467,9 @@ msgstr "neue Daten"
|
||||
msgid "create"
|
||||
msgstr "schaffen"
|
||||
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:166 apps/note/tables.py:190
|
||||
#: apps/note/tables.py:237 apps/permission/models.py:127
|
||||
#: apps/treasury/tables.py:38 apps/wei/tables.py:74
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277
|
||||
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
||||
#: apps/wei/tables.py:74
|
||||
msgid "delete"
|
||||
msgstr "entfernen"
|
||||
|
||||
@@ -498,21 +498,21 @@ msgstr "Changelogs"
|
||||
msgid "Changelog of type \"{action}\" for model {model} at {timestamp}"
|
||||
msgstr "Changelog \"{action}\" für Model {model} an {timestamp}"
|
||||
|
||||
#: apps/member/admin.py:50 apps/member/models.py:226
|
||||
#: apps/member/admin.py:50 apps/member/models.py:231
|
||||
#: apps/member/templates/member/includes/club_info.html:34
|
||||
msgid "membership fee (paid students)"
|
||||
msgstr "Mitgliedschaftpreis (bezahlte Studenten)"
|
||||
|
||||
#: apps/member/admin.py:51 apps/member/models.py:231
|
||||
#: apps/member/admin.py:51 apps/member/models.py:236
|
||||
#: apps/member/templates/member/includes/club_info.html:37
|
||||
msgid "membership fee (unpaid students)"
|
||||
msgstr "Mitgliedschaftpreis (unbezahlte Studenten)"
|
||||
|
||||
#: apps/member/admin.py:65 apps/member/models.py:319
|
||||
#: apps/member/admin.py:65 apps/member/models.py:324
|
||||
msgid "roles"
|
||||
msgstr "Rollen"
|
||||
|
||||
#: apps/member/admin.py:66 apps/member/models.py:333
|
||||
#: apps/member/admin.py:66 apps/member/models.py:338
|
||||
msgid "fee"
|
||||
msgstr "Preis"
|
||||
|
||||
@@ -532,65 +532,82 @@ msgstr "Bericht Frequenz"
|
||||
msgid "Last report date"
|
||||
msgstr "Letzen Bericht Datum"
|
||||
|
||||
#: apps/member/forms.py:52
|
||||
msgid ""
|
||||
"Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) charter read and approved"
|
||||
msgstr ""
|
||||
"Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) Charta gelesen und "
|
||||
"angenommen"
|
||||
|
||||
#: apps/member/forms.py:53
|
||||
msgid ""
|
||||
"Tick after having read and accepted the anti-VSS charter <a "
|
||||
"href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> "
|
||||
"available here in pdf</a>"
|
||||
msgstr ""
|
||||
"Kreuzen Sie an, nachdem Sie die Anti-VSS-Charta gelesen und akzeptiert "
|
||||
"haben, <a href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf "
|
||||
"target=_blank> die hier als pdf-Datei verfügbar ist</a>"
|
||||
|
||||
#: apps/member/forms.py:60
|
||||
msgid "You can't register to the note if you come from the future."
|
||||
msgstr "Sie dürfen nicht einloggen wenn sie aus der Zukunft kommen."
|
||||
|
||||
#: apps/member/forms.py:79
|
||||
#: apps/member/forms.py:86
|
||||
msgid "select an image"
|
||||
msgstr "Wählen sie ein Bild aus"
|
||||
|
||||
#: apps/member/forms.py:80
|
||||
#: apps/member/forms.py:87
|
||||
msgid "Maximal size: 2MB"
|
||||
msgstr "Maximal Größe: 2MB"
|
||||
|
||||
#: apps/member/forms.py:105
|
||||
#: apps/member/forms.py:112
|
||||
msgid "This image cannot be loaded."
|
||||
msgstr "Dieses Bild kann nicht geladen werden."
|
||||
|
||||
#: apps/member/forms.py:141 apps/member/views.py:103
|
||||
#: apps/registration/forms.py:33 apps/registration/views.py:262
|
||||
#: apps/member/forms.py:148 apps/member/views.py:102
|
||||
#: apps/registration/forms.py:34 apps/registration/views.py:266
|
||||
msgid "An alias with a similar name already exists."
|
||||
msgstr "Ein ähnliches Alias ist schon benutzt."
|
||||
|
||||
#: apps/member/forms.py:165 apps/registration/forms.py:71
|
||||
#: apps/member/forms.py:172
|
||||
msgid "Inscription paid by Société Générale"
|
||||
msgstr "Mitgliedschaft von der Société Générale bezahlt"
|
||||
|
||||
#: apps/member/forms.py:167 apps/registration/forms.py:73
|
||||
#: apps/member/forms.py:174
|
||||
msgid "Check this case if the Société Générale paid the inscription."
|
||||
msgstr "Die Société Générale die Mitgliedschaft bezahlt."
|
||||
|
||||
#: apps/member/forms.py:172 apps/registration/forms.py:78
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:79
|
||||
#: apps/wei/forms/registration.py:91
|
||||
msgid "Credit type"
|
||||
msgstr "Kredittype"
|
||||
|
||||
#: apps/member/forms.py:173 apps/registration/forms.py:79
|
||||
#: apps/member/forms.py:180 apps/registration/forms.py:80
|
||||
#: apps/wei/forms/registration.py:92
|
||||
msgid "No credit"
|
||||
msgstr "Kein Kredit"
|
||||
|
||||
#: apps/member/forms.py:175
|
||||
#: apps/member/forms.py:182
|
||||
msgid "You can credit the note of the user."
|
||||
msgstr "Sie dûrfen diese Note kreditieren."
|
||||
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:84
|
||||
#: apps/member/forms.py:186 apps/registration/forms.py:85
|
||||
#: apps/wei/forms/registration.py:97
|
||||
msgid "Credit amount"
|
||||
msgstr "Kreditanzahl"
|
||||
|
||||
#: apps/member/forms.py:196 apps/note/templates/note/transaction_form.html:144
|
||||
#: apps/registration/forms.py:101 apps/treasury/forms.py:135
|
||||
#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144
|
||||
#: apps/registration/forms.py:102 apps/treasury/forms.py:135
|
||||
#: apps/wei/forms/registration.py:114
|
||||
msgid "Bank"
|
||||
msgstr "Bank"
|
||||
|
||||
#: apps/member/forms.py:223
|
||||
#: apps/member/forms.py:230
|
||||
msgid "User"
|
||||
msgstr "User"
|
||||
|
||||
#: apps/member/forms.py:237
|
||||
#: apps/member/forms.py:244
|
||||
msgid "Roles"
|
||||
msgstr "Rollen"
|
||||
|
||||
@@ -777,15 +794,19 @@ msgstr "email bestätigt"
|
||||
msgid "registration valid"
|
||||
msgstr "Anmeldung gültig"
|
||||
|
||||
#: apps/member/models.py:162 apps/member/models.py:163
|
||||
#: apps/member/models.py:138
|
||||
msgid "VSS charter read"
|
||||
msgstr "VSS-Charta gelesen"
|
||||
|
||||
#: apps/member/models.py:167 apps/member/models.py:168
|
||||
msgid "user profile"
|
||||
msgstr "Userprofile"
|
||||
|
||||
#: apps/member/models.py:173
|
||||
#: apps/member/models.py:178
|
||||
msgid "Activate your Note Kfet account"
|
||||
msgstr "Ihre Note Kfet Konto bestätigen"
|
||||
|
||||
#: apps/member/models.py:204
|
||||
#: apps/member/models.py:209
|
||||
#: apps/member/templates/member/includes/club_info.html:55
|
||||
#: apps/member/templates/member/includes/profile_info.html:40
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:22
|
||||
@@ -794,88 +815,88 @@ msgstr "Ihre Note Kfet Konto bestätigen"
|
||||
msgid "email"
|
||||
msgstr "Email"
|
||||
|
||||
#: apps/member/models.py:211
|
||||
#: apps/member/models.py:216
|
||||
msgid "parent club"
|
||||
msgstr "Urclub"
|
||||
|
||||
#: apps/member/models.py:220
|
||||
#: apps/member/models.py:225
|
||||
msgid "require memberships"
|
||||
msgstr "erfordern Mitgliedschaft"
|
||||
|
||||
#: apps/member/models.py:221
|
||||
#: apps/member/models.py:226
|
||||
msgid "Uncheck if this club don't require memberships."
|
||||
msgstr ""
|
||||
"Deaktivieren Sie diese Option, wenn für diesen Club keine Mitgliedschaft "
|
||||
"erforderlich ist."
|
||||
|
||||
#: apps/member/models.py:237
|
||||
#: apps/member/models.py:242
|
||||
#: apps/member/templates/member/includes/club_info.html:26
|
||||
msgid "membership duration"
|
||||
msgstr "Mitgliedscahftzeit"
|
||||
|
||||
#: apps/member/models.py:238
|
||||
#: apps/member/models.py:243
|
||||
msgid "The longest time (in days) a membership can last (NULL = infinite)."
|
||||
msgstr "Wie lang am höchsten eine Mitgliedschaft dauern kann."
|
||||
|
||||
#: apps/member/models.py:245
|
||||
#: apps/member/models.py:250
|
||||
#: apps/member/templates/member/includes/club_info.html:16
|
||||
msgid "membership start"
|
||||
msgstr "Mitgliedschaftanfangsdatum"
|
||||
|
||||
#: apps/member/models.py:246
|
||||
#: apps/member/models.py:251
|
||||
msgid "Date from which the members can renew their membership."
|
||||
msgstr "Ab wann kann man sein Mitgliedschaft erneuern."
|
||||
|
||||
#: apps/member/models.py:252
|
||||
#: apps/member/models.py:257
|
||||
#: apps/member/templates/member/includes/club_info.html:21
|
||||
msgid "membership end"
|
||||
msgstr "Mitgliedschaftenddatum"
|
||||
|
||||
#: apps/member/models.py:253
|
||||
#: apps/member/models.py:258
|
||||
msgid "Maximal date of a membership, after which members must renew it."
|
||||
msgstr ""
|
||||
"Maximales Datum einer Mitgliedschaft, nach dem Mitglieder es erneuern müssen."
|
||||
|
||||
#: apps/member/models.py:288 apps/member/models.py:313
|
||||
#: apps/member/models.py:293 apps/member/models.py:318
|
||||
#: apps/note/models/notes.py:176
|
||||
msgid "club"
|
||||
msgstr "Club"
|
||||
|
||||
#: apps/member/models.py:289
|
||||
#: apps/member/models.py:294
|
||||
msgid "clubs"
|
||||
msgstr "Clubs"
|
||||
|
||||
#: apps/member/models.py:324
|
||||
#: apps/member/models.py:329
|
||||
msgid "membership starts on"
|
||||
msgstr "Mitgliedschaft fängt an"
|
||||
|
||||
#: apps/member/models.py:328
|
||||
#: apps/member/models.py:333
|
||||
msgid "membership ends on"
|
||||
msgstr "Mitgliedschaft endet am"
|
||||
|
||||
#: apps/member/models.py:430
|
||||
#: apps/member/models.py:435
|
||||
#, python-brace-format
|
||||
msgid "The role {role} does not apply to the club {club}."
|
||||
msgstr "Die Rolle {role} ist nicht erlaubt für das Club {club}."
|
||||
|
||||
#: apps/member/models.py:439 apps/member/views.py:712
|
||||
#: apps/member/models.py:444 apps/member/views.py:712
|
||||
msgid "User is already a member of the club"
|
||||
msgstr "User ist schon ein Mitglied dieser club"
|
||||
|
||||
#: apps/member/models.py:451 apps/member/views.py:721
|
||||
#: apps/member/models.py:456 apps/member/views.py:721
|
||||
msgid "User is not a member of the parent club"
|
||||
msgstr "User ist noch nicht Mitglied des Urclubs"
|
||||
|
||||
#: apps/member/models.py:504
|
||||
#: apps/member/models.py:509
|
||||
#, python-brace-format
|
||||
msgid "Membership of {user} for the club {club}"
|
||||
msgstr "Mitgliedschaft von {user} für das Club {club}"
|
||||
|
||||
#: apps/member/models.py:507 apps/note/models/transactions.py:389
|
||||
#: apps/member/models.py:512 apps/note/models/transactions.py:389
|
||||
msgid "membership"
|
||||
msgstr "Mitgliedschaft"
|
||||
|
||||
#: apps/member/models.py:508
|
||||
#: apps/member/models.py:513
|
||||
msgid "memberships"
|
||||
msgstr "Mitgliedschaften"
|
||||
|
||||
@@ -930,7 +951,7 @@ msgid "Account #"
|
||||
msgstr "Konto #"
|
||||
|
||||
#: apps/member/templates/member/base.html:48
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:60
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:59
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:48
|
||||
#: apps/wei/templates/wei/weimembership_form.html:117
|
||||
msgid "Update Profile"
|
||||
@@ -1172,8 +1193,8 @@ msgstr "Click hier um eine Bestätigunglinke zu schicken."
|
||||
msgid "View my memberships"
|
||||
msgstr "Meine Mitgliedschaften schauen"
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:10 apps/member/views.py:254
|
||||
msgid "Note friendships"
|
||||
#: apps/member/templates/member/profile_trust.html:10
|
||||
msgid "Add friends"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:28
|
||||
@@ -1185,6 +1206,10 @@ msgid ""
|
||||
"without needing additional rights among them."
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:39
|
||||
msgid "People having you as a friend"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/templates/member/profile_update.html:18
|
||||
msgid "Save Changes"
|
||||
msgstr "Speichern"
|
||||
@@ -1193,18 +1218,22 @@ msgstr "Speichern"
|
||||
msgid "Registrations"
|
||||
msgstr "Anmeldung"
|
||||
|
||||
#: apps/member/views.py:73 apps/registration/forms.py:23
|
||||
#: apps/member/views.py:72 apps/registration/forms.py:24
|
||||
msgid "This address must be valid."
|
||||
msgstr "Diese Adresse muss gültig sein."
|
||||
|
||||
#: apps/member/views.py:140
|
||||
#: apps/member/views.py:139
|
||||
msgid "Profile detail"
|
||||
msgstr "Profile detail"
|
||||
|
||||
#: apps/member/views.py:206
|
||||
#: apps/member/views.py:205
|
||||
msgid "Search user"
|
||||
msgstr "User finden"
|
||||
|
||||
#: apps/member/views.py:253
|
||||
msgid "Note friendships"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/views.py:308
|
||||
msgid "Update note picture"
|
||||
msgstr "Notebild ändern"
|
||||
@@ -1249,11 +1278,11 @@ msgstr "Die Mitgliedschaft muss nach {:%m-%d-Y} anfängen."
|
||||
msgid "The membership must begin before {:%m-%d-%Y}."
|
||||
msgstr "Die Mitgliedschaft muss vor {:%m-%d-Y} anfängen."
|
||||
|
||||
#: apps/member/views.py:876
|
||||
#: apps/member/views.py:880
|
||||
msgid "Manage roles of an user in the club"
|
||||
msgstr "Rollen in diesen Club bearbeiten"
|
||||
|
||||
#: apps/member/views.py:901
|
||||
#: apps/member/views.py:905
|
||||
msgid "Members of the club"
|
||||
msgstr "Mitlglieder dieses Club"
|
||||
|
||||
@@ -1271,7 +1300,13 @@ msgstr "Empfänger"
|
||||
msgid "amount"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: apps/note/api/serializers.py:199 apps/note/api/serializers.py:205
|
||||
#: apps/note/api/serializers.py:92
|
||||
#, fuzzy
|
||||
#| msgid "This credit is already validated."
|
||||
msgid "This friendship already exists"
|
||||
msgstr "Dieser Kredit ist bereits validiert."
|
||||
|
||||
#: apps/note/api/serializers.py:198 apps/note/api/serializers.py:204
|
||||
#: apps/note/models/transactions.py:228
|
||||
msgid ""
|
||||
"The transaction can't be saved since the source note or the destination note "
|
||||
@@ -1570,7 +1605,7 @@ msgstr "Sondertranskationen"
|
||||
msgid "membership transaction"
|
||||
msgstr "Mitgliedschafttransaktion"
|
||||
|
||||
#: apps/note/models/transactions.py:385 apps/treasury/models.py:292
|
||||
#: apps/note/models/transactions.py:385 apps/treasury/models.py:294
|
||||
msgid "membership transactions"
|
||||
msgstr "Mitgliedschaftttransaktionen"
|
||||
|
||||
@@ -1586,8 +1621,8 @@ msgstr "Klicken Sie zum gültigmachen"
|
||||
msgid "No reason specified"
|
||||
msgstr "Kein Grund gegeben"
|
||||
|
||||
#: apps/note/tables.py:173 apps/note/tables.py:194 apps/note/tables.py:239
|
||||
#: apps/treasury/tables.py:39
|
||||
#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234
|
||||
#: apps/note/tables.py:279 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:75 apps/wei/tables.py:118
|
||||
@@ -1598,7 +1633,17 @@ msgstr "Kein Grund gegeben"
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
|
||||
#: apps/note/tables.py:191
|
||||
msgid "Trust back"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/tables.py:211
|
||||
#, fuzzy
|
||||
#| msgid "Add bus"
|
||||
msgid "Add back"
|
||||
msgstr "Neue Bus"
|
||||
|
||||
#: apps/note/tables.py:262 apps/note/templates/note/conso_form.html:132
|
||||
#: apps/wei/tables.py:49 apps/wei/tables.py:50
|
||||
#: apps/wei/templates/wei/base.html:89
|
||||
#: apps/wei/templates/wei/bus_detail.html:20
|
||||
@@ -1608,7 +1653,7 @@ msgstr "Löschen"
|
||||
msgid "Edit"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: apps/note/tables.py:226 apps/note/tables.py:253
|
||||
#: apps/note/tables.py:266 apps/note/tables.py:293
|
||||
msgid "Hide/Show"
|
||||
msgstr ""
|
||||
|
||||
@@ -1689,7 +1734,7 @@ msgid "Amount"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: apps/note/templates/note/transaction_form.html:132
|
||||
#: apps/treasury/models.py:54
|
||||
#: apps/treasury/models.py:56
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
@@ -1860,7 +1905,7 @@ msgstr "Angabefeld gilt nur zum Anzeigen und Ändern von Berechtigungstypen."
|
||||
msgid "for club"
|
||||
msgstr "Für Club"
|
||||
|
||||
#: apps/permission/models.py:350 apps/permission/models.py:351
|
||||
#: apps/permission/models.py:351 apps/permission/models.py:352
|
||||
msgid "role permissions"
|
||||
msgstr "Berechtigung Rollen"
|
||||
|
||||
@@ -1982,29 +2027,15 @@ msgstr "Alle Rechten"
|
||||
msgid "registration"
|
||||
msgstr "Anmeldung"
|
||||
|
||||
#: apps/registration/forms.py:39
|
||||
#: apps/registration/forms.py:40
|
||||
msgid "This email address is already used."
|
||||
msgstr "Diese email adresse ist schon benutzt."
|
||||
|
||||
#: apps/registration/forms.py:49
|
||||
#, fuzzy
|
||||
#| msgid "You already opened an account in the Société générale."
|
||||
msgid ""
|
||||
"I declare that I opened or I will open soon a bank account in the Société "
|
||||
"générale with the BDE partnership."
|
||||
msgstr "Sie haben bereits ein Konto in der Société générale eröffnet."
|
||||
|
||||
#: apps/registration/forms.py:51
|
||||
msgid ""
|
||||
"Warning: this engages you to open your bank account. If you finally decides "
|
||||
"to don't open your account, you will have to pay the BDE membership."
|
||||
msgstr ""
|
||||
|
||||
#: apps/registration/forms.py:59
|
||||
#: apps/registration/forms.py:60
|
||||
msgid "Register to the WEI"
|
||||
msgstr "Zu WEI anmelden"
|
||||
|
||||
#: apps/registration/forms.py:61
|
||||
#: apps/registration/forms.py:62
|
||||
msgid ""
|
||||
"Check this case if you want to register to the WEI. If you hesitate, you "
|
||||
"will be able to register later, after validating your account in the Kfet."
|
||||
@@ -2013,14 +2044,18 @@ msgstr ""
|
||||
"falls Zweifel, können Sie sich später nach Bestätigung Ihres Kontos im Kfet "
|
||||
"registrieren."
|
||||
|
||||
#: apps/registration/forms.py:106
|
||||
#: apps/registration/forms.py:107
|
||||
msgid "Join BDE Club"
|
||||
msgstr "BDE Mitglieder werden"
|
||||
|
||||
#: apps/registration/forms.py:113
|
||||
#: apps/registration/forms.py:114
|
||||
msgid "Join Kfet Club"
|
||||
msgstr "Kfet Mitglieder werden"
|
||||
|
||||
#: apps/registration/forms.py:123
|
||||
msgid "Join BDA Club"
|
||||
msgstr "BDA Mitglieder werden"
|
||||
|
||||
#: apps/registration/templates/registration/email_validation_complete.html:15
|
||||
msgid "Your email have successfully been validated."
|
||||
msgstr "Ihre E-Mail wurde erfolgreich validiert."
|
||||
@@ -2069,14 +2104,14 @@ msgstr "Registrierung löschen"
|
||||
msgid "Validate account"
|
||||
msgstr "Konto validieren"
|
||||
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:62
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:63
|
||||
#, fuzzy
|
||||
#| msgid "You already opened an account in the Société générale."
|
||||
msgid ""
|
||||
"The user declared that he/she opened a bank account in the Société générale."
|
||||
msgstr "Sie haben bereits ein Konto in der Société générale eröffnet."
|
||||
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:71
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:73
|
||||
#: apps/wei/templates/wei/weimembership_form.html:127
|
||||
#: apps/wei/templates/wei/weimembership_form.html:186
|
||||
msgid "Validate registration"
|
||||
@@ -2128,54 +2163,54 @@ msgstr "Danke"
|
||||
msgid "The Note Kfet team."
|
||||
msgstr "Die NoteKfet Team."
|
||||
|
||||
#: apps/registration/views.py:40
|
||||
#: apps/registration/views.py:41
|
||||
msgid "Register new user"
|
||||
msgstr "Neuen User registrieren"
|
||||
|
||||
#: apps/registration/views.py:98
|
||||
#: apps/registration/views.py:99
|
||||
msgid "Email validation"
|
||||
msgstr "Email validierung"
|
||||
|
||||
#: apps/registration/views.py:100
|
||||
#: apps/registration/views.py:101
|
||||
msgid "Validate email"
|
||||
msgstr "Email validieren"
|
||||
|
||||
#: apps/registration/views.py:144
|
||||
#: apps/registration/views.py:145
|
||||
msgid "Email validation unsuccessful"
|
||||
msgstr "Email validierung unerfolgreich"
|
||||
|
||||
#: apps/registration/views.py:155
|
||||
#: apps/registration/views.py:156
|
||||
msgid "Email validation email sent"
|
||||
msgstr "Validierungsemail wurde gesendet"
|
||||
|
||||
#: apps/registration/views.py:163
|
||||
#: apps/registration/views.py:164
|
||||
msgid "Resend email validation link"
|
||||
msgstr "E-Mail-Validierungslink erneut senden"
|
||||
|
||||
#: apps/registration/views.py:181
|
||||
#: apps/registration/views.py:182
|
||||
msgid "Pre-registered users list"
|
||||
msgstr "Vorregistrierte Userliste"
|
||||
|
||||
#: apps/registration/views.py:205
|
||||
#: apps/registration/views.py:206
|
||||
msgid "Unregistered users"
|
||||
msgstr "Unregistrierte Users"
|
||||
|
||||
#: apps/registration/views.py:218
|
||||
#: apps/registration/views.py:219
|
||||
msgid "Registration detail"
|
||||
msgstr "Registrierung Detailen"
|
||||
|
||||
#: apps/registration/views.py:282
|
||||
#: apps/registration/views.py:293
|
||||
msgid "You must join the BDE."
|
||||
msgstr "Sie müssen die BDE beitreten."
|
||||
|
||||
#: apps/registration/views.py:306
|
||||
#: apps/registration/views.py:323
|
||||
msgid ""
|
||||
"The entered amount is not enough for the memberships, should be at least {}"
|
||||
msgstr ""
|
||||
"Der eingegebene Betrag reicht für die Mitgliedschaft nicht aus, sollte "
|
||||
"mindestens {} betragen"
|
||||
|
||||
#: apps/registration/views.py:387
|
||||
#: apps/registration/views.py:417
|
||||
msgid "Invalidate pre-registration"
|
||||
msgstr "Ungültige Vorregistrierung"
|
||||
|
||||
@@ -2183,7 +2218,7 @@ msgstr "Ungültige Vorregistrierung"
|
||||
msgid "Treasury"
|
||||
msgstr "Quaestor"
|
||||
|
||||
#: apps/treasury/forms.py:26 apps/treasury/models.py:93
|
||||
#: apps/treasury/forms.py:26 apps/treasury/models.py:95
|
||||
#: apps/treasury/templates/treasury/invoice_form.html:22
|
||||
msgid "This invoice is locked and can no longer be edited."
|
||||
msgstr "Diese Rechnung ist gesperrt und kann nicht mehr bearbeitet werden."
|
||||
@@ -2196,7 +2231,7 @@ msgstr "Überweisung ist bereits geschlossen."
|
||||
msgid "You can't change the type of the remittance."
|
||||
msgstr "Sie können die Art der Überweisung nicht ändern."
|
||||
|
||||
#: apps/treasury/forms.py:125 apps/treasury/models.py:267
|
||||
#: apps/treasury/forms.py:125 apps/treasury/models.py:269
|
||||
#: 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
|
||||
@@ -2212,116 +2247,116 @@ msgstr "Keine beigefügte Überweisung"
|
||||
msgid "Invoice identifier"
|
||||
msgstr "Rechnungskennung"
|
||||
|
||||
#: apps/treasury/models.py:40
|
||||
#: apps/treasury/models.py:42
|
||||
msgid "BDE"
|
||||
msgstr "BDE"
|
||||
|
||||
#: apps/treasury/models.py:45
|
||||
#: apps/treasury/models.py:47
|
||||
msgid "Object"
|
||||
msgstr "Objekt"
|
||||
|
||||
#: apps/treasury/models.py:49
|
||||
#: apps/treasury/models.py:51
|
||||
msgid "Description"
|
||||
msgstr "Beschreibung"
|
||||
|
||||
#: apps/treasury/models.py:58
|
||||
#: apps/treasury/models.py:60
|
||||
msgid "Address"
|
||||
msgstr "Adresse"
|
||||
|
||||
#: apps/treasury/models.py:63 apps/treasury/models.py:193
|
||||
#: apps/treasury/models.py:65 apps/treasury/models.py:195
|
||||
msgid "Date"
|
||||
msgstr "Datum"
|
||||
|
||||
#: apps/treasury/models.py:67
|
||||
#: apps/treasury/models.py:69
|
||||
msgid "Acquitted"
|
||||
msgstr "Bezahlt"
|
||||
|
||||
#: apps/treasury/models.py:72
|
||||
#: apps/treasury/models.py:74
|
||||
msgid "Locked"
|
||||
msgstr "Gesperrt"
|
||||
|
||||
#: apps/treasury/models.py:73
|
||||
#: apps/treasury/models.py:75
|
||||
msgid "An invoice can't be edited when it is locked."
|
||||
msgstr "Eine Rechnung kann nicht bearbeitet werden, wenn sie gesperrt ist."
|
||||
|
||||
#: apps/treasury/models.py:79
|
||||
#: apps/treasury/models.py:81
|
||||
msgid "tex source"
|
||||
msgstr "Tex Quelle"
|
||||
|
||||
#: apps/treasury/models.py:113 apps/treasury/models.py:129
|
||||
#: apps/treasury/models.py:115 apps/treasury/models.py:131
|
||||
msgid "invoice"
|
||||
msgstr "Rechnung"
|
||||
|
||||
#: apps/treasury/models.py:114
|
||||
#: apps/treasury/models.py:116
|
||||
msgid "invoices"
|
||||
msgstr "Rechnungen"
|
||||
|
||||
#: apps/treasury/models.py:117
|
||||
#: apps/treasury/models.py:119
|
||||
#, python-brace-format
|
||||
msgid "Invoice #{id}"
|
||||
msgstr "Rechnung #{id}"
|
||||
|
||||
#: apps/treasury/models.py:134
|
||||
#: apps/treasury/models.py:136
|
||||
msgid "Designation"
|
||||
msgstr "Bezeichnung"
|
||||
|
||||
#: apps/treasury/models.py:140
|
||||
#: apps/treasury/models.py:142
|
||||
msgid "Quantity"
|
||||
msgstr "Qualität"
|
||||
|
||||
#: apps/treasury/models.py:145
|
||||
#: apps/treasury/models.py:147
|
||||
msgid "Unit price"
|
||||
msgstr "Einzelpreis"
|
||||
|
||||
#: apps/treasury/models.py:161
|
||||
#: apps/treasury/models.py:163
|
||||
msgid "product"
|
||||
msgstr "Produkt"
|
||||
|
||||
#: apps/treasury/models.py:162
|
||||
#: apps/treasury/models.py:164
|
||||
msgid "products"
|
||||
msgstr "Produkten"
|
||||
|
||||
#: apps/treasury/models.py:182
|
||||
#: apps/treasury/models.py:184
|
||||
msgid "remittance type"
|
||||
msgstr "Überweisungstyp"
|
||||
|
||||
#: apps/treasury/models.py:183
|
||||
#: apps/treasury/models.py:185
|
||||
msgid "remittance types"
|
||||
msgstr "Überweisungstypen"
|
||||
|
||||
#: apps/treasury/models.py:204
|
||||
#: apps/treasury/models.py:206
|
||||
msgid "Comment"
|
||||
msgstr "Kommentar"
|
||||
|
||||
#: apps/treasury/models.py:209
|
||||
#: apps/treasury/models.py:211
|
||||
msgid "Closed"
|
||||
msgstr "Geschlossen"
|
||||
|
||||
#: apps/treasury/models.py:213
|
||||
#: apps/treasury/models.py:215
|
||||
msgid "remittance"
|
||||
msgstr "Überweisung"
|
||||
|
||||
#: apps/treasury/models.py:214
|
||||
#: apps/treasury/models.py:216
|
||||
msgid "remittances"
|
||||
msgstr "Überweisungen"
|
||||
|
||||
#: apps/treasury/models.py:247
|
||||
#: apps/treasury/models.py:249
|
||||
msgid "Remittance #{:d}: {}"
|
||||
msgstr "Überweisung #{:d}:{}"
|
||||
|
||||
#: apps/treasury/models.py:271
|
||||
#: apps/treasury/models.py:273
|
||||
msgid "special transaction proxy"
|
||||
msgstr "spezielle Transaktion Proxy"
|
||||
|
||||
#: apps/treasury/models.py:272
|
||||
#: apps/treasury/models.py:274
|
||||
msgid "special transaction proxies"
|
||||
msgstr "spezielle Transaktion Proxies"
|
||||
|
||||
#: apps/treasury/models.py:298
|
||||
#: apps/treasury/models.py:300
|
||||
msgid "credit transaction"
|
||||
msgstr "Kredit Transaktion"
|
||||
|
||||
#: apps/treasury/models.py:430
|
||||
#: apps/treasury/models.py:432
|
||||
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."
|
||||
@@ -2329,16 +2364,16 @@ msgstr ""
|
||||
"Dieser Benutzer hat nicht genug Geld, um die Mitgliedschaften mit seiner "
|
||||
"Note zu bezahlen."
|
||||
|
||||
#: apps/treasury/models.py:451
|
||||
#: apps/treasury/models.py:452
|
||||
#: apps/treasury/templates/treasury/sogecredit_detail.html:10
|
||||
msgid "Credit from the Société générale"
|
||||
msgstr "Kredit von der Société générale"
|
||||
|
||||
#: apps/treasury/models.py:452
|
||||
#: apps/treasury/models.py:453
|
||||
msgid "Credits from the Société générale"
|
||||
msgstr "Krediten von der Société générale"
|
||||
|
||||
#: apps/treasury/models.py:455
|
||||
#: apps/treasury/models.py:456
|
||||
#, python-brace-format
|
||||
msgid "Soge credit for {user}"
|
||||
msgstr "Kredit von der Société générale für {user}"
|
||||
@@ -2602,7 +2637,7 @@ msgid "The selected user is not validated. Please validate its account first"
|
||||
msgstr ""
|
||||
|
||||
#: apps/wei/forms/registration.py:59 apps/wei/models.py:126
|
||||
#: apps/wei/models.py:323
|
||||
#: apps/wei/models.py:326
|
||||
msgid "bus"
|
||||
msgstr "Bus"
|
||||
|
||||
@@ -2640,7 +2675,7 @@ msgstr "Wählen Sie die Rollen aus, an denen Sie interessiert sind."
|
||||
msgid "This team doesn't belong to the given bus."
|
||||
msgstr "Dieses Team gehört nicht zum angegebenen Bus."
|
||||
|
||||
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:35
|
||||
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38
|
||||
msgid "Choose a word:"
|
||||
msgstr "Wählen Sie ein Wort:"
|
||||
|
||||
@@ -2727,40 +2762,48 @@ msgstr "Nicht binär"
|
||||
msgid "gender"
|
||||
msgstr "Geschlecht"
|
||||
|
||||
#: apps/wei/models.py:213 apps/wei/templates/wei/weimembership_form.html:58
|
||||
#: apps/wei/models.py:212
|
||||
msgid "Unisex"
|
||||
msgstr "Unisex"
|
||||
|
||||
#: apps/wei/models.py:215 apps/wei/templates/wei/weimembership_form.html:58
|
||||
msgid "clothing cut"
|
||||
msgstr "Kleidung Schnitt"
|
||||
|
||||
#: apps/wei/models.py:226 apps/wei/templates/wei/weimembership_form.html:61
|
||||
#: apps/wei/models.py:228 apps/wei/templates/wei/weimembership_form.html:61
|
||||
msgid "clothing size"
|
||||
msgstr "Kleidergröße"
|
||||
|
||||
#: apps/wei/models.py:232 apps/wei/templates/wei/attribute_bus_1A.html:28
|
||||
#: apps/wei/models.py:234 apps/wei/templates/wei/attribute_bus_1A.html:28
|
||||
#: apps/wei/templates/wei/weimembership_form.html:67
|
||||
msgid "health issues"
|
||||
msgstr "Gesundheitsprobleme"
|
||||
|
||||
#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:70
|
||||
#: apps/wei/models.py:239 apps/wei/templates/wei/weimembership_form.html:70
|
||||
msgid "emergency contact name"
|
||||
msgstr "Notfall-Kontakt"
|
||||
|
||||
#: apps/wei/models.py:242 apps/wei/templates/wei/weimembership_form.html:73
|
||||
#: apps/wei/models.py:240
|
||||
msgid "The emergency contact must not be a WEI participant"
|
||||
msgstr "Der Notfallkontakt darf kein WEI-Teilnehmer sein"
|
||||
|
||||
#: apps/wei/models.py:245 apps/wei/templates/wei/weimembership_form.html:73
|
||||
msgid "emergency contact phone"
|
||||
msgstr "Notfallkontakttelefon"
|
||||
|
||||
#: apps/wei/models.py:247 apps/wei/templates/wei/weimembership_form.html:52
|
||||
#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:52
|
||||
msgid "first year"
|
||||
msgstr "Erste Jahr"
|
||||
|
||||
#: apps/wei/models.py:248
|
||||
#: apps/wei/models.py:251
|
||||
msgid "Tells if the user is new in the school."
|
||||
msgstr "Gibt an, ob der USer neu in der Schule ist."
|
||||
|
||||
#: apps/wei/models.py:253
|
||||
#: apps/wei/models.py:256
|
||||
msgid "registration information"
|
||||
msgstr "Registrierung Detailen"
|
||||
|
||||
#: apps/wei/models.py:254
|
||||
#: apps/wei/models.py:257
|
||||
msgid ""
|
||||
"Information about the registration (buses for old members, survey for the "
|
||||
"new members), encoded in JSON"
|
||||
@@ -2768,27 +2811,27 @@ msgstr ""
|
||||
"Informationen zur Registrierung (Busse für alte Mitglieder, Umfrage für neue "
|
||||
"Mitglieder), verschlüsselt in JSON"
|
||||
|
||||
#: apps/wei/models.py:312
|
||||
#: apps/wei/models.py:315
|
||||
msgid "WEI User"
|
||||
msgstr "WEI User"
|
||||
|
||||
#: apps/wei/models.py:313
|
||||
#: apps/wei/models.py:316
|
||||
msgid "WEI Users"
|
||||
msgstr "WEI Users"
|
||||
|
||||
#: apps/wei/models.py:333
|
||||
#: apps/wei/models.py:336
|
||||
msgid "team"
|
||||
msgstr "Team"
|
||||
|
||||
#: apps/wei/models.py:343
|
||||
#: apps/wei/models.py:346
|
||||
msgid "WEI registration"
|
||||
msgstr "WEI Registrierung"
|
||||
|
||||
#: apps/wei/models.py:347
|
||||
#: apps/wei/models.py:350
|
||||
msgid "WEI membership"
|
||||
msgstr "WEI Mitgliedschaft"
|
||||
|
||||
#: apps/wei/models.py:348
|
||||
#: apps/wei/models.py:351
|
||||
msgid "WEI memberships"
|
||||
msgstr "WEI Mitgliedschaften"
|
||||
|
||||
@@ -2933,7 +2976,7 @@ msgstr "Als PDF schauen"
|
||||
#: 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:1028
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1093
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1130
|
||||
msgid "Survey WEI"
|
||||
msgstr "WEI Umfrage"
|
||||
|
||||
@@ -3211,11 +3254,11 @@ msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen."
|
||||
msgid "Validate WEI registration"
|
||||
msgstr "Überprüfen Sie die WEI-Registrierung"
|
||||
|
||||
#: apps/wei/views.py:1186
|
||||
#: apps/wei/views.py:1223
|
||||
msgid "Attribute buses to first year members"
|
||||
msgstr ""
|
||||
|
||||
#: apps/wei/views.py:1211
|
||||
#: apps/wei/views.py:1248
|
||||
msgid "Attribute bus"
|
||||
msgstr ""
|
||||
|
||||
@@ -3361,6 +3404,10 @@ msgstr "Kontakt"
|
||||
msgid "Technical Support"
|
||||
msgstr ""
|
||||
|
||||
#: note_kfet/templates/base.html:198
|
||||
msgid "FAQ (FR)"
|
||||
msgstr "FAQ (FR)"
|
||||
|
||||
#: note_kfet/templates/base_search.html:15
|
||||
msgid "Search by attribute such as name…"
|
||||
msgstr "Suche nach Attributen wie Name…"
|
||||
@@ -3608,10 +3655,16 @@ msgstr ""
|
||||
"müssen Ihre E-Mail-Adresse auch überprüfen, indem Sie dem Link folgen, den "
|
||||
"Sie erhalten haben."
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "You already opened an account in the Société générale."
|
||||
#~ msgid ""
|
||||
#~ "I declare that I opened or I will open soon a bank account in the Société "
|
||||
#~ "générale with the BDE partnership."
|
||||
#~ msgstr "Sie haben bereits ein Konto in der Société générale eröffnet."
|
||||
|
||||
#~ msgid "This user didn't give her/his caution check."
|
||||
#~ msgstr "Dieser User hat seine / ihre Vorsicht nicht überprüft."
|
||||
|
||||
#, python-format
|
||||
#~ msgid ""
|
||||
#~ "A new version of the application is available. This instance runs "
|
||||
#~ "%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider "
|
||||
|
@@ -7,11 +7,11 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-11-15 23:21+0100\n"
|
||||
"POT-Creation-Date: 2022-10-07 09:07+0200\n"
|
||||
"PO-Revision-Date: 2020-11-16 20:21+0000\n"
|
||||
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
||||
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/>"
|
||||
"\n"
|
||||
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/"
|
||||
">\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@@ -27,6 +27,22 @@ msgstr "Alias erfolgreich hinzugefügt"
|
||||
msgid "Alias successfully deleted"
|
||||
msgstr "Alias erfolgreich gelöscht"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:14
|
||||
msgid "You can't add yourself as a friend"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/static/member/js/trust.js:37
|
||||
#, fuzzy
|
||||
#| msgid "Alias successfully added"
|
||||
msgid "Friendship successfully added"
|
||||
msgstr "Alias erfolgreich hinzugefügt"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:53
|
||||
#, fuzzy
|
||||
#| msgid "Alias successfully deleted"
|
||||
msgid "Friendship successfully deleted"
|
||||
msgstr "Alias erfolgreich gelöscht"
|
||||
|
||||
#: apps/note/static/note/js/consos.js:225
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
@@ -46,32 +62,32 @@ msgstr ""
|
||||
"ist negativ."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:232
|
||||
#: apps/note/static/note/js/transfer.js:298
|
||||
#: apps/note/static/note/js/transfer.js:401
|
||||
#: apps/note/static/note/js/transfer.js:309
|
||||
#: apps/note/static/note/js/transfer.js:412
|
||||
#, javascript-format
|
||||
msgid "Warning, the emitter note %s is no more a BDE member."
|
||||
msgstr "Warnung, der Emittent Hinweis %s ist kein BDE-Mitglied mehr."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:253
|
||||
#: apps/note/static/note/js/consos.js:254
|
||||
msgid "The transaction couldn't be validated because of insufficient balance."
|
||||
msgstr ""
|
||||
"Die Transaktion konnte aufgrund eines unzureichenden Saldos nicht validiert "
|
||||
"werden."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:238
|
||||
#: apps/note/static/note/js/transfer.js:249
|
||||
msgid "This field is required and must contain a decimal positive number."
|
||||
msgstr ""
|
||||
"Dieses Feld ist erforderlich und muss eine positive Dezimalzahl enthalten."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:245
|
||||
#: apps/note/static/note/js/transfer.js:256
|
||||
msgid "The amount must stay under 21,474,836.47 €."
|
||||
msgstr "Der Betrag muss unter 21.474.836,47 € bleiben."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:251
|
||||
#: apps/note/static/note/js/transfer.js:262
|
||||
msgid "This field is required."
|
||||
msgstr "Dies ist ein Pflichtfeld."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:277
|
||||
#: apps/note/static/note/js/transfer.js:288
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning: the transaction of %s from %s to %s was not made because it is the "
|
||||
@@ -80,12 +96,12 @@ msgstr ""
|
||||
"Warnung: Die Transaktion von %s von %s nach %s wurde nicht durchgeführt, da "
|
||||
"es sich um die gleiche Quell- und Zielnotiz handelt."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:301
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#, javascript-format
|
||||
msgid "Warning, the destination note %s is no more a BDE member."
|
||||
msgstr "Warnung, der Bestimmungsvermerk %s ist kein BDE-Mitglied mehr."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:307
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@@ -94,7 +110,7 @@ msgstr ""
|
||||
"Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber "
|
||||
"die Emitternote %s ist sehr negativ."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#: apps/note/static/note/js/transfer.js:323
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@@ -103,31 +119,32 @@ msgstr ""
|
||||
"Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber "
|
||||
"die Emitternote %s ist negativ."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#: apps/note/static/note/js/transfer.js:329
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s succeed!"
|
||||
msgstr "Übertragung von %s von %s auf %s gelingt!"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:325
|
||||
#: apps/note/static/note/js/transfer.js:346
|
||||
#: apps/note/static/note/js/transfer.js:353
|
||||
#: apps/note/static/note/js/transfer.js:336
|
||||
#: apps/note/static/note/js/transfer.js:357
|
||||
#: apps/note/static/note/js/transfer.js:364
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s failed: %s"
|
||||
msgstr "Übertragung von %s von %s auf %s fehlgeschlagen: %s"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:347
|
||||
#: apps/note/static/note/js/transfer.js:358
|
||||
msgid "insufficient funds"
|
||||
msgstr "unzureichende Geldmittel"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:400
|
||||
#: apps/note/static/note/js/transfer.js:411
|
||||
msgid "Credit/debit succeed!"
|
||||
msgstr "Kredit/Debit erfolgreich!"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:407
|
||||
#: apps/note/static/note/js/transfer.js:418
|
||||
#, javascript-format
|
||||
msgid "Credit/debit failed: %s"
|
||||
msgstr "Kredit/Debit fehlgeschlagen: %s"
|
||||
|
||||
#: note_kfet/static/js/base.js:366
|
||||
#: note_kfet/static/js/base.js:370
|
||||
msgid "An error occured while (in)validating this transaction:"
|
||||
msgstr "Bei der (Un-)Validierung dieser Transaktion ist ein Fehler aufgetreten:"
|
||||
msgstr ""
|
||||
"Bei der (Un-)Validierung dieser Transaktion ist ein Fehler aufgetreten:"
|
||||
|
@@ -7,16 +7,16 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2022-04-10 22:34+0200\n"
|
||||
"POT-Creation-Date: 2023-09-18 17:27+0200\n"
|
||||
"PO-Revision-Date: 2022-04-11 23:12+0200\n"
|
||||
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
||||
"Last-Translator: bleizi <bleizi@crans.org>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
|
||||
#: apps/activity/apps.py:10 apps/activity/models.py:151
|
||||
#: apps/activity/models.py:167
|
||||
@@ -52,7 +52,7 @@ msgid "You can't invite more than 3 people to this activity."
|
||||
msgstr "Usted no puede invitar más de 3 persona a esta actividad."
|
||||
|
||||
#: apps/activity/models.py:28 apps/activity/models.py:63
|
||||
#: apps/member/models.py:199
|
||||
#: apps/member/models.py:204
|
||||
#: apps/member/templates/member/includes/club_info.html:4
|
||||
#: apps/member/templates/member/includes/profile_info.html:4
|
||||
#: apps/note/models/notes.py:263 apps/note/models/transactions.py:26
|
||||
@@ -113,8 +113,8 @@ msgstr "Lugar donde se organiza la actividad, por ejemplo la Kfet."
|
||||
msgid "type"
|
||||
msgstr "tipo"
|
||||
|
||||
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:307
|
||||
#: apps/note/models/notes.py:148 apps/treasury/models.py:285
|
||||
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:312
|
||||
#: apps/note/models/notes.py:148 apps/treasury/models.py:287
|
||||
#: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13
|
||||
#: apps/wei/templates/wei/survey.html:15
|
||||
msgid "user"
|
||||
@@ -257,19 +257,19 @@ msgstr "Entrado el "
|
||||
msgid "remove"
|
||||
msgstr "quitar"
|
||||
|
||||
#: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:199
|
||||
#: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:201
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
#: apps/activity/tables.py:84 apps/member/forms.py:186
|
||||
#: apps/registration/forms.py:91 apps/treasury/forms.py:131
|
||||
#: apps/activity/tables.py:84 apps/member/forms.py:193
|
||||
#: apps/registration/forms.py:92 apps/treasury/forms.py:131
|
||||
#: apps/wei/forms/registration.py:104
|
||||
msgid "Last name"
|
||||
msgstr "Apellido"
|
||||
|
||||
#: apps/activity/tables.py:86 apps/member/forms.py:191
|
||||
#: apps/activity/tables.py:86 apps/member/forms.py:198
|
||||
#: apps/note/templates/note/transaction_form.html:138
|
||||
#: apps/registration/forms.py:96 apps/treasury/forms.py:133
|
||||
#: apps/registration/forms.py:97 apps/treasury/forms.py:133
|
||||
#: apps/wei/forms/registration.py:109
|
||||
msgid "First name"
|
||||
msgstr "Nombre"
|
||||
@@ -386,7 +386,7 @@ msgid "validate"
|
||||
msgstr "validar"
|
||||
|
||||
#: apps/activity/templates/activity/includes/activity_info.html:71
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:220
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:260
|
||||
msgid "edit"
|
||||
msgstr "modificar"
|
||||
|
||||
@@ -464,9 +464,9 @@ msgstr "nuevos datos"
|
||||
msgid "create"
|
||||
msgstr "crear"
|
||||
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:166 apps/note/tables.py:190
|
||||
#: apps/note/tables.py:237 apps/permission/models.py:127
|
||||
#: apps/treasury/tables.py:38 apps/wei/tables.py:74
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277
|
||||
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
||||
#: apps/wei/tables.py:74
|
||||
msgid "delete"
|
||||
msgstr "suprimir"
|
||||
|
||||
@@ -495,21 +495,21 @@ msgstr "diario de cambios"
|
||||
msgid "Changelog of type \"{action}\" for model {model} at {timestamp}"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/admin.py:50 apps/member/models.py:226
|
||||
#: apps/member/admin.py:50 apps/member/models.py:231
|
||||
#: apps/member/templates/member/includes/club_info.html:34
|
||||
msgid "membership fee (paid students)"
|
||||
msgstr "pago de afiliación (estudiantes pagados)"
|
||||
|
||||
#: apps/member/admin.py:51 apps/member/models.py:231
|
||||
#: apps/member/admin.py:51 apps/member/models.py:236
|
||||
#: apps/member/templates/member/includes/club_info.html:37
|
||||
msgid "membership fee (unpaid students)"
|
||||
msgstr "pago de afiliación (estudiantes no pagados)"
|
||||
|
||||
#: apps/member/admin.py:65 apps/member/models.py:319
|
||||
#: apps/member/admin.py:65 apps/member/models.py:324
|
||||
msgid "roles"
|
||||
msgstr "papel"
|
||||
|
||||
#: apps/member/admin.py:66 apps/member/models.py:333
|
||||
#: apps/member/admin.py:66 apps/member/models.py:338
|
||||
msgid "fee"
|
||||
msgstr "pago"
|
||||
|
||||
@@ -529,65 +529,81 @@ msgstr "Frecuencia de los informes (en días)"
|
||||
msgid "Last report date"
|
||||
msgstr "Fecha del último informe"
|
||||
|
||||
#: apps/member/forms.py:52
|
||||
msgid ""
|
||||
"Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) charter read and approved"
|
||||
msgstr ""
|
||||
"Carta Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) leída y aprobada"
|
||||
|
||||
#: apps/member/forms.py:53
|
||||
msgid ""
|
||||
"Tick after having read and accepted the anti-VSS charter <a "
|
||||
"href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> "
|
||||
"available here in pdf</a>"
|
||||
msgstr ""
|
||||
"Marque después de leer y aceptar la carta anti-VVS <a href=https://"
|
||||
"perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> disponible en "
|
||||
"pdf aquí</a>"
|
||||
|
||||
#: apps/member/forms.py:60
|
||||
msgid "You can't register to the note if you come from the future."
|
||||
msgstr "Usted no puede registrar si viene del futuro."
|
||||
|
||||
#: apps/member/forms.py:79
|
||||
#: apps/member/forms.py:86
|
||||
msgid "select an image"
|
||||
msgstr "elegir una imagen"
|
||||
|
||||
#: apps/member/forms.py:80
|
||||
#: apps/member/forms.py:87
|
||||
msgid "Maximal size: 2MB"
|
||||
msgstr "Tamaño máximo : 2Mo"
|
||||
|
||||
#: apps/member/forms.py:105
|
||||
#: apps/member/forms.py:112
|
||||
msgid "This image cannot be loaded."
|
||||
msgstr "Esta imagen no puede ser cargada."
|
||||
|
||||
#: apps/member/forms.py:141 apps/member/views.py:103
|
||||
#: apps/registration/forms.py:33 apps/registration/views.py:262
|
||||
#: apps/member/forms.py:148 apps/member/views.py:102
|
||||
#: apps/registration/forms.py:34 apps/registration/views.py:266
|
||||
msgid "An alias with a similar name already exists."
|
||||
msgstr "Un alias similar ya existe."
|
||||
|
||||
#: apps/member/forms.py:165 apps/registration/forms.py:71
|
||||
#: apps/member/forms.py:172
|
||||
msgid "Inscription paid by Société Générale"
|
||||
msgstr "Registración pagadas por Société Générale"
|
||||
|
||||
#: apps/member/forms.py:167 apps/registration/forms.py:73
|
||||
#: apps/member/forms.py:174
|
||||
msgid "Check this case if the Société Générale paid the inscription."
|
||||
msgstr "Marcar esta casilla si Société Générale pagó la registración."
|
||||
|
||||
#: apps/member/forms.py:172 apps/registration/forms.py:78
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:79
|
||||
#: apps/wei/forms/registration.py:91
|
||||
msgid "Credit type"
|
||||
msgstr "Tipo de crédito"
|
||||
|
||||
#: apps/member/forms.py:173 apps/registration/forms.py:79
|
||||
#: apps/member/forms.py:180 apps/registration/forms.py:80
|
||||
#: apps/wei/forms/registration.py:92
|
||||
msgid "No credit"
|
||||
msgstr "No crédito"
|
||||
|
||||
#: apps/member/forms.py:175
|
||||
#: apps/member/forms.py:182
|
||||
msgid "You can credit the note of the user."
|
||||
msgstr "Usted puede acreditar la note del usuario."
|
||||
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:84
|
||||
#: apps/member/forms.py:186 apps/registration/forms.py:85
|
||||
#: apps/wei/forms/registration.py:97
|
||||
msgid "Credit amount"
|
||||
msgstr "Valor del crédito"
|
||||
|
||||
#: apps/member/forms.py:196 apps/note/templates/note/transaction_form.html:144
|
||||
#: apps/registration/forms.py:101 apps/treasury/forms.py:135
|
||||
#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144
|
||||
#: apps/registration/forms.py:102 apps/treasury/forms.py:135
|
||||
#: apps/wei/forms/registration.py:114
|
||||
msgid "Bank"
|
||||
msgstr "Banco"
|
||||
|
||||
#: apps/member/forms.py:223
|
||||
#: apps/member/forms.py:230
|
||||
msgid "User"
|
||||
msgstr "Usuario"
|
||||
|
||||
#: apps/member/forms.py:237
|
||||
#: apps/member/forms.py:244
|
||||
msgid "Roles"
|
||||
msgstr "Papeles"
|
||||
|
||||
@@ -772,15 +788,19 @@ msgstr "correo electrónico confirmado"
|
||||
msgid "registration valid"
|
||||
msgstr "registración valida"
|
||||
|
||||
#: apps/member/models.py:162 apps/member/models.py:163
|
||||
#: apps/member/models.py:138
|
||||
msgid "VSS charter read"
|
||||
msgstr "Carta VSS leída"
|
||||
|
||||
#: apps/member/models.py:167 apps/member/models.py:168
|
||||
msgid "user profile"
|
||||
msgstr "perfil usuario"
|
||||
|
||||
#: apps/member/models.py:173
|
||||
#: apps/member/models.py:178
|
||||
msgid "Activate your Note Kfet account"
|
||||
msgstr "Active su cuenta Note Kfet"
|
||||
|
||||
#: apps/member/models.py:204
|
||||
#: apps/member/models.py:209
|
||||
#: apps/member/templates/member/includes/club_info.html:55
|
||||
#: apps/member/templates/member/includes/profile_info.html:40
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:22
|
||||
@@ -789,87 +809,87 @@ msgstr "Active su cuenta Note Kfet"
|
||||
msgid "email"
|
||||
msgstr "correo electrónico"
|
||||
|
||||
#: apps/member/models.py:211
|
||||
#: apps/member/models.py:216
|
||||
msgid "parent club"
|
||||
msgstr "club pariente"
|
||||
|
||||
#: apps/member/models.py:220
|
||||
#: apps/member/models.py:225
|
||||
msgid "require memberships"
|
||||
msgstr "necesita afiliaciones"
|
||||
|
||||
#: apps/member/models.py:221
|
||||
#: apps/member/models.py:226
|
||||
msgid "Uncheck if this club don't require memberships."
|
||||
msgstr "Desmarcar si este club no usa afiliaciones."
|
||||
|
||||
#: apps/member/models.py:237
|
||||
#: apps/member/models.py:242
|
||||
#: apps/member/templates/member/includes/club_info.html:26
|
||||
msgid "membership duration"
|
||||
msgstr "duración de la afiliación"
|
||||
|
||||
#: apps/member/models.py:238
|
||||
#: apps/member/models.py:243
|
||||
msgid "The longest time (in days) a membership can last (NULL = infinite)."
|
||||
msgstr "La duración máxima (en días) de una afiliación (NULL = infinito)."
|
||||
|
||||
#: apps/member/models.py:245
|
||||
#: apps/member/models.py:250
|
||||
#: apps/member/templates/member/includes/club_info.html:16
|
||||
msgid "membership start"
|
||||
msgstr "inicio de la afiliación"
|
||||
|
||||
#: apps/member/models.py:246
|
||||
#: apps/member/models.py:251
|
||||
msgid "Date from which the members can renew their membership."
|
||||
msgstr "Fecha a partir de la cual los miembros pueden prorrogar su afiliación."
|
||||
|
||||
#: apps/member/models.py:252
|
||||
#: apps/member/models.py:257
|
||||
#: apps/member/templates/member/includes/club_info.html:21
|
||||
msgid "membership end"
|
||||
msgstr "fin de la afiliación"
|
||||
|
||||
#: apps/member/models.py:253
|
||||
#: apps/member/models.py:258
|
||||
msgid "Maximal date of a membership, after which members must renew it."
|
||||
msgstr ""
|
||||
"Ultima fecha de una afiliación, después de la cual los miembros tienen que "
|
||||
"prorrogarla."
|
||||
|
||||
#: apps/member/models.py:288 apps/member/models.py:313
|
||||
#: apps/member/models.py:293 apps/member/models.py:318
|
||||
#: apps/note/models/notes.py:176
|
||||
msgid "club"
|
||||
msgstr "club"
|
||||
|
||||
#: apps/member/models.py:289
|
||||
#: apps/member/models.py:294
|
||||
msgid "clubs"
|
||||
msgstr "clubs"
|
||||
|
||||
#: apps/member/models.py:324
|
||||
#: apps/member/models.py:329
|
||||
msgid "membership starts on"
|
||||
msgstr "afiliación empezá el"
|
||||
|
||||
#: apps/member/models.py:328
|
||||
#: apps/member/models.py:333
|
||||
msgid "membership ends on"
|
||||
msgstr "afiliación termina el"
|
||||
|
||||
#: apps/member/models.py:430
|
||||
#: apps/member/models.py:435
|
||||
#, python-brace-format
|
||||
msgid "The role {role} does not apply to the club {club}."
|
||||
msgstr "El papel {role} no se encuentra en el club {club}."
|
||||
|
||||
#: apps/member/models.py:439 apps/member/views.py:712
|
||||
#: apps/member/models.py:444 apps/member/views.py:712
|
||||
msgid "User is already a member of the club"
|
||||
msgstr "Usuario ya esta un miembro del club"
|
||||
|
||||
#: apps/member/models.py:451 apps/member/views.py:721
|
||||
#: apps/member/models.py:456 apps/member/views.py:721
|
||||
msgid "User is not a member of the parent club"
|
||||
msgstr "Usuario no es un miembro del club pariente"
|
||||
|
||||
#: apps/member/models.py:504
|
||||
#: apps/member/models.py:509
|
||||
#, python-brace-format
|
||||
msgid "Membership of {user} for the club {club}"
|
||||
msgstr "Afiliación of {user} for the club {club}"
|
||||
|
||||
#: apps/member/models.py:507 apps/note/models/transactions.py:389
|
||||
#: apps/member/models.py:512 apps/note/models/transactions.py:389
|
||||
msgid "membership"
|
||||
msgstr "afiliación"
|
||||
|
||||
#: apps/member/models.py:508
|
||||
#: apps/member/models.py:513
|
||||
msgid "memberships"
|
||||
msgstr "afiliaciones"
|
||||
|
||||
@@ -921,7 +941,7 @@ msgid "Account #"
|
||||
msgstr "Cuenta n°"
|
||||
|
||||
#: apps/member/templates/member/base.html:48
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:60
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:59
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:48
|
||||
#: apps/wei/templates/wei/weimembership_form.html:117
|
||||
msgid "Update Profile"
|
||||
@@ -1155,9 +1175,9 @@ msgstr "Hacer clic aquí para reenviar un enlace de validación."
|
||||
msgid "View my memberships"
|
||||
msgstr "Ver mis afiliaciones"
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:10 apps/member/views.py:254
|
||||
msgid "Note friendships"
|
||||
msgstr "Amistades de note"
|
||||
#: apps/member/templates/member/profile_trust.html:10
|
||||
msgid "Add friends"
|
||||
msgstr "Añadir amig@s"
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:28
|
||||
msgid ""
|
||||
@@ -1172,6 +1192,10 @@ msgstr ""
|
||||
"simplificar el reembolso entre amig@s por Note Kfet. Pues una persona puede "
|
||||
"crear todas la transacciones sin tener derechos particulares."
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:39
|
||||
msgid "People having you as a friend"
|
||||
msgstr "Personas que tienen usted como amig@"
|
||||
|
||||
#: apps/member/templates/member/profile_update.html:18
|
||||
msgid "Save Changes"
|
||||
msgstr "Guardar cambios"
|
||||
@@ -1180,18 +1204,22 @@ msgstr "Guardar cambios"
|
||||
msgid "Registrations"
|
||||
msgstr "Registraciones"
|
||||
|
||||
#: apps/member/views.py:73 apps/registration/forms.py:23
|
||||
#: apps/member/views.py:72 apps/registration/forms.py:24
|
||||
msgid "This address must be valid."
|
||||
msgstr "Este correo tiene que ser valido."
|
||||
|
||||
#: apps/member/views.py:140
|
||||
#: apps/member/views.py:139
|
||||
msgid "Profile detail"
|
||||
msgstr "Detalles del usuario"
|
||||
|
||||
#: apps/member/views.py:206
|
||||
#: apps/member/views.py:205
|
||||
msgid "Search user"
|
||||
msgstr "Buscar un usuario"
|
||||
|
||||
#: apps/member/views.py:253
|
||||
msgid "Note friendships"
|
||||
msgstr "Amistades de note"
|
||||
|
||||
#: apps/member/views.py:308
|
||||
msgid "Update note picture"
|
||||
msgstr "Modificar la imagen de la note"
|
||||
@@ -1236,11 +1264,11 @@ msgstr "La afiliación tiene que empezar después del {:%d-%m-%Y}."
|
||||
msgid "The membership must begin before {:%m-%d-%Y}."
|
||||
msgstr "La afiliación tiene que empezar antes del {:%d-%m-%Y}."
|
||||
|
||||
#: apps/member/views.py:876
|
||||
#: apps/member/views.py:880
|
||||
msgid "Manage roles of an user in the club"
|
||||
msgstr "Gestionar los papeles de un usuario en el club"
|
||||
|
||||
#: apps/member/views.py:901
|
||||
#: apps/member/views.py:905
|
||||
msgid "Members of the club"
|
||||
msgstr "Miembros del club"
|
||||
|
||||
@@ -1258,7 +1286,13 @@ msgstr "destino"
|
||||
msgid "amount"
|
||||
msgstr "monto"
|
||||
|
||||
#: apps/note/api/serializers.py:199 apps/note/api/serializers.py:205
|
||||
#: apps/note/api/serializers.py:92
|
||||
#, fuzzy
|
||||
#| msgid "This credit is already validated."
|
||||
msgid "This friendship already exists"
|
||||
msgstr "Este crédito ya fue validado."
|
||||
|
||||
#: apps/note/api/serializers.py:198 apps/note/api/serializers.py:204
|
||||
#: apps/note/models/transactions.py:228
|
||||
msgid ""
|
||||
"The transaction can't be saved since the source note or the destination note "
|
||||
@@ -1557,7 +1591,7 @@ msgstr "Transacciones especiales"
|
||||
msgid "membership transaction"
|
||||
msgstr "transacción de afiliación"
|
||||
|
||||
#: apps/note/models/transactions.py:385 apps/treasury/models.py:292
|
||||
#: apps/note/models/transactions.py:385 apps/treasury/models.py:294
|
||||
msgid "membership transactions"
|
||||
msgstr "transacciones de afiliación"
|
||||
|
||||
@@ -1573,8 +1607,8 @@ msgstr "Hacer clic para validar"
|
||||
msgid "No reason specified"
|
||||
msgstr "Ningún motivo dado"
|
||||
|
||||
#: apps/note/tables.py:173 apps/note/tables.py:194 apps/note/tables.py:239
|
||||
#: apps/treasury/tables.py:39
|
||||
#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234
|
||||
#: apps/note/tables.py:279 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:75 apps/wei/tables.py:118
|
||||
@@ -1585,7 +1619,15 @@ msgstr "Ningún motivo dado"
|
||||
msgid "Delete"
|
||||
msgstr "Suprimir"
|
||||
|
||||
#: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
|
||||
#: apps/note/tables.py:191
|
||||
msgid "Trust back"
|
||||
msgstr "Añadir como amig@"
|
||||
|
||||
#: apps/note/tables.py:211
|
||||
msgid "Add back"
|
||||
msgstr "Añadir en retorno"
|
||||
|
||||
#: apps/note/tables.py:262 apps/note/templates/note/conso_form.html:132
|
||||
#: apps/wei/tables.py:49 apps/wei/tables.py:50
|
||||
#: apps/wei/templates/wei/base.html:89
|
||||
#: apps/wei/templates/wei/bus_detail.html:20
|
||||
@@ -1595,7 +1637,7 @@ msgstr "Suprimir"
|
||||
msgid "Edit"
|
||||
msgstr "Editar"
|
||||
|
||||
#: apps/note/tables.py:226 apps/note/tables.py:253
|
||||
#: apps/note/tables.py:266 apps/note/tables.py:293
|
||||
msgid "Hide/Show"
|
||||
msgstr "Ocultar/Mostrar"
|
||||
|
||||
@@ -1676,7 +1718,7 @@ msgid "Amount"
|
||||
msgstr "Monto"
|
||||
|
||||
#: apps/note/templates/note/transaction_form.html:132
|
||||
#: apps/treasury/models.py:54
|
||||
#: apps/treasury/models.py:56
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
@@ -1845,7 +1887,7 @@ msgstr ""
|
||||
msgid "for club"
|
||||
msgstr "interesa el club"
|
||||
|
||||
#: apps/permission/models.py:350 apps/permission/models.py:351
|
||||
#: apps/permission/models.py:351 apps/permission/models.py:352
|
||||
msgid "role permissions"
|
||||
msgstr "permisos por papeles"
|
||||
|
||||
@@ -1963,31 +2005,15 @@ msgstr "Todos los permisos"
|
||||
msgid "registration"
|
||||
msgstr "afiliación"
|
||||
|
||||
#: apps/registration/forms.py:39
|
||||
#: apps/registration/forms.py:40
|
||||
msgid "This email address is already used."
|
||||
msgstr "Este correo electrónico ya esta utilizado."
|
||||
|
||||
#: apps/registration/forms.py:49
|
||||
msgid ""
|
||||
"I declare that I opened or I will open soon a bank account in the Société "
|
||||
"générale with the BDE partnership."
|
||||
msgstr ""
|
||||
"Declaro que ya abrió una cuenta a la Société Générale en colaboración con el "
|
||||
"BDE."
|
||||
|
||||
#: apps/registration/forms.py:51
|
||||
msgid ""
|
||||
"Warning: this engages you to open your bank account. If you finally decides "
|
||||
"to don't open your account, you will have to pay the BDE membership."
|
||||
msgstr ""
|
||||
"Cuidado : esto le obliga abrir su cuenta bancaria. Si cambia de idea y no "
|
||||
"abre su cuenta bancaria, tendrá que pagar su afiliación al BDE."
|
||||
|
||||
#: apps/registration/forms.py:59
|
||||
#: apps/registration/forms.py:60
|
||||
msgid "Register to the WEI"
|
||||
msgstr "Registrarse en el WEI"
|
||||
|
||||
#: apps/registration/forms.py:61
|
||||
#: apps/registration/forms.py:62
|
||||
msgid ""
|
||||
"Check this case if you want to register to the WEI. If you hesitate, you "
|
||||
"will be able to register later, after validating your account in the Kfet."
|
||||
@@ -1995,14 +2021,18 @@ msgstr ""
|
||||
"Marcar esta casilla si usted quiere registrarse en el WEI. Si duda, podrá "
|
||||
"registrarse más tarde, después de validar su cuenta Note Kfet."
|
||||
|
||||
#: apps/registration/forms.py:106
|
||||
#: apps/registration/forms.py:107
|
||||
msgid "Join BDE Club"
|
||||
msgstr "Afiliarse al club BDE"
|
||||
|
||||
#: apps/registration/forms.py:113
|
||||
#: apps/registration/forms.py:114
|
||||
msgid "Join Kfet Club"
|
||||
msgstr "Afiliarse al club Kfet"
|
||||
|
||||
#: apps/registration/forms.py:123
|
||||
msgid "Join BDA Club"
|
||||
msgstr "Afiliarse al club BDA"
|
||||
|
||||
#: apps/registration/templates/registration/email_validation_complete.html:15
|
||||
msgid "Your email have successfully been validated."
|
||||
msgstr "Su correo electrónico fue validado con éxito."
|
||||
@@ -2051,12 +2081,12 @@ msgstr "Suprimir afiliación"
|
||||
msgid "Validate account"
|
||||
msgstr "Validar la cuenta"
|
||||
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:62
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:63
|
||||
msgid ""
|
||||
"The user declared that he/she opened a bank account in the Société générale."
|
||||
msgstr "El usuario declara que ya abrió una cuenta a la Société Générale."
|
||||
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:71
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:73
|
||||
#: apps/wei/templates/wei/weimembership_form.html:127
|
||||
#: apps/wei/templates/wei/weimembership_form.html:186
|
||||
msgid "Validate registration"
|
||||
@@ -2108,54 +2138,54 @@ msgstr "Gracias"
|
||||
msgid "The Note Kfet team."
|
||||
msgstr "El equipo Note Kfet."
|
||||
|
||||
#: apps/registration/views.py:40
|
||||
#: apps/registration/views.py:41
|
||||
msgid "Register new user"
|
||||
msgstr "Registrar un nuevo usuario"
|
||||
|
||||
#: apps/registration/views.py:98
|
||||
#: apps/registration/views.py:99
|
||||
msgid "Email validation"
|
||||
msgstr "Validación del correo electrónico"
|
||||
|
||||
#: apps/registration/views.py:100
|
||||
#: apps/registration/views.py:101
|
||||
msgid "Validate email"
|
||||
msgstr "Validar el correo electrónico"
|
||||
|
||||
#: apps/registration/views.py:144
|
||||
#: apps/registration/views.py:145
|
||||
msgid "Email validation unsuccessful"
|
||||
msgstr "La validación del correo electrónico fracasó"
|
||||
|
||||
#: apps/registration/views.py:155
|
||||
#: apps/registration/views.py:156
|
||||
msgid "Email validation email sent"
|
||||
msgstr "Correo de validación enviado"
|
||||
|
||||
#: apps/registration/views.py:163
|
||||
#: apps/registration/views.py:164
|
||||
msgid "Resend email validation link"
|
||||
msgstr "Reenviar el enlace de validación"
|
||||
|
||||
#: apps/registration/views.py:181
|
||||
#: apps/registration/views.py:182
|
||||
msgid "Pre-registered users list"
|
||||
msgstr "Lista de los usuarios con afiliación pendiente"
|
||||
|
||||
#: apps/registration/views.py:205
|
||||
#: apps/registration/views.py:206
|
||||
msgid "Unregistered users"
|
||||
msgstr "Usuarios con afiliación pendiente"
|
||||
|
||||
#: apps/registration/views.py:218
|
||||
#: apps/registration/views.py:219
|
||||
msgid "Registration detail"
|
||||
msgstr "Detalles de la afiliación"
|
||||
|
||||
#: apps/registration/views.py:282
|
||||
#: apps/registration/views.py:293
|
||||
msgid "You must join the BDE."
|
||||
msgstr "Usted tiene que afiliarse al BDE."
|
||||
|
||||
#: apps/registration/views.py:306
|
||||
#: apps/registration/views.py:323
|
||||
msgid ""
|
||||
"The entered amount is not enough for the memberships, should be at least {}"
|
||||
msgstr ""
|
||||
"El monto dado no es suficiente para las afiliaciones, tiene que ser al menos "
|
||||
"{}"
|
||||
|
||||
#: apps/registration/views.py:387
|
||||
#: apps/registration/views.py:417
|
||||
msgid "Invalidate pre-registration"
|
||||
msgstr "Invalidar la afiliación"
|
||||
|
||||
@@ -2163,7 +2193,7 @@ msgstr "Invalidar la afiliación"
|
||||
msgid "Treasury"
|
||||
msgstr "Tesorería"
|
||||
|
||||
#: apps/treasury/forms.py:26 apps/treasury/models.py:93
|
||||
#: apps/treasury/forms.py:26 apps/treasury/models.py:95
|
||||
#: apps/treasury/templates/treasury/invoice_form.html:22
|
||||
msgid "This invoice is locked and can no longer be edited."
|
||||
msgstr "Esta factura esta bloqueada y no puede ser modificada."
|
||||
@@ -2176,7 +2206,7 @@ msgstr "El descuento ya esta cerrado."
|
||||
msgid "You can't change the type of the remittance."
|
||||
msgstr "No puede cambiar el tipo de descuento."
|
||||
|
||||
#: apps/treasury/forms.py:125 apps/treasury/models.py:267
|
||||
#: apps/treasury/forms.py:125 apps/treasury/models.py:269
|
||||
#: 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
|
||||
@@ -2192,116 +2222,116 @@ msgstr "No hay descuento relacionado"
|
||||
msgid "Invoice identifier"
|
||||
msgstr "Numero de factura"
|
||||
|
||||
#: apps/treasury/models.py:40
|
||||
#: apps/treasury/models.py:42
|
||||
msgid "BDE"
|
||||
msgstr "BDE"
|
||||
|
||||
#: apps/treasury/models.py:45
|
||||
#: apps/treasury/models.py:47
|
||||
msgid "Object"
|
||||
msgstr "Asunto"
|
||||
|
||||
#: apps/treasury/models.py:49
|
||||
#: apps/treasury/models.py:51
|
||||
msgid "Description"
|
||||
msgstr "Descripción"
|
||||
|
||||
#: apps/treasury/models.py:58
|
||||
#: apps/treasury/models.py:60
|
||||
msgid "Address"
|
||||
msgstr "Dirección"
|
||||
|
||||
#: apps/treasury/models.py:63 apps/treasury/models.py:193
|
||||
#: apps/treasury/models.py:65 apps/treasury/models.py:195
|
||||
msgid "Date"
|
||||
msgstr "Fecha"
|
||||
|
||||
#: apps/treasury/models.py:67
|
||||
#: apps/treasury/models.py:69
|
||||
msgid "Acquitted"
|
||||
msgstr "Pagada"
|
||||
|
||||
#: apps/treasury/models.py:72
|
||||
#: apps/treasury/models.py:74
|
||||
msgid "Locked"
|
||||
msgstr "Bloqueada"
|
||||
|
||||
#: apps/treasury/models.py:73
|
||||
#: apps/treasury/models.py:75
|
||||
msgid "An invoice can't be edited when it is locked."
|
||||
msgstr "Une factura no puede ser modificada cuando esta bloqueada."
|
||||
|
||||
#: apps/treasury/models.py:79
|
||||
#: apps/treasury/models.py:81
|
||||
msgid "tex source"
|
||||
msgstr "código fuente TeX"
|
||||
|
||||
#: apps/treasury/models.py:113 apps/treasury/models.py:129
|
||||
#: apps/treasury/models.py:115 apps/treasury/models.py:131
|
||||
msgid "invoice"
|
||||
msgstr "factura"
|
||||
|
||||
#: apps/treasury/models.py:114
|
||||
#: apps/treasury/models.py:116
|
||||
msgid "invoices"
|
||||
msgstr "facturas"
|
||||
|
||||
#: apps/treasury/models.py:117
|
||||
#: apps/treasury/models.py:119
|
||||
#, python-brace-format
|
||||
msgid "Invoice #{id}"
|
||||
msgstr "Factura n°{id}"
|
||||
|
||||
#: apps/treasury/models.py:134
|
||||
#: apps/treasury/models.py:136
|
||||
msgid "Designation"
|
||||
msgstr "Designación"
|
||||
|
||||
#: apps/treasury/models.py:140
|
||||
#: apps/treasury/models.py:142
|
||||
msgid "Quantity"
|
||||
msgstr "Cantidad"
|
||||
|
||||
#: apps/treasury/models.py:145
|
||||
#: apps/treasury/models.py:147
|
||||
msgid "Unit price"
|
||||
msgstr "Precio unitario"
|
||||
|
||||
#: apps/treasury/models.py:161
|
||||
#: apps/treasury/models.py:163
|
||||
msgid "product"
|
||||
msgstr "producto"
|
||||
|
||||
#: apps/treasury/models.py:162
|
||||
#: apps/treasury/models.py:164
|
||||
msgid "products"
|
||||
msgstr "productos"
|
||||
|
||||
#: apps/treasury/models.py:182
|
||||
#: apps/treasury/models.py:184
|
||||
msgid "remittance type"
|
||||
msgstr "tipo de descuento"
|
||||
|
||||
#: apps/treasury/models.py:183
|
||||
#: apps/treasury/models.py:185
|
||||
msgid "remittance types"
|
||||
msgstr "tipos de descuentos"
|
||||
|
||||
#: apps/treasury/models.py:204
|
||||
#: apps/treasury/models.py:206
|
||||
msgid "Comment"
|
||||
msgstr "Comentario"
|
||||
|
||||
#: apps/treasury/models.py:209
|
||||
#: apps/treasury/models.py:211
|
||||
msgid "Closed"
|
||||
msgstr "Cerrada"
|
||||
|
||||
#: apps/treasury/models.py:213
|
||||
#: apps/treasury/models.py:215
|
||||
msgid "remittance"
|
||||
msgstr "descuento"
|
||||
|
||||
#: apps/treasury/models.py:214
|
||||
#: apps/treasury/models.py:216
|
||||
msgid "remittances"
|
||||
msgstr "descuentos"
|
||||
|
||||
#: apps/treasury/models.py:247
|
||||
#: apps/treasury/models.py:249
|
||||
msgid "Remittance #{:d}: {}"
|
||||
msgstr "Descuento n°{:d} : {}"
|
||||
|
||||
#: apps/treasury/models.py:271
|
||||
#: apps/treasury/models.py:273
|
||||
msgid "special transaction proxy"
|
||||
msgstr "proxy de transacción especial"
|
||||
|
||||
#: apps/treasury/models.py:272
|
||||
#: apps/treasury/models.py:274
|
||||
msgid "special transaction proxies"
|
||||
msgstr "proxys de transacciones especiales"
|
||||
|
||||
#: apps/treasury/models.py:298
|
||||
#: apps/treasury/models.py:300
|
||||
msgid "credit transaction"
|
||||
msgstr "transacción de crédito"
|
||||
|
||||
#: apps/treasury/models.py:430
|
||||
#: apps/treasury/models.py:432
|
||||
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."
|
||||
@@ -2310,16 +2340,16 @@ msgstr ""
|
||||
"afiliaciones. Por favor pídelo acreditar su note antes de invalidar este "
|
||||
"crédito."
|
||||
|
||||
#: apps/treasury/models.py:451
|
||||
#: apps/treasury/models.py:452
|
||||
#: apps/treasury/templates/treasury/sogecredit_detail.html:10
|
||||
msgid "Credit from the Société générale"
|
||||
msgstr "Crédito de la Société Générale"
|
||||
|
||||
#: apps/treasury/models.py:452
|
||||
#: apps/treasury/models.py:453
|
||||
msgid "Credits from the Société générale"
|
||||
msgstr "Créditos de la Société Générale"
|
||||
|
||||
#: apps/treasury/models.py:455
|
||||
#: apps/treasury/models.py:456
|
||||
#, python-brace-format
|
||||
msgid "Soge credit for {user}"
|
||||
msgstr "Crédito de la Société Générale para {user}"
|
||||
@@ -2574,7 +2604,7 @@ msgstr ""
|
||||
"El usuario seleccionado no ha sido validado. Validar esta cuenta primero"
|
||||
|
||||
#: apps/wei/forms/registration.py:59 apps/wei/models.py:126
|
||||
#: apps/wei/models.py:323
|
||||
#: apps/wei/models.py:326
|
||||
msgid "bus"
|
||||
msgstr "bus"
|
||||
|
||||
@@ -2612,7 +2642,7 @@ msgstr "Elegir los papeles que le interesa."
|
||||
msgid "This team doesn't belong to the given bus."
|
||||
msgstr "Este equipo no pertenece al bus dado."
|
||||
|
||||
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:35
|
||||
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38
|
||||
msgid "Choose a word:"
|
||||
msgstr "Elegir una palabra :"
|
||||
|
||||
@@ -2699,40 +2729,48 @@ msgstr "No binari@"
|
||||
msgid "gender"
|
||||
msgstr "género"
|
||||
|
||||
#: apps/wei/models.py:213 apps/wei/templates/wei/weimembership_form.html:58
|
||||
#: apps/wei/models.py:212
|
||||
msgid "Unisex"
|
||||
msgstr "Unisex"
|
||||
|
||||
#: apps/wei/models.py:215 apps/wei/templates/wei/weimembership_form.html:58
|
||||
msgid "clothing cut"
|
||||
msgstr "forma de ropa"
|
||||
|
||||
#: apps/wei/models.py:226 apps/wei/templates/wei/weimembership_form.html:61
|
||||
#: apps/wei/models.py:228 apps/wei/templates/wei/weimembership_form.html:61
|
||||
msgid "clothing size"
|
||||
msgstr "medida de ropa"
|
||||
|
||||
#: apps/wei/models.py:232 apps/wei/templates/wei/attribute_bus_1A.html:28
|
||||
#: apps/wei/models.py:234 apps/wei/templates/wei/attribute_bus_1A.html:28
|
||||
#: apps/wei/templates/wei/weimembership_form.html:67
|
||||
msgid "health issues"
|
||||
msgstr "problemas de salud"
|
||||
|
||||
#: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:70
|
||||
#: apps/wei/models.py:239 apps/wei/templates/wei/weimembership_form.html:70
|
||||
msgid "emergency contact name"
|
||||
msgstr "nombre del contacto de emergencia"
|
||||
|
||||
#: apps/wei/models.py:242 apps/wei/templates/wei/weimembership_form.html:73
|
||||
#: apps/wei/models.py:240
|
||||
msgid "The emergency contact must not be a WEI participant"
|
||||
msgstr "El contacto de emergencia no debe ser un participante de WEI"
|
||||
|
||||
#: apps/wei/models.py:245 apps/wei/templates/wei/weimembership_form.html:73
|
||||
msgid "emergency contact phone"
|
||||
msgstr "teléfono del contacto de emergencia"
|
||||
|
||||
#: apps/wei/models.py:247 apps/wei/templates/wei/weimembership_form.html:52
|
||||
#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:52
|
||||
msgid "first year"
|
||||
msgstr "primer año"
|
||||
|
||||
#: apps/wei/models.py:248
|
||||
#: apps/wei/models.py:251
|
||||
msgid "Tells if the user is new in the school."
|
||||
msgstr "Indica si el usuario es nuevo en la escuela."
|
||||
|
||||
#: apps/wei/models.py:253
|
||||
#: apps/wei/models.py:256
|
||||
msgid "registration information"
|
||||
msgstr "informaciones sobre la afiliación"
|
||||
|
||||
#: apps/wei/models.py:254
|
||||
#: apps/wei/models.py:257
|
||||
msgid ""
|
||||
"Information about the registration (buses for old members, survey for the "
|
||||
"new members), encoded in JSON"
|
||||
@@ -2740,27 +2778,27 @@ msgstr ""
|
||||
"Informaciones sobre la afiliacion (bus para miembros ancianos, cuestionario "
|
||||
"para los nuevos miembros), registrado en JSON"
|
||||
|
||||
#: apps/wei/models.py:312
|
||||
#: apps/wei/models.py:315
|
||||
msgid "WEI User"
|
||||
msgstr "Participante WEI"
|
||||
|
||||
#: apps/wei/models.py:313
|
||||
#: apps/wei/models.py:316
|
||||
msgid "WEI Users"
|
||||
msgstr "Participantes WEI"
|
||||
|
||||
#: apps/wei/models.py:333
|
||||
#: apps/wei/models.py:336
|
||||
msgid "team"
|
||||
msgstr "equipo"
|
||||
|
||||
#: apps/wei/models.py:343
|
||||
#: apps/wei/models.py:346
|
||||
msgid "WEI registration"
|
||||
msgstr "Apuntación al WEI"
|
||||
|
||||
#: apps/wei/models.py:347
|
||||
#: apps/wei/models.py:350
|
||||
msgid "WEI membership"
|
||||
msgstr "Afiliación al WEI"
|
||||
|
||||
#: apps/wei/models.py:348
|
||||
#: apps/wei/models.py:351
|
||||
msgid "WEI memberships"
|
||||
msgstr "Afiliaciones al WEI"
|
||||
|
||||
@@ -2891,7 +2929,7 @@ msgstr "Descargar un 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:1028
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1093
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1130
|
||||
msgid "Survey WEI"
|
||||
msgstr "Cuestionario WEI"
|
||||
|
||||
@@ -3162,11 +3200,11 @@ msgstr "Usted no tiene derecho a suprimir esta inscripción WEI."
|
||||
msgid "Validate WEI registration"
|
||||
msgstr "Validar la inscripción WEI"
|
||||
|
||||
#: apps/wei/views.py:1186
|
||||
#: apps/wei/views.py:1223
|
||||
msgid "Attribute buses to first year members"
|
||||
msgstr "Repartir los primer años en los buses"
|
||||
|
||||
#: apps/wei/views.py:1211
|
||||
#: apps/wei/views.py:1248
|
||||
msgid "Attribute bus"
|
||||
msgstr "Repartir en un bus"
|
||||
|
||||
@@ -3316,6 +3354,10 @@ msgstr "Contactarnos"
|
||||
msgid "Technical Support"
|
||||
msgstr "Soporte técnico"
|
||||
|
||||
#: note_kfet/templates/base.html:198
|
||||
msgid "FAQ (FR)"
|
||||
msgstr "FAQ (FR)"
|
||||
|
||||
#: note_kfet/templates/base_search.html:15
|
||||
msgid "Search by attribute such as name…"
|
||||
msgstr "Buscar con atributo, como el nombre…"
|
||||
@@ -3537,6 +3579,26 @@ msgstr ""
|
||||
"pagar su afiliación. Tambien tiene que validar su correo electronico con el "
|
||||
"enlace que recibió."
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "People having you as a friend"
|
||||
#~ msgid "You already have that person as a friend"
|
||||
#~ msgstr "Personas que tienen usted como amig@"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "I declare that I opened or I will open soon a bank account in the Société "
|
||||
#~ "générale with the BDE partnership."
|
||||
#~ msgstr ""
|
||||
#~ "Declaro que ya abrió una cuenta a la Société Générale en colaboración con "
|
||||
#~ "el BDE."
|
||||
|
||||
#~ msgid ""
|
||||
#~ "Warning: this engages you to open your bank account. If you finally "
|
||||
#~ "decides to don't open your account, you will have to pay the BDE "
|
||||
#~ "membership."
|
||||
#~ msgstr ""
|
||||
#~ "Cuidado : esto le obliga abrir su cuenta bancaria. Si cambia de idea y no "
|
||||
#~ "abre su cuenta bancaria, tendrá que pagar su afiliación al BDE."
|
||||
|
||||
#~ msgid "You are not a Kfet member, so you can't use your note account."
|
||||
#~ msgstr "Usted no es un miembro de la Kfet, no puede usar su cuenta note."
|
||||
|
||||
|
@@ -7,16 +7,16 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-11-15 23:21+0100\n"
|
||||
"PO-Revision-Date: 2020-11-21 12:23+0100\n"
|
||||
"POT-Creation-Date: 2022-10-07 09:07+0200\n"
|
||||
"PO-Revision-Date: 2022-10-07 13:20+0200\n"
|
||||
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
|
||||
#: apps/member/static/member/js/alias.js:17
|
||||
msgid "Alias successfully added"
|
||||
@@ -26,6 +26,18 @@ msgstr "Alias añadido con éxito"
|
||||
msgid "Alias successfully deleted"
|
||||
msgstr "Alias suprimido con éxito"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:14
|
||||
msgid "You can't add yourself as a friend"
|
||||
msgstr "No puede añadir asimismo como amig@"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:37
|
||||
msgid "Friendship successfully added"
|
||||
msgstr "Amig@ añadido con éxito"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:53
|
||||
msgid "Friendship successfully deleted"
|
||||
msgstr "Amig@ suprimido con éxito"
|
||||
|
||||
#: apps/note/static/note/js/consos.js:225
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
@@ -44,30 +56,29 @@ msgstr ""
|
||||
"Cuidado, la transacción de %s fue un éxito, pero la note %s está negativa."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:232
|
||||
#: apps/note/static/note/js/transfer.js:298
|
||||
#: apps/note/static/note/js/transfer.js:401
|
||||
#: apps/note/static/note/js/transfer.js:309
|
||||
#: apps/note/static/note/js/transfer.js:412
|
||||
#, javascript-format
|
||||
msgid "Warning, the emitter note %s is no more a BDE member."
|
||||
msgstr "Cuidado, la note remitente %s no está más miembro del BDE."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:253
|
||||
#: apps/note/static/note/js/consos.js:254
|
||||
msgid "The transaction couldn't be validated because of insufficient balance."
|
||||
msgstr ""
|
||||
"La transacción no pudo ser validada por culpa de saldo demasiado bajo."
|
||||
msgstr "La transacción no pudo ser validada por culpa de saldo demasiado bajo."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:238
|
||||
#: apps/note/static/note/js/transfer.js:249
|
||||
msgid "This field is required and must contain a decimal positive number."
|
||||
msgstr "Este campo obligatorio requiere un número decimal positivo."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:245
|
||||
#: apps/note/static/note/js/transfer.js:256
|
||||
msgid "The amount must stay under 21,474,836.47 €."
|
||||
msgstr "El monto no puede superar los 21 474 836,47 €."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:251
|
||||
#: apps/note/static/note/js/transfer.js:262
|
||||
msgid "This field is required."
|
||||
msgstr "Este campo es obligatorio."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:277
|
||||
#: apps/note/static/note/js/transfer.js:288
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning: the transaction of %s from %s to %s was not made because it is the "
|
||||
@@ -76,12 +87,12 @@ msgstr ""
|
||||
"Cuidado : la transacción de %s de %s a %s no fue echa porque la fuente y el "
|
||||
"destino son iguales."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:301
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#, javascript-format
|
||||
msgid "Warning, the destination note %s is no more a BDE member."
|
||||
msgstr "Cuidado, la note destino %s no está más miembro del BDE."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:307
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@@ -90,7 +101,7 @@ msgstr ""
|
||||
"Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero "
|
||||
"la note fuente %s está muy negativa."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#: apps/note/static/note/js/transfer.js:323
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@@ -99,31 +110,31 @@ msgstr ""
|
||||
"Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero "
|
||||
"la note fuente %s está negativa."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#: apps/note/static/note/js/transfer.js:329
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s succeed!"
|
||||
msgstr "¡ La transacción de %s de %s a %s fue un éxito !"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:325
|
||||
#: apps/note/static/note/js/transfer.js:346
|
||||
#: apps/note/static/note/js/transfer.js:353
|
||||
#: apps/note/static/note/js/transfer.js:336
|
||||
#: apps/note/static/note/js/transfer.js:357
|
||||
#: apps/note/static/note/js/transfer.js:364
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s failed: %s"
|
||||
msgstr "La transacción de %s de %s a %s fue un fracaso : %s"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:347
|
||||
#: apps/note/static/note/js/transfer.js:358
|
||||
msgid "insufficient funds"
|
||||
msgstr "fundos insuficientes"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:400
|
||||
#: apps/note/static/note/js/transfer.js:411
|
||||
msgid "Credit/debit succeed!"
|
||||
msgstr "¡ Crédito/débito tubo éxito !"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:407
|
||||
#: apps/note/static/note/js/transfer.js:418
|
||||
#, javascript-format
|
||||
msgid "Credit/debit failed: %s"
|
||||
msgstr "Crédito/débito falló : %s"
|
||||
|
||||
#: note_kfet/static/js/base.js:366
|
||||
#: note_kfet/static/js/base.js:370
|
||||
msgid "An error occured while (in)validating this transaction:"
|
||||
msgstr "Un error ocurrió durante la (in)validación de esta transacción :"
|
||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user