SSAMKO의 개발 이야기

한 페이지에서 여러 개의 폼(forms) 다루기 | Django 본문

Django

한 페이지에서 여러 개의 폼(forms) 다루기 | Django

SSAMKO 2020. 3. 28. 00:36
반응형

기존 로그인 화면

기존 위와 같던 로그인 화면에 아래와 같이 비밀번호 분실시 재설정을 할 수 있도록 바꾸고자 했습니다.

비밀번호 찾기 기능 추가

클릭하면 새 창을 띄우지 않고, Modal로 처리하고 싶었습니다.

클릭 시 Modal Display

이후 확인을 누르면 비밀번호 재설정 메일을 보내는 식으로 View를 구성하고자 했는데요.

여기서 가장 큰 어려움이 새 창이 아니라 Modal을 사용하려면 한 페이지에 2개의 폼을 띄워야 한다는 점이었습니다.

단순히 View 클래스만 상속받아서 복잡하게 코드를 짜면 가능은 하겠지만 Django답지 못하고, Class-based View의 장점을 살리지 못했기에 고민하며 구글링을 시작했습니다.

몇 가지 방법이 나왔지만 가장 만족스러웠던 건

https://www.codementor.io/@lakshminp/handling-multiple-forms-on-the-same-page-in-django-fv89t2s3j

 

Handling Multiple Forms on the Same Page in Django | Codementor

Learn how to implement two different forms on the same page the "Django" way.

www.codementor.io

 

위 사이트였습니다. 그래서 위 사이트의 내용을 뽑아와 적용시켜보기로 했습니다.

(사이트만 보고 하실 분들은 httpResponsseForbidden이 빠져있으니 import해주셔야 합니다.)

from django.http import httpResponseForbidden

일단 기본 원리는 이렇습니다.

View.py

class MultipleFormsLoginView(MultiFormsView):
    template_name = "login.html"
    form_classes = {'login': LoginForm_multi,
                    'get_admin': GetAdminForm,
                    }

    success_urls = {
        'login': reverse_lazy('index'),
        'get_admin': reverse_lazy('send_password_mail'),
    }

    def login_form_valid(self, form):
        # Logic Here
        return HttpResponseRedirect(self.get_success_url(form_name))

    def get_admin_form_valid(self, form):
        # Logic Here
        return HttpResponseRedirect(self.get_success_url(form_name))

Django의 Mixin클래스를 활용하여 재사용이 가능한 하나의 Class-based View 모듈을 만들어 그것을 이용하는 겁니다.

MultiFormsView의 속성(Attributes)들을 간단히 살펴보면,

하나의 Template을 이용해서 (template_name)

여러 개의 폼(form)을 입력받아 (form_classes)

각각의 success_url을 지정해주는 거죠. (success_urls)

그리고 각각 폼이 valid할 때, 즉 최종적으로 처리할 로직을 <form__name>_form_valid()함수로 작성해주면 되는거죠.

Forms.py

class MultipleForm(forms.Form):
    action = forms.CharField(max_length=60, widget=forms.HiddenInput())

class LoginForm(MultipleForm):
    user = forms.CharField(max_length=64, label='아이디')
    password = forms.CharField(widget=forms.PasswordInput, label='비밀번호')

class GetAdminForm(MultipleForm):
    email = forms.EmailField(widget=forms.Emailinput(), label='이메일')
    teacher_name = forms.CharField(max_length=16, label='선생님 성함')

Form에서 MultiForms로 사용할 Form들은 MultipleForm을 상속받습니다.

MultipleForm에는 action이라는 필드가 들어있는데요. 바로 이 action을 사용해 여러 폼들을 구별할 수 있게 됩니다.

마지막으로 위 사이트에 나온 모듈인데요. 해당 모듈은
https://gist.github.com/badri/4a1be2423ce9353373e1b3f2cc67b80b
위 링크에서 확인하실 수 있습니다.

불러오는 중입니다...

구성을 간단히 살펴보면

multiforms.py

from django.views.generic.base import ContextMixin, TemplateResponseMixin
from django.views.generic.edit import ProcessFormView
from django.http import HttpResponseForbidden


