1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-24 03:08:47 +02:00

Compare commits

..

8 Commits

Author SHA1 Message Date
b2d8ccf72b Merge branch 'qrcode' into 'main'
Draft: Qrcode

See merge request bde/nk20!196
2024-03-24 22:50:36 +01:00
e6f3084588 Added a first pass for automatically entering an activity with a qrcode 2023-10-11 18:01:51 +02:00
145e55da75 remove useless comment 2022-03-22 15:06:04 +01:00
d3ba95cdca Insecable space for more clarity 2022-03-22 15:04:41 +01:00
8ffb0ebb56 Use DetailView 2022-03-22 14:59:01 +01:00
5038af9e34 Final html template 2022-03-22 14:58:26 +01:00
819b4214c9 Add QRCode View, URL and test template 2022-03-22 12:26:44 +01:00
b8a93b0b75 Add link to QR code 2022-03-19 16:25:15 +01:00
39 changed files with 1020 additions and 1054 deletions

View File

@ -7,10 +7,25 @@ stages:
variables: variables:
GIT_SUBMODULE_STRATEGY: recursive GIT_SUBMODULE_STRATEGY: recursive
# Ubuntu 22.04 # Debian Buster
py310-django50: py37-django22:
stage: test stage: test
image: ubuntu:22.04 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
image: ubuntu:20.04
before_script: before_script:
# Fix tzdata prompt # Fix tzdata prompt
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone - ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
@ -22,12 +37,12 @@ py310-django50:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py310-django50 script: tox -e py38-django22
# Debian Bookworm # Debian Bullseye
py311-django50: py39-django22:
stage: test stage: test
image: debian:bookworm image: debian:bullseye
before_script: before_script:
- > - >
apt-get update && apt-get update &&
@ -37,11 +52,11 @@ py311-django50:
python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil python3-djangorestframework python3-django-oauth-toolkit python3-psycopg2 python3-pil
python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache python3-babel python3-lockfile python3-pip python3-phonenumbers python3-memcache
python3-bs4 python3-setuptools tox texlive-xetex python3-bs4 python3-setuptools tox texlive-xetex
script: tox -e py311-django50 script: tox -e py39-django22
linters: linters:
stage: quality-assurance stage: quality-assurance
image: debian:bullseye image: debian:buster-backports
before_script: before_script:
- apt-get update && apt-get install -y tox - apt-get update && apt-get install -y tox
script: tox -e linters script: tox -e linters

View File

@ -1,8 +1,6 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from bootstrap_datepicker_plus.widgets import DateTimePickerInput
from datetime import timedelta from datetime import timedelta
from random import shuffle from random import shuffle
@ -12,7 +10,7 @@ from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from member.models import Club from member.models import Club
from note.models import Note, NoteUser from note.models import Note, NoteUser
from note_kfet.inputs import Autocomplete from note_kfet.inputs import Autocomplete, DateTimePickerInput
from note_kfet.middlewares import get_current_request from note_kfet.middlewares import get_current_request
from permission.backends import PermissionBackend from permission.backends import PermissionBackend

View File

