How to Customize Django Oscar Models, Views and URLs

Blog / Django · November 27, 2015 · Updated June 10, 2026 · 9 min read
How to Customize Django Oscar Models, Views and URLs

To customize Django Oscar's models, views and URLs you fork the Oscar core app that owns the class you want to change, then redefine that class inside your fork. In modern Oscar (3.x on Django 4.2/5.x) forking is a single management command — python manage.py oscar_fork_app catalogue yourappsfolder/ — and Oscar's dynamic class loader (get_class) automatically uses any class you redeclare with the same name.

The three customizations break down like this: models are overridden by subclassing Oscar's abstract base (e.g. AbstractProduct) and re-exporting the rest; views are overridden by simply redeclaring the view class in your forked app; URLs are changed by overriding get_urls() on the app config. The old Oscar 1.x Application class and get_core_apps() helper are gone — Oscar 2.0+ replaced them with class-based app configs (OscarConfig / CatalogueConfig).

Key takeaways

  • Fork first, override second. You cannot override an Oscar class until the app that owns it has been forked into your project.
  • oscar_fork_app does the boilerplate. python manage.py oscar_fork_app <app> <destination> creates the package, apps.py, admin.py and copies the migrations for you.
  • Models = subclass the abstract base. Add fields by subclassing AbstractProduct (etc.), then from oscar.apps.<app>.models import * at the bottom of your models.py.
  • You now own the migrations. Overriding a model means makemigrations writes migrations into your app — keep them in version control.
  • Views need no wiring. Redeclare the view class with the same name in your fork and get_class loads yours instead of Oscar's.
  • URLs = override get_urls() on the forked app config (or the root Shop config), not the deprecated Application class.
  • Settings changed. Replace get_core_apps() with explicit *.apps.*Config entries in INSTALLED_APPS.

What changed since the old Oscar tutorials?

Most Django Oscar customization guides you will find online were written for Oscar 1.x and Python 2 — they still talk about an Application class in app.py, get_core_apps() and a manual mkdir fork. None of that is how a current project works. Here is the old-to-modern map so you can translate any legacy snippet you come across:

Task Old Oscar 1.x Modern Oscar 2.0–3.x
Fork an app Manual mkdir + __init__.py + copy migrations manage.py oscar_fork_app <app> <dest>
Register apps get_core_apps([...]) Explicit 'yourapps.catalogue.apps.CatalogueConfig'
App definition Application class in app.py OscarConfig / CatalogueConfig in apps.py
Override a view Subclass + rewire the Application Redeclare class; get_class auto-loads it
Change a URL Override Application.get_urls() Override get_urls() on the app config
Root URLconf from oscar.app import application apps.get_app_config('oscar').urls[0]

How do you fork an Oscar app?

Pick the app that owns the class you want to change: the Product model lives in catalogue, the basket view lives in basket, the checkout views live in checkout, and so on. Then run the fork command, pointing it at the folder that will hold all your forked apps. If this is your first fork, give it an empty package folder (a directory with an __init__.py).

# Fork the catalogue app into your project's apps folder
python manage.py oscar_fork_app catalogue yourappsfolder/

# Oscar generates, with the correct app-label already set:
#   yourappsfolder/catalogue/__init__.py
#   yourappsfolder/catalogue/apps.py        -> CatalogueConfig(name='yourappsfolder.catalogue')
#   yourappsfolder/catalogue/admin.py
#   yourappsfolder/catalogue/models.py      -> re-exports Oscar's models
#   yourappsfolder/catalogue/migrations/    -> copied so you own them

Next, swap Oscar's app for yours in INSTALLED_APPS. The old get_core_apps() helper was removed in Oscar 2.0 — you now list the app config classes explicitly and replace the one line you forked. Keep Oscar's other defaults by importing oscar.defaults so all the OSCAR_* settings stay populated.

# settings.py
from oscar.defaults import *  # noqa  -> pulls in OSCAR_* default settings

