1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-28 20:33:00 +02:00

Compare commits

..

6 Commits

Author SHA1 Message Date
51d60d064c Add waiting lists interfaces
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-18 23:44:49 +02:00
45334e4e02 Add order interface
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-18 17:27:59 +02:00
5174c84b33 Manage food options
Signed-off-by: Emmy D'ANELLO <ynerant@crans.org>
2022-08-18 14:50:45 +02:00
51e5e3669e Add interface to create and see note sheets
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2022-08-18 14:27:02 +02:00
44994a3ae7 Add new application to manage note sheets
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2022-08-18 12:33:10 +02:00
ba017c38c0 Fix permission that allows users to create OAuth2 apps
Signed-off-by: Yohann D'ANELLO <ynerant@crans.org>
2022-07-22 17:18:53 +02:00
93 changed files with 3265 additions and 2160 deletions

1
.gitignore vendored
View File

@ -42,7 +42,6 @@ map.json
backups/ backups/
/static/ /static/
/media/ /media/
/tmp/
# Virtualenv # Virtualenv
env/ env/

View File

@ -6,7 +6,7 @@
"name": "Pot", "name": "Pot",
"manage_entries": true, "manage_entries": true,
"can_invite": true, "can_invite": true,
"guest_entry_fee": 1000 "guest_entry_fee": 500
} }
}, },
{ {
@ -28,25 +28,5 @@
"can_invite": false, "can_invite": false,
"guest_entry_fee": 0 "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
}
} }
] ]

View File

@ -1,5 +0,0 @@
from rest_framework.pagination import PageNumberPagination
class CustomPagination(PageNumberPagination):
page_size_query_param = 'page_size'

View File

@ -26,6 +26,10 @@ if "note" in settings.INSTALLED_APPS:
from note.api.urls import register_note_urls from note.api.urls import register_note_urls
register_note_urls(router, 'note') 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: if "treasury" in settings.INSTALLED_APPS:
from treasury.api.urls import register_treasury_urls from treasury.api.urls import register_treasury_urls
register_treasury_urls(router, 'treasury') register_treasury_urls(router, 'treasury')

View File

@ -47,13 +47,6 @@ class ProfileForm(forms.ModelForm):
last_report = forms.DateTimeField(required=False, disabled=True, label=_("Last report date")) 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): def clean_promotion(self):
promotion = self.cleaned_data["promotion"] promotion = self.cleaned_data["promotion"]
if promotion > timezone.now().year: if promotion > timezone.now().year:

View File

@ -1,4 +1,4 @@
# Generated by Django 2.2.26 on 2022-09-04 21:25 # Generated by Django 2.2.27 on 2022-08-18 11:01
from django.db import migrations, models from django.db import migrations, models

View File

@ -1,18 +0,0 @@
# 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'),
),
]

View File

@ -1,18 +0,0 @@
# 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'),
),
]

View File

@ -134,11 +134,6 @@ class Profile(models.Model):
default=False, default=False,
) )
VSS_charter_read = models.BooleanField(
verbose_name=_("VSS charter read"),
default=False
)
@property @property
def ens_year(self): def ens_year(self):
""" """
@ -268,7 +263,7 @@ class Club(models.Model):
today = datetime.date.today() today = datetime.date.today()
while (today - self.membership_start).days >= 365: if (today - self.membership_start).days >= 365:
if self.membership_start: if self.membership_start:
self.membership_start = datetime.date(self.membership_start.year + 1, self.membership_start = datetime.date(self.membership_start.year + 1,
self.membership_start.month, self.membership_start.day) self.membership_start.month, self.membership_start.day)

View File

@ -1,7 +1,7 @@
/** /**
* On form submit, create a new friendship * On form submit, create a new friendship
*/ */
function form_create_trust (e) { function create_trust (e) {
// Do not submit HTML form // Do not submit HTML form
e.preventDefault() e.preventDefault()
@ -14,35 +14,25 @@ function form_create_trust (e) {
addMsg(gettext("You can't add yourself as a friend"), "danger") addMsg(gettext("You can't add yourself as a friend"), "danger")
return return
} }
create_trust(formData.get('trusting'), trusted_alias.note)
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
}
/**
* 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/', { $.post('/api/note/trust/', {
trusting: trusting, csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'),
trusted: trusted, trusting: formData.get('trusting'),
csrfmiddlewaretoken: CSRF_TOKEN trusted: trusted_alias.note
}).done(function () { }).done(function () {
// Reload tables // Reload table
$('#trust_table').load(location.pathname + ' #trust_table') $('#trust_table').load(location.pathname + ' #trust_table')
$('#trusted_table').load(location.pathname + ' #trusted_table')
addMsg(gettext('Friendship successfully added'), 'success') addMsg(gettext('Friendship successfully added'), 'success')
}).fail(function (xhr, _textStatus, _error) { }).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON) errMsg(xhr.responseJSON)
}) })
}).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON)
})
} }
/** /**
* On click of "delete", delete the trust * On click of "delete", delete the alias
* @param button_id:Integer Trust id to remove * @param button_id:Integer Alias id to remove
*/ */
function delete_button (button_id) { function delete_button (button_id) {
$.ajax({ $.ajax({
@ -52,7 +42,6 @@ function delete_button (button_id) {
}).done(function () { }).done(function () {
addMsg(gettext('Friendship successfully deleted'), 'success') addMsg(gettext('Friendship successfully deleted'), 'success')
$('#trust_table').load(location.pathname + ' #trust_table') $('#trust_table').load(location.pathname + ' #trust_table')
$('#trusted_table').load(location.pathname + ' #trusted_table')
}).fail(function (xhr, _textStatus, _error) { }).fail(function (xhr, _textStatus, _error) {
errMsg(xhr.responseJSON) errMsg(xhr.responseJSON)
}) })
@ -60,5 +49,5 @@ function delete_button (button_id) {
$(document).ready(function () { $(document).ready(function () {
// Attach event // Attach event
document.getElementById('form_trust').addEventListener('submit', form_create_trust) document.getElementById('form_trust').addEventListener('submit', create_trust)
}) })

View File

@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% block profile_content %} {% block profile_content %}
<div class="card bg-light mb-3"> <div class="card bg-light mb-3">
<h3 class="card-header text-center"> <h3 class="card-header text-center">
{% trans "Add friends" %} {% trans "Note friendships" %}
</h3> </h3>
<div class="card-body"> <div class="card-body">
{% if can_create %} {% if can_create %}
@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% render_table trusting %} {% render_table trusting %}
</div> </div>
<div class="alert alert-warning card mb-3"> <div class="alert alert-warning card">
{% blocktrans trimmed %} {% blocktrans trimmed %}
Adding someone as a friend enables them to initiate transactions coming Adding someone as a friend enables them to initiate transactions coming
from your account (while keeping your balance positive). This is from your account (while keeping your balance positive). This is
@ -33,13 +33,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
friends without needing additional rights among them. friends without needing additional rights among them.
{% endblocktrans %} {% endblocktrans %}
</div> </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 %} {% endblock %}
{% block extrajavascript %} {% block extrajavascript %}

View File

@ -183,7 +183,7 @@ class TestMemberships(TestCase):
club = Club.objects.get(name="Kfet") club = Club.objects.get(name="Kfet")
else: else:
club = Club.objects.create( club = Club.objects.create(
name="Second club without BDE", name="Second club " + ("with BDE" if bde_parent else "without BDE"),
parent_club=None, parent_club=None,
email="newclub@example.com", email="newclub@example.com",
require_memberships=True, require_memberships=True,
@ -335,7 +335,6 @@ class TestMemberships(TestCase):
ml_sports_registration=True, ml_sports_registration=True,
ml_art_registration=True, ml_art_registration=True,
report_frequency=7, report_frequency=7,
VSS_charter_read=True
)) ))
self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
self.assertTrue(User.objects.filter(username="toto changed").exists()) self.assertTrue(User.objects.filter(username="toto changed").exists())

View File

