In an earlier article I discussed a simple setup for Django unit testing in general,
using unittest
,
which requires tests to be written using classes.
Unfortunately, the official Django documentation for unittest
does not mention fixtures by name.
Instead, the docs contain a poorly written description of how to use manage.py
’s
dumpdata
and loaddata
commands to manage test data.
I found the class-based approach imposed by unittest
to be limiting because it is incompatible with using programmatically-generated fixtures.
This article focuses on unit testing using PyTest,
which could be described as a loose superset of unittest
that has strong support for programmatically-generated fixtures.
PyTest does not need tests to be arranged in classes, nor are test classes supported.
I actually installed pytest-django because it integrates PyTest with Django.
(aw) $ pip install pytest-django
Below is the pytest
help message.
It is difficult to read, and would benefit from copy editing and formatting:
(aw) $ pytest -h usage: pytest [options] [file_or_dir] [file_or_dir] [...]
positional arguments: file_or_dir
general: -k EXPRESSION only run tests which match the given substring expression. An expression is a python evaluatable expression where all names are substring-matched against test names and their parent classes. Example: -k 'test_method or test_other' matches all test functions and classes whose name contains 'test_method' or 'test_other', while -k 'not test_method' matches those that don't contain 'test_method' in their names. -k 'not test_method and not test_other' will eliminate the matches. Additionally keywords are matched to classes and functions containing extra names in their 'extra_keyword_matches' set, as well as functions which have names assigned directly to them. The matching is case-insensitive. -m MARKEXPR only run tests matching given mark expression. For example: -m 'mark1 and not mark2'. --markers show markers (builtin, plugin and per-project ones). -x, --exitfirst exit instantly on first error or failed test. --fixtures, --funcargs show available fixtures, sorted by plugin appearance (fixtures with leading '_' are only shown with '-v') --fixtures-per-test show fixtures per test --pdb start the interactive Python debugger on errors or KeyboardInterrupt. --pdbcls=modulename:classname start a custom interactive Python debugger on errors. For example: --pdbcls=IPython.terminal.debugger:TerminalPdb --trace Immediately break when running each test. --capture=method per-test capturing method: one of fd|sys|no|tee-sys. -s shortcut for --capture=no. --runxfail report the results of xfail tests as if they were not marked --lf, --last-failed rerun only the tests that failed at the last run (or all if none failed) --ff, --failed-first run all tests, but run the last failures first. This may re-order tests and thus lead to repeated fixture setup/teardown. --nf, --new-first run tests from new files first, then the rest of the tests sorted by file mtime --cache-show=[CACHESHOW] show cache contents, don't perform collection or tests. Optional argument: glob (default: '*'). --cache-clear remove all cache contents at start of test run. --lfnf={all,none}, --last-failed-no-failures={all,none} which tests to run with no previously (known) failures. --sw, --stepwise exit on test failure and continue from last failing test next time --sw-skip, --stepwise-skip ignore the first failing test but stop on the next failing test
reporting: --durations=N show N slowest setup/test durations (N=0 for all). --durations-min=N Minimal duration in seconds for inclusion in slowest list. Default 0.005 -v, --verbose increase verbosity. --no-header disable header --no-summary disable summary -q, --quiet decrease verbosity. --verbosity=VERBOSE set verbosity. Default is 0. -r chars show extra test summary info as specified by chars: (f)ailed, (E)rror, (s)kipped, (x)failed, (X)passed, (p)assed, (P)assed with output, (a)ll except passed (p/P), or (A)ll. (w)arnings are enabled by default (see --disable-warnings), 'N' can be used to reset the list. (default: 'fE'). --disable-warnings, --disable-pytest-warnings disable warnings summary -l, --showlocals show locals in tracebacks (disabled by default). --tb=style traceback print mode (auto/long/short/line/native/no). --show-capture={no,stdout,stderr,log,all} Controls how captured stdout/stderr/log is shown on failed tests. Default is 'all'. --full-trace don't cut any tracebacks (default is to cut). --color=color color terminal output (yes/no/auto). --code-highlight={yes,no} Whether code should be highlighted (only if --color is also enabled) --pastebin=mode send failed|all info to bpaste.net pastebin service. --junit-xml=path create junit-xml style report file at given path. --junit-prefix=str prepend prefix to classnames in junit-xml output
pytest-warnings: -W PYTHONWARNINGS, --pythonwarnings=PYTHONWARNINGS set which warnings to report, see -W option of python itself. --maxfail=num exit after first num failures or errors. --strict-config any warnings encountered while parsing the `pytest` section of the configuration file raise errors. --strict-markers markers not registered in the `markers` section of the configuration file raise errors. --strict (deprecated) alias to --strict-markers. -c file load configuration from `file` instead of trying to locate one of the implicit configuration files. --continue-on-collection-errors Force test execution even if collection errors occur. --rootdir=ROOTDIR Define root directory for tests. Can be relative path: 'root_dir', './root_dir', 'root_dir/another_dir/'; absolute path: '/home/user/root_dir'; path with variables: '$HOME/root_dir'.
collection: --collect-only, --co only collect tests, don't execute them. --pyargs try to interpret all arguments as python packages. --ignore=path ignore path during collection (multi-allowed). --ignore-glob=path ignore path pattern during collection (multi-allowed). --deselect=nodeid_prefix deselect item (via node id prefix) during collection (multi-allowed). --confcutdir=dir only load conftest.py's relative to specified dir. --noconftest Don't load any conftest.py files. --keep-duplicates Keep duplicate tests. --collect-in-virtualenv Don't ignore tests in a local virtualenv directory --import-mode={prepend,append,importlib} prepend/append to sys.path when importing test modules and conftest files, default is to prepend. --doctest-modules run doctests in all .py modules --doctest-report={none,cdiff,ndiff,udiff,only_first_failure} choose another output format for diffs on doctest failure --doctest-glob=pat doctests file matching pattern, default: test*.txt --doctest-ignore-import-errors ignore doctest ImportErrors --doctest-continue-on-failure for a given doctest, continue to run after the first failure
test session debugging and configuration: --basetemp=dir base temporary directory for this test run.(warning: this directory is removed if it exists) -V, --version display pytest version and information about plugins.When given twice, also display information about plugins. -h, --help show help message and configuration info -p name early-load given plugin module name or entry point (multi-allowed). To avoid loading of plugins, use the `no:` prefix, e.g. `no:doctest`. --trace-config trace considerations of conftest.py files. --debug store internal tracing debug information in 'pytestdebug.log'. -o OVERRIDE_INI, --override-ini=OVERRIDE_INI override ini option with "option=value" style, e.g. `-o xfail_strict=True -o cache_dir=cache`. --assert=MODE Control assertion debugging tools. 'plain' performs no assertion debugging. 'rewrite' (the default) rewrites assert statements in test modules on import to provide assert expression information. --setup-only only setup fixtures, do not execute tests. --setup-show show setup of fixtures while executing tests. --setup-plan show what fixtures and tests would be executed but don't execute anything.
logging: --log-level=LEVEL level of messages to catch/display. Not set by default, so it depends on the root/parent log handler's effective level, where it is "WARNING" by default. --log-format=LOG_FORMAT log format as used by the logging module. --log-date-format=LOG_DATE_FORMAT log date format as used by the logging module. --log-cli-level=LOG_CLI_LEVEL cli logging level. --log-cli-format=LOG_CLI_FORMAT log format as used by the logging module. --log-cli-date-format=LOG_CLI_DATE_FORMAT log date format as used by the logging module. --log-file=LOG_FILE path to a file when logging will be written to. --log-file-level=LOG_FILE_LEVEL log file logging level. --log-file-format=LOG_FILE_FORMAT log format as used by the logging module. --log-file-date-format=LOG_FILE_DATE_FORMAT log date format as used by the logging module. --log-auto-indent=LOG_AUTO_INDENT Auto-indent multiline messages passed to the logging module. Accepts true|on, false|off or an integer.
[pytest] ini-options in the first pytest.ini|tox.ini|setup.cfg file found:
markers (linelist): markers for test functions empty_parameter_set_mark (string): default marker for empty parametersets norecursedirs (args): directory patterns to avoid for recursion testpaths (args): directories to search for tests when no files or directories are given in the command line. filterwarnings (linelist): Each line specifies a pattern for warnings.filterwarnings. Processed after -W/--pythonwarnings. usefixtures (args): list of default fixtures to be used with this project python_files (args): glob-style file patterns for Python test module discovery python_classes (args): prefixes or glob names for Python test class discovery python_functions (args): prefixes or glob names for Python test function and method discovery disable_test_id_escaping_and_forfeit_all_rights_to_community_support (bool): disable string escape non-ascii characters, might cause unwanted side effects(use at your own risk) console_output_style (string): console output: "classic", or with additional progress information ("progress" (percentage) | "count"). xfail_strict (bool): default for the strict parameter of xfail markers when not given explicitly (default: False) enable_assertion_pass_hook (bool): Enables the pytest_assertion_pass hook.Make sure to delete any previously generated pyc cache files. junit_suite_name (string): Test suite name for JUnit report junit_logging (string): Write captured log messages to JUnit report: one of no|log|system-out|system-err|out-err|all junit_log_passing_tests (bool): Capture log information for passing tests to JUnit report: junit_duration_report (string): Duration time to report: one of total|call junit_family (string): Emit XML for schema: one of legacy|xunit1|xunit2 doctest_optionflags (args): option flags for doctests doctest_encoding (string): encoding used for doctest files cache_dir (string): cache directory path. log_level (string): default value for --log-level log_format (string): default value for --log-format log_date_format (string): default value for --log-date-format log_cli (bool): enable log display during test run (also known as "live logging"). log_cli_level (string): default value for --log-cli-level log_cli_format (string): default value for --log-cli-format log_cli_date_format (string): default value for --log-cli-date-format log_file (string): default value for --log-file log_file_level (string): default value for --log-file-level log_file_format (string): default value for --log-file-format log_file_date_format (string): default value for --log-file-date-format log_auto_indent (string): default value for --log-auto-indent faulthandler_timeout (string): Dump the traceback of all threads if a test takes more than TIMEOUT seconds to finish. addopts (args): extra command line options minversion (string): minimally required pytest version required_plugins (args): plugins that must be present for pytest to run
environment variables: PYTEST_ADDOPTS extra command line options PYTEST_PLUGINS comma-separated plugins to load during startup PYTEST_DISABLE_PLUGIN_AUTOLOAD set to disable plugin auto-loading PYTEST_DEBUG set to enable debug tracing of pytest's internals
to see available markers type: pytest --markers to see available fixtures type: pytest --fixtures (shown according to specified file_or_dir or current dir if not specified; fixtures with leading '_' are only shown with the '-v' option
I found that PyTest ran extremely slowly for my tests when accessing the database (66 seconds).
Instead of taking seconds it took minutes to create a few simple fixtures.
I installed pytest-timer[termcolor]
to see what was slow.
That showed me that the tests took 0.0029 seconds in all; startup overhead took 66 seconds.
When I disabled all the tests (by annotating them with @pytest.mark.skip()
), pytest completed in 0.04 seconds.
The problem is that PyTest creates a test database and performs all migrations before running tests.
I experience the same delay when using manage.py test
, and for the same reason.
(aw) $ pip install pytest-timer[termcolor]
PyTest is configured by placing a file called
pytest.ini
in the root directory of a Django webapp.
This is the one I created:
[pytest] # Django-specific unit test settings # # See https://pytest-django.readthedocs.io/en/latest/usage.html#django-debug-mode-change-how-debug-is-set # django_debug_mode = true # DJANGO_SETTINGS_MODULE = main.settings.test FAIL_INVALID_TEMPLATE_VARS = True # General Python unit test settings filterwarnings = ignore::django.utils.deprecation.RemovedInDjango40Warning python_files = tests.py test_*.py *_tests.py
PyTest fixtures can either be created by Python code (which is the subject of this article), or by
@pytest.mark.parametrize
annotation.
Fixtures for PyTest unit tests can only be created in a file called conftest.py
.
A conftest.py
file is a Python module, loaded automatically by PyTest, and fixtures defined in it are available to test modules in the same directory and below.
If unit tests for a Django app need fixtures, create a conftest.py
file in the app directory.
For a Django app called core
within a webapp called myWebApp
, the path is myWebApp/core/conftest.py
.
Fixtures are normally created when a test function requests them, by declaring them in the method signature.
By default, fixtures are destroyed when each test finishes.
The scope
parameter of the @pytest.fixture
annotation controls the fixture’s lifespan.
For example, session
scope causes fixtures to be created only once,
and to survive until all tests complete.
Session scope is declared by applying the @pytest.fixture(scope="session")
annotation to the function signature that defines the fixture.
The following code creates a fixture factory
called designer
for unit testing a Django-Oscar webapp.
The code also demonstrates how to create instances of Django-Oscar’s
Country
, Partner
and PartnerAddress
classes.
To access the database in a test fixture another special fixture called
db
must be injected.
The underdocumented db
fixture is part of the django-pytest
plugin.
When db
is injected, the
@pytest.mark.django_db
annotation is not required.
However, because db
is a function-scoped fixture, it can only be injected into other function-scoped fixtures.
This means that the @pytest.fixture
annotation must be used instead of @pytest.fixture(scope="session")
when applied to fixture definitions that inject the db
fixture.
import pytest from django.contrib.auth import get_user_model from oscar.core.loading import get_model from core.models import Design
@pytest.fixture def designer(db): user = get_user_model().objects.create( username = "testy", email = "test@ancientwarmth.com", is_staff = True, is_superuser = True, ) return Designer.objects.create( mission_statement = "Test mission", user = user )
@pytest.fixture def another_fixture(db, designer): # Do something with the injected fixtures (db and designer) # Any number of fixtures can be injected pass
The following fixtures could be helpful for testing Django-oscar
applications:
@pytest.fixture def country(db): OscarCountryMgr = get_model('address', 'Country')._default_manager OscarCountryMgr.all().delete() return OscarCountryMgr.create(iso_3166_1_a2='CA', name="Canada")
@pytest.fixture def partner(db): OscarPartnerMgr = get_model('partner', 'Partner')._default_manager OscarPartnerMgr.all().delete() return OscarPartnerMgr.create(name="Test partner")
@pytest.fixture def partner_address(country, db, partner): OscarPartnerAddressMgr = get_model('partner', 'PartnerAddress')._default_manager OscarPartnerAddressMgr.all().delete() return OscarPartnerAddressMgr.create( title = "Dr", first_name = "Soapy", last_name = "Sudsberger", country = country, postcode = "H2P 2J7", partner = partner)
PyTest fixtures can also set global state for the test suite by enabling the autouse
parameter of @pytest.fixture
.
In the following code, the name of the set_global_state
fixture is not important.
The yield
keyword causes the fixture to pause execution while tests run.
@pytest.fixture(autouse=True) def set_global_state(): """Set environment variables, and other global state""" # Set global state here yield # Tests run now # Restore global state here
The test_mission
test acccepts a fixture created by the
designer
fixture factory in core/conftest.py
.
Note that the test_mission
test does not directly access the database (the fixture does that),
so no annotation such as @pytest.mark.django_db
is required on the test.
import pytest from core.conftest import *
def test_mission(designer): assert designer.mission_statement == "Test mission"
def test_another_fixture(another_fixture): assert another_fixture != None
Django-oscar
applications can test Country
, Partner
and PartnerAddress
like this:
def test_country(country): assert country.name == "Canada" assert country.code == "CA" # assert country.is_shipping_country == True # assert country.printable_name != "" assert country.iso_3166_1_a2 == "CA" # assert country.iso_3166_1_a3 == "CAN"
def test_partner(partner): assert partner.code == 'test-partner' assert partner.display_name == 'Test partner'
order_lines = partner.order_lines assert order_lines.count() == 0 # TODO what is this?
addresses = partner.addresses assert addresses.count() == 0
stockrecords = partner.stockrecords assert stockrecords.count() == 0
users = partner.users assert users.count() == 0
def test_partner_address(partner_address): assert partner_address.city == '' assert partner_address.country_id == 'CA' assert partner_address.first_name == 'Soapy' assert partner_address.get_title_display() == 'Dr' assert partner_address.last_name == 'Sudsberger' assert partner_address.line1 == '' assert partner_address.line2 == '' assert partner_address.line3 == '' assert partner_address.line4 == '' assert partner_address.name == 'Soapy Sudsberger' assert partner_address.partner.display_name == 'Test partner' assert partner_address.postcode == 'H2P 2J7' assert partner_address.salutation == 'Dr Soapy Sudsberger' assert partner_address.state == '' assert partner_address.summary == 'Dr Soapy Sudsberger, H2P 2J7' assert partner_address.title == 'Dr'
To run all tests from the command line, simply type:
(aw) $ pytest
Specific test files can be run by specifying the test file names:
(aw) $ pytest myapp/test/test_1.py
All tests in specific directories can be run by specifying the directory names:
(aw) $ pytest myapp1 myapp2
Debugging pytest issues can be done by using the --log-cli-level
option:
(aw) $ pytest --log-cli-level=DEBUG core/tests.py
This is my VSCode launch configuration for running PyTest on all unit tests for the core
webapp:
[ { "justMyCode": true, "name": "AW PyTest", "type": "python", "request": "launch", "program": "${env:venv}/aw/bin/pytest", "python": "${env:venv}/aw/bin/python", "args": [ "core/tests.py" ], "django": true, "env": { "DJANGO_SETTINGS_MODULE": "main.settings.test" }, "presentation": { "group": "aw", "order": 6 } }, ]
Documenting custom Django apps is made convenient by using the Django admin documentation generator. Django documentation standards are defined here. These documentation standards were written for Django contributors; Django uses Sphinx for creating documentation. The Django admin documentation generator supports a subset of Sphinx-compatible reStructured text.
The Python docutils library must be installed in the virtual Python installation
before the documentation feature can be configured.
For me, that meant adding a line consisting of just had one word, docutils
, to
dev.requirements.in
, and typing:
(aw) $ pip install docutils Collecting docutils Downloading docutils-0.17.1-py2.py3-none-any.whl (575 kB) |████████████████████████████████| 575 kB 1.7 MB/s Installing collected packages: docutils Successfully installed docutils-0.17.1
Unlike most of Python, Docutils is still on SourceForge and uses Subversion instead of git. Clearly this module is stable, but it has been allowed to grow moldy for 10 years or more. The plea for help is clearly visible, and it is at revision 8631!
Beyond just needing a champion, what is really needed is proper funding, which means corporations that use Django should realize that their free ride on F/OSS is not sustainable.
I added the following to INSTALLED_APPS
and MIDDLEWARE
in settings:
INSTALLED_APPS = [ ... 'django.contrib.admindocs', ... ]
MIDDLEWARE = [ ... 'django.contrib.admindocs.middleware.XViewMiddleware', ... ]
A new route is required, just one new route for the entire Django webapp.
For my Django webapp, the new route was placed in main/urls.py
, just above the admin
route.
The documentation was not sufficiently explicit about this for me to understand exactly what was required,
so at first, I added the route to all of my Django apps.
This caused one of my apps to hijack the URL.
The correct placement of the URL is:
from django.urls import include, path, re_path
urlpatterns = [ ... path(route='admin/doc/', include('django.contrib.admindocs.urls')), re_path(route=r'^admin\/?', view=admin.site.urls), ...
Each time the Django webapp is restarted the documentation is regenerated. The process is very quick. I found it convenient to view the generated documentation for a model class, and tweak it, restarting the webapp each time that I made a significant change so I could inspect the result.
Upon reviewing the generated documentation,
I realized that I had not been providing help_text
attributes for many of my Django model field definitions.
The Django documentation generator incorporated the help_text
attributes into the generated documentation.
help_text
attributes are not HTML-escaped, so they must not contain HTML.
I was unable to find documentation on formatting or linking to model fields.
The entire Django webapp’s documentation is generated when the Django webapp starts.
After setting up, a new link called Documentation was displayed on the
admin page, and it pointed to
.
http://localhost:8000/admin/doc/
Each of the links on the main documentation page is shown in the images below.
Tags are viewable at
.
http://localhost:8000/admin/doc/tags/
Filters are viewable at
.
http://localhost:8000/admin/doc/filters/
A clickable listing of all Django models for your webapp is available at
Some generally useful Django-Oscar models to view documentation for are:
http://localhost:8000/admin/doc/models/
address.UserAddress
auth.Permission
auth.Site
auth.User
basket.Basket
basket.Line
basket.LineAttribute
catalogue.AttributeOption
catalogue.AttributeOptionGroup
catalogue.Category
catalogue.Option
catalogue.Product
catalogue.ProductAttribute
catalogue.ProductAttributeValue
catalogue.ProductClass
catalogue.ProductRecommendation
customer.ProductAlert
flatpages.Flatpage
offer.Condition
offer.ConditionalOffer
offer.Range
order.Order
partner.Partner
partner.StockAlert
partner.StockRecord
payment.Transaction
reviews.Vote
shipping.OrderAndItemCharges
voucher.Voucher
voucher.VoucherApplication
voucher.VoucherSet
voucher.VoucherSet
wishlists.Line
wishlists.WishList
Views are viewable at
.
The views are summarized on one page, with the first docstring sentence displayed.
Clicking on a view displays the entire docstring.
http://localhost:8000/admin/doc/views/
The official documentation for Django bookmarklets is skimpy and inaccurate, and all the other documentation on this feature that I was able to find is badly out of date and incomplete. The feature is completely broken, another example of Django bitrot.
This (updated) information is based on two sources, plus my own investigation:
Bookmarklets are bits of JavaScript to be dragged into your browser’s bookmarks bar, and then clicked when visiting pages on your Django-powered website.
Bookmarkets will be activated for any authenticated user who has the is_staff
flag set True
,
or if the website is served from an IP address specified in the
INTERNAL_IPS
setting.
I split my Django webapp’s settings into dev
, prod
and test
;
the INTERNAL_IPS
settings for dev
are:
INTERNAL_IPS = [ '127.0.0.1', ]
To make a bookmarklet available, drag a bookmarklet link from the admin page
(usually
)
to your web browser’s bookmarks toolbar, or right-click the link and add it to your bookmarks.
I used Firefox to create a bookmark folder called Django, and added a link for every type of bookmarklet.
The resulting bookmark folder looked like this:
http://localhost:8000/admin/doc/bookmarklets/
Now, I am supposed to be able to select bookmarklets from the non-admin pages in my webapp.
Here are my results of using each type of bookmarklet on the autogenerated documentation for my
core.Formulation
Django model class, which was displayed at
http://localhost:8000/admin/doc/models/core.formulation/
:
formulation with ID “doc/views/django.contrib.admin.options.ModelAdmin.changelist_view” doesn’t exist.
Perhaps it was deleted?
div
which did not contain useful information.
Edit this object (current window)
Edit this object (new window)
Argh, gather around, and I'll tell ye a tale of the secrets of setting Up Django email. Yon scurvey dogs tend to leave out some important information in the documentation. Even those dastardly cretins answering questions at StackOverflow won’t tell the full tale, not with all the important juicy bits.
These are the DJango settings I use to send email from one of
Rackspace EMail accounts.
Notice how I have highlighted a portion of the EMAIL_BACKEND
value (smtp
).
That is the backend
which will actually send email, instead of merely accepting the email request and logging it without actually sending anything email.
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_HOST = 'secure.emailsrvr.com'
EMAIL_HOST_PASSWORD = '****'
EMAIL_HOST_USER = 'email@domain.com'
EMAIL_PORT = 587
EMAIL_USE_TLS = True
EMAIL_USE_SSL = False
This is the underdocumented backend that will cause you untold grief if you don't know about it:
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
Unfortunately, some overly helpful but horribly misguided soul decided to make the console email backend the default. That backend does not actually send email, it is just a stub.
In an outstanding display of misguided overworking of an API, when in unit test mode, Django changes the middleware without notice. The documentation does mention this misbehavior, if you know where to look. You'll never find that information unless you know it is there.
The middleware that gets swapped in also does not actually send email. No, your code is not broken, you are caught between a poor design decision and inadequate documentation. I highlighted the magic pixie dust you need to make emails work in unit tests.
from django.test import TestCase from django.test.utils import override_settings
# Import EMAIL_BACKEND so the value can be used to override # Django's misguided email middleware switcheroo for unit tests from main.settings import EMAIL_BACKEND from thing_model.models import ThingModel
class ThingModelTests(TestCase):
# See https://stackoverflow.com/a/49909468/553865 @override_settings(EMAIL_BACKEND=EMAIL_BACKEND) def test_result_html(self): thing_model.email("blah@blah.com")
Django can send plain text email, or HTML email, or both together. Here is a simplified example of how to send information from a model class as HTML and plaintext using Django email.
from django.db import models from django.core.mail import send_mail class ThingModel(models.Model): # Define model fields here @property def as_html(self): return "<h2>Title goes here</h2>\n<p>Blah blah</p>\n" @property def as_text(self): return "Title goes here\nBlah blah\n" def email(self, email_address): # result is set to 1 for success, 0 for failure result = send_mail( subject = 'Test Email', message = self.as_text, html_message = self.as_html, from_email = 'email@domain.com', recipient_list = [ email_address ], ) print(f"Result code for sending email={result}")
Django is written in Python, so there are only two protocols that bridge Python code and web servers. The Python / httpd protocol that seems to have the best support and stability is wsgi.
The two top choices for a web server that supports wsgi are Apache httpd
and nginx.
Both Apache httpd and nginx (pronounced engine-x) are a free, open-source,
high-performance HTTP servers and reverse proxies.
I have used them before.
They are both solid.
Apache is decades older.
All the cool kids are using nginx these days.
The Django documentation does not mention nginx. At first, the lack of official support by Django for nginx concerned me somewhat, but my interactions with Django maintainers so far have shown me that there is a significant faction of those maintaners that is resistant to change of any kind.
In looking for defensible reasons for choosing one http server over the other, I found some important information:
mod_wsgi
: Multi-threaded server with all cores fully pegged the entire time.
(Highest CPU usage of all contenders benchmarked.)
mod_wsgi
.)
Kurt Griffiths wrote a terrific benchmark back in 2012. 9 years later, I wonder how results might have changed?
However, there may be a much more interesting option: hosting Django webapps as lambda functions.
Digital Ocean hosts a nice nginx / Django configuration tool that works with any server, not necessarily at Digital Ocean.
]]>(Sending email.)
Django-oscar
defines PIP dependencies with a
setting called install_requires
.
install_requires = [ 'django>=2.2,<3.2', # PIL is required for image fields, Pillow is the "friendly" PIL fork 'pillow>=6.0', # We use the ModelFormSetView from django-extra-views for the basket page 'django-extra-views>=0.13,<0.14', # Search support 'django-haystack>=3.0b1', # Treebeard is used for categories 'django-treebeard>=4.3,<4.5', # Babel is used for currency formatting 'Babel>=1.0,<3.0', # For manipulating search URLs 'purl>=0.7', # For phone number field 'phonenumbers', 'django-phonenumber-field>=3.0.0,<4.0.0', # Used for oscar.test.newfactories 'factory-boy>=2.4.1,<3.0', # Used for automatically building larger HTML tables 'django-tables2>=2.3,<2.4', # Used for manipulating form field attributes in templates (eg: add # a css class) 'django-widget-tweaks>=1.4.1', ]
According to the documentation,
pip-tools/
uses install_requires
to maintain requirements.txt
.
$ pip install pip-tools
I could not get the
install_requires
setting to work with pip-tools
.
Instead, I was able to create a file called requirements.in
to hold top-level dependencies,
and pip-tools
happily used it:
boto3==1.17.27 django django-cors-headers==3.7.0 django-extensions django-oscar>=3.0.2,<4.0.0 django_storages==1.11.1 django-grappelli==2.14.3 pip pip-tools psycopg2-binary==2.8.6 pycountry==20.7.3 python-decouple==3.4 sorl-thumbnail==12.6.3
Notice that pip-tools
is an unpinned requirement :)
With requirements.in
in place, a new requirements.txt
can be generated using the pip-compile
command provided by pip-tools
.
Here is the pip-compile
help message:
(aw) $ pip-compile -h Usage: pip-compile [OPTIONS] [SRC_FILES]... Compiles requirements.txt from requirements.in specs. Options: --version Show the version and exit. -v, --verbose Show more output -q, --quiet Give less output -n, --dry-run Only show what would happen, don't change anything -p, --pre Allow resolving to prereleases (default is not) -r, --rebuild Clear any caches upfront, rebuild from scratch -f, --find-links TEXT Look for archives in this directory or on this HTML page -i, --index-url TEXT Change index URL (defaults to https://pypi.org/simple) --extra-index-url TEXT Add additional index URL to search --cert TEXT Path to alternate CA bundle. --client-cert TEXT Path to SSL client certificate, a single file containing the private key and the certificate in PEM format. --trusted-host TEXT Mark this host as trusted, even though it does not have valid or any HTTPS. --header / --no-header Add header to generated file --emit-trusted-host / --no-emit-trusted-host Add trusted host option to generated file --annotate / --no-annotate Annotate results, indicating where dependencies come from -U, --upgrade Try to upgrade all dependencies to their latest versions -P, --upgrade-package TEXT Specify particular packages to upgrade. -o, --output-file FILENAME Output file name. Required if more than one input file is given. Will be derived from input file otherwise. --allow-unsafe / --no-allow-unsafe Pin packages considered unsafe: distribute, pip, setuptools. WARNING: Future versions of pip-tools will enable this behavior by default. Use --no- allow-unsafe to keep the old behavior. It is recommended to pass the --allow-unsafe now to adapt to the upcoming change. --generate-hashes Generate pip 8 style hashes in the resulting requirements file. --reuse-hashes / --no-reuse-hashes Improve the speed of --generate-hashes by reusing the hashes from an existing output file. --max-rounds INTEGER Maximum number of rounds before resolving the requirements aborts. --build-isolation / --no-build-isolation Enable isolation when building a modern source distribution. Build dependencies specified by PEP 518 must be already installed if build isolation is disabled. --emit-find-links / --no-emit-find-links Add the find-links option to generated file --cache-dir DIRECTORY Store the cache data in DIRECTORY. [default: /home/mslinn/.cache/pip-tools] --pip-args TEXT Arguments to pass directly to the pip command. --emit-index-url / --no-emit-index-url Add index URL to generated file -h, --help Show this message and exit.
Now I was able to update requirements.txt
from requirements.in
,
and then upgrade all PIP packages like this:
(aw) $ pip-compile -U (aw) $ pip install --upgrade -r requirements.txt
This could be written as one line.
(aw) $ pip-compile -U && \
pip install --upgrade -r requirements.txt
I wanted to take advantange of the pip-tools
layered requirements feature.
Overtop the basic dependencies listed in requirements.in
,
I also wanted to manage development dependencies in dev.requirements.in
and deployment dependencies in prod.requirements.in
.
The dev
and prod
layers are siblings.
There is no need to pin django-debug-toolbar
because it is constrained by the
django
dependency in the lower layer.
Jack Cushman, a pip-tools
contributor,
explains why the --generate-hashes
option is important.
-c requirements.txt django-debug-toolbar docutils json5 pytest-django PyYAML
(aw) $ pip-compile dev.requirements.in --generate-hashes --allow-unsafe
This produces dev.requirements.txt
:
# # This file is autogenerated by pip-compile # To update, run: # # pip-compile dev.requirements.in # asgiref==3.3.4 # via # -c requirements.txt # django django-debug-toolbar==3.2 # via # -c requirements.txt # -r dev.requirements.in django==3.1.8 # via # -c requirements.txt # django-debug-toolbar pytz==2021.1 # via # -c requirements.txt # django sqlparse==0.4.1 # via # -c requirements.txt # django # django-debug-toolbar
--allow-unsafe
seems unnecessary –
I believe that --allow-unsafe
should be the default behavior for pip-compile
.
pip-tools
considers some packages “unsafe,”
and as best I can tell it is because it was thought that pinning those packages could potentially break pip
itself,
and thus break the user's ability to recover from a mistake.
--allow-unsafe
is unsafe,
as it means different environments will end up with different versions of key packages despite installing from identical
requirements.txt
files.
I added gunicorn
as a production dependency, and was surprised to find that it declares
lists a specific version of the Pyton setuptools
as a transitive dependency.
-c requirements.txt gunicorn json5
(aw) $ pip-compile prod.requirements.in --generate-hashes --allow-unsafe
This produces prod.requirements.txt
:
# # This file is autogenerated by pip-compile # To update, run: # # pip-compile --allow-unsafe --generate-hashes prod.requirements.in # gunicorn==20.1.0 \ --hash=sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8 # via -r prod.requirements.in # The following packages are considered to be unsafe in a requirements file: setuptools==56.0.0 \ --hash=sha256:08a1c0f99455307c48690f00d5c2ac2c1ccfab04df00454fef854ec145b81302 \ --hash=sha256:7430499900e443375ba9449a9cc5d78506b801e929fef4a186496012f93683b5 # via gunicorn]]>
Django has a super-useful feature: Automatic HTML forms created for model CRUD (Create, Read, Update, Delete). The MDN Django Tutorial has a page on this feature.
Every Django model automatically maps to a database table. Programmatically, new rows can be added to the table by creating new model instances. Those instances can be edited or deleted programmatically as well.
The wonderful thing is that every Django automatally creates an HTML forms-based editor for every model.
These CRUID forms are easily to modify: their appearance can be styled and the content can be laid out easily.
Just visit the admin/
section of a Django website, for example:
.
You will see a listing of all the model classes.
If you are working with http://localhost:8000/admin
django-oscar
, those classes will also be listed,
grouped by Django app name.
When I click on one of the apps I created, called Pricing
, the browser showed all
the model classes defined at
.
Clicking on my http://localhost:8000/adminpricing/
Chemical
model class listed all the instances already defined (there were none)
at
.
When I clicked on the + Add chemical button at the top right of the screen,
I was presented with a form-based editor for all the model fields.
This is the list page for my Ancient Warmth http://localhost:8000/adminpricing/chemical/
Core
app's Chemical
model:
I took advantage of Django's controls for laying out the information on this page.
Most of the Chemical fields were listed, and I created @property
s when a foreign key pointed to something interesting.
@admin.register(models.Chemical) class ChemicalAdmin(admin.ModelAdmin): list_display = ('name', 'full_name', 'formula', 'remaining_inventory', 'retail_price_per_kg', 'recommended_max_percent', 'designs', 'bathers', 'usage_rate') ordering = ('name',) search_fields = ('name', 'formula')
list_display
ordering
search_fields
I wrote a short piece on Django WYSIWYG HTML editors yesterday. I don't know the full story yet. Seems like one of Django's potential strengths is being significantly weakened by the disarray with respect to official interfacing of at least one current WSIWYG HTML editors.
I had previously installed Grapelli,
so I expected to see the
TinyMCE WYSIWYG HTML editor
visible for the Text
fields, but that did not happen.
I looked around for documentation,
but only found a confusing set of out-of-date or poorly written instructions that could not be followed.
What a mess!
Maybe that is why other WYSIWYG editors are talked about – however,
I have not seen good installation documentation for those either yet.
The order that a model's fields appear in the web page can be controlled by defining a companion class
in the same models.py
file.
For a model class called MyModel
, the companion class is called MyModelForm
.
An inner class called Meta
defines the field order, like this:
class MyModelForm(ModelForm): class Meta: model = Chemical fields = [ 'field1', 'field2', 'field3', ]
This worked, and then the order flipped back to the order the fields were declared in models.py
.
Another mystery.
Django is a mixture of wonderfulness and neglected bits, chattering to each other,
rotting forgotten in a dank, dark corner.
Now that some data has been entered into a model's database tables, you probably want to back it up.
Django stores the data for a model class called MyModel
in an app called
MyApp
in a table called MyAppMyModel
.
I wrote the following script to back up the test data I was working with.
#!/bin/bash ./manage.py dumpdata auth.user > backup/auth_user.json APP=myApp for X in model_1_name model_2_name model_3_name ; do ./manage.py dumpdata $APP.$X > backup/$APP.$X.json done
To back up the data for a model in JSON format, use the dumpdata
subcommand of manage.py
.
This is the help message:
(aw) $ ./manage.py dumpdata --help usage: manage.py dumpdata [-h] [--format FORMAT] [--indent INDENT] [--database DATABASE] [-e EXCLUDE] [--natural-foreign] [--natural-primary] [-a] [--pks PRIMARY_KEYS] [-o OUTPUT] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks] [app_label[.ModelName] [app_label[.ModelName] ...]] Output the contents of the database as a fixture of the given format (using each model's default manager unless --all is specified). positional arguments: app_label[.ModelName] Restricts dumped data to the specified app_label or app_label.ModelName. optional arguments: -h, --help show this help message and exit --format FORMAT Specifies the output serialization format for fixtures. --indent INDENT Specifies the indent level to use when pretty-printing output. --database DATABASE Nominates a specific database to dump fixtures from. Defaults to the "default" database. -e EXCLUDE, --exclude EXCLUDE An app_label or app_label.ModelName to exclude (use multiple --exclude to exclude multiple apps/models). --natural-foreign Use natural foreign keys if they are available. --natural-primary Use natural primary keys if they are available. -a, --all Use Django's base manager to dump all models stored in the database, including those that would otherwise be filtered or modified by a custom manager. --pks PRIMARY_KEYS Only dump objects with given primary keys. Accepts a comma-separated list of keys. This option only works when you specify one model. -o OUTPUT, --output OUTPUT Specifies file to which the output is written. --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.
The dumpdata
subcommand needs the app and model field names to be specified in lower case.
For example, to extract data for all the models in the MyApp
app in JSON format, type:
(aw) $ ./manage.py dumpdata myapp
Of course, you can direct the JSON to a file.
Lets first make a backup/
directory:
(aw) $ mkdir backup (aw) $ ./manage.py dumpdata myapp > backup/myapp.json
To extract data for just the MyModel
model MyApp
app in JSON format:
(aw) $ ./manage.py dumpdata myapp.mymodel
To direct the JSON for this one field to a file:
(aw) $ ./manage.py dumpdata myapp > backup/myapp.mymodel.json
To restore the data from a backup for a model in JSON format, use the loaddata
subcommand of manage.py
.
This is the help message:
(aw) $ ./manage.py loaddata --help usage: manage.py loaddata [-h] [--database DATABASE] [--app APP_LABEL] [--ignorenonexistent] [-e EXCLUDE] [--format FORMAT] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [--skip-checks] fixture [fixture ...] Installs the named fixture(s) in the database. positional arguments: fixture Fixture labels. optional arguments: -h, --help show this help message and exit --database DATABASE Nominates a specific database to load fixtures into. Defaults to the "default" database. --app APP_LABEL Only look for fixtures in the specified app. --ignorenonexistent, -i Ignores entries in the serialized data for fields that do not currently exist on the model. -e EXCLUDE, --exclude EXCLUDE An app_label or app_label.ModelName to exclude. Can be used multiple times. --format FORMAT Format of serialized data when reading from stdin. --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.
The loaddata
subcommand just needs the name of the backup file to read.
(aw) $ ./manage.py loaddata backup/myapp.json
I wrote this script to restore from the JSON backups. Seems I am scrubbing and recreating the database every few hours right now.
#!/bin/bash ./manage.py dumpdata auth.user > backup/auth_user.json APP=myApp for X in model_1_name model_2_name model_3_name ; do ./manage.py dumpdata $APP.$X > backup/$APP.$X.json done for X in model_1_name model_2_name model_3_name; do FN="backup/$APP.$X.json" if [ -f "$FN" ]; then ./manage.py loaddata "backup/$APP.$X.json" fi done
Django-oscar
3.0.2 uses v5.6.2 TinyMCE as a WSIWYG HTML Editor.
TinyMCE is used in the django-oscar
dashboard for all textarea elements with the class wysiwyg
.
For example, the Admin Emails Template Editor is powered by TinyMCE.
TinyMCE has successfully been used with Django and django-oscar
for years and
the combination is well understood.
Django-summernote
seems to have been getting more attention lately as a resource for Django apps,
instead of reusing the TinyMCE arftifacts already provided by the Django admin apps.
Is there a significant reason to
use Summernote django-oscar
custom code?
I do not know the answer.
Below is all I know at this point.
Django-oscar.readthedocs.io
shows where TinyMCE is used by django-oscar
.
Directly examining the django-oscar
git repository shows more specific information:
(aw) $ git grep wysiwyg # django-oscar GitHub project docs/source/releases/v0.6.rst:the class ``wysiwyg``. This has better browser support and is easier to docs/source/releases/v0.6.rst: *Textarea with class ``wysiwyg`` now use TinyMCE.* src/oscar/forms/widgets.py: kwargs['attrs']['class'] += ' wysiwyg' src/oscar/static_src/oscar/js/oscar/dashboard.js: $textareas.filter('form.wysiwyg textarea').tinymce(o.dashboard.options.tinyConfig); src/oscar/static_src/oscar/js/oscar/dashboard.js: $textareas.filter('.wysiwyg').tinymce(o.dashboard.options.tinyConfig); src/oscar/templates/oscar/dashboard/catalogue/attribute_option_group_form.html: <form class="form-stacked wysiwyg fixed-actions" method="post" data-behaviour="tab-nav-errors"> src/oscar/templates/oscar/dashboard/catalogue/category_form.html: <form action="{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}" method="post" class="form-stacked wysiwyg fixed-actions" enctype="multipart/form-data" data-behaviour="tab-nav-errors" autocomplete="off"> src/oscar/templates/oscar/dashboard/catalogue/option_form.html: <form class="form-stacked wysiwyg fixed-actions" method="post" data-behaviour="tab-nav-errors"> src/oscar/templates/oscar/dashboard/catalogue/product_class_form.html: <form class="form-stacked wysiwyg fixed-actions" method="post" data-behaviour="tab-nav-errors"> src/oscar/templates/oscar/dashboard/catalogue/product_update.html: <form action="{% if request.GET.urlencode %}?{{ request.GET.urlencode }}{% endif %}" method="post" class="form-stacked wysiwyg fixed-actions" enctype="multipart/form-data" data-behaviour="tab-nav-errors" autocomplete="off"> src/oscar/templates/oscar/dashboard/offers/step_form.html: <form method="post" class="form-stacked wysiwyg fixed-actions"> src/oscar/templates/oscar/dashboard/pages/update.html:<form method="post" class="card card-body form-stacked wysiwyg" enctype="multipart/form-data"> src/oscar/templates/oscar/dashboard/partners/partner_user_form.html: <form method="post" class="card card-body form-stacked wysiwyg" enctype="multipart/form-data"> src/oscar/templates/oscar/dashboard/ranges/range_form.html: <form method="post" class="form-stacked card card-body bg-light wysiwyg"> src/oscar/templates/oscar/dashboard/shipping/weight_based_form.html: <form method="post" class="form-stacked card card-body bg-light wysiwyg">
Git-grep
shows that the django-oscar
dashboard apps that use TinyMCE are:
catalogue
, offers
, pages
, partners
, ranges
, and shipping
.
(aw) $ git grep tinymce docs/source/releases/v0.6.rst:.. figure:: screenshots/0.6/tinymce.png docs/source/releases/v0.6.rst:.. _`TinyMCE 4.0`: http://www.tinymce.com/ docs/source/releases/v2.0.rst:- Upgraded ``tinymce`` to version 4.8.3. docs/source/releases/v2.1.rst:- Upgraded ``tinymce`` to version 5.3. docs/source/releases/v3.0.rst:- Upgraded ``tinymce`` to version 5.6. gulpfile.js/subtasks/copy.js: "node_modules/tinymce/**/*.min.js", gulpfile.js/subtasks/copy.js: "node_modules/tinymce/**/*.min.css", gulpfile.js/subtasks/copy.js: "node_modules/tinymce/**/fonts/*", gulpfile.js/subtasks/copy.js: "node_modules/tinymce/**/img/*", gulpfile.js/subtasks/copy.js: ]).pipe(gulp.dest("src/oscar/static/oscar/js/tinymce")); package-lock.json: "tinymce": { package-lock.json: "resolved": "https://registry.npmjs.org/tinymce/-/tinymce-5.6.2.tgz", package.json: "tinymce": "^5.6.2" src/oscar/static_src/oscar/js/oscar/dashboard.js: $textareas.filter('form.wysiwyg textarea').tinymce(o.dashboard.options.tinyConfig); src/oscar/static_src/oscar/js/oscar/dashboard.js: $textareas.filter('.wysiwyg').tinymce(o.dashboard.options.tinyConfig); src/oscar/templates/oscar/dashboard/layout.html: <script src="{% static "oscar/js/tinymce/tinymce.min.js" %}"></script> src/oscar/templates/oscar/dashboard/layout.html: <script src="{% static "oscar/js/tinymce/jquery.tinymce.min.js" %}"></script>
This article provides:
django-oscar
unit tests.manage.py test
subcommand.django-oscar
webapp.unittest
module
built-in to the Python standard library.
Tox
,
a generic virtualenv management and test command line tool.
django-rest-framework
unit test documentation.Django-oscar-api testcheckout.py
is a good example of unit tests.django-oscar-bluelight
unit tests.django-oscar
unit test documentation
only discusses how to run django-oscar
’s internal unit tests.
This information is useless to others who want to write unit tests for their own django-oscar
project.
In particular, this documentation focuces on the framework used by django-oscar
,
pytest
, which is less popular than unittest
.
DEBUG
setting in your configuration file,
all Django tests run with DEBUG=False
.
This is to ensure that the observed output of your code matches what will be seen in a production setting.
Here is the help information:
(aw) $ ./manage.py test -h usage: manage.py test [-h] [--noinput] [--failfast] [--testrunner TESTRUNNER] [-t TOP_LEVEL] [-p PATTERN] [--keepdb] [-r] [--debug-mode] [-d] [--parallel [N]] [--tag TAGS] [--exclude-tag EXCLUDE_TAGS] [--pdb] [-b] [-k TEST_NAME_PATTERNS] [--version] [-v {0,1,2,3}] [--settings SETTINGS] [--pythonpath PYTHONPATH] [--traceback] [--no-color] [--force-color] [test_label [test_label ...]]
Discover and run tests in the specified modules or the current directory.
positional arguments: test_label Module paths to test; can be modulename, modulename.TestCase or modulename.TestCase.test_method
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. --failfast Tells Django to stop running the test suite after first failed test. --testrunner TESTRUNNER Tells Django to use specified test runner class instead of the one specified by the TEST_RUNNER setting. -t TOP_LEVEL, --top-level-directory TOP_LEVEL Top level of project for unittest discovery. -p PATTERN, --pattern PATTERN The test matching pattern. Defaults to test*.py. --keepdb Preserves the test DB between runs. -r, --reverse Reverses test cases order. --debug-mode Sets settings.DEBUG to True. -d, --debug-sql Prints logged SQL queries on failure. --parallel [N] Run tests using up to N parallel processes. --tag TAGS Run only tests with the specified tag. Can be used multiple times. --exclude-tag EXCLUDE_TAGS Do not run tests with the specified tag. Can be used multiple times. --pdb Runs a debugger (pdb, or ipdb if installed) on error or failure. -b, --buffer Discard output from passing tests. -k TEST_NAME_PATTERNS Only run test methods and classes that match the pattern or substring. Can be used multiple times. Same as unittest -k option. --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.
You can read about the manage.py
test subcommand options.
Running tests for the first time takes a while to create the test database.
The test framework looks up the name of the production database from Django settings and prepends test_
to the name, then creates the database.
My production database was defined in settings/base.py
as ancient_warmth
,
so the test database was automatically called test_ancient_warmth
.
You can launch tests from the command line like this:
(aw) $ ./manage.py test --noinput
Creating test database for alias 'default'...
System check identified no issues (0 silenced).
... test output might occur here...
Ran 1 test in 40.325s
OK
Destroying test database for alias 'default'...
The test database is not deleted after the unit tests run. The next time you run tests, you will see this message:
Got an error creating the test database: database "test_ancient_warmth" already exists
Type 'yes' if you would like to try deleting the test database 'test_ancient_warmth', or 'no' to cancel:
The --noinput
option shown on the command line above automatically answers yes
to
manage.py
prompts.
Alternatively, providing the --keepdb
option preserves the test database and its contents between tests.
Below are 3 Microsoft Visual Studio Code launch configurations.
The file containing the definitions of the launch configurations (.vscode/launch.json
) should be placed in the top-level directory of your Django webapp.
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"justMyCode": false,
"name": "Ancient Warmth / Django-Oscar",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"python": "${env:oscar}/bin/python",
"args": [
"runserver",
"--noreload",
"0.0.0.0:8001",
],
"django": true
},
{
"justMyCode": true,
"name": "Ancient Warmth tests / keep DB",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"python": "${env:oscar}/bin/python",
"args": [
"test",
"--keepdb",
],
"django": true
},
{
"justMyCode": true,
"name": "Ancient Warmth tests / recreate DB",
"type": "python",
"request": "launch",
"program": "${workspaceFolder}/manage.py",
"python": "${env:oscar}/bin/python",
"args": [
"test",
"--noinput"
],
"django": true
},
]
}
django-oscar
that interest me,
including supporting projects.
I discuss unit testing information sources here.
Django-oscar
source code from Sphinx, and
GitHub project.
This is a listing of the PIP modules that I am interested in.
Many of the django-oscar
modules are shown on the
Django-oscar
GitHub page,
but these links are to the PyPi pages, not directly to the GitHub projects.
Better-exceptions
,
pretty and useful exceptions in Python. Here is sample output; notice the value and type of variables is shown (yay!)
(aw) $ bin/run Traceback (most recent call last): File "/var/work/django/oscar/lib/python3.8/site-packages/django/apps/config.py", line 156, in create app_module = import_module(app_name) │ └ 'aw_order' └ <function import_module at 0x7f858f387550> File "/usr/lib/python3.8/importlib/__init__.py", line 127, in import_module return _bootstrap._gcd_import(name[level:], package, level) │ │ │ │ └ 0 │ │ │ └ None │ │ └ 0 │ └ 'aw_order' └ <module 'importlib._bootstrap' (frozen)> File "<frozen importlib._bootstrap>", line 1014, in _gcd_import File "<frozen importlib._bootstrap>", line 991, in _find_and_load File "<frozen importlib._bootstrap>", line 973, in _find_and_load_unlocked ModuleNotFoundError: No module named 'aw_order' During handling of the above exception, another exception occurred: Traceback (most recent call last): File "./manage.py", line 22, in <module> main() └ <function main at 0x7f858f488430> File "./manage.py", line 18, in main execute_from_command_line(sys.argv) │ └ <module 'sys' (built-in)> └ <function execute_from_command_line at 0x7f858e999820> File "/var/work/django/oscar/lib/python3.8/site-packages/django/core/management/__init__.py", line 401, in execute_from_command_line utility.execute() └ <django.core.management.ManagementUtility object at 0x7f858f1ac8e0> File "/var/work/django/oscar/lib/python3.8/site-packages/django/core/management/__init__.py", line 377, in execute django.setup() └ <module 'django' from '/var/work/django/oscar/lib/python3.8/site-packages/django/__init__.py'> File "/var/work/django/oscar/lib/python3.8/site-packages/django/__init__.py", line 24, in setup apps.populate(settings.INSTALLED_APPS) │ └ <LazySettings "main.settings.dev"> └ <django.apps.registry.Apps object at 0x7f858f0f6520> File "/var/work/django/oscar/lib/python3.8/site-packages/django/apps/registry.py", line 91, in populate app_config = AppConfig.create(entry) │ └ 'order.apps.OrderConfig' └ <class 'django.apps.config.AppConfig'> File "/var/work/django/oscar/lib/python3.8/site-packages/django/apps/config.py", line 158, in create raise ImproperlyConfigured( django.core.exceptions.ImproperlyConfigured: Cannot import 'aw_order'. Check that 'order.apps.OrderConfig.name' is correct.
Cookiecutter-django
,
a popular git project for learning and working with Django, and
a companion book called
Two Scoops of Django.
Django-allauth
,
an integrated set of Django applications addressing authentication, registration,
account management and 3rd-party social account authentication.
Django-cacheback
,
smart caching with asynchronous refresh for Django.
Django-grappelli
, a jazzy skin for the Django admin interface.
While it installed easily, it is decidedly not jazzy.
Django-invitations
,
a generic invitations solution with adaptable backend and support for django-allauth.
All emails and messages are customisable.
Django-oscar-accounts
,
managed double-entry accounts for Django. Can also be used without Oscar.
Django-oscar-api
,
a RESTful API for django-oscar
.
Django-oscar-bluelight
,
enhancements and improvements to django-oscar
offers and vouchers features.
Django-oscar-promotions
,
an app for dashboard-editable promotional content in django-oscar
.
GitHub project (PyPi link is broken.)
This project was formerly part of the django-oscar
core,
but was been separated into a standalone app.
Django-oscar-stores
provides physical store support.
Features include a store locator page using Google Maps, store detail pages including opening hours, store grups and a dashboard for messaging stores.
Django-oscar-invoices
.
Very little documentation. PR for Django 4.x not accepted after 6 weeks.
Django-pipeline
, an asset packaging library for Django.
Django-simple-menu
, code-based menus for Django applications.
Django-summernote
integrates the
Summernote WYSIWYG HTML editor with Django.
Django-tinymce
,
v5.6 of this WYSIWYG HTML editor is bundled with django-oscar
3.0.
Django-waffle
, a feature flipper for Django.
Pigar
, generate requirements for a Python project.
I had a problem when attempting to use pigar
on a Django project.
Pip-tools
,
command-line tools to keep pip packages fresh, even when they are pinned.
Python-twitter
, a Python wrapper around the Twitter API.
Django-helcim
support for Django, includes an optional module to connect with Django Oscar.
GitHub project.
Django-paypal
,
Django support for PayPal Payments Standard and Payments Pro.
Django-oscar-paypal
,
PayPal integration for django-oscar
.
Can also be used without django-oscar
.
django-oscar
presentations on PyVideo.org
.
django-book.readthedocs.io
I found the sections entitled
Templates and
Advanced Templates helpful.
All of the installation and getting started pages of the online version are hopelessly out of date, however.
The sections on paths and routing requests is also badly out of date and not worth the time to read.