@ -38,6 +38,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
</a> </a>
<input id="alias" type="text" class="form-control" placeholder="Nom/note ..."> <input id="alias" type="text" class="form-control" placeholder="Nom/note ...">
<button id="trigger" class="btn btn-secondary">Click me !</button>
<hr> <hr>
@ -63,15 +64,46 @@ SPDX-License-Identifier: GPL-3.0-or-later
refreshBalance(); refreshBalance();
} }
function process_qrcode() {
let name = alias_obj.val();
$.get("/api/note/note?search=" + name + "&format=json").done(
function (res) {
let note = res.results[0];
$.post("/api/activity/entry/?format=json", {
csrfmiddlewaretoken: CSRF_TOKEN,
activity: {{ activity.id }},
note: note.id,
guest: null
}).done(function () {
addMsg(interpolate(gettext(
"Entry made for %s whose balance is %s €"),
[note.name, note.balance / 100]), "success", 4000);
reloadTable(true);
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);
});
}).fail(function (xhr) {
errMsg(xhr.responseJSON, 4000);
});
}
alias_obj.keyup(function(event) { alias_obj.keyup(function(event) {
let code = event.originalEvent.keyCode let code = event.originalEvent.keyCode
if (65 <= code <= 122 || code === 13) { if (65 <= code <= 122 || code === 13) {
debounce(reloadTable)() debounce(reloadTable)()
} }
if (code === 0)
process_qrcode();
}); });
$(document).ready(init); $(document).ready(init);
alias_obj2 = document.getElementById("alias");
$("#trigger").click(function (e) {
addMsg("Clicked", "success", 1000);
alias_obj.val(alias_obj.val() + "\0");
alias_obj2.dispatchEvent(new KeyboardEvent('keyup'));
})
function init() { function init() {
$(".table-row").click(function (e) { $(".table-row").click(function (e) {
let target = e.target.parentElement; let target = e.target.parentElement;

View File

@ -18,26 +18,3 @@ SPDX-License-Identifier: GPL-3.0-or-later
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
{% block extrajavascript %}
<script>
var date_end = document.getElementById("id_date_end");
var date_start = document.getElementById("id_date_start");
function update_date_end (){
if(date_end.value=="" || date_end.value<date_start.value){
date_end.value = date_start.value;
};
};
function update_date_start (){
if(date_start.value=="" || date_end.value<date_start.value){
date_start.value = date_end.value;
};
};
date_start.addEventListener('focusout', update_date_end);
date_end.addEventListener('focusout', update_date_start);
</script>
{% endblock %}

View File

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings from django.conf import settings
from django.urls import include, re_path from django.conf.urls import url, include
from rest_framework import routers from rest_framework import routers
from .views import UserInformationView from .views import UserInformationView
@ -47,7 +47,7 @@ app_name = 'api'
# Wire up our API using automatic URL routing. # Wire up our API using automatic URL routing.
# Additionally, we include login URLs for the browsable API. # Additionally, we include login URLs for the browsable API.
urlpatterns = [ urlpatterns = [
re_path('^', include(router.urls)), url('^', include(router.urls)),
re_path('^me/', UserInformationView.as_view()), url('^me/', UserInformationView.as_view()),
re_path('^api-auth/', include('rest_framework.urls', namespace='rest_framework')), url('^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
] ]

View File

@ -3,7 +3,6 @@
import io import io
from bootstrap_datepicker_plus.widgets import DatePickerInput
from PIL import Image, ImageSequence from PIL import Image, ImageSequence
from django import forms from django import forms
from django.conf import settings from django.conf import settings
@ -14,7 +13,7 @@ from django.forms import CheckboxSelectMultiple
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import Autocomplete, AmountInput from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
from permission.models import PermissionMask, Role from permission.models import PermissionMask, Role
from .models import Profile, Club, Membership from .models import Profile, Club, Membership
@ -33,7 +32,7 @@ class UserForm(forms.ModelForm):
# Django usernames can only contain letters, numbers, @, ., +, - and _. # Django usernames can only contain letters, numbers, @, ., +, - and _.
# We want to allow users to have uncommon and unpractical usernames: # We want to allow users to have uncommon and unpractical usernames:
# That is their problem, and we have normalized aliases for us. # That is their problem, and we have normalized aliases for us.
return super()._get_validation_exclusions() | {"username"} return super()._get_validation_exclusions() + ["username"]
class Meta: class Meta:
model = User model = User

View File

@ -60,7 +60,10 @@
{% if user_object.pk == user.pk %} {% if user_object.pk == user.pk %}
<div class="text-center"> <div class="text-center">
<a class="small badge badge-secondary" href="{% url 'member:auth_token' %}"> <a class="small badge badge-secondary" href="{% url 'member:auth_token' %}">
<i class="fa fa-cogs"></i>{% trans 'API token' %} <i class="fa fa-cogs"></i>&nbsp;{% trans 'API token' %}
</a>
<a class="small badge badge-secondary" href="{% url 'member:qr_code' user_object.pk %}">
<i class="fa fa-qrcode"></i>&nbsp;{% trans 'QR Code' %}
</a> </a>
</div> </div>
{% endif %} {% endif %}

View File

@ -0,0 +1,36 @@
{% extends "base.html" %}
{% comment %}
SPDX-License-Identifier: GPL-3.0-or-later
{% endcomment %}
{% load i18n %}
{% block content %}
<div class="card bg-light">
<h3 class="card-header text-center">
{% trans "QR Code for" %} {{ user_object.username }} ({{ user_object.first_name }} {{user_object.last_name }})
</h3>
<div class="text-center" id="qrcode">
</div>
</div>
{% endblock %}
{% block extrajavascript %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js" integrity="sha512-CNgIRecGo7nphbeZ04Sc13ka07paqdeTu0WR1IM4kNcpmBAUSHSQX0FslNhTDadL4O5SAGapGt4FodqL8My0mA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
var qrc = new QRCode(document.getElementById("qrcode"), {
text: "{{ user_object.pk }}\0",
width: 1024,
height: 1024
});
</script>
{% endblock %}
{% block extracss %}
<style>
img {
width: 100%
}
</style>
{% endblock %}

View File

@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase):
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
def test_logout(self): def test_logout(self):
response = self.client.post(reverse("logout")) response = self.client.get(reverse("logout"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_admin_index(self): def test_admin_index(self):

View File

@ -25,4 +25,5 @@ urlpatterns = [
path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"), path('user/<int:pk>/aliases/', views.ProfileAliasView.as_view(), name="user_alias"),
path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"), path('user/<int:pk>/trust', views.ProfileTrustView.as_view(), name="user_trust"),
path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'), path('manage-auth-token/', views.ManageAuthTokens.as_view(), name='auth_token'),
path('user/<int:pk>/qr_code/', views.QRCodeView.as_view(), name='qr_code'),
] ]

View File

@ -26,7 +26,7 @@ from permission.backends import PermissionBackend
from permission.models import Role from permission.models import Role
from permission.views import ProtectQuerysetMixin, ProtectedCreateView from permission.views import ProtectQuerysetMixin, ProtectedCreateView
from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm, \ from .forms import UserForm, ProfileForm, ImageForm, ClubForm, MembershipForm,\
CustomAuthenticationForm, MembershipRolesForm CustomAuthenticationForm, MembershipRolesForm
from .models import Club, Membership from .models import Club, Membership
from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable from .tables import ClubTable, UserTable, MembershipTable, ClubManagerTable
@ -365,6 +365,14 @@ class ManageAuthTokens(LoginRequiredMixin, TemplateView):
context['token'] = Token.objects.get_or_create(user=self.request.user)[0] context['token'] = Token.objects.get_or_create(user=self.request.user)[0]
return context return context
class QRCodeView(LoginRequiredMixin, DetailView):
"""
Affiche le QR Code
"""
model = User
context_object_name = "user_object"
template_name = "member/qr_code.html"
extra_context = {"title": _("QR Code")}
# ******************************* # # ******************************* #
# CLUB # # CLUB #

View File

@ -13,7 +13,7 @@ def register_note_urls(router, path):
router.register(path + '/note', NotePolymorphicViewSet) router.register(path + '/note', NotePolymorphicViewSet)
router.register(path + '/alias', AliasViewSet) router.register(path + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet) router.register(path + '/trust', TrustViewSet)
router.register(path + '/consumer', ConsumerViewSet, basename="consumer") router.register(path + '/consumer', ConsumerViewSet)
router.register(path + '/transaction/category', TemplateCategoryViewSet) router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet) router.register(path + '/transaction/transaction', TransactionViewSet)

View File

@ -13,7 +13,7 @@ from rest_framework import status
from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet, ReadOnlyProtectedModelViewSet
from permission.backends import PermissionBackend from permission.backends import PermissionBackend
from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer, \ from .serializers import NotePolymorphicSerializer, AliasSerializer, ConsumerSerializer,\
TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \ TemplateCategorySerializer, TransactionTemplateSerializer, TransactionPolymorphicSerializer, \
TrustSerializer TrustSerializer
from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust from ..models.notes import Note, Alias, NoteUser, NoteClub, NoteSpecial, Trust
@ -179,10 +179,19 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
# We match first an alias if it is matched without normalization, # We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias. # then if the normalized pattern matches a normalized alias.
queryset = queryset.filter( queryset = queryset.filter(
Q(**{f'name{suffix}': alias_prefix + alias}) **{f'name{suffix}': alias_prefix + alias}
| Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)}) ).union(
| Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()}) queryset.filter(
) Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
& ~Q(**{f'name{suffix}': alias_prefix + alias})
),
all=True).union(
queryset.filter(
Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
& ~Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
& ~Q(**{f'name{suffix}': alias_prefix + alias})
),
all=True)
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \ queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
else queryset.order_by("name") else queryset.order_by("name")

View File

@ -1,15 +1,13 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from datetime import datetime from datetime import datetime
from bootstrap_datepicker_plus.widgets import DateTimePickerInput
from django import forms from django import forms
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.utils.timezone import make_aware from django.utils.timezone import make_aware
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note_kfet.inputs import Autocomplete, AmountInput from note_kfet.inputs import Autocomplete, AmountInput, DateTimePickerInput
from .models import TransactionTemplate, NoteClub, Alias from .models import TransactionTemplate, NoteClub, Alias

View File

@ -18,7 +18,6 @@ def create_special_notes(apps, schema_editor):
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('note', '0001_initial'), ('note', '0001_initial'),
('logs', '0001_initial'),
] ]
operations = [ operations = [

View File

@ -1,25 +0,0 @@
# Generated by Django 5.0.7 on 2024-07-11 09:24
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('contenttypes', '0002_remove_content_type_name'),
('note', '0006_trust'),
]
operations = [
migrations.AlterField(
model_name='note',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
migrations.AlterField(
model_name='transaction',
name='polymorphic_ctype',
field=models.ForeignKey(editable=False, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='polymorphic_%(app_label)s.%(class)s_set+', to='contenttypes.contenttype'),
),
]

View File

@ -9,7 +9,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
name="{{ widget.name }}" name="{{ widget.name }}"
{# Other attributes are loaded #} {# Other attributes are loaded #}
{% for name, value in widget.attrs.items %} {% for name, value in widget.attrs.items %}
{% if value != False %}{{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %} {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %}> {% endfor %}>
<div class="input-group-append"> <div class="input-group-append">
<span class="input-group-text"></span> <span class="input-group-text"></span>

View File

@ -10,7 +10,7 @@ from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from permission.models import Role from permission.models import Role
from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet, \ from ..api.views import AliasViewSet, ConsumerViewSet, NotePolymorphicViewSet, TemplateCategoryViewSet,\
TransactionTemplateViewSet, TransactionViewSet TransactionTemplateViewSet, TransactionViewSet
from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \ from ..models import NoteUser, Transaction, TemplateCategory, TransactionTemplate, RecurrentTransaction, \
MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note MembershipTransaction, SpecialTransaction, NoteSpecial, Alias, Note

View File

@ -5,7 +5,7 @@ from django import forms
from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
# from member.models import Club from member.models import Club
from note.models import NoteSpecial, Alias from note.models import NoteSpecial, Alias
from note_kfet.inputs import AmountInput from note_kfet.inputs import AmountInput
@ -116,11 +116,11 @@ class ValidationForm(forms.Form):
initial=True, initial=True,
) )
# If the bda exists # If the bda exists
# if Club.objects.filter(name__iexact="bda").exists(): if Club.objects.filter(name__iexact="bda").exists():
# The user can join the bda club at the inscription # The user can join the bda club at the inscription
# join_bda = forms.BooleanField( join_bda = forms.BooleanField(
# label=_("Join BDA Club"), label=_("Join BDA Club"),
# required=False, required=False,
# initial=True, initial=True,
# ) )

View File

@ -5,7 +5,7 @@ from django_filters.rest_framework import DjangoFilterBackend
from rest_framework.filters import SearchFilter from rest_framework.filters import SearchFilter
from api.viewsets import ReadProtectedModelViewSet from api.viewsets import ReadProtectedModelViewSet
from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer, \ from .serializers import InvoiceSerializer, ProductSerializer, RemittanceTypeSerializer, RemittanceSerializer,\
SogeCreditSerializer SogeCreditSerializer
from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit from ..models import Invoice, Product, RemittanceType, Remittance, SogeCredit

View File

@ -1,23 +0,0 @@
# Generated by Django 2.2.28 on 2024-03-21 23:45
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('treasury', '0007_auto_20240311_1549'),
]
operations = [
migrations.AddField(
model_name='invoice',
name='payment_date',
field=models.CharField(default='', max_length=255, verbose_name='Payment date'),
),
migrations.AddField(
model_name='invoice',
name='quotation',
field=models.BooleanField(default=False, verbose_name='Quotation'),
),
]

View File

@ -1,19 +0,0 @@
# Generated by Django 5.0.7 on 2024-07-11 09:24
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('note', '0007_alter_note_polymorphic_ctype_and_more'),
('treasury', '0008_auto_20240322_0045'),
]
operations = [
migrations.AlterField(
model_name='sogecredit',
name='transactions',
field=models.ManyToManyField(blank=True, related_name='+', to='note.membershiptransaction', verbose_name='membership transactions'),
),
]

