diff --git a/Dockerfile b/Dockerfile index f1263ee..6391932 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ ARG NODE_IMG_TAG=20.5.1 FROM node:${NODE_IMG_TAG}-bookworm-slim as frontend-base COPY . ./app WORKDIR /app/frontend +RUN mkdir -p ./node_modules RUN npm install RUN mkdir -p ./dist/css RUN npm run build @@ -81,6 +82,7 @@ ENV PORT=8000 \ REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt \ CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt # Install non-dev versions of packages (smaller) +RUN apt-get update && apt-get install -y "curl" RUN set -ex \ && apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install \ @@ -90,6 +92,8 @@ RUN set -ex \ "libjpeg62-turbo" \ "zlib1g" \ "libwebp-dev" \ + "nodejs" \ + "npm" \ && rm -rf /var/lib/apt/lists/* # Copy the entrypoint script into the Docker image COPY --chown=wagtail:wagtail container-entrypoint.sh / @@ -100,6 +104,7 @@ COPY --from=build \ # Copy compiled css from frontend stage # In the final stage of your Dockerfile... COPY --from=frontend-base /app/frontend/dist/css /app/frontend/dist/css +COPY --from=frontend-base /app/frontend/node_modules /app/frontend/node_modules # Use /app folder as a directory where the source code is stored. WORKDIR /app # Copy project diff --git a/README.md b/README.md index 9da7d7c..fc06d06 100644 --- a/README.md +++ b/README.md @@ -30,9 +30,11 @@ This project uses Poetry for dependency management. Poetry is a Python tool that Dependencies are specified in the `pyproject.toml`, and the exact versions of the dependencies are locked in the `poetry.lock`. To install the dependencies, run `poetry install`. To add a new dependency, run `poetry add {dependency}`. +Note that you don't need to run `poetry install`, as Docker handles this when building. + ## Using the Makefile -Ensure you have the superuser credentials you want in your .env.dev file prior to running `make build-dev`. +Ensure you have the superuser credentials you want in your .env.dev file prior to running `make build`. The Makefile provides several commands for building and running the project: - `make build`: Builds and starts the Docker images for the development environment. @@ -43,3 +45,24 @@ The Makefile provides several commands for building and running the project: - `make down-prod`: Stops the Docker containers for the production environment. - `make test`: Runs the tests. - `make refresh-db`: Deletes all Docker volumes (use with caution). + +## Building Tailwind + +When you attempt to use a class that isn't currently used anywhere in the project, the live reload will not rebuild the CSS, and thus, these classes won't work until you rebuild the CSS: + +1. Open Docker Desktop +2. In the Containers tab, click on the `web-1` container (within the `website` container) +3. Move to the `Exec` tab within this container +4. Move into the `frontend` folder with `cd frontend` +5. Run `npm run build` to rebuild the CSS + +You'll need to do this every time you use a currently unused class. + +## Wagtail Migrations + +Wagtail migrations should be handled within the Docker container. The process for doing this is largely the same as building Tailwind: + +1. Open Docker Desktop +2. In the Containers tab, click on the `web-1` container (within the `website` container) +3. Move to the `Exec` tab within this container +4. Run `python manage.py makemigrations` and then `python manage.py migrate` (if successful) diff --git a/app/impact_areas/__init__.py b/app/impact_areas/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/impact_areas/admin.py b/app/impact_areas/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/app/impact_areas/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/app/impact_areas/apps.py b/app/impact_areas/apps.py new file mode 100644 index 0000000..5301481 --- /dev/null +++ b/app/impact_areas/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ImpactAreasConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'app.impact_areas' diff --git a/app/impact_areas/migrations/0001_initial.py b/app/impact_areas/migrations/0001_initial.py new file mode 100644 index 0000000..fa7bd1a --- /dev/null +++ b/app/impact_areas/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 4.2.7 on 2024-05-02 21:22 + +from django.db import migrations, models +import django.db.models.deletion +import wagtail.fields + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('wagtailcore', '0089_log_entry_data_json_null_to_object'), + ] + + operations = [ + migrations.CreateModel( + name='ImpactAreasPage', + fields=[ + ('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')), + ('test', wagtail.fields.RichTextField(blank=True)), + ], + options={ + 'abstract': False, + }, + bases=('wagtailcore.page',), + ), + ] diff --git a/app/impact_areas/migrations/0002_rename_test_impactareaspage_intro.py b/app/impact_areas/migrations/0002_rename_test_impactareaspage_intro.py new file mode 100644 index 0000000..9714fad --- /dev/null +++ b/app/impact_areas/migrations/0002_rename_test_impactareaspage_intro.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.7 on 2024-05-02 21:25 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('impact_areas', '0001_initial'), + ] + + operations = [ + migrations.RenameField( + model_name='impactareaspage', + old_name='test', + new_name='intro', + ), + ] diff --git a/app/impact_areas/migrations/0003_impactareaspage_image.py b/app/impact_areas/migrations/0003_impactareaspage_image.py new file mode 100644 index 0000000..59ace8e --- /dev/null +++ b/app/impact_areas/migrations/0003_impactareaspage_image.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.7 on 2024-05-06 20:58 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('wagtailimages', '0025_alter_image_file_alter_rendition_file'), + ('impact_areas', '0002_rename_test_impactareaspage_intro'), + ] + + operations = [ + migrations.AddField( + model_name='impactareaspage', + name='image', + field=models.ForeignKey(blank=True, help_text='Header image', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'), + ), + ] diff --git a/app/impact_areas/migrations/0004_impactareaspage_impact_area_blocks.py b/app/impact_areas/migrations/0004_impactareaspage_impact_area_blocks.py new file mode 100644 index 0000000..28667f3 --- /dev/null +++ b/app/impact_areas/migrations/0004_impactareaspage_impact_area_blocks.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2024-05-06 23:47 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('impact_areas', '0003_impactareaspage_image'), + ] + + operations = [ + migrations.AddField( + model_name='impactareaspage', + name='impact_area_blocks', + field=wagtail.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.blocks.CharBlock(form_classname='title')), ('description', wagtail.blocks.RichTextBlock()), ('link', wagtail.blocks.URLBlock())], blank=True, use_json_field=True), + ), + ] diff --git a/app/impact_areas/migrations/0005_alter_impactareaspage_impact_area_blocks.py b/app/impact_areas/migrations/0005_alter_impactareaspage_impact_area_blocks.py new file mode 100644 index 0000000..b975942 --- /dev/null +++ b/app/impact_areas/migrations/0005_alter_impactareaspage_impact_area_blocks.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2024-05-06 23:48 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('impact_areas', '0004_impactareaspage_impact_area_blocks'), + ] + + operations = [ + migrations.AlterField( + model_name='impactareaspage', + name='impact_area_blocks', + field=wagtail.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.blocks.CharBlock(form_classname='title')), ('description', wagtail.blocks.RichTextBlock()), ('link', wagtail.blocks.URLBlock())], null=True, use_json_field=True), + ), + ] diff --git a/app/impact_areas/migrations/0006_alter_impactareaspage_impact_area_blocks.py b/app/impact_areas/migrations/0006_alter_impactareaspage_impact_area_blocks.py new file mode 100644 index 0000000..acc6292 --- /dev/null +++ b/app/impact_areas/migrations/0006_alter_impactareaspage_impact_area_blocks.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2024-05-06 23:50 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('impact_areas', '0005_alter_impactareaspage_impact_area_blocks'), + ] + + operations = [ + migrations.AlterField( + model_name='impactareaspage', + name='impact_area_blocks', + field=wagtail.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.blocks.CharBlock(form_classname='title')), ('description', wagtail.blocks.CharBlock()), ('link', wagtail.blocks.URLBlock())], null=True, use_json_field=True), + ), + ] diff --git a/app/impact_areas/migrations/0007_alter_impactareaspage_impact_area_blocks.py b/app/impact_areas/migrations/0007_alter_impactareaspage_impact_area_blocks.py new file mode 100644 index 0000000..960bd8e --- /dev/null +++ b/app/impact_areas/migrations/0007_alter_impactareaspage_impact_area_blocks.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2024-05-06 23:54 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('impact_areas', '0006_alter_impactareaspage_impact_area_blocks'), + ] + + operations = [ + migrations.AlterField( + model_name='impactareaspage', + name='impact_area_blocks', + field=wagtail.fields.StreamField([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.blocks.CharBlock()), ('description', wagtail.blocks.RichTextBlock()), ('link', wagtail.blocks.URLBlock())], null=True, use_json_field=True), + ), + ] diff --git a/app/impact_areas/migrations/0008_alter_impactareaspage_impact_area_blocks.py b/app/impact_areas/migrations/0008_alter_impactareaspage_impact_area_blocks.py new file mode 100644 index 0000000..34fe8c5 --- /dev/null +++ b/app/impact_areas/migrations/0008_alter_impactareaspage_impact_area_blocks.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2024-05-06 23:58 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('impact_areas', '0007_alter_impactareaspage_impact_area_blocks'), + ] + + operations = [ + migrations.AlterField( + model_name='impactareaspage', + name='impact_area_blocks', + field=wagtail.fields.StreamField([('impact_area_block', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.blocks.CharBlock()), ('description', wagtail.blocks.RichTextBlock()), ('link', wagtail.blocks.URLBlock())]))], null=True, use_json_field=True), + ), + ] diff --git a/app/impact_areas/migrations/0009_alter_impactareaspage_impact_area_blocks.py b/app/impact_areas/migrations/0009_alter_impactareaspage_impact_area_blocks.py new file mode 100644 index 0000000..9527fca --- /dev/null +++ b/app/impact_areas/migrations/0009_alter_impactareaspage_impact_area_blocks.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2024-05-07 16:12 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('impact_areas', '0008_alter_impactareaspage_impact_area_blocks'), + ] + + operations = [ + migrations.AlterField( + model_name='impactareaspage', + name='impact_area_blocks', + field=wagtail.fields.StreamField([('impact_area_block', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.blocks.CharBlock()), ('description', wagtail.blocks.RichTextBlock()), ('link', wagtail.blocks.URLBlock(null=True))]))], null=True, use_json_field=True), + ), + ] diff --git a/app/impact_areas/migrations/0010_alter_impactareaspage_impact_area_blocks.py b/app/impact_areas/migrations/0010_alter_impactareaspage_impact_area_blocks.py new file mode 100644 index 0000000..0333000 --- /dev/null +++ b/app/impact_areas/migrations/0010_alter_impactareaspage_impact_area_blocks.py @@ -0,0 +1,21 @@ +# Generated by Django 4.2.7 on 2024-05-07 16:13 + +from django.db import migrations +import wagtail.blocks +import wagtail.fields +import wagtail.images.blocks + + +class Migration(migrations.Migration): + + dependencies = [ + ('impact_areas', '0009_alter_impactareaspage_impact_area_blocks'), + ] + + operations = [ + migrations.AlterField( + model_name='impactareaspage', + name='impact_area_blocks', + field=wagtail.fields.StreamField([('impact_area_block', wagtail.blocks.StructBlock([('image', wagtail.images.blocks.ImageChooserBlock()), ('title', wagtail.blocks.CharBlock()), ('description', wagtail.blocks.RichTextBlock()), ('link', wagtail.blocks.URLBlock(required=False))]))], null=True, use_json_field=True), + ), + ] diff --git a/app/impact_areas/migrations/__init__.py b/app/impact_areas/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/impact_areas/models.py b/app/impact_areas/models.py new file mode 100644 index 0000000..0aa0a50 --- /dev/null +++ b/app/impact_areas/models.py @@ -0,0 +1,41 @@ +from django.db import models + +from wagtail.models import Page +from wagtail.fields import RichTextField, StreamField +from wagtail.blocks import StreamBlock, CharBlock, URLBlock, RichTextBlock, StructBlock +from wagtail.images.blocks import ImageChooserBlock +from wagtail.admin.panels import FieldPanel, MultiFieldPanel + + +class IndividualBlock(StructBlock): + image = ImageChooserBlock() + title = CharBlock() + description = RichTextBlock() + link = URLBlock(required=False) + + +class ImpactAreaBlock(StreamBlock): + impact_area_block = IndividualBlock() + + +class ImpactAreasPage(Page): + intro = RichTextField(blank=True) + + image = models.ForeignKey( + "wagtailimages.Image", + null=True, + blank=True, + on_delete=models.SET_NULL, + related_name="+", + help_text="Header image" + ) + + impact_area_blocks = StreamField(ImpactAreaBlock(), use_json_field=True, null=True) + + content_panels = Page.content_panels + [ + MultiFieldPanel([ + FieldPanel('image'), + FieldPanel('intro') + ], heading="Header section"), + FieldPanel('impact_area_blocks') + ] diff --git a/app/impact_areas/templates/impact_areas/impact_areas_page.html b/app/impact_areas/templates/impact_areas/impact_areas_page.html new file mode 100644 index 0000000..c5867f9 --- /dev/null +++ b/app/impact_areas/templates/impact_areas/impact_areas_page.html @@ -0,0 +1,20 @@ +{% extends "base.html" %} +{% load static %} +{% load wagtailcore_tags %} +{% load wagtailimages_tags %} +{% load compress %} +{% block body_class %}template-impactareaspage{% endblock %} +{% block extra_css %} + {% compress css %} + {% endcompress css %} +{% endblock extra_css %} + +{% block content %} + {% include "ui/components/BasePageHeader.html" with title=page.title intro=page.intro image=page.image %} + +
with some text in it) +- image: A Wagtail image +{% endcomment %} + +{% load wagtailimages_tags %} + +{% image image original as image_p %} +