Initial commit

This commit is contained in:
2024-08-27 20:33:44 +02:00
commit 1f1832267d
14794 changed files with 1599592 additions and 0 deletions

View File

@@ -0,0 +1,43 @@
import time
import tracemalloc
class Benchmark:
repeat = 10
def test(self):
timings = []
memory_usage = []
tracemalloc.start()
for i in range(self.repeat):
before_memory = tracemalloc.take_snapshot()
start_time = time.time()
self.bench()
end_time = time.time()
after_memory = tracemalloc.take_snapshot()
timings.append(end_time - start_time)
memory_usage.append(
sum(
[t.size for t in after_memory.compare_to(before_memory, "filename")]
)
)
print( # noqa: T201
"time min:",
min(timings),
"max:",
max(timings),
"avg:",
sum(timings) / len(timings),
)
print( # noqa: T201
"memory min:",
min(memory_usage),
"max:",
max(memory_usage),
"avg:",
sum(memory_usage) / len(memory_usage),
)

View File

@@ -0,0 +1,9 @@
def do_not_use_static_url(request):
def exception():
raise Exception(
"Do not use STATIC_URL in templates. Use the {% static %} templatetag (or {% versioned_static %} within admin templates) instead."
)
return {
"STATIC_URL": lambda: exception(),
}

View File

@@ -0,0 +1,82 @@
import random
from django.db import models
LOWER_BOUND = -2147483648
UPPER_BOUND = 2147483647
SHIFT = 92147483647
class ConvertedValue(str):
def __new__(cls, value):
value = int(value)
if UPPER_BOUND < value:
display_value = value
db_value = value - SHIFT
else:
db_value = value
display_value = value + SHIFT
self = super().__new__(cls, display_value)
self.db_value = db_value
return self
def __repr__(self):
return f"<{self.__class__.__name__}: {self.db_value}>"
def __eq__(self, other):
if isinstance(other, self.__class__):
return self.db_value == other.db_value
return self.db_value == other
def __hash__(self):
return hash(self.db_value)
class ConvertedValueField(models.IntegerField):
"""
Roughly copied from https://github.com/django/django/blob/d6eaf7c0183cd04b78f2a55e1d60bb7e59598310/tests/custom_pk/fields.py
"""
def pre_save(self, instance, add):
value = getattr(instance, self.attname, None)
if not value:
value = ConvertedValue(random.randint(LOWER_BOUND, UPPER_BOUND))
setattr(instance, self.attname, value)
return value
def to_python(self, value):
if not value:
return
if not isinstance(value, ConvertedValue):
value = ConvertedValue(value)
return value
def get_prep_value(self, value):
if value is None:
return None
if not isinstance(value, ConvertedValue):
value = ConvertedValue(value)
return super().get_prep_value(value.db_value)
def from_db_value(self, value, expression, connection):
if not value:
return
return ConvertedValue(value)
def get_db_prep_save(self, value, connection):
if not value:
return
return ConvertedValue(value).db_value
def get_db_prep_value(self, value, connection, prepared=False):
if not value:
return
return ConvertedValue(value).db_value
def get_searchable_content(self, value):
if not value:
return
return ConvertedValue(value).db_value

View File

