저번 시간에 이어서 이번에는 blog app을 분석해보려고 한다. 코드를 전체 다루지 않고 base app에서 다루지 않았던 새로운 내용만 적어볼 생각이다.

models.py

class BlogPeopleRelationship(Orderable, models.Model):
 
    page = ParentalKey(
        'BlogPage', related_name='blog_person_relationship', on_delete=models.CASCADE
    )
    people = models.ForeignKey(
        'base.People', related_name='person_blog_relationship', on_delete=models.CASCADE
    )
    panels = [
        SnippetChooserPanel('people')
    ]

 

base app에 models.py에 정의 했던 people을 여기서 사용하게 된다. 

ParentalKey을 이용해서 어떤 모델에 연관 시킬건지를 정한다. 여기서는 BlogPage에서 사용하려고 한다.

ForeignKey에서는 base app에서 정의 해 놓은 People 클래스를 가져온다. 

People 클래스를 이미 snippet으로 정의를 해 놓았으니 여기도 panel은 snippet을 사용한다. 

 

class Blog

class BlogPage(Page):
  
    introduction = models.TextField(
        help_text='Text to describe the page',
        blank=True)
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='Landscape mode only; horizontal width between 1000px and 3000px.'
    )
    body = StreamField(
        BaseStreamBlock(), verbose_name="Page body", blank=True
    )
    subtitle = models.CharField(blank=True, max_length=255)
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)
    date_published = models.DateField(
        "Date article published", blank=True, null=True
    )

    content_panels = Page.content_panels + [
        FieldPanel('subtitle', classname="full"),
        FieldPanel('introduction', classname="full"),
        ImageChooserPanel('image'),
        StreamFieldPanel('body'),
        FieldPanel('date_published'),
        InlinePanel(
            'blog_person_relationship', label="Author(s)",
            panels=None, min_num=1),
        FieldPanel('tags'),
    ]

    search_fields = Page.search_fields + [
        index.SearchField('body'),
    ]

    def authors(self):
        authors = [
            n.people for n in self.blog_person_relationship.all()
        ]

        return authors

    @property
    def get_tags(self):
        tags = self.tags.all()
        for tag in tags:
            tag.url = '/' + '/'.join(s.strip('/') for s in [
                self.get_parent().url,
                'tags',
                tag.slug
            ])
        return tags

    parent_page_types = ['BlogIndexPage']

    subpage_types = []

tags = ClusterTaggableManager(through=BlogPageTag, blank=True) 에서는 BlogPageTag을 통해 tags를 정의 해주고 있다. 잠시 BlogPageTag를 살펴 보자면 

class BlogPageTag(TaggedItemBase):
    content_object = ParentalKey('BlogPage', related_name='tagged_items', on_delete=models.CASCADE)

 

ParentalKey을 사용해서 BlogPage의 자식이라는 것을 정의해 놓는다. 이렇게 되면 BlogPage와 manytomany 관계를 생성하도록 해준다. 

 

def author와 def get_tags 를 살펴보도록 하겠다. 

둘다 비슷한 기능을 하는데 여러명의 authors(people)와 여러개의 tags들을 templates에서 표시해줄 수 있게 미리 정의를 해놓았다. template에서 authors 와 get_tags를 사용하게 되면 쉽게 author 와 tag들을 표시할 수 있다. 

 

<div class="blog-meta">
                    {% if page.authors %}
                        <div class="blog-avatars">
                            {% for author in page.authors %}
                                <div class="author">{% image author.image fill-50x50-c100 class="blog-avatar" %}
                                    {{ author.first_name }} {{ author.last_name }}</div>
                            {% endfor %}
                        </div>
                    {% endif %}

                    {% if page.date_published %}
                        <div class="blog-byline">
                            {{ page.date_published }}
                        </div>
                    {% endif %}
                </div>

                {{ page.body }}

                {% if page.get_tags %}
                    Tagged with:<br />
                    {% for tag in page.get_tags  %}
                        <a href="{{ tag.url }}" class="btn btn-sm">{{ tag }}</a>
                    {% endfor %}
                {% endif %}

실제로 blog_page.html 을 살펴보게 되면 blogpage와 관련된 사람이나 태그를 표시해주는 것을 확인 할 수 있다. 

