Merge pull request #736 from ae-utbm/better-scss

Better scss
This commit is contained in:
thomas girod 2024-07-28 16:35:12 +02:00 committed by GitHub
commit e5434961de
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 72 additions and 129 deletions

View File

@ -21,8 +21,6 @@
# #
# #
import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
from core.models import SithFile from core.models import SithFile
@ -37,9 +35,6 @@ class Command(BaseCommand):
) )
def handle(self, *args, **options): 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() files = SithFile.objects.filter(id__in=options["ids"]).all()
for f in files: for f in files:
f._check_fs() f._check_fs()

View File

@ -22,7 +22,7 @@
# #
# #
import os import sys
import sass import sass
from django.conf import settings from django.conf import settings
@ -34,44 +34,36 @@ class Command(BaseCommand):
help = "Compile scss files from static folder" help = "Compile scss files from static folder"
def compile(self, filename): def compile(self, filename: str):
args = {"filename": filename, "include_paths": settings.STATIC_ROOT} args = {
"filename": filename,
"include_paths": settings.STATIC_ROOT.name,
"output_style": "compressed",
}
if settings.SASS_PRECISION: if settings.SASS_PRECISION:
args["precision"] = settings.SASS_PRECISION args["precision"] = settings.SASS_PRECISION
return sass.compile(**args) 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): def handle(self, *args, **options):
if os.path.isdir(settings.STATIC_ROOT): if not settings.STATIC_ROOT.is_dir():
print("---- Compiling scss files ---") raise Exception(
self.exec_on_folder(settings.STATIC_ROOT, self.compilescss) "No static folder availaible, please use collectstatic before compiling scss"
print("---- Removing scss files ----") )
self.exec_on_folder(settings.STATIC_ROOT, self.removescss) to_exec = list(settings.STATIC_ROOT.rglob("*.scss"))
else: if len(to_exec) == 0:
print( self.stdout.write("Nothing to compile.")
"No static folder avalaible, please use collectstatic before compiling scss" 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)
) )

View File

@ -21,7 +21,6 @@
# #
# #
from pathlib import Path
from django.conf import settings from django.conf import settings
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@ -33,7 +32,7 @@ class Command(BaseCommand):
help = "Output the fully rendered SYNTAX.md file" help = "Output the fully rendered SYNTAX.md file"
def handle(self, *args, **options): 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: with open(root_path / "core/fixtures/SYNTAX.md", "r") as md:
result = markdown(md.read()) result = markdown(md.read())
print(result, end="") print(result, end="")

View File

@ -21,7 +21,6 @@
# #
# #
import os
from django.core.management.base import BaseCommand from django.core.management.base import BaseCommand
@ -37,9 +36,6 @@ class Command(BaseCommand):
) )
def handle(self, *args, **options): 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() files = SithFile.objects.filter(id__in=options["ids"]).all()
for f in files: for f in files:
f._repair_fs() f._repair_fs()

View File

@ -13,8 +13,6 @@
# #
# #
from pathlib import Path
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
@ -26,11 +24,11 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
if not settings.DEBUG: if not settings.DEBUG:
raise Exception("Never call this command in prod. Never.") 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" settings.EMAIL_BACKEND = "django.core.mail.backends.dummy.EmailBackend"
if not data_dir.is_dir(): if not data_dir.is_dir():
data_dir.mkdir() data_dir.mkdir()
db_path = Path(settings.BASE_DIR) / "db.sqlite3" db_path = settings.BASE_DIR / "db.sqlite3"
if db_path.exists(): if db_path.exists():
call_command("flush", "--noinput") call_command("flush", "--noinput")
self.stdout.write("Existing database reset") self.stdout.write("Existing database reset")

View File

@ -27,6 +27,7 @@ import importlib
import os import os
import unicodedata import unicodedata
from datetime import date, timedelta from datetime import date, timedelta
from pathlib import Path
from typing import TYPE_CHECKING, Optional from typing import TYPE_CHECKING, Optional
from django.conf import settings from django.conf import settings
@ -56,8 +57,6 @@ from django.utils.html import escape
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField from phonenumber_field.modelfields import PhoneNumberField
from core import utils
if TYPE_CHECKING: if TYPE_CHECKING:
from club.models import Club from club.models import Club
@ -377,7 +376,9 @@ class User(AbstractBaseUser):
USERNAME_FIELD = "username" USERNAME_FIELD = "username"
def promo_has_logo(self): 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): def has_module_perms(self, package_name):
return self.is_active return self.is_active

