fucked things up with egit gui but now its cool.
39
.dockerignore
Normal file
@ -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
|
||||||
18
.project
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<projectDescription>
|
||||||
|
<name>jacksbastards</name>
|
||||||
|
<comment></comment>
|
||||||
|
<projects>
|
||||||
|
</projects>
|
||||||
|
<buildSpec>
|
||||||
|
<buildCommand>
|
||||||
|
<name>org.python.pydev.PyDevBuilder</name>
|
||||||
|
<arguments>
|
||||||
|
</arguments>
|
||||||
|
</buildCommand>
|
||||||
|
</buildSpec>
|
||||||
|
<natures>
|
||||||
|
<nature>org.python.pydev.pythonNature</nature>
|
||||||
|
<nature>org.python.pydev.django.djangoNature</nature>
|
||||||
|
</natures>
|
||||||
|
</projectDescription>
|
||||||
166
.pydevproject
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<?eclipse-pydev version="1.0"?><pydev_project>
|
||||||
|
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||||
|
<path>/${PROJECT_DIR_NAME}</path>
|
||||||
|
</pydev_pathproperty>
|
||||||
|
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python interpreter</pydev_property>
|
||||||
|
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||||
|
<pydev_variables_property name="org.python.pydev.PROJECT_VARIABLE_SUBSTITUTION">
|
||||||
|
<key>env_var:PS1</key>
|
||||||
|
<value>[📦 $FLATPAK_ID \W]\$ </value>
|
||||||
|
<key>env_var:INVOCATION_ID</key>
|
||||||
|
<value>ec08f22997324076b6f99c10ec5d1f64</value>
|
||||||
|
<key>PY</key>
|
||||||
|
<value>314</value>
|
||||||
|
<key>env_var:SWT_GTK4</key>
|
||||||
|
<value>0</value>
|
||||||
|
<key>env_var:HOSTNAME</key>
|
||||||
|
<value>studio-desktop</value>
|
||||||
|
<key>env_var:LANG</key>
|
||||||
|
<value>en_GB.UTF-8</value>
|
||||||
|
<key>env_var:GDM_LANG</key>
|
||||||
|
<value>en_GB.UTF-8</value>
|
||||||
|
<key>env_var:XDG_CONFIG_DIRS</key>
|
||||||
|
<value>/app/etc/xdg:/etc/xdg</value>
|
||||||
|
<key>env_var:GI_TYPELIB_PATH</key>
|
||||||
|
<value>/app/lib/girepository-1.0</value>
|
||||||
|
<key>env_var:PWD</key>
|
||||||
|
<value>/home/maarten</value>
|
||||||
|
<key>env_var:XDG_MENU_PREFIX</key>
|
||||||
|
<value>gnome-</value>
|
||||||
|
<key>env_var:DBUS_SESSION_BUS_ADDRESS</key>
|
||||||
|
<value>unix:path=/run/flatpak/bus</value>
|
||||||
|
<key>env_var:HOME</key>
|
||||||
|
<value>/home/maarten</value>
|
||||||
|
<key>env_var:WAYLAND_DISPLAY</key>
|
||||||
|
<value>wayland-0</value>
|
||||||
|
<key>env_var:XDG_DATA_DIRS</key>
|
||||||
|
<value>/app/share:/usr/share:/usr/share/runtime/share:/run/host/user-share:/run/host/share</value>
|
||||||
|
<key>env_var:GDK_CORE_DEVICE_EVENTS</key>
|
||||||
|
<value>1</value>
|
||||||
|
<key>env_var:MANAGERPID</key>
|
||||||
|
<value>1277</value>
|
||||||
|
<key>env_var:XDG_DATA_HOME</key>
|
||||||
|
<value>/home/maarten/.var/app/org.eclipse.Java/data</value>
|
||||||
|
<key>env_var:XDG_CURRENT_DESKTOP</key>
|
||||||
|
<value>GNOME</value>
|
||||||
|
<key>env_var:FLATPAK_ID</key>
|
||||||
|
<value>org.eclipse.Java</value>
|
||||||
|
<key>env_var:SYSTEMD_EXEC_PID</key>
|
||||||
|
<value>4524</value>
|
||||||
|
<key>env_var:LOGNAME</key>
|
||||||
|
<value>maarten</value>
|
||||||
|
<key>env_var:PATH</key>
|
||||||
|
<value>/app/bin:/usr/bin</value>
|
||||||
|
<key>env_var:XDG_SESSION_CLASS</key>
|
||||||
|
<value>user</value>
|
||||||
|
<key>env_var:GPG_TTY</key>
|
||||||
|
<value>not a tty</value>
|
||||||
|
<key>env_var:GDK_GL</key>
|
||||||
|
<value>gles</value>
|
||||||
|
<key>env_var:XDG_SESSION_TYPE</key>
|
||||||
|
<value>wayland</value>
|
||||||
|
<key>env_var:LD_LIBRARY_PATH</key>
|
||||||
|
<value/>
|
||||||
|
<key>env_var:ALSA_CONFIG_DIR</key>
|
||||||
|
<value>/usr/share/alsa</value>
|
||||||
|
<key>env_var:KDEDIRS</key>
|
||||||
|
<value>/usr</value>
|
||||||
|
<key>env_var:JOURNAL_STREAM</key>
|
||||||
|
<value>9:35499</value>
|
||||||
|
<key>env_var:QT_IM_MODULES</key>
|
||||||
|
<value>wayland;ibus</value>
|
||||||
|
<key>env_var:XMODIFIERS</key>
|
||||||
|
<value>@im=ibus</value>
|
||||||
|
<key>env_var:DEBUGINFOD_IMA_CERT_PATH</key>
|
||||||
|
<value>/etc/keys/ima:</value>
|
||||||
|
<key>DJANGO_MANAGE_LOCATION</key>
|
||||||
|
<value>manage.py</value>
|
||||||
|
<key>env_var:MAIL</key>
|
||||||
|
<value>/var/spool/mail/maarten</value>
|
||||||
|
<key>env_var:HISTCONTROL</key>
|
||||||
|
<value>ignoredups</value>
|
||||||
|
<key>env_var:LESSOPEN</key>
|
||||||
|
<value>||/usr/bin/lesspipe.sh %s</value>
|
||||||
|
<key>env_var:MEMORY_PRESSURE_WRITE</key>
|
||||||
|
<value>c29tZSAyMDAwMDAgMjAwMDAwMAA=</value>
|
||||||
|
<key>env_var:GDMSESSION</key>
|
||||||
|
<value>gnome</value>
|
||||||
|
<key>env_var:MANAGERPIDFDID</key>
|
||||||
|
<value>1278</value>
|
||||||
|
<key>env_var:GJS_DEBUG_TOPICS</key>
|
||||||
|
<value>JS ERROR;JS LOG</value>
|
||||||
|
<key>env_var:XDG_RUNTIME_DIR</key>
|
||||||
|
<value>/run/user/1000</value>
|
||||||
|
<key>env_var:XDG_STATE_HOME</key>
|
||||||
|
<value>/home/maarten/.var/app/org.eclipse.Java/.local/state</value>
|
||||||
|
<key>env_var:LIBOVERLAY_SCROLLBAR</key>
|
||||||
|
<value>0</value>
|
||||||
|
<key>env_var:container</key>
|
||||||
|
<value>flatpak</value>
|
||||||
|
<key>env_var:SSH_ASKPASS</key>
|
||||||
|
<value>/usr/bin/ksshaskpass</value>
|
||||||
|
<key>env_var:SDL_VIDEO_MINIMIZE_ON_FOCUS_LOSS</key>
|
||||||
|
<value>0</value>
|
||||||
|
<key>env_var:XDG_SESSION_DESKTOP</key>
|
||||||
|
<value>gnome</value>
|
||||||
|
<key>env_var:USER</key>
|
||||||
|
<value>maarten</value>
|
||||||
|
<key>env_var:ALSA_CONFIG_PATH</key>
|
||||||
|
<value>/usr/share/alsa/alsa-flatpak.conf</value>
|
||||||
|
<key>env_var:DEBUGINFOD_URLS</key>
|
||||||
|
<value>ima:enforcing https://debuginfod.fedoraproject.org/ ima:ignore </value>
|
||||||
|
<key>env_var:SSH_AUTH_SOCK</key>
|
||||||
|
<value>/run/flatpak/ssh-auth</value>
|
||||||
|
<key>env_var:EDITOR</key>
|
||||||
|
<value>/usr/bin/nano</value>
|
||||||
|
<key>env_var:OXYGEN_DISABLE_INNER_SHADOWS_HACK</key>
|
||||||
|
<value>1</value>
|
||||||
|
<key>env_var:XAUTHORITY</key>
|
||||||
|
<value>/run/user/1000/.mutter-Xwaylandauth.IT3MH3</value>
|
||||||
|
<key>env_var:MEMORY_PRESSURE_WATCH</key>
|
||||||
|
<value>/sys/fs/cgroup/user.slice/user-1000.slice/user@1000.service/session.slice/org.gnome.Shell@wayland.service/memory.pressure</value>
|
||||||
|
<key>env_var:GIO_LAUNCHED_DESKTOP_FILE_PID</key>
|
||||||
|
<value>21757</value>
|
||||||
|
<key>env_var:GNOME_SETUP_DISPLAY</key>
|
||||||
|
<value>:1</value>
|
||||||
|
<key>env_var:PIPENV_VENV_IN_PROJECT</key>
|
||||||
|
<value>1</value>
|
||||||
|
<key>env_var:GST_PLUGIN_SYSTEM_PATH</key>
|
||||||
|
<value>/app/lib/gstreamer-1.0:/usr/lib/extensions/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/codecs-extra/lib/gstreamer-1.0:/usr/lib/x86_64-linux-gnu/gstreamer-1.0</value>
|
||||||
|
<key>env_var:XDG_CACHE_HOME</key>
|
||||||
|
<value>/home/maarten/.var/app/org.eclipse.Java/cache</value>
|
||||||
|
<key>env_var:PYTHONUSERBASE</key>
|
||||||
|
<value>/var/data/python</value>
|
||||||
|
<key>env_var:USERNAME</key>
|
||||||
|
<value>maarten</value>
|
||||||
|
<key>env_var:QT_IM_MODULE</key>
|
||||||
|
<value>ibus</value>
|
||||||
|
<key>env_var:SHELL</key>
|
||||||
|
<value>/bin/sh</value>
|
||||||
|
<key>env_var:SHLVL</key>
|
||||||
|
<value>0</value>
|
||||||
|
<key>env_var:FLATPAK_SANDBOX_DIR</key>
|
||||||
|
<value>/home/maarten/.var/app/org.eclipse.Java/sandbox</value>
|
||||||
|
<key>DJANGO_SETTINGS_MODULE</key>
|
||||||
|
<value>jacksbastards.settings</value>
|
||||||
|
<key>env_var:STEAM_FRAME_FORCE_CLOSE</key>
|
||||||
|
<value>1</value>
|
||||||
|
<key>env_var:AT_SPI_BUS_ADDRESS</key>
|
||||||
|
<value>unix:path=/run/flatpak/at-spi-bus</value>
|
||||||
|
<key>env_var:HISTSIZE</key>
|
||||||
|
<value>1000</value>
|
||||||
|
<key>env_var:GJS_DEBUG_OUTPUT</key>
|
||||||
|
<value>stderr</value>
|
||||||
|
<key>env_var:GIO_LAUNCHED_DESKTOP_FILE</key>
|
||||||
|
<value>/var/lib/flatpak/exports/share/applications/org.eclipse.Java.desktop</value>
|
||||||
|
<key>env_var:XDG_CONFIG_HOME</key>
|
||||||
|
<value>/home/maarten/.var/app/org.eclipse.Java/config</value>
|
||||||
|
<key>env_var:__EGL_EXTERNAL_PLATFORM_CONFIG_DIRS</key>
|
||||||
|
<value>/etc/egl/egl_external_platform.d:/usr/lib/x86_64-linux-gnu/GL/egl/egl_external_platform.d:/usr/share/egl/egl_external_platform.d</value>
|
||||||
|
<key>env_var:MOZ_GMP_PATH</key>
|
||||||
|
<value>/usr/lib64/mozilla/plugins/gmp-gmpopenh264/system-installed</value>
|
||||||
|
<key>env_var:DESKTOP_SESSION</key>
|
||||||
|
<value>gnome</value>
|
||||||
|
</pydev_variables_property>
|
||||||
|
</pydev_project>
|
||||||
60
Dockerfile
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
# Use an official Python runtime based on Debian 12 "bookworm" as a parent image.
|
||||||
|
FROM python:3.12-slim-bookworm
|
||||||
|
|
||||||
|
# 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 \
|
||||||
|
libmariadb-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 jacksbastards.wsgi:application
|
||||||
0
base/__init__.py
Normal file
BIN
base/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
base/__pycache__/admin.cpython-314.pyc
Normal file
BIN
base/__pycache__/apps.cpython-314.pyc
Normal file
BIN
base/__pycache__/models.cpython-314.pyc
Normal file
3
base/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
base/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BaseConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'base'
|
||||||
26
base/migrations/0001_initial.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 20:27
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='NavigationSettings',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('linkedin_url', models.URLField(blank=True, verbose_name='LinkedIn URL')),
|
||||||
|
('github_url', models.URLField(blank=True, verbose_name='GitHub URL')),
|
||||||
|
('mastodon_url', models.URLField(blank=True, verbose_name='Mastodon URL')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,45 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 21:53
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('base', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='navigationsettings',
|
||||||
|
name='github_url',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='navigationsettings',
|
||||||
|
name='linkedin_url',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='navigationsettings',
|
||||||
|
name='mastodon_url',
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='navigationsettings',
|
||||||
|
name='facebook_url',
|
||||||
|
field=models.URLField(blank=True, verbose_name='Facebook URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='navigationsettings',
|
||||||
|
name='gitea_url',
|
||||||
|
field=models.URLField(blank=True, verbose_name='Gitea URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='navigationsettings',
|
||||||
|
name='instagram_url',
|
||||||
|
field=models.URLField(blank=True, verbose_name='Instagram URL'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='navigationsettings',
|
||||||
|
name='youtube_url',
|
||||||
|
field=models.URLField(blank=True, verbose_name='Youtube URL'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
base/migrations/__init__.py
Normal file
BIN
base/migrations/__pycache__/0001_initial.cpython-314.pyc
Normal file
BIN
base/migrations/__pycache__/__init__.cpython-314.pyc
Normal file
29
base/models.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from django.db import models
|
||||||
|
from wagtail.admin.panels import (
|
||||||
|
FieldPanel,
|
||||||
|
MultiFieldPanel,
|
||||||
|
)
|
||||||
|
from wagtail.contrib.settings.models import (
|
||||||
|
BaseGenericSetting,
|
||||||
|
register_setting,
|
||||||
|
)
|
||||||
|
|
||||||
|
@register_setting
|
||||||
|
class NavigationSettings(BaseGenericSetting):
|
||||||
|
youtube_url = models.URLField(verbose_name="Youtube URL", blank=True)
|
||||||
|
facebook_url = models.URLField(verbose_name="Facebook URL", blank=True)
|
||||||
|
instagram_url = models.URLField(verbose_name="Instagram URL", blank=True)
|
||||||
|
gitea_url = models.URLField(verbose_name="Gitea URL", blank=True)
|
||||||
|
|
||||||
|
|
||||||
|
panels = [
|
||||||
|
MultiFieldPanel(
|
||||||
|
[
|
||||||
|
FieldPanel("youtube_url"),
|
||||||
|
FieldPanel("facebook_url"),
|
||||||
|
FieldPanel("instagram_url"),
|
||||||
|
FieldPanel("gitea_url"),
|
||||||
|
],
|
||||||
|
"Social settings",
|
||||||
|
)
|
||||||
|
]
|
||||||
3
base/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
base/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
0
blog/__init__.py
Normal file
BIN
blog/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
blog/__pycache__/admin.cpython-314.pyc
Normal file
BIN
blog/__pycache__/apps.cpython-314.pyc
Normal file
BIN
blog/__pycache__/models.cpython-314.pyc
Normal file
3
blog/admin.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
blog/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class BlogConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'blog'
|
||||||
28
blog/migrations/0001_initial.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 16:42
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import wagtail.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('wagtailcore', '0096_referenceindex_referenceindex_source_object_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BlogIndexPage',
|
||||||
|
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')),
|
||||||
|
('intro', wagtail.fields.RichTextField(blank=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('wagtailcore.page',),
|
||||||
|
),
|
||||||
|
]
|
||||||
29
blog/migrations/0002_blogpage.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 16:59
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import wagtail.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0001_initial'),
|
||||||
|
('wagtailcore', '0096_referenceindex_referenceindex_source_object_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BlogPage',
|
||||||
|
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')),
|
||||||
|
('date', models.DateField(verbose_name='Post date')),
|
||||||
|
('intro', models.CharField(max_length=250)),
|
||||||
|
('body', wagtail.fields.RichTextField(blank=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('wagtailcore.page',),
|
||||||
|
),
|
||||||
|
]
|
||||||
30
blog/migrations/0003_blogpagegalleryimage.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 17:37
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import modelcluster.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0002_blogpage'),
|
||||||
|
('wagtailimages', '0027_image_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BlogPageGalleryImage',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('sort_order', models.IntegerField(blank=True, editable=False, null=True)),
|
||||||
|
('caption', models.CharField(blank=True, max_length=250)),
|
||||||
|
('image', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='+', to='wagtailimages.image')),
|
||||||
|
('page', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='gallery_images', to='blog.blogpage')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'ordering': ['sort_order'],
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
26
blog/migrations/0004_author.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 18:54
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0003_blogpagegalleryimage'),
|
||||||
|
('wagtailimages', '0027_image_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Author',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('name', models.CharField(max_length=255)),
|
||||||
|
('author_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name_plural': 'Authors',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
19
blog/migrations/0005_blogpage_authors.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 19:04
|
||||||
|
|
||||||
|
import modelcluster.fields
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0004_author'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='blogpage',
|
||||||
|
name='authors',
|
||||||
|
field=modelcluster.fields.ParentalManyToManyField(blank=True, to='blog.author'),
|
||||||
|
),
|
||||||
|
]
|
||||||
33
blog/migrations/0006_blogpagetag_blogpage_tags.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 19:26
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import modelcluster.contrib.taggit
|
||||||
|
import modelcluster.fields
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0005_blogpage_authors'),
|
||||||
|
('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BlogPageTag',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('content_object', modelcluster.fields.ParentalKey(on_delete=django.db.models.deletion.CASCADE, related_name='tagged_items', to='blog.blogpage')),
|
||||||
|
('tag', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='%(app_label)s_%(class)s_items', to='taggit.tag')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='blogpage',
|
||||||
|
name='tags',
|
||||||
|
field=modelcluster.contrib.taggit.ClusterTaggableManager(blank=True, help_text='A comma-separated list of tags.', through='blog.BlogPageTag', to='taggit.Tag', verbose_name='Tags'),
|
||||||
|
),
|
||||||
|
]
|
||||||
25
blog/migrations/0007_blogtagindexpage.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 19:36
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('blog', '0006_blogpagetag_blogpage_tags'),
|
||||||
|
('wagtailcore', '0096_referenceindex_referenceindex_source_object_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='BlogTagIndexPage',
|
||||||
|
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')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'abstract': False,
|
||||||
|
},
|
||||||
|
bases=('wagtailcore.page',),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
blog/migrations/__init__.py
Normal file
BIN
blog/migrations/__pycache__/0001_initial.cpython-314.pyc
Normal file
BIN
blog/migrations/__pycache__/0002_blogpage.cpython-314.pyc
Normal file
BIN
blog/migrations/__pycache__/0004_author.cpython-314.pyc
Normal file
BIN
blog/migrations/__pycache__/__init__.cpython-314.pyc
Normal file
86
blog/models.py
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
from django import forms
|
||||||
|
from django.db import models
|
||||||
|
from wagtail.models import Page, Orderable
|
||||||
|
from wagtail.fields import RichTextField
|
||||||
|
from wagtail.snippets.models import register_snippet
|
||||||
|
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
|
||||||
|
from modelcluster.fields import ParentalKey, ParentalManyToManyField
|
||||||
|
from modelcluster.contrib.taggit import ClusterTaggableManager
|
||||||
|
from taggit.models import TaggedItemBase
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class BlogIndexPage(Page):
|
||||||
|
intro = RichTextField(blank=True)
|
||||||
|
# add the get_context method:
|
||||||
|
def get_context(self, request):
|
||||||
|
# Update context to include only published posts, ordered by reverse-chron
|
||||||
|
context = super().get_context(request)
|
||||||
|
blogpages = self.get_children().live().order_by('-first_published_at')
|
||||||
|
context['blogpages'] = blogpages
|
||||||
|
return context
|
||||||
|
|
||||||
|
class BlogPageTag(TaggedItemBase):
|
||||||
|
content_object = ParentalKey(
|
||||||
|
'BlogPage',
|
||||||
|
related_name='tagged_items',
|
||||||
|
on_delete=models.CASCADE
|
||||||
|
)
|
||||||
|
|
||||||
|
class BlogPage(Page):
|
||||||
|
date = models.DateField("Post date")
|
||||||
|
intro = models.CharField(max_length=250)
|
||||||
|
body = RichTextField(blank=True)
|
||||||
|
tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
|
||||||
|
|
||||||
|
def main_image(self):
|
||||||
|
gallery_item = self.gallery_images.first()
|
||||||
|
if gallery_item:
|
||||||
|
return gallery_item.image
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
authors = ParentalManyToManyField('blog.Author', blank=True)
|
||||||
|
|
||||||
|
content_panels = Page.content_panels + [
|
||||||
|
MultiFieldPanel(["date", FieldPanel("authors", widget=forms.CheckboxSelectMultiple), "tags",
|
||||||
|
], heading="Blog information"),
|
||||||
|
"intro", "body", "gallery_images"
|
||||||
|
]
|
||||||
|
class BlogPageGalleryImage(Orderable):
|
||||||
|
page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='gallery_images')
|
||||||
|
image = models.ForeignKey(
|
||||||
|
'wagtailimages.Image', on_delete=models.CASCADE, related_name='+'
|
||||||
|
)
|
||||||
|
caption = models.CharField(blank=True, max_length=250)
|
||||||
|
|
||||||
|
panels = ["image", "caption"]
|
||||||
|
|
||||||
|
class BlogTagIndexPage(Page):
|
||||||
|
|
||||||
|
def get_context(self, request):
|
||||||
|
|
||||||
|
# Filter by tag
|
||||||
|
tag = request.GET.get('tag')
|
||||||
|
blogpages = BlogPage.objects.filter(tags__name=tag)
|
||||||
|
|
||||||
|
# Update template context
|
||||||
|
context = super().get_context(request)
|
||||||
|
context['blogpages'] = blogpages
|
||||||
|
return context
|
||||||
|
|
||||||
|
@register_snippet
|
||||||
|
class Author(models.Model):
|
||||||
|
name = models.CharField(max_length=255)
|
||||||
|
author_image = models.ForeignKey(
|
||||||
|
'wagtailimages.Image', null=True, blank=True,
|
||||||
|
on_delete=models.SET_NULL, related_name='+'
|
||||||
|
)
|
||||||
|
|
||||||
|
panels = ["name", "author_image"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name_plural = 'Authors'
|
||||||
28
blog/templates/blog/blog_index_page.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% load wagtailcore_tags %}
|
||||||
|
|
||||||
|
<!-- Load wagtailimages_tags: -->
|
||||||
|
{% load wagtailcore_tags wagtailimages_tags %}
|
||||||
|
|
||||||
|
{% block body_class %}template-blogindexpage{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ page.title }}</h1>
|
||||||
|
|
||||||
|
<div class="intro">{{ page.intro|richtext }}</div>
|
||||||
|
|
||||||
|
{% for post in blogpages %}
|
||||||
|
{% with post=post.specific %}
|
||||||
|
<h2><a href="{% pageurl post %}">{{ post.title }}</a></h2>
|
||||||
|
|
||||||
|
{% with post.main_image as main_image %}
|
||||||
|
{% if main_image %}{% image main_image fill-160x100 %}{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<p>{{ post.intro }}</p>
|
||||||
|
{{ post.body|richtext }}
|
||||||
|
{% endwith %}
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
47
blog/templates/blog/blog_page.html
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
{% load wagtailcore_tags wagtailimages_tags %}
|
||||||
|
|
||||||
|
{% block body_class %}template-blogpage{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<h1>{{ page.title }}</h1>
|
||||||
|
<p class="meta">{{ page.date }}</p>
|
||||||
|
|
||||||
|
{% with authors=page.authors.all %}
|
||||||
|
{% if authors %}
|
||||||
|
<h3>Posted by:</h3>
|
||||||
|
<ul>
|
||||||
|
{% for author in authors %}
|
||||||
|
<li style="display: inline">
|
||||||
|
{% image author.author_image fill-40x60 style="vertical-align: middle" %}
|
||||||
|
{{ author.name }}
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
<div class="intro">{{ page.intro }}</div>
|
||||||
|
|
||||||
|
{{ page.body|richtext }}
|
||||||
|
|
||||||
|
{% for item in page.gallery_images.all %}
|
||||||
|
<div style="float: inline-start; margin: 10px">
|
||||||
|
{% image item.image fill-320x240 %}
|
||||||
|
<p>{{ item.caption }}</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
<p><a href="{{ page.get_parent.url }}">Return to blog</a></p>
|
||||||
|
|
||||||
|
{% with tags=page.tags.all %}
|
||||||
|
{% if tags %}
|
||||||
|
<div class="tags">
|
||||||
|
<h3>Tags</h3>
|
||||||
|
{% for tag in tags %}
|
||||||
|
<a href="{% slugurl 'tags' %}?tag={{ tag }}"><button type="button">{{ tag }}</button></a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
21
blog/templates/blog/blog_tag_index_page.html
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load wagtailcore_tags %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% if request.GET.tag %}
|
||||||
|
<h4>Showing pages tagged "{{ request.GET.tag }}"</h4>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% for blogpage in blogpages %}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong><a href="{% pageurl blogpage %}">{{ blogpage.title }}</a></strong><br />
|
||||||
|
<small>Revised: {{ blogpage.latest_revision_created_at }}</small><br />
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{% empty %}
|
||||||
|
No pages found with that tag.
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
3
blog/tests.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
blog/views.py
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
BIN
db.sqlite3
Normal file
0
home/__init__.py
Normal file
BIN
home/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
home/__pycache__/apps.cpython-314.pyc
Normal file
BIN
home/__pycache__/models.cpython-314.pyc
Normal file
6
home/apps.py
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class HomeConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "home"
|
||||||
31
home/migrations/0001_initial.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
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",),
|
||||||
|
),
|
||||||
|
]
|
||||||
66
home/migrations/0002_create_homepage.py
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
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 (of type Page) as created by wagtailcore.0002_initial_data,
|
||||||
|
# if it exists
|
||||||
|
page_content_type = ContentType.objects.get(
|
||||||
|
model="page", app_label="wagtailcore"
|
||||||
|
)
|
||||||
|
Page.objects.filter(
|
||||||
|
content_type=page_content_type, slug="home", depth=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),
|
||||||
|
]
|
||||||
19
home/migrations/0003_homepage_body.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 16:25
|
||||||
|
|
||||||
|
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),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.2.9 on 2025-12-19 20:13
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('home', '0003_homepage_body'),
|
||||||
|
('wagtailcore', '0096_referenceindex_referenceindex_source_object_and_more'),
|
||||||
|
('wagtailimages', '0027_image_description'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='homepage',
|
||||||
|
name='hero_cta',
|
||||||
|
field=models.CharField(blank=True, help_text='Text to display on Call to Action', max_length=255, verbose_name='Hero CTA'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='homepage',
|
||||||
|
name='hero_cta_link',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Choose a page to link to for the Call to Action', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailcore.page', verbose_name='Hero CTA link'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='homepage',
|
||||||
|
name='hero_text',
|
||||||
|
field=models.CharField(blank=True, help_text='Write an introduction for the site', max_length=255),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='homepage',
|
||||||
|
name='image',
|
||||||
|
field=models.ForeignKey(blank=True, help_text='Homepage image', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'),
|
||||||
|
),
|
||||||
|
]
|
||||||
0
home/migrations/__init__.py
Normal file
BIN
home/migrations/__pycache__/0001_initial.cpython-314.pyc
Normal file
BIN
home/migrations/__pycache__/0002_create_homepage.cpython-314.pyc
Normal file
BIN
home/migrations/__pycache__/0003_homepage_body.cpython-314.pyc
Normal file
BIN
home/migrations/__pycache__/__init__.cpython-314.pyc
Normal file
50
home/models.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from django.db import models
|
||||||
|
from wagtail.models import Page
|
||||||
|
from wagtail.fields import RichTextField
|
||||||
|
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
|
||||||
|
|
||||||
|
|
||||||
|
class HomePage(Page):
|
||||||
|
# add the Hero section of HomePage:
|
||||||
|
image = models.ForeignKey(
|
||||||
|
"wagtailimages.Image",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
help_text="Homepage image",
|
||||||
|
)
|
||||||
|
hero_text = models.CharField(
|
||||||
|
blank=True,
|
||||||
|
max_length=255, help_text="Write an introduction for the site"
|
||||||
|
)
|
||||||
|
hero_cta = models.CharField(
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Hero CTA",
|
||||||
|
max_length=255,
|
||||||
|
help_text="Text to display on Call to Action",
|
||||||
|
)
|
||||||
|
hero_cta_link = models.ForeignKey(
|
||||||
|
"wagtailcore.Page",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name="+",
|
||||||
|
verbose_name="Hero CTA link",
|
||||||
|
help_text="Choose a page to link to for the Call to Action",
|
||||||
|
)
|
||||||
|
|
||||||
|
body = RichTextField(blank=True)
|
||||||
|
|
||||||
|
content_panels = Page.content_panels + [
|
||||||
|
MultiFieldPanel(
|
||||||
|
[
|
||||||
|
FieldPanel("image"),
|
||||||
|
FieldPanel("hero_text"),
|
||||||
|
FieldPanel("hero_cta"),
|
||||||
|
FieldPanel("hero_cta_link"),
|
||||||
|
],
|
||||||
|
heading="Hero section",
|
||||||
|
),
|
||||||
|
FieldPanel('body'),
|
||||||
|
]
|
||||||
184
home/static/css/welcome_page.css
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
19
home/templates/home/home_page.html
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load wagtailcore_tags wagtailimages_tags %}
|
||||||
|
|
||||||
|
{% block body_class %}template-homepage{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div>
|
||||||
|
<h1>{{ page.title }}</h1>
|
||||||
|
{% image page.image fill-480x320 %}
|
||||||
|
<p>{{ page.hero_text }}</p>
|
||||||
|
{% if page.hero_cta_link %}
|
||||||
|
<a href="{% pageurl page.hero_cta_link %}">
|
||||||
|
{% firstof page.hero_cta page.hero_cta_link.title %}
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ page.body|richtext }}
|
||||||
|
{% endblock content %}
|
||||||
52
home/templates/home/welcome_page.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% load i18n wagtailcore_tags %}
|
||||||
|
|
||||||
|
<header class="header">
|
||||||
|
<div class="logo">
|
||||||
|
<a href="https://wagtail.org/">
|
||||||
|
<svg class="figure-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 342.5 126.2"><title>{% trans "Visit the Wagtail website" %}</title><path fill="#FFF" d="M84 1.9v5.7s-10.2-3.8-16.8 3.1c-4.8 5-5.2 10.6-3 18.1 21.6 0 25 12.1 25 12.1L87 27l6.8-8.3c0-9.8-8.1-16.3-9.8-16.8z"/><circle cx="85.9" cy="15.9" r="2.6"/><path d="M89.2 40.9s-3.3-16.6-24.9-12.1c-2.2-7.5-1.8-13 3-18.1C73.8 3.8 84 7.6 84 7.6V1.9C80.4.3 77 0 73.2 0 59.3 0 51.6 10.4 48.3 17.4L9.2 89.3l11-2.1-20.2 39 14.1-2.5L24.9 93c30.6 0 69.8-11 64.3-52.1z"/><path d="M102.4 27l-8.6-8.3L87 27z"/><path fill="#FFF" d="M30 84.1s1-.2 2.8-.6c1.8-.4 4.3-1 7.3-1.8 1.5-.4 3.1-.9 4.8-1.5 1.7-.6 3.5-1.2 5.2-2 1.8-.7 3.6-1.6 5.4-2.6 1.8-1 3.5-2.1 5.1-3.4.4-.3.8-.6 1.2-1l1.2-1c.7-.7 1.5-1.4 2.2-2.2.7-.7 1.3-1.5 1.9-2.3l.9-1.2.4-.6.4-.6c.2-.4.5-.8.7-1.2.2-.4.4-.8.7-1.2l.3-.6.3-.6c.2-.4.4-.8.5-1.2l.9-2.4c.2-.8.5-1.6.7-2.3.2-.7.3-1.5.5-2.1.1-.7.2-1.3.3-2 .1-.6.2-1.2.2-1.7.1-.5.1-1 .2-1.5.1-1.8.1-2.8.1-2.8l1.6.1s-.1 1.1-.2 2.9c-.1.5-.1 1-.2 1.5-.1.6-.1 1.2-.3 1.8-.1.6-.3 1.3-.4 2-.2.7-.4 1.4-.6 2.2-.2.8-.5 1.5-.8 2.4-.3.8-.6 1.6-1 2.5l-.6 1.2-.3.6-.3.6c-.2.4-.5.8-.7 1.3-.3.4-.5.8-.8 1.2-.1.2-.3.4-.4.6l-.4.6-.9 1.2c-.7.8-1.3 1.6-2.1 2.3-.7.8-1.5 1.4-2.3 2.2l-1.2 1c-.4.3-.8.6-1.3.9-1.7 1.2-3.5 2.3-5.3 3.3-1.8.9-3.7 1.8-5.5 2.5-1.8.7-3.6 1.3-5.3 1.8-1.7.5-3.3 1-4.9 1.3-3 .7-5.6 1.3-7.4 1.6-1.6.6-2.6.8-2.6.8z"/><g fill="#231F20"><path d="M127 83.9h-8.8l-12.6-36.4h7.9l9 27.5 9-27.5h7.9l9 27.5 9-27.5h7.9L153 83.9h-8.8L135.6 59 127 83.9zM200.1 83.9h-7V79c-3 3.6-7 5.4-12.1 5.4-3.8 0-6.9-1.1-9.4-3.2s-3.7-5-3.7-8.6c0-3.6 1.3-6.3 4-8 2.6-1.8 6.2-2.7 10.7-2.7h9.9v-1.4c0-4.8-2.7-7.3-8.1-7.3-3.4 0-6.9 1.2-10.5 3.7l-3.4-4.8c4.4-3.5 9.4-5.3 15.1-5.3 4.3 0 7.8 1.1 10.5 3.2 2.7 2.2 4.1 5.6 4.1 10.2v23.7zm-7.7-13.6v-3.1h-8.6c-5.5 0-8.3 1.7-8.3 5.2 0 1.8.7 3.1 2.1 4.1 1.4.9 3.3 1.4 5.7 1.4 2.4 0 4.6-.7 6.4-2.1 1.8-1.3 2.7-3.1 2.7-5.5zM241.7 47.5v31.7c0 6.4-1.7 11.3-5.2 14.5-3.5 3.2-8 4.8-13.4 4.8-5.5 0-10.4-1.7-14.8-5.1l3.6-5.8c3.6 2.7 7.1 4 10.8 4 3.6 0 6.5-.9 8.6-2.8 2.1-1.9 3.2-4.9 3.2-9v-4.7c-1.1 2.1-2.8 3.9-4.9 5.1-2.1 1.3-4.5 1.9-7.1 1.9-4.8 0-8.8-1.7-11.9-5.1-3.1-3.4-4.7-7.6-4.7-12.6s1.6-9.2 4.7-12.6c3.1-3.4 7.1-5.1 11.9-5.1 4.8 0 8.7 2 11.7 6v-5.4h7.5zm-28.4 16.8c0 3 .9 5.6 2.8 7.7 1.8 2.2 4.3 3.2 7.5 3.2 3.1 0 5.7-1 7.6-3.1 1.9-2.1 2.9-4.7 2.9-7.8 0-3.1-1-5.8-2.9-7.9-2-2.2-4.5-3.2-7.6-3.2-3.1 0-5.6 1.1-7.4 3.4-2 2.1-2.9 4.7-2.9 7.7zM260.9 53.6v18.5c0 1.7.5 3.1 1.4 4.1.9 1 2.2 1.5 3.8 1.5 1.6 0 3.2-.8 4.7-2.4l3.1 5.4c-2.7 2.4-5.7 3.6-8.9 3.6-3.3 0-6-1.1-8.3-3.4-2.3-2.3-3.5-5.3-3.5-9.1V53.6h-4.6v-6.2h4.6V36.1h7.7v11.4h9.6v6.2h-9.6zM309.5 83.9h-7V79c-3 3.6-7 5.4-12.1 5.4-3.8 0-6.9-1.1-9.4-3.2s-3.7-5-3.7-8.6c0-3.6 1.3-6.3 4-8 2.6-1.8 6.2-2.7 10.7-2.7h9.9v-1.4c0-4.8-2.7-7.3-8.1-7.3-3.4 0-6.9 1.2-10.5 3.7l-3.4-4.8c4.4-3.5 9.4-5.3 15.1-5.3 4.3 0 7.8 1.1 10.5 3.2 2.7 2.2 4.1 5.6 4.1 10.2v23.7zm-7.7-13.6v-3.1h-8.6c-5.5 0-8.3 1.7-8.3 5.2 0 1.8.7 3.1 2.1 4.1 1.4.9 3.3 1.4 5.7 1.4 2.4 0 4.6-.7 6.4-2.1 1.8-1.3 2.7-3.1 2.7-5.5zM319.3 40.2c-1-1-1.4-2.1-1.4-3.4 0-1.3.5-2.5 1.4-3.4 1-1 2.1-1.4 3.4-1.4 1.3 0 2.5.5 3.4 1.4 1 1 1.4 2.1 1.4 3.4 0 1.3-.5 2.5-1.4 3.4s-2.1 1.4-3.4 1.4c-1.3.1-2.4-.4-3.4-1.4zm7.2 43.7h-7.7V47.5h7.7v36.4zM342.5 83.9h-7.7V33.1h7.7v50.8z"/></g></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="header-link">
|
||||||
|
{% comment %}
|
||||||
|
This works for all cases but prerelease versions:
|
||||||
|
{% endcomment %}
|
||||||
|
<a href="{% wagtail_documentation_path %}/releases/{% wagtail_release_notes_path %}">
|
||||||
|
{% trans "View the release notes" %}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main class="main">
|
||||||
|
<div class="figure">
|
||||||
|
<svg class="figure-space" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300" aria-hidden="true">
|
||||||
|
<path class="egg" fill="currentColor" d="M150 250c-42.741 0-75-32.693-75-90s42.913-110 75-110c32.088 0 75 52.693 75 110s-32.258 90-75 90z"/>
|
||||||
|
<ellipse fill="#ddd" cx="150" cy="270" rx="40" ry="7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="main-text">
|
||||||
|
<h1>{% trans "Welcome to your new Wagtail site!" %}</h1>
|
||||||
|
<p>{% trans 'Please feel free to <a href="https://github.com/wagtail/wagtail/wiki/Slack">join our community on Slack</a>, or get started with one of the links below.' %}</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
<footer class="footer" role="contentinfo">
|
||||||
|
<a class="option option-one" href="{% wagtail_documentation_path %}/">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M9 21c0 .5.4 1 1 1h4c.6 0 1-.5 1-1v-1H9v1zm3-19C8.1 2 5 5.1 5 9c0 2.4 1.2 4.5 3 5.7V17c0 .5.4 1 1 1h6c.6 0 1-.5 1-1v-2.3c1.8-1.3 3-3.4 3-5.7 0-3.9-3.1-7-7-7zm2.9 11.1l-.9.6V16h-4v-2.3l-.9-.6C7.8 12.2 7 10.6 7 9c0-2.8 2.2-5 5-5s5 2.2 5 5c0 1.6-.8 3.2-2.1 4.1z"/></svg>
|
||||||
|
<div>
|
||||||
|
<h2>{% trans "Wagtail Documentation" %}</h2>
|
||||||
|
<p>{% trans "Topics, references, & how-tos" %}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="option option-two" href="{% wagtail_documentation_path %}/getting_started/tutorial.html">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
|
||||||
|
<div>
|
||||||
|
<h2>{% trans "Tutorial" %}</h2>
|
||||||
|
<p>{% trans "Build your first Wagtail site" %}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<a class="option option-three" href="{% url 'wagtailadmin_home' %}">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true"><path d="M0 0h24v24H0z" fill="none"/><path d="M16.5 13c-1.2 0-3.07.34-4.5 1-1.43-.67-3.3-1-4.5-1C5.33 13 1 14.08 1 16.25V19h22v-2.75c0-2.17-4.33-3.25-6.5-3.25zm-4 4.5h-10v-1.25c0-.54 2.56-1.75 5-1.75s5 1.21 5 1.75v1.25zm9 0H14v-1.25c0-.46-.2-.86-.52-1.22.88-.3 1.96-.53 3.02-.53 2.44 0 5 1.21 5 1.75v1.25zM7.5 12c1.93 0 3.5-1.57 3.5-3.5S9.43 5 7.5 5 4 6.57 4 8.5 5.57 12 7.5 12zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 5.5c1.93 0 3.5-1.57 3.5-3.5S18.43 5 16.5 5 13 6.57 13 8.5s1.57 3.5 3.5 3.5zm0-5.5c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2z"/></svg>
|
||||||
|
<div>
|
||||||
|
<h2>{% trans "Admin Interface" %}</h2>
|
||||||
|
<p>{% trans "Create your superuser first!" %}</p>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</footer>
|
||||||
42
home/tests.py
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
from home.models import HomePage
|
||||||
|
|
||||||
|
from wagtail.models import Page, Site
|
||||||
|
from wagtail.test.utils import WagtailPageTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class HomeSetUpTests(WagtailPageTestCase):
|
||||||
|
"""
|
||||||
|
Tests for basic page structure setup and HomePage creation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_root_create(self):
|
||||||
|
root_page = Page.objects.get(pk=1)
|
||||||
|
self.assertIsNotNone(root_page)
|
||||||
|
|
||||||
|
def test_homepage_create(self):
|
||||||
|
root_page = Page.objects.get(pk=1)
|
||||||
|
homepage = HomePage(title="Home")
|
||||||
|
root_page.add_child(instance=homepage)
|
||||||
|
self.assertTrue(HomePage.objects.filter(title="Home").exists())
|
||||||
|
|
||||||
|
|
||||||
|
class HomeTests(WagtailPageTestCase):
|
||||||
|
"""
|
||||||
|
Tests for homepage functionality and rendering.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""
|
||||||
|
Create a homepage instance for testing.
|
||||||
|
"""
|
||||||
|
root_page = Page.get_first_root_node()
|
||||||
|
Site.objects.create(hostname="testsite", root_page=root_page, is_default_site=True)
|
||||||
|
self.homepage = HomePage(title="Home")
|
||||||
|
root_page.add_child(instance=self.homepage)
|
||||||
|
|
||||||
|
def test_homepage_is_renderable(self):
|
||||||
|
self.assertPageIsRenderable(self.homepage)
|
||||||
|
|
||||||
|
def test_homepage_template_used(self):
|
||||||
|
response = self.client.get(self.homepage.url)
|
||||||
|
self.assertTemplateUsed(response, "home/home_page.html")
|
||||||
0
jacksbastards/__init__.py
Normal file
BIN
jacksbastards/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
jacksbastards/__pycache__/urls.cpython-314.pyc
Normal file
0
jacksbastards/settings/__init__.py
Normal file
BIN
jacksbastards/settings/__pycache__/__init__.cpython-314.pyc
Normal file
BIN
jacksbastards/settings/__pycache__/base.cpython-314.pyc
Normal file
BIN
jacksbastards/settings/__pycache__/dev.cpython-314.pyc
Normal file
185
jacksbastards/settings/base.py
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
"""
|
||||||
|
Django settings for jacksbastards project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 6.0.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/6.0/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/6.0/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
PROJECT_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
BASE_DIR = PROJECT_DIR.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/6.0/howto/deployment/checklist/
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"home",
|
||||||
|
"blog",
|
||||||
|
"base",
|
||||||
|
"search",
|
||||||
|
"wagtail.contrib.forms",
|
||||||
|
"wagtail.contrib.redirects",
|
||||||
|
"wagtail.contrib.settings",
|
||||||
|
"wagtail.embeds",
|
||||||
|
"wagtail.sites",
|
||||||
|
"wagtail.users",
|
||||||
|
"wagtail.snippets",
|
||||||
|
"wagtail.documents",
|
||||||
|
"wagtail.images",
|
||||||
|
"wagtail.search",
|
||||||
|
"wagtail.admin",
|
||||||
|
"wagtail",
|
||||||
|
"modelcluster",
|
||||||
|
"taggit",
|
||||||
|
"django_filters",
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"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",
|
||||||
|
"wagtail.contrib.redirects.middleware.RedirectMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "jacksbastards.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [
|
||||||
|
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",
|
||||||
|
"wagtail.contrib.settings.context_processors.settings",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "jacksbastards.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/6.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/6.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/6.0/howto/static-files/
|
||||||
|
|
||||||
|
STATICFILES_FINDERS = [
|
||||||
|
"django.contrib.staticfiles.finders.FileSystemFinder",
|
||||||
|
"django.contrib.staticfiles.finders.AppDirectoriesFinder",
|
||||||
|
]
|
||||||
|
|
||||||
|
STATICFILES_DIRS = [
|
||||||
|
PROJECT_DIR / "static",
|
||||||
|
]
|
||||||
|
|
||||||
|
STATIC_ROOT = BASE_DIR / "static"
|
||||||
|
STATIC_URL = "/static/"
|
||||||
|
|
||||||
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
MEDIA_URL = "/media/"
|
||||||
|
|
||||||
|
# Default storage settings
|
||||||
|
# See https://docs.djangoproject.com/en/6.0/ref/settings/#std-setting-STORAGES
|
||||||
|
STORAGES = {
|
||||||
|
"default": {
|
||||||
|
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||||
|
},
|
||||||
|
"staticfiles": {
|
||||||
|
"BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Django sets a maximum of 1000 fields per form by default, but particularly complex page models
|
||||||
|
# can exceed this limit within Wagtail's page editor.
|
||||||
|
DATA_UPLOAD_MAX_NUMBER_FIELDS = 10_000
|
||||||
|
|
||||||
|
|
||||||
|
# Wagtail settings
|
||||||
|
|
||||||
|
WAGTAIL_SITE_NAME = "jacksbastards"
|
||||||
|
|
||||||
|
# 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']
|
||||||
18
jacksbastards/settings/dev.py
Normal file
@ -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-c@#kgredf)k#l1m@ebn&koyuva$f^1^7u_2g4ohc9imf4=ue^f"
|
||||||
|
|
||||||
|
# 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
|
||||||
14
jacksbastards/settings/production.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from .base import *
|
||||||
|
|
||||||
|
DEBUG = False
|
||||||
|
|
||||||
|
# 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/6.0/ref/contrib/staticfiles/#manifeststaticfilesstorage
|
||||||
|
STORAGES["staticfiles"]["BACKEND"] = "django.contrib.staticfiles.storage.ManifestStaticFilesStorage"
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .local import *
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
0
jacksbastards/static/css/jacksbastards.css
Normal file
0
jacksbastards/static/js/jacksbastards.js
Normal file
11
jacksbastards/templates/404.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{% 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 %}
|
||||||
13
jacksbastards/templates/500.html
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<!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>
|
||||||
48
jacksbastards/templates/base.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
{% 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="stylesheet" type="text/css" href="{% static 'css/jacksbastards.css' %}">
|
||||||
|
|
||||||
|
{% block extra_css %}
|
||||||
|
{# Override this in templates to add extra stylesheets #}
|
||||||
|
{% endblock %}
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="{% block body_class %}{% endblock %}">
|
||||||
|
{% wagtailuserbar %}
|
||||||
|
|
||||||
|
{% block content %}{% endblock %}
|
||||||
|
|
||||||
|
{% include "includes/footer.html" %}
|
||||||
|
|
||||||
|
{# Global javascript #}
|
||||||
|
<script type="text/javascript" src="{% static 'js/jacksbastards.js' %}"></script>
|
||||||
|
|
||||||
|
{% block extra_js %}
|
||||||
|
{# Override this in templates to add extra javascript #}
|
||||||
|
{% endblock %}
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
23
jacksbastards/templates/includes/footer.html
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
<footer>
|
||||||
|
<p>Built with Wagtail</p>
|
||||||
|
|
||||||
|
{% with youtube_url=settings.base.NavigationSettings.youtube_url facebook_url=settings.base.NavigationSettings.facebook_url instagram_url=settings.base.NavigationSettings.instagram_url gitea_url=settings.base.NavigationSettings.gitea_url %}
|
||||||
|
{% if youtube_url or facebook_url or instagram_url or gitea_url %}
|
||||||
|
<p>
|
||||||
|
Follow me on:
|
||||||
|
{% if youtube_url %}
|
||||||
|
<a href="{{ youtube_url }}">Youtube</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if facebook_url %}
|
||||||
|
<a href="{{ facebook_url }}">Facebook</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if instagram_url %}
|
||||||
|
<a href="{{ instagram_url }}">Instagram</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if gitea_url %}
|
||||||
|
<a href="{{ gitea_url }}">Gitea</a>
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
{% endwith %}
|
||||||
|
</footer>
|
||||||
35
jacksbastards/urls.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
from django.conf import settings
|
||||||
|
from django.urls import include, path
|
||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
from wagtail.admin import urls as wagtailadmin_urls
|
||||||
|
from wagtail import urls as wagtail_urls
|
||||||
|
from wagtail.documents import urls as wagtaildocs_urls
|
||||||
|
|
||||||
|
from search import views as search_views
|
||||||
|
|
||||||
|
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)),
|
||||||
|
]
|
||||||
16
jacksbastards/wsgi.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for jacksbastards 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/6.0/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jacksbastards.settings.dev")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
22
manage.py
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jacksbastards.settings.dev")
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 9.1 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 116 KiB |
BIN
media/images/43b8e519-7d2d-4d23-9c01-25593dd85e2d.original.jpg
Normal file
|
After Width: | Height: | Size: 115 KiB |
|
After Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 47 KiB |