Django 실전편 ch12

김용녀·2022년 7월 28일
0
post-thumbnail

이전 챕터에선 콘텐츠 편집 권한을 Photo앱에도 부여하려한다.
비슷한 내용이 많지만, Album과 Photo의 1:N 관계때문에 설계시 어떻게 작동되는지 구조를 정확히 이해하고 넘어가야한다.

photo/models.py

owner = models.ForeignKey('auth.User',on_delete=models.CASCADE,verbose_name='OWNER',blank=True,null = True)

모델링 파일에서 앨범 클래스와 포토클래스 각각 입력하면된다.

URL conf

포토와 앨범클래스 각각에 대해서 add/update/change/delete를 작성하면된다.

views.py

  • 이전에는 뷰 대부분이 모델폼을 이용해서 폼을 정의할 필요가 없었지만,
    photo는 폼셋을 따로 정의해야하므로 forms.py를 만들어주자.
forms.py
from django.forms import inlineformset_factory  
from photo.models import Album, Photo  


PhotoInlineFormSet = inlineformset_factory(Album, Photo,
    fields = ['image', 'title', 'description'], 
    extra = 2)

인라인 폼셋을 만든느 inlineformset_factory()를 임포트.
Photo에 있는 필드들을 폼셋에 사용한닫. 빈 폼셋의 개수는 2개


#--- Create/Change-list/Update/Delete for Photo
class PhotoCV(LoginRequiredMixin, CreateView):
    model = Photo
    fields = ('album', 'title', 'image', 'description')
    success_url = reverse_lazy('photo:index')

    def form_valid(self, form):
        form.instance.owner = self.request.user
        return super().form_valid(form)

#PhotoCV(createView)는 add할때 쓰이는 view다.

class PhotoChangeLV(LoginRequiredMixin, ListView):
    model = Photo
    template_name = 'photo/photo_change_list.html'

    def get_queryset(self):
        return Photo.objects.filter(owner=self.request.user)

#ChangeLV는 변경하려고할때 리스트를 보여준느 view다

class PhotoUV(OwnerOnlyMixin, UpdateView):
    model = Photo
    fields = ('album', 'title', 'image', 'description')
    success_url = reverse_lazy('photo:index')

#PhotoUV는 photoCV와 같은곳으로 이동한다.OwnerOnlyMixin을 상속받아 권한 허가자만 수정할수있도록 함.

class PhotoDelV(OwnerOnlyMixin, DeleteView):
    model = Photo
    success_url = reverse_lazy('photo:index')

#PhotoDelV는 리스트가 주어진 상황에서 삭제하도록 도와준다.

Change-list/Delete for Album

#--- 
class AlbumChangeLV(LoginRequiredMixin, ListView):
    model = Album
    template_name = 'photo/album_change_list.html'

    def get_queryset(self):
        return Album.objects.filter(owner=self.request.user)
#앨범 변경시 list를 보여준다.


class AlbumDelV(OwnerOnlyMixin, DeleteView):
    model = Album
    success_url = reverse_lazy('photo:index')
#앨범 삭제시 실행



#--- (InlineFormSet) Create/Update for Album
class AlbumPhotoCV(LoginRequiredMixin, CreateView):
    model = Album
    fields = ('name', 'description')
    success_url = reverse_lazy('photo:index')
#앨범을 add하는 경우에 해당 view가 실행된다.
    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.POST:
            context['formset'] = PhotoInlineFormSet(self.request.POST, self.request.FILES)
        else:
        #GET요청인경우
            context['formset'] = PhotoInlineFormSet()
        return context
#컨텍스트 변수에 formset 을 추가한다.

    def form_valid(self, form):
        form.instance.owner = self.request.user
       	#현재 유저에 권한 할당
        context = self.get_context_data()
        formset = context['formset']
#여기까지 폼셋은 유효성검사X 폼은 유효성검사 O

        for photoform in formset:
            photoform.instance.owner = self.request.user
#폼셋에 있는 각 폼의 owner필드에 로그인된 사용자의 User객체를 할당한다.
        if formset.is_valid():
            self.object = form.save()
#폼의 데이터를 테이블에 저장한다.             
            formset.instance = self.object
            formset.save()
