1
0
mirror of https://gitlab.crans.org/bde/nk20 synced 2025-06-23 10:56:35 +02:00

Compare commits

..

7 Commits

37 changed files with 522 additions and 124 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

@ -17,9 +17,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
</form> </form>
</div> </div>
</div> </div>
{% endblock %}
{% block extrajavascript %}
<script> <script>
var date_end = document.getElementById("id_date_end"); var date_end = document.getElementById("id_date_end");
var date_start = document.getElementById("id_date_start"); var date_start = document.getElementById("id_date_start");

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')),
] ]

0
apps/food/__init__.py Normal file
View File

29
apps/food/admin.py Normal file
View File

@ -0,0 +1,29 @@
from django.contrib import admin
from note_kfet.admin import admin_site
from .models import QR_code, Basic_food, Transformed_food, Allergen
@admin.register(QR_code, site = admin_site)
class QR_codeAdmin(admin.ModelAdmin):
"""
TEMPORARY
"""
@admin.register(Basic_food, site = admin_site)
class Basic_foodAdmin(admin.ModelAdmin):
"""
TEMPORARY
"""
@admin.register(Transformed_food, site = admin_site)
class Transformed_foodAdmin(admin.ModelAdmin):
"""
TEMPORARY
"""
@admin.register(Allergen, site = admin_site)
class AllergenAdmin(admin.ModelAdmin):
"""
TEMPORARY
"""

5
apps/food/apps.py Normal file
View File

@ -0,0 +1,5 @@
from django.apps import AppConfig
class FoodkfetConfig(AppConfig):
name = 'foodkfet'

View File

@ -0,0 +1,96 @@
# Generated by Django 2.2.28 on 2024-05-21 12:05
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('member', '0011_profile_vss_charter_read'),
]
operations = [
migrations.CreateModel(
name='Basic_food',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')),
('is_DLC', models.BooleanField(default=False, verbose_name='is DLC')),
('is_DDM', models.BooleanField(default=False, verbose_name='is DDM')),
('expiry_date', models.DateTimeField(blank=True, default=django.utils.timezone.now, verbose_name='expiry date')),
('label', models.ImageField(default='pic/default.png', max_length=255, upload_to='label/', verbose_name='food label')),
('was_eaten', models.BooleanField(default=False, verbose_name='was eaten')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='owner')),
],
options={
'verbose_name': 'Basic food',
'verbose_name_plural': 'Basic foods',
},
),
migrations.CreateModel(
name='Transformed_food',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='name')),
('creation_date', models.DateTimeField(verbose_name='creation date')),
('expiry_date', models.DateTimeField(verbose_name='expiry date')),
('is_active', models.BooleanField(default=True, verbose_name='is active')),
('was_eaten', models.BooleanField(default=False, verbose_name='was eaten')),
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='+', to='member.Club', verbose_name='owner')),
('transformed_ingredient', models.ManyToManyField(blank=True, related_name='transformed_ingredient_inv', to='food.Transformed_food', verbose_name='transformed ingredient')),
],
options={
'verbose_name': 'Transformed food',
'verbose_name_plural': 'Transformed foods',
},
),
migrations.CreateModel(
name='QR_code',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('qr_code_number', models.PositiveIntegerField(verbose_name='QR-code number')),
('basic_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='QR_code', to='food.Basic_food', verbose_name='basic food')),
('transformed_food_container', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='QR_code', to='food.Transformed_food', verbose_name='transformed food container')),
],
options={
'verbose_name': 'QR-code',
'verbose_name_plural': 'QR-codes',
},
),
migrations.AddField(
model_name='basic_food',
name='transformed_food',
field=models.ManyToManyField(blank=True, related_name='Basic_food', to='food.Transformed_food', verbose_name='transformed food'),
),
migrations.CreateModel(
name='Allergen',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('gluten', models.BooleanField(default=False, verbose_name='gluten')),
('nut', models.BooleanField(default=False, verbose_name='nut')),
('crustecean', models.BooleanField(default=False, verbose_name='crustacean')),
('celery', models.BooleanField(default=False, verbose_name='celery')),
('egg', models.BooleanField(default=False, verbose_name='egg')),
('mustard', models.BooleanField(default=False, verbose_name='mustard')),
('fish', models.BooleanField(default=False, verbose_name='fish')),
('soy', models.BooleanField(default=False, verbose_name='soy')),
('milk', models.BooleanField(default=False, verbose_name='milk')),
('sulphite', models.BooleanField(default=False, verbose_name='sulphite')),
('lupine', models.BooleanField(default=False, verbose_name='lupine')),
('mollusc', models.BooleanField(default=False, verbose_name='mollusc')),
('groundnut', models.BooleanField(default=False, verbose_name='groundnut')),
('sesame', models.BooleanField(default=False, verbose_name='sesame')),
('alcohol', models.BooleanField(default=False, verbose_name='alcohol')),
('basic_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Allergen', to='food.Basic_food', verbose_name='basic food')),
('transformed_food', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='Allergen', to='food.Transformed_food', verbose_name='transformed food')),
],
options={
'verbose_name': 'Allergen',
'verbose_name_plural': 'Allergens',
},
),
]