@ -8,6 +8,7 @@ from django.contrib.auth import logout
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.views import LoginView from django.contrib.auth.views import LoginView
from django.contrib.contenttypes.models import ContentType
from django.db import transaction from django.db import transaction
from django.db.models import Q, F from django.db.models import Q, F
from django.shortcuts import redirect from django.shortcuts import redirect
@ -20,7 +21,7 @@ from django_tables2.views import SingleTableView
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from note.models import Alias, NoteClub, NoteUser, Trust from note.models import Alias, NoteClub, NoteUser, Trust
from note.models.transactions import Transaction, SpecialTransaction from note.models.transactions import Transaction, SpecialTransaction
from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable from note.tables import HistoryTable, AliasTable, TrustTable
from note_kfet.middlewares import _set_current_request from note_kfet.middlewares import _set_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from permission.models import Role from permission.models import Role
@ -257,18 +258,17 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
note = context['object'].note note = context['object'].note
context["trusting"] = TrustTable( context["trusting"] = TrustTable(
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all()) 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( context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
trusting=context["object"].note, trusting=context["object"].note,
trusted=context["object"].note trusted=context["object"].note
)) ))
context["widget"] = { context["widget"] = {
"name": "trusted", "name": "trusted",
"resetable": True,
"attrs": { "attrs": {
"model_pk": ContentType.objects.get_for_model(Alias).pk,
"class": "autocomplete form-control", "class": "autocomplete form-control",
"id": "trusted", "id": "trusted",
"resetable": True,
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser", "api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
"name_field": "name", "name_field": "name",
"placeholder": "" "placeholder": ""
@ -753,10 +753,6 @@ class ClubAddMemberView(ProtectQuerysetMixin, ProtectedCreateView):
club = old_membership.club club = old_membership.club
user = old_membership.user 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 form.instance.club = club
# Get form data # Get form data

View File

@ -7,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
PolymorphicChildModelFilter, PolymorphicParentModelAdmin PolymorphicChildModelFilter, PolymorphicParentModelAdmin
from note_kfet.admin import admin_site from note_kfet.admin import admin_site
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \ from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \
RecurrentTransaction, MembershipTransaction, SpecialTransaction RecurrentTransaction, MembershipTransaction, SpecialTransaction
from .templatetags.pretty_money import pretty_money from .templatetags.pretty_money import pretty_money
@ -21,16 +21,6 @@ class AliasInlines(admin.TabularInline):
model = Alias 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) @admin.register(Note, site=admin_site)
class NoteAdmin(PolymorphicParentModelAdmin): class NoteAdmin(PolymorphicParentModelAdmin):
""" """
@ -102,7 +92,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
""" """
Child for an user note, see NoteAdmin Child for an user note, see NoteAdmin
""" """
inlines = (AliasInlines, TrustInlines) inlines = (AliasInlines,)
# We can't change user after creation or the balance # We can't change user after creation or the balance
readonly_fields = ('user', 'balance') readonly_fields = ('user', 'balance')

View File

@ -11,7 +11,6 @@ from member.models import Membership
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from rest_framework.utils import model_meta 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.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \ from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
@ -87,9 +86,11 @@ class TrustSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = Trust model = Trust
fields = '__all__' fields = '__all__'
validators = [UniqueTogetherValidator(
queryset=Trust.objects.all(), fields=('trusting', 'trusted'), def validate(self, attrs):
message=_("This friendship already exists"))] instance = Trust(**attrs)
instance.clean()
return attrs
class AliasSerializer(serializers.ModelSerializer): class AliasSerializer(serializers.ModelSerializer):

View File

@ -325,8 +325,8 @@ class SpecialTransaction(Transaction):
def clean(self): def clean(self):
# SpecialTransaction are only possible with NoteSpecial object # SpecialTransaction are only possible with NoteSpecial object
if self.is_credit() == self.is_debit(): if self.is_credit() == self.is_debit():
raise ValidationError(_("A special transaction is only possible between a" raise(ValidationError(_("A special transaction is only possible between a"
" Note associated to a payment method and a User or a Club")) " Note associated to a payment method and a User or a Club")))
@transaction.atomic @transaction.atomic
def save(self, *args, **kwargs): def save(self, *args, **kwargs):

View File

@ -221,7 +221,7 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
.done(function () { .done(function () {
if (!isNaN(source.balance)) { if (!isNaN(source.balance)) {
const newBalance = source.balance - quantity * amount const newBalance = source.balance - quantity * amount
if (newBalance <= -2000) { if (newBalance <= -5000) {
addMsg(interpolate(gettext('Warning, the transaction from the note %s succeed, ' + 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) 'but the emitter note %s is very negative.'), [source_alias, source_alias]), 'danger', 30000)
} else if (newBalance < 0) { } else if (newBalance < 0) {

View File

@ -314,7 +314,7 @@ $('#btn_transfer').click(function () {
if (!isNaN(source.note.balance)) { if (!isNaN(source.note.balance)) {
const newBalance = source.note.balance - source.quantity * dest.quantity * amount const newBalance = source.note.balance - source.quantity * dest.quantity * amount
if (newBalance <= -2000) { if (newBalance <= -5000) {
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.'), 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) [pretty_money(source.quantity * dest.quantity * amount), source.name, dest.name, source.name]), 'danger', 10000)
reset() reset()

View File

@ -159,11 +159,11 @@ class TrustTable(tables.Table):
template_name = 'django_tables2/bootstrap4.html' template_name = 'django_tables2/bootstrap4.html'
show_header = False show_header = False
trusted = tables.Column(attrs={'td': {'class': 'text-center'}}) trusted = tables.Column(attrs={'td': {'class': 'text_center'}})
delete_col = tables.TemplateColumn( delete_col = tables.TemplateColumn(
template_code=DELETE_TEMPLATE, template_code=DELETE_TEMPLATE,
extra_context={"delete_trans": _('Delete')}, extra_context={"delete_trans": _('delete')},
attrs={ attrs={
'td': { 'td': {
'class': lambda record: 'col-sm-1' 'class': lambda record: 'col-sm-1'
@ -173,46 +173,6 @@ class TrustTable(tables.Table):
verbose_name=_("Delete"),) 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 AliasTable(tables.Table):
class Meta: class Meta:
attrs = { attrs = {

View File

@ -0,0 +1,17 @@
{% 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 %}

View File

@ -0,0 +1,88 @@
{% 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 %}

View File

@ -0,0 +1,152 @@
{% 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 %}

File diff suppressed because it is too large Load Diff

View File

@ -1,19 +0,0 @@
# 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'),
),
]

View File

@ -339,7 +339,6 @@ class Role(models.Model):
"member.Club", "member.Club",
verbose_name=_("for club"), verbose_name=_("for club"),
on_delete=models.PROTECT, on_delete=models.PROTECT,
blank=True,
null=True, null=True,
default=None, default=None,
) )

View File

@ -5,7 +5,6 @@ from django import forms
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import Club
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import AmountInput from note_kfet.inputs import AmountInput
@ -45,14 +44,14 @@ class SignUpForm(UserCreationForm):
fields = ('first_name', 'last_name', 'username', 'email', ) fields = ('first_name', 'last_name', 'username', 'email', )
# class DeclareSogeAccountOpenedForm(forms.Form): class DeclareSogeAccountOpenedForm(forms.Form):
# soge_account = forms.BooleanField( 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 " 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."), "partnership."),
# help_text=_("Warning: this engages you to open your bank account. If you finally decides to don't open your " 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."), "account, you will have to pay the BDE membership."),
# required=False, required=False,
# ) )
class WEISignupForm(forms.Form): class WEISignupForm(forms.Form):
@ -68,11 +67,11 @@ class ValidationForm(forms.Form):
""" """
Validate the inscription of the new users and pay memberships. Validate the inscription of the new users and pay memberships.
""" """
# soge = forms.BooleanField( soge = forms.BooleanField(
# label=_("Inscription paid by Société Générale"), label=_("Inscription paid by Société Générale"),
# required=False, required=False,
# help_text=_("Check this case if the Société Générale paid the inscription."), help_text=_("Check this case if the Société Générale paid the inscription."),
# ) )
credit_type = forms.ModelChoiceField( credit_type = forms.ModelChoiceField(
queryset=NoteSpecial.objects, queryset=NoteSpecial.objects,
@ -115,12 +114,3 @@ class ValidationForm(forms.Form):
required=False, required=False,
initial=True, 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,
)

View File

@ -57,13 +57,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
<h4> {% trans "Validate account" %}</h4> <h4> {% trans "Validate account" %}</h4>
</div> </div>
{% comment "Soge not for membership (only WEI)" %}
{% if declare_soge_account %} {% if declare_soge_account %}
<div class="alert alert-info"> <div class="alert alert-info">
{% trans "The user declared that he/she opened a bank account in the Société générale." %} {% trans "The user declared that he/she opened a bank account in the Société générale." %}
</div> </div>
{% endif %} {% endif %}
{% endcomment %}
<div class="card-body" id="profile_infos"> <div class="card-body" id="profile_infos">
{% csrf_token %} {% csrf_token %}
@ -78,7 +76,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
{% endblock %} {% endblock %}
{% comment "Soge not for membership (only WEI)" %}
{% block extrajavascript %} {% block extrajavascript %}
<script> <script>
soge_field = $("#id_soge"); soge_field = $("#id_soge");
@ -121,4 +118,3 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endif %} {% endif %}
</script> </script>
{% endblock %} {% endblock %}
{% endcomment %}

View File

@ -48,7 +48,6 @@ class TestSignup(TestCase):
ml_events_registration="en", ml_events_registration="en",
ml_sport_registration=True, ml_sport_registration=True,
ml_art_registration=True, ml_art_registration=True,
VSS_charter_read=True
)) ))
self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200) self.assertRedirects(response, reverse("registration:email_validation_sent"), 302, 200)
self.assertTrue(User.objects.filter(username="toto").exists()) self.assertTrue(User.objects.filter(username="toto").exists())
@ -106,7 +105,6 @@ class TestSignup(TestCase):
ml_events_registration="en", ml_events_registration="en",
ml_sport_registration=True, ml_sport_registration=True,
ml_art_registration=True, ml_art_registration=True,
VSS_charter_read=True
)) ))
self.assertTrue(response.status_code, 200) self.assertTrue(response.status_code, 200)
@ -126,7 +124,6 @@ class TestSignup(TestCase):
ml_events_registration="en", ml_events_registration="en",
ml_sport_registration=True, ml_sport_registration=True,
ml_art_registration=True, ml_art_registration=True,
VSS_charter_read=True
)) ))
self.assertTrue(response.status_code, 200) self.assertTrue(response.status_code, 200)
@ -146,27 +143,6 @@ class TestSignup(TestCase):
ml_events_registration="en", ml_events_registration="en",
ml_sport_registration=True, ml_sport_registration=True,
ml_art_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) self.assertTrue(response.status_code, 200)
@ -214,7 +190,7 @@ class TestValidateRegistration(TestCase):
# BDE Membership is mandatory # BDE Membership is mandatory
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( 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_type=NoteSpecial.objects.get(special_type="Chèque").id,
credit_amount=4200, credit_amount=4200,
last_name="TOTO", last_name="TOTO",
@ -228,7 +204,7 @@ class TestValidateRegistration(TestCase):
# Same # Same
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
# soge=False, soge=False,
credit_type="", credit_type="",
credit_amount=0, credit_amount=0,
last_name="TOTO", last_name="TOTO",
@ -242,7 +218,7 @@ class TestValidateRegistration(TestCase):
# The BDE membership is not free # The BDE membership is not free
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( 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_type=NoteSpecial.objects.get(special_type="Espèces").id,
credit_amount=0, credit_amount=0,
last_name="TOTO", last_name="TOTO",
@ -256,7 +232,7 @@ class TestValidateRegistration(TestCase):
# Last and first names are required for a credit # Last and first names are required for a credit
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( 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_type=NoteSpecial.objects.get(special_type="Chèque").id,
credit_amount=4000, credit_amount=4000,
last_name="", last_name="",
@ -273,7 +249,7 @@ class TestValidateRegistration(TestCase):
self.user.username = "admïntoto" self.user.username = "admïntoto"
self.user.save() self.user.save()
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( 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_type=NoteSpecial.objects.get(special_type="Chèque").id,
credit_amount=500, credit_amount=500,
last_name="TOTO", last_name="TOTO",
@ -299,7 +275,7 @@ class TestValidateRegistration(TestCase):
self.user.profile.save() self.user.profile.save()
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( 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_type=NoteSpecial.objects.get(special_type="Chèque").id,
credit_amount=500, credit_amount=500,
last_name="TOTO", last_name="TOTO",
@ -314,7 +290,6 @@ class TestValidateRegistration(TestCase):
self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) 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="BDE", user=self.user).exists())
self.assertFalse(Membership.objects.filter(club__name="Kfet", 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.assertFalse(SogeCredit.objects.filter(user=self.user).exists())
self.assertEqual(Transaction.objects.filter( self.assertEqual(Transaction.objects.filter(
Q(source=self.user.note) | Q(destination=self.user.note)).count(), 2) Q(source=self.user.note) | Q(destination=self.user.note)).count(), 2)
@ -336,7 +311,7 @@ class TestValidateRegistration(TestCase):
self.user.profile.save() self.user.profile.save()
response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( 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_type=NoteSpecial.objects.get(special_type="Espèces").id,
credit_amount=4000, credit_amount=4000,
last_name="TOTO", last_name="TOTO",
@ -351,7 +326,6 @@ class TestValidateRegistration(TestCase):
self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) 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="BDE", user=self.user).exists())
self.assertTrue(Membership.objects.filter(club__name="Kfet", 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.assertFalse(SogeCredit.objects.filter(user=self.user).exists())
self.assertEqual(Transaction.objects.filter( self.assertEqual(Transaction.objects.filter(
Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3)
@ -359,43 +333,42 @@ class TestValidateRegistration(TestCase):
response = self.client.get(self.user.profile.get_absolute_url()) response = self.client.get(self.user.profile.get_absolute_url())
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# def test_validate_kfet_registration_with_soge(self): 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. 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,))) response = self.client.get(reverse("registration:future_user_detail", args=(self.user.pk,)))
# self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
#
# response = self.client.get(self.user.profile.get_absolute_url()) response = self.client.get(self.user.profile.get_absolute_url())
# self.assertEqual(response.status_code, 404) self.assertEqual(response.status_code, 404)
#
# self.user.profile.email_confirmed = True self.user.profile.email_confirmed = True
# self.user.profile.save() self.user.profile.save()
#
# response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict( response = self.client.post(reverse("registration:future_user_detail", args=(self.user.pk,)), data=dict(
# soge=True, soge=True,
# credit_type=NoteSpecial.objects.get(special_type="Espèces").id, credit_type=NoteSpecial.objects.get(special_type="Espèces").id,
# credit_amount=4000, credit_amount=4000,
# last_name="TOTO", last_name="TOTO",
# first_name="Toto", first_name="Toto",
# bank="Société générale", bank="Société générale",
# join_bde=True, join_bde=True,
# join_kfet=True, join_kfet=True,
# )) ))
# self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200) self.assertRedirects(response, self.user.profile.get_absolute_url(), 302, 200)
# self.user.profile.refresh_from_db() self.user.profile.refresh_from_db()
# self.assertTrue(self.user.profile.registration_valid) self.assertTrue(self.user.profile.registration_valid)
# self.assertTrue(NoteUser.objects.filter(user=self.user).exists()) 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="BDE", user=self.user).exists())
# self.assertTrue(Membership.objects.filter(club__name="Kfet", 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.assertTrue(SogeCredit.objects.filter(user=self.user).exists()) self.assertEqual(Transaction.objects.filter(
# self.assertEqual(Transaction.objects.filter( Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3)
# Q(source=self.user.note) | Q(destination=self.user.note)).count(), 3) self.assertFalse(Transaction.objects.filter(valid=True).exists())
# self.assertFalse(Transaction.objects.filter(valid=True).exists())
# response = self.client.get(self.user.profile.get_absolute_url())
# response = self.client.get(self.user.profile.get_absolute_url()) self.assertEqual(response.status_code, 200)
# self.assertEqual(response.status_code, 200)
def test_invalidate_registration(self): def test_invalidate_registration(self):
""" """

View File

@ -24,8 +24,7 @@ from permission.models import Role
from permission.views import ProtectQuerysetMixin from permission.views import ProtectQuerysetMixin
from treasury.models import SogeCredit 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 .tables import FutureUserTable
from .tokens import email_validation_token from .tokens import email_validation_token
@ -43,7 +42,7 @@ class UserCreateView(CreateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
context["profile_form"] = self.second_form(self.request.POST if self.request.POST else None) 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["section"]
del context["profile_form"].fields["report_frequency"] del context["profile_form"].fields["report_frequency"]
del context["profile_form"].fields["last_report"] del context["profile_form"].fields["last_report"]
@ -76,12 +75,12 @@ class UserCreateView(CreateView):
user.profile.send_email_validation_link() user.profile.send_email_validation_link()
# soge_form = DeclareSogeAccountOpenedForm(self.request.POST) soge_form = DeclareSogeAccountOpenedForm(self.request.POST)
# if "soge_account" in soge_form.data and soge_form.data["soge_account"]: 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 # If the user declares that a bank account got opened, prepare the soge credit to warn treasurers
# soge_credit = SogeCredit(user=user) soge_credit = SogeCredit(user=user)
# soge_credit._force_save = True soge_credit._force_save = True
# soge_credit.save() soge_credit.save()
return super().form_valid(form) return super().form_valid(form)
@ -238,12 +237,9 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid fee += bde.membership_fee_paid if user.profile.paid else bde.membership_fee_unpaid
kfet = Club.objects.get(name="Kfet") kfet = Club.objects.get(name="Kfet")
fee += kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid 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["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 return ctx
@ -266,13 +262,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
form.add_error(None, _("An alias with a similar name already exists.")) form.add_error(None, _("An alias with a similar name already exists."))
return self.form_invalid(form) 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 # Get form data
# soge = form.cleaned_data["soge"] soge = form.cleaned_data["soge"]
credit_type = form.cleaned_data["credit_type"] credit_type = form.cleaned_data["credit_type"]
credit_amount = form.cleaned_data["credit_amount"] credit_amount = form.cleaned_data["credit_amount"]
last_name = form.cleaned_data["last_name"] last_name = form.cleaned_data["last_name"]
@ -280,13 +271,11 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
bank = form.cleaned_data["bank"] bank = form.cleaned_data["bank"]
join_bde = form.cleaned_data["join_bde"] join_bde = form.cleaned_data["join_bde"]
join_kfet = form.cleaned_data["join_kfet"] join_kfet = form.cleaned_data["join_kfet"]
if bda_exists:
join_bda = form.cleaned_data["join_bda"]
# if soge: if soge:
# # If Société Générale pays the inscription, the user automatically joins the two clubs. # If Société Générale pays the inscription, the user automatically joins the two clubs.
# join_bde = True join_bde = True
# join_kfet = True join_kfet = True
if not join_bde: if not join_bde:
# This software belongs to the BDE. # This software belongs to the BDE.
@ -303,21 +292,15 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid kfet_fee = kfet.membership_fee_paid if user.profile.paid else kfet.membership_fee_unpaid
# Add extra fee for the full membership # Add extra fee for the full membership
fee += kfet_fee if join_kfet else 0 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 # If the bank pays, then we don't credit now. Treasurers will validate the transaction
# # and credit the note later. # and credit the note later.
# credit_type = None if soge else credit_type credit_type = None if soge else credit_type
# If the user does not select any payment method, then no credit will be performed. # 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 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 # Check if the user credits enough money
form.add_error('credit_type', form.add_error('credit_type',
_("The entered amount is not enough for the memberships, should be at least {}") _("The entered amount is not enough for the memberships, should be at least {}")
@ -337,12 +320,12 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
user.profile.save() user.profile.save()
user.refresh_from_db() user.refresh_from_db()
# if not soge and SogeCredit.objects.filter(user=user).exists(): 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 # If the user declared that a bank account was opened but in the validation form the SoGé case was
# # unchecked, delete the associated credit # unchecked, delete the associated credit
# soge_credit = SogeCredit.objects.get(user=user) soge_credit = SogeCredit.objects.get(user=user)
# soge_credit._force_delete = True soge_credit._force_delete = True
# soge_credit.delete() soge_credit.delete()
if credit_type is not None and credit_amount > 0: if credit_type is not None and credit_amount > 0:
# Credit the note # Credit the note
@ -351,8 +334,7 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
destination=user.note, destination=user.note,
quantity=1, quantity=1,
amount=credit_amount, amount=credit_amount,
reason="Crédit " + credit_type.special_type + " (Inscription)", reason="Crédit " + ("Société générale" if soge else 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, last_name=last_name,
first_name=first_name, first_name=first_name,
bank=bank, bank=bank,
@ -366,8 +348,8 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
user=user, user=user,
fee=bde_fee, fee=bde_fee,
) )
# if soge: if soge:
# membership._soge = True membership._soge = True
membership.save() membership.save()
membership.refresh_from_db() membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent BDE")) membership.roles.add(Role.objects.get(name="Adhérent BDE"))
@ -380,29 +362,17 @@ class FutureUserDetailView(ProtectQuerysetMixin, LoginRequiredMixin, FormMixin,
user=user, user=user,
fee=kfet_fee, fee=kfet_fee,
) )
# if soge: if soge:
# membership._soge = True membership._soge = True
membership.save() membership.save()
membership.refresh_from_db() membership.refresh_from_db()
membership.roles.add(Role.objects.get(name="Adhérent Kfet")) membership.roles.add(Role.objects.get(name="Adhérent Kfet"))
membership.save() membership.save()
if bda_exists and join_bda: if soge:
# Create membership for the user to the BDA starting today soge_credit = SogeCredit.objects.get(user=user)
membership = Membership( # Update the credit transaction amount
club=bda, soge_credit.save()
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 return ret

4
apps/sheets/__init__.py Normal file
View File

@ -0,0 +1,4 @@
# Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
default_app_config = 'sheets.apps.SheetsConfig'

46
apps/sheets/admin.py Normal file
View File

@ -0,0 +1,46 @@
# 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

View File

View File

@ -0,0 +1,55 @@
# 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__'

19
apps/sheets/api/urls.py Normal file
View File

@ -0,0 +1,19 @@
# 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)

78
apps/sheets/api/views.py Normal file
View File

@ -0,0 +1,78 @@
# 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', ]

10
apps/sheets/apps.py Normal file
View File

@ -0,0 +1,10 @@
# 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')

67
apps/sheets/forms.py Normal file
View File

@ -0,0 +1,67 @@
# 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(),
}

View File

@ -0,0 +1,157 @@
# 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'),
),
]

View File

@ -0,0 +1,34 @@
# 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'),
),
]

View File

289
apps/sheets/models.py Normal file
View File

@ -0,0 +1,289 @@
# 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")

22
apps/sheets/tables.py Normal file
View File

@ -0,0 +1,22 @@
# 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,))
}

View File

@ -0,0 +1,86 @@
{% 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 %}

View File

@ -0,0 +1,21 @@
{% 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 %}

View File

@ -0,0 +1,87 @@
{% 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 %}

View File

@ -0,0 +1,21 @@
{% 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 %}

View File

@ -0,0 +1,74 @@
{% 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 %}

View File

26
apps/sheets/urls.py Normal file
View File

@ -0,0 +1,26 @@
# 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"),
]

444
apps/sheets/views.py Normal file
View File

@ -0,0 +1,444 @@
# 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

View File

@ -1,18 +0,0 @@
# 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'),
),
]

View File

@ -1,18 +0,0 @@
# 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'),
),
]

View File

@ -1,5 +1,6 @@
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import datetime
from datetime import date from datetime import date
from django.conf import settings from django.conf import settings
@ -11,8 +12,7 @@ from django.db.models import Q
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
# from member.models import Club, Membership # Club unused because of disabled soge from member.models import Club, Membership
from member.models import Membership
from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction, NoteUser from note.models import NoteSpecial, SpecialTransaction, MembershipTransaction, NoteUser
@ -28,10 +28,8 @@ class Invoice(models.Model):
bde = models.CharField( bde = models.CharField(
max_length=32, max_length=32,
default='SecretStorlist', default='Saperlistpopette',
choices=( choices=(
('SecretStorlist', 'SecretStor[list]'),
('TotalistSpies', 'Tota[list]Spies'),
('Saperlistpopette', 'Saper[list]popette'), ('Saperlistpopette', 'Saper[list]popette'),
('Finalist', 'Fina[list]'), ('Finalist', 'Fina[list]'),
('Listorique', '[List]orique'), ('Listorique', '[List]orique'),
@ -97,7 +95,7 @@ class Invoice(models.Model):
products = self.products.all() products = self.products.all()
self.place = "Gif-sur-Yvette" self.place = "Gif-sur-Yvette"
self.my_name = "BDE ENS Paris Saclay" self.my_name = "BDE ENS Cachan"
self.my_address_street = "4 avenue des Sciences" self.my_address_street = "4 avenue des Sciences"
self.my_city = "91190 Gif-sur-Yvette" self.my_city = "91190 Gif-sur-Yvette"
self.bank_code = 30003 self.bank_code = 30003
@ -312,8 +310,8 @@ class SogeCredit(models.Model):
amount = sum(transaction.total for transaction in self.transactions.all()) amount = sum(transaction.total for transaction in self.transactions.all())
if 'wei' in settings.INSTALLED_APPS: if 'wei' in settings.INSTALLED_APPS:
from wei.models import WEIMembership from wei.models import WEIMembership
if not WEIMembership.objects\ if not WEIMembership.objects.filter(club__weiclub__year=datetime.date.today().year, user=self.user)\
.filter(club__weiclub__year=self.credit_transaction.created_at.year, user=self.user).exists(): .exists():
# 80 € for people that don't go to WEI # 80 € for people that don't go to WEI
amount += 8000 amount += 8000
return amount return amount
@ -326,23 +324,22 @@ class SogeCredit(models.Model):
if self.valid or not self.pk: if self.valid or not self.pk:
return return
# Soge do not pay BDE and kfet memberships since 2022 bde = Club.objects.get(name="BDE")
# bde = Club.objects.get(name="BDE") kfet = Club.objects.get(name="Kfet")
# kfet = Club.objects.get(name="Kfet") bde_qs = Membership.objects.filter(user=self.user, club=bde, date_start__gte=bde.membership_start)
# 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)
# kfet_qs = Membership.objects.filter(user=self.user, club=kfet, date_start__gte=kfet.membership_start)
# if bde_qs.exists(): if bde_qs.exists():
# m = bde_qs.get() m = bde_qs.get()
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
# if m.transaction not in self.transactions.all(): if m.transaction not in self.transactions.all():
# self.transactions.add(m.transaction) self.transactions.add(m.transaction)
#
# if kfet_qs.exists(): if kfet_qs.exists():
# m = kfet_qs.get() m = kfet_qs.get()
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
# if m.transaction not in self.transactions.all(): if m.transaction not in self.transactions.all():
# self.transactions.add(m.transaction) self.transactions.add(m.transaction)
if 'wei' in settings.INSTALLED_APPS: if 'wei' in settings.INSTALLED_APPS:
from wei.models import WEIClub from wei.models import WEIClub
@ -388,6 +385,7 @@ class SogeCredit(models.Model):
for tr in self.transactions.all(): for tr in self.transactions.all():
tr.valid = True tr.valid = True
tr._force_save = True tr._force_save = True
tr.created_at = timezone.now()
tr.save() tr.save()
@transaction.atomic @transaction.atomic
@ -436,11 +434,12 @@ class SogeCredit(models.Model):
for tr in self.transactions.all(): for tr in self.transactions.all():
tr._force_save = True tr._force_save = True
tr.valid = True tr.valid = True
tr.created_at = timezone.now()
tr.save() tr.save()
if self.credit_transaction: if self.credit_transaction:
# If the soge credit is deleted while the user is not validated yet, # If the soge credit is deleted while the user is not validated yet,
# there is not credit transaction. # there is not credit transaction.
# There is a credit transaction if the user declares that no bank account # There is a credit transaction iff the user declares that no bank account
# was opened after the validation of the account. # was opened after the validation of the account.
self.credit_transaction.valid = False self.credit_transaction.valid = False
self.credit_transaction.reason += " (invalide)" self.credit_transaction.reason += " (invalide)"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@ -105,8 +105,8 @@
\renewcommand{\headrulewidth}{0pt} \renewcommand{\headrulewidth}{0pt}
\cfoot{ \cfoot{
\small{\MonNom ~--~ \MonAdresseRue ~ \MonAdresseVille ~--~ Téléphone : +33(0)7 78 17 22 34\newline \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 00029 Site web : bde.ens-cachan.fr ~--~ E-mail : tresorerie.bde@lists.crans.org \newline Numéro SIRET : 399 485 838 00011
} }
} }

View File

@ -108,7 +108,7 @@ class InvoiceListView(LoginRequiredMixin, SingleTableView):
name="", name="",
address="", address="",
) )
if not PermissionBackend.check_perm(self.request, "treasury.view_invoice", sample_invoice): if not PermissionBackend.check_perm(self.request, "treasury.add_invoice", sample_invoice):
raise PermissionDenied(_("You are not able to see the treasury interface.")) raise PermissionDenied(_("You are not able to see the treasury interface."))
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django import forms from django import forms
@ -38,7 +38,7 @@ class WEIRegistrationForm(forms.ModelForm):
class Meta: class Meta:
model = WEIRegistration model = WEIRegistration
exclude = ('wei', 'clothing_cut') exclude = ('wei', )
widgets = { widgets = {
"user": Autocomplete( "user": Autocomplete(
User, User,

View File

@ -2,11 +2,11 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm
from .wei2023 import WEISurvey2023 from .wei2022 import WEISurvey2022
__all__ = [ __all__ = [
'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey', 'WEISurvey', 'WEISurveyInformation', 'WEISurveyAlgorithm', 'CurrentSurvey',
] ]
CurrentSurvey = WEISurvey2023 CurrentSurvey = WEISurvey2022

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay # Copyright (C) 2018-2022 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import time import time
@ -14,17 +14,14 @@ from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInf
from ...models import WEIMembership from ...models import WEIMembership
WORDS = [ WORDS = [
'ABBA', 'After', 'Alcoolique anonyme', 'Ambiance festive', 'Années 2000', 'Apéro', 'Art', '13 organisé', '3ième mi temps', 'Années 2000', 'Apéro', 'BBQ', 'BP', 'Beauf', 'Binge drinking', 'Bon enfant',
'Baby foot billard biere pong', 'BBQ', 'Before', 'Bière pong', 'Bon enfant', 'Calme', 'Canapé', 'Cartouche', 'Catacombes', 'Chansons paillardes', 'Chansons populaires', 'Chanteur', 'Chartreuse', 'Chill',
'Chanson paillarde', 'Chanson populaire', 'Chartreuse', 'Cheerleader', 'Chill', 'Choré', 'Core', 'DJ', 'Dancefloor', 'Danse', 'David Guetta', 'Disco', 'Eau de vie', 'Électro', 'Escalade', 'Familial',
'Cinéma', 'Cocktail', 'Comédie musicle', 'Commercial', 'Copaing', 'Danse', 'Dancefloor', 'Fanfare', 'Fracassage', 'Féria', 'Hard rock', 'Hoeggarden', 'House', 'Huit-six', 'IPA', 'Inclusif', 'Inferno',
'Electro', 'Fanfare', 'Gin tonic', 'Inclusif', 'Jazz', "Jeux d'alcool", 'Jeux de carte', 'Introverti', 'Jager bomb', 'Jazz', 'Jeux d\'alcool', 'Jeux de rôles', 'Jeux vidéo', 'Jul', 'Jus de fruit',
'Jeux de rôle', 'Jeux de société', 'JUL', 'Jus de fruit', 'Kfet', 'Kleptomanie assurée', 'Karaoké', 'LGBTQI+', 'Lady Gaga', 'Loup garou', 'Morning beer', 'Métal', 'Nuit blanche', 'Ovalie', 'Psychedelic',
'LGBTQ+', 'Livre', 'Morning beer', 'Musique', 'NAPS', 'Paillettes', 'Pastis', 'Paté Hénaff', 'Pétanque', 'Rave', 'Reggae', 'Rhum', 'Ricard', 'Rock', 'Rosé', 'Rétro', 'Séducteur', 'Techno', 'Thérapie taxi',
'Peluche', 'Pena baiona', "Peu d'alcool", 'Pilier de bar', 'PMU', 'Poulpe', 'Punch', 'Rap', 'Théâtre', 'Trap', 'Turn up', 'Underground', 'Volley', 'Wati B', 'Zinédine Zidane',
'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'
] ]

View File

@ -1,391 +0,0 @@
# 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()

View File

@ -1,18 +0,0 @@
# 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'),
),
]

View File

@ -1,18 +0,0 @@
# 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'),
),
]

View File

@ -1,18 +0,0 @@
# 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'),
),
]

View File

@ -1,18 +0,0 @@
# 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'),
),
]

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import json import json
@ -209,9 +209,7 @@ class WEIRegistration(models.Model):
choices=( choices=(
('male', _("Male")), ('male', _("Male")),
('female', _("Female")), ('female', _("Female")),
('unisex', _("Unisex")),
), ),
default='unisex',
verbose_name=_("clothing cut"), verbose_name=_("clothing cut"),
) )
@ -237,7 +235,6 @@ class WEIRegistration(models.Model):
emergency_contact_name = models.CharField( emergency_contact_name = models.CharField(
max_length=255, max_length=255,
verbose_name=_("emergency contact name"), verbose_name=_("emergency contact name"),
help_text=_("The emergency contact must not be a WEI participant")
) )
emergency_contact_phone = PhoneNumberField( emergency_contact_phone = PhoneNumberField(

View File

@ -56,7 +56,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
<dd class="col-xl-6">{{ registration.get_gender_display }}</dd> <dd class="col-xl-6">{{ registration.get_gender_display }}</dd>
<dt class="col-xl-6">{% trans 'clothing cut'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'clothing cut'|capfirst %}</dt>
<dd class="col-xl-6">{{ registration.get_clothing_cut_display }}</dd> <dd class="col-xl-6">{{ registration.clothing_cut }}</dd>
<dt class="col-xl-6">{% trans 'clothing size'|capfirst %}</dt> <dt class="col-xl-6">{% trans 'clothing size'|capfirst %}</dt>
<dd class="col-xl-6">{{ registration.clothing_size }}</dd> <dd class="col-xl-6">{{ registration.clothing_size }}</dd>

View File

@ -1,170 +0,0 @@
# 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())

View File

@ -1,4 +1,4 @@
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay # Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
import subprocess import subprocess
@ -380,7 +380,7 @@ class TestWEIRegistration(TestCase):
def test_register_1a(self): def test_register_1a(self):
""" """
Test register a first year member to the WEI. 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))) response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
@ -402,6 +402,21 @@ class TestWEIRegistration(TestCase):
self.assertTrue(qs.exists()) self.assertTrue(qs.exists())
registration = qs.get() registration = qs.get()
self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200) 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 # 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( response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict(
@ -647,7 +662,7 @@ class TestWEIRegistration(TestCase):
first_name="admin", first_name="admin",
bank="Société générale", bank="Société générale",
)) ))
self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200) self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=self.registration.wei.pk)), 302, 200)
# Check if the membership is successfully created # Check if the membership is successfully created
membership = WEIMembership.objects.filter(user_id=self.user.id, club=self.wei) membership = WEIMembership.objects.filter(user_id=self.user.id, club=self.wei)
self.assertTrue(membership.exists()) self.assertTrue(membership.exists())
@ -767,7 +782,7 @@ class TestDefaultWEISurvey(TestCase):
WEISurvey.update_form(None, None) WEISurvey.update_form(None, None)
self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey) self.assertEqual(CurrentSurvey.get_algorithm_class().get_survey_class(), CurrentSurvey)
self.assertEqual(CurrentSurvey.get_year(), 2023) self.assertEqual(CurrentSurvey.get_year(), 2022)
class TestWeiAPI(TestAPI): class TestWeiAPI(TestAPI):

