Serializing Foreign Key objects in Django - python

I have been working on developing some RESTful Services in Django to be used with both Flash and Android apps.
Developing the services interface has been quite simple, but I have been running into an issue with serializing objects that have foreign key and many to many relationships.
I have a model like this:
class Artifact( models.Model ):
name = models.CharField( max_length = 255 )
year_of_origin = models.IntegerField( max_length = 4, blank = True, null = True )
object_type = models.ForeignKey( ObjectType, blank = True, null = True )
individual = models.ForeignKey( Individual, blank = True, null = True )
notes = models.TextField( blank = True, null = True )
Then I would perform a query on this model like this, using select_related(), to be sure that foreign key relationships are followed:
artifact = Artifact.objects.select_related().get(pk=pk)
Once I have the object, I serialize it, and pass that back to my view:
serializers.serialize( "json", [ artifact ] )
This is what I get back, note that the foreign keys (object_type and individual) are just the id's to their related objects.
[
{
pk: 1
model: "artifacts.artifact"
fields: {
year_of_origin: 2010
name: "Dummy Title"
notes: ""
object_type: 1
individual: 1
}
}
]
This is great, but what I was hoping for when using select_related() was that it would automatically populate the foreign key fields with the related object, not just the object's id.
I am recent convert to Django, but put in a fair amount of time developing with CakePHP.
What I really like about the Cake ORM was that it would follow the relationships and create nested objects by default, with the ability to unbind the relationships when you were calling your query.
This made it very easy to abstract the services in a way that did not require any intervention on a case by case basis.
I see that Django does not do this by default, but is there a way to automatically serialize an object and all of it's related objects? Any tips or reading would be much appreciated.

I had a similar requirement although not for RESTful purposes. I was able to achieve what I needed by using a "full" serializing module, in my case Django Full Serializers. This is part of wadofstuff and is distributed under the new BSD license.
Wadofstuff makes this quite easy. For e.g. in your case you'd need to do the following:
First, install wadofstuff.
Second, add the following setting to your settings.py file:
SERIALIZATION_MODULES = {
'json': 'wadofstuff.django.serializers.json'
}
Third, make a slight change to the code used for serialization:
artifact = Artifact.objects.select_related().get(pk=pk)
serializers.serialize( "json", [ artifact ], indent = 4,
relations = ('object_type', 'individual',))
The key change is the relations keyword parameter. The only (minor) gotcha is to use the name of the fields forming the relation not the names of the related models.
Caveat
From the documentation:
The Wad of Stuff serializers are 100% compatible with the Django serializers when serializing a model. When deserializing a data stream the the Deserializer class currently only works with serialized data returned by the standard Django serializers.
(Emphasis added)
Hope this helps.

UPDATE:
Actually Manoj's solution is a bit outdated, Wad of Stuff's serializer has been left un-updated for some time and when I tried that, it seems that it does not support Django 1.6 anymore.
However, take a look at Django's official doc here. It does provide some way around using the built-in natural key. It seems that django's built-in serializer has a a little problem supporting using ImageField as part of the natural key. But that can be easily fixed by your self.

