14 분 소요

Product Attributes

Build : Implementing the Attribute Models

기존의 Attributes Values 테이블과 ProductLine 테이블은 다대다 관계였지만 다대다 관계에서는 특정 송성값이 중복되지 않도록 막거나 유효성을 검사하는 등의 작업을 하기 어렵다.

예를 들어 주문과 주문 상품 사이의 관계에서 중간 테이블에 수량 필드를 추가하거나, 특정 상품이 한 주문 내에서 중복되지 않도록 제약 조건을 설정하기 어렵다.

결론적으로 복잡한 조작이 필요한 상황에서는 OneToManyField 를 사용하는게 관계를 더 세밀하게 제어할 수 있기 때문에 상황에 따라 쓰면 된다.

models.py

  • Attribute 모델: 속성을 나타내는 모델이다. name과 description 필드를 가지고 있다.
  • AttributeValue 모델: 속성의 값을 나타내는 모델이다. attribute_value와 attribute 필드를 가지고 있으며, attribute 필드는 Attribute 모델과의 외래 키 관계다.
  • ProductLineAttributeValue 모델: 상품 라인과 속성 값 간의 중간 테이블을 나타내는 모델이다. attribute_value와 product_line 필드를 가지고 있으며, 각각 AttributeValue와 ProductLine 모델과의 외래 키 관계다. Meta 클래스에서 unique_together 속성을 사용하여 attribute_value와 product_line 필드의 값이 유일하게 유지되도록 설정했다.
  • ProductLine 모델: 상품 라인을 나타내는 모델이다. price, sku, stock_qty, product 등의 필드를 가지고 있으며, attribute_value 필드는 ManyToManyField를 사용하여 AttributeValue 모델과의 관계를 나타냅니다. through 옵션을 사용하여 ProductLineAttributeValue 모델을 통해 중간 테이블을 지정했다. clean 메서드를 사용하여 중복된 order 값을 가지는 객체를 방지하고, save 메서드를 오버라이딩하여 유효성 검사를 수행한 후 저장한다.

이 코드는 속성과 속성 값, 그리고 상품 라인 간의 관계를 정의하고, 중간 테이블을 사용하여 관계를 관리한다. 각 모델은 필요한 필드와 외래 키 관계를 정의하고, through 옵션을 사용하여 중간 테이블을 지정한다. 이를 통해 복잡한 관계를 다룰 수 있고, 중간 테이블에 추가적인 필드나 제약 조건을 설정할 수 있다.

unique_together

unique_together는 Django 모델의 Meta 클래스에서 사용되는 옵션 중 하나로, 여러 필드의 조합이 고유(unique)해야 함을 지정하는 기능이다. 즉, 지정된 필드들의 값들이 중복되지 않도록 제약 조건을 설정할 수 있다.

예를 들어, unique_together를 사용하여 “attribute_value”와 “product_line” 필드의 조합이 고유해야 함을 설정할 수 있다. 이는 “attribute_value”와 “product_line”의 값이 같은 조합이 중복되지 않도록 하는 제약 조건을 생성한다.

다음은 예시 코드다:

class ProductLineAttributeValue(models.Model):
    attribute_value = models.ForeignKey(AttributeValue, on_delete=models.CASCADE, related_name="product_attribute_value_av")
    product_line = models.ForeignKey(ProductLine, on_delete=models.CASCADE, related_name="product_attribute_value_pl")

    class Meta:
        unique_together = ("attribute_value", "product_line")

위 코드에서 “attribute_value”와 “product_line” 필드의 조합이 고유해야 함을 설정했다. 이제 같은 “attribute_value”와 “product_line” 값을 가지는 조합이 중복되지 않도록 제약 조건이 적용된다.

예를 들어, 다음과 같은 데이터가 있다고 가정해보자:

attribute_value: A, B, C product_line: X, Y, Z

이때, 다음과 같은 조합이 유효하다고 가정한다:

(A, X), (B, Y), (C, Z)

하지만, 다음과 같은 조합은 중복이므로 유효하지 않는다:

(A, X), (B, X), (C, Z)

unique_together를 사용하여 중복된 조합을 방지하면 데이터의 일관성을 유지할 수 있고, 잘못된 조합이 생성되는 것을 방지할 수 있다.

through