View File

@ -969,7 +969,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
if not registration.soge_credit and user.note.balance + credit_amount < fee: if not registration.soge_credit and user.note.balance + credit_amount < fee:
# Users must have money before registering to the WEI. # Users must have money before registering to the WEI.
form.add_error('credit_type', form.add_error('bus',
_("This user don't have enough money to join this club, and can't have a negative balance.")) _("This user don't have enough money to join this club, and can't have a negative balance."))
return super().form_invalid(form) return super().form_invalid(form)
@ -1014,7 +1014,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
def get_success_url(self): def get_success_url(self):
self.object.refresh_from_db() self.object.refresh_from_db()
return reverse_lazy("wei:wei_registrations", kwargs={"pk": self.object.club.pk}) return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.club.pk})
class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView): class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
@ -1084,44 +1084,7 @@ class WEISurveyEndView(LoginRequiredMixin, TemplateView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs) context = super().get_context_data(**kwargs)
club = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei context["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 return context

View File

@ -448,10 +448,6 @@ Options
"value": "female", "value": "female",
"display_name": "Femme" "display_name": "Femme"
} }
{
"value": "unisex",
"display_name": "Unisexe"
},
] ]
}, },
"clothing_size": { "clothing_size": {

View File

@ -118,13 +118,13 @@ Exemples
{"F": [ {"F": [
"ADD", "ADD",
["F", "source__balance"], ["F", "source__balance"],
2000] 5000]
} }
} }
] ]
| 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 €, | 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 -20 €. autrement dit le solde final est au-dessus de -50 €.
Masques de permissions Masques de permissions

View File

@ -83,6 +83,13 @@ 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é bien sûr permis pour faciliter des transferts. Tout abus de droits constaté
pourra mener à des sanctions prises par le bureau du BDE. 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 Je suis trésorier d'un club, je n'arrive pas à voir le solde du club / faire des transactions
--------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------

View File

@ -615,7 +615,7 @@ pas déjà fait par créer un utilisateur sur les deux serveurs :
.. code:: bash .. code:: bash
ynerant@bde-note:~$ sudo -u postgres createuser -s ynerant ynerant@bde-note:~$ sudo -u postgres createuser -l ynerant
On réinitialise **sur le serveur de développement** la base de données présente, en On réinitialise **sur le serveur de développement** la base de données présente, en
éteignant tout d'abord le serveur Web : éteignant tout d'abord le serveur Web :

View File

