Seit Django 1.2 kann man mit mehreren Datenbanken gleichzeitig arbeiten.
Dazu tragen wir zuerst die neue Datenbank in die Datei local_settings.py ein:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(SITE_ROOT, 'cookbook.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
},
'newsdb': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(SITE_ROOT, 'news.db'),
},
}
Diese Datenbank soll von einer News App genutzt werden. Sie soll das folgende Datenmodell haben:
Also erstellen wir als erstes die neue App:
$ python manage.py startapp news
Der nächste Schritt ist das Anlegen des abstrakten Models. Dazu legen wir im Konfigurationsverzeichnis die Datei basemodels.py mit folgendem Inhalt an:
from django.db import models
from django.utils.timezone import now
class DateTimeInfo(models.Model):
date_created = models.DateTimeField(editable=False)
date_updated = models.DateTimeField(editable=False)
class Meta:
abstract = True
def save(self, *args, **kwargs):
if not self.id:
self.date_created = now()
self.date_updated = now()
super(DateTimeInfo, self).save(*args, **kwargs)
Danach erstellen wir das Model Article in der Datei news/models.py:
# encoding: utf-8
from django.db import models
from cookbook.basemodels import DateTimeInfo
class Article(DateTimeInfo):
headline = models.CharField(u'Überschrift', max_length=100)
body = models.TextField(u'Inhalt')
class Meta:
verbose_name = u'Artikel'
verbose_name_plural = u'Artikel'
ordering = ['-date_updated']
def __unicode__(self):
return self.headline
Dadurch, dass das Model Article von dem Model DateTimeInfo erbt, erhält es automatisch die beiden DateTimeField Felder und deren Verhalten beim Speichern.
Jetzt brauchen wir noch eine admin.py, um das Model im Admin nutzen zu können:
from django.contrib import admin
from news.models import Article
class ArticleAdmin(admin.ModelAdmin):
list_display = ('headline', 'date_created', 'date_updated')
admin.site.register(Article, ArticleAdmin)
Damit wir die neue Datenbank auch mit der App “news” nutzen können benötigen wir einen “database router”. Diesen legen wir in der Datei router.py im Konfigurationsverzeichnis an:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | class CookbookRouter(object):
"""A router to control all database operations on models in the cookbook site.
"""
def db_for_read(self, model, **hints):
if model._meta.app_label == 'news':
return 'newsdb'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label == 'news':
return 'newsdb'
return None
def allow_relation(self, obj1, obj2, **hints):
if obj1._meta.app_label == 'news' or obj2._meta.app_label == 'news':
return False
return None
def allow_syncdb(self, db, model):
allowed = ['south']
if model._meta.app_label in allowed:
return True
elif db == 'newsdb':
return model._meta.app_label == 'news'
elif model._meta.app_label == 'news':
return False
return None
|
Danach müssen wir DATABASE_ROUTERS in der Datei settings.py konfigurieren:
DATABASE_ROUTERS = ['cookbook.router.CookbookRouter']
Außerdem aktivieren wir noch die neue App “news” in den INSTALLED_APPS.
Da wir im Kapitel Migration auf South umgestellt haben nutzen wir zum Erstellen des neue Models Article nicht mehr den Befehl syncdb, sondern wir erstellen zuerst eine Migration mit dem Kommando schemamigration:
$ python manage.py schemamigration news --initial
Creating migrations directory at '.../cookbook/news/migrations'...
Creating __init__.py in '.../cookbook/news/migrations'...
+ Added model news.Article
Created 0001_initial.py. You can now apply this migration with: ./manage.py migrate news
Da die Datenbank newsdb noch neu ist müssen wir einmalig die Tabellen für South anlegen:
$ python manage.py syncdb --noinput --database=newsdb
Syncing...
Creating tables ...
Creating table south_migrationhistory
Installing custom SQL ...
Installing indexes ...
No fixtures found.
Synced:
> django.contrib.auth
> django.contrib.contenttypes
> django.contrib.sessions
> django.contrib.sites
> django.contrib.messages
> django.contrib.staticfiles
> django.contrib.admin
> debug_toolbar
> userauth
> south
Not synced (use migrations):
- recipes
- news
(use ./manage.py migrate to migrate these)
Dabei sieht es so aus, als ob noch weitere Tabellen angelegt werden. Das ist aber nicht der Fall, denn der CookbookRouter unterbindet das anlegen der Tabellen. Wir können das auch prüfen:
$ python manage.py dbshell --database=newsdb
SQLite version 3.7.6.3
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
south_migrationhistory
Jetzt führen wir die erste Migration durch:
$ python manage.py migrate news --database=newsdb
Running migrations for news:
- Migrating forwards to 0001_initial.
> news:0001_initial
- Loading initial data for news.
No fixtures found.
Danach können wir den Entwicklungs-Webserver starten und einige Artikel in der neuen News App anlegen.
Seit Django 1.2 ist es auch möglich eine existierende Datenbank in Django einzubinden. Dazu müssen wir zuerst eine solche anlegen. Dafür habe ich ein Python Skript geschrieben, dass eine SQLite Datenbank mit Adressen füllt:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 | # encoding: utf-8
import random
import sys
class Fixture(tuple):
def get_random_id(self):
return random.randint(1, len(self))
def get_random_item(self):
return self[random.randrange(0, len(self))]
def get_random_zipcode():
return ''.join(map(str, random.sample(range(0, 10), 5)))
def drop_tables():
sql = """DROP TABLE IF EXISTS "city";
DROP TABLE IF EXISTS "address";
"""
return sql
def create_tables():
sql = """CREATE TABLE "city" (
"id" integer NOT NULL PRIMARY KEY,
"name" varchar(255) NOT NULL
);
CREATE TABLE "address" (
"id" integer NOT NULL PRIMARY KEY,
"first_name" varchar(100) NOT NULL,
"last_name" varchar(100) NOT NULL,
"street" varchar(255) NOT NULL,
"zipcode" varchar(5) NOT NULL,
"city_id" integer NOT NULL REFERENCES "city" ("id")
);
"""
return sql
def insert_cities():
pk = 1
sql = ''
for city in CITIES:
sql += """INSERT INTO "city" VALUES (
%d, "%s"
);
""" % (pk, city)
pk += 1
return sql
def insert_addresses(count):
pk = 1
sql = ''
while pk <= count:
housenumber = random.randint(1, 100)
data = {
'id': pk,
'first_name': FIRST_NAMES.get_random_item(),
'last_name': LAST_NAMES.get_random_item(),
'street': STREETS.get_random_item() + ' %d' % housenumber,
'zipcode': get_random_zipcode(),
'city_id': CITIES.get_random_id(),
}
sql += """INSERT INTO "address" VALUES (
%(id)d, "%(first_name)s", "%(last_name)s", "%(street)s", "%(zipcode)s", %(city_id)d
);
""" % data
pk += 1
return sql
def write(data):
sys.stdout.write(data)
FIRST_NAMES = Fixture(('Malte', 'Andrea', 'Peter', 'Maria', 'Michaela'))
LAST_NAMES = Fixture(('Meier', 'Schulze', 'Drescher', 'Weiland', 'Hirsch'))
STREETS = Fixture(('Alte Straße', 'Hauptstraße', 'Neuer Ring', 'Brunnengasse', 'Am Markt'))
CITIES = Fixture(('Berlin', 'Dresden', 'Hamburg', 'Bonn', 'Bremen', 'Stuttgart'))
if __name__ == '__main__':
try:
ADDRESS_COUNT = int(sys.argv[1])
except IndexError:
ADDRESS_COUNT = 10
write('BEGIN;\n')
write(drop_tables())
write(create_tables())
write(insert_cities())
write(insert_addresses(ADDRESS_COUNT))
write('COMMIT;')
|
Wenn man das Skript an der Kommandozeile aufruft, werden die erzeugten SQL Queries ausgegeben:
$ python sqltestdata.py
Man kann auch mit einem Argument die Anzahl der erzeugten Adressen bestimmen:
$ python sqltestdata.py 200
Zuerst muss aber die Datenbankverbidung in der local_settings.py angelegt werden:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(SITE_ROOT, 'cookbook.db'),
'USER': '',
'PASSWORD': '',
'HOST': '',
'PORT': '',
},
'newsdb': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(SITE_ROOT, 'news.db'),
},
'addressdb': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.join(SITE_ROOT, 'address.db'),
},
}
Nun können wir die Queries mit der neuen Datenbank ausführen:
$ python sqltestdata.py 2000 | python manage.py dbshell --database=addressdb
Und uns auch gleich die Daten ansehen:
$ python manage.py dbshell --database=addressdb
SQLite version 3.7.6.3
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
address city
sqlite> select * from address join city on city_id = city.id limit 10;
1|Andrea|Schulze|Alte Straße 73|64831|5|5|Bremen
2|Malte|Schulze|Neuer Ring 35|87214|5|5|Bremen
3|Maria|Hirsch|Hauptstraße 78|68412|5|5|Bremen
4|Malte|Weiland|Brunnengasse 70|48076|2|2|Dresden
5|Andrea|Drescher|Am Markt 35|91046|1|1|Berlin
6|Maria|Drescher|Hauptstraße 13|08457|6|6|Stuttgart
7|Peter|Drescher|Hauptstraße 67|69318|3|3|Hamburg
8|Maria|Drescher|Alte Straße 89|87126|4|4|Bonn
9|Maria|Hirsch|Hauptstraße 25|41359|4|4|Bonn
10|Maria|Meier|Neuer Ring 17|95746|1|1|Berlin
Als nächstes erstellen wir eine App für die neue Datenbank:
$ python manage.py startapp addressbook
Und lassen Django mit Hilfe des Befehls inspectdb Models aus den Tabellen der Datenbank erzeugen:
$ python manage.py inspectdb --database=addressdb
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
# * Rearrange models' order
# * Make sure each model has one field with primary_key=True
# Feel free to rename the models, but don't rename db_table values or field names.
#
# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'
# into your database.
from django.db import models
class Address(models.Model):
id = models.IntegerField(primary_key=True)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
street = models.CharField(max_length=255)
zipcode = models.CharField(max_length=5)
city = models.ForeignKey(City)
class Meta:
db_table = u'address'
class City(models.Model):
id = models.IntegerField(primary_key=True)
name = models.CharField(max_length=255)
class Meta:
db_table = u'city'
Diese schreiben wir dann in die Datei addressbook/models.py:
$ python manage.py inspectdb --database=addressdb > addressbook/models.py
Damit die Models auch funktionieren passen wir sie noch ein wenig an (Zeilen 5, 10, 14, 16-17, 21, 23, 26, 28-29):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | from django.db import models
class Address(models.Model):
id = models.IntegerField(primary_key=True, editable=False)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
street = models.CharField(max_length=255)
zipcode = models.CharField(max_length=5)
city = models.ForeignKey('City')
class Meta:
db_table = u'address'
managed = False
def __unicode__(self):
return '%s %s' % (self.first_name, self.last_name)
class City(models.Model):
id = models.IntegerField(primary_key=True, editable=False)
name = models.CharField(max_length=255)
class Meta:
db_table = u'city'
managed = False
def __unicode__(self):
return self.name
|
Außerdem müssen wir den CookbookRouter erweitern (Zeilen 7-8, 14-15, 21-22):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | class CookbookRouter(object):
"""A router to control all database operations on models in the cookbook site.
"""
def db_for_read(self, model, **hints):
if model._meta.app_label == 'news':
return 'newsdb'
if model._meta.app_label == 'addressbook':
return 'addressdb'
return None
def db_for_write(self, model, **hints):
if model._meta.app_label == 'news':
return 'newsdb'
if model._meta.app_label == 'addressbook':
return 'addressdb'
return None
def allow_relation(self, obj1, obj2, **hints):
if obj1._meta.app_label == 'news' or obj2._meta.app_label == 'news':
return False
if obj1._meta.app_label == 'addressbook' or obj2._meta.app_label == 'addressbook':
return False
return None
def allow_syncdb(self, db, model):
allowed = ['south']
if model._meta.app_label in allowed:
return True
elif db == 'newsdb':
return model._meta.app_label == 'news'
elif model._meta.app_label == 'news':
return False
return None
|
Jetzt benötigen wir nur noch eine addressbook/admin.py, um die Daten im Admin anzuzeigen. Wir aktivieren Suche und Filter, zeigen mehr Felder in der Liste an und machen alle Felder im Formular nur lesbar:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | from django.contrib import admin
from addressbook.models import Address, City
class AddressAdmin(admin.ModelAdmin):
actions = None
list_display = ('first_name', 'last_name', 'street', 'zipcode', 'city')
list_display_links = ('first_name', 'last_name')
list_filter = ('city', 'last_name')
search_fields = ['first_name', 'last_name', 'street', 'zipcode', 'city__name']
readonly_fields = ('first_name', 'last_name', 'street', 'zipcode', 'city')
admin.site.register(Address, AddressAdmin)
admin.site.register(City)
|
Zuletzt aktivieren wir noch die App addressbook in den INSTALLED_APPS in der settings.py und starten dann den Entwicklungs-Webserver, um uns die Daten anzusehen.