View File

@ -22,7 +22,6 @@
# #
# #
import os
from collections import OrderedDict from collections import OrderedDict
from django.conf import settings from django.conf import settings
@ -37,7 +36,7 @@ class ScssFinder(FileSystemFinder):
def __init__(self, apps=None, *args, **kwargs): def __init__(self, apps=None, *args, **kwargs):
location = settings.STATIC_ROOT location = settings.STATIC_ROOT
if not os.path.isdir(location): if not location.is_dir():
return return
self.locations = [("", location)] self.locations = [("", location)]
self.storages = OrderedDict() self.storages = OrderedDict()

View File

@ -21,61 +21,35 @@
# Place - Suite 330, Boston, MA 02111-1307, USA. # Place - Suite 330, Boston, MA 02111-1307, USA.
# #
# #
import functools
import os from pathlib import Path
from urllib.parse import urljoin
import sass import sass
from django.conf import settings from django.conf import settings
from django.core.files.base import ContentFile from django.core.files.base import ContentFile
from django.templatetags.static import static 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 from core.scss.storage import ScssFileStorage, find_file
class ScssProcessor(object): @functools.cache
"""If DEBUG mode enabled : compile the scss file def _scss_storage():
Else : give the path of the corresponding css supposed to already be compiled return ScssFileStorage()
Don't forget to use compilestatics to compile scss for production.
"""
prefix = iri_to_uri(getattr(settings, "STATIC_URL", "/static/"))
storage = ScssFileStorage()
scss_extensions = [".scss"]
def __init__(self, path=None): def process_scss_path(path: Path):
self.path = path css_path = path.with_suffix(".css")
if settings.DEBUG:
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
compile_args = { compile_args = {
"filename": find_file(self.path), "filename": find_file(path),
"include_paths": settings.SASS_INCLUDE_FOLDERS, "include_paths": settings.SASS_INCLUDE_FOLDERS,
} }
if settings.SASS_PRECISION: if settings.SASS_PRECISION:
compile_args["precision"] = settings.SASS_PRECISION compile_args["precision"] = settings.SASS_PRECISION
content = sass.compile(**compile_args) content = sass.compile(**compile_args)
content = force_bytes(content) storage = _scss_storage()
if storage.exists(css_path):
if self.storage.exists(css_filename): storage.delete(css_path)
self.storage.delete(css_filename) storage.save(css_path, ContentFile(content))
self.storage.save(css_filename, ContentFile(content)) return static(css_path)
return url
def get_converted_scss(self):
if self.path:
return self._convert_scss()
else:
return ""

View File

@ -23,15 +23,17 @@
# #
import datetime import datetime
from pathlib import Path
import phonenumbers import phonenumbers
from django import template from django import template
from django.template import TemplateSyntaxError
from django.template.defaultfilters import stringfilter from django.template.defaultfilters import stringfilter
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import ngettext from django.utils.translation import ngettext
from core.markdown import markdown as md from core.markdown import markdown as md
from core.scss.processor import ScssProcessor from core.scss.processor import process_scss_path
register = template.Library() register = template.Library()
@ -86,5 +88,7 @@ def format_timedelta(value: datetime.timedelta) -> str:
@register.simple_tag() @register.simple_tag()
def scss(path): def scss(path):
"""Return path of the corresponding css file after compilation.""" """Return path of the corresponding css file after compilation."""
processor = ScssProcessor(path) path = Path(path)
return processor.get_converted_scss() if path.suffix != ".scss":
raise TemplateSyntaxError("`scss` tag has been called with a non-scss file")
return process_scss_path(path)

View File

@ -14,7 +14,6 @@
# #
from datetime import date, timedelta from datetime import date, timedelta
from pathlib import Path
from smtplib import SMTPException from smtplib import SMTPException
import freezegun import freezegun
@ -210,7 +209,7 @@ def test_custom_markdown_syntax(md, html):
def test_full_markdown_syntax(): 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() md = (syntax_path / "SYNTAX.md").read_text()
html = (syntax_path / "SYNTAX.html").read_text() html = (syntax_path / "SYNTAX.html").read_text()
result = markdown(md) result = markdown(md)

View File

