Django Rest Framework自定义ViewSet实现slug+uuid组合ID访问
实现Slug+UUID组合路径访问Django DRF资源的方案
1. 自动生成Slug字段
解决POST创建资源时自动根据name生成slug的需求,推荐在模型save方法中直接处理:
from django.db import models import uuid from django.utils.text import slugify class Product(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) slug = models.SlugField(max_length=250, unique_for_date='published') name = models.CharField(max_length=250) # 需补充published字段,匹配slug的unique_for_date规则,不需要该规则可直接删除slug的对应参数 published = models.DateTimeField(auto_now_add=True) def save(self, *args, **kwargs): # 仅创建时生成slug,需要更新name时同步更新slug可去掉if判断 if not self.slug: self.slug = slugify(self.name) super().save(*args, **kwargs) def get_absolute_url(self): # 路由命名对应后续配置的products:product-detail return reverse('products:product-detail', kwargs={'uuid': self.id, 'slug': self.slug})
同时需要在序列化器中将slug设为只读,避免POST请求要求传入该参数:
from rest_framework import serializers from .models import Product class ProductSerializer(serializers.ModelSerializer): class Meta: model = Product fields = ['id', 'slug', 'name', 'published'] extra_kwargs = { 'slug': {'read_only': True} }
可选优化:如果需要支持中文name生成slug,可使用
pypinyin库将中文转为拼音后再生成slug,或者直接使用django-autoslug第三方模块自动处理slug生成逻辑。
2. 自定义路由配置
替换DefaultRouter默认生成的详情路由,手动添加符合<slug>-<uuid>格式的detail路由:
from rest_framework.routers import DefaultRouter from django.urls import path from .views import ProductViewSet app_name = 'products' router = DefaultRouter() # 注册基础路由,自动生成列表查询、资源创建等通用路由 router.register('', ProductViewSet, basename='product') urlpatterns = [ # 自定义详情页路由,绑定对应操作 path('<slug:slug>-<uuid:uuid>/', ProductViewSet.as_view({ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }), name='product-detail'), ] + router.urls
注:手动添加的路由优先级高于router自动生成的默认detail路由,不会出现路径冲突。Django的路径转换器会从右往左匹配,先识别符合UUID格式的部分,剩余前缀自动归为slug,无需担心slug中包含横杠的问题。
3. 重写ViewSet的get_object方法
修改原有的ModelViewSet逻辑,从请求路径中提取slug和uuid两个参数查询资源:
from rest_framework import viewsets, generics from .models import Product from .serializers import ProductSerializer # 原类名ProductList语义不准确,调整为ProductViewSet更符合功能定位 class ProductViewSet(viewsets.ModelViewSet): queryset = Product.objects.all() serializer_class = ProductSerializer def get_object(self): # 从路径参数中提取slug和uuid slug = self.kwargs.get('slug') product_uuid = self.kwargs.get('uuid') # 双参数查询保证资源正确性,避免仅用UUID查询时slug被篡改仍能访问的问题 return generics.get_object_or_404(Product, id=product_uuid, slug=slug)
内容的提问来源于stack exchange,提问作者some nooby questions
相关产品推荐
相关产品推荐

