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 FileModels 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.


Kommentare

Noch keine Kommentare.