TIL & Todo List/Coding for Entrepreneures

Day-2(Signal, get_object & get_queryset, form)

navill 2020. 1. 13. 00:25

Signal(pre_save & post_save)을 이용한 SlugField

  • django의 Signal은 특정 메서드나 함수가 실행될 때 일어나는 신호(signal)를 받아서 추가 작업을 진행할 수 있도록 한다.
    • 예) 모델이 저장될 시점의 전(pre_save), 후(post_save)에 추가적인 작업을 구현할 수 있다.
  • Signal은 signal을 정의한 모델을 사용하는 모든 앱에서 동작한다.
    • videos에 정의된 모델(signal을 포함하는)을 다른 앱에서 저장하거나 업데이트를 할 때 signal이 동작한다.
  • pre_save, post_save 외에 model signal, management signal, request/response signal 등 다양한 signal이 있다.

 

SlugField는 url에서 look up field로 사용될 수 있으며, 웹 검색 시 SEO(Search Engine Optimization)을 향상하는데 도움을 준다.

# modles.py

# pre_save()
def pre_save_video_receiver(sender, instance, *args, **kwargs):
    instance.slug = slugify(instance.title)

pre_save.connect(pre_save_video_receiver, sender=Video)


# post_save()
def post_save_video_receiver(sender, instance, *args, **kwargs):
    instance.slug = slugify(instance.title)
    instance.save()  # 무한 재귀가 일어남
    
post_save.connect(post_save_video_receiver, sender=Video)    


# decorator를 이용한 pre_save()
@receiver(pre_save, sender=Video)
def pre_save_video_receiver(sender, instance, *args, **kwargs):
    instance.slug = slugify(instance.title)

- SlugField를 자동으로 채우기 위해 signal을 이용할 수 있다.

- slugify(django.utils.text.slugify): 입력된 매개변수를 slug 형태로 변환시켜주는 메서드

  • pre_save()는 slug를 생성하고 이를 instance.slug에 할당하고 이후에 save()
  • post_save()는 instance.save()가 일어난 후 slug를 생성하고 instance.slug에 할당하기 때문에 database에 slug가 저장되지 않는다.
    • instance.save()를 post_save안에 등록할 경우 save 호출 -> post_save호출 -> save호출.... 무한 재귀가 일어난다.
  • 따라서 instance의 필드에 추가로 변경된 값을 저장해야 할 경우 pre_save()를 이용해야 한다.

 

Overriding get_object

get_object(): 응답에 필요한 객체를 가져오기 위해 사용되는 메서드

# views.py
class VideoDetailView(DetailView):
    queryset = Video.objects.all()

    # get_object를 오버라이딩 하지 않을 경우 자동으로 url에서 id 또는 slug를 검색하고 해당 객체를 반환한다.
    def get_object(self, queryset=None):
        slug = self.kwargs.get('slug')
        return get_object_or_404(Video, slug=self.kwargs.get(slug))
        
# urls.py
urlpatterns =[
	...
	path('videos/<slug>', VideoDetailView.as_view(), name='video-detail'),
]
  • self.kwargs.get('slug')는 urls.py에 등록된 path의 slug를 의미한다.
    • self.kwargs -> django.views.generics.base.View

 

get_queryset(): 응답에 필요한 쿼리셋을 가져오기 위해 사용되는 메서드

class VideoListView(ListView):
    # queryset = Video.objects.all()  # -> Video의 모든 객체

    def get_queryset(self):
    	# Video에서 title에 'django'를 포함하는 모든 객체
        return Video.objects.filter(title__icontains='django')
  • get_queryset()을 오버 라이딩하여 사용자가 원하는 쿼리 셋을 가져오기 위한 동작을 구성할 수 있다.

 

Form(공식 문서) - CreateView, UpdateView, DeleteView

  • ListView와 DetailView는 클라이언트가 요청한 객체를 전달하기 위해 queryset 또는 get_object를 이용한다. 이와 달리 Create, Update, Delete 작업을 위한 클래스에서는 queryset이 아닌 model 속성을 등록해서 사용한다.
  • POST를 사용할 때 csrf_token(공식 문서) 태그를 반드시 사용해야 한다.
    • Cross Site Request Forgery(CSRF): 사용자가 의도한 요청이 아닌, 외부 유저(해커)가 사이트에 요청을 대신 전달하게 하는 공격 방식
    • csrf_token은 페이지에 접속할 때 유저에게 token을 전달하고 post 동작이 필요할 때 django가 유저의 token을 검사하는 방식을 사용한다.
      1. 게시글을 작성하기 위한 페이지에 접속(유저는 token_a 획득)
      2. 글 작성을 위한 POST 실행
      3. django에서 토큰 확인(token_a? token_b?)
        (중간 과정에서 해커가 접속하여 다른 동작(공격)을 시도하더라도 토큰(token_a)을 가지고 있지 않기 때문에 공격 불가)
# views.py
class VideoCreateView(CreateView):
    # queryset = Video.objects.all()  # ImproperlyConfigured 에러를 일으킨다.
    model = Video
    form_class = VideoForm
    
# forms.py
class VideoForm(forms.ModelForm):
    class Meta:
        model = Video
        fields = [
            'title', 'embed_code'
        ]

# templates/video_create.html(content 블록)
{% block content %}

<h1>Hello!!</h1>
<form method="POST" action="" >
    {% csrf_token %}
    {{ form.as_p }}
    <input type="submit" value="Save" class="btn btn-primary">
</form>
{% endblock %}

form의 field에 등록된 두 개의 필드(title, embed code)에 대한 form이 생성된다.
폼에서 저장한 페이지 출력

 

Search view

get 메서드를 이용한 검색 기능 구현

# views.py
class VideoListView(ListView):
    def get_queryset(self):
        q = self.request.GET.get('q')
        qs = Video.objects.all()
        if q:  # query string이 있을 경우
            qs = qs.filter(title__icontains=q)
        return qs


# templates/video_list.py
...
<form method="GET" action="{% url 'video-list' %}">
    <input type="text" placeholder="Search.." name="q">
</form>
{{ object_list }}

<ul>
    {% for item in object_list%}
    <li>
        <a href="{{ item.get_absolute_url }}">
            {{ item.title}}
        </a></li>
    {% empty %}
    <li>No item Found</li>
    {% endfor %}
</ul>

self.request.GET.get('q'): 요청(get)한 url의 q(query string)에 포함된 단어를 검색어로 사용한다. 검색 입력 창에 단어를 입력 후 엔터를 칠 경우, template의 method(GET)가 동작한다.

title__icontains=query: 검색에 사용될 대상 필드를 찾기위해 사용되는 구문(Django-Field lookups). <필드명>__<Field lookups>로 구성된 키워드 인수이며, icontains 외에 필터 기능에 따라 여러가지 lookup이 존재한다. Field lookup 구문은 SQL에서 'WHERE'절에 해당한다.

name='q': url에 사용될 query string을 담을 변수 명을 지정한다.

{% empty %}: empty tag를 이용해 for 구문에서 조건에 맞는 객체를 찾지 못할 경우(비어있을 경우) 출력할 값 또는 문구를 지정할 수 있다.

 

query string은 url에서 '?q=' 와 같이 구성되며 문자열을 q에 담아 view에 전달하는 방식이다.
empty tag 실행