Compare commits
No commits in common. "master" and "test-menu" have entirely different histories.
84 changed files with 2362 additions and 11542 deletions
|
|
@ -1,29 +1,3 @@
|
|||
.git
|
||||
.gitignore
|
||||
.dockerignore
|
||||
.env
|
||||
*.pyc
|
||||
__pycache__
|
||||
*.sqlite3
|
||||
*.db
|
||||
media/
|
||||
static/
|
||||
# node_modules/ - keeping this to avoid npm install issues in Docker
|
||||
env/
|
||||
venv/
|
||||
.venv/
|
||||
*.log
|
||||
.DS_Store
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
upgrade-logs/
|
||||
publichealth-dev.sqlite3
|
||||
CLAUDE.md
|
||||
README.md
|
||||
docs/
|
||||
*.backup
|
||||
requirements-*.txt
|
||||
fix_migrations.py
|
||||
media
|
||||
|
|
|
|||
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -1,12 +1,12 @@
|
|||
.env
|
||||
env
|
||||
/env/
|
||||
/venv/
|
||||
*.pyc
|
||||
.sass-cache
|
||||
.DS_Store
|
||||
*.swp
|
||||
*.sqlite3
|
||||
/docker-compose.yml
|
||||
/env/
|
||||
/venv/
|
||||
/static/
|
||||
/assets/
|
||||
/media/
|
||||
|
|
@ -14,9 +14,7 @@ env
|
|||
/.keys/
|
||||
/.vagrant/
|
||||
/Vagrantfile.local
|
||||
/docker-compose.yml
|
||||
/docs/_build/
|
||||
/node_modules/
|
||||
/publichealth/static/libs
|
||||
publichealth.home.json
|
||||
.history
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
# Change Log
|
||||
|
||||
Please see [Pull Request history](https://github.com/datalets/public-health-ch/pulls?q=is%3Apr+is%3Aclosed) on GitHub.
|
||||
|
||||
## 2.0.0 - 2022-07-12
|
||||
|
||||
- 10694: Introduce table block to show program details
|
||||
- 10695: Add a header image to the article index template
|
||||
|
|
|
|||
152
Dockerfile
152
Dockerfile
|
|
@ -1,152 +0,0 @@
|
|||
FROM docker.io/python:3.12-slim AS base
|
||||
|
||||
ENV \
|
||||
LANG=C.UTF-8 \
|
||||
LC_ALL=C.UTF-8 \
|
||||
# python:
|
||||
PYTHONFAULTHANDLER=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
PYTHONHASHSEED=random \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
libmagic1 \
|
||||
libpq5 \
|
||||
libjpeg62-turbo \
|
||||
libfreetype6 \
|
||||
zlib1g \
|
||||
gettext \
|
||||
procps \
|
||||
curl \
|
||||
postgresql-client \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV VIRTUAL_ENV=/venv
|
||||
|
||||
# Use production configuration.
|
||||
ENV DJANGO_SETTINGS_MODULE=publichealth.settings.production
|
||||
ENV DJANGO_CONFIGURATION=production
|
||||
|
||||
|
||||
|
||||
## Build frontend
|
||||
|
||||
FROM docker.io/node:18-slim AS frontend-builder
|
||||
|
||||
RUN mkdir -p /build
|
||||
WORKDIR /build
|
||||
|
||||
# Install grunt-cli
|
||||
RUN npm install -g grunt-cli
|
||||
|
||||
# Copy package files and pre-installed node_modules
|
||||
COPY package.json package-lock.json Gruntfile.js ./
|
||||
# Copy node_modules if they exist locally (avoids git clone issues)
|
||||
COPY node_modules ./node_modules
|
||||
|
||||
# Copy static assets and libs
|
||||
COPY publichealth/static/ ./publichealth/static/
|
||||
RUN mkdir -p publichealth/static/libs && \
|
||||
cp -r node_modules/@bower_components/* publichealth/static/libs/
|
||||
|
||||
# Build frontend assets (skip grunt if it fails, assets will be compiled at runtime)
|
||||
RUN grunt || echo "Grunt build skipped, will use django-compressor at runtime"
|
||||
|
||||
|
||||
|
||||
## Build backend
|
||||
|
||||
FROM base AS backend-builder
|
||||
|
||||
ENV \
|
||||
# pip:
|
||||
PIP_NO_CACHE_DIR=off \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=on \
|
||||
PIP_DEFAULT_TIMEOUT=100
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
gcc \
|
||||
libmagic-dev \
|
||||
libpq-dev \
|
||||
libjpeg-dev \
|
||||
libfreetype6-dev \
|
||||
zlib1g-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install python build tools.
|
||||
RUN pip install --upgrade pip wheel
|
||||
RUN python3 -m venv $VIRTUAL_ENV
|
||||
|
||||
RUN mkdir -p /build
|
||||
WORKDIR /build
|
||||
|
||||
# Install python dependencies.
|
||||
COPY requirements.txt .
|
||||
RUN . /venv/bin/activate && pip install -r requirements.txt
|
||||
# We will run dumb-init as pid 1.
|
||||
RUN . /venv/bin/activate && pip install dumb-init
|
||||
# We will run our app using gunicorn.
|
||||
RUN . /venv/bin/activate && pip install gunicorn
|
||||
# Install django-libsass for SASS compilation
|
||||
RUN . /venv/bin/activate && pip install django-libsass
|
||||
|
||||
# Copy our own code into to the build image.
|
||||
# WARNING: use .dockerignore file to exclude unwanted files
|
||||
COPY . /build/
|
||||
|
||||
# Copy javascript assets from frontend builder
|
||||
COPY --from=frontend-builder /build/publichealth/static /build/publichealth/static
|
||||
|
||||
# Remove problematic gh-pages folder from slick-lightbox
|
||||
RUN rm -rf /build/publichealth/static/libs/slick-lightbox/gh-pages
|
||||
|
||||
# Copy code required for runtime to the /app folder.
|
||||
RUN mkdir /app
|
||||
|
||||
# Collect static data into /app/static.
|
||||
RUN . /venv/bin/activate && \
|
||||
SECRET_KEY=dummy \
|
||||
STATIC_DIR=/app/static ./manage.py collectstatic --no-input
|
||||
|
||||
# Compress static files offline (required for COMPRESS_OFFLINE=True in production).
|
||||
RUN . /venv/bin/activate && \
|
||||
SECRET_KEY=dummy \
|
||||
STATIC_DIR=/app/static \
|
||||
./manage.py compress --force
|
||||
|
||||
# Compile messages if locale directory exists
|
||||
RUN if [ -d "locale" ]; then \
|
||||
. /venv/bin/activate && \
|
||||
SECRET_KEY=dummy \
|
||||
./manage.py compilemessages; \
|
||||
fi
|
||||
|
||||
# Copy application code to /app
|
||||
RUN cp -r publichealth feedler manage.py /app/
|
||||
RUN if [ -d "locale" ]; then cp -r locale /app/; fi
|
||||
|
||||
RUN ls -al /app/
|
||||
|
||||
|
||||
|
||||
### runtime image
|
||||
|
||||
FROM base AS runtime
|
||||
|
||||
COPY --from=backend-builder /venv /venv
|
||||
COPY --from=backend-builder /app /app
|
||||
|
||||
RUN groupadd --gid 1001 app \
|
||||
&& useradd --home-dir /app --shell /bin/bash --gid app --uid 1001 app \
|
||||
&& chown -R app:app /app
|
||||
USER app
|
||||
WORKDIR /app
|
||||
|
||||
# Ensure exectutables from virtualenv are prefered.
|
||||
ENV PATH="/venv/bin:$PATH"
|
||||
|
||||
ADD entrypoint /entrypoint
|
||||
ENTRYPOINT ["/entrypoint"]
|
||||
7
Pipfile
7
Pipfile
|
|
@ -10,10 +10,10 @@ tox = "*"
|
|||
django-debug-toolbar = "*"
|
||||
|
||||
[packages]
|
||||
"psycopg2-binary" = "*"
|
||||
"psycopg2-binary" = "<2.9"
|
||||
ConcurrentLogHandler = "*"
|
||||
dj-database-url = "*"
|
||||
django = "<4"
|
||||
django = "<2.3"
|
||||
django-anymail = "*"
|
||||
django-colorful = "*"
|
||||
django-crispy-forms = "*"
|
||||
|
|
@ -30,7 +30,6 @@ Pillow = ">=4.0.0"
|
|||
puput = ">=1.0.0"
|
||||
python-dotenv = "*"
|
||||
stellar = "*"
|
||||
wagtail = ">=2.0,<2.16"
|
||||
wagtail = ">=2.0,<2.13"
|
||||
whitenoise = "*"
|
||||
django-toolbelt = "*"
|
||||
unidecode = "*"
|
||||
|
|
|
|||
760
Pipfile.lock
generated
760
Pipfile.lock
generated
File diff suppressed because it is too large
Load diff
|
|
@ -1,138 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
# Deployment script for publichealth on Minikube
|
||||
# Usage: ./deploy-minikube.sh
|
||||
|
||||
set -e
|
||||
|
||||
echo "🚀 Starting deployment to Minikube..."
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Configuration
|
||||
IMAGE_NAME="publichealth"
|
||||
IMAGE_TAG="latest"
|
||||
NAMESPACE="ph"
|
||||
|
||||
# Function to check if command exists
|
||||
command_exists() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
# Check prerequisites
|
||||
echo "📋 Checking prerequisites..."
|
||||
if ! command_exists minikube; then
|
||||
echo -e "${RED}❌ Minikube is not installed${NC}"
|
||||
echo "Install it from: https://minikube.sigs.k8s.io/docs/start/"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command_exists kubectl; then
|
||||
echo -e "${RED}❌ kubectl is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command_exists docker; then
|
||||
echo -e "${RED}❌ Docker is not installed${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Start Minikube if not running
|
||||
if ! minikube status | grep -q "Running"; then
|
||||
echo "🔧 Starting Minikube..."
|
||||
# Check if running as root and use appropriate driver
|
||||
if [ "$EUID" -eq 0 ]; then
|
||||
echo "Running as root, using none driver..."
|
||||
minikube start --driver=none --cpus=2 --memory=4096
|
||||
else
|
||||
minikube start --driver=docker --cpus=2 --memory=4096
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}✓ Minikube is running${NC}"
|
||||
fi
|
||||
|
||||
# Configure Docker to use Minikube's Docker daemon
|
||||
echo "🔧 Configuring Docker to use Minikube's daemon..."
|
||||
eval $(minikube docker-env)
|
||||
|
||||
# Build the Docker image
|
||||
echo "🏗️ Building Docker image..."
|
||||
docker build -t ${IMAGE_NAME}:${IMAGE_TAG} .
|
||||
echo -e "${GREEN}✓ Docker image built successfully${NC}"
|
||||
|
||||
# Create namespace if it doesn't exist
|
||||
echo "📁 Creating namespace '${NAMESPACE}'..."
|
||||
kubectl create namespace ${NAMESPACE} 2>/dev/null && echo -e "${GREEN}✓ Namespace '${NAMESPACE}' created${NC}" || echo -e "${GREEN}✓ Namespace '${NAMESPACE}' already exists${NC}"
|
||||
|
||||
# Apply storage configurations
|
||||
echo "💾 Setting up storage in namespace '${NAMESPACE}'..."
|
||||
kubectl apply -f k8s/storage.yaml -n ${NAMESPACE}
|
||||
|
||||
# Create secrets if they don't exist
|
||||
if ! kubectl get secret publichealth-secrets -n ${NAMESPACE} >/dev/null 2>&1; then
|
||||
echo -e "${YELLOW}⚠️ Creating default secrets in namespace '${NAMESPACE}' (CHANGE THESE IN PRODUCTION!)${NC}"
|
||||
kubectl create secret generic publichealth-secrets -n ${NAMESPACE} \
|
||||
--from-literal=django-secret-key='change-me-$(openssl rand -hex 32)' \
|
||||
--from-literal=database-url='postgres://publichealth:changeme@postgres-service:5432/publichealth' \
|
||||
--from-literal=postgres-password='changeme'
|
||||
else
|
||||
echo -e "${GREEN}✓ Secrets already exist in namespace '${NAMESPACE}'${NC}"
|
||||
fi
|
||||
|
||||
# Deploy PostgreSQL
|
||||
echo "🐘 Deploying PostgreSQL to namespace '${NAMESPACE}'..."
|
||||
kubectl apply -f k8s/postgres.yaml -n ${NAMESPACE}
|
||||
|
||||
# Deploy Redis
|
||||
echo "🔴 Deploying Redis to namespace '${NAMESPACE}'..."
|
||||
kubectl apply -f k8s/redis.yaml -n ${NAMESPACE}
|
||||
|
||||
# Wait for database to be ready
|
||||
echo "⏳ Waiting for PostgreSQL to be ready in namespace '${NAMESPACE}'..."
|
||||
kubectl wait --for=condition=ready pod -l app=postgres --timeout=120s -n ${NAMESPACE}
|
||||
|
||||
# Deploy the application
|
||||
echo "🚀 Deploying application to namespace '${NAMESPACE}'..."
|
||||
kubectl apply -f k8s/deployment.yaml -n ${NAMESPACE}
|
||||
|
||||
# Wait for deployment to be ready
|
||||
echo "⏳ Waiting for application to be ready in namespace '${NAMESPACE}'..."
|
||||
kubectl wait --for=condition=available --timeout=300s deployment/publichealth -n ${NAMESPACE}
|
||||
|
||||
# Run migrations
|
||||
echo "🔄 Running database migrations in namespace '${NAMESPACE}'..."
|
||||
POD=$(kubectl get pod -l app=publichealth -n ${NAMESPACE} -o jsonpath="{.items[0].metadata.name}")
|
||||
kubectl exec -it ${POD} -n ${NAMESPACE} -- python manage.py migrate
|
||||
|
||||
# Create superuser (optional)
|
||||
echo -e "${YELLOW}Do you want to create a superuser? (y/n)${NC}"
|
||||
read -r response
|
||||
if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]; then
|
||||
kubectl exec -it ${POD} -n ${NAMESPACE} -- python manage.py createsuperuser
|
||||
fi
|
||||
|
||||
# Get the service URL
|
||||
MINIKUBE_IP=$(minikube ip)
|
||||
NODE_PORT=$(kubectl get svc publichealth-service -n ${NAMESPACE} -o jsonpath='{.spec.ports[0].nodePort}')
|
||||
|
||||
echo ""
|
||||
echo "✅ Deployment complete in namespace '${NAMESPACE}'!"
|
||||
echo ""
|
||||
echo "📌 Application URL: http://${MINIKUBE_IP}:${NODE_PORT}"
|
||||
echo "📌 Admin URL: http://${MINIKUBE_IP}:${NODE_PORT}/admin/"
|
||||
echo ""
|
||||
echo "🔍 Useful commands (namespace: ${NAMESPACE}):"
|
||||
echo " - View logs: kubectl logs -f deployment/publichealth -n ${NAMESPACE}"
|
||||
echo " - Scale deployment: kubectl scale deployment publichealth --replicas=3 -n ${NAMESPACE}"
|
||||
echo " - Access pod shell: kubectl exec -it ${POD} -n ${NAMESPACE} -- /bin/bash"
|
||||
echo " - Port forward: kubectl port-forward service/publichealth-service 8000:8000 -n ${NAMESPACE}"
|
||||
echo " - List resources: kubectl get all -n ${NAMESPACE}"
|
||||
echo " - Delete namespace: kubectl delete namespace ${NAMESPACE}"
|
||||
echo " - Stop Minikube: minikube stop"
|
||||
echo ""
|
||||
echo "🌐 To access from your browser, you can also run:"
|
||||
echo " minikube service publichealth-service -n ${NAMESPACE}"
|
||||
16
entrypoint
16
entrypoint
|
|
@ -1,16 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
if [ "${COLLECT_STATIC-}" ]; then
|
||||
./manage.py collectstatic --no-input
|
||||
fi
|
||||
|
||||
exec /venv/bin/dumb-init -- gunicorn \
|
||||
-b [::]:8000 \
|
||||
--log-file=- \
|
||||
--worker-tmp-dir /dev/shm \
|
||||
--workers=2 \
|
||||
--threads=4 \
|
||||
--worker-class=gthread \
|
||||
publichealth.wsgi
|
||||
|
|
@ -6,9 +6,9 @@ __version__ = '0.1'
|
|||
|
||||
FEEDLER_APPS = (
|
||||
# Wagtail apps
|
||||
'wagtail',
|
||||
'wagtail.core',
|
||||
'wagtail.admin',
|
||||
'wagtail_modeladmin', # Separate package for modeladmin in Wagtail 5+
|
||||
'wagtail.contrib.modeladmin',
|
||||
'wagtail.contrib.routable_page',
|
||||
'wagtail.api.v2',
|
||||
|
||||
|
|
|
|||
|
|
@ -3,18 +3,17 @@
|
|||
from django.db import models
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.urls import re_path # django.conf.urls.url removed in Django 4.0
|
||||
from django.conf.urls import url
|
||||
from django.urls import reverse
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import gettext as _ # ugettext deprecated in Django 4.0
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.shortcuts import redirect
|
||||
|
||||
from wagtail.admin import messages
|
||||
# Wagtail 6 moved modeladmin to separate package
|
||||
from wagtail_modeladmin.helpers import AdminURLHelper, ButtonHelper
|
||||
from wagtail_modeladmin.options import ModelAdmin
|
||||
from wagtail_modeladmin.views import IndexView
|
||||
from wagtail.models import Site # wagtail.core.models moved to wagtail.models in 3.0
|
||||
from wagtail.contrib.modeladmin.helpers import AdminURLHelper, ButtonHelper
|
||||
from wagtail.contrib.modeladmin.options import ModelAdmin
|
||||
from wagtail.contrib.modeladmin.views import IndexView
|
||||
from wagtail.core.models import Site
|
||||
|
||||
from feedler.models import Entry
|
||||
from feedler.refresh import refresh_streams
|
||||
|
|
@ -41,10 +40,7 @@ class RefreshAdminURLHelper(AdminURLHelper):
|
|||
This helper constructs the different urls, to overwrite the default behaviour
|
||||
and append the filters to the action.
|
||||
"""
|
||||
def __init__(self, model, **kwargs):
|
||||
super().__init__(model, **kwargs)
|
||||
self.non_object_specific_actions = ('create', 'choose_parent', 'index', 'refresh')
|
||||
|
||||
non_object_specific_actions = ('create', 'choose_parent', 'index', 'refresh')
|
||||
def get_action_url(self, action, *args, **kwargs):
|
||||
query_params = kwargs.pop('query_params', None)
|
||||
url_name = self.get_action_url_name(action)
|
||||
|
|
@ -90,7 +86,7 @@ class EntryModelAdminMixin(object):
|
|||
def get_admin_urls_for_registration(self):
|
||||
urls = super().get_admin_urls_for_registration()
|
||||
urls += (
|
||||
re_path(
|
||||
url(
|
||||
self.url_helper.get_action_url_pattern('refresh'),
|
||||
self.refresh_view,
|
||||
name=self.url_helper.get_action_url_name('refresh')
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from django.core.management.base import BaseCommand
|
|||
from django.contrib.auth.models import User
|
||||
from django.test import Client
|
||||
|
||||
from wagtail.models import Page
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.admin.views.pages import edit
|
||||
|
||||
# Scans for broken editors, via
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import wagtail.fields
|
||||
import wagtail.core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
|
|||
name='FeedPage',
|
||||
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, default='')),
|
||||
('intro', wagtail.core.fields.RichTextField(blank=True, default='')),
|
||||
('stream', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='feedler.Stream', verbose_name='Filter to stream (optional)')),
|
||||
],
|
||||
options={
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 3.2.13 on 2022-05-03 10:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('feedler', '0008_auto_20171117_1647'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='entry',
|
||||
name='expire_at',
|
||||
field=models.DateTimeField(blank=True, null=True, verbose_name='expiry date/time'),
|
||||
),
|
||||
]
|
||||
|
|
@ -7,9 +7,7 @@ from django.db.models.signals import pre_save
|
|||
from django.dispatch import receiver
|
||||
from django.core.mail import send_mail
|
||||
|
||||
# In Wagtail 3+, BaseSetting moved to wagtail.contrib.settings.models
|
||||
from wagtail.contrib.settings.models import BaseSiteSetting as BaseSetting
|
||||
from wagtail.contrib.settings.registry import register_setting
|
||||
from wagtail.contrib.settings.models import BaseSetting, register_setting
|
||||
|
||||
from .models import Stream
|
||||
|
||||
|
|
|
|||
|
|
@ -1,13 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.utils import translation
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from wagtail.models import Page, Orderable # wagtail.core moved to wagtail in v3+
|
||||
from wagtail.admin.panels import FieldPanel # edit_handlers renamed to panels in Wagtail 3+
|
||||
from wagtail.fields import RichTextField # wagtail.core.fields moved to wagtail.fields
|
||||
|
||||
from wagtail.core.models import Page, Orderable
|
||||
from wagtail.admin.edit_handlers import FieldPanel
|
||||
from wagtail.core.fields import RichTextField
|
||||
|
||||
class Stream(models.Model):
|
||||
title = models.CharField(max_length=255)
|
||||
|
|
@ -40,11 +39,6 @@ class Entry(models.Model):
|
|||
lang = models.CharField(max_length=2, blank=True, default='', choices=LANGUAGE_CHOICES)
|
||||
content = models.TextField()
|
||||
tags = models.TextField(blank=True)
|
||||
expire_at = models.DateTimeField(
|
||||
verbose_name=_("expiry date/time"),
|
||||
blank=True,
|
||||
null=True
|
||||
)
|
||||
|
||||
stream = models.ForeignKey(Stream,
|
||||
blank=False, on_delete=models.CASCADE,
|
||||
|
|
@ -74,9 +68,7 @@ class FeedPage(Page):
|
|||
@property
|
||||
def feedentries(self):
|
||||
if self.stream:
|
||||
entries = Entry.objects.filter(stream=self.stream).filter(
|
||||
models.Q(expire_at__isnull=True) | models.Q(expire_at__gt=datetime.datetime.now())
|
||||
)
|
||||
entries = Entry.objects.filter(stream=self.stream)
|
||||
else:
|
||||
entries = Entry.objects.all()
|
||||
# Filter out by chosen language
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
from django.urls import include, re_path
|
||||
from django.conf.urls import include, url
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
|
|
@ -6,5 +6,5 @@ from django.conf.urls.i18n import i18n_patterns
|
|||
from .api import api_router
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r'^api/v2/', api_router.urls),
|
||||
url(r'^api/v2/', api_router.urls),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
from wagtail_modeladmin.options import (
|
||||
from wagtail.contrib.modeladmin.options import (
|
||||
ModelAdmin, modeladmin_register)
|
||||
|
||||
from .admin import EntryModelAdminMixin
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: cert-manager
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-prod
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-v02.api.letsencrypt.org/directory
|
||||
email: admin@django-hosting.ch # Change this to your email
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-prod
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
---
|
||||
apiVersion: cert-manager.io/v1
|
||||
kind: ClusterIssuer
|
||||
metadata:
|
||||
name: letsencrypt-staging
|
||||
spec:
|
||||
acme:
|
||||
server: https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
email: admin@django-hosting.ch # Change this to your email
|
||||
privateKeySecretRef:
|
||||
name: letsencrypt-staging
|
||||
solvers:
|
||||
- http01:
|
||||
ingress:
|
||||
class: nginx
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: publichealth
|
||||
labels:
|
||||
app: publichealth
|
||||
spec:
|
||||
replicas: 2
|
||||
selector:
|
||||
matchLabels:
|
||||
app: publichealth
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: publichealth
|
||||
spec:
|
||||
containers:
|
||||
- name: publichealth
|
||||
image: harbor.k8s.ungleich.ch/publichealth/publichealth:3.0.1
|
||||
imagePullPolicy: IfNotPresent # For local Minikube images
|
||||
ports:
|
||||
- containerPort: 8000
|
||||
env:
|
||||
- name: DJANGO_SETTINGS_MODULE
|
||||
value: "publichealth.settings.production"
|
||||
- name: SECRET_KEY
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: publichealth-secrets
|
||||
key: django-secret-key
|
||||
- name: DATABASE_URL
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: publichealth-secrets
|
||||
key: database-url
|
||||
- name: REDIS_URL
|
||||
value: "redis://redis-service:6379/0"
|
||||
- name: WAGTAILADMIN_BASE_URL
|
||||
value: "http://localhost:30080"
|
||||
- name: LOG_DIR
|
||||
value: "/tmp"
|
||||
- name: ALLOWED_HOSTS
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: publichealth-secrets
|
||||
key: allowed-hosts
|
||||
- name: COLLECT_STATIC
|
||||
value: "1"
|
||||
volumeMounts:
|
||||
- name: media-storage
|
||||
mountPath: /app/media
|
||||
- name: static-storage
|
||||
mountPath: /app/static
|
||||
resources:
|
||||
requests:
|
||||
memory: "512Mi"
|
||||
cpu: "250m"
|
||||
limits:
|
||||
memory: "1Gi"
|
||||
cpu: "500m"
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /en/
|
||||
port: 8000
|
||||
initialDelaySeconds: 30
|
||||
periodSeconds: 30
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /en/
|
||||
port: 8000
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
volumes:
|
||||
- name: media-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: media-pvc
|
||||
- name: static-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: static-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: publichealth-service
|
||||
spec:
|
||||
selector:
|
||||
app: publichealth
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 8000
|
||||
targetPort: 8000
|
||||
nodePort: 30080
|
||||
type: NodePort
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: ingress-nginx
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: publichealth-ingress
|
||||
annotations:
|
||||
kubernetes.io/ingress.class: nginx
|
||||
cert-manager.io/cluster-issuer: letsencrypt-prod
|
||||
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
|
||||
nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/proxy-connect-timeout: "300"
|
||||
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
|
||||
spec:
|
||||
rules:
|
||||
- host: ph.django-hosting.ch
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: publichealth-service
|
||||
port:
|
||||
number: 8000
|
||||
- host: www.ph.django-hosting.ch
|
||||
http:
|
||||
paths:
|
||||
- path: /
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: publichealth-service
|
||||
port:
|
||||
number: 8000
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: postgres-pvc
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteOnce
|
||||
resources:
|
||||
requests:
|
||||
storage: 5Gi
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: postgres
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: postgres
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: postgres
|
||||
spec:
|
||||
containers:
|
||||
- name: postgres
|
||||
image: postgres:15-alpine
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
env:
|
||||
- name: POSTGRES_DB
|
||||
value: publichealth
|
||||
- name: POSTGRES_USER
|
||||
value: publichealth
|
||||
- name: POSTGRES_PASSWORD
|
||||
valueFrom:
|
||||
secretKeyRef:
|
||||
name: publichealth-secrets
|
||||
key: postgres-password
|
||||
volumeMounts:
|
||||
- mountPath: /var/lib/postgresql/data
|
||||
name: postgres-storage
|
||||
subPath: postgres
|
||||
resources:
|
||||
requests:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
limits:
|
||||
memory: "512Mi"
|
||||
cpu: "200m"
|
||||
volumes:
|
||||
- name: postgres-storage
|
||||
persistentVolumeClaim:
|
||||
claimName: postgres-pvc
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: postgres-service
|
||||
spec:
|
||||
selector:
|
||||
app: postgres
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 5432
|
||||
targetPort: 5432
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: redis
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: redis
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: redis
|
||||
spec:
|
||||
containers:
|
||||
- name: redis
|
||||
image: redis:7-alpine
|
||||
ports:
|
||||
- containerPort: 6379
|
||||
resources:
|
||||
requests:
|
||||
memory: "128Mi"
|
||||
cpu: "50m"
|
||||
limits:
|
||||
memory: "256Mi"
|
||||
cpu: "100m"
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: redis-service
|
||||
spec:
|
||||
selector:
|
||||
app: redis
|
||||
ports:
|
||||
- protocol: TCP
|
||||
port: 6379
|
||||
targetPort: 6379
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
# Example secrets file - DO NOT commit actual secrets to git!
|
||||
# Create real secrets with: kubectl create secret generic publichealth-secrets \
|
||||
# --from-literal=django-secret-key='your-secret-key' \
|
||||
# --from-literal=database-url='postgres://user:pass@postgres-service:5432/dbname' \
|
||||
# --from-literal=postgres-password='your-postgres-password'
|
||||
|
||||
apiVersion: v1
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: publichealth-secrets
|
||||
type: Opaque
|
||||
stringData:
|
||||
django-secret-key: "change-me-to-a-real-secret-key"
|
||||
database-url: "postgres://publichealth:changeme@postgres-service:5432/publichealth"
|
||||
postgres-password: "changeme"
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: media-pvc
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 10Gi
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: static-pvc
|
||||
spec:
|
||||
accessModes:
|
||||
- ReadWriteMany
|
||||
resources:
|
||||
requests:
|
||||
storage: 2Gi
|
||||
7839
package-lock.json
generated
7839
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -21,7 +21,7 @@
|
|||
"grunt-contrib-sass": "^1.0.0",
|
||||
"grunt-contrib-watch": "^1.1.0",
|
||||
"grunt-sass": "^3.0.2",
|
||||
"sass": "^1.45.0"
|
||||
"node-sass": "^4.12.0"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
|
|
|
|||
|
|
@ -1,8 +0,0 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class HomeConfig(AppConfig):
|
||||
name = "publichealth.home"
|
||||
|
||||
def ready(self):
|
||||
from . import signals # noqa: F401
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
from django import template
|
||||
from html import unescape
|
||||
|
||||
register = template.Library()
|
||||
|
||||
@register.filter
|
||||
def ph_html_decode(value):
|
||||
return unescape(str(value))
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
"""
|
||||
Find and repair Wagtail rich-text content with unbalanced inline-style or
|
||||
entity tags that crashes the Draftail editor's HTML->ContentState converter
|
||||
with `AssertionError: End of block reached without closing inline style /
|
||||
entity elements`.
|
||||
|
||||
The Draftail converter is strict about tag balance; older Wagtail editors
|
||||
or Word/Outlook paste sometimes left tags unclosed. The fix re-serializes
|
||||
through BeautifulSoup (Python's stdlib html.parser) which auto-closes
|
||||
inline tags, then verifies the output by feeding it back through the same
|
||||
converter — only saves if the result is accepted.
|
||||
|
||||
Defaults to dry-run. Pass --apply to write changes; add --publish to
|
||||
publish the new revision (otherwise it's saved as a draft for editor
|
||||
review). --page-id limits to a single page.
|
||||
"""
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
from wagtail.models import Page
|
||||
from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.blocks import RichTextBlock
|
||||
|
||||
from publichealth.home.rich_text_sanitizer import (
|
||||
get_converter, is_broken, repair as _repair,
|
||||
)
|
||||
|
||||
|
||||
def _build_converter():
|
||||
converter = get_converter()
|
||||
# Best-effort: list the features the converter was built with for the
|
||||
# mode banner. Falls back gracefully if the converter doesn't expose them.
|
||||
features = sorted(getattr(converter, "features", []))
|
||||
return converter, features
|
||||
|
||||
|
||||
def _is_broken(converter, html):
|
||||
return is_broken(html)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Find and fix Wagtail rich-text fields with unbalanced tags."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument("--apply", action="store_true",
|
||||
help="Save changes (default: dry-run, report only).")
|
||||
parser.add_argument("--publish", action="store_true",
|
||||
help="Publish the cleaned revision (otherwise save as draft).")
|
||||
parser.add_argument("--page-id", type=int, default=None,
|
||||
help="Limit to a single page id.")
|
||||
parser.add_argument("--diff-file", type=str, default=None,
|
||||
help="Write full before/after HTML and a unified diff per block to this path.")
|
||||
|
||||
def handle(self, *args, **options):
|
||||
import difflib
|
||||
apply_changes = options["apply"]
|
||||
publish = options["publish"]
|
||||
page_id = options["page_id"]
|
||||
diff_path = options["diff_file"]
|
||||
diff_fp = open(diff_path, "w", encoding="utf-8") if diff_path else None
|
||||
|
||||
def emit_diff(page, field_name, sub_path, old, new):
|
||||
if diff_fp is None:
|
||||
return
|
||||
diff_fp.write("=" * 78 + "\n")
|
||||
diff_fp.write(f"Page {page.id}: {page.title}\n")
|
||||
diff_fp.write(f" url_path: {page.url_path}\n")
|
||||
diff_fp.write(f" field: {field_name}{sub_path}\n")
|
||||
diff_fp.write("-" * 78 + "\n")
|
||||
diff_fp.write("OLD:\n")
|
||||
diff_fp.write(old + "\n")
|
||||
diff_fp.write("-" * 78 + "\n")
|
||||
diff_fp.write("NEW:\n")
|
||||
diff_fp.write(new + "\n")
|
||||
diff_fp.write("-" * 78 + "\n")
|
||||
diff_fp.write("UNIFIED DIFF:\n")
|
||||
old_lines = old.splitlines(keepends=True) or [""]
|
||||
new_lines = new.splitlines(keepends=True) or [""]
|
||||
for line in difflib.unified_diff(old_lines, new_lines,
|
||||
fromfile="old", tofile="new", n=1):
|
||||
diff_fp.write(line if line.endswith("\n") else line + "\n")
|
||||
diff_fp.write("\n")
|
||||
|
||||
converter, features = _build_converter()
|
||||
self.stdout.write(f"Detector using {len(features)} features.")
|
||||
self.stdout.write(f"Mode: {'APPLY' + (' + PUBLISH' if publish else ' (draft)') if apply_changes else 'DRY RUN'}")
|
||||
if diff_fp:
|
||||
self.stdout.write(f"Writing full diffs to: {diff_path}")
|
||||
|
||||
qs = Page.objects.all()
|
||||
if page_id is not None:
|
||||
qs = qs.filter(id=page_id)
|
||||
|
||||
total = qs.count()
|
||||
self.stdout.write(f"Scanning {total} page(s)...")
|
||||
|
||||
pages_with_issues = 0
|
||||
pages_fixed = 0
|
||||
pages_partial = 0
|
||||
blocks_fixed = 0
|
||||
blocks_unfixable = 0
|
||||
|
||||
for page in qs.iterator(chunk_size=50):
|
||||
specific = page.specific
|
||||
changes = [] # successful: (field, sub_path, old, new)
|
||||
unfixable = [] # broken but repair didn't help: (field, sub_path)
|
||||
stream_replacements = {} # field_name -> {index: new_html}
|
||||
rich_text_field_updates = {} # field_name -> new_html
|
||||
|
||||
for field in specific._meta.get_fields():
|
||||
if isinstance(field, RichTextField):
|
||||
html = getattr(specific, field.name, "") or ""
|
||||
if not _is_broken(converter, html):
|
||||
continue
|
||||
fixed = _repair(html)
|
||||
if _is_broken(converter, fixed):
|
||||
unfixable.append((field.name, ""))
|
||||
continue
|
||||
changes.append((field.name, "", html, fixed))
|
||||
rich_text_field_updates[field.name] = fixed
|
||||
|
||||
elif isinstance(field, StreamField):
|
||||
stream_value = getattr(specific, field.name, None)
|
||||
if stream_value is None:
|
||||
continue
|
||||
try:
|
||||
children = list(stream_value)
|
||||
except Exception:
|
||||
continue
|
||||
field_replacements = {}
|
||||
for i, child in enumerate(children):
|
||||
if not isinstance(child.block, RichTextBlock):
|
||||
continue
|
||||
v = child.value
|
||||
html = v.source if hasattr(v, "source") else str(v)
|
||||
if not _is_broken(converter, html):
|
||||
continue
|
||||
fixed = _repair(html)
|
||||
if _is_broken(converter, fixed):
|
||||
unfixable.append((field.name, f"[{i}]"))
|
||||
continue
|
||||
changes.append((field.name, f"[{i}]({child.block_type})", html, fixed))
|
||||
field_replacements[i] = fixed
|
||||
if field_replacements:
|
||||
stream_replacements[field.name] = (children, field_replacements)
|
||||
|
||||
if not changes and not unfixable:
|
||||
continue
|
||||
|
||||
pages_with_issues += 1
|
||||
if changes and not unfixable:
|
||||
pages_fixed += 1
|
||||
elif changes and unfixable:
|
||||
pages_partial += 1
|
||||
blocks_fixed += len(changes)
|
||||
blocks_unfixable += len(unfixable)
|
||||
|
||||
self.stdout.write("")
|
||||
self.stdout.write(f"Page {page.id}: {page.title!r} [{specific.__class__.__name__}]")
|
||||
self.stdout.write(f" url_path: {page.url_path}")
|
||||
for field_name, sub_path, old, new in changes:
|
||||
self.stdout.write(f" FIX {field_name}{sub_path}")
|
||||
self.stdout.write(f" old: {old[:160]!r}{'...' if len(old) > 160 else ''}")
|
||||
self.stdout.write(f" new: {new[:160]!r}{'...' if len(new) > 160 else ''}")
|
||||
emit_diff(page, field_name, sub_path, old, new)
|
||||
for field_name, sub_path in unfixable:
|
||||
self.stdout.write(f" SKIP {field_name}{sub_path} (auto-repair did not produce valid output)")
|
||||
|
||||
if not apply_changes:
|
||||
continue
|
||||
|
||||
# Apply
|
||||
for field_name, new_value in rich_text_field_updates.items():
|
||||
setattr(specific, field_name, new_value)
|
||||
|
||||
for field_name, (children, replacements) in stream_replacements.items():
|
||||
# Rebuild the StreamField as a list of raw {type, value} dicts.
|
||||
# Using `get_prep_value` for unchanged children gives the
|
||||
# JSON-serializable raw form Wagtail expects on assign;
|
||||
# replacements are already raw HTML strings (correct form for
|
||||
# RichTextBlock).
|
||||
new_list = []
|
||||
for i, child in enumerate(children):
|
||||
if i in replacements:
|
||||
new_list.append({"type": child.block_type, "value": replacements[i]})
|
||||
else:
|
||||
raw = child.block.get_prep_value(child.value)
|
||||
new_list.append({"type": child.block_type, "value": raw})
|
||||
setattr(specific, field_name, new_list)
|
||||
|
||||
try:
|
||||
revision = specific.save_revision()
|
||||
if publish:
|
||||
revision.publish()
|
||||
self.stdout.write(self.style.SUCCESS(
|
||||
f" -> revision {revision.id} saved{' & published' if publish else ' (draft)'}"
|
||||
))
|
||||
except Exception as e:
|
||||
self.stdout.write(self.style.ERROR(f" -> SAVE FAILED: {type(e).__name__}: {e}"))
|
||||
|
||||
self.stdout.write("")
|
||||
self.stdout.write("=" * 70)
|
||||
self.stdout.write(f"Pages with issues: {pages_with_issues}")
|
||||
self.stdout.write(f"Pages fully fixable: {pages_fixed}")
|
||||
self.stdout.write(f"Pages partial: {pages_partial} (some blocks unfixable)")
|
||||
self.stdout.write(f"Blocks fixed: {blocks_fixed}")
|
||||
self.stdout.write(f"Blocks unfixable: {blocks_unfixable}")
|
||||
if not apply_changes:
|
||||
self.stdout.write("")
|
||||
self.stdout.write("DRY RUN — no changes saved. Re-run with --apply to write.")
|
||||
|
||||
if diff_fp:
|
||||
diff_fp.close()
|
||||
self.stdout.write(f"Diff log: {diff_path}")
|
||||
|
|
@ -5,8 +5,8 @@ from __future__ import unicode_literals
|
|||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import modelcluster.fields
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
|
|
@ -36,10 +36,10 @@ class Migration(migrations.Migration):
|
|||
('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')),
|
||||
('title_fr', models.CharField(default='', max_length=255)),
|
||||
('date', models.DateField(verbose_name='Date')),
|
||||
('intro_de', wagtail.fields.RichTextField(default='')),
|
||||
('intro_fr', wagtail.fields.RichTextField(default='')),
|
||||
('body_de', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('section', wagtail.blocks.CharBlock(classname='full title'))], blank=True, null=True)),
|
||||
('body_fr', wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('section', wagtail.blocks.CharBlock(classname='full title'))], blank=True, null=True)),
|
||||
('intro_de', wagtail.core.fields.RichTextField(default='')),
|
||||
('intro_fr', wagtail.core.fields.RichTextField(default='')),
|
||||
('body_de', wagtail.core.fields.StreamField([('paragraph', wagtail.core.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title'))], blank=True, null=True)),
|
||||
('body_fr', wagtail.core.fields.StreamField([('paragraph', wagtail.core.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title'))], blank=True, null=True)),
|
||||
('feed_image', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.Image')),
|
||||
],
|
||||
options={
|
||||
|
|
@ -64,31 +64,31 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='body_de',
|
||||
field=wagtail.fields.RichTextField(default=''),
|
||||
field=wagtail.core.fields.RichTextField(default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='body_fr',
|
||||
field=wagtail.fields.RichTextField(default=''),
|
||||
field=wagtail.core.fields.RichTextField(default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='infos_de',
|
||||
field=wagtail.fields.StreamField([('info', wagtail.blocks.StructBlock([(b'title', wagtail.blocks.CharBlock(required=True)), (b'photo', wagtail.images.blocks.ImageChooserBlock()), (b'summary', wagtail.blocks.RichTextBlock(required=True)), (b'action', wagtail.blocks.CharBlock()), (b'url', wagtail.blocks.URLBlock())]))], blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField([('info', wagtail.core.blocks.StructBlock([(b'title', wagtail.core.blocks.CharBlock(required=True)), (b'photo', wagtail.images.blocks.ImageChooserBlock()), (b'summary', wagtail.core.blocks.RichTextBlock(required=True)), (b'action', wagtail.core.blocks.CharBlock()), (b'url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='infos_fr',
|
||||
field=wagtail.fields.StreamField([('info', wagtail.blocks.StructBlock([(b'title', wagtail.blocks.CharBlock(required=True)), (b'photo', wagtail.images.blocks.ImageChooserBlock()), (b'summary', wagtail.blocks.RichTextBlock(required=True)), (b'action', wagtail.blocks.CharBlock()), (b'url', wagtail.blocks.URLBlock())]))], blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField([('info', wagtail.core.blocks.StructBlock([(b'title', wagtail.core.blocks.CharBlock(required=True)), (b'photo', wagtail.images.blocks.ImageChooserBlock()), (b'summary', wagtail.core.blocks.RichTextBlock(required=True)), (b'action', wagtail.core.blocks.CharBlock()), (b'url', wagtail.core.blocks.URLBlock())]))], blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='intro_de',
|
||||
field=wagtail.fields.RichTextField(default=''),
|
||||
field=wagtail.core.fields.RichTextField(default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='intro_fr',
|
||||
field=wagtail.fields.RichTextField(default=''),
|
||||
field=wagtail.core.fields.RichTextField(default=''),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.fields
|
||||
import wagtail.core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -20,11 +20,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='intro_de',
|
||||
field=wagtail.fields.RichTextField(default=''),
|
||||
field=wagtail.core.fields.RichTextField(default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='intro_fr',
|
||||
field=wagtail.fields.RichTextField(default=''),
|
||||
field=wagtail.core.fields.RichTextField(default=''),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.fields
|
||||
import wagtail.core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -16,21 +16,21 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='articleindexpage',
|
||||
name='intro_de',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articleindexpage',
|
||||
name='intro_fr',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='intro_de',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='intro_fr',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.fields
|
||||
import wagtail.core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -16,11 +16,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='homepage',
|
||||
name='body_de',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homepage',
|
||||
name='body_fr',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ from __future__ import unicode_literals
|
|||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import modelcluster.fields
|
||||
import wagtail.fields
|
||||
import wagtail.core.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
|
@ -35,10 +35,10 @@ class Migration(migrations.Migration):
|
|||
('from_address', models.CharField(blank=True, max_length=255, verbose_name='from address')),
|
||||
('subject', models.CharField(blank=True, max_length=255, verbose_name='subject')),
|
||||
('title_fr', models.CharField(default=b'', max_length=255)),
|
||||
('intro_de', wagtail.fields.RichTextField(blank=True, default=b'')),
|
||||
('intro_fr', wagtail.fields.RichTextField(blank=True, default=b'')),
|
||||
('thanks_de', wagtail.fields.RichTextField(blank=True, default=b'')),
|
||||
('thanks_fr', wagtail.fields.RichTextField(blank=True, default=b'')),
|
||||
('intro_de', wagtail.core.fields.RichTextField(blank=True, default=b'')),
|
||||
('intro_fr', wagtail.core.fields.RichTextField(blank=True, default=b'')),
|
||||
('thanks_de', wagtail.core.fields.RichTextField(blank=True, default=b'')),
|
||||
('thanks_fr', wagtail.core.fields.RichTextField(blank=True, default=b'')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Formular',
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
|
|
@ -23,22 +23,22 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='contactform',
|
||||
name='intro_de',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='contactform',
|
||||
name='intro_fr',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='contactform',
|
||||
name='thanks_de',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='contactform',
|
||||
name='thanks_fr',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='contactform',
|
||||
|
|
@ -48,11 +48,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='homepage',
|
||||
name='infos_de',
|
||||
field=wagtail.fields.StreamField((('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock()), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock()), ('url', wagtail.blocks.URLBlock())))),), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock()), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock()), ('url', wagtail.core.blocks.URLBlock())))),), blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homepage',
|
||||
name='infos_fr',
|
||||
field=wagtail.fields.StreamField((('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock()), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock()), ('url', wagtail.blocks.URLBlock())))),), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock()), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock()), ('url', wagtail.core.blocks.URLBlock())))),), blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
|
|
@ -18,21 +18,21 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_de',
|
||||
field=wagtail.fields.StreamField((('paragraph', wagtail.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('section', wagtail.blocks.CharBlock(classname='full title')), ('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False)))))), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('paragraph', wagtail.core.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title')), ('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False)))))), blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_fr',
|
||||
field=wagtail.fields.StreamField((('paragraph', wagtail.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('section', wagtail.blocks.CharBlock(classname='full title')), ('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False)))))), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('paragraph', wagtail.core.blocks.RichTextBlock()), ('image', wagtail.images.blocks.ImageChooserBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title')), ('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False)))))), blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homepage',
|
||||
name='infos_de',
|
||||
field=wagtail.fields.StreamField((('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))))),), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False))))),), blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='homepage',
|
||||
name='infos_fr',
|
||||
field=wagtail.fields.StreamField((('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))))),), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False))))),), blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
|
|
@ -22,17 +22,17 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='articlepage',
|
||||
name='gallery',
|
||||
field=wagtail.fields.StreamField((('image', wagtail.blocks.ListBlock(wagtail.blocks.StructBlock((('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.blocks.TextBlock(required=False)))), icon='image')),), blank=True),
|
||||
field=wagtail.core.fields.StreamField((('image', wagtail.core.blocks.ListBlock(wagtail.core.blocks.StructBlock((('image', wagtail.images.blocks.ImageChooserBlock()), ('caption', wagtail.core.blocks.TextBlock(required=False)))), icon='image')),), blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_de',
|
||||
field=wagtail.fields.StreamField((('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(classname='full title')), ('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))))), ('placer', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='user'))), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('paragraph', wagtail.core.blocks.RichTextBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title')), ('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False))))), ('placer', wagtail.core.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='user'))), blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_fr',
|
||||
field=wagtail.fields.StreamField((('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(classname='full title')), ('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))))), ('placer', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='user'))), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('paragraph', wagtail.core.blocks.RichTextBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title')), ('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False))))), ('placer', wagtail.core.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='user'))), blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='contactformfield',
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
|
|
@ -18,11 +18,11 @@ class Migration(migrations.Migration):
|
|||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_de',
|
||||
field=wagtail.fields.StreamField((('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(classname='full title')), ('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))), icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('paragraph', wagtail.core.blocks.RichTextBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title')), ('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False))), icon='help')), ('media', wagtail.core.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))), blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_fr',
|
||||
field=wagtail.fields.StreamField((('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(classname='full title')), ('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))), icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('paragraph', wagtail.core.blocks.RichTextBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title')), ('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False))), icon='help')), ('media', wagtail.core.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))), blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.core.blocks
|
||||
import wagtail.core.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
|
|
@ -18,7 +18,7 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='intro_en',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
|
|
@ -28,12 +28,12 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='articlepage',
|
||||
name='body_en',
|
||||
field=wagtail.fields.StreamField((('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(classname='full title')), ('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))), icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('paragraph', wagtail.core.blocks.RichTextBlock()), ('section', wagtail.core.blocks.CharBlock(classname='full title')), ('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False))), icon='help')), ('media', wagtail.core.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))), blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articlepage',
|
||||
name='intro_en',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articlepage',
|
||||
|
|
@ -48,16 +48,16 @@ class Migration(migrations.Migration):
|
|||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='body_en',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='infos_en',
|
||||
field=wagtail.fields.StreamField((('info', wagtail.blocks.StructBlock((('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))))),), blank=True, null=True),
|
||||
field=wagtail.core.fields.StreamField((('info', wagtail.core.blocks.StructBlock((('title', wagtail.core.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.core.blocks.RichTextBlock(required=True)), ('action', wagtail.core.blocks.CharBlock(required=False)), ('url', wagtail.core.blocks.URLBlock(required=False))))),), blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='homepage',
|
||||
name='intro_en',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
field=wagtail.core.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,29 +0,0 @@
|
|||
# Generated by Django 3.0.14 on 2021-09-28 16:50
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0029_contactformfield_clean_name'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='subscribe_label_de',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='subscribe_label_en',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='subscribe_label_fr',
|
||||
field=wagtail.fields.RichTextField(blank=True, default=''),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
# Generated by Django 3.0.14 on 2021-09-28 16:52
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0030_auto_20210928_1850'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='subscribe_action',
|
||||
field=models.URLField(blank=True, default=''),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# Generated by Django 3.0.14 on 2021-09-28 16:58
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0031_articleindexpage_subscribe_action'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='articleindexpage',
|
||||
name='subscribe_action',
|
||||
field=models.URLField(blank=True, default='', verbose_name='Action'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articleindexpage',
|
||||
name='subscribe_label_de',
|
||||
field=models.CharField(blank=True, default='', max_length=250, verbose_name='Button Label (de)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articleindexpage',
|
||||
name='subscribe_label_en',
|
||||
field=models.CharField(blank=True, default='', max_length=250, verbose_name='Button Label (en)'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articleindexpage',
|
||||
name='subscribe_label_fr',
|
||||
field=models.CharField(blank=True, default='', max_length=250, verbose_name='Button Label (fr)'),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
# Generated by Django 3.2.11 on 2022-02-07 06:31
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0032_auto_20210928_1858'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='contact',
|
||||
name='news_letter_text',
|
||||
field=models.CharField(blank=True, default='', max_length=256),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contact',
|
||||
name='news_letter_text_en',
|
||||
field=models.CharField(blank=True, default='', max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contact',
|
||||
name='news_letter_text_fr',
|
||||
field=models.CharField(blank=True, default='', max_length=255),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='contact',
|
||||
name='news_letter_url',
|
||||
field=models.URLField(blank=True, default=''),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
# Generated by Django 3.2.13 on 2022-07-14 09:45
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
import wagtail.contrib.table_block.blocks
|
||||
import wagtail.fields
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('wagtailimages', '0023_add_choose_permissions'),
|
||||
('home', '0033_auto_20220207_0731'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='header_image',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='table_de',
|
||||
field=wagtail.fields.StreamField([('table_de', wagtail.contrib.table_block.blocks.TableBlock(template='home/program_table.html'))], blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='table_en',
|
||||
field=wagtail.fields.StreamField([('table_en', wagtail.contrib.table_block.blocks.TableBlock(template='home/program_table.html'))], blank=True, null=True),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articleindexpage',
|
||||
name='table_fr',
|
||||
field=wagtail.fields.StreamField([('table_fr', wagtail.contrib.table_block.blocks.TableBlock(template='home/program_table.html'))], blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
# Generated by Django 3.2.13 on 2023-05-15 12:47
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.embeds.blocks
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0034_auto_20220714_1145'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_de',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media')), ('iframe', wagtail.embeds.blocks.EmbedBlock())], blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_en',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media')), ('iframe', wagtail.embeds.blocks.EmbedBlock())], blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_fr',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media')), ('iframe', wagtail.embeds.blocks.EmbedBlock())], blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
# Generated by Django 3.2.13 on 2023-05-15 13:56
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0035_auto_20230515_1447'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='articlepage',
|
||||
name='html_content',
|
||||
field=wagtail.fields.RichTextField(blank=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_de',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))], blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_en',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))], blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_fr',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media'))], blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
# Generated by Django 3.2.13 on 2023-05-16 10:41
|
||||
|
||||
from django.db import migrations
|
||||
import wagtail.blocks
|
||||
import wagtail.fields
|
||||
import wagtail.images.blocks
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0036_auto_20230515_1556'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='articlepage',
|
||||
name='html_content',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_de',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media')), ('iframe', wagtail.blocks.RichTextBlock())], blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_en',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media')), ('iframe', wagtail.blocks.RichTextBlock())], blank=True, null=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='articlepage',
|
||||
name='body_fr',
|
||||
field=wagtail.fields.StreamField([('paragraph', wagtail.blocks.RichTextBlock()), ('section', wagtail.blocks.CharBlock(form_classname='full title')), ('info', wagtail.blocks.StructBlock([('title', wagtail.blocks.CharBlock(required=True)), ('photo', wagtail.images.blocks.ImageChooserBlock(required=True)), ('summary', wagtail.blocks.RichTextBlock(required=True)), ('action', wagtail.blocks.CharBlock(required=False)), ('url', wagtail.blocks.URLBlock(required=False))], icon='help')), ('media', wagtail.blocks.ChoiceBlock(choices=[('gallery', 'Image gallery')], icon='media')), ('iframe', wagtail.blocks.RichTextBlock())], blank=True, null=True),
|
||||
),
|
||||
]
|
||||
|
|
@ -2,4 +2,3 @@ from .forms import *
|
|||
from .models import *
|
||||
from .snippets import *
|
||||
from .admin import *
|
||||
from .image_formats import *
|
||||
|
|
|
|||
|
|
@ -5,13 +5,11 @@ from django.db.models.signals import pre_save
|
|||
from django.dispatch import receiver
|
||||
from django.core.mail import send_mail
|
||||
|
||||
# In Wagtail 3+, BaseSetting moved and register_setting changed
|
||||
from wagtail.contrib.settings.models import BaseSiteSetting as BaseSetting
|
||||
from wagtail.contrib.settings.registry import register_setting
|
||||
from wagtail.contrib.settings.models import BaseSetting, register_setting
|
||||
|
||||
# A simple feedback module built into the site admin
|
||||
|
||||
@register_setting
|
||||
@register_setting(icon='', classnames='icon icon-help')
|
||||
class DataletsSettings(BaseSetting):
|
||||
feedback_question = models.TextField(verbose_name='Name',
|
||||
help_text='Who should we reply to for questions?', blank=True)
|
||||
|
|
@ -38,8 +36,8 @@ def handle_save_settings(sender, instance, *args, **kwargs):
|
|||
str(instance.feedback_status),
|
||||
instance.feedback_question,
|
||||
instance.feedback_comment,
|
||||
), "info@ungleich.ch",
|
||||
[ "info@ungleich.ch" ]
|
||||
), "wagtail@datalets.ch",
|
||||
[ "support@datalets.ch" ]
|
||||
)
|
||||
instance.feedback_status = None
|
||||
instance.feedback_question = ""
|
||||
|
|
|
|||
|
|
@ -4,11 +4,11 @@ from modelcluster.fields import ParentalKey
|
|||
|
||||
from django.db.models import CharField
|
||||
|
||||
from wagtail.fields import RichTextField
|
||||
from wagtail.core.fields import RichTextField
|
||||
from wagtail.contrib.forms.models import (
|
||||
AbstractEmailForm, AbstractFormField
|
||||
)
|
||||
from wagtail.admin.panels import (
|
||||
from wagtail.admin.edit_handlers import (
|
||||
FieldPanel, FieldRowPanel,
|
||||
InlinePanel, MultiFieldPanel
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
# image_formats.py
|
||||
from django.utils.html import format_html
|
||||
from wagtail.images.formats import Format, register_image_format
|
||||
|
||||
|
||||
class OriginalImageFormat(Format):
|
||||
|
||||
def image_to_html(self, image, alt_text, extra_attributes=None):
|
||||
|
||||
default_html = super().image_to_html(image, alt_text, extra_attributes)
|
||||
|
||||
return format_html("{}", default_html, alt_text)
|
||||
|
||||
|
||||
register_image_format(
|
||||
OriginalImageFormat('original_fullwidth', 'Original image', 'bodytext-image', 'original')
|
||||
)
|
||||
|
|
@ -2,23 +2,21 @@
|
|||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import datetime
|
||||
|
||||
from django.db import models
|
||||
from django.utils import translation
|
||||
from django.conf import settings
|
||||
|
||||
from modelcluster.fields import ParentalKey
|
||||
|
||||
from wagtail.blocks import StructBlock, CharBlock, URLBlock, RichTextBlock, ListBlock, TextBlock, ChoiceBlock
|
||||
from wagtail.models import Page, Orderable
|
||||
from wagtail.fields import StreamField, RichTextField
|
||||
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
|
||||
from wagtail.core.blocks import StructBlock, CharBlock, URLBlock, RichTextBlock, ListBlock, TextBlock, ChoiceBlock
|
||||
from wagtail.core.models import Page, Orderable
|
||||
from wagtail.core.fields import StreamField, RichTextField
|
||||
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel, InlinePanel, MultiFieldPanel
|
||||
from wagtail.images.blocks import ImageChooserBlock
|
||||
from wagtail.images.edit_handlers import ImageChooserPanel
|
||||
from wagtail.search import index
|
||||
from wagtail.contrib.table_block.blocks import TableBlock
|
||||
|
||||
# from puput.models import EntryPage, BlogPage # Commented: needs Wagtail 6 update
|
||||
from puput.models import EntryPage, BlogPage
|
||||
from feedler.models import Entry, Stream
|
||||
from itertools import chain
|
||||
|
||||
|
|
@ -40,13 +38,6 @@ class ArticleIndexPage(Page):
|
|||
'title_en',
|
||||
)
|
||||
|
||||
header_image = models.ForeignKey(
|
||||
'wagtailimages.Image',
|
||||
null=True, blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name='+'
|
||||
)
|
||||
|
||||
intro_de = RichTextField(default='', blank=True)
|
||||
intro_fr = RichTextField(default='', blank=True)
|
||||
intro_en = RichTextField(default='', blank=True)
|
||||
|
|
@ -56,45 +47,6 @@ class ArticleIndexPage(Page):
|
|||
'intro_en',
|
||||
)
|
||||
|
||||
table_en = StreamField(
|
||||
[
|
||||
('table_en', TableBlock(template='home/program_table.html'))
|
||||
],
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
table_de = StreamField(
|
||||
[
|
||||
('table_de', TableBlock(template='home/program_table.html'))
|
||||
],
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
table_fr = StreamField(
|
||||
[
|
||||
('table_fr', TableBlock(template='home/program_table.html'))
|
||||
],
|
||||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
|
||||
trans_table = TranslatedField(
|
||||
'table_de',
|
||||
'table_fr',
|
||||
'table_en',
|
||||
)
|
||||
|
||||
subscribe_label_de = models.CharField("Button Label (de)", default='', blank=True, max_length=250)
|
||||
subscribe_label_fr = models.CharField("Button Label (fr)", default='', blank=True, max_length=250)
|
||||
subscribe_label_en = models.CharField("Button Label (en)", default='', blank=True, max_length=250)
|
||||
subscribe_action = models.URLField("Action", default='', blank=True)
|
||||
|
||||
trans_subscribe_label = TranslatedField(
|
||||
'subscribe_label_de',
|
||||
'subscribe_label_fr',
|
||||
'subscribe_label_en',
|
||||
)
|
||||
|
||||
feed_image = models.ForeignKey(
|
||||
'wagtailimages.Image',
|
||||
null=True, blank=True,
|
||||
|
|
@ -108,21 +60,7 @@ class ArticleIndexPage(Page):
|
|||
FieldPanel('intro_fr'),
|
||||
FieldPanel('title_en'),
|
||||
FieldPanel('intro_en'),
|
||||
FieldPanel('header_image'), # ImageChooserPanel no longer needed in Wagtail 3+
|
||||
FieldPanel('table_en'),
|
||||
FieldPanel('table_fr'),
|
||||
FieldPanel('table_de'),
|
||||
FieldPanel('feed_image'), # ImageChooserPanel no longer needed in Wagtail 3+
|
||||
MultiFieldPanel(
|
||||
[
|
||||
FieldPanel('subscribe_label_de'),
|
||||
FieldPanel('subscribe_label_fr'),
|
||||
FieldPanel('subscribe_label_en'),
|
||||
FieldPanel('subscribe_action')
|
||||
],
|
||||
heading="Newsletters",
|
||||
classname="collapsible collapsed"
|
||||
)
|
||||
ImageChooserPanel('feed_image'),
|
||||
]
|
||||
|
||||
def get_context(self, request):
|
||||
|
|
@ -184,8 +122,7 @@ class ArticlePage(Page):
|
|||
('info', InfoBlock(icon='help')),
|
||||
('media', ChoiceBlock(choices=[
|
||||
('gallery', 'Image gallery'),
|
||||
], icon='media')),
|
||||
('iframe', RichTextBlock())
|
||||
], icon='media'))
|
||||
], null=True, blank=True)
|
||||
body_fr = StreamField([
|
||||
('paragraph', RichTextBlock()),
|
||||
|
|
@ -193,8 +130,7 @@ class ArticlePage(Page):
|
|||
('info', InfoBlock(icon='help')),
|
||||
('media', ChoiceBlock(choices=[
|
||||
('gallery', 'Image gallery'),
|
||||
], icon='media')),
|
||||
('iframe', RichTextBlock())
|
||||
], icon='media'))
|
||||
], null=True, blank=True)
|
||||
body_en = StreamField([
|
||||
('paragraph', RichTextBlock()),
|
||||
|
|
@ -202,8 +138,7 @@ class ArticlePage(Page):
|
|||
('info', InfoBlock(icon='help')),
|
||||
('media', ChoiceBlock(choices=[
|
||||
('gallery', 'Image gallery'),
|
||||
], icon='media')),
|
||||
('iframe', RichTextBlock())
|
||||
], icon='media'))
|
||||
], null=True, blank=True)
|
||||
trans_body = TranslatedField(
|
||||
'body_de',
|
||||
|
|
@ -240,23 +175,23 @@ class ArticlePage(Page):
|
|||
FieldPanel('intro_de'),
|
||||
], heading="Deutsch",
|
||||
classname="collapsible collapsed"),
|
||||
FieldPanel('body_de'), # StreamFieldPanel no longer needed in Wagtail 3+
|
||||
StreamFieldPanel('body_de'),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('title_fr'),
|
||||
FieldPanel('intro_fr'),
|
||||
], heading="Français",
|
||||
classname="collapsible collapsed"),
|
||||
FieldPanel('body_fr'), # StreamFieldPanel no longer needed in Wagtail 3+
|
||||
StreamFieldPanel('body_fr'),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('title_en'),
|
||||
FieldPanel('intro_en'),
|
||||
], heading="English",
|
||||
classname="collapsible collapsed"),
|
||||
FieldPanel('body_en'), # StreamFieldPanel no longer needed in Wagtail 3+
|
||||
StreamFieldPanel('body_en'),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('feed_image'), # ImageChooserPanel no longer needed in Wagtail 3+
|
||||
ImageChooserPanel('feed_image'),
|
||||
], heading="Images"),
|
||||
FieldPanel('gallery'), # StreamFieldPanel no longer needed in Wagtail 3+
|
||||
StreamFieldPanel('gallery'),
|
||||
]
|
||||
promote_panels = [
|
||||
InlinePanel('related_links', label="Links"),
|
||||
|
|
@ -322,19 +257,19 @@ class HomePage(Page):
|
|||
MultiFieldPanel([
|
||||
FieldPanel('intro_de', classname="full"),
|
||||
FieldPanel('body_de', classname="full"),
|
||||
FieldPanel('infos_de'), # StreamFieldPanel no longer needed in Wagtail 3+
|
||||
StreamFieldPanel('infos_de'),
|
||||
], heading="Deutsch",
|
||||
classname="collapsible collapsed"),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('intro_fr', classname="full"),
|
||||
FieldPanel('body_fr', classname="full"),
|
||||
FieldPanel('infos_fr'), # StreamFieldPanel no longer needed in Wagtail 3+
|
||||
StreamFieldPanel('infos_fr'),
|
||||
], heading="Français",
|
||||
classname="collapsible collapsed"),
|
||||
MultiFieldPanel([
|
||||
FieldPanel('intro_en', classname="full"),
|
||||
FieldPanel('body_en', classname="full"),
|
||||
FieldPanel('infos_en'), # StreamFieldPanel no longer needed in Wagtail 3+
|
||||
StreamFieldPanel('infos_en'),
|
||||
], heading="English",
|
||||
classname="collapsible collapsed"),
|
||||
]
|
||||
|
|
@ -352,23 +287,19 @@ class HomePage(Page):
|
|||
@property
|
||||
def blogentries(self):
|
||||
# Get list of latest news
|
||||
# FIXME: Temporarily disabled - puput needs update for Wagtail 6
|
||||
return []
|
||||
# curlang = translation.get_language()
|
||||
# if not curlang in ['de', 'fr', 'en', 'it']: curlang = 'de' # Default language
|
||||
# parent = BlogPage.objects.filter(slug='news-%s' % curlang)
|
||||
# if not parent: return []
|
||||
# posts = EntryPage.objects.live().descendant_of(parent[0])
|
||||
# # Order by most recent date first
|
||||
# posts = posts.order_by('-date')
|
||||
# return posts[:settings.BLOG_ENTRIES_HOME_PAGE]
|
||||
curlang = translation.get_language()
|
||||
if not curlang in ['de', 'fr', 'en', 'it']: curlang = 'de' # Default language
|
||||
parent = BlogPage.objects.filter(slug='news-%s' % curlang)
|
||||
if not parent: return []
|
||||
posts = EntryPage.objects.live().descendant_of(parent[0])
|
||||
# Order by most recent date first
|
||||
posts = posts.order_by('-date')
|
||||
return posts[:settings.BLOG_ENTRIES_HOME_PAGE]
|
||||
|
||||
@property
|
||||
def newsentries(self):
|
||||
# Get the last few news entries for the home page
|
||||
entries = Entry.objects.filter(
|
||||
models.Q(expire_at__isnull=True) | models.Q(expire_at__gt=datetime.datetime.now())
|
||||
).all().order_by('-published')
|
||||
entries = Entry.objects.all().order_by('-published')
|
||||
# Filter out by current language
|
||||
curlang = translation.get_language()
|
||||
if curlang in ['de']:
|
||||
|
|
|
|||
|
|
@ -6,8 +6,9 @@ from django.db import models
|
|||
|
||||
from wagtail.snippets.models import register_snippet
|
||||
|
||||
from wagtail.models import Page
|
||||
from wagtail.admin.panels import FieldPanel
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.images.edit_handlers import ImageChooserPanel
|
||||
from wagtail.admin.edit_handlers import FieldPanel, PageChooserPanel
|
||||
|
||||
from .forms import ContactForm
|
||||
from ..util import TranslatedField
|
||||
|
|
@ -16,7 +17,6 @@ from ..util import TranslatedField
|
|||
SOCIAL_NETWORK_SUPPORTED = (
|
||||
('twitter', 'Twitter'),
|
||||
('facebook', 'Facebook'),
|
||||
('linkedin', 'LinkedIn'),
|
||||
)
|
||||
|
||||
@register_snippet
|
||||
|
|
@ -47,8 +47,6 @@ class SocialContact(models.Model):
|
|||
return "https://twitter.com/%s" % self.profile
|
||||
elif self.network == 'facebook':
|
||||
return "https://facebook.com/%s" % self.profile
|
||||
elif self.network == 'linkedin':
|
||||
return "https://www.linkedin.com/company/%s" % self.profile
|
||||
return "#"
|
||||
def __str__(self):
|
||||
return "%s" % self.network
|
||||
|
|
@ -99,15 +97,6 @@ class Contact(models.Model):
|
|||
on_delete=models.SET_NULL,
|
||||
related_name='+',
|
||||
)
|
||||
news_letter_url = models.URLField(default='', blank=True)
|
||||
news_letter_text = models.CharField(default='', blank=True, max_length=256)
|
||||
news_letter_text_fr = models.CharField(max_length=255, blank=True, default="")
|
||||
news_letter_text_en = models.CharField(max_length=255, blank=True, default="")
|
||||
trans_news_letter_text = TranslatedField(
|
||||
'news_letter_text',
|
||||
'news_letter_text_fr',
|
||||
'news_letter_text_en',
|
||||
)
|
||||
|
||||
panels = Page.content_panels + [
|
||||
FieldPanel('title_fr'),
|
||||
|
|
@ -117,19 +106,12 @@ class Contact(models.Model):
|
|||
FieldPanel('phone'),
|
||||
FieldPanel('email'),
|
||||
FieldPanel('www'),
|
||||
FieldPanel('logo'), # ImageChooserPanel no longer needed in Wagtail 3+
|
||||
ImageChooserPanel('logo'),
|
||||
FieldPanel('color'),
|
||||
FieldPanel('style'),
|
||||
FieldPanel('map_url'),
|
||||
FieldPanel('analytics'),
|
||||
FieldPanel('contact_form'), # PageChooserPanel no longer needed in Wagtail 3+
|
||||
]
|
||||
|
||||
panels = panels + [
|
||||
FieldPanel('news_letter_url', classname="full"),
|
||||
FieldPanel('news_letter_text'),
|
||||
FieldPanel('news_letter_text_fr'),
|
||||
FieldPanel('news_letter_text_en'),
|
||||
PageChooserPanel('contact_form', 'home.ContactForm'),
|
||||
]
|
||||
|
||||
def phone_link(self):
|
||||
|
|
|
|||
|
|
@ -1,112 +0,0 @@
|
|||
"""
|
||||
Detect and repair Wagtail rich-text content with inline/entity tags wrapping
|
||||
block-level elements — a pattern that crashes the Draftail editor's
|
||||
HTML->ContentState converter (`AssertionError: End of block reached without
|
||||
closing inline style/entity elements`).
|
||||
|
||||
Used by:
|
||||
- the `fix_rich_text` management command, for one-off cleanup of legacy data
|
||||
- the pre-save signal handlers in `signals.py`, to prevent future paste-from-Word
|
||||
content from re-introducing the same patterns
|
||||
"""
|
||||
from bs4 import BeautifulSoup, NavigableString
|
||||
|
||||
from wagtail.admin.rich_text.converters.contentstate import ContentstateConverter
|
||||
from wagtail.rich_text import features as feature_registry
|
||||
|
||||
|
||||
_INLINE_TAGS = {"b", "i", "em", "strong", "code", "s", "sub", "sup", "u"}
|
||||
_ENTITY_TAGS = {"a"}
|
||||
_PROBLEM_WRAPPERS = list(_INLINE_TAGS | _ENTITY_TAGS)
|
||||
_BLOCK_TAGS = ["p", "h1", "h2", "h3", "h4", "h5", "h6", "hr",
|
||||
"ul", "ol", "li", "blockquote", "div"]
|
||||
|
||||
|
||||
_converter = None
|
||||
|
||||
|
||||
def get_converter():
|
||||
"""Return a ContentstateConverter loaded with every Draftail-registered feature.
|
||||
|
||||
Lazy-built once per process — the converter init walks the feature registry
|
||||
which isn't ready at import time.
|
||||
"""
|
||||
global _converter
|
||||
if _converter is None:
|
||||
feature_registry.get_default_features()
|
||||
plugins_by_editor = getattr(feature_registry, "plugins_by_editor", {})
|
||||
draftail_plugins = list(plugins_by_editor.get("draftail", {}).keys())
|
||||
default_features = feature_registry.get_default_features()
|
||||
all_features = sorted(set(draftail_plugins) | set(default_features))
|
||||
_converter = ContentstateConverter(features=all_features)
|
||||
return _converter
|
||||
|
||||
|
||||
def is_broken(html):
|
||||
"""Return True if running `html` through the Draftail converter would crash."""
|
||||
if not html:
|
||||
return False
|
||||
try:
|
||||
get_converter().from_database_format(html)
|
||||
except Exception:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _has_significant_other_content(tag, block_child):
|
||||
for c in tag.children:
|
||||
if c is block_child:
|
||||
continue
|
||||
if isinstance(c, NavigableString):
|
||||
if c.strip():
|
||||
return True
|
||||
else:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def repair(html):
|
||||
"""Return repaired HTML by lifting block-wrapping inline/entity tags.
|
||||
|
||||
For every inline (b/i/em/strong/code/s/sub/sup/u) or entity (a) tag that
|
||||
contains a block descendant:
|
||||
- If it has exactly one block child and nothing else significant, invert
|
||||
the nesting: `<a><p>X</p></a>` -> `<p><a>X</a></p>` (preserves link/style).
|
||||
- Otherwise, unwrap the offending tag (drops the styling, keeps content).
|
||||
"""
|
||||
soup = BeautifulSoup(html, "html.parser")
|
||||
while True:
|
||||
offender = None
|
||||
for tag in soup.find_all(_PROBLEM_WRAPPERS):
|
||||
if tag.find(_BLOCK_TAGS):
|
||||
offender = tag
|
||||
break
|
||||
if offender is None:
|
||||
break
|
||||
|
||||
block_children = [c for c in offender.children if getattr(c, "name", None) in _BLOCK_TAGS]
|
||||
if len(block_children) == 1 and not _has_significant_other_content(offender, block_children[0]):
|
||||
block = block_children[0]
|
||||
new_wrapper = soup.new_tag(offender.name, attrs=dict(offender.attrs))
|
||||
for child in list(block.contents):
|
||||
new_wrapper.append(child.extract())
|
||||
block.append(new_wrapper)
|
||||
offender.unwrap()
|
||||
else:
|
||||
offender.unwrap()
|
||||
return str(soup)
|
||||
|
||||
|
||||
def sanitize_if_needed(html):
|
||||
"""Return repaired HTML if the input would crash the editor, else the input unchanged.
|
||||
|
||||
Fast path: if the converter accepts the HTML, return it untouched (no parsing).
|
||||
Repair path: if the cleaned HTML still wouldn't load, return the original to
|
||||
avoid making things worse.
|
||||
"""
|
||||
if not is_broken(html):
|
||||
return html
|
||||
cleaned = repair(html)
|
||||
if is_broken(cleaned):
|
||||
return html
|
||||
return cleaned
|
||||
|
|
@ -4,10 +4,10 @@ from datetime import date
|
|||
|
||||
from django.utils.dateformat import DateFormat
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.translation import gettext_lazy as _ # ugettext_lazy deprecated in Django 4.0
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from wagtail.models import Page # wagtail.core.models moved to wagtail.models in 3.0
|
||||
from wagtail.contrib.search_promotions.models import Query
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.search.models import Query
|
||||
from wagtail.contrib.routable_page.models import RoutablePageMixin, route
|
||||
|
||||
class HomePage(RoutablePageMixin, Page):
|
||||
|
|
|
|||
|
|
@ -1,140 +0,0 @@
|
|||
"""
|
||||
Pre-save signal handlers that sanitize rich-text content before it lands
|
||||
in the database.
|
||||
|
||||
The Draftail editor renders the *latest revision* of a page (not the page
|
||||
row), so the most important hook is `Revision.pre_save` — that catches every
|
||||
admin draft-save and publish flow. `Page.pre_save` is a belt-and-braces
|
||||
catch for direct model writes (shell, migrations, etc).
|
||||
"""
|
||||
import json
|
||||
|
||||
from django.db.models.signals import pre_save
|
||||
from django.dispatch import receiver
|
||||
|
||||
from wagtail.models import Page
|
||||
from wagtail.fields import RichTextField, StreamField
|
||||
from wagtail.blocks import RichTextBlock
|
||||
|
||||
from .rich_text_sanitizer import sanitize_if_needed
|
||||
|
||||
try:
|
||||
from wagtail.models import Revision
|
||||
except ImportError: # pragma: no cover — fallback for older Wagtail layouts
|
||||
Revision = None
|
||||
|
||||
|
||||
def _sanitize_stream_items(items, stream_block):
|
||||
"""Sanitize a list of {type, value, id} dicts in place. Return True if any changed."""
|
||||
changed = False
|
||||
child_blocks = getattr(stream_block, "child_blocks", {})
|
||||
for item in items:
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
block_def = child_blocks.get(item.get("type"))
|
||||
if isinstance(block_def, RichTextBlock):
|
||||
value = item.get("value")
|
||||
if isinstance(value, str):
|
||||
cleaned = sanitize_if_needed(value)
|
||||
if cleaned != value:
|
||||
item["value"] = cleaned
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
|
||||
@receiver(pre_save)
|
||||
def sanitize_revision_content(sender, instance, **kwargs):
|
||||
"""Sanitize rich-text values inside Revision.content before it's persisted."""
|
||||
if Revision is None or sender is not Revision:
|
||||
return
|
||||
ct = getattr(instance, "content_type", None)
|
||||
if ct is None:
|
||||
return
|
||||
model_class = ct.model_class()
|
||||
if model_class is None or not issubclass(model_class, Page):
|
||||
return
|
||||
|
||||
raw = instance.content
|
||||
# Wagtail 4+ stores Revision.content as a JSONField (dict). Older Wagtail
|
||||
# versions and any string-cast paths may give a JSON-encoded string.
|
||||
if isinstance(raw, dict):
|
||||
content_dict = raw
|
||||
was_string = False
|
||||
elif isinstance(raw, str):
|
||||
try:
|
||||
content_dict = json.loads(raw)
|
||||
except (TypeError, ValueError):
|
||||
return
|
||||
was_string = True
|
||||
else:
|
||||
return
|
||||
if not isinstance(content_dict, dict):
|
||||
return
|
||||
|
||||
changed = False
|
||||
for field in model_class._meta.get_fields():
|
||||
if isinstance(field, RichTextField):
|
||||
value = content_dict.get(field.name)
|
||||
if isinstance(value, str):
|
||||
cleaned = sanitize_if_needed(value)
|
||||
if cleaned != value:
|
||||
content_dict[field.name] = cleaned
|
||||
changed = True
|
||||
elif isinstance(field, StreamField):
|
||||
value = content_dict.get(field.name)
|
||||
stream_block = field.stream_block
|
||||
if isinstance(value, list):
|
||||
if _sanitize_stream_items(value, stream_block):
|
||||
changed = True
|
||||
elif isinstance(value, str):
|
||||
try:
|
||||
items = json.loads(value)
|
||||
except (TypeError, ValueError):
|
||||
continue
|
||||
if isinstance(items, list) and _sanitize_stream_items(items, stream_block):
|
||||
content_dict[field.name] = json.dumps(items)
|
||||
changed = True
|
||||
|
||||
if changed:
|
||||
instance.content = json.dumps(content_dict) if was_string else content_dict
|
||||
|
||||
|
||||
@receiver(pre_save)
|
||||
def sanitize_page_fields(sender, instance, **kwargs):
|
||||
"""Sanitize rich-text fields on the Page model itself, for direct writes."""
|
||||
if not isinstance(instance, Page):
|
||||
return
|
||||
|
||||
for field in instance._meta.get_fields():
|
||||
if isinstance(field, RichTextField):
|
||||
value = getattr(instance, field.name, "") or ""
|
||||
cleaned = sanitize_if_needed(value)
|
||||
if cleaned != value:
|
||||
setattr(instance, field.name, cleaned)
|
||||
elif isinstance(field, StreamField):
|
||||
stream_value = getattr(instance, field.name, None)
|
||||
if stream_value is None:
|
||||
continue
|
||||
try:
|
||||
children = list(stream_value)
|
||||
except Exception:
|
||||
continue
|
||||
replacements = {}
|
||||
for i, child in enumerate(children):
|
||||
if not isinstance(child.block, RichTextBlock):
|
||||
continue
|
||||
v = child.value
|
||||
html = v.source if hasattr(v, "source") else str(v)
|
||||
cleaned = sanitize_if_needed(html)
|
||||
if cleaned != html:
|
||||
replacements[i] = cleaned
|
||||
if not replacements:
|
||||
continue
|
||||
new_list = []
|
||||
for i, child in enumerate(children):
|
||||
if i in replacements:
|
||||
new_list.append({"type": child.block_type, "value": replacements[i]})
|
||||
else:
|
||||
raw = child.block.get_prep_value(child.value)
|
||||
new_list.append({"type": child.block_type, "value": raw})
|
||||
setattr(instance, field.name, new_list)
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n wagtailcore_tags wagtailimages_tags %}
|
||||
{% load wagtailcore_tags wagtailimages_tags %}
|
||||
|
||||
{% block body_class %}template-{{ self.get_verbose_name|slugify }}{% endblock %}
|
||||
|
||||
|
|
@ -16,25 +16,11 @@
|
|||
</section>
|
||||
|
||||
{% else %}
|
||||
{% if page.header_image %}
|
||||
{% image page.header_image fill-1908x400 as img %}
|
||||
<div id="carousel-banner" class="slide">
|
||||
<div class="carousel-inner slick slick-initialized slick-slider" role="listbox">
|
||||
|
||||
<div class="slick-list draggable" tabindex="0"><div class="slick-track" style="opacity: 1; width: 1908px;"><div class="item slick-slide slick-active" data-slick-index="0" style="width: 1908px; position: relative; left: 0px; top: 0px; z-index: 900; opacity: 1;">
|
||||
<img style="background-image:url({{img.url}})">
|
||||
</div></div></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<section id="article-index" class="article-index-page">
|
||||
<div class="container">
|
||||
<h2>{{ page.trans_title }}</h2>
|
||||
<p class="lead">{{ page.trans_intro|richtext }}</p>
|
||||
{% if page.subscribe_action %}
|
||||
{% include 'home/subscription_form.html' %}
|
||||
{% endif %}
|
||||
<div class="article-body" role="main">
|
||||
{% for entry in articles %}
|
||||
<div class="item">
|
||||
|
|
@ -45,14 +31,6 @@
|
|||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<!-- Table content -->
|
||||
<div class="article-table table-program" role="main">
|
||||
{% for block in page.trans_table %}
|
||||
{% if block.block_type == 'table_en' or block.block_type == 'table_fr' or block.block_type == 'table_de' %}
|
||||
{% include_block block %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div><!-- /container -->
|
||||
</section>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{% load wagtailcore_tags wagtailimages_tags custom_filters %}
|
||||
{% load wagtailcore_tags wagtailimages_tags %}
|
||||
|
||||
{% if page.feed_image %}
|
||||
<div class="image">
|
||||
|
|
@ -23,10 +23,6 @@
|
|||
{% if block.value == 'gallery' %}
|
||||
{% include 'home/photo_gallery.html' %}
|
||||
{% endif %}
|
||||
{% elif block.block_type == 'iframe' %}
|
||||
{% autoescape off %}
|
||||
{{ block.value|richtext|ph_html_decode}}
|
||||
{% endautoescape %}
|
||||
{% elif block.block_type != 'info' %}
|
||||
<p class="block-{{ block.block_type }}">
|
||||
{% include_block block %}
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
<style>
|
||||
.table-program td, th {
|
||||
padding: 0 10px !important;
|
||||
}
|
||||
</style>
|
||||
<table class="table-program">
|
||||
{% if table_header %}
|
||||
<thead>
|
||||
<tr>
|
||||
{% for column in table_header %}
|
||||
<th>
|
||||
{% if column.strip %}
|
||||
{% if html_renderer %}
|
||||
{{ column.strip|safe|linebreaksbr }}
|
||||
{% else %}
|
||||
{{ column.strip|linebreaksbr }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
</thead>
|
||||
{% endif %}
|
||||
<tbody>
|
||||
{% for row in data %}
|
||||
<tr>
|
||||
{% for column in row %}
|
||||
{% if first_col_is_header and forloop.first %}
|
||||
<th>
|
||||
{% if column.strip %}
|
||||
{% if html_renderer %}
|
||||
{{ column.strip|safe|linebreaksbr }}
|
||||
{% else %}
|
||||
{{ column.strip|linebreaksbr }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% else %}
|
||||
<td>
|
||||
{% if column.strip %}
|
||||
{% if html_renderer %}
|
||||
{{ column.strip|safe|linebreaksbr }}
|
||||
{% else %}
|
||||
{{ column.strip|linebreaksbr }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
<a href="#" class="btn btn-sm btn-primary pull-right" style="text-transform: uppercase;" data-toggle="modal" data-target="#subscribeModal">{{ page.trans_subscribe_label }}</a>
|
||||
<div class="modal fade" id="subscribeModal" tabindex="-1" role="dialog" aria-labelledby="subscribeModal" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
<h4 class="modal-title" id="myModalLabel">{{ page.trans_subscribe_label }}</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div id="mc_embed_signup">
|
||||
<form action="{{page.subscribe_action}}" method="post" id="mc-embedded-subscribe-form" name="mc-embedded-subscribe-form" class="validate" target="_blank" novalidate>
|
||||
<div id="mc_embed_signup_scroll">
|
||||
<div class="indicates-required"><span class="asterisk"></span> indicates required</div>
|
||||
<div class="mc-field-group">
|
||||
<label for="mce-EMAIL">Mailadresse/ Adresse e-mail <span class="asterisk">*</span>
|
||||
</label>
|
||||
<input type="email" value="" name="EMAIL" class="required email" id="mce-EMAIL">
|
||||
</div>
|
||||
<div class="mc-field-group">
|
||||
<label for="mce-FNAME">Vorname/ Prénom <span class="asterisk">*</span>
|
||||
</label>
|
||||
<input type="text" value="" name="FNAME" class="required" id="mce-FNAME">
|
||||
</div>
|
||||
<div class="mc-field-group">
|
||||
<label for="mce-LNAME">Nachname/ Nom <span class="asterisk">*</span>
|
||||
</label>
|
||||
<input type="text" value="" name="LNAME" class="required" id="mce-LNAME">
|
||||
</div>
|
||||
<div class="mc-field-group input-group">
|
||||
<strong>Sprache/ Langue <span class="asterisk">*</span>
|
||||
</strong>
|
||||
<ul><li><input class="pull-left" type="radio" value="Deutsch" name="MMERGE9" id="mce-MMERGE9-0"><label class="pull-left" for="mce-MMERGE9-0">Deutsch</label></li>
|
||||
<li class="clearfix"></li>
|
||||
<li><input class="pull-left" type="radio" value="Français" name="MMERGE9" id="mce-MMERGE9-1"><label class="pull-left" for="mce-MMERGE9-1">Français</label></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div id="mce-responses" class="clear">
|
||||
<div class="response" id="mce-error-response" style="display:none"></div>
|
||||
<div class="response" id="mce-success-response" style="display:none"></div>
|
||||
</div> <!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups-->
|
||||
<div style="position: absolute; left: -5000px;" aria-hidden="true"><input type="text" name="b_8368e636ee0d8404d5452a0ef_77f535b64d" tabindex="-1" value=""></div>
|
||||
<div class="clear"><input type="submit" value="Subscribe" name="subscribe" id="mc-embedded-subscribe" class="button"></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
{% load wagtailcore_tags wagtailimages_tags %}
|
||||
{% load wagtailcore_tags wagtailimages_tags puput_tags %}
|
||||
|
||||
<!-- Front page news -->
|
||||
<section id="news">
|
||||
|
|
|
|||
|
|
@ -1,5 +0,0 @@
|
|||
{% if contact.trans_news_letter_text %}
|
||||
<div style="text-align: center">
|
||||
<a href="{{ contact.news_letter_url }}" style="border: 1px solid; padding: 15px;">{{ contact.trans_news_letter_text }}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
@ -3,6 +3,6 @@
|
|||
<ul class="dropdown-menu">
|
||||
<!--<li><a href="{% pageurl parent %}">{{ parent.title }}</a></li>-->
|
||||
{% for child in menuitems_children %}
|
||||
<li><a href="{% pageurl child %}">{{ child.trans_title }}</a></li>
|
||||
<li><a href="{% pageurl child %}">{{ child.title|title }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
|
|
|||
|
|
@ -35,19 +35,11 @@ def get_contacts(the_site):
|
|||
'socials': social_contacts
|
||||
}
|
||||
|
||||
|
||||
# Contact information (footer)
|
||||
@register.inclusion_tag('tags/contact_info.html')
|
||||
def contact_info(the_site):
|
||||
return get_contacts(the_site)
|
||||
|
||||
|
||||
# Newsletter (footer)
|
||||
@register.inclusion_tag('tags/news_letter.html')
|
||||
def news_letter(the_site):
|
||||
return get_contacts(the_site)
|
||||
|
||||
|
||||
# Contact form (footer)
|
||||
@register.inclusion_tag('tags/footer_form.html')
|
||||
def footer_form(the_site):
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from django import template
|
||||
from django.utils import translation
|
||||
from django.db.models import Case, When
|
||||
|
||||
from wagtail.models import Site, Page
|
||||
from wagtail.core.models import Site, Page
|
||||
from ..models.forms import ContactForm
|
||||
register = template.Library()
|
||||
|
||||
|
|
@ -55,20 +54,19 @@ def top_menu(context, parent, calling_page=None):
|
|||
menuitems = parent.get_children().live().in_menu().specific()
|
||||
for menuitem in menuitems:
|
||||
menuitem.show_dropdown = has_menu_children(menuitem)
|
||||
if calling_page:
|
||||
menuitem.active = (calling_page.url.startswith(menuitem.url)
|
||||
if calling_page else False)
|
||||
try:
|
||||
menuitem.title = menuitem.trans_title
|
||||
except AttributeError:
|
||||
pass
|
||||
menuitem.active = (calling_page.url.startswith(menuitem.url)
|
||||
if calling_page else False)
|
||||
try:
|
||||
menuitem.title = menuitem.trans_title
|
||||
except AttributeError:
|
||||
pass
|
||||
return {
|
||||
'calling_page': calling_page,
|
||||
'menuitems': menuitems,
|
||||
'request': context['request'],
|
||||
}
|
||||
|
||||
def menuitems_children(parent, context=None):
|
||||
def menuitems_children(parent):
|
||||
remove_mitglied = False
|
||||
remove_devenez = False
|
||||
if 'Qui sommes-nous' in parent.title:
|
||||
|
|
@ -79,45 +77,20 @@ def menuitems_children(parent, context=None):
|
|||
items_to_remove = []
|
||||
for menuitem in menuitems_children:
|
||||
try:
|
||||
if type(menuitem) == ContactForm:
|
||||
menuitem.title = menuitem.title.title()
|
||||
else:
|
||||
menuitem.title = menuitem.trans_title
|
||||
if 'devenez' in menuitem.title.lower() and remove_devenez:
|
||||
items_to_remove.append(menuitem)
|
||||
elif 'mitglied werden' in menuitem.title.lower() and remove_mitglied:
|
||||
items_to_remove.append(menuitem)
|
||||
menuitem.title = menuitem.trans_title
|
||||
except AttributeError as ae:
|
||||
except AttributeError:
|
||||
pass
|
||||
for item in items_to_remove:
|
||||
menuitems_children = menuitems_children & Page.objects.not_page(item)
|
||||
if context:
|
||||
request = context['request']
|
||||
site = Site.find_for_request(request)
|
||||
if site.hostname in ['sphc.ch', 'public-health.ch']:
|
||||
# custom page here is the terms and conditions page for public-health.ch and sphc.ch (common)
|
||||
if request.__dict__.get('LANGUAGE_CODE', 'de').lower() == 'en':
|
||||
custom_page_id = 1020
|
||||
else:
|
||||
custom_page_id = 994
|
||||
custom_page = Page.objects.get(id=custom_page_id)
|
||||
menuitems_children = add_item_to_page_queryset(menuitems_children, custom_page)
|
||||
for menuitem in menuitems_children:
|
||||
try:
|
||||
menuitem.title = menuitem.trans_title
|
||||
except AttributeError as aee:
|
||||
pass
|
||||
return menuitems_children
|
||||
|
||||
def add_item_to_page_queryset(queryset, custom_page):
|
||||
# Convert queryset to a list
|
||||
pages_list = list(queryset)
|
||||
# Add the custom page to the list
|
||||
pages_list.append(custom_page)
|
||||
# Create a new ordered queryset based on the list order
|
||||
page_ids = [page.id for page in pages_list]
|
||||
preserved_order = Case(*[When(pk=pk, then=pos) for pos, pk in enumerate(page_ids)])
|
||||
new_queryset = Page.objects.filter(id__in=page_ids).order_by(preserved_order).specific()
|
||||
return new_queryset
|
||||
|
||||
|
||||
# Retrieves the children of the top menu items for the drop downs
|
||||
@register.inclusion_tag('tags/top_menu_children.html', takes_context=True)
|
||||
def top_menu_children(context, parent):
|
||||
|
|
@ -132,6 +105,6 @@ def top_menu_children(context, parent):
|
|||
def footer_menu(context, parent, calling_page=None):
|
||||
return {
|
||||
'calling_page': calling_page,
|
||||
'menuitems': menuitems_children(parent, context),
|
||||
'menuitems': menuitems_children(parent),
|
||||
'request': context['request'],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
from django.shortcuts import render
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
|
||||
from wagtail.models import Page
|
||||
from wagtail.contrib.search_promotions.models import Query
|
||||
from wagtail.core.models import Page
|
||||
from wagtail.search.models import Query
|
||||
|
||||
|
||||
def search(request):
|
||||
|
|
|
|||
|
|
@ -18,9 +18,6 @@ import os
|
|||
PROJECT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
BASE_DIR = os.path.dirname(PROJECT_DIR)
|
||||
|
||||
# Django 4.0 requires DEFAULT_AUTO_FIELD
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
# Project custom settings
|
||||
|
||||
NEWS_ENTRIES_HOME_PAGE = 2
|
||||
|
|
@ -41,13 +38,12 @@ INSTALLED_APPS = [
|
|||
|
||||
'wagtail.contrib.routable_page',
|
||||
'wagtail.contrib.sitemaps',
|
||||
'wagtail_modeladmin', # Separate package for ModelAdmin in Wagtail 5+
|
||||
'wagtail.contrib.modeladmin',
|
||||
'wagtail.contrib.settings',
|
||||
'wagtail.contrib.forms',
|
||||
'wagtail.contrib.redirects',
|
||||
'wagtail.contrib.search_promotions',
|
||||
# "wagtail.contrib.legacy.richtext", # Removed in Wagtail 3.0
|
||||
'wagtail.contrib.table_block',
|
||||
"wagtail.contrib.legacy.richtext",
|
||||
|
||||
'wagtail.embeds',
|
||||
'wagtail.sites',
|
||||
|
|
@ -57,8 +53,7 @@ INSTALLED_APPS = [
|
|||
'wagtail.images',
|
||||
'wagtail.search',
|
||||
'wagtail.admin',
|
||||
# 'wagtail.core' renamed to 'wagtail' in Wagtail 3.0
|
||||
'wagtail',
|
||||
'wagtail.core',
|
||||
|
||||
'wagtail.api.v2',
|
||||
'rest_framework',
|
||||
|
|
@ -67,7 +62,7 @@ INSTALLED_APPS = [
|
|||
'modelcluster',
|
||||
'compressor',
|
||||
'taggit',
|
||||
# 'puput', # Needs update for Wagtail 6.x compatibility
|
||||
'puput',
|
||||
'anymail',
|
||||
'feedler',
|
||||
'colorful',
|
||||
|
|
@ -113,9 +108,6 @@ TEMPLATES = [
|
|||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
'libraries': {
|
||||
'custom_filters': 'publichealth.home.custom_filters',
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
|
|
@ -167,8 +159,7 @@ LANGUAGE_CODE = 'de' # default language
|
|||
TIME_ZONE = 'Europe/Zurich'
|
||||
|
||||
USE_I18N = True
|
||||
# USE_L10N is deprecated in Django 4.0 and always True
|
||||
# USE_L10N = True
|
||||
USE_L10N = True
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
|
|
@ -185,16 +176,9 @@ STATICFILES_DIRS = [
|
|||
os.path.join(PROJECT_DIR, 'static'),
|
||||
]
|
||||
|
||||
# Django 4.2 uses STORAGES setting instead of STATICFILES_STORAGE
|
||||
# Whitenoise compression and caching support
|
||||
# http://whitenoise.evans.io/en/stable/django.html
|
||||
STORAGES = {
|
||||
"default": {
|
||||
"BACKEND": "django.core.files.storage.FileSystemStorage",
|
||||
},
|
||||
"staticfiles": {
|
||||
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
|
||||
},
|
||||
}
|
||||
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'
|
||||
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||
STATIC_URL = '/static/'
|
||||
|
|
@ -214,10 +198,9 @@ COMPRESS_PRECOMPILERS = [
|
|||
|
||||
WAGTAIL_SITE_NAME = "Public Health Schweiz"
|
||||
|
||||
# Wagtail 5 uses Draftail as default editor
|
||||
WAGTAILADMIN_RICH_TEXT_EDITORS = {
|
||||
'default': {
|
||||
'WIDGET': 'wagtail.admin.rich_text.DraftailRichTextArea'
|
||||
'WIDGET': 'wagtail.admin.rich_text.HalloRichTextArea'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
import logging
|
||||
import os
|
||||
import sys
|
||||
import dj_database_url
|
||||
|
||||
from logging.handlers import RotatingFileHandler
|
||||
from .base import *
|
||||
|
||||
# Do not set SECRET_KEY, Postgres or LDAP password or any other sensitive data here.
|
||||
|
|
@ -43,13 +40,9 @@ if 'SECRET_KEY' in env:
|
|||
|
||||
if 'ALLOWED_HOSTS' in env:
|
||||
ALLOWED_HOSTS = env['ALLOWED_HOSTS'].split(',')
|
||||
# Django 4.1+ requires scheme in CSRF_TRUSTED_ORIGINS
|
||||
CSRF_TRUSTED_ORIGINS = ['https://' + host for host in ALLOWED_HOSTS if host != '*']
|
||||
|
||||
if 'PRIMARY_HOST' in env:
|
||||
BASE_URL = 'http://%s/' % env['PRIMARY_HOST']
|
||||
# Also add PRIMARY_HOST to CSRF_TRUSTED_ORIGINS
|
||||
CSRF_TRUSTED_ORIGINS = ['https://%s' % env['PRIMARY_HOST'], 'http://%s' % env['PRIMARY_HOST']]
|
||||
|
||||
if 'SERVER_EMAIL' in env:
|
||||
SERVER_EMAIL = env['SERVER_EMAIL']
|
||||
|
|
@ -76,16 +69,23 @@ if 'MEDIA_DIR' in env:
|
|||
MEDIA_ROOT = env['MEDIA_DIR']
|
||||
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': os.environ.get('SQL_ENGINE', 'django.db.backends.postgresql'),
|
||||
'NAME': os.environ.get('POSTGRES_DB', 'app'),
|
||||
'USER': os.environ.get('POSTGRES_USER', 'app'),
|
||||
'PASSWORD': os.environ.get('POSTGRES_PASSWORD', ''),
|
||||
'HOST': os.environ.get('POSTGRES_HOST', 'localhost'),
|
||||
'PORT': os.environ.get('POSTGRES_PORT', 5432),
|
||||
# Database
|
||||
|
||||
if 'DATABASE_URL' in os.environ:
|
||||
DATABASES = {'default': dj_database_url.config()}
|
||||
|
||||
else:
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql_psycopg2',
|
||||
'NAME': env.get('PGDATABASE', APP_NAME),
|
||||
'CONN_MAX_AGE': 600, # number of seconds database connections should persist for
|
||||
|
||||
# User, host and port can be configured by the PGUSER, PGHOST and
|
||||
# PGPORT environment variables (these get picked up by libpq).
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Email via ESP
|
||||
|
||||
|
|
@ -149,46 +149,42 @@ LOGGING = {
|
|||
'level': 'ERROR',
|
||||
'class': 'django.utils.log.AdminEmailHandler',
|
||||
},
|
||||
'console': {
|
||||
'level': 'DEBUG',
|
||||
'class': 'logging.StreamHandler',
|
||||
'stream': sys.stdout,
|
||||
'formatter': 'simple',
|
||||
},
|
||||
},
|
||||
'formatters': {
|
||||
'default': {
|
||||
'format': '[%(asctime)s] (%(process)d/%(thread)d) %(name)s %(levelname)s: %(message)s'
|
||||
},
|
||||
'simple': {
|
||||
'format': '[%(asctime)s] (%(process)d/%(thread)d) %(name)s %(levelname)s: %(message)s'
|
||||
'verbose': '[%(asctime)s] (%(process)d/%(thread)d) %(name)s %(levelname)s: %(message)s'
|
||||
}
|
||||
},
|
||||
'loggers': {
|
||||
'feedler': {
|
||||
'handlers': ['console'],
|
||||
'handlers': [],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'publichealth': {
|
||||
'handlers': ['console'],
|
||||
'handlers': [],
|
||||
'level': 'INFO',
|
||||
'propagate': False,
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'wagtail': {
|
||||
'handlers': ['console'],
|
||||
'handlers': [],
|
||||
'level': 'WARNING',
|
||||
'propagate': False,
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'django.request': {
|
||||
'handlers': ['console', 'mail_admins'],
|
||||
'level': 'WARNING',
|
||||
'propagate': False,
|
||||
},
|
||||
'django.security': {
|
||||
'handlers': ['console', 'mail_admins'],
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': False,
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'django.security': {
|
||||
'handlers': ['mail_admins'],
|
||||
'level': 'ERROR',
|
||||
'propagate': False,
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -198,7 +194,7 @@ if 'LOG_DIR' in env:
|
|||
# Public Health Schweiz log
|
||||
LOGGING['handlers']['publichealth_file'] = {
|
||||
'level': 'INFO',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'class': 'cloghandler.ConcurrentRotatingFileHandler',
|
||||
'filename': os.path.join(env['LOG_DIR'], 'publichealth.log'),
|
||||
'maxBytes': 5242880, # 5MB
|
||||
'backupCount': 5
|
||||
|
|
@ -208,7 +204,7 @@ if 'LOG_DIR' in env:
|
|||
# Wagtail log
|
||||
LOGGING['handlers']['wagtail_file'] = {
|
||||
'level': 'WARNING',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'class': 'cloghandler.ConcurrentRotatingFileHandler',
|
||||
'filename': os.path.join(env['LOG_DIR'], 'wagtail.log'),
|
||||
'maxBytes': 5242880, # 5MB
|
||||
'backupCount': 5
|
||||
|
|
@ -218,7 +214,7 @@ if 'LOG_DIR' in env:
|
|||
# Error log
|
||||
LOGGING['handlers']['errors_file'] = {
|
||||
'level': 'ERROR',
|
||||
'class': 'logging.handlers.RotatingFileHandler',
|
||||
'class': 'cloghandler.ConcurrentRotatingFileHandler',
|
||||
'filename': os.path.join(env['LOG_DIR'], 'error.log'),
|
||||
'maxBytes': 5242880, # 5MB
|
||||
'backupCount': 5
|
||||
|
|
|
|||
|
|
@ -128,12 +128,5 @@ $slider-nav: 200px;
|
|||
|
||||
// Fix Bootstrap 3 warning
|
||||
.media, .media-body { zoom: none !important; transform: scale(1); }
|
||||
|
||||
#mc_embed_signup input[type=radio] {
|
||||
-webkit-appearance: none; padding: 0px;margin-top: 0px;
|
||||
}
|
||||
#mc_embed_signup {
|
||||
background:#fff; clear:left; font:14px Helvetica,Arial,sans-serif;
|
||||
}
|
||||
|
||||
@import "subsites";
|
||||
|
|
|
|||
|
|
@ -58,28 +58,15 @@ section {
|
|||
}
|
||||
|
||||
// Responsive image/video classes
|
||||
// Note: Wagtail 4.0+ removed the .rich-text wrapper div,
|
||||
// so we target container elements directly.
|
||||
.rich-text img,
|
||||
.article-body img,
|
||||
.lead img {
|
||||
.rich-text img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
// Wagtail 4.0+ no longer wraps embedded rich-text images in a <p> block, so
|
||||
// floating them made supporter/sponsor logos collapse side by side and let
|
||||
// trailing text (captions, contact blocks) wrap beside the last logo. Render
|
||||
// rich-text images as stacked block elements instead: logos sit one under the
|
||||
// other and any following text drops below them. The left/right alignment
|
||||
// classes now position the block via auto margins rather than floating.
|
||||
.richtext-image.left,
|
||||
.richtext-image.right{
|
||||
display:block;
|
||||
float:none;
|
||||
clear:both;
|
||||
.richtext-image.left{
|
||||
float:left;
|
||||
}
|
||||
.richtext-image.right{
|
||||
margin-left:auto;
|
||||
float:right;
|
||||
}
|
||||
.responsive-object {
|
||||
position: relative;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
// Language menu hack, until this is configurable
|
||||
nav .language-nav a[lang='en'] { display: inline; }
|
||||
|
||||
//#footer, .contact-nav .link { display: none; }
|
||||
#footer, .contact-nav .link { display: none; }
|
||||
|
||||
a.navbar-brand {
|
||||
height: 60px;
|
||||
|
|
@ -49,7 +49,6 @@
|
|||
html[lang='fr'] .site-ngo-allianz-ebk .navbar-brand {
|
||||
background-image: url('/static/org/NGO-Allianz_Logo-fr.jpg');
|
||||
}
|
||||
|
||||
// -site-ngo-allianz-ebk
|
||||
|
||||
// Custom styles for gesundheitsmanifest.ch
|
||||
|
|
@ -62,18 +61,3 @@ html[lang='fr'] .site-ngo-allianz-ebk .navbar-brand {
|
|||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
.site-dev-pro-salutech {
|
||||
.navbar-brand {
|
||||
width: 240px;
|
||||
height: 64px;
|
||||
background-size: contain !important;
|
||||
background-repeat: no-repeat;
|
||||
background-image: url('/static/images/pro-salute-logo.png');
|
||||
|
||||
font-size: 0px !important;
|
||||
img { display: none !important; }
|
||||
}
|
||||
#carousel-banner .carousel-caption { display: none; }
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.3 KiB |
|
|
@ -11,6 +11,6 @@
|
|||
|
||||
<h2>Sorry, there seems to be an error. Please try again soon.</h2>
|
||||
|
||||
<p>In case of persistent issues, write to <b>info@ungleich.ch</b></p>
|
||||
<p>In case of persistent issues, write to <b>info@datalets.ch</b></p>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@
|
|||
<meta name="description" content="{% block description %}{% endblock %}">
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
{% if page.subscribe_action %}
|
||||
<link href="//cdn-images.mailchimp.com/embedcode/classic-10_7.css" rel="stylesheet" type="text/css">
|
||||
{% endif %}
|
||||
{% compress css %}
|
||||
<link rel="stylesheet" type="text/x-scss" href="{% static 'css/main.scss' %}">
|
||||
{% endcompress %}
|
||||
|
|
@ -38,28 +35,7 @@
|
|||
<script src="{% static 'libs/slick-carousel/slick/slick.min.js' %}"></script>
|
||||
<script src="{% static 'libs/slick-lightbox/dist/slick-lightbox.js' %}"></script>
|
||||
<script src="{% static 'libs/cookieconsent/build/cookieconsent.min.js' %}"></script>
|
||||
{% if page.subscribe_action %}
|
||||
<script type='text/javascript' src='//s3.amazonaws.com/downloads.mailchimp.com/js/mc-validate.js'></script>
|
||||
<script type='text/javascript'>(function($) {
|
||||
window.fnames = new Array();
|
||||
window.ftypes = new Array();
|
||||
fnames0='EMAIL';
|
||||
ftypes0='email';
|
||||
fnames1='FNAME';
|
||||
ftypes1='text';
|
||||
fnames2='LNAME';
|
||||
ftypes2='text';
|
||||
fnames9='MMERGE9';
|
||||
ftypes9='radio';
|
||||
$.extend($.validator.messages, {
|
||||
required: "Dieses Feld ist ein Pflichtfeld.",
|
||||
maxlength: $.validator.format("Geben Sie bitte maximal {0} Zeichen ein."),
|
||||
minlength: $.validator.format("Geben Sie bitte mindestens {0} Zeichen ein."),
|
||||
email: "Geben Sie bitte eine gültige E-Mail Adresse ein.",
|
||||
});}(jQuery));
|
||||
var $mcj = jQuery.noConflict(true);
|
||||
</script>
|
||||
{% endif %}
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
|
||||
{% compress js %}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@
|
|||
</div>
|
||||
<div class="col-md-4" id="contact-form">
|
||||
{% footer_form the_site=the_site %}
|
||||
{% news_letter the_site=the_site %}
|
||||
</div>
|
||||
</div><!-- /row -->
|
||||
</div><!-- /container -->
|
||||
|
|
@ -32,7 +31,7 @@
|
|||
|
||||
<div class="copyright">
|
||||
{% contact_name the_site=the_site %}
|
||||
© 2022
|
||||
© 2020
|
||||
• <a href="https://public-health.ch/privacy/">Privacy</a>
|
||||
• <a href="https://public-health.ch/impressum/">Impressum</a>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,6 @@
|
|||
{% get_site_root as site_root %}
|
||||
{% get_site as the_site %}
|
||||
|
||||
|
||||
<style>
|
||||
</style>
|
||||
<nav class="navbar-pre navbar-fixed-top">
|
||||
<div class="container">
|
||||
<div class="nav">
|
||||
|
|
@ -30,16 +27,9 @@
|
|||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
|
||||
<a class="navbar-brand" href="{% pageurl site_root %}">
|
||||
|
||||
{% if the_site|default:""|truncatechars:"255" == "dev.pro-salute.ch"%}
|
||||
<img src="{% static 'images/pro-salute-logo.png' %}" alt="Pro Salute Logo">
|
||||
{% else %}
|
||||
<img src="{% static 'images/public-health-logo-sign.png' %}" alt="[logo]">
|
||||
{% endif %}
|
||||
|
||||
<span class="hidden-xs">{% contact_name the_site=the_site %}</span></a>
|
||||
<a class="navbar-brand" href="{% pageurl site_root %}">
|
||||
<img src="{% static 'images/public-health-logo-sign.png' %}" alt="[logo]">
|
||||
<span class="hidden-xs">{% contact_name the_site=the_site %}</span></a>
|
||||
</div>
|
||||
|
||||
{% block menu %}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,30 @@
|
|||
from django.urls import include, re_path, path
|
||||
from django.conf.urls import include, url
|
||||
from django.conf import settings
|
||||
from django.contrib import admin
|
||||
from django.conf.urls.i18n import i18n_patterns
|
||||
|
||||
from wagtail.admin import urls as wagtailadmin_urls
|
||||
from wagtail.documents import urls as wagtaildocs_urls
|
||||
from wagtail import urls as wagtail_urls # wagtail.core renamed to wagtail in 3.0
|
||||
from wagtail.core import urls as wagtail_urls
|
||||
from wagtail.contrib.sitemaps.views import sitemap
|
||||
|
||||
# from puput import urls as puput_urls # Commented: needs Wagtail 6 update
|
||||
from puput import urls as puput_urls
|
||||
from feedler import urls as feedler_urls
|
||||
|
||||
from publichealth.search import views as search_views
|
||||
|
||||
urlpatterns = [
|
||||
# path('', include(puput_urls)), # Commented: needs Wagtail 6 update
|
||||
path('', include(feedler_urls)),
|
||||
url(r'', include(puput_urls)),
|
||||
url(r'', include(feedler_urls)),
|
||||
|
||||
path('django-admin/', admin.site.urls),
|
||||
url(r'^django-admin/', admin.site.urls),
|
||||
|
||||
path('admin/', include(wagtailadmin_urls)),
|
||||
path('documents/', include(wagtaildocs_urls)),
|
||||
url(r'^admin/', include(wagtailadmin_urls)),
|
||||
url(r'^documents/', include(wagtaildocs_urls)),
|
||||
|
||||
path('search/', search_views.search, name='search'),
|
||||
url(r'^search/$', search_views.search, name='search'),
|
||||
|
||||
re_path(r'^sitemap\.xml$', sitemap),
|
||||
url('^sitemap\.xml$', sitemap),
|
||||
]
|
||||
|
||||
|
||||
|
|
@ -39,13 +39,13 @@ if settings.DEBUG:
|
|||
|
||||
# Add views for testing 404 and 500 templates
|
||||
urlpatterns += [
|
||||
path('test404/', TemplateView.as_view(template_name='404.html')),
|
||||
path('test500/', TemplateView.as_view(template_name='500.html')),
|
||||
url(r'^test404/$', TemplateView.as_view(template_name='404.html')),
|
||||
url(r'^test500/$', TemplateView.as_view(template_name='500.html')),
|
||||
]
|
||||
|
||||
|
||||
urlpatterns += i18n_patterns(
|
||||
# These URLs will have /<language_code>/ appended to the beginning
|
||||
|
||||
path('', include(wagtail_urls)),
|
||||
url(r'', include(wagtail_urls)),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
# Django 4.0 Upgrade - Python 3.13 compatible versions
|
||||
|
||||
# Core
|
||||
Django==4.0.10
|
||||
wagtail==3.0.3
|
||||
elasticsearch==5.5.3
|
||||
|
||||
# Database
|
||||
psycopg2-binary
|
||||
dj-database-url==1.2.0
|
||||
|
||||
# Content
|
||||
# Note: puput may need manual fixing for Wagtail 3.0
|
||||
# puput==2.0.0
|
||||
guess-language-spirit==0.5.3
|
||||
|
||||
# Caching
|
||||
django-redis==5.2.0
|
||||
redis
|
||||
|
||||
# Frontend
|
||||
django-anymail==9.0
|
||||
django-colorful==1.3
|
||||
django-contrib-comments==2.2.0
|
||||
django-crispy-forms==1.14.0
|
||||
django-el-pagination==3.3.0
|
||||
django-compressor
|
||||
django-libsass==0.9
|
||||
django-social-share==2.3.0
|
||||
libsass
|
||||
Pillow
|
||||
|
||||
# Runtime dependencies
|
||||
gunicorn
|
||||
whitenoise
|
||||
python-dotenv
|
||||
unidecode
|
||||
|
||||
# Additional Django 4.0 dependencies
|
||||
django-configurations
|
||||
django-taggit
|
||||
djangorestframework
|
||||
django-modelcluster
|
||||
|
|
@ -1,54 +1,33 @@
|
|||
# Updated: 2025-09-18 - Django 5.0 Upgrade
|
||||
# Updated: 21.07.2021
|
||||
|
||||
# Core
|
||||
Django==5.0.9 # Django 5.0 (latest stable in 5.0 series)
|
||||
wagtail==6.0.6 # Wagtail 6.0 supports Django 5.0
|
||||
elasticsearch==7.17.9 # Compatible with Django 5.0
|
||||
wagtail==2.12.5
|
||||
elasticsearch>=5.0.0,<6.0.0
|
||||
|
||||
# Database
|
||||
psycopg2-binary==2.9.10 # Compatible with Django 5.0 and Python 3.13
|
||||
dj-database-url==2.2.0 # Updated for Django 5.0
|
||||
psycopg2-binary>=2.8.6,<2.9
|
||||
dj-database-url==0.5.0
|
||||
|
||||
# Content
|
||||
# Note: puput needs update or replacement for Wagtail 6.x
|
||||
# puput==2.0.0
|
||||
puput==1.1.1
|
||||
guess-language-spirit==0.5.3
|
||||
|
||||
# Caching
|
||||
django-redis==5.4.0 # Compatible with Django 5.0
|
||||
redis==5.0.8
|
||||
django-redis==5.0.0
|
||||
|
||||
# Frontend
|
||||
django-anymail==11.0 # Updated for Django 5.0
|
||||
django-anymail==8.4
|
||||
django-colorful==1.3
|
||||
django-contrib-comments==2.2.0 # May need testing with Django 5.0
|
||||
django-crispy-forms==2.3 # Updated for Django 5.0
|
||||
django-contrib-comments==2.1.0
|
||||
django-crispy-forms==1.12.0
|
||||
django-el-pagination==3.3.0
|
||||
django-compressor==4.5.1 # Updated for Django 5.0
|
||||
django-social-share==2.3.0 # May need testing with Django 5.0
|
||||
libsass==0.23.0 # Updated version
|
||||
Pillow==10.4.0 # Updated for Django 5.0 and Python 3.12+
|
||||
django-libsass==0.8
|
||||
django-social-share==2.2.1
|
||||
libsass==0.21.0
|
||||
Pillow==8.2.0
|
||||
|
||||
# Runtime dependencies
|
||||
gunicorn
|
||||
whitenoise
|
||||
python-dotenv
|
||||
#ConcurrentLogHandler
|
||||
unidecode
|
||||
|
||||
# Additional dependencies for Django 5.0 and Wagtail 6
|
||||
django-configurations
|
||||
django-taggit>=5.0.0 # Required by Wagtail 6
|
||||
wagtail-modeladmin==2.0.0 # Separate package for ModelAdmin in Wagtail 5+
|
||||
djangorestframework>=3.15.0 # Required by Wagtail API
|
||||
django-modelcluster>=6.3 # Required by Wagtail 6
|
||||
django-treebeard>=4.7 # Required by Wagtail 6
|
||||
django-filter>=23.3,<24 # Required by Wagtail 6.0.6 (must be <24)
|
||||
Willow>=1.8.0 # Required by Wagtail for images
|
||||
beautifulsoup4>=4.12.0 # Required by Wagtail
|
||||
html5lib>=1.1 # Required by Wagtail
|
||||
openpyxl>=3.1.0 # For Excel export support
|
||||
l18n # For Wagtail localization
|
||||
telepath>=0.3.1 # Required by Wagtail 6
|
||||
# Django 5.0 timezone support (replaces pytz)
|
||||
tzdata # For timezone support in Django 5.0
|
||||
ConcurrentLogHandler
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
cd /app
|
||||
rm -rf node_modules
|
||||
yarn install
|
||||
mkdir -p /app/publichealth/static/libs
|
||||
echo "created /app/publichealth/static/libs contents "
|
||||
ls /app/publichealth/static/libs
|
||||
cp -rf node_modules/@bower_components/* /app/publichealth/static/libs/
|
||||
|
||||
# static resources
|
||||
python manage.py collectstatic --no-input -i media --settings=publichealth.settings.production
|
||||
python manage.py compress --force --settings=publichealth.settings.production
|
||||
|
||||
if [ -d /app/static ]; then
|
||||
echo "Copying"
|
||||
cp -a /app/static /pod
|
||||
fi
|
||||
Loading…
Add table
Add a link
Reference in a new issue