上次我們的model已經繼承了AbstractUser,那麼接下來就是要爭對我們客製化的model進行細部的調整了~

先打個預防針,我接下來的code並不那麼restful,目前是當練習可能能有更好的寫法,我發現了在來修改吧!

我們的model基本上就固定那樣子了,會更動的是serializers和views,因為我們會需要客製化新建會員和修改會員密碼這兩項,所以呢serializers需要三個

  • AccountSerializer 檢視用的
  • AccountCreateSerializer 新建用的
  • ChangePasswordSerializer 改密碼用的

AccountSerializer

基本上跟上次一樣沒變

class AccountSerializer(serializers.HyperlinkedModelSerializer):

    sex = serializers.SerializerMethodField()

    class Meta:
        model = Account
        fields = ["username", "sex", "email", "phone", "address", "url", "is_superuser", "is_staff"]

    def get_sex(self, obj):
        return obj.get_sex_display()

AccountCreateSerializer

會需要去修改create這個function

class AccountCreateSerializer(serializers.HyperlinkedModelSerializer):

    password = serializers.CharField(write_only=True)

    def create(self, validated_data):
        user = Account.objects.create_user(
                username=validated_data['username'],
                password=validated_data['password'],
                sex=validated_data['sex'],
                email=validated_data['email'],
                phone=validated_data['phone'],
                address=validated_data['address'],
                is_superuser=validated_data['is_superuser'],
                is_staff=validated_data['is_staff'],
            )
        return user

    class Meta:
        model = Account
        fields = ["username", "password", "sex", "email", "phone", "address", "url", "is_superuser", "is_staff"]

複寫的create原本是在ModelSerializer中的function,原始的create會根據Meta中的model去處理

ChangePasswordSerializer

這邊就是定義修改密碼需要的欄位和驗證

class ChangePasswordSerializer(serializers.Serializer):
    """
    Serializer for password change endpoint.
    """
    old_password = serializers.CharField(max_length=128, write_only=True, required=True)
    new_password1 = serializers.CharField(max_length=128, write_only=True, required=True)
    new_password2 = serializers.CharField(max_length=128, write_only=True, required=True)

    def validate_old_password(self, value):
        """
        rest_framework serializers.py 480line explain that how validate_method works
        """
        user = self.context['request'].user
        if not user.check_password(value):
            raise serializers.ValidationError(
                _('Your old password was entered incorrectly. Please enter it again.')
            )
        return value

    def validate(self, data):
        if data['new_password1'] != data['new_password2']:
            raise serializers.ValidationError({'new_password2': _("The two password fields didn't match.")})
        # password_validation.validate_password(data['new_password1'], self.context['request'].user)
        return data

    def save(self, **kwargs):
        password = self.validated_data['new_password1']
        user = self.context['request'].user
        user.set_password(password)
        user.save()
        return user

這邊DRF的validate我覺得挺新奇的可以根據function name自行去驗證該欄位的寫法,像是上面的validate_old_password和validate就會在save之前被呼叫到,雖然很方便但需要花一點時間去理解他是怎麼實作的,不然都不知道自己用了什麼感覺不優

有了這三個serializer後就能寫view囉

AccountView

首先我們先把原本的AccountView中的create擋住

class AccountsView(viewsets.ModelViewSet):
    queryset = Account.objects.all().order_by("id")
    serializer_class = AccountSerializer

    def create(self, request):
        return Response(status=status.HTTP_405_METHOD_NOT_ALLOWED)

CreateAccountView

我們在這邊自定一個新建會員的view

class CreateAccountView(CreateAPIView):
    model = Account
    serializer_class = AccountCreateSerializer

而這邊的CreateAPIView是因為我只需要post這個method

ChangePasswordView

修改密碼的部分

class ChangePasswordView(UpdateAPIView):
    serializer_class = ChangePasswordSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def update(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        # if using drf authtoken, create a new token 
        # if hasattr(user, 'auth_token'):
        #     user.auth_token.delete()
        # token, created = Token.objects.get_or_create(user=user)
        # return new token

        # TODO remove token or create new token with JWT
        logout(request)
        return Response({'message': 'change password successful'}, status=status.HTTP_200_OK)

因為目前token還沒接上所以改完密碼登出先用簡單的django提供的logout funtion,就像前面一樣我們這邊只會修改的步驟所以只需要put 和 patch的method

最後就是urls

from django.urls import path
from account import views

from rest_framework import routers

router = routers.DefaultRouter()
router.register(r'', views.AccountsView)

urlpatterns = [
    path('register/', views.CreateAccountView.as_view()),
    path('logout/', views.LogoutView.as_view()),
    path('update_password/', views.ChangePasswordView.as_view()),
]

urlpatterns += router.urls

這樣就大功告成啦!!

不過改密碼那邊必須要先登入才能修改(這不是廢話嗎XD
所以我們再加上permission

class ChangePasswordView(UpdateAPIView):
    serializer_class = ChangePasswordSerializer
    permission_classes = (permissions.IsAuthenticated,) # 這個

    def update(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.save()
        # if using drf authtoken, create a new token 
        # if hasattr(user, 'auth_token'):
        #     user.auth_token.delete()
        # token, created = Token.objects.get_or_create(user=user)
        # return new token

        # TODO remove token or create new token with JWT
        logout(request)
        return Response({'message': 'change password successful'}, status=status.HTTP_200_OK)

這樣就完成了客製化的新建會員和修改密碼了~

最後如果有什麼更好的做法寫法請告訴我,我想知道各式各樣的做法和寫法!!