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

Compare commits

..

1 Commits

24 changed files with 126 additions and 133 deletions

View File

@ -7,25 +7,10 @@ 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:
# Ubuntu 22.04
py310-django50:
stage: test
image: ubuntu:20.04
image: ubuntu:22.04
before_script:
# Fix tzdata prompt
- ln -sf /usr/share/zoneinfo/Europe/Paris /etc/localtime && echo Europe/Paris > /etc/timezone
@ -37,12 +22,12 @@ py38-django22:
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 py38-django22
script: tox -e py310-django50
# Debian Bullseye
py39-django22:
# Debian Bookworm
py311-django50:
stage: test
image: debian:bullseye
image: debian:bookworm
before_script:
- >
apt-get update &&
@ -52,7 +37,7 @@ py39-django22:
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 py39-django22
script: tox -e py311-django50
linters:
stage: quality-assurance

View File

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

View File

@ -46,4 +46,4 @@ SPDX-License-Identifier: GPL-3.0-or-later
</h3>
{% render_table table %}
</div>
{% endblock %}
{% endblock %}

View File

@ -17,8 +17,7 @@ from django.utils.translation import gettext_lazy as _
from django.views import View
from django.views.decorators.cache import cache_page
from django.views.generic import DetailView, TemplateView, UpdateView
from django.views.generic.list import ListView
from django_tables2.views import MultiTableMixin
from django_tables2.views import SingleTableView
from note.models import Alias, NoteSpecial, NoteUser
from permission.backends import PermissionBackend
from permission.views import ProtectQuerysetMixin, ProtectedCreateView
@ -58,40 +57,27 @@ class ActivityCreateView(ProtectQuerysetMixin, ProtectedCreateView):
return reverse_lazy('activity:activity_detail', kwargs={"pk": self.object.pk})
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, MultiTableMixin, ListView):
class ActivityListView(ProtectQuerysetMixin, LoginRequiredMixin, SingleTableView):
"""
Displays all Activities, and classify if they are on-going or upcoming ones.
"""
model = Activity
tables = [ActivityTable, ActivityTable]
table_class = ActivityTable
ordering = ('-date_start',)
extra_context = {"title": _("Activities")}
def get_queryset(self, **kwargs):
return super().get_queryset(**kwargs).distinct()
def get_tables(self):
tables = super().get_tables()
tables[0].prefix = "all-"
tables[1].prefix = "upcoming-"
return tables
def get_tables_data(self):
# first table = all activities, second table = upcoming
return [
self.get_queryset().order_by("-date_start"),
Activity.objects.filter(date_end__gt=timezone.now())
.filter(PermissionBackend.filter_queryset(self.request, Activity, "view"))
.distinct()
.order_by("date_start")
]
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
tables = context["tables"]
for name, table in zip(["table", "upcoming"], tables):
context[name] = table
upcoming_activities = Activity.objects.filter(date_end__gt=timezone.now())
context['upcoming'] = ActivityTable(
data=upcoming_activities.filter(PermissionBackend.filter_queryset(self.request, Activity, "view")),
prefix='upcoming-',
order_by='date_start',
)
started_activities = self.get_queryset().filter(open=True, valid=True).distinct().all()
context["started_activities"] = started_activities

View File

@ -2,7 +2,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
from django.conf import settings
from django.conf.urls import url, include
from django.urls import include, re_path
from rest_framework import routers
from .views import UserInformationView
@ -47,7 +47,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')),
]

View File

@ -3,6 +3,7 @@
import io
from bootstrap_datepicker_plus.widgets import DatePickerInput
from PIL import Image, ImageSequence
from django import forms
from django.conf import settings
@ -13,7 +14,7 @@ from django.forms import CheckboxSelectMultiple
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from note.models import NoteSpecial, Alias
from note_kfet.inputs import Autocomplete, AmountInput, DatePickerInput
from note_kfet.inputs import Autocomplete, AmountInput
from permission.models import PermissionMask, Role
from .models import Profile, Club, Membership
@ -32,7 +33,7 @@ class UserForm(forms.ModelForm):
# Django usernames can only contain letters, numbers, @, ., +, - and _.
# We want to allow users to have uncommon and unpractical usernames:
# 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:
model = User

View File

@ -44,7 +44,7 @@ class TemplateLoggedInTests(TestCase):
self.assertRedirects(response, settings.LOGIN_REDIRECT_URL, 302, 302)
def test_logout(self):
response = self.client.get(reverse("logout"))
response = self.client.post(reverse("logout"))
self.assertEqual(response.status_code, 200)
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 + '/alias', AliasViewSet)
router.register(path + '/trust', TrustViewSet)
router.register(path + '/consumer', ConsumerViewSet)
router.register(path + '/consumer', ConsumerViewSet, basename="consumer")
router.register(path + '/transaction/category', TemplateCategoryViewSet)
router.register(path + '/transaction/transaction', TransactionViewSet)

View File

