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
uses: awalsh128/cache-apt-pkgs-action@latest
with:
packages: gettext libxapian-dev libgraphviz-dev
packages: gettext libgraphviz-dev
version: 1.0 # increment to reset cache
- name: Install dependencies
run: |
sudo apt update
sudo apt install gettext libxapian-dev libgraphviz-dev
sudo apt install gettext libgraphviz-dev
shell: bash
- name: Set up python
@ -45,7 +45,11 @@ runs:
${{ runner.os }}-poetry-
- 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
- name: Compile gettext messages

View File

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

View File

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

View File

@ -37,6 +37,7 @@ jobs:
git pull
poetry install
poetry run ./manage.py install_xapian
poetry run ./manage.py migrate
echo "yes" | poetry run ./manage.py collectstatic
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 *
admin.site.register(BankAccount)
admin.site.register(ClubAccount)
admin.site.register(GeneralJournal)

View File

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

View File

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

View File

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

View File

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

View File

@ -14,19 +14,19 @@
#
#
from django.urls import reverse
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 decimal import Decimal
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 decimal import Decimal
from core.models import User, SithFile
from club.models import Club
from core.models import SithFile, User
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 core.models import User
from django.test import TestCase
from django.urls import reverse
from accounting.models import (
GeneralJournal,
Operation,
Label,
AccountingType,
GeneralJournal,
Label,
Operation,
SimplifiedAccountingType,
)
from core.models import User
class RefoundAccountTest(TestCase):
def setUp(self):
self.skia = User.objects.filter(username="skia").first()
@classmethod
def setUpTestData(cls):
cls.skia = User.objects.get(username="skia")
# reffil skia's account
self.skia.customer.amount = 800
self.skia.customer.save()
cls.skia.customer.amount = 800
cls.skia.customer.save()
cls.refound_account_url = reverse("accounting:refound_account")
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(
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"))
self.assertTrue(response_get.status_code == 403)
self.assertTrue(response_post.status_code == 403)
response_get = self.client.get(self.refound_account_url)
assert response_get.status_code == 403
assert response_post.status_code == 403
def test_root_granteed(self):
self.client.login(username="root", password="plop")
response_post = self.client.post(
reverse("accounting:refound_account"), {"user": self.skia.id}
)
self.skia = User.objects.filter(username="skia").first()
response_get = self.client.get(reverse("accounting:refound_account"))
self.assertFalse(response_get.status_code == 403)
self.assertTrue('<form action="" method="post">' in str(response_get.content))
self.assertFalse(response_post.status_code == 403)
self.assertTrue(self.skia.customer.amount == 0)
self.client.force_login(User.objects.get(username="root"))
response = self.client.post(self.refound_account_url, {"user": self.skia.id})
self.assertRedirects(response, self.refound_account_url)
self.skia.refresh_from_db()
response = self.client.get(self.refound_account_url)
assert response.status_code == 200
assert '<form action="" method="post">' in str(response.content)
assert self.skia.customer.amount == 0
def test_comptable_granteed(self):
self.client.login(username="comptable", password="plop")
response_post = self.client.post(
reverse("accounting:refound_account"), {"user": self.skia.id}
)
self.skia = User.objects.filter(username="skia").first()
response_get = self.client.get(reverse("accounting:refound_account"))
self.assertFalse(response_get.status_code == 403)
self.assertTrue('<form action="" method="post">' in str(response_get.content))
self.assertFalse(response_post.status_code == 403)
self.assertTrue(self.skia.customer.amount == 0)
self.client.force_login(User.objects.get(username="comptable"))
response = self.client.post(self.refound_account_url, {"user": self.skia.id})
self.assertRedirects(response, self.refound_account_url)
self.skia.refresh_from_db()
response = self.client.get(self.refound_account_url)
assert response.status_code == 200
assert '<form action="" method="post">' in str(response.content)
assert self.skia.customer.amount == 0
class JournalTest(TestCase):
def setUp(self):
self.journal = GeneralJournal.objects.filter(id=1).first()
@classmethod
def setUpTestData(cls):
cls.journal = GeneralJournal.objects.get(id=1)
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(
reverse("accounting:journal_details", args=[self.journal.id])
)
self.assertTrue(response_get.status_code == 200)
self.assertTrue(
"<td>M\\xc3\\xa9thode de paiement</td>" in str(response_get.content)
)
assert response_get.status_code == 200
assert "<td>M\\xc3\\xa9thode de paiement</td>" in str(response_get.content)
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(
reverse("accounting:journal_details", args=[self.journal.id])
)
self.assertTrue(response_get.status_code == 403)
self.assertFalse(
"<td>M\xc3\xa9thode de paiement</td>" in str(response_get.content)
)
assert response_get.status_code == 403
assert "<td>M\xc3\xa9thode de paiement</td>" not in str(response_get.content)
class OperationTest(TestCase):
@ -108,9 +103,8 @@ class OperationTest(TestCase):
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
)
at.save()
l = Label(club_account=self.journal.club_account, name="bob")
l.save()
self.client.login(username="comptable", password="plop")
l = Label.objects.create(club_account=self.journal.club_account, name="bob")
self.client.force_login(User.objects.get(username="comptable"))
self.op1 = Operation(
journal=self.journal,
date=date.today(),
@ -139,8 +133,7 @@ class OperationTest(TestCase):
self.op2.save()
def test_new_operation(self):
self.client.login(username="comptable", password="plop")
at = AccountingType.objects.filter(code="604").first()
at = AccountingType.objects.get(code="604")
response = self.client.post(
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))
def test_bad_new_operation(self):
self.client.login(username="comptable", password="plop")
AccountingType.objects.filter(code="604").first()
AccountingType.objects.get(code="604")
response = self.client.post(
reverse("accounting:op_new", args=[self.journal.id]),
{
@ -199,7 +191,7 @@ class OperationTest(TestCase):
)
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()
response = self.client.post(
reverse("accounting:op_new", args=[self.journal.id]),
@ -226,7 +218,6 @@ class OperationTest(TestCase):
)
def test__operation_simple_accounting(self):
self.client.login(username="comptable", password="plop")
sat = SimplifiedAccountingType.objects.all().first()
response = self.client.post(
reverse("accounting:op_new", args=[self.journal.id]),
@ -263,14 +254,12 @@ class OperationTest(TestCase):
)
def test_nature_statement(self):
self.client.login(username="comptable", password="plop")
response = self.client.get(
reverse("accounting:journal_nature_statement", args=[self.journal.id])
)
self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200)
def test_person_statement(self):
self.client.login(username="comptable", password="plop")
response = self.client.get(
reverse("accounting:journal_person_statement", args=[self.journal.id])
)
@ -292,7 +281,6 @@ class OperationTest(TestCase):
)
def test_accounting_statement(self):
self.client.login(username="comptable", password="plop")
response = self.client.get(
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
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 (
AccountingType,
BankAccount,
ClubAccount,
GeneralJournal,
Operation,
AccountingType,
Company,
SimplifiedAccountingType,
GeneralJournal,
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
@ -521,14 +521,14 @@ class OperationPDFView(CanViewMixin, DetailView):
pk_url_kwarg = "op_id"
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.pagesizes import letter
from reportlab.lib.units import cm
from reportlab.lib.utils import ImageReader
from reportlab.pdfbase.ttfonts import TTFont
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"))
@ -599,7 +599,7 @@ class OperationPDFView(CanViewMixin, DetailView):
payment_mode = ""
for m in settings.SITH_ACCOUNTING_PAYMENT_METHOD:
if m[0] == mode:
payment_mode += "[\u00D7]"
payment_mode += "[\u00d7]"
else:
payment_mode += "[ ]"
payment_mode += " %s\n" % (m[1])

View File

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

View File

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

View File

@ -14,6 +14,4 @@
#
#
from django.test import TestCase
# 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 rest_framework import routers
# Router config
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 rest_framework.decorators import action
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):
@ -64,10 +64,10 @@ class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet):
from .api import *
from .counter import *
from .user import *
from .club import *
from .counter import *
from .group import *
from .launderette import *
from .uv 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.renderers import StaticHTMLRenderer
from rest_framework.response import Response
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.decorators import api_view, renderer_classes
from rest_framework.renderers import StaticHTMLRenderer
from django.conf import settings
from django.core.exceptions import PermissionDenied
from club.models import Club, Mailing
from rest_framework.response import Response
from api.views import RightModelViewSet
from club.models import Club, Mailing
class ClubSerializer(serializers.ModelSerializer):