@@ -0,0 +1,114 @@
[
{
"pk": 1,
"model": "customuser.customuser",
"fields": {
"username": "superuser",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"groups": [],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "superuser@example.com"
}
},
{
"pk": 2,
"model": "customuser.customuser",
"fields": {
"username": "eventeditor",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Event editors"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "eventeditor@example.com"
}
},
{
"pk": 3,
"model": "customuser.customuser",
"fields": {
"username": "eventmoderator",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Event moderators"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "eventmoderator@example.com"
}
},
{
"pk": 4,
"model": "customuser.customuser",
"fields": {
"username": "inactiveuser",
"first_name": "",
"last_name": "",
"is_active": false,
"is_superuser": false,
"is_staff": false,
"groups": [["Event moderators"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "inactiveuser@example.com"
}
},
{
"pk": 5,
"model": "customuser.customuser",
"fields": {
"username": "siteeditor",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Site-wide editors"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "siteeditor@example.com"
}
},
{
"pk": 6,
"model": "customuser.customuser",
"fields": {
"username": "admin_only_user",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Admin non-editors"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "admin_only_user@example.com"
}
},
{
"pk": 7,
"model": "customuser.customuser",
"fields": {
"username": "corporateeditor",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Corporate Editor"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "corporateeditor@example.com"
}
}
]

View File

@@ -0,0 +1,98 @@
[
{
"pk": 1,
"model": "customuser.customuser",
"fields": {
"username": "superman",
"first_name": "Clark",
"last_name": "Kent",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"groups": [],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "superman@example.com"
}
},
{
"pk": 2,
"model": "customuser.customuser",
"fields": {
"username": "jane",
"first_name": "Jane",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [["Group 1"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "jane@example.com"
}
},
{
"pk": 3,
"model": "customuser.customuser",
"fields": {
"username": "bob",
"first_name": "Bob",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [["Group 2"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "bob@example.com"
}
},
{
"pk": 4,
"model": "customuser.customuser",
"fields": {
"username": "sam",
"first_name": "Sam",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [["Group 1"], ["Group 2"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "sam@example.com"
}
},
{
"pk": 5,
"model": "customuser.customuser",
"fields": {
"username": "mary",
"first_name": "Mary",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [],
"user_permissions": [["access_admin", "wagtailadmin", "admin"]],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "mary@example.com"
}
},
{
"pk": 6,
"model": "customuser.customuser",
"fields": {
"username": "josh",
"first_name": "Josh",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [["Group 2"], ["Group 3"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "josh@example.com"
}
}
]

View File

@@ -0,0 +1,18 @@
[
{
"pk": 1,
"model": "customuser.customuser",
"fields": {
"username": "superuser",
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"groups": [],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "superuser@example.com"
}
}
]

View File

@@ -0,0 +1,113 @@
from django.db import migrations, models
from ..fields import ConvertedValueField
class Migration(migrations.Migration):
dependencies = [
("auth", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="CustomUser",
fields=[
("identifier", ConvertedValueField(serialize=False, primary_key=True)),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
null=True, verbose_name="last login", blank=True
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
("username", models.CharField(unique=True, max_length=100)),
("email", models.EmailField(max_length=255, blank=True)),
("is_staff", models.BooleanField(default=True)),
("is_active", models.BooleanField(default=True)),
("first_name", models.CharField(max_length=50, blank=True)),
("last_name", models.CharField(max_length=50, blank=True)),
("country", models.CharField(max_length=100, blank=True)),
(
"groups",
models.ManyToManyField(
related_query_name="user",
related_name="user_set",
to="auth.Group",
blank=True,
help_text=(
"The groups this user belongs to. "
"A user will get all permissions granted to each of their groups."
),
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
to="auth.Permission",
verbose_name="user permissions",
help_text="Specific permissions for this user.",
related_name="user_set",
blank=True,
related_query_name="user",
),
),
],
options={
"abstract": False,
},
bases=(models.Model,),
),
migrations.CreateModel(
name="EmailUser",
fields=[
(
"id",
models.AutoField(
verbose_name="ID",
serialize=False,
auto_created=True,
primary_key=True,
),
),
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
null=True, verbose_name="last login", blank=True
),
),
("email", models.EmailField(unique=True, max_length=255)),
("is_staff", models.BooleanField(default=True)),
("is_active", models.BooleanField(default=True)),
("first_name", models.CharField(max_length=50, blank=True)),
("last_name", models.CharField(max_length=50, blank=True)),
("is_superuser", models.BooleanField(default=False)),
(
"groups",
models.ManyToManyField(
related_name="+", to="auth.Group", blank=True
),
),
(
"user_permissions",
models.ManyToManyField(
related_name="+", to="auth.Permission", blank=True
),
),
],
options={
"abstract": False,
},
bases=(models.Model,),
),
]

View File

@@ -0,0 +1,16 @@
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("customuser", "0001_initial"),
]
operations = [
migrations.AddField(
model_name="CustomUser",
name="attachment",
field=models.FileField(blank=True),
),
]

View File

@@ -0,0 +1,16 @@
# Generated by Django 3.1 on 2020-08-13 21:28
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("customuser", "0002_added_file_field"),
]
operations = [
migrations.DeleteModel(
name="EmailUser",
),
]

View File

@@ -0,0 +1,83 @@
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
from django.db import models
# Custom user models are a common source of circular import errors, since the user model is often
# referenced in Wagtail core code that we may want to import here. To prevent this, Wagtail should
# avoid importing the user model at load time.
# wagtail.admin.auth and wagtail.admin.views.generic are imported here as these have been
# previously identified as sources of circular imports.
from wagtail.admin.auth import permission_denied # noqa: F401
from wagtail.admin.panels import FieldPanel
from wagtail.admin.views.generic import chooser as chooser_views # noqa: F401
from .fields import ConvertedValueField
class CustomUserManager(BaseUserManager):
def _create_user(
self,
username,
email,
password,
is_staff,
is_superuser,
is_active=True,
**extra_fields,
):
"""
Creates and saves a User with the given username, email and password.
"""
if not username:
raise ValueError("The given username must be set")
email = self.normalize_email(email)
user = self.model(
username=username,
email=email,
is_staff=is_staff,
is_active=is_active,
is_superuser=is_superuser,
**extra_fields,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, username, email=None, password=None, **extra_fields):
return self._create_user(
username, email, password, False, False, **extra_fields
)
def create_superuser(self, username, email, password, **extra_fields):
return self._create_user(username, email, password, True, True, **extra_fields)
class CustomUser(AbstractBaseUser, PermissionsMixin):
identifier = ConvertedValueField(primary_key=True)
username = models.CharField(max_length=100, unique=True)
email = models.EmailField(max_length=255, blank=True)
is_staff = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)
first_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
country = models.CharField(max_length=100, blank=True)
attachment = models.FileField(blank=True)
USERNAME_FIELD = "username"
REQUIRED_FIELDS = ["email"]
objects = CustomUserManager()
def get_full_name(self):
return self.first_name + " " + self.last_name
def get_short_name(self):
return self.first_name
panels = [
FieldPanel("first_name"),
FieldPanel("last_name"),
]

View File

@@ -0,0 +1,47 @@
import json
import unittest
from django.conf import settings
from django.db import connections
from django.test import TestCase
from wagtail.test.utils import WagtailTestUtils
from .fields import ConvertedValue, ConvertedValueField
@unittest.skipUnless(
settings.AUTH_USER_MODEL == "customuser.CustomUser", "Only applicable to CustomUser"
)
class TestConvertedValueField(WagtailTestUtils, TestCase):
def setUp(self):
self.user = self.login()
User = self.user.__class__
self.pk_field = User._meta.get_field(User._meta.pk.name)
self.pk_db_value = self.pk_field.get_db_prep_value(
self.user.pk, connections["default"]
)
def test_db_value_is_different(self):
self.assertEqual(self.user.pk, self.pk_db_value)
self.assertNotEqual(str(self.user.pk), str(self.pk_db_value))
def test_custom_user_primary_key_is_hashable(self):
hash(self.user.pk)
def test_custom_user_primary_key_is_jsonable(self):
json_str = json.dumps({"pk": self.user.pk}, separators=(",", ":"))
self.assertEqual(json_str, '{"pk":"%s"}' % self.user.pk)
# verify the json string uses the display value and not the db value
self.assertNotEqual(json_str, '{"pk":"%s"}' % self.pk_db_value)
def test_custom_user_primary_key(self):
self.assertIsInstance(self.user.pk, ConvertedValue)
def test_custom_user_primary_key_is_converted_value_field(self):
self.assertIsInstance(self.pk_field, ConvertedValueField)
def test_get_prep_value_returns_integer_for_db_query(self):
self.assertIsInstance(self.pk_field.get_prep_value(1234), int)

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class DemositeAppConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
name = "wagtail.test.demosite"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,15 @@
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("demosite", "0001_initial"),
]
operations = [
migrations.AlterModelOptions(
name="homepage",
options={"verbose_name": "homepage"},
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 4.0.dev20210817102354 on 2022-01-06 10:25
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("taggit", "0003_taggeditem_add_unique_index"),
("demosite", "0002_capitalizeverbose"),
]
operations = [
migrations.AlterField(
model_name="blogentrypagetag",
name="tag",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="%(app_label)s_%(class)s_items",
to="taggit.tag",
),
),
]

View File

@@ -0,0 +1,694 @@
from datetime import date
from django.core.exceptions import ValidationError
from django.core.paginator import Paginator
from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TaggedItemBase
from wagtail.admin.panels import FieldPanel, InlinePanel, MultiFieldPanel
from wagtail.api import APIField
from wagtail.contrib.forms.models import AbstractForm, AbstractFormField
from wagtail.fields import RichTextField
from wagtail.images.api.fields import ImageRenditionField
from wagtail.models import Orderable, Page
from wagtail.search import index
# ABSTRACT MODELS
# =============================
class AbstractLinkFields(models.Model):
link_external = models.URLField("External link", blank=True)
link_page = models.ForeignKey(
"wagtailcore.Page",
null=True,
blank=True,
related_name="+",
on_delete=models.CASCADE,
)
link_document = models.ForeignKey(
"wagtaildocs.Document",
null=True,
blank=True,
related_name="+",
on_delete=models.CASCADE,
)
@property
def link(self):
if self.link_page:
return self.link_page.url
elif self.link_document:
return self.link_document.url
else:
return self.link_external
def clean(self):
if (
self.link_page is None
and self.link_document is None
and not self.link_external
):
raise ValidationError(
"You must provide a related page, related document or an external URL"
)
api_fields = ("link",)
panels = [
FieldPanel("link_external"),
FieldPanel("link_page"),
FieldPanel("link_document"),
]
class Meta:
abstract = True
class AbstractRelatedLink(AbstractLinkFields):
title = models.CharField(max_length=255, help_text="Link title")
api_fields = ("title",) + AbstractLinkFields.api_fields
panels = [
FieldPanel("title"),
MultiFieldPanel(AbstractLinkFields.panels, "Link"),
]
class Meta:
abstract = True
class AbstractCarouselItem(AbstractLinkFields):
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
embed_url = models.URLField("Embed URL", blank=True)
caption = models.CharField(max_length=255, blank=True)
api_fields = (
"image",
"embed_url",
"caption",
) + AbstractLinkFields.api_fields
panels = [
FieldPanel("image"),
FieldPanel("embed_url"),
FieldPanel("caption"),
MultiFieldPanel(AbstractLinkFields.panels, "Link"),
]
class Meta:
abstract = True
class ContactFieldsMixin(models.Model):
telephone = models.CharField(max_length=20, blank=True)
email = models.EmailField(blank=True)
address_1 = models.CharField(max_length=255, blank=True)
address_2 = models.CharField(max_length=255, blank=True)
city = models.CharField(max_length=255, blank=True)
country = models.CharField(max_length=255, blank=True)
post_code = models.CharField(max_length=10, blank=True)
api_fields = (
"telephone",
"email",
"address_1",
"address_2",
"city",
"country",
"post_code",
)
panels = [
FieldPanel("telephone"),
FieldPanel("email"),
FieldPanel("address_1"),
FieldPanel("address_2"),
FieldPanel("city"),
FieldPanel("country"),
FieldPanel("post_code"),
]
class Meta:
abstract = True
# PAGE MODELS
# =============================
# Home page
class HomePage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
body = RichTextField(blank=True)
api_fields = (
"body",
"carousel_items",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("body"),
]
class Meta:
verbose_name = "homepage"
class HomePageCarouselItem(Orderable, AbstractCarouselItem):
page = ParentalKey(
"HomePage", related_name="carousel_items", on_delete=models.CASCADE
)
class HomePageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"HomePage", related_name="related_links", on_delete=models.CASCADE
)
HomePage.content_panels = Page.content_panels + [
FieldPanel("body"),
InlinePanel("carousel_items", label="Carousel items"),
InlinePanel("related_links", label="Related links"),
]
# Standard pages
class StandardPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
intro = RichTextField(blank=True)
body = RichTextField(blank=True)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"intro",
"body",
"feed_image",
"carousel_items",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("intro"),
index.SearchField("body"),
]
class StandardPageCarouselItem(Orderable, AbstractCarouselItem):
page = ParentalKey(
"StandardPage", related_name="carousel_items", on_delete=models.CASCADE
)
class StandardPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"StandardPage", related_name="related_links", on_delete=models.CASCADE
)
StandardPage.content_panels = Page.content_panels + [
FieldPanel("intro"),
InlinePanel("carousel_items", heading="Carousel items", label="Carousel item"),
FieldPanel("body"),
InlinePanel("related_links", heading="Related links", label="Related link"),
]
StandardPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
class StandardIndexPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
intro = RichTextField(blank=True)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"intro",
"feed_image",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("intro"),
]
class StandardIndexPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"StandardIndexPage", related_name="related_links", on_delete=models.CASCADE
)
StandardIndexPage.content_panels = Page.content_panels + [
FieldPanel("intro"),
InlinePanel("related_links", label="Related links"),
]
StandardIndexPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
# Blog pages
class BlogEntryPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
body = RichTextField()
tags = ClusterTaggableManager(through="BlogEntryPageTag", blank=True)
date = models.DateField("Post date")
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
APIField("body"),
APIField("tags"),
APIField("date"),
APIField("feed_image"),
APIField(
"feed_image_thumbnail",
serializer=ImageRenditionField("fill-300x300", source="feed_image"),
),
APIField("carousel_items"),
APIField("related_links"),
)
search_fields = Page.search_fields + [
index.SearchField("body"),
]
def get_blog_index(self):
# Find closest ancestor which is a blog index
return BlogIndexPage.ancestor_of(self).last()
class BlogEntryPageCarouselItem(Orderable, AbstractCarouselItem):
page = ParentalKey(
"BlogEntryPage", related_name="carousel_items", on_delete=models.CASCADE
)
class BlogEntryPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"BlogEntryPage", related_name="related_links", on_delete=models.CASCADE
)
class BlogEntryPageTag(TaggedItemBase):
content_object = ParentalKey(
"BlogEntryPage", related_name="tagged_items", on_delete=models.CASCADE
)
BlogEntryPage.content_panels = Page.content_panels + [
FieldPanel("date"),
FieldPanel("body"),
InlinePanel("carousel_items", label="Carousel items"),
InlinePanel("related_links", label="Related links"),
]
BlogEntryPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
FieldPanel("tags"),
]
class BlogIndexPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
intro = RichTextField(blank=True)
api_fields = (
"intro",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("intro"),
]
def get_blog_entries(self):
# Get list of live blog pages that are descendants of this page
entries = BlogEntryPage.objects.descendant_of(self).live()
# Order by most recent date first
entries = entries.order_by("-date")
return entries
def get_context(self, request):
# Get blog entries
entries = self.get_blog_entries()
# Filter by tag
tag = request.GET.get("tag")
if tag:
entries = entries.filter(tags__name=tag)
paginator = Paginator(entries, per_page=10)
entries = paginator.get_page(request.GET.get("page"))
# Update template context
context = super().get_context(request)
context["entries"] = entries
return context
class BlogIndexPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"BlogIndexPage", related_name="related_links", on_delete=models.CASCADE
)
BlogIndexPage.content_panels = Page.content_panels + [
FieldPanel("intro"),
InlinePanel("related_links", label="Related links"),
]
# Events pages
class EventPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
AUDIENCE_CHOICES = (
("public", "Public"),
("private", "Private"),
)
date_from = models.DateField("Start date")
date_to = models.DateField(
"End date",
null=True,
blank=True,
help_text="Not required if event is on a single day",
)
time_from = models.TimeField("Start time", null=True, blank=True)
time_to = models.TimeField("End time", null=True, blank=True)
audience = models.CharField(max_length=255, choices=AUDIENCE_CHOICES)
location = models.CharField(max_length=255)
body = RichTextField(blank=True)
cost = models.CharField(max_length=255)
signup_link = models.URLField(blank=True)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"date_from",
"date_to",
"time_from",
"time_to",
"audience",
"location",
"body",
"cost",
"signup_link",
"feed_image",
"carousel_items",
"related_links",
"speakers",
)
search_fields = Page.search_fields + [
index.SearchField("get_audience_display"),
index.SearchField("location"),
index.SearchField("body"),
]
def get_event_index(self):
# Find closest ancestor which is an event index
return EventIndexPage.objects.ancestor_of(self).last()
class EventPageCarouselItem(Orderable, AbstractCarouselItem):
page = ParentalKey(
"EventPage", related_name="carousel_items", on_delete=models.CASCADE
)
class EventPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"EventPage", related_name="related_links", on_delete=models.CASCADE
)
class EventPageSpeaker(Orderable, AbstractLinkFields):
page = ParentalKey("EventPage", related_name="speakers", on_delete=models.CASCADE)
first_name = models.CharField("Name", max_length=255, blank=True)
last_name = models.CharField("Surname", max_length=255, blank=True)
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"first_name",
"last_name",
"image",
)
panels = [
FieldPanel("first_name"),
FieldPanel("last_name"),
FieldPanel("image"),
MultiFieldPanel(AbstractLinkFields.panels, "Link"),
]
EventPage.content_panels = Page.content_panels + [
FieldPanel("date_from"),
FieldPanel("date_to"),
FieldPanel("time_from"),
FieldPanel("time_to"),
FieldPanel("location"),
FieldPanel("audience"),
FieldPanel("cost"),
FieldPanel("signup_link"),
InlinePanel("carousel_items", label="Carousel items"),
FieldPanel("body"),
InlinePanel("speakers", label="Speakers"),
InlinePanel("related_links", label="Related links"),
]
EventPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
class EventIndexPage(Page):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
intro = RichTextField(blank=True)
api_fields = (
"intro",
"related_links",
)
search_fields = Page.search_fields + [
index.SearchField("intro"),
]
def get_events(self):
# Get list of live event pages that are descendants of this page
events = EventPage.objects.descendant_of(self).live()
# Filter events list to get ones that are either
# running now or start in the future
events = events.filter(date_from__gte=date.today())
# Order by date
events = events.order_by("date_from")
return events
class EventIndexPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"EventIndexPage", related_name="related_links", on_delete=models.CASCADE
)
EventIndexPage.content_panels = Page.content_panels + [
FieldPanel("intro"),
InlinePanel("related_links", label="Related links"),
]
# Person page
class PersonPage(Page, ContactFieldsMixin):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
intro = RichTextField(blank=True)
biography = RichTextField(blank=True)
image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"first_name",
"last_name",
"intro",
"biography",
"image",
"feed_image",
"related_links",
) + ContactFieldsMixin.api_fields
search_fields = Page.search_fields + [
index.SearchField("first_name"),
index.SearchField("last_name"),
index.SearchField("intro"),
index.SearchField("biography"),
]
class PersonPageRelatedLink(Orderable, AbstractRelatedLink):
page = ParentalKey(
"PersonPage", related_name="related_links", on_delete=models.CASCADE
)
PersonPage.content_panels = Page.content_panels + [
FieldPanel("first_name"),
FieldPanel("last_name"),
FieldPanel("intro"),
FieldPanel("biography"),
FieldPanel("image"),
MultiFieldPanel(ContactFieldsMixin.panels, "Contact"),
InlinePanel("related_links", label="Related links"),
]
PersonPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
# Contact page
class ContactPage(Page, ContactFieldsMixin):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
body = RichTextField(blank=True)
feed_image = models.ForeignKey(
"wagtailimages.Image",
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name="+",
)
api_fields = (
"body",
"feed_image",
) + ContactFieldsMixin.api_fields
search_fields = Page.search_fields + [
index.SearchField("body"),
]
ContactPage.content_panels = Page.content_panels + [
FieldPanel("body"),
MultiFieldPanel(ContactFieldsMixin.panels, "Contact"),
]
ContactPage.promote_panels = [
MultiFieldPanel(Page.promote_panels, "Common page configuration"),
FieldPanel("feed_image"),
]
class FormField(AbstractFormField):
page = ParentalKey("FormPage", related_name="form_fields", on_delete=models.CASCADE)
class FormPage(AbstractForm):
page_ptr = models.OneToOneField(
Page, parent_link=True, related_name="+", on_delete=models.CASCADE
)
api_fields = [APIField("form_fields")]
content_panels = AbstractForm.content_panels + [
InlinePanel("form_fields", label="Form fields")
]

View File

@@ -0,0 +1,89 @@
# This file contains a file storage backend that imitates behaviours of
# common external storage backends (S3 boto, libcloud, etc).
# The following behaviours have been added to this backend:
# - Calling .path on the storage or image file raises NotImplementedError
# - File.open() after the file has been closed raises an error
# - File.size exceptions raise DummyExternalStorageError
# - Storage._save() fails loudly if the content file's pointer is not at the start
from django.core.files import File
from django.core.files.storage import FileSystemStorage, Storage
from django.utils.deconstruct import deconstructible
@deconstructible
class DummyExternalStorage(Storage):
def __init__(self, *args, **kwargs):
self.wrapped = FileSystemStorage(*args, **kwargs)
def path(self, name):
# Overridden to give it the behaviour of the base Storage class
# This is what an external storage backend would have
raise NotImplementedError("This backend doesn't support absolute paths.")
def _open(self, name, mode="rb"):
# Overridden to return a DummyExternalStorageFile instead of a normal
# File object
return DummyExternalStorageFile(open(self.wrapped.path(name), mode))
def _save(self, name, content):
file_pos = content.tell()
if file_pos != 0:
raise ValueError(
"Content file pointer should be at 0 - got %d instead" % file_pos
)
return self.wrapped._save(name, content)
# Wrap all other functions
def delete(self, name):
self.wrapped.delete(name)
def exists(self, name):
return self.wrapped.exists(name)
def listdir(self, path):
return self.wrapped.listdir(path)
def size(self, name):
return self.wrapped.size(name)
def url(self, name):
return self.wrapped.url(name)
def accessed_time(self, name):
return self.wrapped.accessed_time(name)
def created_time(self, name):
return self.wrapped.created_time(name)
def modified_time(self, name):
return self.wrapped.modified_time(name)
class DummyExternalStorageError(Exception):
pass
class DummyExternalStorageFile(File):
def open(self, mode=None):
# Based on:
# https://github.com/django/django/blob/2c39f282b8389f47fee4b24e785a58567c6c3629/django/core/files/base.py#L135-L141
# I've commented out two lines of this function which stops it checking
# the filesystem for the file. Making it behave as if it is using an
# external file storage.
if not self.closed:
self.seek(0)
# elif self.name and os.path.exists(self.name):
# self.file = open(self.name, mode or self.mode)
else:
raise ValueError("The file cannot be reopened.")
def size(self):
try:
return super().size
except Exception as e: # noqa: BLE001
raise DummyExternalStorageError(str(e))

