diff --git a/.gitignore b/.gitignore index 7487dec..63eb1b0 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ wheels/ media/ deployment/secret.yaml *.json +static/ diff --git a/Dockerfile b/Dockerfile index 89fa732..21e6ece 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,6 @@ RUN uv sync --frozen \ && chgrp -R 0 /app \ && chmod -R g=u /app \ && chmod g+w /app/config/caddy/Caddyfile \ - && SECRET_KEY= python -m hub collectstatic --noinput + && SECRET_KEY=dummy python -m hub build_assets --force CMD ["/usr/local/bin/runhub.sh"] \ No newline at end of file diff --git a/hub/services/management/commands/build_assets.py b/hub/services/management/commands/build_assets.py new file mode 100644 index 0000000..e46813a --- /dev/null +++ b/hub/services/management/commands/build_assets.py @@ -0,0 +1,32 @@ +from django.core.management.base import BaseCommand +from django.core.management import call_command + + +class Command(BaseCommand): + help = "Build and compress static assets for production" + + def add_arguments(self, parser): + parser.add_argument( + "--force", + action="store_true", + help="Force compression even if files exist", + ) + + def handle(self, *args, **options): + self.stdout.write("Building static assets...") + + # Compress CSS and JS files + self.stdout.write("Compressing CSS and JavaScript...") + call_command( + "compress", + force=options.get("force", False), + verbosity=options.get("verbosity", 1), + ) + + # Collect all static files + self.stdout.write("Collecting static files...") + call_command( + "collectstatic", interactive=False, verbosity=options.get("verbosity", 1) + ) + + self.stdout.write(self.style.SUCCESS("Successfully built static assets")) diff --git a/hub/services/templates/services/offering_detail.html b/hub/services/templates/services/offering_detail.html index 8ede07d..9db60d7 100644 --- a/hub/services/templates/services/offering_detail.html +++ b/hub/services/templates/services/offering_detail.html @@ -1,12 +1,36 @@ {% extends 'base.html' %} {% load static %} +{% load compress %} {% load contact_tags %} {% load json_ld_tags %} {% block title %}Managed {{ offering.service.name }} on {{ offering.cloud_provider.name }}{% endblock %} {% block extra_js %} - +{% if debug %} + + +{% else %} + +{% compress js %} + + + + + + + + +{% endcompress %} +{% endif %} {% json_ld_structured_data %} diff --git a/hub/settings.py b/hub/settings.py index 850e03f..ab542cb 100644 --- a/hub/settings.py +++ b/hub/settings.py @@ -76,6 +76,7 @@ INSTALLED_APPS = [ "django.contrib.staticfiles", "django.contrib.sitemaps", # 3rd party + "compressor", "django_prose_editor", "rest_framework", "schema_viewer", @@ -186,6 +187,25 @@ USE_TZ = True STATIC_URL = "static/" STATIC_ROOT = env.path("STATIC_ROOT", default=BASE_DIR / "static") +# Static files configuration +STATICFILES_FINDERS = [ + "django.contrib.staticfiles.finders.FileSystemFinder", + "django.contrib.staticfiles.finders.AppDirectoriesFinder", + "compressor.finders.CompressorFinder", +] + +# Django Compressor settings +COMPRESS_ENABLED = True +COMPRESS_OFFLINE = True # Compress during build, not runtime +COMPRESS_CSS_FILTERS = [ + "compressor.filters.css_default.CssAbsoluteFilter", + "compressor.filters.cssmin.rCSSMinFilter", +] +COMPRESS_JS_FILTERS = [ + "compressor.filters.jsmin.rJSMinFilter", +] +COMPRESS_OUTPUT_DIR = "CACHE" + # Default primary key field type # https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field diff --git a/pyproject.toml b/pyproject.toml index 3a6f525..9e0c702 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,6 +7,7 @@ requires-python = ">=3.13" dependencies = [ "django>=5.2", "django-admin-sortable2>=2.2.4", + "django-compressor>=4.5.1", "django-import-export>=4.3.7", "django-jazzmin>=3.0.1", "django-nested-admin>=4.1.1", diff --git a/uv.lock b/uv.lock index 5c335f2..c4e3800 100644 --- a/uv.lock +++ b/uv.lock @@ -81,6 +81,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/66/c3/e804b1f04546c1060e566f35177c346590820a95bfb981d1f6360b419437/django_admin_sortable2-2.2.4-py3-none-any.whl", hash = "sha256:406c5b6d6e84ad982cc6e53c3f34b5db5f0a3f34891126af90c9fb2c372f53d5", size = 90816, upload-time = "2024-11-15T09:43:13.665Z" }, ] +[[package]] +name = "django-appconf" +version = "1.1.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/61/a9/dcf95ff3fa0620b6818fc02276fbbb8926e7f286039b6d015e56e8b7af39/django-appconf-1.1.0.tar.gz", hash = "sha256:9fcead372f82a0f21ee189434e7ae9c007cbb29af1118c18251720f3d06243e4", size = 15986, upload-time = "2025-02-13T16:09:40.258Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/62/9e/f3a899991e4aaae4b69c1aa187ba4a32e34742475c91eb13010ee7fbe9db/django_appconf-1.1.0-py3-none-any.whl", hash = "sha256:7abd5a163ff57557f216e84d3ce9dac36c37ffce1ab9a044d3d53b7c943dd10f", size = 6389, upload-time = "2025-02-13T16:09:39.133Z" }, +] + [[package]] name = "django-browser-reload" version = "1.17.0" @@ -103,6 +115,21 @@ 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, upload-time = "2023-12-04T17:19:44.355Z" }, ] +[[package]] +name = "django-compressor" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "django" }, + { name = "django-appconf" }, + { name = "rcssmin" }, + { name = "rjsmin" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/15/30/a9994277ae05082ba5df22c5678a87082253a034927c8d9915c3bf3b8c36/django_compressor-4.5.1.tar.gz", hash = "sha256:c1d8a48a2ee4d8b7f23c411eb9c97e2d88db18a18ba1c9e8178d5f5b8366a822", size = 124734, upload-time = "2024-07-22T09:56:47.554Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/00/d9/ac374a1f7a432230cdf4d2ffbe957fd0d4d5d6426bf4d5c17f382b0801c4/django_compressor-4.5.1-py2.py3-none-any.whl", hash = "sha256:87741edee4e7f24f3e0b8072d94a990cfb010cb2ca7cc443944da8e193cdea65", size = 145465, upload-time = "2024-07-22T09:56:45.822Z" }, +] + [[package]] name = "django-import-export" version = "4.3.7" @@ -351,6 +378,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload-time = "2024-08-06T20:33:04.33Z" }, ] +[[package]] +name = "rcssmin" +version = "1.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/26/f38d49c21d933e3e4320ed31c6025c381dbd973e9936edd0af52ce521534/rcssmin-1.1.2.tar.gz", hash = "sha256:bc75eb75bd6d345c0c51fd80fc487ddd6f9fd409dd7861b3fe98dee85018e1e9", size = 582213, upload-time = "2023-10-03T19:57:48.536Z" } + +[[package]] +name = "rjsmin" +version = "1.2.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f0/1c/c0355e8b8b8aca9c0d43519d2a7c473940deae0297ff8544eff359d7f715/rjsmin-1.2.2.tar.gz", hash = "sha256:8c1bcd821143fecf23242012b55e13610840a839cd467b358f16359010d62dae", size = 420634, upload-time = "2023-10-05T07:19:30.857Z" } + [[package]] name = "servala-fe" version = "0.1.0" @@ -358,6 +397,7 @@ source = { virtual = "." } dependencies = [ { name = "django" }, { name = "django-admin-sortable2" }, + { name = "django-compressor" }, { name = "django-import-export" }, { name = "django-jazzmin" }, { name = "django-nested-admin" }, @@ -381,6 +421,7 @@ requires-dist = [ { name = "django", specifier = ">=5.2" }, { name = "django-admin-sortable2", specifier = ">=2.2.4" }, { name = "django-browser-reload", marker = "extra == 'dev'", specifier = "~=1.13" }, + { name = "django-compressor", specifier = ">=4.5.1" }, { name = "django-import-export", specifier = ">=4.3.7" }, { name = "django-jazzmin", specifier = ">=3.0.1" }, { name = "django-nested-admin", specifier = ">=4.1.1" },