How do i pass filters to django tastypie - django

Am a bit puzzled by this behavior of django tastypie, am attempting to pass a filter to my resource via backbone.js but nothing get returned.
My Resource class looks like this;
class TenderResource(ModelResource):
class Meta:
queryset = Tender.objects.all()
authorization = Authorization()
list_allowed_methods = ['get', 'post']
detail_allowed_methods = ['get']
resource_name = 'tender'
filtering = {
'dept_ref':ALL,
}
My URLconf file looks like this;
v1_api = Api(api_name='v1')
v1_api.register(TenderResource())
urlpatterns = patterns('',
# Examples:
url(r'^$', DocView.as_view(), name='docview'),
url(r'^api/$', include(v1_api.urls)),
)
Within my apps.js, containing backbone logic, my url that calls the resource with filter looks like this;
TENDER_API = "/api/v1/tender/?dept_ref=119/";
But when i run the application no result are returned!, if i take this URL and run it straight from the browser i.e.
"http://127.0.0.1:8000/api/v1/tender/?dept_ref=119/"
i receive the json result from the resource;
BUT ...
When i pass the following URL with NO filter from my application i receive all the data;
TENDER_API = "/api/v1/tender/";
What am i missing? or What is the best way to pass filters to django tastypie?
Gath

After a couple of searches i got this question in SO that answered mine perfectly well.
Basically you add filtering to your tastypie resource file,
class TenderResource(ModelResource):
class Meta:
....
filtering = {
"dept_ref":ALL
}
then you pass the filter as javascript object called "data" via your backbone collections.fetch method with
MyCollection.fetch({data:{"dept_ref":"119"}})

Related

Getting Primary Key from Tastypie Resource URI

I have a Django - tastypie Resource as follows. It has multiple fields that has Many to Many Relations.
I am trying to get the fields "workflow_initiators", "workflow_submitters" and "workflow_approvers" and add the user to a respective group namely initiators, submitters and approvers.
My JSON Request as follows :
{
"workflow_approvers": [
"/client/api/v1/user/44/",
"/client/api/v1/user/6/"
],
"workflow_dept": [
"/client/api/v1/departments/1/",
"/client/api/v1/departments/2/"
],
"workflow_initators": [
"/client/api/v1/user/44/",
"/client/api/v1/user/6/"
],
"workflow_name": "Hello Workflow",
"workflow_submitters": [
"/client/api/v1/user/43/",
"/client/api/v1/user/6/"
],
}
I want to get the primary key from resource uri of tastypie in a hydrate() or a obj_create() method. In order to get the pk i used a function get_pk_from_uri(). But it throws an error of the following
error : global name 'get_pk_from_uri' is not defined
My Resource as follows :
class WorkflowResource(ModelResource):
workflow_dept = fields.ToManyField(DepartmentsResource, 'workflow_dept', related_name='departments', full=True)
workflow_initators = fields.ToManyField(UserResource, 'workflow_initators', related_name='user')
workflow_submitters = fields.ToManyField(UserResource, 'workflow_submitters', related_name='user')
workflow_approvers = fields.ToManyField(UserResource, 'workflow_approvers', related_name='user')
def obj_create(self, bundle, **kwargs):
submitters = bundle.data.get('workflow_submitters', [])
for submitter in submitters:
print(get_pk_from_uri(submitter)) # Throws Error
#Adding User to Group Logic
# g = Group.objects.get(name='submitters')
# g.user_set.add(your_user)
class Meta:
queryset = WorkflowIndex.objects.filter(is_active=True)
resource_name = 'workflows'
list_allowed_methods = ['get', 'post']
detail_allowed_methods = ['get', 'post', 'put', 'delete', 'patch']
serializer = Serializer()
default_format = 'application/json'
authentication = Authentication()
authorization = DjangoAuthorization()
always_return_data = True
Is there any other method to get the primary key and other fields from resource uri ? I did see get_via_uri() method but was unsure on how to implement the same.
Kindly guide me in resolving this issue.
References :
Stackoverflow - Get model object from tastypie uri
Tastypie Documents - get_via_uri()
You should go back to this post: Get model object from tastypie uri?
The get_pk_from_uri(uri) method that you can see in this answer is not part of the source code of Tastypie, as you can check here.
I suppose the guy wrote it by himself, and you should do the same sothat you won't get the error : global name 'get_pk_from_uri' is not defined error. I didn't tested his method thought.
Solution
David was correct. The get_pk_from_uri() is not an in built tasty-pie method, which i was mistaken.
P.S : I'm just making the answer clear so that someone find it useful.
When needed to extract the resource name or pk from the resource uri of tastypie. we will be able to access them from **kwargs of the below section. The Kwargs contains the follows
kwargs
{u'api_name': 'v1', u'pk': '1', u'resource_name': 'workflows'}
Add the following code to your resources.py or utils.py and include it in your API's to a get this method get_pk_from_uri
from django.core.urlresolvers import resolve, get_script_prefix
def get_pk_from_uri(uri):
prefix = get_script_prefix()
chomped_uri = uri
if prefix and chomped_uri.startswith(prefix):
chomped_uri = chomped_uri[len(prefix) - 1:]
try:
view, args, kwargs = resolve(chomped_uri)
except Resolver404:
raise NotFound("The URL provided '%s' was not a link to a valid resource." % uri)
return kwargs['pk']

