I am trying to figure out whether I can represent model field choices to clients consuming a tastypie API.
I have a django (1.4.1) application for which I am implementing a django-tastypie (0.9.11) API. I have a Model and ModelResource similar to the following:
class SomeModel(models.Model):
QUEUED, IN_PROCESS, COMPLETE = range(3)
STATUS_CHOICES = (
(QUEUED, 'Queued'),
(IN_PROCESS, 'In Process'),
(COMPLETE, 'Complete'),
)
name = models.CharFIeld(max_length=50)
status = models.IntegerField(choices=STATUS_CHOICES, default=QUEUED)
class SomeModelResource(ModelResource):
class Meta:
queryset = SomeModel.objects.all()
resource_name = 'some_model'
When I look at objects in the API, the name and status fields are displayed as follows:
{
...
"objects":[
{
"name": "Some name 1",
"status": 0
},
{
"name": "Some name 2",
"status": 2
}]
}
I know I can alter SomeModelResource with hydrate/dehydrate methods to display the string values for status as follows, which would have more value to clients:
{
...
"objects":[
{
"name": "Some name 1",
"status": "Queued"
},
{
"name": "Some name 2",
"status": "Complete"
}]
}
But how would the client know the available choices for the status field without knowing the inner workings of SomeModel?
Clients creating objects in the system may not provide a status as the default value of QUEUED is desirable. But clients that are editing objects need to know the available options for status to provide a valid option.
I would like for the choices to be listed in the schema description for SomeModelResource, so the client can introspect the available choices when creating/editing objects. But I am just not sure whether this is something available out of the box in tastypie, or if I should fork tastypie to introduce the capability.
Thanks for any feedback!
You can add the choices to the schema by overriding the method in your resource. If you would want to add the choices to any field (maybe to use with many resources), you could create the method as follows:
def build_schema(self):
base_schema = super(SomeModelResource, self).build_schema()
for f in self._meta.object_class._meta.fields:
if f.name in base_schema['fields'] and f.choices:
base_schema['fields'][f.name].update({
'choices': f.choices,
})
return base_schema
I haven't tested the above code but I hope you get the idea. Note that the object_class will be set only if you use the tastypie's ModelResource as it is being get from the provided queryset.
A simpler solution is to hack the choices information into your help_text blurb.
In our example we were able to do:
source = models.CharField(
help_text="the source of the document, one of: %s" % ', '.join(['%s (%s)' % (t[0], t[1]) for t in DOCUMENT_SOURCES]),
choices=DOCUMENT_SOURCES,
)
Easy peasy, automatically stays up to date, and is pretty much side-effect free.
Related
I have this models (for ilustration only):
class Customer(models.Model):
created = models.DateTimeField(auto_now_add=True)
class Person(Customer):
first_name = models.CharField(max_lenght=40)
last_name = models.CharField(max_length=40)
# More data related to a person
class Company(Customer):
company_name = models.CharField(max_length=100)
# More data related to a company
As you can see, I can have two "types" of customers, but they are both "customers" (I think this is a textbook example of inheritance). Everything works fine at the database level, but now I need to create an endpoint that will "dinamically" show the customer data based on which "type" of customer it is. Something like this:
[
{
"id": 1,
"first_name": "John",
"last_name": "Doe"
},
{
"id": 2,
"company_name": "Big buck"
},
{
"id": 3,
"first_name": "Jane",
"last_name": "Doe"
}
]
When working within my project, I use things like:
customer = Customer.objects.get(pk=100)
if hasattr(customer, 'person'):
pass # Do some stuff
elif hasattr(customer, 'company'):
pass # Do some other stuff
# Do common stuff related to both customer 'types'
So far, I've managed to work around by using different endpoints for "Person" and "Company" customers, but now I need to get both "types" of customers in a single endpoint. And I can't figure out how to write a serializer to get this!
Looking around I found this example for using polymorphic models, however, since the project is quite big now, I'd like to keep things as "vanila" as possible. So my specific questions are:
Is there a way to create a "polymorphic" serializer using the above model definition?
If django-polymorphic (and django-rest-polymorphic) are the way to go, will using them break the functionallity?
I have created an API using DRF That is able to list and view particular records based on the URL pattern specified. For example:
for the request:
curl -v http://127.0.0.1:8000/get_details/120001/
I am able to get a response:
[
{
"subject": "Data Structures",
"course": "CSE"
},
{
"subject": "Thermodynamics",
"course": "Chemistry"
},
{
"subject": "Organic Chemistry",
"course": "Chemistry"
},
{
"subject": "Optics",
"course": "Physics"
}
]
Where '120001' is the user_id the database is searched against.
But the I want the response in the following format:
{'Chemistry': ['Thermodynamics', 'Organic Chemistry'], 'CSE': ['Data Structures'], 'Physics': ['Optics']}
(content wise, I am not considering indentation and other factors)
While I am able to write code for the logic of how to create and populate this dictionary, I am unable to figure out how to return this as response and from where.
I am using generics.ListAPIView as the view class.
Here is my model (models.py):
class Subject(models.Model):
user_id = models.CharField(null = False, max_length=10)
subject = models.CharField(max_length=50)
course = models.CharField(max_length=50)
def __str__(self):
return self.subject
Serializer (serializers.py):
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = ['subject', 'course']
and, views.py (for the first output in default format):
class SubjectView(generics.ListAPIView):
serializer_class = SubjectSerializer
def get_queryset(self):
username = self.kwargs['user_id']
return Subject.objects.filter(user_id = username).only('subject','course')
I have written a logic to create the dictionary to send as response (as described in my desired output) by extracting values using Subject.objects.values(....) and then looping through the results to create my dictionary but I just don't get where (that is, which function) to write it in and return from.
Is there any function provided by the generics.ListAPIView class that can allow me to do this? And if not, then what other alternative approach can I try?
I am an absolute beginner at Django and any help will be appreciated. Also, it will be of great help if anyone can suggest me a practical guide/tutorial/playlist from where I can learn DRF through code examples to speed up my learning process.
Thank you!
You need to override to_representation method of Serializer
from docs
There are some cases where you need to provide extra context to the
serializer in addition to the object being serialized.
class SubjectSerializer(serializers.ModelSerializer):
class Meta:
model = Subject
fields = ['subject', 'course']
def to_representation(self, instance):
data = super(SubjectSerializer, self).to_representation(instance)
# manipulate data here
return data
Goal
If an object has revealed=true it serializes into:
{
"id":1,
"info":"top secret info",
"revealed":true
}
If an object has revealed=false the info field is null:
{
"id":2,
"info":null,
"revealed":false
}
So for a queryset of objects:
[
{
"id":1,
"info":"top secret info 1",
"revealed":true
},
{
"id":2,
"info":null,
"revealed":false
},
{
"id":3,
"info":"top secret info 3",
"revealed":true
}
]
Is it possible to achieve this inside of a Django Rest Framework Model Serializer class?
class InfoSerializer(serializers.ModelSerializer):
class Meta:
model = Info
fields = ('id', 'info', 'revealed')
Background
The DRF docs discuss some advanced serializer usage, and this other post dives into an example. However it doesn't seem to cover this particular issue.
Ideas
A hacky solution would be to iterate over the serialized data afterwards, and remove the info field for every object that has revealed=false. However 1) it involves an extra loop and 2) would need to be implemented everywhere the data is serialized.
I suggest you make the info field appear on all records, but leave it null when revealed is false. If that's acceptable, you should be able to make it happen with a SerializerMethodField.
Alternatively, you could add a revealed_info attribute to the model class, and expose that through the serializer.
#property
def revealed_info(self):
return self.info if self.revealed else None
Context
I have been having conflict. Currenly, I am creating a question answer application. Each question has a tag, and I want to show the most popular tags (e.g. tags that have the most questions associated with it).
Specifics
I am using django-taggit's TaggableManager to do so. Here is the model definition of the Question:
class Question(models.Model):
tags = TaggableManager()
date = models.DateTimeField(default=timezone.now)
text = models.TextField(null=True)
So now I have the several tags attached to questions.
Question
How do I make a tastypie resource to display the tags and sort them by which tag has the most questions associated with it?
My attempts
I have really only had one decently successful attempt at making this work. Here is the tastypie resource:
class TagResource_min(ModelResource):
def dehydrate(self, bundle):
bundle.data['total_questions'] len(Question.objects.filter(tags__slug=bundle.obj.slug))
return bundle
class Meta:
queryset=Tag.objects.all()
Returns a nice JSON with a variable called total_questions with the number of questions associated with it. Although, dehydrate is after the sort_by part of the request cycle specified here, so I cannot sort the resource by total_questions. Here is the JSON:
{
"meta": {
"limit": 20,
"next": null,
"offset": 0,
"previous": null,
"total_count": 13
},
"objects": [
{
"id": 1,
"name": "testing tag",
"resource_uri": "/full/tag/1/",
"slug": "testing-tag",
"total_questions": 2
},
...]
}
Another attempt I had went a different way trying to make the queryset be formed from the Question model:
queryset=Question.objects.filter(~Q(tags__slug=None)).values('tags', 'tags__slug').annotate(total_questions=Count(Tag))
The problem with this was that the filter function turns the result to dict type instead of models.Model type. The key difference was that models.Model has the attribute pk which is a type of identification, and the dict does not have that.
Any contribution is much appriciated! Thank you!
I saw swagger documentation of Flask and Django. In Flask I can design and document my API hand-written.(Include which fields are required, optional etc. under parameters sections).
Here's how we do in Flask
class Todo(Resource):
"Describing elephants"
#swagger.operation(
notes='some really good notes',
responseClass=ModelClass.__name__,
nickname='upload',
parameters=[
{
"name": "body",
"description": "blueprint object that needs to be added. YAML.",
"required": True,
"allowMultiple": False,
"dataType": ModelClass2.__name__,
"paramType": "body"
}
],
responseMessages=[
{
"code": 201,
"message": "Created. The URL of the created blueprint should be in the Location header"
},
{
"code": 405,
"message": "Invalid input"
}
]
)
I can chose which parameters to include, and which not. But how do I implement the same in Django? Django-Swagger Document in
not good at all. My main issue is how do I write my raw-json in Django.
In Django it automates it which does not allows me to customize my json. How do I implement the same kind of thing on Django?
Here is models.py file
class Controller(models.Model):
id = models.IntegerField(primary_key = True)
name = models.CharField(max_length = 255, unique = True)
ip = models.CharField(max_length = 255, unique = True)
installation_id = models.ForeignKey('Installation')
serializers.py
class ActionSerializer(serializers.ModelSerializer):
class Meta:
model = Controller
fields = ('installation',)
urls.py
from django.conf.urls import patterns, url
from rest_framework.urlpatterns import format_suffix_patterns
from modules.actions import views as views
urlpatterns = patterns('',
url(r'(?P<installation>[0-9]+)', views.ApiActions.as_view()),
)
views.py
class ApiActions(APIView):
"""
Returns controllers List
"""
model = Controller
serializer_class = ActionSerializer
def get(self, request, installation,format=None):
controllers = Controller.objects.get(installation_id = installation)
serializer = ActionSerializer(controllers)
return Response(serializer.data)
My questions are
1) If I need to add a field say xyz, which is not in my models how do I add it?
2) Quiet similar to 1st, If i need to add a field which accepts values b/w 3 provided values,ie a dropdown. how do I add it?
3) How I add an optional field? (since in case of PUT request, I might only update 1 field and rest leave it blank, which means optional field).
4) Also how do I add a field that accepts the json string, as this api does?
Thanks
I can do all of these things in Flask by hardcoding my api. But in Django, it automates from my models, which does not(as I believe) gives me the access to customize my api. In Flask, I just need to write my API with hands and then integrate with the Swagger. Does this same thing exist in Django?
Like I just need to add the following json in my Flask code and it will answer all my questions.
# Swagger json:
"models": {
"TodoItemWithArgs": {
"description": "A description...",
"id": "TodoItem",
"properties": {
"arg1": { # I can add any number of arguments I want as per my requirements.
"type": "string"
},
"arg2": {
"type": "string"
},
"arg3": {
"default": "123",
"type": "string"
}
},
"required": [
"arg1",
"arg2" # arg3 is not mentioned and hence 'opional'
]
},
Django-rest-framework does have a lot of useful utility classes such as serializers.ModelSerializer which you are using. However these are optional. You can create totally custom API endpoints.
I suggest that you follow the django rest tutorial here. Part one starts with a custom view like this
from django.forms import widgets
from rest_framework import serializers
from snippets.models import Snippet, LANGUAGE_CHOICES, STYLE_CHOICES
class SnippetSerializer(serializers.Serializer):
pk = serializers.Field() # Note: `Field` is an untyped read-only field.
title = serializers.CharField(required=False,
max_length=100)
code = serializers.CharField(widget=widgets.Textarea,
max_length=100000)
linenos = serializers.BooleanField(required=False)
language = serializers.ChoiceField(choices=LANGUAGE_CHOICES,
default='python')
style = serializers.ChoiceField(choices=STYLE_CHOICES,
default='friendly')
def restore_object(self, attrs, instance=None):
"""
Create or update a new snippet instance, given a dictionary
of deserialized field values.
Note that if we don't define this method, then deserializing
data will simply return a dictionary of items.
"""
if instance:
# Update existing instance
instance.title = attrs.get('title', instance.title)
instance.code = attrs.get('code', instance.code)
instance.linenos = attrs.get('linenos', instance.linenos)
instance.language = attrs.get('language', instance.language)
instance.style = attrs.get('style', instance.style)
return instance
# Create new instance
return Snippet(**attrs)
Note in particular that every API field is specified manually and populated by code here. So they do not have to correspond with model fields.
Your questions
1. Custom field xyz :
As I addressed above, just create a custom serialiser and add a line
class SnippetSerializer(serializers.Serializer):
xyz = serializers.CharField(required=False, max_length=100)
...
2. For options in a list, what you're looking for is a "choice" field.
See the Django documention on choice as Swagger is just the same.
3. How do I make a field optional?
Set the kwarg required=False - note that it's set above for field xyz in my example.
4. Best way to accept a JSON string
Two ways to do this.
Just accept a text string and use a JSON parser in the restore_object code
Define a serialiser that consumes / creates the JSON code and refer to it by name as described here