View File

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

View File

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

View File

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

View File

@ -1,4 +1,5 @@
from typing import List
from rest_framework.decorators import api_view, renderer_classes
from rest_framework.exceptions import PermissionDenied
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.response import Response
from core.views import can_edit
from core.models import User
from core.views import can_edit
from sas.models import Picture

View File

@ -17,12 +17,11 @@
import datetime
from rest_framework import serializers
from rest_framework.response import Response
from rest_framework.decorators import action
from core.models import User
from rest_framework.response import Response
from api.views import RightModelViewSet
from core.models import User
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.renderers import JSONRenderer
from django.core.exceptions import PermissionDenied
from django.conf import settings
from rest_framework import serializers
import urllib.request
import json
from rest_framework.response import Response
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.conf import settings
from django.utils.translation import gettext_lazy as _
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
from club.models import Mailing, MailingSubscription, Club, Membership
from club.models import Club, Mailing, MailingSubscription, Membership
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 core.views.forms import TzAwareDateTimeField
class ClubEditForm(forms.ModelForm):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,21 +24,19 @@
#
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.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.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.utils import timezone
from django.core.validators import RegexValidator, validate_email
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.
@ -209,11 +207,11 @@ class Club(models.Model):
cache.set(f"sith_club_{self.unix_name}", self)
def delete(self, *args, **kwargs):
super().delete(*args, **kwargs)
# Invalidate the cache of this club and of its memberships
for membership in self.members.ongoing().select_related("user"):
cache.delete(f"membership_{self.id}_{membership.user.id}")
cache.delete(f"sith_club_{self.unix_name}")
super().delete(*args, **kwargs)
def __str__(self):
return self.name