View File

@@ -0,0 +1,8 @@
from django.http import HttpResponse
def sendfile(request, filename, **kwargs):
"""
Dummy sendfile backend implementation.
"""
return HttpResponse("Dummy backend response")

View File

@@ -0,0 +1,107 @@
[
{
"pk": "00000000-0000-0000-0000-000000000001",
"model": "emailuser.emailuser",
"fields": {
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"groups": [],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "superuser@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000002",
"model": "emailuser.emailuser",
"fields": {
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Event editors"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "eventeditor@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000003",
"model": "emailuser.emailuser",
"fields": {
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Event moderators"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "eventmoderator@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000004",
"model": "emailuser.emailuser",
"fields": {
"first_name": "",
"last_name": "",
"is_active": false,
"is_superuser": false,
"is_staff": false,
"groups": [["Event moderators"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "inactiveuser@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000005",
"model": "emailuser.emailuser",
"fields": {
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Site-wide editors"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "siteeditor@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000006",
"model": "emailuser.emailuser",
"fields": {
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Admin non-editors"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "admin_only_user@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000007",
"model": "emailuser.emailuser",
"fields": {
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": false,
"is_staff": false,
"groups": [["Corporate Editor"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "corporateeditor@example.com"
}
}
]

View File

@@ -0,0 +1,92 @@
[
{
"pk": "00000000-0000-0000-0000-000000000001",
"model": "emailuser.emailuser",
"fields": {
"first_name": "Clark",
"last_name": "Kent",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"groups": [],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "superman@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000002",
"model": "emailuser.emailuser",
"fields": {
"first_name": "Jane",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [["Group 1"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "jane@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000003",
"model": "emailuser.emailuser",
"fields": {
"first_name": "Bob",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [["Group 2"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "bob@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000004",
"model": "emailuser.emailuser",
"fields": {
"first_name": "Sam",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [["Group 1"], ["Group 2"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "sam@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000005",
"model": "emailuser.emailuser",
"fields": {
"first_name": "Mary",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [],
"user_permissions": [["access_admin", "wagtailadmin", "admin"]],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "mary@example.com"
}
},
{
"pk": "00000000-0000-0000-0000-000000000006",
"model": "emailuser.emailuser",
"fields": {
"first_name": "Josh",
"last_name": "Smith",
"is_active": true,
"is_superuser": false,
"is_staff": true,
"groups": [["Group 2"], ["Group 3"]],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "josh@example.com"
}
}
]

View File

@@ -0,0 +1,17 @@
[
{
"pk": "00000000-0000-0000-0000-000000000001",
"model": "emailuser.emailuser",
"fields": {
"first_name": "",
"last_name": "",
"is_active": true,
"is_superuser": true,
"is_staff": true,
"groups": [],
"user_permissions": [],
"password": "md5$seasalt$1e9bf2bf5606aa5c39852cc30f0f6f22",
"email": "superuser@example.com"
}
}
]

View File

@@ -0,0 +1,65 @@
# Generated by Django 3.2.3 on 2021-05-25 13:26
from django.db import migrations, models
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
("auth", "0012_alter_user_first_name_max_length"),
]
operations = [
migrations.CreateModel(
name="EmailUser",
fields=[
("password", models.CharField(max_length=128, verbose_name="password")),
(
"last_login",
models.DateTimeField(
blank=True, null=True, verbose_name="last login"
),
),
(
"uuid",
models.UUIDField(
default=uuid.uuid4, primary_key=True, serialize=False
),
),
("email", models.EmailField(max_length=255, unique=True)),
("is_staff", models.BooleanField(default=True)),
("is_active", models.BooleanField(default=True)),
("first_name", models.CharField(blank=True, max_length=50)),
("last_name", models.CharField(blank=True, max_length=50)),
("is_superuser", models.BooleanField(default=False)),
(
"groups",
models.ManyToManyField(
blank=True,
help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
related_name="user_set",
related_query_name="user",
to="auth.Group",
verbose_name="groups",
),
),
(
"user_permissions",
models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.Permission",
verbose_name="user permissions",
),
),
],
options={
"abstract": False,
},
),
]

View File

@@ -0,0 +1,53 @@
import uuid
from django.contrib.auth.models import (
AbstractBaseUser,
BaseUserManager,
PermissionsMixin,
)
from django.db import models
class EmailUserManager(BaseUserManager):
def _create_user(self, email, password, is_staff, is_superuser, **extra_fields):
"""
Creates and saves a User with the given email and password.
"""
email = self.normalize_email(email)
user = self.model(
email=email,
is_staff=is_staff,
is_active=True,
is_superuser=is_superuser,
**extra_fields,
)
user.set_password(password)
user.save(using=self._db)
return user
def create_user(self, email=None, password=None, **extra_fields):
return self._create_user(email, password, False, False, **extra_fields)
def create_superuser(self, email, password, **extra_fields):
return self._create_user(email, password, True, True, **extra_fields)
class EmailUser(AbstractBaseUser, PermissionsMixin):
uuid = models.UUIDField(default=uuid.uuid4, primary_key=True)
email = models.EmailField(max_length=255, unique=True)
is_staff = models.BooleanField(default=True)
is_active = models.BooleanField(default=True)
first_name = models.CharField(max_length=50, blank=True)
last_name = models.CharField(max_length=50, blank=True)
is_superuser = models.BooleanField(default=False)
USERNAME_FIELD = "email"
objects = EmailUserManager()
def get_full_name(self):
return self.first_name + " " + self.last_name
def get_short_name(self):
return self.first_name

View File

@@ -0,0 +1,13 @@
"""An alternative urlconf module where Wagtail does not serve front-end URLs"""
from django.urls import include, path
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.documents import urls as wagtaildocs_urls
from wagtail.images import urls as wagtailimages_urls
urlpatterns = [
path("admin/", include(wagtailadmin_urls)),
path("documents/", include(wagtaildocs_urls)),
path("images/", include(wagtailimages_urls)),
]

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class I18nAppConfig(AppConfig):
default_auto_field = "django.db.models.AutoField"
name = "wagtail.test.i18n"

View File

@@ -0,0 +1,176 @@
# Generated by Django 2.2.10 on 2020-07-13 15:59
from django.db import migrations, models
import django.db.models.deletion
import modelcluster.fields
import uuid
class Migration(migrations.Migration):
initial = True
dependencies = [
("wagtailcore", "0057_page_locale_fields_notnull"),
]
operations = [
migrations.CreateModel(
name="TestModel",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"translation_key",
models.UUIDField(default=uuid.uuid4, editable=False),
),
("title", models.CharField(max_length=255)),
(
"locale",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="wagtailcore.Locale",
),
),
],
options={
"abstract": False,
"unique_together": {("translation_key", "locale")},
},
),
migrations.CreateModel(
name="TestPage",
fields=[
(
"page_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="wagtailcore.Page",
),
),
],
options={
"abstract": False,
},
bases=("wagtailcore.page",),
),
migrations.CreateModel(
name="InheritedTestModel",
fields=[
(
"testmodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="i18n.TestModel",
),
),
],
bases=("i18n.testmodel",),
),
migrations.CreateModel(
name="TestNonParentalChildObject",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"translation_key",
models.UUIDField(default=uuid.uuid4, editable=False),
),
(
"sort_order",
models.IntegerField(blank=True, editable=False, null=True),
),
("field", models.TextField()),
(
"locale",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="wagtailcore.Locale",
),
),
(
"page",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="test_nonparentalchildobjects",
to="i18n.TestPage",
),
),
],
options={
"abstract": False,
"unique_together": {("translation_key", "locale")},
},
),
migrations.CreateModel(
name="TestChildObject",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"translation_key",
models.UUIDField(default=uuid.uuid4, editable=False),
),
(
"sort_order",
models.IntegerField(blank=True, editable=False, null=True),
),
("field", models.TextField()),
(
"locale",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="wagtailcore.Locale",
),
),
(
"page",
modelcluster.fields.ParentalKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="test_childobjects",
to="i18n.TestPage",
),
),
],
options={
"ordering": ["sort_order"],
"abstract": False,
"unique_together": {("translation_key", "locale")},
},
),
]

View File

@@ -0,0 +1,125 @@
# Generated by Django 3.1.6 on 2021-02-16 18:04
from django.db import migrations, models
import django.db.models.deletion
import modelcluster.fields
import uuid
class Migration(migrations.Migration):
dependencies = [
("wagtailcore", "0059_apply_collection_ordering"),
("i18n", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="ClusterableTestModel",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"translation_key",
models.UUIDField(default=uuid.uuid4, editable=False),
),
("title", models.CharField(max_length=255)),
(
"locale",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="wagtailcore.locale",
),
),
],
options={
"abstract": False,
"unique_together": {("translation_key", "locale")},
},
),
migrations.CreateModel(
name="ClusterableTestModelChild",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"sort_order",
models.IntegerField(blank=True, editable=False, null=True),
),
("field", models.TextField()),
(
"parent",
modelcluster.fields.ParentalKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="i18n.clusterabletestmodel",
),
),
],
options={
"ordering": ["sort_order"],
"abstract": False,
},
),
migrations.CreateModel(
name="ClusterableTestModelTranslatableChild",
fields=[
(
"id",
models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"translation_key",
models.UUIDField(default=uuid.uuid4, editable=False),
),
(
"sort_order",
models.IntegerField(blank=True, editable=False, null=True),
),
("field", models.TextField()),
(
"locale",
models.ForeignKey(
editable=False,
on_delete=django.db.models.deletion.PROTECT,
related_name="+",
to="wagtailcore.locale",
),
),
(
"parent",
modelcluster.fields.ParentalKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="translatable_children",
to="i18n.clusterabletestmodel",
),
),
],
options={
"ordering": ["sort_order"],
"abstract": False,
"unique_together": {("translation_key", "locale")},
},
),
]

