Merge pull request #704 from ae-utbm/taiste

Mises à jour (django 4.2, Pillow 10, cryptography 42), changement de la CI et enlèvement de l'offre Eurockéennes
This commit is contained in:
thomas girod 2024-07-08 11:16:39 +02:00 committed by GitHub
commit 44c8558aa3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
192 changed files with 4607 additions and 4421 deletions

View File

@ -6,13 +6,13 @@ runs:
- name: Install apt packages - name: Install apt packages
uses: awalsh128/cache-apt-pkgs-action@latest uses: awalsh128/cache-apt-pkgs-action@latest
with: with:
packages: gettext libxapian-dev libgraphviz-dev packages: gettext libgraphviz-dev
version: 1.0 # increment to reset cache version: 1.0 # increment to reset cache
- name: Install dependencies - name: Install dependencies
run: | run: |
sudo apt update sudo apt update
sudo apt install gettext libxapian-dev libgraphviz-dev sudo apt install gettext libgraphviz-dev
shell: bash shell: bash
- name: Set up python - name: Set up python
@ -45,7 +45,11 @@ runs:
${{ runner.os }}-poetry- ${{ runner.os }}-poetry-
- name: Install dependencies - name: Install dependencies
run: poetry install -E testing -E docs run: poetry install --with docs,tests
shell: bash
- name: Install xapian
run: poetry run ./manage.py install_xapian
shell: bash shell: bash
- name: Compile gettext messages - name: Compile gettext messages

View File

@ -8,27 +8,31 @@ on:
workflow_dispatch: workflow_dispatch:
jobs: jobs:
black: pre-commit:
name: Black format name: Launch pre-commits checks (ruff)
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Check out repository - uses: actions/checkout@v4
uses: actions/checkout@v3 - uses: actions/setup-python@v5
- name: Setup Project - uses: pre-commit/action@v3.0.1
uses: ./.github/actions/setup_project with:
- run: poetry run black --check . extra_args: --all-files
tests: tests:
name: Run tests and generate coverage report name: Run tests and generate coverage report
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
fail-fast: false # don't interrupt the other test processes
matrix:
pytest-mark: [slow, not slow]
steps: steps:
- name: Check out repository - name: Check out repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- uses: ./.github/actions/setup_project - uses: ./.github/actions/setup_project
- uses: ./.github/actions/setup_xapian - uses: ./.github/actions/setup_xapian
- uses: ./.github/actions/compile_messages - uses: ./.github/actions/compile_messages
- name: Run tests - name: Run tests
run: poetry run coverage run ./manage.py test run: poetry run coverage run -m pytest -m "${{ matrix.pytest-mark }}"
- name: Generate coverage report - name: Generate coverage report
run: | run: |
poetry run coverage report poetry run coverage report

View File

@ -38,6 +38,7 @@ jobs:
git pull git pull
poetry install poetry install
poetry run ./manage.py install_xapian
poetry run ./manage.py migrate poetry run ./manage.py migrate
echo "yes" | poetry run ./manage.py collectstatic echo "yes" | poetry run ./manage.py collectstatic
poetry run ./manage.py compilestatic poetry run ./manage.py compilestatic

View File

@ -37,6 +37,7 @@ jobs:
git pull git pull
poetry install poetry install
poetry run ./manage.py install_xapian
poetry run ./manage.py migrate poetry run ./manage.py migrate
echo "yes" | poetry run ./manage.py collectstatic echo "yes" | poetry run ./manage.py collectstatic
poetry run ./manage.py compilestatic poetry run ./manage.py compilestatic

10
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,10 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.4.10
hooks:
- id: ruff # just check the code, and print the errors
- id: ruff # actually fix the fixable errors, but print nothing
args: ["--fix", "--silent"]
# Run the formatter.
- id: ruff-format

View File

@ -18,7 +18,6 @@ from django.contrib import admin
from accounting.models import * from accounting.models import *
admin.site.register(BankAccount) admin.site.register(BankAccount)
admin.site.register(ClubAccount) admin.site.register(ClubAccount)
admin.site.register(GeneralJournal) admin.site.register(GeneralJournal)

View File

@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.core.validators import django.core.validators
import accounting.models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
import accounting.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import phonenumber_field.modelfields import phonenumber_field.modelfields
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -14,19 +14,19 @@
# #
# #
from django.urls import reverse from decimal import Decimal
from django.core.exceptions import ValidationError
from django.core import validators
from django.db import models
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.template import defaultfilters
from django.conf import settings
from django.core import validators
from django.core.exceptions import ValidationError
from django.db import models
from django.template import defaultfilters
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from decimal import Decimal
from core.models import User, SithFile
from club.models import Club from club.models import Club
from core.models import SithFile, User
class CurrencyField(models.DecimalField): class CurrencyField(models.DecimalField):

View File