parent_page_types = ['BlogIndexPage'] 을 정의하여 blogpage가 blogindexpage의 자식 page임을 알려준다.

 

class BlogIndexPage

class BlogIndexPage(RoutablePageMixin, Page):
    introduction = models.TextField(
        help_text='Text to describe the page',
        blank=True)
    image = models.ForeignKey(
        'wagtailimages.Image',
        null=True,
        blank=True,
        on_delete=models.SET_NULL,
        related_name='+',
        help_text='Landscape mode only; horizontal width between 1000px and 3000px.'
    )

    content_panels = Page.content_panels + [
        FieldPanel('introduction', classname="full"),
        ImageChooserPanel('image'),
    ]

    subpage_types = ['BlogPage']

    def children(self):
        return self.get_children().specific().live()

    def get_context(self, request):
        context = super(BlogIndexPage, self).get_context(request)
        context['posts'] = BlogPage.objects.descendant_of(
            self).live().order_by(
            '-date_published')
        return context

    @route(r'^tags/$', name='tag_archive')
    @route(r'^tags/([\w-]+)/$', name='tag_archive')
    def tag_archive(self, request, tag=None):

        try:
            tag = Tag.objects.get(slug=tag)
        except Tag.DoesNotExist:
            if tag:
                msg = 'There are no blog posts tagged with "{}"'.format(tag)
                messages.add_message(request, messages.INFO, msg)
            return redirect(self.url)

        posts = self.get_posts(tag=tag)
        context = {
            'tag': tag,
            'posts': posts
        }
        return render(request, 'blog/blog_index_page.html', context)

    def serve_preview(self, request, mode_name):
        # Needed for previews to work
        return self.serve(request)

    def get_posts(self, tag=None):
        posts = BlogPage.objects.live().descendant_of(self)
        if tag:
            posts = posts.filter(tags=tag)
        return posts

    def get_child_tags(self):
        tags = []
        for post in self.get_posts():
            # Not tags.append() because we don't want a list of lists
            tags += post.get_tags
        tags = sorted(set(tags))
        return tags

subpage_types = ['BlogPage'] 는 BlogIndexPage의 자식 Page는 오로지 BlogPage임을 정의해준다.

 

def children(self):
        return self.get_children().specific().live() 을 통해 현재 publish 된 page만 돌려주도록 설정해 놓는다. 

demo site에서는 homepage에서 사용하기 위해서 설정해 놓았다.

 

blogindexpage에서는 get_context을 통해 posts 라는 이름으로 template에 전달하였다.

@route(r'^tags/$', name='tag_archive')
    @route(r'^tags/([\w-]+)/$', name='tag_archive')
    def tag_archive(self, request, tag=None):

        try:
            tag = Tag.objects.get(slug=tag)
        except Tag.DoesNotExist:
            if tag:
                msg = 'There are no blog posts tagged with "{}"'.format(tag)
                messages.add_message(request, messages.INFO, msg)
            return redirect(self.url)

        posts = self.get_posts(tag=tag)
        context = {
            'tag': tag,
            'posts': posts
        }
        return render(request, 'blog/blog_index_page.html', context)

유의 깊게 살펴볼 부분이 이곳이다. 이 함수는 Tag를 보여주는 Custom view이다. 이 View는 연관되어 있는 Tag들을 전부 주고 blogindexpage redirect를 가능하게 해준다. 

@route 를 사용하여 tags 자체 url를 설정해준다. name에 관련 내용을 정의한 함수를 적어준다. 여기서는 tag_archive라는 이름으로 정의 했다.

blogindexpage를 보게 되면 위와 같이 등록된 tag들이 있는 것을 볼 수 있다. 각각의 태그를 누르면 태그에 연관되어 있는 blog들만 나오게 된다. 

전체적인 코드의 흐름을 살펴보자면 tag page의 router를 custom 해주고 tag변수에 Tag object들을 저장한다. tag에는 None으로 초기화를 해주면 된다. 오류 검사를 진행한 다음 

'Back-End > Wagtail, Django' 카테고리의 다른 글

Wagtail Docker 사용 방법  (0) 2021.08.18
Wagtail demo site (breads)  (0) 2021.08.17
Wagtail demo site (base)  (0) 2021.08.15
Wagtail 이미지  (0) 2021.08.13
Wagtail StreamField  (0) 2021.08.11

+ Recent posts