forked from django-oscar/django-oscar
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Warn about possible issues when running 'make sandbox'
- Loading branch information
1 parent
39f57e4
commit 6342e56
Showing
23 changed files
with
1,812 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
================ | ||
Customising apps | ||
================ | ||
|
||
The key mechanism which allows Oscar to be customised is dynamically loading of | ||
classes; this allows any core class to be replaced and extended. | ||
|
||
Dynamic class-loading requires replacing (or "forking") an Oscar app from | ||
``INSTALLED_APPS`` with a custom one that has the same app label. Afterwards, | ||
you should generally be able to override any class/model/view by just dropping | ||
it in the right place and giving it the same name. | ||
|
||
Forking a core app | ||
------------------ | ||
|
||
If this is the first time you're forking an Oscar app, create | ||
a root package under which all your forked apps will live: | ||
|
||
.. code-block:: bash | ||
$ mkdir yourproject | ||
$ touch yourproject/__init__.py | ||
Now use the :ref:`oscar_fork_app` management command to create a local version of a | ||
core app: | ||
|
||
.. code-block:: bash | ||
$ ./manage.py oscar_fork_app order yourproject | ||
Creating folder yourproject/order | ||
Creating __init__.py and admin.py | ||
Creating models.py and copying migrations from [...] to [...] | ||
This will create a new app package ``yourproject/order`` with ``models.py`` | ||
and a copy of the migrations folder. The ``models.py`` will be initialised to | ||
import all models from the corresponding Oscar ``models.py`` module: | ||
|
||
.. code-block:: python | ||
# yourproject/order/models.py | ||
from oscar.apps.order.models import * | ||
Finally, tell Django to use your new custom app: | ||
|
||
.. code-block:: python | ||
# settings.py | ||
from oscar import get_core_apps | ||
INSTALLED_APPS = [ | ||
... # all non-oscar apps | ||
] + get_core_apps(['yourproject.order']) | ||
The ``get_core_apps`` function returns a list of Oscar core apps. If you supply a | ||
list of additional apps, they will be used to replace the Oscar core apps. | ||
In the above example, ``yourproject.order`` will be returned instead of | ||
|
||
Customising a core class | ||
------------------------ | ||
|
||
You can now override every class (that is | ||
:doc:`dynamically loaded </customisation/class_loading_explained>`, which is | ||
almost every class) in the app you've replaced. That means forms, | ||
views, strategies, etc. All you need to do is give it the same name | ||
and place it in a module with the same name. | ||
|
||
Suppose you want to alter the way order numbers are generated. By default, | ||
the class ``oscar.apps.order.utils.OrderNumberGenerator`` is used. So just | ||
create a class within your ``order`` app which | ||
matches the module path from oscar: ``order.utils.OrderNumberGenerator``. This | ||
could subclass the class from Oscar or not:: | ||
|
||
# yourproject/order/utils.py | ||
|
||
from oscar.apps.order.utils import OrderNumberGenerator as CoreOrderNumberGenerator | ||
|
||
class OrderNumberGenerator(CoreOrderNumberGenerator): | ||
|
||
def order_number(self, basket=None): | ||
num = super(OrderNumberGenerator, self).order_number(basket) | ||
return "SHOP-%s" % num |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
=============================== | ||
Dynamic class loading explained | ||
=============================== | ||
|
||
Dynamic class loading is the foundation for making Oscar | ||
customisable. It is hence worth understanding how it works, because most | ||
customisations depend on it. | ||
|
||
It is achieved by :meth:`oscar.core.loading.get_classes` and its | ||
single-class cousin :meth:`~oscar.core.loading.get_class`. Wherever feasible, | ||
Oscar's codebase uses ``get_classes`` instead of a regular import statement. | ||
That is, instead of: | ||
|
||
.. code-block:: python | ||
from oscar.apps.shipping.repository import Repository | ||
Oscar uses something like: | ||
|
||
.. code-block:: python | ||
from oscar.core.loading import get_class | ||
Repository = get_class('shipping.repository', 'Repository') | ||
This is done for almost all classes: views, models, Application | ||
instances, etc. Every class imported by ``get_class`` or ``get_classes`` can be | ||
overridden and customised. | ||
|
||
Why? | ||
---- | ||
|
||
This structure enables a project to create a local ``shipping.repository`` | ||
module, and optionally subclass the class from | ||
``oscar.app.shipping.repository``. When Oscar tries to load the | ||
``Repository`` class, it will load the one from your local project. | ||
|
||
This way, most classes can be overridden with minimal duplication, as only | ||
the to-be-changed classes have to be altered. They can optionally inherit from | ||
Oscar's implementation, which often amounts to little more than a few lines of | ||
custom code for changes to core behaviour. | ||
|
||
Seen on a bigger scale, this structures enables Oscar to ship with classes with | ||
minimal assumptions about the domain, and make it easy to modify behaviour as | ||
needed. | ||
|
||
How it works | ||
------------ | ||
|
||
The ``get_class`` function iterates over ``INSTALLED_APPS`` looking for a | ||
matching app label and, if one is found, will attempt to load the custom class | ||
from the appropriate app module. If the app isn't overridden or the class can't | ||
be found, it will fall back to the default Oscar class. | ||
|
||
In practice | ||
----------- | ||
|
||
For ``get_class`` to pick up the customised class, the Oscar apps need to be | ||
forked. It is usually enough to call ``oscar_fork_app`` and replace the app in | ||
``INSTALLED_APPS``. | ||
|
||
Using ``get_class`` in your own code | ||
------------------------------------ | ||
|
||
Generally, there is no need for ``get_class`` in your own code as the location | ||
of the module for the class is known. Some Oscar developers nonetheless | ||
use ``get_class`` when importing classes from Oscar. This means that if someday | ||
the class is overridden, it will not require code changes. Care should be taken | ||
when doing this, as this is a tricky trade-off between maintainability and | ||
added complexity. | ||
|
||
Testing | ||
------- | ||
|
||
You can test whether your overriding worked by trying to get a class from your | ||
module: | ||
|
||
.. code-block:: python | ||
>>> from oscar.core.loading import get_class | ||
>>> get_class('shipping.repository', 'Repository') | ||
yourproject.shipping.repository.Repository # it worked! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
================= | ||
Customising Oscar | ||
================= | ||
|
||
.. toctree:: | ||
:maxdepth: 1 | ||
:hidden: | ||
|
||
Dynamic loading <class_loading_explained> | ||
Apps <apps> | ||
Models <models> | ||
URLs <urls> | ||
Views <views> | ||
Templates <templates> | ||
|
||
Oscar provides e-commerce functionality that can be extended and customised to | ||
suit your requirements. To achieve this, several techniques are employed: | ||
|
||
Dynamic loading | ||
--------------- | ||
|
||
Oscar loads classes dynamically (rather than using explicit import statements). | ||
For models, Oscar leverages Django's ``get_model`` function. For classes, Oscar | ||
provides a custom loading function: | ||
|
||
- :doc:`/customising/class_loading_explained` | ||
- :doc:`/customising/apps` | ||
|
||
Core models are abstract | ||
------------------------ | ||
|
||
Oscar's apps split models into abstract and concrete versions - these are found | ||
in modules called ``abstract_models.py`` and ``models.py`` respectively. | ||
Oscar's philosophy is to keep the core models lean, where all the fields are | ||
meaningful within any e-commerce domain. Oscar then provides a mechanism for | ||
subclassing these models within your project so domain-specific fields can be | ||
added. | ||
|
||
- :doc:`/customising/models` | ||
|
||
URLs and permissions are handled by ``Application`` instances | ||
------------------------------------------------------------- | ||
|
||
The :class:`oscar.core.application.Application` class handles mapping URLs | ||
to views and permissions at an per-app level. This makes Oscar's apps more | ||
modular, and makes it easy to customise this mapping as they can be overridden | ||
just like any other class in Oscar. | ||
|
||
- :doc:`/customising/models` | ||
- :doc:`/customising/views` | ||
|
||
Templates can be overridden | ||
--------------------------- | ||
|
||
This is a common technique relying on the fact that the template loader can be | ||
configured to look in your project first for templates, before it uses the defaults | ||
from Oscar. | ||
|
||
- :doc:`/customising/templates` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
================== | ||
Customising models | ||
================== | ||
|
||
Oscar core apps come with an abstract and concrete version. | ||
|
||
Adding a field to a model | ||
------------------------- | ||
|
||
Suppose you want to add a ``video_url`` field to the core product model. This | ||
means that you want your application to use a subclass of | ||
:class:`oscar.apps.catalogue.abstract_models.AbstractProduct` which has an | ||
additional field. | ||
|
||
If you haven't already, the first step is to :doc:`fork the catalogue app | ||
</customising/apps>`. Now create a custom ``Product`` model: | ||
|
||
.. code-block:: python | ||
# yourproject/catalogue/models.py | ||
from django.db import models | ||
from oscar.apps.catalogue.abstract_models import AbstractProduct | ||
class Product(AbstractProduct): | ||
video_url = models.URLField() | ||
from oscar.apps.catalogue.models import * | ||
.. tip:: | ||
|
||
Using ``from ... import *`` is strange isn't it? Yes it is, but it needs to | ||
be done at the bottom of the module due to the way Django registers models. | ||
The order that model classes are imported makes a difference with only the | ||
first one for a given class name being registered. | ||
|
||
This should now be sufficient for Oscar to start using your custom model | ||
whenever it looks for a product. | ||
|
||
Migrations for custom models | ||
---------------------------- | ||
|
||
The final step is to create a migration for this new field. When forking an | ||
app, it's recommended that a copy of the Oscar migrations folder is created | ||
in the new app (this is what the :ref:`oscar_fork_app` management command does). | ||
With this in place, a new catalogue migration can be created in the standard | ||
way: | ||
|
||
.. code-block:: bash | ||
$ ./manage.py schemamigration catalogue --auto | ||
.. note:: | ||
|
||
It is possible to simply create a new catalogue migration without having | ||
copied Oscar's migrations into your custom app (using | ||
``./manage.py schemamigration catalogue --initial``) but this isn't recommended as any | ||
dependencies between migrations will need to be applied manually (by adding | ||
a ``depends_on`` attribute to the migration class). | ||
|
||
Finally, running: | ||
|
||
.. code-block:: bash | ||
$ ./manage.py migrate catalogue | ||
will update your database schema and create a new column in the product table. | ||
|
||
Trouble-shooting | ||
---------------- | ||
|
||
Model customisations are not picked up | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
If your custom model is not being loaded as you expect, it is likely that the | ||
core Oscar model is being imported ahead of your custom one. Django's model | ||
registration only respects the first declaration it encounters. | ||
|
||
In your overriding ``models.py``, ensure that you import Oscar's models *after* | ||
your custom ones have been defined. | ||
|
||
If that doesn't help, you have an import | ||
from ``oscar.apps.*.models`` somewhere that is being executed before your models | ||
are parsed. One trick for finding that import: put ``assert False`` in the relevant | ||
Oscar's models.py, and the stack trace will show you the importing module. | ||
|
||
If other modules need to import your models, then import from your local module, | ||
not from Oscar directly. |
Oops, something went wrong.