@ -7,9 +7,9 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-18 17:27+0200\n" "POT-Creation-Date: 2022-04-10 22:34+0200\n"
"PO-Revision-Date: 2020-11-16 20:02+0000\n" "PO-Revision-Date: 2020-11-16 20:02+0000\n"
"Last-Translator: bleizi <bleizi@crans.org>\n" "Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20/de/>\n" "Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20/de/>\n"
"Language: de\n" "Language: de\n"
"MIME-Version: 1.0\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." msgstr "Sie dürfen höchstens 3 Leute zu dieser Veranstaltung einladen."
#: apps/activity/models.py:28 apps/activity/models.py:63 #: apps/activity/models.py:28 apps/activity/models.py:63
#: apps/member/models.py:204 #: apps/member/models.py:199
#: apps/member/templates/member/includes/club_info.html:4 #: apps/member/templates/member/includes/club_info.html:4
#: apps/member/templates/member/includes/profile_info.html:4 #: apps/member/templates/member/includes/profile_info.html:4
#: apps/note/models/notes.py:263 apps/note/models/transactions.py:26 #: 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" msgid "type"
msgstr "Type" msgstr "Type"
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:312 #: 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:287 #: apps/note/models/notes.py:148 apps/treasury/models.py:285
#: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13
#: apps/wei/templates/wei/survey.html:15 #: apps/wei/templates/wei/survey.html:15
msgid "user" msgid "user"
@ -258,19 +258,19 @@ msgstr "Eingetreten um "
msgid "remove" msgid "remove"
msgstr "entfernen" msgstr "entfernen"
#: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:201 #: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:199
msgid "Type" msgid "Type"
msgstr "Type" msgstr "Type"
#: apps/activity/tables.py:84 apps/member/forms.py:193 #: apps/activity/tables.py:84 apps/member/forms.py:186
#: apps/registration/forms.py:92 apps/treasury/forms.py:131 #: apps/registration/forms.py:91 apps/treasury/forms.py:131
#: apps/wei/forms/registration.py:104 #: apps/wei/forms/registration.py:104
msgid "Last name" msgid "Last name"
msgstr "Nachname" msgstr "Nachname"
#: apps/activity/tables.py:86 apps/member/forms.py:198 #: apps/activity/tables.py:86 apps/member/forms.py:191
#: apps/note/templates/note/transaction_form.html:138 #: apps/note/templates/note/transaction_form.html:138
#: apps/registration/forms.py:97 apps/treasury/forms.py:133 #: apps/registration/forms.py:96 apps/treasury/forms.py:133
#: apps/wei/forms/registration.py:109 #: apps/wei/forms/registration.py:109
msgid "First name" msgid "First name"
msgstr "Vorname" msgstr "Vorname"
@ -391,7 +391,7 @@ msgid "validate"
msgstr "" msgstr ""
#: apps/activity/templates/activity/includes/activity_info.html:71 #: apps/activity/templates/activity/includes/activity_info.html:71
#: apps/logs/models.py:64 apps/note/tables.py:260 #: apps/logs/models.py:64 apps/note/tables.py:220
msgid "edit" msgid "edit"
msgstr "bearbeiten" msgstr "bearbeiten"
@ -467,9 +467,9 @@ msgstr "neue Daten"
msgid "create" msgid "create"
msgstr "schaffen" msgstr "schaffen"
#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277 #: apps/logs/models.py:65 apps/note/tables.py:166 apps/note/tables.py:190
#: apps/permission/models.py:127 apps/treasury/tables.py:38 #: apps/note/tables.py:237 apps/permission/models.py:127
#: apps/wei/tables.py:74 #: apps/treasury/tables.py:38 apps/wei/tables.py:74
msgid "delete" msgid "delete"
msgstr "entfernen" msgstr "entfernen"
@ -498,21 +498,21 @@ msgstr "Changelogs"
msgid "Changelog of type \"{action}\" for model {model} at {timestamp}" msgid "Changelog of type \"{action}\" for model {model} at {timestamp}"
msgstr "Changelog \"{action}\" für Model {model} an {timestamp}" msgstr "Changelog \"{action}\" für Model {model} an {timestamp}"
#: apps/member/admin.py:50 apps/member/models.py:231 #: apps/member/admin.py:50 apps/member/models.py:226
#: apps/member/templates/member/includes/club_info.html:34 #: apps/member/templates/member/includes/club_info.html:34
msgid "membership fee (paid students)" msgid "membership fee (paid students)"
msgstr "Mitgliedschaftpreis (bezahlte Studenten)" msgstr "Mitgliedschaftpreis (bezahlte Studenten)"
#: apps/member/admin.py:51 apps/member/models.py:236 #: apps/member/admin.py:51 apps/member/models.py:231
#: apps/member/templates/member/includes/club_info.html:37 #: apps/member/templates/member/includes/club_info.html:37
msgid "membership fee (unpaid students)" msgid "membership fee (unpaid students)"
msgstr "Mitgliedschaftpreis (unbezahlte Studenten)" msgstr "Mitgliedschaftpreis (unbezahlte Studenten)"
#: apps/member/admin.py:65 apps/member/models.py:324 #: apps/member/admin.py:65 apps/member/models.py:319
msgid "roles" msgid "roles"
msgstr "Rollen" msgstr "Rollen"
#: apps/member/admin.py:66 apps/member/models.py:338 #: apps/member/admin.py:66 apps/member/models.py:333
msgid "fee" msgid "fee"
msgstr "Preis" msgstr "Preis"
@ -532,82 +532,65 @@ msgstr "Bericht Frequenz"
msgid "Last report date" msgid "Last report date"
msgstr "Letzen Bericht Datum" 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 #: 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." 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." msgstr "Sie dürfen nicht einloggen wenn sie aus der Zukunft kommen."
#: apps/member/forms.py:86 #: apps/member/forms.py:79
msgid "select an image" msgid "select an image"
msgstr "Wählen sie ein Bild aus" msgstr "Wählen sie ein Bild aus"
#: apps/member/forms.py:87 #: apps/member/forms.py:80
msgid "Maximal size: 2MB" msgid "Maximal size: 2MB"
msgstr "Maximal Größe: 2MB" msgstr "Maximal Größe: 2MB"
#: apps/member/forms.py:112 #: apps/member/forms.py:105
msgid "This image cannot be loaded." msgid "This image cannot be loaded."
msgstr "Dieses Bild kann nicht geladen werden." msgstr "Dieses Bild kann nicht geladen werden."
#: apps/member/forms.py:148 apps/member/views.py:102 #: apps/member/forms.py:141 apps/member/views.py:103
#: apps/registration/forms.py:34 apps/registration/views.py:266 #: apps/registration/forms.py:33 apps/registration/views.py:262
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "Ein ähnliches Alias ist schon benutzt." msgstr "Ein ähnliches Alias ist schon benutzt."
#: apps/member/forms.py:172 #: apps/member/forms.py:165 apps/registration/forms.py:71
msgid "Inscription paid by Société Générale" msgid "Inscription paid by Société Générale"
msgstr "Mitgliedschaft von der Société Générale bezahlt" msgstr "Mitgliedschaft von der Société Générale bezahlt"
#: apps/member/forms.py:174 #: apps/member/forms.py:167 apps/registration/forms.py:73
msgid "Check this case if the Société Générale paid the inscription." msgid "Check this case if the Société Générale paid the inscription."
msgstr "Die Société Générale die Mitgliedschaft bezahlt." msgstr "Die Société Générale die Mitgliedschaft bezahlt."
#: apps/member/forms.py:179 apps/registration/forms.py:79 #: apps/member/forms.py:172 apps/registration/forms.py:78
#: apps/wei/forms/registration.py:91 #: apps/wei/forms/registration.py:91
msgid "Credit type" msgid "Credit type"
msgstr "Kredittype" msgstr "Kredittype"
#: apps/member/forms.py:180 apps/registration/forms.py:80 #: apps/member/forms.py:173 apps/registration/forms.py:79
#: apps/wei/forms/registration.py:92 #: apps/wei/forms/registration.py:92
msgid "No credit" msgid "No credit"
msgstr "Kein Kredit" msgstr "Kein Kredit"
#: apps/member/forms.py:182 #: apps/member/forms.py:175
msgid "You can credit the note of the user." msgid "You can credit the note of the user."
msgstr "Sie dûrfen diese Note kreditieren." msgstr "Sie dûrfen diese Note kreditieren."
#: apps/member/forms.py:186 apps/registration/forms.py:85 #: apps/member/forms.py:179 apps/registration/forms.py:84
#: apps/wei/forms/registration.py:97 #: apps/wei/forms/registration.py:97
msgid "Credit amount" msgid "Credit amount"
msgstr "Kreditanzahl" msgstr "Kreditanzahl"
#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144 #: apps/member/forms.py:196 apps/note/templates/note/transaction_form.html:144
#: apps/registration/forms.py:102 apps/treasury/forms.py:135 #: apps/registration/forms.py:101 apps/treasury/forms.py:135
#: apps/wei/forms/registration.py:114 #: apps/wei/forms/registration.py:114
msgid "Bank" msgid "Bank"
msgstr "Bank" msgstr "Bank"
#: apps/member/forms.py:230 #: apps/member/forms.py:223
msgid "User" msgid "User"
msgstr "User" msgstr "User"
#: apps/member/forms.py:244 #: apps/member/forms.py:237
msgid "Roles" msgid "Roles"
msgstr "Rollen" msgstr "Rollen"
@ -794,19 +777,15 @@ msgstr "email bestätigt"
msgid "registration valid" msgid "registration valid"
msgstr "Anmeldung gültig" msgstr "Anmeldung gültig"
#: apps/member/models.py:138 #: apps/member/models.py:162 apps/member/models.py:163
msgid "VSS charter read"
msgstr "VSS-Charta gelesen"
#: apps/member/models.py:167 apps/member/models.py:168
msgid "user profile" msgid "user profile"
msgstr "Userprofile" msgstr "Userprofile"
#: apps/member/models.py:178 #: apps/member/models.py:173
msgid "Activate your Note Kfet account" msgid "Activate your Note Kfet account"
msgstr "Ihre Note Kfet Konto bestätigen" msgstr "Ihre Note Kfet Konto bestätigen"
#: apps/member/models.py:209 #: apps/member/models.py:204
#: apps/member/templates/member/includes/club_info.html:55 #: apps/member/templates/member/includes/club_info.html:55
#: apps/member/templates/member/includes/profile_info.html:40 #: apps/member/templates/member/includes/profile_info.html:40
#: apps/registration/templates/registration/future_profile_detail.html:22 #: apps/registration/templates/registration/future_profile_detail.html:22
@ -815,88 +794,88 @@ msgstr "Ihre Note Kfet Konto bestätigen"
msgid "email" msgid "email"
msgstr "Email" msgstr "Email"
#: apps/member/models.py:216 #: apps/member/models.py:211
msgid "parent club" msgid "parent club"
msgstr "Urclub" msgstr "Urclub"
#: apps/member/models.py:225 #: apps/member/models.py:220
msgid "require memberships" msgid "require memberships"
msgstr "erfordern Mitgliedschaft" msgstr "erfordern Mitgliedschaft"
#: apps/member/models.py:226 #: apps/member/models.py:221
msgid "Uncheck if this club don't require memberships." msgid "Uncheck if this club don't require memberships."
msgstr "" msgstr ""
"Deaktivieren Sie diese Option, wenn für diesen Club keine Mitgliedschaft " "Deaktivieren Sie diese Option, wenn für diesen Club keine Mitgliedschaft "
"erforderlich ist." "erforderlich ist."
#: apps/member/models.py:242 #: apps/member/models.py:237
#: apps/member/templates/member/includes/club_info.html:26 #: apps/member/templates/member/includes/club_info.html:26
msgid "membership duration" msgid "membership duration"
msgstr "Mitgliedscahftzeit" msgstr "Mitgliedscahftzeit"
#: apps/member/models.py:243 #: apps/member/models.py:238
msgid "The longest time (in days) a membership can last (NULL = infinite)." msgid "The longest time (in days) a membership can last (NULL = infinite)."
msgstr "Wie lang am höchsten eine Mitgliedschaft dauern kann." msgstr "Wie lang am höchsten eine Mitgliedschaft dauern kann."
#: apps/member/models.py:250 #: apps/member/models.py:245
#: apps/member/templates/member/includes/club_info.html:16 #: apps/member/templates/member/includes/club_info.html:16
msgid "membership start" msgid "membership start"
msgstr "Mitgliedschaftanfangsdatum" msgstr "Mitgliedschaftanfangsdatum"
#: apps/member/models.py:251 #: apps/member/models.py:246
msgid "Date from which the members can renew their membership." msgid "Date from which the members can renew their membership."
msgstr "Ab wann kann man sein Mitgliedschaft erneuern." msgstr "Ab wann kann man sein Mitgliedschaft erneuern."
#: apps/member/models.py:257 #: apps/member/models.py:252
#: apps/member/templates/member/includes/club_info.html:21 #: apps/member/templates/member/includes/club_info.html:21
msgid "membership end" msgid "membership end"
msgstr "Mitgliedschaftenddatum" msgstr "Mitgliedschaftenddatum"
#: apps/member/models.py:258 #: apps/member/models.py:253
msgid "Maximal date of a membership, after which members must renew it." msgid "Maximal date of a membership, after which members must renew it."
msgstr "" msgstr ""
"Maximales Datum einer Mitgliedschaft, nach dem Mitglieder es erneuern müssen." "Maximales Datum einer Mitgliedschaft, nach dem Mitglieder es erneuern müssen."
#: apps/member/models.py:293 apps/member/models.py:318 #: apps/member/models.py:288 apps/member/models.py:313
#: apps/note/models/notes.py:176 #: apps/note/models/notes.py:176
msgid "club" msgid "club"
msgstr "Club" msgstr "Club"
#: apps/member/models.py:294 #: apps/member/models.py:289
msgid "clubs" msgid "clubs"
msgstr "Clubs" msgstr "Clubs"
#: apps/member/models.py:329 #: apps/member/models.py:324
msgid "membership starts on" msgid "membership starts on"
msgstr "Mitgliedschaft fängt an" msgstr "Mitgliedschaft fängt an"
#: apps/member/models.py:333 #: apps/member/models.py:328
msgid "membership ends on" msgid "membership ends on"
msgstr "Mitgliedschaft endet am" msgstr "Mitgliedschaft endet am"
#: apps/member/models.py:435 #: apps/member/models.py:430
#, python-brace-format #, python-brace-format
msgid "The role {role} does not apply to the club {club}." msgid "The role {role} does not apply to the club {club}."
msgstr "Die Rolle {role} ist nicht erlaubt für das Club {club}." msgstr "Die Rolle {role} ist nicht erlaubt für das Club {club}."
#: apps/member/models.py:444 apps/member/views.py:712 #: apps/member/models.py:439 apps/member/views.py:712
msgid "User is already a member of the club" msgid "User is already a member of the club"
msgstr "User ist schon ein Mitglied dieser club" msgstr "User ist schon ein Mitglied dieser club"
#: apps/member/models.py:456 apps/member/views.py:721 #: apps/member/models.py:451 apps/member/views.py:721
msgid "User is not a member of the parent club" msgid "User is not a member of the parent club"
msgstr "User ist noch nicht Mitglied des Urclubs" msgstr "User ist noch nicht Mitglied des Urclubs"
#: apps/member/models.py:509 #: apps/member/models.py:504
#, python-brace-format #, python-brace-format
msgid "Membership of {user} for the club {club}" msgid "Membership of {user} for the club {club}"
msgstr "Mitgliedschaft von {user} für das Club {club}" msgstr "Mitgliedschaft von {user} für das Club {club}"
#: apps/member/models.py:512 apps/note/models/transactions.py:389 #: apps/member/models.py:507 apps/note/models/transactions.py:389
msgid "membership" msgid "membership"
msgstr "Mitgliedschaft" msgstr "Mitgliedschaft"
#: apps/member/models.py:513 #: apps/member/models.py:508
msgid "memberships" msgid "memberships"
msgstr "Mitgliedschaften" msgstr "Mitgliedschaften"
@ -951,7 +930,7 @@ msgid "Account #"
msgstr "Konto #" msgstr "Konto #"
#: apps/member/templates/member/base.html:48 #: apps/member/templates/member/base.html:48
#: apps/member/templates/member/base.html:62 apps/member/views.py:59 #: apps/member/templates/member/base.html:62 apps/member/views.py:60
#: apps/registration/templates/registration/future_profile_detail.html:48 #: apps/registration/templates/registration/future_profile_detail.html:48
#: apps/wei/templates/wei/weimembership_form.html:117 #: apps/wei/templates/wei/weimembership_form.html:117
msgid "Update Profile" msgid "Update Profile"
@ -1193,8 +1172,8 @@ msgstr "Click hier um eine Bestätigunglinke zu schicken."
msgid "View my memberships" msgid "View my memberships"
msgstr "Meine Mitgliedschaften schauen" msgstr "Meine Mitgliedschaften schauen"
#: apps/member/templates/member/profile_trust.html:10 #: apps/member/templates/member/profile_trust.html:10 apps/member/views.py:254
msgid "Add friends" msgid "Note friendships"
msgstr "" msgstr ""
#: apps/member/templates/member/profile_trust.html:28 #: apps/member/templates/member/profile_trust.html:28
@ -1206,10 +1185,6 @@ msgid ""
"without needing additional rights among them." "without needing additional rights among them."
msgstr "" 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 #: apps/member/templates/member/profile_update.html:18
msgid "Save Changes" msgid "Save Changes"
msgstr "Speichern" msgstr "Speichern"
@ -1218,22 +1193,18 @@ msgstr "Speichern"
msgid "Registrations" msgid "Registrations"
msgstr "Anmeldung" msgstr "Anmeldung"
#: apps/member/views.py:72 apps/registration/forms.py:24 #: apps/member/views.py:73 apps/registration/forms.py:23
msgid "This address must be valid." msgid "This address must be valid."
msgstr "Diese Adresse muss gültig sein." msgstr "Diese Adresse muss gültig sein."
#: apps/member/views.py:139 #: apps/member/views.py:140
msgid "Profile detail" msgid "Profile detail"
msgstr "Profile detail" msgstr "Profile detail"
#: apps/member/views.py:205 #: apps/member/views.py:206
msgid "Search user" msgid "Search user"
msgstr "User finden" msgstr "User finden"
#: apps/member/views.py:253
msgid "Note friendships"
msgstr ""
#: apps/member/views.py:308 #: apps/member/views.py:308
msgid "Update note picture" msgid "Update note picture"
msgstr "Notebild ändern" msgstr "Notebild ändern"
@ -1278,11 +1249,11 @@ msgstr "Die Mitgliedschaft muss nach {:%m-%d-Y} anfängen."
msgid "The membership must begin before {:%m-%d-%Y}." msgid "The membership must begin before {:%m-%d-%Y}."
msgstr "Die Mitgliedschaft muss vor {:%m-%d-Y} anfängen." msgstr "Die Mitgliedschaft muss vor {:%m-%d-Y} anfängen."
#: apps/member/views.py:880 #: apps/member/views.py:876
msgid "Manage roles of an user in the club" msgid "Manage roles of an user in the club"
msgstr "Rollen in diesen Club bearbeiten" msgstr "Rollen in diesen Club bearbeiten"
#: apps/member/views.py:905 #: apps/member/views.py:901
msgid "Members of the club" msgid "Members of the club"
msgstr "Mitlglieder dieses Club" msgstr "Mitlglieder dieses Club"
@ -1300,13 +1271,7 @@ msgstr "Empfänger"
msgid "amount" msgid "amount"
msgstr "Anzahl" msgstr "Anzahl"
#: apps/note/api/serializers.py:92 #: apps/note/api/serializers.py:199 apps/note/api/serializers.py:205
#, 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 #: apps/note/models/transactions.py:228
msgid "" msgid ""
"The transaction can't be saved since the source note or the destination note " "The transaction can't be saved since the source note or the destination note "
@ -1605,7 +1570,7 @@ msgstr "Sondertranskationen"
msgid "membership transaction" msgid "membership transaction"
msgstr "Mitgliedschafttransaktion" msgstr "Mitgliedschafttransaktion"
#: apps/note/models/transactions.py:385 apps/treasury/models.py:294 #: apps/note/models/transactions.py:385 apps/treasury/models.py:292
msgid "membership transactions" msgid "membership transactions"
msgstr "Mitgliedschaftttransaktionen" msgstr "Mitgliedschaftttransaktionen"
@ -1621,8 +1586,8 @@ msgstr "Klicken Sie zum gültigmachen"
msgid "No reason specified" msgid "No reason specified"
msgstr "Kein Grund gegeben" msgstr "Kein Grund gegeben"
#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234 #: apps/note/tables.py:173 apps/note/tables.py:194 apps/note/tables.py:239
#: apps/note/tables.py:279 apps/treasury/tables.py:39 #: apps/treasury/tables.py:39
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
#: apps/treasury/templates/treasury/sogecredit_detail.html:65 #: apps/treasury/templates/treasury/sogecredit_detail.html:65
#: apps/wei/tables.py:75 apps/wei/tables.py:118 #: apps/wei/tables.py:75 apps/wei/tables.py:118
@ -1633,17 +1598,7 @@ msgstr "Kein Grund gegeben"
msgid "Delete" msgid "Delete"
msgstr "Löschen" msgstr "Löschen"
#: apps/note/tables.py:191 #: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
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/tables.py:49 apps/wei/tables.py:50
#: apps/wei/templates/wei/base.html:89 #: apps/wei/templates/wei/base.html:89
#: apps/wei/templates/wei/bus_detail.html:20 #: apps/wei/templates/wei/bus_detail.html:20
@ -1653,7 +1608,7 @@ msgstr "Neue Bus"
msgid "Edit" msgid "Edit"
msgstr "Bearbeiten" msgstr "Bearbeiten"
#: apps/note/tables.py:266 apps/note/tables.py:293 #: apps/note/tables.py:226 apps/note/tables.py:253
msgid "Hide/Show" msgid "Hide/Show"
msgstr "" msgstr ""
@ -1734,7 +1689,7 @@ msgid "Amount"
msgstr "Anzahl" msgstr "Anzahl"
#: apps/note/templates/note/transaction_form.html:132 #: apps/note/templates/note/transaction_form.html:132
#: apps/treasury/models.py:56 #: apps/treasury/models.py:54
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
@ -1905,7 +1860,7 @@ msgstr "Angabefeld gilt nur zum Anzeigen und Ändern von Berechtigungstypen."
msgid "for club" msgid "for club"
msgstr "Für Club" msgstr "Für Club"
#: apps/permission/models.py:351 apps/permission/models.py:352 #: apps/permission/models.py:350 apps/permission/models.py:351
msgid "role permissions" msgid "role permissions"
msgstr "Berechtigung Rollen" msgstr "Berechtigung Rollen"
@ -2027,15 +1982,29 @@ msgstr "Alle Rechten"
msgid "registration" msgid "registration"
msgstr "Anmeldung" msgstr "Anmeldung"
#: apps/registration/forms.py:40 #: apps/registration/forms.py:39
msgid "This email address is already used." msgid "This email address is already used."
msgstr "Diese email adresse ist schon benutzt." msgstr "Diese email adresse ist schon benutzt."
#: apps/registration/forms.py:60 #: 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
msgid "Register to the WEI" msgid "Register to the WEI"
msgstr "Zu WEI anmelden" msgstr "Zu WEI anmelden"
#: apps/registration/forms.py:62 #: apps/registration/forms.py:61
msgid "" msgid ""
"Check this case if you want to register to the WEI. If you hesitate, you " "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." "will be able to register later, after validating your account in the Kfet."
@ -2044,18 +2013,14 @@ msgstr ""
"falls Zweifel, können Sie sich später nach Bestätigung Ihres Kontos im Kfet " "falls Zweifel, können Sie sich später nach Bestätigung Ihres Kontos im Kfet "
"registrieren." "registrieren."
#: apps/registration/forms.py:107 #: apps/registration/forms.py:106
msgid "Join BDE Club" msgid "Join BDE Club"
msgstr "BDE Mitglieder werden" msgstr "BDE Mitglieder werden"
#: apps/registration/forms.py:114 #: apps/registration/forms.py:113
msgid "Join Kfet Club" msgid "Join Kfet Club"
msgstr "Kfet Mitglieder werden" 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 #: apps/registration/templates/registration/email_validation_complete.html:15
msgid "Your email have successfully been validated." msgid "Your email have successfully been validated."
msgstr "Ihre E-Mail wurde erfolgreich validiert." msgstr "Ihre E-Mail wurde erfolgreich validiert."
@ -2104,14 +2069,14 @@ msgstr "Registrierung löschen"
msgid "Validate account" msgid "Validate account"
msgstr "Konto validieren" msgstr "Konto validieren"
#: apps/registration/templates/registration/future_profile_detail.html:63 #: apps/registration/templates/registration/future_profile_detail.html:62
#, fuzzy #, fuzzy
#| msgid "You already opened an account in the Société générale." #| msgid "You already opened an account in the Société générale."
msgid "" msgid ""
"The user declared that he/she opened a bank account in the Société générale." "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." msgstr "Sie haben bereits ein Konto in der Société générale eröffnet."
#: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/registration/templates/registration/future_profile_detail.html:71
#: apps/wei/templates/wei/weimembership_form.html:127 #: apps/wei/templates/wei/weimembership_form.html:127
#: apps/wei/templates/wei/weimembership_form.html:186 #: apps/wei/templates/wei/weimembership_form.html:186
msgid "Validate registration" msgid "Validate registration"
@ -2163,54 +2128,54 @@ msgstr "Danke"
msgid "The Note Kfet team." msgid "The Note Kfet team."
msgstr "Die NoteKfet Team." msgstr "Die NoteKfet Team."
#: apps/registration/views.py:41 #: apps/registration/views.py:40
msgid "Register new user" msgid "Register new user"
msgstr "Neuen User registrieren" msgstr "Neuen User registrieren"
#: apps/registration/views.py:99 #: apps/registration/views.py:98
msgid "Email validation" msgid "Email validation"
msgstr "Email validierung" msgstr "Email validierung"
#: apps/registration/views.py:101 #: apps/registration/views.py:100
msgid "Validate email" msgid "Validate email"
msgstr "Email validieren" msgstr "Email validieren"
#: apps/registration/views.py:145 #: apps/registration/views.py:144
msgid "Email validation unsuccessful" msgid "Email validation unsuccessful"
msgstr "Email validierung unerfolgreich" msgstr "Email validierung unerfolgreich"
#: apps/registration/views.py:156 #: apps/registration/views.py:155
msgid "Email validation email sent" msgid "Email validation email sent"
msgstr "Validierungsemail wurde gesendet" msgstr "Validierungsemail wurde gesendet"
#: apps/registration/views.py:164 #: apps/registration/views.py:163
msgid "Resend email validation link" msgid "Resend email validation link"
msgstr "E-Mail-Validierungslink erneut senden" msgstr "E-Mail-Validierungslink erneut senden"
#: apps/registration/views.py:182 #: apps/registration/views.py:181
msgid "Pre-registered users list" msgid "Pre-registered users list"
msgstr "Vorregistrierte Userliste" msgstr "Vorregistrierte Userliste"
#: apps/registration/views.py:206 #: apps/registration/views.py:205
msgid "Unregistered users" msgid "Unregistered users"
msgstr "Unregistrierte Users" msgstr "Unregistrierte Users"
#: apps/registration/views.py:219 #: apps/registration/views.py:218
msgid "Registration detail" msgid "Registration detail"
msgstr "Registrierung Detailen" msgstr "Registrierung Detailen"
#: apps/registration/views.py:293 #: apps/registration/views.py:282
msgid "You must join the BDE." msgid "You must join the BDE."
msgstr "Sie müssen die BDE beitreten." msgstr "Sie müssen die BDE beitreten."
#: apps/registration/views.py:323 #: apps/registration/views.py:306
msgid "" msgid ""
"The entered amount is not enough for the memberships, should be at least {}" "The entered amount is not enough for the memberships, should be at least {}"
msgstr "" msgstr ""
"Der eingegebene Betrag reicht für die Mitgliedschaft nicht aus, sollte " "Der eingegebene Betrag reicht für die Mitgliedschaft nicht aus, sollte "
"mindestens {} betragen" "mindestens {} betragen"
#: apps/registration/views.py:417 #: apps/registration/views.py:387
msgid "Invalidate pre-registration" msgid "Invalidate pre-registration"
msgstr "Ungültige Vorregistrierung" msgstr "Ungültige Vorregistrierung"
@ -2218,7 +2183,7 @@ msgstr "Ungültige Vorregistrierung"
msgid "Treasury" msgid "Treasury"
msgstr "Quaestor" msgstr "Quaestor"
#: apps/treasury/forms.py:26 apps/treasury/models.py:95 #: apps/treasury/forms.py:26 apps/treasury/models.py:93
#: apps/treasury/templates/treasury/invoice_form.html:22 #: apps/treasury/templates/treasury/invoice_form.html:22
msgid "This invoice is locked and can no longer be edited." msgid "This invoice is locked and can no longer be edited."
msgstr "Diese Rechnung ist gesperrt und kann nicht mehr bearbeitet werden." msgstr "Diese Rechnung ist gesperrt und kann nicht mehr bearbeitet werden."
@ -2231,7 +2196,7 @@ msgstr "Überweisung ist bereits geschlossen."
msgid "You can't change the type of the remittance." msgid "You can't change the type of the remittance."
msgstr "Sie können die Art der Überweisung nicht ändern." msgstr "Sie können die Art der Überweisung nicht ändern."
#: apps/treasury/forms.py:125 apps/treasury/models.py:269 #: apps/treasury/forms.py:125 apps/treasury/models.py:267
#: apps/treasury/tables.py:97 apps/treasury/tables.py:105 #: apps/treasury/tables.py:97 apps/treasury/tables.py:105
#: apps/treasury/templates/treasury/invoice_list.html:16 #: apps/treasury/templates/treasury/invoice_list.html:16
#: apps/treasury/templates/treasury/remittance_list.html:16 #: apps/treasury/templates/treasury/remittance_list.html:16
@ -2247,116 +2212,116 @@ msgstr "Keine beigefügte Überweisung"
msgid "Invoice identifier" msgid "Invoice identifier"
msgstr "Rechnungskennung" msgstr "Rechnungskennung"
#: apps/treasury/models.py:42 #: apps/treasury/models.py:40
msgid "BDE" msgid "BDE"
msgstr "BDE" msgstr "BDE"
#: apps/treasury/models.py:47 #: apps/treasury/models.py:45
msgid "Object" msgid "Object"
msgstr "Objekt" msgstr "Objekt"
#: apps/treasury/models.py:51 #: apps/treasury/models.py:49
msgid "Description" msgid "Description"
msgstr "Beschreibung" msgstr "Beschreibung"
#: apps/treasury/models.py:60 #: apps/treasury/models.py:58
msgid "Address" msgid "Address"
msgstr "Adresse" msgstr "Adresse"
#: apps/treasury/models.py:65 apps/treasury/models.py:195 #: apps/treasury/models.py:63 apps/treasury/models.py:193
msgid "Date" msgid "Date"
msgstr "Datum" msgstr "Datum"
#: apps/treasury/models.py:69 #: apps/treasury/models.py:67
msgid "Acquitted" msgid "Acquitted"
msgstr "Bezahlt" msgstr "Bezahlt"
#: apps/treasury/models.py:74 #: apps/treasury/models.py:72
msgid "Locked" msgid "Locked"
msgstr "Gesperrt" msgstr "Gesperrt"
#: apps/treasury/models.py:75 #: apps/treasury/models.py:73
msgid "An invoice can't be edited when it is locked." msgid "An invoice can't be edited when it is locked."
msgstr "Eine Rechnung kann nicht bearbeitet werden, wenn sie gesperrt ist." msgstr "Eine Rechnung kann nicht bearbeitet werden, wenn sie gesperrt ist."
#: apps/treasury/models.py:81 #: apps/treasury/models.py:79
msgid "tex source" msgid "tex source"
msgstr "Tex Quelle" msgstr "Tex Quelle"
#: apps/treasury/models.py:115 apps/treasury/models.py:131 #: apps/treasury/models.py:113 apps/treasury/models.py:129
msgid "invoice" msgid "invoice"
msgstr "Rechnung" msgstr "Rechnung"
#: apps/treasury/models.py:116 #: apps/treasury/models.py:114
msgid "invoices" msgid "invoices"
msgstr "Rechnungen" msgstr "Rechnungen"
#: apps/treasury/models.py:119 #: apps/treasury/models.py:117
#, python-brace-format #, python-brace-format
msgid "Invoice #{id}" msgid "Invoice #{id}"
msgstr "Rechnung #{id}" msgstr "Rechnung #{id}"
#: apps/treasury/models.py:136 #: apps/treasury/models.py:134
msgid "Designation" msgid "Designation"
msgstr "Bezeichnung" msgstr "Bezeichnung"
#: apps/treasury/models.py:142 #: apps/treasury/models.py:140
msgid "Quantity" msgid "Quantity"
msgstr "Qualität" msgstr "Qualität"
#: apps/treasury/models.py:147 #: apps/treasury/models.py:145
msgid "Unit price" msgid "Unit price"
msgstr "Einzelpreis" msgstr "Einzelpreis"
#: apps/treasury/models.py:163 #: apps/treasury/models.py:161
msgid "product" msgid "product"
msgstr "Produkt" msgstr "Produkt"
#: apps/treasury/models.py:164 #: apps/treasury/models.py:162
msgid "products" msgid "products"
msgstr "Produkten" msgstr "Produkten"
#: apps/treasury/models.py:184 #: apps/treasury/models.py:182
msgid "remittance type" msgid "remittance type"
msgstr "Überweisungstyp" msgstr "Überweisungstyp"
#: apps/treasury/models.py:185 #: apps/treasury/models.py:183
msgid "remittance types" msgid "remittance types"
msgstr "Überweisungstypen" msgstr "Überweisungstypen"
#: apps/treasury/models.py:206 #: apps/treasury/models.py:204
msgid "Comment" msgid "Comment"
msgstr "Kommentar" msgstr "Kommentar"
#: apps/treasury/models.py:211 #: apps/treasury/models.py:209
msgid "Closed" msgid "Closed"
msgstr "Geschlossen" msgstr "Geschlossen"
#: apps/treasury/models.py:215 #: apps/treasury/models.py:213
msgid "remittance" msgid "remittance"
msgstr "Überweisung" msgstr "Überweisung"
#: apps/treasury/models.py:216 #: apps/treasury/models.py:214
msgid "remittances" msgid "remittances"
msgstr "Überweisungen" msgstr "Überweisungen"
#: apps/treasury/models.py:249 #: apps/treasury/models.py:247
msgid "Remittance #{:d}: {}" msgid "Remittance #{:d}: {}"
msgstr "Überweisung #{:d}:{}" msgstr "Überweisung #{:d}:{}"
#: apps/treasury/models.py:273 #: apps/treasury/models.py:271
msgid "special transaction proxy" msgid "special transaction proxy"
msgstr "spezielle Transaktion Proxy" msgstr "spezielle Transaktion Proxy"
#: apps/treasury/models.py:274 #: apps/treasury/models.py:272
msgid "special transaction proxies" msgid "special transaction proxies"
msgstr "spezielle Transaktion Proxies" msgstr "spezielle Transaktion Proxies"
#: apps/treasury/models.py:300 #: apps/treasury/models.py:298
msgid "credit transaction" msgid "credit transaction"
msgstr "Kredit Transaktion" msgstr "Kredit Transaktion"
#: apps/treasury/models.py:432 #: apps/treasury/models.py:430
msgid "" msgid ""
"This user doesn't have enough money to pay the memberships with its note. " "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." "Please ask her/him to credit the note before invalidating this credit."
@ -2364,16 +2329,16 @@ msgstr ""
"Dieser Benutzer hat nicht genug Geld, um die Mitgliedschaften mit seiner " "Dieser Benutzer hat nicht genug Geld, um die Mitgliedschaften mit seiner "
"Note zu bezahlen." "Note zu bezahlen."
#: apps/treasury/models.py:452 #: apps/treasury/models.py:451
#: apps/treasury/templates/treasury/sogecredit_detail.html:10 #: apps/treasury/templates/treasury/sogecredit_detail.html:10
msgid "Credit from the Société générale" msgid "Credit from the Société générale"
msgstr "Kredit von der Société générale" msgstr "Kredit von der Société générale"
#: apps/treasury/models.py:453 #: apps/treasury/models.py:452
msgid "Credits from the Société générale" msgid "Credits from the Société générale"
msgstr "Krediten von der Société générale" msgstr "Krediten von der Société générale"
#: apps/treasury/models.py:456 #: apps/treasury/models.py:455
#, python-brace-format #, python-brace-format
msgid "Soge credit for {user}" msgid "Soge credit for {user}"
msgstr "Kredit von der Société générale für {user}" msgstr "Kredit von der Société générale für {user}"
@ -2637,7 +2602,7 @@ msgid "The selected user is not validated. Please validate its account first"
msgstr "" msgstr ""
#: apps/wei/forms/registration.py:59 apps/wei/models.py:126 #: apps/wei/forms/registration.py:59 apps/wei/models.py:126
#: apps/wei/models.py:326 #: apps/wei/models.py:323
msgid "bus" msgid "bus"
msgstr "Bus" msgstr "Bus"
@ -2675,7 +2640,7 @@ msgstr "Wählen Sie die Rollen aus, an denen Sie interessiert sind."
msgid "This team doesn't belong to the given bus." msgid "This team doesn't belong to the given bus."
msgstr "Dieses Team gehört nicht zum angegebenen Bus." msgstr "Dieses Team gehört nicht zum angegebenen Bus."
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38 #: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:35
msgid "Choose a word:" msgid "Choose a word:"
msgstr "Wählen Sie ein Wort:" msgstr "Wählen Sie ein Wort:"
@ -2762,48 +2727,40 @@ msgstr "Nicht binär"
msgid "gender" msgid "gender"
msgstr "Geschlecht" msgstr "Geschlecht"
#: apps/wei/models.py:212 #: apps/wei/models.py:213 apps/wei/templates/wei/weimembership_form.html:58
msgid "Unisex"
msgstr "Unisex"
#: apps/wei/models.py:215 apps/wei/templates/wei/weimembership_form.html:58
msgid "clothing cut" msgid "clothing cut"
msgstr "Kleidung Schnitt" msgstr "Kleidung Schnitt"
#: apps/wei/models.py:228 apps/wei/templates/wei/weimembership_form.html:61 #: apps/wei/models.py:226 apps/wei/templates/wei/weimembership_form.html:61
msgid "clothing size" msgid "clothing size"
msgstr "Kleidergröße" msgstr "Kleidergröße"
#: apps/wei/models.py:234 apps/wei/templates/wei/attribute_bus_1A.html:28 #: apps/wei/models.py:232 apps/wei/templates/wei/attribute_bus_1A.html:28
#: apps/wei/templates/wei/weimembership_form.html:67 #: apps/wei/templates/wei/weimembership_form.html:67
msgid "health issues" msgid "health issues"
msgstr "Gesundheitsprobleme" msgstr "Gesundheitsprobleme"
#: apps/wei/models.py:239 apps/wei/templates/wei/weimembership_form.html:70 #: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:70
msgid "emergency contact name" msgid "emergency contact name"
msgstr "Notfall-Kontakt" msgstr "Notfall-Kontakt"
#: apps/wei/models.py:240 #: apps/wei/models.py:242 apps/wei/templates/wei/weimembership_form.html:73
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" msgid "emergency contact phone"
msgstr "Notfallkontakttelefon" msgstr "Notfallkontakttelefon"
#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:52 #: apps/wei/models.py:247 apps/wei/templates/wei/weimembership_form.html:52
msgid "first year" msgid "first year"
msgstr "Erste Jahr" msgstr "Erste Jahr"
#: apps/wei/models.py:251 #: apps/wei/models.py:248
msgid "Tells if the user is new in the school." msgid "Tells if the user is new in the school."
msgstr "Gibt an, ob der USer neu in der Schule ist." msgstr "Gibt an, ob der USer neu in der Schule ist."
#: apps/wei/models.py:256 #: apps/wei/models.py:253
msgid "registration information" msgid "registration information"
msgstr "Registrierung Detailen" msgstr "Registrierung Detailen"
#: apps/wei/models.py:257 #: apps/wei/models.py:254
msgid "" msgid ""
"Information about the registration (buses for old members, survey for the " "Information about the registration (buses for old members, survey for the "
"new members), encoded in JSON" "new members), encoded in JSON"
@ -2811,27 +2768,27 @@ msgstr ""
"Informationen zur Registrierung (Busse für alte Mitglieder, Umfrage für neue " "Informationen zur Registrierung (Busse für alte Mitglieder, Umfrage für neue "
"Mitglieder), verschlüsselt in JSON" "Mitglieder), verschlüsselt in JSON"
#: apps/wei/models.py:315 #: apps/wei/models.py:312
msgid "WEI User" msgid "WEI User"
msgstr "WEI User" msgstr "WEI User"
#: apps/wei/models.py:316 #: apps/wei/models.py:313
msgid "WEI Users" msgid "WEI Users"
msgstr "WEI Users" msgstr "WEI Users"
#: apps/wei/models.py:336 #: apps/wei/models.py:333
msgid "team" msgid "team"
msgstr "Team" msgstr "Team"
#: apps/wei/models.py:346 #: apps/wei/models.py:343
msgid "WEI registration" msgid "WEI registration"
msgstr "WEI Registrierung" msgstr "WEI Registrierung"
#: apps/wei/models.py:350 #: apps/wei/models.py:347
msgid "WEI membership" msgid "WEI membership"
msgstr "WEI Mitgliedschaft" msgstr "WEI Mitgliedschaft"
#: apps/wei/models.py:351 #: apps/wei/models.py:348
msgid "WEI memberships" msgid "WEI memberships"
msgstr "WEI Mitgliedschaften" msgstr "WEI Mitgliedschaften"
@ -2976,7 +2933,7 @@ msgstr "Als PDF schauen"
#: apps/wei/templates/wei/survey.html:11 #: apps/wei/templates/wei/survey.html:11
#: apps/wei/templates/wei/survey_closed.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/templates/wei/survey_end.html:11 apps/wei/views.py:1028
#: apps/wei/views.py:1083 apps/wei/views.py:1130 #: apps/wei/views.py:1083 apps/wei/views.py:1093
msgid "Survey WEI" msgid "Survey WEI"
msgstr "WEI Umfrage" msgstr "WEI Umfrage"
@ -3254,11 +3211,11 @@ msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen."
msgid "Validate WEI registration" msgid "Validate WEI registration"
msgstr "Überprüfen Sie die WEI-Registrierung" msgstr "Überprüfen Sie die WEI-Registrierung"
#: apps/wei/views.py:1223 #: apps/wei/views.py:1186
msgid "Attribute buses to first year members" msgid "Attribute buses to first year members"
msgstr "" msgstr ""
#: apps/wei/views.py:1248 #: apps/wei/views.py:1211
msgid "Attribute bus" msgid "Attribute bus"
msgstr "" msgstr ""
@ -3404,10 +3361,6 @@ msgstr "Kontakt"
msgid "Technical Support" msgid "Technical Support"
msgstr "" msgstr ""
#: note_kfet/templates/base.html:198
msgid "FAQ (FR)"
msgstr "FAQ (FR)"
#: note_kfet/templates/base_search.html:15 #: note_kfet/templates/base_search.html:15
msgid "Search by attribute such as name…" msgid "Search by attribute such as name…"
msgstr "Suche nach Attributen wie Name…" msgstr "Suche nach Attributen wie Name…"
@ -3655,16 +3608,10 @@ msgstr ""
"müssen Ihre E-Mail-Adresse auch überprüfen, indem Sie dem Link folgen, den " "müssen Ihre E-Mail-Adresse auch überprüfen, indem Sie dem Link folgen, den "
"Sie erhalten haben." "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." #~ msgid "This user didn't give her/his caution check."
#~ msgstr "Dieser User hat seine / ihre Vorsicht nicht überprüft." #~ msgstr "Dieser User hat seine / ihre Vorsicht nicht überprüft."
#, python-format
#~ msgid "" #~ msgid ""
#~ "A new version of the application is available. This instance runs " #~ "A new version of the application is available. This instance runs "
#~ "%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider " #~ "%(VERSION)s and the last version is %(LAST_VERSION)s. Please consider "

