Das Django-Modell verfügt nicht über einen Mechanismus für eine optimistische ausschließliche Steuerung. (Kein Recht?) Dieser Abschnitt beschreibt ein Implementierungsbeispiel für eine optimistische exklusive Steuerung in PostgreSQL.
Hinweis)
Erstellen Sie eine Unterklasse, die den Abfrageausdruck erweitert, um die PostgreSQL-Systemspalten abzurufen.
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"', ()
Überschreiben Sie get_queryset
und fügen Sie eine Spalte mit der Zeilenversion (row_version) mit annotate
hinzu.
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
Wenn während der Parallelitätskontrolle ein Fehler auftritt, wird die folgende benutzerdefinierte Ausnahme ausgegeben.
class DbUpdateConcurrencyException(Exception):
pass
Ändern Sie die Manager-Klasse für die Parallelitätskontrolle, um die Methode "save" zu überschreiben und die Parallelitätskontrolle zu implementieren. Wenn die zu aktualisierende Zeile nicht gefunden wird, geben Sie eine "DbUpdateConcurrencyException" aus.
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)
Ändern Sie die Vererbungsquelle von "Model" in "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='Name', help_text='Name', max_length=50)
Fügen Sie eine Zeilenversion hinzu (row_version).
class CustomerSerializer(DynamicFieldsModelSerializer):
row_version = serializers.IntegerField()
class Meta:
model = Customer
fields = (
'id',
'code',
'name',
'row_version',
)
Sie können bestätigen, dass die Zeilenversion erhalten wurde.
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 wird zurückgegeben, weil aufgrund der Parallelitätskontrolle ein Fehler auftritt.
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>
Ich konnte mich erfolgreich registrieren.
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
}
Wenn nichts unternommen wird, wird ein Fehler von 500 zurückgegeben. Steuern Sie ihn daher so, dass er mit 400 zurückgegeben wird.
django-rest-framework
bietet die Möglichkeit, die Ausnahmebehandlung anzupassen.
Verwenden Sie diese Option, um die Reaktion von Fehlern bei der Parallelitätskontrolle zu steuern, die in der API-Ansicht auftreten.
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': ['Es wurde von einem anderen Benutzer geändert.']}).detail, status=status.HTTP_400_BAD_REQUEST)
return response
app/settings.py
REST_FRAMEWORK = {
'EXCEPTION_HANDLER': 'api.handlers.custom_exception_handler',
}
Es wird bei 400 in der folgenden Form zurückgegeben.
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": [
"Es wurde von einem anderen Benutzer geändert."
]
}
Recommended Posts