Django Lessons Learned: Projekt- und App-Verzeichnisstruktur
Es gibt viele gute Vorschläge und Ideen, wie man ein Django-Projekt anlegen und verwenden sollte. Die in diesem Artikel beschriebene Struktur hat sich bei mir so entwickelt und inzwischen verwende ich sie bei allen Projekten.
Grundstruktur
Es beginnt schon beim Anlegen der Grundstruktur mit den entsprechenden Django-Helferchen startproject
und startapp
. Normalerweise macht man folgendes beim Start eines neuen Projekts:
$ django-admin startproject meinprojekt
$ cd meinprojekt
$ python manage.py startapp meineapp
Daraus resultiert diese Verzeichnisstruktur:
.
└── meinprojekt
├── manage.py
├── meineapp
│ ├── admin.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Ich bevorzuge jedoch eine flachere Struktur ohne das zusätzliche Projekt-Verzeichnis. Um das zu erreichen, muss man beim Anlegen des Projekts so vorgehen (man beachte den Punkt am Ende der ersten Zeile):
$ django-admin startproject meinprojekt .
$ python manage.py startapp meineapp
Das Resultat ist nun diese Struktur:
.
├── manage.py
├── meineapp
│ ├── admin.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models.py
│ ├── tests.py
│ └── views.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Mehrere Python-Dateien für Views, Tests und den Admin-Bereich
Im Bereich der App (meineapp
) gibt es standardmäßig jeweils nur eine Datei für Views (views.py
), Tests (tests.py
), Modelle (models.py
), und den Admin-Bereich (admin.py
). Mit wachsendem Projekt merkt man schnell, dass diese Struktur unpraktisch wird. So habe ich gerne für diese und andere Einsatzzwecke ganze Pakete. Hier erzeuge ich einfach gleichnamige Packages und lösche die ursprünglichen Python-Files. Das sieht dann so aus:
.
├── manage.py
├── meineapp
│ ├── admin
│ │ └── __init__.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ └── __init__.py
│ ├── tests
│ │ └── __init__.py
│ └── views
│ └── __init__.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Für Views und Tests funktioniert das einfach so. Views werden sowieso in der urls.py
importiert. Ob da nun noch ein Package- und ein Modulname davor steht oder nicht, macht in der Praxis keinen Unterschied. Minimal komplizierter stellt sich das für den Admin-Bereich und die Models dar. Hier gibt es Logik im Django-Framework, die darauf angewiesen ist, unter meineapp.models
und meineapp.admin
etwas vorzufinden. Damit das funktioniert, muss man in der jeweiligen __init__.py
die jeweiligen Klassen importieren.
Am Beispiel eines (sinnfreien) Modells würde das so aussehen:
# meineapp/models/files.py
from django.db import models
import uuid
class FileModel(models.Model):
uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)
file_name = models.CharField(blank=False, null=False, max_length=255)
Hier erkennt man auch schon meine Benennungsvorgabe. Ein Modellklassenname endet immer auf Model
, der passende Dateiname ist der Überbegriff für die Modelle in dieser Datei Kleinbuchstaben. So würde diese Datei alle Modelle enthalten, die etwas mit »Files« zu tun haben. Jeder Überbegriff erhält seine eigene Datei. Obwohl es mehrere Module gibt, achte ich darauf, dass Modul-Namen eindeutig und einmalig sind.
Entsprechend wird die FileModel
-Klasse aus dem file
-Modul dann in der models/__init__.py
importiert:
# meineapp/models/__init__.py
from meineapp.models.file import FileModel
Der Verzeichnisbaum sieht damit dann so aus:
.
├── manage.py
├── meineapp
│ ├── admin
│ │ └── __init__.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ ├── __init__.py
│ │ └── files.py
│ ├── tests
│ │ └── __init__.py
│ └── views
│ └── __init__.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Genau so wird dann für Admin-Klassen vorgegangen. Meine Benennungsregeln hier: Modulnamen entsprechen den Modulnamen der Models, die Klassennamen bestehen aus dem Modellklassennamen plus dem Suffix »Admin«:
# meineapp/admin/files.py
from django.contrib import admin
from meineapp.models.file import FileModel
class FileModelAdmin(admin.ModelAdmin):
list_display = (
'file_name',
'uuid',
)
search_fields = (
'file_name',
)
ordering = (
'file_name',
)
fieldsets = (
('File', {
'fields': (
'file_name',
)
}),
)
admin.site.register(FileModel, FileModelAdmin)
Auch bei den Admin-Klassen achte ich wieder auf eindeutige und einmalige Namen. Wieder erfolgt der entsprechende Import in der __init__.py
für das Package meineapp.admin
:
# meineapp/admin/__init__.py
from meineapp.admin.file import FileModelAdmin
Nach diesem Schritt sieht die Verzeichnisstruktur nun so aus:
.
├── manage.py
├── meineapp
│ ├── admin
│ │ ├── files.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ ├── files.py
│ │ └── __init__.py
│ ├── tests
│ │ └── __init__.py
│ └── views
│ └── __init__.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Weitere Verzeichnisse
Neben den standardmäßig vorhandenen Modulen, die nun schon in Pakete umgewandelt wurden, benötigt man im Laufe der Entwicklung noch weitere Pakete und Verzeichnisse.
Formulare
Für Formulare erzeuge ich unterhalb von meineapp
ein Package forms
, darin die Packages frontend
und backend
:
.
├── manage.py
├── meineapp
│ ├── admin
│ │ ├── files.py
│ │ └── __init__.py
│ ├── forms
│ │ ├── backend
│ │ │ └── __init__.py
│ │ ├── frontend
│ │ │ └── __init__.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ ├── files.py
│ │ └── __init__.py
│ ├── tests
│ │ └── __init__.py
│ └── views
│ └── __init__.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Das frontend
-Package nutze ich nun für alle Formulardefinitionen und -implementierungen, die ich im Frontend (also der »Benutzerseite«) verwenden möchte. Das backend
-Package enthält Formulardefinitionen für den Admin-Bereich. Für die Benennung von Modulen und Klassen gelten die gleichen Regeln, wie für Modelle. Für ein Formular, dass direkt mit einem Modell korrespondiert, wird als Klassenname der Modellname mit dem Suffix »Form« verwendet. Am Beispiel des bereits besprochenen FileModel
s könnte eine Formulardefinition für den Admin-Bereich so aussehen:
# meineapp/forms/backend/files.py
from django import forms
from meineapps.models.files import FileModel
class FileModelForm(forms.ModelForm):
class Meta:
model = FileModel
fields = [
'file_name'
]
Für die Verwendung des Formulars in der meineapp/admin/files.py
genügt ein regulärer Import:
# meineapp/admin/files.py
from meineapp.forms.backend.files import FileModelForm
Zur Verdeutlichung: Die Verzeichnisstruktur sieht nun so aus:
.
├── manage.py
├── meineapp
│ ├── admin
│ │ ├── files.py
│ │ └── __init__.py
│ ├── forms
│ │ ├── backend
│ │ │ ├── files.py
│ │ │ └── __init__.py
│ │ ├── frontend
│ │ │ └── __init__.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ ├── files.py
│ │ └── __init__.py
│ ├── tests
│ │ └── __init__.py
│ └── views
│ └── __init__.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Datenbank-Fixtures
Datenbank-Fixtures gehören wie Modelle zu einer App. Wenn ich -- und das ist bisher eigentlich immer der Fall gewesen -- nur eine App habe, dann packe ich die Fixtures auch direkt zu dieser App, in diesem Beispiel meineapp
. Wenn ich mehrere Apps habe, dann bietet sich an, ein globales Fixture-Verzeichnis unter meinproject
zu haben.
Hier wird also unter meineapp
ein Verzeichnis fixtures
erzeugt. Dieses Verzeichnis muss kein Python-Package sein und kommt daher ohne __init__.py
aus:
.
├── manage.py
├── meineapp
│ ├── admin
│ │ ├── files.py
│ │ └── __init__.py
│ ├── fixtures
│ ├── forms
│ │ ├── backend
│ │ │ ├── files.py
│ │ │ └── __init__.py
│ │ ├── frontend
│ │ │ └── __init__.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ ├── files.py
│ │ └── __init__.py
│ ├── tests
│ │ └── __init__.py
│ └── views
│ └── __init__.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Damit Django das nun auch weiß, ergänze ich folgendes in der settings.py
:
FIXTURE_DIRS = (
os.path.join(BASE_DIR, 'meineapp', 'fixtures'),
)
Nun kann ich Fixtures abspeichern...
$ python manage.py dumpdata meineapp > meineapp/fixtures/meineapp.json
... und auch wieder laden:
$ python manage.py loaddata meineapp
Statische Dateien
Neben all den Python-Dateien gibt es noch eine Reihe weiterer Dateien, die zu einem Webapplikationsprojekt gehören, z. B. Grafik-, JavaScript- und CSS-Dateien. Aus Sicht des Django-Frameworks sind das »statische Dateien«. Wie bei den Fixtures gilt auch hier für mich in den allermeisten Fällen: Die statischen Dateien sind zur App zugehörig. Also erzeuge ich unter der App einen Ordner static
. Auch dieser Ordner muss kein Python-Paket sein, daher auch keine __init__.py
. Das führt zu dieser Verzeichnisstruktur:
.
├── manage.py
├── meineapp
│ ├── admin
│ │ ├── files.py
│ │ └── __init__.py
│ ├── fixtures
│ ├── forms
│ │ ├── backend
│ │ │ ├── files.py
│ │ │ └── __init__.py
│ │ ├── frontend
│ │ │ └── __init__.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ ├── files.py
│ │ └── __init__.py
│ ├── static
│ ├── tests
│ │ └── __init__.py
│ └── views
│ └── __init__.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Das beste daran: Django erwartet im Ordner meineapp/static
statische Dateien. Es ist also keinerlei Konfiguration erforderlich.
Templates
Auch Templates gehören in aller Regel zur App. So erstelle ich einen Ordner templates
unter meineapp
. Dieser Ordner ist ebenfalls kein Python-Package, also wieder keine __init__.py
. Die Verzeichnisstruktur sieht damit nun so aus:
.
├── manage.py
├── meineapp
│ ├── admin
│ │ ├── files.py
│ │ └── __init__.py
│ ├── fixtures
│ ├── forms
│ │ ├── backend
│ │ │ ├── files.py
│ │ │ └── __init__.py
│ │ ├── frontend
│ │ │ └── __init__.py
│ │ └── __init__.py
│ ├── __init__.py
│ ├── migrations
│ │ └── __init__.py
│ ├── models
│ │ ├── files.py
│ │ └── __init__.py
│ ├── static
│ ├── templates
│ ├── tests
│ │ └── __init__.py
│ └── views
│ └── __init__.py
└── meinprojekt
├── __init__.py
├── settings.py
├── urls.py
└── wsgi.py
Allerdings sucht Django nicht von selbst in den Verzeichnissen von Apps nach Templates. Dies muss man über die settings.py
zunächst in der Template-Konfiguration am Schlüssel APP_DIRS
aktivieren:
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.template.context_processors.i18n',
'django.template.context_processors.media',
'django.template.context_processors.static',
'django.template.context_processors.tz',
'django.contrib.messages.context_processors.messages',
],
},
},
]
Schlussbemerkung
Es gibt hunderte korrekte und gültige Möglichkeiten, Django-Projekte anzulegen und zu strukturieren. Dieser Artikel stellt meine persönliche Lieblingsaufteilung dar und hat sicherlich nicht den Anspruch, alleine gültig oder das Allheilmittel zu sein. Ich freue mich aber sehr über Feedback und andere Ansichten.
Autor:
Jürgen Edelbluth
Themen:
Software-Entwicklung Django
Veröffentlicht:
13.06.2015 15:22
Zuletzt aktualisiert:
13.03.2020 16:57