Discussion:
UniqueTogetherValidator model serializer or model viewset logic for custom "lat-long" django model field with pre-save method
Mark Mikofski
2017-03-23 22:47:11 UTC
Permalink
Hi, I have a Django (1.8.5) model with a custom field that calls pre-save()
to evaluate the field from other model fields:

class LatLongField(models.CharField):
"""
Custom field class based on :class:`~django.db.models.CharField` that
creates a string field from the site coordinates.
"""
def pre_save(self, model_instance, add):
"""
Create the lat long field the every time the model is saved.
"""
# save latitude and longitude with 3 decimal places
value = '%.3f %.3f' % (model_instance.latitude,
model_instance.longitude)
setattr(model_instance, self.attname, value)
return value


This custom field is used in a model with latitude and longitude to create
a string that can be "unique". The custom field is used in a unique
together constraint:

class Weather(models.Model):
latitude = models.FloatField()
longitude = models.FloatField()
header = models.CharField()
datatype= models.ChoiceField()
latlong = models.LatLongField()

If I make a model serializer no matter what I get, lat-long field is
required. If I exclude it from the serializer, I get no validators and an
integrity error exception (500 server error)

Is this a django issue or a drf issue?
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Xavier Ordoquy
2017-03-23 23:28:58 UTC
Permalink
Hi,

I’m definitively not sure about that too.
Could you try to create a Weather instance as Weather.objects.create(latitude=xx, longitude=xx) and see if it works and set correctly automatically the latlong value, then it’s likely a DRF issue.
Otherwise, you may want to discuss that on the django user mailing list first.

Regards,
Xavier Ordoquy,
Linovia.
Post by Mark Mikofski
"""
Custom field class based on :class:`~django.db.models.CharField` that
creates a string field from the site coordinates.
"""
"""
Create the lat long field the every time the model is saved.
"""
# save latitude and longitude with 3 decimal places
value = '%.3f %.3f' % (model_instance.latitude,
model_instance.longitude)
setattr(model_instance, self.attname, value)
return value
latitude = models.FloatField()
longitude = models.FloatField()
header = models.CharField()
datatype= models.ChoiceField()
latlong = models.LatLongField()
If I make a model serializer no matter what I get, lat-long field is required. If I exclude it from the serializer, I get no validators and an integrity error exception (500 server error)
Is this a django issue or a drf issue?
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
For more options, visit https://groups.google.com/d/optout <https://groups.google.com/d/optout>.
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Mark Mikofski
2017-03-24 01:00:37 UTC
Permalink
Hi Xavier,

Thanks for your quick reply. I had to rush off and I didn't get to finish
the post. Sorry!

Yes, I can make model objects and save them to the database using w =
Weather(latitude=x, longitude=y, header="blah", datatype=3) and w.save().

The issue only occurs when I test the unique together constraint by posting
a duplicate.

If the serializer looks like this:

class WeatherSerializer(serializers.ModelSerializer):
"""weather serializer"""

class Meta:
model = Weather
exclude = ('lat_long',)


then I can post new records fine. But if I try to post a duplicate that's
when I get the integrity error, and since the validator is missing from the
serializer, the server says 500.

If I remove the line `exclude = ('lat_long')` then the serializer _does_
have the `UniqueTogetherValidator` which I can see in a Django shell as
WeatherSerializer().validators

However, now if I try to post a duplicate, instead of getting 500, I do get
400, good, but the message is not "non_field_errors": ["The fields
lat_long, header, datatype must make a unique set."], instead I get "lat_long":
["This field is required."]

* I realize I should/could use DecimalField for latitude and longitude and
that would resolve my problem.
* I tried using HiddenField but couldn't get it to make the lat_long value
without knowing the serializer object a priori
* SerializerMethodField with get_lat_long(obj) method didn't work either
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Xavier Ordoquy
2017-03-24 06:29:21 UTC
Permalink
Post by Mark Mikofski
Hi Xavier,
Thanks for your quick reply. I had to rush off and I didn't get to finish the post. Sorry!
Yes, I can make model objects and save them to the database using w = Weather(latitude=x, longitude=y, header="blah", datatype=3) and w.save().
There are slightly but significant differences between the .save() and Weather.objects.create(..)
Post by Mark Mikofski
The issue only occurs when I test the unique together constraint by posting a duplicate.
"""weather serializer"""
model = Weather
exclude = ('lat_long',)
then I can post new records fine. But if I try to post a duplicate that's when I get the integrity error, and since the validator is missing from the serializer, the server says 500.
The traceback would help understand what we are speaking about.
Post by Mark Mikofski
If I remove the line `exclude = ('lat_long')` then the serializer _does_ have the `UniqueTogetherValidator` which I can see in a Django shell as WeatherSerializer().validators
However, now if I try to post a duplicate, instead of getting 500, I do get 400, good, but the message is not "non_field_errors": ["The fields lat_long, header, datatype must make a unique set."], instead I get "lat_long": ["This field is required."]
* I realize I should/could use DecimalField for latitude and longitude and that would resolve my problem.
* I tried using HiddenField but couldn't get it to make the lat_long value without knowing the serializer object a priori
* SerializerMethodField with get_lat_long(obj) method didn't work either
If you have a unique constraint on those fields, serializer will blow because it enforces the check before saving meaning the field isn’t ready.
Exclude the field, remove the constraint and perform the check by yourself.

Regards,
Xavier Ordoquy,
Linovia.
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Mark Mikofski
2017-03-24 06:55:57 UTC
Permalink
Got it. That makes sense. Thanks!

here's the traceback for the case where the field is excluded, so the
validator is empty and so the integrity error exception never gets handled
and the response is 500.

http://pastebin.com/UgvJBug2

DRF is v3.3.3, django is v1.8.5
Post by Mark Mikofski
Hi Xavier,
Thanks for your quick reply. I had to rush off and I didn't get to finish the post. Sorry!
Yes, I can make model objects and save them to the database using w =
Weather(latitude=x, longitude=y, header="blah", datatype=3) and w.save().
There are slightly but significant differences between the .save() and
Weather.objects.create(..)
The issue only occurs when I test the unique together constraint by posting a duplicate.
"""weather serializer"""
model = Weather
exclude = ('lat_long',)
then I can post new records fine. But if I try to post a duplicate that's
when I get the integrity error, and since the validator is missing from the
serializer, the server says 500.
The traceback would help understand what we are speaking about.
If I remove the line `exclude = ('lat_long')` then the serializer _does_
have the `UniqueTogetherValidator` which I can see in a Django shell as
WeatherSerializer().validators
However, now if I try to post a duplicate, instead of getting 500, I do
get 400, good, but the message is not "non_field_errors": ["The fields
["This field is required."]
* I realize I should/could use DecimalField for latitude and longitude and
that would resolve my problem.
* I tried using HiddenField but couldn't get it to make the lat_long value
without knowing the serializer object a priori
* SerializerMethodField with get_lat_long(obj) method didn't work either
If you have a unique constraint on those fields, serializer will blow
because it enforces the check before saving meaning the field isn’t ready.
Exclude the field, remove the constraint and perform the check by yourself.
Regards,
Xavier Ordoquy,
Linovia.
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Mark Mikofski
2017-03-24 09:53:53 UTC
Permalink
SOLVED!

I added a default class with a set_context method to grab the data from the
request and combined with a HiddenField and the CreateOnlyDefault to limit
it to POST only

class LatLongDefault(object):
"""
Provide default ``lat_long`` field for validation on POST.
"""
def set_context(self, serializer_field):
request = serializer_field.context['request']
LOGGER.debug('request data\n:%r', request.data)
self.latitude = float(request.data['latitude'])
self.longitude = float(request.data['longitude'])

def __call__(self):
return '%.3f %.3f' % (self.latitude, self.longitude)

def __repr__(self):
return unicode_to_repr('%s()' % self.__class__.__name__)



class WeatherSerializer(serializers.ModelSerializer):
"""weather serializer"""
lat_long = serializers.HiddenField(
default=serializers.CreateOnlyDefault(LatLongDefault())
)

class Meta:
model = Weather


I copied the code from the CurrentUserDefault in GitHub.

Let me just say that DRF is really well written code, several times I have
gone back to the source and it's always been illuminating!
--
You received this message because you are subscribed to the Google Groups "Django REST framework" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-rest-framework+***@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
Loading...