Ramverk för systemkontroll¶
Ramverket System Check är en uppsättning statiska kontroller för validering av Django-projekt. Det upptäcker vanliga problem och ger tips om hur man åtgärdar dem. Ramverket är utbyggbart så att du enkelt kan lägga till dina egna kontroller.
Kontroller kan utlösas explicit via kommandot check
. Kontroller utlöses implicit före de flesta kommandon, inklusive runserver
och migrate
. Av prestandaskäl körs inte kontrollerna som en del av WSGI-stacken som används vid distributionen. Om du behöver köra systemkontroller på din distributionsserver ska du utlösa dem explicit med check
.
Allvarliga fel förhindrar Django-kommandon (t.ex. runserver
) från att köras överhuvudtaget. Mindre problem rapporteras till konsolen. Om du har undersökt orsaken till en varning och gärna ignorerar den, kan du dölja specifika varningar med hjälp av inställningen SILENCED_SYSTEM_CHECKS
i projektets inställningsfil.
En fullständig lista över alla kontroller som kan ställas in av Django finns i Systemkontrollreferens.
Skriva ut dina egna checkar¶
Ramverket är flexibelt och låter dig skriva funktioner som utför alla andra typer av kontroller som du kan tänkas behöva. Följande är ett exempel på en stubkontrollfunktion:
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
Check-funktionen måste acceptera ett app_configs
-argument; detta argument är listan över applikationer som ska inspekteras. Om None
, måste kontrollen köras på alla installerade appar i projektet.
Kontrollen får ett argument i form av nyckelordet databases
. Detta är en lista över databasalias vars anslutningar kan användas för att inspektera konfigurationen på databasnivå. Om databases
är None
får kontrollen inte använda några databasanslutningar.
Argumentet **kwargs
är nödvändigt för framtida expansion.
Meddelanden¶
Funktionen måste returnera en lista med meddelanden. Om inga problem hittas som ett resultat av kontrollen, måste check-funktionen returnera en tom lista.
De varningar och fel som checkmetoden ger upphov till måste vara instanser av CheckMessage
. En instans av CheckMessage
kapslar in ett enda rapporterbart fel eller varning. Det ger också sammanhang och tips som är tillämpliga på meddelandet och en unik identifierare som används för filtreringsändamål.
Konceptet är mycket likt meddelanden från message framework eller logging framework. Meddelanden taggas med en nivå
som anger hur allvarligt meddelandet är.
Det finns också genvägar som gör det enklare att skapa meddelanden med gemensamma nivåer. När du använder dessa klasser kan du utelämna argumentet level
eftersom det är underförstått av klassnamnet.
Registrering och märkning av kontroller¶
Slutligen måste din kontrollfunktion registreras uttryckligen med systemets kontrollregister. Kontroller bör registreras i en fil som laddas när din applikation laddas; till exempel i metoden AppConfig.ready()
.
- register(*tags)(function)¶
Du kan skicka så många taggar till register
som du vill för att märka din kontroll. Att märka kontroller är användbart eftersom det gör att du bara kan köra en viss grupp av kontroller. Om du t.ex. vill registrera en kompatibilitetskontroll gör du följande anrop:
from django.core.checks import register, Tags
@register(Tags.compatibility)
def my_check(app_configs, **kwargs):
# ... perform compatibility checks and collect errors
return errors
Du kan registrera ”deployment checks” som endast är relevanta för en produktionsinställningsfil på följande sätt:
@register(Tags.security, deploy=True)
def my_check(app_configs, **kwargs): ...
Dessa kontroller kommer endast att köras om alternativet check --deploy
används.
Du kan också använda register
som en funktion i stället för en dekorator genom att skicka ett anropsbart objekt (vanligtvis en funktion) som första argument till register
.
Koden nedan är likvärdig med koden ovan:
def my_check(app_configs, **kwargs): ...
register(my_check, Tags.security, deploy=True)
Kontroll av fält, modell, chef, mallmotor och databas¶
I vissa fall behöver du inte registrera din checkfunktion - du kan använda dig av en befintlig registrering.
Fält, modeller, modellhanterare, mallmotorer och databasbackends implementerar alla en check()
-metod som redan är registrerad med check-ramverket. Om du vill lägga till extra kontroller kan du utöka implementeringen i basklassen, utföra de extra kontroller du behöver och lägga till eventuella meddelanden till dem som genereras av basklassen. Det rekommenderas att du delegerar varje kontroll till separata metoder.
Tänk på ett exempel där du implementerar ett anpassat fält med namnet RangedIntegerField
. Det här fältet lägger till argumenten min
och max
i konstruktören för IntegerField
. Du kanske vill lägga till en kontroll för att se till att användarna anger ett min-värde som är mindre än eller lika med max-värdet. Följande kodutdrag visar hur du kan implementera denna kontroll:
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 []
Om du vill lägga till kontroller till en modellhanterare skulle du använda samma tillvägagångssätt på din underklass av Manager
.
Om du vill lägga till en kontroll i en modellklass är tillvägagångssättet nästan detsamma: den enda skillnaden är att kontrollen är en klassmetod, inte en instansmetod:
class MyModel(models.Model):
@classmethod
def check(cls, **kwargs):
errors = super().check(**kwargs)
# ... your own checks ...
return errors
I äldre versioner implementerade inte mallmotorer en check()
-metod.
Skriva tester¶
Meddelanden är jämförbara. Det gör att du enkelt kan skriva tester:
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)
Skriva integrationstester¶
Med tanke på behovet av att registrera vissa kontroller när applikationen laddas kan det vara användbart att testa deras integration inom ramverket för systemkontroller. Detta kan åstadkommas genom att använda funktionen call_command()
.
Det här testet visar till exempel att inställningen SITE_ID
måste vara ett heltal, en inbyggd check från sites-ramverket:
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")
Tänk på följande kontroll som ger en varning vid distribution om en anpassad inställning med namnet ENABLE_ANALYTICS
inte är inställd på 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
Med tanke på att denna kontroll inte kommer att ge upphov till ett SystemCheckError
, kan förekomsten av varningsmeddelandet i stderr
-utdata hävdas på följande sätt:
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())