class MultiFormMixin(ContextMixin):

    form_classes = {}
    prefixes = {}
    success_urls = {}

    initial = {}
    prefix = None
    success_url = None

    def get_form_classes(self):
        return self.form_classes

    def get_forms(self, form_classes):
        return dict([(key, self._create_form(key, class_name))
                     for key, class_name in form_classes.items()])

    def get_form_kwargs(self, form_name):
        kwargs = {}
        kwargs.update({'initial': self.get_initial(form_name)})
        kwargs.update({'prefix': self.get_prefix(form_name)})
        if self.request.method in ('POST', 'PUT'):
            kwargs.update({
                'data': self.request.POST,
                'files': self.request.FILES,
            })
        return kwargs

    def forms_valid(self, forms, form_name):
        form_valid_method = '%s_form_valid' % form_name
        if hasattr(self, form_valid_method):
            return getattr(self, form_valid_method)(forms[form_name])
        else:
            return HttpResponseRedirect(self.get_success_url(form_name))

    def forms_invalid(self, forms):
        return self.render_to_response(self.get_context_data(forms=forms))

    def get_initial(self, form_name):
        initial_method = 'get_%s_initial' % form_name
        if hasattr(self, initial_method):
            return getattr(self, initial_method)()
        else:
            return {'action': form_name}

    def get_prefix(self, form_name):
        return self.prefixes.get(form_name, self.prefix)

    def get_success_url(self, form_name=None):
        return self.success_urls.get(form_name, self.success_url)

    def _create_form(self, form_name, form_class):
        form_kwargs = self.get_form_kwargs(form_name)
        form = form_class(**form_kwargs)
        return form


class ProcessMultipleFormsView(ProcessFormView):

    def get(self, request, *args, **kwargs):
        form_classes = self.get_form_classes()
        forms = self.get_forms(form_classes)
        return self.render_to_response(self.get_context_data(forms=forms))

    def post(self, request, *args, **kwargs):
        form_classes = self.get_form_classes()
        form_name = request.POST.get('action')
        return self._process_individual_form(form_name, form_classes)

    def _process_individual_form(self, form_name, form_classes):
        forms = self.get_forms(form_classes)
        form = forms.get(form_name)
        if not form:
            return HttpResponseForbidden()
        elif form.is_valid():
            return self.forms_valid(forms, form_name)
        else:
            return self.forms_invalid(forms)


class BaseMultipleFormsView(MultiFormMixin, ProcessMultipleFormsView):
    """
    A base view for displaying several forms.
    """


class MultiFormsView(TemplateResponseMixin, BaseMultipleFormsView):
    """
    A view for displaying several forms, and rendering a template response.
    """

위에도 언급했지만 링크에 가보면 3째줄에 있는 HttpResponseForbidden이 빠져있습니다. 코드 작성시 넣어주세요.

Django에서 Mixin Class View는 보통 Template과 Context를 처리하는 과정을 분리(?)하기 위해 사용됩니다.
이 모듈에 있는 MultiFormsView 역시 마찬가지 입니다.

Template은 Django의 기본 Mixin View인 TemplateResponseMixin을 상속받아 처리하고,

Context부분을 처리하기 위한 클래스를 만들어주고 있습니다.

 

MultiFormsView에서 Context처리를 위해 상속받고 있는 BaseMultipleFormsView 역시 이 모듈에서 정의하는 두 클래스를 상속받고 있습니다.

 

ProcessMultipleFormsView 에서는 get, post, process_individual_form 함수를 정의하고 있는데


get은 모든 폼을 준비시키고
post는 action 필드를 통해 구분된 form을 process_individual_form으로 보내 처리하도록 합니다.

 

MultiFormMixin은 MultiFormsView의 기본적인 뼈대를 갖추고 있습니다.

Conclusion

모듈을 사용해서 어떤 프로젝트에서도 손쉽게 한 페이지에서 여러 개의 폼을 사용할 수 있게 되었습니다. 프론트엔드와 결합된다면 다양한 프로젝트를 구상해볼 수 있을 것 같습니다.

반응형
Comments