I'm aware this topic is years old, however, I'm sharing my solution for the people still searching for an answer (during my search, I ended up here).
Please note, I was looking for a simple function which would give me nested (foreign key) objects/dictionaries (which could contain nested (foreign key) objects/dictionaries as well) within my model/queryset which I could then convert to JSON.
In my models.py, I have a custom function (not within a model class):
Models.py
def django_sub_dict(obj):
allowed_fields = obj.allowed_fields() # pick the list containing the requested fields
sub_dict = {}
for field in obj._meta.fields: # go through all the fields of the model (obj)
if field.name in allowed_fields: # be sure to only pick fields requested
if field.is_relation: # will result in true if it's a foreign key
sub_dict[field.name] = django_sub_dict(
getattr(obj, field.name)) # call this function, with a new object, the model which is being referred to by the foreign key.
else: # not a foreign key? Just include the value (e.g., float, integer, string)
sub_dict[field.name] = getattr(obj, field.name)
return sub_dict # returns the dict generated
This function loops through all the fields in a models.Model object, if the models.Model is provided. I call the function within a model as follows (for completeness sake, including one entire model):
the same Models.py
class sheet_categories(models.Model):
id = models.AutoField(primary_key=True, unique=True)
create_date = models.DateField(auto_now_add=True)
last_change = models.DateField(auto_now=True)
name = models.CharField(max_length=128)
sheet_type = models.ForeignKey(
sheet_types, models.SET_NULL, blank=False, null=True)
balance_sheet_sort = models.IntegerField(unique=True)
def allowed_fields(self):
return [
'name',
'sheet_type',
'balance_sheet_sort',
]
def natural_key(self):
return django_sub_dict(self) # call the custom function (which is included in this models.py)
Note:
The nested JSON objects will only contain fields which are included in the allowed_fields of a model. Thus not including sensitive information.
To ultimately generate a JSON, I have the following view in my views.py.
views.py
class BalanceSheetData(ListView): # I believe this doesn't have to **be** a ListView.
model = models.sheet_categories
def get_queryset(self):
return super().get_queryset().filter() # the filter is for future purposes. For now, not relevant
def get(self, request, *args, **kwargs):
context = {
'queryset': serializers.serialize("json",
self.get_queryset(),
use_natural_foreign_keys=True, # this or the one below makes django include the natural_key() within a model. Not sure.
use_natural_primary_keys=True, # this or the one above makes django include the natural_key() within a model. Not sure.
),
}
return JsonResponse(context)
This ultimately provided me with all the nested details I required in a JSON response. Although I do not share the JSON response, as this one is barely readable.
Feel free to comment.

You can find more information on this ticket:
Allow In-depth serialization by specifying depth to follow relationship
https://code.djangoproject.com/ticket/4656

Adding a newer answer to this older question: I created and recently published django-serializable-model as an easily extensible way to serialize models, managers, and querysets. When your models extend SerializableModel, they receive an overridable .serialize method that has built-in support for all relations.
Using your example, once all of the involved models extend SerializableModel:
joins = ['object_type', 'individual']
artifact = Artifact.objects.select_related(*joins).get(pk=pk)
artifact.serialize(*joins)
Calling .serialize with the relations as arguments will have the library recurse over the related objects, calling .serialize on them as well. This returns a dictionary that looks like:
{
'id': 1,
'year_of_origin': 2010,
'name': 'Dummy Title',
'notes': '',
'object_type_id': 1,
'individual_id': 1,
'object_type': { ... nested object here ... },
'individual': { ... nested object here ... }
}
You can then call json.dumps on this dictionary to transform it to JSON.
By default, extending SerializableModel will also set the model's manager to SerializableManager (you can extend it yourself if you're using a custom manager) which uses SerializableQuerySet. This means you can call .serialize on a manager or queryset as well:
artifacts = Artifact.objects.select_related(*joins).all()
artifacts.serialize(*joins)
This simply calls .serialize on each model object in the queryset, returning a list of dictionaries in the same format as above.
django-serializable-model also allows you to easily override the default behavior on a per model basis, giving you the ability to do things like: add allowlists or denylists applied to each model's .serialize, always serialize certain joins (so you don't have to add them as arguments all the time), and more!

Related

Serializing custom related field in DRF

