Django Oscar - Product Attributes - Part 24

In the previous parts, we used some product attributes.

In this part, we will take a closer look at product attributes in Django Oscar.

Product attributes allow us to add extra information to products without changing the product model itself.

 

Available attribute types

When creating a product attribute, Django Oscar gives us several available types:

  • Text
  • Integer
  • Boolean
  • Float
  • Rich text
  • Date
  • DateTime
  • Option
  • Multi option
  • Entity
  • File
  • Image

Some of them are very easy to understand.

  • Text can be used for author names, brands or subtitles
  • Integer can be used for pages, year or duration
  • Boolean can be used for yes/no values
  • Float can be used for decimal values
  • File can be used for downloadable files
  • Image can be used for extra product images

For this post, we will focus on the less obvious ones:

  • Date / DateTime
  • Option / Multi option
  • Entity
  • Rich text

 

Date and DateTime

Date stores only the date.


 

DateTime stores both date and time.

 

Option and Multi option

Option allows us to select one value from a predefined list.

If the product can only have one language, Option is the right type.


Multi option allows us to select more than one value from a predefined list.

If the product can be available in multiple formats, Multi option makes more sense.

 

Option groups

Before using Option or Multi option, we need to create an option group.

Option Group: Languages

Options:
- Portuguese
- English
- French

Then we can create a product attribute:

Name: Language
Code: language
Type: Option
Option Group: Languages

 

 

Rich text

Rich text allows us to store formatted content.

It can be useful for:

  • Technical descriptions
  • Extra product notes
  • Instructions
  • Formatted specifications

Unlike a normal text attribute, rich text can contain formatted HTML content.

Name: Extra notes
Code: extra_notes
Type: Rich text



 

Entity

Entity is less common.

It is used when an attribute should be related to another Django model instead of storing a simple value.

For example, instead of storing an author as plain text:

Author: Jaime Silva

We could have an Author model and connect the product to that model.

class Author(models.Model):
    name = models.CharField(max_length=255)
    bio = models.TextField(blank=True)

Then the product attribute could point to an author entity.

This feature doesn't have a graphical interface wich means that we will have to use raw code to create this relation. A quick example:
 

from django.contrib.contenttypes.models import ContentType
from oscar.core.loading import get_model

from Store.models import Author


Product = get_model("catalogue", "Product")
ProductAttribute = get_model("catalogue", "ProductAttribute")
ProductAttributeValue = get_model("catalogue", "ProductAttributeValue")


product = Product.objects.get(id=1)
author = Author.objects.get(id=1)

product_class = product.product_class or product.parent.product_class

attribute = ProductAttribute.objects.get(
    product_class=product_class,
    code="entityatt",
)

content_type = ContentType.objects.get_for_model(author)

attribute_value, created = ProductAttributeValue.objects.update_or_create(
    product=product,
    attribute=attribute,
    defaults={
        "entity_content_type": content_type,
        "entity_object_id": author.pk,
    },
)

This would then let you access the Author model through the product attributes

{{ product.attr.entityatt.name }}

And also through regular Python. (Read below)
 

Accessing attributes in templates

We can access product attributes directly from the product object.

{{ product.attr.language }}
{{ product.attr.formats }}
{{ product.attr.publication_date }}

We can also loop through all product attributes:

{% for av in product.get_attribute_values %}
    <tr>
        <th>{{ av.attribute.name }}</th>
        <td>{{ av.value_as_html }}</td>
    </tr>
{% endfor %}

If we need to hide a specific attribute, use the attribute code:

{% for av in product.get_attribute_values %}
    {% if av.attribute.code != "file" %}
        <tr>
            <th>{{ av.attribute.name }}</th>
            <td>{{ av.value_as_html }}</td>
        </tr>
    {% endif %}
{% endfor %}

 

Accessing attributes in Python

language = product.attr.language
formats = product.attr.formats
publication_date = product.attr.publication_date

When the attribute may not exist, use a safer approach:

try:
    ebook_file = product.attr.file
except AttributeError:
    ebook_file = None

Or create a small helper:

def get_product_attribute(product, code):
    try:
        return getattr(product.attr, code)
    except AttributeError:
        return None

 

Attributes vs variants

Attributes describe a product.

Variants represent different versions of a product that can have their own stock and price.

Example:

Attributes:
- Author
- Language
- Publication date
- Number of pages

Variants:
- Paperback
- Hardcover
- Signed edition

We will cover product variants in the next part.