Add page type for talks
Content coming soon, probably
This commit is contained in:
parent
1934b36ec1
commit
bd4c1a193a
13 changed files with 534 additions and 3 deletions
|
@ -1,3 +1,6 @@
|
||||||
{
|
{
|
||||||
"extends": ["stylelint-config-standard-scss", "stylelint-config-prettier-scss"]
|
"extends": ["stylelint-config-standard-scss", "stylelint-config-prettier-scss"],
|
||||||
|
"rules": {
|
||||||
|
"scss/at-extend-no-missing-placeholder": null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-blogpostlistpage {
|
.container.listing {
|
||||||
.date-header {
|
.date-header {
|
||||||
font-size: $size-2;
|
font-size: $size-2;
|
||||||
font-weight: $weight-bold;
|
font-weight: $weight-bold;
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block post_content %}
|
{% block post_content %}
|
||||||
<section class="container">
|
<section class="container listing">
|
||||||
{% for page in listing_pages %}
|
{% for page in listing_pages %}
|
||||||
{% ifchanged %}
|
{% ifchanged %}
|
||||||
<h2 id="date-{{ page.date|date:'Y-m' }}" class="date-header">
|
<h2 id="date-{{ page.date|date:'Y-m' }}" class="date-header">
|
||||||
|
|
|
@ -32,5 +32,27 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if page.slides_url %}
|
||||||
|
<span class="icon-text">
|
||||||
|
<a href="{{ page.slides_url }}">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas fa-lg fa-images"></i>
|
||||||
|
</span>
|
||||||
|
<span>Slides</span>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if page.video_url %}
|
||||||
|
<span class="icon-text">
|
||||||
|
<a href="{{ page.video_url }}">
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas fa-lg fa-film"></i>
|
||||||
|
</span>
|
||||||
|
<span>Video</span>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endwagtailpagecache %}
|
{% endwagtailpagecache %}
|
||||||
|
|
|
@ -42,6 +42,7 @@ INSTALLED_APPS = [
|
||||||
"website.utils",
|
"website.utils",
|
||||||
"website.well_known",
|
"website.well_known",
|
||||||
"website.legacy",
|
"website.legacy",
|
||||||
|
"website.talks",
|
||||||
"website.contrib.code_block",
|
"website.contrib.code_block",
|
||||||
"website.contrib.mermaid_block",
|
"website.contrib.mermaid_block",
|
||||||
"website.contrib.unsplash",
|
"website.contrib.unsplash",
|
||||||
|
|
0
website/talks/__init__.py
Normal file
0
website/talks/__init__.py
Normal file
17
website/talks/factories.py
Normal file
17
website/talks/factories.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from website.common.factories import BaseContentFactory, BaseListingFactory
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
||||||
|
|
||||||
|
class TalksListPageFactory(BaseListingFactory):
|
||||||
|
class Meta:
|
||||||
|
model = models.TalksListPage
|
||||||
|
|
||||||
|
|
||||||
|
class TalkPageFactory(BaseContentFactory):
|
||||||
|
duration = timedelta(minutes=30)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = models.TalkPage
|
352
website/talks/migrations/0001_initial.py
Normal file
352
website/talks/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,352 @@
|
||||||
|
# Generated by Django 5.0.1 on 2024-03-01 16:55
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import django.utils.timezone
|
||||||
|
import wagtail.blocks
|
||||||
|
import wagtail.contrib.routable_page.models
|
||||||
|
import wagtail.contrib.typed_table_block.blocks
|
||||||
|
import wagtail.embeds.blocks
|
||||||
|
import wagtail.fields
|
||||||
|
import wagtail.images.blocks
|
||||||
|
import wagtailmetadata.models
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import website.contrib.code_block.blocks
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("images", "0002_alter_customimage_file_alter_customrendition_file"),
|
||||||
|
("unsplash", "0001_initial"),
|
||||||
|
("wagtailcore", "0089_log_entry_data_json_null_to_object"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="TalkPage",
|
||||||
|
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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("subtitle", wagtail.fields.RichTextField(blank=True)),
|
||||||
|
(
|
||||||
|
"body",
|
||||||
|
wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||||
|
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||||
|
(
|
||||||
|
"lorem",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"paragraphs",
|
||||||
|
wagtail.blocks.IntegerBlock(min_value=1),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("html", wagtail.blocks.RawHTMLBlock()),
|
||||||
|
(
|
||||||
|
"image",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"image",
|
||||||
|
wagtail.images.blocks.ImageChooserBlock(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"caption",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="plain", required=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"code",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"filename",
|
||||||
|
wagtail.blocks.CharBlock(
|
||||||
|
max_length=128, required=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"language",
|
||||||
|
wagtail.blocks.ChoiceBlock(
|
||||||
|
choices=website.contrib.code_block.blocks.get_language_choices
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("source", wagtail.blocks.TextBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tangent",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
wagtail.blocks.CharBlock(max_length=64),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"content",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="simple"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mermaid",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("source", wagtail.blocks.TextBlock()),
|
||||||
|
(
|
||||||
|
"caption",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="plain", required=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"table",
|
||||||
|
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"rich_text",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="plain"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("numeric", wagtail.blocks.FloatBlock()),
|
||||||
|
("text", wagtail.blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"iframe",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", wagtail.blocks.URLBlock()),
|
||||||
|
(
|
||||||
|
"caption",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="plain", required=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
blank=True,
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("date", models.DateField(default=django.utils.timezone.now)),
|
||||||
|
("duration", models.DurationField()),
|
||||||
|
("slides_url", models.URLField(blank=True)),
|
||||||
|
("video_url", models.URLField(blank=True)),
|
||||||
|
(
|
||||||
|
"hero_image",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="images.customimage",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hero_unsplash_photo",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="unsplash.unsplashphoto",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
bases=("wagtailcore.page", wagtailmetadata.models.MetadataMixin),
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name="TalksListPage",
|
||||||
|
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",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"body",
|
||||||
|
wagtail.fields.StreamField(
|
||||||
|
[
|
||||||
|
("embed", wagtail.embeds.blocks.EmbedBlock()),
|
||||||
|
("rich_text", wagtail.blocks.RichTextBlock()),
|
||||||
|
(
|
||||||
|
"lorem",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"paragraphs",
|
||||||
|
wagtail.blocks.IntegerBlock(min_value=1),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("html", wagtail.blocks.RawHTMLBlock()),
|
||||||
|
(
|
||||||
|
"image",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"image",
|
||||||
|
wagtail.images.blocks.ImageChooserBlock(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"caption",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="plain", required=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"code",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"filename",
|
||||||
|
wagtail.blocks.CharBlock(
|
||||||
|
max_length=128, required=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"language",
|
||||||
|
wagtail.blocks.ChoiceBlock(
|
||||||
|
choices=website.contrib.code_block.blocks.get_language_choices
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("source", wagtail.blocks.TextBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"tangent",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"name",
|
||||||
|
wagtail.blocks.CharBlock(max_length=64),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"content",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="simple"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"mermaid",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("source", wagtail.blocks.TextBlock()),
|
||||||
|
(
|
||||||
|
"caption",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="plain", required=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"table",
|
||||||
|
wagtail.contrib.typed_table_block.blocks.TypedTableBlock(
|
||||||
|
[
|
||||||
|
(
|
||||||
|
"rich_text",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="plain"
|
||||||
|
),
|
||||||
|
),
|
||||||
|
("numeric", wagtail.blocks.FloatBlock()),
|
||||||
|
("text", wagtail.blocks.CharBlock()),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"iframe",
|
||||||
|
wagtail.blocks.StructBlock(
|
||||||
|
[
|
||||||
|
("url", wagtail.blocks.URLBlock()),
|
||||||
|
(
|
||||||
|
"caption",
|
||||||
|
wagtail.blocks.RichTextBlock(
|
||||||
|
editor="plain", required=False
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
blank=True,
|
||||||
|
use_json_field=True,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hero_image",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="images.customimage",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"hero_unsplash_photo",
|
||||||
|
models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=django.db.models.deletion.SET_NULL,
|
||||||
|
to="unsplash.unsplashphoto",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
"abstract": False,
|
||||||
|
},
|
||||||
|
bases=(
|
||||||
|
wagtail.contrib.routable_page.models.RoutablePageMixin,
|
||||||
|
"wagtailcore.page",
|
||||||
|
wagtailmetadata.models.MetadataMixin,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
0
website/talks/migrations/__init__.py
Normal file
0
website/talks/migrations/__init__.py
Normal file
52
website/talks/models.py
Normal file
52
website/talks/models.py
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from django.db import models
|
||||||
|
from django.utils import timezone
|
||||||
|
from wagtail.admin.panels import FieldPanel, MultiFieldPanel
|
||||||
|
|
||||||
|
from website.common.models import BaseContentPage, BaseListingPage
|
||||||
|
|
||||||
|
|
||||||
|
class TalksListPage(BaseListingPage):
|
||||||
|
max_count = 1
|
||||||
|
subpage_types = ["talks.TalkPage"]
|
||||||
|
|
||||||
|
|
||||||
|
class TalkPage(BaseContentPage):
|
||||||
|
subpage_types: list[Any] = []
|
||||||
|
parent_page_types = [TalksListPage]
|
||||||
|
|
||||||
|
date = models.DateField(default=timezone.now)
|
||||||
|
|
||||||
|
duration = models.DurationField()
|
||||||
|
|
||||||
|
slides_url = models.URLField(blank=True)
|
||||||
|
video_url = models.URLField(blank=True)
|
||||||
|
|
||||||
|
content_panels = BaseContentPage.content_panels + [
|
||||||
|
MultiFieldPanel(
|
||||||
|
[
|
||||||
|
FieldPanel("slides_url"),
|
||||||
|
FieldPanel("video_url"),
|
||||||
|
],
|
||||||
|
heading="Media",
|
||||||
|
),
|
||||||
|
FieldPanel("duration"),
|
||||||
|
]
|
||||||
|
|
||||||
|
promote_panels = BaseContentPage.promote_panels + [
|
||||||
|
FieldPanel("date"),
|
||||||
|
]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def show_table_of_contents(self) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reading_time(self) -> timedelta:
|
||||||
|
return self.duration
|
||||||
|
|
||||||
|
@property
|
||||||
|
def word_count(self) -> int:
|
||||||
|
return 0
|
11
website/talks/templates/talks/talk_page.html
Normal file
11
website/talks/templates/talks/talk_page.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
{% extends "common/content_page.html" %}
|
||||||
|
|
||||||
|
{% load wagtailembeds_tags %}
|
||||||
|
|
||||||
|
{% block pre_content %}
|
||||||
|
{% if page.video_url %}
|
||||||
|
<section class="container mb-5 content">
|
||||||
|
<div class="block-embed">{% embed page.video_url %}</div>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
26
website/talks/templates/talks/talks_list_page.html
Normal file
26
website/talks/templates/talks/talks_list_page.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{% extends "common/listing_page.html" %}
|
||||||
|
|
||||||
|
{% load wagtailroutablepage_tags %}
|
||||||
|
|
||||||
|
{% block post_content %}
|
||||||
|
<section class="container listing">
|
||||||
|
{% for page in listing_pages %}
|
||||||
|
{% ifchanged %}
|
||||||
|
<h2 id="date-{{ page.date.year }}" class="date-header">
|
||||||
|
<time datetime="{{ page.date.year }}" title="{{ page.date.year }}">
|
||||||
|
{{ page.date.year }}
|
||||||
|
</time>
|
||||||
|
</h2>
|
||||||
|
{% endifchanged %}
|
||||||
|
|
||||||
|
{% include "common/listing-item.html" %}
|
||||||
|
{% endfor %}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{% if listing_pages.has_other_pages %}
|
||||||
|
<section class="container">
|
||||||
|
<hr class="my-5" />
|
||||||
|
{% include "common/pagination.html" with page=listing_pages %}
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
47
website/talks/tests.py
Normal file
47
website/talks/tests.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
from website.home.models import HomePage
|
||||||
|
|
||||||
|
from .factories import TalkPageFactory, TalksListPageFactory
|
||||||
|
|
||||||
|
|
||||||
|
class TalkPageTestCase(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls) -> None:
|
||||||
|
cls.home_page = HomePage.objects.get()
|
||||||
|
cls.list_page = TalksListPageFactory(parent=cls.home_page)
|
||||||
|
cls.page = TalkPageFactory(parent=cls.list_page)
|
||||||
|
|
||||||
|
def test_accessible(self) -> None:
|
||||||
|
response = self.client.get(self.page.url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_queries(self) -> None:
|
||||||
|
with self.assertNumQueries(34):
|
||||||
|
self.client.get(self.page.url)
|
||||||
|
|
||||||
|
|
||||||
|
class TalksListPageTestCase(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls) -> None:
|
||||||
|
cls.home_page = HomePage.objects.get()
|
||||||
|
cls.page = TalksListPageFactory(parent=cls.home_page)
|
||||||
|
|
||||||
|
TalkPageFactory(parent=cls.page)
|
||||||
|
TalkPageFactory(parent=cls.page)
|
||||||
|
|
||||||
|
def test_accessible(self) -> None:
|
||||||
|
response = self.client.get(self.page.url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(len(response.context["listing_pages"]), 2)
|
||||||
|
|
||||||
|
def test_queries(self) -> None:
|
||||||
|
with self.assertNumQueries(35):
|
||||||
|
self.client.get(self.page.url)
|
||||||
|
|
||||||
|
def test_feed_accessible(self) -> None:
|
||||||
|
response = self.client.get(self.page.url + self.page.reverse_subpage("feed"))
|
||||||
|
self.assertRedirects(
|
||||||
|
response, reverse("feed"), status_code=301, fetch_redirect_response=True
|
||||||
|
)
|
Loading…
Reference in a new issue