diff --git a/innovedus_cms/home/migrations/0003_articlepage_categorypage.py b/innovedus_cms/home/migrations/0003_articlepage_categorypage.py new file mode 100644 index 0000000..ed3cc8a --- /dev/null +++ b/innovedus_cms/home/migrations/0003_articlepage_categorypage.py @@ -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',), + ), + ] diff --git a/innovedus_cms/home/migrations/0004_latestpage_recommandedpage.py b/innovedus_cms/home/migrations/0004_latestpage_recommandedpage.py new file mode 100644 index 0000000..50bdb38 --- /dev/null +++ b/innovedus_cms/home/migrations/0004_latestpage_recommandedpage.py @@ -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',), + ), + ] diff --git a/innovedus_cms/home/migrations/0005_rename_recommandedpage_recommendedpage.py b/innovedus_cms/home/migrations/0005_rename_recommandedpage_recommendedpage.py new file mode 100644 index 0000000..738ab54 --- /dev/null +++ b/innovedus_cms/home/migrations/0005_rename_recommandedpage_recommendedpage.py @@ -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', + ), + ] diff --git a/innovedus_cms/home/migrations/0006_articlepage_cover_image_articlepage_recommended.py b/innovedus_cms/home/migrations/0006_articlepage_cover_image_articlepage_recommended.py new file mode 100644 index 0000000..ab23371 --- /dev/null +++ b/innovedus_cms/home/migrations/0006_articlepage_cover_image_articlepage_recommended.py @@ -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='在推薦清單顯示'), + ), + ] diff --git a/innovedus_cms/home/models.py b/innovedus_cms/home/models.py index 5076f57..b7d3a3a 100644 --- a/innovedus_cms/home/models.py +++ b/innovedus_cms/home/models.py @@ -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"), + ] diff --git a/innovedus_cms/home/static/img/default_cover.jpg b/innovedus_cms/home/static/img/default_cover.jpg new file mode 100644 index 0000000..61cb38d Binary files /dev/null and b/innovedus_cms/home/static/img/default_cover.jpg differ diff --git a/innovedus_cms/home/templates/home/article_page.html b/innovedus_cms/home/templates/home/article_page.html new file mode 100644 index 0000000..788c1c0 --- /dev/null +++ b/innovedus_cms/home/templates/home/article_page.html @@ -0,0 +1,15 @@ +{% extends "base.html" %} +{% load wagtailcore_tags wagtailimages_tags %} + +{% block content %} +
+ {% image page.cover_image original as cover %} + {{ page.title }} +

{{ page.title }}

+

{{ page.date }}

+
{{ page.intro }}
+
+ {{ page.body }} +
+
+{% endblock %} \ No newline at end of file diff --git a/innovedus_cms/home/templates/home/category_page.html b/innovedus_cms/home/templates/home/category_page.html new file mode 100644 index 0000000..5ae1796 --- /dev/null +++ b/innovedus_cms/home/templates/home/category_page.html @@ -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 %} diff --git a/innovedus_cms/home/templates/home/home_page.html b/innovedus_cms/home/templates/home/home_page.html index db9e9b0..a7d00e4 100644 --- a/innovedus_cms/home/templates/home/home_page.html +++ b/innovedus_cms/home/templates/home/home_page.html @@ -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 %} - {% 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 %} diff --git a/innovedus_cms/home/templates/home/includes/category_block_list.html b/innovedus_cms/home/templates/home/includes/category_block_list.html new file mode 100644 index 0000000..7f1b955 --- /dev/null +++ b/innovedus_cms/home/templates/home/includes/category_block_list.html @@ -0,0 +1,25 @@ +{% load wagtailimages_tags %} + + +
+ {% for category in category_blocks %} +
+

{{ category.title }}

+ +
+ {% endfor %} +
diff --git a/innovedus_cms/home/templates/home/includes/category_full_list.html b/innovedus_cms/home/templates/home/includes/category_full_list.html new file mode 100644 index 0000000..296d914 --- /dev/null +++ b/innovedus_cms/home/templates/home/includes/category_full_list.html @@ -0,0 +1,39 @@ +{% load wagtailimages_tags %} + +
+ {% with category=category_blocks.0 %} +

{{ category.title }}

+ + + + {% if category.items.paginator.num_pages > 1 %} + + {% endif %} + + {% endwith %} +
diff --git a/innovedus_cms/media/original_images/DefaultArticleCover.jpg b/innovedus_cms/media/original_images/DefaultArticleCover.jpg new file mode 100644 index 0000000..2c1fea8 Binary files /dev/null and b/innovedus_cms/media/original_images/DefaultArticleCover.jpg differ