mirror of
https://github.com/ae-utbm/sith.git
synced 2024-11-26 02:54:20 +00:00
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:
commit
44c8558aa3
10
.github/actions/setup_project/action.yml
vendored
10
.github/actions/setup_project/action.yml
vendored
@ -6,13 +6,13 @@ runs:
|
|||||||
- name: Install apt packages
|
- name: Install apt packages
|
||||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||||
with:
|
with:
|
||||||
packages: gettext libxapian-dev libgraphviz-dev
|
packages: gettext libgraphviz-dev
|
||||||
version: 1.0 # increment to reset cache
|
version: 1.0 # increment to reset cache
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt update
|
sudo apt update
|
||||||
sudo apt install gettext libxapian-dev libgraphviz-dev
|
sudo apt install gettext libgraphviz-dev
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Set up python
|
- name: Set up python
|
||||||
@ -45,7 +45,11 @@ runs:
|
|||||||
${{ runner.os }}-poetry-
|
${{ runner.os }}-poetry-
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: poetry install -E testing -E docs
|
run: poetry install --with docs,tests
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install xapian
|
||||||
|
run: poetry run ./manage.py install_xapian
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
- name: Compile gettext messages
|
- name: Compile gettext messages
|
||||||
|
22
.github/workflows/ci.yml
vendored
22
.github/workflows/ci.yml
vendored
@ -8,27 +8,31 @@ on:
|
|||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
black:
|
pre-commit:
|
||||||
name: Black format
|
name: Launch pre-commits checks (ruff)
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- uses: actions/checkout@v4
|
||||||
uses: actions/checkout@v3
|
- uses: actions/setup-python@v5
|
||||||
- name: Setup Project
|
- uses: pre-commit/action@v3.0.1
|
||||||
uses: ./.github/actions/setup_project
|
with:
|
||||||
- run: poetry run black --check .
|
extra_args: --all-files
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
name: Run tests and generate coverage report
|
name: Run tests and generate coverage report
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false # don't interrupt the other test processes
|
||||||
|
matrix:
|
||||||
|
pytest-mark: [slow, not slow]
|
||||||
steps:
|
steps:
|
||||||
- name: Check out repository
|
- name: Check out repository
|
||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v4
|
||||||
- uses: ./.github/actions/setup_project
|
- uses: ./.github/actions/setup_project
|
||||||
- uses: ./.github/actions/setup_xapian
|
- uses: ./.github/actions/setup_xapian
|
||||||
- uses: ./.github/actions/compile_messages
|
- uses: ./.github/actions/compile_messages
|
||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: poetry run coverage run ./manage.py test
|
run: poetry run coverage run -m pytest -m "${{ matrix.pytest-mark }}"
|
||||||
- name: Generate coverage report
|
- name: Generate coverage report
|
||||||
run: |
|
run: |
|
||||||
poetry run coverage report
|
poetry run coverage report
|
||||||
|
1
.github/workflows/deploy.yml
vendored
1
.github/workflows/deploy.yml
vendored
@ -38,6 +38,7 @@ jobs:
|
|||||||
|
|
||||||
git pull
|
git pull
|
||||||
poetry install
|
poetry install
|
||||||
|
poetry run ./manage.py install_xapian
|
||||||
poetry run ./manage.py migrate
|
poetry run ./manage.py migrate
|
||||||
echo "yes" | poetry run ./manage.py collectstatic
|
echo "yes" | poetry run ./manage.py collectstatic
|
||||||
poetry run ./manage.py compilestatic
|
poetry run ./manage.py compilestatic
|
||||||
|
1
.github/workflows/taiste.yml
vendored
1
.github/workflows/taiste.yml
vendored
@ -37,6 +37,7 @@ jobs:
|
|||||||
|
|
||||||
git pull
|
git pull
|
||||||
poetry install
|
poetry install
|
||||||
|
poetry run ./manage.py install_xapian
|
||||||
poetry run ./manage.py migrate
|
poetry run ./manage.py migrate
|
||||||
echo "yes" | poetry run ./manage.py collectstatic
|
echo "yes" | poetry run ./manage.py collectstatic
|
||||||
poetry run ./manage.py compilestatic
|
poetry run ./manage.py compilestatic
|
||||||
|
10
.pre-commit-config.yaml
Normal file
10
.pre-commit-config.yaml
Normal 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
|
@ -18,7 +18,6 @@ from django.contrib import admin
|
|||||||
|
|
||||||
from accounting.models import *
|
from accounting.models import *
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(BankAccount)
|
admin.site.register(BankAccount)
|
||||||
admin.site.register(ClubAccount)
|
admin.site.register(ClubAccount)
|
||||||
admin.site.register(GeneralJournal)
|
admin.site.register(GeneralJournal)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import accounting.models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import accounting.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import phonenumber_field.modelfields
|
import phonenumber_field.modelfields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -14,19 +14,19 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import reverse
|
from decimal import Decimal
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.core import validators
|
|
||||||
from django.db import models
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.template import defaultfilters
|
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core import validators
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import models
|
||||||
|
from django.template import defaultfilters
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
from phonenumber_field.modelfields import PhoneNumberField
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
|
|
||||||
from decimal import Decimal
|
|
||||||
from core.models import User, SithFile
|
|
||||||
from club.models import Club
|
from club.models import Club
|
||||||
|
from core.models import SithFile, User
|
||||||
|
|
||||||
|
|
||||||
class CurrencyField(models.DecimalField):
|
class CurrencyField(models.DecimalField):
|
||||||
|
@ -14,87 +14,82 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.core.management import call_command
|
|
||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
from core.models import User
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
from accounting.models import (
|
from accounting.models import (
|
||||||
GeneralJournal,
|
|
||||||
Operation,
|
|
||||||
Label,
|
|
||||||
AccountingType,
|
AccountingType,
|
||||||
|
GeneralJournal,
|
||||||
|
Label,
|
||||||
|
Operation,
|
||||||
SimplifiedAccountingType,
|
SimplifiedAccountingType,
|
||||||
)
|
)
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
class RefoundAccountTest(TestCase):
|
class RefoundAccountTest(TestCase):
|
||||||
def setUp(self):
|
@classmethod
|
||||||
self.skia = User.objects.filter(username="skia").first()
|
def setUpTestData(cls):
|
||||||
|
cls.skia = User.objects.get(username="skia")
|
||||||
# reffil skia's account
|
# reffil skia's account
|
||||||
self.skia.customer.amount = 800
|
cls.skia.customer.amount = 800
|
||||||
self.skia.customer.save()
|
cls.skia.customer.save()
|
||||||
|
cls.refound_account_url = reverse("accounting:refound_account")
|
||||||
|
|
||||||
def test_permission_denied(self):
|
def test_permission_denied(self):
|
||||||
self.client.login(username="guy", password="plop")
|
self.client.force_login(User.objects.get(username="guy"))
|
||||||
response_post = self.client.post(
|
response_post = self.client.post(
|
||||||
reverse("accounting:refound_account"), {"user": self.skia.id}
|
self.refound_account_url, {"user": self.skia.id}
|
||||||
)
|
)
|
||||||
response_get = self.client.get(reverse("accounting:refound_account"))
|
response_get = self.client.get(self.refound_account_url)
|
||||||
self.assertTrue(response_get.status_code == 403)
|
assert response_get.status_code == 403
|
||||||
self.assertTrue(response_post.status_code == 403)
|
assert response_post.status_code == 403
|
||||||
|
|
||||||
def test_root_granteed(self):
|
def test_root_granteed(self):
|
||||||
self.client.login(username="root", password="plop")
|
self.client.force_login(User.objects.get(username="root"))
|
||||||
response_post = self.client.post(
|
response = self.client.post(self.refound_account_url, {"user": self.skia.id})
|
||||||
reverse("accounting:refound_account"), {"user": self.skia.id}
|
self.assertRedirects(response, self.refound_account_url)
|
||||||
)
|
self.skia.refresh_from_db()
|
||||||
self.skia = User.objects.filter(username="skia").first()
|
response = self.client.get(self.refound_account_url)
|
||||||
response_get = self.client.get(reverse("accounting:refound_account"))
|
assert response.status_code == 200
|
||||||
self.assertFalse(response_get.status_code == 403)
|
assert '<form action="" method="post">' in str(response.content)
|
||||||
self.assertTrue('<form action="" method="post">' in str(response_get.content))
|
assert self.skia.customer.amount == 0
|
||||||
self.assertFalse(response_post.status_code == 403)
|
|
||||||
self.assertTrue(self.skia.customer.amount == 0)
|
|
||||||
|
|
||||||
def test_comptable_granteed(self):
|
def test_comptable_granteed(self):
|
||||||
self.client.login(username="comptable", password="plop")
|
self.client.force_login(User.objects.get(username="comptable"))
|
||||||
response_post = self.client.post(
|
response = self.client.post(self.refound_account_url, {"user": self.skia.id})
|
||||||
reverse("accounting:refound_account"), {"user": self.skia.id}
|
self.assertRedirects(response, self.refound_account_url)
|
||||||
)
|
self.skia.refresh_from_db()
|
||||||
self.skia = User.objects.filter(username="skia").first()
|
response = self.client.get(self.refound_account_url)
|
||||||
response_get = self.client.get(reverse("accounting:refound_account"))
|
assert response.status_code == 200
|
||||||
self.assertFalse(response_get.status_code == 403)
|
assert '<form action="" method="post">' in str(response.content)
|
||||||
self.assertTrue('<form action="" method="post">' in str(response_get.content))
|
assert self.skia.customer.amount == 0
|
||||||
self.assertFalse(response_post.status_code == 403)
|
|
||||||
self.assertTrue(self.skia.customer.amount == 0)
|
|
||||||
|
|
||||||
|
|
||||||
class JournalTest(TestCase):
|
class JournalTest(TestCase):
|
||||||
def setUp(self):
|
@classmethod
|
||||||
self.journal = GeneralJournal.objects.filter(id=1).first()
|
def setUpTestData(cls):
|
||||||
|
cls.journal = GeneralJournal.objects.get(id=1)
|
||||||
|
|
||||||
def test_permission_granted(self):
|
def test_permission_granted(self):
|
||||||
self.client.login(username="comptable", password="plop")
|
self.client.force_login(User.objects.get(username="comptable"))
|
||||||
response_get = self.client.get(
|
response_get = self.client.get(
|
||||||
reverse("accounting:journal_details", args=[self.journal.id])
|
reverse("accounting:journal_details", args=[self.journal.id])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(response_get.status_code == 200)
|
assert response_get.status_code == 200
|
||||||
self.assertTrue(
|
assert "<td>M\\xc3\\xa9thode de paiement</td>" in str(response_get.content)
|
||||||
"<td>M\\xc3\\xa9thode de paiement</td>" in str(response_get.content)
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_permission_not_granted(self):
|
def test_permission_not_granted(self):
|
||||||
self.client.login(username="skia", password="plop")
|
self.client.force_login(User.objects.get(username="skia"))
|
||||||
response_get = self.client.get(
|
response_get = self.client.get(
|
||||||
reverse("accounting:journal_details", args=[self.journal.id])
|
reverse("accounting:journal_details", args=[self.journal.id])
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertTrue(response_get.status_code == 403)
|
assert response_get.status_code == 403
|
||||||
self.assertFalse(
|
assert "<td>M\xc3\xa9thode de paiement</td>" not in str(response_get.content)
|
||||||
"<td>M\xc3\xa9thode de paiement</td>" in str(response_get.content)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class OperationTest(TestCase):
|
class OperationTest(TestCase):
|
||||||
@ -108,9 +103,8 @@ class OperationTest(TestCase):
|
|||||||
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
|
code="443", label="Ce code n'existe pas", movement_type="CREDIT"
|
||||||
)
|
)
|
||||||
at.save()
|
at.save()
|
||||||
l = Label(club_account=self.journal.club_account, name="bob")
|
l = Label.objects.create(club_account=self.journal.club_account, name="bob")
|
||||||
l.save()
|
self.client.force_login(User.objects.get(username="comptable"))
|
||||||
self.client.login(username="comptable", password="plop")
|
|
||||||
self.op1 = Operation(
|
self.op1 = Operation(
|
||||||
journal=self.journal,
|
journal=self.journal,
|
||||||
date=date.today(),
|
date=date.today(),
|
||||||
@ -139,8 +133,7 @@ class OperationTest(TestCase):
|
|||||||
self.op2.save()
|
self.op2.save()
|
||||||
|
|
||||||
def test_new_operation(self):
|
def test_new_operation(self):
|
||||||
self.client.login(username="comptable", password="plop")
|
at = AccountingType.objects.get(code="604")
|
||||||
at = AccountingType.objects.filter(code="604").first()
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("accounting:op_new", args=[self.journal.id]),
|
reverse("accounting:op_new", args=[self.journal.id]),
|
||||||
{
|
{
|
||||||
@ -172,8 +165,7 @@ class OperationTest(TestCase):
|
|||||||
self.assertTrue("<td>Le fantome de la nuit</td>" in str(response_get.content))
|
self.assertTrue("<td>Le fantome de la nuit</td>" in str(response_get.content))
|
||||||
|
|
||||||
def test_bad_new_operation(self):
|
def test_bad_new_operation(self):
|
||||||
self.client.login(username="comptable", password="plop")
|
AccountingType.objects.get(code="604")
|
||||||
AccountingType.objects.filter(code="604").first()
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("accounting:op_new", args=[self.journal.id]),
|
reverse("accounting:op_new", args=[self.journal.id]),
|
||||||
{
|
{
|
||||||
@ -199,7 +191,7 @@ class OperationTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_new_operation_not_authorized(self):
|
def test_new_operation_not_authorized(self):
|
||||||
self.client.login(username="skia", password="plop")
|
self.client.force_login(self.skia)
|
||||||
at = AccountingType.objects.filter(code="604").first()
|
at = AccountingType.objects.filter(code="604").first()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("accounting:op_new", args=[self.journal.id]),
|
reverse("accounting:op_new", args=[self.journal.id]),
|
||||||
@ -226,7 +218,6 @@ class OperationTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test__operation_simple_accounting(self):
|
def test__operation_simple_accounting(self):
|
||||||
self.client.login(username="comptable", password="plop")
|
|
||||||
sat = SimplifiedAccountingType.objects.all().first()
|
sat = SimplifiedAccountingType.objects.all().first()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("accounting:op_new", args=[self.journal.id]),
|
reverse("accounting:op_new", args=[self.journal.id]),
|
||||||
@ -263,14 +254,12 @@ class OperationTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_nature_statement(self):
|
def test_nature_statement(self):
|
||||||
self.client.login(username="comptable", password="plop")
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("accounting:journal_nature_statement", args=[self.journal.id])
|
reverse("accounting:journal_nature_statement", args=[self.journal.id])
|
||||||
)
|
)
|
||||||
self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200)
|
self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200)
|
||||||
|
|
||||||
def test_person_statement(self):
|
def test_person_statement(self):
|
||||||
self.client.login(username="comptable", password="plop")
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("accounting:journal_person_statement", args=[self.journal.id])
|
reverse("accounting:journal_person_statement", args=[self.journal.id])
|
||||||
)
|
)
|
||||||
@ -292,7 +281,6 @@ class OperationTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_accounting_statement(self):
|
def test_accounting_statement(self):
|
||||||
self.client.login(username="comptable", password="plop")
|
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("accounting:journal_accounting_statement", args=[self.journal.id])
|
reverse("accounting:journal_accounting_statement", args=[self.journal.id])
|
||||||
)
|
)
|
||||||
|
@ -14,41 +14,41 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.views.generic import ListView, DetailView
|
|
||||||
from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView
|
|
||||||
from django.urls import reverse_lazy, reverse
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.forms.models import modelform_factory
|
|
||||||
from django.core.exceptions import PermissionDenied, ValidationError
|
|
||||||
from django.forms import HiddenInput
|
|
||||||
from django.db import transaction
|
|
||||||
from django.db.models import Sum
|
|
||||||
from django.conf import settings
|
|
||||||
from django import forms
|
|
||||||
from django.http import HttpResponse
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
from ajax_select.fields import AutoCompleteSelectField
|
from ajax_select.fields import AutoCompleteSelectField
|
||||||
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import PermissionDenied, ValidationError
|
||||||
|
from django.db import transaction
|
||||||
|
from django.db.models import Sum
|
||||||
|
from django.forms import HiddenInput
|
||||||
|
from django.forms.models import modelform_factory
|
||||||
|
from django.http import HttpResponse
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic import DetailView, ListView
|
||||||
|
from django.views.generic.edit import CreateView, DeleteView, FormView, UpdateView
|
||||||
|
|
||||||
from core.views import (
|
|
||||||
CanViewMixin,
|
|
||||||
CanEditMixin,
|
|
||||||
CanEditPropMixin,
|
|
||||||
CanCreateMixin,
|
|
||||||
TabedViewMixin,
|
|
||||||
)
|
|
||||||
from core.views.forms import SelectFile, SelectDate
|
|
||||||
from accounting.models import (
|
from accounting.models import (
|
||||||
|
AccountingType,
|
||||||
BankAccount,
|
BankAccount,
|
||||||
ClubAccount,
|
ClubAccount,
|
||||||
GeneralJournal,
|
|
||||||
Operation,
|
|
||||||
AccountingType,
|
|
||||||
Company,
|
Company,
|
||||||
SimplifiedAccountingType,
|
GeneralJournal,
|
||||||
Label,
|
Label,
|
||||||
|
Operation,
|
||||||
|
SimplifiedAccountingType,
|
||||||
)
|
)
|
||||||
from counter.models import Counter, Selling, Product
|
from core.views import (
|
||||||
|
CanCreateMixin,
|
||||||
|
CanEditMixin,
|
||||||
|
CanEditPropMixin,
|
||||||
|
CanViewMixin,
|
||||||
|
TabedViewMixin,
|
||||||
|
)
|
||||||
|
from core.views.forms import SelectDate, SelectFile
|
||||||
|
from counter.models import Counter, Product, Selling
|
||||||
|
|
||||||
# Main accounting view
|
# Main accounting view
|
||||||
|
|
||||||
@ -521,14 +521,14 @@ class OperationPDFView(CanViewMixin, DetailView):
|
|||||||
pk_url_kwarg = "op_id"
|
pk_url_kwarg = "op_id"
|
||||||
|
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
from reportlab.pdfgen import canvas
|
|
||||||
from reportlab.lib.units import cm
|
|
||||||
from reportlab.platypus import Table, TableStyle
|
|
||||||
from reportlab.lib import colors
|
from reportlab.lib import colors
|
||||||
from reportlab.lib.pagesizes import letter
|
from reportlab.lib.pagesizes import letter
|
||||||
|
from reportlab.lib.units import cm
|
||||||
from reportlab.lib.utils import ImageReader
|
from reportlab.lib.utils import ImageReader
|
||||||
from reportlab.pdfbase.ttfonts import TTFont
|
|
||||||
from reportlab.pdfbase import pdfmetrics
|
from reportlab.pdfbase import pdfmetrics
|
||||||
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
from reportlab.platypus import Table, TableStyle
|
||||||
|
|
||||||
pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf"))
|
pdfmetrics.registerFont(TTFont("DejaVu", "DejaVuSerif.ttf"))
|
||||||
|
|
||||||
@ -599,7 +599,7 @@ class OperationPDFView(CanViewMixin, DetailView):
|
|||||||
payment_mode = ""
|
payment_mode = ""
|
||||||
for m in settings.SITH_ACCOUNTING_PAYMENT_METHOD:
|
for m in settings.SITH_ACCOUNTING_PAYMENT_METHOD:
|
||||||
if m[0] == mode:
|
if m[0] == mode:
|
||||||
payment_mode += "[\u00D7]"
|
payment_mode += "[\u00d7]"
|
||||||
else:
|
else:
|
||||||
payment_mode += "[ ]"
|
payment_mode += "[ ]"
|
||||||
payment_mode += " %s\n" % (m[1])
|
payment_mode += " %s\n" % (m[1])
|
||||||
|
@ -14,6 +14,4 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
|
|
||||||
# Register your models here.
|
# Register your models here.
|
||||||
|
@ -14,6 +14,4 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
@ -14,6 +14,4 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
# Create your tests here.
|
||||||
|
@ -14,10 +14,10 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.urls import re_path, path, include
|
from django.urls import include, path, re_path
|
||||||
|
from rest_framework import routers
|
||||||
|
|
||||||
from api.views import *
|
from api.views import *
|
||||||
from rest_framework import routers
|
|
||||||
|
|
||||||
# Router config
|
# Router config
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
|
@ -14,13 +14,13 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework import viewsets
|
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from rest_framework.decorators import action
|
|
||||||
from django.db.models.query import QuerySet
|
from django.db.models.query import QuerySet
|
||||||
|
from rest_framework import viewsets
|
||||||
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from core.views import can_view, can_edit
|
from core.views import can_edit, can_view
|
||||||
|
|
||||||
|
|
||||||
def check_if(obj, user, test):
|
def check_if(obj, user, test):
|
||||||
@ -64,10 +64,10 @@ class RightModelViewSet(ManageModelMixin, viewsets.ModelViewSet):
|
|||||||
|
|
||||||
|
|
||||||
from .api import *
|
from .api import *
|
||||||
from .counter import *
|
|
||||||
from .user import *
|
|
||||||
from .club import *
|
from .club import *
|
||||||
|
from .counter import *
|
||||||
from .group import *
|
from .group import *
|
||||||
from .launderette import *
|
from .launderette import *
|
||||||
from .uv import *
|
|
||||||
from .sas import *
|
from .sas import *
|
||||||
|
from .user import *
|
||||||
|
from .uv import *
|
||||||
|
@ -14,9 +14,9 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.decorators import api_view, renderer_classes
|
from rest_framework.decorators import api_view, renderer_classes
|
||||||
from rest_framework.renderers import StaticHTMLRenderer
|
from rest_framework.renderers import StaticHTMLRenderer
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from core.templatetags.renderer import markdown
|
from core.templatetags.renderer import markdown
|
||||||
|
|
||||||
|
@ -14,17 +14,15 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework.response import Response
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.decorators import api_view, renderer_classes
|
from rest_framework.decorators import api_view, renderer_classes
|
||||||
from rest_framework.renderers import StaticHTMLRenderer
|
from rest_framework.renderers import StaticHTMLRenderer
|
||||||
|
from rest_framework.response import Response
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
|
|
||||||
from club.models import Club, Mailing
|
|
||||||
|
|
||||||
from api.views import RightModelViewSet
|
from api.views import RightModelViewSet
|
||||||
|
from club.models import Club, Mailing
|
||||||
|
|
||||||
|
|
||||||
class ClubSerializer(serializers.ModelSerializer):
|
class ClubSerializer(serializers.ModelSerializer):
|
||||||
|
@ -15,12 +15,11 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
from counter.models import Counter
|
|
||||||
|
|
||||||
from api.views import RightModelViewSet
|
from api.views import RightModelViewSet
|
||||||
|
from counter.models import Counter
|
||||||
|
|
||||||
|
|
||||||
class CounterSerializer(serializers.ModelSerializer):
|
class CounterSerializer(serializers.ModelSerializer):
|
||||||
|
@ -16,9 +16,8 @@
|
|||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
|
|
||||||
from core.models import RealGroup
|
|
||||||
|
|
||||||
from api.views import RightModelViewSet
|
from api.views import RightModelViewSet
|
||||||
|
from core.models import RealGroup
|
||||||
|
|
||||||
|
|
||||||
class GroupSerializer(serializers.ModelSerializer):
|
class GroupSerializer(serializers.ModelSerializer):
|
||||||
|
@ -15,12 +15,11 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
from launderette.models import Launderette, Machine, Token
|
|
||||||
|
|
||||||
from api.views import RightModelViewSet
|
from api.views import RightModelViewSet
|
||||||
|
from launderette.models import Launderette, Machine, Token
|
||||||
|
|
||||||
|
|
||||||
class LaunderettePlaceSerializer(serializers.ModelSerializer):
|
class LaunderettePlaceSerializer(serializers.ModelSerializer):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
from rest_framework.decorators import api_view, renderer_classes
|
from rest_framework.decorators import api_view, renderer_classes
|
||||||
from rest_framework.exceptions import PermissionDenied
|
from rest_framework.exceptions import PermissionDenied
|
||||||
from rest_framework.generics import get_object_or_404
|
from rest_framework.generics import get_object_or_404
|
||||||
@ -6,8 +7,8 @@ from rest_framework.renderers import JSONRenderer
|
|||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
from rest_framework.response import Response
|
from rest_framework.response import Response
|
||||||
|
|
||||||
from core.views import can_edit
|
|
||||||
from core.models import User
|
from core.models import User
|
||||||
|
from core.views import can_edit
|
||||||
from sas.models import Picture
|
from sas.models import Picture
|
||||||
|
|
||||||
|
|
||||||
|
@ -17,12 +17,11 @@
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.response import Response
|
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
|
from rest_framework.response import Response
|
||||||
from core.models import User
|
|
||||||
|
|
||||||
from api.views import RightModelViewSet
|
from api.views import RightModelViewSet
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
|
|
||||||
class UserSerializer(serializers.ModelSerializer):
|
class UserSerializer(serializers.ModelSerializer):
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
from rest_framework.response import Response
|
import json
|
||||||
|
import urllib.request
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from rest_framework import serializers
|
||||||
from rest_framework.decorators import api_view, renderer_classes
|
from rest_framework.decorators import api_view, renderer_classes
|
||||||
from rest_framework.renderers import JSONRenderer
|
from rest_framework.renderers import JSONRenderer
|
||||||
from django.core.exceptions import PermissionDenied
|
from rest_framework.response import Response
|
||||||
from django.conf import settings
|
|
||||||
from rest_framework import serializers
|
|
||||||
import urllib.request
|
|
||||||
import json
|
|
||||||
|
|
||||||
from pedagogy.views import CanCreateUVFunctionMixin
|
from pedagogy.views import CanCreateUVFunctionMixin
|
||||||
|
|
||||||
|
@ -23,18 +23,15 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.conf import settings
|
from ajax_select.fields import AutoCompleteSelectMultipleField
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultipleField
|
from club.models import Club, Mailing, MailingSubscription, Membership
|
||||||
|
|
||||||
from club.models import Mailing, MailingSubscription, Club, Membership
|
|
||||||
|
|
||||||
from core.models import User
|
from core.models import User
|
||||||
from core.views.forms import SelectDate, SelectDateTime
|
from core.views.forms import SelectDate, TzAwareDateTimeField
|
||||||
from counter.models import Counter
|
from counter.models import Counter
|
||||||
from core.views.forms import TzAwareDateTimeField
|
|
||||||
|
|
||||||
|
|
||||||
class ClubEditForm(forms.ModelForm):
|
class ClubEditForm(forms.ModelForm):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
from club.models import Club
|
from club.models import Club
|
||||||
from core.operations import PsqlRunOnly
|
from core.operations import PsqlRunOnly
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
def generate_club_pages(apps, schema_editor):
|
def generate_club_pages(apps, schema_editor):
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import club.models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import club.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -24,21 +24,19 @@
|
|||||||
#
|
#
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
from django.db import models
|
|
||||||
from django.core import validators
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core import validators
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.core.exceptions import ObjectDoesNotExist, ValidationError
|
||||||
|
from django.core.validators import RegexValidator, validate_email
|
||||||
|
from django.db import models, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.timezone import now
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
|
||||||
from django.db import transaction
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.core.validators import RegexValidator, validate_email
|
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.models import User, MetaGroup, Group, SithFile, RealGroup, Notification, Page
|
from core.models import Group, MetaGroup, Notification, Page, RealGroup, SithFile, User
|
||||||
|
|
||||||
# Create your models here.
|
# Create your models here.
|
||||||
|
|
||||||
@ -209,11 +207,11 @@ class Club(models.Model):
|
|||||||
cache.set(f"sith_club_{self.unix_name}", self)
|
cache.set(f"sith_club_{self.unix_name}", self)
|
||||||
|
|
||||||
def delete(self, *args, **kwargs):
|
def delete(self, *args, **kwargs):
|
||||||
super().delete(*args, **kwargs)
|
|
||||||
# Invalidate the cache of this club and of its memberships
|
# Invalidate the cache of this club and of its memberships
|
||||||
for membership in self.members.ongoing().select_related("user"):
|
for membership in self.members.ongoing().select_related("user"):
|
||||||
cache.delete(f"membership_{self.id}_{membership.user.id}")
|
cache.delete(f"membership_{self.id}_{membership.user.id}")
|
||||||
cache.delete(f"sith_club_{self.unix_name}")
|
cache.delete(f"sith_club_{self.unix_name}")
|
||||||
|
super().delete(*args, **kwargs)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.name
|
return self.name
|
||||||
|
488
club/tests.py
488
club/tests.py
File diff suppressed because it is too large
Load Diff
@ -26,50 +26,42 @@
|
|||||||
import csv
|
import csv
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django import forms
|
from django.core.exceptions import NON_FIELD_ERRORS, PermissionDenied, ValidationError
|
||||||
from django.views.generic import ListView, DetailView, TemplateView, View
|
from django.core.paginator import InvalidPage, Paginator
|
||||||
from django.views.generic.edit import DeleteView
|
from django.db.models import Sum
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
|
||||||
from django.views.generic.edit import UpdateView, CreateView
|
|
||||||
from django.http import (
|
from django.http import (
|
||||||
HttpResponseRedirect,
|
|
||||||
HttpResponse,
|
|
||||||
Http404,
|
Http404,
|
||||||
|
HttpResponseRedirect,
|
||||||
StreamingHttpResponse,
|
StreamingHttpResponse,
|
||||||
)
|
)
|
||||||
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse, reverse_lazy
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.utils.translation import gettext as _t
|
from django.utils.translation import gettext as _t
|
||||||
from django.core.exceptions import PermissionDenied, ValidationError, NON_FIELD_ERRORS
|
from django.utils.translation import gettext_lazy as _
|
||||||
from django.core.paginator import Paginator, InvalidPage
|
from django.views.generic import DetailView, ListView, TemplateView, View
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||||
from django.db.models import Sum
|
|
||||||
|
|
||||||
|
from club.forms import ClubEditForm, ClubMemberForm, MailingForm, SellingsForm
|
||||||
from core.views import (
|
from club.models import Club, Mailing, MailingSubscription, Membership
|
||||||
CanCreateMixin,
|
from com.views import (
|
||||||
CanViewMixin,
|
PosterCreateBaseView,
|
||||||
CanEditMixin,
|
PosterDeleteBaseView,
|
||||||
CanEditPropMixin,
|
PosterEditBaseView,
|
||||||
UserIsRootMixin,
|
PosterListBaseView,
|
||||||
TabedViewMixin,
|
|
||||||
PageEditViewBase,
|
|
||||||
DetailFormView,
|
|
||||||
)
|
)
|
||||||
from core.models import PageRev
|
from core.models import PageRev
|
||||||
|
from core.views import (
|
||||||
from counter.models import Selling
|
CanCreateMixin,
|
||||||
|
CanEditMixin,
|
||||||
from com.views import (
|
CanEditPropMixin,
|
||||||
PosterListBaseView,
|
CanViewMixin,
|
||||||
PosterCreateBaseView,
|
DetailFormView,
|
||||||
PosterEditBaseView,
|
PageEditViewBase,
|
||||||
PosterDeleteBaseView,
|
TabedViewMixin,
|
||||||
|
UserIsRootMixin,
|
||||||
)
|
)
|
||||||
|
from counter.models import Selling
|
||||||
from club.models import Club, Membership, Mailing, MailingSubscription
|
|
||||||
from club.forms import MailingForm, ClubEditForm, ClubMemberForm, SellingsForm
|
|
||||||
|
|
||||||
|
|
||||||
class ClubTabsMixin(TabedViewMixin):
|
class ClubTabsMixin(TabedViewMixin):
|
||||||
@ -611,7 +603,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
|
|||||||
}
|
}
|
||||||
return kwargs
|
return kwargs
|
||||||
|
|
||||||
def add_new_mailing(self, cleaned_data) -> ValidationError:
|
def add_new_mailing(self, cleaned_data) -> ValidationError | None:
|
||||||
"""
|
"""
|
||||||
Create a new mailing list from the form
|
Create a new mailing list from the form
|
||||||
"""
|
"""
|
||||||
@ -628,7 +620,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
|
|||||||
mailing.save()
|
mailing.save()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def add_new_subscription(self, cleaned_data) -> ValidationError:
|
def add_new_subscription(self, cleaned_data) -> ValidationError | None:
|
||||||
"""
|
"""
|
||||||
Add mailing subscriptions for each user given and/or for the specified email in form
|
Add mailing subscriptions for each user given and/or for the specified email in form
|
||||||
"""
|
"""
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
import django.db.models.deletion
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.db.models.deletion
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -23,22 +23,20 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.shortcuts import render
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.core.mail import EmailMultiAlternatives
|
||||||
from django.db import models, transaction
|
from django.db import models, transaction
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.shortcuts import render
|
||||||
from django.utils import timezone
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.conf import settings
|
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.core.mail import EmailMultiAlternatives
|
from django.urls import reverse
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core import utils
|
|
||||||
from core.models import User, Preferences, RealGroup, Notification, SithFile
|
|
||||||
from club.models import Club
|
from club.models import Club
|
||||||
|
from core import utils
|
||||||
|
from core.models import Notification, Preferences, RealGroup, User
|
||||||
|
|
||||||
|
|
||||||
class Sith(models.Model):
|
class Sith(models.Model):
|
||||||
|
102
com/tests.py
102
com/tests.py
@ -13,51 +13,53 @@
|
|||||||
# OR WITHIN THE LOCAL FILE "LICENSE"
|
# OR WITHIN THE LOCAL FILE "LICENSE"
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
import pytest
|
||||||
|
from django.conf import settings
|
||||||
from django.core.files.uploadedfile import SimpleUploadedFile
|
from django.core.files.uploadedfile import SimpleUploadedFile
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.conf import settings
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.core.management import call_command
|
|
||||||
from django.utils import html
|
from django.utils import html
|
||||||
from django.utils.timezone import localtime, now
|
from django.utils.timezone import localtime, now
|
||||||
from django.utils.translation import gettext as _
|
from django.utils.translation import gettext as _
|
||||||
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, Membership
|
||||||
from com.models import Sith, News, Weekmail, WeekmailArticle, Poster
|
from com.models import News, Poster, Sith, Weekmail, WeekmailArticle
|
||||||
from core.models import User, RealGroup, AnonymousUser
|
from core.models import AnonymousUser, RealGroup, User
|
||||||
|
|
||||||
|
|
||||||
class ComAlertTest(TestCase):
|
@pytest.fixture()
|
||||||
def test_page_is_working(self):
|
def user_community():
|
||||||
self.client.login(username="comunity", password="plop")
|
return User.objects.get(username="comunity")
|
||||||
response = self.client.get(reverse("com:alert_edit"))
|
|
||||||
self.assertNotEqual(response.status_code, 500)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
|
|
||||||
class ComInfoTest(TestCase):
|
@pytest.mark.django_db
|
||||||
def test_page_is_working(self):
|
@pytest.mark.parametrize(
|
||||||
self.client.login(username="comunity", password="plop")
|
"url",
|
||||||
response = self.client.get(reverse("com:info_edit"))
|
[
|
||||||
self.assertNotEqual(response.status_code, 500)
|
reverse("com:alert_edit"),
|
||||||
self.assertEqual(response.status_code, 200)
|
reverse("com:info_edit"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_com_page_is_working(client, url, user_community):
|
||||||
|
client.force_login(user_community)
|
||||||
|
response = client.get(url)
|
||||||
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
class ComTest(TestCase):
|
class ComTest(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpTestData(cls):
|
def setUpTestData(cls):
|
||||||
cls.skia = User.objects.filter(username="skia").first()
|
cls.skia = User.objects.get(username="skia")
|
||||||
cls.com_group = RealGroup.objects.filter(
|
cls.com_group = RealGroup.objects.filter(
|
||||||
id=settings.SITH_GROUP_COM_ADMIN_ID
|
id=settings.SITH_GROUP_COM_ADMIN_ID
|
||||||
).first()
|
).first()
|
||||||
cls.skia.groups.set([cls.com_group])
|
cls.skia.groups.set([cls.com_group])
|
||||||
cls.skia.save()
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.login(username=self.skia.username, password="plop")
|
self.client.force_login(self.skia)
|
||||||
|
|
||||||
def test_alert_msg(self):
|
def test_alert_msg(self):
|
||||||
response = self.client.post(
|
self.client.post(
|
||||||
reverse("com:alert_edit"),
|
reverse("com:alert_edit"),
|
||||||
{
|
{
|
||||||
"alert_msg": """
|
"alert_msg": """
|
||||||
@ -68,7 +70,6 @@ class ComTest(TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
r = self.client.get(reverse("core:index"))
|
r = self.client.get(reverse("core:index"))
|
||||||
self.assertTrue(r.status_code == 200)
|
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
r,
|
r,
|
||||||
"""<div id="alert_box">
|
"""<div id="alert_box">
|
||||||
@ -77,7 +78,7 @@ class ComTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_info_msg(self):
|
def test_info_msg(self):
|
||||||
response = self.client.post(
|
self.client.post(
|
||||||
reverse("com:info_edit"),
|
reverse("com:info_edit"),
|
||||||
{
|
{
|
||||||
"info_msg": """
|
"info_msg": """
|
||||||
@ -86,7 +87,6 @@ class ComTest(TestCase):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
r = self.client.get(reverse("core:index"))
|
r = self.client.get(reverse("core:index"))
|
||||||
self.assertTrue(r.status_code == 200)
|
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
r,
|
r,
|
||||||
"""<div id="info_box">
|
"""<div id="info_box">
|
||||||
@ -94,7 +94,7 @@ class ComTest(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def test_birthday_non_subscribed_user(self):
|
def test_birthday_non_subscribed_user(self):
|
||||||
self.client.login(username="guy", password="plop")
|
self.client.force_login(User.objects.get(username="guy"))
|
||||||
response = self.client.get(reverse("core:index"))
|
response = self.client.get(reverse("core:index"))
|
||||||
self.assertContains(
|
self.assertContains(
|
||||||
response,
|
response,
|
||||||
@ -123,13 +123,13 @@ class SithTest(TestCase):
|
|||||||
sith: Sith = Sith.objects.first()
|
sith: Sith = Sith.objects.first()
|
||||||
|
|
||||||
com_admin = User.objects.get(username="comunity")
|
com_admin = User.objects.get(username="comunity")
|
||||||
self.assertTrue(sith.is_owned_by(com_admin))
|
assert sith.is_owned_by(com_admin)
|
||||||
|
|
||||||
anonymous = AnonymousUser()
|
anonymous = AnonymousUser()
|
||||||
self.assertFalse(sith.is_owned_by(anonymous))
|
assert not sith.is_owned_by(anonymous)
|
||||||
|
|
||||||
sli = User.objects.get(username="sli")
|
sli = User.objects.get(username="sli")
|
||||||
self.assertFalse(sith.is_owned_by(sli))
|
assert not sith.is_owned_by(sli)
|
||||||
|
|
||||||
|
|
||||||
class NewsTest(TestCase):
|
class NewsTest(TestCase):
|
||||||
@ -154,10 +154,10 @@ class NewsTest(TestCase):
|
|||||||
or by their author but nobody else
|
or by their author but nobody else
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.assertTrue(self.new.is_owned_by(self.com_admin))
|
assert self.new.is_owned_by(self.com_admin)
|
||||||
self.assertTrue(self.new.is_owned_by(self.author))
|
assert self.new.is_owned_by(self.author)
|
||||||
self.assertFalse(self.new.is_owned_by(self.anonymous))
|
assert not self.new.is_owned_by(self.anonymous)
|
||||||
self.assertFalse(self.new.is_owned_by(self.sli))
|
assert not self.new.is_owned_by(self.sli)
|
||||||
|
|
||||||
def test_news_viewer(self):
|
def test_news_viewer(self):
|
||||||
"""
|
"""
|
||||||
@ -165,26 +165,26 @@ class NewsTest(TestCase):
|
|||||||
and not moderated news only by com admins
|
and not moderated news only by com admins
|
||||||
"""
|
"""
|
||||||
# by default a news isn't moderated
|
# by default a news isn't moderated
|
||||||
self.assertTrue(self.new.can_be_viewed_by(self.com_admin))
|
assert self.new.can_be_viewed_by(self.com_admin)
|
||||||
self.assertFalse(self.new.can_be_viewed_by(self.sli))
|
assert not self.new.can_be_viewed_by(self.sli)
|
||||||
self.assertFalse(self.new.can_be_viewed_by(self.anonymous))
|
assert not self.new.can_be_viewed_by(self.anonymous)
|
||||||
self.assertFalse(self.new.can_be_viewed_by(self.author))
|
assert not self.new.can_be_viewed_by(self.author)
|
||||||
|
|
||||||
self.new.is_moderated = True
|
self.new.is_moderated = True
|
||||||
self.new.save()
|
self.new.save()
|
||||||
self.assertTrue(self.new.can_be_viewed_by(self.com_admin))
|
assert self.new.can_be_viewed_by(self.com_admin)
|
||||||
self.assertTrue(self.new.can_be_viewed_by(self.sli))
|
assert self.new.can_be_viewed_by(self.sli)
|
||||||
self.assertTrue(self.new.can_be_viewed_by(self.anonymous))
|
assert self.new.can_be_viewed_by(self.anonymous)
|
||||||
self.assertTrue(self.new.can_be_viewed_by(self.author))
|
assert self.new.can_be_viewed_by(self.author)
|
||||||
|
|
||||||
def test_news_editor(self):
|
def test_news_editor(self):
|
||||||
"""
|
"""
|
||||||
Test that only com admins can edit news
|
Test that only com admins can edit news
|
||||||
"""
|
"""
|
||||||
self.assertTrue(self.new.can_be_edited_by(self.com_admin))
|
assert self.new.can_be_edited_by(self.com_admin)
|
||||||
self.assertFalse(self.new.can_be_edited_by(self.sli))
|
assert not self.new.can_be_edited_by(self.sli)
|
||||||
self.assertFalse(self.new.can_be_edited_by(self.anonymous))
|
assert not self.new.can_be_edited_by(self.anonymous)
|
||||||
self.assertFalse(self.new.can_be_edited_by(self.author))
|
assert not self.new.can_be_edited_by(self.author)
|
||||||
|
|
||||||
|
|
||||||
class WeekmailArticleTest(TestCase):
|
class WeekmailArticleTest(TestCase):
|
||||||
@ -207,10 +207,10 @@ class WeekmailArticleTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
Test that weekmails are owned only by com admins
|
Test that weekmails are owned only by com admins
|
||||||
"""
|
"""
|
||||||
self.assertTrue(self.article.is_owned_by(self.com_admin))
|
assert self.article.is_owned_by(self.com_admin)
|
||||||
self.assertFalse(self.article.is_owned_by(self.author))
|
assert not self.article.is_owned_by(self.author)
|
||||||
self.assertFalse(self.article.is_owned_by(self.anonymous))
|
assert not self.article.is_owned_by(self.anonymous)
|
||||||
self.assertFalse(self.article.is_owned_by(self.sli))
|
assert not self.article.is_owned_by(self.sli)
|
||||||
|
|
||||||
|
|
||||||
class PosterTest(TestCase):
|
class PosterTest(TestCase):
|
||||||
@ -233,8 +233,8 @@ class PosterTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
Test that poster are owned by com admins and board members in clubs
|
Test that poster are owned by com admins and board members in clubs
|
||||||
"""
|
"""
|
||||||
self.assertTrue(self.poster.is_owned_by(self.com_admin))
|
assert self.poster.is_owned_by(self.com_admin)
|
||||||
self.assertFalse(self.poster.is_owned_by(self.anonymous))
|
assert not self.poster.is_owned_by(self.anonymous)
|
||||||
|
|
||||||
self.assertFalse(self.poster.is_owned_by(self.susbcriber))
|
assert not self.poster.is_owned_by(self.susbcriber)
|
||||||
self.assertTrue(self.poster.is_owned_by(self.sli))
|
assert self.poster.is_owned_by(self.sli)
|
||||||
|
45
com/views.py
45
com/views.py
@ -23,38 +23,35 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.shortcuts import redirect, get_object_or_404
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.views.generic import ListView, DetailView, View
|
|
||||||
from django.views.generic.edit import UpdateView, CreateView, DeleteView
|
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.urls import reverse, reverse_lazy
|
|
||||||
from django.core.exceptions import ValidationError
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.conf import settings
|
|
||||||
from django.db.models import Max
|
|
||||||
from django.forms.models import modelform_factory
|
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
from django import forms
|
|
||||||
|
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
from smtplib import SMTPRecipientsRefused
|
from smtplib import SMTPRecipientsRefused
|
||||||
|
|
||||||
from com.models import Sith, News, NewsDate, Weekmail, WeekmailArticle, Screen, Poster
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import PermissionDenied, ValidationError
|
||||||
|
from django.db.models import Max
|
||||||
|
from django.forms.models import modelform_factory
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils import timezone
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic import DetailView, ListView, View
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||||
|
|
||||||
|
from club.models import Club, Mailing
|
||||||
|
from com.models import News, NewsDate, Poster, Screen, Sith, Weekmail, WeekmailArticle
|
||||||
|
from core.models import Notification, RealGroup, User
|
||||||
from core.views import (
|
from core.views import (
|
||||||
CanViewMixin,
|
CanCreateMixin,
|
||||||
CanEditMixin,
|
CanEditMixin,
|
||||||
CanEditPropMixin,
|
CanEditPropMixin,
|
||||||
TabedViewMixin,
|
CanViewMixin,
|
||||||
CanCreateMixin,
|
|
||||||
QuickNotifMixin,
|
QuickNotifMixin,
|
||||||
|
TabedViewMixin,
|
||||||
)
|
)
|
||||||
from core.views.forms import SelectDateTime, MarkdownInput
|
from core.views.forms import MarkdownInput, TzAwareDateTimeField
|
||||||
from core.models import Notification, RealGroup, User
|
|
||||||
from club.models import Club, Mailing
|
|
||||||
from core.views.forms import TzAwareDateTimeField
|
|
||||||
|
|
||||||
|
|
||||||
# Sith object
|
# Sith object
|
||||||
|
|
||||||
|
14
conftest.py
Normal file
14
conftest.py
Normal 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")
|
@ -14,12 +14,12 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.contrib import admin
|
|
||||||
from ajax_select import make_ajax_form
|
from ajax_select import make_ajax_form
|
||||||
from core.models import User, Page, RealGroup, MetaGroup, SithFile
|
from django.contrib import admin
|
||||||
from django.contrib.auth.models import Group as AuthGroup
|
from django.contrib.auth.models import Group as AuthGroup
|
||||||
from haystack.admin import SearchModelAdmin
|
from haystack.admin import SearchModelAdmin
|
||||||
|
|
||||||
|
from core.models import MetaGroup, Page, RealGroup, SithFile, User
|
||||||
|
|
||||||
admin.site.unregister(AuthGroup)
|
admin.site.unregister(AuthGroup)
|
||||||
admin.site.register(MetaGroup)
|
admin.site.register(MetaGroup)
|
||||||
|
@ -34,8 +34,8 @@ class SithConfig(AppConfig):
|
|||||||
verbose_name = "Core app of the Sith"
|
verbose_name = "Core app of the Sith"
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
|
import core.signals # noqa F401
|
||||||
from forum.models import Forum
|
from forum.models import Forum
|
||||||
import core.signals
|
|
||||||
|
|
||||||
cache.clear()
|
cache.clear()
|
||||||
|
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
from core.models import Page
|
|
||||||
|
|
||||||
|
|
||||||
class FourDigitYearConverter:
|
class FourDigitYearConverter:
|
||||||
regex = "[0-9]{4}"
|
regex = "[0-9]{4}"
|
||||||
|
|
||||||
|
@ -14,15 +14,14 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
|
from ajax_select import LookupChannel, register
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from ajax_select import register, LookupChannel
|
|
||||||
|
|
||||||
from core.views.site import search_user
|
|
||||||
from core.models import User, Group, SithFile
|
|
||||||
from club.models import Club
|
|
||||||
from counter.models import Product, Counter, Customer
|
|
||||||
from accounting.models import ClubAccount, Company
|
from accounting.models import ClubAccount, Company
|
||||||
from eboutic.models import BasketItem
|
from club.models import Club
|
||||||
|
from core.models import Group, SithFile, User
|
||||||
|
from core.views.site import search_user
|
||||||
|
from counter.models import Counter, Customer, Product
|
||||||
|
|
||||||
|
|
||||||
def check_token(request):
|
def check_token(request):
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.core.management import call_command
|
|
||||||
|
|
||||||
from core.models import SithFile
|
from core.models import SithFile
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.management.commands import compilemessages
|
from django.core.management.commands import compilemessages
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,9 +24,10 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import sass
|
import sass
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
@ -24,10 +24,9 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
from http.server import test, CGIHTTPRequestHandler
|
from http.server import CGIHTTPRequestHandler, test
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.utils import autoreload
|
from django.utils import autoreload
|
||||||
|
68
core/management/commands/install_xapian.py
Normal file
68
core/management/commands/install_xapian.py
Normal 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")
|
47
core/management/commands/install_xapian.sh
Executable file
47
core/management/commands/install_xapian.sh
Executable 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"
|
@ -23,6 +23,7 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
from core.markdown import markdown
|
from core.markdown import markdown
|
||||||
|
@ -24,38 +24,37 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from io import StringIO, BytesIO
|
from io import BytesIO, StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from django.contrib.auth.models import Permission
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.core.management import call_command
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import connection
|
from django.contrib.auth.models import Permission
|
||||||
from django.contrib.sites.models import Site
|
from django.contrib.sites.models import Site
|
||||||
|
from django.core.management import call_command
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import connection
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from core.models import Group, User, Page, PageRev, SithFile
|
|
||||||
from accounting.models import (
|
from accounting.models import (
|
||||||
GeneralJournal,
|
AccountingType,
|
||||||
BankAccount,
|
BankAccount,
|
||||||
ClubAccount,
|
ClubAccount,
|
||||||
Operation,
|
|
||||||
AccountingType,
|
|
||||||
SimplifiedAccountingType,
|
|
||||||
Company,
|
Company,
|
||||||
|
GeneralJournal,
|
||||||
|
Operation,
|
||||||
|
SimplifiedAccountingType,
|
||||||
)
|
)
|
||||||
from core.utils import resize_image
|
|
||||||
from club.models import Club, Membership
|
from club.models import Club, Membership
|
||||||
from subscription.models import Subscription
|
from com.models import News, NewsDate, Sith, Weekmail
|
||||||
from counter.models import Customer, ProductType, Product, Counter, Selling, StudentCard
|
from core.models import Group, Page, PageRev, SithFile, User
|
||||||
from com.models import Sith, Weekmail, News, NewsDate
|
from core.utils import resize_image
|
||||||
from election.models import Election, Role, Candidature, ElectionList
|
from counter.models import Counter, Customer, Product, ProductType, Selling, StudentCard
|
||||||
|
from election.models import Candidature, Election, ElectionList, Role
|
||||||
from forum.models import Forum, ForumTopic
|
from forum.models import Forum, ForumTopic
|
||||||
from pedagogy.models import UV
|
from pedagogy.models import UV
|
||||||
from sas.models import Album, Picture, PeoplePictureRelation
|
from sas.models import Album, PeoplePictureRelation, Picture
|
||||||
|
from subscription.models import Subscription
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@ -1099,8 +1098,10 @@ Welcome to the wiki page!
|
|||||||
n = News(
|
n = News(
|
||||||
title="Repas barman",
|
title="Repas barman",
|
||||||
summary="Enjoy la fin du semestre!",
|
summary="Enjoy la fin du semestre!",
|
||||||
content="Viens donc t'enjailler avec les autres barmans aux "
|
content=(
|
||||||
"frais du BdF! \o/",
|
"Viens donc t'enjailler avec les autres barmans aux "
|
||||||
|
"frais du BdF! \\o/"
|
||||||
|
),
|
||||||
type="EVENT",
|
type="EVENT",
|
||||||
club=bar_club,
|
club=bar_club,
|
||||||
author=subscriber,
|
author=subscriber,
|
||||||
|
@ -23,8 +23,8 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from django.core.management import call_command
|
|
||||||
|
|
||||||
from core.models import SithFile
|
from core.models import SithFile
|
||||||
|
|
||||||
|
@ -15,8 +15,9 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
@ -16,8 +16,9 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
from mistune import Renderer, InlineGrammar, InlineLexer, Markdown, escape, escape_link
|
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from mistune import InlineGrammar, InlineLexer, Markdown, Renderer, escape, escape_link
|
||||||
|
|
||||||
|
|
||||||
class SithRenderer(Renderer):
|
class SithRenderer(Renderer):
|
||||||
|
@ -16,12 +16,13 @@
|
|||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.functional import SimpleLazyObject
|
|
||||||
from django.contrib.auth import get_user
|
from django.contrib.auth import get_user
|
||||||
from django.contrib.auth.middleware import (
|
from django.contrib.auth.middleware import (
|
||||||
AuthenticationMiddleware as DjangoAuthenticationMiddleware,
|
AuthenticationMiddleware as DjangoAuthenticationMiddleware,
|
||||||
)
|
)
|
||||||
|
from django.utils.functional import SimpleLazyObject
|
||||||
|
|
||||||
module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1)
|
module, klass = settings.AUTH_ANONYMOUS_MODEL.rsplit(".", 1)
|
||||||
AnonymousUser = getattr(importlib.import_module(module), klass)
|
AnonymousUser = getattr(importlib.import_module(module), klass)
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.contrib.auth.models
|
import django.contrib.auth.models
|
||||||
import django.db.models.deletion
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
import core.models
|
import django.db.models.deletion
|
||||||
import phonenumber_field.modelfields
|
import phonenumber_field.modelfields
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
import django.db.models.deletion
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import core.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
import core.models
|
import core.models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.utils.timezone
|
import django.utils.timezone
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
import core.models
|
import core.models
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.utils.timezone
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.core.validators
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
from django.conf import settings
|
|
||||||
import django.utils.timezone
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import core.models
|
|
||||||
import django.db.models.deletion
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import core.models
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
# Generated by Django 2.2.6 on 2019-11-14 15:10
|
# Generated by Django 2.2.6 on 2019-11-14 15:10
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
import django.db.models.deletion
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
class Migration(migrations.Migration):
|
||||||
|
19
core/migrations/0038_alter_preferences_receive_weekmail.py
Normal file
19
core/migrations/0038_alter_preferences_receive_weekmail.py
Normal 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"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -23,36 +23,39 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
import importlib
|
import importlib
|
||||||
from typing import Union, Optional, List
|
import os
|
||||||
|
import unicodedata
|
||||||
|
from datetime import date, timedelta
|
||||||
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from django.core.cache import cache
|
from django.conf import settings
|
||||||
from django.core.mail import send_mail
|
|
||||||
from django.contrib.auth.models import (
|
from django.contrib.auth.models import (
|
||||||
AbstractBaseUser,
|
AbstractBaseUser,
|
||||||
UserManager,
|
UserManager,
|
||||||
Group as AuthGroup,
|
)
|
||||||
GroupManager as AuthGroupManager,
|
from django.contrib.auth.models import (
|
||||||
AnonymousUser as AuthAnonymousUser,
|
AnonymousUser as AuthAnonymousUser,
|
||||||
)
|
)
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.contrib.auth.models import (
|
||||||
from django.utils import timezone
|
Group as AuthGroup,
|
||||||
from django.core import validators
|
)
|
||||||
from django.core.exceptions import ValidationError, PermissionDenied
|
from django.contrib.auth.models import (
|
||||||
from django.urls import reverse
|
GroupManager as AuthGroupManager,
|
||||||
from django.conf import settings
|
)
|
||||||
from django.db import models, transaction
|
|
||||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||||
from django.utils.html import escape
|
from django.core import validators
|
||||||
|
from django.core.cache import cache
|
||||||
|
from django.core.exceptions import PermissionDenied, ValidationError
|
||||||
|
from django.core.mail import send_mail
|
||||||
|
from django.db import models, transaction
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
|
from django.utils.html import escape
|
||||||
import os
|
from django.utils.translation import gettext_lazy as _
|
||||||
from core import utils
|
|
||||||
|
|
||||||
from phonenumber_field.modelfields import PhoneNumberField
|
from phonenumber_field.modelfields import PhoneNumberField
|
||||||
|
|
||||||
from datetime import timedelta, date
|
from core import utils
|
||||||
|
|
||||||
import unicodedata
|
|
||||||
|
|
||||||
|
|
||||||
class RealGroupManager(AuthGroupManager):
|
class RealGroupManager(AuthGroupManager):
|
||||||
@ -698,9 +701,11 @@ class User(AbstractBaseUser):
|
|||||||
<em>%s</em>
|
<em>%s</em>
|
||||||
</a>
|
</a>
|
||||||
""" % (
|
""" % (
|
||||||
|
(
|
||||||
self.profile_pict.get_download_url()
|
self.profile_pict.get_download_url()
|
||||||
if self.profile_pict
|
if self.profile_pict
|
||||||
else staticfiles_storage.url("core/img/unknown.jpg"),
|
else staticfiles_storage.url("core/img/unknown.jpg")
|
||||||
|
),
|
||||||
_("Profile"),
|
_("Profile"),
|
||||||
escape(self.get_display_name()),
|
escape(self.get_display_name()),
|
||||||
)
|
)
|
||||||
|
@ -23,9 +23,9 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This page is useful for custom migration tricks.
|
This page is useful for custom migration tricks.
|
||||||
Sometimes, when you need to have a migration hack and you think it can be
|
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.
|
useful again, put it there, we never know if we might need the hack again.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from django.db import connection, migrations
|
from django.db import connection, migrations
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.staticfiles.finders import FileSystemFinder
|
from django.contrib.staticfiles.finders import FileSystemFinder
|
||||||
from django.core.files.storage import FileSystemStorage
|
from django.core.files.storage import FileSystemStorage
|
||||||
|
@ -24,12 +24,14 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sass
|
|
||||||
from urllib.parse import urljoin
|
from urllib.parse import urljoin
|
||||||
from django.utils.encoding import force_bytes, iri_to_uri
|
|
||||||
|
import sass
|
||||||
|
from django.conf import settings
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from django.templatetags.static import static
|
from django.templatetags.static import static
|
||||||
from django.conf import settings
|
from django.utils.encoding import force_bytes, iri_to_uri
|
||||||
|
|
||||||
from core.scss.storage import ScssFileStorage, find_file
|
from core.scss.storage import ScssFileStorage, find_file
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from haystack import indexes, signals
|
from haystack import indexes, signals
|
||||||
|
|
||||||
from core.models import User
|
from core.models import User
|
||||||
|
@ -8,10 +8,10 @@ from core.models import User
|
|||||||
@receiver(m2m_changed, sender=User.groups.through, dispatch_uid="user_groups_changed")
|
@receiver(m2m_changed, sender=User.groups.through, dispatch_uid="user_groups_changed")
|
||||||
def user_groups_changed(sender, instance: User, **kwargs):
|
def user_groups_changed(sender, instance: User, **kwargs):
|
||||||
"""
|
"""
|
||||||
Clear the cached clubs of the user
|
Clear the cached groups of the user
|
||||||
"""
|
"""
|
||||||
# As a m2m relationship doesn't live within the model
|
# As a m2m relationship doesn't live within the model
|
||||||
# but rather on an intermediary table, there is no
|
# but rather on an intermediary table, there is no
|
||||||
# model method to override, meaning we must use
|
# model method to override, meaning we must use
|
||||||
# a signal to invalidate the cache when a user is removed from a club
|
# a signal to invalidate the cache when a user is removed from a group
|
||||||
cache.delete(f"user_{instance.id}_groups")
|
cache.delete(f"user_{instance.pk}_groups")
|
||||||
|
@ -162,8 +162,9 @@
|
|||||||
<div>
|
<div>
|
||||||
{% trans %}Not subscribed{% endtrans %}
|
{% trans %}Not subscribed{% endtrans %}
|
||||||
{% if user.is_board_member %}
|
{% if user.is_board_member %}
|
||||||
<a href="{{ url('subscription:subscription') }}?member={{ profile.id }}">{% trans %}New subscription{% endtrans
|
<a href="{{ url('subscription:subscription') }}?member={{ profile.id }}">
|
||||||
%}</a>
|
{% trans %}New subscription{% endtrans %}
|
||||||
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,8 +133,9 @@
|
|||||||
</p>
|
</p>
|
||||||
{%- elif user.is_root -%}
|
{%- elif user.is_root -%}
|
||||||
<p>
|
<p>
|
||||||
<a href="{{ url('core:password_root_change', user_id=form.instance.id) }}">{%- trans -%}Change user password{%-
|
<a href="{{ url('core:password_root_change', user_id=form.instance.id) }}">
|
||||||
endtrans -%}</a>
|
{%- trans -%}Change user password{%- endtrans -%}
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
{%- endif -%}
|
{%- endif -%}
|
||||||
|
|
||||||
|
@ -24,15 +24,15 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import phonenumbers
|
|
||||||
|
|
||||||
|
import phonenumbers
|
||||||
from django import template
|
from django import template
|
||||||
from django.template.defaultfilters import stringfilter
|
from django.template.defaultfilters import stringfilter
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import ngettext
|
from django.utils.translation import ngettext
|
||||||
from core.scss.processor import ScssProcessor
|
|
||||||
|
|
||||||
from core.markdown import markdown as md
|
from core.markdown import markdown as md
|
||||||
|
from core.scss.processor import ScssProcessor
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from django.template.exceptions import TemplateSyntaxError
|
|
||||||
from django import template
|
from django import template
|
||||||
from django.template.defaultfilters import stringfilter
|
from django.template.defaultfilters import stringfilter
|
||||||
|
from django.template.exceptions import TemplateSyntaxError
|
||||||
|
|
||||||
register = template.Library()
|
register = template.Library()
|
||||||
|
|
||||||
|
461
core/tests.py
461
core/tests.py
@ -18,10 +18,12 @@ import os
|
|||||||
from datetime import date, timedelta
|
from datetime import date, timedelta
|
||||||
|
|
||||||
import freezegun
|
import freezegun
|
||||||
|
import pytest
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
from django.test import Client, TestCase
|
from django.test import TestCase
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
from pytest_django.asserts import assertRedirects
|
||||||
|
|
||||||
from club.models import Membership
|
from club.models import Membership
|
||||||
from core.markdown import markdown
|
from core.markdown import markdown
|
||||||
@ -29,270 +31,105 @@ from core.models import AnonymousUser, Group, Page, User
|
|||||||
from core.utils import get_semester_code, get_start_of_semester
|
from core.utils import get_semester_code, get_start_of_semester
|
||||||
from sith import settings
|
from sith import settings
|
||||||
|
|
||||||
"""
|
|
||||||
to run these tests :
|
|
||||||
python3 manage.py test
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
class UserRegistrationTest(TestCase):
|
class TestUserRegistration:
|
||||||
@classmethod
|
@pytest.fixture()
|
||||||
def setUpTestData(cls):
|
def valid_payload(self):
|
||||||
User.objects.all().delete()
|
return {
|
||||||
|
"first_name": "this user does not exist (yet)",
|
||||||
def test_register_user_form_ok(self):
|
"last_name": "this user does not exist (yet)",
|
||||||
"""
|
"email": "i-dont-exist-yet@git.an",
|
||||||
Should register a user correctly
|
|
||||||
"""
|
|
||||||
c = Client()
|
|
||||||
response = c.post(
|
|
||||||
reverse("core:register"),
|
|
||||||
{
|
|
||||||
"first_name": "Guy",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "guy@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
"password1": "plop",
|
||||||
"password2": "plop",
|
"password2": "plop",
|
||||||
"captcha_0": "dummy-value",
|
"captcha_0": "dummy-value",
|
||||||
"captcha_1": "PASSED",
|
"captcha_1": "PASSED",
|
||||||
},
|
}
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue("TEST_REGISTER_USER_FORM_OK" in str(response.content))
|
|
||||||
|
|
||||||
def test_register_user_form_fail_password(self):
|
def test_register_user_form_ok(self, client, valid_payload):
|
||||||
"""
|
"""Should register a user correctly."""
|
||||||
Should not register a user correctly
|
response = client.post(reverse("core:register"), valid_payload)
|
||||||
"""
|
assert response.status_code == 200
|
||||||
c = Client()
|
assert "TEST_REGISTER_USER_FORM_OK" in str(response.content)
|
||||||
response = c.post(
|
|
||||||
reverse("core:register"),
|
|
||||||
{
|
|
||||||
"first_name": "Guy",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop2",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
|
|
||||||
|
|
||||||
def test_register_user_form_fail_email(self):
|
@pytest.mark.parametrize(
|
||||||
"""
|
"payload_edit",
|
||||||
Should not register a user correctly
|
[
|
||||||
"""
|
{"password2": "not the same as password1"},
|
||||||
c = Client()
|
{"email": "not-an-email"},
|
||||||
response = c.post(
|
{"first_name": ""},
|
||||||
reverse("core:register"),
|
{"last_name": ""},
|
||||||
{
|
{"captcha_1": "WRONG_CAPTCHA"},
|
||||||
"first_name": "Guy",
|
],
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou.git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
def test_register_user_form_fail(self, client, valid_payload, payload_edit):
|
||||||
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
|
"""Should not register a user correctly."""
|
||||||
|
payload = valid_payload | payload_edit
|
||||||
|
response = client.post(reverse("core:register"), payload)
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content)
|
||||||
|
|
||||||
def test_register_user_form_fail_missing_name(self):
|
def test_register_user_form_fail_already_exists(self, client, valid_payload):
|
||||||
"""
|
"""Should not register a user correctly if it already exists."""
|
||||||
Should not register a user correctly
|
# create the user, then try to create it again
|
||||||
"""
|
client.post(reverse("core:register"), valid_payload)
|
||||||
c = Client()
|
response = client.post(reverse("core:register"), valid_payload)
|
||||||
response = c.post(
|
assert response.status_code == 200
|
||||||
reverse("core:register"),
|
assert "TEST_REGISTER_USER_FORM_FAIL" in str(response.content)
|
||||||
{
|
|
||||||
"first_name": "Guy",
|
|
||||||
"last_name": "",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
|
|
||||||
|
|
||||||
def test_register_user_form_fail_missing_date_of_birth(self):
|
|
||||||
"""
|
|
||||||
Should not register a user correctly
|
|
||||||
"""
|
|
||||||
c = Client()
|
|
||||||
response = c.post(
|
|
||||||
reverse("core:register"),
|
|
||||||
{
|
|
||||||
"first_name": "",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
|
|
||||||
|
|
||||||
def test_register_user_form_fail_missing_first_name(self):
|
@pytest.mark.django_db
|
||||||
"""
|
class TestUserLogin:
|
||||||
Should not register a user correctly
|
@pytest.fixture()
|
||||||
"""
|
def user(self) -> User:
|
||||||
c = Client()
|
return User.objects.first()
|
||||||
response = c.post(
|
|
||||||
reverse("core:register"),
|
|
||||||
{
|
|
||||||
"first_name": "",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
|
|
||||||
|
|
||||||
def test_register_user_form_fail_wrong_captcha(self):
|
def test_login_fail(self, client, user):
|
||||||
"""
|
|
||||||
Should not register a user correctly
|
|
||||||
"""
|
|
||||||
c = Client()
|
|
||||||
response = c.post(
|
|
||||||
reverse("core:register"),
|
|
||||||
{
|
|
||||||
"first_name": "Bibou",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "WRONG_CAPTCHA",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
|
|
||||||
|
|
||||||
def test_register_user_form_fail_already_exists(self):
|
|
||||||
"""
|
|
||||||
Should not register a user correctly
|
|
||||||
"""
|
|
||||||
c = Client()
|
|
||||||
c.post(
|
|
||||||
reverse("core:register"),
|
|
||||||
{
|
|
||||||
"first_name": "Guy",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
response = c.post(
|
|
||||||
reverse("core:register"),
|
|
||||||
{
|
|
||||||
"first_name": "Bibou",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 200)
|
|
||||||
self.assertTrue("TEST_REGISTER_USER_FORM_FAIL" in str(response.content))
|
|
||||||
|
|
||||||
def test_login_success(self):
|
|
||||||
"""
|
|
||||||
Should login a user correctly
|
|
||||||
"""
|
|
||||||
c = Client()
|
|
||||||
c.post(
|
|
||||||
reverse("core:register"),
|
|
||||||
{
|
|
||||||
"first_name": "Guy",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
response = c.post(
|
|
||||||
reverse("core:login"), {"username": "gcarlier", "password": "plop"}
|
|
||||||
)
|
|
||||||
self.assertTrue(response.status_code == 302)
|
|
||||||
# self.assertTrue('Hello, world' in str(response.content))
|
|
||||||
|
|
||||||
def test_login_fail(self):
|
|
||||||
"""
|
"""
|
||||||
Should not login a user correctly
|
Should not login a user correctly
|
||||||
"""
|
"""
|
||||||
c = Client()
|
|
||||||
c.post(
|
response = client.post(
|
||||||
reverse("core:register"),
|
reverse("core:login"),
|
||||||
{
|
{"username": user.username, "password": "wrong-password"},
|
||||||
"first_name": "Guy",
|
|
||||||
"last_name": "Carlier",
|
|
||||||
"email": "bibou@git.an",
|
|
||||||
"date_of_birth": "12/6/1942",
|
|
||||||
"password1": "plop",
|
|
||||||
"password2": "plop",
|
|
||||||
"captcha_0": "dummy-value",
|
|
||||||
"captcha_1": "PASSED",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
response = c.post(
|
assert response.status_code == 200
|
||||||
reverse("core:login"), {"username": "gcarlier", "password": "guy"}
|
assert (
|
||||||
)
|
'<p class="alert alert-red">Votre nom d\'utilisateur '
|
||||||
self.assertTrue(response.status_code == 200)
|
"et votre mot de passe ne correspondent pas. Merci de réessayer.</p>"
|
||||||
self.assertTrue(
|
) in str(response.content.decode())
|
||||||
"""<p class="alert alert-red">Votre nom d\\'utilisateur et votre mot de passe ne correspondent pas. Merci de r\\xc3\\xa9essayer.</p>"""
|
|
||||||
in str(response.content)
|
def test_login_success(self, client, user):
|
||||||
|
"""
|
||||||
|
Should login a user correctly
|
||||||
|
"""
|
||||||
|
response = client.post(
|
||||||
|
reverse("core:login"), {"username": user.username, "password": "plop"}
|
||||||
)
|
)
|
||||||
|
assertRedirects(response, reverse("core:index"))
|
||||||
|
|
||||||
|
|
||||||
class MarkdownTest(TestCase):
|
def test_full_markdown_syntax():
|
||||||
def test_full_markdown_syntax(self):
|
|
||||||
root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
root_path = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file:
|
with open(os.path.join(root_path) + "/doc/SYNTAX.md", "r") as md_file:
|
||||||
md = md_file.read()
|
md = md_file.read()
|
||||||
with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file:
|
with open(os.path.join(root_path) + "/doc/SYNTAX.html", "r") as html_file:
|
||||||
html = html_file.read()
|
html = html_file.read()
|
||||||
result = markdown(md)
|
result = markdown(md)
|
||||||
self.assertTrue(result == html)
|
assert result == html
|
||||||
|
|
||||||
|
|
||||||
class PageHandlingTest(TestCase):
|
class PageHandlingTest(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.root = User.objects.get(username="root")
|
||||||
|
cls.root_group = Group.objects.get(name="Root")
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.client.login(username="root", password="plop")
|
self.client.force_login(self.root)
|
||||||
self.root_group = Group.objects.get(name="Root")
|
|
||||||
|
|
||||||
def test_create_page_ok(self):
|
def test_create_page_ok(self):
|
||||||
"""
|
"""Should create a page correctly."""
|
||||||
Should create a page correctly
|
|
||||||
"""
|
|
||||||
|
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("core:page_new"),
|
reverse("core:page_new"),
|
||||||
@ -301,19 +138,17 @@ class PageHandlingTest(TestCase):
|
|||||||
self.assertRedirects(
|
self.assertRedirects(
|
||||||
response, reverse("core:page", kwargs={"page_name": "guy"})
|
response, reverse("core:page", kwargs={"page_name": "guy"})
|
||||||
)
|
)
|
||||||
self.assertTrue(Page.objects.filter(name="guy").exists())
|
assert Page.objects.filter(name="guy").exists()
|
||||||
|
|
||||||
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
|
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
|
||||||
self.assertEqual(response.status_code, 200)
|
assert response.status_code == 200
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
self.assertIn('<a href="/page/guy/hist/">', html)
|
assert '<a href="/page/guy/hist/">' in html
|
||||||
self.assertIn('<a href="/page/guy/edit/">', html)
|
assert '<a href="/page/guy/edit/">' in html
|
||||||
self.assertIn('<a href="/page/guy/prop/">', html)
|
assert '<a href="/page/guy/prop/">' in html
|
||||||
|
|
||||||
def test_create_child_page_ok(self):
|
def test_create_child_page_ok(self):
|
||||||
"""
|
"""Should create a page correctly."""
|
||||||
Should create a page correctly
|
|
||||||
"""
|
|
||||||
# remove all other pages to make sure there is no side effect
|
# remove all other pages to make sure there is no side effect
|
||||||
Page.objects.all().delete()
|
Page.objects.all().delete()
|
||||||
self.client.post(
|
self.client.post(
|
||||||
@ -321,7 +156,7 @@ class PageHandlingTest(TestCase):
|
|||||||
{"parent": "", "name": "guy", "owner_group": str(self.root_group.id)},
|
{"parent": "", "name": "guy", "owner_group": str(self.root_group.id)},
|
||||||
)
|
)
|
||||||
page = Page.objects.first()
|
page = Page.objects.first()
|
||||||
response = self.client.post(
|
self.client.post(
|
||||||
reverse("core:page_new"),
|
reverse("core:page_new"),
|
||||||
{
|
{
|
||||||
"parent": str(page.id),
|
"parent": str(page.id),
|
||||||
@ -332,8 +167,8 @@ class PageHandlingTest(TestCase):
|
|||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("core:page", kwargs={"page_name": "guy/bibou"})
|
reverse("core:page", kwargs={"page_name": "guy/bibou"})
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
assert response.status_code == 200
|
||||||
self.assertTrue('<a href="/page/guy/bibou/">' in str(response.content))
|
assert '<a href="/page/guy/bibou/">' in str(response.content)
|
||||||
|
|
||||||
def test_access_child_page_ok(self):
|
def test_access_child_page_ok(self):
|
||||||
"""
|
"""
|
||||||
@ -346,7 +181,7 @@ class PageHandlingTest(TestCase):
|
|||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("core:page", kwargs={"page_name": "guy/bibou"})
|
reverse("core:page", kwargs={"page_name": "guy/bibou"})
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
assert response.status_code == 200
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
self.assertIn('<a href="/page/guy/bibou/edit/">', html)
|
self.assertIn('<a href="/page/guy/bibou/edit/">', html)
|
||||||
|
|
||||||
@ -355,7 +190,7 @@ class PageHandlingTest(TestCase):
|
|||||||
Should not display a page correctly
|
Should not display a page correctly
|
||||||
"""
|
"""
|
||||||
response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"}))
|
response = self.client.get(reverse("core:page", kwargs={"page_name": "swagg"}))
|
||||||
self.assertTrue(response.status_code == 200)
|
assert response.status_code == 200
|
||||||
html = response.content.decode()
|
html = response.content.decode()
|
||||||
self.assertIn('<a href="/page/create/?page=swagg">', html)
|
self.assertIn('<a href="/page/create/?page=swagg">', html)
|
||||||
|
|
||||||
@ -383,8 +218,8 @@ http://git.an
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
|
response = self.client.get(reverse("core:page", kwargs={"page_name": "guy"}))
|
||||||
self.assertTrue(response.status_code == 200)
|
assert response.status_code == 200
|
||||||
self.assertTrue(
|
assert (
|
||||||
'<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n'
|
'<p>Guy <em>bibou</em></p>\\n<p><a href="http://git.an">http://git.an</a></p>\\n'
|
||||||
+ "<h1>Swag</h1>\\n<guy>Bibou</guy>"
|
+ "<h1>Swag</h1>\\n<guy>Bibou</guy>"
|
||||||
+ "<script>alert(\\'Guy\\');</script>"
|
+ "<script>alert(\\'Guy\\');</script>"
|
||||||
@ -392,35 +227,19 @@ http://git.an
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class UserToolsTest(TestCase):
|
class UserToolsTest:
|
||||||
def test_anonymous_user_unauthorized(self):
|
def test_anonymous_user_unauthorized(self, client):
|
||||||
response = self.client.get(reverse("core:user_tools"))
|
"""An anonymous user shouldn't have access to the tools page"""
|
||||||
self.assertEqual(response.status_code, 403)
|
response = client.get(reverse("core:user_tools"))
|
||||||
|
assert response.status_code == 403
|
||||||
|
|
||||||
def test_page_is_working(self):
|
@pytest.mark.parametrize("username", ["guy", "root", "skia", "comunity"])
|
||||||
|
def test_page_is_working(self, client, username):
|
||||||
|
"""All existing users should be able to see the test page"""
|
||||||
# Test for simple user
|
# Test for simple user
|
||||||
self.client.login(username="guy", password="plop")
|
client.force_login(User.objects.get(username=username))
|
||||||
response = self.client.get(reverse("core:user_tools"))
|
response = client.get(reverse("core:user_tools"))
|
||||||
self.assertNotEqual(response.status_code, 500)
|
assert response.status_code == 200
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# Test for root
|
|
||||||
self.client.login(username="root", password="plop")
|
|
||||||
response = self.client.get(reverse("core:user_tools"))
|
|
||||||
self.assertNotEqual(response.status_code, 500)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# Test for skia
|
|
||||||
self.client.login(username="skia", password="plop")
|
|
||||||
response = self.client.get(reverse("core:user_tools"))
|
|
||||||
self.assertNotEqual(response.status_code, 500)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
# Test for comunity
|
|
||||||
self.client.login(username="comunity", password="plop")
|
|
||||||
response = self.client.get(reverse("core:user_tools"))
|
|
||||||
self.assertNotEqual(response.status_code, 500)
|
|
||||||
self.assertEqual(response.status_code, 200)
|
|
||||||
|
|
||||||
|
|
||||||
# TODO: many tests on the pages:
|
# TODO: many tests on the pages:
|
||||||
@ -442,12 +261,12 @@ class FileHandlingTest(TestCase):
|
|||||||
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
|
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id}),
|
||||||
{"folder_name": "GUY_folder_test"},
|
{"folder_name": "GUY_folder_test"},
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 302)
|
assert response.status_code == 302
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
|
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
assert response.status_code == 200
|
||||||
self.assertTrue("GUY_folder_test</a>" in str(response.content))
|
assert "GUY_folder_test</a>" in str(response.content)
|
||||||
|
|
||||||
def test_upload_file_home(self):
|
def test_upload_file_home(self):
|
||||||
with open("/bin/ls", "rb") as f:
|
with open("/bin/ls", "rb") as f:
|
||||||
@ -457,12 +276,12 @@ class FileHandlingTest(TestCase):
|
|||||||
),
|
),
|
||||||
{"file_field": f},
|
{"file_field": f},
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 302)
|
assert response.status_code == 302
|
||||||
response = self.client.get(
|
response = self.client.get(
|
||||||
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
|
reverse("core:file_detail", kwargs={"file_id": self.subscriber.home.id})
|
||||||
)
|
)
|
||||||
self.assertTrue(response.status_code == 200)
|
assert response.status_code == 200
|
||||||
self.assertTrue("ls</a>" in str(response.content))
|
assert "ls</a>" in str(response.content)
|
||||||
|
|
||||||
|
|
||||||
class UserIsInGroupTest(TestCase):
|
class UserIsInGroupTest(TestCase):
|
||||||
@ -477,6 +296,10 @@ class UserIsInGroupTest(TestCase):
|
|||||||
|
|
||||||
cls.root_group = Group.objects.get(name="Root")
|
cls.root_group = Group.objects.get(name="Root")
|
||||||
cls.public = Group.objects.get(name="Public")
|
cls.public = Group.objects.get(name="Public")
|
||||||
|
cls.skia = User.objects.get(username="skia")
|
||||||
|
cls.toto = User.objects.create(
|
||||||
|
username="toto", first_name="a", last_name="b", email="a.b@toto.fr"
|
||||||
|
)
|
||||||
cls.subscribers = Group.objects.get(name="Subscribers")
|
cls.subscribers = Group.objects.get(name="Subscribers")
|
||||||
cls.old_subscribers = Group.objects.get(name="Old subscribers")
|
cls.old_subscribers = Group.objects.get(name="Old subscribers")
|
||||||
cls.accounting_admin = Group.objects.get(name="Accounting admin")
|
cls.accounting_admin = Group.objects.get(name="Accounting admin")
|
||||||
@ -493,21 +316,15 @@ class UserIsInGroupTest(TestCase):
|
|||||||
)
|
)
|
||||||
cls.main_club = Club.objects.get(id=1)
|
cls.main_club = Club.objects.get(id=1)
|
||||||
|
|
||||||
def setUp(self) -> None:
|
|
||||||
self.toto = User.objects.create(
|
|
||||||
username="toto", first_name="a", last_name="b", email="a.b@toto.fr"
|
|
||||||
)
|
|
||||||
self.skia = User.objects.get(username="skia")
|
|
||||||
|
|
||||||
def assert_in_public_group(self, user):
|
def assert_in_public_group(self, user):
|
||||||
self.assertTrue(user.is_in_group(pk=self.public.id))
|
assert user.is_in_group(pk=self.public.id)
|
||||||
self.assertTrue(user.is_in_group(name=self.public.name))
|
assert user.is_in_group(name=self.public.name)
|
||||||
|
|
||||||
def assert_in_club_metagroups(self, user, club):
|
def assert_in_club_metagroups(self, user, club):
|
||||||
meta_groups_board = club.unix_name + settings.SITH_BOARD_SUFFIX
|
meta_groups_board = club.unix_name + settings.SITH_BOARD_SUFFIX
|
||||||
meta_groups_members = club.unix_name + settings.SITH_MEMBER_SUFFIX
|
meta_groups_members = club.unix_name + settings.SITH_MEMBER_SUFFIX
|
||||||
self.assertFalse(user.is_in_group(name=meta_groups_board))
|
assert user.is_in_group(name=meta_groups_board) is False
|
||||||
self.assertFalse(user.is_in_group(name=meta_groups_members))
|
assert user.is_in_group(name=meta_groups_members) is False
|
||||||
|
|
||||||
def assert_only_in_public_group(self, user):
|
def assert_only_in_public_group(self, user):
|
||||||
self.assert_in_public_group(user)
|
self.assert_in_public_group(user)
|
||||||
@ -519,12 +336,12 @@ class UserIsInGroupTest(TestCase):
|
|||||||
self.subscribers,
|
self.subscribers,
|
||||||
self.old_subscribers,
|
self.old_subscribers,
|
||||||
):
|
):
|
||||||
self.assertFalse(user.is_in_group(pk=group.pk))
|
assert not user.is_in_group(pk=group.pk)
|
||||||
self.assertFalse(user.is_in_group(name=group.name))
|
assert not user.is_in_group(name=group.name)
|
||||||
meta_groups_board = self.club.unix_name + settings.SITH_BOARD_SUFFIX
|
meta_groups_board = self.club.unix_name + settings.SITH_BOARD_SUFFIX
|
||||||
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
|
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
|
||||||
self.assertFalse(user.is_in_group(name=meta_groups_board))
|
assert user.is_in_group(name=meta_groups_board) is False
|
||||||
self.assertFalse(user.is_in_group(name=meta_groups_members))
|
assert user.is_in_group(name=meta_groups_members) is False
|
||||||
|
|
||||||
def test_anonymous_user(self):
|
def test_anonymous_user(self):
|
||||||
"""
|
"""
|
||||||
@ -583,15 +400,13 @@ class UserIsInGroupTest(TestCase):
|
|||||||
)
|
)
|
||||||
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
|
meta_groups_members = self.club.unix_name + settings.SITH_MEMBER_SUFFIX
|
||||||
cache.clear()
|
cache.clear()
|
||||||
self.assertTrue(self.toto.is_in_group(name=meta_groups_members))
|
assert self.toto.is_in_group(name=meta_groups_members) is True
|
||||||
self.assertEqual(
|
assert membership == cache.get(f"membership_{self.club.id}_{self.toto.id}")
|
||||||
membership, cache.get(f"membership_{self.club.id}_{self.toto.id}")
|
|
||||||
)
|
|
||||||
membership.end_date = now() - timedelta(minutes=5)
|
membership.end_date = now() - timedelta(minutes=5)
|
||||||
membership.save()
|
membership.save()
|
||||||
cached_membership = cache.get(f"membership_{self.club.id}_{self.toto.id}")
|
cached_membership = cache.get(f"membership_{self.club.id}_{self.toto.id}")
|
||||||
self.assertEqual(cached_membership, "not_member")
|
assert cached_membership == "not_member"
|
||||||
self.assertFalse(self.toto.is_in_group(name=meta_groups_members))
|
assert self.toto.is_in_group(name=meta_groups_members) is False
|
||||||
|
|
||||||
def test_cache_properly_cleared_group(self):
|
def test_cache_properly_cleared_group(self):
|
||||||
"""
|
"""
|
||||||
@ -600,24 +415,24 @@ class UserIsInGroupTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
# testing with pk
|
# testing with pk
|
||||||
self.toto.groups.add(self.com_admin.pk)
|
self.toto.groups.add(self.com_admin.pk)
|
||||||
self.assertTrue(self.toto.is_in_group(pk=self.com_admin.pk))
|
assert self.toto.is_in_group(pk=self.com_admin.pk) is True
|
||||||
|
|
||||||
self.toto.groups.remove(self.com_admin.pk)
|
self.toto.groups.remove(self.com_admin.pk)
|
||||||
self.assertFalse(self.toto.is_in_group(pk=self.com_admin.pk))
|
assert self.toto.is_in_group(pk=self.com_admin.pk) is False
|
||||||
|
|
||||||
# testing with name
|
# testing with name
|
||||||
self.toto.groups.add(self.sas_admin.pk)
|
self.toto.groups.add(self.sas_admin.pk)
|
||||||
self.assertTrue(self.toto.is_in_group(name="SAS admin"))
|
assert self.toto.is_in_group(name="SAS admin") is True
|
||||||
|
|
||||||
self.toto.groups.remove(self.sas_admin.pk)
|
self.toto.groups.remove(self.sas_admin.pk)
|
||||||
self.assertFalse(self.toto.is_in_group(name="SAS admin"))
|
assert self.toto.is_in_group(name="SAS admin") is False
|
||||||
|
|
||||||
def test_not_existing_group(self):
|
def test_not_existing_group(self):
|
||||||
"""
|
"""
|
||||||
Test that searching for a not existing group
|
Test that searching for a not existing group
|
||||||
returns False
|
returns False
|
||||||
"""
|
"""
|
||||||
self.assertFalse(self.skia.is_in_group(name="This doesn't exist"))
|
assert self.skia.is_in_group(name="This doesn't exist") is False
|
||||||
|
|
||||||
|
|
||||||
class DateUtilsTest(TestCase):
|
class DateUtilsTest(TestCase):
|
||||||
@ -639,29 +454,25 @@ class DateUtilsTest(TestCase):
|
|||||||
"""
|
"""
|
||||||
Test that the get_semester function returns the correct semester string
|
Test that the get_semester function returns the correct semester string
|
||||||
"""
|
"""
|
||||||
self.assertEqual(get_semester_code(self.autumn_semester_january), "A24")
|
assert get_semester_code(self.autumn_semester_january) == "A24"
|
||||||
self.assertEqual(get_semester_code(self.autumn_semester_september), "A24")
|
assert get_semester_code(self.autumn_semester_september) == "A24"
|
||||||
self.assertEqual(get_semester_code(self.autumn_first_day), "A24")
|
assert get_semester_code(self.autumn_first_day) == "A24"
|
||||||
|
|
||||||
self.assertEqual(get_semester_code(self.spring_semester_march), "P23")
|
assert get_semester_code(self.spring_semester_march) == "P23"
|
||||||
self.assertEqual(get_semester_code(self.spring_first_day), "P23")
|
assert get_semester_code(self.spring_first_day) == "P23"
|
||||||
|
|
||||||
def test_get_start_of_semester_fixed_date(self):
|
def test_get_start_of_semester_fixed_date(self):
|
||||||
"""
|
"""
|
||||||
Test that the get_start_of_semester correctly the starting date of the semester.
|
Test that the get_start_of_semester correctly the starting date of the semester.
|
||||||
"""
|
"""
|
||||||
automn_2024 = date(2024, self.autumn_month, self.autumn_day)
|
automn_2024 = date(2024, self.autumn_month, self.autumn_day)
|
||||||
self.assertEqual(
|
assert get_start_of_semester(self.autumn_semester_january) == automn_2024
|
||||||
get_start_of_semester(self.autumn_semester_january), automn_2024
|
assert get_start_of_semester(self.autumn_semester_september) == automn_2024
|
||||||
)
|
assert get_start_of_semester(self.autumn_first_day) == automn_2024
|
||||||
self.assertEqual(
|
|
||||||
get_start_of_semester(self.autumn_semester_september), automn_2024
|
|
||||||
)
|
|
||||||
self.assertEqual(get_start_of_semester(self.autumn_first_day), automn_2024)
|
|
||||||
|
|
||||||
spring_2023 = date(2023, self.spring_month, self.spring_day)
|
spring_2023 = date(2023, self.spring_month, self.spring_day)
|
||||||
self.assertEqual(get_start_of_semester(self.spring_semester_march), spring_2023)
|
assert get_start_of_semester(self.spring_semester_march) == spring_2023
|
||||||
self.assertEqual(get_start_of_semester(self.spring_first_day), spring_2023)
|
assert get_start_of_semester(self.spring_first_day) == spring_2023
|
||||||
|
|
||||||
def test_get_start_of_semester_today(self):
|
def test_get_start_of_semester_today(self):
|
||||||
"""
|
"""
|
||||||
@ -669,10 +480,10 @@ class DateUtilsTest(TestCase):
|
|||||||
when no date is given
|
when no date is given
|
||||||
"""
|
"""
|
||||||
with freezegun.freeze_time(self.autumn_semester_september):
|
with freezegun.freeze_time(self.autumn_semester_september):
|
||||||
self.assertEqual(get_start_of_semester(), self.autumn_first_day)
|
assert get_start_of_semester() == self.autumn_first_day
|
||||||
|
|
||||||
with freezegun.freeze_time(self.spring_semester_march):
|
with freezegun.freeze_time(self.spring_semester_march):
|
||||||
self.assertEqual(get_start_of_semester(), self.spring_first_day)
|
assert get_start_of_semester() == self.spring_first_day
|
||||||
|
|
||||||
def test_get_start_of_semester_changing_date(self):
|
def test_get_start_of_semester_changing_date(self):
|
||||||
"""
|
"""
|
||||||
@ -685,8 +496,8 @@ class DateUtilsTest(TestCase):
|
|||||||
mid_autumn = autumn_2023 + timedelta(days=45)
|
mid_autumn = autumn_2023 + timedelta(days=45)
|
||||||
|
|
||||||
with freezegun.freeze_time(mid_spring) as frozen_time:
|
with freezegun.freeze_time(mid_spring) as frozen_time:
|
||||||
self.assertEqual(get_start_of_semester(), spring_2023)
|
assert get_start_of_semester() == spring_2023
|
||||||
|
|
||||||
# forward time to the middle of the next semester
|
# forward time to the middle of the next semester
|
||||||
frozen_time.move_to(mid_autumn)
|
frozen_time.move_to(mid_autumn)
|
||||||
self.assertEqual(get_start_of_semester(), autumn_2023)
|
assert get_start_of_semester() == autumn_2023
|
||||||
|
@ -25,12 +25,12 @@
|
|||||||
|
|
||||||
from django.urls import path, re_path, register_converter
|
from django.urls import path, re_path, register_converter
|
||||||
|
|
||||||
from core.views import *
|
|
||||||
from core.converters import (
|
from core.converters import (
|
||||||
|
BooleanStringConverter,
|
||||||
FourDigitYearConverter,
|
FourDigitYearConverter,
|
||||||
TwoDigitMonthConverter,
|
TwoDigitMonthConverter,
|
||||||
BooleanStringConverter,
|
|
||||||
)
|
)
|
||||||
|
from core.views import *
|
||||||
|
|
||||||
register_converter(FourDigitYearConverter, "yyyy")
|
register_converter(FourDigitYearConverter, "yyyy")
|
||||||
register_converter(TwoDigitMonthConverter, "mm")
|
register_converter(TwoDigitMonthConverter, "mm")
|
||||||
|
@ -26,8 +26,9 @@ from typing import Optional
|
|||||||
import PIL
|
import PIL
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.files.base import ContentFile
|
from django.core.files.base import ContentFile
|
||||||
from PIL import ExifTags
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
from PIL import ExifTags
|
||||||
|
from PIL.Image import Resampling
|
||||||
|
|
||||||
|
|
||||||
def get_git_revision_short_hash() -> str:
|
def get_git_revision_short_hash() -> str:
|
||||||
@ -109,7 +110,8 @@ def resize_image(im, edge, format):
|
|||||||
(w, h) = im.size
|
(w, h) = im.size
|
||||||
(width, height) = scale_dimension(w, h, long_edge=edge)
|
(width, height) = scale_dimension(w, h, long_edge=edge)
|
||||||
content = BytesIO()
|
content = BytesIO()
|
||||||
im = im.resize((width, height), PIL.Image.ANTIALIAS)
|
# use the lanczos filter for antialiasing
|
||||||
|
im = im.resize((width, height), Resampling.LANCZOS)
|
||||||
try:
|
try:
|
||||||
im.save(
|
im.save(
|
||||||
fp=content,
|
fp=content,
|
||||||
|
@ -25,26 +25,21 @@
|
|||||||
|
|
||||||
import types
|
import types
|
||||||
|
|
||||||
from sentry_sdk import last_event_id
|
from django.core.exceptions import (
|
||||||
from django.shortcuts import render
|
ImproperlyConfigured,
|
||||||
|
PermissionDenied,
|
||||||
|
)
|
||||||
from django.http import (
|
from django.http import (
|
||||||
HttpResponseForbidden,
|
HttpResponseForbidden,
|
||||||
HttpResponseNotFound,
|
HttpResponseNotFound,
|
||||||
HttpResponseServerError,
|
HttpResponseServerError,
|
||||||
)
|
)
|
||||||
from django.template import RequestContext
|
|
||||||
from django.core.exceptions import (
|
|
||||||
PermissionDenied,
|
|
||||||
ObjectDoesNotExist,
|
|
||||||
ImproperlyConfigured,
|
|
||||||
)
|
|
||||||
from django.views.generic.base import View
|
|
||||||
from django.views.generic.edit import FormView
|
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.db.models import Count
|
from django.views.generic.base import View
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
from django.views.generic.edit import FormView
|
||||||
|
from sentry_sdk import last_event_id
|
||||||
|
|
||||||
from core.models import Group
|
|
||||||
from core.views.forms import LoginForm
|
from core.views.forms import LoginForm
|
||||||
|
|
||||||
|
|
||||||
@ -314,9 +309,8 @@ class QuickNotifMixin:
|
|||||||
quick_notif_list = []
|
quick_notif_list = []
|
||||||
|
|
||||||
def dispatch(self, request, *arg, **kwargs):
|
def dispatch(self, request, *arg, **kwargs):
|
||||||
self.quick_notif_list = (
|
# In some cases, the class can stay instanciated, so we need to reset the list
|
||||||
[]
|
self.quick_notif_list = []
|
||||||
) # In some cases, the class can stay instanciated, so we need to reset the list
|
|
||||||
return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs)
|
return super(QuickNotifMixin, self).dispatch(request, *arg, **kwargs)
|
||||||
|
|
||||||
def get_success_url(self):
|
def get_success_url(self):
|
||||||
@ -362,8 +356,8 @@ class DetailFormView(SingleObjectMixin, FormView):
|
|||||||
return super(DetailFormView, self).get_object()
|
return super(DetailFormView, self).get_object()
|
||||||
|
|
||||||
|
|
||||||
from .user import *
|
|
||||||
from .page import *
|
|
||||||
from .files import *
|
from .files import *
|
||||||
from .site import *
|
|
||||||
from .group import *
|
from .group import *
|
||||||
|
from .page import *
|
||||||
|
from .site import *
|
||||||
|
from .user import *
|
||||||
|
@ -15,29 +15,28 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# This file contains all the views that concern the page model
|
# This file contains all the views that concern the page model
|
||||||
from django.shortcuts import redirect, get_object_or_404
|
|
||||||
from django.utils.http import http_date
|
|
||||||
from django.views.generic import ListView, DetailView, TemplateView
|
|
||||||
from django.views.generic.edit import UpdateView, FormMixin, DeleteView
|
|
||||||
from django.views.generic.detail import SingleObjectMixin
|
|
||||||
from django.forms.models import modelform_factory
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.http import Http404, HttpResponse
|
|
||||||
from wsgiref.util import FileWrapper
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.core.exceptions import PermissionDenied
|
|
||||||
from django import forms
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
from wsgiref.util import FileWrapper
|
||||||
|
|
||||||
from ajax_select import make_ajax_field
|
from ajax_select import make_ajax_field
|
||||||
|
from django import forms
|
||||||
|
from django.conf import settings
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from django.forms.models import modelform_factory
|
||||||
|
from django.http import Http404, HttpResponse
|
||||||
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.http import http_date
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic import DetailView, ListView, TemplateView
|
||||||
|
from django.views.generic.detail import SingleObjectMixin
|
||||||
|
from django.views.generic.edit import DeleteView, FormMixin, UpdateView
|
||||||
|
|
||||||
from core.models import SithFile, RealGroup, Notification
|
from core.models import Notification, RealGroup, SithFile
|
||||||
from core.views import (
|
from core.views import (
|
||||||
CanViewMixin,
|
|
||||||
CanEditMixin,
|
CanEditMixin,
|
||||||
CanEditPropMixin,
|
CanEditPropMixin,
|
||||||
|
CanViewMixin,
|
||||||
can_view,
|
can_view,
|
||||||
)
|
)
|
||||||
from counter.models import Counter
|
from counter.models import Counter
|
||||||
@ -79,12 +78,35 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleFileInput(forms.ClearableFileInput):
|
||||||
|
allow_multiple_selected = True
|
||||||
|
|
||||||
|
|
||||||
|
class _MultipleFieldMixin:
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
kwargs.setdefault("widget", MultipleFileInput())
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def clean(self, data, initial=None):
|
||||||
|
single_file_clean = super().clean
|
||||||
|
if isinstance(data, (list, tuple)):
|
||||||
|
result = [single_file_clean(d, initial) for d in data]
|
||||||
|
else:
|
||||||
|
result = [single_file_clean(data, initial)]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleFileField(_MultipleFieldMixin, forms.FileField): ...
|
||||||
|
|
||||||
|
|
||||||
|
class MultipleImageField(_MultipleFieldMixin, forms.ImageField): ...
|
||||||
|
|
||||||
|
|
||||||
class AddFilesForm(forms.Form):
|
class AddFilesForm(forms.Form):
|
||||||
folder_name = forms.CharField(
|
folder_name = forms.CharField(
|
||||||
label=_("Add a new folder"), max_length=30, required=False
|
label=_("Add a new folder"), max_length=30, required=False
|
||||||
)
|
)
|
||||||
file_field = forms.FileField(
|
file_field = MultipleFileField(
|
||||||
widget=forms.ClearableFileInput(attrs={"multiple": True}),
|
|
||||||
label=_("Files"),
|
label=_("Files"),
|
||||||
required=False,
|
required=False,
|
||||||
)
|
)
|
||||||
|
@ -21,40 +21,37 @@
|
|||||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from ajax_select import make_ajax_field
|
||||||
|
from ajax_select.fields import AutoCompleteSelectField
|
||||||
from captcha.fields import CaptchaField
|
from captcha.fields import CaptchaField
|
||||||
from django.contrib.auth.forms import UserCreationForm, AuthenticationForm
|
|
||||||
from django import forms
|
from django import forms
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db import transaction
|
from django.contrib.auth.forms import AuthenticationForm, UserCreationForm
|
||||||
from django.templatetags.static import static
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.core.exceptions import ValidationError
|
from django.core.exceptions import ValidationError
|
||||||
|
from django.db import transaction
|
||||||
from django.forms import (
|
from django.forms import (
|
||||||
CheckboxSelectMultiple,
|
CheckboxSelectMultiple,
|
||||||
Select,
|
|
||||||
DateInput,
|
DateInput,
|
||||||
TextInput,
|
|
||||||
DateTimeInput,
|
DateTimeInput,
|
||||||
Textarea,
|
Textarea,
|
||||||
|
TextInput,
|
||||||
)
|
)
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from django.utils.translation import gettext
|
|
||||||
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
|
|
||||||
from ajax_select.fields import AutoCompleteSelectField
|
|
||||||
from ajax_select import make_ajax_field
|
|
||||||
from django.utils.dateparse import parse_datetime
|
|
||||||
from django.utils import timezone
|
|
||||||
import datetime
|
|
||||||
from django.forms.utils import to_current_timezone
|
from django.forms.utils import to_current_timezone
|
||||||
|
from django.templatetags.static import static
|
||||||
import re
|
from django.urls import reverse
|
||||||
|
from django.utils import timezone
|
||||||
from core.models import User, Page, SithFile, Gift
|
from django.utils.dateparse import parse_datetime
|
||||||
|
from django.utils.translation import gettext
|
||||||
from core.utils import resize_image
|
from django.utils.translation import gettext_lazy as _
|
||||||
from io import BytesIO
|
from phonenumber_field.widgets import PhoneNumberInternationalFallbackWidget
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from core.models import Gift, Page, SithFile, User
|
||||||
|
from core.utils import resize_image
|
||||||
|
|
||||||
# Widgets
|
# Widgets
|
||||||
|
|
||||||
|
@ -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 ajax_select.fields import AutoCompleteSelectMultipleField
|
||||||
|
from django import forms
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from django.views.generic import ListView
|
||||||
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||||
|
|
||||||
from core.models import RealGroup, User
|
from core.models import RealGroup, User
|
||||||
from core.views import CanCreateMixin, CanEditMixin, DetailFormView
|
from core.views import CanCreateMixin, CanEditMixin, DetailFormView
|
||||||
|
@ -15,16 +15,16 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# This file contains all the views that concern the page model
|
# This file contains all the views that concern the page model
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.views.generic import ListView, DetailView
|
|
||||||
from django.views.generic.edit import UpdateView, CreateView, DeleteView
|
|
||||||
from django.forms.models import modelform_factory
|
from django.forms.models import modelform_factory
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
|
from django.urls import reverse_lazy
|
||||||
|
from django.views.generic import DetailView, ListView
|
||||||
|
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||||
|
|
||||||
from core.models import Page, PageRev, LockError
|
from core.models import LockError, Page, PageRev
|
||||||
|
from core.views import CanCreateMixin, CanEditMixin, CanEditPropMixin, CanViewMixin
|
||||||
from core.views.forms import MarkdownInput, PageForm, PagePropForm
|
from core.views.forms import MarkdownInput, PageForm, PagePropForm
|
||||||
from core.views import CanViewMixin, CanEditMixin, CanEditPropMixin, CanCreateMixin
|
|
||||||
|
|
||||||
|
|
||||||
class CanEditPagePropMixin(CanEditPropMixin):
|
class CanEditPagePropMixin(CanEditPropMixin):
|
||||||
|
@ -23,23 +23,22 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
from django.shortcuts import render, redirect
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from django.core import serializers
|
|
||||||
from django.contrib.auth.decorators import login_required
|
|
||||||
from django.utils import html
|
|
||||||
from django.views.generic import ListView, TemplateView
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.text import slugify
|
|
||||||
from django.db.models.query import QuerySet
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.contrib.auth.decorators import login_required
|
||||||
|
from django.core import serializers
|
||||||
|
from django.db.models.query import QuerySet
|
||||||
|
from django.http import JsonResponse
|
||||||
|
from django.shortcuts import redirect, render
|
||||||
|
from django.utils import html
|
||||||
|
from django.utils.text import slugify
|
||||||
|
from django.views.generic import ListView, TemplateView
|
||||||
from haystack.query import SearchQuerySet
|
from haystack.query import SearchQuerySet
|
||||||
|
|
||||||
from core.models import User, Notification
|
|
||||||
from core.utils import doku_to_markdown, bbcode_to_markdown
|
|
||||||
from club.models import Club
|
from club.models import Club
|
||||||
|
from core.models import Notification, User
|
||||||
|
from core.utils import bbcode_to_markdown, doku_to_markdown
|
||||||
|
|
||||||
|
|
||||||
def index(request, context=None):
|
def index(request, context=None):
|
||||||
@ -100,9 +99,8 @@ def search_club(query, as_json=False):
|
|||||||
if query:
|
if query:
|
||||||
clubs = Club.objects.filter(name__icontains=query).all()
|
clubs = Club.objects.filter(name__icontains=query).all()
|
||||||
clubs = clubs[:5]
|
clubs = clubs[:5]
|
||||||
if (
|
if as_json:
|
||||||
as_json
|
# Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
|
||||||
): # Re-loads json to avoid double encoding by JsonResponse, but still benefit from serializers
|
|
||||||
clubs = json.loads(serializers.serialize("json", clubs, fields=("name")))
|
clubs = json.loads(serializers.serialize("json", clubs, fields=("name")))
|
||||||
else:
|
else:
|
||||||
clubs = list(clubs)
|
clubs = list(clubs)
|
||||||
|
@ -24,50 +24,49 @@
|
|||||||
#
|
#
|
||||||
|
|
||||||
# This file contains all the views that concern the user model
|
# This file contains all the views that concern the user model
|
||||||
from django.shortcuts import render, redirect, get_object_or_404
|
import logging
|
||||||
|
from datetime import date, timedelta
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib.auth import views
|
from django.contrib.auth import views
|
||||||
from django.contrib.auth.forms import PasswordChangeForm
|
from django.contrib.auth.forms import PasswordChangeForm
|
||||||
from django.utils.translation import gettext as _
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.core.exceptions import PermissionDenied, ValidationError
|
from django.core.exceptions import PermissionDenied, ValidationError
|
||||||
|
from django.forms import CheckboxSelectMultiple
|
||||||
|
from django.forms.models import modelform_factory
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
from django.views.generic.edit import UpdateView
|
from django.shortcuts import get_object_or_404, redirect, render
|
||||||
|
from django.template.response import TemplateResponse
|
||||||
|
from django.urls import reverse, reverse_lazy
|
||||||
|
from django.utils.translation import gettext as _
|
||||||
from django.views.generic import (
|
from django.views.generic import (
|
||||||
ListView,
|
|
||||||
DetailView,
|
|
||||||
TemplateView,
|
|
||||||
CreateView,
|
CreateView,
|
||||||
DeleteView,
|
DeleteView,
|
||||||
|
DetailView,
|
||||||
|
ListView,
|
||||||
|
TemplateView,
|
||||||
)
|
)
|
||||||
from django.forms.models import modelform_factory
|
from django.views.generic.dates import MonthMixin, YearMixin
|
||||||
from django.forms import CheckboxSelectMultiple
|
from django.views.generic.edit import UpdateView
|
||||||
from django.urls import reverse_lazy
|
|
||||||
from django.template.response import TemplateResponse
|
|
||||||
from django.conf import settings
|
|
||||||
from django.views.generic.dates import YearMixin, MonthMixin
|
|
||||||
|
|
||||||
from datetime import timedelta, date
|
|
||||||
import logging
|
|
||||||
from api.views.sas import all_pictures_of_user
|
from api.views.sas import all_pictures_of_user
|
||||||
|
from core.models import Gift, Preferences, SithFile, User
|
||||||
from core.views import (
|
from core.views import (
|
||||||
CanViewMixin,
|
|
||||||
CanEditMixin,
|
CanEditMixin,
|
||||||
CanEditPropMixin,
|
CanEditPropMixin,
|
||||||
UserIsLoggedMixin,
|
CanViewMixin,
|
||||||
TabedViewMixin,
|
|
||||||
QuickNotifMixin,
|
QuickNotifMixin,
|
||||||
|
TabedViewMixin,
|
||||||
|
UserIsLoggedMixin,
|
||||||
)
|
)
|
||||||
from core.views.forms import (
|
from core.views.forms import (
|
||||||
RegisteringForm,
|
|
||||||
UserProfileForm,
|
|
||||||
LoginForm,
|
|
||||||
UserGodfathersForm,
|
|
||||||
GiftForm,
|
GiftForm,
|
||||||
|
LoginForm,
|
||||||
|
RegisteringForm,
|
||||||
|
UserGodfathersForm,
|
||||||
|
UserProfileForm,
|
||||||
)
|
)
|
||||||
from core.models import User, SithFile, Preferences, Gift
|
|
||||||
from subscription.models import Subscription
|
|
||||||
from counter.forms import StudentCardForm
|
from counter.forms import StudentCardForm
|
||||||
|
from subscription.models import Subscription
|
||||||
from trombi.views import UserTrombiForm
|
from trombi.views import UserTrombiForm
|
||||||
|
|
||||||
|
|
||||||
@ -501,9 +500,10 @@ class UserStatsView(UserTabsMixin, CanViewMixin, DetailView):
|
|||||||
|
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
kwargs = super(UserStatsView, self).get_context_data(**kwargs)
|
kwargs = super(UserStatsView, self).get_context_data(**kwargs)
|
||||||
from counter.models import Counter
|
|
||||||
from django.db.models import Sum
|
from django.db.models import Sum
|
||||||
|
|
||||||
|
from counter.models import Counter
|
||||||
|
|
||||||
foyer = Counter.objects.filter(name="Foyer").first()
|
foyer = Counter.objects.filter(name="Foyer").first()
|
||||||
mde = Counter.objects.filter(name="MDE").first()
|
mde = Counter.objects.filter(name="MDE").first()
|
||||||
gommette = Counter.objects.filter(name="La Gommette").first()
|
gommette = Counter.objects.filter(name="La Gommette").first()
|
||||||
@ -601,10 +601,12 @@ class UserUploadProfilePictView(CanEditMixin, DetailView):
|
|||||||
template_name = "core/user_edit.jinja"
|
template_name = "core/user_edit.jinja"
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
from core.utils import resize_image
|
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
from core.utils import resize_image
|
||||||
|
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
if self.object.profile_pict:
|
if self.object.profile_pict:
|
||||||
raise ValidationError(_("User already has a profile picture"))
|
raise ValidationError(_("User already has a profile picture"))
|
||||||
|
@ -31,4 +31,4 @@ class CounterConfig(AppConfig):
|
|||||||
verbose_name = _("counter")
|
verbose_name = _("counter")
|
||||||
|
|
||||||
def ready(self):
|
def ready(self):
|
||||||
import counter.signals
|
import counter.signals # noqa F401
|
||||||
|
@ -3,15 +3,15 @@ from ajax_select.fields import AutoCompleteSelectField, AutoCompleteSelectMultip
|
|||||||
from django import forms
|
from django import forms
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from core.views.forms import TzAwareDateTimeField, SelectDate
|
from core.views.forms import SelectDate, TzAwareDateTimeField
|
||||||
from counter.models import (
|
from counter.models import (
|
||||||
BillingInfo,
|
BillingInfo,
|
||||||
StudentCard,
|
|
||||||
Customer,
|
|
||||||
Refilling,
|
|
||||||
Counter,
|
Counter,
|
||||||
Product,
|
Customer,
|
||||||
Eticket,
|
Eticket,
|
||||||
|
Product,
|
||||||
|
Refilling,
|
||||||
|
StudentCard,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user