INSTALLED_APPS = [
    # ... your own non-Oscar apps ...
    'oscar.config.Shop',                               # Oscar's root app
    'oscar.apps.analytics.apps.AnalyticsConfig',
    'oscar.apps.checkout.apps.CheckoutConfig',
    'oscar.apps.basket.apps.BasketConfig',
    # ... the remaining Oscar core apps, unchanged ...

    'yourappsfolder.catalogue.apps.CatalogueConfig',   # <- your fork REPLACES oscar.apps.catalogue
]

How do you override a model (add a field to Product)?

Subclass Oscar's abstract model — never its concrete model — add your fields, then import the remaining models with a wildcard. Order matters: your class must be defined before the import * line so Django registers yours and skips Oscar's identically named concrete model.

# yourappsfolder/catalogue/models.py
from django.db import models

from oscar.apps.catalogue.abstract_models import AbstractProduct


class Product(AbstractProduct):
    # your custom field(s)
    is_featured = models.BooleanField(default=False)


# Re-export every other catalogue model unchanged.
# This MUST come after your overrides.
from oscar.apps.catalogue.models import *  # noqa isort:skip

Because the new field lives in your app, you own the migrations. Generate and apply them against your forked app label:

python manage.py makemigrations catalogue
python manage.py migrate catalogue

Commit yourappsfolder/catalogue/migrations/ to version control. Oscar will not manage these for you, and a missing migration is the single most common cause of an OperationalError: no such column after a fork. If you are forking an app that already has data in production, treat the copied migrations as your new baseline and generate fresh ones for every schema change from then on.

How do you customize a view?

This is where modern Oscar is dramatically simpler than the old tutorials. Thanks to dynamic class loading you do not rewire anything — just declare a class with the same name in your forked app's views.py, and Oscar's internal get_class('catalogue.views', 'ProductDetailView') call resolves to your version automatically.

Customizing the checkout views is the most common next step; see understanding the checkout flow in Django Oscar before you override anything in the checkout app.

# yourappsfolder/catalogue/views.py
from oscar.apps.catalogue.views import ProductDetailView as CoreProductDetailView


class ProductDetailView(CoreProductDetailView):
    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['related_products'] = self.object.recommended_products.all()
        return ctx

Anywhere Oscar loads that view via get_class (including its own URLconf), your subclass is now used — no URL change required. The same rule applies in your code: if you import an Oscar class directly you bypass the override system, so use the loader instead of a hard import to keep your code fork-friendly.

# Override-friendly: resolves to a forked class if one exists
from oscar.core.loading import get_class

ProductDetailView = get_class('catalogue.views', 'ProductDetailView')

# Avoid: a direct import ignores any fork
# from oscar.apps.catalogue.views import ProductDetailView

How do you change a URL?

URLs are owned by each app's config through its get_urls() method. To add a route to one app, override get_urls() on that forked app's config. To rename a path for the whole shop (for example /basket/ to /cart/), override get_urls() on the root Shop config. Always finish by returning self.post_process_urls(...) so Oscar re-applies its permission decorators.

# yourappsfolder/offer/apps.py
from django.urls import path

from oscar.apps.offer.apps import OfferConfig as CoreOfferConfig

from .views import MyExtraView


class OfferConfig(CoreOfferConfig):
    def ready(self):
        super().ready()
        self.extra_view = MyExtraView

    def get_urls(self):
        urls = super().get_urls()
        urls += [
            path('extra/', self.extra_view.as_view(), name='extra'),
        ]
        return self.post_process_urls(urls)

To rename a top-level path, fork the root app and override the root get_urls(). The root config is oscar.config.Shop; subclass it and keep the inherited name = 'oscar' so the app label stays the same.

# myproject/config.py
from django.urls import path

from oscar.config import Shop as CoreShop


class Shop(CoreShop):
    def get_urls(self):
        urls = super().get_urls()
        # change /basket/ to /cart/
        for i, u in enumerate(urls):
            if getattr(u, 'app_name', None) == 'basket':
                urls[i] = path('cart/', self.basket_app.urls)
        return self.post_process_urls(urls)

