Url_For of a Custom Restful Resource (Composite Key; Not Just Id)

url_for of a custom RESTful resource (composite key; not just id)

How about fixing url_for for the PostsController? May not be very elegant but it is DRY and things should just work.

# app/controllers/posts_controller.rb 
class PostsController < ApplicationController

protected

def url_for(options = {} )
if options[:year].class.to_s == "Post"
obj = options[:year]
options[:year] = obj.year
options[:month] = obj.month
options[:slug] = obj.slug
end
super(options)
end

end

composite key resource REST service

I too like the aesthetics of /api/Products/1/Parts/2. You could also have multiple routes go to the same action, so you could double up and also offer /api/Parts/2/Products/1 as an alternate URL for the same resource.

As for POST, you already know the composite key. So why not eliminate the need for POST and just use PUT for both creation and updates? POST to a collection resource URL is great if your system generates the primary key, but in cases where you have a composite of already known primary keys, why do you need POST?

That said, I also like the idea of having a separate ProductPartAssocController to contain the actions for these URL's. You would have to do a custom route mapping, but if you're using something like AttributeRouting.NET that is very easy to do.

For example we do this for managing users in roles:

PUT, GET, DELETE /api/users/1/roles/2
PUT, GET, DELETE /api/roles/2/users/1

6 URL's, but only 3 actions, all in the GrantsController (we call the gerund between users and roles a "Grant"). Class ends up looking something like this, using AttributeRouting.NET:

[RoutePrefix("api")]
[Authorize(Roles = RoleName.RoleGrantors)]
public class GrantsController : ApiController
{
[PUT("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
[PUT("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
public HttpResponseMessage PutInRole(int userId, int roleId)
{
...
}

[DELETE("users/{userId}/roles/{roleId}", ActionPrecedence = 1)]
[DELETE("roles/{roleId}/users/{userId}", ActionPrecedence = 2)]
public HttpResponseMessage DeleteFromRole(int userId, int roleId)
{
...
}

...etc
}

This seems a fairly intuitive approach to me. Keeping the actions in a separate controller also makes for leaner controllers.

Composite keys in Django Rest Framework?

You can generate the custom urls using the lookup_fields. Can you follow this link below here Multiple LookUp Args and Custom Urls.

django-rest-framework HyperlinkedIdentityField with multiple lookup args

I'm not sure if you've solved this problem yet, but this may be useful for anyone else who has this issue. There isn't much you can do apart from overriding HyperlinkedIdentityField and creating a custom serializer field yourself. An example of this issue is in the github link below (along with some source code to get around it):

https://github.com/tomchristie/django-rest-framework/issues/1024

The code that is specified there is this:

from rest_framework.relations import HyperlinkedIdentityField
from rest_framework.reverse import reverse

class ParameterisedHyperlinkedIdentityField(HyperlinkedIdentityField):
"""
Represents the instance, or a property on the instance, using hyperlinking.

lookup_fields is a tuple of tuples of the form:
('model_field', 'url_parameter')
"""
lookup_fields = (('pk', 'pk'),)

def __init__(self, *args, **kwargs):
self.lookup_fields = kwargs.pop('lookup_fields', self.lookup_fields)
super(ParameterisedHyperlinkedIdentityField, self).__init__(*args, **kwargs)

def get_url(self, obj, view_name, request, format):
"""
Given an object, return the URL that hyperlinks to the object.

May raise a `NoReverseMatch` if the `view_name` and `lookup_field`
attributes are not configured to correctly match the URL conf.
"""
kwargs = {}
for model_field, url_param in self.lookup_fields:
attr = obj
for field in model_field.split('.'):
attr = getattr(attr,field)
kwargs[url_param] = attr

return reverse(view_name, kwargs=kwargs, request=request, format=format)

This should work, in your case you would call it like this:

url = ParameterisedHyperlinkedIdentityField(view_name="user-device-detail", lookup_fields=(('<model_field_1>', 'user_pk'), ('<model_field_2>', 'uid')), read_only=True)

Where <model_field_1> and <model_field_2> are the model fields, probably 'id' and 'uid' in your case.

Note the above issue was reported 2 years ago, I have no idea if they've included something like that in newer versions of DRF (I haven't found any) but the above code works for me.

What are best practices for REST nested resources?

What you have done is correct. In general there can be many URIs to the same resource - there are no rules that say you shouldn't do that.

And generally, you may need to access items directly or as a subset of something else - so your structure makes sense to me.

Just because employees are accessible under department:

company/{companyid}/department/{departmentid}/employees

Doesn't mean they can't be accessible under company too:

company/{companyid}/employees

Which would return employees for that company. It depends on what is needed by your consuming client - that is what you should be designing for.

But I would hope that all URLs handlers use the same backing code to satisfy the requests so that you aren't duplicating code.

django rest framework change primary key to use a unqiue field

Assuming your GameProfile model looks like:

class GameProfile(models.Model)
user = models.OneToOneField('User')

The serializer will be:

class GameProfileSerializer(serializers.HyperlinkedModelSerializer):
user_id = serializers.Field(source='user.id')

class Meta:
model = GameProfile

Set the .lookup_field attribute on the view correctly:

    lookup_field = 'user_id'

Url will be:

/gameprofile/<user_id>

Flask-RESTful API: multiple and complex endpoints

Your are making two mistakes.

First, Flask-RESTful leads you to think that a resource is implemented with a single URL. In reality, you can have many different URLs that return resources of the same type. In Flask-RESTful you will need to create a different Resource subclass for each URL, but conceptually those URLs belong to the same resource. Note that you have, in fact, created two instances per resource already to handle the list and the individual requests.

The second mistake that you are making is that you expect the client to know all the URLs in your API. This is not a good way to build APIs, ideally the client only knows a few top-level URLs and then discovers the rest from data in the responses from the top-level ones.

In your API you may want to expose the /api/users and /api/cities as top-level APIs. The URLs to individual cities and users will be included in the responses. For example, if I invoke http://example.com/api/users to get the list of users I may get this response:

{
"users": [
{
"url": "http://example.com/api/user/1",
"name": "John Smith",
"city": "http://example.com/api/city/35"
},
{
"url": "http://example.com/api/user/2",
"name": "Susan Jones",
"city": "http://example.com/api/city/2"
}
]
}

Note that the JSON representation of a user includes the URL for that user, and also the URL for the city. The client does not need to know how to build these, because they are given to it.

Getting cities by their name

The URL for a city is /api/city/<id>, and the URL to get the complete list of cities is /api/cities, as you have it defined.

If you also need to search for cities by their name you can extend the "cities" endpoint to do that. For example, you could have URLs in the form /api/cities/<name> return the list of cities that match the search term given as <name>.

With Flask-RESTful you will need to define a new Resource subclass for that, for example:

    class CitiesByNameAPI(Resource):
def __init__(self):
# ...
def get(self, name):
# ...

api.add_resource(CitiesByNameAPI, '/api/cities/<name>', endpoint = 'cities_by_name')

Getting all the users that belong to a city

When the client asks for a city it should get a response that includes a URL to get the users in that city. For example, let's say that from the /api/users response above I want to find out about the city of the first user. So now I send a request to http://example/api/city/35, and I get back the following JSON response:

{
"url": "http://example.com/api/city/35",
"name": "San Francisco",
"users": "http://example/com/api/city/35/users"
}

Now I have the city, and that gave me a URL that I can use to get all the users in that city.

Note that it does not matter that your URLs are ugly or hard to construct, because the client never needs to build most of these from scratch, it just gets them from the server. This also enables you to change the format of the URLs in the future.

To implement the URL that gets users by city you add yet another Resource subclass:

    class UsersByCityAPI(Resource):
def __init__(self):
# ...
def get(self, id):
# ...

api.add_resource(UsersByCityAPI, '/api/cities/<int:id>/users', endpoint = 'users_by_city')

I hope this helps!



Related Topics



Leave a reply



Submit