File diff suppressed because it is too large Load Diff

View File

@ -26,50 +26,42 @@
import csv
from django.conf import settings
from django import forms
from django.views.generic import ListView, DetailView, TemplateView, View
from django.views.generic.edit import DeleteView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import UpdateView, CreateView
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
from django.core.paginator import InvalidPage, Paginator
from django.db.models import Sum
from django.http import (
HttpResponseRedirect,
HttpResponse,
Http404,
HttpResponseRedirect,
StreamingHttpResponse,
)
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.utils.translation import gettext as _t
from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS
from django.core.paginator import Paginator, InvalidPage
from django.shortcuts import get_object_or_404, redirect
from django.db.models import Sum
from django.utils.translation import gettext_lazy as _
from django.views.generic import DetailView, ListView, TemplateView, View
from django.views.generic.edit import CreateView, DeleteView, UpdateView
from core.views import (
CanCreateMixin,
CanViewMixin,
CanEditMixin,
CanEditPropMixin,
UserIsRootMixin,
TabedViewMixin,
PageEditViewBase,
DetailFormView,
from club.forms import ClubEditForm, ClubMemberForm, MailingForm, SellingsForm
from club.models import Club, Mailing, MailingSubscription, Membership
from com.views import (
PosterCreateBaseView,
PosterDeleteBaseView,
PosterEditBaseView,
PosterListBaseView,
)
from core.models import PageRev
from counter.models import Selling
from com.views import (
PosterListBaseView,
PosterCreateBaseView,
PosterEditBaseView,
PosterDeleteBaseView,
from core.views import (
CanCreateMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
DetailFormView,
PageEditViewBase,
TabedViewMixin,
UserIsRootMixin,
)
from club.models import Club, Membership, Mailing, MailingSubscription
from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsForm
from counter.models import Selling
class ClubTabsMixin(TabedViewMixin):
@ -611,7 +603,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
}
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
"""
@ -628,7 +620,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
mailing.save()
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
"""

View File

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

View File

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

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
from django.conf import settings
import django.db.models.deletion
from django.db import migrations, models
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.models import Q
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.urls import reverse
from django.conf import settings
from django.shortcuts import render
from django.templatetags.static import static
from django.core.mail import EmailMultiAlternatives
from django.core.exceptions import ValidationError
from django.urls import reverse
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 core import utils
from core.models import Notification, Preferences, RealGroup, User
class Sith(models.Model):

View File

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

View File

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

View File

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

View File

@ -14,15 +14,14 @@
#
#
from ajax_select import LookupChannel, register
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 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):

