From 3b8f038dd63f2d89277c637056952e3b8d6285d2 Mon Sep 17 00:00:00 2001
From: "Edgar P. Burkhart" <git@edgarpierre.fr>
Date: Wed, 12 Jun 2024 15:29:33 +0200
Subject: [PATCH] Initial commit

---
 .gitignore                                  |   4 +
 .pre-commit-config.yaml                     |  27 +++
 lps/.dockerignore                           |  39 ++++
 lps/Dockerfile                              |  60 ++++++
 lps/home/__init__.py                        |   0
 lps/home/migrations/0001_initial.py         |  30 +++
 lps/home/migrations/0002_create_homepage.py |  60 ++++++
 lps/home/migrations/0003_homepage_body.py   |  18 ++
 lps/home/migrations/__init__.py             |   0
 lps/home/models.py                          |  11 ++
 lps/home/static/css/welcome_page.css        | 185 +++++++++++++++++++
 lps/home/templates/home/home_page.html      |   6 +
 lps/lps/__init__.py                         |   0
 lps/lps/settings/__init__.py                |   0
 lps/lps/settings/base.py                    | 191 ++++++++++++++++++++
 lps/lps/settings/dev.py                     |  18 ++
 lps/lps/settings/production.py              |   8 +
 lps/lps/static/css/lps.css                  |   3 +
 lps/lps/static/js/lps.js                    |   0
 lps/lps/templates/404.html                  |   7 +
 lps/lps/templates/500.html                  |  12 ++
 lps/lps/templates/base.html                 |  43 +++++
 lps/lps/templates/nav.html                  |  15 ++
 lps/lps/urls.py                             |  33 ++++
 lps/lps/wsgi.py                             |  16 ++
 lps/manage.py                               |  10 +
 lps/requirements.txt                        |   2 +
 lps/search/__init__.py                      |   0
 lps/search/templates/search/search.html     |  33 ++++
 lps/search/views.py                         |  45 +++++
 30 files changed, 876 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .pre-commit-config.yaml
 create mode 100644 lps/.dockerignore
 create mode 100644 lps/Dockerfile
 create mode 100644 lps/home/__init__.py
 create mode 100644 lps/home/migrations/0001_initial.py
 create mode 100644 lps/home/migrations/0002_create_homepage.py
 create mode 100644 lps/home/migrations/0003_homepage_body.py
 create mode 100644 lps/home/migrations/__init__.py
 create mode 100644 lps/home/models.py
 create mode 100644 lps/home/static/css/welcome_page.css
 create mode 100644 lps/home/templates/home/home_page.html
 create mode 100644 lps/lps/__init__.py
 create mode 100644 lps/lps/settings/__init__.py
 create mode 100644 lps/lps/settings/base.py
 create mode 100644 lps/lps/settings/dev.py
 create mode 100644 lps/lps/settings/production.py
 create mode 100644 lps/lps/static/css/lps.css
 create mode 100644 lps/lps/static/js/lps.js
 create mode 100644 lps/lps/templates/404.html
 create mode 100644 lps/lps/templates/500.html
 create mode 100644 lps/lps/templates/base.html
 create mode 100644 lps/lps/templates/nav.html
 create mode 100644 lps/lps/urls.py
 create mode 100644 lps/lps/wsgi.py
 create mode 100644 lps/manage.py
 create mode 100644 lps/requirements.txt
 create mode 100644 lps/search/__init__.py
 create mode 100644 lps/search/templates/search/search.html
 create mode 100644 lps/search/views.py

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..98c5b4b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+/env
+/node_modules
+__pycache__
+/lps/db.sqlite3
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..6a62a6e
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,27 @@
+repos:
+  - repo: https://github.com/PyCQA/isort
+    rev: 5.12.0
+    hooks:
+      - id: isort
+        args: ["--profile", "black"]
+  - repo: https://github.com/psf/black
+    rev: 23.3.0
+    hooks:
+      - id: black
+  - repo: https://github.com/PyCQA/flake8
+    rev: "6.0.0"
+    hooks:
+      - id: flake8
+        args: ["--max-line-length=88", "--extend-ignore=E203"]
+        exclude: "lps/lps/settings/"
+  - repo: https://github.com/Riverside-Healthcare/djLint
+    rev: v1.23.3
+    hooks:
+      - id: djlint-django
+        args: ["--reformat"]
+  - repo: https://github.com/pre-commit/mirrors-prettier
+    rev: "v3.0.0-alpha.6"
+    hooks:
+      - id: prettier
+        types_or: ["css", "javascript", "svg"]
+        exclude: "css"
diff --git a/lps/.dockerignore b/lps/.dockerignore
new file mode 100644
index 0000000..63ce98d
--- /dev/null
+++ b/lps/.dockerignore
@@ -0,0 +1,39 @@
+# Django project
+/media/
+/static/
+*.sqlite3
+
+# Python and others
+__pycache__
+*.pyc
+.DS_Store
+*.swp
+/venv/
+/tmp/
+/.vagrant/
+/Vagrantfile.local
+node_modules/
+/npm-debug.log
+/.idea/
+.vscode
+coverage
+.python-version
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+*.egg-info/
+.installed.cfg
+*.egg
diff --git a/lps/Dockerfile b/lps/Dockerfile
new file mode 100644
index 0000000..d7dda82
--- /dev/null
+++ b/lps/Dockerfile
@@ -0,0 +1,60 @@
+# Use an official Python runtime based on Debian 10 "buster" as a parent image.
+FROM python:3.8.1-slim-buster
+
+# Add user that will be used in the container.
+RUN useradd wagtail
+
+# Port used by this container to serve HTTP.
+EXPOSE 8000
+
+# Set environment variables.
+# 1. Force Python stdout and stderr streams to be unbuffered.
+# 2. Set PORT variable that is used by Gunicorn. This should match "EXPOSE"
+#    command.
+ENV PYTHONUNBUFFERED=1 \
+    PORT=8000
+
+# Install system packages required by Wagtail and Django.
+RUN apt-get update --yes --quiet && apt-get install --yes --quiet --no-install-recommends \
+    build-essential \
+    libpq-dev \
+    libmariadbclient-dev \
+    libjpeg62-turbo-dev \
+    zlib1g-dev \
+    libwebp-dev \
+ && rm -rf /var/lib/apt/lists/*
+
+# Install the application server.
+RUN pip install "gunicorn==20.0.4"
+
+# Install the project requirements.
+COPY requirements.txt /
+RUN pip install -r /requirements.txt
+
+# Use /app folder as a directory where the source code is stored.
+WORKDIR /app
+
+# Set this directory to be owned by the "wagtail" user. This Wagtail project
+# uses SQLite, the folder needs to be owned by the user that
+# will be writing to the database file.
+RUN chown wagtail:wagtail /app
+
+# Copy the source code of the project into the container.
+COPY --chown=wagtail:wagtail . .
+
+# Use user "wagtail" to run the build commands below and the server itself.
+USER wagtail
+
+# Collect static files.
+RUN python manage.py collectstatic --noinput --clear
+
+# Runtime command that executes when "docker run" is called, it does the
+# following:
+#   1. Migrate the database.
+#   2. Start the application server.
+# WARNING:
+#   Migrating database at the same time as starting the server IS NOT THE BEST
+#   PRACTICE. The database should be migrated manually or using the release
+#   phase facilities of your hosting platform. This is used only so the
+#   Wagtail instance can be started with a simple "docker run" command.
+CMD set -xe; python manage.py migrate --noinput; gunicorn lps.wsgi:application
diff --git a/lps/home/__init__.py b/lps/home/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lps/home/migrations/0001_initial.py b/lps/home/migrations/0001_initial.py
new file mode 100644
index 0000000..27146c6
--- /dev/null
+++ b/lps/home/migrations/0001_initial.py
@@ -0,0 +1,30 @@
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("wagtailcore", "0040_page_draft_title"),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name="HomePage",
+            fields=[
+                (
+                    "page_ptr",
+                    models.OneToOneField(
+                        on_delete=models.CASCADE,
+                        parent_link=True,
+                        auto_created=True,
+                        primary_key=True,
+                        serialize=False,
+                        to="wagtailcore.Page",
+                    ),
+                ),
+            ],
+            options={
+                "abstract": False,
+            },
+            bases=("wagtailcore.page",),
+        ),
+    ]
diff --git a/lps/home/migrations/0002_create_homepage.py b/lps/home/migrations/0002_create_homepage.py
new file mode 100644
index 0000000..a8586fb
--- /dev/null
+++ b/lps/home/migrations/0002_create_homepage.py
@@ -0,0 +1,60 @@
+from django.db import migrations
+
+
+def create_homepage(apps, schema_editor):
+    # Get models
+    ContentType = apps.get_model("contenttypes.ContentType")
+    Page = apps.get_model("wagtailcore.Page")
+    Site = apps.get_model("wagtailcore.Site")
+    HomePage = apps.get_model("home.HomePage")
+
+    # Delete the default homepage
+    # If migration is run multiple times, it may have already been deleted
+    Page.objects.filter(id=2).delete()
+
+    # Create content type for homepage model
+    homepage_content_type, __ = ContentType.objects.get_or_create(
+        model="homepage", app_label="home"
+    )
+
+    # Create a new homepage
+    homepage = HomePage.objects.create(
+        title="Home",
+        draft_title="Home",
+        slug="home",
+        content_type=homepage_content_type,
+        path="00010001",
+        depth=2,
+        numchild=0,
+        url_path="/home/",
+    )
+
+    # Create a site with the new homepage set as the root
+    Site.objects.create(hostname="localhost", root_page=homepage, is_default_site=True)
+
+
+def remove_homepage(apps, schema_editor):
+    # Get models
+    ContentType = apps.get_model("contenttypes.ContentType")
+    HomePage = apps.get_model("home.HomePage")
+
+    # Delete the default homepage
+    # Page and Site objects CASCADE
+    HomePage.objects.filter(slug="home", depth=2).delete()
+
+    # Delete content type for homepage model
+    ContentType.objects.filter(model="homepage", app_label="home").delete()
+
+
+class Migration(migrations.Migration):
+    run_before = [
+        ("wagtailcore", "0053_locale_model"),
+    ]
+
+    dependencies = [
+        ("home", "0001_initial"),
+    ]
+
+    operations = [
+        migrations.RunPython(create_homepage, remove_homepage),
+    ]
diff --git a/lps/home/migrations/0003_homepage_body.py b/lps/home/migrations/0003_homepage_body.py
new file mode 100644
index 0000000..9785208
--- /dev/null
+++ b/lps/home/migrations/0003_homepage_body.py
@@ -0,0 +1,18 @@
+# Generated by Django 5.0.6 on 2024-06-11 15:57
+
+import wagtail.fields
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+    dependencies = [
+        ("home", "0002_create_homepage"),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name="homepage",
+            name="body",
+            field=wagtail.fields.RichTextField(blank=True),
+        ),
+    ]
diff --git a/lps/home/migrations/__init__.py b/lps/home/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lps/home/models.py b/lps/home/models.py
new file mode 100644
index 0000000..fe0adbe
--- /dev/null
+++ b/lps/home/models.py
@@ -0,0 +1,11 @@
+from wagtail.admin.panels import FieldPanel
+from wagtail.fields import RichTextField
+from wagtail.models import Page
+
+
+class HomePage(Page):
+    body = RichTextField(blank=True)
+
+    content_panels = Page.content_panels + [
+        FieldPanel("body"),
+    ]
diff --git a/lps/home/static/css/welcome_page.css b/lps/home/static/css/welcome_page.css
new file mode 100644
index 0000000..7eafb8f
--- /dev/null
+++ b/lps/home/static/css/welcome_page.css
@@ -0,0 +1,185 @@
+html {
+  box-sizing: border-box;
+}
+
+*,
+*:before,
+*:after {
+  box-sizing: inherit;
+}
+
+body {
+  max-width: 960px;
+  min-height: 100vh;
+  margin: 0 auto;
+  padding: 0 15px;
+  color: #231f20;
+  font-family: "Helvetica Neue", "Segoe UI", Arial, sans-serif;
+  line-height: 1.25;
+}
+
+a {
+  background-color: transparent;
+  color: #308282;
+  text-decoration: underline;
+}
+
+a:hover {
+  color: #ea1b10;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+p,
+ul {
+  padding: 0;
+  margin: 0;
+  font-weight: 400;
+}
+
+svg:not(:root) {
+  overflow: hidden;
+}
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-top: 20px;
+  padding-bottom: 10px;
+  border-bottom: 1px solid #e6e6e6;
+}
+
+.logo {
+  width: 150px;
+  margin-inline-end: 20px;
+}
+
+.logo a {
+  display: block;
+}
+
+.figure-logo {
+  max-width: 150px;
+  max-height: 55.1px;
+}
+
+.release-notes {
+  font-size: 14px;
+}
+
+.main {
+  padding: 40px 0;
+  margin: 0 auto;
+  text-align: center;
+}
+
+.figure-space {
+  max-width: 265px;
+}
+
+@keyframes pos {
+  0%,
+  100% {
+    transform: rotate(-6deg);
+  }
+  50% {
+    transform: rotate(6deg);
+  }
+}
+
+.egg {
+  fill: #43b1b0;
+  animation: pos 3s ease infinite;
+  transform: translateY(50px);
+  transform-origin: 50% 80%;
+}
+
+.main-text {
+  max-width: 400px;
+  margin: 5px auto;
+}
+
+.main-text h1 {
+  font-size: 22px;
+}
+
+.main-text p {
+  margin: 15px auto 0;
+}
+
+.footer {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+  border-top: 1px solid #e6e6e6;
+  padding: 10px;
+}
+
+.option {
+  display: block;
+  padding: 10px 10px 10px 34px;
+  position: relative;
+  text-decoration: none;
+}
+
+.option svg {
+  width: 24px;
+  height: 24px;
+  fill: gray;
+  border: 1px solid #d9d9d9;
+  padding: 5px;
+  border-radius: 100%;
+  top: 10px;
+  inset-inline-start: 0;
+  position: absolute;
+}
+
+.option h2 {
+  font-size: 19px;
+  text-decoration: underline;
+}
+
+.option p {
+  padding-top: 3px;
+  color: #231f20;
+  font-size: 15px;
+  font-weight: 300;
+}
+
+@media (max-width: 996px) {
+  body {
+    max-width: 780px;
+  }
+}
+
+@media (max-width: 767px) {
+  .option {
+    flex: 0 0 50%;
+  }
+}
+
+@media (max-width: 599px) {
+  .main {
+    padding: 20px 0;
+  }
+
+  .figure-space {
+    max-width: 200px;
+  }
+
+  .footer {
+    display: block;
+    width: 300px;
+    margin: 0 auto;
+  }
+}
+
+@media (max-width: 360px) {
+  .header-link {
+    max-width: 100px;
+  }
+}
diff --git a/lps/home/templates/home/home_page.html b/lps/home/templates/home/home_page.html
new file mode 100644
index 0000000..539fa7d
--- /dev/null
+++ b/lps/home/templates/home/home_page.html
@@ -0,0 +1,6 @@
+{% extends "base.html" %}
+{% load wagtailcore_tags %}
+{% block body_class %}template-homepage{% endblock %}
+{% block content %}
+    <div class="container mx-auto">{{ page.body|richtext }}</div>
+{% endblock content %}
diff --git a/lps/lps/__init__.py b/lps/lps/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lps/lps/settings/__init__.py b/lps/lps/settings/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lps/lps/settings/base.py b/lps/lps/settings/base.py
new file mode 100644
index 0000000..0811b10
--- /dev/null
+++ b/lps/lps/settings/base.py
@@ -0,0 +1,191 @@
+"""
+Django settings for lps project.
+
+Generated by 'django-admin startproject' using Django 5.0.6.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.0/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/5.0/ref/settings/
+"""
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+import os
+
+PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BASE_DIR = os.path.dirname(PROJECT_DIR)
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
+
+
+# Application definition
+
+INSTALLED_APPS = [
+    "home",
+    "search",
+    "wagtail.contrib.forms",
+    "wagtail.contrib.redirects",
+    "wagtail.embeds",
+    "wagtail.sites",
+    "wagtail.users",
+    "wagtail.snippets",
+    "wagtail.documents",
+    "wagtail.images",
+    "wagtail.search",
+    "wagtail.admin",
+    "wagtail",
+    "modelcluster",
+    "taggit",
+    "django.contrib.admin",
+    "django.contrib.auth",
+    "django.contrib.contenttypes",
+    "django.contrib.sessions",
+    "django.contrib.messages",
+    "django.contrib.staticfiles",
+]
+
+MIDDLEWARE = [
+    "django.contrib.sessions.middleware.SessionMiddleware",
+    "django.middleware.common.CommonMiddleware",
+    "django.middleware.csrf.CsrfViewMiddleware",
+    "django.contrib.auth.middleware.AuthenticationMiddleware",
+    "django.contrib.messages.middleware.MessageMiddleware",
+    "django.middleware.clickjacking.XFrameOptionsMiddleware",
+    "django.middleware.security.SecurityMiddleware",
+    "wagtail.contrib.redirects.middleware.RedirectMiddleware",
+]
+
+ROOT_URLCONF = "lps.urls"
+
+TEMPLATES = [
+    {
+        "BACKEND": "django.template.backends.django.DjangoTemplates",
+        "DIRS": [
+            os.path.join(PROJECT_DIR, "templates"),
+        ],
+        "APP_DIRS": True,
+        "OPTIONS": {
+            "context_processors": [
+                "django.template.context_processors.debug",
+                "django.template.context_processors.request",
+                "django.contrib.auth.context_processors.auth",
+                "django.contrib.messages.context_processors.messages",
+            ],
+        },
+    },
+]
+
+WSGI_APPLICATION = "lps.wsgi.application"
+
+
+# Database
+# https://docs.djangoproject.com/en/5.0/ref/settings/#databases
+
+DATABASES = {
+    "default": {
+        "ENGINE": "django.db.backends.sqlite3",
+        "NAME": os.path.join(BASE_DIR, "db.sqlite3"),
+    }
+}
+
+
+# Password validation
+# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators
+
+AUTH_PASSWORD_VALIDATORS = [
+    {
+        "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
+    },
+    {
+        "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
+    },
+]
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/5.0/topics/i18n/
+
+LANGUAGE_CODE = "en-us"
+
+TIME_ZONE = "UTC"
+
+USE_I18N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/5.0/howto/static-files/
+
+STATICFILES_FINDERS = [
+    "django.contrib.staticfiles.finders.FileSystemFinder",
+    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
+]
+
+STATICFILES_DIRS = [
+    os.path.join(PROJECT_DIR, "static"),
+]
+
+STATIC_ROOT = os.path.join(BASE_DIR, "static")
+STATIC_URL = "/static/"
+
+MEDIA_ROOT = os.path.join(BASE_DIR, "media")
+MEDIA_URL = "/media/"
+
+# Default storage settings, with the staticfiles storage updated.
+# See https://docs.djangoproject.com/en/5.0/ref/settings/#std-setting-STORAGES
+STORAGES = {
+    "default": {
+        "BACKEND": "django.core.files.storage.FileSystemStorage",
+    },
+    # ManifestStaticFilesStorage is recommended in production, to prevent
+    # outdated JavaScript / CSS assets being served from cache
+    # (e.g. after a Wagtail upgrade).
+    # See https://docs.djangoproject.com/en/5.0/ref/contrib/staticfiles/#manifeststaticfilesstorage
+    "staticfiles": {
+        "BACKEND": "django.contrib.staticfiles.storage.ManifestStaticFilesStorage",
+    },
+}
+
+
+# Wagtail settings
+
+WAGTAIL_SITE_NAME = "lps"
+
+# Search
+# https://docs.wagtail.org/en/stable/topics/search/backends.html
+WAGTAILSEARCH_BACKENDS = {
+    "default": {
+        "BACKEND": "wagtail.search.backends.database",
+    }
+}
+
+# Base URL to use when referring to full URLs within the Wagtail admin backend -
+# e.g. in notification emails. Don't include '/admin' or a trailing slash
+WAGTAILADMIN_BASE_URL = "http://example.com"
+
+# Allowed file extensions for documents in the document library.
+# This can be omitted to allow all files, but note that this may present a security risk
+# if untrusted users are allowed to upload files -
+# see https://docs.wagtail.org/en/stable/advanced_topics/deploying.html#user-uploaded-files
+WAGTAILDOCS_EXTENSIONS = [
+    "csv",
+    "docx",
+    "key",
+    "odt",
+    "pdf",
+    "pptx",
+    "rtf",
+    "txt",
+    "xlsx",
+    "zip",
+]
diff --git a/lps/lps/settings/dev.py b/lps/lps/settings/dev.py
new file mode 100644
index 0000000..b2a8952
--- /dev/null
+++ b/lps/lps/settings/dev.py
@@ -0,0 +1,18 @@
+from .base import *
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = "django-insecure-*jrorl4vv88qa--z#=p=of@x%zx^k7*pqhscz_*3-hcw9^yvsf"
+
+# SECURITY WARNING: define the correct hosts in production!
+ALLOWED_HOSTS = ["*"]
+
+EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
+
+
+try:
+    from .local import *
+except ImportError:
+    pass
diff --git a/lps/lps/settings/production.py b/lps/lps/settings/production.py
new file mode 100644
index 0000000..9ca4ed7
--- /dev/null
+++ b/lps/lps/settings/production.py
@@ -0,0 +1,8 @@
+from .base import *
+
+DEBUG = False
+
+try:
+    from .local import *
+except ImportError:
+    pass
diff --git a/lps/lps/static/css/lps.css b/lps/lps/static/css/lps.css
new file mode 100644
index 0000000..65fc1af
--- /dev/null
+++ b/lps/lps/static/css/lps.css
@@ -0,0 +1,3 @@
+:root {
+	--pico-font-family: Inter, sans-serif;
+}
diff --git a/lps/lps/static/js/lps.js b/lps/lps/static/js/lps.js
new file mode 100644
index 0000000..e69de29
diff --git a/lps/lps/templates/404.html b/lps/lps/templates/404.html
new file mode 100644
index 0000000..47ead9a
--- /dev/null
+++ b/lps/lps/templates/404.html
@@ -0,0 +1,7 @@
+{% extends "base.html" %}
+{% block title %}Page not found{% endblock %}
+{% block body_class %}template-404{% endblock %}
+{% block content %}
+    <h1>Page not found</h1>
+    <h2>Sorry, this page could not be found.</h2>
+{% endblock %}
diff --git a/lps/lps/templates/500.html b/lps/lps/templates/500.html
new file mode 100644
index 0000000..725c0df
--- /dev/null
+++ b/lps/lps/templates/500.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+    <head>
+        <meta charset="utf-8" />
+        <title>Internal server error</title>
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+    </head>
+    <body>
+        <h1>Internal server error</h1>
+        <h2>Sorry, there seems to be an error. Please try again soon.</h2>
+    </body>
+</html>
diff --git a/lps/lps/templates/base.html b/lps/lps/templates/base.html
new file mode 100644
index 0000000..64e51a5
--- /dev/null
+++ b/lps/lps/templates/base.html
@@ -0,0 +1,43 @@
+{% load static wagtailcore_tags wagtailuserbar %}
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="utf-8" />
+        <title>
+            {% block title %}
+                {% if page.seo_title %}
+                    {{ page.seo_title }}
+                {% else %}
+                    {{ page.title }}
+                {% endif %}
+            {% endblock %}
+            {% block title_suffix %}
+                {% wagtail_site as current_site %}
+                {% if current_site and current_site.site_name %}- {{ current_site.site_name }}{% endif %}
+            {% endblock %}
+        </title>
+        {% if page.search_description %}<meta name="description" content="{{ page.search_description }}" />{% endif %}
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        {# Force all links in the live preview panel to be opened in a new tab #}
+        {% if request.in_preview_panel %}<base target="_blank">{% endif %}
+        {# Global stylesheets #}
+        <link rel="preconnect" href="https://rsms.me/">
+        <link rel="stylesheet" href="https://rsms.me/inter/inter.css">
+        <link rel="stylesheet"
+              href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.classless.min.css" />
+        <link rel="stylesheet" type="text/css" href="{% static 'css/lps.css' %}">
+        {% block extra_css %}{# Override this in templates to add extra stylesheets #}{% endblock %}
+    </head>
+    <body class="{% block body_class %}{% endblock %}">
+        {% wagtailuserbar %}
+        <header>
+            {% include "nav.html" %}
+        </header>
+        <main>
+            {% block content %}{% endblock %}
+        </main>
+        {# Global javascript #}
+        <script type="text/javascript" src="{% static 'js/lps.js' %}"></script>
+        {% block extra_js %}{# Override this in templates to add extra javascript #}{% endblock %}
+    </body>
+</html>
diff --git a/lps/lps/templates/nav.html b/lps/lps/templates/nav.html
new file mode 100644
index 0000000..b8009c3
--- /dev/null
+++ b/lps/lps/templates/nav.html
@@ -0,0 +1,15 @@
+<nav>
+    <ul>
+        <li>
+            <strong>Lycée du Pays de Soule</strong>
+        </li>
+    </ul>
+    <ul>
+        <li>
+            <a href="#">Blog</a>
+        </li>
+        <li>
+            <a href="#">À propos</a>
+        </li>
+    </ul>
+</nav>
diff --git a/lps/lps/urls.py b/lps/lps/urls.py
new file mode 100644
index 0000000..9b09f89
--- /dev/null
+++ b/lps/lps/urls.py
@@ -0,0 +1,33 @@
+from django.conf import settings
+from django.contrib import admin
+from django.urls import include, path
+from search import views as search_views
+from wagtail import urls as wagtail_urls
+from wagtail.admin import urls as wagtailadmin_urls
+from wagtail.documents import urls as wagtaildocs_urls
+
+urlpatterns = [
+    path("django-admin/", admin.site.urls),
+    path("admin/", include(wagtailadmin_urls)),
+    path("documents/", include(wagtaildocs_urls)),
+    path("search/", search_views.search, name="search"),
+]
+
+
+if settings.DEBUG:
+    from django.conf.urls.static import static
+    from django.contrib.staticfiles.urls import staticfiles_urlpatterns
+
+    # Serve static and media files from development server
+    urlpatterns += staticfiles_urlpatterns()
+    urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
+
+urlpatterns = urlpatterns + [
+    # For anything not caught by a more specific rule above, hand over to
+    # Wagtail's page serving mechanism. This should be the last pattern in
+    # the list:
+    path("", include(wagtail_urls)),
+    # Alternatively, if you want Wagtail pages to be served from a subpath
+    # of your site, rather than the site root:
+    #    path("pages/", include(wagtail_urls)),
+]
diff --git a/lps/lps/wsgi.py b/lps/lps/wsgi.py
new file mode 100644
index 0000000..fe4d683
--- /dev/null
+++ b/lps/lps/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for lps project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lps.settings.dev")
+
+application = get_wsgi_application()
diff --git a/lps/manage.py b/lps/manage.py
new file mode 100644
index 0000000..a83fe87
--- /dev/null
+++ b/lps/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "lps.settings.dev")
+
+    from django.core.management import execute_from_command_line
+
+    execute_from_command_line(sys.argv)
diff --git a/lps/requirements.txt b/lps/requirements.txt
new file mode 100644
index 0000000..54c1cbd
--- /dev/null
+++ b/lps/requirements.txt
@@ -0,0 +1,2 @@
+Django>=4.2,<5.1
+wagtail>=6.1,<6.2
diff --git a/lps/search/__init__.py b/lps/search/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/lps/search/templates/search/search.html b/lps/search/templates/search/search.html
new file mode 100644
index 0000000..dc05d92
--- /dev/null
+++ b/lps/search/templates/search/search.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+{% load static wagtailcore_tags %}
+{% block body_class %}template-searchresults{% endblock %}
+{% block title %}Search{% endblock %}
+{% block content %}
+    <h1>Search</h1>
+    <form action="{% url 'search' %}" method="get">
+        <input type="text"
+               name="query"
+               {% if search_query %}value="{{ search_query }}"{% endif %}>
+        <input type="submit" value="Search" class="button">
+    </form>
+    {% if search_results %}
+        <ul>
+            {% for result in search_results %}
+                <li>
+                    <h4>
+                        <a href="{% pageurl result %}">{{ result }}</a>
+                    </h4>
+                    {% if result.search_description %}{{ result.search_description }}{% endif %}
+                </li>
+            {% endfor %}
+        </ul>
+        {% if search_results.has_previous %}
+            <a href="{% url 'search' %}?query={{ search_query|urlencode }}&amp;page={{ search_results.previous_page_number }}">Previous</a>
+        {% endif %}
+        {% if search_results.has_next %}
+            <a href="{% url 'search' %}?query={{ search_query|urlencode }}&amp;page={{ search_results.next_page_number }}">Next</a>
+        {% endif %}
+    {% elif search_query %}
+        No results found
+    {% endif %}
+{% endblock %}
diff --git a/lps/search/views.py b/lps/search/views.py
new file mode 100644
index 0000000..c8a81d3
--- /dev/null
+++ b/lps/search/views.py
@@ -0,0 +1,45 @@
+from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
+from django.template.response import TemplateResponse
+from wagtail.models import Page
+
+# To enable logging of search queries for use with the "Promoted search results" module
+# <https://docs.wagtail.org/en/stable/reference/contrib/searchpromotions.html>
+# uncomment the following line and the lines indicated in the search function
+# (after adding wagtail.contrib.search_promotions to INSTALLED_APPS):
+
+# from wagtail.contrib.search_promotions.models import Query
+
+
+def search(request):
+    search_query = request.GET.get("query", None)
+    page = request.GET.get("page", 1)
+
+    # Search
+    if search_query:
+        search_results = Page.objects.live().search(search_query)
+
+        # To log this query for use with the "Promoted search results" module:
+
+        # query = Query.get(search_query)
+        # query.add_hit()
+
+    else:
+        search_results = Page.objects.none()
+
+    # Pagination
+    paginator = Paginator(search_results, 10)
+    try:
+        search_results = paginator.page(page)
+    except PageNotAnInteger:
+        search_results = paginator.page(1)
+    except EmptyPage:
+        search_results = paginator.page(paginator.num_pages)
+
+    return TemplateResponse(
+        request,
+        "search/search.html",
+        {
+            "search_query": search_query,
+            "search_results": search_results,
+        },
+    )