@ -14,87 +14,82 @@
# #
# #
from django.test import TestCase
from django.urls import reverse
from django.core.management import call_command
from datetime import date, timedelta from datetime import date, timedelta
from core.models import User from django.test import TestCase
from django.urls import reverse
from accounting.models import ( from accounting.models import (
GeneralJournal,
Operation,
Label,
AccountingType, AccountingType,
GeneralJournal,
Label,
Operation,
SimplifiedAccountingType, SimplifiedAccountingType,
) )
from core.models import User
class RefoundAccountTest(TestCase): class RefoundAccountTest(TestCase):
def setUp(self): @classmethod
self.skia = User.objects.filter(username="skia").first() def setUpTestData(cls):
cls.skia = User.objects.get(username="skia")
# reffil skia's account # reffil skia's account
self.skia.customer.amount = 800 cls.skia.customer.amount = 800
self.skia.customer.save() cls.skia.customer.save()
cls.refound_account_url = reverse("accounting:refound_account")
def test_permission_denied(self): def test_permission_denied(self):
self.client.login(username="guy", password="plop") self.client.force_login(User.objects.get(username="guy"))
response_post = self.client.post( response_post = self.client.post(
reverse("accounting:refound_account"), {"user": self.skia.id} self.refound_account_url, {"user": self.skia.id}
) )
response_get = self.client.get(reverse("accounting:refound_account")) response_get = self.client.get(self.refound_account_url)
self.assertTrue(response_get.status_code == 403) assert response_get.status_code == 403
self.assertTrue(response_post.status_code == 403) assert response_post.status_code == 403
def test_root_granteed(self): def test_root_granteed(self):
self.client.login(username="root", password="plop") self.client.force_login(User.objects.get(username="root"))
response_post = self.client.post( response = self.client.post(self.refound_account_url, {"user": self.skia.id})
reverse("accounting:refound_account"), {"user": self.skia.id} self.assertRedirects(response, self.refound_account_url)
) self.skia.refresh_from_db()
self.skia = User.objects.filter(username="skia").first() response = self.client.get(self.refound_account_url)
response_get = self.client.get(reverse("accounting:refound_account")) assert response.status_code == 200
self.assertFalse(response_get.status_code == 403) assert '<form action="" method="post">' in str(response.content)
self.assertTrue('<form action="" method="post">' in str(response_get.content)) assert self.skia.customer.amount == 0
self.assertFalse(response_post.status_code == 403)
self.assertTrue(self.skia.customer.amount == 0)
def test_comptable_granteed(self): def test_comptable_granteed(self):
self.client.login(username="comptable", password="plop") self.client.force_login(User.objects.get(username="comptable"))
response_post = self.client.post( response = self.client.post(self.refound_account_url, {"user": self.skia.id})
reverse("accounting:refound_account"), {"user": self.skia.id} self.assertRedirects(response, self.refound_account_url)
) self.skia.refresh_from_db()
self.skia = User.objects.filter(username="skia").first() response = self.client.get(self.refound_account_url)
response_get = self.client.get(reverse("accounting:refound_account")) assert response.status_code == 200
self.assertFalse(response_get.status_code == 403) assert '<form action="" method="post">' in str(response.content)
self.assertTrue('<form action="" method="post">' in str(response_get.content)) assert self.skia.customer.amount == 0
self.assertFalse(response_post.status_code == 403)
self.assertTrue(self.skia.customer.amount == 0)
class JournalTest(TestCase): class JournalTest(TestCase):
def setUp(self): @classmethod
self.journal = GeneralJournal.objects.filter(id=1).first() def setUpTestData(cls):
cls.journal = GeneralJournal.objects.get(id=1)
def test_permission_granted(self): def test_permission_granted(self):
self.client.login(username="comptable", password="plop") self.client.force_login(User.objects.get(username="comptable"))
response_get = self.client.get( response_get = self.client.get(
reverse("accounting:journal_details", args=[self.journal.id]) reverse("accounting:journal_details", args=[self.journal.id])
) )
self.assertTrue(response_get.status_code == 200) assert response_get.status_code == 200
self.assertTrue( assert "<td>M\\xc3\\xa9thode de paiement</td>" in str(response_get.content)
"<td>M\\xc3\\xa9thode de paiement</td>" in str(response_get.content)
)
def test_permission_not_granted(self): def test_permission_not_granted(self):
self.client.login(username="skia", password="plop") self.client.force_login(User.objects.get(username="skia"))
response_get = self.client.get( response_get = self.client.get(
reverse("accounting:journal_details", args=[self.journal.id]) reverse("accounting:journal_details", args=[self.journal.id])
) )
self.assertTrue(response_get.status_code == 403) assert response_get.status_code == 403
self.assertFalse( assert "<td>M\xc3\xa9thode de paiement</td>" not in str(response_get.content)
"<td>M\xc3\xa9thode de paiement</td>" in str(response_get.content)
)
class OperationTest(TestCase): class OperationTest(TestCase):
@ -108,9 +103,8 @@ class OperationTest(TestCase):
code="443", label="Ce code n'existe pas", movement_type="CREDIT" code="443", label="Ce code n'existe pas", movement_type="CREDIT"
) )
at.save() at.save()
l = Label(club_account=self.journal.club_account, name="bob") l = Label.objects.create(club_account=self.journal.club_account, name="bob")
l.save() self.client.force_login(User.objects.get(username="comptable"))
self.client.login(username="comptable", password="plop")
self.op1 = Operation( self.op1 = Operation(
journal=self.journal, journal=self.journal,
date=date.today(), date=date.today(),
@ -139,8 +133,7 @@ class OperationTest(TestCase):
self.op2.save() self.op2.save()
def test_new_operation(self): def test_new_operation(self):
self.client.login(username="comptable", password="plop") at = AccountingType.objects.get(code="604")
at = AccountingType.objects.filter(code="604").first()
response = self.client.post( response = self.client.post(
reverse("accounting:op_new", args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
{ {
@ -172,8 +165,7 @@ class OperationTest(TestCase):
self.assertTrue("<td>Le fantome de la nuit</td>" in str(response_get.content)) self.assertTrue("<td>Le fantome de la nuit</td>" in str(response_get.content))
def test_bad_new_operation(self): def test_bad_new_operation(self):
self.client.login(username="comptable", password="plop") AccountingType.objects.get(code="604")
AccountingType.objects.filter(code="604").first()
response = self.client.post( response = self.client.post(
reverse("accounting:op_new", args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
{ {
@ -199,7 +191,7 @@ class OperationTest(TestCase):
) )
def test_new_operation_not_authorized(self): def test_new_operation_not_authorized(self):
self.client.login(username="skia", password="plop") self.client.force_login(self.skia)
at = AccountingType.objects.filter(code="604").first() at = AccountingType.objects.filter(code="604").first()
response = self.client.post( response = self.client.post(
reverse("accounting:op_new", args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
@ -226,7 +218,6 @@ class OperationTest(TestCase):
) )
def test__operation_simple_accounting(self): def test__operation_simple_accounting(self):
self.client.login(username="comptable", password="plop")
sat = SimplifiedAccountingType.objects.all().first() sat = SimplifiedAccountingType.objects.all().first()
response = self.client.post( response = self.client.post(
reverse("accounting:op_new", args=[self.journal.id]), reverse("accounting:op_new", args=[self.journal.id]),
@ -263,14 +254,12 @@ class OperationTest(TestCase):
) )
def test_nature_statement(self): def test_nature_statement(self):
self.client.login(username="comptable", password="plop")
response = self.client.get( response = self.client.get(
reverse("accounting:journal_nature_statement", args=[self.journal.id]) reverse("accounting:journal_nature_statement", args=[self.journal.id])
) )
self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200) self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200)
def test_person_statement(self): def test_person_statement(self):
self.client.login(username="comptable", password="plop")
response = self.client.get( response = self.client.get(
reverse("accounting:journal_person_statement", args=[self.journal.id]) reverse("accounting:journal_person_statement", args=[self.journal.id])
) )
@ -292,7 +281,6 @@ class OperationTest(TestCase):
) )
def test_accounting_statement(self): def test_accounting_statement(self):
self.client.login(username="comptable", password="plop")
response = self.client.get( response = self.client.get(
reverse("accounting:journal_accounting_statement", args=[self.journal.id]) reverse("accounting:journal_accounting_statement", args=[self.journal.id])
) )

View File

@ -14,41 +14,41 @@
# #
# #
from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView
from django.urls import reverse_lazy, reverse
from django.utils.translation import gettext_lazy as _
from django.forms.models import modelform_factory
from django.core.exceptions import PermissionDenied, ValidationError
from django.forms import HiddenInput
from django.db import transaction
from django.db.models import Sum
from django.conf import settings
from django import forms
from django.http import HttpResponse
import collections import collections
from ajax_select.fields import AutoCompleteSelectField from ajax_select.fields import AutoCompleteSelectField
from django import forms
from django.conf import settings
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import transaction
from django.db.models import Sum
from django.forms import HiddenInput
from django.forms.models import modelform_factory
from django.http import HttpResponse
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
from core.views import (
CanViewMixin,
CanEditMixin,
CanEditPropMixin,
CanCreateMixin,
TabedViewMixin,
)
from core.views.forms import SelectFile, SelectDate
from accounting.models import ( from accounting.models import (
AccountingType,
BankAccount, BankAccount,
ClubAccount, ClubAccount,
GeneralJournal,
Operation,
AccountingType,
Company, Company,
SimplifiedAccountingType, GeneralJournal,
Label, Label,
Operation,
SimplifiedAccountingType,
) )
from counter.models import Counter, Selling, Product from core.views import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
TabedViewMixin,
)
from core.views.forms import SelectDate, SelectFile
from counter.models import Counter, Product, Selling
# Main accounting view # Main accounting view
@ -521,14 +521,14 @@ class OperationPDFView(CanViewMixin, DetailView):
pk_url_kwarg = "op_id" pk_url_kwarg = "op_id"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
from reportlab.pdfgen import canvas
from reportlab.lib.units import cm
from reportlab.platypus import Table, TableStyle
from reportlab.lib import colors from reportlab.lib import colors
from reportlab.lib.pagesizes import letter from reportlab.lib.pagesizes import letter
from reportlab.lib.units import cm
from reportlab.lib.utils import ImageReader from reportlab.lib.utils import ImageReader
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas
from reportlab.platypus import Table, TableStyle
pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf")) pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf"))
@ -599,7 +599,7 @@ class OperationPDFView(CanViewMixin, DetailView):
payment_mode = "" payment_mode = ""
for m in settings.SITH_ACCOUNTING_PAYMENT_METHOD: for m in settings.SITH_ACCOUNTING_PAYMENT_METHOD:
if m[0] == mode: if m[0] == mode:
payment_mode += "[\u00D7]" payment_mode += "[\u00d7]"
else: else:
payment_mode += "[ ]" payment_mode += "[ ]"
payment_mode += " %s\n" % (m[1]) payment_mode += " %s\n" % (m[1])

View File

@ -14,6 +14,4 @@
# #
# #
from django.contrib import admin
# Register your models here. # Register your models here.

View File

@ -14,6 +14,4 @@
# #
# #
from django.db import models
# Create your models here. # Create your models here.

View File

@ -14,6 +14,4 @@
# #
# #
from django.test import TestCase
# Create your tests here. # Create your tests here.

View File

@ -14,10 +14,10 @@
# #
# #
from django.urls import re_path, path, include from django.urls import include, path, re_path
from rest_framework import routers
from api.views import * from api.views import *
from rest_framework import routers
# Router config # Router config
router = routers.DefaultRouter() router = routers.DefaultRouter()

View File

@ -14,13 +14,13 @@
# #
# #
from rest_framework.response import Response
from rest_framework import viewsets
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from rest_framework.decorators import action
from django.db.models.query import QuerySet from django.db.models.query import QuerySet
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from core.views import can_view, can_edit from core.views import can_edit, can_view
def check_if(obj, user, test): def check_if(obj, user, test):
@ -64,10 +64,10 @@ class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet):
from .api import * from .api import *
from .counter import *
from .user import *
from .club import * from .club import *
from .counter import *
from .group import * from .group import *
from .launderette import * from .launderette import *
from .uv import *
from .sas import * from .sas import *
from .user import *
from .uv import *

View File

@ -14,9 +14,9 @@
# #
# #
from rest_framework.response import Response
from rest_framework.decorators import api_view, renderer_classes from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import StaticHTMLRenderer from rest_framework.renderers import StaticHTMLRenderer
from rest_framework.response import Response
from core.templatetags.renderer import markdown from core.templatetags.renderer import markdown

View File

@ -14,17 +14,15 @@
# #
# #
from rest_framework.response import Response from django.conf import settings
from django.core.exceptions import PermissionDenied
from rest_framework import serializers from rest_framework import serializers
from rest_framework.decorators import api_view, renderer_classes from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import StaticHTMLRenderer from rest_framework.renderers import StaticHTMLRenderer
from rest_framework.response import Response
from django.conf import settings
from django.core.exceptions import PermissionDenied
from club.models import Club, Mailing
from api.views import RightModelViewSet from api.views import RightModelViewSet
from club.models import Club, Mailing
class ClubSerializer(serializers.ModelSerializer): class ClubSerializer(serializers.ModelSerializer):

View File

@ -15,12 +15,11 @@
# #
from rest_framework import serializers from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response
from counter.models import Counter
from api.views import RightModelViewSet from api.views import RightModelViewSet
from counter.models import Counter
class CounterSerializer(serializers.ModelSerializer): class CounterSerializer(serializers.ModelSerializer):

View File

@ -16,9 +16,8 @@
from rest_framework import serializers from rest_framework import serializers
from core.models import RealGroup
from api.views import RightModelViewSet from api.views import RightModelViewSet
from core.models import RealGroup
class GroupSerializer(serializers.ModelSerializer): class GroupSerializer(serializers.ModelSerializer):

View File

@ -15,12 +15,11 @@
# #
from rest_framework import serializers from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response
from launderette.models import Launderette, Machine, Token
from api.views import RightModelViewSet from api.views import RightModelViewSet
from launderette.models import Launderette, Machine, Token
class LaunderettePlaceSerializer(serializers.ModelSerializer): class LaunderettePlaceSerializer(serializers.ModelSerializer):

View File

@ -1,4 +1,5 @@
from typing import List from typing import List
from rest_framework.decorators import api_view, renderer_classes from rest_framework.decorators import api_view, renderer_classes
from rest_framework.exceptions import PermissionDenied from rest_framework.exceptions import PermissionDenied
from rest_framework.generics import get_object_or_404 from rest_framework.generics import get_object_or_404
@ -6,8 +7,8 @@ from rest_framework.renderers import JSONRenderer
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from core.views import can_edit
from core.models import User from core.models import User
from core.views import can_edit
from sas.models import Picture from sas.models import Picture

View File

@ -17,12 +17,11 @@
import datetime import datetime
from rest_framework import serializers from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.response import Response
from core.models import User
from api.views import RightModelViewSet from api.views import RightModelViewSet
from core.models import User
class UserSerializer(serializers.ModelSerializer): class UserSerializer(serializers.ModelSerializer):

View File

@ -1,11 +1,12 @@
from rest_framework.response import Response import json
import urllib.request
from django.conf import settings
from django.core.exceptions import PermissionDenied
from rest_framework import serializers
from rest_framework.decorators import api_view, renderer_classes from rest_framework.decorators import api_view, renderer_classes
from rest_framework.renderers import JSONRenderer from rest_framework.renderers import JSONRenderer
from django.core.exceptions import PermissionDenied from rest_framework.response import Response
from django.conf import settings
from rest_framework import serializers
import urllib.request
import json
from pedagogy.views import CanCreateUVFunctionMixin from pedagogy.views import CanCreateUVFunctionMixin

View File

@ -23,18 +23,15 @@
# #
# #
from django.conf import settings from ajax_select.fields import AutoCompleteSelectMultipleField
from django import forms from django import forms
from django.conf import settings
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField from club.models import Club, Mailing, MailingSubscription, Membership
from club.models import Mailing, MailingSubscription, Club, Membership
from core.models import User from core.models import User
from core.views.forms import SelectDate, SelectDateTime from core.views.forms import SelectDate, TzAwareDateTimeField
from counter.models import Counter from counter.models import Counter
from core.views.forms import TzAwareDateTimeField
class ClubEditForm(forms.ModelForm): class ClubEditForm(forms.ModelForm):

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.core.validators import django.core.validators
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,11 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import re import re
import django.core.validators import django.core.validators
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
import django.db.models.deletion
from django.db import migrations, models from django.db import migrations, models
from club.models import Club from club.models import Club
from core.operations import PsqlRunOnly from core.operations import PsqlRunOnly
import django.db.models.deletion
def generate_club_pages(apps, schema_editor): def generate_club_pages(apps, schema_editor):

View File

@ -1,9 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import club.models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
import club.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -24,21 +24,19 @@
# #
from typing import Optional from typing import Optional
from django.core.cache import cache
from django.db import models
from django.core import validators
from django.conf import settings from django.conf import settings
from django.core import validators
from django.core.cache import cache
from django.core.exceptions import ObjectDoesNotExist, ValidationError
from django.core.validators import RegexValidator, validate_email
from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.utils.timezone import now
from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db import transaction
from django.urls import reverse from django.urls import reverse
from django.utils import timezone from django.utils import timezone
from django.core.validators import RegexValidator, validate_email
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.translation import gettext_lazy as _
from core.models import User, MetaGroup, Group, SithFile, RealGroup, Notification, Page from core.models import Group, MetaGroup, Notification, Page, RealGroup, SithFile, User
# Create your models here. # Create your models here.
@ -209,11 +207,11 @@ class Club(models.Model):
cache.set(f"sith_club_{self.unix_name}", self) cache.set(f"sith_club_{self.unix_name}", self)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
# Invalidate the cache of this club and of its memberships # Invalidate the cache of this club and of its memberships
for membership in self.members.ongoing().select_related("user"): for membership in self.members.ongoing().select_related("user"):
cache.delete(f"membership_{self.id}_{membership.user.id}") cache.delete(f"membership_{self.id}_{membership.user.id}")
cache.delete(f"sith_club_{self.unix_name}") cache.delete(f"sith_club_{self.unix_name}")
super().delete(*args, **kwargs)
def __str__(self): def __str__(self):
return self.name return self.name

File diff suppressed because it is too large Load Diff

View File

@ -26,50 +26,42 @@
import csv import csv
from django.conf import settings from django.conf import settings
from django import forms from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
from django.views.generic import ListView, DetailView, TemplateView, View from django.core.paginator import InvalidPage, Paginator
from django.views.generic.edit import DeleteView from django.db.models import Sum
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import UpdateView, CreateView
from django.http import ( from django.http import (
HttpResponseRedirect,
HttpResponse,
Http404, Http404,
HttpResponseRedirect,
StreamingHttpResponse, StreamingHttpResponse,
) )
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext as _t from django.utils.translation import gettext as _t
from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS from django.utils.translation import gettext_lazy as _
from django.core.paginator import Paginator, InvalidPage from django.views.generic import DetailView, ListView, TemplateView, View
from django.shortcuts import get_object_or_404, redirect from django.views.generic.edit import CreateView, DeleteView, UpdateView
from django.db.models import Sum
from club.forms import ClubEditForm, ClubMemberForm, MailingForm, SellingsForm
from core.views import ( from club.models import Club, Mailing, MailingSubscription, Membership
CanCreateMixin, from com.views import (
CanViewMixin, PosterCreateBaseView,
CanEditMixin, PosterDeleteBaseView,
CanEditPropMixin, PosterEditBaseView,
UserIsRootMixin, PosterListBaseView,
TabedViewMixin,
PageEditViewBase,
DetailFormView,
) )
from core.models import PageRev from core.models import PageRev
from core.views import (
from counter.models import Selling CanCreateMixin,
CanEditMixin,
from com.views import ( CanEditPropMixin,
PosterListBaseView, CanViewMixin,
PosterCreateBaseView, DetailFormView,
PosterEditBaseView, PageEditViewBase,
PosterDeleteBaseView, TabedViewMixin,
UserIsRootMixin,
) )
from counter.models import Selling
from club.models import Club, Membership, Mailing, MailingSubscription
from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsForm
class ClubTabsMixin(TabedViewMixin): class ClubTabsMixin(TabedViewMixin):
@ -611,7 +603,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
} }
return kwargs return kwargs
def add_new_mailing(self, cleaned_data) -> ValidationError: def add_new_mailing(self, cleaned_data) -> ValidationError | None:
""" """
Create a new mailing list from the form Create a new mailing list from the form
""" """
@ -628,7 +620,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
mailing.save() mailing.save()
return None return None
def add_new_subscription(self, cleaned_data) -> ValidationError: def add_new_subscription(self, cleaned_data) -> ValidationError | None:
""" """
Add mailing subscriptions for each user given and/or for the specified email in form Add mailing subscriptions for each user given and/or for the specified email in form
""" """

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models import django.db.models.deletion
import django.utils.timezone import django.utils.timezone
from django.conf import settings from django.conf import settings
import django.db.models.deletion from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -23,22 +23,20 @@
# #
# #
from django.shortcuts import render from django.conf import settings
from django.core.exceptions import ValidationError
from django.core.mail import EmailMultiAlternatives
from django.db import models, transaction from django.db import models, transaction
from django.db.models import Q from django.db.models import Q
from django.utils.translation import gettext_lazy as _ from django.shortcuts import render
from django.utils import timezone
from django.urls import reverse
from django.conf import settings
from django.templatetags.static import static from django.templatetags.static import static
from django.core.mail import EmailMultiAlternatives from django.urls import reverse
from django.core.exceptions import ValidationError
from django.utils import timezone from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from core import utils
from core.models import User, Preferences, RealGroup, Notification, SithFile
from club.models import Club from club.models import Club
from core import utils
from core.models import Notification, Preferences, RealGroup, User
class Sith(models.Model): class Sith(models.Model):

View File

@ -13,51 +13,53 @@
# OR WITHIN THE LOCAL FILE "LICENSE" # OR WITHIN THE LOCAL FILE "LICENSE"
# #
# #
import pytest
from django.conf import settings
from django.core.files.uploadedfile import SimpleUploadedFile from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import TestCase from django.test import TestCase
from django.conf import settings
from django.urls import reverse from django.urls import reverse
from django.core.management import call_command
from django.utils import html from django.utils import html
from django.utils.timezone import localtime, now from django.utils.timezone import localtime, now
from django.utils.translation import gettext as _ from django.utils.translation import gettext as _
from club.models import Club, Membership from club.models import Club, Membership
from com.models import Sith, News, Weekmail, WeekmailArticle, Poster from com.models import News, Poster, Sith, Weekmail, WeekmailArticle
from core.models import User, RealGroup, AnonymousUser from core.models import AnonymousUser, RealGroup, User
class ComAlertTest(TestCase): @pytest.fixture()
def test_page_is_working(self): def user_community():
self.client.login(username="comunity", password="plop") return User.objects.get(username="comunity")
response = self.client.get(reverse("com:alert_edit"))
self.assertNotEqual(response.status_code, 500)
self.assertEqual(response.status_code, 200)
class ComInfoTest(TestCase): @pytest.mark.django_db
def test_page_is_working(self): @pytest.mark.parametrize(
self.client.login(username="comunity", password="plop") "url",
response = self.client.get(reverse("com:info_edit")) [
self.assertNotEqual(response.status_code, 500) reverse("com:alert_edit"),
self.assertEqual(response.status_code, 200) reverse("com:info_edit"),
],
)
def test_com_page_is_working(client, url, user_community):
client.force_login(user_community)
response = client.get(url)
assert response.status_code == 200
class ComTest(TestCase): class ComTest(TestCase):
@classmethod @classmethod
def setUpTestData(cls): def setUpTestData(cls):
cls.skia = User.objects.filter(username="skia").first() cls.skia = User.objects.get(username="skia")
cls.com_group = RealGroup.objects.filter( cls.com_group = RealGroup.objects.filter(
id=settings.SITH_GROUP_COM_ADMIN_ID id=settings.SITH_GROUP_COM_ADMIN_ID
).first() ).first()
cls.skia.groups.set([cls.com_group]) cls.skia.groups.set([cls.com_group])
cls.skia.save()
def setUp(self): def setUp(self):
self.client.login(username=self.skia.username, password="plop") self.client.force_login(self.skia)
def test_alert_msg(self): def test_alert_msg(self):
response = self.client.post( self.client.post(
reverse("com:alert_edit"), reverse("com:alert_edit"),
{ {
"alert_msg": """ "alert_msg": """
@ -68,7 +70,6 @@ class ComTest(TestCase):
}, },
) )
r = self.client.get(reverse("core:index")) r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200)
self.assertContains( self.assertContains(
r, r,
"""<div id="alert_box"> """<div id="alert_box">
@ -77,7 +78,7 @@ class ComTest(TestCase):
) )
def test_info_msg(self): def test_info_msg(self):
response = self.client.post( self.client.post(
reverse("com:info_edit"), reverse("com:info_edit"),
{ {
"info_msg": """ "info_msg": """
@ -86,7 +87,6 @@ class ComTest(TestCase):
}, },
) )
r = self.client.get(reverse("core:index")) r = self.client.get(reverse("core:index"))
self.assertTrue(r.status_code == 200)
self.assertContains( self.assertContains(
r, r,
"""<div id="info_box"> """<div id="info_box">
@ -94,7 +94,7 @@ class ComTest(TestCase):
) )
def test_birthday_non_subscribed_user(self): def test_birthday_non_subscribed_user(self):
self.client.login(username="guy", password="plop") self.client.force_login(User.objects.get(username="guy"))
response = self.client.get(reverse("core:index")) response = self.client.get(reverse("core:index"))
self.assertContains( self.assertContains(
response, response,
@ -123,13 +123,13 @@ class SithTest(TestCase):
sith: Sith = Sith.objects.first() sith: Sith = Sith.objects.first()
com_admin = User.objects.get(username="comunity") com_admin = User.objects.get(username="comunity")
self.assertTrue(sith.is_owned_by(com_admin)) assert sith.is_owned_by(com_admin)
anonymous = AnonymousUser() anonymous = AnonymousUser()
self.assertFalse(sith.is_owned_by(anonymous)) assert not sith.is_owned_by(anonymous)
sli = User.objects.get(username="sli") sli = User.objects.get(username="sli")
self.assertFalse(sith.is_owned_by(sli)) assert not sith.is_owned_by(sli)
class NewsTest(TestCase): class NewsTest(TestCase):
@ -154,10 +154,10 @@ class NewsTest(TestCase):
or by their author but nobody else or by their author but nobody else
""" """
self.assertTrue(self.new.is_owned_by(self.com_admin)) assert self.new.is_owned_by(self.com_admin)
self.assertTrue(self.new.is_owned_by(self.author)) assert self.new.is_owned_by(self.author)
self.assertFalse(self.new.is_owned_by(self.anonymous)) assert not self.new.is_owned_by(self.anonymous)
self.assertFalse(self.new.is_owned_by(self.sli)) assert not self.new.is_owned_by(self.sli)
def test_news_viewer(self): def test_news_viewer(self):
""" """
@ -165,26 +165,26 @@ class NewsTest(TestCase):
and not moderated news only by com admins and not moderated news only by com admins
""" """
# by default a news isn't moderated # by default a news isn't moderated
self.assertTrue(self.new.can_be_viewed_by(self.com_admin)) assert self.new.can_be_viewed_by(self.com_admin)
self.assertFalse(self.new.can_be_viewed_by(self.sli)) assert not self.new.can_be_viewed_by(self.sli)
self.assertFalse(self.new.can_be_viewed_by(self.anonymous)) assert not self.new.can_be_viewed_by(self.anonymous)
self.assertFalse(self.new.can_be_viewed_by(self.author)) assert not self.new.can_be_viewed_by(self.author)
self.new.is_moderated = True self.new.is_moderated = True
self.new.save() self.new.save()
self.assertTrue(self.new.can_be_viewed_by(self.com_admin)) assert self.new.can_be_viewed_by(self.com_admin)
self.assertTrue(self.new.can_be_viewed_by(self.sli)) assert self.new.can_be_viewed_by(self.sli)
self.assertTrue(self.new.can_be_viewed_by(self.anonymous)) assert self.new.can_be_viewed_by(self.anonymous)
self.assertTrue(self.new.can_be_viewed_by(self.author)) assert self.new.can_be_viewed_by(self.author)
def test_news_editor(self): def test_news_editor(self):
""" """
Test that only com admins can edit news Test that only com admins can edit news
""" """
self.assertTrue(self.new.can_be_edited_by(self.com_admin)) assert self.new.can_be_edited_by(self.com_admin)
self.assertFalse(self.new.can_be_edited_by(self.sli)) assert not self.new.can_be_edited_by(self.sli)
self.assertFalse(self.new.can_be_edited_by(self.anonymous)) assert not self.new.can_be_edited_by(self.anonymous)
self.assertFalse(self.new.can_be_edited_by(self.author)) assert not self.new.can_be_edited_by(self.author)
class WeekmailArticleTest(TestCase): class WeekmailArticleTest(TestCase):
@ -207,10 +207,10 @@ class WeekmailArticleTest(TestCase):
""" """
Test that weekmails are owned only by com admins Test that weekmails are owned only by com admins
""" """
self.assertTrue(self.article.is_owned_by(self.com_admin)) assert self.article.is_owned_by(self.com_admin)
self.assertFalse(self.article.is_owned_by(self.author)) assert not self.article.is_owned_by(self.author)
self.assertFalse(self.article.is_owned_by(self.anonymous)) assert not self.article.is_owned_by(self.anonymous)
self.assertFalse(self.article.is_owned_by(self.sli)) assert not self.article.is_owned_by(self.sli)
class PosterTest(TestCase): class PosterTest(TestCase):
@ -233,8 +233,8 @@ class PosterTest(TestCase):
""" """
Test that poster are owned by com admins and board members in clubs Test that poster are owned by com admins and board members in clubs
""" """
self.assertTrue(self.poster.is_owned_by(self.com_admin)) assert self.poster.is_owned_by(self.com_admin)
self.assertFalse(self.poster.is_owned_by(self.anonymous)) assert not self.poster.is_owned_by(self.anonymous)
self.assertFalse(self.poster.is_owned_by(self.susbcriber)) assert not self.poster.is_owned_by(self.susbcriber)
self.assertTrue(self.poster.is_owned_by(self.sli)) assert self.poster.is_owned_by(self.sli)

View File

@ -23,38 +23,35 @@
# #
# #
from django.shortcuts import redirect, get_object_or_404
from django.http import HttpResponseRedirect
from django.views.generic import ListView, DetailView, View
from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.utils.translation import gettext_lazy as _
from django.urls import reverse, reverse_lazy
from django.core.exceptions import ValidationError
from django.utils import timezone
from django.conf import settings
from django.db.models import Max
from django.forms.models import modelform_factory
from django.core.exceptions import PermissionDenied
from django import forms
from datetime import timedelta from datetime import timedelta
from smtplib import SMTPRecipientsRefused from smtplib import SMTPRecipientsRefused
from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster from django import forms
from django.conf import settings
from django.core.exceptions import PermissionDenied, ValidationError
from django.db.models import Max
from django.forms.models import modelform_factory
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, View
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from club.models import Club, Mailing
from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle
from core.models import Notification, RealGroup, User
from core.views import ( from core.views import (
CanViewMixin, CanCreateMixin,
CanEditMixin, CanEditMixin,
CanEditPropMixin, CanEditPropMixin,
TabedViewMixin, CanViewMixin,
CanCreateMixin,
QuickNotifMixin, QuickNotifMixin,
TabedViewMixin,
) )
from core.views.forms import SelectDateTime, MarkdownInput from core.views.forms import MarkdownInput, TzAwareDateTimeField
from core.models import Notification, RealGroup, User
from club.models import Club, Mailing
from core.views.forms import TzAwareDateTimeField
# Sith object # Sith object

14
conftest.py Normal file
View File

@ -0,0 +1,14 @@
import pytest
from django.core.management import call_command
from django.utils.translation import activate
@pytest.fixture(scope="session")
def django_db_setup(django_db_setup, django_db_blocker):
with django_db_blocker.unblock():
call_command("populate")
@pytest.fixture(scope="session", autouse=True)
def set_default_language():
activate("fr")

View File

@ -14,12 +14,12 @@
# #
# #
from django.contrib import admin
from ajax_select import make_ajax_form from ajax_select import make_ajax_form
from core.models import User, Page, RealGroup, MetaGroup, SithFile from django.contrib import admin
from django.contrib.auth.models import Group as AuthGroup from django.contrib.auth.models import Group as AuthGroup
from haystack.admin import SearchModelAdmin from haystack.admin import SearchModelAdmin
from core.models import MetaGroup, Page, RealGroup, SithFile, User
admin.site.unregister(AuthGroup) admin.site.unregister(AuthGroup)
admin.site.register(MetaGroup) admin.site.register(MetaGroup)

View File

@ -34,8 +34,8 @@ class SithConfig(AppConfig):
verbose_name = "Core app of the Sith" verbose_name = "Core app of the Sith"
def ready(self): def ready(self):
import core.signals # noqa F401
from forum.models import Forum from forum.models import Forum
import core.signals
cache.clear() cache.clear()

View File

@ -1,6 +1,3 @@
from core.models import Page
class FourDigitYearConverter: class FourDigitYearConverter:
regex = "[0-9]{4}" regex = "[0-9]{4}"

View File

@ -14,15 +14,14 @@
# #
# #
from ajax_select import LookupChannel, register
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from ajax_select import register, LookupChannel
from core.views.site import search_user
from core.models import User, Group, SithFile
from club.models import Club
from counter.models import Product, Counter, Customer
from accounting.models import ClubAccount, Company from accounting.models import ClubAccount, Company
from eboutic.models import BasketItem from club.models import Club
from core.models import Group, SithFile, User
from core.views.site import search_user
from counter.models import Counter, Customer, Product
def check_token(request): def check_token(request):

View File

@ -23,8 +23,8 @@
# #
import os import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.core.management import call_command
from core.models import SithFile from core.models import SithFile

View File

@ -25,6 +25,7 @@
import os import os
from django.core.management.commands import compilemessages from django.core.management.commands import compilemessages

View File

@ -24,9 +24,10 @@
# #
import os import os
import sass import sass
from django.core.management.base import BaseCommand
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -24,10 +24,9 @@
# #
import os import os
import sys
import signal import signal
import sys
from http.server import test, CGIHTTPRequestHandler from http.server import CGIHTTPRequestHandler, test
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.utils import autoreload from django.utils import autoreload

View File

@ -0,0 +1,68 @@
# -*- coding:utf-8 -*
#
# Copyright 2024 © AE UTBM
# ae@utbm.fr / ae.info@utbm.fr
#
# This file is part of the website of the UTBM Student Association (AE UTBM),
# https://ae.utbm.fr.
#
# You can find the source code of the website at https://github.com/ae-utbm/sith3
#
# LICENSED UNDER THE GNU GENERAL PUBLIC LICENSE VERSION 3 (GPLv3)
# SEE : https://raw.githubusercontent.com/ae-utbm/sith3/master/LICENSE
# OR WITHIN THE LOCAL FILE "LICENSE"
#
#
import os
import subprocess
from pathlib import Path
import tomli
from django.core.management.base import BaseCommand, CommandParser
class Command(BaseCommand):
help = "Install xapian"
def add_arguments(self, parser: CommandParser):
parser.add_argument(
"-f",
"--force",
action="store_true",
help="Force installation even if already installed",
)
def _current_version(self) -> str | None:
try:
import xapian
except ImportError:
return None
return xapian.version_string()
def _desired_version(self) -> str:
with open(
Path(__file__).parent.parent.parent.parent / "pyproject.toml", "rb"
) as f:
pyproject = tomli.load(f)
return pyproject["tool"]["xapian"]["version"]
def handle(self, force: bool, *args, **options):
if not os.environ.get("VIRTUAL_ENV", None):
print("No virtual environment detected, this command can't be used")
return
desired = self._desired_version()
if desired == self._current_version():
if not force:
print(
f"Version {desired} is already installed, use --force to re-install"
)
return
print(f"Version {desired} is already installed, re-installing")
print(f"Installing xapian version {desired} at {os.environ['VIRTUAL_ENV']}")
subprocess.run(
[str(Path(__file__).parent / "install_xapian.sh"), desired],
env=dict(os.environ),
).check_returncode()
print("Installation success")

View File

@ -0,0 +1,47 @@
#!/usr/bin/env bash
# Originates from https://gist.github.com/jorgecarleitao/ab6246c86c936b9c55fd
# first argument of the script is Xapian version (e.g. 1.2.19)
VERSION=$1
# Cleanup env vars for auto discovery mechanism
export CPATH=
export LIBRARY_PATH=
export CFLAGS=
export LDFLAGS=
export CCFLAGS=
export CXXFLAGS=
export CPPFLAGS=
# prepare
rm -rf "$VIRTUAL_ENV/packages"
mkdir -p "$VIRTUAL_ENV/packages" && cd "$VIRTUAL_ENV/packages" || exit 1
CORE=xapian-core-$VERSION
BINDINGS=xapian-bindings-$VERSION
# download
echo "Downloading source..."
curl -O "https://oligarchy.co.uk/xapian/$VERSION/${CORE}.tar.xz"
curl -O "https://oligarchy.co.uk/xapian/$VERSION/${BINDINGS}.tar.xz"
# extract
echo "Extracting source..."
tar xf "${CORE}.tar.xz"
tar xf "${BINDINGS}.tar.xz"
# install
echo "Installing Xapian-core..."
cd "$VIRTUAL_ENV/packages/${CORE}" || exit 1
./configure --prefix="$VIRTUAL_ENV" && make && make install
PYTHON_FLAG=--with-python3
echo "Installing Xapian-bindings..."
cd "$VIRTUAL_ENV/packages/${BINDINGS}" || exit 1
./configure --prefix="$VIRTUAL_ENV" $PYTHON_FLAG XAPIAN_CONFIG="$VIRTUAL_ENV/bin/xapian-config" && make && make install
# clean
rm -rf "$VIRTUAL_ENV/packages"
# test
python -c "import xapian"

View File

@ -23,6 +23,7 @@
# #
import os import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from core.markdown import markdown from core.markdown import markdown

View File

@ -24,38 +24,37 @@
import os import os
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from io import StringIO, BytesIO from io import BytesIO, StringIO
from pathlib import Path from pathlib import Path
from django.contrib.auth.models import Permission
from django.core.management.base import BaseCommand
from django.core.management import call_command
from django.conf import settings from django.conf import settings
from django.db import connection from django.contrib.auth.models import Permission
from django.contrib.sites.models import Site from django.contrib.sites.models import Site
from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.db import connection
from django.utils import timezone from django.utils import timezone
from PIL import Image from PIL import Image
from core.models import Group, User, Page, PageRev, SithFile
from accounting.models import ( from accounting.models import (
GeneralJournal, AccountingType,
BankAccount, BankAccount,
ClubAccount, ClubAccount,
Operation,
AccountingType,
SimplifiedAccountingType,
Company, Company,
GeneralJournal,
Operation,
SimplifiedAccountingType,
) )
from core.utils import resize_image
from club.models import Club, Membership from club.models import Club, Membership
from subscription.models import Subscription from com.models import News, NewsDate, Sith, Weekmail
from counter.models import Customer, ProductType, Product, Counter, Selling, StudentCard from core.models import Group, Page, PageRev, SithFile, User
from com.models import Sith, Weekmail, News, NewsDate from core.utils import resize_image
from election.models import Election, Role, Candidature, ElectionList from counter.models import Counter, Customer, Product, ProductType, Selling, StudentCard
from election.models import Candidature, Election, ElectionList, Role
from forum.models import Forum, ForumTopic from forum.models import Forum, ForumTopic
from pedagogy.models import UV from pedagogy.models import UV
from sas.models import Album, Picture, PeoplePictureRelation from sas.models import Album, PeoplePictureRelation, Picture
from subscription.models import Subscription
class Command(BaseCommand): class Command(BaseCommand):
@ -1099,8 +1098,10 @@ Welcome to the wiki page!
n = News( n = News(
title="Repas barman", title="Repas barman",
summary="Enjoy la fin du semestre!", summary="Enjoy la fin du semestre!",
content="Viens donc t'enjailler avec les autres barmans aux " content=(
"frais du BdF! \o/", "Viens donc t'enjailler avec les autres barmans aux "
"frais du BdF! \\o/"
),
type="EVENT", type="EVENT",
club=bar_club, club=bar_club,
author=subscriber, author=subscriber,

View File

@ -23,8 +23,8 @@
# #
import os import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from django.core.management import call_command
from core.models import SithFile from core.models import SithFile

View File

@ -15,8 +15,9 @@
# #
import os import os
from django.core.management.base import BaseCommand
from django.core.management import call_command from django.core.management import call_command
from django.core.management.base import BaseCommand
class Command(BaseCommand): class Command(BaseCommand):

View File

@ -16,8 +16,9 @@
import os import os
import re import re
from mistune import Renderer, InlineGrammar, InlineLexer, Markdown, escape, escape_link
from django.urls import reverse from django.urls import reverse
from mistune import InlineGrammar, InlineLexer, Markdown, Renderer, escape, escape_link
class SithRenderer(Renderer): class SithRenderer(Renderer):

View File

@ -16,12 +16,13 @@
import importlib import importlib
import threading import threading
from django.conf import settings from django.conf import settings
from django.utils.functional import SimpleLazyObject
from django.contrib.auth import get_user from django.contrib.auth import get_user
from django.contrib.auth.middleware import ( from django.contrib.auth.middleware import (
AuthenticationMiddleware as DjangoAuthenticationMiddleware, AuthenticationMiddleware as DjangoAuthenticationMiddleware,
) )
from django.utils.functional import SimpleLazyObject
module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1) module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1)
AnonymousUser = getattr(importlib.import_module(module), klass) AnonymousUser = getattr(importlib.import_module(module), klass)

View File

@ -1,14 +1,14 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.contrib.auth.models import django.contrib.auth.models
import django.db.models.deletion
import django.core.validators import django.core.validators
import core.models import django.db.models.deletion
import phonenumber_field.modelfields import phonenumber_field.modelfields
from django.conf import settings from django.conf import settings
import django.db.models.deletion from django.db import migrations, models
import core.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.core.validators import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
import core.models import core.models

View File

@ -1,8 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.utils.timezone import django.utils.timezone
from django.db import migrations, models
import core.models import core.models

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.utils.timezone
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.core.validators import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,9 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import django.core.validators import django.core.validators
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
from django.conf import settings
import django.utils.timezone
import django.db.models.deletion import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,9 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import core.models
import django.db.models.deletion import django.db.models.deletion
from django.db import migrations, models
import core.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -1,8 +1,8 @@
# Generated by Django 2.2.6 on 2019-11-14 15:10 # Generated by Django 2.2.6 on 2019-11-14 15:10
import django.db.models.deletion
from django.conf import settings from django.conf import settings
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration): class Migration(migrations.Migration):

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2 on 2024-06-26 09:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("core", "0037_auto_20211105_1708"),
]
operations = [
migrations.AlterField(
model_name="preferences",
name="receive_weekmail",
field=models.BooleanField(
default=False, verbose_name="receive the Weekmail"
),
),
]

