/
niemand.leermann@thomas-guettler.de
Django Webframework
Dieser Vortrag ist veraltet: Die neue Version
1 Einführung
[toc]
1.1 Wie kam ich zu Django?
[toc]
Vorher: 1997: CGI mit Shellscript, '98: PHP, '99: Perl, '00: Java
(Cocoon, JServe/Tomcat), '01: Python+Zope, '02: Python+ZODB+Quixote.
Keine bisherige Lösung hat mir 100% gefallen.
2007: Für neues Projekt habe ich Ruby on Rails, Turbo Gears
und Django verglichen und mich für Letzteres entschieden.
1.2 Überblick
[toc]
Allgemein:
- Gut dokumentiert
- Stabil durch umfangreiche Unittests
- Aktive User- und Entwicklerszene.
Details:
- URL zu Funktion/Template Mapping (reguläre Ausdrücke)
- Webserver Anbindung: mod_wsgi, SCGI, FastCGI, ...
- Programmiersprache: Python
- Volle Unicode Unterstützung
- OR-Mapping (Objektrelationales Mapping) (*)
- Forms Bibliothek (*)
- Templatesprache (*)
- Admin-Interface (*)
- Session-Verwaltung (Cookies) (*)
(*): optional
1.3 Darstellung: Shell / Datei
[toc]
Bei diesem Vortrag wird folgende Konvention eingehalten:
# Shell / Interaktiv
nutzer@rechner:~/verzeichnis$ befehl
# Datei: /pfad/zur/datei
...
2 Schritt für Schritt
[toc]
Django SVN Trunk ist stabil: http://www.djangoproject.com/download/
2.1 Projekt anlegen
[toc]
# PYTHONPATH ggf. anpassen
tilldu@r51:~$ django/bin/django-admin.py startproject mysite
--> mysite/
__init__.py
manage.py
settings.py # Konfiguration
urls.py # URL zu Funktion/Template Mapping
2.2 Anwendung (Application) anlegen
[toc]
# Ein Projekt besteht aus N Anwendungen
# Projekt ('mysite') Anwendungen: Admin-Interface, Quiz, Blog, Wiki, ...
tilldu@r51:~$ cd mysite
tilldu@r51:~/mysite$ python manage.py startapp quiz
--> quiz/
__init__.py
models.py
views.py
2.3 Hallo Welt!
[toc]
Django lässt sich auch ohne Datenbank, ohne Admin-Interface und ohne Templates verwenden:
#Datei mysite/urls.py: (URLs des gesamten Projekts)
...
'^quiz/', include('quiz.urls') # Ähnlich dem Unix mount-Befehl
...
#Datei mysite/quiz/urls.py: (URLs dieser Applikation)
from django.conf.urls.defaults import patterns
urlpatterns = patterns('',
# URL http://.../quiz/
# quiz=Verzeichnis views=Datei hallo=Funktion
(r'^$', 'quiz.views.hallo')
)
#Datei mysite/quiz/views.py
import django
def hallo(request):
return django.http.HttpResponse("<html><body>Hallo Welt!</body></html>")
2.4 Test Server
[toc]
tilldu@r51:~/mysite$ manage.py runserver (Testserver, http://localhost:8000/)
tilldu@r51:~/mysite$ firefox http://localhost:8000/quiz/
2.5 Datenbank Anbindung
[toc]
2.5.1 Datenbank anlegen
[toc]
# Postgres (SQLite, MySQL oder Oracle)
# Wenn Postgresnutzer dem Unixnutzernamen entspricht,
# ist kein Passwort nötig.
postgres@r51:~$ createuser tilldu
Soll die neue Rolle ein Superuser sein? (j/n) n
Soll die neue Rolle Datenbanken erzeugen dürfen? (j/n) j
Soll die neue Rolle weitere neue Rollen erzeugen dürfen? (j/n) n
tilldu@r51:~$ createdb -E unicode mysite
CREATE DATABASE
2.5.2 settings.py
[toc]
tilldu@r51:~/mysite$ $EDITOR settings.py # Projektweite Einstellungen
--> Datenbankverbindung konfigurieren
2.5.3 OR-Mapping
[toc]
Beim Objektrelationalem Mapping (Siehe auch: Wikipedia
OR-Mapping) werden die Objekte des Programms in einer relationalen
Datenbank gespeichert.
- Vorteile:
- Der Programmierer braucht keine SELECT/INSERT/UPDATE/DELETE Anweisungen zu schreiben.
- Das Quoting wird von der Bibliothek vorgenommen.
- Die Datentypen der Datenbank, werden automatisch zu den Datentypen der Programmiersprache gewandelt. (z.B. zu datetime.date)
- Einfache, schnelle Softwareentwicklung.
- Nachteile:
- Es werden nicht alle Möglichkeiten des verwendeten SQL-Dialekts abgebildet.
- Meist langsamer in der Ausführung
# mysite/quiz/models.py
from django.db import models
class Question(models.Model):
text=models.CharField(max_length=1024)
answer=models.CharField(max_length=64)
explanation=models.CharField(max_length=1024, blank=True)
class Admin:
pass
def __unicode__(self):
return u'#%s %s' % (self.id, self.text)
Klasse --> Tabelle
Attribut --> Spalte
1:N Beziehung --> ForeignKey(OtherClass)
N:M Beziehung --> ManyToManyField(OtherClass)
Klassen für Attribute/Spalten: AutoField, BooleanField, CharField,
DateField, DecimalField, FileField, IntegerField ...
Siehe Django Doku: Creating Models
Tabellen erzeugen:
tilldu@r51:~/mysite$ python manage.py syncdb
Mit der SQL-Shell die angelegten Tabellen anzeigen:
psql (Postgres Datenbankshell)
\d --> Tabellen anzeigen (appname_klassenname)
\d quiz_question (Anzeigen der Spalten)
2.6 DB-API
[toc]
2.6.1 Objekte erstellen
[toc]
tilldu@r51:~/mysite$ python manage shell
from quiz.models import Question
question=Question(text="...", answer="...")
question.save() # --> INSERT
2.6.2 Objekte verändern
[toc]
question.explanation="..."
question.save() # --> UPDATE
2.6.3 Objekte holen
[toc]
# Auf eine bestimmte Frage zugreifen
Question.objects.get(id="...") # Zugriff über die *Klasse*
# Alle Fragen
Question.objects.all()
# SELECT ... WHERE text ILIKE '%foo%'
# Siehe Django Doku: Field Lookups
Question.objects.filter(text__icontains="foo")
# Anzahl der Fragen: SELECT COUNT(*)
Question.objects.filter(...).count()
SQL JOIN
# Datei mysite/quiz/models.py:
class Group(models.Model):
name=models.CharField(max_length=32)
class Question(models.Model):
...
group=ForeignKey(Group, related_name="questions") # 1:N Beziehung
# API Aufruf
Question.objects.filter(group__name="Geographie") # SELECT ... JOIN
# entspricht
Groups.objects.get(group="Geographie").questions
2.6.4 Ausblick
[toc]
-
Die DB-API hat bisher alle meine Anforderungen erfüllt. Sollte das
jedoch einmal nicht ausreichen kann man mit
django.db.connection
eigene SQL-Aufrufe ausführen. Oder
eigene WHERE Bedingungen mit extra()
hinzufügen.
- Sicherheit: SQL-Injektion
ist nicht möglich: Alle Datenbankanfragen über die DB-ABI werden
entsprechend gequoted. (Außer direkt über
django.db.connection
)
- Siehe Django Doku: DB-API
2.7 Admin Interface
[toc]
# Datei mysite/settings.py:
INSTALLED_APPS = ... 'django.contrib.admin', ...
tilldu@r51:~/mysite$ python manage.py syncdb (Tabellen werden erzeugt)
tilldu@r51:~/mysite$ $EDITOR urls.py (Admin URLs einbinden)
2.8 Land in Sicht (Views)
[toc]
# Datei mysite/quiz/urls.py:
...
# URL http://.../quiz/ask/2/
(r'^ask/(?P<question_id>\d+)/$', 'quiz.views.ask'),
(r'^random/$', 'quiz.views.rand')
...
# Datei mysite/quiz/views.py:
def ask(request, question_id):
question=Question.objects.get(id=question_id)
if request.method=="POST":
# Jemand versucht die Frage zu lösen.
post=request.POST
else:
# Das Formular wird zum ersten Mal aufgerufen.
post=None
form=QuestionForm(question, post)
...
def rand(request):
count=Question.objects.all().count() # SELECT count(*) FROM quiz_question
i=random.randint(0, count-1)
question=Question.objects.all()[i] # SELECT cols... FROM ... LIMIT 1 OFFSET N
# Anzeigen der bisherigen SQL-Anweisungen
#assert False, django.db.connection.queries
return django.http.HttpResponseRedirect("/quiz/ask/%d/" % question.id)
3 Formularbibliothek (newforms)
[toc]
3.1 Allgemein
[toc]
Siehe Django Doku: Newforms
3.2 Beispiel
[toc]
class QuestionForm(forms.ModelForm):
class Meta:
model=Question
def clean_answer(self):
answer=self.cleaned_data["answer"]
if self.instance.answer!=answer:
raise forms.ValidationError("Die Antwort ist falsch. Richtig: %s" %
self.question.answer)
3.3 Newforms Tipps
[toc]
-
Redirect after POST (Bei allen Webanwendungen sinnvoll):
from django import newforms as forms
from myapp.models import Question
class QuestionForm(forms.ModelForm):
class Meta:
model=Question
def edit(request, question_id):
if question_id!='add':
question=Question.objects.get(id=question_id)
else:
question=None
if request.method=="POST":
post=request.POST
else:
post=None
form=QuestionForm(post, instance=question)
if not post is None:
if form.is_valid():
question=form.save()
return django.http.HttpResponseRedirect(question.get_absolute_url())
...
return django.http.HttpResponse(...)
- Mittels 'prefix' lassen sich mit einer Seite mehrere Instanzen in einem Formular bearbeiten:
# Auf einer Seite alle Fragen einer Kategorie bearbeiten:
form_list=[]
for question in Question.objects.filter(kategorie=...).order_by('id'):
form_list.append(QuestionForm(post, instance=question, prefix='question%d' % question.id))
...
# Django Template
{% for form in form_list %} {{form}} {% endfor %}
- Formulare die mit ModelForm erstellt wurden können angepasst
werden, in dem der Konstruktor implementiert wird:
class MyForm(forms.ModelForm):
class Meta:
model=MyModel
def __init__(self, *args, **kwargs):
super(MyForm, self).__init__(*args, **kwargs) # Konstruktor der Basisklasse aufrufen.
self.fields['my_field'].widget.attrs['size']=64 # Attribut (size="64") des HTML-Widgets setzen.
4.1 Allgemein
[toc]
Django wird in der Regel über mod_wsgi, SCGI oder FastCGI
angebunden. Bei diesen Methoden wird ein Server gestartet, der den
Python-Interpreter lädt und diesen Interpreter für die nächsten
Requests wieder verwendet.
Die Anbindung mittels einfachem CGI (für jeden
HTTP-Request wird ein Python Interpreter gestartet) ist möglich, aber
nicht zu empfehlen. Siehe Django Wiki: ServerArrangements
Mein Tipp:
- mod_wsgi
4.2 Middleware
[toc]
Eine Middleware stellt Methoden bereit, die immer am Anfang und am
Ende jedes Requests aufgerufen werden.
Beispiele:
Methoden, die bereitgestellt werden können:
process_request(self, request)
process_response(self, request, response)
process_exception(self, request, exception)
Beispiele:
- Im Fehlerfall (Exception, die nicht aufgefangen wird), die
Exception in ein Logverzeichnis schreiben und den Administrator per
Email benachrichtigen.
- Session basiertes Nachrichtensystem: Ticket 4604
- Client-IP Adresse mittels DNS-Blacklists prüfen und ggf. ablehnen.
- Für gewisse Bereiche der Seite das Anmelden erzwingen (Weiterleiten zu einer Login-Seite)
- Falls settings.DEBUG==True, HTML Seiten in process_response mit
tidy nach Syntaxfehlern durchsuchen.
Siehe Djano Doku: Middleware
4.3 Request Objekt
[toc]
Attribute des Request Objekts:
POST | POST Parameter (<form action="./" method="POST">) |
GET | GET Parameter http://.../mypage/?variable=wert |
META | Dictionary HTTP_REFERER=http://.. |
path | URL des Aufrufs (ohne Server und ohne GET Parameter) |
Hinweis: POST und GET sind zwei verschiedene Dictionaries. POST sollte
verwendet werden, wenn mit dem Request Daten auf dem Server verändert
werden. GET sollte für lesende Zugriffe verwendet werden.
Siehe Django Doku: Request+Response
5 Unittests
[toc]
Vertrauen ist gut, Kontrolle ist besser
Django bietet zwei Möglichkeiten des automatisierten Testens:
- Doctest: Das sind Tests, die in der Dokumentationszeichenkette einer Funktion/Klasse/Methode stehen:
def my_func(a_list, idx):
"""
>>> a = ['larry', 'curly', 'moe']
>>> my_func(a, 0)
'larry'
>>> my_func(a, 1)
'curly'
"""
return a_list[idx]
- Unit tests: Das sind Tests, die als Methoden einer von
unittest.TestCase abgeleiteten Klasse definiert werden.
from django.test import TestCase
class MyFuncTestCase(TestCase)
def testBasic(self):
a = ['larry', 'curly', 'moe']
self.assertEquals(my_func(a, 0), 'larry')
self.assertEquals(my_func(a, 1), 'curly')
Für die Tests wird temporär eine neue Datenbank angelegt. Optional
können Testdaten in die Datenbank geladen werden. Nach dem Test wird
die Testdatenbank wieder gelöscht.
Sehr praktisch ist der Test Client. Damit kann man die Aufrufe mittels einem Webbrowser simulieren:
# Datei mysite/quiz/tests.py
from django.test import TestCase
class SimpleTest(TestCase):
def test_add(self):
response = self.client.post('/quiz/question/add/',
{'text': 'Hauptstadt von Frankreich', 'answer': 'Paris'})
self.assertContains(response, 'wurde angelegt', status_code=200)
Unittest ausführen:
tilldu@r51:~/mysite$ manage.py test # Alle Tests aller Anwendungen
tilldu@r51:~/mysite$ manage.py test quiz # Alle Tests der Anwendung 'quiz'
tilldu@r51:~/mysite$ manage.py test quiz.SimpleTest.test_add # Einen einzelnen Test aufrufen
Siehe: Django Doku: Testing
6 Templatesprache
[toc]
Die Templatesprache von Django ist sehr vielseitig. Sie unterstützt
z.B. Vererbung: In abgeleiteten Templates können einzelne Blöcke des
Elterntemplates überschrieben werden.
Siehe Django Doku: Templates for Authors und Templates for Programmers.
Besonders interessant ist Autoescape.
7 Sonstiges
[toc]
- i18n: (Internationalisierung): ugettext_lazy. Siehe Django Doku:
i18n Da erst beim "Rendern" der Ergebnisseite die Ausgabesprache
bekannt ist, sind die Übersetzungen 'lazy' ('faul'). Sie werden erst
bei der Ausgabe übersetzt.
- Zwei Bücher erscheinen in Kürze:
Django Book (Apress, Englisch) und Django (Open Source Press, Deutsch)
- Caching: Gesamte Seiten oder Teilbereiche einer Seite lassen sich
wiederverwenden, um die Datenbankzugriffe und die Systemlast zu
reduzieren: Django Doku:
Caching
- Versenden von Emails. Vorteil gegenüber Python Bibliothek: Bei
Unittests werden die Emails nicht versendet, sondern auf Modulebene
gespeichert, so dass das Erstellen von Emails getestet werden
kann. Siehe Django Doku:
Sending e-mails
- Was für SQL-Anweisungen werden ausgeführt?
from django.db import connections
def myview(request):
...
# Falls DEBUG=True werden alle SQL Anweisungen angezeigt.
assert False, connections.queries
Oder SQLLogMiddleware
- settings.py: APPEND_SLASH=False (Redirects vermeiden)
- AJAX (JSON)
from django.utils import simplejson
def my_ajax_server_function(request):
# Beliebig tief geschachtelte Datenstruktur aus:
# Dictionaries, Listen, True, False, None
something=...
return django.http.HttpResponse(simplejson.dumps(something))
- Schöne URLs:
- Slash am Ende: urls.py: Pattern
.../$
- Objekt/Methode/
/question/ID/ --> views/question.py view(request, question_id)
/question/ID/edit/ --> views/question.py edit(request, question_id)
- Struktur der Views:
Django erstellt bei 'manage.py startapp ...' views.py. Bei
umfangreichen Projekten ist es besser anstatt der Datei ein
Verzeichnis zu nehmen:
mysite/quiz/views/
__init__.py # Leere Datei ist nötig
question.py
def view(request, question_id): ...
def edit(request, question_id): ...
group.py
def view(request, group_id): ...
...
- Da Python schon ein umfangreiches Logging Bibliothek besitzt,
gibt es in kein django-spezifisches Logging Modul. In
settings.py
kann die entsprechende Konfiguration
erfolgen. Doch Achtung: Diese Datei wird ggf. mehrfach ausgeführt, so
dass die Logeinträge immer doppelt auftreten. Darum dieser Trick:
Eine Variable wird im Namensraum des Loggin Moduls hinterlassen:
# Datei settings.py
...
import logging
if not getattr(logging, "set_up_done", None):
logger=logging.getLogger()
# logfile: Im Verzeichnis von settings.py: log/mysite.log
logfile=os.path.join(os.path.dirname(__file__), "log", "mysite.log")
logger.addHandler(logging.FileHandler(logfile))
logger.setLevel(logging.NOTSET)
logging.set_up_done=True
# Datei myapp/views/beispiel.py
import logging
def view(request):
logging.info("...")
- Werden in einer HTML-Seite URLs per
Hand zusammengebaut
(
'...href="/myapp/%(object_id)s/%(attachments)s/view"...' %
locals()
) besteht die Gefahr, dass defekte URLs erstellt
werden. Darum ist es sinnvoll die URL aus der Angabe in urls.py zu berechnen:
from django.core.urlresolvers import reverse
'... href="%s"...' % reverse('myapp.views.attachment_view', kwargs={'object_id': object.id})
Anhand dem Mapping in urls.py wird die URL erstellt. Der Vorteil ist,
dass im Fehlerfall eine Exception auftritt. Das fällt in einem
Unittest sofort auf, während ein defekter Link erst auffällt wenn
jemand ihn anklickt.
Leider funktioniert reverse() erst, wenn alle URLs eigelesen wurden. Ein einem Decorator für einen View ist das ggf. noch nicht def Fall. Mit folgender Funktion funktioniert es:
# file lazy.py
from django.core import urlresolvers
class lazy_string(object):
def __init__(self, function, *args, **kwargs):
self.function=function
self.args=args
self.kwargs=kwargs
def __str__(self):
if not hasattr(self, 'str'):
self.str=self.function(*self.args, **self.kwargs)
return self.str
def reverse(*args, **kwargs):
return lazy_string(urlresolvers.reverse, *args, **kwargs)
- Nutze
FileField
nicht, wenn du es nicht brauchst. Da
bei einem FileField nur der Dateiname in der Datenbank gespeichert
wird und nicht der Inhalt gibt es einen Bruch im
Transaktionssystem. Man sollte sich die Frage stellen, ob man
überhaupt den Dateinamen in der Datenbank benötigt? Ich habe
z.B. festgestellt, dass es einfacher ist mit einem Verzeichnis im
Dateisystem (z.B. media_root/objects/OBJECT_ID/) zu arbeiten. Die
Dateinamen wandel ich mit django.utils.http.urlquote() von unicode zu
ascii. Diese Dateien können zwar nicht mit dem Admin-Interface
verwaltet werden, dafür aber mit üblichen Betriebssystem Funktionen
(rm, cp, scp, ...).
- Passwort zurücksetzen.
user@host> ./manage.py shell
>>> from django.contrib.auth.models import User
>>> u=User.objects.get(username='myuser')
>>> u.set_password('mypassword')
>>> u.save()
- Bestimmer Nutzer einer Gruppe zuordnen:
# Alle Nutzer die einer Suchanfragen (.filter(...)) entsprechen
# einer Gruppe hinzufügen:
from django.contrib.auth.models import User, Group
g=Group.objects.get(name='...')
for user in User.objects.filter(...):
if g in user.groups.all():
continue
user.groups.add(g)
- Anstatt alle Model-Klassen in der Datei models.py zu speichern, kann man
auch ein Verzeichnis models/erstellen und in models/__init__.py die
Dateien es Verzeichnisses importieren. In der Meta-Klasse der Models muss jedoch
dann app_label gesetzt sein:
class MyModel(models.Model):
class Meta:
app_label = 'myapp'
...
- Debug-Template nutzen: Fast alle Fehler, die beim Programmiern
passieren, werden bei Django in einer ausführlichen Fehlerseite
dargestellt. Besonders hilfreich sind oft die lokalen Variablen der
letzten Zeile im Stacktrace. Leider sind dort nur die lokalen
Variablen zu sehen. Ggf muss man etwas nach helfen um wie im
folgenden Beispiel den Inhalt von self.cleaned_data zu sehen:
class MyForm(forms.Form):
...
def clean(self):
...
cd=self.cleaned_data # Debug-Zeile für das Fehlertemplate: Lokale Variable 'cd'
- Erst lesen, dann loslegen! Das spart Zeit. Die Dokumentation ist
umfangreich und gut strukturiert.
- Keine Angst vor fremdem Quelltext: Falls sich Django nicht so
verhällt, wie du das willst, dann öffne die entsprechenden
Python-Dateien und füge z.B. eine Assert- oder Loggin-Anweisung ein.
# Beispiel: Bei einer Exception auf Datenbankebene, diese Exception per
# Logging Modul ausgeben. Anschließend Exception 'weiterwerfen'.
Index: django/db/backends/util.py
===================================================================
--- django/db/backends/util.py (Revision 7189)
+++ django/db/backends/util.py (Arbeitskopie)
@@ -16,6 +16,10 @@
start = time()
try:
return self.cursor.execute(sql, params)
+ except Exception, exc:
+ import logging
+ logging.error('execute SQL failed: %s sql=%s params=%s' % (exc, sql, params), exc_info=True)
+ raise
finally:
stop = time()
sql = self.db.ops.last_executed_query(self.cursor, sql, params)
- Leider muss für das derzeitige Admin-Interface TEMPLATE_STRING_IF_INVALID leer sein. Für eigenen Code kann man diese Variable entweder mit einer Zeichenkette belegen, oder mit folgendem Schnippsel auch eine Exception auslösen, falls in einem Template versucht wird eine nicht existierende Variable zu lesen:
# Datei settings.py
# ...
class InvalidVarException(object):
def __mod__(self, missing):
try:
missing_str=unicode(missing)
except:
missing_str='Failed to create string representation'
raise Exception('Unknown template variable %r %s' % (missing, missing_str))
def __contains__(self, search):
if search=='%s':
return True
return False
TEMPLATE_DEBUG=True
#TEMPLATE_STRING_IF_INVALID = u'MISSING TEMPLATE VAR >>>%s<<<'
TEMPLATE_STRING_IF_INVALID = InvalidVarException()
- PostgreSQL: Wenn Datenbank und Django auf dem gleichen Rechner
laufen, verwende ich in der Datenbank und als Systemnutzer den
gleichen Namen. Damit lässt sich unter Postgres auf ein Passwort
verzichten:
# in settings.py
import os
import pwd
pwdtuple=pwd.getpwuid(os.getuid())
USER=pwdtuple[0]
DATABASE_USER = USER
# password, host and port are empty
- Will man zwischen dem Anonymen Nutzer und angemeldeten Nutzern
unterscheiden gibt es die Methode user.is_authenticated(). Da
die die Attribute is_active und is_superuser jedoch Properties
sind, empfehle ich immer is_active zu verwenden. Denn wenn man die
Klammern hinter is_authenticated() vergisst, ist der Ausdruck
immer wahr!
if user.is_active:
....
else:
...
- Initial Daten für die Datenbank. Es gibt mehrere Wege um nach dem
Aufruf von
./manage.py syncdb
automatisch Daten in die
Datenbank einzuspielen.
appname/sql/modelname.sql
Siehe Model API: initial SQl Data
- post_syncdb signal: Bei dieser Methode wird eine Methode als
Event-Listener registiert. Siehe Wiki: Signals
oder B-List
Blog
# Datei: management.py
# Beispiel: tsearch2 in der Postgres Datenbank installieren,
# falls dies noch nicht geschehen ist:
# Python
import os
import logging
import subprocess
# Django
from django.dispatch import dispatcher
from django.db.models import signals
from django.db import connection
# Myapp
import myapp.models
from django.conf import settings
tsvector_file='/usr/share/postgresql/contrib/tsearch2.sql'
def post_syncdb_myapp(app, created_models, verbosity):
if not myapp.models.Page in created_models:
return
cursor=connection.cursor()
cursor.execute('''SELECT COUNT(*) from pg_type where typname='tsvector' ''')
if int(cursor.fetchone()[0]):
logging.info('tsvector ist schon intalliert')
return
assert os.path.exists(tsvector_file)
cmd=['psql', '-f', tsvector_file, '-d', settings.DATABASE_NAME]
ret=subprocess.call(cmd)
assert ret==0, 'cmd=%s failed' % cmd
logging.info('tsvector wurde intalliert')
dispatcher.connect(post_syncdb_myapp, sender=myapp.models,
signal=signals.post_syncdb)
if __name__=='__main__':
post_syncdb_myapp(None, [myapp.models.Page], None)
9 Best of Django Imports
[toc]
Folgende Import Anweisungen führe ich oft aus:
from django.newforms import forms
from django.conf import settings # Settings-Datei des Projekts
from django.core.urlresolvers import reverse
from django.utils.html import conditional_escape as escape
from django.utils.safestring import mark_safe
from django.http import HttpResponse, HttpResponseRedirect
from django.utils.http import urlquote
11 Nachteile
[toc]
- Das Speichern von Dateien ist bisher nicht sehr flexibel. Eine
besser Lösung ist in Arbeit. Siehe Pluggable backends
for FileField
- Bevor die Version 1.0 erscheint wollen die Programmierer noch
einiges Entwickeln. Die API des aktuellen SVN Trunk wird aber im
Groben bestehen bleiben.
- Aktuelles Admin-Interface arbeitet noch mit der "oldforms"
Bibliothek. An Newforms Admin wird gerade gearbeitet.
- Im Bugtracking-System gibt es zu viele offene Tickets. Obwohl
viele Tickets einen Patch enthalten, werden die Änderungswünschte
selten schnell umgesetzt: Qualität statt Quantität.
- Aus meiner Sicht sollte eine Abfrage:
MyClass.objects.filter(field=None)
dem Aufruf von
.filter(field__isnull=True)
entsprechen. Leider liefert
die erste Abfrage kein Ergebnis, während die zweite die gewünschten
Zeilen mit der Null-Spalte zurückgibt. Die entsprechenden
Vorbereitungen wurden schon getroffen: #2737.
- Die zweispaltige Ausgabe von Newforms gefällt mir nicht, da das
Eingabefeld und Fehlermeldungen in einer Zelle sind. Ich habe mir
eine Funktion form2str() mit einer dreispaltigen Ausgabe
(Feldname, Eingabfeld, ggf. Fehlermeldung) geschrieben.
- Etwas verwirrend für Anfänger ist, dass es zwei
Field
Klassen gibt:
django.db
Datenbankfelder
django.newforms
Felder für Eingabemasken
syncdb
erstellt nur Tabellen. Wird eine bestehende
Klasse geändert, müssen die Tabellen per Hand (ALTER TABLE
...
) angepasst werden. Zwei Tipps:
- Die Ausgaben von
python
manage.py sqlall
vergleichen:
Die Ausgabe des Befehls vor und nach der Änderung in Dateien umleiten
und dann mit diff
vergleichen. Mit etwas SQL-Kenntniss
lassen sich die nötigen ALTER-Anweisungen daraus leicht ableiten.
- Die Ausgaben von 'pg_dump -s' vergleichen:
user@host> pg_dump -s > current.schema # Speichern des Schemas (Tabellendefinition, ohne Tabelleninhalt)
user@host> vi settings.py # Datenbankname zu 'test' ändern.
user@host> createdb -E unicode test # Neue, leere Datenbank erstellen
user@host> ./manage.py syncdb # Tabellen von Django erstellen lassen.
user@host> pg_dump -s > fresh.schema
user@host> diff current.schema fresh.schema
- Falls man keine Adminrechte besitzt, und versucht das
Admininterface aufzurufen, erhält man keine passende
Fehlermeldung. Man wird aufgefordert sein Passwort erneut einzugeben.
- Django Templates: Greift man in einem Django Template auf
Variablen zu, die es nicht gibt (z.B. aufgrund eines Tippfehlers),
erhält man keine Fehlermeldung. Der Fehler wird stillschweigend
übergangen. Siehe settings.TEMPLATE_STRING_IF_INVALID
© 2007 Thomas Güttler. Der Text darf nach belieben
kopiert und modifiziert werden, solange dieser Hinweis zum
Copyright und ein Links zu dem Original unter www.thomas-guettler.de
erhalten bleibt. Es wäre nett, wenn Sie mir Verbesserungsvorschläge
mitteilen:
guettli@thomas-guettler.de
Dieser Vortrag ist veraltet: Die neue Version