View File

267
apps/food/models.py Normal file
View File

@ -0,0 +1,267 @@
# Copyright (C) 2018-2024 by BDE ENS Paris-Saclay
# SPDX-License-Identifier: GPL-3.0-or-later
from datetime import date
from django.conf import settings
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Q
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from member.models import Club
class QR_code(models.Model):
"""
An QR_code model
"""
qr_code_number = models.PositiveIntegerField(
verbose_name=_("QR-code number"),
)
transformed_food_container = models.ForeignKey(
'Transformed_food',
on_delete = models.PROTECT,
related_name = 'QR_code',
null = True,
blank = True,
verbose_name = _('transformed food container'),
)
basic_food = models.ForeignKey(
'Basic_food',
on_delete = models.PROTECT,
related_name = 'QR_code',
null = True,
blank = True,
verbose_name = _('basic food'),
)
class Meta:
verbose_name = _("QR-code")
verbose_name_plural = _("QR-codes")
def __str__(self):
return _("QR-code number {qr_code_number}").format(qr_code_number=self.qr_code_number)
class Allergen(models.Model):
"""
A list of allergen and alimentary restrictions
"""
gluten = models.BooleanField(
default = False,
verbose_name = _('gluten'),
)
nut = models.BooleanField(
default = False,
verbose_name = _('nut'),
)
crustecean = models.BooleanField(
default = False,
verbose_name = _('crustacean'),
)
celery = models.BooleanField(
default = False,
verbose_name = _('celery'),
)
egg = models.BooleanField(
default = False,
verbose_name = _('egg'),
)
mustard = models.BooleanField(
default = False,
verbose_name = _('mustard'),
)
fish = models.BooleanField(
default = False,
verbose_name = _('fish'),
)
soy = models.BooleanField(
default = False,
verbose_name = _('soy'),
)
milk = models.BooleanField(
default = False,
verbose_name = _('milk'),
)
sulphite = models.BooleanField(
default = False,
verbose_name = _('sulphite'),
)
lupine = models.BooleanField(
default = False,
verbose_name = _('lupine'),
)
mollusc = models.BooleanField(
default = False,
verbose_name = _('mollusc'),
)
groundnut = models.BooleanField(
default = False,
verbose_name = _('groundnut'),
)
sesame = models.BooleanField(
default = False,
verbose_name = _('sesame'),
)
alcohol = models.BooleanField(
default = False,
verbose_name = _('alcohol'),
)
transformed_food = models.ForeignKey(
'Transformed_food',
on_delete = models.CASCADE,
related_name = 'Allergen',
blank = True,
null = True,
verbose_name = _('transformed food'),
)
basic_food = models.ForeignKey(
'Basic_food',
on_delete = models.CASCADE,
related_name = 'Allergen',
blank = True,
null = True,
verbose_name = _('basic food'),
)
class Meta:
verbose_name = _('Allergen')
verbose_name_plural = _('Allergens')
def __str__(self):
return _('Allergens of #{id}').format(id=self.id)
class Basic_food(models.Model):
"""
Food which has been directly buy on supermarket
"""
name = models.CharField(
verbose_name=_('name'),
max_length=255,
)
is_DLC = models.BooleanField(
verbose_name=_("is DLC"),
default=False,
)
is_DDM = models.BooleanField(
verbose_name=_("is DDM"),
default=False,
)
expiry_date = models.DateTimeField(
verbose_name=_('expiry date'),
default=timezone.now,
blank=True,
)
owner = models.ForeignKey(
Club,
on_delete=models.PROTECT,
related_name= '+',
verbose_name=_('owner'),
)
label = models.ImageField(
verbose_name=_('food label'),
max_length=255,
blank=False,
null=False,
upload_to='label/',
default= 'pic/default.png',
)
was_eaten = models.BooleanField(
verbose_name=_('was eaten'),
default = False,
)
transformed_food = models.ManyToManyField(
'Transformed_food',
related_name= 'Basic_food',
blank = True,
verbose_name = _('transformed food'),
)
class Meta:
verbose_name=_('Basic food')
verbose_name_plural=_('Basic foods')
def __str__(self):
return self.name
class Transformed_food(models.Model):
"""
Transformed food are a mix between basic food and meal
"""
name = models.CharField(
max_length = 255,
verbose_name =_('name'),
)
creation_date = models.DateTimeField(
verbose_name =_('creation date'),
)
expiry_date = models.DateTimeField(
verbose_name =_('expiry date'),
)
owner = models.ForeignKey(
Club,
on_delete = models.PROTECT,
related_name = '+',
verbose_name =_('owner'),
)
transformed_ingredient = models.ManyToManyField(
"self",
blank = True,
symmetrical = False,
related_name = 'transformed_ingredient_inv',
verbose_name = _('transformed ingredient'),
)
is_active = models.BooleanField(
default = True,
verbose_name = _('is active'),
)
was_eaten = models.BooleanField(
default = False,
verbose_name = _('was eaten'),
)
class Meta:
verbose_name = _('Transformed food')
verbose_name_plural = _('Transformed foods')
def __str__(self):
return self.name