View File

@ -23,36 +23,39 @@
# #
# #
import importlib import importlib
from typing import Union, Optional, List import os
import unicodedata
from datetime import date, timedelta
from typing import List, Optional, Union
from django.core.cache import cache from django.conf import settings
from django.core.mail import send_mail
from django.contrib.auth.models import ( from django.contrib.auth.models import (
AbstractBaseUser, AbstractBaseUser,
UserManager, UserManager,
Group as AuthGroup, )
GroupManager as AuthGroupManager, from django.contrib.auth.models import (
AnonymousUser as AuthAnonymousUser, AnonymousUser as AuthAnonymousUser,
) )
from django.utils.translation import gettext_lazy as _ from django.contrib.auth.models import (
from django.utils import timezone Group as AuthGroup,
from django.core import validators )
from django.core.exceptions import ValidationError, PermissionDenied from django.contrib.auth.models import (
from django.urls import reverse GroupManager as AuthGroupManager,
from django.conf import settings )
from django.db import models, transaction
from django.contrib.staticfiles.storage import staticfiles_storage from django.contrib.staticfiles.storage import staticfiles_storage
from django.utils.html import escape from django.core import validators
from django.core.cache import cache
from django.core.exceptions import PermissionDenied, ValidationError
from django.core.mail import send_mail
from django.db import models, transaction
from django.urls import reverse
from django.utils import timezone
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.html import escape
import os from django.utils.translation import gettext_lazy as _
from core import utils
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from datetime import timedelta, date from core import utils
import unicodedata
class RealGroupManager(AuthGroupManager): class RealGroupManager(AuthGroupManager):
@ -698,9 +701,11 @@ class User(AbstractBaseUser):
<em>%s</em> <em>%s</em>
</a> </a>
""" % ( """ % (
(
self.profile_pict.get_download_url() self.profile_pict.get_download_url()
if self.profile_pict if self.profile_pict
else staticfiles_storage.url("core/img/unknown.jpg"), else staticfiles_storage.url("core/img/unknown.jpg")
),
_("Profile"), _("Profile"),
escape(self.get_display_name()), escape(self.get_display_name()),
) )

