Python - Cómo implementar el cambio de contraseña en Django Rest Framework sin repetir el código (principio DRY)

CorePress2024-01-25  9

Tengo una aplicación Django que ya tiene una interfaz web para cambiar contraseñas. Utiliza funciones django.contrib.auth y django.views.generic.UpdateView. Aquí está el código:

class PasswordChangeView(LoginRequiredMixin, generic.UpdateView):
    form_class = PasswordChangeForm
    template_name = 'form.html'
    input_value = 'update password'

    def post(self, request, *args, **kwargs):
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            update_session_auth_hash(request, user)  # Important!
            try:
                request.user.auth_token.delete()  # Important!
            except (AttributeError, ObjectDoesNotExist):
                pass
            messages.success(request, 'Your password was successfully updated!')
            return redirect('/')
        else:
            messages.error(request, 'Please correct the error below.')

    def get(self, request, **kwargs):
        form = PasswordChangeForm(request.user)
        return render(request, self.template_name, {'form': form, 'input_value': self.input_value})

El código anterior funciona bien en la interfaz web. Ahora quiero implementar la API REST para cambiar contraseñas. Sé que puedo crear APIView/viewset y serializador para hacerlo (como las respuestas a esta pregunta), pero violará el principio DRY. ¿Cuál es la mejor manera de implementar la interfaz API REST dado que ya existe una interfaz web completamente funcional?



------------------------------------

Implementación del marco de descanso de DjangoEl trabajo electrónico para API y las vistas web estándar de Django son un poco diferentes. Le sugiero que cree diferentes puntos finales para ellos.



------------------------------------

He encapsulado la parte común de cambiar la contraseña en una función llamada update_user_after_password_change para que pueda usarse tanto en la vista como en el conjunto de vistas. Entonces terminé usando la siguiente estructura de código.

En vistas.py:
class PasswordChangeView(LoginRequiredMixin,
                         generic.UpdateView):
    form_class = PasswordChangeForm
    template_name = 'form.html'
    input_value = 'update password'

    def post(self, request, *args, **kwargs):
        form = PasswordChangeForm(request.user, request.POST)
        if form.is_valid():
            user = form.save()
            update_user_after_password_change(request, user)
            messages.success(request, 'Your password was successfully updated!')
            return redirect('/')
        else:
            messages.error(request, 'Please correct the error below.')

    def get(self, request, **kwargs):
        form = PasswordChangeForm(request.user)
        return render(request, self.template_name, {'form': form, 'input_value': self.input_value})


class PasswordChangeViewSet(mixins.UpdateModelMixin,
                            viewsets.GenericViewSet):
    permission_classes = [IsAuthenticated]

    def get_object(self):
        user = self.request.user
        return user

    def update(self, request, *args, **kwargs):
        user = self.get_object()
        serializer = serializers.PasswordChangeSerializer(data=request.data)

        if serializer.is_valid():
            old_password = serializer.data.get('old_password')
            if not user.check_password(old_password):
                return Response({'old_password': ['Wrong password.']},
                                status=status.HTTP_400_BAD_REQUEST)

            user.set_password(serializer.data.get('new_password'))
            user.save()
            update_user_after_password_change(request, user)
            return Response(status=status.HTTP_204_NO_CONTENT)

        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


def update_user_after_password_change(request, user):
    update_session_auth_hash(request, user)
    if hasattr(user, 'auth_token'):
        user.auth_token.delete()
En serializadores.py:
class PasswordChangeSerializer(serializers.Serializer):
    old_password = serializers.CharField(required=True)
    new_password = serializers.CharField(required=True)

    def validate_new_password(self, value):
        validate_password(value)
        return value

Puede haber una mejor manera de usar esto, pero no sé cómo proceder.

Su guía para un futuro mejor - libreflare
Su guía para un futuro mejor - libreflare