https://docs.wagtail.io/en/stable/advanced_topics/api/v2/usage.html#fetching-content

 

Wagtail API v2 Usage Guide — Wagtail Documentation 2.15.1 documentation

To fetch content over the API, perform a GET request against one of the following endpoints: Wagtail sites contain many page types, each with their own set of fields. The pages endpoint will only expose the common fields by default (such as title and slug)

docs.wagtail.io

 

API에서 원하는 data를 가져오는 방법 정리

HomePage API

 

 

1) 모든 데이터 가져오기

?fields=*

 

2) 특정 조건에 맞는 page가져오기 (GET메소드랑 동일하다.)

?title=Home or ?id=3 ...

 

3) 모든 데이터 가져오지 말기

?fields=_

 

4) 특정 데이터 가져오지 말기

?fields=-title
?fields=*,-title

 

5) 특정 데이터들 가져오지 말기

?fields=-title,-id

 

6) 특정 데이터만 가져오기

?fields=_,id,title,banner_title ...

 

7) 특정 페이지 갯수만 가져오기 (앞쪽부터 가져오기)

?limit=4
?limit=4&offset=4 -> 앞에 4페이지를 제외한 페이지 가져오기

 

8) 특정 field 기준으로 정렬하기

?order=title
?order=-title -> 역차순
?order=random -> 랜덤으로 가져오기

 

 

9) 메뉴 가져오기

?show_in_menus=true

 

10) 특정 Page의 자식 Pages 가져오기

?child_of=5 -> id가 5인 page의 자식 pages 가져오기
?descendant_of=5

 

11) 검색하기

?search=blog
search_fields = Page.search_fields + [

]

 

12) html_url

http://127.0.0.1:8000/api/v2/pages/?html_path=/ -> HomePage
http://127.0.0.1:8000/api/v2/pages/?html_path=blog/blog-post-1/ -> SPA에 유용

 

저번 포스트에는 Wagtail에서 API v2의 기본 Settings에 대해서 알아봤다. 

이제 본격적으로 사용하는 방법에 대해서 알아보자. 

 

먼저 Wagtail에서 작성한 Flex Page API와 Flex Page Model을 보자.

 

flex page api

 

flex/models.py

from django.db import models

from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.core.models import Page
from wagtail.core.fields import StreamField

from streams import blocks


class FlexPage(Page):
    
    template = "flex/flex_page.html"
    
    content = StreamField(
        [
            ("title_and_text", blocks.TitleAndTextBlock()),
            ("full_richtext", blocks.RichtextBlock()),
            ("simple_richtext", blocks.SimpleRichtextBlock()),
            ("cards", blocks.CardBlock()),
            ("cta", blocks.CTABlock()),
            ("button", blocks.ButtonBlock())
        ],
        null=True,
        blank=True
    )
    
    subtitle = models.CharField(max_length=100, null=True, blank=True)
    
    content_panels = Page.content_panels + [
        FieldPanel("subtitle"),
        StreamFieldPanel("content"),
    ]

    class Meta:
        verbose_name = "Flex Page"
        verbose_name_plural = "Flex Pages"

정보가 전부 표시되지 않고 있다. 나는 FlexPage에 subtitle과 content Fields를 가지고 있는데 API에서는 표시되지 않는다. API Field에 내가 작성한 Fields을 등록하지 않아서 그렇다. 간단하게 등록할 수 있다. 

 

from django.db import models

from wagtail.api import APIField # 추가
from wagtail.admin.edit_handlers import FieldPanel, StreamFieldPanel
from wagtail.core.models import Page
from wagtail.core.fields import StreamField

from streams import blocks


class FlexPage(Page):
    
    template = "flex/flex_page.html"
    
    content = StreamField(
        [
            ("title_and_text", blocks.TitleAndTextBlock()),
            ("full_richtext", blocks.RichtextBlock()),
            ("simple_richtext", blocks.SimpleRichtextBlock()),
            ("cards", blocks.CardBlock()),
            ("cta", blocks.CTABlock()),
            ("button", blocks.ButtonBlock())
        ],
        null=True,
        blank=True
    )
    
    subtitle = models.CharField(max_length=100, null=True, blank=True)
    
    #추가
    api_fields = [
        APIField('subtitle'),
        APIField('content')
    ]
    
    content_panels = Page.content_panels + [
        FieldPanel("subtitle"),
        StreamFieldPanel("content"),
    ]

    class Meta:
        verbose_name = "Flex Page"
        verbose_name_plural = "Flex Pages"

 

 