View File

@ -25,6 +25,7 @@
import os import os
from collections import OrderedDict from collections import OrderedDict
from django.conf import settings from django.conf import settings
from django.contrib.staticfiles.finders import FileSystemFinder from django.contrib.staticfiles.finders import FileSystemFinder
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage

View File

@ -24,12 +24,14 @@
# #
import os import os
import sass
from urllib.parse import urljoin from urllib.parse import urljoin
from django.utils.encoding import force_bytes, iri_to_uri
import sass
from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.templatetags.static import static from django.templatetags.static import static
from django.conf import settings from django.utils.encoding import force_bytes, iri_to_uri
from core.scss.storage import ScssFileStorage, find_file from core.scss.storage import ScssFileStorage, find_file

View File

@ -24,7 +24,6 @@
# #
from django.db import models from django.db import models
from haystack import indexes, signals from haystack import indexes, signals
from core.models import User from core.models import User

View File

@ -8,10 +8,10 @@ from core.models import User
@receiver(m2m_changed, sender=User.groups.through, dispatch_uid="user_groups_changed") @receiver(m2m_changed, sender=User.groups.through, dispatch_uid="user_groups_changed")
def user_groups_changed(sender, instance: User, **kwargs): def user_groups_changed(sender, instance: User, **kwargs):
""" """
Clear the cached clubs of the user Clear the cached groups of the user
""" """
# As a m2m relationship doesn't live within the model # As a m2m relationship doesn't live within the model
# but rather on an intermediary table, there is no # but rather on an intermediary table, there is no
# model method to override, meaning we must use # model method to override, meaning we must use
# a signal to invalidate the cache when a user is removed from a club # a signal to invalidate the cache when a user is removed from a group
cache.delete(f"user_{instance.id}_groups") cache.delete(f"user_{instance.pk}_groups")