Django의 ManyToManyField에서 through 옵션은 관계의 중간에 직접적인 링크 테이블(link table)을 정의하고자 할 때 사용된다. 일반적으로 ManyToManyField는 자동으로 생성되는 링크 테이블을 사용하여 관계를 처리하지만, through 옵션을 사용하면 개발자가 직접 링크 테이블을 정의하고 추가적인 필드를 포함시킬 수 있다.

through 옵션을 사용하여 링크 테이블을 정의할 때는 중간 모델(intermediate model)을 생성해야 합니다. 이 중간 모델은 ManyToManyField를 연결하는 데 사용되며, 중간 모델에는 추가적인 필드를 포함할 수 있다.

위 코드에서 ProductLine 모델의 attribute_value 필드는 ManyToMany 관계를 정의하는데, ProductLineAttributeValue 모델을 중간 모델로 사용하도록 설정했다.
ProductLineAttributeValue 모델은 AttributeValueProductLine 사이의 관계를 매핑하며, 추가적인 필드를 포함하고 있다.

이 예시에서 ProductLineAttributeValue 모델은 attribute_valueproduct_line 필드로 ForeignKey를 가지고 있으며, 중간 모델의 Meta 클래스에서 unique_together를 사용하여 두 필드의 조합이 고유해야 함을 설정했다. 이렇게 함으로써 중복된 조합이 생성되지 않도록 제약 조건을 설정할 수 있다.

through 옵션을 사용하여 직접 링크 테이블을 정의하면, 중간 모델에 추가적인 필드를 포함하여 관계에 대한 세부 정보를 저장하거나 추적할 수 있다. 이를 통해 더 복잡한 관계를 다루거나 관계에 대한 추가적인 정보를 저장하는 등의 작업을 수행할 수 있다.

Build : Admin Site Attribute Value Inlines

admin.py

models.py

  • AttributeValueInline 클래스는 ProductLineAdmin 페이지에서 ProductLine 모델과 함께 인라인으로 표시되는 AttributeValue 모델을 설정한다. AttributeValueProductLine 사이의 관계를 통해 링크 테이블을 처리하도록 설정한다.

  • ProductLineAdmin 클래스는 ProductLine 모델을 관리하기 위한 설정을 제공한다. ProductImageInline 클래스와 AttributeValueInline 클래스를 인라인으로 표시하도록 설정한다.

AttributeValue.product_line_attribute_value.throughAttributeValue 모델과 ProductLine 모델 사이의 링크 테이블에 대한 참조다.

ManyToManyField를 사용하여 관계를 설정할 때 Django는 자동으로 링크 테이블을 생성한다. 이 링크 테이블은 두 모델 사이의 관계를 유지하고 관리한다. through 속성을 사용하면 링크 테이블에 직접적으로 접근할 수 있다.

위의 코드에서 AttributeValueInline 클래스에서 AttributeValue.product_line_attribute_value.through를 설정했다. 이를 통해 AttributeValueProductLine 사이의 링크 테이블을 처리할 수 있다. through 속성을 사용하여 링크 테이블의 모델을 지정하고, 이를 통해 AttributeValueProductLine 사이의 관계를 조작할 수 있다.

예를 들어, AttributeValueProductLine 사이의 링크 테이블에 새로운 연결을 생성하려면 다음과 같이 할 수 있다:

attribute_value = AttributeValue.objects.get(id=1)
product_line = ProductLine.objects.get(id=1)
link = attribute_value.product_line_attribute_value.through(attribute_value=attribute_value, product_line=product_line)
link.save()

링크 테이블을 통해 AttributeValueProductLine 사이의 추가 데이터를 저장하고 관리할 수 있다. 이를 통해 두 모델 간의 많은 관계를 조작할 수 있다.

Build : Product Attribute Serializer

현재 속성을 추가하면 숫자만 나오는 상태인데 이렇게 되면 프론트 쪽에서 뭔지 알기가 힘들다. 따라서 이름도 같이 나오게 만드는게 좋다.

serializers.py

class AttributeSerializer(serializers.ModelSerializer):
    class Meta:
        model = Attribute
        fields = ("name", "id")

class AttributeValueSerializer(serializers.ModelSerializer):
    attribute = AttributeSerializer(many=False)
    class Meta:
        model = AttributeValue
        fields = (
            "attribute",
            "attribute_value",
        )

