본문 바로가기

Dev/Python

[django] ModelForm을 사용하여 데이터 등록

환경: django 3.2.8

 

django 프로젝트 진행 중 ModelForm을 이용하여 데이터를 등록하는 작업을 진행하였습니다.

 

template(html)과 url은 구현이 되어있다고 가정하였을 때, views.py에는 아래와 같이 구현합니다.

def registerAcademy(request):
    form = AcademyForm()

    if request.method =='POST':
        form = AcademyForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('home')

    context = {'form':form}
    return render(request, 'base/academy_form.html',context)

위 코드에서 사용된 AcademyForm()은 아래와 같이 구현되어있습니다.

from django.forms import ModelForm, fields
from .models import Academy

class AcademyForm(ModelForm):
    class Meta:
        model = Academy
        fields = '__all__'

 

위 코드에서 순서대로 살펴보도록 하겠습니다.

먼저 AcademyForm은 ModelForm을 상속받아 구현되어 있습니다.

ModelForm은 아래와 같이 구현되어 있습니다.

# 경로 django/forms/models.py

class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
    pass

BaseModelForm을 상속받아 구현되어 있으며 이것은 아래와 같이 구현되어 있습니다.

# 경로 django/forms/models.py

class BaseModelForm(BaseForm):
    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=None,
                 empty_permitted=False, instance=None, use_required_attribute=None,
                 renderer=None):
        opts = self._meta
        if opts.model is None:
            raise ValueError('ModelForm has no model class specified.')
        if instance is None:
            # if we didn't get an instance, instantiate a new one
            self.instance = opts.model()
            object_data = {}
    ...생략...

BaseForm을 상속받아 구현되어 있으며 이것은 아래와 같이 구현되어 있습니다.

#경로: django/forms/forms.py

class BaseForm:
    """
    The main implementation of all the Form logic. Note that this class is
    different than Form. See the comments by the Form class for more info. Any
    improvements to the form API should be made to this class, not to the Form
    class.
    """
    default_renderer = None
    field_order = None
    prefix = None
    use_required_attribute = True

    def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                 initial=None, error_class=ErrorList, label_suffix=None,
                 empty_permitted=False, field_order=None, use_required_attribute=None, renderer=None):
        self.is_bound = data is not None or files is not None
        self.data = MultiValueDict() if data is None else data
        self.files = MultiValueDict() if files is None else files
        self.auto_id = auto_id
        if prefix is not None:
            self.prefix = prefix
        self.initial = initial or {}
    #...생략...
    
    @property
    def errors(self):
        """Return an ErrorDict for the data provided for the form."""
        if self._errors is None:
            self.full_clean()
        return self._errors
    
    def is_valid(self):
        """Return True if the form has no errors, or False otherwise."""
        print (f'is_valid 호출')
        return self.is_bound and not self.errors

 

위를 참고하여 소스코드를 다시 분석하도록 하겠습니다.

1. [아래 소스코드 참조] request.method가 POST라면 AcademyForm에 request.POST 데이터를 넘기며 객체를 만들어 form에 할당합니다.

2. [BaseForm 소스코드 참조] 파라미터로 넘겨진 데이터는 BaseForm 클래스의 __init__함수를 보시면 data로 넘겨받게 되어 있습니다.

3. [BaseForm 소스코드 참조] 넘겨받은 data는 None이 아니라면 MultiValueDict()로 self.data에 할당합니다. 이때 self.is_bound 변수도 data가 None이 아닌지 files이 None이 아닌지 저장하게 됩니다. 

4. [아래 소스코드 참조] form.is_valid()를 호출을 하여 데이터가 유효한지 확인합니다.

5. [BaseForm 소스코드 참조] is_valid 함수를 호출하면 self.is_bound를 호출하여 값을 확인하고 self.errors를 호출하여 값을 리턴해줍니다.

 

위의 동작이 모두 정상이라면 form을 save 해주는데 해당 동작은 BaseModelForm에 구현되어 있습니다.

def registerAcademy(request):
    form = AcademyForm()

    if request.method =='POST':
        form = AcademyForm(request.POST)
        if form.is_valid():
            form.save()
            return redirect('home')

    context = {'form':form}
    return render(request, 'base/academy_form.html',context)

아래는 BaseModelForm에 구현되어 있는 save 함수입니다.

def save(self, commit=True):
    """
    Save this form's self.instance object if commit=True. Otherwise, add
    a save_m2m() method to the form which can be called after the instance
    is saved manually at a later time. Return the model instance.
    """
    if self.errors:
        raise ValueError(
            "The %s could not be %s because the data didn't validate." % (
                self.instance._meta.object_name,
                'created' if self.instance._state.adding else 'changed',
            )
        )
    if commit:
        # If committing, save the instance and the m2m data immediately.
        self.instance.save()
        self._save_m2m()
    else:
        # If not committing, add a method to the form to allow deferred
        # saving of m2m data.
        self.save_m2m = self._save_m2m
    return self.instance

위 함수에서 erros가 있는지 체크하고 commit이 True이니 데이터를 저장하도록 합니다.

self.instance.save()와 self._save_m2m()은 이후 다시 다루어 보겠습니다.