By default, when using django-tastypie and fetching a resource list, the response is of the format:
{
"meta": {
"limit": 20,
"next": null,
"offset": 0,
"previous": null,
"total_count": 3
},
"objects": [{
"body": "Welcome to my blog!",
"id": "1",
"pub_date": "2011-05-20T00:46:38",
"resource_uri": "/api/v1/entry/1/",
"slug": "first-post",
"title": "First Post",
"user": "/api/v1/user/1/"
},
...
]
}
I've dug into the documentation and looked & looked, but I can't seem to find any kind of meta option or setting to change the "objects" key to actually describe the returned items. For example, let's say I have list of locations in one api call and a list of people in another. I'd like to be able to differentiate the key to "locations" and "people". The real reason for this is because I'm using RestKit on iOS and want to be able to set up multiple mappings.
The Resource hooks alter_* can be used to alter the structure of the data.
An example Resource using 'locations' would be:
class MyLocationsResource(ModelResource):
def alter_list_data_to_serialize(self, request, data):
data['locations'] = data['objects']
del data['objects']
return data
def alter_deserialized_list_data(self, request, data):
data['objects'] = data['locations']
del data['locations']
return data
Related
I asked a similar question here about setting the default value of a TextChoices field on a model. Now I'm taking it to the next level.
I've moved the choices settings from the model to the serializer ChoiceField. The site is Django/DRF/django_tenants backend with Vue frontend. I want the front/backends to be using the same set of choices and, just as important, the same default values. So I created an OptionGroup table. Here are two sample rows from that table (some options omitted):
{
"results": {
"id": 1,
"name": "payment_method",
"defined_in": "option_table",
"default_option": "5",
"options": [
{
"id": 2,
"name": "Bank Transfer",
"value": "2"
},
{
"id": 5,
"name": "Credit Card",
"value": "5"
},
{
"id": 8,
"name": "PayPal",
"value": "8"
}
]
}
}
{
"results": {
"id": 3,
"name": "invoice_type",
"defined_in": "class",
"app_label": "invoices",
"choices_class": "InvoiceType",
"default_option": "standard",
"options": [
{
"name": "Standard",
"value": "standard"
},
{
"name": "Retainer",
"value": "retainer"
},
{
"order": 1,
"name": "Estimate",
"value": "estimate"
}
]
}
}
The first one (defined_in = "option_table") has its options defined in a related Option model. The second one has its options defined in a subclass of TextChoices in the backend code. In addition to keeping the front/backend in sync for options and default values, this will also allow users to set which option for each select list field they want as the default value. Note the default_option field above.
I created a class, extended from the ChoiceField class (from DRF), and added logic to the __init__() method to get the default value from the OptionGroup model and set it for the serializer field being defined.
class CustomChoiceField(serializers.ChoiceField):
def __init__(self, choices, **kwargs):
option_group = kwargs.pop('option_group', None)
if option_group and 'default' not in kwargs:
group = OptionGroup.objects.filter(name__iexact=option_group)
if group and group.default_option:
kwargs['default'] = group.default_option
super().__init__(choices, **kwargs)
And this is how it is being used:
type = CustomChoiceField(
choices=choices.InvoiceType.choices,
option_group='invoice_type',
)
The problem is that the active schema at that point is "public" instead of the actual tenant schema. It seems the __init__() for my custom class is being executed too early in the request process - before django_tenants has connected to the proper schema.
Does anyone have a suggestion as to how this can be accomplished, or is what I'm trying to do just not possible?
I have following resources
Question /api/v1/questions/{questionID}
Category /api/v1/categories/{questionID}
Comment /api/v1/comments/{questionID}
Recommendations /api/v1/recommendations/{questionID}
Pattern answers /api/v1/patternanswers/{questionID}
The API is made to deliver children objects by parent object ID, and I don't see that compatible with Ember-data out-of-the-box (or without hacking, only proper configuration).
Question resource gets me JSON like this:
{
"errors": [],
"data": {
"created": 1439832769693,
"updated": 1440012378723,
"userID": 20,
"user": {
"password": null,
"created": null,
"updated": null,
"photoID": null,
"photo": null,
"email": "notsoimportant#host.com",
"emailConfirmed": false,
"phoneNumber": null,
"phoneNumberConfirmed": false,
"accessFailedCount": 0,
"id": 20,
"userName": "qwerty"
},
"categories": [], // does not exist in json
"addCategoriesIDs": [],
"removeCategoriesIDs": [],
"recommendations": [],
"removeRecommendstionIDs": [],
"patternAnswers": [],
"removePatternAnswerIDs": [],
"comments": [], //does not exist in json
"hint": null,
"version": 1,
"commonVersion": 8,
"id": 7,
"questionText": "What is your name?",
"weight": 0,
"answerType": 0,
"status": 0,
"estimatedTime": null,
"private": false
}
}
Properties "patternAnswers", "recommendations" are in the question, but "comments" and "categories" are not.
More over I don't get Question with listed properties filled with IDs of dependent objects. I wish I could get
{
"errors": [],
"data": {
"comments" : [11,12,13,14],
"categories" : [21,22,23,24],
"recommendations": [1,2,3,4],
"removeRecommendstionIDs": [],
"patternAnswers": [5,6,7,8],
"removePatternAnswerIDs": [],
"hint": null,
"version": 1,
"commonVersion": 8,
"id": 7,
....
}
}
.. but they are empty :(
I would like to configure my Question Model and Question Serializer to grab all that dependent objects asynchronously.
I would like to get everything just issuing
var QuestionModel = this.store.find('question',7);
var comments = QuestionModel.get('comments');
var categories= QuestionModel.get('categories');
var recommendations= QuestionModel.get('recommendations');
var patternAnswers= QuestionModel.get('patternAnswers');
How is it feasible?
If you want this to work out-of-the-box, you should set up your API according to ember guides (http://guides.emberjs.com/v2.0.0/models/the-rest-adapter/). That said, endpoints must be:
Question /api/v1/questions/{questionID}
Category /api/v1/categories/{categoryID}
Comment /api/v1/comments/{commentID}
Recommendations /api/v1/recommendations/{recommendationID}
Pattern answers /api/v1/patternanswers/{patternanswerID}
And payload from GET /api/v1/questions/{questionID} should look like:
{
"question": {
"categories": [], // an array of ids
"recommendations": [], // an array of ids
"patternAnswers": [], // an array of ids
"comments": [], // an array of ids
"id": Number, // question id
... // others
}
}
Otherwise, you need to change the default behavior of DS.Model or/and RESTAdapter, or use QuestionController with attached models of Category, Comment, etc. Or you can pack all things in model and setupController hooks of route represented that data.
I suggest you to answer yourself these questions:
Do you really need such a complex model at the client side? The only reason you might need model is the requirement to create/change data in that set all at once.
Can you change the API? It's better to change API in regard to REST specifications, to not maintain spaghetti at client side.
How far you can go with changing the API? There is another but similar scheme:
Question /api/v1/questions/{questionID}
Category /api/v1/questions/{questionID}/categories/{categoryID}
Categories /api/v1/questions/{questionID}/categories
It's a bit easier to configure in Ember then your version.
With Ember Data and Jsonapi. How is a json paginated resource supposed to look like?
I built my response so it looks like:
"meta": {
"page": {
"number": 1,
"size": 5,
"total": 39
}
},
"links": {
"self": "http://localhost:3099/api/v1/articles",
"prev": null,
"next": "http://localhost:3099/api/v1/articles?page[number]=2",
"first": "http://localhost:3099/api/v1/articles?page[number]=1",
"last": "http://localhost:3099/api/v1/articles?page[number]=39"
},
"data": [
...
]
But I am not exactly sure if this is the right format. based on the explanation at http://jsonapi.org/format/#fetching-pagination
Or, are the pagination links (i.e. prev, next, first and last) supposed to be in meta.page ?
You could use ember-cli-pagination and its format to do pagination. I'm pretty sure Ember Data does not follow the JSON API spec strictly.
Based on your sample this could be a format:
{
"meta": {
"total_pages": 3,
"page": 1
},
"articles": [
{"id": 1, "title": "Hello World", "body": "More to Come"},
// ......
]
}
The request URL of this payload could be http://localhost:3099/api/v1/articles?page=1. See the API for more info.
Ember Data doesn't follow the JSON spec strictly so you should concentrate more on setting up the JSON with what ED needs. I would personally move the 'links' info into the meta tag. Otherwise Ember-Data will attempt to put them into a model called 'links', which may not be what you want. If you do intend to store those inside a separate 'links' model, then what you have is fine.
we would like to add lazy loading functionality to our ember project, but it looks like ember will always override fields not returned by the response JSON with NULL. First I get a list of users:
GET https://example.com/users
{
"users": [
{
"id": 1,
"name": 'user1',
"email": 'email#user1.com',
"images": [],
"posts": []
},
{
"id": 2,
"name": 'user2',
"email": 'email#user2.com',
"images": [],
"posts": []
},
{
"id": 3,
"name": 'user3',
"email": 'email#user3.com',
"images": [],
"posts": []
},
]
}
This provides a minimal set of user information, with two empty hasMany relations "images" and "posts".
Now, if somebody want to see the users posts or images he would click a button which triggers the lazy loading:
GET https://example.com/userImages/1
{
"user": {
"id": 1,
"images": [1,2,3,4]
},
"images": [
{
"id": 1,
"name": "image1",
"path" "path/to/img1/"
},
{
"id": 2,
"name": "image2",
"path" "path/to/img2/"
},
{
"id": 3,
"name": "image3",
"path" "path/to/img3/"
},
{
"id": 4,
"name": "image4",
"path" "path/to/img4/"
}
]
}
To reduce traffic to a minimum, only the newly loaded information is included. After the adapter has deserialzed and pushed the new data to the store, all fields from User1 which are not included in the payload (name, email) are set to NULL in the ember store (tested with store.pushPayload('model', payload)).
Is there a possibility to update only incoming data? Or is there a common best practice to handle such a case?
Thanks in advance for your help
EDIT:
One possibility would be to extend the ember-data "_load()" function with the block
for (var key in record._data) {
var property = record._data[key];
if( typeof(data[key]) == 'object' && data[key] == null ) {
data[key] = property;
}
}
But this is the worst possible solution I can imagine.
I think what you want is the store's update method. It's like push (or pushPayload), except that it only updates the data that you give it.
Your property returns a promise and that promise returns whatever came back from the server.
foobar: function() {
return this.store.find('foobar');
}
When the promise resolves, you have two versions of the data, the one already rendered in the client (dataOld) and the one that just returned from the backend (dataNew). To update the client without removing what hasn't change, you have to merge the old and the new. Something along the lines of:
foobar: function() {
var dataOld = this.get('foobar');
return this.store.find('foobar').then(function(dataNew) {
return Ember.$.merge(dataOld, dataNew);
});
}
I am working with Web Api and Ember JS, all worked well until i started using Ember Data, which seems to need the object name included in the json.
How can i add this?
Currently getting :
[
{
"id": 1,
"title": "maxima",
"subTitle": null,
"description": "Maxima de boot",
"image1": null,
"image2": null,
"active": false,
"skipper": null
},
{
"id": 2,
"title": "beatrix",
"subTitle": null,
"description": "Beatrix de boot",
"image1": null,
"image2": null,
"active": false,
"skipper": null
}
]
Thanks so much!
Having worked with Web API and Ember Data plenty you will find it easier to fix the json client side using a serializer. Assuming you're finding posts, `this.store.find('post') you would create a custom serializer for it:
App.PostSerializer = DS.RESTSerializer.extend({
extractArray: function(store, type, payload) {
payload = {posts: payload};
return this._super(store, type, payload);
}
});
Example: http://emberjs.jsbin.com/OxIDiVU/623/edit
Read more about it (and other methods) here http://emberjs.com/api/data/classes/DS.RESTSerializer.html#method_extractArray and here https://github.com/emberjs/data/blob/master/TRANSITION.md
Ember data doesn't know how to map that data, your json should look like this instead.
{ blogs: [
{ "id": 1, "title": "maxima", "subTitle": null, "description": "Maxima de boot", "image1": null, "image2": null, "active": false, "skipper": null },
{ "id": 2, "title": "beatrix", "subTitle": null, "description": "Beatrix de boot", "image1": null, "image2": null, "active": false, "skipper": null } ]
}
Now ember knows that the objects from the server, should be mapped to the blogs model.
I'm pretty new at this and haven't worked with Ember before, but... Instead of returning an array of "stuff" objects, can you not modify the code to return an object that internally just contains an array of "stuff" objects? Then, I expect the default json serializer should take care of this for you.