GeoDjango: transform SRID in DRF Serializer - django

I am currently writing an API using an OSM DB, a regular DB, DRF3 and django-rest-framework-gis.
The OSM DB is using SRID 900913, but I would like the API to only output coordinates according to SRID 4326. This is achievable by using the transform(srid=4326) on my geoquerysets, but unfortunately that is not an option in my case (explained why further down).
Of what I understand, I should be able to specify another SRID in the field definition of the model like so:
class Polygon(models.Model):
osm_id = models.AutoField(primary_key=True)
name = models.CharField(null=True)
way = models.PolygonField(null=True, srid=4326)
admin_level = models.CharField(default='2', null=True)
objects = models.GeoManager()
class Meta:
db_table = 'osm_poly_lu'
But the API keeps returning polygons with coordinates in the 900913 format.
Do I understand it correctly; should I be able to set the srid on my polygon field to 4326 for Django to automatically convert it from my DBs 900913? If so, I guess I must be missing something; any ideas of what may be wrong? I feel like a true newbie in this area - any help is very appreciated.
The reason why I can not use transform() on my querysets:
What I am doing is querying my member table, and the member model is a regular model. It does however have a relation to the OSM Polygon table (field is called osm_id_origin) and this is a geoDjango model. So when I serialize the member with a DRF3 Serializer, I have a nested Serializer in the member serializer that serializes the osm_id_origin field in the member model as a geoJSON. The nested serializer I am using for creating the geoJSON is a subclass of the django-rest-framework-gis serializer GeoFeatureModelSerializer. Since I query the member model, which is a regular queryset, I can not run the transform(srid=4326) on it.

This is a late answer, but I encounter the same issue and I created a dedicated serializer field to handle transformation. As you will see, this code stores the geometry in srid 31370, and transform the geometry to srid 3857 (which is quite the same as 900913) when serialized:
from rest_framework_gis.fields import GeometryField
class ProjectedGeometrySerializerField(GeometryField):
"""
Transform a geometry from SRID 3857 (representation data)
to 31370 (internal value)
"""
def to_representation(self, value):
if isinstance(value, dict) or value is None:
return value
cloned = value.clone()
cloned.transform(3857)
return super().to_representation(cloned)
def to_internal_value(self, value):
geom = super().to_internal_value(value)
geom.srid = 3857
geom.transform(31370)
return geom

Related

Return a value of a model based on the max value of another field of the model

I have a model in Django with two fields: name and meter. I have created a views file, and I want to find and print the name with the biggest meter. The code below find and prints the biggest meter itself which is good, but not what I want. I want to print the name that is related to that meter. Any idea how to do so?
My Views:
def details (request):
top_meter = Energy_Consuption.objects.aggregate(Max('meter'))
return render(request,'details.html', {'top_meter':top_meter})
(The view is connected to an HTML page.)
My Model:
class Energy_Consuption(models.Model):
name=models.IntegerField()
meter=models.IntegerField()
class Meta:
db_table:'Energy_Consuption'
You can do something like this:
Energy_Consuption.objects.filter(
meter=Energy_Consuption.objects.aggregate(Max('meter'))['meter__max']
).values_list('name', flat=True)
This will return the list of the names of the Energy_Consuption objects that matches the max value. Note that you need filter here, because there can be more than one Energy_Consuption with the max value.
You can just sort the QuerySet by meter and take the latest item.
Additionally, you can use getattr in case of QuerySet being empty.
biggest_item = EnergyConsumption.objects.latest('meter')
biggest_name = getattr(biggest_item, 'name', None)
If your meter field is not unique, you can provide additional fields to the latest method to disambiguate:
biggest_item = EnergyConsumption.objects.latest('meter', 'id')

Django DRF serializer Custom relational fields How to build return value of to_internal_value?

