2. Deferred Model Pattern¶
Until django-SHOP version 0.2, there were abstract and concrete and models:
Order and finally,
The concrete models were stored in
shop.models, whereas abstract models were stored in
shop.models_bases. This was quite confusing and made it difficult to find the right model
definition whenever one had to access the definition of one of the models.
Additionally, if someone wanted to subclass a model, he had to use a configuration directive, say
ORDER_MODEL_ITEM from the projects
This made configuration quite complicate and causes other drawbacks:
- Unless all models have been overridden, the native ones appeared in the administration backend below the category Shop, while the customized ones appeared under the given project’s name. To merchants, this inconsistency in the backend was quite difficult to explain.
- In the past, mixing subclassed with native models caused many issues with circular dependencies.
Therefore in django-SHOP, since version 0.9 all concrete models,
CartItem have been removed. These model definitions now all are
abstract and named
BaseOrderItem, etc. They all have been moved
into the folder
shop/models/, because that’s the location a programmer expects them.
2.1. Materializing Models¶
Materializing such an abstract base model, means to create a concrete model with an associated database table. This model creation is performed in the concrete project implementing the shop; it must be done for each base model in the shop software.
For instance, materialize the cart by using this code snippet inside our own shop’s
from shop.models import cart class Cart(cart.BaseCart): my_extra_field = ... class Meta: app_label = 'my_shop' class CartItem(cart.BaseCartItem): other_field = ... class Meta: app_label = 'my_shop'
Of course, we can add as many extra model fields to this concrete cart model, as we wish.
All shop models, now are managed through our project instance. This means that the models
Cart, Order, etc. are now managed by the common database migrations tools, such as
./manage.py makemigration my_shop and
./manage.py migrate my_shop. This
also means that these models, in the Django admin backend, are visible under my_shop.
2.1.1. Use the default Models¶
Often we don’t need extra fields, hence the abstract shop base model is enough. Then,
materializing the models can be done using some convenience classes as found in
shop/models/defaults. We can simply import them into
our own shop project:
from shop.models.defaults.cart import Cart # nopyflakes from shop.models.defaults.cart_item import CartItem # nopyflakes
nopyflakes has been added to suppress warnings, since these classes
arern’t used anywhere in
All the configuration settings from django-SHOP <0.9:
ORDER_MODEL_ITEM, etc. are not required anymore and can safely be removed from our
2.2. Accessing the deferred models¶
Since models in django-SHOP are yet unknown during instantiation, one has to access their materialized instance using the lazy object pattern. For instance in order to access the Cart, use:
from shop.models.cart import CartModel def my_view(request): cart = CartModel.objects.get_from_request(request) cart.items.all() # contains the queryset for all items in the cart
CartModel is a lazy object resolved during runtime and pointing on the materialized, or,
to say it in other words, real Cart model.
2.3. Technical Internals¶
2.3.1. Mapping of Foreign Keys¶
One might argue, that this can’t work, since foreign keys must refer to a real model, not to
abstract ones! Therefore one can not add a field
ManyToManyField which refers an abstract model in the django-SHOP project. But
relations are fundamental for a properly working software. Imagine a
CartItem without a foreign
Fortunately there is a neat trick to solve this problem. By deferring the mapping onto a real model,
instead of using a real
ForeignKey, one can use a special “lazy” field, declaring a relation
with an abstract model. Now, whenever the models are “materialized”, then these abstract relations
are converted into real foreign keys. The only drawback for this solution is, that one may derive
from an abstract model only once, but for django-SHOP that’s a non-issue and doesn’t differ from
the current situation, where one can subclass
BaseCart only once anyway.
Therefore, when using this deferred model pattern, instead of using
models.ManyToManyField, use the special fields
Django materializes the model, these deferred fields are resolved into real foreign keys.
2.3.2. Accessing the materialized model¶
While programming with abstract model classes, sometimes they must access their model manager
or their concrete model definition. A query such as
therefore can not function and will throw an exception. To facilitate this, the deferred model’s
metaclasses adds an additional member
_materialized_model to their base class, while building
the model class. This model class then can be accessed through lazy evaluation, using