@ -13,7 +13,6 @@
# #
# #
import os
import re import re
import subprocess import subprocess
from datetime import date from datetime import date
@ -96,10 +95,6 @@ def get_semester_code(d: Optional[date] = None) -> str:
return "P" + str(start.year)[-2:] return "P" + str(start.year)[-2:]
def file_exist(path):
return os.path.exists(path)
def scale_dimension(width, height, long_edge): def scale_dimension(width, height, long_edge):
if width > height: if width > height:
ratio = long_edge * 1.0 / width ratio = long_edge * 1.0 / width

View File

@ -14,7 +14,6 @@
# #
# This file contains all the views that concern the page model # This file contains all the views that concern the page model
import os
from wsgiref.util import FileWrapper from wsgiref.util import FileWrapper
from ajax_select import make_ajax_field 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 raise PermissionDenied
name = f.__getattribute__(file_attr).name 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 # check if file exists on disk
if not os.path.exists(filepath.encode("utf-8")): if not filepath.exists():
raise Http404() raise Http404
with open(filepath.encode("utf-8"), "rb") as filename: with open(filepath, "rb") as filename:
wrapper = FileWrapper(filename) wrapper = FileWrapper(filename)
response = HttpResponse(wrapper, content_type=f.mime_type) response = HttpResponse(wrapper, content_type=f.mime_type)
response["Last-Modified"] = http_date(f.date.timestamp()) 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( response["Content-Disposition"] = ('inline; filename="%s"' % f.name).encode(
"utf-8" "utf-8"
) )

View File

@ -13,7 +13,6 @@
# #
# #
import os
from io import BytesIO from io import BytesIO
from django.conf import settings from django.conf import settings
@ -46,9 +45,7 @@ class Picture(SithFile):
@property @property
def is_vertical(self): def is_vertical(self):
with open( with open(settings.MEDIA_ROOT / self.file.name, "rb") as f:
os.path.join(settings.MEDIA_ROOT, self.file.name).encode("utf-8"), "rb"
) as f:
im = Image.open(BytesIO(f.read())) im = Image.open(BytesIO(f.read()))
(w, h) = im.size (w, h) = im.size
return (w / h) < 1 return (w / h) < 1
@ -112,9 +109,7 @@ class Picture(SithFile):
def rotate(self, degree): def rotate(self, degree):
for attr in ["file", "compressed", "thumbnail"]: for attr in ["file", "compressed", "thumbnail"]:
name = self.__getattribute__(attr).name name = self.__getattribute__(attr).name
with open( with open(settings.MEDIA_ROOT / name, "r+b") as file:
os.path.join(settings.MEDIA_ROOT, name).encode("utf-8"), "r+b"
) as file:
if file: if file:
im = Image.open(BytesIO(file.read())) im = Image.open(BytesIO(file.read()))
file.seek(0) file.seek(0)

View File

@ -34,10 +34,9 @@ https://docs.djangoproject.com/en/1.8/ref/settings/
""" """
import binascii import binascii
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
import os import os
import sys import sys
from pathlib import Path
import sentry_sdk import sentry_sdk
from django.utils.translation import gettext_lazy as _ 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 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" os.environ["HTTPS"] = "off"
@ -212,7 +211,7 @@ REST_FRAMEWORK = {}
DATABASES = { DATABASES = {
"default": { "default": {
"ENGINE": "django.db.backends.sqlite3", "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 USE_TZ = True
LOCALE_PATHS = (os.path.join(BASE_DIR, "locale"),) LOCALE_PATHS = [BASE_DIR / "locale"]
PHONENUMBER_DEFAULT_REGION = "FR" PHONENUMBER_DEFAULT_REGION = "FR"
# Medias # Medias
MEDIA_ROOT = "./data/"
MEDIA_URL = "/data/" MEDIA_URL = "/data/"
MEDIA_ROOT = 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 = "./static/" STATIC_ROOT = 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 = [
@ -288,7 +287,6 @@ HONEYPOT_VALUE = "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 = "message2" # Only used on forum
# Email # Email
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
EMAIL_HOST = "localhost" EMAIL_HOST = "localhost"
@ -725,7 +723,6 @@ if SENTRY_DSN:
environment=SENTRY_ENV, environment=SENTRY_ENV,
) )
SITH_FRONT_DEP_VERSIONS = { SITH_FRONT_DEP_VERSIONS = {
"https://github.com/Stuk/jszip-utils": "0.1.0", "https://github.com/Stuk/jszip-utils": "0.1.0",
"https://github.com/Stuk/jszip": "3.10.1", "https://github.com/Stuk/jszip": "3.10.1",