Benutzer:WonderBlood/Bücher/Python und Django/Django
Kurze Geschichte von Django
Django entstand ursprünglich als Framework für eine Online-Zeitung. Bei der Entwicklung waren hauptsächlich zwei Dinge wichtig: Deadlines einzuhalten und die hohen Anforderungen von Webentwicklern zu erfüllen. So entstand ein einfach zu benutzendes Framework, mit dem sich in kürzester Zeit Webseiten erstellen lassen, welches einem aber trotzdem große Flexibilität bietet.
Da Django vor vielen anderen Frameworks entstand - und somit auch den Ruby on Rails-Hype verpasst hat - sind viele Dinge anders/ungewohnt, aber nicht unbedingt schlecht. Das geht beim Templatesystem los, welches entgegen aktuellen Trends nicht auf XML basiert (und das aus gutem Grund, mehr dazu später) und endet beim eigens dafür entwickelten Objektrelationale Abbildung (ORM).
Los geht's: Das Projekt erstellen
Um grob zu sehen, wie Django aufgebaut ist wird ein einfaches Beispiel dienen. Hierbei wird vorausgesetzt, dass Django bereits installiert ist und ein Datenbankserver vorhanden ist. Im ersten Schritt muss am Anfang ein Projekt gestartet werden, dazu einfach in der Shell (Hinweis: Unter Windows funktioniert die Eingabeaufforderung genauso gut) mit django-admin.py startproject testproject das Projekt testproject starten. Hierbei wird ein Verzeichnis testproject im aktuellen Pfad angelegt und initialisiert. Folgende Dateien werden hierbei erstellt:
- __init__.py: Markiert das Verzeichnis als Python-Modul, hier testproject
- manage.py: Script zur Verwaltung des Projekts
- urls.py: Konfiguration der URLs
- settings.py: Konfiguration, beispielsweise Zugangsdaten zur Datenbank
Mit python manage.py runserver kann nun schon ein Entwicklungsserver gestartet werden, der sogar schon eine Webseite ausliefert. Der Webserver ist über http://localhost:8000/ erreichbar. Ist dieser Port vergeben kann über python manage.py runserver PORT ein anderer Port verwendet werden. Der Server kann mit Str+C beendet werden.
Um Django später verwenden zu können muss die Datenbankkonfiguration in der Datei settings.py vorgenommen werden. Die nötigen Werte sind:
- DATABASE_ENGINE - Je nach Datenbank: ‘postgresql_psycopg2’, ‘mysql’ oder ‘sqlite3’. Andere Datenbankbackends sind in der Django-Dokumentation gelistet.
- DATABASE_NAME - Datenbankname oder absoluter Pfad zur SQLite-Datei (nur bei SQLite)
- DATABASE_USER - Datenbankuser (nicht nötig bei SQLite)
- DATABASE_PASSWORD - Datenbankpasswort (nicht nötig bei SQLite)
- DATABASE_HOST - Datenbankserver, ist der Server lokal so kann dieser Wert leer gelassen werden (nicht nötig bei SQLite)
Anwendungen/Applications
Damit Django etwas interessanteres macht, als nur die langweilige Standardseite anzuzeigen, muss nun eine Anwendung gestartet werden.
- Ein Django-Projekt ist in verschiedene Anwendungen (Applications) aufgeteilt. Diese können im Idealfall zwischen Projekten ausgetauscht werden.
- webmasterpro.de wäre hierbei ein Projekt, das Artikelsystem sowie die News sind einzelne Anwendungen. Wenn ein Webmasterpro-Admin nun eine neue Seite startet könnte er das Newssystem hier auch verwenden, da es relativ unabhängig vom Projekt ist.
Eine Anwendung wird mit dem Befehl python manage.py startapp sample gestartet, hierbei wird ein Unterverzeichnis sample erstellt, das folgende Dateien beinhaltet:
- __init__.py: Markiert das Verzeichnis als Python-Modul, hier testproject.sample
- models.py: Datenbank-Modell/Datenobjekte für das ORM
- views.py: View-Funktionen zur Anzeige der Objekte
Damit Django die neu erstellte Anwendung auch benutzt muss sie in der Konfiguration eingetragen sein. Hierzu wird einfach testproject.sample in der Datei settings.py unter INSTALLED_APPS hinzugefügt. Im selben Schritt können alle vorhandenen Anwendungen auskommentiert werden, da sie für das Beispiel nicht nötig sind (aber auch nicht schaden).
Das Datenbank-Modell
Nun soll ein einfaches Datenbank-Modell erstellt werden, als Beispiel wird ein sehr einfaches Newssystem erstellt. Folgendes DB-Modell wird dazu in die Datei sample/models.py geschrieben:
News Datenbank-Modell
from django.db import models
from datetime import datetime
class NewsItem(models.Model):
title = models.CharField(maxlength=255)
text = models.TextField()
pub_date = models.DateTimeField(default=datetime.now)
def __str__(self):
return self.title
Die Datenbankfelder sollten relativ eindeutig sein, alle möglichen Datenbankfelder sind in der Dokumentation gelistet. __str__() wird verwendet um eine String-Repräsentation des Objekts zu generieren/abzurufen. In Zukunft sollte stattdessen aber __unicode__() verwendet werden, da Django alle Strings auf Unicode umgestellt hat. Bisher ist diese Änderung aber nur im SVN.
Damit dieses Modell in die Datenbank geschrieben wird muss Django die Datenbank abgleichen. Der Befehl python manage.py syncdb erledigt das. Django wird nun die Tabelle sample_newsitem anlegen, der genaue verwendete Befehl kann mit python manage.py sql sample ausgegeben werden. Wird SQLite verwendet sieht das beispielsweise so aus:
Tabelle sample_newsitem, generiertes SQL von Django
BEGIN;
CREATE TABLE "sample_newsitem" (
"id" integer NOT NULL PRIMARY KEY,
"title" varchar(255) NOT NULL,
"text" text NOT NULL,
"pub_date" datetime NOT NULL
)
;
COMMIT;
Python-Shell zum Testen
Nun kann direkt am Datenmodell gearbeitet werden, als erstes werden einige Daten eingetragen. Für den Anfang eignet sich am Besten die Shell, später bietet sich der mitgelieferte Admin oder selbst geschrieben Views an. Die Shell lässt sich mit python manage.py shell starten. Nun können einfach NewsItem-Objekte erstellt und gespeichert werden, wie es von normalen Python-Objekten gewohnt ist. Um Objekte in die Datenbank zu speichern muss save() aufgerufen werden.
Erstellen von zwei Beispieleinträgen
>>> # Python-Import von NewsItem
>>> from testproject.sample.models import NewsItem
>>> # Erstes Objekt erstellen
>>> n1 = NewsItem(title='Test1', text='Nur ein Test')
>>> print n1.id # keine ID bisher
None
>>> # Datum wurde mit Standardwert gefüllt
>>> print n1.pub_date
2007-12-05 11:26:43.684673
>>> n1.save() # Speichern von n1
>>> print n1.id # Nun hat n1 eine ID
1
>>> n2 = NewsItem(title='Test2', text='Noch einer')
>>> n2.save()
>>>
Es lassen sich auf der Shell zwar die Inhalte der Datenbank schön bearbeiten und verwalten, eine Webseite macht das aber noch lange nicht aus. Als nächsten Schritt muss deswegen eine URL-Konfiguration erstellt werden die eine Adresse einem View zuordnet. Für unser Beispiel reicht eine einfache URL-Konfiguration, die folgendes enthält:
URL-Konfiguration von testproject
from django.conf.urls.defaults import *
urlpatterns = patterns('',
(r'^$', 'testproject.views.helloworld'),
(r'^about/$', 'testproject.views.about'),
(r'^news/', include('testproject.sample.urls')),
)
Die Konfiguration verwendet für die Adresse / den View testproject.views.helloworld, für about/ den View testproject.views.about und bei Zugriff auf news/ wird die URL-Konfiguration unter testproject.sample.urls verwendet. Die URL-Konfiguration für die News befindet sich also in einer separaten Datei, was eine Anwendung sehr modular macht.
Der Aufbau der Konfigurationsdatei ist denkbar einfach, die Adressen sind in einer Liste organisiert, die - bis auf den ersten Eintrag, aber der wird erst später erklärt - aus Tupeln mit zwei (oder drei) Einträgen besteht. Der erste Wert im Tupel ist ein Regex, der auf die aufgerufene Adresse passen muss, damit der im zweiten Wert angegebene Views aufgerufen wird. Passt ein Regex auf die Adresse so wird der Views verwendet und die Suche abgebrochen. Als dritter Wert im Tupel sind Zusatzparameter für den Views möglich, die in einem Dictionary angegeben werden.
Als erster Schritt zum Verständnis der Views und des Templatesystems soll die Helloworld-Seite gefüllt werden, die ohne Template-System oder Datenbankzugriff auskommt. Hierzu muss eine Datei views.py im Projektpfad erstellt werden. Jeder View in Django ist eine einfache Funktion, die als ersten Parameter die Anfrage als Objekt bekommt (request). Weitere Parameter können von der URL-Konfiguration abhängig aus der URL extrahiert werden, dazu aber später mehr.
Auch der Rückgabewert des Views ist ein Objekt (response), hierfür gibt es aber verschiedene Grundobjekte, wie beispielsweise auch welche für HTTP-Redirects. Der Views selbst entscheidet was er macht um den Rückgabewert zu erzeugen, hierbei kann er das Templatesystem nutzen, ist aber nicht dazu gezwungen.
Django verwendet kein klassisches MVC-Pattern. In der Dokumentation wird von einem MTV-Pattern gesprochen. Der View übernimmt hierbei die Aufgabe der Rechteprüfung, das Abrufen der Objekte und rendert dann das Template mit bestimmten Parametern.
Die Controller-Aufgaben wie die Rechteprüfung können natürlich in eigene Funktionen gekapselt werden oder über Python-Decorator vereinfacht werden, Django selbst bietet hierzu aber keine Hilfsmittel.
Der erste View: helloworld()
In der views.py müssen also zwei Funktionen definiert werden: helloworld() und about(). Als ersten wird helloworld() erstellt, da hier hier kein Templatesystem zum Einsatz kommen soll:
"views.py" mit helloworld()
from django.http import HttpResponse
def helloworld(request):
return HttpResponse('hello world')
Um den View zu testen muss der Testserver mit python manage.py runserver wieder gestartet werden. Da der Server Änderungen automatisch neu lädt kann er bei allen folgenden Änderungen einfach laufen gelassen werden. Wird http://localhost:8000/ aufgerufen erscheint wie erwartet hello world. Das ist leider aber immer noch nicht viel spannender als die Standardseite von Django.
about() unter Verwendung von Templates
Für die About-Seite soll nun das Templatesystem verwendet werden. Hierzu wird ein Verzeichnis templates/ im Projektpfad angelegt und der absolute Pfad des Verzeichnisses zu TEMPLATE_DIRS in der settings.py hinzugefügt:
Tipp: Mit os.path kann man den absoluten Pfad dynamisch generieren.
import os.path
TEMPLATE_DIRS = (
os.path.join(os.path.dirname(__file__), 'templates'),
)
Grundlegendes zu Templates
Alle Templates in Django sind normale Textdateien. Das hat den Vorteil, dass sie einfach zu schreiben sind und sich problemlos auch für Emails oder zum Generieren von dynamischem Javascript verwenden lassen.
In den Templates gibt es zwei grundlegende Elemente:
- Ausgabe von Variablen mit {{ variablenname }}
- Aufruf eines Template-Tags für komplexere Dinge mit
{% tagname ... %}
, zum Beispiel:{% if variable %}...{% endif %}
Filter
Zur Ausgabe von Variablen können Filter verwendet werden, die die Variable vorverarbeiten. Soll die Variable HTML-maskiert ausgegeben werden ist beispielsweise der escape-Filter hilfreich:
{{ variable|escape }}
Anmerkung: Die SVN-Version von Django maskiert automatisch jegliche ausgegebenen Variablen. Der escape-Filter wird also in naher Zukunft nicht mehr zwingend nötig sein. Stattdessen müssen dann Variablen die nicht maskiert werden sollen mit dem safe-Filter markiert werden oder Autoescaping mit dem autoescape-Tag deaktiviert werden.
Außerdem ist der Zugriff auf Untervariablen möglich, ist var beispielsweise ein Dictionary kann auf var['index'] zugegriffen werden indem die einzelnen Namen durch einen Punkt getrennt sind, also beispielsweise:
{{ var.index }}
Der Zugriff per Punkt versucht der Reihe nach zuerst den Zugriff als Dictionary, dann normalen Attributzugriff, danach den Methodenaufruf und als letztes den Zugriff als Listen-Index.
Filter können beliebig kombiniert werden, außerdem können sie über filtername:"parameter" Parameter übergeben bekommen. So beispielsweise beim default-Filter:
{{ var|default:"nicht gesetzt" }}
Eine Liste aller Filter ist in der offiziellen Dokumentation einsehbar.
Template-Tags
Templatetags können Parameter erhalten, oft sind hier auch Variablen möglich (siehe if-Tag oben). Je nach Tag gibt es einen optionalen schließenden Tag, der meist dem Tagnamen mit vorangestelltem end entspricht.
Die wichtigsten Tags für den Anfang sind:
{% if var %}...{% else %} ...{% endif %}
: Bedingung, else is optional{% for var in list %}...{% endfor %}
: Schleife, Iteration über list{% block name %}...{% endblock %}
: Definition von Blöcken, siehe unten{% load modul %}
: Laden von Modulen für zusätzliche Tags/Filter, können auch selbst geschrieben werden{% extends "template.html" %}
: Template von Basistemplate ableiten, siehe unten{% include "template.html" %}
: Anderes Template einbinden
Auch eine Liste aller Tags ist in der Dokumentation zu finden. Blöcke und abgeleitete Templates
Templates in Django können in Blöcke aufgeteilt werden. Hat eine Seite ein Menü oder einen Inhaltsbereich können diese als Template-Block angelegt werden. Blöcke können in abgeleiteten Templates überschrieben oder erweitert werden. Existiert ein Basistemplate base.html kann in einem Template sub.html, welches am Anfang der Datei {% extends "base.html" %}
verwendet, alle Blöcke ersetzen/erweitern, indem sie in sub.html mit selbem Namen nochmal geschrieben werden. Der Inhalt des Blocks vom Elterntemplate kann mit
{{ block.super }}
ausgegeben werden.
Auch hier ist es möglich beliebig tiefe Ableitungsketten zu bauen. In abgeleiteten Templates können außerdem neue Blöcke definiert werden, um in weiter abgeleiteten Templates genauer aufgeteilte Bereiche der Seite zu füllen.
Eine genaue Dokumentation zu Blöcken und abgeleiteten Templates findet sich auf der Django-Homepage.
Die Templates für about()
Für about() soll bereits ein Basistemplate erstellt werden, welches für alle späteren Views verwendet wird. Hier definieren wir den grundlegenden Aufbau der Seite mit allen möglichen Bereichen die später gefüllt werden. Dieses Basistemplate wird unter templates/base.html gespeichert:
Einfaches Basistemplate "base.html"
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="de" lang="de">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{% block title %}Startseite{% endblock %} - Testprojekt</title>
</head>
<body>
<ul id="menu">
<li><a href="/">Start</a></li>
<li><a href="/about/">About</a></li>
<li><a href="/news/">News</a></li>
</ul>
<div id="content">
{% block content %}{% endblock %}
</div>
</body>
Im Template für about() kann dieses Template nun erweitert werden, hierzu wird eine Datei about.html angelegt:
Template für about(), "about.html"
{% extends "base.html" %}
{% block title %}About{% endblock %}
{% block content %}
<h1>About</h1>
<p>
Hier gehts nur über "testproject".
</p>
{% endblock %}
Damit die Seite auch angezeigt wird fehlt jetzt nur noch der nötige View, dieser wird in die views.py im Projektpfad eingefügt.
View für about()
from django.shortcuts import render_to_response
def about(request):
return render_to_response('about.html')
In obigem Code wird gleich eine Abkürzung verwendet um das Template zu rendern. Es ginge aber auch weitaus aufwändiger, manuell. Bei Benutzung von E-Mail-Templates beispielsweise ist der manuelle Weg allerdings der geeignetere.
Beim Aufruf von http://localhost:8000/about/ sollte nun eine sehr einfache Seite erscheinen, die inzwischen aber eine vollständige HTML-Seite ist und - für den Besucher natürlich nicht sichtbar - aus einem Template erzeugt wurde, bzw. sogar aus zwei Templates unter Verwendung von Blöcken.
News ausgeben
URL-Konfiguration
Um die News ausgeben zu können muss auch für die News eine URL-Konfiguration erstellt werden. Oben wurde include()
verwendet um diese zu laden, der Verweis auf die nötige Datei ist also schon vorhanden und der Aufruf von news/ würde schon jetzt versuchen diese Konfiguration zu laden (und mit einem Fehler abbrechen). Für das Beispiel sollten folgende einfache Adressen reichen:
news/
: Auflistend er letzten 10 Newsnews/{id}/
: Anzeige einer News
Die zugehörige URL-Konfiguration "sample/urls.py"
from django.conf.urls.defaults import *
urlpatterns = patterns('testproject.sample.views',
(r'^$', 'list_news'),
(r'^(?P<id>\d+)/$', 'show_newsitem'),
)
Als erstes sollte auffallen, dass news/ nirgends auftaucht. Dieser Teil wurde bereits von der darüberliegenden URL-Konfiguration entfernt (gefressen). Diesmal ist außerdem der erste Parameter für die URL-Pattern gefüllt, er gibt den Suchpfad für alle angegebenen Views an. Aus index im ersten Tupel wird also dann der View testproject.sample.views.index
.
Im zweiten Tupel wird eine Regex-Gruppe definiert, die mit dem Namen id versehen ist. Diese Gruppe wird dem View später als Parameter übergeben. Der Name ist optional, mit Namen wird allerdings ein benannter Parameter verwendet (view(param1) vs. view(paramname=param1)), wodurch die Reihenfolge der Parameter vertauscht werden kann.
Die Views
Die Views für die News müssen mehrere Dinge machen: Objekt(e) abrufen (hierbei Parameter beachten) und ein Template rendern, welches die nötigen Variablen als Parameter enthält. Hierzu werden folgende Funktionen verwendet:
Views für die News
from django.shortcuts import render_to_response
# DB-Modell importieren
from testproject.sample.models import NewsItem
def list_news(request):
# News nach pub_date (rückwärts) sortiert,
# auf 10 Einträge limitiert
news = NewsItem.objects.order_by('-pub_date')[:10]
return render_to_response('list_news.html', {'news': news})
def show_newsitem(request, id):
# Ein NewsItem abrufen, "pk" entspricht
# immer dem primären Schlüssel
newsitem = NewsItem.objects.get(pk=id)
return render_to_response('show_newsitem.html', {'newsitem': newsitem})
Die generierten Objekte/Listen werden dem Template als Dictionary übergeben (zweiter Parameter von render_to_response()).
DB-API
Außerdem werden hier einige Dinge zur Datenbank-API sichtbar:
NewsItem.objects
bietet Zugriff auf die Objekte (allgemein ModelObject.objects)order_by()
wird verwendet um die Ergebnisse zu sortierenLIMIT(/OFFSET)
kann über normale Python-Slice-Operatoren erreicht werden- Mit
get()
wird ein einzelnes Objekt abgerufen
Jede Funktion, die eine Liste erstellt (wie order_by()
) gibt hierbei ein QuerySet-Objekt zurück. Dieses Objekt speichert nur den Query selbst, führt ihn aber nicht aus, bis ein Zugriff auf die Daten erfolgt. Jedes QuerySet kann erweitert werden, so ist beispielsweise folgendes möglich:
NewsItem.objects.filter(title__icontains='foo')...
...exclude(pk=1).order_by('-pub_date')[:10]
Hier wird title__icontains verwendet, was im SQL WHERE title ILIKE '%foo%' entsprechen würde.
Die genaue API für den Zugriff ist in der Dokumentation nachzulesen, genauso wie alle möglichen Konditionen für filter()/exclude().
Templates
Die nötigen Templates sind nun schnell geschrieben. Für die News-Liste kann der bereits erwähnte for-Tag ({% for ... %}) verwendet werden. Ansonsten reicht einfache Variablenausgabe.
Template "list_news.html"
{% extends "base.html" %}
{% block title %}Newsliste{% endblock %}
{% block content %}
<h1>Newsliste</h1>
<ul>
{% for newsitem in news %}
<li><a href="{{ newsitem.id }}/">{{ newsitem.title|escape }}</a></li>
{% endfor %}
</ul>
{% endblock %}
Um die Adresse für ein NewsItem zu erzeugen wird hierbei einfach
{{ newsitem.id }}/
verwendet. Django bietet mehrere Möglichkeiten um URLs zu erzeugen/zentral zu definieren, wie beispielsweise die Methode get_absolute_url() bei einem Datenbank-Modell oder den url-Templatetag. Für dieses Beispiel reicht diese direkte Methode, bei großen Projekten kann das aber schnell sehr unübersichtlich werden. Für den Titel könnte auch
{{ newsitem|escape }}
verwendet werden, da eine __str__()-Methode definiert wurde (siehe oben).
Template "show_newsitem.html"
{% extends "base.html" %}
{% block title %}{{ newsitem.title|escape }}{% endblock %}
{% block content %}
<h1>{{ newsitem.title|escape }}</h1>
<p>
{{ newsitem.text|escape|urlize }}
</p>
{% endblock %}
Quelle
http://www.webmasterpro.de/coding/article/framework-einfuehrung-in-django.html