Wie registriere ich Benutzer im Django REST Framework?

77

Ich codiere eine REST-API mit dem Django REST-Framework . Die API wird das Backend einer Social Mobile App sein. Nachdem ich dem Tutorial gefolgt bin, kann ich alle meine Modelle serialisieren und neue Ressourcen erstellen und aktualisieren.

Ich verwende AuthToken zur Authentifizierung.

Meine Frage ist:

Sobald ich die /usersRessource habe, möchte ich, dass sich der App-Benutzer registrieren kann. Ist es also besser, eine separate Ressource zu haben /registeroder anonymen Benutzern zu erlauben, auf /userseine neue Ressource zu posten ?

Außerdem wäre eine Anleitung zu Berechtigungen großartig.

chaim
quelle

Antworten:

49

Ich habe meine eigene benutzerdefinierte Ansicht für die Registrierung erstellt, da mein Serializer nicht erwartet, dass das Kennwort angezeigt / abgerufen wird. Ich habe die URL von der Ressource / users unterschieden.

Meine URL conf:

url(r'^users/register', 'myapp.views.create_auth'),

Meine Sicht:

@api_view(['POST'])
def create_auth(request):
    serialized = UserSerializer(data=request.DATA)
    if serialized.is_valid():
        User.objects.create_user(
            serialized.init_data['email'],
            serialized.init_data['username'],
            serialized.init_data['password']
        )
        return Response(serialized.data, status=status.HTTP_201_CREATED)
    else:
        return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)

Ich kann mich irren, aber es scheint nicht so, als müssten Sie die Berechtigungen für diese Ansicht einschränken, da Sie nicht authentifizierte Anforderungen wünschen ...

Cahlan Sharp
quelle
12
Ich wollte darauf hinweisen, dass Cahlan init_data anstelle von Daten verwendet, weil er UserSerializerkeine Passwörter liest / schreibt. Nach dem Anruf is_valid()ist es in Ordnung, wenn jemand verwenden möchte serialized.data['email']und serialized.data['username']das Passwort nur in verfügbar ist serialized.init_data['password']. Auch die Reihenfolge der E-Mail- und Benutzernamenparameter sollte geändert werden (zumindest in Django 1.6). oder Sie können immer benannte Parameter übergeben, zBUser.objects.create_user(email='[email protected]', username='admin', password='admin123')
Nur neugierig, wäre diese Lösung nicht unsicher? Dies bedeutet, dass jeder Körper mit Kenntnis dieses Endpunkts und weiterhin Benutzer registrieren?
DjangoRocks
1
@ DjangoRocks Sie haben Recht, aber Sie können Drosselung verwenden
Yossi
1
@yossi Die Lösung besteht darin, CAPTCHA zu verwenden. Durch die Drosselung wird das Problem nicht vollständig behoben.
Purrell
Gibt es eine Möglichkeit, den Benutzernamen als E-Mail in serialisierte Daten einzufügen?
Hardik Gajjar
93

Django REST Framework 3 ermöglicht die Überschreibungsmethode createin Serialisierern:

from rest_framework import serializers
from django.contrib.auth import get_user_model # If used custom user model

UserModel = get_user_model()


