上次我們的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)
這樣就完成了客製化的新建會員和修改密碼了~
最後如果有什麼更好的做法寫法請告訴我,我想知道各式各樣的做法和寫法!!