태그 선택란 추가하기

Posted on 2021-08-25 by GKSRUDTN99
Django로 웹사이트 만들기 장고

장고에서 기본적으로 제공하는 ManyToMany Field에 대한 Form은 새로운 태그를 추가하기는 어렵다.
태그를 텍스트로 직접 입력할 수 있도록 구현해보자.

템플릿 파일에 input 추가하기

{{ form }}밑에 tags 필드를 추가한다.

<!-- post_form.html -->
{% extends 'blog/base_full_width.html' %}

{% block head_title %}Create Post - Blog{% endblock %}


{% block main_area %}
    <h1>Create New Post</h1>
    <hr/>
    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <table>
            {{ form }}
            <tr>
                <th><label for="id_tags_str">Tags:</label></th>
                <td><input type="text" name="tags_str" id="id_tags_str"></td>
            </tr>
        </table>
        <button type="submit" class="btn btn-primary float-end">Submit</button>
    </form>
{% endblock %}

테스트 코드 작성하기

쉼표와 세미콜론으로 구분된 태그들을 추가한다.

이미 존재하는 태그는 추가하지 않고, 새롭게 추가된 태그들만 추가되어야 한다.

# tests.py
    def test_create_post(self):
        # 로그인 하지 않으면 status code가 200이면 안된다!
        response = self.client.get('/blog/create_post/')
        self.assertNotEqual(response.status_code, 200)

        # staff가 아닌 trump가 로그인을 한다.
        self.client.login(username='trump', password='somepassword')
        response = self.client.get('/blog/create_post/')
        self.assertNotEqual(response.status_code, 200)

        # staff인 obama로 로그인한다.
        self.client.login(username='obama', password='somepassword')
        response = self.client.get('/blog/create_post/')
        self.assertEqual(response.status_code, 200)
        soup = BeautifulSoup(response.content, 'html.parser')

        self.assertEqual('Create Post - Blog', soup.title.text)
        main_area = soup.find('div', id='main-area')
        self.assertIn('Create New Post', main_area.text)

        tag_str_input = main_area.find('input', id='id_tags_str')
        self.assertTrue(tag_str_input)

        self.client.post(
            '/blog/create_post/',
            {
                'title': 'Post form 만들기',
                'content': "Post Form 페이지를 만듭시다.",
                'tags_str': 'new tag; 한글 태그, python'
            }
        )


        last_post = Post.objects.last()
        self.assertEqual(last_post.title, "Post form 만들기")
        self.assertEqual(last_post.author.username, 'obama')

        self.assertEqaul(last_post.tags.count(), 3)
        self.assertTrue(Tag.objects.get(name='new tag'))
        self.assertTrue(Tag.objects.get(name='한글 태그'))
        self.assertEqual(Tag.objects.count(), 5)

views.py 수정하기

post_form에 추가한 name='tags_str'인 input 요소에 입력된 값을 가져오기 위해 form_valid()에 기능을 추가한다.

# views.py
from django.utils.text import slugify

...

class PostCreate(LoginRequiredMixin, UserPassesTestMixin, CreateView):
    model = Post
    fields = ['title', 'hook_text', 'content', 'head_image', 'file_upload', 'category']

    def test_func(self):
        return self.request.user.is_superuser or self.request.user.is_staff

    def form_valid(self, form):
        current_user = self.request.user
        if current_user.is_authenticated and (current_user.is_staff or current_user.is_superuser):
            form.instance.author = current_user
            response = super(PostCreate, self).form_valid(form)

            # tags_str이라는 이름을 가진 post 데이터를 가져온다.
            tags_str = self.request.POST.get('tags_str')

            if tags_str:
                tags_str = tags_str.strip()

                tags_str = tags_str.replace(',', ';')
                tags_list = tags_str.split(';')

                for t in tags_list:
                    t = t.strip()
                    tag, is_tag_created = Tag.objects.get_or_create(name=t)
                    if is_tag_created:
                        tag.slug = slugify(t, allow_unicode=True)
                        tag.save()
                    self.object.tags.add(tag)

            return response
        else:
            return redirect('/blog/')

self.request.POST.get('tags_str')

tags_str이름을 가진 post 데이터를 가져온다.

tags_str = tags_str.strip()

strip 함수는 parameter(default = whitespace)를 문자열의 양 끝에서 제거한다.

tag, is_tag_created = Tag.objects.get_or_create(name=t)

get_or_create함수는 두가지 값을 동시에 리턴한다. 첫 번째는 Tag 모델의 인스턴스이고,
두 번째는 이 인스턴스가 새로 생성되었는지를 나타내는 bool 형태의 값이다.

tag.slug = slugify(t, allow_unicode=True)

slug는 사람이 읽기 편한 텍스트로 고유 url을 생성하고 싶을 때 사용하는 필드이다.
관리자페이지에서는 slug를 자동으로 채워넣어 생성해주지만,
이번에는 get_or_create 함수로 생성하였기 때문에 slug를 직접 저장해야한다.
slugify는 관리자 페이지에서 slug를 생성하는 것과 같은 원리로 동작한다.

self.objects.tags.add(tag)

태그를 새로 생성했든 가져왔든, 새로 만든 Post 객체의 tags 필드에 추가해주어야 하므로 add()를 통해 추가해준다.