View File

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

View File

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

View File

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

View File

@ -24,10 +24,9 @@
#
import os
import sys
import signal
from http.server import test, CGIHTTPRequestHandler
import sys
from http.server import CGIHTTPRequestHandler, test
from django.core.management.base import BaseCommand
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
from django.core.management.base import BaseCommand
from core.markdown import markdown

View File

@ -24,38 +24,37 @@
import os
from datetime import date, datetime, timedelta
from io import StringIO, BytesIO
from io import BytesIO, StringIO
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.db import connection
from django.contrib.auth.models import Permission
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 PIL import Image
from core.models import Group, User, Page, PageRev, SithFile
from accounting.models import (
GeneralJournal,
AccountingType,
BankAccount,
ClubAccount,
Operation,
AccountingType,
SimplifiedAccountingType,
Company,
GeneralJournal,
Operation,
SimplifiedAccountingType,
)
from core.utils import resize_image
from club.models import Club, Membership
from subscription.models import Subscription
from counter.models import Customer, ProductType, Product, Counter, Selling, StudentCard
from com.models import Sith, Weekmail, News, NewsDate
from election.models import Election, Role, Candidature, ElectionList
from com.models import News, NewsDate, Sith, Weekmail
from core.models import Group, Page, PageRev, SithFile, User
from core.utils import resize_image
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 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):
@ -1099,8 +1098,10 @@ Welcome to the wiki page!
n = News(
title="Repas barman",
summary="Enjoy la fin du semestre!",
content="Viens donc t'enjailler avec les autres barmans aux "
"frais du BdF! \o/",
content=(
"Viens donc t'enjailler avec les autres barmans aux "
"frais du BdF! \\o/"
),
type="EVENT",
club=bar_club,
author=subscriber,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
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.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
# -*- coding: utf-8 -*-
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.utils.timezone
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):

View File

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

View File

