Published 2021-02-11. Last modified 2021-03-03.
Time to read: about 7 minutes.
In a previous blog post I discussed why I decided to check out the django-oscar
e-commerce framework in depth. The next step in my evaluation was to set up and run django-oscar
to experience it in action. This post shows the details of how I installed django
v3.1.6 and django-oscar
v3.0 on Ubuntu 20.04 with Python 3.8.6.
The very first page in the django-oscar
documentation describes a sample project called Frobshop. This blog post describes my Frobshop installation. A follow-on post discusses why I disabled Django Haystack for Frobshop and also did not install Apache Solr.

Installing django-oscar
For manageability of Python runtime versions and libraries, a virtual Python environment should be set up. The required virtual environment will house compatible versions of Python, Django, Django-Oscar and all their dependencies. I decided to locate the virtual environment at ~/oscar
, like this:
$ sudo apt install python3-virtualenv $ cd $ virtualenv oscar created virtual environment CPython3.8.6.final.0-64 in 528ms creator CPython3Posix(dest=/home/mslinn/oscar, clear=False, global=False) seeder FromAppData(download=False, pip=bundle, setuptools=bundle, wheel=bundle, via=copy, app_data_dir=/home/mslinn/.local/share/virtualenv) added seed packages: pip==20.1.1, pkg_resources==0.0.0, setuptools==44.0.0, wheel==0.34.2 activators BashActivator,CShellActivator,FishActivator,PowerShellActivator,PythonActivator,XonshActivator $ source ~/oscar/bin/activate (oscar) $
Notice that the last command above changed the shell prompt, in that (oscar)
was prepended to the normal prompt. To cause all future shells to use this virtual environment by default, add the last command above to ~/.bashrc
, like this:
$ echo "source ~/oscar/bin/activate" >> ~/.bashrc
At this point the virtual environment just contained executable images for Python.
$ ls ~/oscar/** oscar/pyvenv.cfg oscar/bin: activate activate.ps1 chardetect distro easy_install pip pip3.8 python3.8 wheel3 activate.csh activate.xsh chardetect-3.8 distro-3.8 easy_install-3.8 pip-3.8 python wheel activate.fish activate_this.py chardetect3 distro3 easy_install3 pip3 python3 wheel-3.8 oscar/lib: python3.8
Next, I installed the Python libraries and their dependencies. django-oscar
installs Django as a dependency, plus I specified a few other dependencies that do not automatically get pulled in. I also installed django.db.backends.postgresql
because I wanted to use PostgreSQL instead of SQLite. BTW, django.db.backends.postgresql_psycopg2
was renamed in django 1.9 to django.db.backends.postgresql
, and is installed by pip
as django-postgresql
.
(oscar) $ pip install django-oscar[sorl-thumbnail] \
django-postgresql Pillow pycountry
Now the virtual environment also contains 3 Django executable images:
(oscar) $ ls ~/oscar/** /home/mslinn/oscar/pyvenv.cfg /home/mslinn/oscar/bin: __pycache__ activate.ps1 chardetect-3.8 distro3 easy_install-3.8 pip-3.8 python wheel activate activate.xsh chardetect3 django-admin easy_install3 pip3 python3 wheel-3.8 activate.csh activate_this.py distro django-admin.py faker pip3.8 python3.8 wheel3 activate.fish chardetect distro-3.8 easy_install pip pybabel sqlformat /home/mslinn/oscar/lib: python3.8
Let’s see information about the versions of django
and django-oscar
that were just installed into the oscar virtual environment:
(oscar) $ pip show django django-oscar Name: Django Version: 3.1.6 Summary: A high-level Python Web framework that encourages rapid development and clean, pragmatic design. Home-page: https://www.djangoproject.com/ Author: Django Software Foundation Author-email: foundation@djangoproject.com License: BSD-3-Clause Location: /home/mslinn/oscar/lib/python3.8/site-packages Requires: asgiref, sqlparse, pytz Required-by: django-treebeard, django-tables2, django-phonenumber-field, django-oscar, django-haystack, django-extra-views --- Name: django-oscar Version: 3.0 Summary: A domain-driven e-commerce framework for Django Home-page: https://github.com/django-oscar/django-oscar Author: David Winterbottom Author-email: david.winterbottom@gmail.com License: BSD Location: /home/mslinn/oscar/lib/python3.8/site-packages Requires: django-haystack, phonenumbers, django-widget-tweaks, django-treebeard, django-extra-views, Babel, purl, factory-boy, django-tables2, django-phonenumber-field, pillow, django Required-by:
Generating Frobshop
Now it was time to generate the frobshop
project from the template:
(oscar) $ mkdir $work/django && cd $work/django (oscar) $ django-admin startproject frobshop (oscar) $ cd frobshop
For convenience, I made frobshop
’s copy of the manage.py
Django command-line utility for administrative tasks executable:
(oscar) $ chmod a+x manage.py
Creating a Git Repository

I checked in the frobshop
project into a private git repository of the same name:
(oscar) $ git init (oscar) $ hub create -p frobshop (oscar) $ cat > .gitignore <<EOF **/__pycache__/ .idea/ .vscode/ EOF (oscar) $ git add . (oscar) $ git commit -m - (oscar) $ git push origin master
As I worked through the tutorial I used git
commits, branches and tags to insulate me from messing things up while experimenting. Git made it easy to restore the project to previous states, and to see what had changed between commits. My commit
script saved a lot of typing.
#!/bin/bash # Originally written May 9/05 by Mike Slinn function help { echo "Runs git commit without prompting for a message." echo "Usage: commit [options] [file...]" echo " Where options are:" echo " -a \"tag message\"" echo " -d # enables debug mode" echo " -m \"commit message\"" echo "Examples:" echo " commit # The default commit message is just a single dash (-)" echo " commit -m \"This is a commit message\"" echo " commit -a 0.1.2" exit 1 } function isGitProject { cd `git rev-parse --git-dir`/.. if [ -d .git ]; then true; else false; fi } BRANCH="$(git rev-parse --abbrev-ref HEAD)" MSG="" while getopts "a:dhm:\?" opt; do case $opt in a ) TAG="$OPTARG" git tag -a "$TAG" -m "v$TAG" git push origin --tags exit ;; d ) set -xv ;; m ) MSG="$OPTARG" ;; h ) help ;; \?) help ;; esac done shift $(($OPTIND-1)) for o in $*; do if [ "$o" == "-m" ]; then unset MSG; fi done CWD=`pwd` if [ isGitProject ]; then HAVE_GIT=true; fi if [ "$HAVE_GIT" ]; then if [ "$@" ]; then git add -A $@ else git add -A . fi shift if [ "$MSG" == "" ]; then MSG="-"; fi git commit -m "$MSG" $@ git push origin "$BRANCH" fi if [ -f 0 ]; then rm -f 0; fi
$ commit -h Runs git commit without prompting for a message. Usage: commit [options] [file...] Where options are: -a "tag message" -d # enables debug mode -m "commit message" Examples: commit # The default commit message is just a single dash (-) commit -m "This is a commit message" commit -a 0.1.2
manage.py Help
The manage.py
script can do many things. Here are its subcommands:
$ ./manage.py Type 'manage.py help' for help on a specific subcommand. Available subcommands: [auth] changepassword createsuperuser [contenttypes] remove_stale_contenttypes [django] check compilemessages createcachetable dbshell diffsettings dumpdata flush inspectdb loaddata makemessages makemigrations migrate sendtestemail shell showmigrations sqlflush sqlmigrate sqlsequencereset squashmigrations startapp startproject test testserver [haystack] build_solr_schema clear_index haystack_info rebuild_index update_index [oscar] oscar_calculate_scores oscar_cleanup_alerts oscar_find_duplicate_emails oscar_fork_app oscar_fork_statics oscar_generate_email_content oscar_import_catalogue oscar_import_catalogue_images oscar_populate_countries oscar_send_alerts oscar_update_product_ratings [sessions] clearsessions [staticfiles] collectstatic findstatic runserver [thumbnail] thumbnail
requirements.txt
I found it strange that Frobshop did not provide requirements.txt
to install managed dependencies. I created the file within the frobshop
directory so I could replicate this installation on other machines:
(oscar) $ pip freeze > requirements.txt
The file looked like this:
appdirs==1.4.3 asgiref==3.3.1 Babel==2.9.0 CacheControl==0.12.6 certifi==2019.11.28 chardet==3.0.4 colorama==0.4.3 contextlib2==0.6.0 distlib==0.3.0 distro==1.4.0 Django==3.1.6 django-extra-views==0.13.0 django-haystack==3.0 django-oscar==3.0 django-phonenumber-field==3.0.1 django-tables2==2.3.4 django-treebeard==4.4 django-widget-tweaks==1.4.8 factory-boy==2.12.0 Faker==6.1.1 html5lib==1.0.1 idna==2.8 ipaddr==2.2.0 lockfile==0.12.2 msgpack==0.6.2 packaging==20.3 pep517==0.8.2 phonenumbers==8.12.18 Pillow==8.1.0 progress==1.5 purl==1.5 pycountry==20.7.3 pyparsing==2.4.6 python-dateutil==2.8.1 pytoml==0.1.21 pytz==2021.1 requests==2.22.0 retrying==1.3.3 six==1.14.0 sorl-thumbnail==12.6.3 sqlparse==0.4.1 text-unidecode==1.3 urllib3==1.25.8 webencodings==0.5.1
I think that requirements.txt
should have been provided with Frobshop. If it had been provided, all one would have to do in order to install the proper version of all the Python modules would be to type:
(oscar) $ pip install -r requirements.txt
This was a good time to commit my work to the git repository:
(oscar) $ commit -m "First cut at dependencies"
Configuring Frobshop
The Frobshop documentation says to make massive changes to configuration files, without explaining why. No references are given, but I linked to all the documentation I could find. It seems some core apps, essential to proper operation, are undocumented.
Caution: the contents of these files change quite a bit between recent Django versions, so be warned that the config files are tightly bound to the Django version that it was generated for. This means that if you see a solution on StackOverflow to a problem that you currently have, blindly applying that solution may cause more problems instead of fixing them.

