Django hat einen fantastischen etag/condition/last_modified-Dekorator. Es funktioniert nicht mit den DRF-Klassen-basierten Ansichten, da Sie "get" nicht mit ihnen dekorieren können. Da get ein Objekt zurückgibt, das keine http-Antwort ist, gibt es keine Möglichkeit, der Antwort den etag-Header hinzuzufügen.
Ich würde gerne einen Weg sehen, dies von drf aus zu tun. Ich denke an eine überschreibbare Methode für eine Ressource oder eine Ansicht (oder ein Mixin), die zum Generieren des E-Tags verwendet werden kann.
Die andere Möglichkeit, dies in django zu tun, besteht darin, die Middleware zu verwenden, aber sie kann die Ausführung des Hauptteils der Ansicht nicht vollständig abkürzen, wie dies der Dekorator kann.
Okay, ich habe einen vorläufigen Patch: https://github.com/schinckel/django-rest-framework/commit/cc3a88edc6be21347a9b35929d158b8831ba9bd3
Freue mich aber sehr über Feedback dazu.
Okay, anfangs habe ich folgendes geschrieben....
Cool, ja, ich würde das wirklich gerne sehen.
Coupla Gedanken - Sie sollten in der Lage sein, nur View.add_header zu verwenden, anstatt die View._ETAG, die Sie derzeit haben.
(Und es sieht so aus, als ob .add_header wahrscheinlich in die ResponseMixin-Klasse verschoben werden sollte.)
Zweitens würde ich gerne sehen, dass @condition , @etag und @last_modified decors wahrscheinlich so ziemlich ein https://github.com/django/django/blob/master/django/views/decorators sein können /http.py , ersetzt nur ein paar add_header und ErrorResponses
Aber ich habe mir die Dinge ein bisschen genauer angeschaut...
Und vielleicht ist das doch nicht der richtige Weg...
Sie können HttpResponses tatsächlich aus REST-Framework-Ansichten zurückgeben, sie erhalten nur nicht alle üblichen Inhalte zur Aushandlung/Serialisierung. Die Dekoratoren @last_modified
, @etag
und @condition
immer nur leere HttpResponses zurück, das ist also nicht wirklich ein Problem.
Ich denke also, wenn wir einfach __setitem__
__getitem__
und has_header
zur Antwortklasse hinzufügen, dann denke ich, dass Djangos vorhandene @last_modified
, @etag
und @condition
Dekoratoren sollten in einer REST-Framework-Ansicht gut funktionieren, solange_ die Ansicht den Stil return Response(status, data)
und nicht den Stil return data
.
Offensichtlich wäre es hilfreich, wenn wir das dokumentieren würden, aber es könnte sinnvoller sein, als etwas replizieren zu müssen, das Django bereits tut.
Was denkst du?
Es kann ein Problem bei der Verwendung der Django-Dekoratoren geben: Ich bin nicht sicher, ob sie mit Methoden arbeiten, nur mit bloßen Funktionen. Der von mir geschriebene Decorator basierte stark auf einem, den ich nur für diesen Fall über StackOverflow gefunden habe.
Das kann nicht der Fall sein, in diesem Fall klingt diese Lösung überlegen.
Trotzdem habe ich den Rückgabedatenstil zurückgegeben, da er weniger Boilerplate ist, und ich gebe normalerweise nur Objekte zurück, die ich als Json serialisieren möchte. Vielleicht können wir es mit beiden Möglichkeiten zum Laufen bringen.
Eine andere Option kann ein Mixin sein, das diese der View-Klasse hinzufügt.
Mir fällt auch ein, dass die Django-Dekoratoren in Bezug auf bedingte PUT-, POST- und DELETE-Anforderungen möglicherweise nicht das Richtige tun. Habe gerade einen Patch an sinatra gesendet, um dieses Problem zu beheben.
Ignorieren Sie das letzte Bit: Offensichtlich hatte ich den Code nicht richtig gelesen.
Tatsächlich gibt es da einen Punkt: Diese Dekorateure funktionieren möglicherweise nicht mit _methods_, da sie das zusätzliche Argument 'self' haben. Ich werde das prüfen und eventuell ein Ticket bei Django einreichen, da die auch mit CBVs funktionieren sollten...
Ah, okay - ich sehe jetzt den @method_decorator ... https://docs.djangoproject.com/en/dev/topics/class-based-views/#decorating -class-based-views
Ich denke also, dass wir Folgendes brauchen, damit dies geschlossen wird:
Ich bin mir sicher, dass ich in diesem Dokument nachgesehen habe, aber ich habe diesen Dekorateur nicht gesehen!
Ich habe versucht, den Django-Decorator zu verwenden, und das anfängliche Ergebnis war wirklich seltsam, mit Informationen, die nur in die Ansichten gehören sollten, die in nicht verwandten etag-Funktionen erscheinen.
Nachdem ich ein paar Stunden versucht hatte, es ein wenig besser zu machen, kam ich zu einer Lösung, die meine Probleme löste und allgemein genug erscheint. Was ist deine Meinung dazu:
https://bitbucket.org/vitormazzi/django-rest-framework/changeset/6f8de4500c6f
In der Hoffnung, dieser Ausgabe etwas neues Leben einzuhauchen, jetzt, da wir bei den 2.x-Releases sind.
Meine Gedanken hier sind größtenteils zusammengeschustert, weil ich versucht habe, die Funktionalität in einem Projekt hinzuzufügen und diesen Beitrag zu lesen, also definitiv noch grob.
Ich sehe zwei Bereiche, in denen DRF ETags berücksichtigen muss - die Verwendung in Ansichten und wie man die eindeutige Darstellung der Version einer Instanz erhält.
WERDEN
GET-Anforderungen müssen lediglich die ETag-Objekte im entsprechenden Header bereitstellen. Eine einzeilige Änderung in RetrieveModelMixin
kann dies leicht hinzufügen:
def retrieve(self, request, *args, **kwargs):
self.object = self.get_object()
serializer = self.get_serializer(self.object)
headers = {'ETag': self.object.etag}
return Response(serializer.data, headers=headers)
EINSETZEN, PATCHEN, LÖSCHEN
Eine allgemeine Überprüfung zum Aktualisieren von HTTP-Verben könnte in dispatch
der Ansicht durchgeführt oder möglicherweise in eine andere Methode gezogen werden, da überprüft werden muss, ob ETags aktiviert sind (siehe Abschnitt Optionen unten):
header_etag = request.META.get('HTTP_IF_MATCH')
if header_etag is None:
return Response({'error': 'IF_MATCH header is required'}, status=400)
Dann eine detailliertere Prüfung nach dem Abrufen des Objekts, um zu sehen, ob die Anfrage denkt, dass sie das richtige Objekt betrachtet:
if self.object.etag != header_etag:
return Response({'error': 'object has been updated since you last saw it'}, status=412)
Ich denke nicht, dass die tatsächliche Generierung des ETags eines Objekts das Problem von DRF sein sollte. Ich habe mit der Epochenzeit des updated
Felds meines Objekts getestet, aber ich konnte leicht erkennen, dass dies komplexer sein muss.
Ich schlage vor, dass DRF standardmäßig nach obj.etag
sucht, aber es ist mit dem normalen CBV-Flow konfigurierbar, zB get_etag()
und etag_var = 'get_my_objects_etag'
.
Wir müssen auch erzwingen, dass ETags von Objekten als String abgerufen werden, da wir mit einem Header vergleichen und versuchen, den Typ zu interpretieren, wäre bestenfalls mühsam.
use_etags
(oder etwas Ähnliches) - ein boolescher etag_var
- String eines Funktionsnamens, den wir getattr
für das fragliche Objekt verwenden können@ghickman - Ich möchte, dass das Verhalten zum Bestimmen von ETags und LastModified ähnlich wie bei den anderen Pluggable-Klassen aussieht. Dh. Habe sowas wie:
class MyView(views.APIView):
cache_lookup_classes = []
Das Caching von Signaturen sollte sowohl mit ETags als auch mit LastModified umgehen, und es gibt zwei verschiedene Dinge, die wir bereitstellen möchten:
Es würde ein BaseCacheLookup
, mit zwei Methodensignaturen, die etwa so aussehen könnten:
.object_etag_and_last_modified(self, view, obj)
.preemptive_etag_and_last_modified(self, view, request, *view_kwargs, **view_kwargs)
Ein gegebenes Objekt gibt ein Zwei-Tupel von
(etag, last modified)
, von denen jedes einfach keins sein kann.
Wenn die eingehende Anfrage einen übereinstimmenden If-Modified-Since- oder If-None-Match-Header enthält, wird eine 304 Not Modified-Antwort zurückgegeben. Wenn die eingehende Antwort eine übereinstimmende If-Match- oder If-Unmodified-Seit-Antwort enthält, wird eine 412-Vorbedingungs-Fehlgeschlagen-Antwort zurückgegeben.
Dies würde eine CacheLookupClass ermöglichen, die der von Ihnen beschriebenen Implementierung entspricht, aber auch andere Varianten.
Sie können auch mehrere Cache-Lookup-Klassen mit unterschiedlicher zuletzt geänderter Granularität anwenden, z.
umfassen ein GlobalLastModifiedLookup
auf ein zusätzlich ObjectETagLookup
. Dies würde es der Ansicht ermöglichen, präventiv zurückzukehren, bevor Datenbankaufrufe ausgeführt werden, wenn seit der zwischengespeicherten Kopie keine Schreibvorgänge vorgenommen wurden. (Selbst wirklich grundlegende Richtlinien wie diese können einen großen Unterschied machen, wenn Sie serverseitiges Caching mit Varnish verwenden.)
Klingt die steckbare Klasse davon für Sie vernünftig?
Ich hatte nicht an LastModified gedacht, da ich es in meiner aktuellen Implementierung nicht verwende, aber es ist definitiv sinnvoll, es aufgrund seines Zwecks einzubeziehen.
Die Pluggable-Klassen klingen nach einer großartigen Idee, insbesondere wenn wir LastModified- und ETag-Implementierungen als grundlegende Beispiele einbeziehen. Ich mag die Idee, dass das GET-Caching mit minimalen Änderungen an einem Projekt super einfach zu aktivieren wäre.
Ich würde es vorziehen, die etag- und last_modified-Generierung in zwei Methoden (mit diesen Namen) aufzuteilen, die, wie Sie vorgeschlagen haben, None
wenn sie nicht implementiert sind. CacheLookup-Back-Ends könnten sich dann entscheiden, das eine und/oder das andere zu implementieren. Wir könnten der Einfachheit halber immer eine Hilfsmethode anbieten ( cachable_obj_repr
oder unique_obj_repr
vielleicht?), die beides kombiniert, wenn Sie es für nützlich halten.
tl;dr ja die Pluggable-Klasse-Seite klingt vernünftig und sollte viel mehr Flexibilität bieten. Ich freue mich, den Patch dafür zu schreiben.
Hallo an alle. Wenn Sie interessiert sind, habe ich einen anderen Ansatz für die Etag-Unterstützung in meiner Erweiterungsbibliothek http://chibisov.github.io/drf-extensions/docs/ implementiert.
@chibisov Neato. Wir sollten #1019 wirklich beenden, damit wir irgendwo in der Dokumentation einen Link zu Paketen wie diesem haben.
Das Schließen als #1019 wurde geschlossen und das Paket von @chibisov wird aufgelistet.
Dieser wurde absichtlich als 3.3 markiert, da ich möchte, dass wir irgendwann eine formelle Richtung dazu geben. Ich bin nicht sehr besorgt, wenn wir dies geschlossen lassen, aber es war auf meiner internen Roadmap.
Leider ist die Standardimplementierung und Dokumentation der Etag-Funktionalität in drf-Erweiterungen einfach falsch und gefährlich fehlerhaft. Es ändert das Etag, wenn sich die _Anfrage_ ändert, nicht wenn sich die _Antwort_ ändert. Das ist genau das, was Sie für serverseitiges Caching wollen, und genau das, was Sie für ein Etag nicht wollen.
@mbox Das Beste wäre, ein Problem dazu auf drf-Erweiterungen zu eröffnen oder wenn Sie denken, dass es sich um ein DRF-"Kern"-Problem handelt, das hier offen ist. Bitte beachten Sie, dass ein nicht bestandener Test ein guter Anfang für uns wäre, das Problem zu untersuchen.
@mbox @xordoquy
Ich habe gerade eine PR an drf-extensions (https://github.com/chibisov/drf-extensions/pull/171) gesendet, die eine optimistische Parallelitätskontrolle für die Manipulation von Ressourcen über die DRF-API ermöglicht. Es verwendet einen semantischen Hash aller Objektfelder und ich habe eine Test-App zu Demonstrationszwecken eingefügt. Es wurde gegen DRF>=3.3.1 und django>=1.8 mit Python 2.7, 3.4, 3.5 getestet.
Danke
Nur eine Anmerkung für die zukünftigen Leser - Ich habe ein kleines Paket erstellt, um bedingte Dekoratoren von Django zusammen mit DRF zu verwenden. Also bei Interesse:
https://github.com/jozo/django-rest-framework-condition
Hilfreichster Kommentar
Leider ist die Standardimplementierung und Dokumentation der Etag-Funktionalität in drf-Erweiterungen einfach falsch und gefährlich fehlerhaft. Es ändert das Etag, wenn sich die _Anfrage_ ändert, nicht wenn sich die _Antwort_ ändert. Das ist genau das, was Sie für serverseitiges Caching wollen, und genau das, was Sie für ein Etag nicht wollen.