Parfois, l'utilisation de vues de base de données plutôt que de tables de base de données peut s'avérer très utile pour interroger des données agrégées. Généralement dans Django ORM, une table de base de données est liée à un modèle Django ORM, mais nous avons constaté qu'il est également possible de lier une vue de base de données avec un modèle Django ORM. Ce faisant, vous pouvez masquer la logique d'agrégation des données au niveau de la base de données (dans le cadre de la création de SQL). Et la plupart des fonctionnalités ORM, comme la recherche de clé étrangère avec double trait de soulignement, fonctionnent toujours pour le modèle lié à une vue de base de données. Dans cet article de blog, je vais vous présenter un exemple simple pour illustrer comment procéder.
Les modèles réguliers
Notre exemple utilise les modèles réguliers suivants. Chaque modèle est associé à une table de base de données.
à partir de django.db importer des modèles classe Job(models.Model) : nom = models.CharField(max_length=200) classe User(models.Model) : nom = models.CharField(max_length=200) classe Charge(models.Model) : MATÉRIEL = 'hw' LOGICIEL = 'sw' TYPE_CHOICES = ( (MATÉRIEL, 'Matériel'), (LOGICIEL, 'Logiciel'), ) type = models.CharField(choices=TYPE_CHOICES, max_length=2) montant = models.DecimalField( max_digits=8, decimal_places=2) mois = models.DateField() utilisateur = models.ForeignKey(User) job = models.ForeignKey(Job, null=True, blank=True)
Le modèle Charge est utilisé pour stocker les charges. Il possède une clé étrangère vers le modèle utilisateur et une clé étrangère facultative vers le modèle de travail. Tous les enregistrements de frais ne peuvent pas être associés à une tâche.
La vue de la base de données
La vue de la base de données est créée en suivant le SQL et peut être injectée dans une migration de données personnalisée avec la commande d'exécution SQL brute.
CREATE OR REPLACE VIEW app_chargesummary AS SELECT row_number() OVER () as id, ci.user_id, ci.job_id, ci.month, SUM(CASE WHEN ci.type = 'hw' THEN ci.amount ELSE 0 END) AS hardware, SOMME (CAS QUAND ci.type = 'sw' ALORS ci.amount ELSE 0 END) AS logiciel FROM app_charge ci GROUP BY ci.user_id, ci.job_id, ci.month;
Cette vue récapitule tous les frais de matériel et de logiciel pour les enregistrements de frais avec le même travail, utilisateur et mois. Il contient les colonnes suivantes :
[id, user_id, job_id, mois, matériel, logiciel]
L'étape suivante consiste à créer un modèle Django qui correspond à cette vue afin que nous puissions utiliser Django ORM pour récupérer les données de la vue.
Le modèle pour la vue
Nous avons créé le modèle ci-dessous pour mapper la vue de la base de données.
classe ChargeSummary(models.Model) : id = models.BigIntegerField(primary_key=True) user = models.ForeignKey(User, on_delete=models.DO_NOTHING) job = models.ForeignKey(Job, on_delete=models.DO_NOTHING) mois = models. DateField() matériel = models.DecimalField(max_digits=19, decimal_places=2) logiciel = models.DecimalField(max_digits=19, decimal_places=2) classe Meta : géré = False db_table = 'app_chargesummary'
Bien que le modèle ci-dessus ressemble beaucoup à un modèle Django classique, il existe trois différences à noter.
- Pour les champs de clé étrangère, nous devons spécifier un kwarg supplémentaire : on_delete=models.DO_NOTHING. Bien que normalement nous n'essayons pas d'appeler delete() sur le modèle ChargeSummary car la vue derrière n'est pas inscriptible, sans spécifier ce kwarg entraînerait l'échec de certains cas de test. Cela peut être lié au mécanisme de nettoyage automatique de la base de données de test après l'exécution des scénarios de test.
- Managed = False doit être spécifié dans la classe Meta afin que la migration automatique de la base de données South ou Django ignore ce modèle et n'essaie pas de créer une migration de schéma de base de données pour celui-ci.
- db_table = 'name_of_the_view' doit être spécifié dans la classe Meta pour permettre à l'ORM Django de savoir de quelle entité de base de données récupérer les données.
Requête avec le nouveau modèle
Une fois le nouveau modèle configuré, vous pouvez utiliser les méthodes de requête ORM classiques sans différence par rapport au modèle Django standard.
Obtenez tous les objets ChargeSummary :
ChargeSummary.object.all()
Obtenez tous les objets ChargeSummary pour mai 2015 :
ChargeSummary.object.filter(mois=dateheure(année=2015, mois=5, jour=1, tzinfo=utc))
Obtenez tous les objets ChargeSummary avec un certain nom de tâche :
ChargeSummary.object.filter(job__name='Tâche de test')
Obtenez tous les objets ChargeSummary avec une adresse e-mail d'utilisateur spécifique (supposons que vous ayez un modèle de profil utilisateur 1 pour 1 associé au modèle utilisateur) :
ChargeSummary.object.filter(user__profile__email='irwen@rescale.com')
Bien sûr, certaines méthodes comme save() ou delete() échoueront probablement puisque les SQL DML ne sont pas applicables par nature aux vues de base de données.
Last but not least
Si vous mettez à niveau votre application vers Django 1.7 ou version ultérieure à partir d'une version antérieure, vous devez exécuter
python manage.py makemigrations your_app_label
pour générer 0001_initial.py, puis exécutez la commande ci-dessous pour mettre à niveau votre base de données.
python manage.py migrer --fake-initial
Malheureusement, cela échouerait si vous disposez de la vue de base de données et du modèle ChargeSummary non géré. La raison en est que lorsque Django exécute les migrations avec le faux indicateur initial, il vérifie que les tables existent dans la base de données pour tous les modèles des opérations CreateModel dans la migration 0001_inital.py. Les opérations CreateModel sont créées pour chaque modèle, même ceux avec Managed=False. Ainsi, pour le modèle ChargeSummary, il échouera car il ne trouvera pas la table réelle. Heureusement, nous pouvons contourner ce problème en ajoutant la vue créer sql dans 0001_initial.py, puis exécuter les migrations avec le faux indicateur initial.
... migrations.RunSQL( """ DROP VIEW IF EXISTS app_chargesummary; CREATE OR REPLACE VIEW app_chargesummary ...; """ ), ...
Bonne modélisation et codage avec Django ORM.
