Add article and category page models, templates, and default cover image
This commit is contained in:
parent
0874255859
commit
3232de90d4
@ -0,0 +1,39 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-17 04:15
|
||||
|
||||
import django.db.models.deletion
|
||||
import wagtail.fields
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0002_create_homepage'),
|
||||
('wagtailcore', '0095_groupsitepermission'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ArticlePage',
|
||||
fields=[
|
||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
|
||||
('date', models.DateField(verbose_name='Published date')),
|
||||
('intro', models.CharField(blank=True, max_length=250)),
|
||||
('body', wagtail.fields.StreamField([('heading', 0), ('paragraph', 1), ('image', 2), ('embed', 3)], block_lookup={0: ('wagtail.blocks.CharBlock', (), {'form_classname': 'full title'}), 1: ('wagtail.blocks.RichTextBlock', (), {'features': ['bold', 'italic', 'link']}), 2: ('wagtail.images.blocks.ImageChooserBlock', (), {}), 3: ('wagtail.embeds.blocks.EmbedBlock', (), {})})),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('wagtailcore.page',),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='CategoryPage',
|
||||
fields=[
|
||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('wagtailcore.page',),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,35 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-17 07:28
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0003_articlepage_categorypage'),
|
||||
('wagtailcore', '0095_groupsitepermission'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='LatestPage',
|
||||
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='RecommandedPage',
|
||||
fields=[
|
||||
('page_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='wagtailcore.page')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
bases=('wagtailcore.page',),
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-17 07:36
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0004_latestpage_recommandedpage'),
|
||||
('wagtailcore', '0095_groupsitepermission'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='RecommandedPage',
|
||||
new_name='RecommendedPage',
|
||||
),
|
||||
]
|
||||
@ -0,0 +1,25 @@
|
||||
# Generated by Django 5.2.7 on 2025-10-17 07:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('home', '0005_rename_recommandedpage_recommendedpage'),
|
||||
('wagtailimages', '0027_image_description'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='articlepage',
|
||||
name='cover_image',
|
||||
field=models.ForeignKey(blank=True, help_text='文章列表與分享用的首圖', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='wagtailimages.image'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='articlepage',
|
||||
name='recommended',
|
||||
field=models.BooleanField(default=False, help_text='在推薦清單顯示'),
|
||||
),
|
||||
]
|
||||
@ -1,7 +1,174 @@
|
||||
from django.db import models
|
||||
|
||||
from wagtail.models import Page
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
|
||||
BLOCK_SIZE = 5
|
||||
PAGE_SIZE = 10
|
||||
|
||||
class HomePage(Page):
|
||||
pass
|
||||
class CategoryMixin:
|
||||
def build_category_blocks(self, request=None):
|
||||
blocks = []
|
||||
subcategories = self.get_children().type(CategoryPage).live()
|
||||
if subcategories.exists():
|
||||
for category in subcategories :
|
||||
blocks.append({
|
||||
"title": category.title,
|
||||
"items": ArticlePage.objects.child_of(category).live().order_by("-first_published_at")[:BLOCK_SIZE],
|
||||
"url": category.url,
|
||||
})
|
||||
else:
|
||||
paginator = Paginator(ArticlePage.objects.child_of(self).live().order_by("-first_published_at"), PAGE_SIZE)
|
||||
page_number = request.GET.get("page") if request else None
|
||||
|
||||
try:
|
||||
page_obj = paginator.page(page_number)
|
||||
except PageNotAnInteger:
|
||||
page_obj = paginator.page(1)
|
||||
except EmptyPage:
|
||||
page_obj = paginator.page(paginator.num_pages)
|
||||
|
||||
blocks.append({
|
||||
"title": self.title,
|
||||
"items": page_obj,
|
||||
"url": self.url,
|
||||
})
|
||||
return blocks
|
||||
|
||||
def get_latest_articles(self, request=None):
|
||||
latestPage = LatestPage.objects.first()
|
||||
if not request:
|
||||
return {
|
||||
"title": latestPage.title,
|
||||
"items": ArticlePage.objects.live().order_by("-first_published_at")[:BLOCK_SIZE],
|
||||
"url": latestPage.url,
|
||||
}
|
||||
else:
|
||||
paginator = Paginator(ArticlePage.objects.live().order_by("-first_published_at"), PAGE_SIZE)
|
||||
page_number = request.GET.get("page")
|
||||
|
||||
try:
|
||||
page_obj = paginator.page(page_number)
|
||||
except PageNotAnInteger:
|
||||
page_obj = paginator.page(1)
|
||||
except EmptyPage:
|
||||
page_obj = paginator.page(paginator.num_pages)
|
||||
return {
|
||||
"title": self.title,
|
||||
"items": page_obj,
|
||||
"url": self.url,
|
||||
}
|
||||
|
||||
def get_recommended_articles(self, request=None):
|
||||
recommendedPage = RecommendedPage.objects.first()
|
||||
if not request:
|
||||
return {
|
||||
"title": recommendedPage.title,
|
||||
"items": ArticlePage.objects.filter(recommended=True).live()[:BLOCK_SIZE],
|
||||
"url": recommendedPage.url,
|
||||
}
|
||||
else:
|
||||
paginator = Paginator(ArticlePage.objects.filter(recommended=True).live(), PAGE_SIZE)
|
||||
page_number = request.GET.get("page")
|
||||
|
||||
try:
|
||||
page_obj = paginator.page(page_number)
|
||||
except PageNotAnInteger:
|
||||
page_obj = paginator.page(1)
|
||||
except EmptyPage:
|
||||
page_obj = paginator.page(paginator.num_pages)
|
||||
return {
|
||||
"title": self.title,
|
||||
"items": page_obj,
|
||||
"url": self.url,
|
||||
}
|
||||
return blocks
|
||||
|
||||
class HomePage(Page, CategoryMixin):
|
||||
def get_context(self, request):
|
||||
context = super().get_context(request)
|
||||
|
||||
category_blocks = [
|
||||
self.get_latest_articles(),
|
||||
self.get_recommended_articles(),
|
||||
]
|
||||
|
||||
# 找出第一層 CategoryPage(HomePage 直屬子項)
|
||||
categories = CategoryPage.objects.child_of(self).live().in_menu()
|
||||
|
||||
# 若第一層沒有分類,就嘗試抓所有 descendant CategoryPage
|
||||
if not categories.exists():
|
||||
categories = CategoryPage.objects.descendant_of(self).live().in_menu()
|
||||
|
||||
for category in categories:
|
||||
subcategories = category.get_children().type(CategoryPage).live()
|
||||
category_blocks.append({
|
||||
"title": category.title,
|
||||
"type": "category",
|
||||
"items": subcategories or ArticlePage.objects.child_of(category).live()[:BLOCK_SIZE],
|
||||
"url": category.url,
|
||||
})
|
||||
|
||||
context["category_blocks"] = category_blocks
|
||||
return context
|
||||
|
||||
class LatestPage(Page, CategoryMixin):
|
||||
template = "home/category_page.html"
|
||||
def get_context(self, request):
|
||||
context = super().get_context(request)
|
||||
context["category_blocks"] = [
|
||||
self.get_latest_articles(request)
|
||||
]
|
||||
return context
|
||||
|
||||
class RecommendedPage(Page, CategoryMixin):
|
||||
template = "home/category_page.html"
|
||||
def get_context(self, request):
|
||||
context = super().get_context(request)
|
||||
context["category_blocks"] = [
|
||||
self.get_recommended_articles(request)
|
||||
]
|
||||
return context
|
||||
|
||||
class CategoryPage(Page, CategoryMixin):
|
||||
@property
|
||||
def has_subcategories(self):
|
||||
return self.get_children().type(CategoryPage).live().exists()
|
||||
|
||||
def get_context(self, request):
|
||||
context = super().get_context(request)
|
||||
context["category_blocks"] = self.build_category_blocks(request)
|
||||
return context
|
||||
|
||||
# from wagtail.fields import RichTextField
|
||||
from wagtail.admin.panels import FieldPanel
|
||||
from wagtail import blocks
|
||||
from wagtail.embeds.blocks import EmbedBlock
|
||||
from wagtail.images.blocks import ImageChooserBlock
|
||||
from wagtail.fields import StreamField
|
||||
|
||||
class ArticlePage(Page):
|
||||
cover_image = models.ForeignKey(
|
||||
"wagtailimages.Image",
|
||||
null=True, blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
related_name="+",
|
||||
help_text="文章列表與分享用的首圖"
|
||||
)
|
||||
date = models.DateField("Published date")
|
||||
intro = models.CharField(max_length=250, blank=True)
|
||||
body = StreamField([
|
||||
("heading", blocks.CharBlock(form_classname="full title")),
|
||||
("paragraph", blocks.RichTextBlock(features=["bold", "italic", "link"])),
|
||||
("image", ImageChooserBlock()),
|
||||
("embed", EmbedBlock()),
|
||||
], use_json_field=True)
|
||||
recommended = models.BooleanField(default=False, help_text="在推薦清單顯示")
|
||||
|
||||
content_panels = Page.content_panels + [
|
||||
FieldPanel("recommended"),
|
||||
FieldPanel("cover_image"),
|
||||
FieldPanel("date"),
|
||||
FieldPanel("intro"),
|
||||
FieldPanel("body"),
|
||||
]
|
||||
|
||||
BIN
innovedus_cms/home/static/img/default_cover.jpg
Normal file
BIN
innovedus_cms/home/static/img/default_cover.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.0 KiB |
15
innovedus_cms/home/templates/home/article_page.html
Normal file
15
innovedus_cms/home/templates/home/article_page.html
Normal file
@ -0,0 +1,15 @@
|
||||
{% extends "base.html" %}
|
||||
{% load wagtailcore_tags wagtailimages_tags %}
|
||||
|
||||
{% block content %}
|
||||
<article>
|
||||
{% image page.cover_image original as cover %}
|
||||
<img src="{{ cover.url }}" alt="{{ page.title }}">
|
||||
<h1>{{ page.title }}</h1>
|
||||
<p class="date">{{ page.date }}</p>
|
||||
<div class="intro">{{ page.intro }}</div>
|
||||
<div class="body">
|
||||
{{ page.body }}
|
||||
</div>
|
||||
</article>
|
||||
{% endblock %}
|
||||
9
innovedus_cms/home/templates/home/category_page.html
Normal file
9
innovedus_cms/home/templates/home/category_page.html
Normal file
@ -0,0 +1,9 @@
|
||||
{% extends "base.html" %}
|
||||
{% load wagtailcore_tags %}
|
||||
{% block content %}
|
||||
{% if page.has_subcategories %}
|
||||
{% include "home/includes/category_block_list.html" %}
|
||||
{% else %}
|
||||
{% include "home/includes/category_full_list.html" %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
@ -5,17 +5,10 @@
|
||||
|
||||
{% block extra_css %}
|
||||
|
||||
{% comment %}
|
||||
Delete the line below if you're just getting started and want to remove the welcome screen!
|
||||
{% endcomment %}
|
||||
<link rel="stylesheet" href="{% static 'css/welcome_page.css' %}">
|
||||
{% endblock extra_css %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
{% comment %}
|
||||
Delete the line below if you're just getting started and want to remove the welcome screen!
|
||||
{% endcomment %}
|
||||
{% include 'home/welcome_page.html' %}
|
||||
{% include "home/includes/category_block_list.html" with category_blocks=category_blocks %}
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@ -0,0 +1,25 @@
|
||||
{% load wagtailimages_tags %}
|
||||
|
||||
|
||||
<div class="category-block-list">
|
||||
{% for category in category_blocks %}
|
||||
<section class="category-section">
|
||||
<h2><a href="{{ category.url }}">{{ category.title }}</a></h2>
|
||||
<ul>
|
||||
{% for article in category.items %}
|
||||
<li>
|
||||
<a href="{{ article.url }}">
|
||||
{% if article.cover_image %}
|
||||
{% image article.cover_image max-200x200 as cover %}
|
||||
<img src="{{ cover.url }}" alt="{{ article.title }}" height="200" width="200" style="width:200px;height:200px;object-fit:contain;display:block;"/>
|
||||
{% else %}
|
||||
<img src="/static/img/default_cover.jpg" alt="{{ article.title }}" height="200" width="200" style="width:200px;height:200px;object-fit:contain;display:block;"/>
|
||||
{% endif %}
|
||||
{{ article.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -0,0 +1,39 @@
|
||||
{% load wagtailimages_tags %}
|
||||
|
||||
<div class="category-full-list">
|
||||
{% with category=category_blocks.0 %}
|
||||
<h2><a href="{{ category.url }}">{{ category.title }}</a></h2>
|
||||
|
||||
<ul>
|
||||
{% for article in category.items %}
|
||||
<article>
|
||||
<li>
|
||||
<a href="{{ article.url }}">
|
||||
{% if article.cover_image %}
|
||||
{% image article.cover_image max-200x200 as cover %}
|
||||
<img src="{{ cover.url }}" alt="{{ article.title }}" height="200" width="200" style="width:200px;height:200px;object-fit:contain;display:block;"/>
|
||||
{% else %}
|
||||
<img src="/static/img/default_cover.jpg" alt="{{ article.title }}" height="200" width="200" style="width:200px;height:200px;object-fit:contain;display:block;"/>
|
||||
{% endif %}
|
||||
{{ article.title }}
|
||||
</a>
|
||||
</li>
|
||||
<!-- <p>{{ article.search_description }}</p> -->
|
||||
</article>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
{% if category.items.paginator.num_pages > 1 %}
|
||||
<div class="pagination">
|
||||
{% if category.items.has_previous %}
|
||||
<a href="?page={{ category.items.previous_page_number }}">← 上一頁</a>
|
||||
{% endif %}
|
||||
<span>第 {{ category.items.number }} / {{ category.items.paginator.num_pages }} 頁</span>
|
||||
{% if category.items.has_next %}
|
||||
<a href="?page={{ category.items.next_page_number }}">下一頁 →</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endwith %}
|
||||
</div>
|
||||
BIN
innovedus_cms/media/original_images/DefaultArticleCover.jpg
Normal file
BIN
innovedus_cms/media/original_images/DefaultArticleCover.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
Loading…
x
Reference in New Issue
Block a user