Funktionsweise und Erstellung von benutzerdefinierten Django-Dekoratoren

Dekoratoren sind leistungsstarke Werkzeuge in Django, mit denen Sie gemeinsam genutzte Logik einfach zu View-Funktionen oder -Klassen hinzufügen können. In diesem Artikel erkläre ich Ihnen schrittweise die Funktionsweise und die Erstellungsweise von Dekoratoren, damit Sie in der Lage sind, Ihre eigenen benutzerdefinierten Dekoratoren zu schreiben.

1. Was ist ein Dekorator?

Ein Dekorator ist eine höherwertige Funktion in Python. Das bedeutet, dass er eine Funktion als Argument entgegennimmt und innerhalb von ihr eine neue Funktion zurückgibt.

  • Rolle des Dekorators:
    • Er kann eine Funktion umfassen, um bestimmte Funktionalität hinzuzufügen oder zu ändern.
    • Er macht den Code wiederverwendbar und bündig.

2. Grundlegende Funktionsweise von Dekoratoren

Um die Funktionsweise von Dekoratoren zu verstehen, betrachten wir ein einfaches Beispiel.

def simple_decorator(func):
    def wrapper():
        print("Bevor die Funktion ausgeführt wird")
        func()
        print("Nachdem die Funktion ausgeführt wurde")
    return wrapper

@simple_decorator
def say_hello():
    print("Hallo!")

say_hello()

Ausgabe:

Bevor die Funktion ausgeführt wird
Hallo!
Nachdem die Funktion ausgeführt wurde

Erklärung:

  1. @simple_decorator übergibt die Funktion say_hello an die Funktion simple_decorator.
  2. simple_decorator erzeugt intern eine neue Funktion wrapper und gibt sie zurück.
  3. Wenn say_hello aufgerufen wird, wird anstelle der ursprünglichen Funktion wrapper ausgeführt.
  4. Im Inneren der wrapper-Funktion werden zusätzliche Aktionen (Drucknachricht) ausgeführt und die ursprüngliche Funktion wird aufgerufen.

3. Prinzipien zur Erstellung benutzerdefinierter Dekoratoren in Django

Um Dekoratoren in Django zu erstellen, müssen Sie mit dem Request-Objekt der View-Funktion umgehen können. Damit können Sie je nach bestimmten Bedingungen HTTP-Antworten manipulieren oder Logik ausführen.

4. Schritt-für-Schritt-Struktur zur Erstellung eines benutzerdefinierten Dekorators

Hier ist die grundlegende Struktur zur Erstellung eines benutzerdefinierten Dekorators in Django:

  1. functools.wraps verwenden: Verwenden Sie @wraps, um Metadaten wie den Namen der ursprünglichen Funktion und die Dokumentationszeichenfolgen beizubehalten.
  2. Logik basierend auf request hinzufügen: Schreiben Sie Logik, um das Request-Objekt zu analysieren oder zu validieren.
  3. Die ursprüngliche Funktion aufrufen oder eine alternative Antwort zurückgeben: Sie können die Aufrufe der ursprünglichen Funktion unter bestimmten Bedingungen blockieren und andere Antworten zurückgeben.

Hier ist ein Beispielcode:

from functools import wraps
from django.http import HttpResponseForbidden

def custom_decorator(view_func):
    @wraps(view_func)
    def _wrapped_view(request, *args, **kwargs):
        # Überprüfen Sie Benutzerinformationen im Request-Objekt
        if not request.user.is_authenticated:
            return HttpResponseForbidden("Sie müssen eingeloggt sein, um auf diese Seite zuzugreifen.")
        
        # Rufe die ursprüngliche View-Funktion auf
        return view_func(request, *args, **kwargs)
    
    return _wrapped_view

Eine visuell ansprechende Illustration, die erklärt, wie ein benutzerdefinierter Dekorator in Django funktioniert

5. Wie fügt man Parameter zu einem Dekorator hinzu?

Wenn ein Dekorator Parameter annehmen soll, muss eine zweiseitige Funktionsstruktur verwendet werden. Der Grund dafür ist, dass die erste Funktion verwendet wird, um die Parameter des Dekorators zu behandeln, während die zweite Funktion das tatsächliche Verpacken der View-Funktion übernimmt.