View File

@@ -0,0 +1,40 @@
# Generated by Django 5.0.6 on 2024-06-21 06:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('i18n', '0002_clusterabletestmodel_clusterabletestmodelchild_clusterabletestmodeltranslatablechild'),
('wagtailcore', '0094_alter_page_locale'),
]
operations = [
migrations.AlterField(
model_name='clusterabletestmodel',
name='locale',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale', verbose_name='locale'),
),
migrations.AlterField(
model_name='clusterabletestmodeltranslatablechild',
name='locale',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale', verbose_name='locale'),
),
migrations.AlterField(
model_name='testchildobject',
name='locale',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale', verbose_name='locale'),
),
migrations.AlterField(
model_name='testmodel',
name='locale',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale', verbose_name='locale'),
),
migrations.AlterField(
model_name='testnonparentalchildobject',
name='locale',
field=models.ForeignKey(editable=False, on_delete=django.db.models.deletion.PROTECT, related_name='+', to='wagtailcore.locale', verbose_name='locale'),
),
]

View File

@@ -0,0 +1,56 @@
from django.db import models
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from wagtail.models import Orderable, Page, TranslatableMixin
class TestPage(Page):
pass
class TestModel(TranslatableMixin):
title = models.CharField(max_length=255)
class InheritedTestModel(TestModel):
class Meta:
unique_together = None
class TestChildObject(TranslatableMixin, Orderable):
page = ParentalKey(TestPage, related_name="test_childobjects")
field = models.TextField()
class Meta(TranslatableMixin.Meta, Orderable.Meta):
pass
class TestNonParentalChildObject(TranslatableMixin, Orderable):
page = models.ForeignKey(
TestPage, on_delete=models.CASCADE, related_name="test_nonparentalchildobjects"
)
field = models.TextField()
class ClusterableTestModel(TranslatableMixin, ClusterableModel):
title = models.CharField(max_length=255)
class ClusterableTestModelChild(Orderable):
parent = ParentalKey(
ClusterableTestModel, on_delete=models.CASCADE, related_name="children"
)
field = models.TextField()
class ClusterableTestModelTranslatableChild(TranslatableMixin, Orderable):
parent = ParentalKey(
ClusterableTestModel,
on_delete=models.CASCADE,
related_name="translatable_children",
)
field = models.TextField()
class Meta(TranslatableMixin.Meta, Orderable.Meta):
pass

