Django-rest-framework: Einfach E-Tags hinzufügen/validieren.

Erstellt am 29. Juni 2011  ·  24Kommentare  ·  Quelle: encode/django-rest-framework

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.

Enhancement

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.

Alle 24 Kommentare

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:

  1. Ein bisschen Anpassung an Response
  2. Etwas Lichtdokumentation

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.

Ansichten

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)

Eindeutige Darstellung einer Instanzversion

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.

Optionen

  • Globale Einstellung (wie bei Serializern usw.), um die Verwendung von ETags ein- oder auszuschalten.
  • Zwei Einstellungen für Ansichten:

    • 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:

  • Bestimmen Sie ein Etag und/oder eine letzte Änderung bei einer Objektinstanz.
  • Bestimmen Sie präventiv ein Etag und/oder eine letzte Änderung angesichts der eingehenden Anfrage.

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

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen