Django 기본 CBV API (Generic display views)

2022. 1. 10. 16:31강의 정리/Django Views

반응형

출처 : https://www.inflearn.com/course/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%A5%EA%B3%A0-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4/dashboard

 

장고(Django)를 배우기 시작한 입문자이시거나, 또는 배우고 싶은 생각이 있으신 분은 위 출처의 강의를 적극 추천드립니다!!!

 

 


 

 

 

  • Generic Display View : 객체의 리스트를 보여주거나, 특정 객체의 상세 정보를 보여준다.
  • Generic Display View
    • DetailView: 객체 하나에 대한 상세한 정보를 보여준다.
    • ListView: 조건에 맞는 여러 개의 객체를 보여준다.

 


 

DetailView: Model 객체 하나에 대한 상세한 정보를 보여준다.

class SingleObjectMixin(ContextMixin):
    """
    Provide the ability to retrieve a single object for further manipulation.
    """
    model = None
    queryset = None
    slug_field = 'slug'
    context_object_name = None
    slug_url_kwarg = 'slug'
    pk_url_kwarg = 'pk'
    query_pk_and_slug = False

    def get_object(self, queryset=None):
        """
        Return the object the view is displaying.
        Require `self.queryset` and a `pk` or `slug` argument in the URLconf.
        Subclasses can override this to return any object.
        """
        # Use a custom queryset if provided; this is required for subclasses
        # like DateDetailView
        if queryset is None:
            queryset = self.get_queryset()

        # Next, try looking up by primary key.
        pk = self.kwargs.get(self.pk_url_kwarg)
        slug = self.kwargs.get(self.slug_url_kwarg)
        if pk is not None:
            queryset = queryset.filter(pk=pk)

        # Next, try looking up by slug.
        if slug is not None and (pk is None or self.query_pk_and_slug):
            slug_field = self.get_slug_field()
            queryset = queryset.filter(**{slug_field: slug})

        # If none of those are defined, it's an error.
        if pk is None and slug is None:
            raise AttributeError(
                "Generic detail view %s must be called with either an object "
                "pk or a slug in the URLconf." % self.__class__.__name__
            )

        try:
            # Get the single item from the filtered queryset
            obj = queryset.get()
        except queryset.model.DoesNotExist:
            raise Http404(_("No %(verbose_name)s found matching the query") %
                          {'verbose_name': queryset.model._meta.verbose_name})
        return obj

    def get_queryset(self):
        """
        Return the `QuerySet` that will be used to look up the object.
        This method is called by the default implementation of get_object() and
        may not be called if get_object() is overridden.
        """
        if self.queryset is None:
            if self.model:
                return self.model._default_manager.all()
            else:
                raise ImproperlyConfigured(
                    "%(cls)s is missing a QuerySet. Define "
                    "%(cls)s.model, %(cls)s.queryset, or override "
                    "%(cls)s.get_queryset()." % {
                        'cls': self.__class__.__name__
                    }
                )
        return self.queryset.all()

    def get_slug_field(self):
        """Get the name of a slug field to be used to look up by slug."""
        return self.slug_field

    def get_context_object_name(self, obj):
        """Get the name to use for the object."""
        if self.context_object_name:
            return self.context_object_name
        elif isinstance(obj, models.Model):
            return obj._meta.model_name
        else:
            return None

    def get_context_data(self, **kwargs):
        """Insert the single object into the context dict."""
        context = {}
        if self.object:
            context['object'] = self.object
            context_object_name = self.get_context_object_name(self.object)
            if context_object_name:
                context[context_object_name] = self.object
        context.update(kwargs)
        return super().get_context_data(**context)
  • get_object
    • 모델로부터 object를 얻어오는 메소드이다
    • 위에서 지정한 클래스 변수들에 대해서 queryset, pk, slug를 설정해서 원하는 queryset을 만들어주는 역할이다
    • 이 메소드를 통해 객체를 가져오고, 실패할 경우 에러를 표시한다
  • get_queryset
    • 클래스 변수 queryset이 지정되어 있으면 그 queryset에 맞게 객체를 return한다
    • 기본적으로는 모델의 모든 객체들을 전부 반환한다
  • get_context_data
    • 모델의 이름으로 context를 넘겨주는 역할이다
    • 모델 이름 외에도 object라는 이름으로도 사용 가능
  • 1개 모델1개 Object에 대한 템플릿 처리
  • 모델명소문자 이름의 Model Instance를 템플릿에 전달 
    • 지정 pk 혹은 slug에 대응하는 Model Instance
      • slug란 URL의 구성요소로 웹사이트의 특정 페이지를 사람이 읽기 쉬운 형식의 식별자로 바꿔주는 것
  • template_name 인자가 지정되지 않았다면, 모델명으로 템플릿 경로 유추

 

 

 