View File

@@ -0,0 +1,10 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "wagtail.test.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

@@ -0,0 +1,31 @@
from django.http import Http404, HttpResponse, HttpResponseForbidden
from django.utils.deprecation import MiddlewareMixin
from wagtail.models import Page
from wagtail.views import serve
class BlockDodgyUserAgentMiddleware(MiddlewareMixin):
# Used to test that we're correctly handling responses returned from middleware during page
# previews. If a client with user agent "EvilHacker" calls an admin view that performs a
# preview, the request to /admin/... will pass this middleware, but the fake request used for
# the preview (which keeps the user agent header, but uses the URL path of the front-end page)
# will trigger a Forbidden response. In this case, the expected behaviour is to return that
# response back to the user.
def process_request(self, request):
if (
not request.path.startswith("/admin/")
and request.headers.get("user-agent") == "EvilHacker"
):
return HttpResponseForbidden("Forbidden")
class SimplePageViewInterceptorMiddleware(MiddlewareMixin):
def process_view(self, request, view_func, view_args, view_kwargs):
if serve == view_func:
page = Page.find_for_request(request, *view_args, **view_kwargs)
if page is None:
raise Http404
elif page.content == "Intercept me":
return HttpResponse("Intercepted")

View File

@@ -0,0 +1,16 @@
"""An alternative urlconf module where Wagtail front-end URLs
are rooted at '/site/' rather than '/'"""
from django.urls import include, path
from wagtail import urls as wagtail_urls
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.documents import urls as wagtaildocs_urls
from wagtail.images import urls as wagtailimages_urls
urlpatterns = [
path("admin/", include(wagtailadmin_urls)),
path("documents/", include(wagtaildocs_urls)),
path("images/", include(wagtailimages_urls)),
path("site/", include(wagtail_urls)),
]