class UserSerializer(serializers.ModelSerializer):

    password = serializers.CharField(write_only=True)

    def create(self, validated_data):

        user = UserModel.objects.create(
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()

        return user

    class Meta:
        model = UserModel
        # Tuple of serialized model fields (see link [2])
        fields = ( "id", "username", "password", )

Serialisierte Felder für Klassen, von denen geerbt wurde, ModelSerializermüssen Metafür Django Rest Framework v3.5 und die neueste Version offen deklariert werden .

Datei api.py :

from rest_framework import permissions
from rest_framework.generics import CreateAPIView
from django.contrib.auth import get_user_model # If used custom user model

from .serializers import UserSerializer


class CreateUserView(CreateAPIView):

    model = get_user_model()
    permission_classes = [
        permissions.AllowAny # Or anon users can't register
    ]
    serializer_class = UserSerializer
Dunaevsky Maxim
quelle
2
Dies ist der schnellste und aktuellste Weg, dies zu tun.
SeedyROM
Warum verwenden Sie user.set_password, anstatt den Schlüsselwortparameter password in UserModel.objects.create () festzulegen?
Personjerry
Ah, egal, ich sehe, dass Sie create_user nicht verwendet haben, das das Passwort-Hashing behandelt
personjerry
Wie kann man der Registrierung zusätzliche Felder hinzufügen?
Über den
41

Die einfachste Lösung, die in DRF 3.x funktioniert:

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username', 'password', 'email', 'first_name', 'last_name')
        write_only_fields = ('password',)
        read_only_fields = ('id',)

    def create(self, validated_data):
        user = User.objects.create(
            username=validated_data['username'],
            email=validated_data['email'],
            first_name=validated_data['first_name'],
            last_name=validated_data['last_name']
        )

        user.set_password(validated_data['password'])
        user.save()

        return user

Es sind keine weiteren Änderungen erforderlich. Stellen Sie lediglich sicher, dass nicht authentifizierte Benutzer die Berechtigung zum Erstellen eines neuen Benutzerobjekts haben.

write_only_fieldsstellt sicher, dass Passwörter (tatsächlich: ihr von uns gespeicherter Hash) nicht angezeigt werden, während die überschriebene createMethode sicherstellt, dass das Passwort nicht im Klartext, sondern als Hash gespeichert wird.

cpury
quelle
Entschuldigung, wenn ich falsch liege, aber ist es unbedingt erforderlich, die Erstellungsmethode zu überschreiben? Ich habe versucht, nur die Felder write_only_fields und read_only_fields hinzuzufügen, und es hat so funktioniert, wie ich es erwartet hatte. Irgendwelche Hinweise?
Vabada
8
@dabad Wenn Sie das tun, wird das Passwort wahrscheinlich im Klartext in der Datenbank gespeichert, was Sie absolut nicht wollen. Die einzige Zeile, die die benutzerdefinierte createMethode hinzufügt, ist die Django-native set_passwordMethode zum Generieren eines Hashs für das Kennwort.
cpury
33

Normalerweise behandle ich die Benutzeransicht wie jeden anderen API-Endpunkt, für den eine Autorisierung erforderlich ist, außer dass ich nur den Berechtigungssatz der Ansichtsklasse mit meinem eigenen für POST (auch bekannt als create) überschreibe. Ich benutze normalerweise dieses Muster:

from django.contrib.auth import get_user_model
from rest_framework import viewsets
from rest_framework.permissions import AllowAny


class UserViewSet(viewsets.ModelViewSet):
    queryset = get_user_model().objects
    serializer_class = UserSerializer

    def get_permissions(self):
        if self.request.method == 'POST':
            self.permission_classes = (AllowAny,)

        return super(UserViewSet, self).get_permissions()

Hier ist der Serializer, den ich normalerweise verwende:

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = get_user_model()
        fields = (
            'id',
            'username',
            'password',
            'email',
            ...,
        )
        extra_kwargs = {
            'password': {'write_only': True},
        }

    def create(self, validated_data):
        user = get_user_model().objects.create_user(**validated_data)
        return user

    def update(self, instance, validated_data):
        if 'password' in validated_data:
            password = validated_data.pop('password')
            instance.set_password(password)
        return super(UserSerializer, self).update(instance, validated_data)

djangorestframework 3.3.x / Django 1.8.x.

digitalfoo
quelle
1
Ich habe mir alle Antworten angesehen und es scheint, dass jeder vorschlägt, im Serialisierer eine Logik zum Speichern von Modellen zu erstellen. Ich denke, dies verstößt gegen die MVVM-Richtlinien von Django, nach denen die "Controller" -Logik angezeigt werden sollte
Oleg Belousov,
27

Ich habe Cahlans Antwort aktualisiert, um benutzerdefinierte Benutzermodelle von Django 1.5 zu unterstützen, und die Benutzer-ID in der Antwort zurückgegeben.

from django.contrib.auth import get_user_model

from rest_framework import status, serializers
from rest_framework.decorators import api_view
from rest_framework.response import Response

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = get_user_model()

@api_view(['POST'])
def register(request):
    VALID_USER_FIELDS = [f.name for f in get_user_model()._meta.fields]
    DEFAULTS = {
        # you can define any defaults that you would like for the user, here
    }
    serialized = UserSerializer(data=request.DATA)
    if serialized.is_valid():
        user_data = {field: data for (field, data) in request.DATA.items() if field in VALID_USER_FIELDS}
        user_data.update(DEFAULTS)
        user = get_user_model().objects.create_user(
            **user_data
        )
        return Response(UserSerializer(instance=user).data, status=status.HTTP_201_CREATED)
    else:
        return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)
