Le modèle Django n'a pas de mécanisme de contrôle exclusif optimiste. (Aucun droit?) Cette section décrit un exemple d'implémentation pour un contrôle exclusif optimiste dans PostgreSQL.
xmin
pour le champ de gestion de la version de ligne (row_version)Remarque)
Créez une sous-classe qui étend l'expression de requête pour obtenir les colonnes système PostgreSQL.
from django.db.models import Expression, PositiveIntegerField
class XMin(Expression):
output_field = PositiveIntegerField()
def as_postgresql(self, compiler, connection):
return f'"{compiler.query.base_table}"."xmin"', ()
Remplacez get_queryset
et ajoutez une colonne de version de ligne (row_version) avec ʻannotate`.
from django.db.models import Manager
class ConcurrentManager(Manager):
def get_queryset(self):
super_query = super().get_queryset().annotate(row_version=XMin())
return super_query
Si une erreur se produit pendant le contrôle d'accès concurrentiel, l'exception personnalisée suivante sera émise.
class DbUpdateConcurrencyException(Exception):
pass
Modifiez la classe du gestionnaire de contrôle d'accès concurrentiel pour remplacer la méthode save
et implémenter le contrôle d'accès concurrentiel.
Si la ligne à mettre à jour n'est pas trouvée, émettez une exception DbUpdateConcurrencyException
.
from django.db.models import Model
class ConcurrentModel(Model):
objects = ConcurrentManager()
class Meta:
abstract = True
base_manager_name = 'objects'
def save(self, **kwargs):
cls = self.__class__
if self.pk and not kwargs.get('force_insert', None):
rows = cls.objects.filter(
pk=self.pk, row_version=self.row_version)
if not rows:
raise DbUpdateConcurrencyException(cls.__name__, self.pk)
super().save(**kwargs)
Modifiez la source d'héritage de «Model» à «ConcurrentModel».
class Customer(ConcurrentModel):
id = models.UUIDField(primary_key=True, default=uuid4, editable=False)
code = models.CharField(verbose_name='code', help_text='code', max_length=10)
name = models.CharField(verbose_name='Nom', help_text='Nom', max_length=50)
Ajoutez une version de ligne (row_version).
class CustomerSerializer(DynamicFieldsModelSerializer):
row_version = serializers.IntegerField()
class Meta:
model = Customer
fields = (
'id',
'code',
'name',
'row_version',
)
Vous pouvez confirmer que la version de ligne a été obtenue.
curl -s -X GET "http://localhost:18000/api/customers/" -H "accept: application/json" | jq .
[
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1",
"code": "001",
"name": "test1",
"row_version": 588
},
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx2",
"code": "002",
"name": "test2",
"row_version": 592
}
]
curl -s -X GET "http://localhost:18000/api/customers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1/" -H "accept: application/json" | jq .
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1",
"code": "001",
"name": "test",
"row_version": 588
}
500 est renvoyé car une erreur se produit en raison du contrôle d'accès concurrentiel.
curl -X PUT "http://localhost:18000/api/customers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1/" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"code\": \"001\", \"name\": \"test2\", \"row_version\": 0}"
<!doctype html>
<html lang="en">
<head>
<title>Server Error (500)</title>
</head>
<body>
<h1>Server Error (500)</h1><p></p>
</body>
</html>
J'ai pu m'inscrire avec succès.
curl -X PUT "http://localhost:18000/api/customers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1/" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"code\": \"001\", \"name\": \"test2\", \"row_version\": 588}" | jq .
{
"id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1",
"code": "001",
"name": "test2",
"row_version": 588
}
Si rien n'est fait, il reviendra avec une erreur 500, alors contrôlez-le pour qu'il revienne avec 400.
django-rest-framework
a la capacité de personnaliser la gestion des exceptions.
Utilisez cette option pour contrôler la réponse aux erreurs de contrôle d'accès concurrentiel qui se produisent dans la vue API.
api/handlers.custom_exception_handler
from rest_framework import status
from rest_framework.validators import ValidationError
from rest_framework.response import Response
from rest_framework.views import exception_handler
from xxxxx import DbUpdateConcurrencyException
def custom_exception_handler(exc, context):
response = exception_handler(exc, context)
if isinstance(exc, DbUpdateConcurrencyException):
return Response(ValidationError({'db_update_concurrency': ['Il a été modifié par un autre utilisateur.']}).detail, status=status.HTTP_400_BAD_REQUEST)
return response
app/settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'api.handlers.custom_exception_handler',
}
Il sera retourné à 400 sous la forme suivante.
curl -X PUT "http://localhost:18000/api/customers/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx1/" -H "accept: application/json" -H "Content-Type: application/json" -d "{ \"code\": \"001\", \"name\": \"test2\", \"row_version\": 0}"
{
"db_update_concurrency": [
"Il a été modifié par un autre utilisateur."
]
}
Recommended Posts