View File

@@ -0,0 +1,8 @@
from django.apps import AppConfig
from django.utils.translation import gettext_lazy as _
class WagtailRoutablePageTestsAppConfig(AppConfig):
name = "wagtail.test.routablepage"
label = "routablepagetests"
verbose_name = _("Wagtail routable page tests")

View File

@@ -0,0 +1,40 @@
# Generated by Django 1.9.1 on 2016-02-12 10:55
import django.db.models.deletion
from django.db import migrations, models
import wagtail.contrib.routable_page.models
class Migration(migrations.Migration):
initial = True
dependencies = [
("wagtailcore", "0024_alter_page_content_type_on_delete_behaviour"),
]
operations = [
migrations.CreateModel(
name="RoutablePageTest",
fields=[
(
"page_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="wagtailcore.Page",
),
),
],
options={
"abstract": False,
},
bases=(
wagtail.contrib.routable_page.models.RoutablePageMixin,
"wagtailcore.page",
),
),
]

View File

@@ -0,0 +1,38 @@
# Generated by Django 1.9.6 on 2016-05-10 10:31
from django.db import migrations, models
import django.db.models.deletion
import wagtail.contrib.routable_page.models
class Migration(migrations.Migration):
dependencies = [
("wagtailcore", "0028_merge"),
("routablepagetests", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="RoutablePageWithOverriddenIndexRouteTest",
fields=[
(
"page_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="wagtailcore.Page",
),
),
],
options={
"abstract": False,
},
bases=(
wagtail.contrib.routable_page.models.RoutablePageMixin,
"wagtailcore.page",
),
),
]