#예를 들어서 로그인 한 유저만 특정 권한(글, 게시판 접근 가능)을 부여하고 싶다면 get_queryset이라는 함수를 구현해야한다

  def get_queryset(self):
        """
        Return the `QuerySet` that will be used to look up the object.
        This method is called by the default implementation of get_object() and
        may not be called if get_object() is overridden.
        """
        if self.queryset is None:
            if self.model:
                return self.model._default_manager.all()
            else:
                raise ImproperlyConfigured(
                    "%(cls)s is missing a QuerySet. Define "
                    "%(cls)s.model, %(cls)s.queryset, or override "
                    "%(cls)s.get_queryset()." % {
                        'cls': self.__class__.__name__
                    }
                )
        return self.queryset.all()
class PostDetailView(DetailView):
    model = Post

    def get_queryset(self):
        # 오버라이딩
        qs = super().get_queryset()
        # 만약 유저가 인증, 로그인 하지않았다면
        if not self.request.user.is_authenticated:
            # 공개된 것만 확인하도록
            qs = qs.filter(is_public=True)
        return qs

 

사용방법

 

# instagram/views.py

from django.views.generic import DetailView

post_detail = DetailView.as_view(
    model = Post,
    queryset = Post.objects.all()
)
  • model은 템플릿에 넘겨줄 model Instance를 의미한다
  • queryset은 넘겨줄 model Instance를 특정 filter등으로 더 정돈되게 템플릿에 넘겨준다

 

# instagram/templates/instagram/post_detail.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>Author : {{ post.author }}</h2>

    {% if post.photo %}
        <div>
            <img src="{{ post.photo.url }}" />
        </div>
    {% endif %}

    {{ post.message }}


</body>
</html>

 

 

 


 

ListView: 조건에 맞는 여러 개의 객체를 보여준다.

class MultipleObjectMixin(ContextMixin):
    """A mixin for views manipulating multiple objects."""
    allow_empty = True
    queryset = None
    model = None
    paginate_by = None
    paginate_orphans = 0
    context_object_name = None
    paginator_class = Paginator
    page_kwarg = 'page'
    ordering = None

    def get_queryset(self):
        """
        Return the list of items for this view.
        The return value must be an iterable and may be an instance of
        `QuerySet` in which case `QuerySet` specific behavior will be enabled.
        """
        if self.queryset is not None:
            queryset = self.queryset
            if isinstance(queryset, QuerySet):
                queryset = queryset.all()
        elif self.model is not None:
            queryset = self.model._default_manager.all()
        else:
            raise ImproperlyConfigured(
                "%(cls)s is missing a QuerySet. Define "
                "%(cls)s.model, %(cls)s.queryset, or override "
                "%(cls)s.get_queryset()." % {
                    'cls': self.__class__.__name__
                }
            )
        ordering = self.get_ordering()
        if ordering:
            if isinstance(ordering, str):
                ordering = (ordering,)
            queryset = queryset.order_by(*ordering)

        return queryset

    def get_ordering(self):
        """Return the field or fields to use for ordering the queryset."""
        return self.ordering

    def paginate_queryset(self, queryset, page_size):
        """Paginate the queryset, if needed."""
        paginator = self.get_paginator(
            queryset, page_size, orphans=self.get_paginate_orphans(),
            allow_empty_first_page=self.get_allow_empty())
        page_kwarg = self.page_kwarg
        page = self.kwargs.get(page_kwarg) or self.request.GET.get(page_kwarg) or 1
        try:
            page_number = int(page)
        except ValueError:
            if page == 'last':
                page_number = paginator.num_pages
            else:
                raise Http404(_("Page is not 'last', nor can it be converted to an int."))
        try:
            page = paginator.page(page_number)
            return (paginator, page, page.object_list, page.has_other_pages())
        except InvalidPage as e:
            raise Http404(_('Invalid page (%(page_number)s): %(message)s') % {
                'page_number': page_number,
                'message': str(e)
            })

    def get_paginate_by(self, queryset):
        """
        Get the number of items to paginate by, or ``None`` for no pagination.
        """
        return self.paginate_by

    def get_paginator(self, queryset, per_page, orphans=0,
                      allow_empty_first_page=True, **kwargs):
        """Return an instance of the paginator for this view."""
        return self.paginator_class(
            queryset, per_page, orphans=orphans,
            allow_empty_first_page=allow_empty_first_page, **kwargs)

    def get_paginate_orphans(self):
        """
        Return the maximum number of orphans extend the last page by when
        paginating.
        """
        return self.paginate_orphans

    def get_allow_empty(self):
        """
        Return ``True`` if the view should display empty lists and ``False``
        if a 404 should be raised instead.
        """
        return self.allow_empty

    def get_context_object_name(self, object_list):
        """Get the name of the item to be used in the context."""
        if self.context_object_name:
            return self.context_object_name
        elif hasattr(object_list, 'model'):
            return '%s_list' % object_list.model._meta.model_name
        else:
            return None

    def get_context_data(self, *, object_list=None, **kwargs):
        """Get the context for this view."""
        queryset = object_list if object_list is not None else self.object_list
        page_size = self.get_paginate_by(queryset)
        context_object_name = self.get_context_object_name(queryset)
        if page_size:
            paginator, page, queryset, is_paginated = self.paginate_queryset(queryset, page_size)
            context = {
                'paginator': paginator,
                'page_obj': page,
                'is_paginated': is_paginated,
                'object_list': queryset
            }
        else:
            context = {
                'paginator': None,
                'page_obj': None,
                'is_paginated': False,
                'object_list': queryset
            }
        if context_object_name is not None:
            context[context_object_name] = queryset
        context.update(kwargs)
        return super().get_context_data(**context)
  • get_context_data
    • context 를 얻는 과정이며, pagination 을 할 경우에는 queryset 도 부분적으로 나눠서 가져와야 할 것이고, 여러가지로 바뀌어야 하는 부분이 많다. 이 부분이 if 문을 통한 분기로 구현이 되어있다.
  • 1개 모델에 대한 List 템플릿 처리
    • 모델명소문자_list 이름의 Qureyset을 템플릿에 전달
  • 페이징 처리 지원 # ex) 300개의 게시물을 한 페이지에 다 보여주는 것이 아닌 페이지를 나누어서 보여주는 것
  • 인자는 model 이름페이징 처리할 페이지 갯수를 받는다
  • template_name 인자가 지정되지 않았다면, 모델명으로 템플릿 경로 유추

 