@ -1,8 +1,8 @@
# Generated by Django 2.2.6 on 2019-11-14 15:10
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
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
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.core.mail import send_mail
from django.conf import settings
from django.contrib.auth.models import (
AbstractBaseUser,
UserManager,
Group as AuthGroup,
GroupManager as AuthGroupManager,
)
from django.contrib.auth.models import (
AnonymousUser as AuthAnonymousUser,
)
from django.utils.translation import gettext_lazy as _
from django.utils import timezone
from django.core import validators
from django.core.exceptions import ValidationError, PermissionDenied
from django.urls import reverse
from django.conf import settings
from django.db import models, transaction
from django.contrib.auth.models import (
Group as AuthGroup,
)
from django.contrib.auth.models import (
GroupManager as AuthGroupManager,
)
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
import os
from core import utils
from django.utils.html import escape
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from datetime import timedelta, date
import unicodedata
from core import utils
class RealGroupManager(AuthGroupManager):
@ -698,9 +701,11 @@ class User(AbstractBaseUser):
<em>%s</em>
</a>
""" % (
(
self.profile_pict.get_download_url()
if self.profile_pict
else staticfiles_storage.url("core/img/unknown.jpg"),
else staticfiles_storage.url("core/img/unknown.jpg")
),
_("Profile"),
escape(self.get_display_name()),
)

View File

@ -23,9 +23,9 @@
#
"""
This page is useful for custom migration tricks.
Sometimes, when you need to have a migration hack and you think it can be
useful again, put it there, we never know if we might need the hack again.
This page is useful for custom migration tricks.
Sometimes, when you need to have a migration hack and you think it can be
useful again, put it there, we never know if we might need the hack again.
"""
from django.db import connection, migrations

View File

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

View File

@ -24,12 +24,14 @@
#
import os
import sass
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.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

View File

@ -24,7 +24,6 @@
#
from django.db import models
from haystack import indexes, signals
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")
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
# but rather on an intermediary table, there is no
# model method to override, meaning we must use
# a signal to invalidate the cache when a user is removed from a club
cache.delete(f"user_{instance.id}_groups")
# a signal to invalidate the cache when a user is removed from a group
cache.delete(f"user_{instance.pk}_groups")

View File

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

View File

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

View File

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

View File

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

View File

@ -18,10 +18,12 @@ import os
from datetime import date, timedelta
import freezegun
import pytest
from django.core.cache import cache
from django.test import Client, TestCase
from django.test import TestCase
from django.urls import reverse
from django.utils.timezone import now
from pytest_django.asserts import assertRedirects
from club.models import Membership
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 sith import settings
"""
to run these tests :
python3 manage.py test
"""
class UserRegistrationTest(TestCase):
@classmethod
def setUpTestData(cls):
User.objects.all().delete()
def test_register_user_form_ok(self):
"""
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",
@pytest.mark.django_db
class TestUserRegistration:
@pytest.fixture()
def valid_payload(self):
return {
"first_name": "this user does not exist (yet)",
"last_name": "this user does not exist (yet)",
"email": "i-dont-exist-yet@git.an",
"password1": "plop",
"password2": "plop",
"captcha_0": "dummy-value",
"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):
"""
Should not register a user correctly
"""
c = Client()
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_ok(self, client, valid_payload):
"""Should register a user correctly."""
response = client.post(reverse("core:register"), valid_payload)
assert response.status_code == 200
assert "TEST_REGISTER_USER_FORM_OK" in str(response.content)
def test_register_user_form_fail_email(self):
"""
Should not register a user correctly
"""
c = Client()
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": "plop",
"captcha_0": "dummy-value",
"captcha_1": "PASSED",
},
@pytest.mark.parametrize(
"payload_edit",
[
{"password2": "not the same as password1"},
{"email": "not-an-email"},
{"first_name": ""},
{"last_name": ""},
{"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(self, client, valid_payload, payload_edit):
"""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):
"""
Should not register a user correctly
"""
c = Client()
response = c.post(
reverse("core:register"),
{
"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_already_exists(self, client, valid_payload):
"""Should not register a user correctly if it already exists."""
# create the user, then try to create it again
client.post(reverse("core:register"), valid_payload)
response = client.post(reverse("core:register"), valid_payload)
assert response.status_code == 200
assert "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):
"""
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": "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))
@pytest.mark.django_db
class TestUserLogin:
@pytest.fixture()
def user(self) -> User:
return User.objects.first()
def test_register_user_form_fail_wrong_captcha(self):
"""
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):
def test_login_fail(self, client, user):
"""
Should not 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 = client.post(
reverse("core:login"),
{"username": user.username, "password": "wrong-password"},
)
response = c.post(
reverse("core:login"), {"username": "gcarlier", "password": "guy"}
)
self.assertTrue(response.status_code == 200)
self.assertTrue(
"""<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)
assert response.status_code == 200
assert (
'<p class="alert alert-red">Votre nom d\'utilisateur '
"et votre mot de passe ne correspondent pas. Merci de réessayer.</p>"
) in str(response.content.decode())
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(self):
def test_full_markdown_syntax():
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:
md = md_file.read()
with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file:
html = html_file.read()
result = markdown(md)
self.assertTrue(result == html)
assert result == html
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):
self.client.login(username="root", password="plop")
self.root_group = Group.objects.get(name="Root")
self.client.force_login(self.root)
def test_create_page_ok(self):
"""
Should create a page correctly
"""
"""Should create a page correctly."""
response = self.client.post(
reverse("core:page_new"),
@ -301,19 +138,17 @@ class PageHandlingTest(TestCase):
self.assertRedirects(
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"}))
self.assertEqual(response.status_code, 200)
assert response.status_code == 200
html = response.content.decode()
self.assertIn('<a href="/page/guy/hist/">', html)
self.assertIn('<a href="/page/guy/edit/">', html)
self.assertIn('<a href="/page/guy/prop/">', html)
assert '<a href="/page/guy/hist/">' in html
assert '<a href="/page/guy/edit/">' in html
assert '<a href="/page/guy/prop/">' in html
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
Page.objects.all().delete()
self.client.post(
@ -321,7 +156,7 @@ class PageHandlingTest(TestCase):
{"parent": "", "name": "guy", "owner_group": str(self.root_group.id)},
)
page = Page.objects.first()
response = self.client.post(
self.client.post(
reverse("core:page_new"),
{
"parent": str(page.id),
@ -332,8 +167,8 @@ class PageHandlingTest(TestCase):
response = self.client.get(
reverse("core:page", kwargs={"page_name": "guy/bibou"})
)
self.assertTrue(response.status_code == 200)
self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content))
assert response.status_code == 200
assert '<a href="/page/guy/bibou/">' in str(response.content)
def test_access_child_page_ok(self):
"""
@ -346,7 +181,7 @@ class PageHandlingTest(TestCase):
response = self.client.get(
reverse("core:page", kwargs={"page_name": "guy/bibou"})
)
self.assertTrue(response.status_code == 200)
assert response.status_code == 200
html = response.content.decode()
self.assertIn('<a href="/page/guy/bibou/edit/">', html)
@ -355,7 +190,7 @@ class PageHandlingTest(TestCase):
Should not display a page correctly
"""
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()
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"}))
self.assertTrue(response.status_code == 200)
self.assertTrue(
assert response.status_code == 200
assert (
'<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;"
+ "&lt;script&gt;alert(\\'Guy\\');&lt;/script&gt;"
@ -392,35 +227,19 @@ http://git.an
)
class UserToolsTest(TestCase):
def test_anonymous_user_unauthorized(self):
response = self.client.get(reverse("core:user_tools"))
self.assertEqual(response.status_code, 403)
class UserToolsTest:
def test_anonymous_user_unauthorized(self, client):
"""An anonymous user shouldn't have access to the tools page"""
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
self.client.login(username="guy", password="plop")
response = self.client.get(reverse("core:user_tools"))
self.assertNotEqual(response.status_code, 500)
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)
client.force_login(User.objects.get(username=username))
response = client.get(reverse("core:user_tools"))
assert response.status_code == 200
# TODO: many tests on the pages:
@ -442,12 +261,12 @@ class FileHandlingTest(TestCase):
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
{"folder_name": "GUY_folder_test"},
)
self.assertTrue(response.status_code == 302)
assert response.status_code == 302
response = self.client.get(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
)
self.assertTrue(response.status_code == 200)
self.assertTrue("GUY_folder_test</a>" in str(response.content))
assert response.status_code == 200
assert "GUY_folder_test</a>" in str(response.content)
def test_upload_file_home(self):
with open("/bin/ls", "rb") as f:
@ -457,12 +276,12 @@ class FileHandlingTest(TestCase):
),
{"file_field": f},
)
self.assertTrue(response.status_code == 302)
assert response.status_code == 302
response = self.client.get(
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
)
self.assertTrue(response.status_code == 200)
self.assertTrue("ls</a>" in str(response.content))
assert response.status_code == 200
assert "ls</a>" in str(response.content)
class UserIsInGroupTest(TestCase):
@ -477,6 +296,10 @@ class UserIsInGroupTest(TestCase):
cls.root_group = Group.objects.get(name="Root")
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.old_subscribers = Group.objects.get(name="Old subscribers")
cls.accounting_admin = Group.objects.get(name="Accounting admin")
@ -493,21 +316,15 @@ class UserIsInGroupTest(TestCase):
)
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):
self.assertTrue(user.is_in_group(pk=self.public.id))
self.assertTrue(user.is_in_group(name=self.public.name))
assert user.is_in_group(pk=self.public.id)
assert user.is_in_group(name=self.public.name)
def assert_in_club_metagroups(self, user, club):
meta_groups_board = club.unix_name + settings.SITH_BOARD_SUFFIX
meta_groups_members = club.unix_name + settings.SITH_MEMBER_SUFFIX
self.assertFalse(user.is_in_group(name=meta_groups_board))
self.assertFalse(user.is_in_group(name=meta_groups_members))
assert user.is_in_group(name=meta_groups_board) is False
assert user.is_in_group(name=meta_groups_members) is False
def assert_only_in_public_group(self, user):
self.assert_in_public_group(user)
@ -519,12 +336,12 @@ class UserIsInGroupTest(TestCase):
self.subscribers,
self.old_subscribers,
):
self.assertFalse(user.is_in_group(pk=group.pk))
self.assertFalse(user.is_in_group(name=group.name))
assert not user.is_in_group(pk=group.pk)
assert not user.is_in_group(name=group.name)
meta_groups_board = self.club.unix_name + settings.SITH_BOARD_SUFFIX
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
self.assertFalse(user.is_in_group(name=meta_groups_board))
self.assertFalse(user.is_in_group(name=meta_groups_members))
assert user.is_in_group(name=meta_groups_board) is False
assert user.is_in_group(name=meta_groups_members) is False
def test_anonymous_user(self):
"""
@ -583,15 +400,13 @@ class UserIsInGroupTest(TestCase):
)
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
cache.clear()
self.assertTrue(self.toto.is_in_group(name=meta_groups_members))
self.assertEqual(
membership, cache.get(f"membership_{self.club.id}_{self.toto.id}")
)
assert self.toto.is_in_group(name=meta_groups_members) is True
assert membership == cache.get(f"membership_{self.club.id}_{self.toto.id}")
membership.end_date = now() - timedelta(minutes=5)
membership.save()
cached_membership = cache.get(f"membership_{self.club.id}_{self.toto.id}")
self.assertEqual(cached_membership, "not_member")
self.assertFalse(self.toto.is_in_group(name=meta_groups_members))
assert cached_membership == "not_member"
assert self.toto.is_in_group(name=meta_groups_members) is False
def test_cache_properly_cleared_group(self):
"""
@ -600,24 +415,24 @@ class UserIsInGroupTest(TestCase):
"""
# testing with 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.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
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.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):
"""
Test that searching for a not existing group
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):
@ -639,29 +454,25 @@ class DateUtilsTest(TestCase):
"""
Test that the get_semester function returns the correct semester string
"""
self.assertEqual(get_semester_code(self.autumn_semester_january), "A24")
self.assertEqual(get_semester_code(self.autumn_semester_september), "A24")
self.assertEqual(get_semester_code(self.autumn_first_day), "A24")
assert get_semester_code(self.autumn_semester_january) == "A24"
assert get_semester_code(self.autumn_semester_september) == "A24"
assert get_semester_code(self.autumn_first_day) == "A24"
self.assertEqual(get_semester_code(self.spring_semester_march), "P23")
self.assertEqual(get_semester_code(self.spring_first_day), "P23")
assert get_semester_code(self.spring_semester_march) == "P23"
assert get_semester_code(self.spring_first_day) == "P23"
def test_get_start_of_semester_fixed_date(self):
"""
Test that the get_start_of_semester correctly the starting date of the semester.
"""
automn_2024 = date(2024, self.autumn_month, self.autumn_day)
self.assertEqual(
get_start_of_semester(self.autumn_semester_january), 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)
assert 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
spring_2023 = date(2023, self.spring_month, self.spring_day)
self.assertEqual(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_semester_march) == spring_2023
assert get_start_of_semester(self.spring_first_day) == spring_2023
def test_get_start_of_semester_today(self):
"""
@ -669,10 +480,10 @@ class DateUtilsTest(TestCase):
when no date is given
"""
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):
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):
"""
@ -685,8 +496,8 @@ class DateUtilsTest(TestCase):
mid_autumn = autumn_2023 + timedelta(days=45)
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
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 core.views import *
from core.converters import (
BooleanStringConverter,
FourDigitYearConverter,
TwoDigitMonthConverter,
BooleanStringConverter,
)
from core.views import *
register_converter(FourDigitYearConverter, "yyyy")
register_converter(TwoDigitMonthConverter, "mm")

View File

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

View File

@ -25,26 +25,21 @@
import types
from sentry_sdk import last_event_id
from django.shortcuts import render
from django.core.exceptions import (
ImproperlyConfigured,
PermissionDenied,
)
from django.http import (
HttpResponseForbidden,
HttpResponseNotFound,
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.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
@ -314,9 +309,8 @@ class QuickNotifMixin:
quick_notif_list = []
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
# In some cases, the class can stay instanciated, so we need to reset the list
self.quick_notif_list = []
return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs)
def get_success_url(self):
@ -362,8 +356,8 @@ class DetailFormView(SingleObjectMixin, FormView):
return super(DetailFormView, self).get_object()
from .user import *
from .page import *
from .files import *
from .site 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
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
from wsgiref.util import FileWrapper
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 (
CanViewMixin,
CanEditMixin,
CanEditPropMixin,
CanViewMixin,
can_view,
)
from counter.models import Counter
@ -79,12 +78,35 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
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):
folder_name = forms.CharField(
label=_("Add a new folder"), max_length=30, required=False
)
file_field = forms.FileField(
widget=forms.ClearableFileInput(attrs={"multiple": True}),
file_field = MultipleFileField(
label=_("Files"),
required=False,
)

View File

@ -21,40 +21,37 @@
# 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 django.contrib.auth.forms import UserCreationForm, AuthenticationForm
from django import forms
from django.conf import settings
from django.db import transaction
from django.templatetags.static import static
from django.urls import reverse
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
from django.core.exceptions import ValidationError
from django.db import transaction
from django.forms import (
CheckboxSelectMultiple,
Select,
DateInput,
TextInput,
DateTimeInput,
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
import re
from core.models import User, Page, SithFile, Gift
from core.utils import resize_image
from io import BytesIO
from django.templatetags.static import static
from django.urls import reverse
from django.utils import timezone
from django.utils.dateparse import parse_datetime
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
from PIL import Image
from core.models import Gift, Page, SithFile, User
from core.utils import resize_image
# Widgets

View File

@ -15,18 +15,15 @@
#
"""
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 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.views import CanCreateMixin, CanEditMixin, DetailFormView