Gr Grundstruktur:

def decorator_with_params(param1, param2):
    def actual_decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            # Logik unter Verwendung der Parameter param1 und param2
            return view_func(request, *args, **kwargs)
        return _wrapped_view
    return actual_decorator

Erklärung:

  1. Die erste Funktion decorator_with_params behandelt die Parameter des Dekorators (param1, param2).
  2. Sie gibt die Dekoratorfunktion (actual_decorator) zurück, die die tatsächliche View-Funktion umschließt.
  3. Innerhalb von actual_decorator wird die ursprüngliche View-Funktion aufgerufen oder zusätzliche Logik ausgeführt.

6. Praktisches Beispiel: Zeitbeschränkungs-Dekorator

Hier ist ein Beispiel für einen Dekorator, der nur zu bestimmten Zeiten zugänglich ist. Dieser Dekorator verarbeitet erlaubte Zeitspannen als Parameter.

from datetime import datetime
from django.http import HttpResponseForbidden

def time_restricted_access(start_hour, end_hour):
    def decorator(view_func):
        @wraps(view_func)
        def _wrapped_view(request, *args, **kwargs):
            current_hour = datetime.now().hour
            if not (start_hour <= current_hour < end_hour):
                return HttpResponseForbidden("Diese Seite ist nur zu bestimmten Zeiten zugänglich.")
            return view_func(request, *args, **kwargs)
        return _wrapped_view
    return decorator

@time_restricted_access(9, 17)  # Erlaubt von 9 Uhr bis 17 Uhr
def working_hours_view(request):
    return render(request, 'working_hours.html')

Funktionsweise Erklärung:

  1. time_restricted_access ist die erste Funktion, die die Parameter (start_hour, end_hour) verarbeitet.
  2. Die erste Funktion gibt intern die Dekoratorfunktion zurück, die die View umschließt.
  3. Die Dekoratorfunktion akzeptiert den request und prüft die aktuelle Stunde (datetime.now().hour) und je nach zeitlichen Kriterien:
    • Wenn die Bedingungen erfüllt sind, wird die View aufgerufen und das Ergebnis zurückgegeben.
    • Wenn die Bedingungen nicht erfüllt sind, wird der Zugang mit HttpResponseForbidden blockiert.

Warum müssen Funktionen zweimal umschlossen werden?

Die Funktionen müssen zweimal umschlossen werden, um die Rollen der Parameterverarbeitung und der View-Funktionsverarbeitung zu trennen.

  • Die erste Funktion: Nimmt die Parameter (start_hour, end_hour) entgegen und generiert intern den Dekorator.
  • Die zweite Funktion: Der generierte Dekorator umschließt die View-Funktion und funktioniert basierend auf der Anfrage.

Diese Struktur macht Dekoratoren flexibel und wiederverwendbar.

7. Verwendung in Klassenbasierten Views (CBV)

Für klassenbasierte Views müssen Sie method_decorator verwenden.

from django.utils.decorators import method_decorator
from django.views import View

@method_decorator(time_restricted_access(9, 17), name='dispatch')
class MyView(View):
    def get(self, request):
        return render(request, 'my_template.html')

8. Fazit: Hinweise zur Erstellung benutzerdefinierter Dekoratoren

  1. Einfachheit bewahren: Die Logik des Dekorators sollte einfach und klar sein.
  2. Wiederverwendbarkeit berücksichtigen: Wenn bestimmte Logik in mehreren Views verwendet wird, extrahieren Sie sie in einen Dekorator.
  3. Parameterverarbeitung: Verwenden Sie die zweiseitige Struktur, wenn Parameter erforderlich sind.
  4. Metadaten bewahren: Verwenden Sie @wraps, um die Metadaten der ursprünglichen Funktion beizubehalten.

Schlussfolgerung

Jetzt haben Sie verstanden, wie benutzerdefinierte Dekoratoren in Django funktionieren und sind in der Lage, solche zu erstellen, die Parameter entgegennehmen. Nutzen Sie diesen Artikel, um Dekoratoren zu schreiben, die zu Ihrem Projekt passen!