사용 방법

from wagtail.api import APIField

 

    api_fields = [
        APIField('subtitle'),
        APIField('content')
    ]

Wagtail에서 content_panels 사용방법이랑 같다. 이렇게 하면 API 서버에 쉽게 원하는 Field를 등록할 수 있다. 

 

추가된 정보들

 

그렇다면 원하는 Data를 가져오는 방법에 대해서 간단하게 알아보자. 

 

 

1) 모든 data 가져오지 말기

http://127.0.0.1:8000/api/v2/pages/4/?fields=_

 

2) 특정 data 가져오지 말기

http://127.0.0.1:8000/api/v2/pages/4/?fields=-title

 

3) 특정 data들 가져오지 말기

http://127.0.0.1:8000/api/v2/pages/4/?fields=-title,-seo_title

 

4) 모든 data 가져오기

http://127.0.0.1:8000/api/v2/pages/4/?fields=*

 

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

Wagtail API v2 Exposing Orderable Data and StreamFields  (0) 2021.11.24
Wagtail API v2 Fetching Fields  (0) 2021.11.24
Wagtail API v2 Settings(Headless CMS)  (0) 2021.11.24
Wagtail Menu System  (0) 2021.11.23
Wagtail, Django Paginator  (0) 2021.11.21

Wagtail에서 API를 사용하는 방법에 대해서 알아보자. 

Headless CMS에 대해서 간단하게 이해하고 넘어가려고 한다.

 

CMS는 Content Management System의 약자이다. 기존 블로그에 소개한 Wagtail 화면 구현방법은 FrontEnd와 BackEnd가 분리 되지 않아 FrontEnd에서 데이터를 표현할 때에는 HTML에 템플릿 언어를 통해 구현했다.

하지만, 개발 규모가 커져서 FrontEnd개발자와 BackEnd 개발자가 따로 있게 된다면 FrontEnd 개발자도 템플릿 언어와 BackEnd에 대해서 알아야 하는 문제가 생긴다. FrontEnd와 BackEnd가 분리되지 않는 것이다. 스마트폰 APP으로도 서비스를 출시하고 싶을 때에도 문제가 생긴다. 데이터의 표현 방법이 전부 다르기 때문이다. 

 

그래서 나온 것이 API이다. BackEnd에서는 API서버로 데이터를 보내주고 API서버를 통해 FrontEnd에서 필요한 데이터만 가져와서 사용하는 것이다. 이렇게 되면 BackEnd와 FrontEnd가 분리되어 개발 능률이 향상된다. 그렇다면 지금부터 Wagtail에서 API를 사용하는 방법에 대해서 알아보자. 

 

Wagtail에서는 API v2를 사용한다. 기본적으로 Wagtail은 Django 기반이기 때문에 Django RestFrameWork도 필요하다. 

 

 

API 통신을 위한 기본 Settings

 

1) django restframework 설치

pip install djangorestframework

 

 

2) base.py 수정

 

settings/base.py

INSTALLED_APP = [
	'wagtail.api.v2',
    
    	'rest_framework',
]

 

 

3) mysite/api.py 작성

 

mysite/api.py

from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.images.api.v2.views import ImagesAPIViewSet
from wagtail.documents.api.v2.views import DocumentsAPIViewSet

# Create the router. "wagtailapi" is the URL namespace
api_router = WagtailAPIRouter('wagtailapi')

# Add the three endpoints using the "register_endpoint" method.
# The first parameter is the name of the endpoint (eg. pages, images). This
# is used in the URL of the endpoint
# The second parameter is the endpoint class that handles the requests
api_router.register_endpoint('pages', PagesAPIViewSet)
api_router.register_endpoint('images', ImagesAPIViewSet)
api_router.register_endpoint('documents', DocumentsAPIViewSet)

 

4) urls.py 수정

 

mysite/urls.py

from .api import api_router

urlpatterns = [
    path('django-admin/', admin.site.urls),

    path('admin/', include(wagtailadmin_urls)),
    path('documents/', include(wagtaildocs_urls)),

    path('search/', search_views.search, name='search'),

    path('sitemap.xml', sitemap),

    path("api/v2/", api_router.urls),

wagtail에서 url을 추가할 때에는 wagtail_urls path보다 위에 위치해야한다. 

 

 

TEST

 

setting이 완료되었다. 이제 서버를 실행시키고 127.0.0.1:8000/api/v2/로 접속해보자. 

404오류가 뜰 것이다. 정상이다. 아까 setting에서 작성한 api.py를 살펴보자. 

 

api_router.register_endpoint('pages', PagesAPIViewSet)

위의 코드에서 PagesAPIViewSet은 Wagtail에서 우리가 작성한 모든 Page의 Set이다. 그리고 앞에 'pages'는 이곳을 경로로 들어가겠다는 의미이다. 즉, 127.0.0.1:8000/api/v2/pages/ 로 접속하면 pages의 Set을 볼 수 있다. 그리고 'pages'말고 'pages-set'같은 이름으로 바꾸었을 때에는 'pages'대신 'pages-set'으로 접속하면 된다. 

 

특정한 page의 정보가 보고 싶다면 끝에 page의 id 를 추가 해주면된다. 기본적으로 127.0.0.1:8000/api/v2/pages/3/는 홈페이지 이다. 

 

추가) Site port Setting

 

Wagtail Site에 들어가보면 Port가 80으로 되어 있을 것이다. 8000으로 바꿔주자.

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

Wagtail API v2 Fetching Fields  (0) 2021.11.24
Wagtail API v2 Exposing Custom Fields  (0) 2021.11.24
Wagtail Menu System  (0) 2021.11.23
Wagtail, Django Paginator  (0) 2021.11.21
Wagtail StructValue  (0) 2021.11.18

Wagtail에서 Menu를 관리하는 방법에 대해서 알아보려고 한다. 

Navigation Bar나 Footer에 표시된 Menu를 누르면 관련 링크로 이동하는 것인데,

하드 코딩없이 서버에서 Data로 관리하는 방법이다. 

 

새로운 menus App을 만들어서 진행했다. 

 

1. Models.py

"""Menus models"""
from django.db import models

from django_extensions.db.fields import AutoSlugField

from wagtail.admin.edit_handlers import (
    MultiFieldPanel,
    InlinePanel,
    FieldPanel,
    PageChooserPanel,
)
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from wagtail.snippets.models import register_snippet
from wagtail.core.models import Orderable


class MenuItem(Orderable):

    link_title = models.CharField(
        blank=True,
        null=True,
        max_length=50,
    )
    link_url = models.CharField(
        max_length=500,
        blank=True,
    )
    link_page = models.ForeignKey(
        "wagtailcore.Page",
        null=True,
        blank=True,
        related_name="+",
        on_delete=models.CASCADE,
    )
    open_in_new_tab = models.BooleanField(default=False, blank=True)

    page = ParentalKey("Menu", related_name="menu_items")

    panels = [
        FieldPanel("link_title"),
        FieldPanel("link_url"),
        PageChooserPanel("link_page"),
        FieldPanel("open_in_new_tab"),
    ]

    @property
    def link(self) -> str:
        if self.link_page:
            return self.link_page.url
        elif self.link_url:
            return self.link_url
        return '#'

    @property
    def title(self):
        if self.link_page and not self.link_title:
            return self.link_page.title
        elif self.link_title:
            return self.link_title
        return "Missing Title"


@register_snippet
class Menu(ClusterableModel):
    """The main menu clusterable model."""

    title = models.CharField(max_length=100) 
    slug = AutoSlugField(populate_from="title", editable=True)

    panels = [
        MultiFieldPanel(
            [
                FieldPanel("title"),
                FieldPanel("slug"),        
            ], heading="Menu",
        ),
        InlinePanel("menu_items", label="Menu Item")
    ]

    def __str__(self):
        return self.title

Model은 Orderable과 ClusterableModel 2개를 만들어주었다. 간단하게 설명하자면 Orderable은 순서배치가 가능한 모델을 말하고 Clusterable은 나열가능한 모델을 갖는 모델을 말한다. 즉, Clusterable을 순서배치가 가능한 Orderable 모델을 가질 수 있다. 

 

@property를 통해 link title과 link url을 리턴해주었다. 

def에 -> str을 사용하면 리턴값이 문자열이 된다. 

 

django_extensions에 AutoSlugField가 있다. 

    title = models.CharField(max_length=100) 
    slug = AutoSlugField(populate_from="title", editable=True)

populate_form에 slug로 변환할 변수를 넣으면 자동으로 slug를 만들어준다. 수정가능하게 하고 싶다면 eidtable값을 True로 해준다.

 

 

2. menus_tags.py