@ -179,19 +179,10 @@ class ConsumerViewSet(ReadOnlyProtectedModelViewSet):
# We match first an alias if it is matched without normalization,
# then if the normalized pattern matches a normalized alias.
queryset = queryset.filter(
**{f'name{suffix}': alias_prefix + alias}
).union(
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)
Q(**{f'name{suffix}': alias_prefix + alias})
| Q(**{f'normalized_name{suffix}': alias_prefix + Alias.normalize(alias)})
| Q(**{f'normalized_name{suffix}': alias_prefix + alias.lower()})
)
queryset = queryset if settings.DATABASES[queryset.db]["ENGINE"] == 'django.db.backends.postgresql' \
else queryset.order_by("name")

View File

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

View File

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

View File

@ -0,0 +1,25 @@
# 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 }}"
{# 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 != False %}{{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}
{% endfor %}>
<div class="input-group-append">
<span class="input-group-text"></span>

View File

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

View File

@ -439,7 +439,7 @@ class TestWEIRegistration(TestCase):
emergency_contact_phone='+33123456789',
))
self.assertEqual(response.status_code, 200)
self.assertTrue("This user can&#39;t be in her/his first year since he/she has already participated to a WEI."
self.assertTrue("This user can&#x27;t be in her/his first year since he/she has already participated to a WEI."
in str(response.context["form"].errors))
# 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.assertFalse(response.context["form"].is_valid())
self.assertTrue("This team doesn&#39;t belong to the given bus." in str(response.context["form"].errors))
self.assertTrue("This team doesn&#x27;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(
roles=[WEIRole.objects.get(name="GC WEI").id],

View File

@ -400,37 +400,37 @@ msgstr "Inviter"
msgid "Create new activity"
msgstr "Créer une nouvelle activité"
#: apps/activity/views.py:68 note_kfet/templates/base.html:90
#: apps/activity/views.py:67 note_kfet/templates/base.html:90
msgid "Activities"
msgstr "Activités"
#: apps/activity/views.py:108
#: apps/activity/views.py:93
msgid "Activity detail"
msgstr "Détails de l'activité"
#: apps/activity/views.py:128
#: apps/activity/views.py:113
msgid "Update activity"
msgstr "Modifier l'activité"
#: apps/activity/views.py:155
#: apps/activity/views.py:140
msgid "Invite guest to the activity \"{}\""
msgstr "Invitation pour l'activité « {} »"
#: apps/activity/views.py:193
#: apps/activity/views.py:178
msgid "You are not allowed to display the entry interface for this activity."
msgstr ""
"Vous n'êtes pas autorisé·e à afficher l'interface des entrées pour cette "
"activité."
#: apps/activity/views.py:196
#: apps/activity/views.py:181
msgid "This activity does not support activity entries."
msgstr "Cette activité ne requiert pas d'entrées."
#: apps/activity/views.py:199
#: apps/activity/views.py:184
msgid "This activity is closed."
msgstr "Cette activité est fermée."
#: apps/activity/views.py:295
#: apps/activity/views.py:280
msgid "Entry for activity \"{}\""
msgstr "Entrées pour l'activité « {} »"

View File

@ -25,19 +25,13 @@ 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)
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:
from mailer.admin import *
@ -50,9 +44,3 @@ if "rest_framework" in settings.INSTALLED_APPS:
from rest_framework.authtoken.admin import *
from rest_framework.authtoken.models import *
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

@ -41,7 +41,7 @@ INSTALLED_APPS = [
'bootstrap_datepicker_plus',
'colorfield',
'crispy_forms',
'django_htcpcp_tea',
'crispy_bootstrap4',
'django_tables2',
'mailer',
'phonenumber_field',
@ -90,7 +90,6 @@ 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',
@ -295,3 +294,6 @@ PHONENUMBER_DEFAULT_REGION = 'FR'
# We add custom information to CAS, in order to give a normalized name to other services
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 %}
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 != False %}{{ name }}{% if value != True %}="{{ value|stringformat:'s' }}"{% endif %}{% endif %}
{% endfor %}
aria-describedby="{{widget.attrs.id}}_tooltip">
{% if widget.resetable %}

View File

@ -126,9 +126,12 @@ SPDX-License-Identifier: GPL-3.0-or-later
<a class="dropdown-item" href="{% url 'member:user_detail' pk=request.user.pk %}">
<i class="fa fa-user"></i> {% trans "My account" %}
</a>
<a class="dropdown-item" href="{% url 'logout' %}">
<form method="post" action="{% url 'logout' %}">
{% csrf_token %}
<button class="dropdown-item" type="submit">
<i class="fa fa-sign-out"></i> {% trans "Log out" %}
</a>
</button>
</form>
</div>
</li>
{% else %}

View File

@ -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
@ -46,11 +43,6 @@ if "oauth2_provider" in settings.INSTALLED_APPS:
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:
import debug_toolbar
urlpatterns = [

View File

@ -1,19 +1,17 @@
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
beautifulsoup4~=4.12.3
crispy-bootstrap4~=2024.1
Django~=5.0.7
django-bootstrap-datepicker-plus~=5.0.5
django-colorfield~=0.11.0
django-crispy-forms~=2.2
django-extensions~=3.2.3
django-filter~=24.2
django-mailer~=2.3.2
django-oauth-toolkit~=2.4.0
django-phonenumber-field~=8.0.0
django-polymorphic~=3.1.0
django-rest-polymorphic~=0.1.10
django-tables2~=2.7.0
djangorestframework~=3.15.2
phonenumbers~=8.13.40
Pillow>=5.4.1

View File

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