serializer.data is missing some of the data - django

Context: I am having problem accessing fields which are validated by nested serializers.
I have a very sample model as shown below.
For 2 of the fields I have their specific serializers. When I try to access the data it returns all the fields except the one validated by the specific serializers.
Models looks like this
class Sampler(models.Model):
sample_name = models.CharField(unique=True, max_length=100)
sampling_by = JSONField(max_length=100)
extractions = JSONField(max_length=100)
max_samples = models.IntegerField(default=100)
Serializers
class ExtractionsSerializer(serializers.BaseSerializer):
table_name = serializers.CharField()
extraction_type = serializers.ChoiceField(["ABC"])
dependencies = serializers.ListField(child=RecursiveField(), allow_empty=True, required=False)
class SamplingBySerializer(serializers.BaseSerializer):
"""
Validate sampling_by
"""
def to_internal_value(self, samples):
methods = ["ABC"]
sampling_not_supported = [sample for sample in samples
if sample['by'] not in methods]
if sampling_not_supported:
raise ValidationError("{} not in {}".format(sampling_not_supported, methods))
class SamplerSerializer(serializers.ModelSerializer):
extractions = ExtractionsSerializer(read_only=True)
sampling_by = SamplingBySerializer(read_only=True)
class Meta:
model = Sampler
fields = ('sample_name', 'database', 'schema', 'sampling_by', 'extractions', 'max_samples')
Now I do
data = {
"database": "postgresql://..",
"sampling_by":[{
"by":"ABC",
"value": ["l32=turn_the"]
}],
"max_samples":3,
"extractions" : [{
"table_name": "person",
"extraction_type": "ABC"
}]
}
sampler = SamplerSerializer(data=data)
sampler.is_valid() #returns True
sampler.data => does not contain data of the nested fields. Like the `sampling_by` and `extractions`. Contains all other fields
sampler.validated_data => same problem as above
Any help would be appreciated! thanks

You probably missed the fact that your nested serializers are flagged as read_only=True
class SamplerSerializer(serializers.ModelSerializer):
extractions = ExtractionsSerializer(read_only=True)
sampling_by = SamplingBySerializer(read_only=True)
Remove that part, implement the serializer's create / update and you're good to go.
On a side note, it doesn't make sense to access serializer.data when deserializing.
Edit: the authority source is validated_data.

Related

Django filter that also grabs a reverse ForeignKey, or better way to combine associated database info?

