Bases de données multiples¶
Ce guide thématique décrit la prise en charge de plusieurs bases de données par Django. La plupart de la documentation de Django suppose que vous travaillez avec une seule base de données. Si vous avez besoin de travailler avec plusieurs bases de données, il sera nécessaire de procéder à quelques configurations supplémentaires.
Voir aussi
Consultez Prise en charge de plusieurs bases de données pour des informations sur les tests avec plusieurs bases de données.
Définition des bases de données¶
La première chose à faire lorsqu’on veut utiliser plus d’une base de données avec Django est de le renseigner au sujet des serveurs de base de données à utiliser. Cela se fait au niveau du réglage DATABASES
. Ce réglage fait correspondre des alias de bases de données, qui sont une manière de se référer à des bases de données spécifiques dans le code de Django, à un dictionnaire de réglages pour la connexion concernée. Les réglages du dictionnaire interne sont détaillés dans la documentation de DATABASES
.
L’alias de base de données peut être librement choisi. Cependant, l’alias default
a une signification spéciale. Django utilise la base de données ayant l’alias default
lorsqu’aucune autre base de données n’a été sélectionnée.
L’exemple suivant est un extrait de settings.py
définissant deux bases de données, une base de données PostgreSQL par défaut et une base de données MySQL nommée users
:
DATABASES = {
"default": {
"NAME": "app_data",
"ENGINE": "django.db.backends.postgresql",
"USER": "postgres_user",
"PASSWORD": "s3krit",
},
"users": {
"NAME": "user_data",
"ENGINE": "django.db.backends.mysql",
"USER": "mysql_user",
"PASSWORD": "priv4te",
},
}
Si le concept d’une base de données par défaut n’a pas de sens dans le contexte de votre projet, vous devez vous efforcez de systématiquement indiquer la base de données à utiliser. Django exige qu’une base de données nommée default
soit définie, mais le dictionnaire des paramètres peut rester vierge s’il n’est pas utilisé. Pour faire cela, vous devez configurer DATABASE_ROUTERS
pour tous les modèles de vos applications, y compris ceux des applications « contrib » ou d’applications tierces que vous utilisez, afin qu’aucune requête ne soit dirigée vers la base de données par défaut. L’exemple suivant est un extrait de settings.py
définissant deux bases de données spécifiques, avec l’entrée default
volontairement laissée vide :
DATABASES = {
"default": {},
"users": {
"NAME": "user_data",
"ENGINE": "django.db.backends.mysql",
"USER": "mysql_user",
"PASSWORD": "superS3cret",
},
"customers": {
"NAME": "customer_data",
"ENGINE": "django.db.backends.mysql",
"USER": "mysql_cust",
"PASSWORD": "veryPriv@ate",
},
}
Si vous essayez d’accéder à une base de données qui n’y pas été définie dans le réglage DATABASES
, Django génère une exception django.utils.connection.ConnectionDoesNotExist
.
Synchronisation des bases de données¶
La commande de gestion migrate
opère sur une seule base de données à la fois. Par défaut, elle agit sur la base de données default
, mais en renseignant l’option --database
, vous pouvez lui demander de synchroniser une autre base de données. Ainsi, pour synchroniser tous les modèles de toutes les bases de données dans le premier exemple ci-dessus, il faudrait exécuter :
$ ./manage.py migrate
$ ./manage.py migrate --database=users
Si vous ne voulez pas que chaque application soit synchronisée vers une base de données particulière, vous pouvez définir un routeur de base de données implémentant une politique de restriction de disponibilité de certains modèles.
Si comme dans le second exemple ci-dessus vous avez laissé vide la base de données default
, vous devez fournir un nom de base de données chaque fois que vous lancez migrate
. Sans cela, une erreur sera produite. Pour le second exemple :
$ ./manage.py migrate --database=users
$ ./manage.py migrate --database=customers
Utilisation d’autres commandes de gestion¶
La plupart des autres commandes django-admin
interagissant avec la base de données fonctionnent de la même façon que migrate
, c’est-à-dire qu’ils n’opèrent toujours que sur une seule base de données à la fois, en se basant sur le paramètre --database
pour savoir quelle base de données utiliser.
Une exception à cette règle est la commande makemigrations
. Elle valide l’historique des migrations dans la base de données pour détecter d’éventuels problèmes avec les fichiers de migration existants (par exemple suite à une édition manuelle) avant de créer de nouvelles migrations. Par défaut, elle ne contrôle que la base de données default
, mais elle consulte la méthode allow_migrate()
des routeurs pour autant qu’il y en ait.
Routage automatique de base de données¶
La façon la plus simple d’utiliser plusieurs bases de données est de définir un plan de routage de base de données. Le plan de routage par défaut garantit que les objets restent « attachés » à leur base de données originale (c’est-à-dire qu’un objet extrait de la base de données foo
sera également enregistré dans la même base). Le plan de routage par défaut garantit que sans indication particulière de base de données, toutes les requêtes sont dirigées vers la base de données default
.
Vous n’avez rien à faire de particulier pour activer le plan de routage par défaut, il est activé d’origine pour tout projet Django. Cependant, si vous avez l’intention d’implémenter un comportement plus élaboré de distribution entre bases de données, vous avez la possibilité de définir et installer vos propres routeurs de base de données.
Routeurs de base de données¶
Un routeur de base de données (Router
) est une classe fournissant jusqu’à quatre méthodes :
-
db_for_read
(model, **hints)¶ Suggère la base de données à utiliser pour les opérations de lecture pour les objets de type
model
.Si une opération de base de données est capable de fournir des informations supplémentaires pouvant aider à choisir la base de données, celles-ci seront contenues dans le dictionnaire
hints
. Vous trouverez ci-dessous plus de détails au sujet des contenus possibles dehints
.Renvoie
None
s’il n’y a pas de suggestion.
-
db_for_write
(model, **hints)¶ Suggère la base de données à utiliser pour les opérations d’écriture pour les objets de type
Model
.Si une opération de base de données est capable de fournir des informations supplémentaires pouvant aider à choisir la base de données, celles-ci seront contenues dans le dictionnaire
hints
. Vous trouverez ci-dessous plus de détails au sujet des contenus possibles dehints
.Renvoie
None
s’il n’y a pas de suggestion.
-
allow_relation
(obj1, obj2, **hints)¶ Renvoie
True
si une relation entreobj1
etobj2
est autorisée,False
si la relation est interdite ouNone
si le routeur n’a pas d’avis. Il s’agit purement d’une opération de validation utilisée par les opérations de clé étrangère et de relations plusieurs-à-plusieurs pour déterminer si une relation entre deux objets doit être permise ou non.Si aucun routeur n’a d’opinion (c-à-d. tous les routeurs renvoient
None
), seules les relations à l’intérieur d’une même base de données sont acceptées.
-
allow_migrate
(db, app_label, model_name=None, **hints)¶ Détermine si l’opération de migration est autorisée à s’exécuter pour la base de données ayant l’alias
db
. RenvoieTrue
si l’opération doit être appliquée,False
si elle ne doit pas l’être ouNone
si le routeur n’a pas d’avis.Le paramètre positionnel
app_label
est l’étiquette de l’application en cours de migration.model_name
est défini par la plupart des opérations de migration à la valeur demodel._meta.model_name
(la version en minuscules du__name__
du modèle) du modèle en cours de migration. Sa valeur estNone
pour les opérationsRunPython
etRunSQL
sauf si elles le fournissent par des indications (hints
).hints
est utilisé par certaines opérations pour communiquer des informations supplémentaires au routeur.Lorsque
model_name
est défini,hints
contient normalement la classe du modèle dans la clé'model'
. Notez qu’il peut s’agir d’un modèle historique, et de ce fait ne pas comporter d’attributs, méthodes ou gestionnaires personnalisés. Vous ne pouvez compter que sur_meta
.Cette méthode peut aussi être utilisée pour déterminer la disponibilité d’un modèle dans une base de données précise.
makemigrations
crée toujours des migrations pour les modifications de modèles, mais siallow_migrate()
renvoieFalse
, toute opération de migration pourmodel_name
sera silencieusement ignorée lors de l’exécution demigrate
pourdb
. La modification du comportement deallow_migrate()
pour des modèles qui ont déjà des migrations peut aboutir à des clés étrangères cassées, des tables en trop ou des tables manquantes. Lorsquemakemigrations
vérifie l’historique des migrations, elle ignore les bases de données où aucune application n’autorise les migrations.
Un routeur ne doit pas absolument implémenter toutes ces méthodes, il peut en omettre une ou plusieurs. Si l’une des méthode est absente, Django saute ce routeur lorsqu’il effectue le contrôle correspondant.
Indications (hints
)¶
Les indications reçues par le routeur de base de données dans le dictionnaire hints
peuvent servir à décider quelle base de données doit recevoir une requête donnée.
Actuellement, la seule indication fournie est instance
, une instance d’objet liée à l’opération de lecture ou d’écriture en cours. Il peut s’agir de l’instance en train d’être enregistrée ou d’une instance à ajouter dans une relation plusieurs-à-plusieurs. Dans certains cas, aucune indication d’instance n’est présente. Le routeur vérifie l’existence d’une indication d’instance et détermine lui-même si cette instance doit être utilisée pour modifier le comportement de routage.
Utilisation des routeurs¶
Les routeurs de base de données sont installés par le réglage DATABASE_ROUTERS
. Ce réglage définit une liste de noms de classes, chacune définissant un routeur devant être utilisé par le routeur de base (django.db.router
).
Le routeur principal est utilisé par les opérations de base de données de Django pour désigner les bases de données à utiliser. Chaque fois qu’une requête a besoin de savoir quelle base de données utiliser, elle appelle le routeur principal, en indiquant un modèle ainsi qu’un indice (si disponible). Le routeur principal essaie chaque classe de routeur tour à tour jusqu’à ce qu’une de ces classes renvoie une suggestion de base de données. Si aucun routeur ne renvoie de suggestion, le routeur principal essaie d’utiliser la propriété instance._state.db
de l’instance d’indice. Si aucun indice n’a été indiqué ou si instance._state.db
contient None
, le routeur principal désigne la base de données default
.
Un exemple¶
À titre d’exemple uniquement !
Cet exemple est prévu pour démontrer la manière dont l’infrastructure de routage peut être utilisée pour modifier l’utilisation des bases de données. Il ignore volontairement certaines questions complexes afin de démontrer comment les routeurs peuvent être utilisés.
Cet exemple ne fonctionnera pas si l’un des modèles de myapp
contient des relations vers des modèles stockés ailleurs que dans la base de données other
. Les relations croisées entre base de données induisent des problèmes d’intégrité référentielle que Django ne peut actuellement pas gérer.
La configuration primaire/réplique présentée ici (désignée comme maître/esclave par certaines bases de données) est également biaisée, car elle ne propose aucune solution pour gérer les délais de réplication (c’est-à-dire les incohérences de requêtes dues au temps nécessaire à propager les écritures vers les répliques). Elle ne considère pas non plus l’interaction des transactions avec la stratégie d’utilisation des bases de données.
Mais qu’est-ce que cela signifie en pratique ? Considérons un autre exemple de configuration. Celle-ci contiendra plusieurs bases de données : une pour l’application auth
et toutes les autres applications utiliseront une configuration primaire/réplique avec deux répliques en lecture seule. Voici les réglages définissant ces bases de données :
DATABASES = {
"default": {},
"auth_db": {
"NAME": "auth_db_name",
"ENGINE": "django.db.backends.mysql",
"USER": "mysql_user",
"PASSWORD": "swordfish",
},
"primary": {
"NAME": "primary_name",
"ENGINE": "django.db.backends.mysql",
"USER": "mysql_user",
"PASSWORD": "spam",
},
"replica1": {
"NAME": "replica1_name",
"ENGINE": "django.db.backends.mysql",
"USER": "mysql_user",
"PASSWORD": "eggs",
},
"replica2": {
"NAME": "replica2_name",
"ENGINE": "django.db.backends.mysql",
"USER": "mysql_user",
"PASSWORD": "bacon",
},
}
Nous devons maintenant gérer le routage. Nous désirons premièrement un routeur sachant envoyer des requêtes pour les applications auth
et contenttypes
vers auth_db
(les modèles auth
sont liés à ContentType
, ils doivent donc être stockés dans la même base de données)
class AuthRouter:
"""
A router to control all database operations on models in the
auth and contenttypes applications.
"""
route_app_labels = {"auth", "contenttypes"}
def db_for_read(self, model, **hints):
"""
Attempts to read auth and contenttypes models go to auth_db.
"""
if model._meta.app_label in self.route_app_labels:
return "auth_db"
return None
def db_for_write(self, model, **hints):
"""
Attempts to write auth and contenttypes models go to auth_db.
"""
if model._meta.app_label in self.route_app_labels:
return "auth_db"
return None
def allow_relation(self, obj1, obj2, **hints):
"""
Allow relations if a model in the auth or contenttypes apps is
involved.
"""
if (
obj1._meta.app_label in self.route_app_labels
or obj2._meta.app_label in self.route_app_labels
):
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
Make sure the auth and contenttypes apps only appear in the
'auth_db' database.
"""
if app_label in self.route_app_labels:
return db == "auth_db"
return None
Et nous voulons également un routeur pour que toutes les autres applications envoient leurs requêtes vers la configuration primaire/réplique, tout en choisissant au hasard une réplique pour la lecture :
import random
class PrimaryReplicaRouter:
def db_for_read(self, model, **hints):
"""
Reads go to a randomly-chosen replica.
"""
return random.choice(["replica1", "replica2"])
def db_for_write(self, model, **hints):
"""
Writes always go to primary.
"""
return "primary"
def allow_relation(self, obj1, obj2, **hints):
"""
Relations between objects are allowed if both objects are
in the primary/replica pool.
"""
db_set = {"primary", "replica1", "replica2"}
if obj1._state.db in db_set and obj2._state.db in db_set:
return True
return None
def allow_migrate(self, db, app_label, model_name=None, **hints):
"""
All non-auth models end up in this pool.
"""
return True
Pour terminer, nous ajoutons dans le fichier de réglages le code suivant (remplaçant path.to.
par le chemin Python réel vers les modules où les routeurs sont définis) :
DATABASE_ROUTERS = ["path.to.AuthRouter", "path.to.PrimaryReplicaRouter"]
L’ordre dans lequel les routeurs sont traités a son importance. Les routeurs sont interrogés dans leur ordre d’apparition dans le réglage DATABASE_ROUTERS
. Dans cet exemple, AuthRouter
est traité avant PrimaryReplicaRouter
, et par conséquent, les décisions concernant les modèles de auth
sont prises avant toute autre décision. Si le réglage DATABASE_ROUTERS
contenait les deux routeurs dans l’ordre inverse, PrimaryReplicaRouter.allow_migrate()
serait traité en premier. La nature « ramasse-tout » de l’implémentation de PrimaryReplicaRouter
signifierait que tous les modèles seraient accessibles dans toutes les bases de données.
Avec l’installation de cette configuration et toutes les bases de données migrées en accord avec Synchronisation des bases de données, exécutons un peu de code Django :
>>> # This retrieval will be performed on the 'auth_db' database
>>> fred = User.objects.get(username="fred")
>>> fred.first_name = "Frederick"
>>> # This save will also be directed to 'auth_db'
>>> fred.save()
>>> # These retrieval will be randomly allocated to a replica database
>>> dna = Person.objects.get(name="Douglas Adams")
>>> # A new object has no database allocation when created
>>> mh = Book(title="Mostly Harmless")
>>> # This assignment will consult the router, and set mh onto
>>> # the same database as the author object
>>> mh.author = dna
>>> # This save will force the 'mh' instance onto the primary database...
>>> mh.save()
>>> # ... but if we re-retrieve the object, it will come back on a replica
>>> mh = Book.objects.get(title="Mostly Harmless")
Cet exemple définissait un routeur pour gérer l’interaction avec les modèles de l’application auth
et d’autres routeurs pour gérer l’interaction avec toutes les autres applications. Si vous avez laissé vide la base de données default
et que vous ne vouliez pas définir un routeur de base de données « fourre-tout » pour gérer toutes les applications non spécifiées explicitement, les routeurs doivent gérer les noms de toutes les applications dans INSTALLED_APPS
avant d’effectuer la migration. Voir Comportement des applications contribuées pour plus d’informations sur les applications contribuées qui doivent être ensemble dans la même base de données.
Sélection manuelle d’une base de données¶
Django offre également une API permettant de conserver un contrôle total sur l’utilisation des base de données dans votre code. L’attribution manuelle d’une base de données est prioritaire par rapport à l’attribution provenant d’un routeur.
Sélection manuelle d’une base de données pour un QuerySet
¶
Vous pouvez choisir la base de données d’un QuerySet
à tout moment dans la « chaîne » du QuerySet
. Appelez using()
sur le QuerySet
pour obtenir un autre QuerySet
utilisant la base de données indiquée.
using()
accepte un seul paramètre : l’alias de la base de données devant recevoir la requête à exécuter. Par exemple :
>>> # This will run on the 'default' database.
>>> Author.objects.all()
>>> # So will this.
>>> Author.objects.using("default")
>>> # This will run on the 'other' database.
>>> Author.objects.using("other")
Sélection d’une base de données pour save()
¶
Utilisez le paramètre nommé using
de Model.save()
pour indiquer dans quelle base de données les données doivent être enregistrées.
Par exemple, pour enregistrer un objet dans la base de données legacy_users
, il faut écrire ceci :
>>> my_object.save(using="legacy_users")
Si vous ne renseignez pas using
, la méthode save()
enregistre dans la base de données par défaut attribuée par les routeurs.
Déplacement d’un objet entre bases de données¶
Si vous avez enregistré une instance dans une base de données, il peut être tentant d’utiliser save(using=...)
comme astuce pour copier l’instance vers une nouvelle base de données. Cependant, si vous ne prenez pas certaines précautions nécessaires, cela peut amener à des conséquences inattendues.
Considérez l’exemple suivant :
>>> p = Person(name="Fred")
>>> p.save(using="first") # (statement 1)
>>> p.save(using="second") # (statement 2)
Dans l’instruction 1, un nouvel objet Person
est enregistré dans la base de données first
. À ce moment, p
n’a pas encore de clé primaire, ce qui fait que Django génère une instruction SQL INSERT
. La clé primaire est donc créée et Django attribue cette clé primaire à p
.
Lorsque l’enregistrement est effectué à l’instruction 2, p
a déjà reçu une valeur de clé primaire et Django va utiliser cette clé primaire dans la nouvelle base de données. Si cette valeur de clé primaire n’est pas déjà utilisée dans la base de données second
, vous n’aurez pas de problème, l’objet sera effectivement copié dans la nouvelle base de données.
Cependant, si la clé primaire de p
est déjà utilisée dans la base de données second
, l’objet existant dans la base de données second
sera écrasé lors de l’enregistrement de p
.
Vous pouvez empêcher cela de deux manières. Premièrement, vous pouvez effacer la clé primaire de l’instance. Si un objet n’a pas de clé primaire, Django le traite comme un nouvel objet, évitant ainsi toute perte de données dans la base de données second
:
>>> p = Person(name="Fred")
>>> p.save(using="first")
>>> p.pk = None # Clear the primary key.
>>> p.save(using="second") # Write a completely new object.
La seconde possibilité est d’utiliser l’option force_insert
de save()
pour être certain que Django produise une instruction SQL INSERT
:
>>> p = Person(name="Fred")
>>> p.save(using="first")
>>> p.save(using="second", force_insert=True)
Cela garantit que la personne nommée Fred
possédera la même clé primaire dans les deux bases de données. Si cette clé primaire est déjà utilisée lorsque vous essayez d’enregistrer dans la base de données second
, une erreur sera générée.
Sélection d’une base de données pour la suppression¶
Par défaut, un appel à la suppression d’un objet existant sera exécuté dans la même base de données qui a été utilisée pour extraire l’objet au préalable :
>>> u = User.objects.using("legacy_users").get(username="fred")
>>> u.delete() # will delete from the `legacy_users` database
Pour indiquer la base de données dans laquelle un modèle sera supprimé, transmettez un paramètre nommé using
à la méthode Model.delete()
. Ce paramètre joue le même rôle que le même paramètre avec save()
.
Par exemple, si vous migrez un utilisateur à partir de la base de données legacy_users
vers la base de données new_users
, voici les commandes que vous pourriez utiliser :
>>> user_obj.save(using="new_users")
>>> user_obj.delete(using="legacy_users")
Utilisation de gestionnaires avec plusieurs bases de données¶
Utilisez la méthode db_manager()
des gestionnaires pour donner à ceux-ci accès à une base de données autre que celle par défaut.
Par exemple, admettons que vous ayez une méthode d’un gestionnaire personnalisé agissant sur la base de données, User.objects.create_user()
. Comme create_user()
est une méthode de gestionnaire, et non une méthode de QuerySet
, il n’est pas possible d’écrire User.objects.using('new_users').create_user()
(la méthode create_user()
n’est disponible que sur User.objects
, le gestionnaire, et non pas sur les objets QuerySet
dérivés du gestionnaire). La solution est d’utiliser db_manager()
, comme ceci :
User.objects.db_manager("new_users").create_user(...)
db_manager()
renvoie une copie du gestionnaire lié à la base de données que vous indiquez.
Utilisation de get_queryset()
avec plusieurs bases de données¶
Si vous surchargez get_queryset()
dans votre gestionnaire, prenez soin de soit appeler la méthode du parent (en utilisant super()
) ou soit de gérer de manière appropriée l’attribut _db
du gestionnaire (une chaîne contenant le nom de la base de données à utiliser).
Par exemple, si vous souhaitiez renvoyer une classe QuerySet
personnalisée à partir de la méthode get_queryset
, vous pourriez faire ceci :
class MyManager(models.Manager):
def get_queryset(self):
qs = CustomQuerySet(self.model)
if self._db is not None:
qs = qs.using(self._db)
return qs
Exposition de plusieurs bases de données dans l’interface d’administration¶
L’interface d’administration de Django ne contient pas de prise en charge explicite de plusieurs bases de données. Si vous souhaitez mettre à disposition une interface d’administration pour un modèle sur une autre base de données que celle indiquée par votre chaîne de routage, il vous faudra écrire des classes ModelAdmin
personnalisées qui vont piloter l’administration vers l’utilisation d’une base de données spécifique pour le contenu.
Les objets ModelAdmin
possèdent les méthodes suivantes qui nécessitent une adaptation pour la prise en charge de plusieurs bases de données :
class MultiDBModelAdmin(admin.ModelAdmin):
# A handy constant for the name of the alternate database.
using = "other"
def save_model(self, request, obj, form, change):
# Tell Django to save objects to the 'other' database.
obj.save(using=self.using)
def delete_model(self, request, obj):
# Tell Django to delete objects from the 'other' database
obj.delete(using=self.using)
def get_queryset(self, request):
# Tell Django to look for objects on the 'other' database.
return super().get_queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
return super().formfield_for_foreignkey(
db_field, request, using=self.using, **kwargs
)
def formfield_for_manytomany(self, db_field, request, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
return super().formfield_for_manytomany(
db_field, request, using=self.using, **kwargs
)
L’exemple présenté ci-dessus implémente une stratégie basée sur plusieurs bases de données où tous les objets d’un type donné sont stockés dans une base de données spécifique (par ex. tous les objets User
se trouvent dans la base de données other
). Si votre utilisation de plusieurs bases de données est plus complexe, votre classe ModelAdmin
devra refléter cette stratégie.
Les objets InlineModelAdmin
peuvent être gérés d’une manière similaire. Ils nécessitent trois méthodes personnalisées :
class MultiDBTabularInline(admin.TabularInline):
using = "other"
def get_queryset(self, request):
# Tell Django to look for inline objects on the 'other' database.
return super().get_queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
return super().formfield_for_foreignkey(
db_field, request, using=self.using, **kwargs
)
def formfield_for_manytomany(self, db_field, request, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
return super().formfield_for_manytomany(
db_field, request, using=self.using, **kwargs
)
Après avoir écrit les définitions des classes d’administration des modèles, celles-ci peuvent être inscrites dans n’importe quelle instance Admin
:
from django.contrib import admin
# Specialize the multi-db admin objects for use with specific models.
class BookInline(MultiDBTabularInline):
model = Book
class PublisherAdmin(MultiDBModelAdmin):
inlines = [BookInline]
admin.site.register(Author, MultiDBModelAdmin)
admin.site.register(Publisher, PublisherAdmin)
othersite = admin.AdminSite("othersite")
othersite.register(Publisher, MultiDBModelAdmin)
Cet exemple configure deux sites d’administration. Dans le premier site, les objets Author
et Publisher
sont exposés ; les objets Publisher
possèdent un sous-formulaire tabulaire affichant les livres publiés par cet éditeur. Le second site n’expose que les éditeurs, sans sous-formulaire.
Utilisation directe de curseurs avec plusieurs bases de données¶
Si vous utilisez plus d’une base de données, vous pouvez utiliser django.db.connections
pour obtenir la connexion (et le curseur) d’une base de données spécifique. django.db.connections
est un objet de type dictionnaire permettant d’obtenir une connexion particulière au moyen de son alias :
from django.db import connections
with connections["my_db_alias"].cursor() as cursor:
...
Limites des bases de données multiples¶
Relations croisées entre bases de données¶
Django n’offre actuellement aucune prise en charge des clés étrangères ou des relations plusieurs-à-plusieurs entre différentes bases de données. Si vous avez utilisé un routeur pour distribuer les modèles entre différentes bases de données, toute relation de clé étrangère ou plusieurs-à-plusieurs définie par ces modèles doit être interne à une seule base de données.
L’intégrité référentielle en est la cause. Afin de maintenir une relation entre deux objets, Django a besoin de savoir que la clé primaire de l’objet lié est valide. Si la clé primaire est stockée dans une autre base de données, il n’est pas possible d’évaluer de manière simple la validité de la clé primaire.
Si vous utilisez Postgres, SQLite, Oracle ou MySQL avec InnoDB, cette garantie d’intégrité est assurée au niveau de la base de données, les contraintes de clés au niveau de la base de données empêchant la création de relations qui ne peuvent pas être validées.
Cependant, si vous utilisez MySQL avec des tables MyISAM, il n’y a pas d’intégrité référentielle ; par conséquent, il est possible de créer des clés étrangères « simulées » entre bases de données. Toutefois, cette configuration n’est pas prise en charge officiellement par Django.
Comportement des applications contribuées¶
Plusieurs applications contribuées contiennent des modèles, et certaines applications ont des dépendances. Comme les relations croisées entre bases de données sont impossibles, il en résulte certaines restrictions sur la façon dont ces modèles peuvent être partagés entre différentes bases de données :
- Les modèles
contenttypes.ContentType
,sessions.Session
etsites.Site
peuvent être indépendamment stockés dans n’importe quelle base de données, pour autant qu’un routeur adéquat soit utilisé. - Les modèles
auth
(User
,Group
etPermission
) sont liés les uns aux autres et àContentType
, ils doivent donc être stockés dans la même base de données queContentType
. admin
dépend deauth
, donc ses modèles doivent se trouver dans la même base de données queauth
.flatpages
etredirects
dépendent desites
, donc leurs modèles doivent être dans la même base de données quesites
.
De plus, certains objets sont automatiquement créés juste après que migrate
crée la table pour les stocker en base de données :
- un
Site
par défaut, - un
ContentType
pour chaque modèle (y compris ceux qui ne sont pas stockés en base de données), - les
Permission
pour chaque modèle (y compris ceux qui ne sont pas stockés en base de données).
Pour des configurations courantes avec plusieurs bases de données, il n’est pas utile de disposer de ces objets dans plus d’une base de données. Ces configurations courantes incluent les configurations maître-esclave et la connexion à des bases de données externes. Il est donc recommandé d’écrire un routeur de base de données autorisant la synchronisation de ces trois modèles vers une seule base de données. Utilisez la même approche pour les autres applications contribuées ou de tierce partie dont les tables n’ont pas besoin de se trouver dans plusieurs bases de données.
Avertissement
Si vous synchronisez les types de contenus dans plus d’une base de données, soyez conscient que leur clés primaires peuvent différer d’une base de données à l’autre. Cela peut aboutir à des corruptions ou des pertes de données.