tastypie return empty resource_uri in get

This is the resource
class TagResource(ModelResource):
user = tastypie.fields.ForeignKey(UserResource,'user')
class Meta:
queryset = Tag.objects.all()
resource_name = 'tag'
authorization= Authorization()
object_class = Tag
filtering = {
'name' : ALL,
}
simple get request
http://localhost:8000/api/v1/tag/1/?format=json
returns with empty resource_uri
{"created": "2014-03-26T15:14:11.928068",
"id": 1, "name": "test",
"resource_uri": "", "user": ""}
Why is that ?
I tried
def hydrate_resource_uri(self, bundle):
return bundle.get_resource_uri()
It didn't work and i'm pretty sure it's not supposed to require special care.
What am i missing ?
I know this is old, but I know your problem, I just had it on mine, you have a "namespace" on the API URL include or on any URL includes further up in your URL tree.
I had the same problem, and it was because I forgot to register my resource in the urls.py.
Ensure you have something like this in your urls.py file:
myapi.register(TagResource())
I have created a blog for highlighting the same problem. link to my blog
Tastypie give couple of options to create a Model Resource.
ModelResource
NamespacedModelResource
When namespace is included in urls.py then Tastypie logic of generating resource_uri fails as it also expects the same namespace in the api urls. To overcome this problem either one has to remove the namespace from module level urls.py or implement namespace with Tastypie. First solution looks easy but it may break your application. The below code will help you use second approach.
Api.py
from tastypie.resources import NamespacedModelResource
class EntityResource(NamespacedModelResource):
class Meta:
queryset = Entity.objects.all()
allowed_methods = ['get']
resource_name = 'entity'
authentication = SessionAuthentication()
authorization = Authorization()
mymodule/url.py
from tastypie.api import NamespacedApi
from mymodule.api import EntityResource
v1_api = NamespacedApi(api_name='v1', urlconf_namespace='mymodule')
v1_api.register(EntityResource())
urlpatterns = patterns('',
url(r'^api/', include(v1_api.urls)),
)
Make sure you use same namespace for module and its api urls. The above code will surely generate proper resource_uri.
Try to remove the object_class, I think that if this is ModelResource, you don't need this.
This may be because, for some reason, the api_name is missing.
Try to add it in the resource meta.
For instance, if your resource uri is /api/v1/YourResourceName, try to add in your resource Meta:
api_name = 'v1'
Hope it helps.
Only if someone else get this problem caused by a namespace in urls.py. You have to use NamespacedModelResource instead of ModelResource:
from tastypie.resources import NamespacedModelResource
class TagResource(NamespacedModelResource):
user = tastypie.fields.ForeignKey(UserResource,'user')
class Meta:
queryset = Tag.objects.all()
resource_name = 'tag'
authorization= Authorization()
object_class = Tag
filtering = {
'name' : ALL,
}
and then into your module's urls.py
from tastypie.api import NamespacedApi
v1_api = NamespacedApi(api_name='v1', urlconf_namespace='your_module')
v1_api.register(TagResource())
urlpatterns = patterns(
'',
url(r'^api/', include(v1_api.urls)),
)
Check this post.