View File

@ -41,10 +41,6 @@ class Invoice(models.Model):
), ),
verbose_name=_("BDE"), verbose_name=_("BDE"),
) )
quotation = models.BooleanField(
default=False,
verbose_name=_("Quotation"),
)
object = models.CharField( object = models.CharField(
max_length=255, max_length=255,
@ -69,12 +65,6 @@ class Invoice(models.Model):
verbose_name=_("Date"), verbose_name=_("Date"),
) )
payment_date = models.CharField(
default="",
max_length=255,
verbose_name=_("Payment date"),
)
acquitted = models.BooleanField( acquitted = models.BooleanField(
verbose_name=_("Acquitted"), verbose_name=_("Acquitted"),
default=False, default=False,

View File

@ -76,11 +76,8 @@
\def\FactureNum {{"{"}}{{ obj.id }}} % Numéro de facture \def\FactureNum {{"{"}}{{ obj.id }}} % Numéro de facture
\def\FactureAcquittee {% if obj.acquitted %} {oui} {% else %} {non} {% endif %} % Facture acquittée : oui/non \def\FactureAcquittee {% if obj.acquitted %} {oui} {% else %} {non} {% endif %} % Facture acquittée : oui/non
\def\Devis {% if obj.quotation %} {oui} {% else %} {non} {% endif %}
% Devis : oui/non
\def\FactureLieu {{"{"}}{{ obj.place|escape_tex }}} % Lieu de l'édition de la facture \def\FactureLieu {{"{"}}{{ obj.place|escape_tex }}} % Lieu de l'édition de la facture
\def\FactureDate {{"{"}}{{ obj.date }}} % Date de l'édition de la facture \def\FactureDate {{"{"}}{{ obj.date }}} % Date de l'édition de la facture
\def\FacturePaymentDate {{"{"}}{{ obj.payment_date|escape_tex }}} % Date de paiement de la facture
\def\FactureObjet {{"{"}}{{ obj.object|escape_tex }} } % Objet du document \def\FactureObjet {{"{"}}{{ obj.object|escape_tex }} } % Objet du document
% Description de la facture % Description de la facture
\def\FactureDescr {{"{"}}{{ obj.description|escape_tex }}} \def\FactureDescr {{"{"}}{{ obj.description|escape_tex }}}
@ -121,12 +118,10 @@
% Nom et adresse de la société % Nom et adresse de la société
\MonNom \\ \MonNom \\
\MonAdresseRue \\ \MonAdresseRue \\
\MonAdresseVille \\ \MonAdresseVille
\ifthenelse{\equal{\Devis}{oui}}{
Devis n°\FactureNum
}{
Facture n°\FactureNum Facture n°\FactureNum
}
{\addtolength{\leftskip}{10.5cm} %in ERT {\addtolength{\leftskip}{10.5cm} %in ERT
\ClientNom \\ \ClientNom \\
@ -144,7 +139,6 @@ Facture n°\FactureNum
\textnormal{\FactureDescr} \textnormal{\FactureDescr}
~\\ ~\\
\begin{center} \begin{center}
@ -160,11 +154,6 @@ Facture n°\FactureNum
\ifthenelse{\equal{\FactureAcquittee}{oui}}{ \ifthenelse{\equal{\FactureAcquittee}{oui}}{
Facture acquittée. Facture acquittée.
}{ }{
Echéance de paiement : \FacturePaymentDate
Conditions d'escompte : Aucune
Taux de pénalité en cas de non paiement ou retard de paiement : 0 \%
À régler par chèque ou par virement bancaire : À régler par chèque ou par virement bancaire :
@ -187,6 +176,5 @@ Facture n°\FactureNum
TVA non applicable, article 293 B du CGI. TVA non applicable, article 293 B du CGI.
\end{center} \end{center}
\end{document} \end{document}
{% endlanguage %} {% endlanguage %}

View File

@ -69,11 +69,9 @@ class TestInvoices(TestCase):
response = self.client.post(reverse("treasury:invoice_create"), data={ response = self.client.post(reverse("treasury:invoice_create"), data={
"id": 42, "id": 42,
"object": "Same object", "object": "Same object",
"quotation": True,
"description": "Longer description", "description": "Longer description",
"name": "Me and others", "name": "Me and others",
"address": "Alwways earth", "address": "Alwways earth",
"payment_date": "Maybe someday...",
"acquitted": True, "acquitted": True,
"products-0-designation": "Designation", "products-0-designation": "Designation",
"products-0-quantity": 1, "products-0-quantity": 1,
@ -99,10 +97,8 @@ class TestInvoices(TestCase):
"object": "Same object", "object": "Same object",
"description": "Longer description", "description": "Longer description",
"name": "Me and others", "name": "Me and others",
"quotation": False,
"address": "Always earth", "address": "Always earth",
"acquitted": True, "acquitted": True,
"payment_date": "Never",
"locked": True, "locked": True,
"products-0-designation": "Designation", "products-0-designation": "Designation",
"products-0-quantity": 1, "products-0-quantity": 1,

View File

@ -3,8 +3,8 @@
from django.urls import path from django.urls import path
from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView, \ from .views import InvoiceCreateView, InvoiceListView, InvoiceUpdateView, InvoiceDeleteView, InvoiceRenderView,\
RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView, \ RemittanceListView, RemittanceCreateView, RemittanceUpdateView, LinkTransactionToRemittanceView,\
UnlinkTransactionToRemittanceView, SogeCreditListView, SogeCreditManageView UnlinkTransactionToRemittanceView, SogeCreditListView, SogeCreditManageView
app_name = 'treasury' app_name = 'treasury'

View File

@ -1,14 +1,13 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay # Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later # SPDX-License-Identifier: GPL-3.0-or-later
from bootstrap_datepicker_plus.widgets import DatePickerInput
from django import forms from django import forms
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.db.models import Q from django.db.models import Q
from django.forms import CheckboxSelectMultiple from django.forms import CheckboxSelectMultiple
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from note.models import NoteSpecial, NoteUser from note.models import NoteSpecial, NoteUser
from note_kfet.inputs import AmountInput, Autocomplete, ColorWidget from note_kfet.inputs import AmountInput, DatePickerInput, Autocomplete, ColorWidget
from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole from ..models import WEIClub, WEIRegistration, Bus, BusTeam, WEIMembership, WEIRole

View File

@ -439,7 +439,7 @@ class TestWEIRegistration(TestCase):
emergency_contact_phone='+33123456789', emergency_contact_phone='+33123456789',
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertTrue("This user can&#x27;t be in her/his first year since he/she has already participated to a WEI." self.assertTrue("This user can&#39;t be in her/his first year since he/she has already participated to a WEI."
in str(response.context["form"].errors)) in str(response.context["form"].errors))
# Check that if the WEI is started, we can't register anyone # Check that if the WEI is started, we can't register anyone
@ -635,7 +635,7 @@ class TestWEIRegistration(TestCase):
)) ))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
self.assertFalse(response.context["form"].is_valid()) self.assertFalse(response.context["form"].is_valid())
self.assertTrue("This team doesn&#x27;t belong to the given bus." in str(response.context["form"].errors)) self.assertTrue("This team doesn&#39;t belong to the given bus." in str(response.context["form"].errors))
response = self.client.post(reverse("wei:validate_registration", kwargs=dict(pk=self.registration.pk)), dict( response = self.client.post(reverse("wei:validate_registration", kwargs=dict(pk=self.registration.pk)), dict(
roles=[WEIRole.objects.get(name="GC WEI").id], roles=[WEIRole.objects.get(name="GC WEI").id],

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -25,13 +25,19 @@ admin_site.register(Site, SiteAdmin)
# Add external apps model # Add external apps model
if "oauth2_provider" in settings.INSTALLED_APPS: if "oauth2_provider" in settings.INSTALLED_APPS:
from oauth2_provider.admin import ApplicationAdmin, GrantAdmin, AccessTokenAdmin, RefreshTokenAdmin from oauth2_provider.admin import Application, ApplicationAdmin, Grant, \
from oauth2_provider.models import Application, Grant, AccessToken, RefreshToken GrantAdmin, AccessToken, AccessTokenAdmin, RefreshToken, RefreshTokenAdmin
admin_site.register(Application, ApplicationAdmin) admin_site.register(Application, ApplicationAdmin)
admin_site.register(Grant, GrantAdmin) admin_site.register(Grant, GrantAdmin)
admin_site.register(AccessToken, AccessTokenAdmin) admin_site.register(AccessToken, AccessTokenAdmin)
admin_site.register(RefreshToken, RefreshTokenAdmin) admin_site.register(RefreshToken, RefreshTokenAdmin)
if "django_htcpcp_tea" in settings.INSTALLED_APPS:
from django_htcpcp_tea.admin import *
from django_htcpcp_tea.models import *
admin_site.register(Pot, PotAdmin)
admin_site.register(TeaType, TeaTypeAdmin)
admin_site.register(Addition, AdditionAdmin)
if "mailer" in settings.INSTALLED_APPS: if "mailer" in settings.INSTALLED_APPS:
from mailer.admin import * from mailer.admin import *
@ -44,3 +50,9 @@ if "rest_framework" in settings.INSTALLED_APPS:
from rest_framework.authtoken.admin import * from rest_framework.authtoken.admin import *
from rest_framework.authtoken.models import * from rest_framework.authtoken.models import *
admin_site.register(Token, TokenAdmin) admin_site.register(Token, TokenAdmin)
if "cas_server" in settings.INSTALLED_APPS:
from cas_server.admin import *
from cas_server.models import *
admin_site.register(ServicePattern, ServicePatternAdmin)
admin_site.register(FederatedIendityProvider, FederatedIendityProviderAdmin)

View File

@ -4,7 +4,7 @@
"pk": 1, "pk": 1,
"fields": { "fields": {
"domain": "note.crans.org", "domain": "note.crans.org",
"name": "La Note Kfet 🍪" "name": "La Note Kfet \ud83c\udf7b"
} }
} }
] ]

View File

@ -41,7 +41,7 @@ INSTALLED_APPS = [
'bootstrap_datepicker_plus', 'bootstrap_datepicker_plus',
'colorfield', 'colorfield',
'crispy_forms', 'crispy_forms',
'crispy_bootstrap4', 'django_htcpcp_tea',
'django_tables2', 'django_tables2',
'mailer', 'mailer',
'phonenumber_field', 'phonenumber_field',
@ -90,6 +90,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.locale.LocaleMiddleware', 'django.middleware.locale.LocaleMiddleware',
'django.contrib.sites.middleware.CurrentSiteMiddleware', 'django.contrib.sites.middleware.CurrentSiteMiddleware',
'django_htcpcp_tea.middleware.HTCPCPTeaMiddleware',
'note_kfet.middlewares.SessionMiddleware', 'note_kfet.middlewares.SessionMiddleware',
'note_kfet.middlewares.LoginByIPMiddleware', 'note_kfet.middlewares.LoginByIPMiddleware',
'note_kfet.middlewares.TurbolinksMiddleware', 'note_kfet.middlewares.TurbolinksMiddleware',
@ -294,6 +295,3 @@ PHONENUMBER_DEFAULT_REGION = 'FR'
# We add custom information to CAS, in order to give a normalized name to other services # We add custom information to CAS, in order to give a normalized name to other services
CAS_AUTH_CLASS = 'member.auth.CustomAuthUser' CAS_AUTH_CLASS = 'member.auth.CustomAuthUser'
# Default field for primary key
DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@ -8,7 +8,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
{% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %} {% if widget.value != None and widget.value != "" %}value="{{ widget.value }}"{% endif %}
name="{{ widget.name }}_name" autocomplete="off" name="{{ widget.name }}_name" autocomplete="off"
{% for name, value in widget.attrs.items %} {% for name, value in widget.attrs.items %}
{% if value != False %}{{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %} {% ifnotequal value False %}{{ name }}{% ifnotequal value True %}="{{ value|stringformat:'s' }}"{% endifnotequal %}{% endifnotequal %}
{% endfor %} {% endfor %}
aria-describedby="{{widget.attrs.id}}_tooltip"> aria-describedby="{{widget.attrs.id}}_tooltip">
{% if widget.resetable %} {% if widget.resetable %}

View File

@ -126,12 +126,9 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}"> <a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}">
<i class="fa fa-user"></i> {% trans "My account" %} <i class="fa fa-user"></i> {% trans "My account" %}
</a> </a>
<form method="post" action="{% url 'logout' %}"> <a class="dropdown-item" href="{% url 'logout' %}">
{% csrf_token %}
<button class="dropdown-item" type="submit">
<i class="fa fa-sign-out"></i> {% trans "Log out" %} <i class="fa fa-sign-out"></i> {% trans "Log out" %}
</button> </a>
</form>
</div> </div>
</li> </li>
{% else %} {% else %}
@ -197,8 +194,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
class="text-muted">{% trans "Contact us" %}</a> &mdash; class="text-muted">{% trans "Contact us" %}</a> &mdash;
<a href="mailto:{{ "SUPPORT_EMAIL" | getenv }}" <a href="mailto:{{ "SUPPORT_EMAIL" | getenv }}"
class="text-muted">{% trans "Technical Support" %}</a> &mdash; class="text-muted">{% trans "Technical Support" %}</a> &mdash;
<a href="https://perso.crans.org/club-bde/charte_informatique.pdf"
class="text-muted">{% trans "Charte Info (FR)" %}</a> &mdash;
<a href="https://note.crans.org/doc/faq/" <a href="https://note.crans.org/doc/faq/"
class="text-muted">{% trans "FAQ (FR)" %}</a> &mdash; class="text-muted">{% trans "FAQ (FR)" %}</a> &mdash;
</span> </span>

View File

@ -30,6 +30,9 @@ urlpatterns = [
path('accounts/', include('django.contrib.auth.urls')), path('accounts/', include('django.contrib.auth.urls')),
path('api/', include('api.urls')), path('api/', include('api.urls')),
path('permission/', include('permission.urls')), path('permission/', include('permission.urls')),
# Make coffee
path('coffee/', include('django_htcpcp_tea.urls')),
] ]
# During development, serve static and media files # During development, serve static and media files
@ -43,6 +46,11 @@ if "oauth2_provider" in settings.INSTALLED_APPS:
path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')) path('o/', include('oauth2_provider.urls', namespace='oauth2_provider'))
) )
if "cas_server" in settings.INSTALLED_APPS:
urlpatterns.append(
path('cas/', include('cas_server.urls', namespace='cas_server'))
)
if "debug_toolbar" in settings.INSTALLED_APPS: if "debug_toolbar" in settings.INSTALLED_APPS:
import debug_toolbar import debug_toolbar
urlpatterns = [ urlpatterns = [

View File

@ -1,17 +1,19 @@
beautifulsoup4~=4.12.3 beautifulsoup4~=4.7.1
crispy-bootstrap4~=2024.1 Django~=2.2.15
Django~=5.0.7 django-bootstrap-datepicker-plus~=3.0.5
django-bootstrap-datepicker-plus~=5.0.5 django-cas-server~=1.2.0
django-colorfield~=0.11.0 django-colorfield~=0.3.2
django-crispy-forms~=2.2 django-crispy-forms~=1.7.2
django-extensions~=3.2.3 django-extensions>=2.1.4
django-filter~=24.2 django-filter~=2.1
django-mailer~=2.3.2 django-htcpcp-tea~=0.3.1
django-oauth-toolkit~=2.4.0 django-mailer~=2.0.1
django-phonenumber-field~=8.0.0 django-oauth-toolkit~=1.3.3
django-polymorphic~=3.1.0 django-phonenumber-field~=5.0.0
django-rest-polymorphic~=0.1.10 django-polymorphic>=2.0.3,<3.0.0
django-tables2~=2.7.0 djangorestframework>=3.9.0,<3.13.0
djangorestframework~=3.15.2 django-rest-polymorphic~=0.1.9
phonenumbers~=8.13.40 django-tables2~=2.3.1
python-memcached~=1.59
phonenumbers~=8.9.10
Pillow>=5.4.1 Pillow>=5.4.1

View File

@ -1,10 +1,13 @@
[tox] [tox]
envlist = envlist =
# Debian Buster Python
py37-django22
# Ubuntu 20.04 Python # Ubuntu 20.04 Python
py310-django50 py38-django22
# Debian Bullseye Python # Debian Bullseye Python
py311-django50 py39-django22
linters linters
skipsdist = True skipsdist = True