Here are the configured files that were altered, including a few changes I found necessary to make things work.
""" Django settings for frobshop project. Generated by 'django-admin startproject' using Django 3.1.6. For more information on this file, see https://docs.djangoproject.com/en/3.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.1/ref/settings/ """ from pathlib import Path from oscar.defaults import * # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = 'irs@7(qzjki2)gv7%b)zj5$t-6!l^kod0n4+!&hbgcku&b=$iv' # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ['*'] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django.contrib.sites', 'django.contrib.flatpages', 'oscar.config.Shop', 'oscar.apps.analytics.apps.AnalyticsConfig', 'oscar.apps.checkout.apps.CheckoutConfig', 'oscar.apps.address.apps.AddressConfig', 'oscar.apps.shipping.apps.ShippingConfig', 'oscar.apps.catalogue.apps.CatalogueConfig', 'oscar.apps.catalogue.reviews.apps.CatalogueReviewsConfig', 'oscar.apps.communication.apps.CommunicationConfig', 'oscar.apps.partner.apps.PartnerConfig', 'oscar.apps.basket.apps.BasketConfig', 'oscar.apps.payment.apps.PaymentConfig', 'oscar.apps.offer.apps.OfferConfig', 'oscar.apps.order.apps.OrderConfig', 'oscar.apps.customer.apps.CustomerConfig', 'oscar.apps.search.apps.SearchConfig', 'oscar.apps.voucher.apps.VoucherConfig', 'oscar.apps.wishlists.apps.WishlistsConfig', 'oscar.apps.dashboard.apps.DashboardConfig', 'oscar.apps.dashboard.reports.apps.ReportsDashboardConfig', 'oscar.apps.dashboard.users.apps.UsersDashboardConfig', 'oscar.apps.dashboard.orders.apps.OrdersDashboardConfig', 'oscar.apps.dashboard.catalogue.apps.CatalogueDashboardConfig', 'oscar.apps.dashboard.offers.apps.OffersDashboardConfig', 'oscar.apps.dashboard.partners.apps.PartnersDashboardConfig', 'oscar.apps.dashboard.pages.apps.PagesDashboardConfig', 'oscar.apps.dashboard.ranges.apps.RangesDashboardConfig', 'oscar.apps.dashboard.reviews.apps.ReviewsDashboardConfig', 'oscar.apps.dashboard.vouchers.apps.VouchersDashboardConfig', 'oscar.apps.dashboard.communications.apps.CommunicationsDashboardConfig', 'oscar.apps.dashboard.shipping.apps.ShippingDashboardConfig', # 3rd-party apps that oscar depends on 'widget_tweaks', 'haystack', 'treebeard', 'sorl.thumbnail', # Default thumbnail backend, can be replaced 'django_tables2', ] SITE_ID = 1 MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'oscar.apps.basket.middleware.BasketMiddleware', 'django.contrib.flatpages.middleware.FlatpageFallbackMiddleware', ] ROOT_URLCONF = 'frobshop.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'oscar.apps.search.context_processors.search_form', 'oscar.apps.checkout.context_processors.checkout', 'oscar.apps.communication.notifications.context_processors.notifications', 'oscar.core.context_processors.metadata', ], }, }, ] WSGI_APPLICATION = 'frobshop.wsgi.application' # Database # https://docs.djangoproject.com/en/3.1/ref/settings/#databases DATABASES = { 'default': { #'ENGINE': 'django.db.backends.sqlite3', #'NAME': BASE_DIR / 'db.sqlite3', 'ENGINE': 'django.db.backends.postgresql', 'HOST': 'localhost', 'NAME': 'frobshop', 'PASSWORD': 'secret', 'PORT': '5432', 'USER': 'postgres', 'ATOMIC_REQUESTS': True, } } # Password validation # https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] AUTHENTICATION_BACKENDS = ( 'oscar.apps.customer.auth_backends.EmailBackend', 'django.contrib.auth.backends.ModelBackend', ) EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' HAYSTACK_CONNECTIONS = { 'default': { 'ENGINE': 'haystack.backends.simple_backend.SimpleEngine', #'ENGINE': 'haystack.backends.solr_backend.SolrEngine', #'URL': 'http://127.0.0.1:8983/solr/tester', # Assuming you created a core named 'tester' as described in installing search engines. #'ADMIN_URL': 'http://127.0.0.1:8983/solr/admin/cores' # ...or for multicore... # 'URL': 'http://127.0.0.1:8983/solr/mysite', }, } # Internationalization # https://docs.djangoproject.com/en/3.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'America/New_York' USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.1/howto/static-files/ STATIC_URL = '/static/'
Here is documentation that helps explain what the above application-related settings above pertain to:
Django’s routing table is defined using urls.py
:
""" frobshop URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.1/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import include, path urlpatterns = [ # path('i18n/', include('django.conf.urls.i18n')), path('admin/', admin.site.urls), path('', include(apps.get_app_config('oscar').urls[0])), ]
Time once again to commit my work:
(oscar) $ commit -m "Initial settings."
Installing PostgreSQL
(oscar) $ sudo curl https://www.pgadmin.org/static/packages_pgadmin_org.pub | sudo apt-key add (oscar) $ sudo sh -c 'echo "deb https://ftp.postgresql.org/pub/pgadmin/pgadmin4/apt/$(lsb_release -cs) pgadmin4 main" > /etc/apt/sources.list.d/pgadmin4.list && apt update' (oscar) $ yes | sudo apt install postgresql pgadmin4 (oscar) $ sudo service postgresql start (oscar) $ sudo -u postgres /usr/bin/psql -h localhost -c "ALTER USER postgres PASSWORD 'whatever';"
Creating the Database
I had previously installed PostgreSQL. I just needed to create an empty database called frobshop
. One option for doing that would be to run psql
and pass it parameters that indicate the network node and user to connect, then create the database:
$ /usr/bin/psql -U postgres -h localhost -c 'create database frobshop'
BTW, I had originally followed the Frobshop tutorial's suggestion to use SQLite. I then tried to convert the frobshop
SQLite database to PostgreSQL like this, but the conversion failed:
$ sudo apt install pgloader $ PGPASSWORD=secret /bin/createdb -U postgres -h localhost frobshop $ PGPASSWORD=secret pgloader -U postgres -h localhost \ db.sqlite3 postgresql:///frobshop $ PGPASSWORD=secret pgloader -U postgres \ ./sqlite.to.postgres
load database from db.sqlite3 into postgresql:///frobshop with include drop, create tables, create indexes, reset sequences set work_mem to '16MB', maintenance_work_mem to '512 MB';
I view SQLite as a toy, which does not lend itself to proper experimentation. It would make sense to just set up PostgreSQL straight away if you have any desire to understand how django-oscar
works.
Since this step did not change any Frobshop files there was nothing to commit to the git repository.
oscar_populate_countries Help
A django-oscar
website will not work until country information is defined. The oscar_populate_countries
subcommand of manage.py
defines countries. Here is the help information for that subcommand:
$ ./manage.py oscar_populate_countries -h usage: manage.py oscar_populate_countries [-h] [--no-shipping] [--initial-only] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks] Populates the list of countries with data from pycountry. optional arguments: -h, --help show this help message and exit --no-shipping Don't mark countries for shipping --initial-only Exit quietly without doing anything if countries were already populated. --version show program's version number and exit -v {0,1,2,3}, --verbosity {0,1,2,3} Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output --settings SETTINGS The Python path to a settings module, e.g. "myproject.settings.main". If this isn't provided, the DJANGO_SETTINGS_MODULE environment variable will be used. --pythonpath PYTHONPATH A directory to add to the Python path, e.g. "/home/djangoprojects/myproject". --traceback Raise on CommandError exceptions --no-color Don't colorize the command output. --force-color Force colorization of the command output. --skip-checks Skip system checks.
Defining Countries
I just want to be able to ship to Canada and the USA. Several steps are required to do that:
- Invoke
oscar_populate_countries
with--no-shipping
Shell$ ./manage.py oscar_populate_countries --no-shipping Successfully added 249 countries.
- Use
django-admin
(for me that washttp://localhost:8001/admin/
). - Navigate to the country editor.
- Click on Canada.
- Enable Is shipping country and press Save.
- Click on Countries at the top of the left-hand menu.
- Type
United States
into the text search field, click on United States, enable Is shipping country and press Save.
Since this step did not change any Frobshop files there was nothing to commit to the git repository.
manage.py migrate Options

The Frobshop tutorial does not define data migrations very well. This is a gentle introduction to Django migrations. This is the official documentation, and this is information about Writing database migrations. The help for manage.py migrate
is:
(oscar) $ ./manage.py migrate -h usage: manage.py migrate [-h] [--noinput] [--database DATABASE] [--fake] [--fake-initial] [--plan] [--run-syncdb] [--check] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks] [app_label] [migration_name] Updates database schema. Manages both apps with migrations and those without. positional arguments: app_label App label of an application to synchronize the state. migration_name Database state will be brought to the state after that migration. Use the name "zero" to unapply all migrations. optional arguments: -h, --help show this help message and exit --noinput, --no-input Tells Django to NOT prompt the user for input of any kind. --database DATABASE Nominates a database to synchronize. Defaults to the "default" database. --fake Mark migrations as run without actually running them. --fake-initial Detect if tables already exist and fake-apply initial migrations if so. Make sure that the current database schema matches your initial migration before using this flag. Django will only check for an existing table name. --plan Shows a list of the migration actions that will be performed. --run-syncdb Creates tables for apps without migrations. --check Exits with a non-zero status if unapplied migrations exist. --version show program's version number and exit -v {0,1,2,3}, --verbosity {0,1,2,3} Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output --settings SETTINGS The Python path to a settings module, e.g. "myproject.settings.main". If this isn't provided, the DJANGO_SETTINGS_MODULE environment variable will be used. --pythonpath PYTHONPATH A directory to add to the Python path, e.g. "/home/djangoprojects/myproject". --traceback Raise on CommandError exceptions --no-color Don't colorize the command output. --force-color Force colorization of the command output. --skip-checks Skip system checks.
Creating the Database from Migrations
$ ./manage.py migrate System check identified some issues: WARNINGS: catalogue.ProductAttributeValue.value_boolean: (fields.W903) NullBooleanField is deprecated. Support for it (except in historical migrations) will be removed in Django 4.0. HINT: Use BooleanField(null=True) instead. Operations to perform: Apply all migrations: address, admin, analytics, auth, basket, catalogue, communication, contenttypes, customer, flatpages, offer, order, partner, payment, reviews, sessions, shipping, sites, thumbnail, voucher, wishlists Running migrations: Applying contenttypes.0001_initial... OK Applying auth.0001_initial... OK Applying address.0001_initial... OK Applying address.0002_auto_20150927_1547... OK Applying address.0003_auto_20150927_1551... OK Applying address.0004_auto_20170226_1122... OK Applying address.0005_regenerate_user_address_hashes... OK Applying address.0006_auto_20181115_1953... OK Applying admin.0001_initial... OK Applying admin.0002_logentry_remove_auto_add... OK Applying admin.0003_logentry_add_action_flag_choices... OK Applying catalogue.0001_initial... OK Applying analytics.0001_initial... OK Applying analytics.0002_auto_20140827_1705... OK Applying analytics.0003_auto_20200801_0817... OK Applying contenttypes.0002_remove_content_type_name... OK Applying auth.0002_alter_permission_name_max_length... OK Applying auth.0003_alter_user_email_max_length... OK Applying auth.0004_alter_user_username_opts... OK Applying auth.0005_alter_user_last_login_null... OK Applying auth.0006_require_contenttypes_0002... OK Applying auth.0007_alter_validators_add_error_messages... OK Applying auth.0008_alter_user_username_max_length... OK Applying auth.0009_alter_user_last_name_max_length... OK Applying auth.0010_alter_group_name_max_length... OK Applying auth.0011_update_proxy_permissions... OK Applying auth.0012_alter_user_first_name_max_length... OK Applying sites.0001_initial... OK Applying partner.0001_initial... OK Applying customer.0001_initial... OK Applying basket.0001_initial... OK Applying basket.0002_auto_20140827_1705... OK Applying order.0001_initial... OK Applying offer.0001_initial... OK Applying voucher.0001_initial... OK Applying basket.0003_basket_vouchers... OK Applying basket.0004_auto_20141007_2032... OK Applying basket.0005_auto_20150604_1450... OK Applying basket.0006_auto_20160111_1108... OK Applying basket.0007_slugfield_noop... OK Applying basket.0008_auto_20181115_1953... OK Applying basket.0009_line_date_updated... OK Applying catalogue.0002_auto_20150217_1221... OK Applying catalogue.0003_data_migration_slugs... OK Applying catalogue.0004_auto_20150217_1710... OK Applying catalogue.0005_auto_20150604_1450... OK Applying catalogue.0006_auto_20150807_1725... OK Applying catalogue.0007_auto_20151207_1440... OK Applying catalogue.0008_auto_20160304_1652... OK Applying catalogue.0009_slugfield_noop... OK Applying catalogue.0010_auto_20170420_0439... OK Applying catalogue.0011_auto_20170422_1355... OK Applying catalogue.0012_auto_20170609_1902... OK Applying catalogue.0013_auto_20170821_1548... OK Applying catalogue.0014_auto_20181115_1953... OK Applying catalogue.0015_product_is_public... OK Applying catalogue.0016_auto_20190327_0757... OK Applying catalogue.0017_auto_20190816_0938... OK Applying catalogue.0018_auto_20191220_0920... OK Applying catalogue.0019_option_required... OK Applying catalogue.0020_auto_20200801_0817... OK Applying catalogue.0021_auto_20201005_0844... OK Applying order.0002_auto_20141007_2032... OK Applying order.0003_auto_20150113_1629... OK Applying order.0004_auto_20160111_1108... OK Applying order.0005_update_email_length... OK Applying order.0006_orderstatuschange... OK Applying order.0007_auto_20181115_1953... OK Applying customer.0002_auto_20150807_1725... OK Applying customer.0003_update_email_length... OK Applying customer.0004_email_save... OK Applying customer.0005_auto_20181115_1953... OK Applying communication.0001_initial... OK Applying order.0008_auto_20190301_1035... OK Applying communication.0002_reset_table_names... OK Applying communication.0003_remove_notification_category_make_code_uppercase... OK Applying communication.0004_auto_20200801_0817... OK Applying customer.0006_auto_20190430_1736... OK Applying customer.0007_auto_20200801_0817... OK Applying flatpages.0001_initial... OK Applying offer.0002_auto_20151210_1053... OK Applying offer.0003_auto_20161120_1707... OK Applying offer.0004_auto_20170415_1518... OK Applying offer.0005_auto_20170423_1217... OK Applying offer.0006_auto_20170504_0616... OK Applying offer.0007_conditionaloffer_exclusive... OK Applying offer.0008_auto_20181115_1953... OK Applying offer.0009_auto_20200801_0817... OK Applying offer.0010_conditionaloffer_combinations... OK Applying order.0009_surcharge... OK Applying order.0010_auto_20200724_0909... OK Applying order.0011_auto_20200801_0817... OK Applying partner.0002_auto_20141007_2032... OK Applying partner.0003_auto_20150604_1450... OK Applying partner.0004_auto_20160107_1755... OK Applying partner.0005_auto_20181115_1953... OK Applying partner.0006_auto_20200724_0909... OK Applying payment.0001_initial... OK Applying payment.0002_auto_20141007_2032... OK Applying payment.0003_auto_20160323_1520... OK Applying payment.0004_auto_20181115_1953... OK Applying payment.0005_auto_20200801_0817... OK Applying reviews.0001_initial... OK Applying reviews.0002_update_email_length... OK Applying reviews.0003_auto_20160802_1358... OK Applying reviews.0004_auto_20170429_0941... OK Applying sessions.0001_initial... OK Applying shipping.0001_initial... OK Applying shipping.0002_auto_20150604_1450... OK Applying shipping.0003_auto_20181115_1953... OK Applying sites.0002_alter_domain_unique... OK Applying thumbnail.0001_initial... OK Applying voucher.0002_auto_20170418_2132... OK Applying voucher.0003_auto_20171212_0411... OK Applying voucher.0004_auto_20180228_0940... OK Applying voucher.0005_auto_20180402_1425... OK Applying voucher.0006_auto_20180413_0911... OK Applying voucher.0007_auto_20181115_1953... OK Applying voucher.0008_auto_20200801_0817... OK Applying wishlists.0001_initial... OK Applying wishlists.0002_auto_20160111_1108... OK Applying wishlists.0003_auto_20181115_1953... OK
Since this step did not change any Frobshop files there was nothing to commit to the git repository.
Mysterious Warning from manage.py
This warning appeared every time manage.py
ran:
WARNINGS: catalogue.ProductAttributeValue.value_boolean: (fields.W903) NullBooleanField is deprecated. Support for it (except in historical migrations) will be removed in Django 4.0. HINT: Use BooleanField(null=True) instead.
Thanks to StackOverflow, I was able to make this django-oscar
warning disappear. This is what the offending line looked like (line folded for legibility):
value_boolean = models.NullBooleanField(_('Boolean'), blank=True, db_index=True)
This is what I understood the suggested change to be (line folded for legibility):
value_boolean = models.BooleanField(_('Boolean'),
blank=True, db_index=True)
Here is the incantation I used, which relies on an environment variable called oscar
to point to the Python virtual environment. Line 1043 of abstract_models.py
was modified, within the definition of a class called AbstractProductAttributeValue
.
$ sed -i 's/models.NullBooleanField/models.BooleanField/' \
$oscar/lib/python3.8/site-packages/oscar/apps/catalogue/abstract_models.py
Update: After further experimentation, whereby I added a new Django app called aw_pricing
to frobshop, I encountered this error:
$ ./manage.py makemigrations aw_pricing You are trying to change the nullable field 'value_boolean' on productattributevalue to non-nullable without a default; we can't do that (the database needs something to populate existing rows). Please select a fix: 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) 2) Ignore for now, and let me handle existing rows with NULL myself (e.g. because you added a RunPython or RunSQL operation to handle NULL values in a previous data migration) 3) Quit, and let me add a default in models.py Select an option:
I cancelled the makemigrations
subcommand and changed the definition of the field to allow nulls, so it looked like what was in the django-oscar
master branch (line folded for legibility):
value_boolean = models.BooleanField(_('Boolean'), blank=True,
null=True, db_index=True)
The error went away the next time I ran makemigrations
. The sed command to make this change is:
$ sed -i \
-e 's/value_boolean = /models.NullBooleanField/models.BooleanField/' \
-e 's/value_boolean = /)/, null=True)/' \
$oscar/lib/python3.8/site-packages/oscar/apps/catalogue/abstract_models.py
Creating Django Superuser

The Frobshop documentation did not mention this topic. It is easy to do.
(oscar) $ ./manage.py createsuperuser
Username (leave blank to use 'mslinn'):
Email address: mslinn@ancientwarmth.com
Password:
Password (again):
Superuser created successfully.
Since this step did not change any Frobshop files there was nothing to commit to the git repository.
manage.py runserver Options
The help for manage.py runserver
is:
(oscar) $ ./manage.py runserver -h usage: manage.py runserver [-h] [--ipv6] [--nothreading] [--noreload] [--nostatic] [--insecure] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [addrport] Starts a lightweight Web server for development and also serves static files. positional arguments: addrport Optional port number, or ipaddr:port optional arguments: -h, --help show this help message and exit --ipv6, -6 Tells Django to use an IPv6 address. --nothreading Tells Django to NOT use threading. --noreload Tells Django to NOT use the auto-reloader. --nostatic Tells Django to NOT automatically serve static files at STATIC_URL. --insecure Allows serving static files even if DEBUG is False. --version show program's version number and exit -v {0,1,2,3}, --verbosity {0,1,2,3} Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output --settings SETTINGS The Python path to a settings module, e.g. "myproject.settings.main". If this isn't provided, the DJANGO_SETTINGS_MODULE environment variable will be used. --pythonpath PYTHONPATH A directory to add to the Python path, e.g. "/home/djangoprojects/myproject". --traceback Raise on CommandError exceptions --no-color Don't colorize the command output. --force-color Force colorization of the command output.
Launching Oscar

Finally it was time to launch oscar
on port 8000:
(oscar) $ ./manage.py runserver Watching for file changes with StatReloader February 11, 2021 - 15:45:01 Django version 3.1.6, using settings 'frobshop.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
Launch Failure
Frobshop failed to launch. This was due to a serious django-oscar
implementation deficiency:
-
django-oscar
forms is hard-coded to requiredjango-haystack
:Excerpt from oscar/apps/search/forms.htmlfrom haystack.forms import FacetedSearchForm
- Apache Solr is mandated for Haystack:Oscar uses Haystack to abstract away from different search backends. Unfortunately, writing backend-agnostic code is nonetheless hard and Apache Solr is currently the only supported production-grade backend.While the
django-oscar
documentation describes various other fulltext search backends (ElasticSearch, Whoosh, and Xapian), the official position is that they should not be used in production.
I wrote up a blog post on this issue, and made further suggestions in a GitHub issue that I created for this topic.
Workaround
There is a django-haystack
backend called SimpleEngine
that uses ordinary SQL queries instead of fulltext search.
After I installed and enabled django-haystack
, and configured frobshop/settings.py
to use SimpleEngine
django-oscar
presented itself in my browser at http://localhost:8000
.

I logged in as the superuser and saw:

Time once again to commit my work:
(oscar) $ commit -m "Switched haystack.backends from SolrEngine to SimpleEngine."
Order Pipeline
The Frobshop tutorial does not offer any information about how the order status of line items affects the status of orders. I found How to set up order processing, which addressed the issue. The tutorial should link to that page.
Reading “How to set up order processing”, it seems there is a checkout
app and an order
app. A link to these from the article “How to set up order processing” would have been helpful. This page would be a good place to provide a discussion of how apps work together to enable django-oscar
’s functionality, but I could only find a generic explanation of Django apps in the Django documentation.
Unfortunately, “How to set up order processing” does not provide a sufficiently detailed explanation to understand the suggested settings in the Frobshop tutorial, which are:
OSCAR_INITIAL_ORDER_STATUS = 'Pending' OSCAR_INITIAL_LINE_STATUS = 'Pending' OSCAR_ORDER_STATUS_PIPELINE = { 'Pending': ('Being processed', 'Cancelled',), 'Being processed': ('Processed', 'Cancelled',), 'Cancelled': (), }
Reading the Frobshop tutorial, I expected that if I copied the above settings into frobshop/settings.py
that “With these three settings defined in your project you’ll be able to see the different statuses in the order management dashboard.” However, the information presented in “How to set up order processing” made me think that some custom programming would be required. This Frobshop tutorial is horrible!
I pasted the above into frobshop/settings.py
, and django-oscar
started without errors or warnings. I am unsure how to ‘order’ a product from this tutorial, however.
Time once again to commit my work:
(oscar) $ commit -m "Defined order pipeline without understanding what I did."
More Clues
The django-oscar
docs are sorely lacking. I found this buried in the source code for django-oscar
: “The pipeline defines the statuses that an order or line item can have and what transitions are allowed in any given status. The pipeline is defined as a dictionary where the keys are the available statuses. Allowed transitions are defined as iterable values for the corresponding status.”
Additional settings in the same internal document include:
OSCAR_ORDER_STATUS_CASCADE
This defines a mapping of status changes for order lines which cascade from an order status change. For example:
OSCAR_ORDER_STATUS_CASCADE = { 'Being processed': 'In progress' }
With this mapping, when an order has its status set to Being processed
, all lines within it have their status set to In progress
. In a sense, the status change cascades down to the related objects. Note that this cascade ignores restrictions from the OSCAR_LINE_STATUS_PIPELINE
.
OSCAR_LINE_STATUS_PIPELINE
Same as OSCAR_ORDER_STATUS_PIPELINE
but for lines. Default: {}
Conclusion
Django-oscar
seems well designed, and seems to have all the features that I am looking for. The community is active, repsonsive and helpful. However, django-oscar
, like many open-source products, suffers from poor documentation and has many problems. Nothing is perfect. However, django-oscar
might be good enough.