On Github accavdar / DjangoBestPractices
This presentation is prepared based on the great book Two Scoops of Django: Best Practices For Django 1.5 by Daniel Greenfeld and Audrey Roy
The Order:
Don't do this:
Do this:
Don't do this:
The reason for this is to avoid implicitly loading all of another Python module’s locals into and over our current module’s namespace, which can produce unpredictable and sometimes catastrophic results.
A common developer pitfall is using SQLite3 for local development and PostgreSQL (or another database besides SQLite3) in production.
They may not behave identical in different environments.
django-admin.py startproject --template=https://github.com/twoscoops/django-twoscoops-project/zipball/master --extension=py,rst,html $project-name
Whatever layout is chosen should be documented clearly
Each app should be tightly focused on its task
If an app can’t be explained in a single sentence of moderate length, or you need to say 'and' more than once, it probably means the app is too big and should be broken up
Try and keep your apps small. Remember, it’s better to have many small apps than to have a few giant apps.
Let’s break up development, staging, test, and production settings into separate components that inherit from a common base file all tracked by version control
You’ll also want to have a ci.py module containing that server’s settings.
python manage.py runserver --settings=udemy.settings.local
base.txt
local.txt
for development env
for production env
Don't do this:
Do this:
Do this:
No Model Inheritance if models have a common field, give both models that field
Abstract base classes tables are only created for derived models
Multi-table inheritance tables are created for both parent and child. An implied OneToOneField links parent and child
Proxy Models a table is only created for the original model
Multi-table inheritance, sometimes called "concrete inheritance" is considered by the authors and many other developers to be a bad thing. We strongly recommend against using it
Every time we use the Django ORM to query a model, we are using an interface called a model manager to interact with the database
Django provides a default model manager for each model class, but we can define our own
PS: It uses braces lib
Every HTML form that alters data must submit its data via the POST method
The only exception you’ll ever see to using POST in forms is with search forms, which typically submit queries that don’t result in any alteration of data.
When you call form.is_valid(), a lot of things happen behind the scenes. The following things occur according to this workflow:
Once you dig into forms, keep yourself focused on clarity of code and testability
Use django-rest-framework
REST API views should go into views.py modules and follow the same guidelines we endorse when it comes to any other view
The same goes for app or model specific serializers and renderers.
It’s a good idea to try to keep as much logic as possible out of API views
How do you build a project-wide API that looks like this?
When building a project-wide API we write the REST views in the views.py modules, wire them into a URLConf called something like core/api.py or core/apiv1.py and include that from the project root’s urls.py module
Find simple, elegant ways to put more of your business logic into Python code rather than into templates
Complex template hierarchies make it exceedingly difficult to debug, modify, and extend HTML pages and tie in CSS styles
When you have large, multi-line chunks of the same or very similar code in separate templates, refactoring that code into reusable blocks will make your code more maintainable.
Don't do this:
Do this:
Do this:
Don't do this:
Use select_related() method
Do this:
AbstractBaseUser is the bare-bones option with only 3 fields: password, last_login, and is_active
Choose this option if:
Depending on the needs of a project, they can either continue with the current way of doing things or customize the actual user model
Use coverage.py and django-discover-runner
The first thing we do is delete the default but useless tests.py module that django-admin.py startapp creates
In its place, we create a tests directory and place an __init__.py file in it so it becomes a valid Python module. Then, inside the new tests module, because most apps need them, we create test_forms.py, test_models.py, test_views.py modules. Tests that apply to forms go into test_forms.py, model tests go into test_models.py, and so on
It’s critically important that we always prefix test modules with test_, otherwise we can’t configure django-discover-runner to discover just our test files.
A single test should assert the behavior of a single view, model, form, method or function
Be absolutely minimalistic when constructing the environment
Tests should be written as simply as possible. If the code in a test or called to help run a test feels complicated or abstracted, then you have a problem
Don’t Repeat Yourself Doesn’t Apply to Writing Tests
Views, Models, Forms, Validators, Filters, Signals, Template Tags, ...
For medium and large projects, we recommend setting up a continuous integration (CI) server to run the project’s test suite whenever code is committed and pushed to the project repo
Try to get test coverage as high as possible. Every work day we increase our test coverage is a victory, and every day the coverage goes down is a loss
Create a settings/test.py module and add the following
The game has a single rule:
If you’re using PostgreSQL, you can use EXPLAIN ANALYZE to get an extremely detailed query plan and analysis of any raw SQL query. The MySQL equivalent is the EXPLAIN command, which isn’t as detailed but is still helpful
Simply setting up Django’s built-in caching system with Memcached or Redis
Additional Features of 3rd party packages:
Django provides tools for you: GZipMiddleware and the {% spaceless %} template tag. However, compression and minification take up system resources, which can create bottlenecks of their own
A better approach is to use Apache and Nginx web servers configured to compress the outgoing content
Upstream caches such as Varnish are very useful. They run in front of your web server and speed up web page or content serving significantly
Content Delivery Networks (CDNs) like Akamai and Amazon Cloudfront serve static media such as images, video, CSS, and JavaScript files
Change your SSH port and disable/remove unnecessary services
Django has lots of security features. Know how to configure them. Django security features include:
Your production site should not be running in DEBUG mode
Keep Secret Keys Out With Environment Variables
When you enable HSTS, your site’s web pages include a HTTP header that tells HSTS-compliant browsers to only connect to the site via secure connections:
Your site’s cookies should also be served over HTTPS. You’ll need to set the following in your settings:
You must set ALLOWED_HOSTS in your settings to a list of allowed host/domain names. This is a security measure to prevent use of fake HTTP Host headers to submit requests
Django comes with Cross-site Request Forgery Protection (CSRF) built in
You should use Django’s CsrfViewMiddleware as blanket protection across your site rather than manually decorating views with csrf_protect
Django by default escapes a lot of specific characters meaning most attacks fail
When using ModelForms, always use Meta.fields. Never use Meta.exclude. The use of Meta.exclude is considered a grave security risk. We can’t stress this strongly enough. Don’t do it
The Django ORM generates properly-escaped SQL which will protect your site from users attempting to execute malignant, arbitrary SQL code
When using raw SQL, be especially careful to escape your SQL code properly
We recommend using third-party services like Stripe, Balanced Payments, PayPal, and others that handle storing this information for you
Since the Django admin gives your site admins special powers that ordinary users don’t have, it’s good practice to make it extra secure
Check your web servers’ access and error logs regularly. Install monitoring tools and check on them frequently
You should always update your projects to work with the latest stable release of Django. This is particularly important when a release includes security fixes
It’s a good idea to publish information on your site about how users can report security vulnerabilities to you
LOG Levels: DEBUG, INFO, WARNING, ERROR, and CRITICAL
In your production environment, we recommend using every log level except for DEBUG
Ex: if your code relies on an internal web service being available, and if that web service is part of your site’s core functionality, then you might log at the CRITICAL level anytime that the web service is inaccessible
We recommend that you use the ERROR log level whenever you need to log an error that is worthy of being emailed to you or your site admins
This level is good for logging events that are unusual and potentially bad, but not as bad as ERROR-level events
Ex: if you are using django-admin-honeypot to set up a fake admin/ login form, you might want to log intruders' login attempts to this level
Whenever you use logging in another module, don’t import and reuse a logger from elsewhere. Instead, define a new logger specific to the module like this
A common way to set up log rotation is to use the UNIX logrotate utility with logging.handlers.WatchedFileHandler
Our way of handling our utilities is to place them into a Django app called core that contains modules which contains functions and objects for use across a project
Django has a number of useful helper functions that don’t have a better home than the django.utils package
his is a set of localized template filters designed to give user presented data a more 'human' touch
When you need to accept content from users and want to strip out a list of tags, this function removes those tags for you while keeping all other content untouched
When you need to accept content from users and have to strip out anything that could be HTML, this function removes those tags for you while keeping all the existing text between tags
Whatever you do, don’t write your own version of the slugify() function; as any inconsistency from what Django does with this function will cause subtle yet nasty problems. Instead, use the same function that Django uses and slugify() consistently
When you use Django’s time zone support, date and time information is stored in the database uniformly in UTC format and converted to local time zones as needed
Much of the non-English speaking world appreciates use of this tool, as it provides Django’s i18n support
You should deploy your Django projects with WSGI
Most common setup: