Cross-table serialization Django REST Framework

2019-05-31 17:27发布

问题:

I have two models with a many-to-many relationship and I am trying to return some geojson by using the Django REST Framework. The data I am trying to return is th pub_date and coordinates (represented by a PointField in GeoDjango). Everytime I try and return the geojson I get the error Field name 'city' is not valid for model 'Article'. I'm pretty new to django/geodjango and this is the first time I've used the Django REST Framework. I've gone through the docs but can't work out where I'm going wrong (or maybe even where to start).

Here my models and serializers.

models.py:

class Location(models.Model):
    city = models.CharField(max_length=200)
    country = models.CharField(max_length=200)
    continent = models.CharField(max_length=200)
    point = models.PointField(srid=4326)
    objects = models.GeoManager()

    def __unicode__(self):
        return u'%s' % (self.point)

    class Meta:
        db_table = 'location'

class Article(models.Model):
    authors = models.ManyToManyField(Author)
    locations = models.ManyToManyField(Location, related_name='places')
    article_title = models.CharField(max_length=200, unique_for_date="pub_date")
    pub_date = models.DateTimeField('date published')
    article_keywords = ArrayField(ArrayField(models.CharField(max_length=20, blank=True), size=10), size=10,)
    title_id = models.CharField(max_length=200)
    section_id = models.CharField(max_length=200)

    def __unicode__(self):
        return u'%s %s %s' % (self.article_title, self.pub_date, self.article_keywords)     

    class Meta:
        db_table = 'article'

serializers.py

class ArticleSerializer(serializers.ModelSerializer):
    places = serializers.PrimaryKeyRelatedField(many=True, read_only=True)
    class Meta:
        model = Article
        fields = ('places')

And the output I would like:

{
"type": "FeatureCollection",
"features": [
    {
        "type": "Feature",
        "properties": {
            "time": "2013-01-22 08:42:26+01"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [
                7.582512743,
                51.933292258,
                1
            ]
        }
    },
    {
        "type": "Feature",
        "properties": {
            "time": "2013-01-22 10:00:26+01"
        },
        "geometry": {
            "type": "Point",
            "coordinates": [
                7.602516645,
                51.94962073,
                1
            ]
        }
    }

Thanks in advance!

UPDATE! I managed to get somewhere with a raw SQL query embeded in a queryset but this is not not quite right:

serialize('geojson', Article.objects.raw('
select a.id, a.pub_date, al.location_id, l.point 
from article a 
join article_locations al on a.id = al.article_id 
join location l on al.location_id = l.id'),
geometry_field = 'point',
fields=('pub_date','locations',))

The result is this:

{  
"type":"FeatureCollection",
"crs":{  
    "type":"name",
    "properties":{  
        "name":"EPSG:4326"
    }
},
"features":[  
    {  
        "geometry":null,
        "type":"Feature",
        "properties":{  
            "pub_date":"2015-04-06T20:38:59Z",
            "locations":[  
                3
            ]
        }
    }

回答1:

DRF serializers can do two things:

  1. Serialize complex data (such as querysets) to native Python datatypes

    serializer = CommentSerializer(comment)
    serializer.data
    # {'email': u'leila@example.com', 'content': u'foo bar', 'created': datetime.datetime(2012, 8, 22, 16, 20, 9, 822774)}
    
  2. Deserialize data from native Python datatypes

    serializer = CommentSerializer(data=data)
    serializer.is_valid()
    # True
    serializer.validated_data
    # {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
    

In your case where

All I want is to return all article pub_dates and their corresponding article coordinates lat/long's (from the pointField).

You will have to do two things:

  1. Create a complex data structure using either an object, a list of objects or a queryset.

    In your case this is pretty easy, you just need a queryset with all articles and with prefetched locations in order to prevent unnecessary db hits for each location.

    Article.objects.all().prefetch_related('locations')[:10]
    
  2. Create a serializer which can serialize this queryset.

    Since you have nested data structure (one article can have many locations) you better split this into two separate serializers.

    The first one will know how to serialize locations only, and the second one will know how to serialize articles only, but it will use the first one for the article.locations serialization.

    class LocationSerializer(serializers.ModelSerializer):
        class Meta:
            model = Location
            fields = ('point',)
    
    
    class ArticleSerializer(serializers.ModelSerializer):
        #this way we override the default serialization 
        #behaviour for this field.
        locations = LocationSerializer(many=True)
    
        class Meta:
            model = Article
            fields = ('pub_date', 'locations')
    

Finally you can combine 1 and 2 via a ViewSet

class ArticleViewSet(viewsets.ModelViewSet):
    serializer_class = ArticleSerializer
    #read about pagination in order to split this into pages
    queryset = Article.objects.all().prefetch_related('locations')[:10]