View File

@ -7,11 +7,11 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-07 09:07+0200\n" "POT-Creation-Date: 2020-11-15 23:21+0100\n"
"PO-Revision-Date: 2020-11-16 20:21+0000\n" "PO-Revision-Date: 2020-11-16 20:21+0000\n"
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n" "Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/" "Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/>"
">\n" "\n"
"Language: de\n" "Language: de\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
@ -27,22 +27,6 @@ msgstr "Alias erfolgreich hinzugefügt"
msgid "Alias successfully deleted" msgid "Alias successfully deleted"
msgstr "Alias erfolgreich gelöscht" 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 #: apps/note/static/note/js/consos.js:225
#, javascript-format #, javascript-format
msgid "" msgid ""
@ -62,32 +46,32 @@ msgstr ""
"ist negativ." "ist negativ."
#: apps/note/static/note/js/consos.js:232 #: apps/note/static/note/js/consos.js:232
#: apps/note/static/note/js/transfer.js:309 #: apps/note/static/note/js/transfer.js:298
#: apps/note/static/note/js/transfer.js:412 #: apps/note/static/note/js/transfer.js:401
#, javascript-format #, javascript-format
msgid "Warning, the emitter note %s is no more a BDE member." msgid "Warning, the emitter note %s is no more a BDE member."
msgstr "Warnung, der Emittent Hinweis %s ist kein BDE-Mitglied mehr." msgstr "Warnung, der Emittent Hinweis %s ist kein BDE-Mitglied mehr."
#: apps/note/static/note/js/consos.js:254 #: apps/note/static/note/js/consos.js:253
msgid "The transaction couldn't be validated because of insufficient balance." msgid "The transaction couldn't be validated because of insufficient balance."
msgstr "" msgstr ""
"Die Transaktion konnte aufgrund eines unzureichenden Saldos nicht validiert " "Die Transaktion konnte aufgrund eines unzureichenden Saldos nicht validiert "
"werden." "werden."
#: apps/note/static/note/js/transfer.js:249 #: apps/note/static/note/js/transfer.js:238
msgid "This field is required and must contain a decimal positive number." msgid "This field is required and must contain a decimal positive number."
msgstr "" msgstr ""
"Dieses Feld ist erforderlich und muss eine positive Dezimalzahl enthalten." "Dieses Feld ist erforderlich und muss eine positive Dezimalzahl enthalten."
#: apps/note/static/note/js/transfer.js:256 #: apps/note/static/note/js/transfer.js:245
msgid "The amount must stay under 21,474,836.47 €." msgid "The amount must stay under 21,474,836.47 €."
msgstr "Der Betrag muss unter 21.474.836,47 € bleiben." msgstr "Der Betrag muss unter 21.474.836,47 € bleiben."
#: apps/note/static/note/js/transfer.js:262 #: apps/note/static/note/js/transfer.js:251
msgid "This field is required." msgid "This field is required."
msgstr "Dies ist ein Pflichtfeld." msgstr "Dies ist ein Pflichtfeld."
#: apps/note/static/note/js/transfer.js:288 #: apps/note/static/note/js/transfer.js:277
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning: the transaction of %s from %s to %s was not made because it is the " "Warning: the transaction of %s from %s to %s was not made because it is the "
@ -96,12 +80,12 @@ msgstr ""
"Warnung: Die Transaktion von %s von %s nach %s wurde nicht durchgeführt, da " "Warnung: Die Transaktion von %s von %s nach %s wurde nicht durchgeführt, da "
"es sich um die gleiche Quell- und Zielnotiz handelt." "es sich um die gleiche Quell- und Zielnotiz handelt."
#: apps/note/static/note/js/transfer.js:312 #: apps/note/static/note/js/transfer.js:301
#, javascript-format #, javascript-format
msgid "Warning, the destination note %s is no more a BDE member." msgid "Warning, the destination note %s is no more a BDE member."
msgstr "Warnung, der Bestimmungsvermerk %s ist kein BDE-Mitglied mehr." msgstr "Warnung, der Bestimmungsvermerk %s ist kein BDE-Mitglied mehr."
#: apps/note/static/note/js/transfer.js:318 #: apps/note/static/note/js/transfer.js:307
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning, the transaction of %s from the note %s to the note %s succeed, but " "Warning, the transaction of %s from the note %s to the note %s succeed, but "
@ -110,7 +94,7 @@ msgstr ""
"Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber " "Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber "
"die Emitternote %s ist sehr negativ." "die Emitternote %s ist sehr negativ."
#: apps/note/static/note/js/transfer.js:323 #: apps/note/static/note/js/transfer.js:312
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning, the transaction of %s from the note %s to the note %s succeed, but " "Warning, the transaction of %s from the note %s to the note %s succeed, but "
@ -119,32 +103,31 @@ msgstr ""
"Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber " "Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber "
"die Emitternote %s ist negativ." "die Emitternote %s ist negativ."
#: apps/note/static/note/js/transfer.js:329 #: apps/note/static/note/js/transfer.js:318
#, javascript-format #, javascript-format
msgid "Transfer of %s from %s to %s succeed!" msgid "Transfer of %s from %s to %s succeed!"
msgstr "Übertragung von %s von %s auf %s gelingt!" msgstr "Übertragung von %s von %s auf %s gelingt!"
#: apps/note/static/note/js/transfer.js:336 #: apps/note/static/note/js/transfer.js:325
#: apps/note/static/note/js/transfer.js:357 #: apps/note/static/note/js/transfer.js:346
#: apps/note/static/note/js/transfer.js:364 #: apps/note/static/note/js/transfer.js:353
#, javascript-format #, javascript-format
msgid "Transfer of %s from %s to %s failed: %s" msgid "Transfer of %s from %s to %s failed: %s"
msgstr "Übertragung von %s von %s auf %s fehlgeschlagen: %s" msgstr "Übertragung von %s von %s auf %s fehlgeschlagen: %s"
#: apps/note/static/note/js/transfer.js:358 #: apps/note/static/note/js/transfer.js:347
msgid "insufficient funds" msgid "insufficient funds"
msgstr "unzureichende Geldmittel" msgstr "unzureichende Geldmittel"
#: apps/note/static/note/js/transfer.js:411 #: apps/note/static/note/js/transfer.js:400
msgid "Credit/debit succeed!" msgid "Credit/debit succeed!"
msgstr "Kredit/Debit erfolgreich!" msgstr "Kredit/Debit erfolgreich!"
#: apps/note/static/note/js/transfer.js:418 #: apps/note/static/note/js/transfer.js:407
#, javascript-format #, javascript-format
msgid "Credit/debit failed: %s" msgid "Credit/debit failed: %s"
msgstr "Kredit/Debit fehlgeschlagen: %s" msgstr "Kredit/Debit fehlgeschlagen: %s"
#: note_kfet/static/js/base.js:370 #: note_kfet/static/js/base.js:366
msgid "An error occured while (in)validating this transaction:" msgid "An error occured while (in)validating this transaction:"
msgstr "" msgstr "Bei der (Un-)Validierung dieser Transaktion ist ein Fehler aufgetreten:"
"Bei der (Un-)Validierung dieser Transaktion ist ein Fehler aufgetreten:"

View File