I am working on some backend django work that requires me to grab an Employee by filtering, but I also need to grab the EmployeeAddress object that is associated to the Employee. I was wondering if this was possible within a single query. I need the employees address, and employee info to be in a combined single dictionary, to access on the front end side with JS.
I have models as such,
Class Employee(models.model):
first_name
last_name
email
Class EmployeeAddress(models.model):
employee = models.ForeignKey(Employee):
street
city
state
I have a view, that kinda does the job, but having trouble merging merging the two separate QuerySets into a single listed dictionary with all values.
I was hoping there was just a way to get the EmployeeAddress without even writing the second query, and just grabbing that associated data in the first employee_list query?
def employee_ajax_list(request):
email = request.GET.get('email', None)
employee_list = Employee.objects.filter(email=email)
employee_address = EmployeeAddress.objects.filter(employee_id__in=employee_list).values(
'street', 'city', 'state',)
# this chain kinda works, but splits them into 2 separate dictionaries?
employee_info = list(chain(employee_list.values(), employee_address))
data = {
'employee_list': employee_info
}
return JsonResponse(data)
Just looking on some advice to make this work a little smoother!
Change this line
employee = models.ForeignKey(Employee)
to
employee = models.ForeignKey(Employee, related_name="address")
That will let you access an employee's address by doing employee.address in regular code or employee__address in queries.
For example:
Employee.objects
.filter(email=email)
.exclude(address=None)
.values(
'email',
'name',
'address__street',
'address__city',
'address__state'
)
(The .exclude() clause is in case someone doesn't have an address set.)
That should output something like:
<QuerySet [{'email': 'johnsmith#example.com',
'name': 'John Smith', 'address__street':
'123 Maple St', 'address__city': 'Springfield',
'address__state': 'MO'}]>
Maybe something like this should be a bit better and in a single query:
employee_info = EmployeeAddress.objects.filter(employee__email=email).values("employee__email", "employee__<another_field>", "street", "city", "state")
data = {
'employee_list': employee_info
}
A best way of doing it should be with DRF serializers:
class EmployeeAddressSerializer(serializers.ModelSerializer):
class Meta:
fields = "__all__"
model = Employee
class EmployeeSerializer(serializers.ModelSerializer):
# you should add related_name="addresses" in the
# foreignkey if you want this works.
addresses = EmployeeAddressSerializer(many=True)
class Meta:
fields = "__all__"
model = Employee
Then to use:
employee = Employee.objects.filter(email=email).last()
return JsonResponse(EmployeeSerializer(employee).data)

Maintain the order of json data when serializing

I have a data set like this.
{
'album_name': 'Dear John',
'artist': 'Loney Dear',
'tracks': [
'Airport Surroundings',
'Everything Turns to You',
'I Was Only Going Out',
]
}
when I serialize it, my json file does not look like the same every time. Because order of 'tracks' change every time.
I was looking at 'to_representation' but since this data doesn't have a Key i failed to implement it as i expected.
Can any one give a hint, to make sure that 'tracks' always in same order.
Edit:
This where i am so far,
class QaDetailSerializer(ModelSerializer):
"""Serializer to map the Model instance into JSON format."""
album_name = CharField(source='album_name')
artist = StringRelatedField()
tracks = TracksSerializer()
class Meta:
"""Meta class to map serializer's fields with the model fields."""
order_by = (('id',))
model = Qa
fields = (
'id',
'album_name',
'artist',
'tracks',
)
class TracksSerializer(ModelSerializer):
def to_representation(self, value):
representation = super().to_representation(value)
attributes_dict = representation['tracks']
attribute_keys_sorted = sorted(attributes_dict.keys())
sorted_attribute_dict = collections.OrderedDict()
for key in attribute_keys_sorted:
sorted_attribute_dict[key] = attributes_dict[key]
representation['paraphrases'] = sorted_attribute_dict
return representation
The problem you are experiencing is database problem not serializer problem.
You never told your database how to sort your tracks so each time database return tracks in different order.
In your Track model (not serializer) add this Meta class
class Track(models.Model):
...fields
class Meta:
ordering = ("pk",)
this will cause your tracks to be always ordered by primary key.
You can order by any other field and you can also order by multiple fields
i.e ordering = ("music_type", "name")

Using DRF serializer to validate a list of dictionaries

How do I write a serializer that validates a list of dictionaries?
Sample payload being sent is:
"payment_discount": [
{
"discount_rule_id": 1,
"discount_rule_name": "10 day early payment",
"discount_earned": "298.00"
},
{
"discount_rule_id": 2,
"discount_rule_name": "Store discount",
"discount_earned": "5.50"
},
]
Taking from this SO answer and this:
class PaymentDiscountSerializer(serializers.DictField):
discount_rule_id = serializers.IntegerField(required=False)
discount_rule_name = serializers.CharField(max_length=50)
discount_earned = serializers.DecimalField(max_digits=10, decimal_places=3)
class PaymentDiscountListSerializer(serializers.ListField):
"""Serialize discount object"""
child = PaymentDiscountSerializer()
class PaymentSerializer(serializers.ModelSerializer):
payment_discount = PaymentDiscountListSerializer()
# Other model fields
With this, I can get access to the payment_discount object in the payload using serializer.data but unfortunately no validations are being done against the dictionary if e.g. the payload includes discount_earned value of that is not of type Decimal
using: Django==1.10.2 & djangorestframework==3.5.1
I think you can simplify your serializers...
Haven't tested it but you could use something like this:
class PaymentDiscountSerializer(serializers.Serializer):
"""Serialize discounts"""
discount_rule_id = serializers.IntegerField(required=False)
discount_rule_name = serializers.CharField(max_length=50)
discount_earned = serializers.DecimalField(max_digits=10, decimal_places=3)
class PaymentSerializer(serializers.ModelSerializer):
payment_discount = PaymentDiscountSerializer(many=True)
# Other model fields
It should give you a list of object like you want.
For the validation, it should work right out of the box like this.
But again, I haven't tested it. If you are having problems with your validations, you can define your own.
Example:
class PaymentDiscountSerializer(serializers.Serializer):
"""Serialize discounts"""
discount_rule_id = serializers.IntegerField(required=False)
discount_rule_name = serializers.CharField(max_length=50)
discount_earned = serializers.DecimalField(max_digits=10, decimal_places=3)
def validate_discount_rule_id(self, value):
# Validation logic of the discount_rule_id field
#
return value
def validate(self, attrs):
# Validation logic of all the fields
#
return attrs
see http://www.django-rest-framework.org/api-guide/serializers/#field-level-validation for more infos.

Django tastypie model attribute of format

Simple problem. What happens when you have the name "format" as an attribute of a model with TastyPie?
How do you handle the query for http://0.0.0.0:9000/api/v1/library_type/?format=json? when you have a Model which looks like this.
class LibraryType(models.Model):
"""The information about each library type."""
format = models.IntegerField(choices=LIBRARYTYPE_CHOICES)
equiv = models.IntegerField()
name = models.CharField(max_length=96)
prefix = models.CharField(max_length=96)
description = models.CharField(max_length=255, db_column='remark')
You end up with:
{
"error": "Invalid resource lookup data provided (mismatched type)."
}
Obviously this makes sense but how do you work with it? The corresponding Resource definition.
class LibraryTypeResource(ModelResource):
class Meta:
queryset = LibraryType.objects.all()
resource_name = 'library_type'
list_allowed_methods = ['get',]
detail_allowed_methods = ['get', ]
filtering = {
'id': ('exact', ),
'name': ALL,
'format': ALL,
'prefix': ALL,
'description': ALL,
'site': ALL_WITH_RELATIONS,
}
Are you using only the json format?
If so, you can use the TASTYPIE_DEFAULT_FORMATS to set it as json and never use format=json again.
If not, you can use one of tastypie's hooks to retrieve the format query param and do whatever you want with it.
I would change the name of the query param.

How to write a django-rest-framework serializer / field to merge data from generic relations?

I have objects with a generic relation pointing to various other objects, and I need them to be merged (inlined) so the serialized objects look like one whole objects.
E.G:
class Enrollement(models.Model):
hq = models.ForeignKey(Hq)
enrollement_date = models.Datetime()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey('content_type', 'object_id')
class Nurse(models.Model):
hospital = models.ForeignKey(Hospital)
enrollement = GenericRelation(Enrollement)
class Pilot(models.Model):
plane = models.ForeignKey(plane)
enrollement = GenericRelation(Enrollement)
When serialized, I'd like to get something like this:
{
count: 50,
next: 'http...',
previous: null,
results: [
{
type: "nurse",
hq: 'http://url/to/hq-detail/view',
enrollement_date: '2003-01-01 01:01:01',
hospital: 'http://url/to/hospital-detail/view'
},
{
type: "pilot",
hq: 'http://url/to/hq-detail/view',
enrollement_date: '2003-01-01 01:01:01',
plante: 'http://url/to/plane-detail/view'
},
]
}
Can I do it, and if yes, how ?
I can nest a generic relation, and I could post process the serilizer.data to obtain what I want, but I'm wondering if there is a better way.
DEAR FRIENDS FROM THE FUTURE: At time of writing, the Django REST Framework team seems to be working on adding more mature support for generic relations. But it is not yet finished. Before copy-pasting this answer into your code base, check https://github.com/tomchristie/django-rest-framework/pull/755 first to see if it's been merged into the repo. There may be a more elegant solution awaiting you. — Your ancient ancestor Tyler
Given you're using Django REST Framework, if you did want to do some post-processing (even though you seem hesitant to) you can accomplish something your goal by overriding get_queryset or list in your view. Something like this:
views.py:
from rest_framework.generics import ListAPIView
from rest_framework.response import Response
from models import *
from itertools import chain
class ResultsList(ListAPIView):
def list(self, request, *args, **kwargs):
nurses = Nurse.objects.all()
pilots = Pilot.objects.all()
results = list()
entries = list(chain(nurses, pilots)) # combine the two querysets
for entry in entries:
type = entry.__class__.__name__.lower() # 'nurse', 'pilot'
if isinstance(entry, Nurse):
serializer = NurseSerializer(entry)
hospital = serializer.data['hospital']
enrollement_date = serializer.data['enrollement.date']
hq = serializer.data['enrollement.hq']
dictionary = {'type': type, 'hospital': hospital, 'hq': hq, 'enrollement_date': enrollement_date}
if isinstance(entry, Pilot):
serializer = PilotSerializer(entry)
plane = serializer.data['plane']
enrollement_date = serializer.data['enrollement.date']
hq = serializer.data['enrollement.hq']
dictionary = {'type': type, 'plane': plane, 'hq': hq, 'enrollement_date': enrollement_date}
results.append(dictionary)
return Response(results)
serializers.py
class EnrollementSerializer(serializer.ModelSerializer):
class Meta:
model = Enrollement
fields = ('hq', 'enrollement_date')
class NurseSerializer(serializer.ModelSerializer):
enrollement = EnrollementSerializer(source='enrollement.get')
class Meta:
model = Nurse
fields = ('hospital', 'enrollement')
class PilotSerializer(serializer.ModelSerializer):
enrollement = EnrollementSerializer(source='enrollement.get')
class Meta:
model = Pilot
fields = ('plane', 'enrollement')
Returned response would look like:
[
{
type: "nurse",
hq: "http://url/to/hq-detail/view",
enrollement_date: "2003-01-01 01:01:01",
hospital: "http://url/to/hospital-detail/view"
},
{
type: "pilot",
hq: "http://url/to/hq-detail/view",
enrollement_date: "2003-01-01 01:01:01",
plane: "http://url/to/plane-detail/view"
},
]
Noteworthy:
My serializers.py may be a bit off here because my memory of how to represent generic relations in serializers is a bit foggy. YMMV.
Similarly to ^^ this assumes your serializers.py is in order and has properly set up its generic relationships in line with your models.
We do the get in source=enrollement.get because otherwise a GenericRelatedObjectManager object will be returned if we don't specify a source. That's because that's what a generic relation represents. Using .get forces a query (as in QuerySet query) which accesses the model you set as the source of the generic relation (in this case, class Enrollement(models.Model).
We have to use list(chain()) instead of the | operator because the querysets come from different models. That's why we can't do entries = nurses | pilots.
for entry in entries can surely be made more dry. GLHF.