views.py
I'm creating a queryset that I want to serialize and return as JSON. The queryset looks like this:
all_objects = Program.objects.all()
test_data = serializers.serialize("json", all_objects, use_natural_keys=True)
This pulls back everything except for the 'User' model (which is linked across two models).
models.py
from django.db import models
from django.contrib.auth.models import User
class Time(models.Model):
user = models.ForeignKey(User)
...
class CostCode(models.Model):
program_name = models.TextField()
...
class Program(models.Model):
time = models.ForeignKey(Time)
program_select = models.ForeignKey(CostCode)
...
Question
My returned data has Time, Program, and CostCode information, but I'm unable to query back the 'User' table. How can I get back say the 'username' (from User Table) in the same queryset?
Note: I've changed my queryset to all_objects = Time.objects.all() and this gets User info, but then it doesn't pull in 'CostCode'. My models also have ModelManagers that return the get_by_natural_key so the relevant fields appear in my JSON.
Ultimately, I want data from all four models to appear in my serialized JSON fields, I'm just missing 'username'.
Here's a picture of how the JSON object currently appears in Firebug:
Thanks for any help!
It seems a bit heavyweight at first glance but you could look at using Django REST Framework:
http://www.django-rest-framework.org/api-guide/serializers#modelserializer
You can define and use the serializer classes without having to do anything else with the framework. The serializer returns a python dict which can then be easily dumped to JSON.
To get all fields from each related model as nested dicts you could do:
class ProgramSerializer(serializers.ModelSerializer):
class Meta:
model = Program
depth = 2
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
To customise which fields are included for each model you will need to define a ModelSerializer class for each of your models, for example to output only the username for the time.user:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', )
class TimeSerializer(serializers.ModelSerializer):
"""
specifying the field here rather than relying on `depth` to automatically
render nested relations allows us to specify a custom serializer class
"""
user = UserSerializer()
class Meta:
model = Time
class ProgramSerializer(serializers.ModelSerializer):
time = TimeSerializer()
class Meta:
model = Program
depth = 1 # render nested CostCode with default output
all_objects = Program.objects.all()
serializer = ProgramSerializer(all_objects, many=True)
json_str = json.dumps(serializer.data)
What you really want is a "deep" serialization of objects which Django does not natively support. This is a common problem, and it is discussed in detail here: Serializing Foreign Key objects in Django. See that question for some alternatives.
Normally Django expects you to serialize the Time, CostCode, Program, and User objects separately (i.e. a separate JSON array for each) and to refer to them by IDs. The IDs can either be the numeric primary keys (PKs) or a "natural" key defined with natural_key.
You could use natural_key to return any fields you want, including user.username. Alternatively, you could define a custom serializer output whatever you want there. Either of these approaches will probably make it impossible to load the data back into a Django database, which may not be a problem for you.
Related
Right now I am creating a user department with a list of users that are a foreign key back to the main user model. I had this working yesterday, but for some reason I screwed it up. I imagine it has something to do with the serializers. I want to be able to post a list of users in this format
['jack', 'tom']
However, even using the raw data api this is not allowing me to do this. Here is my code:
Serializers:
class DepartmentSerializer(serializers.ModelSerializer):
user_department = UserSerializer(many=True)
class Meta:
model = Departments
fields = '__all__'
class DepartmentUpdateSerializer(serializers.ModelSerializer):
user_department = UserSerializer(many=True)
class Meta:
model = Departments
fields = ['department_name', 'department_head', 'user_department']
I swear yesterday it was allowing me to select from a list of users in the api. I could also post and it would work from the front end. However, now whenever I create a department it's expecting a dictionary, which I am not trying to pass.
Dudes, for whatever reason, removing () after the UserSerializer fixed it. If anyone can explain why that would be even better!
class DepartmentSerializer(serializers.ModelSerializer):
user_department = UserSerializer
class Meta:
model = Departments
fields =['department_name', 'department_head', 'user_department']
class DepartmentUpdateSerializer(serializers.ModelSerializer):
user_department = UserSerializer
class Meta:
model = Departments
fields = ['department_name', 'department_head', 'user_department']
When you use the nested serializer you need to add the nested serializer field (user_department in your case) to the fields too, as you can see you used
fields = '__all__'
which does not include your nested serializer field, you need to manually add that to the meta fields
How can I add a custom hyperlink field in a serializer? I would like to have a hyperlink field in my serializer that has query params in it. Since there is no way to pass query params from HyperlinkedRelatedField or HyperlinkedIdentityField as far as I know, I've tried using a SerializerMethodField. However, this only serializes to a string, and is not a clickable URL when I visit the API through my browser. My code looks something like this:
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ('url', 'custom_field')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view'),
urllib.urlencode({'param': 'foo'})
)
return result
Also, I am having trouble understanding the difference between a HyperlinkedRelatedField and a HyperlinkedIdentityField, so a brief explanation would be appreciated.
This should do the trick:
from rest_framework.reverse import reverse
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
class Meta:
model = MyModel
fields = ('url', 'custom_field')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view', args=[obj.id], request=self.context['request']),
'param=foo'
)
return result
The reverse function in rest_framework takes a view name (whatever view you'd like to link to), either an args list (the object id, in this case) or kwargs, and a request object (which can be accessed inside the serializer at self.context['request']). It can additionally take a format parameter and any extra parameters (as a dictionary) that you want to pass to it.
The reverse function then builds a nice, fully-formed URL for you. You can add query params to it by simply adding as many ?{}&{}&{} to your result variable and then filling in the series of query params beneath the 'param=foo' inside your format function with whatever other params you want.
The HyperlinkedIdentityField is used on the object itself that is being serialized. So a HyperlinkedIdentifyField is being used in place of your primary key field on MyModel because you are using a HyperlinkedModelSerializer which creates a HyperlinkedIdentityField for the pk of the object itself being serialized.
The HyperlinkedRelatedField is used to define hyperlinked relationships to RELATED objects. So if there were a MySecondModel with a foreign key relationship to MyModel and you wanted to have a hyperlink on your MyModel serializer to all the related MySecondModel objects you would use a HyperlinkedRelatedField like so (remember to add the new field to your fields attribute in Meta):
class MySerializer(serializers.HyperlinkedModelSerializer):
custom_field = serializers.SerializerMethodField()
mysecondmodels = serializers.HyperlinkedRelatedField(
many=True
read_only=True,
view_name='mysecondmodel-detail'
)
class Meta:
model = MyModel
fields = ('url', 'custom_field', 'mysecondmodels')
def get_custom_field(self, obj):
result = '{}?{}'.format(
reverse('my-view', args=[obj.id], request=self.context['request']),
'param=foo'
)
return result
If it were a OneToOneField rather than ForeignKey field on MySecondModel then you would set many=False.
Hope this helps!
I am trying to make a tool for drawing diagrams on the web. I have a model like so:
class PlaneableItem(Model):
name = models.CharField(max_length=NAME_LENGTH, blank=True)
class View(PlaneableItem):
# Some useful details
class Anchor(Model):
view = models.ForeignKey(View)
planeable = models.ForeignKey(PlaneableItem)
class BlockRepresentation(Anchor):
# Useful details
class LineRepresentation(Anchor):
# Useful details
I try to make a rest API that returns lists of all blocks and lines for a specific view, including the name of the planeable that they refer to.
I can get a queryset for this using:
qs = BlockRepresentation.objects.filter(view=theview).all()
qs.select_related('planeable')
qs.extra(select={'name': 'rest_api_planeableitem.name'})
However, now I can't use a ModelSerializer on it, because the field 'name' is not part of the BlockRepresentation.
I really like ModelSerializers, is there a better way of doing this?
Is there a particular reason you need that extra() call? If the sole purpose of that call is to rename a field, you can omit that from the queryset and rename the field using a SerializerMethodField from your serializer. I will assume planeable is the ForeignKey field in BlockRepresentation model to the PlaneableItem model. Sample code:
from rest_framework import serializers
class BlockRepresentationSerializer(serializers.ModelSerializer):
# Some fields
name = serializers.SerializerMethodField()
class Meta:
model = BlockRepresentation
def get_name(self, obj):
if obj.planeable:
return obj.planeable.name
return ''
Suppose you want to include 5 images and the total image count for your restaurant when you serialize a restaurant.
You can query DB for each field in separate method,
but it would be beneficial to work in one method to utilize a QuerySet.
Is there a way to serialize multiple fields at once in DRF?
If all of these fall under one model then building a simple model serializer will account for them all. Take for instance:
# Model
class Restaurant(models.Model):
#property
def image_count(self):
queryset = apps.get_model('app_label', 'Image')
count = queryset.objects.filter(restaurant=self).count()
return count
class Image(models.Model):
restaurant = models.ForeignKey(Restaurant, related_name="image_set")
image = models.ImageField()
Serializers.py
class RestaurantSerializer(serializers.ModelSerializer):
class Meta:
model = Restaurant
fields = ['image_count', ]
# Property allows something as such to be serialized, the method could also just use the related name.
If you want to further serialize the Image object you must read http://www.django-rest-framework.org/api-guide/relations/
Basically, I want to filter out inactive users from a related field of a ModelSerializer. I tried Dynamically limiting queryset of related field as well as the following:
class MySerializer(serializers.ModelSerializer):
users = serializers.PrimaryKeyRelatedField(queryset=User.objects.filter(active=True), many=True)
class Meta:
model = MyModel
fields = ('users',)
Neither of these approaches worked for just filtering the queryset. I want to do this for a nested related Serializer class as a field (but couldn't even get it to work with a RelatedField).
How do I filter queryset for nested relation?
I'll be curious to see a better solution as well. I've used a custom method in my serializer to do that. It's a bit more verbose but at least it's explicit.
Some pseudo code where a GarageSerializer would filter the nested relation of cars:
class MyGarageSerializer(...):
users = serializers.SerializerMethodField('get_cars')
def get_cars(self, garage):
cars_queryset = Car.objects.all().filter(Q(garage=garage) | ...).select_related()
serializer = CarSerializer(instance=cars_queryset, many=True, context=self.context)
return serializer.data
Obviously replace the queryset with whatever you want. You don't always need the to give the context (I used it to retrieve some query parameters in the nested serializer) and you probably don't need the .select_related (that was an optimisation).
One way to do this is to create a method on the Model itself and reference it in the serializer:
#Models.py
class MyModel(models.Model):
#...
def my_filtered_field (self):
return self.othermodel_set.filter(field_a = 'value_a').order_by('field_b')[:10]
#Serialziers.py
class MyModelSerialzer(serializers.ModelSerializer):
my_filtered_field = OtherModelSerializer (many=True, read_only=True)
class Meta:
model = MyModel
fields = [
'my_filtered_field' ,
#Other fields ...
]
Another way to avoid the SerializerMethodField solution and therefore still allow writing to the serializer as well would be to subclass the RelatedField and do the filtering there.
To only allow active users as values for the field, the example would look like:
class ActiveUsersPrimaryKeyField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
return super().get_queryset().filter(active=True)
class MySerializer(serializers.ModelSerializer):
users = ActiveUsersPrimaryKeyField(many=True)
class Meta:
model = MyModel
fields = ('users',)
Also see this response.
Note that this only restricts the set of input values to active users, though, i.e. only when creating or updating model instances, inactive users will be disallowed.
If you also use your serializer for reading and MyModel already has a relation to a user that has become inactive in the meantime, it will still be serialized. To prevent this, one way is to filter the relation using django's Prefetch objects. Basically, you'll filter out inactive users before they even get into the serializer:
from django.db.models import Prefetch
# Fetch a model instance, eagerly prefetching only those users that are active
model_with_active_users = MyModel.objects.prefetch_related(
Prefetch("users", queryset=User.objects.filter(active=True))
).first()
# serialize the data with the serializer defined above and see that only active users are returned
data = MyModelSerializer(model_with_active_users).data