View File

@ -162,8 +162,9 @@
<div> <div>
{% trans %}Not subscribed{% endtrans %} {% trans %}Not subscribed{% endtrans %}
{% if user.is_board_member %} {% if user.is_board_member %}
<a href="{{ url('subscription:subscription') }}?member={{ profile.id }}">{% trans %}New subscription{% endtrans <a href="{{ url('subscription:subscription') }}?member={{ profile.id }}">
%}</a> {% trans %}New subscription{% endtrans %}
</a>
{% endif %} {% endif %}
{% endif %} {% endif %}
</div> </div>

View File

@ -133,8 +133,9 @@
</p> </p>
{%- elif user.is_root -%} {%- elif user.is_root -%}
<p> <p>
<a href="{{ url('core:password_root_change', user_id=form.instance.id) }}">{%- trans -%}Change user password{%- <a href="{{ url('core:password_root_change', user_id=form.instance.id) }}">
endtrans -%}</a> {%- trans -%}Change user password{%- endtrans -%}
</a>
</p> </p>
{%- endif -%} {%- endif -%}

View File

@ -24,15 +24,15 @@
# #
import datetime import datetime
import phonenumbers
import phonenumbers
from django import template from django import template
from django.template.defaultfilters import stringfilter from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ngettext from django.utils.translation import ngettext
from core.scss.processor import ScssProcessor
from core.markdown import markdown as md from core.markdown import markdown as md
from core.scss.processor import ScssProcessor
register = template.Library() register = template.Library()

View File

@ -1,6 +1,6 @@
from django.template.exceptions import TemplateSyntaxError
from django import template from django import template
from django.template.defaultfilters import stringfilter from django.template.defaultfilters import stringfilter
from django.template.exceptions import TemplateSyntaxError
register = template.Library() register = template.Library()

View File