Chris
quelle
Wie gehst du hier mit dem Passwort um?
blueFast
19
GEFAHR Wenn ich mich nicht irre, erlaubt dieser Code die Übermittlung von is_superuserund is_staffWerten. Zulässige Felder sollten explizit IMO angegeben werden, wie in den anderen Beispielen gezeigt.
Marcel Chastain
12

@cpury oben vorgeschlagen mit write_only_fieldsOption. Dies hat jedoch in DRF 3.3.3 bei mir nicht funktioniert

In DRF 3.0 wurde die write_only_fieldsOption in ModelSerializer nach PendingDeprecation verschoben und in DRF 3.2 durch ein allgemeineres extra_kwargs ersetzt:

extra_kwargs = {'password': {'write_only': True}}

Tadej Krevh
quelle
7

Alle bisherigen Antworten erstellen den Benutzer und aktualisieren dann das Kennwort des Benutzers. Dies führt zu zwei DB-Schreibvorgängen. Um ein zusätzliches unnötiges Schreiben der Datenbank zu vermeiden, legen Sie das Kennwort des Benutzers fest, bevor Sie es speichern:

from rest_framework.serializers import ModelSerializer

class UserSerializer(ModelSerializer):

    class Meta:
        model = User

    def create(self, validated_data):
        user = User(**validated_data)
        # Hash the user's password.
        user.set_password(validated_data['password'])
        user.save()
        return user
yndolok
quelle
1
Guter Punkt. Schlimmer noch, es in zwei Schritten zu tun, scheint ein Sicherheitsrisiko zu sein. Wenn bei der atomaren Transaktion ein Fehler zwischen der
Erstellung
5

Ein bisschen zu spät zur Party, könnte aber jemandem helfen, der nicht mehr Codezeilen schreiben möchte.

Wir können die superMethode verwenden, um dies zu erreichen.

class UserSerializer(serializers.ModelSerializer):

    password = serializers.CharField(
          write_only=True,
    )

    class Meta:
       model = User
       fields = ('password', 'username', 'first_name', 'last_name',)

    def create(self, validated_data):
        user = super(UserSerializer, self).create(validated_data)
        if 'password' in validated_data:
              user.set_password(validated_data['password'])
              user.save()
        return user
Karan Kumar
quelle
Muss ich für die Registrierungen eine andere URL festlegen?
Bernard 'Beta Berlin' Parah
Hier ist ein zusätzliches Bit für diejenigen, die das Kennwortfeld verbergen möchten: Klasse UserSerializer (serializers.HyperlinkedModelSerializer): password = serializers.CharField (write_only = True, style = {'input_type': 'password', 'placeholder': 'Password' },)
timi95
2

Eine auf Viewset basierende Implementierung von Python 3, Django 2 und Django REST Framework:

Datei: serializers.py

from rest_framework.serializers import ModelSerializers
from django.contrib.auth import get_user_model

UserModel = get_user_model()

class UserSerializer(ModelSerializer):
    password = serializers.CharField(write_only=True)

    def create(self, validated_data):
        user = UserModel.objects.create_user(
            username=validated_data['username'],
            password=validated_data['password'],
            first_name=validated_data['first_name'],
            last_name=validated_data['last_name'],
        )
        return user

    class Meta:
        model = UserModel
        fields = ('password', 'username', 'first_name', 'last_name',)

Datei views.py :

from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import CreateModelMixin
from django.contrib.auth import get_user_model
from .serializers import UserSerializer

class CreateUserView(CreateModelMixin, GenericViewSet):
    queryset = get_user_model().objects.all()
    serializer_class = UserSerializer

Datei urls.py

from rest_framework.routers import DefaultRouter
from .views import CreateUserView

router = DefaultRouter()
router.register(r'createuser', CreateUserView)

urlpatterns = router.urls
Nick Van Berckelaer
quelle