3
apps/food/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

7
apps/food/urls.py Normal file
View File

@ -0,0 +1,7 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.index, name='index')
]

5
apps/food/views.py Normal file
View File

@ -0,0 +1,5 @@
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return HttpResponse('test')

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

@ -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

@ -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

@ -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

@ -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

@ -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],

View File

@ -3388,10 +3388,6 @@ msgstr "Support technique"
msgid "FAQ (FR)" msgid "FAQ (FR)"
msgstr "FAQ (FR)" msgstr "FAQ (FR)"
#: note_kfet/templates/base.html:200
msgid "Charte Info (FR)"
msgstr "Charte Info (FR)"
#: note_kfet/templates/base_search.html:15 #: note_kfet/templates/base_search.html:15
msgid "Search by attribute such as name…" msgid "Search by attribute such as name…"
msgstr "Chercher par un attribut tel que le nom …" msgstr "Chercher par un attribut tel que le nom …"

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',
@ -77,6 +77,7 @@ INSTALLED_APPS = [
'scripts', 'scripts',
'treasury', 'treasury',
'wei', 'wei',
'food',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -90,6 +91,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 +296,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

@ -21,6 +21,7 @@ urlpatterns = [
path('activity/', include('activity.urls')), path('activity/', include('activity.urls')),
path('treasury/', include('treasury.urls')), path('treasury/', include('treasury.urls')),
path('wei/', include('wei.urls')), path('wei/', include('wei.urls')),
path('food/',include('food.urls')),
# Include Django Contrib and Core routers # Include Django Contrib and Core routers
path('i18n/', include('django.conf.urls.i18n')), path('i18n/', include('django.conf.urls.i18n')),
@ -30,6 +31,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 +47,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