mirror of
https://github.com/ae-utbm/sith.git
synced 2025-07-13 13:29:23 +00:00
Compare commits
20 Commits
windows-up
...
docker
Author | SHA1 | Date | |
---|---|---|---|
f477346f1e | |||
8cc23f01fd | |||
d456a1d9d8 | |||
e200f28267 | |||
a4c6439981 | |||
6ee2e8c5da | |||
f4af29acb4 | |||
b26e85ebb2 | |||
8b8a295e16 | |||
894690a97f | |||
843ce2e3a7 | |||
9f33ddd883 | |||
a2dc4f1964 | |||
cca486f2b9 | |||
b9e27ef191 | |||
29e875bcde | |||
4226ba88ae | |||
672bc91e36 | |||
2db3290bed | |||
0d3fd954a3 |
83
.env.example
Normal file
83
.env.example
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
HTTPS=off
|
||||||
|
DEBUG=true
|
||||||
|
|
||||||
|
# This is not the real key used in prod
|
||||||
|
SECRET_KEY=(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2
|
||||||
|
|
||||||
|
DATABASE_URL=sqlite:///db.sqlite3
|
||||||
|
# uncomment the next line if you want to use a postgres database
|
||||||
|
#DATABASE_URL=postgres://user:password@127.0.0.1:5432/sith
|
||||||
|
CACHE_URL=redis://127.0.0.1:6379/0
|
||||||
|
|
||||||
|
MEDIA_ROOT=data
|
||||||
|
STATIC_ROOT=static
|
||||||
|
|
||||||
|
DEFAULT_FROM_EMAIL=bibou@git.an
|
||||||
|
SITH_COM_EMAIL=bibou_com@git.an
|
||||||
|
|
||||||
|
HONEYPOT_VALUE=content
|
||||||
|
HONEYPOT_FIELD_NAME=body2
|
||||||
|
HONEYPOT_FIELD_NAME_FORUM=message2
|
||||||
|
|
||||||
|
EMAIL_BACKEND=django.core.mail.backends.console.EmailBackend
|
||||||
|
EMAIL_HOST=localhost
|
||||||
|
EMAIL_PORT=25
|
||||||
|
|
||||||
|
SITH_URL=127.0.0.1:8000
|
||||||
|
SITH_NAME="AE UTBM"
|
||||||
|
|
||||||
|
SITH_MAIN_CLUB_ID=1
|
||||||
|
|
||||||
|
SITH_GROUP_ROOT_ID=1
|
||||||
|
SITH_GROUP_PUBLIC_ID=2
|
||||||
|
SITH_GROUP_SUBSCRIBERS_ID=3
|
||||||
|
SITH_GROUP_OLD_SUBSCRIBERS_ID=4
|
||||||
|
SITH_GROUP_ACCOUNTING_ADMIN_ID=5
|
||||||
|
SITH_GROUP_COM_ADMIN_ID=6
|
||||||
|
SITH_GROUP_COUNTER_ADMIN_ID=7
|
||||||
|
SITH_GROUP_SAS_ADMIN_ID=8
|
||||||
|
SITH_GROUP_FORUM_ADMIN_ID=9
|
||||||
|
SITH_GROUP_PEDAGOGY_ADMIN_ID=10
|
||||||
|
|
||||||
|
SITH_GROUP_BANNED_ALCOHOL_ID=11
|
||||||
|
SITH_GROUP_BANNED_COUNTER_ID=12
|
||||||
|
SITH_GROUP_BANNED_SUBSCRIPTION_ID=13
|
||||||
|
|
||||||
|
SITH_CLUB_REFOUND_ID=89
|
||||||
|
SITH_COUNTER_REFOUND_ID=38
|
||||||
|
SITH_PRODUCT_REFOUND_ID=5
|
||||||
|
|
||||||
|
# Counter
|
||||||
|
|
||||||
|
SITH_COUNTER_ACCOUNT_DUMP_ID=39
|
||||||
|
|
||||||
|
# Defines which product type is the refilling type, and thus increases the account amount
|
||||||
|
SITH_COUNTER_PRODUCTTYPE_REFILLING=3
|
||||||
|
|
||||||
|
SITH_ECOCUP_CONS=1152
|
||||||
|
SITH_ECOCUP_DECO=1151
|
||||||
|
|
||||||
|
# Defines which product is the one year subscription and which one is the six month subscription
|
||||||
|
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER=1
|
||||||
|
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS=2
|
||||||
|
SITH_PRODUCTTYPE_SUBSCRIPTION=2
|
||||||
|
|
||||||
|
# Defines which clubs let its members the ability to see users subscription history
|
||||||
|
SITH_CAN_CREATE_SUBSCRIPTION_HISTORY=1
|
||||||
|
SITH_CAN_READ_SUBSCRIPTION_HISTORY=1
|
||||||
|
|
||||||
|
# SAS variables
|
||||||
|
SITH_SAS_ROOT_DIR_ID=4
|
||||||
|
|
||||||
|
# ET variables
|
||||||
|
SITH_EBOUTIC_CB_ENABLED=true
|
||||||
|
SITH_EBOUTIC_ET_URL="https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi"
|
||||||
|
SITH_EBOUTIC_PBX_SITE=1999888
|
||||||
|
SITH_EBOUTIC_PBX_RANG=32
|
||||||
|
SITH_EBOUTIC_PBX_IDENTIFIANT=2
|
||||||
|
SITH_EBOUTIC_HMAC_KEY=0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
|
||||||
|
SITH_EBOUTIC_PUB_KEY_PATH=sith/et_keys/pubkey.pem
|
||||||
|
|
||||||
|
SITH_MAILING_FETCH_KEY=IloveMails
|
||||||
|
SENTRY_DSN=
|
||||||
|
SENTRY_ENV=production
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@ -7,6 +7,10 @@ on:
|
|||||||
branches: [master, taiste]
|
branches: [master, taiste]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
|
env:
|
||||||
|
SECRET_KEY: notTheRealOne
|
||||||
|
DATABASE_URL: sqlite:///db.sqlite3
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pre-commit:
|
pre-commit:
|
||||||
name: Launch pre-commits checks (ruff)
|
name: Launch pre-commits checks (ruff)
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -21,3 +21,4 @@ node_modules/
|
|||||||
|
|
||||||
# compiled documentation
|
# compiled documentation
|
||||||
site/
|
site/
|
||||||
|
.env
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
from django.db.models.base import post_save
|
from django.db.models.signals import post_delete, post_save
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
|
|
||||||
from com.calendar import IcsCalendar
|
from com.calendar import IcsCalendar
|
||||||
from com.models import News
|
from com.models import News
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=News, dispatch_uid="update_internal_ics")
|
@receiver([post_save, post_delete], sender=News, dispatch_uid="update_internal_ics")
|
||||||
def update_internal_ics(*args, **kwargs):
|
def update_internal_ics(*args, **kwargs):
|
||||||
_ = IcsCalendar.make_internal()
|
_ = IcsCalendar.make_internal()
|
||||||
|
@ -125,7 +125,7 @@
|
|||||||
<i class="fa-brands fa-discord fa-xl"></i>
|
<i class="fa-brands fa-discord fa-xl"></i>
|
||||||
<a rel="nofollow" target="#" href="https://discord.gg/QvTm3XJrHR">{% trans %}Discord AE{% endtrans %}</a>
|
<a rel="nofollow" target="#" href="https://discord.gg/QvTm3XJrHR">{% trans %}Discord AE{% endtrans %}</a>
|
||||||
{% if user.was_subscribed %}
|
{% if user.was_subscribed %}
|
||||||
- <a rel="nofollow" target="#" href="https://discord.gg/XK9WfPsUFm">{% trans %}Dev Team{% endtrans %}</a>
|
- <a rel="nofollow" target="#" href="https://discord.gg/u6EuMfyGaJ">{% trans %}Dev Team{% endtrans %}</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
|
@ -13,42 +13,12 @@
|
|||||||
#
|
#
|
||||||
#
|
#
|
||||||
|
|
||||||
import hashlib
|
|
||||||
import multiprocessing
|
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
import tarfile
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Self
|
|
||||||
|
|
||||||
import tomli
|
import tomli
|
||||||
import urllib3
|
from django.core.management.base import BaseCommand, CommandParser
|
||||||
from django.core.management.base import BaseCommand, CommandParser, OutputWrapper
|
|
||||||
from urllib3.response import HTTPException
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class XapianSpec:
|
|
||||||
version: str
|
|
||||||
core_sha1: str
|
|
||||||
bindings_sha1: str
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_pyproject(cls) -> Self:
|
|
||||||
with open(
|
|
||||||
Path(__file__).parent.parent.parent.parent / "pyproject.toml", "rb"
|
|
||||||
) as f:
|
|
||||||
pyproject = tomli.load(f)
|
|
||||||
spec = pyproject["tool"]["xapian"]
|
|
||||||
return cls(
|
|
||||||
version=spec["version"],
|
|
||||||
core_sha1=spec["core-sha1"],
|
|
||||||
bindings_sha1=spec["bindings-sha1"],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@ -69,6 +39,13 @@ class Command(BaseCommand):
|
|||||||
return None
|
return None
|
||||||
return xapian.version_string()
|
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, *args, force: bool, **options):
|
def handle(self, *args, force: bool, **options):
|
||||||
if not os.environ.get("VIRTUAL_ENV", None):
|
if not os.environ.get("VIRTUAL_ENV", None):
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
@ -76,185 +53,20 @@ class Command(BaseCommand):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
desired = XapianSpec.from_pyproject()
|
desired = self._desired_version()
|
||||||
if desired.version == self._current_version():
|
if desired == self._current_version():
|
||||||
if not force:
|
if not force:
|
||||||
self.stdout.write(
|
self.stdout.write(
|
||||||
f"Version {desired.version} is already installed, use --force to re-install"
|
f"Version {desired} is already installed, use --force to re-install"
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
self.stdout.write(
|
self.stdout.write(f"Version {desired} is already installed, re-installing")
|
||||||
f"Version {desired.version} is already installed, re-installing"
|
self.stdout.write(
|
||||||
)
|
f"Installing xapian version {desired} at {os.environ['VIRTUAL_ENV']}"
|
||||||
XapianInstaller(desired, self.stdout, self.stderr).run()
|
)
|
||||||
|
subprocess.run(
|
||||||
|
[str(Path(__file__).parent / "install_xapian.sh"), desired],
|
||||||
|
env=dict(os.environ),
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
self.stdout.write("Installation success")
|
self.stdout.write("Installation success")
|
||||||
|
|
||||||
|
|
||||||
class XapianInstaller:
|
|
||||||
def __init__(
|
|
||||||
self,
|
|
||||||
spec: XapianSpec,
|
|
||||||
stdout: OutputWrapper,
|
|
||||||
stderr: OutputWrapper,
|
|
||||||
):
|
|
||||||
self._version = spec.version
|
|
||||||
self._core_sha1 = spec.core_sha1
|
|
||||||
self._bindings_sha1 = spec.bindings_sha1
|
|
||||||
|
|
||||||
self._stdout = stdout
|
|
||||||
self._stderr = stderr
|
|
||||||
self._virtual_env = os.environ.get("VIRTUAL_ENV", None)
|
|
||||||
|
|
||||||
if not self._virtual_env:
|
|
||||||
raise RuntimeError("You are not inside a virtual environment")
|
|
||||||
self._virtual_env = Path(self._virtual_env)
|
|
||||||
|
|
||||||
self._dest_dir = Path(self._virtual_env) / "packages"
|
|
||||||
self._core = f"xapian-core-{self._version}"
|
|
||||||
self._bindings = f"xapian-bindings-{self._version}"
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _is_windows(self) -> bool:
|
|
||||||
return platform.system() == "Windows"
|
|
||||||
|
|
||||||
def _util_download(self, url: str, dest: Path, sha1_hash: str) -> None:
|
|
||||||
resp = urllib3.request("GET", url)
|
|
||||||
if resp.status != 200:
|
|
||||||
raise HTTPException(f"Could not download {url}")
|
|
||||||
if hashlib.sha1(resp.data).hexdigest() != sha1_hash:
|
|
||||||
raise ValueError(f"File downloaded from {url} is compromised")
|
|
||||||
with open(dest, "wb") as f:
|
|
||||||
f.write(resp.data)
|
|
||||||
|
|
||||||
def _setup_env(self):
|
|
||||||
os.environ.update(
|
|
||||||
{
|
|
||||||
"CPATH": "",
|
|
||||||
"LIBRARY_PATH": "",
|
|
||||||
"CFLAGS": "",
|
|
||||||
"LDFLAGS": "",
|
|
||||||
"CCFLAGS": "",
|
|
||||||
"CXXFLAGS": "",
|
|
||||||
"CPPFLAGS": "",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
def _prepare_dest_folder(self):
|
|
||||||
shutil.rmtree(self._dest_dir, ignore_errors=True)
|
|
||||||
self._dest_dir.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
def _download(self):
|
|
||||||
self._stdout.write("Downloading source…")
|
|
||||||
|
|
||||||
core = self._dest_dir / f"{self._core}.tar.xz"
|
|
||||||
bindings = self._dest_dir / f"{self._bindings}.tar.xz"
|
|
||||||
self._util_download(
|
|
||||||
f"https://oligarchy.co.uk/xapian/{self._version}/{self._core}.tar.xz",
|
|
||||||
core,
|
|
||||||
self._core_sha1,
|
|
||||||
)
|
|
||||||
self._util_download(
|
|
||||||
f"https://oligarchy.co.uk/xapian/{self._version}/{self._bindings}.tar.xz",
|
|
||||||
bindings,
|
|
||||||
self._bindings_sha1,
|
|
||||||
)
|
|
||||||
self._stdout.write("Extracting source …")
|
|
||||||
with tarfile.open(core) as tar:
|
|
||||||
tar.extractall(self._dest_dir)
|
|
||||||
with tarfile.open(bindings) as tar:
|
|
||||||
tar.extractall(self._dest_dir)
|
|
||||||
|
|
||||||
os.remove(core)
|
|
||||||
os.remove(bindings)
|
|
||||||
|
|
||||||
def _install(self):
|
|
||||||
self._stdout.write("Installing Xapian-core…")
|
|
||||||
def configure() -> list[str]:
|
|
||||||
if self._is_windows:
|
|
||||||
return ["sh", "configure"]
|
|
||||||
return ["./configure"]
|
|
||||||
def enable_static() -> list[str]:
|
|
||||||
if self._is_windows:
|
|
||||||
return ["--enable-shared", "--disable-static"]
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Make sure that xapian finds the correct executable
|
|
||||||
os.environ["PYTHON3"] = str(Path(sys.executable).as_posix())
|
|
||||||
|
|
||||||
subprocess.run(
|
|
||||||
[*configure(), "--prefix", str(self._virtual_env.as_posix()), *enable_static(),],
|
|
||||||
env=dict(os.environ),
|
|
||||||
cwd=self._dest_dir / self._core,
|
|
||||||
check=False,
|
|
||||||
shell=self._is_windows,
|
|
||||||
).check_returncode()
|
|
||||||
subprocess.run(
|
|
||||||
[
|
|
||||||
"make",
|
|
||||||
"-j",
|
|
||||||
str(multiprocessing.cpu_count()),
|
|
||||||
],
|
|
||||||
env=dict(os.environ),
|
|
||||||
cwd=self._dest_dir / self._core,
|
|
||||||
check=False,
|
|
||||||
shell=self._is_windows,
|
|
||||||
).check_returncode()
|
|
||||||
subprocess.run(
|
|
||||||
["make", "install"],
|
|
||||||
env=dict(os.environ),
|
|
||||||
cwd=self._dest_dir / self._core,
|
|
||||||
check=False,
|
|
||||||
shell=self._is_windows,
|
|
||||||
|
|
||||||
).check_returncode()
|
|
||||||
|
|
||||||
|
|
||||||
self._stdout.write("Installing Xapian-bindings")
|
|
||||||
subprocess.run(
|
|
||||||
[
|
|
||||||
*configure(),
|
|
||||||
"--prefix",
|
|
||||||
str(self._virtual_env.as_posix()),
|
|
||||||
"--with-python3",
|
|
||||||
f"XAPIAN_CONFIG={(self._virtual_env / 'bin'/'xapian-config').as_posix()}",
|
|
||||||
*enable_static(),
|
|
||||||
],
|
|
||||||
env=dict(os.environ),
|
|
||||||
cwd=self._dest_dir / self._bindings,
|
|
||||||
check=False,
|
|
||||||
shell=self._is_windows,
|
|
||||||
).check_returncode()
|
|
||||||
subprocess.run(
|
|
||||||
[
|
|
||||||
"make",
|
|
||||||
"-j",
|
|
||||||
str(multiprocessing.cpu_count()),
|
|
||||||
],
|
|
||||||
env=dict(os.environ),
|
|
||||||
cwd=self._dest_dir / self._bindings,
|
|
||||||
check=False,
|
|
||||||
shell=self._is_windows,
|
|
||||||
).check_returncode()
|
|
||||||
subprocess.run(
|
|
||||||
["make", "install"],
|
|
||||||
env=dict(os.environ),
|
|
||||||
cwd=self._dest_dir / self._bindings,
|
|
||||||
check=False,
|
|
||||||
shell=self._is_windows,
|
|
||||||
).check_returncode()
|
|
||||||
|
|
||||||
def _post_clean(self):
|
|
||||||
shutil.rmtree(self._dest_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def _test(self):
|
|
||||||
subprocess.run(
|
|
||||||
[sys.executable, "-c", "import xapian"], check=False, shell=self._is_windows,
|
|
||||||
).check_returncode()
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
self._setup_env()
|
|
||||||
self._prepare_dest_folder()
|
|
||||||
self._download()
|
|
||||||
self._install()
|
|
||||||
self._post_clean()
|
|
||||||
self._test()
|
|
||||||
|
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 -j"$(nproc)" && 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 -j"$(nproc)" && make install
|
||||||
|
|
||||||
|
# clean
|
||||||
|
rm -rf "$VIRTUAL_ENV/packages"
|
||||||
|
|
||||||
|
# test
|
||||||
|
python -c "import xapian"
|
@ -460,6 +460,7 @@ Welcome to the wiki page!
|
|||||||
limit_age=18,
|
limit_age=18,
|
||||||
)
|
)
|
||||||
cons = Product.objects.create(
|
cons = Product.objects.create(
|
||||||
|
id=settings.SITH_ECOCUP_CONS,
|
||||||
name="Consigne Eco-cup",
|
name="Consigne Eco-cup",
|
||||||
code="CONS",
|
code="CONS",
|
||||||
product_type=verre,
|
product_type=verre,
|
||||||
@ -469,6 +470,7 @@ Welcome to the wiki page!
|
|||||||
club=main_club,
|
club=main_club,
|
||||||
)
|
)
|
||||||
dcons = Product.objects.create(
|
dcons = Product.objects.create(
|
||||||
|
id=settings.SITH_ECOCUP_DECO,
|
||||||
name="Déconsigne Eco-cup",
|
name="Déconsigne Eco-cup",
|
||||||
code="DECO",
|
code="DECO",
|
||||||
product_type=verre,
|
product_type=verre,
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
@ -29,7 +30,7 @@ class Command(BaseCommand):
|
|||||||
if not data_dir.is_dir():
|
if not data_dir.is_dir():
|
||||||
data_dir.mkdir()
|
data_dir.mkdir()
|
||||||
db_path = settings.BASE_DIR / "db.sqlite3"
|
db_path = settings.BASE_DIR / "db.sqlite3"
|
||||||
if db_path.exists():
|
if db_path.exists() or connection.vendor != "sqlite":
|
||||||
call_command("flush", "--noinput")
|
call_command("flush", "--noinput")
|
||||||
self.stdout.write("Existing database reset")
|
self.stdout.write("Existing database reset")
|
||||||
call_command("migrate")
|
call_command("migrate")
|
||||||
|
73
core/static/bundled/core/read-more-index.ts
Normal file
73
core/static/bundled/core/read-more-index.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import clip from "@arendjr/text-clipper";
|
||||||
|
|
||||||
|
/*
|
||||||
|
This script adds a way to have a 'show more / show less' button
|
||||||
|
on some text content.
|
||||||
|
|
||||||
|
The usage is very simple, you just have to add the attribute `show-more`
|
||||||
|
with the desired max size to the element you want to add the button to.
|
||||||
|
This script does html matching and is able to properly cut rendered markdown.
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
<p show-more="20">
|
||||||
|
My very long text will be cut by this script
|
||||||
|
</p>
|
||||||
|
*/
|
||||||
|
|
||||||
|
function showMore(element: HTMLElement) {
|
||||||
|
if (!element.hasAttribute("show-more")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark element as loaded so we can hide unloaded
|
||||||
|
// tags with css and avoid blinking text
|
||||||
|
element.setAttribute("show-more-loaded", "");
|
||||||
|
|
||||||
|
const fullContent = element.innerHTML;
|
||||||
|
const clippedContent = clip(
|
||||||
|
element.innerHTML,
|
||||||
|
Number.parseInt(element.getAttribute("show-more") as string),
|
||||||
|
{
|
||||||
|
html: true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// If already at the desired size, we don't do anything
|
||||||
|
if (clippedContent === fullContent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionLink = document.createElement("a");
|
||||||
|
actionLink.setAttribute("class", "show-more-link");
|
||||||
|
|
||||||
|
let opened = false;
|
||||||
|
|
||||||
|
const setText = () => {
|
||||||
|
if (opened) {
|
||||||
|
element.innerHTML = fullContent;
|
||||||
|
actionLink.innerText = gettext("Show less");
|
||||||
|
} else {
|
||||||
|
element.innerHTML = clippedContent;
|
||||||
|
actionLink.innerText = gettext("Show more");
|
||||||
|
}
|
||||||
|
element.appendChild(document.createElement("br"));
|
||||||
|
element.appendChild(actionLink);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggle = () => {
|
||||||
|
opened = !opened;
|
||||||
|
setText();
|
||||||
|
};
|
||||||
|
|
||||||
|
setText();
|
||||||
|
actionLink.addEventListener("click", (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
toggle();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
for (const elem of document.querySelectorAll("[show-more]")) {
|
||||||
|
showMore(elem as HTMLElement);
|
||||||
|
}
|
||||||
|
});
|
@ -1,11 +1,27 @@
|
|||||||
|
.ts-wrapper.multi .ts-control {
|
||||||
|
min-width: calc(100% - 0.2rem);
|
||||||
|
}
|
||||||
|
|
||||||
/* This also requires ajax-select-index.css */
|
/* This also requires ajax-select-index.css */
|
||||||
.ts-dropdown {
|
.ts-dropdown {
|
||||||
|
width: calc(100% - 0.2rem);
|
||||||
|
left: 0.1rem;
|
||||||
|
top: calc(100% - 0.2rem - var(--nf-input-border-bottom-width));
|
||||||
|
border: var(--nf-input-border-color) var(--nf-input-border-width) solid;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom-width: var(--nf-input-border-bottom-width);
|
||||||
|
|
||||||
|
.option.active {
|
||||||
|
background-color: #e5eafa;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
.select-item {
|
.select-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
@ -16,19 +32,44 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ts-wrapper {
|
.ts-wrapper.single {
|
||||||
margin: 5px;
|
> .ts-control {
|
||||||
|
box-shadow: none;
|
||||||
|
max-width: 300px;
|
||||||
|
background-color: var(--nf-input-background-color);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
> .ts-dropdown {
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ts-wrapper.single {
|
.ts-wrapper input[type="text"] {
|
||||||
width: 263px; // same length as regular text inputs
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ts-wrapper.multi, .ts-wrapper.single {
|
||||||
|
.ts-control:has(input:focus) {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--nf-input-focus-border-color);
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove {
|
.ts-wrapper.plugin-remove_button:not(.rtl) .item .remove {
|
||||||
border-left: 1px solid #aaa;
|
border-left: 1px solid #aaa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ts-wrapper.multi .ts-control {
|
.ts-wrapper.multi.has-items .ts-control {
|
||||||
|
padding: calc(var(--nf-input-size) * 0.65);
|
||||||
|
display: flex;
|
||||||
|
gap: calc(var(--nf-input-size) / 3);
|
||||||
|
|
||||||
[data-value],
|
[data-value],
|
||||||
[data-value].active {
|
[data-value].active {
|
||||||
background-image: none;
|
background-image: none;
|
||||||
@ -37,19 +78,17 @@
|
|||||||
border: 1px solid #aaa;
|
border: 1px solid #aaa;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 5px;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
padding-right: 10px;
|
padding-right: 10px;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
text-shadow: none;
|
text-shadow: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|
||||||
|
.remove {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ts-dropdown {
|
.ts-wrapper.focus .ts-control {
|
||||||
.option.active {
|
box-shadow: none;
|
||||||
background-color: #e5eafa;
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -48,7 +48,8 @@
|
|||||||
|
|
||||||
input,
|
input,
|
||||||
textarea[type="text"],
|
textarea[type="text"],
|
||||||
[type="number"] {
|
[type="number"],
|
||||||
|
.ts-control {
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: $background-button-color;
|
background-color: $background-button-color;
|
||||||
@ -69,7 +70,7 @@
|
|||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select, .ts-control {
|
||||||
border: none;
|
border: none;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-size: 1.2em;
|
font-size: 1.2em;
|
||||||
@ -177,7 +178,7 @@ form {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// wrap texts
|
// wrap texts
|
||||||
label, legend, ul.errorlist>li, .helptext {
|
label, legend, ul.errorlist > li, .helptext {
|
||||||
text-wrap: wrap;
|
text-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,23 +219,25 @@ form {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"],
|
:not(.ts-control) > {
|
||||||
input[type="email"],
|
input[type="text"],
|
||||||
input[type="tel"],
|
input[type="email"],
|
||||||
input[type="url"],
|
input[type="tel"],
|
||||||
input[type="password"],
|
input[type="url"],
|
||||||
input[type="number"],
|
input[type="password"],
|
||||||
input[type="date"],
|
input[type="number"],
|
||||||
input[type="week"],
|
input[type="date"],
|
||||||
input[type="time"],
|
input[type="week"],
|
||||||
input[type="month"],
|
input[type="time"],
|
||||||
input[type="search"],
|
input[type="search"],
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
input[type="month"],
|
||||||
min-width: 300px;
|
select {
|
||||||
|
min-width: 300px;
|
||||||
|
|
||||||
&.grow {
|
&.grow {
|
||||||
width: 95%;
|
width: 95%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,7 +256,8 @@ form {
|
|||||||
input[type="month"],
|
input[type="month"],
|
||||||
input[type="search"],
|
input[type="search"],
|
||||||
textarea,
|
textarea,
|
||||||
select {
|
select,
|
||||||
|
.ts-control {
|
||||||
background: var(--nf-input-background-color);
|
background: var(--nf-input-background-color);
|
||||||
font-size: var(--nf-input-font-size);
|
font-size: var(--nf-input-font-size);
|
||||||
border-color: var(--nf-input-border-color);
|
border-color: var(--nf-input-border-color);
|
||||||
@ -713,7 +717,11 @@ form {
|
|||||||
|
|
||||||
// ---------------- SELECT
|
// ---------------- SELECT
|
||||||
|
|
||||||
select {
|
select,
|
||||||
|
.ts-wrapper.multi .ts-control,
|
||||||
|
.ts-wrapper.single .ts-control,
|
||||||
|
.ts-wrapper.single.input-active .ts-control {
|
||||||
|
background-color: var(--nf-input-background-color);
|
||||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-chevron-down'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%236B7280' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-chevron-down'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
|
||||||
background-position: right calc(var(--nf-input-size) * 0.75) bottom 50%;
|
background-position: right calc(var(--nf-input-size) * 0.75) bottom 50%;
|
||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
|
@ -131,6 +131,10 @@ body {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[show-more]:not([show-more-loaded]) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
/*--------------------------------HEADER-------------------------------*/
|
/*--------------------------------HEADER-------------------------------*/
|
||||||
|
|
||||||
#popupheader {
|
#popupheader {
|
||||||
|
@ -125,15 +125,14 @@
|
|||||||
navbar.style.setProperty("display", current === "none" ? "block" : "none");
|
navbar.style.setProperty("display", current === "none" ? "block" : "none");
|
||||||
}
|
}
|
||||||
|
|
||||||
$(document).keydown(function (e) {
|
document.addEventListener("keydown", (e) => {
|
||||||
if ($(e.target).is('input')) { return }
|
// Looking at the `s` key when not typing in a form
|
||||||
if ($(e.target).is('textarea')) { return }
|
if (e.keyCode !== 83 || ["INPUT", "TEXTAREA", "SELECT"].includes(e.target.nodeName)) {
|
||||||
if ($(e.target).is('select')) { return }
|
return;
|
||||||
if (e.keyCode === 83) {
|
|
||||||
$("#search").focus();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
});
|
document.getElementById("search").focus();
|
||||||
|
e.preventDefault(); // Don't type the character in the focused search input
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</body>
|
</body>
|
||||||
|
@ -57,13 +57,4 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% block script %}
|
|
||||||
{{ super() }}
|
|
||||||
{% if popup %}
|
|
||||||
<script>
|
|
||||||
parent.$(".choose_file_widget").css("height", "75%");
|
|
||||||
</script>
|
|
||||||
{% endif %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -244,27 +244,30 @@
|
|||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script>
|
<script>
|
||||||
$(function () {
|
// Image selection
|
||||||
var keys = [];
|
for (const img of document.querySelectorAll("#small_pictures img")){
|
||||||
var pattern = "71,85,89,71,85,89";
|
img.addEventListener("click", (e) => {
|
||||||
$(document).keydown(function (e) {
|
const displayed = document.querySelector("#big_picture img");
|
||||||
keys.push(e.keyCode);
|
displayed.src = e.target.src;
|
||||||
if (keys.toString() == pattern) {
|
displayed.alt = e.target.alt;
|
||||||
keys = [];
|
displayed.title = e.target.title;
|
||||||
$("#big_picture img").attr("src", "{{ static('core/img/yug.jpg') }}");
|
|
||||||
}
|
|
||||||
if (keys.length == 6) {
|
|
||||||
keys.shift();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
$(function () {
|
|
||||||
$("#small_pictures img").click(function () {
|
|
||||||
$("#big_picture img").attr("src", $(this)[0].src);
|
|
||||||
$("#big_picture img").attr("alt", $(this)[0].alt);
|
|
||||||
$("#big_picture img").attr("title", $(this)[0].title);
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let keys = [];
|
||||||
|
const pattern = "71,85,89,71,85,89";
|
||||||
|
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
keys.push(e.keyCode);
|
||||||
|
if (keys.toString() === pattern) {
|
||||||
|
keys = [];
|
||||||
|
document.querySelector("#big_picture img").src = "{{ static('core/img/yug.jpg') }}";
|
||||||
|
}
|
||||||
|
if (keys.length === 6) {
|
||||||
|
keys.shift();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
$(function () {
|
$(function () {
|
||||||
$("#drop_gifts").accordion({
|
$("#drop_gifts").accordion({
|
||||||
heightStyle: "content",
|
heightStyle: "content",
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<li><a href="{{ url('rootplace:operation_logs') }}">{% trans %}Operation logs{% endtrans %}</a></li>
|
<li><a href="{{ url('rootplace:operation_logs') }}">{% trans %}Operation logs{% endtrans %}</a></li>
|
||||||
<li><a href="{{ url('rootplace:delete_forum_messages') }}">{% trans %}Delete user's forum messages{% endtrans %}</a></li>
|
<li><a href="{{ url('rootplace:delete_forum_messages') }}">{% trans %}Delete user's forum messages{% endtrans %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.has_perm("core:view_userban") %}
|
{% if user.has_perm("core.view_userban") %}
|
||||||
<li><a href="{{ url("rootplace:ban_list") }}">{% trans %}Bans{% endtrans %}</a></li>
|
<li><a href="{{ url("rootplace:ban_list") }}">{% trans %}Bans{% endtrans %}</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if user.can_create_subscription or user.is_root %}
|
{% if user.can_create_subscription or user.is_root %}
|
||||||
|
@ -76,7 +76,15 @@ export class CounterProductSelect extends AutoCompleteSelectBase {
|
|||||||
return {
|
return {
|
||||||
...super.tomSelectSettings(),
|
...super.tomSelectSettings(),
|
||||||
openOnFocus: false,
|
openOnFocus: false,
|
||||||
searchField: ["code", "text"],
|
// We make searching on exact code matching a higher priority
|
||||||
|
// We need to manually set weights or it results on an inconsistent
|
||||||
|
// behavior between production and development environment
|
||||||
|
searchField: [
|
||||||
|
// @ts-ignore documentation says it's fine, specified type is wrong
|
||||||
|
{ field: "code", weight: 2 },
|
||||||
|
// @ts-ignore documentation says it's fine, specified type is wrong
|
||||||
|
{ field: "text", weight: 0.5 },
|
||||||
|
],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,6 +236,10 @@ class TestCounterClick(TestFullClickBase):
|
|||||||
BanGroup.objects.get(pk=settings.SITH_GROUP_BANNED_COUNTER_ID)
|
BanGroup.objects.get(pk=settings.SITH_GROUP_BANNED_COUNTER_ID)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
cls.gift = product_recipe.make(
|
||||||
|
selling_price="-1.5",
|
||||||
|
special_selling_price="-1.5",
|
||||||
|
)
|
||||||
cls.beer = product_recipe.make(
|
cls.beer = product_recipe.make(
|
||||||
limit_age=18, selling_price="1.5", special_selling_price="1"
|
limit_age=18, selling_price="1.5", special_selling_price="1"
|
||||||
)
|
)
|
||||||
@ -253,7 +257,12 @@ class TestCounterClick(TestFullClickBase):
|
|||||||
limit_age=0, selling_price="1.5", special_selling_price="1"
|
limit_age=0, selling_price="1.5", special_selling_price="1"
|
||||||
)
|
)
|
||||||
|
|
||||||
cls.counter.products.add(cls.beer, cls.beer_tap, cls.snack)
|
cls.cons = Product.objects.get(id=settings.SITH_ECOCUP_CONS)
|
||||||
|
cls.dcons = Product.objects.get(id=settings.SITH_ECOCUP_DECO)
|
||||||
|
|
||||||
|
cls.counter.products.add(
|
||||||
|
cls.gift, cls.beer, cls.beer_tap, cls.snack, cls.cons, cls.dcons
|
||||||
|
)
|
||||||
|
|
||||||
cls.other_counter.products.add(cls.snack)
|
cls.other_counter.products.add(cls.snack)
|
||||||
|
|
||||||
@ -594,6 +603,84 @@ class TestCounterClick(TestFullClickBase):
|
|||||||
else:
|
else:
|
||||||
assert not counter.has_annotated_barman
|
assert not counter.has_annotated_barman
|
||||||
|
|
||||||
|
def test_selling_ordering(self):
|
||||||
|
# Cheaper items should be processed with a higher priority
|
||||||
|
self.login_in_bar(self.barmen)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
self.submit_basket(
|
||||||
|
self.customer,
|
||||||
|
[
|
||||||
|
BasketItem(self.beer.id, 1),
|
||||||
|
BasketItem(self.gift.id, 1),
|
||||||
|
],
|
||||||
|
).status_code
|
||||||
|
== 302
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.updated_amount(self.customer) == 0
|
||||||
|
|
||||||
|
def test_recordings(self):
|
||||||
|
self.refill_user(self.customer, self.cons.selling_price * 3)
|
||||||
|
self.login_in_bar(self.barmen)
|
||||||
|
assert (
|
||||||
|
self.submit_basket(
|
||||||
|
self.customer,
|
||||||
|
[BasketItem(self.cons.id, 3)],
|
||||||
|
).status_code
|
||||||
|
== 302
|
||||||
|
)
|
||||||
|
assert self.updated_amount(self.customer) == 0
|
||||||
|
|
||||||
|
assert (
|
||||||
|
self.submit_basket(
|
||||||
|
self.customer,
|
||||||
|
[BasketItem(self.dcons.id, 3)],
|
||||||
|
).status_code
|
||||||
|
== 302
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.updated_amount(self.customer) == self.dcons.selling_price * -3
|
||||||
|
|
||||||
|
assert (
|
||||||
|
self.submit_basket(
|
||||||
|
self.customer,
|
||||||
|
[BasketItem(self.dcons.id, settings.SITH_ECOCUP_LIMIT)],
|
||||||
|
).status_code
|
||||||
|
== 302
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.updated_amount(self.customer) == self.dcons.selling_price * (
|
||||||
|
-3 - settings.SITH_ECOCUP_LIMIT
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
self.submit_basket(
|
||||||
|
self.customer,
|
||||||
|
[BasketItem(self.dcons.id, 1)],
|
||||||
|
).status_code
|
||||||
|
== 200
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.updated_amount(self.customer) == self.dcons.selling_price * (
|
||||||
|
-3 - settings.SITH_ECOCUP_LIMIT
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
self.submit_basket(
|
||||||
|
self.customer,
|
||||||
|
[
|
||||||
|
BasketItem(self.cons.id, 1),
|
||||||
|
BasketItem(self.dcons.id, 1),
|
||||||
|
],
|
||||||
|
).status_code
|
||||||
|
== 302
|
||||||
|
)
|
||||||
|
|
||||||
|
assert self.updated_amount(self.customer) == self.dcons.selling_price * (
|
||||||
|
-3 - settings.SITH_ECOCUP_LIMIT
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class TestCounterStats(TestCase):
|
class TestCounterStats(TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -194,7 +194,11 @@ class CounterClick(CounterTabsMixin, CanViewMixin, SingleObjectMixin, FormView):
|
|||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
self.request.session["last_basket"] = []
|
self.request.session["last_basket"] = []
|
||||||
|
|
||||||
for form in formset:
|
# We sort items from cheap to expensive
|
||||||
|
# This is important because some items have a negative price
|
||||||
|
# Negative priced items gives money to the customer and should
|
||||||
|
# be processed first so that we don't throw a not enough money error
|
||||||
|
for form in sorted(formset, key=lambda form: form.product.price):
|
||||||
self.request.session["last_basket"].append(
|
self.request.session["last_basket"].append(
|
||||||
f"{form.cleaned_data['quantity']} x {form.product.name}"
|
f"{form.cleaned_data['quantity']} x {form.product.name}"
|
||||||
)
|
)
|
||||||
|
36
docker-compose.yml
Normal file
36
docker-compose.yml
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:16.6
|
||||||
|
restart: unless-stopped
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 512M
|
||||||
|
ports:
|
||||||
|
- "5431:5432"
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: sith
|
||||||
|
POSTGRES_PASSWORD: sith
|
||||||
|
POSTGRES_DB: sith
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:latest
|
||||||
|
restart: unless-stopped
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: '0.5'
|
||||||
|
memory: 512M
|
||||||
|
ports:
|
||||||
|
- "6378:6379"
|
||||||
|
command: redis-server
|
||||||
|
volumes:
|
||||||
|
- redis_data:/var/lib/redis/data/
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
driver: local
|
||||||
|
redis_data:
|
||||||
|
driver: local
|
||||||
|
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
Pour connecter l'application à une instance de sentry (ex: https://sentry.io),
|
Pour connecter l'application à une instance de sentry (ex: https://sentry.io),
|
||||||
il est nécessaire de configurer la variable `SENTRY_DSN`
|
il est nécessaire de configurer la variable `SENTRY_DSN`
|
||||||
dans le fichier `settings_custom.py`.
|
dans le fichier `.env`.
|
||||||
Cette variable est composée d'un lien complet vers votre projet sentry.
|
Cette variable est composée d'un lien complet vers votre projet sentry.
|
||||||
|
|
||||||
## Récupérer les statiques
|
## Récupérer les statiques
|
||||||
|
|
||||||
Nous utilisons du SCSS dans le projet.
|
Nous utilisons du SCSS dans le projet.
|
||||||
En environnement de développement (`DEBUG=True`),
|
En environnement de développement (`DEBUG=true`),
|
||||||
le SCSS est compilé à chaque fois que le fichier est demandé.
|
le SCSS est compilé à chaque fois que le fichier est demandé.
|
||||||
Pour la production, le projet considère
|
Pour la production, le projet considère
|
||||||
que chacun des fichiers est déjà compilé.
|
que chacun des fichiers est déjà compilé.
|
||||||
|
@ -47,19 +47,19 @@ Commencez par installer les dépendances système :
|
|||||||
=== "Debian/Ubuntu"
|
=== "Debian/Ubuntu"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install postgresql redis libq-dev nginx
|
sudo apt install postgresql libq-dev nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "Arch Linux"
|
=== "Arch Linux"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo pacman -S postgresql redis nginx
|
sudo pacman -S postgresql nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "macOS"
|
=== "macOS"
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install postgresql redis lipbq nginx
|
brew install postgresql lipbq nginx
|
||||||
export PATH="/usr/local/opt/libpq/bin:$PATH"
|
export PATH="/usr/local/opt/libpq/bin:$PATH"
|
||||||
source ~/.zshrc
|
source ~/.zshrc
|
||||||
```
|
```
|
||||||
@ -77,34 +77,6 @@ uv sync --group prod
|
|||||||
C'est parce que ces dépendances compilent certains modules
|
C'est parce que ces dépendances compilent certains modules
|
||||||
à l'installation.
|
à l'installation.
|
||||||
|
|
||||||
## Configurer Redis
|
|
||||||
|
|
||||||
Redis est utilisé comme cache.
|
|
||||||
Assurez-vous qu'il tourne :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl redis status
|
|
||||||
```
|
|
||||||
|
|
||||||
Et s'il ne tourne pas, démarrez-le :
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo systemctl start redis
|
|
||||||
sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot
|
|
||||||
```
|
|
||||||
|
|
||||||
Puis ajoutez le code suivant à la fin de votre fichier
|
|
||||||
`settings_custom.py` :
|
|
||||||
|
|
||||||
```python
|
|
||||||
CACHES = {
|
|
||||||
"default": {
|
|
||||||
"BACKEND": "django.core.cache.backends.redis.RedisCache",
|
|
||||||
"LOCATION": "redis://127.0.0.1:6379",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configurer PostgreSQL
|
## Configurer PostgreSQL
|
||||||
|
|
||||||
PostgreSQL est utilisé comme base de données.
|
PostgreSQL est utilisé comme base de données.
|
||||||
@ -139,26 +111,19 @@ en étant connecté en tant que postgres :
|
|||||||
psql -d sith -c "GRANT ALL PRIVILEGES ON SCHEMA public to sith";
|
psql -d sith -c "GRANT ALL PRIVILEGES ON SCHEMA public to sith";
|
||||||
```
|
```
|
||||||
|
|
||||||
Puis ajoutez le code suivant à la fin de votre
|
Puis modifiez votre `.env`.
|
||||||
`settings_custom.py` :
|
Dedans, décommentez l'url de la base de données
|
||||||
|
de postgres et commentez l'url de sqlite :
|
||||||
|
|
||||||
```python
|
```dotenv
|
||||||
DATABASES = {
|
#DATABASE_URL=sqlite:///db.sqlite3
|
||||||
"default": {
|
DATABASE_URL=postgres://sith:password@localhost:5432/sith
|
||||||
"ENGINE": "django.db.backends.postgresql",
|
|
||||||
"NAME": "sith",
|
|
||||||
"USER": "sith",
|
|
||||||
"PASSWORD": "password",
|
|
||||||
"HOST": "localhost",
|
|
||||||
"PORT": "", # laissez ce champ vide pour que le choix du port soit automatique
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Enfin, créez vos données :
|
Enfin, créez vos données :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
uv run ./manage.py populate
|
uv run ./manage.py setup
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
@ -247,7 +212,7 @@ Puis lancez ou relancez nginx :
|
|||||||
sudo systemctl restart nginx
|
sudo systemctl restart nginx
|
||||||
```
|
```
|
||||||
|
|
||||||
Dans votre `settings_custom.py`, remplacez `DEBUG=True` par `DEBUG=False`.
|
Dans votre `.env`, remplacez `DEBUG=true` par `DEBUG=false`.
|
||||||
|
|
||||||
Enfin, démarrez le serveur Django :
|
Enfin, démarrez le serveur Django :
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ Certaines dépendances sont nécessaires niveau système :
|
|||||||
- libjpeg
|
- libjpeg
|
||||||
- zlib1g-dev
|
- zlib1g-dev
|
||||||
- gettext
|
- gettext
|
||||||
|
- redis
|
||||||
|
|
||||||
### Installer WSL
|
### Installer WSL
|
||||||
|
|
||||||
@ -65,8 +66,8 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo apt install curl build-essential libssl-dev \
|
sudo apt install curl build-essential libssl-dev \
|
||||||
libjpeg-dev zlib1g-dev npm libffi-dev pkg-config \
|
libjpeg-dev zlib1g-dev npm libffi-dev pkg-config \
|
||||||
gettext git
|
gettext git redis
|
||||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -75,7 +76,7 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
|
|||||||
```bash
|
```bash
|
||||||
sudo pacman -Syu # on s'assure que les dépôts et le système sont à jour
|
sudo pacman -Syu # on s'assure que les dépôts et le système sont à jour
|
||||||
|
|
||||||
sudo pacman -S uv gcc git gettext pkgconf npm
|
sudo pacman -S uv gcc git gettext pkgconf npm redis
|
||||||
```
|
```
|
||||||
|
|
||||||
=== "macOS"
|
=== "macOS"
|
||||||
@ -84,7 +85,7 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
|
|||||||
Il est également nécessaire d'avoir installé xcode
|
Il est également nécessaire d'avoir installé xcode
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
brew install git uv npm
|
brew install git uv npm redis
|
||||||
|
|
||||||
# Pour bien configurer gettext
|
# Pour bien configurer gettext
|
||||||
brew link gettext # (suivez bien les instructions supplémentaires affichées)
|
brew link gettext # (suivez bien les instructions supplémentaires affichées)
|
||||||
@ -99,6 +100,24 @@ cd /mnt/<la_lettre_du_disque>/vos/fichiers/comme/dhab
|
|||||||
Python ne fait pas parti des dépendances puisqu'il est automatiquement
|
Python ne fait pas parti des dépendances puisqu'il est automatiquement
|
||||||
installé par uv.
|
installé par uv.
|
||||||
|
|
||||||
|
Parmi les dépendances installées se trouve redis (que nous utilisons comme cache).
|
||||||
|
Redis est un service qui doit être activé pour être utilisé.
|
||||||
|
Pour cela, effectuez les commandes :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl start redis
|
||||||
|
sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot
|
||||||
|
```
|
||||||
|
|
||||||
|
Parmi les dépendances installées se trouve redis (que nous utilisons comme cache).
|
||||||
|
Redis est un service qui doit être activé pour être utilisé.
|
||||||
|
Pour cela, effectuez les commandes :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl start redis
|
||||||
|
sudo systemctl enable redis # si vous voulez que redis démarre automatiquement au boot
|
||||||
|
```
|
||||||
|
|
||||||
## Finaliser l'installation
|
## Finaliser l'installation
|
||||||
|
|
||||||
Clonez le projet (depuis votre console WSL, si vous utilisez WSL)
|
Clonez le projet (depuis votre console WSL, si vous utilisez WSL)
|
||||||
@ -120,20 +139,24 @@ uv run ./manage.py install_xapian
|
|||||||
de texte à l'écran.
|
de texte à l'écran.
|
||||||
C'est normal, il ne faut pas avoir peur.
|
C'est normal, il ne faut pas avoir peur.
|
||||||
|
|
||||||
Maintenant que les dépendances sont installées, nous
|
Une fois les dépendances installées, il faut encore
|
||||||
allons créer la base de données, la remplir avec des données de test,
|
mettre en place quelques éléments de configuration,
|
||||||
et compiler les traductions.
|
qui peuvent varier d'un environnement à l'autre.
|
||||||
Cependant, avant de faire cela, il est nécessaire de modifier
|
Ces variables sont stockées dans un fichier `.env`.
|
||||||
la configuration pour signifier que nous sommes en mode développement.
|
Pour le créer, vous pouvez copier le fichier `.env.example` :
|
||||||
Pour cela, nous allons créer un fichier `sith/settings_custom.py`
|
|
||||||
et l'utiliser pour surcharger les settings de base.
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
echo "DEBUG=True" > sith/settings_custom.py
|
cp .env.example .env
|
||||||
echo 'SITH_URL = "localhost:8000"' >> sith/settings_custom.py
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Enfin, nous pouvons lancer les commandes suivantes :
|
Les variables par défaut contenues dans le fichier `.env`
|
||||||
|
devraient convenir pour le développement, sans modification.
|
||||||
|
|
||||||
|
Maintenant que les dépendances sont installées
|
||||||
|
et la configuration remplie, nous allons pouvoir générer
|
||||||
|
des données utiles pendant le développement.
|
||||||
|
|
||||||
|
Pour cela, lancez les commandes suivantes :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Prépare la base de données
|
# Prépare la base de données
|
||||||
@ -171,6 +194,30 @@ uv run ./manage.py runserver
|
|||||||
[http://localhost:8000/api/docs](http://localhost:8000/api/docs),
|
[http://localhost:8000/api/docs](http://localhost:8000/api/docs),
|
||||||
une interface swagger, avec toutes les routes de l'API.
|
une interface swagger, avec toutes les routes de l'API.
|
||||||
|
|
||||||
|
!!! question "Pourquoi l'installation est aussi complexe ?"
|
||||||
|
|
||||||
|
Cette question nous a été posée de nombreuses fois par des personnes
|
||||||
|
essayant d'installer le projet.
|
||||||
|
Il y a en effet un certain nombre d'étapes à suivre,
|
||||||
|
de paquets à installer et de commandes à exécuter.
|
||||||
|
|
||||||
|
Le processus d'installation peut donc sembler complexe.
|
||||||
|
|
||||||
|
En réalité, il est difficile de faire plus simple.
|
||||||
|
En effet, un site web a besoin de beaucoup de composants
|
||||||
|
pour être développé : il lui faut au minimum
|
||||||
|
une base de données, un cache, un bundler Javascript
|
||||||
|
et un interpréteur pour le code du serveur.
|
||||||
|
Pour nos besoin particuliers, nous utilisons également
|
||||||
|
un moteur de recherche full-text.
|
||||||
|
|
||||||
|
Nous avons tenté au maximum de limiter le nombre de dépendances
|
||||||
|
et de sélecionner les plus simples à installer.
|
||||||
|
Cependant, il est impossible de retirer l'intégralité
|
||||||
|
de la complexité du processus.
|
||||||
|
Si vous rencontrez des difficulté lors de l'installation,
|
||||||
|
n'hésitez pas à demander de l'aide.
|
||||||
|
|
||||||
## Générer la documentation
|
## Générer la documentation
|
||||||
|
|
||||||
La documentation est automatiquement mise en ligne à chaque envoi de code sur GitHub.
|
La documentation est automatiquement mise en ligne à chaque envoi de code sur GitHub.
|
||||||
|
@ -72,12 +72,14 @@ sith/
|
|||||||
├── .gitattributes
|
├── .gitattributes
|
||||||
├── .gitignore
|
├── .gitignore
|
||||||
├── .mailmap
|
├── .mailmap
|
||||||
├── manage.py (26)
|
├── .env (26)
|
||||||
├── mkdocs.yml (27)
|
├── .env.example (27)
|
||||||
|
├── manage.py (28)
|
||||||
|
├── mkdocs.yml (29)
|
||||||
├── uv.lock
|
├── uv.lock
|
||||||
├── pyproject.toml (28)
|
├── pyproject.toml (30)
|
||||||
├── .venv/ (29)
|
├── .venv/ (31)
|
||||||
├── .python-version (30)
|
├── .python-version (32)
|
||||||
└── README.md
|
└── README.md
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
@ -121,15 +123,19 @@ sith/
|
|||||||
de manière transparente pour l'utilisateur.
|
de manière transparente pour l'utilisateur.
|
||||||
24. Fichier de configuration de coverage.
|
24. Fichier de configuration de coverage.
|
||||||
25. Fichier de configuration de direnv.
|
25. Fichier de configuration de direnv.
|
||||||
26. Fichier généré automatiquement par Django. C'est lui
|
26. Contient les variables d'environnement, qui sont susceptibles
|
||||||
|
de varier d'une machine à l'autre.
|
||||||
|
27. Contient des valeurs par défaut pour le `.env`
|
||||||
|
pouvant convenir à un environnment de développement local
|
||||||
|
28. Fichier généré automatiquement par Django. C'est lui
|
||||||
qui permet d'appeler des commandes de gestion du projet
|
qui permet d'appeler des commandes de gestion du projet
|
||||||
avec la syntaxe `python ./manage.py <nom de la commande>`
|
avec la syntaxe `python ./manage.py <nom de la commande>`
|
||||||
27. Le fichier de configuration de la documentation,
|
29. Le fichier de configuration de la documentation,
|
||||||
avec ses plugins et sa table des matières.
|
avec ses plugins et sa table des matières.
|
||||||
28. Le fichier où sont déclarés les dépendances et la configuration
|
30. Le fichier où sont déclarés les dépendances et la configuration
|
||||||
de certaines d'entre elles.
|
de certaines d'entre elles.
|
||||||
29. Dossier d'environnement virtuel généré par uv
|
31. Dossier d'environnement virtuel généré par uv
|
||||||
30. Fichier qui contrôle quel version de python utiliser pour le projet
|
32. Fichier qui contrôle quelle version de python utiliser pour le projet
|
||||||
|
|
||||||
|
|
||||||
## L'application principale
|
## L'application principale
|
||||||
@ -144,10 +150,9 @@ Il est organisé comme suit :
|
|||||||
```
|
```
|
||||||
sith/
|
sith/
|
||||||
├── settings.py (1)
|
├── settings.py (1)
|
||||||
├── settings_custom.py (2)
|
├── toolbar_debug.py (2)
|
||||||
├── toolbar_debug.py (3)
|
├── urls.py (3)
|
||||||
├── urls.py (4)
|
└── wsgi.py (4)
|
||||||
└── wsgi.py (5)
|
|
||||||
```
|
```
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -155,13 +160,10 @@ sith/
|
|||||||
Ce fichier contient les paramètres de configuration du projet.
|
Ce fichier contient les paramètres de configuration du projet.
|
||||||
Par exemple, il contient la liste des applications
|
Par exemple, il contient la liste des applications
|
||||||
installées dans le projet.
|
installées dans le projet.
|
||||||
2. Configuration maison pour votre environnement.
|
2. Configuration de la barre de debug.
|
||||||
Toute variable que vous définissez dans ce fichier sera prioritaire
|
|
||||||
sur la configuration donnée dans `settings.py`.
|
|
||||||
3. Configuration de la barre de debug.
|
|
||||||
C'est inutilisé en prod, mais c'est très pratique en développement.
|
C'est inutilisé en prod, mais c'est très pratique en développement.
|
||||||
4. Fichier de configuration des urls du projet.
|
3. Fichier de configuration des urls du projet.
|
||||||
5. Fichier de configuration pour le serveur WSGI.
|
4. Fichier de configuration pour le serveur WSGI.
|
||||||
WSGI est un protocole de communication entre le serveur
|
WSGI est un protocole de communication entre le serveur
|
||||||
et les applications.
|
et les applications.
|
||||||
Ce fichier ne vous servira sans doute pas sur un environnement
|
Ce fichier ne vous servira sans doute pas sur un environnement
|
||||||
|
@ -15,8 +15,8 @@ $min_col_width: 100px;
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
gap: $gap;
|
gap: $gap;
|
||||||
|
|
||||||
> input,
|
>input,
|
||||||
> label {
|
>label {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -25,12 +25,12 @@ $min_col_width: 100px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.election_vote {
|
#page #content {
|
||||||
overflow-x: scroll !important;
|
overflow-x: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
.election_table {
|
.election_table {
|
||||||
width: 100%;
|
width: inherit;
|
||||||
|
|
||||||
>.lists {
|
>.lists {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -93,16 +93,30 @@ $min_col_width: 100px;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
row-gap: 10px;
|
||||||
padding: $padding;
|
padding: $padding;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
|
||||||
>.role_text {
|
>.role_text {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
>h4 {
|
>h4 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
>p {
|
|
||||||
|
.role_description {
|
||||||
|
flex-grow: 1;
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
|
text-wrap: auto;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
// Show more/less element
|
||||||
|
a {
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,9 +126,9 @@ $min_col_width: 100px;
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: $gap;
|
gap: $gap;
|
||||||
|
|
||||||
> button,
|
>button,
|
||||||
> button > i,
|
>button>i,
|
||||||
> a {
|
>a {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
@ -127,23 +141,23 @@ $min_col_width: 100px;
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:hover > i {
|
&:hover>i {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> button {
|
>button {
|
||||||
width: 30px;
|
width: 30px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> button[disabled] {
|
>button[disabled] {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
|
|
||||||
>i,
|
>i,
|
||||||
&:hover,
|
&:hover,
|
||||||
&:hover > i {
|
&:hover>i {
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,12 +192,12 @@ $min_col_width: 100px;
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
gap: $gap;
|
gap: $gap;
|
||||||
|
|
||||||
>input[type="radio"]:checked + label,
|
>input[type="radio"]:checked+label,
|
||||||
>input[type="checkbox"]:checked + label {
|
>input[type="checkbox"]:checked+label {
|
||||||
background-color: lightgray;
|
background-color: lightgray;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
|
||||||
>figure>.edit_btns>a:hover{
|
>figure>.edit_btns>a:hover {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,7 +229,9 @@ $min_col_width: 100px;
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.candidate_program {
|
.candidate_program {
|
||||||
|
text-wrap: auto;
|
||||||
margin: 5px 0;
|
margin: 5px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -228,7 +244,7 @@ $min_col_width: 100px;
|
|||||||
right: $gap;
|
right: $gap;
|
||||||
gap: $gap;
|
gap: $gap;
|
||||||
|
|
||||||
> a {
|
>a {
|
||||||
width: 20px;
|
width: 20px;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
background-color: #e9e9e9;
|
background-color: #e9e9e9;
|
||||||
@ -253,40 +269,44 @@ $min_col_width: 100px;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.election_details {
|
#content {
|
||||||
margin: .5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons {
|
.election_details {
|
||||||
display: flex;
|
margin: .5em 0;
|
||||||
flex-direction: row;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: $gap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
border: none;
|
|
||||||
color: black;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: $primary-neutral-light-color;
|
|
||||||
padding: 0.4em;
|
|
||||||
margin: 0.1em;
|
|
||||||
font-size: 1.18em;
|
|
||||||
border-radius: 5px;
|
|
||||||
box-shadow: #dfdfdf 0 0 1px;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: black;
|
|
||||||
background: #d4d4d4;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&_send {
|
.buttons {
|
||||||
background-color: #59aee2;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: $gap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
border: none;
|
||||||
|
color: black;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: $primary-neutral-light-color;
|
||||||
|
padding: 0.4em;
|
||||||
|
margin: 0.1em;
|
||||||
|
font-size: 1.18em;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: #dfdfdf 0 0 1px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgb(130, 186, 235);
|
color: black;
|
||||||
|
background: #d4d4d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_send {
|
||||||
|
background-color: #59aee2;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgb(130, 186, 235);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,13 +4,12 @@
|
|||||||
{{ object.title }}
|
{{ object.title }}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block head %}
|
{% block additional_js %}
|
||||||
{{ super() -}}
|
<script type="module" src="{{ static('bundled/core/read-more-index.ts') }}"></script>
|
||||||
<link rel="stylesheet" href="{{ static('election/css/election.scss') }}">
|
{% endblock %}
|
||||||
{%- endblock %}
|
|
||||||
|
|
||||||
{% block additional_css %}
|
{% block additional_css %}
|
||||||
<script src="{{ static('bundled/vendored/jquery.shorten.min.js') }}"></script>
|
<link rel="stylesheet" href="{{ static('election/css/election.scss') }}">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
@ -47,7 +46,6 @@
|
|||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<table class="election_table">
|
<table class="election_table">
|
||||||
{%- set election_lists = election.election_lists.all() -%}
|
{%- set election_lists = election.election_lists.all() -%}
|
||||||
<caption></caption>
|
|
||||||
<thead class="lists">
|
<thead class="lists">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="column" style="width: {{ 100 / (election_lists.count() + 1) }}%">{% trans %}Blank vote{% endtrans %}</th>
|
<th class="column" style="width: {{ 100 / (election_lists.count() + 1) }}%">{% trans %}Blank vote{% endtrans %}</th>
|
||||||
@ -70,7 +68,7 @@
|
|||||||
<td class="role_title">
|
<td class="role_title">
|
||||||
<div class="role_text">
|
<div class="role_text">
|
||||||
<h4>{{ role.title }}</h4>
|
<h4>{{ role.title }}</h4>
|
||||||
<p class="role_description">{{ role.description }}</p>
|
<p class="role_description" show-more="300">{{ role.description }}</p>
|
||||||
{%- if role.max_choice > 1 and not election.has_voted(user) and election.can_vote(user) %}
|
{%- if role.max_choice > 1 and not election.has_voted(user) and election.can_vote(user) %}
|
||||||
<strong>{% trans %}You may choose up to{% endtrans %} {{ role.max_choice }} {% trans %}people.{% endtrans %}</strong>
|
<strong>{% trans %}You may choose up to{% endtrans %} {{ role.max_choice }} {% trans %}people.{% endtrans %}</strong>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
@ -141,7 +139,9 @@
|
|||||||
<figcaption class="candidate__details">
|
<figcaption class="candidate__details">
|
||||||
<h5>{{ candidature.user.first_name }} <em>{{candidature.user.nick_name or ''}} </em>{{ candidature.user.last_name }}</h5>
|
<h5>{{ candidature.user.first_name }} <em>{{candidature.user.nick_name or ''}} </em>{{ candidature.user.last_name }}</h5>
|
||||||
{%- if not election.is_vote_finished %}
|
{%- if not election.is_vote_finished %}
|
||||||
<q class="candidate_program">{{ candidature.program | markdown or '' }}</q>
|
<q class="candidate_program" show-more="200">
|
||||||
|
{{ candidature.program|markdown or '' }}
|
||||||
|
</q>
|
||||||
{%- endif %}
|
{%- endif %}
|
||||||
</figcaption>
|
</figcaption>
|
||||||
{%- if user.can_edit(candidature) -%}
|
{%- if user.can_edit(candidature) -%}
|
||||||
@ -200,18 +200,6 @@
|
|||||||
|
|
||||||
{% block script %}
|
{% block script %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<script type="text/javascript">
|
|
||||||
$('.role_description').shorten({
|
|
||||||
moreText: "{% trans %}Show more{% endtrans %}",
|
|
||||||
lessText: "{% trans %}Show less{% endtrans %}",
|
|
||||||
showChars: 50
|
|
||||||
});
|
|
||||||
$('.candidate_program').shorten({
|
|
||||||
moreText: "{% trans %}Show more{% endtrans %}",
|
|
||||||
lessText: "{% trans %}Show less{% endtrans %}",
|
|
||||||
showChars: 200
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
document.querySelectorAll('.role__multiple-choices').forEach(setupRestrictions);
|
document.querySelectorAll('.role__multiple-choices').forEach(setupRestrictions);
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-01-05 16:39+0100\n"
|
"POT-Creation-Date: 2025-01-08 12:23+0100\n"
|
||||||
"PO-Revision-Date: 2016-07-18\n"
|
"PO-Revision-Date: 2016-07-18\n"
|
||||||
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
"Last-Translator: Maréchal <thomas.girod@utbm.fr\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@ -935,6 +935,10 @@ msgstr "rôle"
|
|||||||
msgid "description"
|
msgid "description"
|
||||||
msgstr "description"
|
msgstr "description"
|
||||||
|
|
||||||
|
#: club/models.py
|
||||||
|
msgid "past member"
|
||||||
|
msgstr "ancien membre"
|
||||||
|
|
||||||
#: club/models.py
|
#: club/models.py
|
||||||
msgid "Email address"
|
msgid "Email address"
|
||||||
msgstr "Adresse email"
|
msgstr "Adresse email"
|
||||||
@ -2062,16 +2066,12 @@ msgid "reason"
|
|||||||
msgstr "raison"
|
msgstr "raison"
|
||||||
|
|
||||||
#: core/models.py
|
#: core/models.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid "user"
|
|
||||||
msgid "user ban"
|
msgid "user ban"
|
||||||
msgstr "utilisateur"
|
msgstr "utilisateur banni"
|
||||||
|
|
||||||
#: core/models.py
|
#: core/models.py
|
||||||
#, fuzzy
|
|
||||||
#| msgid "user"
|
|
||||||
msgid "user bans"
|
msgid "user bans"
|
||||||
msgstr "utilisateur"
|
msgstr "utilisateurs bannis"
|
||||||
|
|
||||||
#: core/models.py
|
#: core/models.py
|
||||||
msgid "receive the Weekmail"
|
msgid "receive the Weekmail"
|
||||||
@ -3328,8 +3328,8 @@ msgstr "Nom d'utilisateur, email, ou numéro de compte AE"
|
|||||||
|
|
||||||
#: core/views/forms.py
|
#: core/views/forms.py
|
||||||
msgid ""
|
msgid ""
|
||||||
"Profile: you need to be visible on the picture, in order to be recognized (e."
|
"Profile: you need to be visible on the picture, in order to be recognized "
|
||||||
"g. by the barmen)"
|
"(e.g. by the barmen)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Photo de profil: vous devez être visible sur la photo afin d'être reconnu "
|
"Photo de profil: vous devez être visible sur la photo afin d'être reconnu "
|
||||||
"(par exemple par les barmen)"
|
"(par exemple par les barmen)"
|
||||||
@ -3935,8 +3935,8 @@ msgstr ""
|
|||||||
#: counter/templates/counter/mails/account_dump.jinja
|
#: counter/templates/counter/mails/account_dump.jinja
|
||||||
msgid "If you think this was a mistake, please mail us at ae@utbm.fr."
|
msgid "If you think this was a mistake, please mail us at ae@utbm.fr."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Si vous pensez qu'il s'agit d'une erreur, veuillez envoyer un mail à ae@utbm."
|
"Si vous pensez qu'il s'agit d'une erreur, veuillez envoyer un mail à "
|
||||||
"fr."
|
"ae@utbm.fr."
|
||||||
|
|
||||||
#: counter/templates/counter/mails/account_dump.jinja
|
#: counter/templates/counter/mails/account_dump.jinja
|
||||||
msgid ""
|
msgid ""
|
||||||
@ -4456,14 +4456,6 @@ msgstr "Ajouter un nouveau rôle"
|
|||||||
msgid "Submit the vote !"
|
msgid "Submit the vote !"
|
||||||
msgstr "Envoyer le vote !"
|
msgstr "Envoyer le vote !"
|
||||||
|
|
||||||
#: election/templates/election/election_detail.jinja
|
|
||||||
msgid "Show more"
|
|
||||||
msgstr "Montrer plus"
|
|
||||||
|
|
||||||
#: election/templates/election/election_detail.jinja
|
|
||||||
msgid "Show less"
|
|
||||||
msgstr "Montrer moins"
|
|
||||||
|
|
||||||
#: election/templates/election/election_list.jinja
|
#: election/templates/election/election_list.jinja
|
||||||
msgid "Election list"
|
msgid "Election list"
|
||||||
msgstr "Liste des élections"
|
msgstr "Liste des élections"
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2025-01-04 23:07+0100\n"
|
"POT-Creation-Date: 2025-01-08 12:23+0100\n"
|
||||||
"PO-Revision-Date: 2024-09-17 11:54+0200\n"
|
"PO-Revision-Date: 2024-09-17 11:54+0200\n"
|
||||||
"Last-Translator: Sli <antoine@bartuccio.fr>\n"
|
"Last-Translator: Sli <antoine@bartuccio.fr>\n"
|
||||||
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
"Language-Team: AE info <ae.info@utbm.fr>\n"
|
||||||
@ -113,6 +113,14 @@ msgstr "Guide markdown"
|
|||||||
msgid "Unsupported NFC card"
|
msgid "Unsupported NFC card"
|
||||||
msgstr "Carte NFC non supportée"
|
msgstr "Carte NFC non supportée"
|
||||||
|
|
||||||
|
#: core/static/bundled/core/read-more-index.ts
|
||||||
|
msgid "Show less"
|
||||||
|
msgstr "Montrer moins"
|
||||||
|
|
||||||
|
#: core/static/bundled/core/read-more-index.ts
|
||||||
|
msgid "Show more"
|
||||||
|
msgstr "Montrer plus"
|
||||||
|
|
||||||
#: core/static/bundled/user/family-graph-index.js
|
#: core/static/bundled/user/family-graph-index.js
|
||||||
msgid "family_tree.%(extension)s"
|
msgid "family_tree.%(extension)s"
|
||||||
msgstr "arbre_genealogique.%(extension)s"
|
msgstr "arbre_genealogique.%(extension)s"
|
||||||
|
13
package-lock.json
generated
13
package-lock.json
generated
@ -10,6 +10,7 @@
|
|||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alpinejs/sort": "^3.14.7",
|
"@alpinejs/sort": "^3.14.7",
|
||||||
|
"@arendjr/text-clipper": "npm:@jsr/arendjr__text-clipper@^3.0.0",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
"@fullcalendar/core": "^6.1.15",
|
"@fullcalendar/core": "^6.1.15",
|
||||||
"@fullcalendar/daygrid": "^6.1.15",
|
"@fullcalendar/daygrid": "^6.1.15",
|
||||||
@ -30,7 +31,6 @@
|
|||||||
"htmx.org": "^2.0.3",
|
"htmx.org": "^2.0.3",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"jquery-ui": "^1.14.0",
|
"jquery-ui": "^1.14.0",
|
||||||
"jquery.shorten": "^1.0.0",
|
|
||||||
"native-file-system-adapter": "^3.0.1",
|
"native-file-system-adapter": "^3.0.1",
|
||||||
"three": "^0.169.0",
|
"three": "^0.169.0",
|
||||||
"three-spritetext": "^1.9.0",
|
"three-spritetext": "^1.9.0",
|
||||||
@ -85,6 +85,12 @@
|
|||||||
"url": "https://github.com/sponsors/philsturgeon"
|
"url": "https://github.com/sponsors/philsturgeon"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@arendjr/text-clipper": {
|
||||||
|
"name": "@jsr/arendjr__text-clipper",
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://npm.jsr.io/~/11/@jsr/arendjr__text-clipper/3.0.0.tgz",
|
||||||
|
"integrity": "sha512-Uu3CYSvFrNdDkYKEaEKHAk0decaxVFlSSqf50Okte/9vJjO2rESzPF1ngQjS9H1aX45RIXRGMYOXJ/LPDFwUdQ=="
|
||||||
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.24.7",
|
"version": "7.24.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz",
|
||||||
@ -4392,11 +4398,6 @@
|
|||||||
"jquery": ">=1.12.0 <5.0.0"
|
"jquery": ">=1.12.0 <5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/jquery.shorten": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/jquery.shorten/-/jquery.shorten-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-49rJlpcyVI/Y2eQRwSexyz6l+fwTKfurO0XttXK4XnG9eQxIuE2Fb4rwNqnsnzStJ8M7ynlhH31fWE9P70B9rg=="
|
|
||||||
},
|
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alpinejs/sort": "^3.14.7",
|
"@alpinejs/sort": "^3.14.7",
|
||||||
|
"@arendjr/text-clipper": "npm:@jsr/arendjr__text-clipper@^3.0.0",
|
||||||
"@fortawesome/fontawesome-free": "^6.6.0",
|
"@fortawesome/fontawesome-free": "^6.6.0",
|
||||||
"@fullcalendar/core": "^6.1.15",
|
"@fullcalendar/core": "^6.1.15",
|
||||||
"@fullcalendar/daygrid": "^6.1.15",
|
"@fullcalendar/daygrid": "^6.1.15",
|
||||||
@ -56,7 +57,6 @@
|
|||||||
"htmx.org": "^2.0.3",
|
"htmx.org": "^2.0.3",
|
||||||
"jquery": "^3.7.1",
|
"jquery": "^3.7.1",
|
||||||
"jquery-ui": "^1.14.0",
|
"jquery-ui": "^1.14.0",
|
||||||
"jquery.shorten": "^1.0.0",
|
|
||||||
"native-file-system-adapter": "^3.0.1",
|
"native-file-system-adapter": "^3.0.1",
|
||||||
"three": "^0.169.0",
|
"three": "^0.169.0",
|
||||||
"three-spritetext": "^1.9.0",
|
"three-spritetext": "^1.9.0",
|
||||||
|
@ -44,6 +44,8 @@ dependencies = [
|
|||||||
"django-honeypot<2.0.0,>=1.2.1",
|
"django-honeypot<2.0.0,>=1.2.1",
|
||||||
"pydantic-extra-types<3.0.0,>=2.10.1",
|
"pydantic-extra-types<3.0.0,>=2.10.1",
|
||||||
"ical<9.0.0,>=8.3.0",
|
"ical<9.0.0,>=8.3.0",
|
||||||
|
"redis[hiredis]<6.0.0,>=5.2.0",
|
||||||
|
"environs[django]<15.0.0,>=14.1.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
@ -53,7 +55,6 @@ documentation = "https://sith-ae.readthedocs.io/"
|
|||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
prod = [
|
prod = [
|
||||||
"psycopg[c]<4.0.0,>=3.2.3",
|
"psycopg[c]<4.0.0,>=3.2.3",
|
||||||
"redis[hiredis]<6.0.0,>=5.2.0",
|
|
||||||
]
|
]
|
||||||
dev = [
|
dev = [
|
||||||
"django-debug-toolbar<5.0.0,>=4.4.6",
|
"django-debug-toolbar<5.0.0,>=4.4.6",
|
||||||
@ -84,8 +85,6 @@ default-groups = ["dev", "tests", "docs"]
|
|||||||
|
|
||||||
[tool.xapian]
|
[tool.xapian]
|
||||||
version = "1.4.25"
|
version = "1.4.25"
|
||||||
core-sha1 = "e2b4b4cf6076873ec9402cab7b9a3b71dcf95e20"
|
|
||||||
bindings-sha1 = "782f568d2ea3ca751c519a2814a35c7dc86df3a4"
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
output-format = "concise" # makes ruff error logs easier to read
|
output-format = "concise" # makes ruff error logs easier to read
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% if user.has_perm("core:add_userban") %}
|
{% if user.has_perm("core.add_userban") %}
|
||||||
<a href="{{ url("rootplace:ban_create") }}" class="btn btn-red margin-bottom">
|
<a href="{{ url("rootplace:ban_create") }}" class="btn btn-red margin-bottom">
|
||||||
<i class="fa fa-person-circle-xmark"></i>
|
<i class="fa fa-person-circle-xmark"></i>
|
||||||
{% trans %}Ban a user{% endtrans %}
|
{% trans %}Ban a user{% endtrans %}
|
||||||
@ -44,7 +44,7 @@
|
|||||||
<summary class="clickable">{% trans %}Reason{% endtrans %}</summary>
|
<summary class="clickable">{% trans %}Reason{% endtrans %}</summary>
|
||||||
<p>{{ user_ban.reason }}</p>
|
<p>{{ user_ban.reason }}</p>
|
||||||
</details>
|
</details>
|
||||||
{% if user.has_perm("core:delete_userban") %}
|
{% if user.has_perm("core.delete_userban") %}
|
||||||
<span>
|
<span>
|
||||||
<a
|
<a
|
||||||
href="{{ url("rootplace:ban_remove", ban_id=user_ban.id) }}"
|
href="{{ url("rootplace:ban_remove", ban_id=user_ban.id) }}"
|
||||||
|
161
sith/settings.py
161
sith/settings.py
@ -34,7 +34,6 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import binascii
|
import binascii
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
@ -43,25 +42,32 @@ from pathlib import Path
|
|||||||
import sentry_sdk
|
import sentry_sdk
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
from environs import Env
|
||||||
from sentry_sdk.integrations.django import DjangoIntegration
|
from sentry_sdk.integrations.django import DjangoIntegration
|
||||||
|
|
||||||
from .honeypot import custom_honeypot_error
|
from .honeypot import custom_honeypot_error
|
||||||
|
|
||||||
BASE_DIR = Path(__file__).parent.parent.resolve()
|
env = Env()
|
||||||
|
env.read_env()
|
||||||
|
|
||||||
os.environ["HTTPS"] = "off"
|
BASE_DIR = Path(__file__).parent.parent.resolve()
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Quick-start development settings - unsuitable for production
|
||||||
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
|
# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
SECRET_KEY = "(4sjxvhz@m5$0a$j0_pqicnc$s!vbve)z+&++m%g%bjhlz4+g2"
|
SECRET_KEY = env.str("SECRET_KEY")
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
DEBUG = False
|
DEBUG = env.bool("DEBUG", default=False)
|
||||||
TESTING = "pytest" in sys.modules
|
TESTING = "pytest" in sys.modules
|
||||||
INTERNAL_IPS = ["127.0.0.1"]
|
INTERNAL_IPS = ["127.0.0.1"]
|
||||||
|
|
||||||
|
# force csrf tokens and cookies to be secure when in https
|
||||||
|
CSRF_COOKIE_SECURE = env.bool("HTTPS", default=True)
|
||||||
|
SESSION_COOKIE_SECURE = env.bool("HTTPS", default=True)
|
||||||
|
X_FRAME_OPTIONS = "SAMEORIGIN"
|
||||||
|
|
||||||
ALLOWED_HOSTS = ["*"]
|
ALLOWED_HOSTS = ["*"]
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
@ -208,12 +214,12 @@ WSGI_APPLICATION = "sith.wsgi.application"
|
|||||||
# Database
|
# Database
|
||||||
|
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": env.dj_db_url("DATABASE_URL", conn_max_age=None, conn_health_checks=True)
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
|
||||||
"NAME": BASE_DIR / "db.sqlite3",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if "CACHE_URL" in os.environ:
|
||||||
|
CACHES = {"default": env.dj_cache_url("CACHE_URL")}
|
||||||
|
|
||||||
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||||
|
|
||||||
# Logging
|
# Logging
|
||||||
@ -265,13 +271,13 @@ PHONENUMBER_DEFAULT_REGION = "FR"
|
|||||||
|
|
||||||
# Medias
|
# Medias
|
||||||
MEDIA_URL = "/data/"
|
MEDIA_URL = "/data/"
|
||||||
MEDIA_ROOT = BASE_DIR / "data"
|
MEDIA_ROOT = env.path("MEDIA_ROOT", default=BASE_DIR / "data")
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
# Static files (CSS, JavaScript, Images)
|
||||||
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
# https://docs.djangoproject.com/en/1.8/howto/static-files/
|
||||||
|
|
||||||
STATIC_URL = "/static/"
|
STATIC_URL = "/static/"
|
||||||
STATIC_ROOT = BASE_DIR / "static"
|
STATIC_ROOT = env.path("STATIC_ROOT", default=BASE_DIR / "static")
|
||||||
|
|
||||||
# Static files finders which allow to see static folder in all apps
|
# Static files finders which allow to see static folder in all apps
|
||||||
STATICFILES_FINDERS = [
|
STATICFILES_FINDERS = [
|
||||||
@ -295,24 +301,28 @@ AUTHENTICATION_BACKENDS = ["core.auth_backends.SithModelBackend"]
|
|||||||
LOGIN_URL = "/login"
|
LOGIN_URL = "/login"
|
||||||
LOGOUT_URL = "/logout"
|
LOGOUT_URL = "/logout"
|
||||||
LOGIN_REDIRECT_URL = "/"
|
LOGIN_REDIRECT_URL = "/"
|
||||||
DEFAULT_FROM_EMAIL = "bibou@git.an"
|
DEFAULT_FROM_EMAIL = env.str("DEFAULT_FROM_EMAIL", default="bibou@git.an")
|
||||||
SITH_COM_EMAIL = "bibou_com@git.an"
|
SITH_COM_EMAIL = env.str("SITH_COM_EMAIL", default="bibou_com@git.an")
|
||||||
|
|
||||||
# Those values are to be changed in production to be more effective
|
# Those values are to be changed in production to be more effective
|
||||||
HONEYPOT_FIELD_NAME = "body2"
|
HONEYPOT_FIELD_NAME = env.str("HONEYPOT_FIELD_NAME", default="body2")
|
||||||
HONEYPOT_VALUE = "content"
|
HONEYPOT_VALUE = env.str("HONEYPOT_VALUE", default="content")
|
||||||
HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious
|
HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious
|
||||||
HONEYPOT_FIELD_NAME_FORUM = "message2" # Only used on forum
|
HONEYPOT_FIELD_NAME_FORUM = env.str(
|
||||||
|
"HONEYPOT_FIELD_NAME_FORUM", default="message2"
|
||||||
|
) # Only used on forum
|
||||||
|
|
||||||
# Email
|
# Email
|
||||||
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
|
EMAIL_BACKEND = env.str(
|
||||||
EMAIL_HOST = "localhost"
|
"EMAIL_BACKEND", default="django.core.mail.backends.dummy.EmailBackend"
|
||||||
EMAIL_PORT = 25
|
)
|
||||||
|
EMAIL_HOST = env.str("EMAIL_HOST", default="localhost")
|
||||||
|
EMAIL_PORT = env.int("EMAIL_PORT", default=25)
|
||||||
|
|
||||||
# Below this line, only Sith-specific variables are defined
|
# Below this line, only Sith-specific variables are defined
|
||||||
|
|
||||||
SITH_URL = "my.url.git.an"
|
SITH_URL = env.str("SITH_URL", default="127.0.0.1:8000")
|
||||||
SITH_NAME = "Sith website"
|
SITH_NAME = env.str("SITH_NAME", default="AE UTBM")
|
||||||
SITH_TWITTER = "@ae_utbm"
|
SITH_TWITTER = "@ae_utbm"
|
||||||
|
|
||||||
# Enable experimental features
|
# Enable experimental features
|
||||||
@ -321,7 +331,7 @@ SITH_ENABLE_GALAXY = False
|
|||||||
|
|
||||||
# AE configuration
|
# AE configuration
|
||||||
# TODO: keep only that first setting, with the ID, and do the same for the other clubs
|
# TODO: keep only that first setting, with the ID, and do the same for the other clubs
|
||||||
SITH_MAIN_CLUB_ID = 1
|
SITH_MAIN_CLUB_ID = env.int("SITH_MAIN_CLUB_ID", default=1)
|
||||||
SITH_MAIN_CLUB = {
|
SITH_MAIN_CLUB = {
|
||||||
"name": "AE",
|
"name": "AE",
|
||||||
"unix_name": "ae",
|
"unix_name": "ae",
|
||||||
@ -356,26 +366,28 @@ SITH_SCHOOL_START_YEAR = 1999
|
|||||||
# id of the Root account
|
# id of the Root account
|
||||||
SITH_ROOT_USER_ID = 0
|
SITH_ROOT_USER_ID = 0
|
||||||
|
|
||||||
SITH_GROUP_ROOT_ID = 1
|
SITH_GROUP_ROOT_ID = env.int("SITH_GROUP_ROOT_ID", default=1)
|
||||||
SITH_GROUP_PUBLIC_ID = 2
|
SITH_GROUP_PUBLIC_ID = env.int("SITH_GROUP_PUBLIC_ID", default=2)
|
||||||
SITH_GROUP_SUBSCRIBERS_ID = 3
|
SITH_GROUP_SUBSCRIBERS_ID = env.int("SITH_GROUP_SUBSCRIBERS_ID", default=3)
|
||||||
SITH_GROUP_OLD_SUBSCRIBERS_ID = 4
|
SITH_GROUP_OLD_SUBSCRIBERS_ID = env.int("SITH_GROUP_OLD_SUBSCRIBERS_ID", default=4)
|
||||||
SITH_GROUP_ACCOUNTING_ADMIN_ID = 5
|
SITH_GROUP_ACCOUNTING_ADMIN_ID = env.int("SITH_GROUP_ACCOUNTING_ADMIN_ID", default=5)
|
||||||
SITH_GROUP_COM_ADMIN_ID = 6
|
SITH_GROUP_COM_ADMIN_ID = env.int("SITH_GROUP_COM_ADMIN_ID", default=6)
|
||||||
SITH_GROUP_COUNTER_ADMIN_ID = 7
|
SITH_GROUP_COUNTER_ADMIN_ID = env.int("SITH_GROUP_COUNTER_ADMIN_ID", default=7)
|
||||||
SITH_GROUP_SAS_ADMIN_ID = 8
|
SITH_GROUP_SAS_ADMIN_ID = env.int("SITH_GROUP_SAS_ADMIN_ID", default=8)
|
||||||
SITH_GROUP_FORUM_ADMIN_ID = 9
|
SITH_GROUP_FORUM_ADMIN_ID = env.int("SITH_GROUP_FORUM_ADMIN_ID", default=9)
|
||||||
SITH_GROUP_PEDAGOGY_ADMIN_ID = 10
|
SITH_GROUP_PEDAGOGY_ADMIN_ID = env.int("SITH_GROUP_PEDAGOGY_ADMIN_ID", default=10)
|
||||||
|
|
||||||
SITH_GROUP_BANNED_ALCOHOL_ID = 11
|
SITH_GROUP_BANNED_ALCOHOL_ID = env.int("SITH_GROUP_BANNED_ALCOHOL_ID", default=11)
|
||||||
SITH_GROUP_BANNED_COUNTER_ID = 12
|
SITH_GROUP_BANNED_COUNTER_ID = env.int("SITH_GROUP_BANNED_COUNTER_ID", default=12)
|
||||||
SITH_GROUP_BANNED_SUBSCRIPTION_ID = 13
|
SITH_GROUP_BANNED_SUBSCRIPTION_ID = env.int(
|
||||||
|
"SITH_GROUP_BANNED_SUBSCRIPTION_ID", default=13
|
||||||
|
)
|
||||||
|
|
||||||
SITH_CLUB_REFOUND_ID = 89
|
SITH_CLUB_REFOUND_ID = env.int("SITH_CLUB_REFOUND_ID", default=89)
|
||||||
SITH_COUNTER_REFOUND_ID = 38
|
SITH_COUNTER_REFOUND_ID = env.int("SITH_COUNTER_REFOUND_ID", default=38)
|
||||||
SITH_PRODUCT_REFOUND_ID = 5
|
SITH_PRODUCT_REFOUND_ID = env.int("SITH_PRODUCT_REFOUND_ID", default=5)
|
||||||
|
|
||||||
SITH_COUNTER_ACCOUNT_DUMP_ID = 39
|
SITH_COUNTER_ACCOUNT_DUMP_ID = env.int("SITH_COUNTER_ACCOUNT_DUMP_ID", default=39)
|
||||||
|
|
||||||
# Pages
|
# Pages
|
||||||
SITH_CORE_PAGE_SYNTAX = "Aide_sur_la_syntaxe"
|
SITH_CORE_PAGE_SYNTAX = "Aide_sur_la_syntaxe"
|
||||||
@ -385,7 +397,7 @@ SITH_CORE_PAGE_SYNTAX = "Aide_sur_la_syntaxe"
|
|||||||
SITH_FORUM_PAGE_LENGTH = 30
|
SITH_FORUM_PAGE_LENGTH = 30
|
||||||
|
|
||||||
# SAS variables
|
# SAS variables
|
||||||
SITH_SAS_ROOT_DIR_ID = 4
|
SITH_SAS_ROOT_DIR_ID = env.int("SITH_SAS_ROOT_DIR_ID", default=4)
|
||||||
SITH_SAS_IMAGES_PER_PAGE = 60
|
SITH_SAS_IMAGES_PER_PAGE = 60
|
||||||
|
|
||||||
SITH_BOARD_SUFFIX = "-bureau"
|
SITH_BOARD_SUFFIX = "-bureau"
|
||||||
@ -492,9 +504,9 @@ SITH_LOG_OPERATION_TYPE = [
|
|||||||
|
|
||||||
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
|
SITH_PEDAGOGY_UTBM_API = "https://extranet1.utbm.fr/gpedago/api/guide"
|
||||||
|
|
||||||
SITH_ECOCUP_CONS = 1152
|
SITH_ECOCUP_CONS = env.int("SITH_ECOCUP_CONS", default=1151)
|
||||||
|
|
||||||
SITH_ECOCUP_DECO = 1151
|
SITH_ECOCUP_DECO = env.int("SITH_ECOCUP_DECO", default=1152)
|
||||||
|
|
||||||
# The limit is the maximum difference between cons and deco possible for a customer
|
# The limit is the maximum difference between cons and deco possible for a customer
|
||||||
SITH_ECOCUP_LIMIT = 3
|
SITH_ECOCUP_LIMIT = 3
|
||||||
@ -509,21 +521,31 @@ SITH_ACCOUNT_DUMP_DELTA = timedelta(days=30)
|
|||||||
|
|
||||||
# Defines which product type is the refilling type,
|
# Defines which product type is the refilling type,
|
||||||
# and thus increases the account amount
|
# and thus increases the account amount
|
||||||
SITH_COUNTER_PRODUCTTYPE_REFILLING = 3
|
SITH_COUNTER_PRODUCTTYPE_REFILLING = env.int(
|
||||||
|
"SITH_COUNTER_PRODUCTTYPE_REFILLING", default=3
|
||||||
|
)
|
||||||
|
|
||||||
# Defines which product is the one year subscription
|
# Defines which product is the one year subscription
|
||||||
# and which one is the six month subscription
|
# and which one is the six month subscription
|
||||||
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = 1
|
SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER = env.int(
|
||||||
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = 2
|
"SITH_PRODUCT_SUBSCRIPTION_ONE_SEMESTER", default=1
|
||||||
SITH_PRODUCTTYPE_SUBSCRIPTION = 2
|
)
|
||||||
|
SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS = env.int(
|
||||||
|
"SITH_PRODUCT_SUBSCRIPTION_TWO_SEMESTERS", default=2
|
||||||
|
)
|
||||||
|
SITH_PRODUCTTYPE_SUBSCRIPTION = env.int("SITH_PRODUCTTYPE_SUBSCRIPTION", default=2)
|
||||||
|
|
||||||
# Defines which club lets its member the ability to make subscriptions
|
# Defines which club lets its member the ability to make subscriptions
|
||||||
# Elements of this list are club's id
|
# Elements of this list are club's id
|
||||||
SITH_CAN_CREATE_SUBSCRIPTIONS = [1]
|
SITH_CAN_CREATE_SUBSCRIPTIONS = env.list(
|
||||||
|
"SITH_CAN_CREATE_SUBSCRIPTION_HISTORY", default=[1]
|
||||||
|
)
|
||||||
|
|
||||||
# Defines which clubs lets its members the ability to see users subscription history
|
# Defines which clubs lets its members the ability to see users subscription history
|
||||||
# Elements of this list are club's id
|
# Elements of this list are club's id
|
||||||
SITH_CAN_READ_SUBSCRIPTION_HISTORY = []
|
SITH_CAN_READ_SUBSCRIPTION_HISTORY = env.list(
|
||||||
|
"SITH_CAN_READ_SUBSCRIPTION_HISTORY", default=[1]
|
||||||
|
)
|
||||||
|
|
||||||
# Number of weeks before the end of a subscription when the subscriber can resubscribe
|
# Number of weeks before the end of a subscription when the subscriber can resubscribe
|
||||||
SITH_SUBSCRIPTION_END = 10
|
SITH_SUBSCRIPTION_END = 10
|
||||||
@ -632,21 +654,29 @@ SITH_BARMAN_TIMEOUT = 30
|
|||||||
SITH_LAST_OPERATIONS_LIMIT = 10
|
SITH_LAST_OPERATIONS_LIMIT = 10
|
||||||
|
|
||||||
# ET variables
|
# ET variables
|
||||||
SITH_EBOUTIC_CB_ENABLED = True
|
SITH_EBOUTIC_CB_ENABLED = env.bool("SITH_EBOUTIC_CB_ENABLED", default=True)
|
||||||
SITH_EBOUTIC_ET_URL = (
|
SITH_EBOUTIC_ET_URL = env.str(
|
||||||
"https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi"
|
"SITH_EBOUTIC_ET_URL",
|
||||||
|
default="https://preprod-tpeweb.e-transactions.fr/cgi/MYchoix_pagepaiement.cgi",
|
||||||
)
|
)
|
||||||
SITH_EBOUTIC_PBX_SITE = "1999888"
|
SITH_EBOUTIC_PBX_SITE = env.str("SITH_EBOUTIC_PBX_SITE", default="1999888")
|
||||||
SITH_EBOUTIC_PBX_RANG = "32"
|
SITH_EBOUTIC_PBX_RANG = env.str("SITH_EBOUTIC_PBX_RANG", default="32")
|
||||||
SITH_EBOUTIC_PBX_IDENTIFIANT = "2"
|
SITH_EBOUTIC_PBX_IDENTIFIANT = env.str("SITH_EBOUTIC_PBX_IDENTIFIANT", default="2")
|
||||||
SITH_EBOUTIC_HMAC_KEY = binascii.unhexlify(
|
SITH_EBOUTIC_HMAC_KEY = binascii.unhexlify(
|
||||||
"0123456789ABCDEF0123456789ABCDEF"
|
env.str(
|
||||||
"0123456789ABCDEF0123456789ABCDEF"
|
"SITH_EBOUTIC_HMAC_KEY",
|
||||||
"0123456789ABCDEF0123456789ABCDEF"
|
default=(
|
||||||
"0123456789ABCDEF0123456789ABCDEF"
|
"0123456789ABCDEF0123456789ABCDEF"
|
||||||
|
"0123456789ABCDEF0123456789ABCDEF"
|
||||||
|
"0123456789ABCDEF0123456789ABCDEF"
|
||||||
|
"0123456789ABCDEF0123456789ABCDEF"
|
||||||
|
),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
SITH_EBOUTIC_PUB_KEY = ""
|
SITH_EBOUTIC_PUB_KEY = ""
|
||||||
with open(os.path.join(os.path.dirname(__file__), "et_keys/pubkey.pem")) as f:
|
with open(
|
||||||
|
env.path("SITH_EBOUTIC_PUB_KEY_PATH", default=BASE_DIR / "sith/et_keys/pubkey.pem")
|
||||||
|
) as f:
|
||||||
SITH_EBOUTIC_PUB_KEY = f.read()
|
SITH_EBOUTIC_PUB_KEY = f.read()
|
||||||
|
|
||||||
# Launderette variables
|
# Launderette variables
|
||||||
@ -688,24 +718,17 @@ SITH_QUICK_NOTIF = {
|
|||||||
# Mailing related settings
|
# Mailing related settings
|
||||||
|
|
||||||
SITH_MAILING_DOMAIN = "utbm.fr"
|
SITH_MAILING_DOMAIN = "utbm.fr"
|
||||||
SITH_MAILING_FETCH_KEY = "IloveMails"
|
SITH_MAILING_FETCH_KEY = env.str("SITH_MAILING_FETCH_KEY", default="ILoveMails")
|
||||||
|
|
||||||
SITH_GIFT_LIST = [("AE Tee-shirt", _("AE tee-shirt"))]
|
SITH_GIFT_LIST = [("AE Tee-shirt", _("AE tee-shirt"))]
|
||||||
|
|
||||||
SENTRY_DSN = ""
|
SENTRY_DSN = env.str("SENRY_DSN", default=None)
|
||||||
SENTRY_ENV = "production"
|
SENTRY_ENV = env.str("SENTRY_ENV", default="production")
|
||||||
|
|
||||||
TOXIC_DOMAINS_PROVIDERS = [
|
TOXIC_DOMAINS_PROVIDERS = [
|
||||||
"https://www.stopforumspam.com/downloads/toxic_domains_whole.txt",
|
"https://www.stopforumspam.com/downloads/toxic_domains_whole.txt",
|
||||||
]
|
]
|
||||||
|
|
||||||
try:
|
|
||||||
from .settings_custom import * # noqa F403 (this star-import is actually useful)
|
|
||||||
|
|
||||||
logging.getLogger("django").info("Custom settings imported")
|
|
||||||
except ImportError:
|
|
||||||
logging.getLogger("django").warning("Custom settings failed")
|
|
||||||
|
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
INSTALLED_APPS += ("debug_toolbar",)
|
INSTALLED_APPS += ("debug_toolbar",)
|
||||||
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE)
|
MIDDLEWARE = ("debug_toolbar.middleware.DebugToolbarMiddleware", *MIDDLEWARE)
|
||||||
|
@ -81,7 +81,7 @@ def sentry_debug(request):
|
|||||||
The error will be displayed on Sentry
|
The error will be displayed on Sentry
|
||||||
inside the "development" environment
|
inside the "development" environment
|
||||||
|
|
||||||
NOTE : you need to specify the SENTRY_DSN setting in settings_custom.py
|
NOTE : you need to specify the SENTRY_DSN setting in .env
|
||||||
"""
|
"""
|
||||||
if settings.SENTRY_ENV != "development" or not settings.SENTRY_DSN:
|
if settings.SENTRY_ENV != "development" or not settings.SENTRY_DSN:
|
||||||
raise Http404
|
raise Http404
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import subprocess
|
import subprocess
|
||||||
import platform
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
@ -95,7 +94,7 @@ class JSBundler:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def compile():
|
def compile():
|
||||||
"""Bundle js files with the javascript bundler for production."""
|
"""Bundle js files with the javascript bundler for production."""
|
||||||
process = subprocess.Popen(["npm", "run", "compile"], shell=platform.system() == "Windows")
|
process = subprocess.Popen(["npm", "run", "compile"])
|
||||||
process.wait()
|
process.wait()
|
||||||
if process.returncode:
|
if process.returncode:
|
||||||
raise RuntimeError(f"Bundler failed with returncode {process.returncode}")
|
raise RuntimeError(f"Bundler failed with returncode {process.returncode}")
|
||||||
@ -104,7 +103,7 @@ class JSBundler:
|
|||||||
def runserver() -> subprocess.Popen:
|
def runserver() -> subprocess.Popen:
|
||||||
"""Bundle js files automatically in background when called in debug mode."""
|
"""Bundle js files automatically in background when called in debug mode."""
|
||||||
logging.getLogger("django").info("Running javascript bundling server")
|
logging.getLogger("django").info("Running javascript bundling server")
|
||||||
return subprocess.Popen(["npm", "run", "serve"], shell=platform.system() == "Windows")
|
return subprocess.Popen(["npm", "run", "serve"])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_manifest() -> JSBundlerManifest:
|
def get_manifest() -> JSBundlerManifest:
|
||||||
@ -198,4 +197,4 @@ class OpenApi:
|
|||||||
with open(out, "w") as f:
|
with open(out, "w") as f:
|
||||||
_ = f.write(schema)
|
_ = f.write(schema)
|
||||||
|
|
||||||
subprocess.run(["npx", "openapi-ts"], check=True, shell=platform.system() == "Windows")
|
subprocess.run(["npx", "openapi-ts"], check=True)
|
||||||
|
86
uv.lock
generated
86
uv.lock
generated
@ -155,7 +155,7 @@ name = "click"
|
|||||||
version = "8.1.8"
|
version = "8.1.8"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||||
]
|
]
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
|
sdist = { url = "https://files.pythonhosted.org/packages/b9/2e/0090cbf739cee7d23781ad4b89a9894a41538e4fcf4c31dcdd705b78eb8b/click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a", size = 226593 }
|
||||||
wheels = [
|
wheels = [
|
||||||
@ -276,6 +276,28 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 },
|
{ url = "https://files.pythonhosted.org/packages/91/a1/cf2472db20f7ce4a6be1253a81cfdf85ad9c7885ffbed7047fb72c24cf87/distlib-0.3.9-py2.py3-none-any.whl", hash = "sha256:47f8c22fd27c27e25a65601af709b38e4f0a45ea4fc2e710f65755fa8caaaf87", size = 468973 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dj-database-url"
|
||||||
|
version = "2.3.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "django" },
|
||||||
|
{ name = "typing-extensions" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/98/9f/fc9905758256af4f68a55da94ab78a13e7775074edfdcaddd757d4921686/dj_database_url-2.3.0.tar.gz", hash = "sha256:ae52e8e634186b57e5a45e445da5dc407a819c2ceed8a53d1fac004cc5288787", size = 10980 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e5/91/641a4e5c8903ed59f6cbcce571003bba9c5d2f731759c31db0ba83bb0bdb/dj_database_url-2.3.0-py3-none-any.whl", hash = "sha256:bb0d414ba0ac5cd62773ec7f86f8cc378a9dbb00a80884c2fc08cc570452521e", size = 7793 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dj-email-url"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/14/ef/8eb478accd9b0369d46a98d1b43027ee0c254096149265c78e6b2e2fa3b0/dj-email-url-1.0.6.tar.gz", hash = "sha256:55ffe3329e48f54f8a75aa36ece08f365e09d61f8a209773ef09a1d4760e699a", size = 15590 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/8a/f9/fcb9745099d821f9a26092d3d6f4df8f10049885045c3a93ff726d2e40a6/dj_email_url-1.0.6-py2.py3-none-any.whl", hash = "sha256:cbd08327fbb08b104eac160fb4703f375532e4c0243eb230f5b960daee7a96db", size = 6296 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django"
|
name = "django"
|
||||||
version = "4.2.17"
|
version = "4.2.17"
|
||||||
@ -290,6 +312,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/5e/85/457360cb3de496382e35db4c2af054066df5c40e26df31400d0109a0500c/Django-4.2.17-py3-none-any.whl", hash = "sha256:3a93350214ba25f178d4045c0786c61573e7dbfa3c509b3551374f1e11ba8de0", size = 7993390 },
|
{ url = "https://files.pythonhosted.org/packages/5e/85/457360cb3de496382e35db4c2af054066df5c40e26df31400d0109a0500c/Django-4.2.17-py3-none-any.whl", hash = "sha256:3a93350214ba25f178d4045c0786c61573e7dbfa3c509b3551374f1e11ba8de0", size = 7993390 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "django-cache-url"
|
||||||
|
version = "3.4.5"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/20/28/d420aaa89028d2ec0cf17c1510d06ff6a8ed0bf1abfb7f33c999e1c5befa/django-cache-url-3.4.5.tar.gz", hash = "sha256:eb9fb194717524348c95cad9905b70b647452741c1d9e481fac6d2125f0ad917", size = 7230 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/48/90/01755e4a42558b763f7021e9369aa6aa94c2ede7313deed56cb7483834ab/django_cache_url-3.4.5-py2.py3-none-any.whl", hash = "sha256:5f350759978483ab85dc0e3e17b3d53eed3394a28148f6bf0f53d11d0feb5b3c", size = 4760 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "django-countries"
|
name = "django-countries"
|
||||||
version = "7.6.1"
|
version = "7.6.1"
|
||||||
@ -439,6 +470,26 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", size = 570472 },
|
{ url = "https://files.pythonhosted.org/packages/93/69/e391bd51bc08ed9141ecd899a0ddb61ab6465309f1eb470905c0c8868081/docutils-0.19-py3-none-any.whl", hash = "sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc", size = 570472 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "environs"
|
||||||
|
version = "14.1.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "marshmallow" },
|
||||||
|
{ name = "python-dotenv" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/3c/8f/952bd034eac79c8b68b6c770cb78c2bdcb3140d31ff224847f3520077d75/environs-14.1.0.tar.gz", hash = "sha256:a5f2afe9d5a21b468e74a3cceacf5d2371fd67dbb9a7e54fe62290c75a09cdfa", size = 30985 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/ad/57cfa3e8a006df88e723524127dbab2971a4877c97e7bad070257e15cb6c/environs-14.1.0-py3-none-any.whl", hash = "sha256:a7edda1668ddf1fbfcb7662bdc242dac25648eff2c7fdbaa5d959693afed7a3e", size = 15332 },
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.optional-dependencies]
|
||||||
|
django = [
|
||||||
|
{ name = "dj-database-url" },
|
||||||
|
{ name = "dj-email-url" },
|
||||||
|
{ name = "django-cache-url" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "executing"
|
name = "executing"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
@ -708,6 +759,18 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
{ url = "https://files.pythonhosted.org/packages/4f/65/6079a46068dfceaeabb5dcad6d674f5f5c61a6fa5673746f42a9f4c233b3/MarkupSafe-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:e444a31f8db13eb18ada366ab3cf45fd4b31e4db1236a4448f68778c1d1a5a2f", size = 15739 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "marshmallow"
|
||||||
|
version = "3.25.0"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
dependencies = [
|
||||||
|
{ name = "packaging" },
|
||||||
|
]
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bd/5c/cbfa41491d6c83b36471f2a2f75602349d20a8f88afd94f83c1e68bbc298/marshmallow-3.25.0.tar.gz", hash = "sha256:5ba94a4eb68894ad6761a505eb225daf7e5cb7b4c32af62d4a45e9d42665bc31", size = 176751 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/76/26/b347619b719d4c048e038929769f8f6b28c6d930149b40d950bbdde31d48/marshmallow-3.25.0-py3-none-any.whl", hash = "sha256:50894cd57c6b097a6c6ed2bf216af47d10146990a54db52d03e32edb0448c905", size = 49480 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "matplotlib-inline"
|
name = "matplotlib-inline"
|
||||||
version = "0.1.7"
|
version = "0.1.7"
|
||||||
@ -744,7 +807,7 @@ version = "1.6.1"
|
|||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "click" },
|
{ name = "click" },
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
{ name = "colorama", marker = "platform_system == 'Windows'" },
|
||||||
{ name = "ghp-import" },
|
{ name = "ghp-import" },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
{ name = "markdown" },
|
{ name = "markdown" },
|
||||||
@ -1229,6 +1292,15 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-dotenv"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/bc/57/e84d88dfe0aec03b7a2d4327012c1627ab5f03652216c63d49846d7a6c58/python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca", size = 39115 }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6a/3e/b68c118422ec867fa7ab88444e1274aa40681c606d59ac27de5a5588f082/python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a", size = 19863 },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.2"
|
version = "6.0.2"
|
||||||
@ -1428,6 +1500,7 @@ dependencies = [
|
|||||||
{ name = "django-ordered-model" },
|
{ name = "django-ordered-model" },
|
||||||
{ name = "django-phonenumber-field" },
|
{ name = "django-phonenumber-field" },
|
||||||
{ name = "django-simple-captcha" },
|
{ name = "django-simple-captcha" },
|
||||||
|
{ name = "environs", extra = ["django"] },
|
||||||
{ name = "ical" },
|
{ name = "ical" },
|
||||||
{ name = "jinja2" },
|
{ name = "jinja2" },
|
||||||
{ name = "libsass" },
|
{ name = "libsass" },
|
||||||
@ -1436,6 +1509,7 @@ dependencies = [
|
|||||||
{ name = "pillow" },
|
{ name = "pillow" },
|
||||||
{ name = "pydantic-extra-types" },
|
{ name = "pydantic-extra-types" },
|
||||||
{ name = "python-dateutil" },
|
{ name = "python-dateutil" },
|
||||||
|
{ name = "redis", extra = ["hiredis"] },
|
||||||
{ name = "reportlab" },
|
{ name = "reportlab" },
|
||||||
{ name = "sentry-sdk" },
|
{ name = "sentry-sdk" },
|
||||||
{ name = "sphinx" },
|
{ name = "sphinx" },
|
||||||
@ -1462,7 +1536,6 @@ docs = [
|
|||||||
]
|
]
|
||||||
prod = [
|
prod = [
|
||||||
{ name = "psycopg", extra = ["c"] },
|
{ name = "psycopg", extra = ["c"] },
|
||||||
{ name = "redis", extra = ["hiredis"] },
|
|
||||||
]
|
]
|
||||||
tests = [
|
tests = [
|
||||||
{ name = "freezegun" },
|
{ name = "freezegun" },
|
||||||
@ -1486,6 +1559,7 @@ requires-dist = [
|
|||||||
{ name = "django-ordered-model", specifier = ">=3.7.4,<4.0.0" },
|
{ name = "django-ordered-model", specifier = ">=3.7.4,<4.0.0" },
|
||||||
{ name = "django-phonenumber-field", specifier = ">=8.0.0,<9.0.0" },
|
{ name = "django-phonenumber-field", specifier = ">=8.0.0,<9.0.0" },
|
||||||
{ name = "django-simple-captcha", specifier = ">=0.6.0,<1.0.0" },
|
{ name = "django-simple-captcha", specifier = ">=0.6.0,<1.0.0" },
|
||||||
|
{ name = "environs", extras = ["django"], specifier = ">=14.1.0,<15.0.0" },
|
||||||
{ name = "ical", specifier = ">=8.3.0,<9.0.0" },
|
{ name = "ical", specifier = ">=8.3.0,<9.0.0" },
|
||||||
{ name = "jinja2", specifier = ">=3.1.4,<4.0.0" },
|
{ name = "jinja2", specifier = ">=3.1.4,<4.0.0" },
|
||||||
{ name = "libsass", specifier = ">=0.23.0,<1.0.0" },
|
{ name = "libsass", specifier = ">=0.23.0,<1.0.0" },
|
||||||
@ -1494,6 +1568,7 @@ requires-dist = [
|
|||||||
{ name = "pillow", specifier = ">=11.0.0,<12.0.0" },
|
{ name = "pillow", specifier = ">=11.0.0,<12.0.0" },
|
||||||
{ name = "pydantic-extra-types", specifier = ">=2.10.1,<3.0.0" },
|
{ name = "pydantic-extra-types", specifier = ">=2.10.1,<3.0.0" },
|
||||||
{ name = "python-dateutil", specifier = ">=2.9.0.post0,<3.0.0.0" },
|
{ name = "python-dateutil", specifier = ">=2.9.0.post0,<3.0.0.0" },
|
||||||
|
{ name = "redis", extras = ["hiredis"], specifier = ">=5.2.0,<6.0.0" },
|
||||||
{ name = "reportlab", specifier = ">=4.2.5,<5.0.0" },
|
{ name = "reportlab", specifier = ">=4.2.5,<5.0.0" },
|
||||||
{ name = "sentry-sdk", specifier = ">=2.19.2,<3.0.0" },
|
{ name = "sentry-sdk", specifier = ">=2.19.2,<3.0.0" },
|
||||||
{ name = "sphinx", specifier = ">=5,<6" },
|
{ name = "sphinx", specifier = ">=5,<6" },
|
||||||
@ -1518,10 +1593,7 @@ docs = [
|
|||||||
{ name = "mkdocstrings", specifier = ">=0.27.0,<1.0.0" },
|
{ name = "mkdocstrings", specifier = ">=0.27.0,<1.0.0" },
|
||||||
{ name = "mkdocstrings-python", specifier = ">=1.12.2,<2.0.0" },
|
{ name = "mkdocstrings-python", specifier = ">=1.12.2,<2.0.0" },
|
||||||
]
|
]
|
||||||
prod = [
|
prod = [{ name = "psycopg", extras = ["c"], specifier = ">=3.2.3,<4.0.0" }]
|
||||||
{ name = "psycopg", extras = ["c"], specifier = ">=3.2.3,<4.0.0" },
|
|
||||||
{ name = "redis", extras = ["hiredis"], specifier = ">=5.2.0,<6.0.0" },
|
|
||||||
]
|
|
||||||
tests = [
|
tests = [
|
||||||
{ name = "freezegun", specifier = ">=1.5.1,<2.0.0" },
|
{ name = "freezegun", specifier = ">=1.5.1,<2.0.0" },
|
||||||
{ name = "model-bakery", specifier = ">=1.20.0,<2.0.0" },
|
{ name = "model-bakery", specifier = ">=1.20.0,<2.0.0" },
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
// biome-ignore lint/correctness/noNodejsModules: this is backend side
|
// biome-ignore lint/correctness/noNodejsModules: this is backend side
|
||||||
import { parse, resolve, sep } from "node:path";
|
import { parse, resolve } from "node:path";
|
||||||
import inject from "@rollup/plugin-inject";
|
import inject from "@rollup/plugin-inject";
|
||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
import { type AliasOptions, type UserConfig, defineConfig } from "vite";
|
import { type AliasOptions, type UserConfig, defineConfig } from "vite";
|
||||||
@ -31,7 +31,7 @@ function getAliases(): AliasOptions {
|
|||||||
function getRelativeAssetPath(path: string): string {
|
function getRelativeAssetPath(path: string): string {
|
||||||
let relativePath: string[] = [];
|
let relativePath: string[] = [];
|
||||||
const fullPath = parse(path);
|
const fullPath = parse(path);
|
||||||
for (const dir of fullPath.dir.split(sep).reverse()) {
|
for (const dir of fullPath.dir.split("/").reverse()) {
|
||||||
if (dir === "bundled") {
|
if (dir === "bundled") {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -40,7 +40,7 @@ function getRelativeAssetPath(path: string): string {
|
|||||||
// We collected folders in reverse order, we put them back in the original order
|
// We collected folders in reverse order, we put them back in the original order
|
||||||
relativePath = relativePath.reverse();
|
relativePath = relativePath.reverse();
|
||||||
relativePath.push(fullPath.name);
|
relativePath.push(fullPath.name);
|
||||||
return relativePath.join(sep);
|
return relativePath.join("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
// biome-ignore lint/style/noDefaultExport: this is recommended by documentation
|
// biome-ignore lint/style/noDefaultExport: this is recommended by documentation
|
||||||
@ -97,10 +97,6 @@ export default defineConfig((config: UserConfig) => {
|
|||||||
src: resolve(nodeModules, "jquery-ui/dist/jquery-ui.min.js"),
|
src: resolve(nodeModules, "jquery-ui/dist/jquery-ui.min.js"),
|
||||||
dest: vendored,
|
dest: vendored,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
src: resolve(nodeModules, "jquery.shorten/src/jquery.shorten.min.js"),
|
|
||||||
dest: vendored,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
Reference in New Issue
Block a user