django tastypie: how do I control RelatedField "fullness" with url parameter?

I'm using django tastypie to publish a model with a Related (ToOne) field to another model resource. The uri is:
/api/map/?format=json
I want to let the client include a full_pages url parameter to get the full related page resource: /api/map/?full_pages=1&format=json
I don't really understand the Relationship Fields docs, but I made a get_full callable:
def get_full(bundle):
if bundle.request.GET.get('full_pages', 0):
return True
return False
I tried passing the callable to the full argument of ToOneField:
from tastypie.contrib.gis import resources as gis_resources
class MapResource(gis_resources.ModelResource):
page = fields.ToOneField('pages.api.PageResource', 'page', full=get_full)
But when I check with pdb, get_full is never invoked.
So then I tried creating a custom FillableToOneField with a full attribute:
class FillableToOneField(fields.ToOneFIeld):
full = get_full
class MapResource(ModelResource):
page = FillableToOneField('pages.api.PageResource', 'page')
Again, get_full is never invoked.
Is there a better, easier way to do this?
After reading Amyth's answer and django-boundaryservice code, I got this to work by defaulting full to True and altering it in the dehydrate method on the Related PageResource:
class MapResource(gis_resources.ModelResource):
page = fields.ToOneField('pages.api.PageResource', 'page', full=True)
pages.api:
class PageResource(ModelResource):
...
def dehydrate(self, bundle):
if not bundle.request.GET.get('full_pages'):
bundle = bundle.data['resource_uri']
return bundle
You can simply achieve this under the dehydrate method as follows.
class MapResource(ModelResource):
page = fields.ToOneField('pages.api.PageResource', 'page')
def dehydrate(self, bundle):
if bundle.request.Get.get('full_pages'):
self.page.full = True
return bundle
and have them send a request as /api/map/?full_pages=True&format=json

Convert POST to PUT with Tastypie