@ -7,16 +7,16 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2023-09-18 17:27+0200\n" "POT-Creation-Date: 2022-04-10 22:34+0200\n"
"PO-Revision-Date: 2022-04-11 23:12+0200\n" "PO-Revision-Date: 2022-04-11 23:12+0200\n"
"Last-Translator: bleizi <bleizi@crans.org>\n" "Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
"Language-Team: \n" "Language-Team: \n"
"Language: es\n" "Language: es\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0.1\n" "X-Generator: Poedit 3.0\n"
#: apps/activity/apps.py:10 apps/activity/models.py:151 #: apps/activity/apps.py:10 apps/activity/models.py:151
#: apps/activity/models.py:167 #: 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." msgstr "Usted no puede invitar más de 3 persona a esta actividad."
#: apps/activity/models.py:28 apps/activity/models.py:63 #: apps/activity/models.py:28 apps/activity/models.py:63
#: apps/member/models.py:204 #: apps/member/models.py:199
#: apps/member/templates/member/includes/club_info.html:4 #: apps/member/templates/member/includes/club_info.html:4
#: apps/member/templates/member/includes/profile_info.html:4 #: apps/member/templates/member/includes/profile_info.html:4
#: apps/note/models/notes.py:263 apps/note/models/transactions.py:26 #: 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" msgid "type"
msgstr "tipo" msgstr "tipo"
#: apps/activity/models.py:89 apps/logs/models.py:22 apps/member/models.py:312 #: 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:287 #: apps/note/models/notes.py:148 apps/treasury/models.py:285
#: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13 #: apps/wei/models.py:173 apps/wei/templates/wei/attribute_bus_1A.html:13
#: apps/wei/templates/wei/survey.html:15 #: apps/wei/templates/wei/survey.html:15
msgid "user" msgid "user"
@ -257,19 +257,19 @@ msgstr "Entrado el "
msgid "remove" msgid "remove"
msgstr "quitar" msgstr "quitar"
#: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:201 #: apps/activity/tables.py:82 apps/note/forms.py:68 apps/treasury/models.py:199
msgid "Type" msgid "Type"
msgstr "Tipo" msgstr "Tipo"
#: apps/activity/tables.py:84 apps/member/forms.py:193 #: apps/activity/tables.py:84 apps/member/forms.py:186
#: apps/registration/forms.py:92 apps/treasury/forms.py:131 #: apps/registration/forms.py:91 apps/treasury/forms.py:131
#: apps/wei/forms/registration.py:104 #: apps/wei/forms/registration.py:104
msgid "Last name" msgid "Last name"
msgstr "Apellido" msgstr "Apellido"
#: apps/activity/tables.py:86 apps/member/forms.py:198 #: apps/activity/tables.py:86 apps/member/forms.py:191
#: apps/note/templates/note/transaction_form.html:138 #: apps/note/templates/note/transaction_form.html:138
#: apps/registration/forms.py:97 apps/treasury/forms.py:133 #: apps/registration/forms.py:96 apps/treasury/forms.py:133
#: apps/wei/forms/registration.py:109 #: apps/wei/forms/registration.py:109
msgid "First name" msgid "First name"
msgstr "Nombre" msgstr "Nombre"
@ -386,7 +386,7 @@ msgid "validate"
msgstr "validar" msgstr "validar"
#: apps/activity/templates/activity/includes/activity_info.html:71 #: apps/activity/templates/activity/includes/activity_info.html:71
#: apps/logs/models.py:64 apps/note/tables.py:260 #: apps/logs/models.py:64 apps/note/tables.py:220
msgid "edit" msgid "edit"
msgstr "modificar" msgstr "modificar"
@ -464,9 +464,9 @@ msgstr "nuevos datos"
msgid "create" msgid "create"
msgstr "crear" msgstr "crear"
#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277 #: apps/logs/models.py:65 apps/note/tables.py:166 apps/note/tables.py:190
#: apps/permission/models.py:127 apps/treasury/tables.py:38 #: apps/note/tables.py:237 apps/permission/models.py:127
#: apps/wei/tables.py:74 #: apps/treasury/tables.py:38 apps/wei/tables.py:74
msgid "delete" msgid "delete"
msgstr "suprimir" msgstr "suprimir"
@ -495,21 +495,21 @@ msgstr "diario de cambios"
msgid "Changelog of type \"{action}\" for model {model} at {timestamp}" msgid "Changelog of type \"{action}\" for model {model} at {timestamp}"
msgstr "" msgstr ""
#: apps/member/admin.py:50 apps/member/models.py:231 #: apps/member/admin.py:50 apps/member/models.py:226
#: apps/member/templates/member/includes/club_info.html:34 #: apps/member/templates/member/includes/club_info.html:34
msgid "membership fee (paid students)" msgid "membership fee (paid students)"
msgstr "pago de afiliación (estudiantes pagados)" msgstr "pago de afiliación (estudiantes pagados)"
#: apps/member/admin.py:51 apps/member/models.py:236 #: apps/member/admin.py:51 apps/member/models.py:231
#: apps/member/templates/member/includes/club_info.html:37 #: apps/member/templates/member/includes/club_info.html:37
msgid "membership fee (unpaid students)" msgid "membership fee (unpaid students)"
msgstr "pago de afiliación (estudiantes no pagados)" msgstr "pago de afiliación (estudiantes no pagados)"
#: apps/member/admin.py:65 apps/member/models.py:324 #: apps/member/admin.py:65 apps/member/models.py:319
msgid "roles" msgid "roles"
msgstr "papel" msgstr "papel"
#: apps/member/admin.py:66 apps/member/models.py:338 #: apps/member/admin.py:66 apps/member/models.py:333
msgid "fee" msgid "fee"
msgstr "pago" msgstr "pago"
@ -529,81 +529,65 @@ msgstr "Frecuencia de los informes (en días)"
msgid "Last report date" msgid "Last report date"
msgstr "Fecha del último informe" 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 #: 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." msgid "You can't register to the note if you come from the future."
msgstr "Usted no puede registrar si viene del futuro." msgstr "Usted no puede registrar si viene del futuro."
#: apps/member/forms.py:86 #: apps/member/forms.py:79
msgid "select an image" msgid "select an image"
msgstr "elegir una imagen" msgstr "elegir una imagen"
#: apps/member/forms.py:87 #: apps/member/forms.py:80
msgid "Maximal size: 2MB" msgid "Maximal size: 2MB"
msgstr "Tamaño máximo : 2Mo" msgstr "Tamaño máximo : 2Mo"
#: apps/member/forms.py:112 #: apps/member/forms.py:105
msgid "This image cannot be loaded." msgid "This image cannot be loaded."
msgstr "Esta imagen no puede ser cargada." msgstr "Esta imagen no puede ser cargada."
#: apps/member/forms.py:148 apps/member/views.py:102 #: apps/member/forms.py:141 apps/member/views.py:103
#: apps/registration/forms.py:34 apps/registration/views.py:266 #: apps/registration/forms.py:33 apps/registration/views.py:262
msgid "An alias with a similar name already exists." msgid "An alias with a similar name already exists."
msgstr "Un alias similar ya existe." msgstr "Un alias similar ya existe."
#: apps/member/forms.py:172 #: apps/member/forms.py:165 apps/registration/forms.py:71
msgid "Inscription paid by Société Générale" msgid "Inscription paid by Société Générale"
msgstr "Registración pagadas por Société Générale" msgstr "Registración pagadas por Société Générale"
#: apps/member/forms.py:174 #: apps/member/forms.py:167 apps/registration/forms.py:73
msgid "Check this case if the Société Générale paid the inscription." 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." msgstr "Marcar esta casilla si Société Générale pagó la registración."
#: apps/member/forms.py:179 apps/registration/forms.py:79 #: apps/member/forms.py:172 apps/registration/forms.py:78
#: apps/wei/forms/registration.py:91 #: apps/wei/forms/registration.py:91
msgid "Credit type" msgid "Credit type"
msgstr "Tipo de crédito" msgstr "Tipo de crédito"
#: apps/member/forms.py:180 apps/registration/forms.py:80 #: apps/member/forms.py:173 apps/registration/forms.py:79
#: apps/wei/forms/registration.py:92 #: apps/wei/forms/registration.py:92
msgid "No credit" msgid "No credit"
msgstr "No crédito" msgstr "No crédito"
#: apps/member/forms.py:182 #: apps/member/forms.py:175
msgid "You can credit the note of the user." msgid "You can credit the note of the user."
msgstr "Usted puede acreditar la note del usuario." msgstr "Usted puede acreditar la note del usuario."
#: apps/member/forms.py:186 apps/registration/forms.py:85 #: apps/member/forms.py:179 apps/registration/forms.py:84
#: apps/wei/forms/registration.py:97 #: apps/wei/forms/registration.py:97
msgid "Credit amount" msgid "Credit amount"
msgstr "Valor del crédito" msgstr "Valor del crédito"
#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144 #: apps/member/forms.py:196 apps/note/templates/note/transaction_form.html:144
#: apps/registration/forms.py:102 apps/treasury/forms.py:135 #: apps/registration/forms.py:101 apps/treasury/forms.py:135
#: apps/wei/forms/registration.py:114 #: apps/wei/forms/registration.py:114
msgid "Bank" msgid "Bank"
msgstr "Banco" msgstr "Banco"
#: apps/member/forms.py:230 #: apps/member/forms.py:223
msgid "User" msgid "User"
msgstr "Usuario" msgstr "Usuario"
#: apps/member/forms.py:244 #: apps/member/forms.py:237
msgid "Roles" msgid "Roles"
msgstr "Papeles" msgstr "Papeles"
@ -788,19 +772,15 @@ msgstr "correo electrónico confirmado"
msgid "registration valid" msgid "registration valid"
msgstr "registración valida" msgstr "registración valida"
#: apps/member/models.py:138 #: apps/member/models.py:162 apps/member/models.py:163
msgid "VSS charter read"
msgstr "Carta VSS leída"
#: apps/member/models.py:167 apps/member/models.py:168
msgid "user profile" msgid "user profile"
msgstr "perfil usuario" msgstr "perfil usuario"
#: apps/member/models.py:178 #: apps/member/models.py:173
msgid "Activate your Note Kfet account" msgid "Activate your Note Kfet account"
msgstr "Active su cuenta Note Kfet" msgstr "Active su cuenta Note Kfet"
#: apps/member/models.py:209 #: apps/member/models.py:204
#: apps/member/templates/member/includes/club_info.html:55 #: apps/member/templates/member/includes/club_info.html:55
#: apps/member/templates/member/includes/profile_info.html:40 #: apps/member/templates/member/includes/profile_info.html:40
#: apps/registration/templates/registration/future_profile_detail.html:22 #: apps/registration/templates/registration/future_profile_detail.html:22
@ -809,87 +789,87 @@ msgstr "Active su cuenta Note Kfet"
msgid "email" msgid "email"
msgstr "correo electrónico" msgstr "correo electrónico"
#: apps/member/models.py:216 #: apps/member/models.py:211
msgid "parent club" msgid "parent club"
msgstr "club pariente" msgstr "club pariente"
#: apps/member/models.py:225 #: apps/member/models.py:220
msgid "require memberships" msgid "require memberships"
msgstr "necesita afiliaciones" msgstr "necesita afiliaciones"
#: apps/member/models.py:226 #: apps/member/models.py:221
msgid "Uncheck if this club don't require memberships." msgid "Uncheck if this club don't require memberships."
msgstr "Desmarcar si este club no usa afiliaciones." msgstr "Desmarcar si este club no usa afiliaciones."
#: apps/member/models.py:242 #: apps/member/models.py:237
#: apps/member/templates/member/includes/club_info.html:26 #: apps/member/templates/member/includes/club_info.html:26
msgid "membership duration" msgid "membership duration"
msgstr "duración de la afiliación" msgstr "duración de la afiliación"
#: apps/member/models.py:243 #: apps/member/models.py:238
msgid "The longest time (in days) a membership can last (NULL = infinite)." 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)." msgstr "La duración máxima (en días) de una afiliación (NULL = infinito)."
#: apps/member/models.py:250 #: apps/member/models.py:245
#: apps/member/templates/member/includes/club_info.html:16 #: apps/member/templates/member/includes/club_info.html:16
msgid "membership start" msgid "membership start"
msgstr "inicio de la afiliación" msgstr "inicio de la afiliación"
#: apps/member/models.py:251 #: apps/member/models.py:246
msgid "Date from which the members can renew their membership." msgid "Date from which the members can renew their membership."
msgstr "Fecha a partir de la cual los miembros pueden prorrogar su afiliación." msgstr "Fecha a partir de la cual los miembros pueden prorrogar su afiliación."
#: apps/member/models.py:257 #: apps/member/models.py:252
#: apps/member/templates/member/includes/club_info.html:21 #: apps/member/templates/member/includes/club_info.html:21
msgid "membership end" msgid "membership end"
msgstr "fin de la afiliación" msgstr "fin de la afiliación"
#: apps/member/models.py:258 #: apps/member/models.py:253
msgid "Maximal date of a membership, after which members must renew it." msgid "Maximal date of a membership, after which members must renew it."
msgstr "" msgstr ""
"Ultima fecha de una afiliación, después de la cual los miembros tienen que " "Ultima fecha de una afiliación, después de la cual los miembros tienen que "
"prorrogarla." "prorrogarla."
#: apps/member/models.py:293 apps/member/models.py:318 #: apps/member/models.py:288 apps/member/models.py:313
#: apps/note/models/notes.py:176 #: apps/note/models/notes.py:176
msgid "club" msgid "club"
msgstr "club" msgstr "club"
#: apps/member/models.py:294 #: apps/member/models.py:289
msgid "clubs" msgid "clubs"
msgstr "clubs" msgstr "clubs"
#: apps/member/models.py:329 #: apps/member/models.py:324
msgid "membership starts on" msgid "membership starts on"
msgstr "afiliación empezá el" msgstr "afiliación empezá el"
#: apps/member/models.py:333 #: apps/member/models.py:328
msgid "membership ends on" msgid "membership ends on"
msgstr "afiliación termina el" msgstr "afiliación termina el"
#: apps/member/models.py:435 #: apps/member/models.py:430
#, python-brace-format #, python-brace-format
msgid "The role {role} does not apply to the club {club}." msgid "The role {role} does not apply to the club {club}."
msgstr "El papel {role} no se encuentra en el club {club}." msgstr "El papel {role} no se encuentra en el club {club}."
#: apps/member/models.py:444 apps/member/views.py:712 #: apps/member/models.py:439 apps/member/views.py:712
msgid "User is already a member of the club" msgid "User is already a member of the club"
msgstr "Usuario ya esta un miembro del club" msgstr "Usuario ya esta un miembro del club"
#: apps/member/models.py:456 apps/member/views.py:721 #: apps/member/models.py:451 apps/member/views.py:721
msgid "User is not a member of the parent club" msgid "User is not a member of the parent club"
msgstr "Usuario no es un miembro del club pariente" msgstr "Usuario no es un miembro del club pariente"
#: apps/member/models.py:509 #: apps/member/models.py:504
#, python-brace-format #, python-brace-format
msgid "Membership of {user} for the club {club}" msgid "Membership of {user} for the club {club}"
msgstr "Afiliación of {user} for the club {club}" msgstr "Afiliación of {user} for the club {club}"
#: apps/member/models.py:512 apps/note/models/transactions.py:389 #: apps/member/models.py:507 apps/note/models/transactions.py:389
msgid "membership" msgid "membership"
msgstr "afiliación" msgstr "afiliación"
#: apps/member/models.py:513 #: apps/member/models.py:508
msgid "memberships" msgid "memberships"
msgstr "afiliaciones" msgstr "afiliaciones"
@ -941,7 +921,7 @@ msgid "Account #"
msgstr "Cuenta n°" msgstr "Cuenta n°"
#: apps/member/templates/member/base.html:48 #: apps/member/templates/member/base.html:48
#: apps/member/templates/member/base.html:62 apps/member/views.py:59 #: apps/member/templates/member/base.html:62 apps/member/views.py:60
#: apps/registration/templates/registration/future_profile_detail.html:48 #: apps/registration/templates/registration/future_profile_detail.html:48
#: apps/wei/templates/wei/weimembership_form.html:117 #: apps/wei/templates/wei/weimembership_form.html:117
msgid "Update Profile" msgid "Update Profile"
@ -1175,9 +1155,9 @@ msgstr "Hacer clic aquí para reenviar un enlace de validación."
msgid "View my memberships" msgid "View my memberships"
msgstr "Ver mis afiliaciones" msgstr "Ver mis afiliaciones"
#: apps/member/templates/member/profile_trust.html:10 #: apps/member/templates/member/profile_trust.html:10 apps/member/views.py:254
msgid "Add friends" msgid "Note friendships"
msgstr "Añadir amig@s" msgstr "Amistades de note"
#: apps/member/templates/member/profile_trust.html:28 #: apps/member/templates/member/profile_trust.html:28
msgid "" msgid ""
@ -1192,10 +1172,6 @@ msgstr ""
"simplificar el reembolso entre amig@s por Note Kfet. Pues una persona puede " "simplificar el reembolso entre amig@s por Note Kfet. Pues una persona puede "
"crear todas la transacciones sin tener derechos particulares." "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 #: apps/member/templates/member/profile_update.html:18
msgid "Save Changes" msgid "Save Changes"
msgstr "Guardar cambios" msgstr "Guardar cambios"
@ -1204,22 +1180,18 @@ msgstr "Guardar cambios"
msgid "Registrations" msgid "Registrations"
msgstr "Registraciones" msgstr "Registraciones"
#: apps/member/views.py:72 apps/registration/forms.py:24 #: apps/member/views.py:73 apps/registration/forms.py:23
msgid "This address must be valid." msgid "This address must be valid."
msgstr "Este correo tiene que ser valido." msgstr "Este correo tiene que ser valido."
#: apps/member/views.py:139 #: apps/member/views.py:140
msgid "Profile detail" msgid "Profile detail"
msgstr "Detalles del usuario" msgstr "Detalles del usuario"
#: apps/member/views.py:205 #: apps/member/views.py:206
msgid "Search user" msgid "Search user"
msgstr "Buscar un usuario" msgstr "Buscar un usuario"
#: apps/member/views.py:253
msgid "Note friendships"
msgstr "Amistades de note"
#: apps/member/views.py:308 #: apps/member/views.py:308
msgid "Update note picture" msgid "Update note picture"
msgstr "Modificar la imagen de la note" msgstr "Modificar la imagen de la note"
@ -1264,11 +1236,11 @@ msgstr "La afiliación tiene que empezar después del {:%d-%m-%Y}."
msgid "The membership must begin before {:%m-%d-%Y}." msgid "The membership must begin before {:%m-%d-%Y}."
msgstr "La afiliación tiene que empezar antes del {:%d-%m-%Y}." msgstr "La afiliación tiene que empezar antes del {:%d-%m-%Y}."
#: apps/member/views.py:880 #: apps/member/views.py:876
msgid "Manage roles of an user in the club" msgid "Manage roles of an user in the club"
msgstr "Gestionar los papeles de un usuario en el club" msgstr "Gestionar los papeles de un usuario en el club"
#: apps/member/views.py:905 #: apps/member/views.py:901
msgid "Members of the club" msgid "Members of the club"
msgstr "Miembros del club" msgstr "Miembros del club"
@ -1286,13 +1258,7 @@ msgstr "destino"
msgid "amount" msgid "amount"
msgstr "monto" msgstr "monto"
#: apps/note/api/serializers.py:92 #: apps/note/api/serializers.py:199 apps/note/api/serializers.py:205
#, 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 #: apps/note/models/transactions.py:228
msgid "" msgid ""
"The transaction can't be saved since the source note or the destination note " "The transaction can't be saved since the source note or the destination note "
@ -1591,7 +1557,7 @@ msgstr "Transacciones especiales"
msgid "membership transaction" msgid "membership transaction"
msgstr "transacción de afiliación" msgstr "transacción de afiliación"
#: apps/note/models/transactions.py:385 apps/treasury/models.py:294 #: apps/note/models/transactions.py:385 apps/treasury/models.py:292
msgid "membership transactions" msgid "membership transactions"
msgstr "transacciones de afiliación" msgstr "transacciones de afiliación"
@ -1607,8 +1573,8 @@ msgstr "Hacer clic para validar"
msgid "No reason specified" msgid "No reason specified"
msgstr "Ningún motivo dado" msgstr "Ningún motivo dado"
#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234 #: apps/note/tables.py:173 apps/note/tables.py:194 apps/note/tables.py:239
#: apps/note/tables.py:279 apps/treasury/tables.py:39 #: apps/treasury/tables.py:39
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30 #: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
#: apps/treasury/templates/treasury/sogecredit_detail.html:65 #: apps/treasury/templates/treasury/sogecredit_detail.html:65
#: apps/wei/tables.py:75 apps/wei/tables.py:118 #: apps/wei/tables.py:75 apps/wei/tables.py:118
@ -1619,15 +1585,7 @@ msgstr "Ningún motivo dado"
msgid "Delete" msgid "Delete"
msgstr "Suprimir" msgstr "Suprimir"
#: apps/note/tables.py:191 #: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
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/tables.py:49 apps/wei/tables.py:50
#: apps/wei/templates/wei/base.html:89 #: apps/wei/templates/wei/base.html:89
#: apps/wei/templates/wei/bus_detail.html:20 #: apps/wei/templates/wei/bus_detail.html:20
@ -1637,7 +1595,7 @@ msgstr "Añadir en retorno"
msgid "Edit" msgid "Edit"
msgstr "Editar" msgstr "Editar"
#: apps/note/tables.py:266 apps/note/tables.py:293 #: apps/note/tables.py:226 apps/note/tables.py:253
msgid "Hide/Show" msgid "Hide/Show"
msgstr "Ocultar/Mostrar" msgstr "Ocultar/Mostrar"
@ -1718,7 +1676,7 @@ msgid "Amount"
msgstr "Monto" msgstr "Monto"
#: apps/note/templates/note/transaction_form.html:132 #: apps/note/templates/note/transaction_form.html:132
#: apps/treasury/models.py:56 #: apps/treasury/models.py:54
msgid "Name" msgid "Name"
msgstr "Nombre" msgstr "Nombre"
@ -1887,7 +1845,7 @@ msgstr ""
msgid "for club" msgid "for club"
msgstr "interesa el club" msgstr "interesa el club"
#: apps/permission/models.py:351 apps/permission/models.py:352 #: apps/permission/models.py:350 apps/permission/models.py:351
msgid "role permissions" msgid "role permissions"
msgstr "permisos por papeles" msgstr "permisos por papeles"
@ -2005,15 +1963,31 @@ msgstr "Todos los permisos"
msgid "registration" msgid "registration"
msgstr "afiliación" msgstr "afiliación"
#: apps/registration/forms.py:40 #: apps/registration/forms.py:39
msgid "This email address is already used." msgid "This email address is already used."
msgstr "Este correo electrónico ya esta utilizado." msgstr "Este correo electrónico ya esta utilizado."
#: apps/registration/forms.py:60 #: 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
msgid "Register to the WEI" msgid "Register to the WEI"
msgstr "Registrarse en el WEI" msgstr "Registrarse en el WEI"
#: apps/registration/forms.py:62 #: apps/registration/forms.py:61
msgid "" msgid ""
"Check this case if you want to register to the WEI. If you hesitate, you " "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." "will be able to register later, after validating your account in the Kfet."
@ -2021,18 +1995,14 @@ msgstr ""
"Marcar esta casilla si usted quiere registrarse en el WEI. Si duda, podrá " "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." "registrarse más tarde, después de validar su cuenta Note Kfet."
#: apps/registration/forms.py:107 #: apps/registration/forms.py:106
msgid "Join BDE Club" msgid "Join BDE Club"
msgstr "Afiliarse al club BDE" msgstr "Afiliarse al club BDE"
#: apps/registration/forms.py:114 #: apps/registration/forms.py:113
msgid "Join Kfet Club" msgid "Join Kfet Club"
msgstr "Afiliarse al club Kfet" 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 #: apps/registration/templates/registration/email_validation_complete.html:15
msgid "Your email have successfully been validated." msgid "Your email have successfully been validated."
msgstr "Su correo electrónico fue validado con éxito." msgstr "Su correo electrónico fue validado con éxito."
@ -2081,12 +2051,12 @@ msgstr "Suprimir afiliación"
msgid "Validate account" msgid "Validate account"
msgstr "Validar la cuenta" msgstr "Validar la cuenta"
#: apps/registration/templates/registration/future_profile_detail.html:63 #: apps/registration/templates/registration/future_profile_detail.html:62
msgid "" msgid ""
"The user declared that he/she opened a bank account in the Société générale." "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." msgstr "El usuario declara que ya abrió una cuenta a la Société Générale."
#: apps/registration/templates/registration/future_profile_detail.html:73 #: apps/registration/templates/registration/future_profile_detail.html:71
#: apps/wei/templates/wei/weimembership_form.html:127 #: apps/wei/templates/wei/weimembership_form.html:127
#: apps/wei/templates/wei/weimembership_form.html:186 #: apps/wei/templates/wei/weimembership_form.html:186
msgid "Validate registration" msgid "Validate registration"
@ -2138,54 +2108,54 @@ msgstr "Gracias"
msgid "The Note Kfet team." msgid "The Note Kfet team."
msgstr "El equipo Note Kfet." msgstr "El equipo Note Kfet."
#: apps/registration/views.py:41 #: apps/registration/views.py:40
msgid "Register new user" msgid "Register new user"
msgstr "Registrar un nuevo usuario" msgstr "Registrar un nuevo usuario"
#: apps/registration/views.py:99 #: apps/registration/views.py:98
msgid "Email validation" msgid "Email validation"
msgstr "Validación del correo electrónico" msgstr "Validación del correo electrónico"
#: apps/registration/views.py:101 #: apps/registration/views.py:100
msgid "Validate email" msgid "Validate email"
msgstr "Validar el correo electrónico" msgstr "Validar el correo electrónico"
#: apps/registration/views.py:145 #: apps/registration/views.py:144
msgid "Email validation unsuccessful" msgid "Email validation unsuccessful"
msgstr "La validación del correo electrónico fracasó" msgstr "La validación del correo electrónico fracasó"
#: apps/registration/views.py:156 #: apps/registration/views.py:155
msgid "Email validation email sent" msgid "Email validation email sent"
msgstr "Correo de validación enviado" msgstr "Correo de validación enviado"
#: apps/registration/views.py:164 #: apps/registration/views.py:163
msgid "Resend email validation link" msgid "Resend email validation link"
msgstr "Reenviar el enlace de validación" msgstr "Reenviar el enlace de validación"
#: apps/registration/views.py:182 #: apps/registration/views.py:181
msgid "Pre-registered users list" msgid "Pre-registered users list"
msgstr "Lista de los usuarios con afiliación pendiente" msgstr "Lista de los usuarios con afiliación pendiente"
#: apps/registration/views.py:206 #: apps/registration/views.py:205
msgid "Unregistered users" msgid "Unregistered users"
msgstr "Usuarios con afiliación pendiente" msgstr "Usuarios con afiliación pendiente"
#: apps/registration/views.py:219 #: apps/registration/views.py:218
msgid "Registration detail" msgid "Registration detail"
msgstr "Detalles de la afiliación" msgstr "Detalles de la afiliación"
#: apps/registration/views.py:293 #: apps/registration/views.py:282
msgid "You must join the BDE." msgid "You must join the BDE."
msgstr "Usted tiene que afiliarse al BDE." msgstr "Usted tiene que afiliarse al BDE."
#: apps/registration/views.py:323 #: apps/registration/views.py:306
msgid "" msgid ""
"The entered amount is not enough for the memberships, should be at least {}" "The entered amount is not enough for the memberships, should be at least {}"
msgstr "" msgstr ""
"El monto dado no es suficiente para las afiliaciones, tiene que ser al menos " "El monto dado no es suficiente para las afiliaciones, tiene que ser al menos "
"{}" "{}"
#: apps/registration/views.py:417 #: apps/registration/views.py:387
msgid "Invalidate pre-registration" msgid "Invalidate pre-registration"
msgstr "Invalidar la afiliación" msgstr "Invalidar la afiliación"
@ -2193,7 +2163,7 @@ msgstr "Invalidar la afiliación"
msgid "Treasury" msgid "Treasury"
msgstr "Tesorería" msgstr "Tesorería"
#: apps/treasury/forms.py:26 apps/treasury/models.py:95 #: apps/treasury/forms.py:26 apps/treasury/models.py:93
#: apps/treasury/templates/treasury/invoice_form.html:22 #: apps/treasury/templates/treasury/invoice_form.html:22
msgid "This invoice is locked and can no longer be edited." msgid "This invoice is locked and can no longer be edited."
msgstr "Esta factura esta bloqueada y no puede ser modificada." msgstr "Esta factura esta bloqueada y no puede ser modificada."
@ -2206,7 +2176,7 @@ msgstr "El descuento ya esta cerrado."
msgid "You can't change the type of the remittance." msgid "You can't change the type of the remittance."
msgstr "No puede cambiar el tipo de descuento." msgstr "No puede cambiar el tipo de descuento."
#: apps/treasury/forms.py:125 apps/treasury/models.py:269 #: apps/treasury/forms.py:125 apps/treasury/models.py:267
#: apps/treasury/tables.py:97 apps/treasury/tables.py:105 #: apps/treasury/tables.py:97 apps/treasury/tables.py:105
#: apps/treasury/templates/treasury/invoice_list.html:16 #: apps/treasury/templates/treasury/invoice_list.html:16
#: apps/treasury/templates/treasury/remittance_list.html:16 #: apps/treasury/templates/treasury/remittance_list.html:16
@ -2222,116 +2192,116 @@ msgstr "No hay descuento relacionado"
msgid "Invoice identifier" msgid "Invoice identifier"
msgstr "Numero de factura" msgstr "Numero de factura"
#: apps/treasury/models.py:42 #: apps/treasury/models.py:40
msgid "BDE" msgid "BDE"
msgstr "BDE" msgstr "BDE"
#: apps/treasury/models.py:47 #: apps/treasury/models.py:45
msgid "Object" msgid "Object"
msgstr "Asunto" msgstr "Asunto"
#: apps/treasury/models.py:51 #: apps/treasury/models.py:49
msgid "Description" msgid "Description"
msgstr "Descripción" msgstr "Descripción"
#: apps/treasury/models.py:60 #: apps/treasury/models.py:58
msgid "Address" msgid "Address"
msgstr "Dirección" msgstr "Dirección"
#: apps/treasury/models.py:65 apps/treasury/models.py:195 #: apps/treasury/models.py:63 apps/treasury/models.py:193
msgid "Date" msgid "Date"
msgstr "Fecha" msgstr "Fecha"
#: apps/treasury/models.py:69 #: apps/treasury/models.py:67
msgid "Acquitted" msgid "Acquitted"
msgstr "Pagada" msgstr "Pagada"
#: apps/treasury/models.py:74 #: apps/treasury/models.py:72
msgid "Locked" msgid "Locked"
msgstr "Bloqueada" msgstr "Bloqueada"
#: apps/treasury/models.py:75 #: apps/treasury/models.py:73
msgid "An invoice can't be edited when it is locked." msgid "An invoice can't be edited when it is locked."
msgstr "Une factura no puede ser modificada cuando esta bloqueada." msgstr "Une factura no puede ser modificada cuando esta bloqueada."
#: apps/treasury/models.py:81 #: apps/treasury/models.py:79
msgid "tex source" msgid "tex source"
msgstr "código fuente TeX" msgstr "código fuente TeX"
#: apps/treasury/models.py:115 apps/treasury/models.py:131 #: apps/treasury/models.py:113 apps/treasury/models.py:129
msgid "invoice" msgid "invoice"
msgstr "factura" msgstr "factura"
#: apps/treasury/models.py:116 #: apps/treasury/models.py:114
msgid "invoices" msgid "invoices"
msgstr "facturas" msgstr "facturas"
#: apps/treasury/models.py:119 #: apps/treasury/models.py:117
#, python-brace-format #, python-brace-format
msgid "Invoice #{id}" msgid "Invoice #{id}"
msgstr "Factura n°{id}" msgstr "Factura n°{id}"
#: apps/treasury/models.py:136 #: apps/treasury/models.py:134
msgid "Designation" msgid "Designation"
msgstr "Designación" msgstr "Designación"
#: apps/treasury/models.py:142 #: apps/treasury/models.py:140
msgid "Quantity" msgid "Quantity"
msgstr "Cantidad" msgstr "Cantidad"
#: apps/treasury/models.py:147 #: apps/treasury/models.py:145
msgid "Unit price" msgid "Unit price"
msgstr "Precio unitario" msgstr "Precio unitario"
#: apps/treasury/models.py:163 #: apps/treasury/models.py:161
msgid "product" msgid "product"
msgstr "producto" msgstr "producto"
#: apps/treasury/models.py:164 #: apps/treasury/models.py:162
msgid "products" msgid "products"
msgstr "productos" msgstr "productos"
#: apps/treasury/models.py:184 #: apps/treasury/models.py:182
msgid "remittance type" msgid "remittance type"
msgstr "tipo de descuento" msgstr "tipo de descuento"
#: apps/treasury/models.py:185 #: apps/treasury/models.py:183
msgid "remittance types" msgid "remittance types"
msgstr "tipos de descuentos" msgstr "tipos de descuentos"
#: apps/treasury/models.py:206 #: apps/treasury/models.py:204
msgid "Comment" msgid "Comment"
msgstr "Comentario" msgstr "Comentario"
#: apps/treasury/models.py:211 #: apps/treasury/models.py:209
msgid "Closed" msgid "Closed"
msgstr "Cerrada" msgstr "Cerrada"
#: apps/treasury/models.py:215 #: apps/treasury/models.py:213
msgid "remittance" msgid "remittance"
msgstr "descuento" msgstr "descuento"
#: apps/treasury/models.py:216 #: apps/treasury/models.py:214
msgid "remittances" msgid "remittances"
msgstr "descuentos" msgstr "descuentos"
#: apps/treasury/models.py:249 #: apps/treasury/models.py:247
msgid "Remittance #{:d}: {}" msgid "Remittance #{:d}: {}"
msgstr "Descuento n°{:d} : {}" msgstr "Descuento n°{:d} : {}"
#: apps/treasury/models.py:273 #: apps/treasury/models.py:271
msgid "special transaction proxy" msgid "special transaction proxy"
msgstr "proxy de transacción especial" msgstr "proxy de transacción especial"
#: apps/treasury/models.py:274 #: apps/treasury/models.py:272
msgid "special transaction proxies" msgid "special transaction proxies"
msgstr "proxys de transacciones especiales" msgstr "proxys de transacciones especiales"
#: apps/treasury/models.py:300 #: apps/treasury/models.py:298
msgid "credit transaction" msgid "credit transaction"
msgstr "transacción de crédito" msgstr "transacción de crédito"
#: apps/treasury/models.py:432 #: apps/treasury/models.py:430
msgid "" msgid ""
"This user doesn't have enough money to pay the memberships with its note. " "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." "Please ask her/him to credit the note before invalidating this credit."
@ -2340,16 +2310,16 @@ msgstr ""
"afiliaciones. Por favor pídelo acreditar su note antes de invalidar este " "afiliaciones. Por favor pídelo acreditar su note antes de invalidar este "
"crédito." "crédito."
#: apps/treasury/models.py:452 #: apps/treasury/models.py:451
#: apps/treasury/templates/treasury/sogecredit_detail.html:10 #: apps/treasury/templates/treasury/sogecredit_detail.html:10
msgid "Credit from the Société générale" msgid "Credit from the Société générale"
msgstr "Crédito de la Société Générale" msgstr "Crédito de la Société Générale"
#: apps/treasury/models.py:453 #: apps/treasury/models.py:452
msgid "Credits from the Société générale" msgid "Credits from the Société générale"
msgstr "Créditos de la Société Générale" msgstr "Créditos de la Société Générale"
#: apps/treasury/models.py:456 #: apps/treasury/models.py:455
#, python-brace-format #, python-brace-format
msgid "Soge credit for {user}" msgid "Soge credit for {user}"
msgstr "Crédito de la Société Générale para {user}" msgstr "Crédito de la Société Générale para {user}"
@ -2604,7 +2574,7 @@ msgstr ""
"El usuario seleccionado no ha sido validado. Validar esta cuenta primero" "El usuario seleccionado no ha sido validado. Validar esta cuenta primero"
#: apps/wei/forms/registration.py:59 apps/wei/models.py:126 #: apps/wei/forms/registration.py:59 apps/wei/models.py:126
#: apps/wei/models.py:326 #: apps/wei/models.py:323
msgid "bus" msgid "bus"
msgstr "bus" msgstr "bus"
@ -2642,7 +2612,7 @@ msgstr "Elegir los papeles que le interesa."
msgid "This team doesn't belong to the given bus." msgid "This team doesn't belong to the given bus."
msgstr "Este equipo no pertenece al bus dado." msgstr "Este equipo no pertenece al bus dado."
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38 #: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:35
msgid "Choose a word:" msgid "Choose a word:"
msgstr "Elegir una palabra :" msgstr "Elegir una palabra :"
@ -2729,48 +2699,40 @@ msgstr "No binari@"
msgid "gender" msgid "gender"
msgstr "género" msgstr "género"
#: apps/wei/models.py:212 #: apps/wei/models.py:213 apps/wei/templates/wei/weimembership_form.html:58
msgid "Unisex"
msgstr "Unisex"
#: apps/wei/models.py:215 apps/wei/templates/wei/weimembership_form.html:58
msgid "clothing cut" msgid "clothing cut"
msgstr "forma de ropa" msgstr "forma de ropa"
#: apps/wei/models.py:228 apps/wei/templates/wei/weimembership_form.html:61 #: apps/wei/models.py:226 apps/wei/templates/wei/weimembership_form.html:61
msgid "clothing size" msgid "clothing size"
msgstr "medida de ropa" msgstr "medida de ropa"
#: apps/wei/models.py:234 apps/wei/templates/wei/attribute_bus_1A.html:28 #: apps/wei/models.py:232 apps/wei/templates/wei/attribute_bus_1A.html:28
#: apps/wei/templates/wei/weimembership_form.html:67 #: apps/wei/templates/wei/weimembership_form.html:67
msgid "health issues" msgid "health issues"
msgstr "problemas de salud" msgstr "problemas de salud"
#: apps/wei/models.py:239 apps/wei/templates/wei/weimembership_form.html:70 #: apps/wei/models.py:237 apps/wei/templates/wei/weimembership_form.html:70
msgid "emergency contact name" msgid "emergency contact name"
msgstr "nombre del contacto de emergencia" msgstr "nombre del contacto de emergencia"
#: apps/wei/models.py:240 #: apps/wei/models.py:242 apps/wei/templates/wei/weimembership_form.html:73
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" msgid "emergency contact phone"
msgstr "teléfono del contacto de emergencia" msgstr "teléfono del contacto de emergencia"
#: apps/wei/models.py:250 apps/wei/templates/wei/weimembership_form.html:52 #: apps/wei/models.py:247 apps/wei/templates/wei/weimembership_form.html:52
msgid "first year" msgid "first year"
msgstr "primer año" msgstr "primer año"
#: apps/wei/models.py:251 #: apps/wei/models.py:248
msgid "Tells if the user is new in the school." msgid "Tells if the user is new in the school."
msgstr "Indica si el usuario es nuevo en la escuela." msgstr "Indica si el usuario es nuevo en la escuela."
#: apps/wei/models.py:256 #: apps/wei/models.py:253
msgid "registration information" msgid "registration information"
msgstr "informaciones sobre la afiliación" msgstr "informaciones sobre la afiliación"
#: apps/wei/models.py:257 #: apps/wei/models.py:254
msgid "" msgid ""
"Information about the registration (buses for old members, survey for the " "Information about the registration (buses for old members, survey for the "
"new members), encoded in JSON" "new members), encoded in JSON"
@ -2778,27 +2740,27 @@ msgstr ""
"Informaciones sobre la afiliacion (bus para miembros ancianos, cuestionario " "Informaciones sobre la afiliacion (bus para miembros ancianos, cuestionario "
"para los nuevos miembros), registrado en JSON" "para los nuevos miembros), registrado en JSON"
#: apps/wei/models.py:315 #: apps/wei/models.py:312
msgid "WEI User" msgid "WEI User"
msgstr "Participante WEI" msgstr "Participante WEI"
#: apps/wei/models.py:316 #: apps/wei/models.py:313
msgid "WEI Users" msgid "WEI Users"
msgstr "Participantes WEI" msgstr "Participantes WEI"
#: apps/wei/models.py:336 #: apps/wei/models.py:333
msgid "team" msgid "team"
msgstr "equipo" msgstr "equipo"
#: apps/wei/models.py:346 #: apps/wei/models.py:343
msgid "WEI registration" msgid "WEI registration"
msgstr "Apuntación al WEI" msgstr "Apuntación al WEI"
#: apps/wei/models.py:350 #: apps/wei/models.py:347
msgid "WEI membership" msgid "WEI membership"
msgstr "Afiliación al WEI" msgstr "Afiliación al WEI"
#: apps/wei/models.py:351 #: apps/wei/models.py:348
msgid "WEI memberships" msgid "WEI memberships"
msgstr "Afiliaciones al WEI" msgstr "Afiliaciones al WEI"
@ -2929,7 +2891,7 @@ msgstr "Descargar un PDF"
#: apps/wei/templates/wei/survey.html:11 #: apps/wei/templates/wei/survey.html:11
#: apps/wei/templates/wei/survey_closed.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/templates/wei/survey_end.html:11 apps/wei/views.py:1028
#: apps/wei/views.py:1083 apps/wei/views.py:1130 #: apps/wei/views.py:1083 apps/wei/views.py:1093
msgid "Survey WEI" msgid "Survey WEI"
msgstr "Cuestionario WEI" msgstr "Cuestionario WEI"
@ -3200,11 +3162,11 @@ msgstr "Usted no tiene derecho a suprimir esta inscripción WEI."
msgid "Validate WEI registration" msgid "Validate WEI registration"
msgstr "Validar la inscripción WEI" msgstr "Validar la inscripción WEI"
#: apps/wei/views.py:1223 #: apps/wei/views.py:1186
msgid "Attribute buses to first year members" msgid "Attribute buses to first year members"
msgstr "Repartir los primer años en los buses" msgstr "Repartir los primer años en los buses"
#: apps/wei/views.py:1248 #: apps/wei/views.py:1211
msgid "Attribute bus" msgid "Attribute bus"
msgstr "Repartir en un bus" msgstr "Repartir en un bus"
@ -3354,10 +3316,6 @@ msgstr "Contactarnos"
msgid "Technical Support" msgid "Technical Support"
msgstr "Soporte técnico" msgstr "Soporte técnico"
#: note_kfet/templates/base.html:198
msgid "FAQ (FR)"
msgstr "FAQ (FR)"
#: note_kfet/templates/base_search.html:15 #: note_kfet/templates/base_search.html:15
msgid "Search by attribute such as name…" msgid "Search by attribute such as name…"
msgstr "Buscar con atributo, como el nombre…" msgstr "Buscar con atributo, como el nombre…"
@ -3579,26 +3537,6 @@ msgstr ""
"pagar su afiliación. Tambien tiene que validar su correo electronico con el " "pagar su afiliación. Tambien tiene que validar su correo electronico con el "
"enlace que recibió." "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." #~ 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." #~ msgstr "Usted no es un miembro de la Kfet, no puede usar su cuenta note."