View File

@@ -0,0 +1,90 @@
from django.http import HttpResponse
from django.shortcuts import redirect
from wagtail.contrib.routable_page.models import RoutablePage, path, re_path, route
from wagtail.models import PreviewableMixin
def routable_page_external_view(request, arg="ARG NOT SET"):
return HttpResponse("EXTERNAL VIEW: " + arg)
class RoutablePageTest(RoutablePage):
@route(r"^archive/year/1984/$")
def archive_for_1984(self, request):
# check that routes are tested in order (and thus this takes precedence over archive_by_year)
return HttpResponse("we were always at war with eastasia")
@route(r"^archive/year/(\d+)/$")
def archive_by_year(self, request, year):
return HttpResponse("ARCHIVE BY YEAR: " + str(year))
@route(r"^archive/author/(?P<author_slug>.+)/$")
def archive_by_author(self, request, author_slug):
return HttpResponse("ARCHIVE BY AUTHOR: " + author_slug)
@path("archive/title/<slug:title>/")
def archive_by_title(self, request, title):
return HttpResponse("ARCHIVE BY TITLE: " + title)
@re_path(r"^archive/category/(?P<category_slug>.+)/$")
def archive_by_category(self, request, category_slug):
return HttpResponse("ARCHIVE BY CATEGORY: " + category_slug)
@route(r"^permanant-homepage-redirect/$")
def permanent_homepage_redirect(self, request):
return redirect("/", permanent=True)
@route(r"^temporary-homepage-redirect/$")
def temporary_homepage_redirect(self, request):
return redirect("/", permanent=False)
@route(r"^external/(.+)/$")
@route(r"^external-no-arg/$")
def external_view(self, *args, **kwargs):
return routable_page_external_view(*args, **kwargs)
# By default, the method name would be used as the url name but when the
# "name" kwarg is specified, this should override the default.
@route(r"^override-name-test/$", name="name_overridden")
def override_name_test(self, request):
pass
@route(r"^render-method-test/$")
def render_method_test(self, request):
return self.render(request, context_overrides={"self": None, "foo": "bar"})
@route(r"^render-method-test-custom-template/$")
def render_method_test_custom_template(self, request):
return self.render(
request,
context_overrides={"self": 1, "foo": "fighters"},
template="routablepagetests/routable_page_test_alternate.html",
)
@route(r"^render-method-with-arg/(?P<slug>.+)/$")
def render_method_test_with_arg(self, request, slug):
return self.render(request)
def get_route_paths(self):
return [
"/",
"/render-method-test/",
"not-a-valid-route",
]
preview_modes = PreviewableMixin.DEFAULT_PREVIEW_MODES + [
("extra", "Extra"),
("broken", "Broken"),
]
def serve_preview(self, request, mode_name):
if mode_name == "broken":
raise AttributeError("Something is broken!")
return super().serve_preview(request, mode_name)
class RoutablePageWithOverriddenIndexRouteTest(RoutablePage):
@route(r"^$")
def main(self, request):
return HttpResponse("OVERRIDDEN INDEX ROUTE")

Some files were not shown because too many files have changed in this diff Show More