2016-06-20 2 views
4

Je suis en train de construire un modèle d'activité, quelque peu similaire à celui-ci package. Il a un acteur, un verbe et la cible.sérialiseur django-rest-framework pour l'objet ContentType

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) 

Maintenant, chaque fois que la création d'un nouvel objet de modèles selon (offres, emploi et Nouvelles), un nouvel objet d'activité est créé, avec le target étant l'objet de l'une de ces trois modèles.

eg. user (actor) published (verb) title (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) 

Pour obtenir ces données, je fais une API qui me obtenir les données JSON nécessaires. J'utilise django-rest-framework pour cela et très nouveau avec elle.

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') 

Dans les serializers ci-dessus, je savais que actor sera l'utilisateur. Et donc j'ai utilisé le modèle de l'utilisateur pour la classe ActorSerializer. Mais comme pour le target, il peut s'agir de l'un de ces trois modèles (News/Job/Tender).

Comment puis-je créer un sérialiseur (par exemple, classe TargetSerialier) pour l'objet ContentType afin que je puisse utiliser le target dans le champ de classe ActivitySerializer?

Répondre

1

Bon alors répondre à ma propre question ici. J'ai eu de l'aide pour la réponse de zymud. Donc, apparemment dans le documentation, il existe un moyen de sérialiser la relation générique.

Alors, tout ce que je devais faire était de créer un champ personnalisé et associer ce domaine dans le sérialiseur lui-même:

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') 
+0

Mais est-ce que cela signifie que ces champs sont seulement lus maintenant? Que faire si j'essaie de sauvegarder un ActivityObject? Si vous ne lui donnez pas un read_only, vous devez lui donner un jeu de questions, mais je ne sais pas quel sera le modèle avant qu'il ne soit là. Suis-je un malentendu? –

2

Vous pouvez implémenter un champ personnalisé pour une clé générique. Exemple:

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 

Exemple d'utilisation:

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

Je reçois cette erreur: 'inverse pour « nouvelles-détail » avec des arguments «() 'et mots-clés' {'pk': 3} 'non trouvé. 0 pattern (s) essayé (s): [] ' – Aamu

+0

C'est parce que vous n'avez pas de point de terminaison pour le modèle' News', ou que le nom de base n'est pas 'news-detail'. Le modèle DRF doit avoir un point de terminaison à représenter en tant qu'URL. – zymud