2. Deferred Model Pattern¶
Until django-SHOP version 0.2, there were abstract and concrete and models: BaseProduct
and
Product
, BaseCart
and Cart
, BaseCartItem
and CartItem
, BaseOrder
and
Order
and finally, BaseOrderItem
and OrderItem
.
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
PRODUCT_MODEL
, ORDER_MODEL
, ORDER_MODEL_ITEM
from the projects settings.py
.
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, Product
, Order
,
OrderItem
, Cart
, CartItem
have been removed. These model definitions now all are
abstract and named BaseProduct
, BaseOrder
, 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
models/shopmodels.py
files:
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 models.py
or models/__init__.py
in
our own shop project:
from shop.models.defaults.cart import Cart # nopyflakes
from shop.models.defaults.cart_item import CartItem # nopyflakes
Note
The comment nopyflakes
has been added to suppress warnings, since these classes
arern’t used anywhere in models.py
.
All the configuration settings from django-SHOP <0.9: PRODUCT_MODEL
, ORDER_MODEL
,
ORDER_MODEL_ITEM
, etc. are not required anymore and can safely be removed from our
settings.py
.
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
Here 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 ForeignKey
, OneToOneField
or
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
relation to Cart
.
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.ForeignKey
,
models.OneToOneField
or models.ManyToManyField
, use the special fields
deferred.ForeignKey
, deferred.OneToOneField
and deferred.ManyToManyField
. When
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 BaseCartItem.objects.filter(cart=cart)
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 CartModel
,
CartItemModel
, OrderModel
, OrderItemModel
, etc.