class ProductLineSerializer(serializers.ModelSerializer):
    product_image = ProductImageSerializer(many=True)
    attribute_value = AttributeValueSerializer(many=True)

    class Meta:
        model = ProductLine
        fields = (
            "price",
            "sku",
            "stock_qty",
            "order",
            "product_image",
            "attribute_value",

        )

admin.py

product_line_attribute_value.throughAttributeValue 모델과 ProductLine 모델 간의 링크 테이블을 가리키는 참조다.

AttributeValueInline 클래스에서 model 속성을 AttributeValue.product_line_attribute_value.through로 설정함으로써, AttributeValueProductLine 사이의 링크 테이블을 관리하는 데 사용된다.

링크 테이블은 ManyToManyField 관계에서 자동으로 생성되는 중간 테이블이다. 이 테이블은 AttributeValueProductLine의 관계를 기록하고 유지한다. through 속성을 사용하여 링크 테이블의 모델을 지정하면, 해당 모델을 통해 링크 테이블에 직접 접근할 수 있다.

위의 코드에서 AttributeValueInline 클래스에서 model 속성을 AttributeValue.product_line_attribute_value.through로 설정했으므로, 링크 테이블을 관리하는 데 사용되는 모델을 지정하게 된다. 이를 통해 AttributeValueProductLine 사이의 관계를 효율적으로 다룰 수 있다.

예를 들어, AttributeValueInline 인라인 모델을 사용하여 ProductLineAdmin에 속성 값을 인라인으로 표시할 수 있다. 이를 통해 특정 ProductLine의 속성 값들을 관리하고 편집할 수 있다.

models.py **

위의 코드는 ProductLine 모델에서 attribute_value 필드를 정의하는 부분이다.

attribute_valueManyToManyField 관계로 선언되었다. 이 필드는 AttributeValue 모델과의 다대다 관계를 나타낸다.

through 매개변수를 사용하여 중간 테이블을 지정했다. 중간 테이블은 ProductLineAttributeValue 이다. 이를 통해 ProductLineAttributeValue 사이의 관계를 기록하고 유지한다.

related_name 매개변수는 ProductLine 모델에서 AttributeValue에 역참조할 때 사용할 이름을 지정한다. 위의 코드에서는 "product_line_attribute_value"로 설정되었다.

이 관계를 통해 ProductLineAttributeValue는 다대다 관계를 가지며, 중간 테이블 ProductLineAttributeValue를 통해 서로를 연결한다. ProductLineAttributeValue 모델은 중간 테이블을 다루는 역할을 하며, ProductLineAttributeValue 사이의 속성 값을 저장하고 관리한다.

이렇게 설정된 attribute_value 필드를 통해 ProductLine 인스턴스와 관련된 속성 값들을 효율적으로 다룰 수 있다. 예를 들어, 특정 ProductLine의 속성 값을 가져오거나 수정하는 등의 작업을 수행할 수 있다.

Design : Product Types and Type Attributes

위와 같이 선택할 옵션이 많아질 경우 컨트롤 하기가 어려워진다.
따라서 중간에 Product type 테이블을 만든 후 거기에 속성 값을 나눠줘야 한다.

Build : Implementing the Product Type Tables

모델 필드 레퍼런스 공문

models.py 위의 코드는 ProductType 모델과 Attribute 모델 사이의 다대다 관계를 나타내기 위한 중간 모델인 ProductTypeAttribute를 정의하는 부분이다.

ProductTypeAttribute 모델은 ProductTypeAttribute 모델 사이의 관계를 기록하고 유지한다. product_type 필드는 ProductType 모델과의 외래 키 관계를 나타낸다. 이를 통해 ProductType 인스턴스와 관련된 속성을 참조할 수 있다.

attribute 필드는 Attribute 모델과의 외래 키 관계를 나타낸다. 이를 통해 Attribute 인스턴스와 관련된 속성을 참조할 수 있다.

related_name 매개변수는 ProductType 모델에서 ProductTypeAttribute에 역참조할 때 사용할 이름을 지정한다. 위의 코드에서는 "product_type_attribute_pt"로 설정되었다. 마찬가지로, Attribute 모델에서 역참조할 때는 "product_type_attribute_a"로 설정된다.

