When you create a new app, Django creates models.py
, views.py
, and tests.py
files for your code.
Putting all your models, views, or tests in the same file is usually
not a good idea, especially on a team project with many people working
on different features.
A better way is to divide your code into multiple files.
For example, if we separated tests into individual files the
structure might be:
polls/
__init__.py
apps.py
models.py
urls.py
views.py
tests/
__init__.py
test_question.py
test_choice.py
test_views.py
Similarly for models and views.
- Divide App Code into Multiple Files shows the structure
- Reorganize Models how to refactor models into a
models
package - Reorganize Tests how to refactor tests
Cookiecutter Templates Project and Cookiecutter Django have more ideas on project setup and organization.
Divide App Code into Multiple Files
Here is an example for the ‘polls’ app:
BEFORE AFTER
polls/ polls/
__init__.py __init__.py
apps.py apps.py
forms.py forms.py
models.py models/
__init__.py
question.py
choice.py
vote.py
tests.py tests/
__init__.py
test_question.py
test_choice.py
test_vote.py
test_views.py
views.py views/ (*)
index.py
detail.py
results.py
(*)
dividing views may not be needed if the file is small.
To the rest of your application, the code looks the same!
When you finish, you’ll still be able to use from models import Question
exactly as in the original code. Similarly for views and tests.
Divide Models into Separate Files
Using the Django polls app as an example
from django.db import models
class Question(models.Model):
# code for Question class
...
class Choice(models.Model):
# code for Choice class
...
-
In the
polls
app, create a subdirectory namedmodels
. This will be a Python “package”. - Separate the code in
models.py
into one file for each class, such asmodels/question.py
,models/choice.py
,models/vote.py
.polls/ models/ __init__.py question.py choice.py vote.py
- To make the models package look like the original models module, you need some code in
__init__.py
. This is critical to making the change transparent to your other code and to Django migrations.# in models/__init__.py from .question import Question from .choice import Choice from .vote import Vote
If there is anything else in
models
that other parts of your code need to import, add those imports to__init__.py
as well. - Modify Imports in the Model Classes.
Inside of choice.py, you need a relative import for other models:
from .question import Question
and similarly for any other imports. Add imports to each model file.
- Run your tests. Do they still pass?
Optional: Meta-class Information for Database Table Names
This Meta
class information is not necessary in Django 3.x and newer, but it used to be required. Its included for reference (may be useful in other situations).
This [wiki page][splitting-model-data] states that Django code has been modified to automatically detect when models are in a package. When I tried this in 2020 (Django 3.x), the Meta class info was not necessary. For Django 2.2, the Meta class was needed.
The issue was:
Django creates a table for each model class using a table name in the form app_classname
, such as polls_question
and polls_choice
. When the model class is in a subfolder, it would infer the app name incorrectly and misname the tables.
To preserve the default table names,
you would add a class Meta
inside each model class with 2 values:
db_table
= name of the database tableapp_label
= name of the “app” for this table
# File: polls/models/question.py
from django.db import models
class Question(models.Model):
question_text = models.CharField(max_length=80)
...
class Meta:
"""
See https://code.djangoproject.com/wiki/CookBookSplitModelsToFiles
"""
db_table = "polls_question"
app_label = "polls"
Similarly for other models.
Divide Views and Tests
Dividing views is just like dividing models, but you can combine several view functions in one file if you prefer. The real benefit is separate views for different “features” so your team can work on features in branches without merge conflicts.
Separating tests is easy! You can use an empty __init__.py
file in the tests/
folder, because nothing else needs to import tests.
You should name test files using Django’s naming convention, so they will be autodiscovered. For example:
manage.py
polls/
tests/
__init__.py (this file can be empty)
test_question.py
test_voting.py
test_views.py
views/
__init__.py (import views into this file)
index_view.py
details.py
results.py
Reorganize Tests
1. Put your tests in a folder named tests
In your “app” directory create a folder named tests
.
For the Django Polls tutorial, the folder would be polls/tests
.
In the tests folder create an empty __init__.py
file so that folder is a package (Python doesn’t require this but unittest discovery does).
2. Move tests.py
to the tests
folder
3. Divide your tests based on target or type
Separate tests.py
into test_question.py
(tests for Question),
test_views.py
, etc.
Since Choice is closely tied to Question, its OK to test both Question
and Choice in one file.
You may also divide tests based on type of behavior you want to test, such as test_authorization.py
.
The Django test runner looks for files named test_something.py
by default. It should automatically discover your test files.
For the tests you wrote in the Polls tutorial, divide the tests into separate files based on what is tested:
polls/
tests/
__init__.py - this can be an empty file
test_question.py - tests of Question model
test_choice.py - tests of Choice model
test_views.py - tests of views
and then delete tests.py
or rename it so it won’t be run, e.g. ‘old-tests.py’.
4. Modify imports in test files
Since the tests are now in their own package, you will need to modify the imports in your tests.
Old Import:
# OLD: this won't work
from .models import Question, Choice, Vote
New Import:
# NEW: import from named package
from polls.models import Question, Choice, Vote
5. Run the Tests and Verify They Work
Run manage.py test app_name
and verify that it discovers all tests, e.g.
python manage.py test -v 2 polls
The -v 2
is optional; it displays one line for each test method run.
Reference
Cookbook - Splitting Models across multiple files from the Django code wiki. (2011)