menus/templatetags/menus_tags.py

from django import template

from ..models import Menu

register = template.Library()

@register.simple_tag()
def get_menu(slug):
    return Menu.objects.get(slug=slug)

 

 

Custom Template tag 기능을 사용하여 get_menu라는 이름의 함수를 만들어준다.

template에서 get_menu를 호출하면 Menu objects에서 slug 값이 같은 객체를 찾아서 리턴해준다. 

 

 

3. Template

admin 페이지에서 snippets에 있는 Menu로 이동해서 Main Nav라는 title의 모델을 하나 만들어준다.

 

 

base.html

{% load menus_tags %}

{% get_menu "main-nav" as navigation %}

{% for item in navigation.menu_items.all %}
  <li class="nav-item">
  	<a class="nav-link" href="{{ item.link }}"{% if item.open_in_new_tab %} target="_blank"{% endif %}>{{ item.title }}</a>
  </li>
{% endfor %}

 

main-nav의 변수를 slug로 get_menu 함수에 전달해서 return 값을 받아 navigation이라는 template 변수로 받는다. 

 

이후 for문법을 사용하여 네비게이션을 설정해주면 된다. 

(참고로 a태그에 target을 _blank을 주면 새로운 창을 열어서 표시한다.)

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

Wagtail API v2 Exposing Custom Fields  (0) 2021.11.24
Wagtail API v2 Settings(Headless CMS)  (0) 2021.11.24
Wagtail, Django Paginator  (0) 2021.11.21
Wagtail StructValue  (0) 2021.11.18
Wagtail Sitemap  (0) 2021.11.17

Web개발에 필수인 Paginator를 구현하는 방법에 대해서 알아보자. 

Django와 Wagtail 둘다 Django Paginator을 사용하니 방법은 동일하다. 

 

 

1. Models.py (django에서는 View)

  from django.core.paginator import EmptyPage, PageNotAnInteger, Paginator
    
    
    def get_context(self, request, *args, **kwargs):
        context = super().get_context(request, *args, **kwargs)
        all_posts = BlogDetailPage.objects.live().public().order_by('-first_published_at')
        
        paginator = Paginator(all_posts, 2) # @todo change to 5 per page
        
        page = request.GET.get("page")
        
        try:
            posts = paginator.page(page)
        except PageNotAnInteger:
            posts = paginator.page(1)
        except EmptyPage:
            posts = paginator.page(paginator.num_pages)
            
        context['posts'] = posts
        context['categories'] = BlogCategory.objects.all()     
        return context

코드를 살펴보자.

 

 

1)

all_posts = BlogDetailPage.objects.live().public().order_by('-first_published_at')

all_posts 변수에 publish된 모든 BlogDetailPage의 객체들을 발행 순서대로 저장한다는 뜻이다. 재발행은 순서에 영향을 미치지 않는다.

 

 

2)

paginator = Paginator(all_posts, 2) # @todo change to 5 per page

Paginator 모듈을 사용하여 한 페이지에 보여질 객체의 갯수를 정한다. all_posts에 저장된 객체들(포스트)을 페이지에 2개씩 표시한다는 의미이다. 

 

 

3)

page = request.GET.get("page")

page 번호를 GET 메소드를 사용하여 가져온다. 

 

 

4) 

try:
	posts = paginator.page(page)
except PageNotAnInteger:
	posts = paginator.page(1)
except EmptyPage:
	posts = paginator.page(paginator.num_pages)

try, except를 사용하여 오류처리를 해준다. GET 메소드를 사용하여 가져온 페이지 번호와 포스트들을 posts에 저장한다.

 

PageNotAnInteger는 파라미터로 전달 된 post의 값이 숫자가 아닌 값이 들어올 때, 페이지 1번으로 이동하라는 의미이다.

 

EmptyPage는 페이지가 비어있는 페이지일 때, 처리하는 방법이다. 2페이지 밖에 없는데 파라미터 값으로 100같은 숫자가 들어올 때는 존재하는 page 객체들을 posts에 저장한다.

 

 