#폼셋 저장 앨범 레코드에 1:N관계로 연결된 여러 레코드를 테이블에 저장.
            return redirect(self.get_success_url())
        else:
            return self.render_to_response(self.get_context_data(form=form))
#유효하지 않으면, 메인 폼 및 인라인 폼셋을 출력한다. 이전에 사용자가 입력한 데이터를 다시 보여준다.

updateView(UV)는 CV와 모두 같다고 보면된다.

class AlbumPhotoUV(OwnerOnlyMixin, UpdateView):
    model = Album
    fields = ('name', 'description')
    success_url = reverse_lazy('photo:index')

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        if self.request.POST:
            context['formset'] = PhotoInlineFormSet(self.request.POST, self.request.FILES, instance=self.object)
        else:
            context['formset'] = PhotoInlineFormSet(instance=self.object)
        return context

    def form_valid(self, form):
        context = self.get_context_data()
        formset = context['formset']
        if formset.is_valid():
            self.object = form.save()
            formset.instance = self.object
            formset.save()
            return redirect(self.get_success_url())
        else:
            return self.render_to_response(self.get_context_data(form=form))

AlbumPhotoCV,AlbumPhotoUV 은 다른 뷰들과 차별을 두어야하는게, Photo들을 inline한 상태로 같이 입력받아야 하기 때문이다. 그렇기때문에 클래스 뷰 내에 get_context_data와 form_valid를 두고 넘어오는 데이터를 검증한다.

Template 작성

  • 시작하기에 앞서 base.html에서 메뉴바의 Album과 Photo부분을 링크를 살려줘야한다.

photo_form.html

{% extends "base.html" %}
{% load widget_tweaks %}

{% block title %}photo_form.html{% endblock %}

{% block content %}
    <h1>Photo Create/Update - {{user}}</h1>
    <p class="font-italic">This is a creation or update form for your photo.</p>

    {% if form.errors %}
    <div class="alert alert-danger">
        <div class="font-weight-bold">Wrong! Please correct the error(s) below.</div>
        {{ form.errors }}
    </div>
    {% endif %}

    {% if form.is_multipart %}
#is_multipart()메서든느 폼이나 폼셋을 미리 체크해 인코딩이 필요한지 알려준다.    
    <form enctype="multipart/form-data" action="" method="post" class="card pt-3">
    {% else %}
    <form action="." method="post" class="card pt-3">
    {% endif %}
    {% csrf_token %}

        <div class="form-group row">
            {{ form.album|add_label_class:"col-form-label col-sm-2 ml-3 font-weight-bold" }}
            <div class="col-sm-2">
                {{ form.album|add_class:"form-control" }}
            </div> 
#앨범을 선택하는 드롭다운 박스 위젯 출력 Photo테이블이지만, 1:N관계의 Album 레코드도 정할수있다. 

            <div class="col-sm-2 my-auto">
                <a href="{% url 'photo:album_add' %}" class="btn btn-outline-primary btn-sm">
                Add Album</a>
            </div>
#사진 레코드 생성 폼이지만, 사진이 소속될 앨범 또한 add할수있다.
        </div>

        <div class="form-group row">
            {{ form.title|add_label_class:"col-form-label col-sm-2 ml-3 font-weight-bold" }}
            <div class="col-sm-5">
                {{ form.title|add_class:"form-control"|attr:"autofocus" }}
            </div>
        </div>

        <div class="form-group row">
            {{ form.image|add_label_class:"col-form-label col-sm-2 ml-3 font-weight-bold" }}
            <div class="col-sm-5">
                {{ form.image|add_class:"form-control-file" }}
            </div>
        </div>

        <div class="form-group row">
            {{ form.description|add_label_class:"col-form-label col-sm-2 ml-3 font-weight-bold" }}
            <div class="col-sm-8">
                {{ form.description|add_class:"form-control"|attr:"rows:3" }}
            </div>
        </div>

        <div class="form-group">
            <div class="offset-sm-2 col-sm-5">
                <input type="submit" value="Submit" class="btn btn-info"/>
            </div>
        </div>

    </form>

{% endblock %}

이후 photo_delete,change_list도 이전 챕터와 구조적으로 비슷하고,
Album_delete,change,list,form또한 유사하다.



profile
어서오세요

0개의 댓글