사용방법

 

# instagram/views.py

1.
post_list = ListView.as_view(model=Post, paginate_by= 10)


2. 상속을 통한 방법
class PostListView(ListView):
    model = Post
    paginate_by = 10

post_list = PostListView.as_view()

 

한 페이지에 10개씩 글이 처리된 모습
검색창에 ?page를 적자 해당 페이지로 이동한 모습

 

 


페이징 UI 간략 코딩

https://django-bootstrap4.readthedocs.io/en/latest/templatetags.html#bootstrap-pagination

 

Template tags and filters — django-bootstrap4 21.2 documentation

Note In each of the following examples it is understood that you have already loaded the bootstrap4 template tag library, placing the code below at the beginning of each template in which the bootstrap4 template tag library will be used. Read the Installat

django-bootstrap4.readthedocs.io

 

# instagram/templates/instagram/post_list.html

{% load bootstrap4 %}

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@4.5.2/dist/minty/bootstrap.min.css" integrity="sha384-H4X+4tKc7b8s4GoMrylmy2ssQYpDHoqzPa9aKXbDwPoPUA3Ra8PA5dGzijN+ePnH" crossorigin="anonymous">    
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.min.js" integrity="sha384-QJHtvGhmr9XOIpI6YVutG+2QOK9T+ZnN4kzFN1RtK3zEFEIsxhlmWl5/YESvpZ13" crossorigin="anonymous"></script>
    <title> Instagram / Post List </title>
    <style>
        @media(max-width: 600px){
            body{}
        }
    </style>
</head>
<body>

    <form action="" method="get">
        <input type="text" name="q" value="{{ q }}" />
        <input type="submit" value="검색" />
    </form>


    <table class="table table-borderd table-hover">
        <tbody>
            {% for post in post_list %}
                <tr>
                    <td>
                        {{ post.pk }}
                    </td>
                    <td>
                        {% if post.photo %}
                            <img src="{{ post.photo.url }}" style="width: 100px;"/>
                        {% else %}
                            No Photo    
                        {% endif %}
                    </td>
                    <td>
                        {{ post.message }}                    
                    </td>
                </tr>
            {% endfor %}
        </tbody>
    </table>

    {{ is_paginated }}
    {{ page_obj }}
    {% if is_paginated %}
        {% bootstrap_pagination page_obj size="small" justify_content="center" %}
    {% endif %}
</body>
</html>

 


 

포스팅 갯수 뻥튀기 하는 방법

>python manage.py shell



KeyboardInterrupt
>>> for i in range(100):                         
...     post = random.choice(post_list)
...     post.pk = None
...     post.save()
... 

>>> exit()

# pk값이 None이면 새롭게 포스팅이 Insert된다. 왜냐하면 모델.saver()는 pk 값이 None이면 Insert하고, None이 아니면 업데이트를 해주기 때문이다.

반응형