unique_together 속성은 Meta 클래스 내에서 설정되어 중복되지 않는 유일한 조합을 지정한다. 위의 코드에서는 ("product_type", "attribute")로 설정되었으므로, ProductTypeAttribute 사이의 관계에서 같은 조합이 중복되지 않도록 보장된다.

이를 통해 ProductTypeAttribute은 다대다 관계를 가지며, 중간 테이블 ProductTypeAttribute를 통해 서로를 연결하고 관리한다. 이를 통해 특정 ProductType과 연결된 Attribute들을 효율적으로 다룰 수 있다.

Build : Admin Site Product Type Inlines

models.py

admin.py

Build : Customizing the Serialization Output

json 이 딕셔너리랑 비슷하니 attribute_value 키의 데이터를 뽑아서 다시 가공하면 아래와 같이 커스텀 할 수 있다.

Build : Protect Attribute Validation

현재 속성을 보면 중복으로 등록이 가능하다.
이게 무슨 소리냐면 신발로 따지면 파란색 신발 270, 280이 같이 등록이 가능하다는 뜻이다.
파란색 신발을 골랐을 때 사이즈는 270, 280 따로 따로 상품군이 나눠줘야한다. (파란색 신발 270, 파란색 신발 280)

self.id != object.id 조건을 통해 현재 라인 자체는 중복 검사에서 제외한다. 그리고 self.order == object.order 조건을 통해 현재 라인과 다른 라인의 순서 값이 동일한지 확인한다.

save() 메서드는 모델을 저장하기 전에 유효성 검사를 수행하는 역할을 한다. 먼저 self.full_clean()을 호출하여 clean() 메서드를 실행하여 모델의 유효성을 검사한다. 유효성 검사에 통과하면 super(ProductLine, self).save(*args, **kwargs)를 호출하여 상위 클래스의 save() 메서드를 실행하여 실제 저장 작업을 수행한다.

이렇게 함으로써, ProductLine 모델의 저장 작업이 실행되기 전에 유효성을 검사하고, 중복된 순서 값이 있는 경우 저장이 차단된다.

full_clean()

full_clean() 메서드는 Django에서 제공하는 모델의 유효성 검사 메서드다. 이 메서드는 모델 인스턴스에 대해 정의된 필드 유효성 검사를 수행한다.

일반적으로 full_clean()은 폼 데이터의 유효성 검사 시나리오에서 자동으로 호출된다. 예를 들어, ModelForm을 사용하여 모델 인스턴스를 생성하거나 수정할 때, form.is_valid()를 호출하면 내부적으로 full_clean()이 실행되어 모델 필드의 유효성을 검사한다.

full_clean() 메서드는 다음과 같은 작업을 수행한다:

  1. 각 필드의 유효성 검사: 각 필드의 clean_<field_name>() 메서드를 호출하여 해당 필드의 유효성을 검사한다. 예를 들어, CharField의 경우 문자열의 길이나 포맷을 확인한다. 필드가 blank=False이면서 값이 비어 있을 경우 ValidationError 예외가 발생한다.

  2. 유니크한 필드 값의 중복 검사: 유니크 필드인 경우, 해당 필드 값이 다른 레코드와 중복되는지 확인한다. 중복이 발견되면 ValidationError 예외가 발생한다.

  3. 관계 필드의 유효성 검사: 관계 필드의 유효성도 검사된다. 예를 들어, ForeignKey 필드가 다른 테이블과 연결되어 있는 경우, 해당 관계가 유효한지 검사한다.

full_clean() 메서드는 유효성 검사에 실패할 경우 ValidationError 예외를 발생시킨다. 이 예외는 폼 유효성 검사나 모델 저장 시에 잡을 수 있다. full_clean()은 모델 인스턴스의 데이터를 변경하지 않으며, 오직 유효성 검사만을 수행한다.

full_clean()은 모델에서 직접 호출할 수도 있다. 예를 들어, 위의 예시 코드에서 save() 메서드에서 self.full_clean()을 호출하여 모델 저장 전에 유효성을 검사하고 있다. 이렇게 함으로써 모델을 저장하기 전에 필수적인 유효성 검사를 수행할 수 있다.

Performance : Product Attribute Query

현재는 쿼리문이 7개가 진행된다.