2. Templates

		{% if posts.paginator.num_pages > 1 %}
                    <div class="pagination">
                        {% if posts.has_previous %}
                            <li class="page-item">
                                <a href="?page={{ posts.previous_page_number }}" class="page-link">
                                    <span>&laquo;</span>
                                </a>
                            </li>
                        {% endif %}

                        {% for page_num in posts.paginator.page_range %}
                            <li class="page-item {% if page_num == posts.number %} active{% endif %}">
                                <a href="?page={{ page_num }}" class="page-link">
                                    {{ page_num }}
                                </a>
                            </li>
                        {% endfor %}

                        {% if posts.has_next %}
                            <li class="page-item">
                                <a href="?page={{ posts.next_page_number }}" class="page-link">
                                    <span>&raquo;</span>
                                </a>
                            </li>
                        {% endif %}
                    </div>
                {% endif %}

Template에서의 표현이다. posts.paginator의 page 갯수가 1개 이상일 때만 표시가 된다. 

 

has_previous는 이전페이지

has_next는 다음 페이지

page_range는 페이지 갯수를 의미한다. 

 

그리고 GET 메소드 사용을 위하여 href에서 page라는 변수에 파라미터 값으로 현재 페이지의 번호를 전달한다.

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

Wagtail API v2 Settings(Headless CMS)  (0) 2021.11.24
Wagtail Menu System  (0) 2021.11.23
Wagtail StructValue  (0) 2021.11.18
Wagtail Sitemap  (0) 2021.11.17
Wagtail 참고 사이트  (0) 2021.11.17
class ButtonBlock(blocks.StructBlock):
      button_page = blocks.PageChooserBlock(required=False)
      button_url = blocks.URLBlock(required=False)

여기 StructBlock에 2개의 Field Block이 있다. 이걸로 a태그로 만들어 관련 페이지로 이동하게 하는 기능을 만들고 싶어한다고 가정해보자. 내부 Link로 이동하고 싶으면 button_page를 외부 링크로 이동하고 싶으면 button_url을 사용한다. (이동하는 url은 한개이기 때문에 Field Block 한개만 사용한다. )

 

 

이럴경우 보통 Template에서

{% if card.button_page %}
  <a href="{{ card.button_page.url }}" class="btn btn-primary">
  	Learn More (internal)
	</a>
{% elif card.button_url %}
  <a href="{{ card.button_url }}" class="btn btn-primary">
  	Learn More (external)
  </a>
{% endif %}

위와 같이 사용한다. 물론 잘못된 것이 아니다. 하지만 2개의 Field Block말고 수많은 Field Block중 한가지만 사용하고 싶을 때에는 아마도 elif가 끊임 없이 늘어나서 Template이 지저분해질 것이다. (신경 안쓰인다면 뒤로가기 버튼을 누르면 된다.)

 

그래서 If Logic을 미리 BackEnd에서 처리하는 방법을 알아보려고 한다.

 

 

class LinkStructValue(blocks.StructValue):
    """Additional logic for our urls."""
    
    def url(self):
        button_page = self.get('button_page')
        button_url = self.get('button_url')
        if button_page:
            return button_page.url
        elif button_url:
            return button_url
        
        return None

방법은 간단하다. StructValue를 상속받은 LinkStructValue를 만들어주었다. (StructBlock이 아니다.) 

그리고 여기서 관련 Logic을 작성해주면 된다.

 

이후 간단하게 ButtonBlock에

class ButtonBlock(blocks.StructBlock):
    """An external or internal URL."""
    
    button_page = blocks.PageChooserBlock(required=False, help_text="If selected, this url will be used first")
    button_url = blocks.URLBlock(required=False, help_text="If added, this url will be used secondarily to the button page")
       
    class Meta:
        template = "streams/button_block.html"
        icon = "placeholder"
        label = "Single Button"
        value_class = LinkStructValue

value_class를 등록해주면 된다. 

자, 이제 이렇게 되면 BackEnd 단계에서 Logic을 처리하고 FrontEnd의 템플릿은 깔끔해질 것이다. 

 

심지어

class LinkStructValue(blocks.StructValue):
    """Additional logic for our urls."""
    
    def url(self):
        button_page = self.get('button_page')
        button_url = self.get('button_url')
        if button_page:
            return button_page.url
        elif button_url:
            return button_url
        
        return None
    
    def latest_posts(self):
        return BlogDetailPage.objects.live().public()[:3]

위와 같이 다른 객체를 가져와서 return해주는 것도 가능하다. 

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

Wagtail Menu System  (0) 2021.11.23
Wagtail, Django Paginator  (0) 2021.11.21
Wagtail Sitemap  (0) 2021.11.17
Wagtail 참고 사이트  (0) 2021.11.17
Wagtail git ignore setting  (0) 2021.11.16

+ Recent posts