【问题标题】:django-rest-framework serializer for ContentType objectContentType 对象的 django-rest-framework 序列化程序
【发布时间】:2016-10-22 04:16:35
【问题描述】:

我正在构建一个活动模型,有点类似于package。它有一个演员、动词和目标。

class Activity(models.Model):
    actor_type = models.ForeignKey(ContentType, related_name='actor_type_activities')
    actor_id = models.PositiveIntegerField()
    actor = GenericForeignKey('actor_type', 'actor_id')
    verb = models.CharField(max_length=10)
    target_type = models.ForeignKey(ContentType, related_name='target_type_activities')
    target_id = models.PositiveIntegerField()
    target = GenericForeignKey('target_type', 'target_id')
    pub_date = models.DateTimeField(default=timezone.now)

现在,无论何时创建任何模型(Tender、Job 和 News)的新对象,都会创建一个新的 Activity 对象,target 是这三个模型中任何一个的对象。

例如。用户(演员)发布(动词)标题(目标)

class Tender(models.Model):
    title = models.CharField(max_length=256)
    description = models.TextField()

class Job(models.Model):
    title = models.CharField(max_length=256)
    qualification = models.CharField(max_length=256)

class News(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL)
    title = models.CharField(max_length=150)

为了获取这些数据,我正在制作一个 API,它将为我获取所需的 json 数据。我正在为此使用django-rest-framework,并且非常新。

class ActorSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = User
        fields = ('id', 'username', 'email')

class ActivitySerializer(serializers.HyperlinkedModelSerializer):
    actor = ActorSerializer()
    class Meta:
        model = Activity
        fields = ('url', 'actor', 'verb', 'pub_date')

在上面的序列化程序中,我知道actor 将是用户。所以我将 User 模型用于ActorSerializer 类。但至于target,它可以是这三个模型(News/Job/Tender)中的任何一个。

如何为 ContentType 对象创建一个序列化程序(例如 TargetSerialier 类),以便可以在 ActivitySerializer 类字段中使用 target

【问题讨论】:

    标签: django django-rest-framework django-contenttypes


    【解决方案1】:

    好的,在这里回答我自己的问题。我对 zymud 的回答有一些帮助。所以,显然在documentation 中,有一种方法可以序列化Generic 关系。

    所以,我所要做的就是创建一个自定义字段并将该字段关联到序列化程序本身:

    class ActivityObjectRelatedField(serializers.RelatedField):
        def to_representation(self, value):
            if isinstance(value, User):
                return 'User: ' + value.username
            elif isinstance(value, News):
                return 'News: ' + value.title
            elif isinstance(value, Job):
                return 'Job: ' + value.title
            elif isinstance(value, Tender):
                return 'Tender: ' + value.title
            raise Exception('Unexpected type of tagged object')
    
    
    class ActivitySerializer(serializers.HyperlinkedModelSerializer):
        actor = ActivityObjectRelatedField(read_only=True)
        target = ActivityObjectRelatedField(read_only=True)
    
        class Meta:
            model = Activity
            fields = ('url', 'actor', 'verb', 'target', 'pub_date')
    

    【讨论】:

    • 但这是否意味着这些字段现在是只读的?如果我试图保存一个 ActivityObject 怎么办?如果你不给它一个只读的,你必须给它一个查询集,但我不知道在它到达那里之前它会是什么模型。我是不是误会了?
    【解决方案2】:

    您可以为通用键实现自定义字段。示例:

    from django.core.urlresolvers import resolve
    from rest_framework.fields import Field
    
    class GenericRelatedField(Field):
        """
        A custom field that expect object URL as input and transforms it
        to django model instance.
        """
        read_only = False
        _default_view_name = '%(model_name)s-detail'
        lookup_field = 'pk'
    
        def __init__(self, related_models=(), **kwargs):
            super(GenericRelatedField, self).__init__(**kwargs)
            # related models - list of models that should be acceptable by 
            # field. Note that all this models should have corresponding 
            # endpoint.
            self.related_models = related_models
    
        def _get_url_basename(self, obj):
            """ Get object URL basename """
            format_kwargs = {
                'app_label': obj._meta.app_label,
                'model_name': obj._meta.object_name.lower()
            }
            return self._default_view_name % format_kwargs
    
        def _get_request(self):
            try:
                return self.context['request']
            except KeyError:
                raise AttributeError('GenericRelatedField have to be initialized with `request` in context')
    
        def to_representation(self, obj):
            """ Serializes any object to its URL representation """
            kwargs = {self.lookup_field: getattr(obj, self.lookup_field)}
            request = self._get_request()
            return request.build_absolute_uri(reverse(self._get_url_basename(obj), kwargs=kwargs))
    
        def clear_url(self, url):
            """ Removes domain and protocol from url """
            if url.startswith('http'):
                 return '/' + url.split('/', 3)[-1]
            return url
    
        def get_model_from_resolve_match(self, match):
            queryset = match.func.cls.queryset
            if queryset is not None:
                return queryset.model
            else:
                return match.func.cls.model
    
        def instance_from_url(self, url):
            url = self.clear_url(url)
            match = resolve(url)
            model = self.get_model_from_resolve_match(match)
            return model.objects.get(**match.kwargs)
    
    
        def to_internal_value(self, data):
            """ Restores model instance from its URL """
            if not data:
                return None
            request = self._get_request()
            user = request.user
            try:
                obj = self.instance_from_url(data)
                model = obj.__class__
            except (Resolver404, AttributeError, MultipleObjectsReturned, ObjectDoesNotExist):
                raise serializers.ValidationError("Can`t restore object from url: %s" % data)
            if model not in self.related_models:
                raise serializers.ValidationError('%s object does not support such relationship' % str(obj))
            return obj
    

    使用示例:

    class ActivitySerializer(serializers.HyperlinkedModelSerializer):
        target = GenericRelatedField(related_models=(News, Job, Tender))
        ...
    

    【讨论】:

    • 我收到此错误:Reverse for 'news-detail' with arguments '()' and keyword arguments '{'pk': 3}' not found. 0 pattern(s) tried: []
    • 那是因为您没有News 模型的端点,或者它的基本名称不是news-detail。 DRF 模型需要有端点来表示为 URL。
    【解决方案3】:

    根据文档,有一个第三方库已经完成了繁重的工作:

    https://www.django-rest-framework.org/api-guide/relations/#rest-framework-generic-relations

    实际上很简洁,我的序列化程序类结束了几行可读的行:

    class ActivityTypeSerializer(serializers.ModelSerializer):
        target = GenericRelatedField({
            User: UserSerializer(),
            Device: DeviceSerializer(),
        })
    
        class Meta:
            model = Activity
            fields = ('target', 'target_id', 'verb', 'target_ct',)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-01
      • 2017-07-12
      • 2014-02-09
      • 2016-12-18
      • 2017-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多