diff --git a/core/management/commands/check_fs.py b/core/management/commands/check_fs.py index 16764eb9..8e970ced 100644 --- a/core/management/commands/check_fs.py +++ b/core/management/commands/check_fs.py @@ -21,8 +21,6 @@ # # -import os - from django.core.management.base import BaseCommand from core.models import SithFile @@ -37,9 +35,6 @@ class Command(BaseCommand): ) def handle(self, *args, **options): - root_path = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - ) files = SithFile.objects.filter(id__in=options["ids"]).all() for f in files: f._check_fs() diff --git a/core/management/commands/compilestatic.py b/core/management/commands/compilestatic.py index 1e6566cf..d2f64bfd 100644 --- a/core/management/commands/compilestatic.py +++ b/core/management/commands/compilestatic.py @@ -22,7 +22,7 @@ # # -import os +import sys import sass from django.conf import settings @@ -34,44 +34,36 @@ class Command(BaseCommand): help = "Compile scss files from static folder" - def compile(self, filename): - args = {"filename": filename, "include_paths": settings.STATIC_ROOT} + def compile(self, filename: str): + args = { + "filename": filename, + "include_paths": settings.STATIC_ROOT.name, + "output_style": "compressed", + } if settings.SASS_PRECISION: args["precision"] = settings.SASS_PRECISION return sass.compile(**args) - def is_compilable(self, file, ext_list): - path, ext = os.path.splitext(file) - return ext in ext_list - - def exec_on_folder(self, folder, func): - to_exec = [] - for file in os.listdir(folder): - file = os.path.join(folder, file) - if os.path.isdir(file): - self.exec_on_folder(file, func) - elif self.is_compilable(file, [".scss"]): - to_exec.append(file) - - for file in to_exec: - func(file) - - def compilescss(self, file): - print("compiling %s" % file) - with open(file.replace(".scss", ".css"), "w") as newfile: - newfile.write(self.compile(file)) - - def removescss(self, file): - print("removing %s" % file) - os.remove(file) - def handle(self, *args, **options): - if os.path.isdir(settings.STATIC_ROOT): - print("---- Compiling scss files ---") - self.exec_on_folder(settings.STATIC_ROOT, self.compilescss) - print("---- Removing scss files ----") - self.exec_on_folder(settings.STATIC_ROOT, self.removescss) - else: - print( - "No static folder avalaible, please use collectstatic before compiling scss" + if not settings.STATIC_ROOT.is_dir(): + raise Exception( + "No static folder availaible, please use collectstatic before compiling scss" ) + to_exec = list(settings.STATIC_ROOT.rglob("*.scss")) + if len(to_exec) == 0: + self.stdout.write("Nothing to compile.") + sys.exit(0) + self.stdout.write("---- Compiling scss files ---") + for file in to_exec: + # remove existing css files that will be replaced + # keeping them while compiling the scss would break + # import statements resolution + css_file = file.with_suffix(".css") + if css_file.exists(): + css_file.unlink() + compiled_files = {file: self.compile(str(file.resolve())) for file in to_exec} + for file, scss in compiled_files.items(): + file.replace(file.with_suffix(".css")).write_text(scss) + self.stdout.write( + "Files compiled : \n" + "\n- ".join(str(f) for f in compiled_files) + ) diff --git a/core/management/commands/markdown.py b/core/management/commands/markdown.py index 09a03d0d..8b43e5ac 100644 --- a/core/management/commands/markdown.py +++ b/core/management/commands/markdown.py @@ -21,7 +21,6 @@ # # -from pathlib import Path from django.conf import settings from django.core.management.base import BaseCommand @@ -33,7 +32,7 @@ class Command(BaseCommand): help = "Output the fully rendered SYNTAX.md file" def handle(self, *args, **options): - root_path = Path(settings.BASE_DIR) + root_path = settings.BASE_DIR with open(root_path / "core/fixtures/SYNTAX.md", "r") as md: result = markdown(md.read()) print(result, end="") diff --git a/core/management/commands/repair_fs.py b/core/management/commands/repair_fs.py index 5c8cca6c..cf88d108 100644 --- a/core/management/commands/repair_fs.py +++ b/core/management/commands/repair_fs.py @@ -21,7 +21,6 @@ # # -import os from django.core.management.base import BaseCommand @@ -37,9 +36,6 @@ class Command(BaseCommand): ) def handle(self, *args, **options): - root_path = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - ) files = SithFile.objects.filter(id__in=options["ids"]).all() for f in files: f._repair_fs() diff --git a/core/management/commands/setup.py b/core/management/commands/setup.py index 291908e6..0db339a5 100644 --- a/core/management/commands/setup.py +++ b/core/management/commands/setup.py @@ -13,8 +13,6 @@ # # -from pathlib import Path - from django.conf import settings from django.core.management import call_command from django.core.management.base import BaseCommand @@ -26,11 +24,11 @@ class Command(BaseCommand): def handle(self, *args, **options): if not settings.DEBUG: raise Exception("Never call this command in prod. Never.") - data_dir = Path(settings.BASE_DIR) / "data" + data_dir = settings.BASE_DIR / "data" settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend" if not data_dir.is_dir(): data_dir.mkdir() - db_path = Path(settings.BASE_DIR) / "db.sqlite3" + db_path = settings.BASE_DIR / "db.sqlite3" if db_path.exists(): call_command("flush", "--noinput") self.stdout.write("Existing database reset") diff --git a/core/models.py b/core/models.py index 13332abe..c9aceab6 100644 --- a/core/models.py +++ b/core/models.py @@ -27,6 +27,7 @@ import importlib import os import unicodedata from datetime import date, timedelta +from pathlib import Path from typing import TYPE_CHECKING, Optional from django.conf import settings @@ -56,8 +57,6 @@ from django.utils.html import escape from django.utils.translation import gettext_lazy as _ from phonenumber_field.modelfields import PhoneNumberField -from core import utils - if TYPE_CHECKING: from club.models import Club @@ -377,7 +376,9 @@ class User(AbstractBaseUser): USERNAME_FIELD = "username" def promo_has_logo(self): - return utils.file_exist("./core/static/core/img/promo_%02d.png" % self.promo) + return Path( + settings.BASE_DIR / f"core/static/core/img/promo_{self.promo}.png" + ).exists() def has_module_perms(self, package_name): return self.is_active diff --git a/core/scss/finder.py b/core/scss/finder.py index cf8915ef..a4b7d030 100644 --- a/core/scss/finder.py +++ b/core/scss/finder.py @@ -22,7 +22,6 @@ # # -import os from collections import OrderedDict from django.conf import settings @@ -37,7 +36,7 @@ class ScssFinder(FileSystemFinder): def __init__(self, apps=None, *args, **kwargs): location = settings.STATIC_ROOT - if not os.path.isdir(location): + if not location.is_dir(): return self.locations = [("", location)] self.storages = OrderedDict() diff --git a/core/scss/processor.py b/core/scss/processor.py index ac851cd8..eb682747 100644 --- a/core/scss/processor.py +++ b/core/scss/processor.py @@ -21,61 +21,35 @@ # Place - Suite 330, Boston, MA 02111-1307, USA. # # - -import os -from urllib.parse import urljoin +import functools +from pathlib import Path import sass from django.conf import settings from django.core.files.base import ContentFile from django.templatetags.static import static -from django.utils.encoding import force_bytes, iri_to_uri +from django_jinja.builtins.filters import static from core.scss.storage import ScssFileStorage, find_file -class ScssProcessor(object): - """If DEBUG mode enabled : compile the scss file - Else : give the path of the corresponding css supposed to already be compiled - Don't forget to use compilestatics to compile scss for production. - """ +@functools.cache +def _scss_storage(): + return ScssFileStorage() - prefix = iri_to_uri(getattr(settings, "STATIC_URL", "/static/")) - storage = ScssFileStorage() - scss_extensions = [".scss"] - def __init__(self, path=None): - self.path = path - - def _convert_scss(self): - basename, ext = os.path.splitext(self.path) - css_filename = self.path.replace(".scss", ".css") - url = urljoin(self.prefix, css_filename) - - if not settings.DEBUG: - return url - - if ext not in self.scss_extensions: - return static(self.path) - - # Compilation on the fly +def process_scss_path(path: Path): + css_path = path.with_suffix(".css") + if settings.DEBUG: compile_args = { - "filename": find_file(self.path), + "filename": find_file(path), "include_paths": settings.SASS_INCLUDE_FOLDERS, } if settings.SASS_PRECISION: compile_args["precision"] = settings.SASS_PRECISION content = sass.compile(**compile_args) - content = force_bytes(content) - - if self.storage.exists(css_filename): - self.storage.delete(css_filename) - self.storage.save(css_filename, ContentFile(content)) - - return url - - def get_converted_scss(self): - if self.path: - return self._convert_scss() - else: - return "" + storage = _scss_storage() + if storage.exists(css_path): + storage.delete(css_path) + storage.save(css_path, ContentFile(content)) + return static(css_path) diff --git a/core/templatetags/renderer.py b/core/templatetags/renderer.py index 8d6bac64..cfd3ef91 100644 --- a/core/templatetags/renderer.py +++ b/core/templatetags/renderer.py @@ -23,15 +23,17 @@ # import datetime +from pathlib import Path import phonenumbers from django import template +from django.template import TemplateSyntaxError from django.template.defaultfilters import stringfilter from django.utils.safestring import mark_safe from django.utils.translation import ngettext from core.markdown import markdown as md -from core.scss.processor import ScssProcessor +from core.scss.processor import process_scss_path register = template.Library() @@ -86,5 +88,7 @@ def format_timedelta(value: datetime.timedelta) -> str: @register.simple_tag() def scss(path): """Return path of the corresponding css file after compilation.""" - processor = ScssProcessor(path) - return processor.get_converted_scss() + path = Path(path) + if path.suffix != ".scss": + raise TemplateSyntaxError("`scss` tag has been called with a non-scss file") + return process_scss_path(path) diff --git a/core/tests.py b/core/tests.py index 6fa24874..1377a5ae 100644 --- a/core/tests.py +++ b/core/tests.py @@ -14,7 +14,6 @@ # from datetime import date, timedelta -from pathlib import Path from smtplib import SMTPException import freezegun @@ -210,7 +209,7 @@ def test_custom_markdown_syntax(md, html): def test_full_markdown_syntax(): - syntax_path = Path(settings.BASE_DIR) / "core" / "fixtures" + syntax_path = settings.BASE_DIR / "core" / "fixtures" md = (syntax_path / "SYNTAX.md").read_text() html = (syntax_path / "SYNTAX.html").read_text() result = markdown(md) diff --git a/core/utils.py b/core/utils.py index 41d132bc..b336a125 100644 --- a/core/utils.py +++ b/core/utils.py @@ -13,7 +13,6 @@ # # -import os import re import subprocess from datetime import date @@ -96,10 +95,6 @@ def get_semester_code(d: Optional[date] = None) -> str: return "P" + str(start.year)[-2:] -def file_exist(path): - return os.path.exists(path) - - def scale_dimension(width, height, long_edge): if width > height: ratio = long_edge * 1.0 / width diff --git a/core/views/files.py b/core/views/files.py index 83f7e00c..161578d0 100644 --- a/core/views/files.py +++ b/core/views/files.py @@ -14,7 +14,6 @@ # # This file contains all the views that concern the page model -import os from wsgiref.util import FileWrapper from ajax_select import make_ajax_field @@ -59,17 +58,17 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"): ): raise PermissionDenied name = f.__getattribute__(file_attr).name - filepath = os.path.join(settings.MEDIA_ROOT, name) + filepath = settings.MEDIA_ROOT / name # check if file exists on disk - if not os.path.exists(filepath.encode("utf-8")): - raise Http404() + if not filepath.exists(): + raise Http404 - with open(filepath.encode("utf-8"), "rb") as filename: + with open(filepath, "rb") as filename: wrapper = FileWrapper(filename) response = HttpResponse(wrapper, content_type=f.mime_type) response["Last-Modified"] = http_date(f.date.timestamp()) - response["Content-Length"] = os.path.getsize(filepath.encode("utf-8")) + response["Content-Length"] = filepath.stat().st_size response["Content-Disposition"] = ('inline; filename="%s"' % f.name).encode( "utf-8" ) diff --git a/sas/models.py b/sas/models.py index 7c1ae6e8..7330d823 100644 --- a/sas/models.py +++ b/sas/models.py @@ -13,7 +13,6 @@ # # -import os from io import BytesIO from django.conf import settings @@ -46,9 +45,7 @@ class Picture(SithFile): @property def is_vertical(self): - with open( - os.path.join(settings.MEDIA_ROOT, self.file.name).encode("utf-8"), "rb" - ) as f: + with open(settings.MEDIA_ROOT / self.file.name, "rb") as f: im = Image.open(BytesIO(f.read())) (w, h) = im.size return (w / h) < 1 @@ -112,9 +109,7 @@ class Picture(SithFile): def rotate(self, degree): for attr in ["file", "compressed", "thumbnail"]: name = self.__getattribute__(attr).name - with open( - os.path.join(settings.MEDIA_ROOT, name).encode("utf-8"), "r+b" - ) as file: + with open(settings.MEDIA_ROOT / name, "r+b") as file: if file: im = Image.open(BytesIO(file.read())) file.seek(0) diff --git a/sith/settings.py b/sith/settings.py index 479c91c3..7f2c4b0d 100644 --- a/sith/settings.py +++ b/sith/settings.py @@ -34,10 +34,9 @@ https://docs.djangoproject.com/en/1.8/ref/settings/ """ import binascii - -# Build paths inside the project like this: os.path.join(BASE_DIR, ...) import os import sys +from pathlib import Path import sentry_sdk from django.utils.translation import gettext_lazy as _ @@ -45,7 +44,7 @@ from sentry_sdk.integrations.django import DjangoIntegration from .honeypot import custom_honeypot_error -BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +BASE_DIR = Path(__file__).parent.parent.resolve() os.environ["HTTPS"] = "off" @@ -212,7 +211,7 @@ REST_FRAMEWORK = {} DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", - "NAME": os.path.join(BASE_DIR, "db.sqlite3"), + "NAME": BASE_DIR / "db.sqlite3", } } @@ -252,19 +251,19 @@ USE_I18N = True USE_TZ = True -LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) +LOCALE_PATHS = [BASE_DIR / "locale"] PHONENUMBER_DEFAULT_REGION = "FR" # Medias -MEDIA_ROOT = "./data/" MEDIA_URL = "/data/" +MEDIA_ROOT = BASE_DIR / "data" # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/1.8/howto/static-files/ STATIC_URL = "/static/" -STATIC_ROOT = "./static/" +STATIC_ROOT = BASE_DIR / "static" # Static files finders which allow to see static folder in all apps STATICFILES_FINDERS = [ @@ -288,7 +287,6 @@ HONEYPOT_VALUE = "content" HONEYPOT_RESPONDER = custom_honeypot_error # Make honeypot errors less suspicious HONEYPOT_FIELD_NAME_FORUM = "message2" # Only used on forum - # Email EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_HOST = "localhost" @@ -725,7 +723,6 @@ if SENTRY_DSN: environment=SENTRY_ENV, ) - SITH_FRONT_DEP_VERSIONS = { "https://github.com/Stuk/jszip-utils": "0.1.0", "https://github.com/Stuk/jszip": "3.10.1",