@ -18,10 +18,12 @@ import os
from datetime import date, timedelta from datetime import date, timedelta
import freezegun import freezegun
import pytest
from django.core.cache import cache from django.core.cache import cache
from django.test import Client, TestCase from django.test import TestCase
from django.urls import reverse from django.urls import reverse
from django.utils.timezone import now from django.utils.timezone import now
from pytest_django.asserts import assertRedirects
from club.models import Membership from club.models import Membership
from core.markdown import markdown from core.markdown import markdown
@ -29,270 +31,105 @@ from core.models import AnonymousUser, Group, Page, User
from core.utils import get_semester_code, get_start_of_semester from core.utils import get_semester_code, get_start_of_semester
from sith import settings from sith import settings
"""
to run these tests :
python3 manage.py test
"""
@pytest.mark.django_db
class UserRegistrationTest(TestCase): class TestUserRegistration:
@classmethod @pytest.fixture()
def setUpTestData(cls): def valid_payload(self):
User.objects.all().delete() return {
"first_name": "this user does not exist (yet)",
def test_register_user_form_ok(self): "last_name": "this user does not exist (yet)",
""" "email": "i-dont-exist-yet@git.an",
Should register a user correctly
"""
c = Client()
response = c.post(
reverse("core:register"),
{
"first_name": "Guy",
"last_name": "Carlier",
"email": "guy@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop", "password1": "plop",
"password2": "plop", "password2": "plop",
"captcha_0": "dummy-value", "captcha_0": "dummy-value",
"captcha_1": "PASSED", "captcha_1": "PASSED",
}, }
)
self.assertTrue(response.status_code == 200)
self.assertTrue("TEST_REGISTER_USER_FORM_OK" in str(response.content))
def test_register_user_form_fail_password(self): def test_register_user_form_ok(self, client, valid_payload):
""" """Should register a user correctly."""
Should not register a user correctly response = client.post(reverse("core:register"), valid_payload)
""" assert response.status_code == 200
c = Client() assert "TEST_REGISTER_USER_FORM_OK" in str(response.content)
response = c.post(
reverse("core:register"),
{
"first_name": "Guy",
"last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop2",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200)
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_email(self): @pytest.mark.parametrize(
""" "payload_edit",
Should not register a user correctly [
""" {"password2": "not the same as password1"},
c = Client() {"email": "not-an-email"},
response = c.post( {"first_name": ""},
reverse("core:register"), {"last_name": ""},
{ {"captcha_1": "WRONG_CAPTCHA"},
"first_name": "Guy", ],
"last_name": "Carlier",
"email": "bibou.git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
) )
self.assertTrue(response.status_code == 200) def test_register_user_form_fail(self, client, valid_payload, payload_edit):
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content)) """Should not register a user correctly."""
payload = valid_payload | payload_edit
response = client.post(reverse("core:register"), payload)
assert response.status_code == 200
assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content)
def test_register_user_form_fail_missing_name(self): def test_register_user_form_fail_already_exists(self, client, valid_payload):
""" """Should not register a user correctly if it already exists."""
Should not register a user correctly # create the user, then try to create it again
""" client.post(reverse("core:register"), valid_payload)
c = Client() response = client.post(reverse("core:register"), valid_payload)
response = c.post( assert response.status_code == 200
reverse("core:register"), assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content)
{
"first_name": "Guy",
"last_name": "",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200)
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_missing_date_of_birth(self):
"""
Should not register a user correctly
"""
c = Client()
response = c.post(
reverse("core:register"),
{
"first_name": "",
"last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200)
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_missing_first_name(self): @pytest.mark.django_db
""" class TestUserLogin:
Should not register a user correctly @pytest.fixture()
""" def user(self) -> User:
c = Client() return User.objects.first()
response = c.post(
reverse("core:register"),
{
"first_name": "",
"last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200)
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_wrong_captcha(self): def test_login_fail(self, client, user):
"""
Should not register a user correctly
"""
c = Client()
response = c.post(
reverse("core:register"),
{
"first_name": "Bibou",
"last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "WRONG_CAPTCHA",
},
)
self.assertTrue(response.status_code == 200)
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_register_user_form_fail_already_exists(self):
"""
Should not register a user correctly
"""
c = Client()
c.post(
reverse("core:register"),
{
"first_name": "Guy",
"last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
response = c.post(
reverse("core:register"),
{
"first_name": "Bibou",
"last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
self.assertTrue(response.status_code == 200)
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
def test_login_success(self):
"""
Should login a user correctly
"""
c = Client()
c.post(
reverse("core:register"),
{
"first_name": "Guy",
"last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
)
response = c.post(
reverse("core:login"), {"username": "gcarlier", "password": "plop"}
)
self.assertTrue(response.status_code == 302)
# self.assertTrue('Hello, world' in str(response.content))
def test_login_fail(self):
""" """
Should not login a user correctly Should not login a user correctly
""" """
c = Client()
c.post( response = client.post(
reverse("core:register"), reverse("core:login"),
{ {"username": user.username, "password": "wrong-password"},
"first_name": "Guy",
"last_name": "Carlier",
"email": "bibou@git.an",
"date_of_birth": "12/6/1942",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
) )
response = c.post( assert response.status_code == 200
reverse("core:login"), {"username": "gcarlier", "password": "guy"} assert (
) '<p class="alert alert-red">Votre nom d\'utilisateur '
self.assertTrue(response.status_code == 200) "et votre mot de passe ne correspondent pas. Merci de réessayer.</p>"
self.assertTrue( ) in str(response.content.decode())
"""<p class="alert alert-red">Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.</p>"""
in str(response.content) def test_login_success(self, client, user):
"""
Should login a user correctly
"""
response = client.post(
reverse("core:login"), {"username": user.username, "password": "plop"}
) )
assertRedirects(response, reverse("core:index"))
class MarkdownTest(TestCase): def test_full_markdown_syntax():
def test_full_markdown_syntax(self):
root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file: with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file:
md = md_file.read() md = md_file.read()
with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file: with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file:
html = html_file.read() html = html_file.read()
result = markdown(md) result = markdown(md)
self.assertTrue(result == html) assert result == html
class PageHandlingTest(TestCase): class PageHandlingTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.root = User.objects.get(username="root")
cls.root_group = Group.objects.get(name="Root")
def setUp(self): def setUp(self):
self.client.login(username="root", password="plop") self.client.force_login(self.root)
self.root_group = Group.objects.get(name="Root")
def test_create_page_ok(self): def test_create_page_ok(self):
""" """Should create a page correctly."""
Should create a page correctly
"""
response = self.client.post( response = self.client.post(
reverse("core:page_new"), reverse("core:page_new"),
@ -301,19 +138,17 @@ class PageHandlingTest(TestCase):
self.assertRedirects( self.assertRedirects(
response, reverse("core:page", kwargs={"page_name": "guy"}) response, reverse("core:page", kwargs={"page_name": "guy"})
) )
self.assertTrue(Page.objects.filter(name="guy").exists()) assert Page.objects.filter(name="guy").exists()
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
self.assertEqual(response.status_code, 200) assert response.status_code == 200
html = response.content.decode() html = response.content.decode()
self.assertIn('<a href="/page/guy/hist/">', html) assert '<a href="/page/guy/hist/">' in html
self.assertIn('<a href="/page/guy/edit/">', html) assert '<a href="/page/guy/edit/">' in html
self.assertIn('<a href="/page/guy/prop/">', html) assert '<a href="/page/guy/prop/">' in html
def test_create_child_page_ok(self): def test_create_child_page_ok(self):
""" """Should create a page correctly."""
Should create a page correctly
"""
# remove all other pages to make sure there is no side effect # remove all other pages to make sure there is no side effect
Page.objects.all().delete() Page.objects.all().delete()
self.client.post( self.client.post(
@ -321,7 +156,7 @@ class PageHandlingTest(TestCase):
{"parent": "", "name": "guy", "owner_group": str(self.root_group.id)}, {"parent": "", "name": "guy", "owner_group": str(self.root_group.id)},
) )
page = Page.objects.first() page = Page.objects.first()
response = self.client.post( self.client.post(
reverse("core:page_new"), reverse("core:page_new"),
{ {
"parent": str(page.id), "parent": str(page.id),
@ -332,8 +167,8 @@ class PageHandlingTest(TestCase):
response = self.client.get( response = self.client.get(
reverse("core:page", kwargs={"page_name": "guy/bibou"}) reverse("core:page", kwargs={"page_name": "guy/bibou"})
) )
self.assertTrue(response.status_code == 200) assert response.status_code == 200
self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content)) assert '<a href="/page/guy/bibou/">' in str(response.content)
def test_access_child_page_ok(self): def test_access_child_page_ok(self):
""" """
@ -346,7 +181,7 @@ class PageHandlingTest(TestCase):
response = self.client.get( response = self.client.get(
reverse("core:page", kwargs={"page_name": "guy/bibou"}) reverse("core:page", kwargs={"page_name": "guy/bibou"})
) )
self.assertTrue(response.status_code == 200) assert response.status_code == 200
html = response.content.decode() html = response.content.decode()
self.assertIn('<a href="/page/guy/bibou/edit/">', html) self.assertIn('<a href="/page/guy/bibou/edit/">', html)
@ -355,7 +190,7 @@ class PageHandlingTest(TestCase):
Should not display a page correctly Should not display a page correctly
""" """
response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"})) response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"}))
self.assertTrue(response.status_code == 200) assert response.status_code == 200
html = response.content.decode() html = response.content.decode()
self.assertIn('<a href="/page/create/?page=swagg">', html) self.assertIn('<a href="/page/create/?page=swagg">', html)
@ -383,8 +218,8 @@ http://git.an
}, },
) )
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"})) response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
self.assertTrue(response.status_code == 200) assert response.status_code == 200
self.assertTrue( assert (
'<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n' '<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n'
+ "<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;" + "<h1>Swag</h1>\\n&lt;guy&gt;Bibou&lt;/guy&gt;"
+ "&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;" + "&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;"
@ -392,35 +227,19 @@ http://git.an
) )
class UserToolsTest(TestCase): class UserToolsTest:
def test_anonymous_user_unauthorized(self): def test_anonymous_user_unauthorized(self, client):
response = self.client.get(reverse("core:user_tools")) """An anonymous user shouldn't have access to the tools page"""
self.assertEqual(response.status_code, 403) response = client.get(reverse("core:user_tools"))
assert response.status_code == 403
def test_page_is_working(self): @pytest.mark.parametrize("username", ["guy", "root", "skia", "comunity"])
def test_page_is_working(self, client, username):
"""All existing users should be able to see the test page"""
# Test for simple user # Test for simple user
self.client.login(username="guy", password="plop") client.force_login(User.objects.get(username=username))
response = self.client.get(reverse("core:user_tools")) response = client.get(reverse("core:user_tools"))
self.assertNotEqual(response.status_code, 500) assert response.status_code == 200
self.assertEqual(response.status_code, 200)
# Test for root
self.client.login(username="root", password="plop")
response = self.client.get(reverse("core:user_tools"))
self.assertNotEqual(response.status_code, 500)
self.assertEqual(response.status_code, 200)
# Test for skia
self.client.login(username="skia", password="plop")
response = self.client.get(reverse("core:user_tools"))
self.assertNotEqual(response.status_code, 500)
self.assertEqual(response.status_code, 200)
# Test for comunity
self.client.login(username="comunity", password="plop")
response = self.client.get(reverse("core:user_tools"))
self.assertNotEqual(response.status_code, 500)
self.assertEqual(response.status_code, 200)
# TODO: many tests on the pages: # TODO: many tests on the pages:
@ -442,12 +261,12 @@ class FileHandlingTest(TestCase):
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}), reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
{"folder_name": "GUY_folder_test"}, {"folder_name": "GUY_folder_test"},
) )
self.assertTrue(response.status_code == 302) assert response.status_code == 302
response = self.client.get( response = self.client.get(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}) reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
) )
self.assertTrue(response.status_code == 200) assert response.status_code == 200
self.assertTrue("GUY_folder_test</a>" in str(response.content)) assert "GUY_folder_test</a>" in str(response.content)
def test_upload_file_home(self): def test_upload_file_home(self):
with open("/bin/ls", "rb") as f: with open("/bin/ls", "rb") as f:
@ -457,12 +276,12 @@ class FileHandlingTest(TestCase):
), ),
{"file_field": f}, {"file_field": f},
) )
self.assertTrue(response.status_code == 302) assert response.status_code == 302
response = self.client.get( response = self.client.get(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}) reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
) )
self.assertTrue(response.status_code == 200) assert response.status_code == 200
self.assertTrue("ls</a>" in str(response.content)) assert "ls</a>" in str(response.content)
class UserIsInGroupTest(TestCase): class UserIsInGroupTest(TestCase):
@ -477,6 +296,10 @@ class UserIsInGroupTest(TestCase):
cls.root_group = Group.objects.get(name="Root") cls.root_group = Group.objects.get(name="Root")
cls.public = Group.objects.get(name="Public") cls.public = Group.objects.get(name="Public")
cls.skia = User.objects.get(username="skia")
cls.toto = User.objects.create(
username="toto", first_name="a", last_name="b", email="a.b@toto.fr"
)
cls.subscribers = Group.objects.get(name="Subscribers") cls.subscribers = Group.objects.get(name="Subscribers")
cls.old_subscribers = Group.objects.get(name="Old subscribers") cls.old_subscribers = Group.objects.get(name="Old subscribers")
cls.accounting_admin = Group.objects.get(name="Accounting admin") cls.accounting_admin = Group.objects.get(name="Accounting admin")
@ -493,21 +316,15 @@ class UserIsInGroupTest(TestCase):
) )
cls.main_club = Club.objects.get(id=1) cls.main_club = Club.objects.get(id=1)
def setUp(self) -> None:
self.toto = User.objects.create(
username="toto", first_name="a", last_name="b", email="a.b@toto.fr"
)
self.skia = User.objects.get(username="skia")
def assert_in_public_group(self, user): def assert_in_public_group(self, user):
self.assertTrue(user.is_in_group(pk=self.public.id)) assert user.is_in_group(pk=self.public.id)
self.assertTrue(user.is_in_group(name=self.public.name)) assert user.is_in_group(name=self.public.name)
def assert_in_club_metagroups(self, user, club): def assert_in_club_metagroups(self, user, club):
meta_groups_board = club.unix_name + settings.SITH_BOARD_SUFFIX meta_groups_board = club.unix_name + settings.SITH_BOARD_SUFFIX
meta_groups_members = club.unix_name + settings.SITH_MEMBER_SUFFIX meta_groups_members = club.unix_name + settings.SITH_MEMBER_SUFFIX
self.assertFalse(user.is_in_group(name=meta_groups_board)) assert user.is_in_group(name=meta_groups_board) is False
self.assertFalse(user.is_in_group(name=meta_groups_members)) assert user.is_in_group(name=meta_groups_members) is False
def assert_only_in_public_group(self, user): def assert_only_in_public_group(self, user):
self.assert_in_public_group(user) self.assert_in_public_group(user)
@ -519,12 +336,12 @@ class UserIsInGroupTest(TestCase):
self.subscribers, self.subscribers,
self.old_subscribers, self.old_subscribers,
): ):
self.assertFalse(user.is_in_group(pk=group.pk)) assert not user.is_in_group(pk=group.pk)
self.assertFalse(user.is_in_group(name=group.name)) assert not user.is_in_group(name=group.name)
meta_groups_board = self.club.unix_name + settings.SITH_BOARD_SUFFIX meta_groups_board = self.club.unix_name + settings.SITH_BOARD_SUFFIX
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
self.assertFalse(user.is_in_group(name=meta_groups_board)) assert user.is_in_group(name=meta_groups_board) is False
self.assertFalse(user.is_in_group(name=meta_groups_members)) assert user.is_in_group(name=meta_groups_members) is False
def test_anonymous_user(self): def test_anonymous_user(self):
""" """
@ -583,15 +400,13 @@ class UserIsInGroupTest(TestCase):
) )
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
cache.clear() cache.clear()
self.assertTrue(self.toto.is_in_group(name=meta_groups_members)) assert self.toto.is_in_group(name=meta_groups_members) is True
self.assertEqual( assert membership == cache.get(f"membership_{self.club.id}_{self.toto.id}")
membership, cache.get(f"membership_{self.club.id}_{self.toto.id}")
)
membership.end_date = now() - timedelta(minutes=5) membership.end_date = now() - timedelta(minutes=5)
membership.save() membership.save()
cached_membership = cache.get(f"membership_{self.club.id}_{self.toto.id}") cached_membership = cache.get(f"membership_{self.club.id}_{self.toto.id}")
self.assertEqual(cached_membership, "not_member") assert cached_membership == "not_member"
self.assertFalse(self.toto.is_in_group(name=meta_groups_members)) assert self.toto.is_in_group(name=meta_groups_members) is False
def test_cache_properly_cleared_group(self): def test_cache_properly_cleared_group(self):
""" """
@ -600,24 +415,24 @@ class UserIsInGroupTest(TestCase):
""" """
# testing with pk # testing with pk
self.toto.groups.add(self.com_admin.pk) self.toto.groups.add(self.com_admin.pk)
self.assertTrue(self.toto.is_in_group(pk=self.com_admin.pk)) assert self.toto.is_in_group(pk=self.com_admin.pk) is True
self.toto.groups.remove(self.com_admin.pk) self.toto.groups.remove(self.com_admin.pk)
self.assertFalse(self.toto.is_in_group(pk=self.com_admin.pk)) assert self.toto.is_in_group(pk=self.com_admin.pk) is False
# testing with name # testing with name
self.toto.groups.add(self.sas_admin.pk) self.toto.groups.add(self.sas_admin.pk)
self.assertTrue(self.toto.is_in_group(name="SAS admin")) assert self.toto.is_in_group(name="SAS admin") is True
self.toto.groups.remove(self.sas_admin.pk) self.toto.groups.remove(self.sas_admin.pk)
self.assertFalse(self.toto.is_in_group(name="SAS admin")) assert self.toto.is_in_group(name="SAS admin") is False
def test_not_existing_group(self): def test_not_existing_group(self):
""" """
Test that searching for a not existing group Test that searching for a not existing group
returns False returns False
""" """
self.assertFalse(self.skia.is_in_group(name="This doesn't exist")) assert self.skia.is_in_group(name="This doesn't exist") is False
class DateUtilsTest(TestCase): class DateUtilsTest(TestCase):
@ -639,29 +454,25 @@ class DateUtilsTest(TestCase):
""" """
Test that the get_semester function returns the correct semester string Test that the get_semester function returns the correct semester string
""" """
self.assertEqual(get_semester_code(self.autumn_semester_january), "A24") assert get_semester_code(self.autumn_semester_january) == "A24"
self.assertEqual(get_semester_code(self.autumn_semester_september), "A24") assert get_semester_code(self.autumn_semester_september) == "A24"
self.assertEqual(get_semester_code(self.autumn_first_day), "A24") assert get_semester_code(self.autumn_first_day) == "A24"
self.assertEqual(get_semester_code(self.spring_semester_march), "P23") assert get_semester_code(self.spring_semester_march) == "P23"
self.assertEqual(get_semester_code(self.spring_first_day), "P23") assert get_semester_code(self.spring_first_day) == "P23"
def test_get_start_of_semester_fixed_date(self): def test_get_start_of_semester_fixed_date(self):
""" """
Test that the get_start_of_semester correctly the starting date of the semester. Test that the get_start_of_semester correctly the starting date of the semester.
""" """
automn_2024 = date(2024, self.autumn_month, self.autumn_day) automn_2024 = date(2024, self.autumn_month, self.autumn_day)
self.assertEqual( assert get_start_of_semester(self.autumn_semester_january) == automn_2024
get_start_of_semester(self.autumn_semester_january), automn_2024 assert get_start_of_semester(self.autumn_semester_september) == automn_2024
) assert get_start_of_semester(self.autumn_first_day) == automn_2024
self.assertEqual(
get_start_of_semester(self.autumn_semester_september), automn_2024
)
self.assertEqual(get_start_of_semester(self.autumn_first_day), automn_2024)
spring_2023 = date(2023, self.spring_month, self.spring_day) spring_2023 = date(2023, self.spring_month, self.spring_day)
self.assertEqual(get_start_of_semester(self.spring_semester_march), spring_2023) assert get_start_of_semester(self.spring_semester_march) == spring_2023
self.assertEqual(get_start_of_semester(self.spring_first_day), spring_2023) assert get_start_of_semester(self.spring_first_day) == spring_2023
def test_get_start_of_semester_today(self): def test_get_start_of_semester_today(self):
""" """
@ -669,10 +480,10 @@ class DateUtilsTest(TestCase):
when no date is given when no date is given
""" """
with freezegun.freeze_time(self.autumn_semester_september): with freezegun.freeze_time(self.autumn_semester_september):
self.assertEqual(get_start_of_semester(), self.autumn_first_day) assert get_start_of_semester() == self.autumn_first_day
with freezegun.freeze_time(self.spring_semester_march): with freezegun.freeze_time(self.spring_semester_march):
self.assertEqual(get_start_of_semester(), self.spring_first_day) assert get_start_of_semester() == self.spring_first_day
def test_get_start_of_semester_changing_date(self): def test_get_start_of_semester_changing_date(self):
""" """
@ -685,8 +496,8 @@ class DateUtilsTest(TestCase):
mid_autumn = autumn_2023 + timedelta(days=45) mid_autumn = autumn_2023 + timedelta(days=45)
with freezegun.freeze_time(mid_spring) as frozen_time: with freezegun.freeze_time(mid_spring) as frozen_time:
self.assertEqual(get_start_of_semester(), spring_2023) assert get_start_of_semester() == spring_2023
# forward time to the middle of the next semester # forward time to the middle of the next semester
frozen_time.move_to(mid_autumn) frozen_time.move_to(mid_autumn)
self.assertEqual(get_start_of_semester(), autumn_2023) assert get_start_of_semester() == autumn_2023