View File

@ -7,16 +7,16 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-07 09:07+0200\n" "POT-Creation-Date: 2020-11-15 23:21+0100\n"
"PO-Revision-Date: 2022-10-07 13:20+0200\n" "PO-Revision-Date: 2020-11-21 12:23+0100\n"
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
"Language-Team: \n"
"Language: es\n" "Language: es\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.0.1\n" "Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
"Language-Team: \n"
"X-Generator: Poedit 2.3\n"
#: apps/member/static/member/js/alias.js:17 #: apps/member/static/member/js/alias.js:17
msgid "Alias successfully added" msgid "Alias successfully added"
@ -26,18 +26,6 @@ msgstr "Alias añadido con éxito"
msgid "Alias successfully deleted" msgid "Alias successfully deleted"
msgstr "Alias suprimido con éxito" 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 #: apps/note/static/note/js/consos.js:225
#, javascript-format #, javascript-format
msgid "" msgid ""
@ -56,29 +44,30 @@ msgstr ""
"Cuidado, la transacción de %s fue un éxito, pero la note %s está negativa." "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/consos.js:232
#: apps/note/static/note/js/transfer.js:309 #: apps/note/static/note/js/transfer.js:298
#: apps/note/static/note/js/transfer.js:412 #: apps/note/static/note/js/transfer.js:401
#, javascript-format #, javascript-format
msgid "Warning, the emitter note %s is no more a BDE member." 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." msgstr "Cuidado, la note remitente %s no está más miembro del BDE."
#: apps/note/static/note/js/consos.js:254 #: apps/note/static/note/js/consos.js:253
msgid "The transaction couldn't be validated because of insufficient balance." 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:249 #: apps/note/static/note/js/transfer.js:238
msgid "This field is required and must contain a decimal positive number." msgid "This field is required and must contain a decimal positive number."
msgstr "Este campo obligatorio requiere un número decimal positivo." msgstr "Este campo obligatorio requiere un número decimal positivo."
#: apps/note/static/note/js/transfer.js:256 #: apps/note/static/note/js/transfer.js:245
msgid "The amount must stay under 21,474,836.47 €." msgid "The amount must stay under 21,474,836.47 €."
msgstr "El monto no puede superar los 21 474 836,47 €." msgstr "El monto no puede superar los 21 474 836,47 €."
#: apps/note/static/note/js/transfer.js:262 #: apps/note/static/note/js/transfer.js:251
msgid "This field is required." msgid "This field is required."
msgstr "Este campo es obligatorio." msgstr "Este campo es obligatorio."
#: apps/note/static/note/js/transfer.js:288 #: apps/note/static/note/js/transfer.js:277
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning: the transaction of %s from %s to %s was not made because it is the " "Warning: the transaction of %s from %s to %s was not made because it is the "
@ -87,12 +76,12 @@ msgstr ""
"Cuidado : la transacción de %s de %s a %s no fue echa porque la fuente y el " "Cuidado : la transacción de %s de %s a %s no fue echa porque la fuente y el "
"destino son iguales." "destino son iguales."
#: apps/note/static/note/js/transfer.js:312 #: apps/note/static/note/js/transfer.js:301
#, javascript-format #, javascript-format
msgid "Warning, the destination note %s is no more a BDE member." 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." msgstr "Cuidado, la note destino %s no está más miembro del BDE."
#: apps/note/static/note/js/transfer.js:318 #: apps/note/static/note/js/transfer.js:307
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning, the transaction of %s from the note %s to the note %s succeed, but " "Warning, the transaction of %s from the note %s to the note %s succeed, but "
@ -101,7 +90,7 @@ msgstr ""
"Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero " "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." "la note fuente %s está muy negativa."
#: apps/note/static/note/js/transfer.js:323 #: apps/note/static/note/js/transfer.js:312
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning, the transaction of %s from the note %s to the note %s succeed, but " "Warning, the transaction of %s from the note %s to the note %s succeed, but "
@ -110,31 +99,31 @@ msgstr ""
"Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero " "Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero "
"la note fuente %s está negativa." "la note fuente %s está negativa."
#: apps/note/static/note/js/transfer.js:329 #: apps/note/static/note/js/transfer.js:318
#, javascript-format #, javascript-format
msgid "Transfer of %s from %s to %s succeed!" msgid "Transfer of %s from %s to %s succeed!"
msgstr "¡ La transacción de %s de %s a %s fue un éxito !" msgstr "¡ La transacción de %s de %s a %s fue un éxito !"
#: apps/note/static/note/js/transfer.js:336 #: apps/note/static/note/js/transfer.js:325
#: apps/note/static/note/js/transfer.js:357 #: apps/note/static/note/js/transfer.js:346
#: apps/note/static/note/js/transfer.js:364 #: apps/note/static/note/js/transfer.js:353
#, javascript-format #, javascript-format
msgid "Transfer of %s from %s to %s failed: %s" msgid "Transfer of %s from %s to %s failed: %s"
msgstr "La transacción de %s de %s a %s fue un fracaso : %s" msgstr "La transacción de %s de %s a %s fue un fracaso : %s"
#: apps/note/static/note/js/transfer.js:358 #: apps/note/static/note/js/transfer.js:347
msgid "insufficient funds" msgid "insufficient funds"
msgstr "fundos insuficientes" msgstr "fundos insuficientes"
#: apps/note/static/note/js/transfer.js:411 #: apps/note/static/note/js/transfer.js:400
msgid "Credit/debit succeed!" msgid "Credit/debit succeed!"
msgstr "¡ Crédito/débito tubo éxito !" msgstr "¡ Crédito/débito tubo éxito !"
#: apps/note/static/note/js/transfer.js:418 #: apps/note/static/note/js/transfer.js:407
#, javascript-format #, javascript-format
msgid "Credit/debit failed: %s" msgid "Credit/debit failed: %s"
msgstr "Crédito/débito falló : %s" msgstr "Crédito/débito falló : %s"
#: note_kfet/static/js/base.js:370 #: note_kfet/static/js/base.js:366
msgid "An error occured while (in)validating this transaction:" msgid "An error occured while (in)validating this transaction:"
msgstr "Un error ocurrió durante la (in)validación de esta transacción :" msgstr "Un error ocurrió durante la (in)validación de esta transacción :"