I am trying to make a serializer with a nested "many to many" relationship. The goal is to get a serialized JSON object contain an array of serialized related objects. The models look like this (names changed, structure preserved)
from django.contrib.auth.models import User
PizzaTopping(models.Model):
name = models.CharField(max_length=255)
inventor = models.ForeignKey(User)
Pizza(models.Model):
name = models.CharField(max_length=255)
toppings = models.ManyToManyField(PizzaTopping)
The incoming JSON looks like this
{
"name": "My Pizza",
"toppings": [
{"name": "cheese", "inventor": "bob"},
{"name": "tomatoes", "inventor": "alice"}
]
}
My current serializer code looks like this
class ToppingRelatedField(RelatedField):
def get_queryset(self):
return Topping.objects.all()
def to_representation(self, instance):
return {'name': instance.name, 'inventor': instance.inventor.username}
def to_internal_value(self, data):
name = data.get('name', None)
inventor = data.get('inventor', None)
try:
user = User.objects.get(username=inventor)
except Setting.DoesNotExist:
raise serializers.ValidationError('bad inventor')
return Topping(name=name, inventor=user)
class PizzaSerializer(ModelSerializer):
toppings = ToppingRelatedField(many=True)
class Meta:
model = Pizza
fields = ('name', 'toppings')
It seems that since I defined the to_internal_value() for the custom field, it should create/update the many-to-many field automatically. But when I try to create pizzas, I get "Cannot add "": the value for field "pizzatopping" is None" ValueError. It looks like somewhere deep inside, Django decided that the many to many field should be called by the model name. How do I convince it otherwise?
Edit #1: It seems that this might be a genuine bug somewhere in Django or DRF. DRF seems to be doing the right thing, it detects that it is dealing with a ManyToMany field and tries to create toppings from the data using the custom field and add them to the pizza. Since it only has a pizza instance and a field name, it uses setattr(pizza, 'toppings', toppings) to do it. Django seems to be doing the right thing. The __set__ is defined and seems to figure out that it needs to use add() method in the manager. But somewhere along the way, the field name 'toppings' gets lost and replaced by the default. Which is "related model name in lower case".
Edit #2: I have found a solution. I will document it in an answer once I am allowed. It seems that the to_internal_value() method in the RelatedField subclass needs to return a saved instance of a Topping for the ManyToMany thing to work properly. The existing docs show the opposite, a this link (http://www.django-rest-framework.org/api-guide/fields/#custom-fields) the example clearly returns an unsaved instance.
Seems like there is an undocumented requirement. For write operations to work with a custom ManyToMany field, the custom field class to_internal_value() method needs to save the instance before returning it. The DRF docs omit this and the example of making a custom field (at http://www.django-rest-framework.org/api-guide/fields/#custom-fields) shows the method returning an unsaved instance. I am going to update the issue I opened with the DRF team.
I was also trying to return multiple fields as json but getting error unhashable type: 'dict. Finally, I found what's wrong with my approach here - https://github.com/encode/django-rest-framework/issues/5104
RelatedFields generally represent a related object as a single value
(eg, a slug, primary key, url, etc...). If you want to provide a
nested object representation, then you should use a nested serializer.

Django rest framework one to one relation

So I have follwoing models:
class A(models.Model):
name = models.CharField()
age = models.SmallIntergerField()
class B(models.Model):
a = models.OneToOneField(A)
salary = model.IntergerField()
Now I want to create one rest end point for there two as they are one to one. So I want following as get
{
url: 'http://localhost/customs/1/',
name: 'abc',
age: 24,
salary: 10000
}
Similary, I want to create records and update as well. Please let me know how can I achieve this in django rest framework 3.
I just encountered the same problem, it would indeed be useful to make the response structure less tied to the underlying model structure. Here's my take :
Reading is easy
Serializer fields have a source parameter, which can take dotted names to traverse attributes.
class ABSerializer(serializers.ModelSerializer):
class Meta:
model = A
fields = ['name', 'age', 'salary']
salary = serializer.IntegerField(source='b.salary') # this is your related_name
Writing is ... not officially supported
Validated data will show a nested structure, and the standard create and update methods will choke trying to assign a data dict to a OneToOneField.
The good news is that you can work around it by overriding create and update methods. Here's an example with update :
class ABSerializer(serializers.ModelSerializer):
class Meta:
model = A
fields = ['name', 'age', 'salary']
related_fields = ['b']
salary = serializer.IntegerField(source='b.salary') # this is your related_name
def update(self, instance, validated_data):
# Handle related objects
for related_obj_name in self.Meta.related_fields:
# Validated data will show the nested structure
data = validated_data.pop(related_obj_name)
related_instance = getattr(instance, related_obj_name)
# Same as default update implementation
for attr_name, value in data.items():
setattr(related_instance, attr_name, value)
related_instance.save()
return super(ABSerializer,self).update(instance, validated_data)
Of course, this example is very simplistic, doesn't do any exception handling, and won't work with more deeply nested objects... but you get the idea.
Another option
You could also create a read-write flavor of SerializerMethodField, which would consider both a getter and a setter, however that would probably end up being far more verbose in the end.
Hope that helps !
I know this is an old post but I came across this and after some research and reading through the Django Rest Framework documentation
So a quick search I found that you could use the related_name parameter for reverse relationships as stated here:
reverse relationships are not automatically included by the
ModelSerializer and HyperlinkedModelSerializer classes. To include
a reverse relationship, you must explicitly add it to the fields list.
For example:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['tracks', ...]
You'll normally want to ensure that you've set an appropriate
related_name argument on the relationship, that you can use as the
field name.
For example:
class Track(models.Model):
album = models.ForeignKey(Album, related_name='tracks',
on_delete=models.CASCADE)
...
If you have not set a related name for the reverse relationship,
you'll need to use the automatically generated related name in the
fields argument.
For example:
class AlbumSerializer(serializers.ModelSerializer):
class Meta:
fields = ['track_set', ...]
Also, see the Django documentation on reverse
relationships
for more details.

Dynamically attach properties to an instance of ndb.Model and have them in the to_dict() representation

I'm writing a REST api over Google App Engine NDB
I excluded existing libraries because I need control over transactions and caching.
I excluded Google Endpoints for a similar reason and also because I don't want to use the javascript client they provide.
When evaluating architectural decisions I encountered some problems and weird situations, this is because my experience with python and pythonic style is probably not big enough.
In this moment I tried to come up with some guidelines that should shape my codebase:
in Handlers -> create the dictionary representation of the objects
and return them as json; perform authentication and authorization
checks
encapsulate ndb interaction in Services
Model classes always receive Model objects or keys as parameters and return Model objects or Model lists
Model classes are imported and used in services
One particular thing I encountered is this
I have a many to many relationship that I implemented with a mapping Model, something like
UserOrganizationMembership, that has the keys of the User and the Organization
now, in my Service, at some point I want to return an object that has the list of the organizations the current user is member of and recursively fetch the companies that are in each organization:
'organizations': [
{
'name': 'TEST'
'companies': [ {company1}, {company2}]
},
{
...
}
]
I do this
def user_organizatios_with_companies(user):
def fetch_companies(x):
x.companies = Company.by_organization(x) #NOTICE THIS
return x
user_organizations = [ membership.organization.get() for membership in UserOrganizationMembership.by_user(user)]
return [fetch_companies(x) for x in user_organizations]
in the highlighted line I attach the dynamic property 'companies' to the Organization Model
now if I call this method in my Handler, when I create the dictionary representation to output json, ndb.Model.to_dict() implementation ignores dynamically attached properties.
One solution I tried is this (in my Handler)
xs = Service.user_organizatios_with_companies(u)
organizations = [x.to_dict() for x in xs]
for x in xrange(0,len(xs)):
organizations[x]['companies'] = xs[x].to_dict()
but I don't like it because I need to know that each organization has a 'companies' property and the code seems a bit complicated and not obvious
another approach is to override ndb.Model.to_dict()
isolating dynamically attached properties and providing a dictionary representation, this simplifies my code in the Handler letting me to only call to_dict() on the stuff returned by the Service.
from google.appengine.ext import ndb
import util
class BaseModel(ndb.Model):
created = ndb.DateTimeProperty(auto_now_add = True)
updated = ndb.DateTimeProperty(auto_now = True)
# App Engine clock times are always
# expressed in coordinated universal time (UTC).s
def to_dict(self, include=None, exclude=None):
result = super(BaseModel,self).to_dict(include=include, exclude=exclude)
result['key'] = self.key.id() #get the key as a string
# add properties dynamically added to the class
dynamic_vars = {k:v for (k,v) in vars(self).iteritems() if not k.startswith('_')}
for prop, val in dynamic_vars.iteritems():
result[prop] = val.to_dict() if not isinstance(val, list) else [x.to_dict() for x in val]
return util.model_db_to_object(result)
do you have any recommendation against this approach? any thought will be appreciated!
ndb supports dynamic properties through the Expando type.
Instead of defining your models as:
class BaseModel(ndb.Model):
Define it using Expando:
class BaseModel(ndb.Expando):
Now, if you write x.companies = [...], calling _to_dict() will output those companies. Just be careful about when you put() these entities, as any dynamically added properties will also be put into the Datastore.

Understanding Querysets, creating the perfect database relationship

I'm am currently trying to figure out the best way to structure my database schema based on a few models. I'll try and explain this the best I can so I can work out the best way to tackle the problem.
Firstly, I have 3 models that are "related"
User which is extended to contain the field api_key, Campaign and finally Beacon.
User's can have many Campaign's but a Campaign can only relate to one User my first choice here was to have Campaign have a foreign key to User, correct me if I'm wrong, but I feel that is the best choice there. Likewise, Campaign can have many Beacon's but a Beacon can only relate to one Campaign at a time. Again, I'm presuming that a foreign key here would work the best.
The issue arises when I try and query the Beacon's that relate to any given Campaign. I wish to return all Beacon's that relate to the User whilst also getting the data for Campaign.
I wish to return a JSON string like the following:
{
XXXX-YYYYY: {
message: "Hello World",
destination: "http://example.com"
}
XXXX-YYYYY: {
message: "Hello World",
destination: "http://example.com"
}
}
XXXX-YYYYY being the Beacon.factory_id and message/destination being Campaign.message and Campaign.destination
I'm thinking Queryset's here, but I've never worked with them before and it just confused me.
According to your question, you have something like this:
class User(models.Model):
pass
class Campaign(models.Model):
user = models.ForeignKey(User, verbose_name="Attached to")
message = models.CharField()
destination = models.CharField()
class Beacon(models.Model):
factory_id = models.CharField()
campaign = models.ForeignKey(Campaign, verbose_name="Campaign")
You can follow ForeignKey "backward", by using campaign_set generated attribute:
If a model has a ForeignKey, instances of the foreign-key model will have access to a Manager that returns all instances of the first model. By default, this Manager is named FOO_set, where FOO is the source model name, lowercased.
So you can query your Beacon model like this:
beacon = Beacon.objects.get(factory_id="XXXX-YYYYY")
# Get every campaigns related and only relevant fields (in a list of dict)
campaigns = beacon.campaign_set.all().values('message', 'destination')
for campaign in campaigns:
print(campaign['message'])
print(campaign['destination'])
For your dictionary, it is impossible to make it exactly like this. You can't have a duplicate key.
I wish to return all Beacons that relate to the User whilst also getting the data for Campaign
beacons = Beacon.objects.filter(campaign__user=user).select_related('campaign')
You can then easily process this into your desired data structure.
I'm thinking Querysets here, but I've never worked with them before and it just confused me
A QuerySet is simply how the Django ORM represents a query to your database that results in a set of items. So the above is a QuerySet, as is something as simple as User.objects.all(). You can read some introductory material about QuerySets in the documentation.

Cleaner / reusable way to emit specific JSON for django models

I'm rewriting the back end of an app to use Django, and I'd like to keep the front end as untouched as possible. I need to be consistent with the JSON that is sent between projects.
In models.py I have:
class Resource(models.Model):
# Name chosen for consistency with old app
_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=255)
#property
def bookingPercentage(self):
from bookings.models import Booking
return Booking.objects.filter(resource=self)
.aggregate(models.Sum("percent"))["percent__sum"]
And in views.py that gets all resource data as JSON:
def get_resources(request):
resources = []
for resource in Resource.objects.all():
resources.append({
"_id": resource._id,
"name": resource.first,
"bookingPercentage": resource.bookingPercentage
})
return HttpResponse(json.dumps(resources))
This works exactly as I need it to, but it seems somewhat antithetical to Django and/or Python. Using .all().values will not work because bookinPercentage is a derived property.
Another issue is that there are other similar models that will need JSON representations in pretty much the same way. I would be rewriting similar code and just using different names for the values of the models. In general is there a better way to do this that is more pythonic/djangothonic/does not require manual creation of the JSON?
Here's what I do in this situation:
def get_resources(request):
resources = list(Resource.objects.all())
for resource in resources:
resource.booking = resource.bookingPercentage()
That is, I create a new attribute for each entity using the derived property. It's only a local attribute (not stored in the database), but it's available for your json.dumps() call.
It sounds like you just want a serialisation of your models, in JSON. You can use the serialisers in core:
from django.core import serializers
data = serializers.serialize('json', Resource.objects.all(), fields=('name','_id', 'bookingPercentage'))
So just pass in your Model class, and the fields you want to serialize into your view:
get_resources(request, model_cls, fields):
documentation here https://docs.djangoproject.com/en/dev/topics/serialization/#id2

Categories

Resources