View File

@ -25,12 +25,12 @@
from django.urls import path, re_path, register_converter from django.urls import path, re_path, register_converter
from core.views import *
from core.converters import ( from core.converters import (
BooleanStringConverter,
FourDigitYearConverter, FourDigitYearConverter,
TwoDigitMonthConverter, TwoDigitMonthConverter,
BooleanStringConverter,
) )
from core.views import *
register_converter(FourDigitYearConverter, "yyyy") register_converter(FourDigitYearConverter, "yyyy")
register_converter(TwoDigitMonthConverter, "mm") register_converter(TwoDigitMonthConverter, "mm")

View File

@ -26,8 +26,9 @@ from typing import Optional
import PIL import PIL
from django.conf import settings from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from PIL import ExifTags
from django.utils import timezone from django.utils import timezone
from PIL import ExifTags
from PIL.Image import Resampling
def get_git_revision_short_hash() -> str: def get_git_revision_short_hash() -> str:
@ -109,7 +110,8 @@ def resize_image(im, edge, format):
(w, h) = im.size (w, h) = im.size
(width, height) = scale_dimension(w, h, long_edge=edge) (width, height) = scale_dimension(w, h, long_edge=edge)
content = BytesIO() content = BytesIO()
im = im.resize((width, height), PIL.Image.ANTIALIAS) # use the lanczos filter for antialiasing
im = im.resize((width, height), Resampling.LANCZOS)
try: try:
im.save( im.save(
fp=content, fp=content,

View File

@ -25,26 +25,21 @@
import types import types
from sentry_sdk import last_event_id from django.core.exceptions import (
from django.shortcuts import render ImproperlyConfigured,
PermissionDenied,
)
from django.http import ( from django.http import (
HttpResponseForbidden, HttpResponseForbidden,
HttpResponseNotFound, HttpResponseNotFound,
HttpResponseServerError, HttpResponseServerError,
) )
from django.template import RequestContext
from django.core.exceptions import (
PermissionDenied,
ObjectDoesNotExist,
ImproperlyConfigured,
)
from django.views.generic.base import View
from django.views.generic.edit import FormView
from django.views.generic.detail import SingleObjectMixin
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.db.models import Count from django.views.generic.base import View
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import FormView
from sentry_sdk import last_event_id
from core.models import Group
from core.views.forms import LoginForm from core.views.forms import LoginForm
@ -314,9 +309,8 @@ class QuickNotifMixin:
quick_notif_list = [] quick_notif_list = []
def dispatch(self, request, *arg, **kwargs): def dispatch(self, request, *arg, **kwargs):
self.quick_notif_list = ( # In some cases, the class can stay instanciated, so we need to reset the list
[] self.quick_notif_list = []
) # In some cases, the class can stay instanciated, so we need to reset the list
return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs) return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs)
def get_success_url(self): def get_success_url(self):
@ -362,8 +356,8 @@ class DetailFormView(SingleObjectMixin, FormView):
return super(DetailFormView, self).get_object() return super(DetailFormView, self).get_object()
from .user import *
from .page import *
from .files import * from .files import *
from .site import *
from .group import * from .group import *
from .page import *
from .site import *
from .user import *

