pythondjangoauthenticationdjango-rest-frameworkuser-accounts

How to Implement a Guest Login in Django REST Framework


I currently have a backend setup where users can register by providing an email, name, and password. These fields are required for registration. I want to implement a guest login feature where a guest account is deleted when the browser is closed or the guest logs out.

How should I proceed to create a guest account, and what information should I include for the guest account (email, name, password)?

Here is the current code:

models.py

class UserManager(BaseUserManager):
    """Manager for user"""

    def create_user(self, email, name, password=None, **extra_fields):
        """Create, save and return a new user."""
        if not email:
            raise ValueError('User must have an email address.')
        user = self.model(email=self.normalize_email(email), name=name, **extra_fields)
        user.set_password(password)
        user.save(using=self._db)
        return user

    def create_superuser(self, email, name, password):
        """Create and save a new superuser with given details"""
        user = self.create_user(email, name, password)
        user.is_superuser = True
        user.is_staff = True
        user.save(using=self._db)
        return user


class User(AbstractBaseUser, PermissionsMixin):
    """Database model for users in the system"""
    id = models.AutoField(primary_key=True)
    email = models.EmailField(unique=True)
    name = models.CharField(max_length=50)
    is_active = models.BooleanField(default=True)
    is_staff = models.BooleanField(default=False)
    is_guest = models.BooleanField(default=False)
    phone_number = models.CharField(max_length=20, blank=True, null=True)
    avatar_color = models.CharField(max_length=7)

    objects = UserManager()

    USERNAME_FIELD = 'email'
    REQUIRED_FIELDS = ['name']

    def save(self, *args, **kwargs):
        if not self.pk:
            self.avatar_color = random.choice([
                '#FF5733', '#C70039', '#900C3F', '#581845',
                '#8E44AD', '#1F618D', '#008000', '#A52A2A', '#000080'
            ])
        super().save(*args, **kwargs)

serializers.py

class UserSerializer(serializers.ModelSerializer):
    """Serializer for the user object."""

    class Meta:
        model = get_user_model()
        fields = ['id', 'email', 'password', 'name', 'phone_number', 'avatar_color']
        extra_kwargs = {
            'email': {'required': False},
            'password': {'required': False, 'write_only': True, 'style': {'input_type': 'password'}, 'min_length': 6},
            'name': {'required': False},
            'phone_number': {'required': False},
            'avatar_color': {'read_only': True}
        }

    def create(self, validated_data):
        """Create and return a user with encrypted password."""
        return get_user_model().objects.create_user(**validated_data)

    def update(self, instance, validated_data):
        """Update and return user."""
        password = validated_data.pop('password', None)
        user = super().update(instance, validated_data)

        if password:
            user.set_password(password)
            user.save()

        return user


class GuestSerializer(serializers.ModelSerializer):
    """Serializer for guests."""

    class Meta:
        model = get_user_model()
        fields = ['id', 'email', 'name', 'is_guest', 'avatar_color']
        extra_kwargs = {
            'email': {'required': False},
            'name': {'required': False},
            'is_guest': {'default': True},
            'avatar_color': {'read_only': True}
        }

    def create(self, validated_data):
        # Create and return a guest user with default values.
        validated_data['email'] = f'guest_{random.randint(100000, 999999)}@example.com'
        validated_data['name'] = 'Guest'
        validated_data['is_guest'] = True
        return get_user_model().objects.create_user(**validated_data)


class AuthTokenSerializer(serializers.Serializer):
    """Serializer for the user auth token."""
    email = serializers.EmailField()
    password = serializers.CharField(
        style={'input_type': 'password'},
        trim_whitespace=False,
    )

    def validate(self, attrs):
        """Validate and authenticate the user."""
        email = attrs.get('email')
        password = attrs.get('password')
        user = authenticate(
            request=self.context.get('request'),
            username=email,
            password=password,
        )
        if not user:
            msg = _('Unable to authenticate with provided credentials.')
            raise serializers.ValidationError(msg, code='authorization')

        attrs['user'] = user
        return attrs

views.py

class CreateUserView(generics.CreateAPIView):
    """Create a new user in the system."""
    serializer_class = UserSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [permissions.UpdateOwnProfile]
    filter_backends = (filters.SearchFilter,)
    search_fields = ('name', 'email',)


class UserViewSet(viewsets.ModelViewSet):
    """List all users."""
    queryset = User.objects.all()
    serializer_class = UserSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]


class CreateGuestView(generics.CreateAPIView):
    """Create a new guest."""
    serializer_class = GuestSerializer

    def perform_create(self, serializer):
        user = serializer.save()
        token, created = Token.objects.get_or_create(user=user)
        print('TOKEN: ', token.key)
        return Response({'token': token}, status=status.HTTP_201_CREATED)


class GuestLogoutView(APIView):
    """Logout a guest and delete."""
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):
        if request.user.is_guest:
            request.user.auth_token.delete()
            request.user.delete()
            return Response(status=status.HTTP_200_OK)
        else:
            return Response(status=status.HTTP_400_BAD_REQUEST)


class CreateTokenView(ObtainAuthToken):
    """Create a new auth token for user."""
    serializer_class = AuthTokenSerializer
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES


class UserLoginApiView(ObtainAuthToken):
    """Handle creating user authentication tokens"""
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES


class LogoutView(APIView):
    """Logout the authenticated user."""
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def post(self, request, *args, **kwargs):
        logout(request)
        return Response(status=status.HTTP_200_OK)


class ManageUserView(generics.RetrieveUpdateAPIView):
    """Manage the authenticated user."""
    serializer_class = UserSerializer
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    def get_object(self):
        """Retrieve and return the authenticated user."""
        return self.request.user

urls.py

urlpatterns = [
    path('register/', views.CreateUserView.as_view(), name='register'),
    path('register/guest/', views.CreateGuestView.as_view(), name='register_guest'),
    path('logout/', views.LogoutView.as_view(), name='logout'),
    path('logout/guest', views.GuestLogoutView.as_view(), name='logout_guest'),
    path('token/', views.CreateTokenView.as_view(), name='token'),
    path('me/', views.ManageUserView.as_view(), name='me'),
    path('', include(router.urls)),
]

Solution

  • 1. Add a new attribute to your AUTH_USER_MODEL to recognize guests:

    class User(AbstractBaseUser, PermissionsMixin):
        """Database model for users in the system"""
        •••Rest of code•••
        is_active = models.BooleanField(default=True)
        is_staff = models.BooleanField(default=False)
        is_guest = models.BooleanField(default=False)
        •••Rest of code•••

    2. Have a different View specifically to have guests:

     class CreateGuestView(generics.CreateAPIView):
        """Create a new guest."""
        serializer_class = GuestSerializer
        •••Rest of code•••

    3. Create the GuestSerializer used above:

    class GuestSerializer(serializers.ModelSerializer):
        """Serializer for guests."""
        •••Rest of code•••
        def create(self, validated_data):
            """Create and return a user with is_guest."""
            return get_user_model().objects.create_user(is_guest = True, **validated_data)
        •••Rest of code•••

    4. Delete a guest on logout:

    class GuestLogoutView(APIView):
        """Logout a guest and delete."""
        •••Rest of code•••
        def post(self, request, *args, **kwargs):
            request.user.auth_token.delete()
            request.user.delete()
            return Response(status=status.HTTP_200_OK)

    Remember to register all these new Views to a URL. An assumption made was that settings.AUTH_USER_MODEL is correctly setup. One good reason for making this assumption is your use of get_user_model().