중복되는 쿼리문을 없애기 위해 다음과 같이 코드를 작성했다.
views.py

Product 에서 시작해서 Product line으로 이동하고 제품 라인에서 역방향 쿼리 관련이름 (related_name) attribute_value 로 이동한다.그리고 마지막 속성 attribute로 이동해야 중복된 쿼리들이 없어진다.

이렇게 하면 전체 쿼리수는 5로 줄어든다.
이를 1000개 또는 2000개의 제품으로 확장하게 된다면 상당히 많은 쿼리가 발생하게 된다.

하지만 큰 문제가 되지 않는데 왜냐하면 이 데이터는 정적 생성(static generation) 및 서버 사이드 렌더링(server-side rendering) 기술과 함께 사용될 것이기 때문이다.

자세히는 모르지만 정적 생성 및 서버 사이드 렌더링 기술을 사용하면 애플리케이션을 빌드할 때 페이지 또는 일부 페이지와 데이터가 미리 렌더링된다. 이렇게 되면 이러한 쿼리는 한 번 실행되고, 그 페이지는 정적인 형태로 생성된다. 이후에는 이 페이지를 요청할 때마다 쿼리를 다시 실행하지 않으므로, 이러한 쿼리는 필요할 때에만 실행되고 사용자에게 제공된다.

따라서 이러한 쿼리는 사실상 Next.js와 같은 프론트엔드 기술과 함께 사용될 때, 필요한 정적 페이지를 생성할 때에만 실행된다. 사용자에게 그 페이지를 제공할 때는 이미 쿼리가 실행되어 렌더링된 페이지가 준비되어 있다.

또한 clean, save 두 메서드를 통해 데이터베이스에 중복된 값이 저장되는 것을 방지할 수 있다. clean 메서드는 모델의 필드 유효성을 검사하고, save 메서드는 유효성 검사를 통과한 경우에만 모델을 저장한다.

이렇게 중복 값을 방지함으로써 데이터의 일관성을 유지할 수 있고, 예기치 않은 결과를 방지할 수 있다.

Build : Custom Field with SerializerMethodField

Product에 TV나 다른 제품들을 추가할 경우 Product 와 Product type을 연결해줘야 한다.

Product line 뿐만 아니라 Product type 도 Product도 반환해줘야 한다.

해당 사항을 고려한다면 위와 같이 스키마가 변경되어야 한다.

models.py Product 와 product_type을 먼저 연결 시켜준다.

Serializers.py

  1. get_attribute 메소드:

    • get_attribute 메소드는 attribute 필드의 데이터를 가져오기 위해 사용된다.
    • 해당 제품의 속성을 필터링하여 가져온 뒤, AttributeSerializer를 사용하여 시리얼라이즈된 데이터를 반환한다.
    • AttributeSerializer(attribute, many=True).dataAttributeSerializer를 사용하여 attribute 쿼리셋을 시리얼라이즈하고 데이터를 추출하는 작업을 한다.
    • AttributeSerializerAttribute 모델의 데이터를 시리얼라이즈하기 위해 정의된 시리얼라이저다. AttributeSerializer(attribute, many=True)AttributeSerializerattribute 쿼리셋에 적용하고, many=True 파라미터를 통해 여러 개의 인스턴스를 시리얼라이즈하도록 지정한다.
    • .data 속성은 시리얼라이즈된 데이터를 추출하기 위해 사용된다. 이를 통해 시리얼라이저가 생성한 데이터를 파이썬의 기본 데이터 타입으로 반환한다.
    • 따라서 AttributeSerializer(attribute, many=True).dataattribute 쿼리셋을 시리얼라이즈하고 해당 데이터를 추출하여 파이썬의 기본 데이터 타입으로 반환한다. 이를 통해 get_attribute 메소드에서 속성 데이터를 시리얼라이즈된 형태로 가져올 수 있다.
    • 이 메소드는 attribute 필드에 대한 커스텀 시리얼라이즈된 데이터를 제공하기 위해 사용된다.
  2. to_representation 메소드:

    • to_representation 메소드는 직렬화된 데이터를 표현하기 위해 사용된다.
    • to_representation 메소드는 상위 클래스의 to_representation 메소드를 호출하여 데이터를 가져온 뒤, 커스텀 로직을 적용하여 데이터를 변형한다.
    • 이 메소드에서는 attribute 필드의 데이터를 변형하여 "type specification"이라는 새로운 필드를 추가하고 해당 필드의 값을 업데이트한다.
    • "type specification" 필드는 av_data에서 가져온 속성 데이터를 사용하여 업데이트된다.

