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.

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.db.utils.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 de hints.

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 de hints.

Renvoie None s’il n’y a pas de suggestion.

allow_relation(obj1, obj2, **hints)

Renvoie True si une relation entre obj1 et obj2 est autorisée, False si la relation est interdite ou None 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. Renvoie True si l’opération doit être appliquée, False si elle ne doit pas l’être ou None 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 de model._meta.model_name (la version en minuscules du __name__ du modèle) du modèle en cours de migration. Sa valeur est None pour les opérations RunPython et RunSQL 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 si allow_migrate() renvoie False, toute opération de migration pour model_name sera silencieusement ignorée lors de l’exécution de migrate pour db. La modification du comportement de allow_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. Lorsque makemigrations 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 maître (django.db.router).

Le routeur maître 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 maître, en indiquant un modèle ainsi qu’un indice (si disponible). Django essaie ensuite chaque routeur tour à tour jusqu’à ce qu’une suggestion de base de données soit trouvée. Si aucune suggestion n’est trouvée, il essaie d’utiliser la propriété _state.db de l’instance de l’indice. Si aucun indice n’a été indiqué ou si l’instance n’a actuellement pas d’état de base de données, le routeur maître 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',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'swordfish',
    },
    'primary': {
        'NAME': 'primary',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'spam',
    },
    'replica1': {
        'NAME': 'replica1',
        'ENGINE': 'django.db.backends.mysql',
        'USER': 'mysql_user',
        'PASSWORD': 'eggs',
    },
    'replica2': {
        'NAME': 'replica2',
        '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 l’application auth vers auth_db:

class AuthRouter:
    """
    A router to control all database operations on models in the
    auth application.
    """
    def db_for_read(self, model, **hints):
        """
        Attempts to read auth models go to auth_db.
        """
        if model._meta.app_label == 'auth':
            return 'auth_db'
        return None

    def db_for_write(self, model, **hints):
        """
        Attempts to write auth models go to auth_db.
        """
        if model._meta.app_label == 'auth':
            return 'auth_db'
        return None

    def allow_relation(self, obj1, obj2, **hints):
        """
        Allow relations if a model in the auth app is involved.
        """
        if obj1._meta.app_label == 'auth' or \
           obj2._meta.app_label == 'auth':
           return True
        return None

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        Make sure the auth app only appears in the 'auth_db'
        database.
        """
        if app_label == 'auth':
            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_list = ('primary', 'replica1', 'replica2')
        if obj1._state.db in db_list and obj2._state.db in db_list:
            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, exécutons quelque extraits 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. Il suffit d’appeler 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').all()

>>> # This will run on the 'other' database.
>>> Author.objects.using('other').all()

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 ont cinq méthodes 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
cursor = connections['my_db_alias'].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, 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 SQLite ou 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 et sites.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 et Permission) sont liés les uns aux autres et à ContentType, ils doivent donc être stockés dans la même base de données que ContentType.
  • admin dépend de auth, donc ses modèles doivent se trouver dans la même base de données que auth.
  • flatpages et redirects dépendent de sites, donc leurs modèles doivent être dans la même base de données que sites.

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),
  • trois objets 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.

Back to Top