Full Disclosure: Cross posted to Tastypie Google Group
I have a situation where I have limited control over what is being sent to my api. Essentially there are two webservices that I need to be able to accept POST data from. Both use plain POST actions with urlencoded data (basic form submission essentially).
Thinking about it in "curl" terms it's like:
curl --data "id=1&foo=2" http://path/to/api
My problem is that I can't update records using POST. So I need to adjust the model resource (I believe) such that if an ID is specified, the POST acts as a PUT instead of a POST.
api.py
class urlencodeSerializer(Serializer):
formats = ['json', 'jsonp', 'xml', 'yaml', 'html', 'plist', 'urlencoded']
content_types = {
'json': 'application/json',
'jsonp': 'text/javascript',
'xml': 'application/xml',
'yaml': 'text/yaml',
'html': 'text/html',
'plist': 'application/x-plist',
'urlencoded': 'application/x-www-form-urlencoded',
}
# cheating
def to_urlencoded(self,content):
pass
# this comes from an old patch on github, it was never implemented
def from_urlencoded(self, data,options=None):
""" handles basic formencoded url posts """
qs = dict((k, v if len(v)>1 else v[0] )
for k, v in urlparse.parse_qs(data).iteritems())
return qs
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
resource_name = 'foo'
authorization = Authorization() # only temporary, I know.
serializer = urlencodeSerializer()
urls.py
foo_resource = FooResource
...
url(r'^api/',include(foo_resource.urls)),
)
In #tastypie on Freenode, Ghost[], suggested that I overwrite post_list() by creating a function in the model resource like so, however, I have not been successful in using this as yet.
def post_list(self, request, **kwargs):
if request.POST.get('id'):
return self.put_detail(request,**kwargs)
else:
return super(YourResource, self).post_list(request,**kwargs)
Unfortunately this method isn't working for me. I'm hoping the larger community could provide some guidance or a solution for this problem.
Note: I cannot overwrite the headers that come from the client (as per: http://django-tastypie.readthedocs.org/en/latest/resources.html#using-put-delete-patch-in-unsupported-places)
I had a similar problem on user creation where I wasn't able to check if the record already existed. I ended up creating a custom validation method which validated if the user didn't exist in which case post would work fine. If the user did exist I updated the record from the validation method. The api still returns a 400 response but the record is updated. It feels a bit hacky but...
from tastypie.validation import Validation
class MyValidation(Validation):
def is_valid(self, bundle, request=None):
errors = {}
#if this dict is empty validation passes.
my_foo = foo.objects.filter(id=1)
if not len(my_foo) == 0: #if object exists
foo[0].foo = 'bar' #so existing object updated
errors['status'] = 'object updated' #this will be returned in the api response
return errors
#so errors is empty if object does not exist and validation passes. Otherwise object
#updated and response notifies you of this
class FooResource(ModelResource):
class Meta:
queryset = Foo.objects.all() # "id" = models.AutoField(primary_key=True)
validation = MyValidation()
With Cathal's recommendation I was able to utilize a validation function to update the records I needed. While this does not return a valid code... it works.
from tastypie.validation import Validation
import string # wrapping in int() doesn't work
class Validator(Validation):
def __init__(self,**kwargs):
pass
def is_valid(self,bundle,request=None):
if string.atoi(bundle.data['id']) in Foo.objects.values_list('id',flat=True):
# ... update code here
else:
return {}
Make sure you specify the validation = Validator() in the ModelResource meta.

Django Haystack custom SearchView for pretty urls

I'm trying to setup Django Haystack to search based on some pretty urls. Here is my urlpatterns.
urlpatterns += patterns('',
url(r'^search/$', SearchView(),
name='search_all',
),
url(r'^search/(?P<category>\w+)/$', CategorySearchView(
form_class=SearchForm,
),
name='search_category',
),
)
My custom SearchView class looks like this:
class CategorySearchView(SearchView):
def __name__(self):
return "CategorySearchView"
def __call__(self, request, category):
self.category = category
return super(CategorySearchView, self).__call__(request)
def build_form(self, form_kwargs=None):
data = None
kwargs = {
'load_all': self.load_all,
}
if form_kwargs:
kwargs.update(form_kwargs)
if len(self.request.GET):
data = self.request.GET
kwargs['searchqueryset'] = SearchQuerySet().models(self.category)
return self.form_class(data, **kwargs)
I keep getting this error running the Django dev web server if I try and visit /search/Vendor/q=Microsoft
UserWarning: The model u'Vendor' is not registered for search.
warnings.warn('The model %r is not registered for search.' % model)
And this on my page
The model being added to the query must derive from Model.
If I visit /search/q=Microsoft, it works fine. Is there another way to accomplish this?
Thanks for any pointers
-Jay
There are a couple of things going on here. In your __call__ method you're assigning a category based on a string in the URL. In this error:
UserWarning: The model u'Vendor' is not registered for search
Note the unicode string. If you got an error like The model <class 'mymodel.Model'> is not registered for search then you'd know that you haven't properly created an index for that model. However this is a string, not a model! The models method on the SearchQuerySet class requires a class instance, not a string.
The first thing you could do is use that string to look up a model by content type. This is probably not a good idea! Even if you don't have models indexed which you'd like to keep away from prying eyes, you could at least generate some unnecessary errors.
Better to use a lookup in your view to route the query to the correct model index, using conditionals or perhaps a dictionary. In your __call__ method:
self.category = category.lower()
And if you have several models:
my_querysets = {
'model1': SearchQuerySet().models(Model1),
'model2': SearchQuerySet().models(Model2),
'model3': SearchQuerySet().models(Model3),
}
# Default queryset then searches everything
kwargs['searchqueryset'] = my_querysets.get(self.category, SearchQuerySet())