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.
Textcan be used for author names, brands or subtitlesIntegercan be used for pages, year or durationBooleancan be used for yes/no valuesFloatcan be used for decimal valuesFilecan be used for downloadable filesImagecan 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.