View File

@ -15,29 +15,28 @@
# #
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
from django.shortcuts import redirect, get_object_or_404
from django.utils.http import http_date
from django.views.generic import ListView, DetailView, TemplateView
from django.views.generic.edit import UpdateView, FormMixin, DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.forms.models import modelform_factory
from django.conf import settings
from django.utils.translation import gettext_lazy as _
from django.http import Http404, HttpResponse
from wsgiref.util import FileWrapper
from django.urls import reverse
from django.core.exceptions import PermissionDenied
from django import forms
import os import os
from wsgiref.util import FileWrapper
from ajax_select import make_ajax_field from ajax_select import make_ajax_field
from django import forms
from django.conf import settings
from django.core.exceptions import PermissionDenied
from django.forms.models import modelform_factory
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.http import http_date
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, TemplateView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import DeleteView, FormMixin, UpdateView
from core.models import SithFile, RealGroup, Notification from core.models import Notification, RealGroup, SithFile
from core.views import ( from core.views import (
CanViewMixin,
CanEditMixin, CanEditMixin,
CanEditPropMixin, CanEditPropMixin,
CanViewMixin,
can_view, can_view,
) )
from counter.models import Counter from counter.models import Counter
@ -79,12 +78,35 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
return response return response
class MultipleFileInput(forms.ClearableFileInput):
allow_multiple_selected = True
class _MultipleFieldMixin:
def __init__(self, *args, **kwargs):
kwargs.setdefault("widget", MultipleFileInput())
super().__init__(*args, **kwargs)
def clean(self, data, initial=None):
single_file_clean = super().clean
if isinstance(data, (list, tuple)):
result = [single_file_clean(d, initial) for d in data]
else:
result = [single_file_clean(data, initial)]
return result
class MultipleFileField(_MultipleFieldMixin, forms.FileField): ...
class MultipleImageField(_MultipleFieldMixin, forms.ImageField): ...
class AddFilesForm(forms.Form): class AddFilesForm(forms.Form):
folder_name = forms.CharField( folder_name = forms.CharField(
label=_("Add a new folder"), max_length=30, required=False label=_("Add a new folder"), max_length=30, required=False
) )
file_field = forms.FileField( file_field = MultipleFileField(
widget=forms.ClearableFileInput(attrs={"multiple": True}),
label=_("Files"), label=_("Files"),
required=False, required=False,
) )

View File

@ -21,40 +21,37 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
import datetime
import re
from io import BytesIO
from ajax_select import make_ajax_field
from ajax_select.fields import AutoCompleteSelectField
from captcha.fields import CaptchaField from captcha.fields import CaptchaField
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django import forms from django import forms
from django.conf import settings from django.conf import settings
from django.db import transaction from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.templatetags.static import static
from django.urls import reverse
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.db import transaction
from django.forms import ( from django.forms import (
CheckboxSelectMultiple, CheckboxSelectMultiple,
Select,
DateInput, DateInput,
TextInput,
DateTimeInput, DateTimeInput,
Textarea, Textarea,
TextInput,
) )
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
from ajax_select.fields import AutoCompleteSelectField
from ajax_select import make_ajax_field
from django.utils.dateparse import parse_datetime
from django.utils import timezone
import datetime
from django.forms.utils import to_current_timezone from django.forms.utils import to_current_timezone
from django.templatetags.static import static
import re from django.urls import reverse
from django.utils import timezone
from core.models import User, Page, SithFile, Gift from django.utils.dateparse import parse_datetime
from django.utils.translation import gettext
from core.utils import resize_image from django.utils.translation import gettext_lazy as _
from io import BytesIO from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
from PIL import Image from PIL import Image
from core.models import Gift, Page, SithFile, User
from core.utils import resize_image
# Widgets # Widgets

View File

@ -18,15 +18,12 @@
This module contains views to manage Groups This module contains views to manage Groups
""" """
from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.views.generic import ListView
from django.views.generic.edit import FormView
from django.urls import reverse_lazy
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from django import forms
from ajax_select.fields import AutoCompleteSelectMultipleField from ajax_select.fields import AutoCompleteSelectMultipleField
from django import forms
from django.urls import reverse_lazy
from django.utils.translation import gettext_lazy as _
from django.views.generic import ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from core.models import RealGroup, User from core.models import RealGroup, User
from core.views import CanCreateMixin, CanEditMixin, DetailFormView from core.views import CanCreateMixin, CanEditMixin, DetailFormView

View File

@ -15,16 +15,16 @@
# #
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
from django.urls import reverse_lazy
from django.views.generic import ListView, DetailView
from django.views.generic.edit import UpdateView, CreateView, DeleteView
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.http import Http404 from django.http import Http404
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.views.generic import DetailView, ListView
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from core.models import Page, PageRev, LockError from core.models import LockError, Page, PageRev
from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin
from core.views.forms import MarkdownInput, PageForm, PagePropForm from core.views.forms import MarkdownInput, PageForm, PagePropForm
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin
class CanEditPagePropMixin(CanEditPropMixin): class CanEditPagePropMixin(CanEditPropMixin):

View File

@ -23,23 +23,22 @@
# #
# #
from django.shortcuts import render, redirect
from django.http import JsonResponse
from django.core import serializers
from django.contrib.auth.decorators import login_required
from django.utils import html
from django.views.generic import ListView, TemplateView
from django.conf import settings
from django.utils.text import slugify
from django.db.models.query import QuerySet
import json import json
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.core import serializers
from django.db.models.query import QuerySet
from django.http import JsonResponse
from django.shortcuts import redirect, render
from django.utils import html
from django.utils.text import slugify
from django.views.generic import ListView, TemplateView
from haystack.query import SearchQuerySet from haystack.query import SearchQuerySet
from core.models import User, Notification
from core.utils import doku_to_markdown, bbcode_to_markdown
from club.models import Club from club.models import Club
from core.models import Notification, User
from core.utils import bbcode_to_markdown, doku_to_markdown
def index(request, context=None): def index(request, context=None):
@ -100,9 +99,8 @@ def search_club(query, as_json=False):
if query: if query:
clubs = Club.objects.filter(name__icontains=query).all() clubs = Club.objects.filter(name__icontains=query).all()
clubs = clubs[:5] clubs = clubs[:5]
if ( if as_json:
as_json # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
): # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
clubs = json.loads(serializers.serialize("json", clubs, fields=("name"))) clubs = json.loads(serializers.serialize("json", clubs, fields=("name")))
else: else:
clubs = list(clubs) clubs = list(clubs)

View File

@ -24,50 +24,49 @@
# #
# This file contains all the views that concern the user model # This file contains all the views that concern the user model
from django.shortcuts import render, redirect, get_object_or_404 import logging
from datetime import date, timedelta
from django.conf import settings
from django.contrib.auth import views from django.contrib.auth import views
from django.contrib.auth.forms import PasswordChangeForm from django.contrib.auth.forms import PasswordChangeForm
from django.utils.translation import gettext as _
from django.urls import reverse
from django.core.exceptions import PermissionDenied, ValidationError from django.core.exceptions import PermissionDenied, ValidationError
from django.forms import CheckboxSelectMultiple
from django.forms.models import modelform_factory
from django.http import Http404, HttpResponse from django.http import Http404, HttpResponse
from django.views.generic.edit import UpdateView from django.shortcuts import get_object_or_404, redirect, render
from django.template.response import TemplateResponse
from django.urls import reverse, reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import ( from django.views.generic import (
ListView,
DetailView,
TemplateView,
CreateView, CreateView,
DeleteView, DeleteView,
DetailView,
ListView,
TemplateView,
) )
from django.forms.models import modelform_factory from django.views.generic.dates import MonthMixin, YearMixin
from django.forms import CheckboxSelectMultiple from django.views.generic.edit import UpdateView
from django.urls import reverse_lazy
from django.template.response import TemplateResponse
from django.conf import settings
from django.views.generic.dates import YearMixin, MonthMixin
from datetime import timedelta, date
import logging
from api.views.sas import all_pictures_of_user from api.views.sas import all_pictures_of_user
from core.models import Gift, Preferences, SithFile, User
from core.views import ( from core.views import (
CanViewMixin,
CanEditMixin, CanEditMixin,
CanEditPropMixin, CanEditPropMixin,
UserIsLoggedMixin, CanViewMixin,
TabedViewMixin,
QuickNotifMixin, QuickNotifMixin,
TabedViewMixin,
UserIsLoggedMixin,
) )
from core.views.forms import ( from core.views.forms import (
RegisteringForm,
UserProfileForm,
LoginForm,
UserGodfathersForm,
GiftForm, GiftForm,
LoginForm,
RegisteringForm,
UserGodfathersForm,
UserProfileForm,
) )
from core.models import User, SithFile, Preferences, Gift
from subscription.models import Subscription
from counter.forms import StudentCardForm from counter.forms import StudentCardForm
from subscription.models import Subscription
from trombi.views import UserTrombiForm from trombi.views import UserTrombiForm
@ -501,9 +500,10 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super(UserStatsView, self).get_context_data(**kwargs) kwargs = super(UserStatsView, self).get_context_data(**kwargs)
from counter.models import Counter
from django.db.models import Sum from django.db.models import Sum
from counter.models import Counter
foyer = Counter.objects.filter(name="Foyer").first() foyer = Counter.objects.filter(name="Foyer").first()
mde = Counter.objects.filter(name="MDE").first() mde = Counter.objects.filter(name="MDE").first()
gommette = Counter.objects.filter(name="La Gommette").first() gommette = Counter.objects.filter(name="La Gommette").first()
@ -601,10 +601,12 @@ class UserUploadProfilePictView(CanEditMixin, DetailView):
template_name = "core/user_edit.jinja" template_name = "core/user_edit.jinja"
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
from core.utils import resize_image
from io import BytesIO from io import BytesIO
from PIL import Image from PIL import Image
from core.utils import resize_image
self.object = self.get_object() self.object = self.get_object()
if self.object.profile_pict: if self.object.profile_pict:
raise ValidationError(_("User already has a profile picture")) raise ValidationError(_("User already has a profile picture"))

View File

@ -31,4 +31,4 @@ class CounterConfig(AppConfig):
verbose_name = _("counter") verbose_name = _("counter")
def ready(self): def ready(self):
import counter.signals import counter.signals # noqa F401

View File

@ -3,15 +3,15 @@ from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultip
from django import forms from django import forms
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from core.views.forms import TzAwareDateTimeField, SelectDate from core.views.forms import SelectDate, TzAwareDateTimeField
from counter.models import ( from counter.models import (
BillingInfo, BillingInfo,
StudentCard,
Customer,
Refilling,
Counter, Counter,
Product, Customer,
Eticket, Eticket,
Product,
Refilling,
StudentCard,
) )

View File

@ -1,11 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import unicode_literals from __future__ import unicode_literals
from django.db import migrations, models
import accounting.models
import django.db.models.deletion import django.db.models.deletion
from django.conf import settings from django.conf import settings
import django.db.models.deletion from django.db import migrations, models
import accounting.models
class Migration(migrations.Migration): class Migration(migrations.Migration):

Some files were not shown because too many files have changed in this diff Show More