File diff suppressed because it is too large Load Diff

View File

@ -3,11 +3,12 @@
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-10-07 09:07+0200\n" "POT-Creation-Date: 2020-11-15 23:21+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -25,18 +26,6 @@ msgstr "Alias ajouté avec succès"
msgid "Alias successfully deleted" msgid "Alias successfully deleted"
msgstr "Alias supprimé avec succès" msgstr "Alias supprimé avec succès"
#: apps/member/static/member/js/trust.js:14
msgid "You can't add yourself as a friend"
msgstr "Vous ne pouvez pas vous ajouter vous-même en ami"
#: apps/member/static/member/js/trust.js:37
msgid "Friendship successfully added"
msgstr "Amitié ajoutée avec succès"
#: apps/member/static/member/js/trust.js:53
msgid "Friendship successfully deleted"
msgstr "Amitié supprimée avec succès"
#: apps/note/static/note/js/consos.js:225 #: apps/note/static/note/js/consos.js:225
#, javascript-format #, javascript-format
msgid "" msgid ""
@ -56,31 +45,31 @@ msgstr ""
"la note émettrice %s est en négatif." "la note émettrice %s est en négatif."
#: apps/note/static/note/js/consos.js:232 #: apps/note/static/note/js/consos.js:232
#: apps/note/static/note/js/transfer.js:309 #: apps/note/static/note/js/transfer.js:298
#: apps/note/static/note/js/transfer.js:412 #: apps/note/static/note/js/transfer.js:401
#, javascript-format #, javascript-format
msgid "Warning, the emitter note %s is no more a BDE member." msgid "Warning, the emitter note %s is no more a BDE member."
msgstr "Attention, la note émettrice %s n'est plus adhérente." msgstr "Attention, la note émettrice %s n'est plus adhérente."
#: apps/note/static/note/js/consos.js:254 #: apps/note/static/note/js/consos.js:253
msgid "The transaction couldn't be validated because of insufficient balance." msgid "The transaction couldn't be validated because of insufficient balance."
msgstr "" msgstr ""
"La transaction n'a pas pu être validée pour cause de solde insuffisant." "La transaction n'a pas pu être validée pour cause de solde insuffisant."
#: apps/note/static/note/js/transfer.js:249 #: apps/note/static/note/js/transfer.js:238
msgid "This field is required and must contain a decimal positive number." msgid "This field is required and must contain a decimal positive number."
msgstr "" msgstr ""
"Ce champ est requis et doit comporter un nombre décimal strictement positif." "Ce champ est requis et doit comporter un nombre décimal strictement positif."
#: apps/note/static/note/js/transfer.js:256 #: apps/note/static/note/js/transfer.js:245
msgid "The amount must stay under 21,474,836.47 €." msgid "The amount must stay under 21,474,836.47 €."
msgstr "Le montant ne doit pas excéder 21 474 836.47 €." msgstr "Le montant ne doit pas excéder 21 474 836.47 €."
#: apps/note/static/note/js/transfer.js:262 #: apps/note/static/note/js/transfer.js:251
msgid "This field is required." msgid "This field is required."
msgstr "Ce champ est requis." msgstr "Ce champ est requis."
#: apps/note/static/note/js/transfer.js:288 #: apps/note/static/note/js/transfer.js:277
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning: the transaction of %s from %s to %s was not made because it is the " "Warning: the transaction of %s from %s to %s was not made because it is the "
@ -89,12 +78,12 @@ msgstr ""
"Attention : la transaction de %s de la note %s vers la note %s n'a pas été " "Attention : la transaction de %s de la note %s vers la note %s n'a pas été "
"faite car il s'agit de la même note au départ et à l'arrivée." "faite car il s'agit de la même note au départ et à l'arrivée."
#: apps/note/static/note/js/transfer.js:312 #: apps/note/static/note/js/transfer.js:301
#, javascript-format #, javascript-format
msgid "Warning, the destination note %s is no more a BDE member." msgid "Warning, the destination note %s is no more a BDE member."
msgstr "Attention, la note de destination %s n'est plus adhérente." msgstr "Attention, la note de destination %s n'est plus adhérente."
#: apps/note/static/note/js/transfer.js:318 #: apps/note/static/note/js/transfer.js:307
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning, the transaction of %s from the note %s to the note %s succeed, but " "Warning, the transaction of %s from the note %s to the note %s succeed, but "
@ -103,7 +92,7 @@ msgstr ""
"Attention, La transaction de %s depuis la note %s vers la note %s a été " "Attention, La transaction de %s depuis la note %s vers la note %s a été "
"réalisée avec succès, mais la note émettrice %s est en négatif sévère." "réalisée avec succès, mais la note émettrice %s est en négatif sévère."
#: apps/note/static/note/js/transfer.js:323 #: apps/note/static/note/js/transfer.js:312
#, javascript-format #, javascript-format
msgid "" msgid ""
"Warning, the transaction of %s from the note %s to the note %s succeed, but " "Warning, the transaction of %s from the note %s to the note %s succeed, but "
@ -112,33 +101,33 @@ msgstr ""
"Attention, La transaction de %s depuis la note %s vers la note %s a été " "Attention, La transaction de %s depuis la note %s vers la note %s a été "
"réalisée avec succès, mais la note émettrice %s est en négatif." "réalisée avec succès, mais la note émettrice %s est en négatif."
#: apps/note/static/note/js/transfer.js:329 #: apps/note/static/note/js/transfer.js:318
#, javascript-format #, javascript-format
msgid "Transfer of %s from %s to %s succeed!" msgid "Transfer of %s from %s to %s succeed!"
msgstr "" msgstr ""
"Le transfert de %s de la note %s vers la note %s a été fait avec succès !" "Le transfert de %s de la note %s vers la note %s a été fait avec succès !"
#: apps/note/static/note/js/transfer.js:336 #: apps/note/static/note/js/transfer.js:325
#: apps/note/static/note/js/transfer.js:357 #: apps/note/static/note/js/transfer.js:346
#: apps/note/static/note/js/transfer.js:364 #: apps/note/static/note/js/transfer.js:353
#, javascript-format #, javascript-format
msgid "Transfer of %s from %s to %s failed: %s" msgid "Transfer of %s from %s to %s failed: %s"
msgstr "Le transfert de %s de la note %s vers la note %s a échoué : %s" msgstr "Le transfert de %s de la note %s vers la note %s a échoué : %s"
#: apps/note/static/note/js/transfer.js:358 #: apps/note/static/note/js/transfer.js:347
msgid "insufficient funds" msgid "insufficient funds"
msgstr "solde insuffisant" msgstr "solde insuffisant"
#: apps/note/static/note/js/transfer.js:411 #: apps/note/static/note/js/transfer.js:400
msgid "Credit/debit succeed!" msgid "Credit/debit succeed!"
msgstr "Le crédit/retrait a bien été effectué !" msgstr "Le crédit/retrait a bien été effectué !"
#: apps/note/static/note/js/transfer.js:418 #: apps/note/static/note/js/transfer.js:407
#, javascript-format #, javascript-format
msgid "Credit/debit failed: %s" msgid "Credit/debit failed: %s"
msgstr "Le crédit/retrait a échoué : %s" msgstr "Le crédit/retrait a échoué : %s"
#: note_kfet/static/js/base.js:370 #: note_kfet/static/js/base.js:366
msgid "An error occured while (in)validating this transaction:" msgid "An error occured while (in)validating this transaction:"
msgstr "" msgstr ""
"Une erreur est survenue lors de la validation/dévalidation de cette " "Une erreur est survenue lors de la validation/dévalidation de cette "

View File

@ -18,7 +18,7 @@ MAILTO=notekfet2020@lists.crans.org
# Spammer les gens en négatif # Spammer les gens en négatif
00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 1 -v 0 00 5 * * 2 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --spam --negative-amount 1 -v 0
# Envoyer le rapport mensuel aux trésoriers et respos info # Envoyer le rapport mensuel aux trésoriers et respos info
00 8 * * 5 root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0 00 8 6 * * root cd /var/www/note_kfet && env/bin/python manage.py send_mail_to_negative_balances --report --add-years 1 -v 0
# Envoyer les rapports aux gens # Envoyer les rapports aux gens
55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0 55 6 * * * root cd /var/www/note_kfet && env/bin/python manage.py send_reports -v 0
# Mettre à jour les boutons mis en avant # Mettre à jour les boutons mis en avant

View File

@ -75,6 +75,7 @@ INSTALLED_APPS = [
'permission', 'permission',
'registration', 'registration',
'scripts', 'scripts',
'sheets',
'treasury', 'treasury',
'wei', 'wei',
] ]
@ -252,7 +253,7 @@ REST_FRAMEWORK = {
'rest_framework.authentication.TokenAuthentication', 'rest_framework.authentication.TokenAuthentication',
'oauth2_provider.contrib.rest_framework.OAuth2Authentication', 'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
], ],
'DEFAULT_PAGINATION_CLASS': 'apps.api.pagination.CustomPagination', 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 20, 'PAGE_SIZE': 20,
} }

79
note_kfet/static/css/custom.css Executable file → Normal file
View File

@ -65,10 +65,7 @@ mark {
/* Last BDE colors */ /* Last BDE colors */
.bg-primary { .bg-primary {
/* background-color: rgb(18, 67, 4) !important; */ background-color: rgb(102, 83, 105) !important;
/* MODE VIEUXCON=ON */
/* background-color: rgb(166, 0, 2) !important; */
background-color: rgb(0, 0, 0) !important;
} }
html { html {
@ -83,15 +80,15 @@ body {
.btn-outline-primary:hover, .btn-outline-primary:hover,
.btn-outline-primary:not(:disabled):not(.disabled).active, .btn-outline-primary:not(:disabled):not(.disabled).active,
.btn-outline-primary:not(:disabled):not(.disabled):active { .btn-outline-primary:not(:disabled):not(.disabled):active {
color: rgb(241, 229, 52); color: #fff;
background-color: rgb(228, 35, 132); background-color: rgb(102, 83, 105);
border-color: rgb(228, 35, 132); border-color: rgb(102, 83, 105);
} }
.btn-outline-primary { .btn-outline-primary {
color: #fff; color: rgb(102, 83, 105);
background-color: #000; background-color: rgba(248, 249, 250, 0.9);
border-color: #464647; border-color: rgb(102, 83, 105);
} }
.turbolinks-progress-bar { .turbolinks-progress-bar {
@ -100,64 +97,40 @@ body {
.btn-primary:hover, .btn-primary:hover,
.btn-primary:not(:disabled):not(.disabled).active, .btn-primary:not(:disabled):not(.disabled).active,
.btn-primary:not(:disabled):not(.disabled):active { .btn-primary:not(:disabled):not(.disabled):active,
color: rgb(241, 229, 52); a.badge-primary:hover,
background-color: rgb(228, 35, 132); a.badge-primary:not(:disabled):not(.disabled).active,
border-color: rgb(228, 35, 132); a.badge-primary:not(:disabled):not(.disabled):active {
color: #fff;
background-color: rgb(102, 83, 105);
border-color: rgb(102, 83, 105);
} }
.btn-primary { .btn-primary, a.badge-primary {
color: #fff; color: rgba(248, 249, 250, 0.9);
background-color: #000; background-color: rgb(102, 83, 105);
border-color: #adb5bd; border-color: rgb(102, 83, 105);
} }
.border-primary { .border-primary {
border-color: rgb(228, 35, 132) !important; border-color: rgb(115, 15, 115) !important;
} }
.btn-secondary {
color: #fff;
background-color: #000;
border-color: #adb5bd;
}
.btn-secondary:hover,
.btn-secondary:not(:disabled):not(.disabled).active,
.btn-secondary:not(:disabled):not(.disabled):active {
color: rgb(241, 229, 52);
background-color: rgb(228, 35, 132);
border-color: rgb(228, 35, 132);
}
.btn-outline-dark {
color: #343a40;
border-color: #343a40;
}
.btn-outline-dark:hover,
.btn-outline-dark:not(:disabled):not(.disabled).active,
.btn-outline-dark:not(:disabled):not(.disabled):active {
color: rgb(241, 229, 52);
background-color: rgb(228, 35, 132);
border-color: rgb(228, 35, 132);
}
a { a {
color: rgb(228, 35, 132); color: rgb(102, 83, 105);
} }
a:hover { a:hover {
color: rgb(228, 35, 132); color: rgb(200, 30, 200);
} }
.form-control:focus { .form-control:focus {
box-shadow: 0 0 0 0.25rem rgb(228 35 132 / 50%); box-shadow: 0 0 0 0.25rem rgba(200, 30, 200, 0.25);
border-color: rgb(228, 35, 132); border-color: rgb(200, 30, 200);
} }
.btn-outline-primary.focus { .btn-outline-primary.focus {
box-shadow: 0 0 0 0.25rem rgb(228 35 132 / 10%); box-shadow: 0 0 0 0.25rem rgba(200, 30, 200, 0.5);
} }

View File

@ -1,5 +1,3 @@
const keycodes = [32, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 106, 107, 109, 110, 111, 186, 187, 188, 189, 190, 191, 219, 220, 221, 222]
$(document).ready(function () { $(document).ready(function () {
$('.autocomplete').keyup(function (e) { $('.autocomplete').keyup(function (e) {
const target = $('#' + e.target.id) const target = $('#' + e.target.id)
@ -12,6 +10,7 @@ $(document).ready(function () {
const input = target.val() const input = target.val()
target.addClass('is-invalid') target.addClass('is-invalid')
target.removeClass('is-valid') target.removeClass('is-valid')
$('#' + prefix + '_reset').removeClass('d-none')
$.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) { $.getJSON(api_url + (api_url.includes('?') ? '&' : '?') + 'format=json&search=^' + input + api_url_suffix, function (objects) {
let html = '<ul class="list-group list-group-flush" id="' + prefix + '_list">' let html = '<ul class="list-group list-group-flush" id="' + prefix + '_list">'
@ -42,14 +41,11 @@ $(document).ready(function () {
if (typeof autocompleted !== 'undefined') { autocompleted(obj, prefix) } if (typeof autocompleted !== 'undefined') { autocompleted(obj, prefix) }
}) })
if (input === obj[name_field]) { $('#' + prefix + '_pk').val(obj.id) }
}) })
if (objects.results.length >= 2) { if (objects.results.length === 1 && e.originalEvent.keyCode >= 32) {
$('#' + prefix + '_pk').val(objects.results[0].id)
}
if (objects.results.length === 1 &&
(keycodes.includes(e.originalEvent.keyCode) ||
input === objects.results[0][name_field])) {
$('#' + prefix + '_' + objects.results[0].id).trigger('click') $('#' + prefix + '_' + objects.results[0].id).trigger('click')
} }
}) })
@ -59,6 +55,7 @@ $(document).ready(function () {
const name = $(this).attr('id').replace('_reset', '') const name = $(this).attr('id').replace('_reset', '')
$('#' + name + '_pk').val('') $('#' + name + '_pk').val('')
$('#' + name).val('') $('#' + name).val('')
$('#' + name).tooltip('hide') $('#' + name + '_list').html('')
$(this).addClass('d-none')
}) })
}) })

View File

@ -96,7 +96,7 @@ function displayStyle (note) {
if (!note) { return '' } if (!note) { return '' }
const balance = note.balance const balance = note.balance
var css = '' var css = ''
if (balance < -2000) { css += ' text-danger bg-dark' } if (balance < -5000) { css += ' text-danger bg-dark' }
else if (balance < -1000) { css += ' text-danger' } else if (balance < -1000) { css += ' text-danger' }
else if (balance < 0) { css += ' text-warning' } else if (balance < 0) { css += ' text-warning' }
if (!note.email_confirmed) { css += ' bg-primary' } if (!note.email_confirmed) { css += ' bg-primary' }

View File

@ -12,6 +12,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% endfor %} {% endfor %}
aria-describedby="{{widget.attrs.id}}_tooltip"> aria-describedby="{{widget.attrs.id}}_tooltip">
{% if widget.resetable %} {% if widget.resetable %}
<a id="{{ widget.attrs.id }}_reset" class="btn btn-light autocomplete-reset">{% trans "Reset" %}</a> <a id="{{ widget.attrs.id }}_reset" class="btn btn-light autocomplete-reset{% if not widget.value %} d-none{% endif %}">{% trans "Reset" %}</a>
{% endif %} {% endif %}

View File

@ -194,8 +194,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
class="text-muted">{% trans "Contact us" %}</a> &mdash; class="text-muted">{% trans "Contact us" %}</a> &mdash;
<a href="mailto:{{ "SUPPORT_EMAIL" | getenv }}" <a href="mailto:{{ "SUPPORT_EMAIL" | getenv }}"
class="text-muted">{% trans "Technical Support" %}</a> &mdash; class="text-muted">{% trans "Technical Support" %}</a> &mdash;
<a href="https://note.crans.org/doc/faq/"
class="text-muted">{% trans "FAQ (FR)" %}</a> &mdash;
</span> </span>
{% csrf_token %} {% csrf_token %}
<select title="language" name="language" <select title="language" name="language"

View File

@ -23,7 +23,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% csrf_token %} {% csrf_token %}
{{ form|crispy }} {{ form|crispy }}
{{ profile_form|crispy }} {{ profile_form|crispy }}
{% comment "Soge not for membership (only WEI)" %} {{ soge_form|crispy }} {% endcomment %} {{ soge_form|crispy }}
<button class="btn btn-success" type="submit"> <button class="btn btn-success" type="submit">
{% trans "Sign up" %} {% trans "Sign up" %}
</button> </button>

View File

@ -21,6 +21,7 @@ urlpatterns = [
path('activity/', include('activity.urls')), path('activity/', include('activity.urls')),
path('treasury/', include('treasury.urls')), path('treasury/', include('treasury.urls')),
path('wei/', include('wei.urls')), path('wei/', include('wei.urls')),
path('sheets/', include('sheets.urls')),
# Include Django Contrib and Core routers # Include Django Contrib and Core routers
path('i18n/', include('django.conf.urls.i18n')), path('i18n/', include('django.conf.urls.i18n')),

View File

@ -35,7 +35,7 @@ commands =
flake8 apps --extend-exclude apps/scripts flake8 apps --extend-exclude apps/scripts
[flake8] [flake8]
ignore = W503, I100, I101, B019 ignore = W503, I100, I101
exclude = exclude =
.tox, .tox,
.git, .git,