I'm trying to implement something very similar to Djang Rest Framework tutorial Custom relational fields.
For the reminder, the provided code snippet is:
import time
class TrackListingField(serializers.RelatedField):
def to_representation(self, value):
duration = time.strftime('%M:%S', time.gmtime(value.duration))
return 'Track %d: %s (%s)' % (value.order, value.name, duration)
class AlbumSerializer(serializers.ModelSerializer):
tracks = TrackListingField(many=True)
class Meta:
model = Album
fields = ['album_name', 'artist', 'tracks']
And "This custom field would then serialize to the following representation" (quoted in the tutorial):
{
'album_name': 'Sometimes I Wish We Were an Eagle',
'artist': 'Bill Callahan',
'tracks': [
'Track 1: Jim Cain (04:39)',
'Track 2: Eid Ma Clack Shaw (04:19)',
'Track 3: The Wind and the Dove (04:34)',
...
]
}
I understand that and have implemented it for my particular case.
What I don't understand is the way to implement to_internal_value(self, data) as I want to provide a read-write API.
I understand that to_internal_value(self, data) should return an AlbumTrack object, but I don't understand how to build it. In particular how to get back the Album related id?
If we post the JSON structure above, to_internal_value(self, data) will be called once per track with 'Track 1: Jim Cain (04:39)'... for data values. I don't see how we can update the tracks model from those data values.
It seems that you're trying to implement writable nested serializers. While nested serializers are read-only by default, the DRF has a section that explains how to implement writable ones: https://www.django-rest-framework.org/api-guide/relations/#writable-nested-serializers
Since you want the TrackListingField to serialize the Track model it should inherit from ModelSerializer:
class TrackSerializer(serializers.ModelSerializer):
class Meta:
model = Track
fields = ['order', 'name', 'duration']
You'll then have to override the create method for AlbumSerializer:
def create(self, validated_data):
tracks_data = validated_data.pop('tracks')
album = Album.objects.create(**validated_data)
for track_data in tracks_data:
Track.objects.create(album=album, **track_data)
return album
Please note that the above will make one database query per track. You can make use of Track.objects.bulk_create to make only one query to create all tracks.
To answer your initial question about to_internal_value, you can see what the default is by adding this print statement to the overridden to_internal_value:
class TrackSerializer(serializers.ModelSerializer):
...
def to_internal_value(self, data):
default_return_value = super(TrackSerializer, self).to_internal_value(data)
print(default_return_value)
return default_return_value
In the case of a ModelSerializer the DRF uses an OrderedDict output for to_internal_value. Your custom to_internal_value would have to extract the order, name and duration from the data string using a regex, and put them in an OrderedDict. However in this case it'd probably be easier to use a dictionary as representation for the tracks.

How to have a field represent the count of related objects

I have a couple of Django models, Channel and Recording. Channels have a one-to-many relationship with recordings, such that a channel may have many recordings. Within the Channel model, I currently have 2 fields, num_recordings, and storage_size, which are right now created as Integers, but which I want to have dynamically generated as the result of a DB query. E.g. num_recordings should be the current count of recordings on a given channel, and storage_size should be the sum of a field "size" for each of the recordings on a given channel.
I'm using Django rest framework to give a JSON representation of the models, and want it so that when I query the Channel, I see these two fields as Integers, but don't want to have to calculate these separately, e.g. it would be ideal if when you query the Channel endpoint, it would perform a count on the recording relationship, and return that as "num_recordings" and a sum on the recording.size field for all recordings in the relationship and report that on the storage_size field.
How can I do this?
You can approach it by benefiting from the related_name attribute of ForeignKey fields in Django and standard Python #property decorator.
At first, let's assume that you have defined your relationship in models.py like this:
class Channel(models.Model):
...
class Recording(models.Model):
channel = models.ForeignKey(Channel, on_delete=..., related_name='recordings')
# ________________________________________________________________^
# This is the reverse relation name, e.g. channel.recordings
From now on, you can access the related recordings of a specific channel from the channel itself:
>>> channel = Channel.objects.create(...)
>>> recording1 = Recording.objects.create(channel=channel, ...)
>>> recording2 = Recording.objects.create(channel=channel, ...)
>>> channel.recordings.all()
<QuerySet [<Recording: 1>, <Recording: 2>]>
>>> channel.recordings.count()
2
You can use those methods in your Channel model directly:
class Channel(models.Model):
...
#property
def num_of_recordings(self):
return self.recordings.count()
# __________^ (related_name)
Now for your storage_size, you can aggregate it via the aggregate() method of QuerySet and the Sum aggregation function:
from django.db.models import Sum
class Channel(models.Model):
...
#property
def storage_size(self):
return self.recordings.aggregate(storage_size=Sum('size'))['storage_size']
And now the final Channel model:
class Channel(models.Model):
...
#property
def num_of_recordings(self):
return self.recordings.count()
#property
def storage_size(self):
return self.recordings.aggregate(storage_size=Sum('size'))['storage_size']
The last step is to add these new properties num_of_recordings and storage_size in your channel serializer class in order to display them. Since they are decorated with #property, they are read-only by design and thus are calculated dynamically based on the related recordings.