Then point your project URLconf at the app config. The legacy from oscar.app import application import no longer exists — look the root app up by its label instead. (Remember to also replace 'oscar.config.Shop' with 'myproject.config.Shop' in INSTALLED_APPS.)

# myproject/urls.py
from django.apps import apps
from django.urls import include, path

urlpatterns = [
    # ... your other URLs ...
    path('', include(apps.get_app_config('oscar').urls[0])),
]

What should you fork for each kind of change?

Not everything needs a fork. Use this table to decide what to override and how — and note that templates and many behaviours need no fork at all:

You want to change… Override this Mechanism
A model / add a field models.py (subclass Abstract*) Inherit abstract base + re-export; you own migrations
A view or its context views.py (same class name) Dynamic loading via get_class
A URL path or new route apps.py -> get_urls() Override on the app config (or root Shop)
A template templates/oscar/... in your project Template override directory — no fork needed
A form, table or utility class matching module + class name get_class resolves your version
Catalogue size, currency, names nothing OSCAR_* settings only

When should you fork vs. just configure?

Fork only when you must change Python behaviour or the database schema. A surprising amount of Oscar is controlled by OSCAR_* settings — OSCAR_SHOP_NAME, OSCAR_PRODUCTS_PER_PAGE, OSCAR_DEFAULT_CURRENCY, OSCAR_ALLOW_ANON_CHECKOUT and dozens more — so check the settings reference before you reach for oscar_fork_app. Forking adds long-term maintenance because your fork has to track Oscar's upstream changes.

If you are just getting started, set the project up first with our guide on building your own ecommerce shop using Django Oscar, and if you need wallets or store credit, see how to integrate django-oscar-accounts — a clean example of forking and extending a packaged app. For larger builds where forks start to compound, our Django development services team designs Oscar customizations that survive version upgrades.

Frequently Asked Questions

Do I have to fork an Oscar app to override a class?

Yes, for any Python class (model, view, form, table, repository, utility). Oscar's dynamic class loader only looks for your override inside a forked app that has replaced the core one in INSTALLED_APPS. The exceptions are templates (use a template override directory) and behaviour controlled by settings (change the relevant OSCAR_* value) — neither of those needs a fork.

What replaced get_core_apps() in modern Django Oscar?

Oscar 2.0 removed get_core_apps(). You now list every Oscar app explicitly in INSTALLED_APPS as its app-config dotted path (for example 'oscar.apps.basket.apps.BasketConfig') and swap in your forked config ('yourapps.catalogue.apps.CatalogueConfig') for the one app you changed. Import oscar.defaults in your settings so the OSCAR_* defaults stay populated.

Does overriding an Oscar model break migrations?

It does not break them, but it transfers ownership. Once you fork an app and subclass an abstract model, the concrete model belongs to your app, so makemigrations writes new migrations into your app's migrations/ folder. oscar_fork_app copies the existing migrations as a baseline; commit them, and generate a fresh migration for every schema change after that. Forgetting to apply one is the usual cause of a missing-column error.

How do I customize an Oscar view without changing its URL?

Fork the app that owns the view, then redeclare a class with the same name in the fork's views.py (typically subclassing Oscar's version and extending get_context_data or a form-handling method). Because Oscar resolves its own URLconf entries through get_class, your subclass is picked up automatically — the URL, name and template stay exactly the same.

Can I change an Oscar URL without forking the whole shop?

Yes. To add a route or rename a path inside one app, fork just that app and override get_urls() on its config, returning self.post_process_urls(urls). You only fork the root oscar.config.Shop when you want to restructure top-level paths (such as renaming /basket/ to /cart/) or remove an app's URLs entirely.

Which Django and Oscar versions does this approach apply to?

This is the workflow for Django Oscar 3.x running on Django 4.2 LTS and Django 5.x, where forking uses oscar_fork_app, apps are defined as OscarConfig subclasses, and views load dynamically via get_class. If you are on Oscar 1.x with the old Application class and get_core_apps(), upgrading to the current app-config approach is the recommended first step.

Share this article