mirror of
https://gitlab.crans.org/bde/nk20
synced 2025-06-26 03:57:36 +02:00
Compare commits
50 Commits
VSS
...
82fea65b5e
Author | SHA1 | Date | |
---|---|---|---|
82fea65b5e | |||
abc88d0118 | |||
b6b81a8b8f | |||
d228dbf225 | |||
516a7f4be5 | |||
2f8c9b54e7 | |||
e9f18c3ed9 | |||
ff3c30517e | |||
f481ea6acb | |||
802fd8c2d7 | |||
5209a586a9 | |||
24f54ac876 | |||
988b4c9e88 | |||
e32c267995 | |||
5e39209ab1 | |||
08b2fabe07 | |||
405479e5ad | |||
0cc130092f | |||
ff6e207512 | |||
0f1e4d2e60 | |||
6255bcbbb1 | |||
d82a1001c4 | |||
31a54482f0 | |||
4ee02345d4 | |||
422c087d17 | |||
30d6e2c95e | |||
f3a3f07e38 | |||
a5e802f370 | |||
540f3bc354 | |||
2d19457506 | |||
72786d0d2b | |||
f099cbc879 | |||
977eb7c0d4 | |||
d81b1f2710 | |||
6a69590a82 | |||
7afc583282 | |||
4fb0b7d736 | |||
18a5b65a1c | |||
6bc52be707 | |||
834d68fe35 | |||
c6a2849d35 | |||
4ab22c92b3 | |||
c328c1457c | |||
96da7d01ae | |||
d27f942339 | |||
738d6c932d | |||
1760196578 | |||
13b9b6edea | |||
e06e3b2972 | |||
9596aa7b8c |
@ -7,21 +7,6 @@ stages:
|
||||
variables:
|
||||
GIT_SUBMODULE_STRATEGY: recursive
|
||||
|
||||
# Debian Buster
|
||||
py37-django22:
|
||||
stage: test
|
||||
image: debian:buster-backports
|
||||
before_script:
|
||||
- >
|
||||
apt-get update &&
|
||||
apt-get install --no-install-recommends -t buster-backports -y
|
||||
python3-django python3-django-crispy-forms
|
||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
script: tox -e py37-django22
|
||||
|
||||
# Ubuntu 20.04
|
||||
py38-django22:
|
||||
stage: test
|
||||
@ -54,9 +39,26 @@ py39-django22:
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
script: tox -e py39-django22
|
||||
|
||||
# Debian Bookworm
|
||||
py311-django42:
|
||||
stage: test
|
||||
image: debian:bookworm
|
||||
before_script:
|
||||
- >
|
||||
apt-get update &&
|
||||
apt-get install --no-install-recommends -y
|
||||
python3-django python3-django-crispy-forms
|
||||
python3-django-extensions python3-django-filters python3-django-polymorphic
|
||||
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
|
||||
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
|
||||
python3-bs4 python3-setuptools tox texlive-xetex
|
||||
script: tox -e py311-django42
|
||||
|
||||
|
||||
|
||||
linters:
|
||||
stage: quality-assurance
|
||||
image: debian:buster-backports
|
||||
image: debian:bookworm
|
||||
before_script:
|
||||
- apt-get update && apt-get install -y tox
|
||||
script: tox -e linters
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import os
|
||||
@ -123,6 +123,14 @@ class Activity(models.Model):
|
||||
verbose_name=_('open'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("activity")
|
||||
verbose_name_plural = _("activities")
|
||||
unique_together = ("name", "date_start", "date_end",)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
@ -144,14 +152,6 @@ class Activity(models.Model):
|
||||
if settings.DATABASES["default"]["ENGINE"] == 'django.db.backends.postgresql' else refresh_activities()
|
||||
return ret
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("activity")
|
||||
verbose_name_plural = _("activities")
|
||||
unique_together = ("name", "date_start", "date_end",)
|
||||
|
||||
|
||||
class Entry(models.Model):
|
||||
"""
|
||||
@ -252,14 +252,13 @@ class Guest(models.Model):
|
||||
verbose_name=_("inviter"),
|
||||
)
|
||||
|
||||
@property
|
||||
def has_entry(self):
|
||||
try:
|
||||
if self.entry:
|
||||
return True
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
class Meta:
|
||||
verbose_name = _("guest")
|
||||
verbose_name_plural = _("guests")
|
||||
unique_together = ("activity", "last_name", "first_name", )
|
||||
|
||||
def __str__(self):
|
||||
return self.first_name + " " + self.last_name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
@ -290,13 +289,14 @@ class Guest(models.Model):
|
||||
|
||||
return super().save(force_insert, force_update, using, update_fields)
|
||||
|
||||
def __str__(self):
|
||||
return self.first_name + " " + self.last_name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("guest")
|
||||
verbose_name_plural = _("guests")
|
||||
unique_together = ("activity", "last_name", "first_name", )
|
||||
@property
|
||||
def has_entry(self):
|
||||
try:
|
||||
if self.entry:
|
||||
return True
|
||||
return False
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
|
||||
class GuestTransaction(Transaction):
|
||||
|
@ -2,7 +2,8 @@
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import url, include
|
||||
from django.conf.urls import include
|
||||
from django.urls import re_path
|
||||
from rest_framework import routers
|
||||
|
||||
from .views import UserInformationView
|
||||
@ -47,7 +48,7 @@ app_name = 'api'
|
||||
# Wire up our API using automatic URL routing.
|
||||
# Additionally, we include login URLs for the browsable API.
|
||||
urlpatterns = [
|
||||
url('^', include(router.urls)),
|
||||
url('^me/', UserInformationView.as_view()),
|
||||
url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
re_path('^', include(router.urls)),
|
||||
re_path('^me/', UserInformationView.as_view()),
|
||||
re_path('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
]
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.conf import settings
|
||||
@ -76,9 +76,6 @@ class Changelog(models.Model):
|
||||
verbose_name=_('timestamp'),
|
||||
)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
raise ValidationError(_("Logs cannot be destroyed."))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("changelog")
|
||||
verbose_name_plural = _("changelogs")
|
||||
@ -86,3 +83,6 @@ class Changelog(models.Model):
|
||||
def __str__(self):
|
||||
return _("Changelog of type \"{action}\" for model {model} at {timestamp}").format(
|
||||
action=self.get_action_display(), model=str(self.model), timestamp=str(self.timestamp))
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
raise ValidationError(_("Logs cannot be destroyed."))
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import datetime
|
||||
@ -28,7 +28,6 @@ class Profile(models.Model):
|
||||
We do not want to patch the Django Contrib :model:`auth.User`model;
|
||||
so this model add an user profile with additional information.
|
||||
"""
|
||||
|
||||
user = models.OneToOneField(
|
||||
settings.AUTH_USER_MODEL,
|
||||
on_delete=models.CASCADE,
|
||||
@ -139,6 +138,17 @@ class Profile(models.Model):
|
||||
default=False
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('user profile')
|
||||
verbose_name_plural = _('user profile')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('member:user_detail', args=(self.user_id,))
|
||||
|
||||
@property
|
||||
def ens_year(self):
|
||||
"""
|
||||
@ -163,17 +173,6 @@ class Profile(models.Model):
|
||||
return SogeCredit.objects.filter(user=self.user, credit_transaction__isnull=False).exists()
|
||||
return False
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('user profile')
|
||||
verbose_name_plural = _('user profile')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('member:user_detail', args=(self.user_id,))
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
def send_email_validation_link(self):
|
||||
subject = "[Note Kfet] " + str(_("Activate your Note Kfet account"))
|
||||
token = email_validation_token.make_token(self.user)
|
||||
@ -205,9 +204,11 @@ class Club(models.Model):
|
||||
max_length=255,
|
||||
unique=True,
|
||||
)
|
||||
|
||||
email = models.EmailField(
|
||||
verbose_name=_('email'),
|
||||
)
|
||||
|
||||
parent_club = models.ForeignKey(
|
||||
'self',
|
||||
null=True,
|
||||
@ -258,6 +259,27 @@ class Club(models.Model):
|
||||
help_text=_('Maximal date of a membership, after which members must renew it.'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("club")
|
||||
verbose_name_plural = _("clubs")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if not self.require_memberships:
|
||||
self.membership_fee_paid = 0
|
||||
self.membership_fee_unpaid = 0
|
||||
self.membership_duration = None
|
||||
self.membership_start = None
|
||||
self.membership_end = None
|
||||
super().save(force_insert, force_update, update_fields)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('member:club_detail', args=(self.pk,))
|
||||
|
||||
def update_membership_dates(self):
|
||||
"""
|
||||
This function is called each time the club detail view is displayed.
|
||||
@ -278,27 +300,6 @@ class Club(models.Model):
|
||||
self._force_save = True
|
||||
self.save(force_update=True)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None,
|
||||
update_fields=None):
|
||||
if not self.require_memberships:
|
||||
self.membership_fee_paid = 0
|
||||
self.membership_fee_unpaid = 0
|
||||
self.membership_duration = None
|
||||
self.membership_start = None
|
||||
self.membership_end = None
|
||||
super().save(force_insert, force_update, update_fields)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("club")
|
||||
verbose_name_plural = _("clubs")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse_lazy('member:club_detail', args=(self.pk,))
|
||||
|
||||
|
||||
class Membership(models.Model):
|
||||
"""
|
||||
@ -338,6 +339,66 @@ class Membership(models.Model):
|
||||
verbose_name=_('fee'),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('membership')
|
||||
verbose_name_plural = _('memberships')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
||||
def __str__(self):
|
||||
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||
"""
|
||||
# Ensure that club membership dates are valid
|
||||
old_membership_start = self.club.membership_start
|
||||
self.club.update_membership_dates()
|
||||
if self.club.membership_start != old_membership_start:
|
||||
self.club.save()
|
||||
|
||||
created = not self.pk
|
||||
if not created:
|
||||
for role in self.roles.all():
|
||||
club = role.for_club
|
||||
if club is not None:
|
||||
if club.pk != self.club_id:
|
||||
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
||||
.format(role=role.name, club=club.name))
|
||||
else:
|
||||
if Membership.objects.filter(
|
||||
user=self.user,
|
||||
club=self.club,
|
||||
date_start__lte=self.date_start,
|
||||
date_end__gte=self.date_start,
|
||||
).exists():
|
||||
raise ValidationError(_('User is already a member of the club'))
|
||||
|
||||
if self.club.parent_club is not None:
|
||||
# Check that the user is already a member of the parent club if the membership is created
|
||||
if not Membership.objects.filter(
|
||||
user=self.user,
|
||||
club=self.club.parent_club,
|
||||
date_start__gte=self.club.parent_club.membership_start,
|
||||
).exists():
|
||||
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
|
||||
self.renew_parent()
|
||||
else:
|
||||
raise ValidationError(_('User is not a member of the parent club')
|
||||
+ ' ' + self.club.parent_club.name)
|
||||
|
||||
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
|
||||
|
||||
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
|
||||
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
|
||||
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
|
||||
self.date_end = self.club.membership_end
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
self.make_transaction()
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
"""
|
||||
@ -415,58 +476,6 @@ class Membership(models.Model):
|
||||
parent_membership.roles.set(Role.objects.filter(name="Membre de club").all())
|
||||
parent_membership.save()
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
Calculate fee and end date before saving the membership and creating the transaction if needed.
|
||||
"""
|
||||
# Ensure that club membership dates are valid
|
||||
old_membership_start = self.club.membership_start
|
||||
self.club.update_membership_dates()
|
||||
if self.club.membership_start != old_membership_start:
|
||||
self.club.save()
|
||||
|
||||
created = not self.pk
|
||||
if not created:
|
||||
for role in self.roles.all():
|
||||
club = role.for_club
|
||||
if club is not None:
|
||||
if club.pk != self.club_id:
|
||||
raise ValidationError(_('The role {role} does not apply to the club {club}.')
|
||||
.format(role=role.name, club=club.name))
|
||||
else:
|
||||
if Membership.objects.filter(
|
||||
user=self.user,
|
||||
club=self.club,
|
||||
date_start__lte=self.date_start,
|
||||
date_end__gte=self.date_start,
|
||||
).exists():
|
||||
raise ValidationError(_('User is already a member of the club'))
|
||||
|
||||
if self.club.parent_club is not None:
|
||||
# Check that the user is already a member of the parent club if the membership is created
|
||||
if not Membership.objects.filter(
|
||||
user=self.user,
|
||||
club=self.club.parent_club,
|
||||
date_start__gte=self.club.parent_club.membership_start,
|
||||
).exists():
|
||||
if hasattr(self, '_force_renew_parent') and self._force_renew_parent:
|
||||
self.renew_parent()
|
||||
else:
|
||||
raise ValidationError(_('User is not a member of the parent club')
|
||||
+ ' ' + self.club.parent_club.name)
|
||||
|
||||
self.fee = self.club.membership_fee_paid if self.user.profile.paid else self.club.membership_fee_unpaid
|
||||
|
||||
self.date_end = self.date_start + datetime.timedelta(days=self.club.membership_duration) \
|
||||
if self.club.membership_duration is not None else self.date_start + datetime.timedelta(days=424242)
|
||||
if self.club.membership_end is not None and self.date_end > self.club.membership_end:
|
||||
self.date_end = self.club.membership_end
|
||||
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
self.make_transaction()
|
||||
|
||||
def make_transaction(self):
|
||||
"""
|
||||
Create Membership transaction associated to this membership.
|
||||
@ -504,11 +513,3 @@ class Membership(models.Model):
|
||||
soge_credit.save()
|
||||
else:
|
||||
transaction.save(force_insert=True)
|
||||
|
||||
def __str__(self):
|
||||
return _("Membership of {user} for the club {club}").format(user=self.user.username, club=self.club.name, )
|
||||
|
||||
class Meta:
|
||||
verbose_name = _('membership')
|
||||
verbose_name_plural = _('memberships')
|
||||
indexes = [models.Index(fields=['user'])]
|
||||
|
@ -1,7 +1,7 @@
|
||||
/**
|
||||
* On form submit, create a new friendship
|
||||
*/
|
||||
function create_trust (e) {
|
||||
function form_create_trust (e) {
|
||||
// Do not submit HTML form
|
||||
e.preventDefault()
|
||||
|
||||
@ -14,25 +14,35 @@ function create_trust (e) {
|
||||
addMsg(gettext("You can't add yourself as a friend"), "danger")
|
||||
return
|
||||
}
|
||||
$.post('/api/note/trust/', {
|
||||
csrfmiddlewaretoken: formData.get('csrfmiddlewaretoken'),
|
||||
trusting: formData.get('trusting'),
|
||||
trusted: trusted_alias.note
|
||||
}).done(function () {
|
||||
// Reload table
|
||||
$('#trust_table').load(location.pathname + ' #trust_table')
|
||||
addMsg(gettext('Friendship successfully added'), 'success')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
create_trust(formData.get('trusting'), trusted_alias.note)
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On click of "delete", delete the alias
|
||||
* @param button_id:Integer Alias id to remove
|
||||
* Create a trust between users
|
||||
* @param trusting:Integer trusting note id
|
||||
* @param trusted:Integer trusted note id
|
||||
*/
|
||||
function create_trust(trusting, trusted) {
|
||||
$.post('/api/note/trust/', {
|
||||
trusting: trusting,
|
||||
trusted: trusted,
|
||||
csrfmiddlewaretoken: CSRF_TOKEN
|
||||
}).done(function () {
|
||||
// Reload tables
|
||||
$('#trust_table').load(location.pathname + ' #trust_table')
|
||||
$('#trusted_table').load(location.pathname + ' #trusted_table')
|
||||
addMsg(gettext('Friendship successfully added'), 'success')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* On click of "delete", delete the trust
|
||||
* @param button_id:Integer Trust id to remove
|
||||
*/
|
||||
function delete_button (button_id) {
|
||||
$.ajax({
|
||||
@ -42,6 +52,7 @@ function delete_button (button_id) {
|
||||
}).done(function () {
|
||||
addMsg(gettext('Friendship successfully deleted'), 'success')
|
||||
$('#trust_table').load(location.pathname + ' #trust_table')
|
||||
$('#trusted_table').load(location.pathname + ' #trusted_table')
|
||||
}).fail(function (xhr, _textStatus, _error) {
|
||||
errMsg(xhr.responseJSON)
|
||||
})
|
||||
@ -49,5 +60,5 @@ function delete_button (button_id) {
|
||||
|
||||
$(document).ready(function () {
|
||||
// Attach event
|
||||
document.getElementById('form_trust').addEventListener('submit', create_trust)
|
||||
document.getElementById('form_trust').addEventListener('submit', form_create_trust)
|
||||
})
|
||||
|
@ -7,7 +7,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% block profile_content %}
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{% trans "Note friendships" %}
|
||||
{% trans "Add friends" %}
|
||||
</h3>
|
||||
<div class="card-body">
|
||||
{% if can_create %}
|
||||
@ -24,7 +24,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% render_table trusting %}
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning card">
|
||||
<div class="alert alert-warning card mb-3">
|
||||
{% blocktrans trimmed %}
|
||||
Adding someone as a friend enables them to initiate transactions coming
|
||||
from your account (while keeping your balance positive). This is
|
||||
@ -33,6 +33,13 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
friends without needing additional rights among them.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
|
||||
<div class="card bg-light mb-3">
|
||||
<h3 class="card-header text-center">
|
||||
{% trans "People having you as a friend" %}
|
||||
</h3>
|
||||
{% render_table trusted_by %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block extrajavascript %}
|
||||
|
@ -8,7 +8,6 @@ from django.contrib.auth import logout
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.views import LoginView
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import transaction
|
||||
from django.db.models import Q, F
|
||||
from django.shortcuts import redirect
|
||||
@ -21,13 +20,13 @@ from django_tables2.views import SingleTableView
|
||||
from rest_framework.authtoken.models import Token
|
||||
from note.models import Alias, NoteClub, NoteUser, Trust
|
||||
from note.models.transactions import Transaction, SpecialTransaction
|
||||
from note.tables import HistoryTable, AliasTable, TrustTable
|
||||
from note.tables import HistoryTable, AliasTable, TrustTable, TrustedTable
|
||||
from note_kfet.middlewares import _set_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.models import Role
|
||||
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
|
||||
|
||||
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
|
||||
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \
|
||||
CustomAuthenticationForm, MembershipRolesForm
|
||||
from .models import Club, Membership
|
||||
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
|
||||
@ -258,17 +257,18 @@ class ProfileTrustView(ProtectQuerysetMixin, LoginRequiredMixin, DetailView):
|
||||
note = context['object'].note
|
||||
context["trusting"] = TrustTable(
|
||||
note.trusting.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
|
||||
context["trusted_by"] = TrustedTable(
|
||||
note.trusted.filter(PermissionBackend.filter_queryset(self.request, Trust, "view")).distinct().all())
|
||||
context["can_create"] = PermissionBackend.check_perm(self.request, "note.add_trust", Trust(
|
||||
trusting=context["object"].note,
|
||||
trusted=context["object"].note
|
||||
))
|
||||
context["widget"] = {
|
||||
"name": "trusted",
|
||||
"resetable": True,
|
||||
"attrs": {
|
||||
"model_pk": ContentType.objects.get_for_model(Alias).pk,
|
||||
"class": "autocomplete form-control",
|
||||
"id": "trusted",
|
||||
"resetable": True,
|
||||
"api_url": "/api/note/alias/?note__polymorphic_ctype__model=noteuser",
|
||||
"name_field": "name",
|
||||
"placeholder": ""
|
||||
|
@ -7,7 +7,7 @@ from polymorphic.admin import PolymorphicChildModelAdmin, \
|
||||
PolymorphicChildModelFilter, PolymorphicParentModelAdmin
|
||||
from note_kfet.admin import admin_site
|
||||
|
||||
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser
|
||||
from .models.notes import Alias, Note, NoteClub, NoteSpecial, NoteUser, Trust
|
||||
from .models.transactions import Transaction, TemplateCategory, TransactionTemplate, \
|
||||
RecurrentTransaction, MembershipTransaction, SpecialTransaction
|
||||
from .templatetags.pretty_money import pretty_money
|
||||
@ -21,6 +21,16 @@ class AliasInlines(admin.TabularInline):
|
||||
model = Alias
|
||||
|
||||
|
||||
class TrustInlines(admin.TabularInline):
|
||||
"""
|
||||
Define trusts when editing the trusting note
|
||||
"""
|
||||
model = Trust
|
||||
fk_name = "trusting"
|
||||
extra = 0
|
||||
readonly_fields = ("trusted",)
|
||||
|
||||
|
||||
@admin.register(Note, site=admin_site)
|
||||
class NoteAdmin(PolymorphicParentModelAdmin):
|
||||
"""
|
||||
@ -92,7 +102,7 @@ class NoteUserAdmin(PolymorphicChildModelAdmin):
|
||||
"""
|
||||
Child for an user note, see NoteAdmin
|
||||
"""
|
||||
inlines = (AliasInlines,)
|
||||
inlines = (AliasInlines, TrustInlines)
|
||||
|
||||
# We can't change user after creation or the balance
|
||||
readonly_fields = ('user', 'balance')
|
||||
|
@ -11,6 +11,7 @@ from member.models import Membership
|
||||
from note_kfet.middlewares import get_current_request
|
||||
from permission.backends import PermissionBackend
|
||||
from rest_framework.utils import model_meta
|
||||
from rest_framework.validators import UniqueTogetherValidator
|
||||
|
||||
from ..models.notes import Note, NoteClub, NoteSpecial, NoteUser, Alias, Trust
|
||||
from ..models.transactions import TransactionTemplate, Transaction, MembershipTransaction, TemplateCategory, \
|
||||
@ -86,11 +87,9 @@ class TrustSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Trust
|
||||
fields = '__all__'
|
||||
|
||||
def validate(self, attrs):
|
||||
instance = Trust(**attrs)
|
||||
instance.clean()
|
||||
return attrs
|
||||
validators = [UniqueTogetherValidator(
|
||||
queryset=Trust.objects.all(), fields=('trusting', 'trusted'),
|
||||
message=_("This friendship already exists"))]
|
||||
|
||||
|
||||
class AliasSerializer(serializers.ModelSerializer):
|
||||
|
@ -13,7 +13,7 @@ from rest_framework import status
|
||||
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
|
||||
from permission.backends import PermissionBackend
|
||||
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
|
||||
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \
|
||||
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
|
||||
TrustSerializer
|
||||
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import unicodedata
|
||||
@ -293,6 +293,11 @@ class Alias(models.Model):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def normalize(string):
|
||||
"""
|
||||
@ -321,11 +326,6 @@ class Alias(models.Model):
|
||||
pass
|
||||
self.normalized_name = normalized_name
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, using=None, keep_parents=False):
|
||||
if self.name == str(self.note):
|
||||
raise ValidationError(_("You can't delete your main alias."),
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
@ -59,6 +59,7 @@ class TransactionTemplate(models.Model):
|
||||
amount = models.PositiveIntegerField(
|
||||
verbose_name=_('amount'),
|
||||
)
|
||||
|
||||
category = models.ForeignKey(
|
||||
TemplateCategory,
|
||||
on_delete=models.PROTECT,
|
||||
@ -87,12 +88,12 @@ class TransactionTemplate(models.Model):
|
||||
verbose_name = _("transaction template")
|
||||
verbose_name_plural = _("transaction templates")
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('note:template_update', args=(self.pk,))
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('note:template_update', args=(self.pk,))
|
||||
|
||||
|
||||
class Transaction(PolymorphicModel):
|
||||
"""
|
||||
@ -101,7 +102,6 @@ class Transaction(PolymorphicModel):
|
||||
amount is store in centimes of currency, making it a positive integer
|
||||
value. (from someone to someone else)
|
||||
"""
|
||||
|
||||
source = models.ForeignKey(
|
||||
Note,
|
||||
on_delete=models.PROTECT,
|
||||
@ -166,6 +166,50 @@ class Transaction(PolymorphicModel):
|
||||
models.Index(fields=['destination']),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
|
||||
+ pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
When saving, also transfer money between two notes
|
||||
"""
|
||||
if self.source.pk == self.destination.pk:
|
||||
# When source == destination, no money is transferred and no transaction is created
|
||||
return
|
||||
|
||||
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
||||
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
||||
|
||||
# Check that the amounts stay between big integer bounds
|
||||
diff_source, diff_dest = self.validate()
|
||||
|
||||
if not (hasattr(self, '_force_save') and self._force_save) \
|
||||
and (not self.source.is_active or not self.destination.is_active):
|
||||
raise ValidationError(_("The transaction can't be saved since the source note "
|
||||
"or the destination note is not active."))
|
||||
|
||||
# If the aliases are not entered, we assume that the used alias is the name of the note
|
||||
if not self.source_alias:
|
||||
self.source_alias = str(self.source)
|
||||
|
||||
if not self.destination_alias:
|
||||
self.destination_alias = str(self.destination)
|
||||
|
||||
# We save first the transaction, in case of the user has no right to transfer money
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Save notes
|
||||
self.source.refresh_from_db()
|
||||
self.source.balance += diff_source
|
||||
self.source._force_save = True
|
||||
self.source.save()
|
||||
self.destination.refresh_from_db()
|
||||
self.destination.balance += diff_dest
|
||||
self.destination._force_save = True
|
||||
self.destination.save()
|
||||
|
||||
def validate(self):
|
||||
previous_source_balance = self.source.balance
|
||||
previous_dest_balance = self.destination.balance
|
||||
@ -208,46 +252,6 @@ class Transaction(PolymorphicModel):
|
||||
|
||||
return source_balance - previous_source_balance, dest_balance - previous_dest_balance
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
When saving, also transfer money between two notes
|
||||
"""
|
||||
if self.source.pk == self.destination.pk:
|
||||
# When source == destination, no money is transferred and no transaction is created
|
||||
return
|
||||
|
||||
self.source = Note.objects.select_for_update().get(pk=self.source_id)
|
||||
self.destination = Note.objects.select_for_update().get(pk=self.destination_id)
|
||||
|
||||
# Check that the amounts stay between big integer bounds
|
||||
diff_source, diff_dest = self.validate()
|
||||
|
||||
if not (hasattr(self, '_force_save') and self._force_save) \
|
||||
and (not self.source.is_active or not self.destination.is_active):
|
||||
raise ValidationError(_("The transaction can't be saved since the source note "
|
||||
"or the destination note is not active."))
|
||||
|
||||
# If the aliases are not entered, we assume that the used alias is the name of the note
|
||||
if not self.source_alias:
|
||||
self.source_alias = str(self.source)
|
||||
|
||||
if not self.destination_alias:
|
||||
self.destination_alias = str(self.destination)
|
||||
|
||||
# We save first the transaction, in case of the user has no right to transfer money
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
# Save notes
|
||||
self.source.refresh_from_db()
|
||||
self.source.balance += diff_source
|
||||
self.source._force_save = True
|
||||
self.source.save()
|
||||
self.destination.refresh_from_db()
|
||||
self.destination.balance += diff_dest
|
||||
self.destination._force_save = True
|
||||
self.destination.save()
|
||||
|
||||
@property
|
||||
def total(self):
|
||||
return self.amount * self.quantity
|
||||
@ -256,46 +260,40 @@ class Transaction(PolymorphicModel):
|
||||
def type(self):
|
||||
return _('Transfer')
|
||||
|
||||
def __str__(self):
|
||||
return self.__class__.__name__ + " from " + str(self.source) + " to " + str(self.destination) + " of "\
|
||||
+ pretty_money(self.quantity * self.amount) + ("" if self.valid else " invalid")
|
||||
|
||||
|
||||
class RecurrentTransaction(Transaction):
|
||||
"""
|
||||
Special type of :model:`note.Transaction` associated to a :model:`note.TransactionTemplate`.
|
||||
"""
|
||||
|
||||
template = models.ForeignKey(
|
||||
TransactionTemplate,
|
||||
on_delete=models.PROTECT,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("recurrent transaction")
|
||||
verbose_name_plural = _("recurrent transactions")
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def clean(self):
|
||||
if self.template.destination != self.destination and not (hasattr(self, '_force_save') and self._force_save):
|
||||
raise ValidationError(
|
||||
_("The destination of this transaction must equal to the destination of the template."))
|
||||
return super().clean()
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return _('Template')
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("recurrent transaction")
|
||||
verbose_name_plural = _("recurrent transactions")
|
||||
|
||||
|
||||
class SpecialTransaction(Transaction):
|
||||
"""
|
||||
Special type of :model:`note.Transaction` associated to transactions with special notes
|
||||
"""
|
||||
|
||||
last_name = models.CharField(
|
||||
max_length=255,
|
||||
verbose_name=_("name"),
|
||||
@ -312,6 +310,15 @@ class SpecialTransaction(Transaction):
|
||||
blank=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Special transaction")
|
||||
verbose_name_plural = _("Special transactions")
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
return _('Credit') if isinstance(self.source, NoteSpecial) else _("Debit")
|
||||
@ -328,11 +335,6 @@ class SpecialTransaction(Transaction):
|
||||
raise ValidationError(_("A special transaction is only possible between a"
|
||||
" Note associated to a payment method and a User or a Club"))
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
self.clean()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
@staticmethod
|
||||
def validate_payment_form(form):
|
||||
"""
|
||||
@ -363,17 +365,11 @@ class SpecialTransaction(Transaction):
|
||||
|
||||
return not error
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Special transaction")
|
||||
verbose_name_plural = _("Special transactions")
|
||||
|
||||
|
||||
class MembershipTransaction(Transaction):
|
||||
"""
|
||||
Special type of :model:`note.Transaction` associated to a :model:`member.Membership`.
|
||||
|
||||
"""
|
||||
|
||||
membership = models.OneToOneField(
|
||||
'member.Membership',
|
||||
on_delete=models.PROTECT,
|
||||
|
@ -258,3 +258,39 @@ function consume (source, source_alias, dest, quantity, amount, reason, type, ca
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var searchbar = document.getElementById("search-input")
|
||||
var search_results = document.getElementById("search-results")
|
||||
|
||||
var old_pattern = null;
|
||||
var firstMatch = null;
|
||||
/**
|
||||
* Updates the button search tab
|
||||
* @param force Forces the update even if the pattern didn't change
|
||||
*/
|
||||
function updateSearch(force = false) {
|
||||
let pattern = searchbar.value
|
||||
if (pattern === "")
|
||||
firstMatch = null;
|
||||
if ((pattern === old_pattern || pattern === "") && !force)
|
||||
return;
|
||||
firstMatch = null;
|
||||
const re = new RegExp(pattern, "i");
|
||||
Array.from(search_results.children).forEach(function(b) {
|
||||
if (re.test(b.innerText)) {
|
||||
b.hidden = false;
|
||||
if (firstMatch === null) {
|
||||
firstMatch = b;
|
||||
}
|
||||
} else
|
||||
b.hidden = true;
|
||||
});
|
||||
}
|
||||
|
||||
searchbar.addEventListener("input", function (e) {
|
||||
debounce(updateSearch)()
|
||||
});
|
||||
searchbar.addEventListener("keyup", function (e) {
|
||||
if (firstMatch && e.key === "Enter")
|
||||
firstMatch.click()
|
||||
});
|
||||
|
@ -159,11 +159,11 @@ class TrustTable(tables.Table):
|
||||
template_name = 'django_tables2/bootstrap4.html'
|
||||
|
||||
show_header = False
|
||||
trusted = tables.Column(attrs={'td': {'class': 'text_center'}})
|
||||
trusted = tables.Column(attrs={'td': {'class': 'text-center'}})
|
||||
|
||||
delete_col = tables.TemplateColumn(
|
||||
template_code=DELETE_TEMPLATE,
|
||||
extra_context={"delete_trans": _('delete')},
|
||||
extra_context={"delete_trans": _('Delete')},
|
||||
attrs={
|
||||
'td': {
|
||||
'class': lambda record: 'col-sm-1'
|
||||
@ -173,6 +173,46 @@ class TrustTable(tables.Table):
|
||||
verbose_name=_("Delete"),)
|
||||
|
||||
|
||||
class TrustedTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {
|
||||
'class': 'table table condensed table-striped',
|
||||
'id': 'trusted_table'
|
||||
}
|
||||
Model = Trust
|
||||
fields = ("trusting",)
|
||||
template_name = "django_tables2/bootstrap4.html"
|
||||
|
||||
show_header = False
|
||||
trusting = tables.Column(attrs={
|
||||
'td': {'class': 'text-center', 'width': '100%'}})
|
||||
|
||||
trust_back = tables.Column(
|
||||
verbose_name=_("Trust back"),
|
||||
accessor="pk",
|
||||
attrs={
|
||||
'td': {
|
||||
'class': '',
|
||||
'id': lambda record: "trust_back_" + str(record.pk),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
def render_trust_back(self, record):
|
||||
user_note = record.trusted
|
||||
trusting_note = record.trusting
|
||||
if Trust.objects.filter(trusted=trusting_note, trusting=user_note):
|
||||
return ""
|
||||
val = '<button id="'
|
||||
val += str(record.pk)
|
||||
val += '" class="btn btn-success btn-sm text-nowrap" \
|
||||
onclick="create_trust(' + str(record.trusted.pk) + ',' + \
|
||||
str(record.trusting.pk) + ')">'
|
||||
val += str(_("Add back"))
|
||||
val += '</button>'
|
||||
return mark_safe(val)
|
||||
|
||||
|
||||
class AliasTable(tables.Table):
|
||||
class Meta:
|
||||
attrs = {
|
||||
|
@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
name="{{ widget.name }}"
|
||||
{# Other attributes are loaded #}
|
||||
{% for name, value in widget.attrs.items %}
|
||||
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
|
||||
{% if value is not False %}{{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}
|
||||
{% endfor %}>
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text">€</span>
|
||||
|
@ -103,6 +103,11 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link font-weight-bold" data-toggle="tab" href="#search">
|
||||
{% trans "Search" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@ -123,6 +128,20 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="tab-pane" id="search">
|
||||
<input class="form-control mx-auto d-block mb-3"
|
||||
placeholder="{% trans "Search button..." %}" type="search" id="search-input"/>
|
||||
<div class="d-inline-flex flex-wrap justify-content-center" id="search-results">
|
||||
{% for button in all_buttons %}
|
||||
{% if button.display %}
|
||||
<button class="btn btn-outline-dark rounded-0 flex-fill" hidden
|
||||
id="search_button{{ button.id }}" name="button" value="{{ button.name }}">
|
||||
{{ button.name }} ({{ button.amount | pretty_money }})
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -163,7 +182,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
<script type="text/javascript">
|
||||
{% for button in highlighted %}
|
||||
{% if button.display %}
|
||||
$("#highlighted_button{{ button.id }}").click(function() {
|
||||
document.getElementById("highlighted_button{{ button.id }}").addEventListener("click", function() {
|
||||
addConso({{ button.destination_id }}, {{ button.amount }},
|
||||
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
||||
{{ button.id }}, "{{ button.name|escapejs }}");
|
||||
@ -174,7 +193,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% for category in categories %}
|
||||
{% for button in category.templates_filtered %}
|
||||
{% if button.display %}
|
||||
$("#button{{ button.id }}").click(function() {
|
||||
document.getElementById("button{{ button.id }}").addEventListener("click", function() {
|
||||
addConso({{ button.destination_id }}, {{ button.amount }},
|
||||
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
||||
{{ button.id }}, "{{ button.name|escapejs }}");
|
||||
@ -182,5 +201,15 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
|
||||
{% for button in all_buttons %}
|
||||
{% if button.display %}
|
||||
document.getElementById("search_button{{ button.id }}").addEventListener("click", function() {
|
||||
addConso({{ button.destination_id }}, {{ button.amount }},
|
||||
{{ polymorphic_ctype }}, {{ button.category_id }}, "{{ button.category.name|escapejs }}",
|
||||
{{ button.id }}, "{{ button.name|escapejs }}");
|
||||
});
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
@ -10,7 +10,7 @@ from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from permission.models import Role
|
||||
|
||||
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
|
||||
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \
|
||||
TransactionTemplateViewSet, TransactionViewSet
|
||||
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
|
||||
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note
|
||||
|
@ -10,12 +10,12 @@ from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Q, F
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import CreateView, UpdateView, DetailView
|
||||
from django_tables2 import SingleTableView
|
||||
from django.urls import reverse_lazy
|
||||
from django_tables2 import SingleTableView
|
||||
from activity.models import Entry
|
||||
from note_kfet.inputs import AmountInput
|
||||
from permission.backends import PermissionBackend
|
||||
from permission.views import ProtectQuerysetMixin
|
||||
from note_kfet.inputs import AmountInput
|
||||
|
||||
from .forms import TransactionTemplateForm, SearchTransactionForm
|
||||
from .models import TemplateCategory, Transaction, TransactionTemplate, RecurrentTransaction, NoteSpecial, Note
|
||||
@ -190,6 +190,10 @@ class ConsoView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
|
||||
).order_by('name').all()
|
||||
context['polymorphic_ctype'] = ContentType.objects.get_for_model(RecurrentTransaction).pk
|
||||
|
||||
context['all_buttons'] = TransactionTemplate.objects.filter(
|
||||
PermissionBackend.filter_queryset(self.request, TransactionTemplate, "view")
|
||||
).filter(display=True).order_by('name').all()
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
@ -198,6 +198,41 @@ class PermissionBackend(ModelBackend):
|
||||
def has_module_perms(self, user_obj, app_label):
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
@memoize
|
||||
def has_model_perm(request, model, type):
|
||||
"""
|
||||
Check is the given user has the permission over a given model for a given action.
|
||||
The result is then memoized.
|
||||
:param request: The current request
|
||||
:param model: The model that the permissions shoud apply
|
||||
:param type: The type of the permissions: view, change, add or delete
|
||||
For view action, it is consider possible if user can view or change the model
|
||||
"""
|
||||
# Requested by a shell
|
||||
if request is None:
|
||||
return False
|
||||
|
||||
user_obj = request.user
|
||||
sess = request.session
|
||||
|
||||
if hasattr(request, 'auth') and request.auth is not None and hasattr(request.auth, 'scope'):
|
||||
# OAuth2 Authentication
|
||||
user_obj = request.auth.user
|
||||
|
||||
if user_obj is None or user_obj.is_anonymous:
|
||||
return False
|
||||
|
||||
if user_obj.is_superuser and sess.get("permission_mask", -1) >= 42:
|
||||
return True
|
||||
|
||||
ct = ContentType.objects.get_for_model(model)
|
||||
if any(PermissionBackend.permissions(request, ct, type)):
|
||||
return True
|
||||
if type == "view" and any(PermissionBackend.permissions(request, ct, "change")):
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_all_permissions(self, user_obj, obj=None):
|
||||
ct = ContentType.objects.get_for_model(obj)
|
||||
return list(self.permissions(get_current_request(), ct, "view"))
|
||||
|
@ -3073,7 +3073,7 @@
|
||||
],
|
||||
"query": "[\"AND\", {\"source__trusting__trusted\": [\"user\", \"note\"]}, [\"OR\", {\"source__balance__gte\": {\"F\": [\"MUL\", [\"F\", \"amount\"], [\"F\", \"quantity\"]]}}, {\"valid\": false}]]",
|
||||
"type": "add",
|
||||
"mask": 2,
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Transférer de l'argent depuis une note amie en restant positif"
|
||||
@ -3093,6 +3093,22 @@
|
||||
"field": "",
|
||||
"permanent": false,
|
||||
"description": "Créer un crédit quelconque"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "permission.permission",
|
||||
"pk": 198,
|
||||
"fields": {
|
||||
"model": [
|
||||
"note",
|
||||
"trust"
|
||||
],
|
||||
"query": "{\"trusted__noteuser__user\": [\"user\"]}",
|
||||
"type": "view",
|
||||
"mask": 1,
|
||||
"field": "",
|
||||
"permanent": true,
|
||||
"description": "Voir ceux nous ayant pour ami, pour toujours"
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -3132,10 +3148,11 @@
|
||||
187,
|
||||
188,
|
||||
189,
|
||||
190,
|
||||
191,
|
||||
195,
|
||||
196
|
||||
190,
|
||||
191,
|
||||
195,
|
||||
196,
|
||||
198
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2021 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import functools
|
||||
@ -26,6 +26,15 @@ class InstancedPermission:
|
||||
self.mask = mask
|
||||
self.kwargs = kwargs
|
||||
|
||||
def __repr__(self):
|
||||
if self.field:
|
||||
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
|
||||
else:
|
||||
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
def applies(self, obj, permission_type, field_name=None):
|
||||
"""
|
||||
Returns True if the permission applies to
|
||||
@ -84,21 +93,11 @@ class InstancedPermission:
|
||||
# noinspection PyProtectedMember
|
||||
self.query = Permission._about(self.raw_query, **self.kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
if self.field:
|
||||
return _("Can {type} {model}.{field} in {query}").format(type=self.type, model=self.model, field=self.field, query=self.query)
|
||||
else:
|
||||
return _("Can {type} {model} in {query}").format(type=self.type, model=self.model, query=self.query)
|
||||
|
||||
def __str__(self):
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
class PermissionMask(models.Model):
|
||||
"""
|
||||
Permissions that are hidden behind a mask
|
||||
"""
|
||||
|
||||
rank = models.PositiveSmallIntegerField(
|
||||
unique=True,
|
||||
verbose_name=_('rank'),
|
||||
@ -110,13 +109,13 @@ class PermissionMask(models.Model):
|
||||
verbose_name=_('description'),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("permission mask")
|
||||
verbose_name_plural = _("permission masks")
|
||||
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
|
||||
class Permission(models.Model):
|
||||
|
||||
@ -194,16 +193,19 @@ class Permission(models.Model):
|
||||
verbose_name = _("permission")
|
||||
verbose_name_plural = _("permissions")
|
||||
|
||||
def clean(self):
|
||||
self.query = json.dumps(json.loads(self.query))
|
||||
if self.field and self.type not in {'view', 'change'}:
|
||||
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, **kwargs):
|
||||
self.full_clean()
|
||||
super().save()
|
||||
|
||||
def clean(self):
|
||||
self.query = json.dumps(json.loads(self.query))
|
||||
if self.field and self.type not in {'view', 'change'}:
|
||||
raise ValidationError(_("Specifying field applies only to view and change permission types."))
|
||||
|
||||
@staticmethod
|
||||
def compute_f(oper, **kwargs):
|
||||
if isinstance(oper, list):
|
||||
@ -317,9 +319,6 @@ class Permission(models.Model):
|
||||
# query = self._about(query, **kwargs)
|
||||
return InstancedPermission(self.model, query, self.type, self.field, self.mask, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return self.description
|
||||
|
||||
|
||||
class Role(models.Model):
|
||||
"""
|
||||
@ -344,9 +343,9 @@ class Role(models.Model):
|
||||
default=None,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("role permissions")
|
||||
verbose_name_plural = _("role permissions")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
@ -44,6 +44,8 @@ class PermissionOAuth2Validator(OAuth2Validator):
|
||||
subset of permissions.
|
||||
"""
|
||||
|
||||
oidc_claim_scope = None # fix breaking change of django-oauth-toolkit 2.0.0
|
||||
|
||||
valid_scopes = set()
|
||||
|
||||
for t in Permission.PERMISSION_TYPES:
|
||||
|
@ -5,7 +5,7 @@ from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.filters import SearchFilter
|
||||
from api.viewsets import ReadProtectedModelViewSet
|
||||
|
||||
from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer,\
|
||||
from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer, \
|
||||
SogeCreditSerializer
|
||||
from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
from datetime import date
|
||||
|
||||
@ -20,7 +20,6 @@ class Invoice(models.Model):
|
||||
"""
|
||||
An invoice model that can generates a true invoice.
|
||||
"""
|
||||
|
||||
id = models.PositiveIntegerField(
|
||||
primary_key=True,
|
||||
verbose_name=_("Invoice identifier"),
|
||||
@ -81,6 +80,13 @@ class Invoice(models.Model):
|
||||
verbose_name=_("tex source"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("invoice")
|
||||
verbose_name_plural = _("invoices")
|
||||
|
||||
def __str__(self):
|
||||
return _("Invoice #{id}").format(id=self.id)
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
"""
|
||||
@ -111,19 +117,11 @@ class Invoice(models.Model):
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("invoice")
|
||||
verbose_name_plural = _("invoices")
|
||||
|
||||
def __str__(self):
|
||||
return _("Invoice #{id}").format(id=self.id)
|
||||
|
||||
|
||||
class Product(models.Model):
|
||||
"""
|
||||
Product that appears on an invoice.
|
||||
"""
|
||||
|
||||
invoice = models.ForeignKey(
|
||||
Invoice,
|
||||
on_delete=models.CASCADE,
|
||||
@ -147,6 +145,13 @@ class Product(models.Model):
|
||||
verbose_name=_("Unit price"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("product")
|
||||
verbose_name_plural = _("products")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.designation} ({self.invoice})"
|
||||
|
||||
@property
|
||||
def amount_euros(self):
|
||||
return "{:.2f}".format(self.amount / 100)
|
||||
@ -159,37 +164,28 @@ class Product(models.Model):
|
||||
def total_euros(self):
|
||||
return "{:.2f}".format(self.total / 100)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("product")
|
||||
verbose_name_plural = _("products")
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.designation} ({self.invoice})"
|
||||
|
||||
|
||||
class RemittanceType(models.Model):
|
||||
"""
|
||||
Store what kind of remittances can be stored.
|
||||
"""
|
||||
|
||||
note = models.OneToOneField(
|
||||
NoteSpecial,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.note)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("remittance type")
|
||||
verbose_name_plural = _("remittance types")
|
||||
|
||||
def __str__(self):
|
||||
return str(self.note)
|
||||
|
||||
|
||||
class Remittance(models.Model):
|
||||
"""
|
||||
Treasurers want to regroup checks or bank transfers in bank remittances.
|
||||
"""
|
||||
|
||||
date = models.DateTimeField(
|
||||
default=timezone.now,
|
||||
verbose_name=_("Date"),
|
||||
@ -215,6 +211,17 @@ class Remittance(models.Model):
|
||||
verbose_name = _("remittance")
|
||||
verbose_name_plural = _("remittances")
|
||||
|
||||
def __str__(self):
|
||||
return _("Remittance #{:d}: {}").format(self.id, self.comment, )
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
# Check if all transactions have the right type.
|
||||
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
||||
raise ValidationError("All transactions in a remittance must have the same type")
|
||||
|
||||
return super().save(force_insert, force_update, using, update_fields)
|
||||
|
||||
@property
|
||||
def transactions(self):
|
||||
"""
|
||||
@ -237,17 +244,6 @@ class Remittance(models.Model):
|
||||
"""
|
||||
return sum(transaction.total for transaction in self.transactions.all())
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
|
||||
# Check if all transactions have the right type.
|
||||
if self.transactions.exists() and self.transactions.filter(~Q(source=self.remittance_type.note)).exists():
|
||||
raise ValidationError("All transactions in a remittance must have the same type")
|
||||
|
||||
return super().save(force_insert, force_update, using, update_fields)
|
||||
|
||||
def __str__(self):
|
||||
return _("Remittance #{:d}: {}").format(self.id, self.comment, )
|
||||
|
||||
|
||||
class SpecialTransactionProxy(models.Model):
|
||||
"""
|
||||
@ -255,7 +251,6 @@ class SpecialTransactionProxy(models.Model):
|
||||
That's why we create a proxy in this app, to link special transactions and remittances.
|
||||
If it isn't very clean, it does what we want.
|
||||
"""
|
||||
|
||||
transaction = models.OneToOneField(
|
||||
SpecialTransaction,
|
||||
on_delete=models.CASCADE,
|
||||
@ -301,6 +296,43 @@ class SogeCredit(models.Model):
|
||||
null=True,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Credit from the Société générale")
|
||||
verbose_name_plural = _("Credits from the Société générale")
|
||||
|
||||
def __str__(self):
|
||||
return _("Soge credit for {user}").format(user=str(self.user))
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
# This is a pre-registered user that declared that a SoGé account was opened.
|
||||
# No note exists yet.
|
||||
if not NoteUser.objects.filter(user=self.user).exists():
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
if not self.credit_transaction:
|
||||
credit_transaction = SpecialTransaction(
|
||||
source=NoteSpecial.objects.get(special_type="Virement bancaire"),
|
||||
destination=self.user.note,
|
||||
quantity=1,
|
||||
amount=0,
|
||||
reason="Crédit société générale",
|
||||
last_name=self.user.last_name,
|
||||
first_name=self.user.first_name,
|
||||
bank="Société générale",
|
||||
valid=False,
|
||||
)
|
||||
credit_transaction._force_save = True
|
||||
credit_transaction.save()
|
||||
credit_transaction.refresh_from_db()
|
||||
self.credit_transaction = credit_transaction
|
||||
elif not self.valid:
|
||||
self.credit_transaction.amount = self.amount
|
||||
self.credit_transaction._force_save = True
|
||||
self.credit_transaction.save()
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def valid(self):
|
||||
return self.credit_transaction and self.credit_transaction.valid
|
||||
@ -338,11 +370,11 @@ class SogeCredit(models.Model):
|
||||
# if m.transaction not in self.transactions.all():
|
||||
# self.transactions.add(m.transaction)
|
||||
#
|
||||
# if kfet_qs.exists():
|
||||
# m = kfet_qs.get()
|
||||
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||
# if m.transaction not in self.transactions.all():
|
||||
# self.transactions.add(m.transaction)
|
||||
# if kfet_qs.exists():
|
||||
# m = kfet_qs.get()
|
||||
# if MembershipTransaction.objects.filter(membership=m).exists(): # non-free membership
|
||||
# if m.transaction not in self.transactions.all():
|
||||
# self.transactions.add(m.transaction)
|
||||
|
||||
if 'wei' in settings.INSTALLED_APPS:
|
||||
from wei.models import WEIClub
|
||||
@ -390,36 +422,6 @@ class SogeCredit(models.Model):
|
||||
tr._force_save = True
|
||||
tr.save()
|
||||
|
||||
@transaction.atomic
|
||||
def save(self, *args, **kwargs):
|
||||
# This is a pre-registered user that declared that a SoGé account was opened.
|
||||
# No note exists yet.
|
||||
if not NoteUser.objects.filter(user=self.user).exists():
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
if not self.credit_transaction:
|
||||
credit_transaction = SpecialTransaction(
|
||||
source=NoteSpecial.objects.get(special_type="Virement bancaire"),
|
||||
destination=self.user.note,
|
||||
quantity=1,
|
||||
amount=0,
|
||||
reason="Crédit société générale",
|
||||
last_name=self.user.last_name,
|
||||
first_name=self.user.first_name,
|
||||
bank="Société générale",
|
||||
valid=False,
|
||||
)
|
||||
credit_transaction._force_save = True
|
||||
credit_transaction.save()
|
||||
credit_transaction.refresh_from_db()
|
||||
self.credit_transaction = credit_transaction
|
||||
elif not self.valid:
|
||||
self.credit_transaction.amount = self.amount
|
||||
self.credit_transaction._force_save = True
|
||||
self.credit_transaction.save()
|
||||
|
||||
return super().save(*args, **kwargs)
|
||||
|
||||
def delete(self, **kwargs):
|
||||
"""
|
||||
Deleting a SogeCredit is equivalent to say that the Société générale didn't pay.
|
||||
@ -447,10 +449,3 @@ class SogeCredit(models.Model):
|
||||
self.credit_transaction._force_save = True
|
||||
self.credit_transaction.save()
|
||||
super().delete(**kwargs)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Credit from the Société générale")
|
||||
verbose_name_plural = _("Credits from the Société générale")
|
||||
|
||||
def __str__(self):
|
||||
return _("Soge credit for {user}").format(user=str(self.user))
|
||||
|
@ -385,8 +385,7 @@ class TestSogeCredits(TestCase):
|
||||
|
||||
response = self.client.post(reverse("treasury:manage_soge_credit", args=(soge_credit.pk,)),
|
||||
data=dict(delete=True))
|
||||
# 403 because no SogeCredit exists anymore, then a PermissionDenied is raised
|
||||
self.assertRedirects(response, reverse("treasury:soge_credits"), 302, 403)
|
||||
self.assertRedirects(response, reverse("treasury:soge_credits"), 302, 200)
|
||||
self.assertFalse(SogeCredit.objects.filter(pk=soge_credit.pk))
|
||||
self.user.note.refresh_from_db()
|
||||
self.assertEqual(self.user.note.balance, 0)
|
||||
|
@ -3,8 +3,8 @@
|
||||
|
||||
from django.urls import path
|
||||
|
||||
from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView,\
|
||||
RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView,\
|
||||
from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView, \
|
||||
RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, \
|
||||
UnlinkTransactionToRemittanceView, SogeCreditListView, SogeCreditManageView
|
||||
|
||||
app_name = 'treasury'
|
||||
|
@ -101,14 +101,7 @@ class InvoiceListView(LoginRequiredMixin, SingleTableView):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
sample_invoice = Invoice(
|
||||
id=0,
|
||||
object="",
|
||||
description="",
|
||||
name="",
|
||||
address="",
|
||||
)
|
||||
if not PermissionBackend.check_perm(self.request, "treasury.view_invoice", sample_invoice):
|
||||
if not PermissionBackend.has_model_perm(self.request, Invoice(), "view"):
|
||||
raise PermissionDenied(_("You are not able to see the treasury interface."))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@ -278,11 +271,7 @@ class RemittanceListView(LoginRequiredMixin, TemplateView):
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
sample_remittance = Remittance(
|
||||
remittance_type_id=1,
|
||||
comment="",
|
||||
)
|
||||
if not PermissionBackend.check_perm(self.request, "treasury.add_remittance", sample_remittance):
|
||||
if not PermissionBackend.has_model_perm(self.request, Remittance(), "view"):
|
||||
raise PermissionDenied(_("You are not able to see the treasury interface."))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
@ -408,7 +397,7 @@ class SogeCreditListView(LoginRequiredMixin, ProtectQuerysetMixin, SingleTableVi
|
||||
if not request.user.is_authenticated:
|
||||
return self.handle_no_permission()
|
||||
|
||||
if not super().get_queryset().exists():
|
||||
if not PermissionBackend.has_model_perm(self.request, SogeCredit(), "view"):
|
||||
raise PermissionDenied(_("You are not able to see the treasury interface."))
|
||||
return super().dispatch(request, *args, **kwargs)
|
||||
|
||||
|
@ -1,90 +1,189 @@
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import time
|
||||
from functools import lru_cache
|
||||
from random import Random
|
||||
|
||||
from django import forms
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from .base import WEISurvey, WEISurveyInformation, WEISurveyAlgorithm, WEIBusInformation
|
||||
from ...models import WEIMembership
|
||||
|
||||
WORDS = [
|
||||
'ABBA', 'After', 'Alcoolique anonyme', 'Ambiance festive', 'Années 2000', 'Apéro', 'Art',
|
||||
'Baby foot billard biere pong', 'BBQ', 'Before', 'Bière pong', 'Bon enfant', 'Calme', 'Canapé',
|
||||
'Chanson paillarde', 'Chanson populaire', 'Chartreuse', 'Cheerleader', 'Chill', 'Choré',
|
||||
'Cinéma', 'Cocktail', 'Comédie musicle', 'Commercial', 'Copaing', 'Danse', 'Dancefloor',
|
||||
'Electro', 'Fanfare', 'Gin tonic', 'Inclusif', 'Jazz', "Jeux d'alcool", 'Jeux de carte',
|
||||
'Jeux de rôle', 'Jeux de société', 'JUL', 'Jus de fruit', 'Kfet', 'Kleptomanie assurée',
|
||||
'LGBTQ+', 'Livre', 'Morning beer', 'Musique', 'NAPS', 'Paillettes', 'Pastis', 'Paté Hénaff',
|
||||
'Peluche', 'Pena baiona', "Peu d'alcool", 'Pilier de bar', 'PMU', 'Poulpe', 'Punch', 'Rap',
|
||||
'Réveil', 'Rock', 'Rugby', 'Sandwich', 'Serge', 'Shot', 'Sociable', 'Spectacle', 'Techno',
|
||||
'Techno house', 'Thérapie Taxi', 'Tradition kchanaises', 'Troisième mi-temps', 'Turn up',
|
||||
'Vodka', 'Vodka pomme', 'Volley', 'Vomi stratégique'
|
||||
]
|
||||
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 choose 20 words, from which we calculate the best associated bus.
|
||||
Members answer 20 questions, from which we calculate the best associated bus.
|
||||
"""
|
||||
|
||||
word = forms.ChoiceField(
|
||||
label=_("Choose a word:"),
|
||||
widget=forms.RadioSelect(),
|
||||
)
|
||||
|
||||
def set_registration(self, registration):
|
||||
"""
|
||||
Filter the bus selector with the buses of the current WEI.
|
||||
"""
|
||||
information = WEISurveyInformation2023(registration)
|
||||
if not information.seed:
|
||||
information.seed = int(1000 * time.time())
|
||||
information.save(registration)
|
||||
registration._force_save = True
|
||||
registration.save()
|
||||
|
||||
if self.data:
|
||||
self.fields["word"].choices = [(w, w) for w in WORDS]
|
||||
if self.is_valid():
|
||||
return
|
||||
|
||||
rng = Random((information.step + 1) * information.seed)
|
||||
|
||||
words = None
|
||||
|
||||
buses = WEISurveyAlgorithm2023.get_buses()
|
||||
informations = {bus: WEIBusInformation2023(bus) for bus in buses}
|
||||
scores = sum((list(informations[bus].scores.values()) for bus in buses), [])
|
||||
average_score = sum(scores) / len(scores)
|
||||
|
||||
preferred_words = {bus: [word for word in WORDS
|
||||
if informations[bus].scores[word] >= average_score]
|
||||
for bus in buses}
|
||||
while words is None or len(set(words)) != len(words):
|
||||
# Ensure that there is no the same word 2 times
|
||||
words = [rng.choice(words) for _ignored2, words in preferred_words.items()]
|
||||
rng.shuffle(words)
|
||||
words = [(w, w) for w in words]
|
||||
self.fields["word"].choices = words
|
||||
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 word, the bus has a score
|
||||
For each question, the bus has ordered answers
|
||||
"""
|
||||
scores: dict
|
||||
|
||||
def __init__(self, bus):
|
||||
self.scores = {}
|
||||
for word in WORDS:
|
||||
self.scores[word] = 0.0
|
||||
for question in WORDS:
|
||||
self.scores[question] = []
|
||||
super().__init__(bus)
|
||||
|
||||
|
||||
@ -93,13 +192,13 @@ 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.
|
||||
"""
|
||||
# Random seed that is stored at the first time to ensure that words are generated only once
|
||||
seed = 0
|
||||
|
||||
step = 0
|
||||
questions = list(WORDS.keys())
|
||||
|
||||
def __init__(self, registration):
|
||||
for i in range(1, 21):
|
||||
setattr(self, "word" + str(i), None)
|
||||
for question in WORDS:
|
||||
setattr(self, str(question), None)
|
||||
super().__init__(registration)
|
||||
|
||||
|
||||
@ -127,9 +226,11 @@ class WEISurvey2023(WEISurvey):
|
||||
|
||||
@transaction.atomic
|
||||
def form_valid(self, form):
|
||||
word = form.cleaned_data["word"]
|
||||
self.information.step += 1
|
||||
setattr(self.information, "word" + str(self.information.step), word)
|
||||
for question in WORDS:
|
||||
if question in form.cleaned_data:
|
||||
answer = form.cleaned_data[question]
|
||||
setattr(self.information, question, answer)
|
||||
self.save()
|
||||
|
||||
@classmethod
|
||||
@ -140,16 +241,10 @@ class WEISurvey2023(WEISurvey):
|
||||
"""
|
||||
The survey is complete once the bus is chosen.
|
||||
"""
|
||||
return self.information.step == 20
|
||||
|
||||
@classmethod
|
||||
@lru_cache()
|
||||
def word_mean(cls, word):
|
||||
"""
|
||||
Calculate the mid-score given by all buses.
|
||||
"""
|
||||
buses = cls.get_algorithm_class().get_buses()
|
||||
return sum([cls.get_algorithm_class().get_bus_information(bus).scores[word] for bus in buses]) / buses.count()
|
||||
for question in WORDS:
|
||||
if not getattr(self.information, question):
|
||||
return False
|
||||
return True
|
||||
|
||||
@lru_cache()
|
||||
def score(self, bus):
|
||||
@ -158,8 +253,9 @@ class WEISurvey2023(WEISurvey):
|
||||
|
||||
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 = sum(bus_info.scores[getattr(self.information, 'word' + str(i))]
|
||||
- self.word_mean(getattr(self.information, 'word' + str(i))) for i in range(1, 21)) / 20
|
||||
s = 0
|
||||
for question in WORDS:
|
||||
s += bus_info.scores[question][str(getattr(self.information, question))]
|
||||
return s
|
||||
|
||||
@lru_cache()
|
||||
@ -174,7 +270,6 @@ class WEISurvey2023(WEISurvey):
|
||||
|
||||
@classmethod
|
||||
def clear_cache(cls):
|
||||
cls.word_mean.cache_clear()
|
||||
return super().clear_cache()
|
||||
|
||||
|
||||
|
18
apps/wei/migrations/0008_auto_20240111_1545.py
Normal file
18
apps/wei/migrations/0008_auto_20240111_1545.py
Normal file
@ -0,0 +1,18 @@
|
||||
# Generated by Django 2.2.28 on 2024-01-11 14:45
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wei', '0007_help_text_emergency_contact'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='weiclub',
|
||||
name='year',
|
||||
field=models.PositiveIntegerField(default=2024, unique=True, verbose_name='year'),
|
||||
),
|
||||
]
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2018-2023 by BDE ENS Paris-Saclay
|
||||
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
|
||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
import json
|
||||
@ -33,6 +33,10 @@ class WEIClub(Club):
|
||||
verbose_name=_("date end"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("WEI")
|
||||
verbose_name_plural = _("WEI")
|
||||
|
||||
@property
|
||||
def is_current_wei(self):
|
||||
"""
|
||||
@ -46,10 +50,6 @@ class WEIClub(Club):
|
||||
"""
|
||||
return
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("WEI")
|
||||
verbose_name_plural = _("WEI")
|
||||
|
||||
|
||||
class Bus(models.Model):
|
||||
"""
|
||||
@ -84,6 +84,14 @@ class Bus(models.Model):
|
||||
help_text=_("Information about the survey for new members, encoded in JSON"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Bus")
|
||||
verbose_name_plural = _("Buses")
|
||||
unique_together = ('wei', 'name',)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def information(self):
|
||||
"""
|
||||
@ -106,14 +114,6 @@ class Bus(models.Model):
|
||||
registrations = [r for r in registrations if 'selected_bus_pk' in r.information]
|
||||
return sum(1 for r in registrations if r.information['selected_bus_pk'] == self.pk)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Bus")
|
||||
verbose_name_plural = _("Buses")
|
||||
unique_together = ('wei', 'name',)
|
||||
|
||||
|
||||
class BusTeam(models.Model):
|
||||
"""
|
||||
@ -142,20 +142,19 @@ class BusTeam(models.Model):
|
||||
verbose_name=_("description"),
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.name + " (" + str(self.bus) + ")"
|
||||
|
||||
class Meta:
|
||||
unique_together = ('bus', 'name',)
|
||||
verbose_name = _("Bus team")
|
||||
verbose_name_plural = _("Bus teams")
|
||||
|
||||
def __str__(self):
|
||||
return self.name + " (" + str(self.bus) + ")"
|
||||
|
||||
|
||||
class WEIRole(Role):
|
||||
"""
|
||||
A Role for the WEI can be bus chief, team chief, free electron, ...
|
||||
"""
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("WEI Role")
|
||||
verbose_name_plural = _("WEI Roles")
|
||||
@ -165,7 +164,6 @@ class WEIRegistration(models.Model):
|
||||
"""
|
||||
Store personal data that can be useful for the WEI.
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
on_delete=models.PROTECT,
|
||||
@ -258,6 +256,14 @@ class WEIRegistration(models.Model):
|
||||
"encoded in JSON"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'wei',)
|
||||
verbose_name = _("WEI User")
|
||||
verbose_name_plural = _("WEI Users")
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
@property
|
||||
def information(self):
|
||||
"""
|
||||
@ -307,14 +313,6 @@ class WEIRegistration(models.Model):
|
||||
except AttributeError:
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return str(self.user)
|
||||
|
||||
class Meta:
|
||||
unique_together = ('user', 'wei',)
|
||||
verbose_name = _("WEI User")
|
||||
verbose_name_plural = _("WEI Users")
|
||||
|
||||
|
||||
class WEIMembership(Membership):
|
||||
bus = models.ForeignKey(
|
||||
|
@ -2,9 +2,12 @@
|
||||
# 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
|
||||
@ -20,11 +23,27 @@ class TestWEIAlgorithm(TestCase):
|
||||
"""
|
||||
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",
|
||||
date_start='2023-09-16',
|
||||
date_end='2023-09-18',
|
||||
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,
|
||||
)
|
||||
|
||||
@ -33,8 +52,8 @@ class TestWEIAlgorithm(TestCase):
|
||||
bus = Bus.objects.create(wei=self.wei, name=f"Bus {i}", size=10)
|
||||
self.buses.append(bus)
|
||||
information = WEIBusInformation2023(bus)
|
||||
for word in WORDS:
|
||||
information.scores[word] = random.randint(0, 101)
|
||||
for question in WORDS:
|
||||
information.scores[question] = {answer: random.randint(1, 5) for answer in WORDS[question][1]}
|
||||
information.save()
|
||||
bus.save()
|
||||
|
||||
@ -52,8 +71,8 @@ class TestWEIAlgorithm(TestCase):
|
||||
birth_date='2000-01-01',
|
||||
)
|
||||
information = WEISurveyInformation2023(registration)
|
||||
for j in range(1, 21):
|
||||
setattr(information, f'word{j}', random.choice(WORDS))
|
||||
for question in WORDS:
|
||||
setattr(information, question, random.randint(1, 5))
|
||||
information.step = 20
|
||||
information.save(registration)
|
||||
registration.save()
|
||||
@ -82,8 +101,8 @@ class TestWEIAlgorithm(TestCase):
|
||||
birth_date='2000-01-01',
|
||||
)
|
||||
information = WEISurveyInformation2023(registration)
|
||||
for j in range(1, 21):
|
||||
setattr(information, f'word{j}', random.choice(WORDS))
|
||||
for question in WORDS:
|
||||
setattr(information, question, random.randint(1, 5))
|
||||
information.step = 20
|
||||
information.save(registration)
|
||||
registration.save()
|
||||
@ -108,3 +127,44 @@ class TestWEIAlgorithm(TestCase):
|
||||
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())
|
||||
|
@ -380,7 +380,7 @@ class TestWEIRegistration(TestCase):
|
||||
|
||||
def test_register_1a(self):
|
||||
"""
|
||||
Test register a first year member to the WEI and complete the survey.
|
||||
Test register a first year member to the WEI.
|
||||
"""
|
||||
response = self.client.get(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
@ -402,21 +402,6 @@ class TestWEIRegistration(TestCase):
|
||||
self.assertTrue(qs.exists())
|
||||
registration = qs.get()
|
||||
self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302, 200)
|
||||
for i in range(1, 21):
|
||||
# Fill 1A Survey, 20 pages
|
||||
response = self.client.post(reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), dict(
|
||||
word="Jus de fruit",
|
||||
))
|
||||
registration.refresh_from_db()
|
||||
survey = CurrentSurvey(registration)
|
||||
self.assertRedirects(response, reverse("wei:wei_survey", kwargs=dict(pk=registration.pk)), 302,
|
||||
302 if survey.is_complete() else 200)
|
||||
self.assertIsNotNone(getattr(survey.information, "word" + str(i)), "Survey page #" + str(i) + " failed")
|
||||
survey = CurrentSurvey(registration)
|
||||
self.assertTrue(survey.is_complete())
|
||||
survey.select_bus(self.bus)
|
||||
survey.save()
|
||||
self.assertIsNotNone(survey.information.get_selected_bus())
|
||||
|
||||
# Check that the user can't be registered twice
|
||||
response = self.client.post(reverse("wei:wei_register_1A", kwargs=dict(wei_pk=self.wei.pk)), dict(
|
||||
@ -662,7 +647,7 @@ class TestWEIRegistration(TestCase):
|
||||
first_name="admin",
|
||||
bank="Société générale",
|
||||
))
|
||||
self.assertRedirects(response, reverse("wei:wei_detail", kwargs=dict(pk=self.registration.wei.pk)), 302, 200)
|
||||
self.assertRedirects(response, reverse("wei:wei_registrations", kwargs=dict(pk=self.registration.wei.pk)), 302, 200)
|
||||
# Check if the membership is successfully created
|
||||
membership = WEIMembership.objects.filter(user_id=self.user.id, club=self.wei)
|
||||
self.assertTrue(membership.exists())
|
||||
|
@ -969,7 +969,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
|
||||
if not registration.soge_credit and user.note.balance + credit_amount < fee:
|
||||
# Users must have money before registering to the WEI.
|
||||
form.add_error('bus',
|
||||
form.add_error('credit_type',
|
||||
_("This user don't have enough money to join this club, and can't have a negative balance."))
|
||||
return super().form_invalid(form)
|
||||
|
||||
@ -1014,7 +1014,7 @@ class WEIValidateRegistrationView(ProtectQuerysetMixin, ProtectedCreateView):
|
||||
|
||||
def get_success_url(self):
|
||||
self.object.refresh_from_db()
|
||||
return reverse_lazy("wei:wei_detail", kwargs={"pk": self.object.club.pk})
|
||||
return reverse_lazy("wei:wei_registrations", kwargs={"pk": self.object.club.pk})
|
||||
|
||||
|
||||
class WEISurveyView(LoginRequiredMixin, BaseFormView, DetailView):
|
||||
@ -1084,7 +1084,44 @@ class WEISurveyEndView(LoginRequiredMixin, TemplateView):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["club"] = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei
|
||||
club = WEIRegistration.objects.get(pk=self.kwargs["pk"]).wei
|
||||
context["club"] = club
|
||||
|
||||
random_user = User.objects.filter(~Q(wei__wei__in=[club])).first()
|
||||
|
||||
if random_user is None:
|
||||
# This case occurs when all users are registered to the WEI.
|
||||
# Don't worry, Pikachu never went to the WEI.
|
||||
# This bug can arrive only in dev mode.
|
||||
context["can_add_first_year_member"] = True
|
||||
context["can_add_any_member"] = True
|
||||
else:
|
||||
# Check if the user has the right to create a registration of a random first year member.
|
||||
empty_fy_registration = WEIRegistration(
|
||||
wei=club,
|
||||
user=random_user,
|
||||
first_year=True,
|
||||
birth_date="1970-01-01",
|
||||
gender="No",
|
||||
emergency_contact_name="No",
|
||||
emergency_contact_phone="No",
|
||||
)
|
||||
context["can_add_first_year_member"] = PermissionBackend \
|
||||
.check_perm(self.request, "wei.add_weiregistration", empty_fy_registration)
|
||||
|
||||
# Check if the user has the right to create a registration of a random old member.
|
||||
empty_old_registration = WEIRegistration(
|
||||
wei=club,
|
||||
user=User.objects.filter(~Q(wei__wei__in=[club])).first(),
|
||||
first_year=False,
|
||||
birth_date="1970-01-01",
|
||||
gender="No",
|
||||
emergency_contact_name="No",
|
||||
emergency_contact_phone="No",
|
||||
)
|
||||
context["can_add_any_member"] = PermissionBackend \
|
||||
.check_perm(self.request, "wei.add_weiregistration", empty_old_registration)
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
@ -615,7 +615,7 @@ pas déjà fait par créer un utilisateur sur les deux serveurs :
|
||||
|
||||
.. code:: bash
|
||||
|
||||
ynerant@bde-note:~$ sudo -u postgres createuser -l ynerant
|
||||
ynerant@bde-note:~$ sudo -u postgres createuser -s ynerant
|
||||
|
||||
On réinitialise **sur le serveur de développement** la base de données présente, en
|
||||
éteignant tout d'abord le serveur Web :
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-08-31 13:25+0200\n"
|
||||
"POT-Creation-Date: 2023-10-25 19:12+0200\n"
|
||||
"PO-Revision-Date: 2020-11-16 20:02+0000\n"
|
||||
"Last-Translator: bleizi <bleizi@crans.org>\n"
|
||||
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20/de/>\n"
|
||||
@ -263,14 +263,14 @@ msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#: apps/activity/tables.py:84 apps/member/forms.py:193
|
||||
#: apps/registration/forms.py:93 apps/treasury/forms.py:131
|
||||
#: apps/registration/forms.py:92 apps/treasury/forms.py:131
|
||||
#: apps/wei/forms/registration.py:104
|
||||
msgid "Last name"
|
||||
msgstr "Nachname"
|
||||
|
||||
#: apps/activity/tables.py:86 apps/member/forms.py:198
|
||||
#: apps/note/templates/note/transaction_form.html:138
|
||||
#: apps/registration/forms.py:98 apps/treasury/forms.py:133
|
||||
#: apps/registration/forms.py:97 apps/treasury/forms.py:133
|
||||
#: apps/wei/forms/registration.py:109
|
||||
msgid "First name"
|
||||
msgstr "Vorname"
|
||||
@ -391,7 +391,7 @@ msgid "validate"
|
||||
msgstr ""
|
||||
|
||||
#: apps/activity/templates/activity/includes/activity_info.html:71
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:220
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:260
|
||||
msgid "edit"
|
||||
msgstr "bearbeiten"
|
||||
|
||||
@ -467,9 +467,9 @@ msgstr "neue Daten"
|
||||
msgid "create"
|
||||
msgstr "schaffen"
|
||||
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:166 apps/note/tables.py:190
|
||||
#: apps/note/tables.py:237 apps/permission/models.py:127
|
||||
#: apps/treasury/tables.py:38 apps/wei/tables.py:74
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277
|
||||
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
||||
#: apps/wei/tables.py:74
|
||||
msgid "delete"
|
||||
msgstr "entfernen"
|
||||
|
||||
@ -536,7 +536,8 @@ msgstr "Letzen Bericht Datum"
|
||||
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"
|
||||
"Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) Charta gelesen und "
|
||||
"angenommen"
|
||||
|
||||
#: apps/member/forms.py:53
|
||||
msgid ""
|
||||
@ -544,9 +545,9 @@ msgid ""
|
||||
"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>"
|
||||
"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."
|
||||
@ -564,8 +565,8 @@ msgstr "Maximal Größe: 2MB"
|
||||
msgid "This image cannot be loaded."
|
||||
msgstr "Dieses Bild kann nicht geladen werden."
|
||||
|
||||
#: apps/member/forms.py:148 apps/member/views.py:103
|
||||
#: apps/registration/forms.py:35 apps/registration/views.py:266
|
||||
#: apps/member/forms.py:148 apps/member/views.py:102
|
||||
#: apps/registration/forms.py:34 apps/registration/views.py:266
|
||||
msgid "An alias with a similar name already exists."
|
||||
msgstr "Ein ähnliches Alias ist schon benutzt."
|
||||
|
||||
@ -577,12 +578,12 @@ msgstr "Mitgliedschaft von der Société Générale bezahlt"
|
||||
msgid "Check this case if the Société Générale paid the inscription."
|
||||
msgstr "Die Société Générale die Mitgliedschaft bezahlt."
|
||||
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:80
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:79
|
||||
#: apps/wei/forms/registration.py:91
|
||||
msgid "Credit type"
|
||||
msgstr "Kredittype"
|
||||
|
||||
#: apps/member/forms.py:180 apps/registration/forms.py:81
|
||||
#: apps/member/forms.py:180 apps/registration/forms.py:80
|
||||
#: apps/wei/forms/registration.py:92
|
||||
msgid "No credit"
|
||||
msgstr "Kein Kredit"
|
||||
@ -591,13 +592,13 @@ msgstr "Kein Kredit"
|
||||
msgid "You can credit the note of the user."
|
||||
msgstr "Sie dûrfen diese Note kreditieren."
|
||||
|
||||
#: apps/member/forms.py:186 apps/registration/forms.py:86
|
||||
#: apps/member/forms.py:186 apps/registration/forms.py:85
|
||||
#: apps/wei/forms/registration.py:97
|
||||
msgid "Credit amount"
|
||||
msgstr "Kreditanzahl"
|
||||
|
||||
#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144
|
||||
#: apps/registration/forms.py:103 apps/treasury/forms.py:135
|
||||
#: apps/registration/forms.py:102 apps/treasury/forms.py:135
|
||||
#: apps/wei/forms/registration.py:114
|
||||
msgid "Bank"
|
||||
msgstr "Bank"
|
||||
@ -950,7 +951,7 @@ msgid "Account #"
|
||||
msgstr "Konto #"
|
||||
|
||||
#: apps/member/templates/member/base.html:48
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:60
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:59
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:48
|
||||
#: apps/wei/templates/wei/weimembership_form.html:117
|
||||
msgid "Update Profile"
|
||||
@ -1192,8 +1193,8 @@ msgstr "Click hier um eine Bestätigunglinke zu schicken."
|
||||
msgid "View my memberships"
|
||||
msgstr "Meine Mitgliedschaften schauen"
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:10 apps/member/views.py:254
|
||||
msgid "Note friendships"
|
||||
#: apps/member/templates/member/profile_trust.html:10
|
||||
msgid "Add friends"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:28
|
||||
@ -1205,6 +1206,10 @@ msgid ""
|
||||
"without needing additional rights among them."
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:39
|
||||
msgid "People having you as a friend"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/templates/member/profile_update.html:18
|
||||
msgid "Save Changes"
|
||||
msgstr "Speichern"
|
||||
@ -1213,18 +1218,22 @@ msgstr "Speichern"
|
||||
msgid "Registrations"
|
||||
msgstr "Anmeldung"
|
||||
|
||||
#: apps/member/views.py:73 apps/registration/forms.py:24
|
||||
#: apps/member/views.py:72 apps/registration/forms.py:24
|
||||
msgid "This address must be valid."
|
||||
msgstr "Diese Adresse muss gültig sein."
|
||||
|
||||
#: apps/member/views.py:140
|
||||
#: apps/member/views.py:139
|
||||
msgid "Profile detail"
|
||||
msgstr "Profile detail"
|
||||
|
||||
#: apps/member/views.py:206
|
||||
#: apps/member/views.py:205
|
||||
msgid "Search user"
|
||||
msgstr "User finden"
|
||||
|
||||
#: apps/member/views.py:253
|
||||
msgid "Note friendships"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/views.py:308
|
||||
msgid "Update note picture"
|
||||
msgstr "Notebild ändern"
|
||||
@ -1277,21 +1286,27 @@ msgstr "Rollen in diesen Club bearbeiten"
|
||||
msgid "Members of the club"
|
||||
msgstr "Mitlglieder dieses Club"
|
||||
|
||||
#: apps/note/admin.py:129 apps/note/models/transactions.py:109
|
||||
#: apps/note/admin.py:139 apps/note/models/transactions.py:109
|
||||
msgid "source"
|
||||
msgstr "Sender"
|
||||
|
||||
#: apps/note/admin.py:137 apps/note/admin.py:205
|
||||
#: apps/note/admin.py:147 apps/note/admin.py:215
|
||||
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:122
|
||||
msgid "destination"
|
||||
msgstr "Empfänger"
|
||||
|
||||
#: apps/note/admin.py:210 apps/note/models/transactions.py:60
|
||||
#: apps/note/admin.py:220 apps/note/models/transactions.py:60
|
||||
#: apps/note/models/transactions.py:140
|
||||
msgid "amount"
|
||||
msgstr "Anzahl"
|
||||
|
||||
#: apps/note/api/serializers.py:199 apps/note/api/serializers.py:205
|
||||
#: apps/note/api/serializers.py:92
|
||||
#, fuzzy
|
||||
#| msgid "This credit is already validated."
|
||||
msgid "This friendship already exists"
|
||||
msgstr "Dieser Kredit ist bereits validiert."
|
||||
|
||||
#: apps/note/api/serializers.py:198 apps/note/api/serializers.py:204
|
||||
#: apps/note/models/transactions.py:228
|
||||
msgid ""
|
||||
"The transaction can't be saved since the source note or the destination note "
|
||||
@ -1606,8 +1621,8 @@ msgstr "Klicken Sie zum gültigmachen"
|
||||
msgid "No reason specified"
|
||||
msgstr "Kein Grund gegeben"
|
||||
|
||||
#: apps/note/tables.py:173 apps/note/tables.py:194 apps/note/tables.py:239
|
||||
#: apps/treasury/tables.py:39
|
||||
#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234
|
||||
#: apps/note/tables.py:279 apps/treasury/tables.py:39
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
|
||||
#: apps/treasury/templates/treasury/sogecredit_detail.html:65
|
||||
#: apps/wei/tables.py:75 apps/wei/tables.py:118
|
||||
@ -1618,7 +1633,17 @@ msgstr "Kein Grund gegeben"
|
||||
msgid "Delete"
|
||||
msgstr "Löschen"
|
||||
|
||||
#: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
|
||||
#: apps/note/tables.py:191
|
||||
msgid "Trust back"
|
||||
msgstr ""
|
||||
|
||||
#: apps/note/tables.py:211
|
||||
#, fuzzy
|
||||
#| msgid "Add bus"
|
||||
msgid "Add back"
|
||||
msgstr "Neue Bus"
|
||||
|
||||
#: apps/note/tables.py:262 apps/note/templates/note/conso_form.html:151
|
||||
#: apps/wei/tables.py:49 apps/wei/tables.py:50
|
||||
#: apps/wei/templates/wei/base.html:89
|
||||
#: apps/wei/templates/wei/bus_detail.html:20
|
||||
@ -1628,7 +1653,7 @@ msgstr "Löschen"
|
||||
msgid "Edit"
|
||||
msgstr "Bearbeiten"
|
||||
|
||||
#: apps/note/tables.py:226 apps/note/tables.py:253
|
||||
#: apps/note/tables.py:266 apps/note/tables.py:293
|
||||
msgid "Hide/Show"
|
||||
msgstr ""
|
||||
|
||||
@ -1659,15 +1684,27 @@ msgstr "Konsumieren!"
|
||||
msgid "Highlighted buttons"
|
||||
msgstr "Hervorgehobene Tasten"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:138
|
||||
#: apps/note/templates/note/conso_form.html:108
|
||||
#, fuzzy
|
||||
#| msgid "Search WEI"
|
||||
msgid "Search"
|
||||
msgstr "WEI finden"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:133
|
||||
#, fuzzy
|
||||
#| msgid "Search button"
|
||||
msgid "Search button..."
|
||||
msgstr "Tatsen finden"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:157
|
||||
msgid "Single consumptions"
|
||||
msgstr "Solo Modus"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:143
|
||||
#: apps/note/templates/note/conso_form.html:162
|
||||
msgid "Double consumptions"
|
||||
msgstr "Doppelte Modus"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:154
|
||||
#: apps/note/templates/note/conso_form.html:173
|
||||
#: apps/note/templates/note/transaction_form.html:163
|
||||
msgid "Recent transactions history"
|
||||
msgstr "Verlauf der letzten Transaktionen"
|
||||
@ -1801,7 +1838,7 @@ msgstr "Verbräuche"
|
||||
msgid "You can't see any button."
|
||||
msgstr "Sie können keine Taste sehen."
|
||||
|
||||
#: apps/note/views.py:200
|
||||
#: apps/note/views.py:208
|
||||
msgid "Search transactions"
|
||||
msgstr "Transaktion finden"
|
||||
|
||||
@ -2002,15 +2039,15 @@ msgstr "Alle Rechten"
|
||||
msgid "registration"
|
||||
msgstr "Anmeldung"
|
||||
|
||||
#: apps/registration/forms.py:41
|
||||
#: apps/registration/forms.py:40
|
||||
msgid "This email address is already used."
|
||||
msgstr "Diese email adresse ist schon benutzt."
|
||||
|
||||
#: apps/registration/forms.py:61
|
||||
#: apps/registration/forms.py:60
|
||||
msgid "Register to the WEI"
|
||||
msgstr "Zu WEI anmelden"
|
||||
|
||||
#: apps/registration/forms.py:63
|
||||
#: apps/registration/forms.py:62
|
||||
msgid ""
|
||||
"Check this case if you want to register to the WEI. If you hesitate, you "
|
||||
"will be able to register later, after validating your account in the Kfet."
|
||||
@ -2019,15 +2056,15 @@ msgstr ""
|
||||
"falls Zweifel, können Sie sich später nach Bestätigung Ihres Kontos im Kfet "
|
||||
"registrieren."
|
||||
|
||||
#: apps/registration/forms.py:108
|
||||
#: apps/registration/forms.py:107
|
||||
msgid "Join BDE Club"
|
||||
msgstr "BDE Mitglieder werden"
|
||||
|
||||
#: apps/registration/forms.py:115
|
||||
#: apps/registration/forms.py:114
|
||||
msgid "Join Kfet Club"
|
||||
msgstr "Kfet Mitglieder werden"
|
||||
|
||||
#: apps/registration/forms.py:124
|
||||
#: apps/registration/forms.py:123
|
||||
msgid "Join BDA Club"
|
||||
msgstr "BDA Mitglieder werden"
|
||||
|
||||
@ -2377,12 +2414,12 @@ msgid "Yes"
|
||||
msgstr "Ja"
|
||||
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:10
|
||||
#: apps/treasury/views.py:180
|
||||
#: apps/treasury/views.py:173
|
||||
msgid "Delete invoice"
|
||||
msgstr "Rechnung löschen"
|
||||
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:15
|
||||
#: apps/treasury/views.py:184
|
||||
#: apps/treasury/views.py:177
|
||||
msgid "This invoice is locked and can't be deleted."
|
||||
msgstr "Eine Rechnung kann nicht gelöscht werden, wenn sie gesperrt ist."
|
||||
|
||||
@ -2568,36 +2605,36 @@ msgstr "Neue Rechnung"
|
||||
msgid "Invoices list"
|
||||
msgstr "Rechnunglist"
|
||||
|
||||
#: apps/treasury/views.py:112 apps/treasury/views.py:286
|
||||
#: apps/treasury/views.py:412
|
||||
#: apps/treasury/views.py:105 apps/treasury/views.py:275
|
||||
#: apps/treasury/views.py:401
|
||||
msgid "You are not able to see the treasury interface."
|
||||
msgstr "Sie können die Quaestor-App nicht sehen."
|
||||
|
||||
#: apps/treasury/views.py:122
|
||||
#: apps/treasury/views.py:115
|
||||
msgid "Update an invoice"
|
||||
msgstr "Rechnung bearbeiten"
|
||||
|
||||
#: apps/treasury/views.py:247
|
||||
#: apps/treasury/views.py:240
|
||||
msgid "Create a new remittance"
|
||||
msgstr "Neue Überweisung"
|
||||
|
||||
#: apps/treasury/views.py:274
|
||||
#: apps/treasury/views.py:267
|
||||
msgid "Remittances list"
|
||||
msgstr "Überweisungliste"
|
||||
|
||||
#: apps/treasury/views.py:337
|
||||
#: apps/treasury/views.py:326
|
||||
msgid "Update a remittance"
|
||||
msgstr "Überweisung bearbeiten"
|
||||
|
||||
#: apps/treasury/views.py:360
|
||||
#: apps/treasury/views.py:349
|
||||
msgid "Attach a transaction to a remittance"
|
||||
msgstr "Fügen Sie einer Überweisung eine Transaktion hinzu"
|
||||
|
||||
#: apps/treasury/views.py:404
|
||||
#: apps/treasury/views.py:393
|
||||
msgid "List of credits from the Société générale"
|
||||
msgstr "Kreditliste von Société générale"
|
||||
|
||||
#: apps/treasury/views.py:449
|
||||
#: apps/treasury/views.py:438
|
||||
msgid "Manage credits from the Société générale"
|
||||
msgstr "Krediten von der Société générale handeln"
|
||||
|
||||
@ -2651,7 +2688,6 @@ msgid "This team doesn't belong to the given bus."
|
||||
msgstr "Dieses Team gehört nicht zum angegebenen Bus."
|
||||
|
||||
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38
|
||||
#: apps/wei/forms/surveys/wei2023.py:38
|
||||
msgid "Choose a word:"
|
||||
msgstr "Wählen Sie ein Wort:"
|
||||
|
||||
@ -2952,7 +2988,7 @@ msgstr "Als PDF schauen"
|
||||
#: apps/wei/templates/wei/survey.html:11
|
||||
#: apps/wei/templates/wei/survey_closed.html:11
|
||||
#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1028
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1093
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1130
|
||||
msgid "Survey WEI"
|
||||
msgstr "WEI Umfrage"
|
||||
|
||||
@ -3230,11 +3266,11 @@ msgstr "Sie haben nicht das Recht, diese WEI-Registrierung zu löschen."
|
||||
msgid "Validate WEI registration"
|
||||
msgstr "Überprüfen Sie die WEI-Registrierung"
|
||||
|
||||
#: apps/wei/views.py:1186
|
||||
#: apps/wei/views.py:1223
|
||||
msgid "Attribute buses to first year members"
|
||||
msgstr ""
|
||||
|
||||
#: apps/wei/views.py:1211
|
||||
#: apps/wei/views.py:1248
|
||||
msgid "Attribute bus"
|
||||
msgstr ""
|
||||
|
||||
|
@ -7,11 +7,11 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-11-15 23:21+0100\n"
|
||||
"POT-Creation-Date: 2022-10-07 09:07+0200\n"
|
||||
"PO-Revision-Date: 2020-11-16 20:21+0000\n"
|
||||
"Last-Translator: Yohann D'ANELLO <ynerant@crans.org>\n"
|
||||
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/>"
|
||||
"\n"
|
||||
"Language-Team: German <http://translate.ynerant.fr/projects/nk20/nk20-js/de/"
|
||||
">\n"
|
||||
"Language: de\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
@ -27,6 +27,22 @@ msgstr "Alias erfolgreich hinzugefügt"
|
||||
msgid "Alias successfully deleted"
|
||||
msgstr "Alias erfolgreich gelöscht"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:14
|
||||
msgid "You can't add yourself as a friend"
|
||||
msgstr ""
|
||||
|
||||
#: apps/member/static/member/js/trust.js:37
|
||||
#, fuzzy
|
||||
#| msgid "Alias successfully added"
|
||||
msgid "Friendship successfully added"
|
||||
msgstr "Alias erfolgreich hinzugefügt"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:53
|
||||
#, fuzzy
|
||||
#| msgid "Alias successfully deleted"
|
||||
msgid "Friendship successfully deleted"
|
||||
msgstr "Alias erfolgreich gelöscht"
|
||||
|
||||
#: apps/note/static/note/js/consos.js:225
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
@ -46,32 +62,32 @@ msgstr ""
|
||||
"ist negativ."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:232
|
||||
#: apps/note/static/note/js/transfer.js:298
|
||||
#: apps/note/static/note/js/transfer.js:401
|
||||
#: apps/note/static/note/js/transfer.js:309
|
||||
#: apps/note/static/note/js/transfer.js:412
|
||||
#, javascript-format
|
||||
msgid "Warning, the emitter note %s is no more a BDE member."
|
||||
msgstr "Warnung, der Emittent Hinweis %s ist kein BDE-Mitglied mehr."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:253
|
||||
#: apps/note/static/note/js/consos.js:254
|
||||
msgid "The transaction couldn't be validated because of insufficient balance."
|
||||
msgstr ""
|
||||
"Die Transaktion konnte aufgrund eines unzureichenden Saldos nicht validiert "
|
||||
"werden."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:238
|
||||
#: apps/note/static/note/js/transfer.js:249
|
||||
msgid "This field is required and must contain a decimal positive number."
|
||||
msgstr ""
|
||||
"Dieses Feld ist erforderlich und muss eine positive Dezimalzahl enthalten."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:245
|
||||
#: apps/note/static/note/js/transfer.js:256
|
||||
msgid "The amount must stay under 21,474,836.47 €."
|
||||
msgstr "Der Betrag muss unter 21.474.836,47 € bleiben."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:251
|
||||
#: apps/note/static/note/js/transfer.js:262
|
||||
msgid "This field is required."
|
||||
msgstr "Dies ist ein Pflichtfeld."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:277
|
||||
#: apps/note/static/note/js/transfer.js:288
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning: the transaction of %s from %s to %s was not made because it is the "
|
||||
@ -80,12 +96,12 @@ msgstr ""
|
||||
"Warnung: Die Transaktion von %s von %s nach %s wurde nicht durchgeführt, da "
|
||||
"es sich um die gleiche Quell- und Zielnotiz handelt."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:301
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#, javascript-format
|
||||
msgid "Warning, the destination note %s is no more a BDE member."
|
||||
msgstr "Warnung, der Bestimmungsvermerk %s ist kein BDE-Mitglied mehr."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:307
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@ -94,7 +110,7 @@ msgstr ""
|
||||
"Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber "
|
||||
"die Emitternote %s ist sehr negativ."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#: apps/note/static/note/js/transfer.js:323
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@ -103,31 +119,32 @@ msgstr ""
|
||||
"Warnung, die Transaktion von %s von der Note %s zur Note %s gelingt, aber "
|
||||
"die Emitternote %s ist negativ."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#: apps/note/static/note/js/transfer.js:329
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s succeed!"
|
||||
msgstr "Übertragung von %s von %s auf %s gelingt!"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:325
|
||||
#: apps/note/static/note/js/transfer.js:346
|
||||
#: apps/note/static/note/js/transfer.js:353
|
||||
#: apps/note/static/note/js/transfer.js:336
|
||||
#: apps/note/static/note/js/transfer.js:357
|
||||
#: apps/note/static/note/js/transfer.js:364
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s failed: %s"
|
||||
msgstr "Übertragung von %s von %s auf %s fehlgeschlagen: %s"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:347
|
||||
#: apps/note/static/note/js/transfer.js:358
|
||||
msgid "insufficient funds"
|
||||
msgstr "unzureichende Geldmittel"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:400
|
||||
#: apps/note/static/note/js/transfer.js:411
|
||||
msgid "Credit/debit succeed!"
|
||||
msgstr "Kredit/Debit erfolgreich!"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:407
|
||||
#: apps/note/static/note/js/transfer.js:418
|
||||
#, javascript-format
|
||||
msgid "Credit/debit failed: %s"
|
||||
msgstr "Kredit/Debit fehlgeschlagen: %s"
|
||||
|
||||
#: note_kfet/static/js/base.js:366
|
||||
#: note_kfet/static/js/base.js:370
|
||||
msgid "An error occured while (in)validating this transaction:"
|
||||
msgstr "Bei der (Un-)Validierung dieser Transaktion ist ein Fehler aufgetreten:"
|
||||
msgstr ""
|
||||
"Bei der (Un-)Validierung dieser Transaktion ist ein Fehler aufgetreten:"
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-08-31 13:25+0200\n"
|
||||
"POT-Creation-Date: 2023-10-25 19:12+0200\n"
|
||||
"PO-Revision-Date: 2022-04-11 23:12+0200\n"
|
||||
"Last-Translator: bleizi <bleizi@crans.org>\n"
|
||||
"Language-Team: \n"
|
||||
@ -16,7 +16,7 @@ msgstr ""
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"X-Generator: Poedit 3.0\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
|
||||
#: apps/activity/apps.py:10 apps/activity/models.py:151
|
||||
#: apps/activity/models.py:167
|
||||
@ -262,14 +262,14 @@ msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
#: apps/activity/tables.py:84 apps/member/forms.py:193
|
||||
#: apps/registration/forms.py:93 apps/treasury/forms.py:131
|
||||
#: apps/registration/forms.py:92 apps/treasury/forms.py:131
|
||||
#: apps/wei/forms/registration.py:104
|
||||
msgid "Last name"
|
||||
msgstr "Apellido"
|
||||
|
||||
#: apps/activity/tables.py:86 apps/member/forms.py:198
|
||||
#: apps/note/templates/note/transaction_form.html:138
|
||||
#: apps/registration/forms.py:98 apps/treasury/forms.py:133
|
||||
#: apps/registration/forms.py:97 apps/treasury/forms.py:133
|
||||
#: apps/wei/forms/registration.py:109
|
||||
msgid "First name"
|
||||
msgstr "Nombre"
|
||||
@ -386,7 +386,7 @@ msgid "validate"
|
||||
msgstr "validar"
|
||||
|
||||
#: apps/activity/templates/activity/includes/activity_info.html:71
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:220
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:260
|
||||
msgid "edit"
|
||||
msgstr "modificar"
|
||||
|
||||
@ -464,9 +464,9 @@ msgstr "nuevos datos"
|
||||
msgid "create"
|
||||
msgstr "crear"
|
||||
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:166 apps/note/tables.py:190
|
||||
#: apps/note/tables.py:237 apps/permission/models.py:127
|
||||
#: apps/treasury/tables.py:38 apps/wei/tables.py:74
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277
|
||||
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
||||
#: apps/wei/tables.py:74
|
||||
msgid "delete"
|
||||
msgstr "suprimir"
|
||||
|
||||
@ -541,9 +541,9 @@ msgid ""
|
||||
"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>"
|
||||
"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."
|
||||
@ -561,8 +561,8 @@ msgstr "Tamaño máximo : 2Mo"
|
||||
msgid "This image cannot be loaded."
|
||||
msgstr "Esta imagen no puede ser cargada."
|
||||
|
||||
#: apps/member/forms.py:148 apps/member/views.py:103
|
||||
#: apps/registration/forms.py:35 apps/registration/views.py:266
|
||||
#: apps/member/forms.py:148 apps/member/views.py:102
|
||||
#: apps/registration/forms.py:34 apps/registration/views.py:266
|
||||
msgid "An alias with a similar name already exists."
|
||||
msgstr "Un alias similar ya existe."
|
||||
|
||||
@ -574,12 +574,12 @@ msgstr "Registración pagadas por Société Générale"
|
||||
msgid "Check this case if the Société Générale paid the inscription."
|
||||
msgstr "Marcar esta casilla si Société Générale pagó la registración."
|
||||
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:80
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:79
|
||||
#: apps/wei/forms/registration.py:91
|
||||
msgid "Credit type"
|
||||
msgstr "Tipo de crédito"
|
||||
|
||||
#: apps/member/forms.py:180 apps/registration/forms.py:81
|
||||
#: apps/member/forms.py:180 apps/registration/forms.py:80
|
||||
#: apps/wei/forms/registration.py:92
|
||||
msgid "No credit"
|
||||
msgstr "No crédito"
|
||||
@ -588,13 +588,13 @@ msgstr "No crédito"
|
||||
msgid "You can credit the note of the user."
|
||||
msgstr "Usted puede acreditar la note del usuario."
|
||||
|
||||
#: apps/member/forms.py:186 apps/registration/forms.py:86
|
||||
#: apps/member/forms.py:186 apps/registration/forms.py:85
|
||||
#: apps/wei/forms/registration.py:97
|
||||
msgid "Credit amount"
|
||||
msgstr "Valor del crédito"
|
||||
|
||||
#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144
|
||||
#: apps/registration/forms.py:103 apps/treasury/forms.py:135
|
||||
#: apps/registration/forms.py:102 apps/treasury/forms.py:135
|
||||
#: apps/wei/forms/registration.py:114
|
||||
msgid "Bank"
|
||||
msgstr "Banco"
|
||||
@ -941,7 +941,7 @@ msgid "Account #"
|
||||
msgstr "Cuenta n°"
|
||||
|
||||
#: apps/member/templates/member/base.html:48
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:60
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:59
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:48
|
||||
#: apps/wei/templates/wei/weimembership_form.html:117
|
||||
msgid "Update Profile"
|
||||
@ -1175,9 +1175,9 @@ msgstr "Hacer clic aquí para reenviar un enlace de validación."
|
||||
msgid "View my memberships"
|
||||
msgstr "Ver mis afiliaciones"
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:10 apps/member/views.py:254
|
||||
msgid "Note friendships"
|
||||
msgstr "Amistades de note"
|
||||
#: apps/member/templates/member/profile_trust.html:10
|
||||
msgid "Add friends"
|
||||
msgstr "Añadir amig@s"
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:28
|
||||
msgid ""
|
||||
@ -1192,6 +1192,10 @@ msgstr ""
|
||||
"simplificar el reembolso entre amig@s por Note Kfet. Pues una persona puede "
|
||||
"crear todas la transacciones sin tener derechos particulares."
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:39
|
||||
msgid "People having you as a friend"
|
||||
msgstr "Personas que tienen usted como amig@"
|
||||
|
||||
#: apps/member/templates/member/profile_update.html:18
|
||||
msgid "Save Changes"
|
||||
msgstr "Guardar cambios"
|
||||
@ -1200,18 +1204,22 @@ msgstr "Guardar cambios"
|
||||
msgid "Registrations"
|
||||
msgstr "Registraciones"
|
||||
|
||||
#: apps/member/views.py:73 apps/registration/forms.py:24
|
||||
#: apps/member/views.py:72 apps/registration/forms.py:24
|
||||
msgid "This address must be valid."
|
||||
msgstr "Este correo tiene que ser valido."
|
||||
|
||||
#: apps/member/views.py:140
|
||||
#: apps/member/views.py:139
|
||||
msgid "Profile detail"
|
||||
msgstr "Detalles del usuario"
|
||||
|
||||
#: apps/member/views.py:206
|
||||
#: apps/member/views.py:205
|
||||
msgid "Search user"
|
||||
msgstr "Buscar un usuario"
|
||||
|
||||
#: apps/member/views.py:253
|
||||
msgid "Note friendships"
|
||||
msgstr "Amistades de note"
|
||||
|
||||
#: apps/member/views.py:308
|
||||
msgid "Update note picture"
|
||||
msgstr "Modificar la imagen de la note"
|
||||
@ -1264,21 +1272,27 @@ msgstr "Gestionar los papeles de un usuario en el club"
|
||||
msgid "Members of the club"
|
||||
msgstr "Miembros del club"
|
||||
|
||||
#: apps/note/admin.py:129 apps/note/models/transactions.py:109
|
||||
#: apps/note/admin.py:139 apps/note/models/transactions.py:109
|
||||
msgid "source"
|
||||
msgstr "fuente"
|
||||
|
||||
#: apps/note/admin.py:137 apps/note/admin.py:205
|
||||
#: apps/note/admin.py:147 apps/note/admin.py:215
|
||||
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:122
|
||||
msgid "destination"
|
||||
msgstr "destino"
|
||||
|
||||
#: apps/note/admin.py:210 apps/note/models/transactions.py:60
|
||||
#: apps/note/admin.py:220 apps/note/models/transactions.py:60
|
||||
#: apps/note/models/transactions.py:140
|
||||
msgid "amount"
|
||||
msgstr "monto"
|
||||
|
||||
#: apps/note/api/serializers.py:199 apps/note/api/serializers.py:205
|
||||
#: apps/note/api/serializers.py:92
|
||||
#, fuzzy
|
||||
#| msgid "This credit is already validated."
|
||||
msgid "This friendship already exists"
|
||||
msgstr "Este crédito ya fue validado."
|
||||
|
||||
#: apps/note/api/serializers.py:198 apps/note/api/serializers.py:204
|
||||
#: apps/note/models/transactions.py:228
|
||||
msgid ""
|
||||
"The transaction can't be saved since the source note or the destination note "
|
||||
@ -1593,8 +1607,8 @@ msgstr "Hacer clic para validar"
|
||||
msgid "No reason specified"
|
||||
msgstr "Ningún motivo dado"
|
||||
|
||||
#: apps/note/tables.py:173 apps/note/tables.py:194 apps/note/tables.py:239
|
||||
#: apps/treasury/tables.py:39
|
||||
#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234
|
||||
#: apps/note/tables.py:279 apps/treasury/tables.py:39
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
|
||||
#: apps/treasury/templates/treasury/sogecredit_detail.html:65
|
||||
#: apps/wei/tables.py:75 apps/wei/tables.py:118
|
||||
@ -1605,7 +1619,15 @@ msgstr "Ningún motivo dado"
|
||||
msgid "Delete"
|
||||
msgstr "Suprimir"
|
||||
|
||||
#: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
|
||||
#: apps/note/tables.py:191
|
||||
msgid "Trust back"
|
||||
msgstr "Añadir como amig@"
|
||||
|
||||
#: apps/note/tables.py:211
|
||||
msgid "Add back"
|
||||
msgstr "Añadir en retorno"
|
||||
|
||||
#: apps/note/tables.py:262 apps/note/templates/note/conso_form.html:151
|
||||
#: apps/wei/tables.py:49 apps/wei/tables.py:50
|
||||
#: apps/wei/templates/wei/base.html:89
|
||||
#: apps/wei/templates/wei/bus_detail.html:20
|
||||
@ -1615,7 +1637,7 @@ msgstr "Suprimir"
|
||||
msgid "Edit"
|
||||
msgstr "Editar"
|
||||
|
||||
#: apps/note/tables.py:226 apps/note/tables.py:253
|
||||
#: apps/note/tables.py:266 apps/note/tables.py:293
|
||||
msgid "Hide/Show"
|
||||
msgstr "Ocultar/Mostrar"
|
||||
|
||||
@ -1646,15 +1668,27 @@ msgstr "¡ Consumir !"
|
||||
msgid "Highlighted buttons"
|
||||
msgstr "Botones resaltados"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:138
|
||||
#: apps/note/templates/note/conso_form.html:108
|
||||
#, fuzzy
|
||||
#| msgid "Search WEI"
|
||||
msgid "Search"
|
||||
msgstr "Buscar un WEI"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:133
|
||||
#, fuzzy
|
||||
#| msgid "Search button"
|
||||
msgid "Search button..."
|
||||
msgstr "Buscar un botón"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:157
|
||||
msgid "Single consumptions"
|
||||
msgstr "Consumiciones simples"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:143
|
||||
#: apps/note/templates/note/conso_form.html:162
|
||||
msgid "Double consumptions"
|
||||
msgstr "Consumiciones dobles"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:154
|
||||
#: apps/note/templates/note/conso_form.html:173
|
||||
#: apps/note/templates/note/transaction_form.html:163
|
||||
msgid "Recent transactions history"
|
||||
msgstr "Historial de las transacciones recientes"
|
||||
@ -1784,7 +1818,7 @@ msgstr "Consumiciones"
|
||||
msgid "You can't see any button."
|
||||
msgstr "Usted no puede ver ningún botón."
|
||||
|
||||
#: apps/note/views.py:200
|
||||
#: apps/note/views.py:208
|
||||
msgid "Search transactions"
|
||||
msgstr "Buscar transacciones"
|
||||
|
||||
@ -1983,15 +2017,15 @@ msgstr "Todos los permisos"
|
||||
msgid "registration"
|
||||
msgstr "afiliación"
|
||||
|
||||
#: apps/registration/forms.py:41
|
||||
#: apps/registration/forms.py:40
|
||||
msgid "This email address is already used."
|
||||
msgstr "Este correo electrónico ya esta utilizado."
|
||||
|
||||
#: apps/registration/forms.py:61
|
||||
#: apps/registration/forms.py:60
|
||||
msgid "Register to the WEI"
|
||||
msgstr "Registrarse en el WEI"
|
||||
|
||||
#: apps/registration/forms.py:63
|
||||
#: apps/registration/forms.py:62
|
||||
msgid ""
|
||||
"Check this case if you want to register to the WEI. If you hesitate, you "
|
||||
"will be able to register later, after validating your account in the Kfet."
|
||||
@ -1999,15 +2033,15 @@ msgstr ""
|
||||
"Marcar esta casilla si usted quiere registrarse en el WEI. Si duda, podrá "
|
||||
"registrarse más tarde, después de validar su cuenta Note Kfet."
|
||||
|
||||
#: apps/registration/forms.py:108
|
||||
#: apps/registration/forms.py:107
|
||||
msgid "Join BDE Club"
|
||||
msgstr "Afiliarse al club BDE"
|
||||
|
||||
#: apps/registration/forms.py:115
|
||||
#: apps/registration/forms.py:114
|
||||
msgid "Join Kfet Club"
|
||||
msgstr "Afiliarse al club Kfet"
|
||||
|
||||
#: apps/registration/forms.py:124
|
||||
#: apps/registration/forms.py:123
|
||||
msgid "Join BDA Club"
|
||||
msgstr "Afiliarse al club BDA"
|
||||
|
||||
@ -2356,12 +2390,12 @@ msgid "Yes"
|
||||
msgstr "Sí"
|
||||
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:10
|
||||
#: apps/treasury/views.py:180
|
||||
#: apps/treasury/views.py:173
|
||||
msgid "Delete invoice"
|
||||
msgstr "Suprimir la factura"
|
||||
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:15
|
||||
#: apps/treasury/views.py:184
|
||||
#: apps/treasury/views.py:177
|
||||
msgid "This invoice is locked and can't be deleted."
|
||||
msgstr "Esta factura esta bloqueada y no puede ser suprimida."
|
||||
|
||||
@ -2537,36 +2571,36 @@ msgstr "Crear una nueva factura"
|
||||
msgid "Invoices list"
|
||||
msgstr "Lista de las facturas"
|
||||
|
||||
#: apps/treasury/views.py:112 apps/treasury/views.py:286
|
||||
#: apps/treasury/views.py:412
|
||||
#: apps/treasury/views.py:105 apps/treasury/views.py:275
|
||||
#: apps/treasury/views.py:401
|
||||
msgid "You are not able to see the treasury interface."
|
||||
msgstr "Usted no tiene derecho a ver la interfaz de tesorería."
|
||||
|
||||
#: apps/treasury/views.py:122
|
||||
#: apps/treasury/views.py:115
|
||||
msgid "Update an invoice"
|
||||
msgstr "Modificar una factura"
|
||||
|
||||
#: apps/treasury/views.py:247
|
||||
#: apps/treasury/views.py:240
|
||||
msgid "Create a new remittance"
|
||||
msgstr "Crear un nuevo descuento"
|
||||
|
||||
#: apps/treasury/views.py:274
|
||||
#: apps/treasury/views.py:267
|
||||
msgid "Remittances list"
|
||||
msgstr "Lista de los descuentos"
|
||||
|
||||
#: apps/treasury/views.py:337
|
||||
#: apps/treasury/views.py:326
|
||||
msgid "Update a remittance"
|
||||
msgstr "Modificar un descuento"
|
||||
|
||||
#: apps/treasury/views.py:360
|
||||
#: apps/treasury/views.py:349
|
||||
msgid "Attach a transaction to a remittance"
|
||||
msgstr "Unir una transacción con un descuento"
|
||||
|
||||
#: apps/treasury/views.py:404
|
||||
#: apps/treasury/views.py:393
|
||||
msgid "List of credits from the Société générale"
|
||||
msgstr "Lista de los créditos de la Société Générale"
|
||||
|
||||
#: apps/treasury/views.py:449
|
||||
#: apps/treasury/views.py:438
|
||||
msgid "Manage credits from the Société générale"
|
||||
msgstr "Gestionar los créditos de la Société Générale"
|
||||
|
||||
@ -2621,7 +2655,6 @@ msgid "This team doesn't belong to the given bus."
|
||||
msgstr "Este equipo no pertenece al bus dado."
|
||||
|
||||
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38
|
||||
#: apps/wei/forms/surveys/wei2023.py:38
|
||||
msgid "Choose a word:"
|
||||
msgstr "Elegir una palabra :"
|
||||
|
||||
@ -2908,7 +2941,7 @@ msgstr "Descargar un PDF"
|
||||
#: apps/wei/templates/wei/survey.html:11
|
||||
#: apps/wei/templates/wei/survey_closed.html:11
|
||||
#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1028
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1093
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1130
|
||||
msgid "Survey WEI"
|
||||
msgstr "Cuestionario WEI"
|
||||
|
||||
@ -3179,11 +3212,11 @@ msgstr "Usted no tiene derecho a suprimir esta inscripción WEI."
|
||||
msgid "Validate WEI registration"
|
||||
msgstr "Validar la inscripción WEI"
|
||||
|
||||
#: apps/wei/views.py:1186
|
||||
#: apps/wei/views.py:1223
|
||||
msgid "Attribute buses to first year members"
|
||||
msgstr "Repartir los primer años en los buses"
|
||||
|
||||
#: apps/wei/views.py:1211
|
||||
#: apps/wei/views.py:1248
|
||||
msgid "Attribute bus"
|
||||
msgstr "Repartir en un bus"
|
||||
|
||||
@ -3558,6 +3591,11 @@ msgstr ""
|
||||
"pagar su afiliación. Tambien tiene que validar su correo electronico con el "
|
||||
"enlace que recibió."
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "People having you as a friend"
|
||||
#~ msgid "You already have that person as a friend"
|
||||
#~ msgstr "Personas que tienen usted como amig@"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "I declare that I opened or I will open soon a bank account in the Société "
|
||||
#~ "générale with the BDE partnership."
|
||||
|
@ -7,16 +7,16 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-11-15 23:21+0100\n"
|
||||
"PO-Revision-Date: 2020-11-21 12:23+0100\n"
|
||||
"POT-Creation-Date: 2022-10-07 09:07+0200\n"
|
||||
"PO-Revision-Date: 2022-10-07 13:20+0200\n"
|
||||
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
||||
"Language-Team: \n"
|
||||
"Language: es\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
"Last-Translator: elkmaennchen <elkmaennchen@crans.org>\n"
|
||||
"Language-Team: \n"
|
||||
"X-Generator: Poedit 2.3\n"
|
||||
"X-Generator: Poedit 3.0.1\n"
|
||||
|
||||
#: apps/member/static/member/js/alias.js:17
|
||||
msgid "Alias successfully added"
|
||||
@ -26,6 +26,18 @@ msgstr "Alias añadido con éxito"
|
||||
msgid "Alias successfully deleted"
|
||||
msgstr "Alias suprimido con éxito"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:14
|
||||
msgid "You can't add yourself as a friend"
|
||||
msgstr "No puede añadir asimismo como amig@"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:37
|
||||
msgid "Friendship successfully added"
|
||||
msgstr "Amig@ añadido con éxito"
|
||||
|
||||
#: apps/member/static/member/js/trust.js:53
|
||||
msgid "Friendship successfully deleted"
|
||||
msgstr "Amig@ suprimido con éxito"
|
||||
|
||||
#: apps/note/static/note/js/consos.js:225
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
@ -44,30 +56,29 @@ msgstr ""
|
||||
"Cuidado, la transacción de %s fue un éxito, pero la note %s está negativa."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:232
|
||||
#: apps/note/static/note/js/transfer.js:298
|
||||
#: apps/note/static/note/js/transfer.js:401
|
||||
#: apps/note/static/note/js/transfer.js:309
|
||||
#: apps/note/static/note/js/transfer.js:412
|
||||
#, javascript-format
|
||||
msgid "Warning, the emitter note %s is no more a BDE member."
|
||||
msgstr "Cuidado, la note remitente %s no está más miembro del BDE."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:253
|
||||
#: apps/note/static/note/js/consos.js:254
|
||||
msgid "The transaction couldn't be validated because of insufficient balance."
|
||||
msgstr ""
|
||||
"La transacción no pudo ser validada por culpa de saldo demasiado bajo."
|
||||
msgstr "La transacción no pudo ser validada por culpa de saldo demasiado bajo."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:238
|
||||
#: apps/note/static/note/js/transfer.js:249
|
||||
msgid "This field is required and must contain a decimal positive number."
|
||||
msgstr "Este campo obligatorio requiere un número decimal positivo."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:245
|
||||
#: apps/note/static/note/js/transfer.js:256
|
||||
msgid "The amount must stay under 21,474,836.47 €."
|
||||
msgstr "El monto no puede superar los 21 474 836,47 €."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:251
|
||||
#: apps/note/static/note/js/transfer.js:262
|
||||
msgid "This field is required."
|
||||
msgstr "Este campo es obligatorio."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:277
|
||||
#: apps/note/static/note/js/transfer.js:288
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning: the transaction of %s from %s to %s was not made because it is the "
|
||||
@ -76,12 +87,12 @@ msgstr ""
|
||||
"Cuidado : la transacción de %s de %s a %s no fue echa porque la fuente y el "
|
||||
"destino son iguales."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:301
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#, javascript-format
|
||||
msgid "Warning, the destination note %s is no more a BDE member."
|
||||
msgstr "Cuidado, la note destino %s no está más miembro del BDE."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:307
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@ -90,7 +101,7 @@ msgstr ""
|
||||
"Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero "
|
||||
"la note fuente %s está muy negativa."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#: apps/note/static/note/js/transfer.js:323
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@ -99,31 +110,31 @@ msgstr ""
|
||||
"Cuidado, la transacción de %s de la note %s a la note %s fue un éxito, pero "
|
||||
"la note fuente %s está negativa."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#: apps/note/static/note/js/transfer.js:329
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s succeed!"
|
||||
msgstr "¡ La transacción de %s de %s a %s fue un éxito !"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:325
|
||||
#: apps/note/static/note/js/transfer.js:346
|
||||
#: apps/note/static/note/js/transfer.js:353
|
||||
#: apps/note/static/note/js/transfer.js:336
|
||||
#: apps/note/static/note/js/transfer.js:357
|
||||
#: apps/note/static/note/js/transfer.js:364
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s failed: %s"
|
||||
msgstr "La transacción de %s de %s a %s fue un fracaso : %s"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:347
|
||||
#: apps/note/static/note/js/transfer.js:358
|
||||
msgid "insufficient funds"
|
||||
msgstr "fundos insuficientes"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:400
|
||||
#: apps/note/static/note/js/transfer.js:411
|
||||
msgid "Credit/debit succeed!"
|
||||
msgstr "¡ Crédito/débito tubo éxito !"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:407
|
||||
#: apps/note/static/note/js/transfer.js:418
|
||||
#, javascript-format
|
||||
msgid "Credit/debit failed: %s"
|
||||
msgstr "Crédito/débito falló : %s"
|
||||
|
||||
#: note_kfet/static/js/base.js:366
|
||||
#: note_kfet/static/js/base.js:370
|
||||
msgid "An error occured while (in)validating this transaction:"
|
||||
msgstr "Un error ocurrió durante la (in)validación de esta transacción :"
|
||||
|
@ -7,7 +7,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2023-08-31 13:25+0200\n"
|
||||
"POT-Creation-Date: 2023-10-25 19:12+0200\n"
|
||||
"PO-Revision-Date: 2022-04-11 22:05+0200\n"
|
||||
"Last-Translator: bleizi <bleizi@crans.org>\n"
|
||||
"Language-Team: French <http://translate.ynerant.fr/projects/nk20/nk20/fr/>\n"
|
||||
@ -263,14 +263,14 @@ msgid "Type"
|
||||
msgstr "Type"
|
||||
|
||||
#: apps/activity/tables.py:84 apps/member/forms.py:193
|
||||
#: apps/registration/forms.py:93 apps/treasury/forms.py:131
|
||||
#: apps/registration/forms.py:92 apps/treasury/forms.py:131
|
||||
#: apps/wei/forms/registration.py:104
|
||||
msgid "Last name"
|
||||
msgstr "Nom de famille"
|
||||
|
||||
#: apps/activity/tables.py:86 apps/member/forms.py:198
|
||||
#: apps/note/templates/note/transaction_form.html:138
|
||||
#: apps/registration/forms.py:98 apps/treasury/forms.py:133
|
||||
#: apps/registration/forms.py:97 apps/treasury/forms.py:133
|
||||
#: apps/wei/forms/registration.py:109
|
||||
msgid "First name"
|
||||
msgstr "Prénom"
|
||||
@ -388,7 +388,7 @@ msgid "validate"
|
||||
msgstr "valider"
|
||||
|
||||
#: apps/activity/templates/activity/includes/activity_info.html:71
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:220
|
||||
#: apps/logs/models.py:64 apps/note/tables.py:260
|
||||
msgid "edit"
|
||||
msgstr "modifier"
|
||||
|
||||
@ -466,9 +466,9 @@ msgstr "nouvelles données"
|
||||
msgid "create"
|
||||
msgstr "créer"
|
||||
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:166 apps/note/tables.py:190
|
||||
#: apps/note/tables.py:237 apps/permission/models.py:127
|
||||
#: apps/treasury/tables.py:38 apps/wei/tables.py:74
|
||||
#: apps/logs/models.py:65 apps/note/tables.py:230 apps/note/tables.py:277
|
||||
#: apps/permission/models.py:127 apps/treasury/tables.py:38
|
||||
#: apps/wei/tables.py:74
|
||||
msgid "delete"
|
||||
msgstr "supprimer"
|
||||
|
||||
@ -534,8 +534,7 @@ msgstr "Date de dernier rapport"
|
||||
#: apps/member/forms.py:52
|
||||
msgid ""
|
||||
"Anti-VSS (<em>Violences Sexistes et Sexuelles</em>) charter read and approved"
|
||||
msgstr ""
|
||||
"Charte Anti-VSS (Violences Sexistes et Sexuelles) lue et approuvée"
|
||||
msgstr "Charte Anti-VSS (Violences Sexistes et Sexuelles) lue et approuvée"
|
||||
|
||||
#: apps/member/forms.py:53
|
||||
msgid ""
|
||||
@ -543,9 +542,9 @@ msgid ""
|
||||
"href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> "
|
||||
"available here in pdf</a>"
|
||||
msgstr ""
|
||||
"Cochez après avoir lu la chartre anti-VSS <a "
|
||||
"href=https://perso.crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> "
|
||||
"disponible en pdf ici</a>"
|
||||
"Cochez après avoir lu la chartre anti-VSS <a href=https://perso."
|
||||
"crans.org/club-bde/Charte-anti-VSS.pdf target=_blank> disponible en pdf ici</"
|
||||
"a>"
|
||||
|
||||
#: apps/member/forms.py:60
|
||||
msgid "You can't register to the note if you come from the future."
|
||||
@ -563,8 +562,8 @@ msgstr "Taille maximale : 2 Mo"
|
||||
msgid "This image cannot be loaded."
|
||||
msgstr "Cette image ne peut pas être chargée."
|
||||
|
||||
#: apps/member/forms.py:148 apps/member/views.py:103
|
||||
#: apps/registration/forms.py:35 apps/registration/views.py:266
|
||||
#: apps/member/forms.py:148 apps/member/views.py:102
|
||||
#: apps/registration/forms.py:34 apps/registration/views.py:266
|
||||
msgid "An alias with a similar name already exists."
|
||||
msgstr "Un alias avec un nom similaire existe déjà."
|
||||
|
||||
@ -576,12 +575,12 @@ msgstr "Inscription payée par la Société générale"
|
||||
msgid "Check this case if the Société Générale paid the inscription."
|
||||
msgstr "Cochez cette case si la Société Générale a payé l'inscription."
|
||||
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:80
|
||||
#: apps/member/forms.py:179 apps/registration/forms.py:79
|
||||
#: apps/wei/forms/registration.py:91
|
||||
msgid "Credit type"
|
||||
msgstr "Type de rechargement"
|
||||
|
||||
#: apps/member/forms.py:180 apps/registration/forms.py:81
|
||||
#: apps/member/forms.py:180 apps/registration/forms.py:80
|
||||
#: apps/wei/forms/registration.py:92
|
||||
msgid "No credit"
|
||||
msgstr "Pas de rechargement"
|
||||
@ -590,13 +589,13 @@ msgstr "Pas de rechargement"
|
||||
msgid "You can credit the note of the user."
|
||||
msgstr "Vous pouvez créditer la note de l'utilisateur avant l'adhésion."
|
||||
|
||||
#: apps/member/forms.py:186 apps/registration/forms.py:86
|
||||
#: apps/member/forms.py:186 apps/registration/forms.py:85
|
||||
#: apps/wei/forms/registration.py:97
|
||||
msgid "Credit amount"
|
||||
msgstr "Montant à créditer"
|
||||
|
||||
#: apps/member/forms.py:203 apps/note/templates/note/transaction_form.html:144
|
||||
#: apps/registration/forms.py:103 apps/treasury/forms.py:135
|
||||
#: apps/registration/forms.py:102 apps/treasury/forms.py:135
|
||||
#: apps/wei/forms/registration.py:114
|
||||
msgid "Bank"
|
||||
msgstr "Banque"
|
||||
@ -944,7 +943,7 @@ msgid "Account #"
|
||||
msgstr "Compte n°"
|
||||
|
||||
#: apps/member/templates/member/base.html:48
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:60
|
||||
#: apps/member/templates/member/base.html:62 apps/member/views.py:59
|
||||
#: apps/registration/templates/registration/future_profile_detail.html:48
|
||||
#: apps/wei/templates/wei/weimembership_form.html:117
|
||||
msgid "Update Profile"
|
||||
@ -1178,9 +1177,9 @@ msgstr "Cliquez ici pour renvoyer un lien de validation."
|
||||
msgid "View my memberships"
|
||||
msgstr "Voir mes adhésions"
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:10 apps/member/views.py:254
|
||||
msgid "Note friendships"
|
||||
msgstr "Amitiés note"
|
||||
#: apps/member/templates/member/profile_trust.html:10
|
||||
msgid "Add friends"
|
||||
msgstr "Ajouter des amis"
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:28
|
||||
msgid ""
|
||||
@ -1195,6 +1194,10 @@ msgstr ""
|
||||
"ami⋅es via note. En effet, une personne peut effectuer tous les transferts "
|
||||
"sans posséder de droits supplémentaires."
|
||||
|
||||
#: apps/member/templates/member/profile_trust.html:39
|
||||
msgid "People having you as a friend"
|
||||
msgstr "Personnes vous ayant ajouté"
|
||||
|
||||
#: apps/member/templates/member/profile_update.html:18
|
||||
msgid "Save Changes"
|
||||
msgstr "Sauvegarder les changements"
|
||||
@ -1203,18 +1206,22 @@ msgstr "Sauvegarder les changements"
|
||||
msgid "Registrations"
|
||||
msgstr "Inscriptions"
|
||||
|
||||
#: apps/member/views.py:73 apps/registration/forms.py:24
|
||||
#: apps/member/views.py:72 apps/registration/forms.py:24
|
||||
msgid "This address must be valid."
|
||||
msgstr "Cette adresse doit être valide."
|
||||
|
||||
#: apps/member/views.py:140
|
||||
#: apps/member/views.py:139
|
||||
msgid "Profile detail"
|
||||
msgstr "Détails de l'utilisateur"
|
||||
|
||||
#: apps/member/views.py:206
|
||||
#: apps/member/views.py:205
|
||||
msgid "Search user"
|
||||
msgstr "Chercher un utilisateur"
|
||||
|
||||
#: apps/member/views.py:253
|
||||
msgid "Note friendships"
|
||||
msgstr "Amitiés note"
|
||||
|
||||
#: apps/member/views.py:308
|
||||
msgid "Update note picture"
|
||||
msgstr "Modifier la photo de la note"
|
||||
@ -1267,21 +1274,25 @@ msgstr "Gérer les rôles d'un utilisateur dans le club"
|
||||
msgid "Members of the club"
|
||||
msgstr "Membres du club"
|
||||
|
||||
#: apps/note/admin.py:129 apps/note/models/transactions.py:109
|
||||
#: apps/note/admin.py:139 apps/note/models/transactions.py:109
|
||||
msgid "source"
|
||||
msgstr "source"
|
||||
|
||||
#: apps/note/admin.py:137 apps/note/admin.py:205
|
||||
#: apps/note/admin.py:147 apps/note/admin.py:215
|
||||
#: apps/note/models/transactions.py:56 apps/note/models/transactions.py:122
|
||||
msgid "destination"
|
||||
msgstr "destination"
|
||||
|
||||
#: apps/note/admin.py:210 apps/note/models/transactions.py:60
|
||||
#: apps/note/admin.py:220 apps/note/models/transactions.py:60
|
||||
#: apps/note/models/transactions.py:140
|
||||
msgid "amount"
|
||||
msgstr "montant"
|
||||
|
||||
#: apps/note/api/serializers.py:199 apps/note/api/serializers.py:205
|
||||
#: apps/note/api/serializers.py:92
|
||||
msgid "This friendship already exists"
|
||||
msgstr "Cette amitié existe déjà"
|
||||
|
||||
#: apps/note/api/serializers.py:198 apps/note/api/serializers.py:204
|
||||
#: apps/note/models/transactions.py:228
|
||||
msgid ""
|
||||
"The transaction can't be saved since the source note or the destination note "
|
||||
@ -1599,8 +1610,8 @@ msgstr "Cliquez pour valider"
|
||||
msgid "No reason specified"
|
||||
msgstr "Pas de motif spécifié"
|
||||
|
||||
#: apps/note/tables.py:173 apps/note/tables.py:194 apps/note/tables.py:239
|
||||
#: apps/treasury/tables.py:39
|
||||
#: apps/note/tables.py:166 apps/note/tables.py:173 apps/note/tables.py:234
|
||||
#: apps/note/tables.py:279 apps/treasury/tables.py:39
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:30
|
||||
#: apps/treasury/templates/treasury/sogecredit_detail.html:65
|
||||
#: apps/wei/tables.py:75 apps/wei/tables.py:118
|
||||
@ -1611,7 +1622,15 @@ msgstr "Pas de motif spécifié"
|
||||
msgid "Delete"
|
||||
msgstr "Supprimer"
|
||||
|
||||
#: apps/note/tables.py:222 apps/note/templates/note/conso_form.html:132
|
||||
#: apps/note/tables.py:191
|
||||
msgid "Trust back"
|
||||
msgstr "Ajouter en ami"
|
||||
|
||||
#: apps/note/tables.py:211
|
||||
msgid "Add back"
|
||||
msgstr "Ajouter"
|
||||
|
||||
#: apps/note/tables.py:262 apps/note/templates/note/conso_form.html:151
|
||||
#: apps/wei/tables.py:49 apps/wei/tables.py:50
|
||||
#: apps/wei/templates/wei/base.html:89
|
||||
#: apps/wei/templates/wei/bus_detail.html:20
|
||||
@ -1621,7 +1640,7 @@ msgstr "Supprimer"
|
||||
msgid "Edit"
|
||||
msgstr "Éditer"
|
||||
|
||||
#: apps/note/tables.py:226 apps/note/tables.py:253
|
||||
#: apps/note/tables.py:266 apps/note/tables.py:293
|
||||
msgid "Hide/Show"
|
||||
msgstr "Afficher/Masquer"
|
||||
|
||||
@ -1652,15 +1671,23 @@ msgstr "Consommer !"
|
||||
msgid "Highlighted buttons"
|
||||
msgstr "Boutons mis en avant"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:138
|
||||
#: apps/note/templates/note/conso_form.html:108
|
||||
msgid "Search"
|
||||
msgstr "Recherche"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:133
|
||||
msgid "Search button..."
|
||||
msgstr "Chercher un bouton..."
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:157
|
||||
msgid "Single consumptions"
|
||||
msgstr "Consommations simples"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:143
|
||||
#: apps/note/templates/note/conso_form.html:162
|
||||
msgid "Double consumptions"
|
||||
msgstr "Consommations doubles"
|
||||
|
||||
#: apps/note/templates/note/conso_form.html:154
|
||||
#: apps/note/templates/note/conso_form.html:173
|
||||
#: apps/note/templates/note/transaction_form.html:163
|
||||
msgid "Recent transactions history"
|
||||
msgstr "Historique des transactions récentes"
|
||||
@ -1790,7 +1817,7 @@ msgstr "Consommations"
|
||||
msgid "You can't see any button."
|
||||
msgstr "Vous ne pouvez pas voir le moindre bouton."
|
||||
|
||||
#: apps/note/views.py:200
|
||||
#: apps/note/views.py:208
|
||||
msgid "Search transactions"
|
||||
msgstr "Rechercher des transactions"
|
||||
|
||||
@ -1992,15 +2019,15 @@ msgstr "Tous les droits"
|
||||
msgid "registration"
|
||||
msgstr "inscription"
|
||||
|
||||
#: apps/registration/forms.py:41
|
||||
#: apps/registration/forms.py:40
|
||||
msgid "This email address is already used."
|
||||
msgstr "Cet email est déjà pris."
|
||||
|
||||
#: apps/registration/forms.py:61
|
||||
#: apps/registration/forms.py:60
|
||||
msgid "Register to the WEI"
|
||||
msgstr "S'inscrire au WEI"
|
||||
|
||||
#: apps/registration/forms.py:63
|
||||
#: apps/registration/forms.py:62
|
||||
msgid ""
|
||||
"Check this case if you want to register to the WEI. If you hesitate, you "
|
||||
"will be able to register later, after validating your account in the Kfet."
|
||||
@ -2009,15 +2036,15 @@ msgstr ""
|
||||
"pourrez toujours vous inscrire plus tard, après avoir validé votre compte à "
|
||||
"la Kfet."
|
||||
|
||||
#: apps/registration/forms.py:108
|
||||
#: apps/registration/forms.py:107
|
||||
msgid "Join BDE Club"
|
||||
msgstr "Adhérer au club BDE"
|
||||
|
||||
#: apps/registration/forms.py:115
|
||||
#: apps/registration/forms.py:114
|
||||
msgid "Join Kfet Club"
|
||||
msgstr "Adhérer au club Kfet"
|
||||
|
||||
#: apps/registration/forms.py:124
|
||||
#: apps/registration/forms.py:123
|
||||
msgid "Join BDA Club"
|
||||
msgstr "Adhérer au club BDA"
|
||||
|
||||
@ -2363,12 +2390,12 @@ msgid "Yes"
|
||||
msgstr "Oui"
|
||||
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:10
|
||||
#: apps/treasury/views.py:180
|
||||
#: apps/treasury/views.py:173
|
||||
msgid "Delete invoice"
|
||||
msgstr "Supprimer la facture"
|
||||
|
||||
#: apps/treasury/templates/treasury/invoice_confirm_delete.html:15
|
||||
#: apps/treasury/views.py:184
|
||||
#: apps/treasury/views.py:177
|
||||
msgid "This invoice is locked and can't be deleted."
|
||||
msgstr "Cette facture est verrouillée et ne peut pas être supprimée."
|
||||
|
||||
@ -2548,36 +2575,36 @@ msgstr "Créer une nouvelle facture"
|
||||
msgid "Invoices list"
|
||||
msgstr "Liste des factures"
|
||||
|
||||
#: apps/treasury/views.py:112 apps/treasury/views.py:286
|
||||
#: apps/treasury/views.py:412
|
||||
#: apps/treasury/views.py:105 apps/treasury/views.py:275
|
||||
#: apps/treasury/views.py:401
|
||||
msgid "You are not able to see the treasury interface."
|
||||
msgstr "Vous n'êtes pas autorisé à voir l'interface de trésorerie."
|
||||
|
||||
#: apps/treasury/views.py:122
|
||||
#: apps/treasury/views.py:115
|
||||
msgid "Update an invoice"
|
||||
msgstr "Modifier la facture"
|
||||
|
||||
#: apps/treasury/views.py:247
|
||||
#: apps/treasury/views.py:240
|
||||
msgid "Create a new remittance"
|
||||
msgstr "Créer une nouvelle remise"
|
||||
|
||||
#: apps/treasury/views.py:274
|
||||
#: apps/treasury/views.py:267
|
||||
msgid "Remittances list"
|
||||
msgstr "Liste des remises"
|
||||
|
||||
#: apps/treasury/views.py:337
|
||||
#: apps/treasury/views.py:326
|
||||
msgid "Update a remittance"
|
||||
msgstr "Modifier la remise"
|
||||
|
||||
#: apps/treasury/views.py:360
|
||||
#: apps/treasury/views.py:349
|
||||
msgid "Attach a transaction to a remittance"
|
||||
msgstr "Joindre une transaction à une remise"
|
||||
|
||||
#: apps/treasury/views.py:404
|
||||
#: apps/treasury/views.py:393
|
||||
msgid "List of credits from the Société générale"
|
||||
msgstr "Liste des crédits de la Société générale"
|
||||
|
||||
#: apps/treasury/views.py:449
|
||||
#: apps/treasury/views.py:438
|
||||
msgid "Manage credits from the Société générale"
|
||||
msgstr "Gérer les crédits de la Société générale"
|
||||
|
||||
@ -2633,7 +2660,6 @@ msgid "This team doesn't belong to the given bus."
|
||||
msgstr "Cette équipe n'appartient pas à ce bus."
|
||||
|
||||
#: apps/wei/forms/surveys/wei2021.py:35 apps/wei/forms/surveys/wei2022.py:38
|
||||
#: apps/wei/forms/surveys/wei2023.py:38
|
||||
msgid "Choose a word:"
|
||||
msgstr "Choisissez un mot :"
|
||||
|
||||
@ -2922,7 +2948,7 @@ msgstr "Télécharger au format PDF"
|
||||
#: apps/wei/templates/wei/survey.html:11
|
||||
#: apps/wei/templates/wei/survey_closed.html:11
|
||||
#: apps/wei/templates/wei/survey_end.html:11 apps/wei/views.py:1028
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1093
|
||||
#: apps/wei/views.py:1083 apps/wei/views.py:1130
|
||||
msgid "Survey WEI"
|
||||
msgstr "Questionnaire WEI"
|
||||
|
||||
@ -3196,11 +3222,11 @@ msgstr "Vous n'avez pas la permission de supprimer cette inscription au WEI."
|
||||
msgid "Validate WEI registration"
|
||||
msgstr "Valider l'inscription WEI"
|
||||
|
||||
#: apps/wei/views.py:1186
|
||||
#: apps/wei/views.py:1223
|
||||
msgid "Attribute buses to first year members"
|
||||
msgstr "Répartir les 1A dans les bus"
|
||||
|
||||
#: apps/wei/views.py:1211
|
||||
#: apps/wei/views.py:1248
|
||||
msgid "Attribute bus"
|
||||
msgstr "Attribuer un bus"
|
||||
|
||||
@ -3586,6 +3612,11 @@ msgstr ""
|
||||
"d'adhésion. Vous devez également valider votre adresse email en suivant le "
|
||||
"lien que vous avez reçu."
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "People having you as a friend"
|
||||
#~ msgid "You already have that person as a friend"
|
||||
#~ msgstr "Personnes vous ayant ajouté"
|
||||
|
||||
#~ 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."
|
||||
|
@ -3,12 +3,11 @@
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2020-11-15 23:21+0100\n"
|
||||
"POT-Creation-Date: 2022-10-07 09:07+0200\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@ -26,6 +25,18 @@ msgstr "Alias ajouté avec succès"
|
||||
msgid "Alias successfully deleted"
|
||||
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
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
@ -45,31 +56,31 @@ msgstr ""
|
||||
"la note émettrice %s est en négatif."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:232
|
||||
#: apps/note/static/note/js/transfer.js:298
|
||||
#: apps/note/static/note/js/transfer.js:401
|
||||
#: apps/note/static/note/js/transfer.js:309
|
||||
#: apps/note/static/note/js/transfer.js:412
|
||||
#, javascript-format
|
||||
msgid "Warning, the emitter note %s is no more a BDE member."
|
||||
msgstr "Attention, la note émettrice %s n'est plus adhérente."
|
||||
|
||||
#: apps/note/static/note/js/consos.js:253
|
||||
#: apps/note/static/note/js/consos.js:254
|
||||
msgid "The transaction couldn't be validated because of insufficient balance."
|
||||
msgstr ""
|
||||
"La transaction n'a pas pu être validée pour cause de solde insuffisant."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:238
|
||||
#: apps/note/static/note/js/transfer.js:249
|
||||
msgid "This field is required and must contain a decimal positive number."
|
||||
msgstr ""
|
||||
"Ce champ est requis et doit comporter un nombre décimal strictement positif."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:245
|
||||
#: apps/note/static/note/js/transfer.js:256
|
||||
msgid "The amount must stay under 21,474,836.47 €."
|
||||
msgstr "Le montant ne doit pas excéder 21 474 836.47 €."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:251
|
||||
#: apps/note/static/note/js/transfer.js:262
|
||||
msgid "This field is required."
|
||||
msgstr "Ce champ est requis."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:277
|
||||
#: apps/note/static/note/js/transfer.js:288
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning: the transaction of %s from %s to %s was not made because it is the "
|
||||
@ -78,12 +89,12 @@ msgstr ""
|
||||
"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."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:301
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#, javascript-format
|
||||
msgid "Warning, the destination note %s is no more a BDE member."
|
||||
msgstr "Attention, la note de destination %s n'est plus adhérente."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:307
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@ -92,7 +103,7 @@ msgstr ""
|
||||
"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."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:312
|
||||
#: apps/note/static/note/js/transfer.js:323
|
||||
#, javascript-format
|
||||
msgid ""
|
||||
"Warning, the transaction of %s from the note %s to the note %s succeed, but "
|
||||
@ -101,33 +112,33 @@ msgstr ""
|
||||
"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."
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:318
|
||||
#: apps/note/static/note/js/transfer.js:329
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s succeed!"
|
||||
msgstr ""
|
||||
"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:325
|
||||
#: apps/note/static/note/js/transfer.js:346
|
||||
#: apps/note/static/note/js/transfer.js:353
|
||||
#: apps/note/static/note/js/transfer.js:336
|
||||
#: apps/note/static/note/js/transfer.js:357
|
||||
#: apps/note/static/note/js/transfer.js:364
|
||||
#, javascript-format
|
||||
msgid "Transfer of %s from %s to %s failed: %s"
|
||||
msgstr "Le transfert de %s de la note %s vers la note %s a échoué : %s"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:347
|
||||
#: apps/note/static/note/js/transfer.js:358
|
||||
msgid "insufficient funds"
|
||||
msgstr "solde insuffisant"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:400
|
||||
#: apps/note/static/note/js/transfer.js:411
|
||||
msgid "Credit/debit succeed!"
|
||||
msgstr "Le crédit/retrait a bien été effectué !"
|
||||
|
||||
#: apps/note/static/note/js/transfer.js:407
|
||||
#: apps/note/static/note/js/transfer.js:418
|
||||
#, javascript-format
|
||||
msgid "Credit/debit failed: %s"
|
||||
msgstr "Le crédit/retrait a échoué : %s"
|
||||
|
||||
#: note_kfet/static/js/base.js:366
|
||||
#: note_kfet/static/js/base.js:370
|
||||
msgid "An error occured while (in)validating this transaction:"
|
||||
msgstr ""
|
||||
"Une erreur est survenue lors de la validation/dévalidation de cette "
|
||||
|
@ -25,8 +25,8 @@ admin_site.register(Site, SiteAdmin)
|
||||
|
||||
# Add external apps model
|
||||
if "oauth2_provider" in settings.INSTALLED_APPS:
|
||||
from oauth2_provider.admin import Application, ApplicationAdmin, Grant, \
|
||||
GrantAdmin, AccessToken, AccessTokenAdmin, RefreshToken, RefreshTokenAdmin
|
||||
from oauth2_provider.admin import ApplicationAdmin, GrantAdmin, AccessTokenAdmin, RefreshTokenAdmin
|
||||
from oauth2_provider.models import Application, Grant, AccessToken, RefreshToken
|
||||
admin_site.register(Application, ApplicationAdmin)
|
||||
admin_site.register(Grant, GrantAdmin)
|
||||
admin_site.register(AccessToken, AccessTokenAdmin)
|
||||
|
@ -41,7 +41,7 @@ INSTALLED_APPS = [
|
||||
'bootstrap_datepicker_plus',
|
||||
'colorfield',
|
||||
'crispy_forms',
|
||||
'django_htcpcp_tea',
|
||||
# 'django_htcpcp_tea',
|
||||
'django_tables2',
|
||||
'mailer',
|
||||
'phonenumber_field',
|
||||
@ -90,12 +90,14 @@ MIDDLEWARE = [
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.contrib.sites.middleware.CurrentSiteMiddleware',
|
||||
'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware',
|
||||
'note_kfet.middlewares.SessionMiddleware',
|
||||
'note_kfet.middlewares.LoginByIPMiddleware',
|
||||
'note_kfet.middlewares.TurbolinksMiddleware',
|
||||
'note_kfet.middlewares.ClacksMiddleware',
|
||||
]
|
||||
if "django_htcpcp_tea" in INSTALLED_APPS:
|
||||
MIDDLEWARE.append('django_htcpcp_tea.middleware.HTCPCPTeaMiddleware')
|
||||
|
||||
|
||||
ROOT_URLCONF = 'note_kfet.urls'
|
||||
|
||||
@ -263,6 +265,9 @@ OAUTH2_PROVIDER = {
|
||||
'REFRESH_TOKEN_EXPIRE_SECONDS': timedelta(days=14),
|
||||
}
|
||||
|
||||
# PKCE (fix a breaking change of django-oauth-toolkit 2.0.0)
|
||||
PKCE_REQUIRED = False
|
||||
|
||||
# Take control on how widget templates are sourced
|
||||
# See https://docs.djangoproject.com/en/2.2/ref/forms/renderers/#templatessetting
|
||||
FORM_RENDERER = 'django.forms.renderers.TemplatesSetting'
|
||||
|
@ -1,3 +1,5 @@
|
||||
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 () {
|
||||
$('.autocomplete').keyup(function (e) {
|
||||
const target = $('#' + e.target.id)
|
||||
@ -10,7 +12,6 @@ $(document).ready(function () {
|
||||
const input = target.val()
|
||||
target.addClass('is-invalid')
|
||||
target.removeClass('is-valid')
|
||||
$('#' + prefix + '_reset').removeClass('d-none')
|
||||
|
||||
$.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">'
|
||||
@ -41,11 +42,14 @@ $(document).ready(function () {
|
||||
|
||||
if (typeof autocompleted !== 'undefined') { autocompleted(obj, prefix) }
|
||||
})
|
||||
|
||||
if (input === obj[name_field]) { $('#' + prefix + '_pk').val(obj.id) }
|
||||
})
|
||||
|
||||
if (objects.results.length === 1 && e.originalEvent.keyCode >= 32) {
|
||||
if (objects.results.length >= 2) {
|
||||
$('#' + 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')
|
||||
}
|
||||
})
|
||||
@ -55,7 +59,6 @@ $(document).ready(function () {
|
||||
const name = $(this).attr('id').replace('_reset', '')
|
||||
$('#' + name + '_pk').val('')
|
||||
$('#' + name).val('')
|
||||
$('#' + name + '_list').html('')
|
||||
$(this).addClass('d-none')
|
||||
$('#' + name).tooltip('hide')
|
||||
})
|
||||
})
|
||||
|
@ -8,10 +8,10 @@ SPDX-License-Identifier: GPL-3.0-or-later
|
||||
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
|
||||
name="{{ widget.name }}_name" autocomplete="off"
|
||||
{% for name, value in widget.attrs.items %}
|
||||
{% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
|
||||
{% if value is not False %}{{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}
|
||||
{% endfor %}
|
||||
aria-describedby="{{widget.attrs.id}}_tooltip">
|
||||
{% if widget.resetable %}
|
||||
<a id="{{ widget.attrs.id }}_reset" class="btn btn-light autocomplete-reset{% if not widget.value %} d-none{% endif %}">{% trans "Reset" %}</a>
|
||||
<a id="{{ widget.attrs.id }}_reset" class="btn btn-light autocomplete-reset">{% trans "Reset" %}</a>
|
||||
{% endif %}
|
||||
|
||||
|
@ -30,9 +30,6 @@ urlpatterns = [
|
||||
path('accounts/', include('django.contrib.auth.urls')),
|
||||
path('api/', include('api.urls')),
|
||||
path('permission/', include('permission.urls')),
|
||||
|
||||
# Make coffee
|
||||
path('coffee/', include('django_htcpcp_tea.urls')),
|
||||
]
|
||||
|
||||
# During development, serve static and media files
|
||||
@ -57,6 +54,11 @@ if "debug_toolbar" in settings.INSTALLED_APPS:
|
||||
path('__debug__/', include(debug_toolbar.urls)),
|
||||
] + urlpatterns
|
||||
|
||||
if "django_htcpcp_tea" in settings.INSTALLED_APPS:
|
||||
# Make coffee
|
||||
urlpatterns.append(
|
||||
path('coffee/', include('django_htcpcp_tea.urls'))
|
||||
)
|
||||
|
||||
handler400 = bad_request
|
||||
handler403 = permission_denied
|
||||
|
@ -1,19 +1,19 @@
|
||||
beautifulsoup4~=4.7.1
|
||||
Django~=2.2.15
|
||||
django-bootstrap-datepicker-plus~=3.0.5
|
||||
django-cas-server~=1.2.0
|
||||
django-colorfield~=0.3.2
|
||||
django-crispy-forms~=1.7.2
|
||||
django-extensions>=2.1.4
|
||||
django-filter~=2.1
|
||||
django-htcpcp-tea~=0.3.1
|
||||
django-mailer~=2.0.1
|
||||
django-oauth-toolkit~=1.3.3
|
||||
django-phonenumber-field~=5.0.0
|
||||
django-polymorphic>=2.0.3,<3.0.0
|
||||
djangorestframework>=3.9.0,<3.13.0
|
||||
django-rest-polymorphic~=0.1.9
|
||||
django-tables2~=2.3.1
|
||||
python-memcached~=1.59
|
||||
phonenumbers~=8.9.10
|
||||
Pillow>=5.4.1
|
||||
beautifulsoup4~=4.12.3
|
||||
Django~=4.2.9
|
||||
django-bootstrap-datepicker-plus~=5.0.5
|
||||
#django-cas-server~=2.0.0
|
||||
django-colorfield~=0.11.0
|
||||
django-crispy-forms~=2.1.0
|
||||
django-extensions>=3.2.3
|
||||
django-filter~=23.5
|
||||
#django-htcpcp-tea~=0.8.1
|
||||
django-mailer~=2.3.1
|
||||
django-oauth-toolkit~=2.3.0
|
||||
django-phonenumber-field~=7.3.0
|
||||
django-polymorphic~=3.1.0
|
||||
djangorestframework~=3.14.0
|
||||
django-rest-polymorphic~=0.1.10
|
||||
django-tables2~=2.7.0
|
||||
python-memcached~=1.62
|
||||
phonenumbers~=8.13.28
|
||||
Pillow>=10.2.0
|
||||
|
Reference in New Issue
Block a user