이러한 커스텀 메소드들을 통해 ProductSerializer는 제품과 관련된 브랜드, 카테고리, 제품 라인 및 속성에 대한 시리얼라이즈된 데이터를 제공한다. 커스텀 메소드를 사용하여 필요한 로직을 추가하고 데이터를 원하는 형태로 변형할 수 있다.

[
  {
    "name": "monitor x",
    "slug": "p1",
    "description": "",
    "brand_name": "b1",
    "category_name": "c1",
    "product_line": [
      {
        "price": "1.00",
        "sku": "1",
        "stock_qty": 1,
        "order": 1,
        "product_image": [],
        "specification": {}
      },
      {
        "price": "1.00",
        "sku": "1",
        "stock_qty": 1,
        "order": 2,
        "product_image": [],
        "specification": {}
      },
      {
        "price": "1.00",
        "sku": "1",
        "stock_qty": 1,
        "order": 3,
        "product_image": [],
        "specification": {}
      }
    ],
    "type specification": {
      "1": "screen size",
      "2": "weight",
      "3": "colour"
    }
  }
]

Testing : Handling Test Failures

pytest 중 처음 오류가 나면 멈추게 설정했다.
따로 설정이 안되어 있다면 테스트를 전부 진행하게 되는데 만약 따로 환경설정을 하지 않았다면 pytest -x 로 실행하면 된다.

우선 현재 에러는 “product_product” 테이블에서 “product_type_id” 필드에 NULL 값을 허용하지 않는 설정이 적용되어 있음에도 불구하고 해당 필드가 NULL로 설정되었을 때 발생한다.

즉, “product_product” 테이블의 “product_type_id” 필드는 필수 필드로 설정되어 있어서 해당 필드에는 NULL 값을 가질 수 없다. 따라서 모든 제품에는 제품 유형에 대한 유효한 값을 가져야 한다.

결론적으로 해당 오류가 난 이유는 이전에 모델 관계를 바꿨기 때문이다.

테스트 팩토리에서도 관계 설정을 다시 해주면 오류가 없이 pass가 뜬다.

Testing : Factory Boy Many to Many Relationships

우선 테이블 관계가 달라졌으니 테스트 데이터랑 테스트 코드를 변경해줘야한다. factories.py

import factory

from product.models import (
    Attribute,
    AttributeValue,
    Brand,
    Category,
    Product,
    ProductImage,
    ProductLine,
    ProductType
    )

class CategoryFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Category
    name = factory.Sequence(lambda n: "Category_%d" % n)

class BrandFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Brand
    name = factory.Sequence(lambda n: "Brand_%d" % n)

class AttributeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Attribute
    name = "attribute_name_test"
    description = "attr_description_test"

class ProductTypeFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProductType
    name = "test_type"

    @factory.post_generation
    def attribute(self, create, extracted, **kwargs):
        if not create or not extracted:
            return
        self.attribute.add(*extracted)

class ProductFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = Product

    name = "test_Product"
    description = "test_description"
    is_digital = True
    brand = factory.SubFactory(BrandFactory)
    category = factory.SubFactory(CategoryFactory)
    is_active = True
    product_type = factory.SubFactory(ProductTypeFactory)

class AttributeValueFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = AttributeValue

    attribute_value = "attr_test"
    attribute = factory.SubFactory(AttributeFactory)

class ProductLineFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProductLine

    price = 10.00
    sku = "12345"
    stock_qty = 1
    product = factory.SubFactory(ProductFactory)
    is_active = True

    @factory.post_generation
    def attribute_value(self, create, extracted, **kwargs):
        if not create or not extracted:
            return
        self.attribute_value.add(*extracted)

class ProductImageFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = ProductImage

    alternative_text = "test alternative text"
    url = "test.jpg"
    productline = factory.SubFactory(ProductLineFactory)

@factory.post_generationfactory_boy 라이브러리의 기능 중 하나다. 이 기능은 팩토리가 생성된 후에 추가적인 후처리 작업을 수행할 수 있도록 한다.

