Django – ModelForm field 사용자 정의 data- 속성 추가하기

Form field 가 html 로 렌더링 될 때 원하는 속성값을 부가 하고 싶은데, 장고 코드에서 자동으로 하고 싶다.

이래 저래 찾다가 코드가 발견

class CompanySelect(forms.Select):
    def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
        option = super().create_option(name, value, label, selected, index, subindex, attrs)
        if value:
            option["attrs"]["data-company"] = value.instance.company_id
        return option


class ProductProfileForm(forms.ModelForm):
    class Meta:
        model = ProductProfile
        fields = ["company", "name", "product", "fwver1", "fwver2", "fwver3", "fwver4", "testset", "boxlabel", "boxpq"]
        # exclude = ["order", "status", "date_done", "elapsed_time", "mode", "desc", "is_deleted", "updated_at", "created_at"]
        # fields = ["order", "product", "workline", "fwver", "testset", "quantity", "date_due", "box", "boxpq", "status"]
        labels = {
            "company": _("*회사명"),
            "product": _("상품명"),
...}

        widgets = {
            "company": forms.Select(attrs={"class": "form-select"}),
            # "product": forms.Select(attrs={"class": "form-select flex-fill", "required": True, "style": "width: 100px;"}),
            "product": CompanySelect(attrs={"class": "form-select flex-fill companyselect", "required": True}),
...

원래 그냥 forms.Select 만 쓰면 특별히 해당 필드 값을 가지고 속성을 별도로 주는 것이 어렵다. 클래스 처럼 한꺼번에 줄 수 는 있어도 해당 값에 맞는 값을 DB 에서 가져와서 넣어주는게 힘들다. 예를 들면 해당 레코드별로 있는 ID 값을 data-code 속성에 넣어주는 그런것은 하기 어렵다.

이때는 forms.Select 를 오버라이딩 해서 사용하면 된다.

CompanySelect 클래스를 참고하면 된다. 원하는 DB instance 의 값을 바로 쓸 수 있다.

아래 처럼 data-company 값이 떡 하니 적혀져서 렌더링 된다. 이값을 이용해서 코드를 작성하면 편리하게 활용할 수 있다.

<select name="form-0-product" class="form-select companyselect" required="" id="id_form-0-product">
  <option value="" selected="" style="display: none;">---------</option>

  <option value="200" data-company="1" style="display: none;">test1 (twareLAB)</option>

  <option value="208" data-company="1" style="display: none;">test11 (twareLAB)</option>

  <option value="221" data-company="1" style="display: none;">TJ100 (twareLAB)</option>

  <option value="244" data-company="1" style="display: none;">TW100XR (twareLAB)</option>

  <option value="222" data-company="9" style="">Product1 (WIZnet)</option>

  <option value="3" data-company="9" style="">Test (WIZnet)</option>

  <option value="210" data-company="10" style="display: none;">Product1-1 (TEST Company)</option>

  <option value="219" data-company="10" style="display: none;">Product1-111 (TEST Company)</option>

  <option value="220" data-company="10" style="display: none;">Product1-123 (TEST Company)</option>
</select>

빙에게서 받은 예제도 여기에 첨부해 둡니다.

Django의 forms.Select 위젯을 오버라이딩하는 예제를 제공해 드리겠습니다. forms.Select 위젯은 ChoiceField의 기본 위젯으로 HTML의 <select> 요소를 생성합니다.

예를 들어, 다음과 같은 ModelForm이 있다고 가정해봅시다.

from django import forms
from myapp.models import MyModel

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = ['myfield']

이 경우 myfieldChoiceField인 경우, 다음과 같이 forms.Select 위젯을 오버라이딩하여 사용자 정의 위젯을 만들 수 있습니다.

from django import forms
from myapp.models import MyModel

class MySelect(forms.Select):
    def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
        option = super(MySelect, self).create_option(name, value, label, selected, index, subindex=subindex, attrs=attrs)
        option['attrs']['data-myattribute'] = 'myvalue'
        return option

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = ['myfield']
        widgets = {
            'myfield': MySelect
        }

위와 같이 수정하면 myfield 필드의 <option> 요소에 data-myattribute="myvalue" 속성이 추가됩니다.

create_option 메서드는 Django의 forms.Select 위젯에서 <option> 요소를 생성하는 데 사용됩니다. 이 메서드는 다음과 같은 인수를 받습니다.

  • name: 필드의 이름입니다.
  • value: 옵션의 값입니다.
  • label: 옵션의 레이블입니다.
  • selected: 옵션이 선택되었는지 여부를 나타내는 불리언 값입니다.
  • index: 옵션의 인덱스입니다.
  • subindex: 옵션의 하위 인덱스입니다. 기본값은 None입니다.
  • attrs: 옵션의 HTML 속성을 나타내는 딕셔너리입니다. 기본값은 None입니다.

create_option 메서드는 위와 같은 인수를 받아서 딕셔너리 형태의 옵션 객체를 반환합니다. 이 딕셔너리에는 다음과 같은 키가 있습니다.

  • name: 필드의 이름입니다.
  • value: 옵션의 값입니다.
  • label: 옵션의 레이블입니다.
  • selected: 옵션이 선택되었는지 여부를 나타내는 불리언 값입니다.
  • index: 옵션의 인덱스입니다.
  • attrs: 옵션의 HTML 속성을 나타내는 딕셔너리입니다.

create_option 메서드를 오버라이딩하여 위와 같은 인수를 받고 딕셔너리 형태의 옵션 객체를 반환하는 사용자 정의 메서드를 만들 수 있습니다. 이렇게 하면 <option> 요소의 속성을 동적으로 변경할 수 있습니다.

create_option 메서드를 오버라이딩하는 예제를 더 제공해 드리겠습니다. 이전 예제에서는 create_option 메서드를 오버라이딩하여 <option> 요소에 data-myattribute="myvalue" 속성을 추가하는 방법을 보여드렸습니다.

이번에는 create_option 메서드를 오버라이딩하여 <option> 요소의 레이블을 동적으로 변경하는 방법을 보여드리겠습니다.

from django import forms
from myapp.models import MyModel

class MySelect(forms.Select):
    def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
        option = super(MySelect, self).create_option(name, value, label, selected, index, subindex=subindex, attrs=attrs)
        option['label'] = label.upper()
        return option

class MyModelForm(forms.ModelForm):
    class Meta:
        model = MyModel
        fields = ['myfield']
        widgets = {
            'myfield': MySelect
        }

위와 같이 수정하면 myfield 필드의 <option> 요소의 레이블이 모두 대문자로 변경됩니다.

위젯을 오버라이딩하는 이유

Django의 위젯은 폼 필드의 HTML 표현을 담당합니다. 기본적으로 Django는 각 필드 유형에 대해 기본 위젯을 제공하지만, 때로는 이러한 기본 위젯이 원하는 요구 사항을 충족시키지 못할 수 있습니다. 이 경우 위젯을 오버라이딩하여 사용자 정의 위젯을 만들 수 있습니다.

위젯을 오버라이딩하는 이유는 다음과 같습니다.

  • HTML 요소의 속성을 동적으로 변경하려는 경우
  • HTML 요소의 레이아웃이나 스타일을 변경하려는 경우
  • JavaScript 이벤트 핸들러를 추가하려는 경우
  • 기타 사용자 정의 요구 사항을 충족시키기 위해

위와 같은 이유로 위젯을 오버라이딩하여 사용자 정의 위젯을 만들 수 있습니다.

![Close-up of Modern Gadget Screen with Widget](assets/images/2023/04/13003482.jpg?resize=1024%2C767&ssl=1)

JQuery – data field 값에 따라 option 표시

특정 제품이 선택되면 종속적인 옵션을 보여주고, 숨기고 해야 하는 응용이 상당히 많이 있다.

DB에서 가져올 때 필터링 해서 가져오면 좋은데, 대부분 경우 이미 가져온 데이터를 브라우저 렌더링시에 필터링해서 보여주고 숨기고 하는것이 효율적이다. show / hide 기능을 쓸수 있어야 하는데,

편법인지 모르지만 data-xxx=”xxx” 이런 html tag 에 추가해두면 JQuery 에서 data() 함수로 바로 읽을 수 있다.

이를 이용해서 .show() .hide() 기능을 활용하면 원하는 결과를 얻을 수 있다.

JQuery 코드는 아래와 같다.

$("[name$='product']").on("change", function(){
    var product_code = parseInt($(this).val());
    $(".productselect option").hide().filter('[data-product='+product_code+']').show();
    $(".productsetselect option").hide().filter('[data-'+product_code+'='+product_code+']').show();
    $(".productsetselect option").hide();
    var options = $('.productsetselect option');
    var values = $.map(options ,function(option) {
      var getdata = $(option).data("product");
      // if (jQuery.type(getdata) === "number") {
      //   if (getdata == product_code) {
      //     $(option).show();
      //   }
      // }
      if (jQuery.type(getdata) === "array") {
        if (getdata.indexOf(product_code) >= 0) {
          $(option).show();
        }
      }
    });
});

적용되는 HTML 코드는 아래와 같다. 볼드로 강조해 두었다.

<div class="col-auto"><label class="form-label">펌웨어</label><select name="fwver1" class="form-select flex-fill companyselect productsetselect" id="id_fwver1">
  <option value="" selected>---------</option>

  <option value="51" <strong>data-company="1" data-208="208" data-200="200" data-product="[208, 200]"</strong>>avatar.png (twareLAB)</option>

  <option value="54" data-company="1" data-product="[]">filename (twareLAB)</option>

  <option value="65" <strong>data-company="1" data-208="208" data-product="[208]"</strong>>img001.gif (twareLAB)</option>

  <option value="66" data-company="1" data-product="[]">img002.gif (twareLAB)</option>

  <option value="67" data-company="1" data-product="[]">img003.png (twareLAB)</option>

  <option value="69" data-company="1" data-product="[]">img005.png (twareLAB)</option>

  <option value="70" data-company="1" data-208="208" data-product="[208]">img016.png (twareLAB)</option>

  <option value="68" data-company="1" data-product="[]">img016.png (twareLAB)</option>

  <option value="52" data-company="1" data-208="208" data-product="[208]">img020.png (twareLAB)</option>

  <option value="58" data-company="1" data-product="[]">img097.png (twareLAB)</option>

  <option value="56" data-company="1" data-221="221" data-product="[221]">img098.png (twareLAB)</option>

이렇게 되면, Product 가 선택될 때 해당 프로덕트에 맞는 펌웨어 리스트만 나오도록 표현할 수 있다.

productselect 클래스의 option을 전부 hide() 한 다음, 에서 data-product 값이 해당 값인 경우 show() 하도록 코드를 구성했다.

array 값이 올 수 도 있기 때문에, 이경우에는 간단하게 filter 함수로 이 components 들을 다 가져올 수 없어서, map() 함수로 iteration 하도록 구현해 봤다.

대안으로 data-productcode=”productcode” 형태로 나오도록 또 추가해서 간단하게 한줄로 가능하게 하는 것도 같이 추가해뒀다.

참고하시길..

Django Formfield 값에 data-xxx 속성을 넣는 방법은 다른 포스팅에서 올리겠다.

![](assets/images/2023/03/img103.gif?resize=546%2C282&ssl=1)