How does drf serialize manytomany fields

How does DRF by default handle serializing a manytomany?
I see it defaults to render the field as an array of ids ex: [1,2,3]
And only uses 2 queries when I prefetch the related model.
However, when I generate it myself with .values_list('id', flat=True) it makes an extra query for every row.
Models
class Fails(models.Model):
runs = models.ManyToManyField(Runs, related_name='fails')
class Runs(models.Model):
name = models.TextField()
View
class FailsViewSet(viewsets.ModelViewSet):
...
def get_queryset(self):
...
return Fails.objects.filter(**params).prefetch_related('runs')
Serializer
class FailsSerializer(QueryFieldsMixin, serializers.ModelSerializer):
runs = serializers.SerializerMethodField()
def get_failbin_regressions(self, obj):
runids = self.context.get('runids')
return obj.runs.values_list('id', flat=True) #this creates an extra query for every row
The end goal is to get runs to display a filtered list of runids.
return obj.runs.values_list('id', flat=True).filter(id__in=runids)
or
runs = obj.runs.values_list('id', flat=True)
return [x for x in runs if x in runids] #to avoid an extra query from the .filter
I know the filter creates more queries, I assume the prefetch model is lost in the serializerMethodField.
Is there a way of getting the list of ids like drf does it without the extra query cost when I do it manually?
I can't find any documentation on how they implement the manytomany render.
By calling:
obj.runs.values_list('id', flat=True)
you are performing a new DB query. Since it will be called for every instance, you'll have a lot of extra queries.
prefetch_related loads the associated instances. So you can interact with the Python objects without extra queries. You could fix your issue with:
def get_failbin_regressions(self, obj):
runids = self.context.get('runids')
return [run.id for run in obj.runs.all() if run.id in runids]

django update model while dropping extra kwargs

I have a Row class and a SpottedPub model. They are almost the same except that Row has a couple extra fields.
What i need to do is to gracefully update spottedpub fields from row object attributes. I tried this
spotted_pubs = SpottedPub.objects.filter(notification__rule__suite__campaign=self,
name=particular_row.name)
if spotted_pubs.all():
spotted_pubs.update(**row.__dict__)
but I get an error saying that I pass too many kwargs to update():
FieldDoesNotExist: SpottedPub has no field named 'profit_weight'
is there a way to drop kwargs that don't correspond to fields?
I tried writing a custom manager
class CustomSpottedPubManager(models.Manager):
def update_drop_fields(self, **fields):
queryset = super(CustomSpottedPubManager, self).get_queryset()
print(queryset)
# drop unwanted fields here
queryset.update(**fields)
and attaching it to the model
class SpottedPub(BaseUserObject):
objects = CustomSpottedPubManager()
but update_drop_fields() method doesn't get called because I access this method already after filtering:
filter(notification__rule__suite__campaign=self,
name=particular_row.name)
Your idea to create an update_drop_fields and filter the fields looks good.
You can create a queryset with the custom update_drop_fields() method,
class CustomSpottedPubQueryset(models.QuerySet):
def update_drop_fields(self, **fields):
return self.update(**fields)
then create a manager with the queryset methods.
class SpottedPub(BaseUserObject):
objects = CustomSpottedPubQueryset.as_manager()