위의 코드에서 attribute 메서드는 ProductTypeFactory에서 사용되고 있다. 이 메서드는 ProductType 객체가 생성된 후에 실행되며, attribute 필드에 대한 후처리 작업을 수행한다.

함수의 인자 설명:

  • self: 현재 생성되는 ProductType 객체
  • create: 객체가 실제로 생성되는지 여부를 나타내는 불리언 값
  • extracted: 팩토리 호출 시 ProductTypeFactory에서 attribute 필드에 전달된 값
  • kwargs: 기타 키워드 인자

위의 코드에서는 create 값이 True이고 extracted 값이 존재하는 경우에만 후처리 작업을 수행한다. 이는 attribute 필드에 연결된 Attribute 객체를 추가하는 작업이다. extracted에는 AttributeFactory로부터 생성된 Attribute 객체가 전달된 다.

self.attribute.add(*extracted)attribute 필드에 extracted에 전달된 Attribute 객체들을 추가하는 역할을 합니다. add 메서드를 사용하여 여러 개의 Attribute 객체를 한 번에 추가할 수 있다.

이를 통해 ProductType 객체가 생성된 후에 attribute 필드에 관련된 Attribute 객체들이 자동으로 추가되게 된다.

import pytest
from django.core.exceptions import ValidationError
from product.models import ProductTypeAttribute

pytestmark = pytest.mark.django_db

class TestCategoryModel:
    def test_str_method(self, category_factory):
        x = category_factory(name="test_cat")
        assert x.__str__() == "test_cat"

class TestBrandModel:
    def test_str_method(self, brand_factory):
        x = brand_factory(name="test_brand")
        assert x.__str__() == "test_brand"

class TestProductModel:
    def test_str_method(self, product_factory):
        x = product_factory(name="test_product")
        assert x.__str__() == "test_product"

class TestProductLineModel:
    def test_str_method(self, product_line_factory, attribute_value_factory):
        attr = attribute_value_factory(attribute_value="test")
        obj = product_line_factory.create(sku="12345", attribute_value=(attr,))
        assert obj.__str__() == "12345"

    def test_duplicate_order_values(self, product_line_factory, product_factory):
        obj = product_factory()
        product_line_factory(order=1, product=obj)
        with pytest.raises(ValidationError):
            product_line_factory(order=1, product=obj).clean()

class TestProductImageModel:
    def test_str_method(self, product_image_factory):
        obj = product_image_factory(order=1)
        assert obj.__str__() == "1"

  
class TestProductTypeModel:
    def test_str_method(self, product_type_factory, attribute_factory):
        test = attribute_factory(name="test")
        obj = product_type_factory.create(name="test_type", attribute=(test,))
        x = ProductTypeAttribute.objects.get(id=1)
        print(x)
        assert obj.__str__() == "test_type"

  
class TestAttributeModel:
    def test_str_method(self, attribute_factory):
        obj = attribute_factory(name="test_attribute")
        assert obj.__str__() == "test_attribute"

class TestAttributeValueModel:
    def test_str_method(self, attribute_value_factory, attribute_factory):
        obj_a = attribute_factory(name="test_attribute")
        obj_b = attribute_value_factory(attribute_value="test_value", attribute=obj_a)

        assert obj_b.__str__() == "test_attribute-test_value"

기존의 TestProductTypeModel 을 살펴본다면 이전에는 product_line_factory(order=1, product=obj) 방식을 통해 객체를 생성했다. 이 경우에는 product_line_factory 에서 정의한 기본 값이 적용되지 않고 명시적으로 지정한 값들이 사용된다.

obj = product_line_factory.create(sku="12345", attribute_value=(attr,)) 로 변경된 방법은 product_line_factory를 사용하여 ProductLine 객체를 생성하는데, create 메서드를 호출하여 객체를 생성한다.
이 경우에는 product_line_factory에서 정의한 기본값이 적용되고, create 메서드의 매개변수를 통해 추가적인 속성을 지정할 수 있다.

첫 번째 방법은 생성된 객체를 변수에 할당하지 않고 바로 사용할 수 있다.
두 번째 방법은 create 메서드를 통해 생성된 객체를 변수에 할당하여 필요한 경우에 사용할 수 있다.

댓글남기기