View File

@ -15,16 +15,16 @@
#
# 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.http import Http404
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 import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin
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
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 core.models import User, Notification
from core.utils import doku_to_markdown, bbcode_to_markdown
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):
@ -100,9 +99,8 @@ def search_club(query, as_json=False):
if query:
clubs = Club.objects.filter(name__icontains=query).all()
clubs = clubs[:5]
if (
as_json
): # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
if as_json:
# Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
clubs = json.loads(serializers.serialize("json", clubs, fields=("name")))
else:
clubs = list(clubs)

View File

@ -24,50 +24,49 @@
#
# 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.forms import PasswordChangeForm
from django.utils.translation import gettext as _
from django.urls import reverse
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.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 (
ListView,
DetailView,
TemplateView,
CreateView,
DeleteView,
DetailView,
ListView,
TemplateView,
)
from django.forms.models import modelform_factory
from django.forms import CheckboxSelectMultiple
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 django.views.generic.dates import MonthMixin, YearMixin
from django.views.generic.edit import UpdateView
from datetime import timedelta, date
import logging
from api.views.sas import all_pictures_of_user
from core.models import Gift, Preferences, SithFile, User
from core.views import (
CanViewMixin,
CanEditMixin,
CanEditPropMixin,
UserIsLoggedMixin,
TabedViewMixin,
CanViewMixin,
QuickNotifMixin,
TabedViewMixin,
UserIsLoggedMixin,
)
from core.views.forms import (
RegisteringForm,
UserProfileForm,
LoginForm,
UserGodfathersForm,
GiftForm,
LoginForm,
RegisteringForm,
UserGodfathersForm,
UserProfileForm,
)
from core.models import User, SithFile, Preferences, Gift
from subscription.models import Subscription
from counter.forms import StudentCardForm
from subscription.models import Subscription
from trombi.views import UserTrombiForm
@ -501,9 +500,10 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
def get_context_data(self, **kwargs):
kwargs = super(UserStatsView, self).get_context_data(**kwargs)
from counter.models import Counter
from django.db.models import Sum
from counter.models import Counter
foyer = Counter.objects.filter(name="Foyer").first()
mde = Counter.objects.filter(name="MDE").first()
gommette = Counter.objects.filter(name="La Gommette").first()
@ -601,10 +601,12 @@ class UserUploadProfilePictView(CanEditMixin, DetailView):
template_name = "core/user_edit.jinja"
def post(self, request, *args, **kwargs):
from core.utils import resize_image
from io import BytesIO
from PIL import Image
from core.utils import resize_image
self.object = self.get_object()
if self.object.profile_pict:
raise ValidationError(_("User already has a profile picture"))

View File

@ -31,4 +31,4 @@ class CounterConfig(AppConfig):
verbose_name = _("counter")
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.utils.translation import gettext_lazy as _
from core.views.forms import TzAwareDateTimeField, SelectDate
from core.views.forms import SelectDate, TzAwareDateTimeField
from counter.models import (
BillingInfo,
StudentCard,
Customer,
Refilling,
Counter,
Product,
Customer,
Eticket,
Product,
Refilling,
StudentCard,
)

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