Infrastructure de contrôle du système¶
L’infrastructure de contrôle du système est un ensemble de contrôles statiques pour valider les projets Django. Elle détecte les problèmes courants et fournit des conseils sur la façon de les corriger. L’infrastructure est extensible de sorte que vous pouvez facilement ajouter vos propres contrôles.
Les contrôles peuvent être déclenchées de manière explicite par la commande check
. Les contrôles sont déclenchés implicitement avant la plupart des commandes, y compris runserver
et migrate
. Pour des raisons de performance, les contrôles ne sont pas effectués dans le contexte de la pile WSGI utilisée en mode déployé. Si vous avez besoin d’effectuer les contrôles systèmes sur le serveur de déploiement, appelez explicitement la commande check
.
Les erreurs graves empêcheront les commandes de Django (comme runserver
) de fonctionner, tout court. Les problèmes mineurs sont signalés dans la console. Si vous avez inspecté la cause d’un avertissement et que vous êtes content de l’ignorer, vous pouvez masquer des avertissements spécifiques en utilisant le réglage SILENCED_SYSTEM_CHECKS
dans votre fichier de réglages du projet.
Une liste complète de tous les contrôles pouvant être levées par Django peuvent être trouvés dans la référence des contrôles système.
Écrire vos propres contrôles¶
Le système est souple et vous permet d’écrire des fonctions qui effectuent tout autre type de contrôle dont vous auriez besoin. Ce qui suit est un exemple de squelette d’une fonction de contrôle :
from django.core.checks import Error, register
@register()
def example_check(app_configs, **kwargs):
errors = []
# ... your check logic here
if check_failed:
errors.append(
Error(
"an error",
hint="A hint.",
obj=checked_object,
id="myapp.E001",
)
)
return errors
La fonction de contrôle doit accepter un argument app_configs
; cet argument est la liste des applications qui doivent être inspectés. Si None
, la vérification doit être effectué sur toutes les applications installées du projet.
Le contrôle recevra un paramètre nommé databases
. Il s’agit d’une liste des alias de bases de données dont les connexions peuvent être utilisées pour inspecter la configuration au niveau base de données. Si databases
vaut None
, le contrôle ne doit utiliser aucune connexion de base de données.
Le paramètre **kwargs
est nécessaire pour prendre en compte de futures modifications.
Messages¶
La fonction doit renvoyer une liste de messages. Si aucun problème n’est détecté suite à la vérification, la fonction de contrôle doit renvoyer une liste vide.
Les avertissements et les erreurs levés par la méthode de contrôle doivent être des instances de CheckMessage
. Une instance de CheckMessage
encapsule une seule erreur à signaler ou un seul avertissement. Elle fournit également des conseils et le contexte applicable au message, et un identifiant unique qui est utilisé à des fins de filtrage.
Le concept est très similaire aux messages du système de message ou de la journalisation. Les messages sont étiquettés avec un level
indiquant la gravité du message.
Il existe également des raccourcis pour faciliter la création des messages avec les niveaux de base. Lorsque vous utilisez ces classes, vous pouvez omettre le paramètre level
car il est sous-entendu par le nom de la classe.
Enregistrement et étiquetage des contrôles¶
Finalement, votre fonction de contrôle doit être explicitement inscrite dans le registre des contrôles systèmes. Les contrôles doivent être inscrits dans un fichier qui est chargé au moment du chargement de l’application. Par exemple, dans la méthode AppConfig.ready()
.
-
register
(*tags)(function)¶
Vous pouvez passer autant d’étiquettes que vous le souhaitez à register
pour labeliser votre contrôle. L’étiquetage des contrôles est utile car elle vous permet de n’exécuter qu’un certain groupe de contrôles. Par exemple, pour enregistrer un contrôle de compatibilité, vous feriez l’appel suivant :
from django.core.checks import register, Tags
@register(Tags.compatibility)
def my_check(app_configs, **kwargs):
# ... perform compatibility checks and collect errors
return errors
Vous pouvez inscrire des « contrôles de déploiement » qui ne sont applicables qu’à un fichier de réglages de production de la manière suivante :
@register(Tags.security, deploy=True)
def my_check(app_configs, **kwargs): ...
Ces contrôles ne seront exécutés que si l’option check --deploy
est utilisée.
Vous pouvez aussi employer register
comme une fonction au lieu d’un décorateur en lui transmettant un objet exécutable (habituellement une fonction) comme premier paramètre.
Le code ci-dessous est équivalent au code ci-dessus :
def my_check(app_configs, **kwargs): ...
register(my_check, Tags.security, deploy=True)
Contrôles sur les champs, modèles, gestionnaires, moteurs de gabarits et bases de données¶
Dans certains cas, vous n’aurez pas besoin d’enregistrer votre fonction de contrôle – vous pouvez la greffer sur un enregistrement existant.
Les champs, les modèles, les gestionnaires de modèle, les moteurs de gabarits et les moteurs de base de données implémentent tous une méthode check()
qui est déjà enregistrée auprès du système de vérification. Si vous souhaitez ajouter des contrôles supplémentaires, vous pouvez étendre l’implémentation de la classe de base, effectuer tous contrôles supplémentaires nécessaires, et ajouter tous les messages à ceux générés par la classe de base. Il est recommandé que vous déléguiez chaque contrôle à des méthodes distinctes.
Prenons un exemple où vous implémentez un champ personnalisé nommé RangedIntegerField
. Ce champ ajoute les arguments min
et max
au constructeur d”IntegerField
. Vous pourriez ajouter une vérification afin de vous assurer que les utilisateurs fournissent une valeur min qui est inférieur ou égale à la valeur max. Le code suivant montre comment vous pouvez mettre en œuvre ce contrôle :
from django.core import checks
from django.db import models
class RangedIntegerField(models.IntegerField):
def __init__(self, min=None, max=None, **kwargs):
super().__init__(**kwargs)
self.min = min
self.max = max
def check(self, **kwargs):
# Call the superclass
errors = super().check(**kwargs)
# Do some custom checks and add messages to `errors`:
errors.extend(self._check_min_max_values(**kwargs))
# Return all errors and warnings
return errors
def _check_min_max_values(self, **kwargs):
if self.min is not None and self.max is not None and self.min > self.max:
return [
checks.Error(
"min greater than max.",
hint="Decrease min or increase max.",
obj=self,
id="myapp.E001",
)
]
# When no error, return an empty list
return []
Si vous vouliez ajouter des contrôles à un gestionnaire de modèle, vous prendriez la même approche avec votre sous-classe de Manager
.
Si vous souhaitez ajouter un contrôle à une classe de modèle, l’approche est presque la même : la seule différence est que le contrôle est une méthode de classe, pas une méthode d’instance :
class MyModel(models.Model):
@classmethod
def check(cls, **kwargs):
errors = super().check(**kwargs)
# ... your own checks ...
return errors
Dans les versions précédentes, les moteurs de gabarits n’implémentaient pas de méthode check()
.
Écriture de tests¶
Les messages sont comparables. Cela vous permet d’écrire facilement des tests :
from django.core.checks import Error
errors = checked_object.check()
expected_errors = [
Error(
"an error",
hint="A hint.",
obj=checked_object,
id="myapp.E001",
)
]
self.assertEqual(errors, expected_errors)
Écriture de tests d’intégration¶
Étant donné le besoin d’inscrire certains contrôles au chargement de l’application, il peut être utile de tester leur intégration dans l’infrastructure des contrôles systèmes. Cela peut être fait en appelant la fonction call_command()
.
Par exemple, ce test démontre que le réglage SITE_ID
doit être un nombre entier, ce qui est fait par un contrôle intégré au système des sites:
from django.core.management import call_command
from django.core.management.base import SystemCheckError
from django.test import SimpleTestCase, modify_settings, override_settings
class SystemCheckIntegrationTest(SimpleTestCase):
@override_settings(SITE_ID="non_integer")
@modify_settings(INSTALLED_APPS={"prepend": "django.contrib.sites"})
def test_non_integer_site_id(self):
message = "(sites.E101) The SITE_ID setting must be an integer."
with self.assertRaisesMessage(SystemCheckError, message):
call_command("check")
Considérez le contrôle suivant qui émet un avertissement lors du déploiement si un réglage personnalisé nommé ENABLE_ANALYTICS
n’est pas défini à True
:
from django.conf import settings
from django.core.checks import Warning, register
@register("myapp", deploy=True)
def check_enable_analytics_is_true_on_deploy(app_configs, **kwargs):
errors = []
if getattr(settings, "ENABLE_ANALYTICS", None) is not True:
errors.append(
Warning(
"The ENABLE_ANALYTICS setting should be set to True in deployment.",
id="myapp.W001",
)
)
return errors
Sachant que ce contrôle ne produira pas de SystemCheckError
, la présence du message d’avertissement dans la sortie stderr
peut être testée comme ceci :
from io import StringIO
from django.core.management import call_command
from django.test import SimpleTestCase, override_settings
class EnableAnalyticsDeploymentCheckTest(SimpleTestCase):
@override_settings(ENABLE_ANALYTICS=None)
def test_when_set_to_none(self):
stderr = StringIO()
call_command("check", "-t", "myapp", "--deploy", stderr=stderr)
message = (
"(myapp.W001) The ENABLE_ANALYTICS setting should be set "
"to True in deployment."
)
self.assertIn(message, stderr.getvalue())