如何在Django和DRF中使用Serializer与Generic View实现用户删除功能,并对现有用户CRUD代码进行安全性与正确性评审
Implementing User Deletion via API
To add user deletion functionality, you have two secure options depending on your needs:
Option 1: Delete own account without PK (most secure)
This lets users delete only their own account without exposing user IDs in the URL:
Add to views.py:
from rest_framework import status from rest_framework.exceptions import PermissionDenied class UserDeleteView(APIView): permission_classes = (IsAuthenticated,) def delete(self, request): request.user.delete() return Response( {"message": "User account deleted successfully"}, status=status.HTTP_204_NO_CONTENT )
Add to urls.py:
path('delete/', UserDeleteView.as_view(), name='delete_user'),
Option 2: Delete by PK (with permission checks)
If you need to support admin-level deletion or explicit PK targeting (but still restrict users to their own account):
Add to views.py:
class UserDeleteView(generics.DestroyAPIView): permission_classes = (IsAuthenticated,) User = get_user_model() def get_object(self): user_obj = self.User.objects.get(pk=self.kwargs['pk']) if user_obj != self.request.user: raise PermissionDenied("You can only delete your own account.") return user_obj
Add to urls.py:
path('delete/<int:pk>/', UserDeleteView.as_view(), name='delete_user'),
Code Review & Improvements
Let’s walk through your code to fix security gaps, reduce redundancy, and follow Django/DRF best practices:
1. Serializers.py
UserSerializer:
- Remove redundant
write_only=Truefor password (set once in field definition or extra_kwargs, not both) - Use
get_user_model()instead of directUserimport for flexibility (critical if using custom user models) - Simplify
create()method with Django’s built-increate_user():from django.contrib.auth import get_user_model User = get_user_model() class UserSerializer(serializers.ModelSerializer): password = serializers.CharField( max_length=128, min_length=8, write_only=True ) class Meta: model = User fields = ('email', 'password', 'first_name', 'last_name') extra_kwargs = { 'first_name': {'required': True}, 'last_name': {'required': True}, } def create(self, validated_data): return User.objects.create_user(**validated_data)
UpdateUserSerializer:
- Move permission checks to the view (serializers should handle data validation, not access control)
- Simplify
update()using parent class method:class UpdateUserSerializer(serializers.ModelSerializer): email = serializers.EmailField(required=True) class Meta: model = User fields = ('first_name', 'last_name', 'email') extra_kwargs = { 'first_name': {'required': True}, 'last_name': {'required': True}, } def validate_email(self, value): user = self.context['request'].user if User.objects.exclude(pk=user.pk).filter(email=value).exists(): raise serializers.ValidationError({"email": "This email is already in use."}) return value def update(self, instance, validated_data): return super().update(instance, validated_data)
Missing ChangePasswordSerializer:
Add this to your serializers (you imported it but didn’t include it):
class ChangePasswordSerializer(serializers.ModelSerializer): password = serializers.CharField(write_only=True, required=True, min_length=8) password2 = serializers.CharField(write_only=True, required=True) old_password = serializers.CharField(write_only=True, required=True) class Meta: model = User fields = ('old_password', 'password', 'password2') def validate(self, attrs): if attrs['password'] != attrs['password2']: raise serializers.ValidationError({"password": "Password fields don't match."}) return attrs def validate_old_password(self, value): user = self.context['request'].user if not user.check_password(value): raise serializers.ValidationError({"old_password": "Old password is incorrect."}) return value def update(self, instance, validated_data): instance.set_password(validated_data['password']) instance.save() return instance
2. Views.py
Critical Security Fix: Your current UpdateUserView and ChangePasswordView allow authenticated users to modify any user’s data if they know the PK. Fix this by restricting access to the current user:
class UpdateUserView(generics.UpdateAPIView): permission_classes = (IsAuthenticated,) serializer_class = UpdateUserSerializer def get_object(self): return self.request.user class ChangePasswordView(generics.UpdateAPIView): permission_classes = (IsAuthenticated,) serializer_class = ChangePasswordSerializer def get_object(self): return self.request.user
3. Urls.py
Update URLs to remove PK requirements (since views now target the current user):
urlpatterns = [ path('create/', UserCreate.as_view(), name='user_create'), path('token/', views.TokenObtainPairView.as_view(), name='token_obtain_pair'), path('token/refresh/', views.TokenRefreshView.as_view(), name='token_refresh'), path('hello/', HelloView.as_view(), name='hello'), path('update/', UpdateUserView.as_view(), name='update_user'), path('change_password/', ChangePasswordView.as_view(), name='auth_change_password'), path('delete/', UserDeleteView.as_view(), name='delete_user'), # Add this ]
Additional Security Best Practices
- Rate Limiting: Add throttling to login/password endpoints to prevent brute-force attacks (use DRF’s built-in
throttle_classesordjango-ratelimit) - Password Strength: Add validation for uppercase letters, numbers, and special characters in
UserSerializer - Email Verification: Implement email confirmation for new users to verify ownership
- HTTPS: Always use HTTPS in production to protect tokens and sensitive data
- Admin Controls: For admin-level user management, create separate views with
IsAdminUserpermission
内容的提问来源于stack exchange,提问作者Rashid Kalwar

