Django Tastypie ToManyField not properly resolving related URIs - django

I've got a Django backend that is using Tastypie to generate its RESTful API. The endpoint of this API does not sit at the top-level of the domain; rather, it exists in a "subfolder" location through the use of WSGIScriptAlias. Most, of the time, this works fine. But I've discovered that when one of my resources has a ToManyField relationship, Tastypie throws an error. Here is some of my relevant code. The WSGIScriptAlias line:
WSGIScriptAlias /english/rubric /var/webapps/django/rubric/apache/django.wsgi
Snippet from the resource that has problems:
class TraitResource(ModelResource):
criteria = fields.ToManyField('rubric.rubric.api.CriterionResource', "criteria", related_name='trait', full = True)
class Meta:
queryset = Trait.objects.all()
authentication=SessionAuthentication()
list_allowed_methods=['get']
always_return_data=True
class CriterionResource(ModelResource):
class Meta:
queryset = Criterion.objects.all()
authentication=SessionAuthentication()
list_allowed_methods=['get']
always_return_data=True
My urls.py:
v1_api = Api(api_name='v1')
v1_api.register(TraitResource())
v1_api.register(CriterionResource())
urlpatterns = patterns('',
(r'^login/$', 'django_cas.views.login'),
(r'^logout/$', 'django_cas.views.logout'),
(r'^api/', include(v1_api.urls))
)
Snippet from the error message:
{
error_message: "{'path': u'english/rubric/api/v1/rubric', 'tried': [[<RegexURLPattern None ^login/$>], [<RegexURLPattern None ^logout/$>], [<RegexURLResolver [<RegexURLPattern api_v1_top_level ^(?P<api_name>v1)/?$>, <RegexURLResolver [<RegexURLPattern api_dispatch_list ...
(it goes on from there to list all of the URLs it has tried and failed to find a match for).
So if I understand correctly, what is happening is that, because of my WSGIScriptAlias, the related objects are reporting their full path as the resource URI, but then when tastypie goes to get the related objects to return with the one I've called, they aren't matching anything in urls.py (because it only needs the url patterns relative to the WSGI app mount point). I've verified this is the case by temporarily modifying the WSGIScriptAlias so the API sits at the root -- and then it all works fine. So what do I need to do in order to keep it with the subfolder URL pattern? I've played around a bit with both overriding prepend_urls and get_resource_uri, but am not really getting how those functions are used in a resource class and so I'm not sure if that's the right strategy for me. Thanks in advance.

What version of tasyypie are you using?
I had the same problem, seems to be solved by upgrading fro 0.9.12 to 0.9.15.

Related

Django syndication framework: prevent appending SITE_ID to the links

According to the documentation here: https://djangobook.com/syndication-feed-framework/
If link doesn’t return the domain, the syndication framework will
insert the domain of the current site, according to your SITE_ID
setting
However, I'm trying to generate a feed of magnet: links. The framework doesn't recognize this and attempts to append the SITE_ID, such that the links end up like this (on localhost):
<link>http://localhost:8000magnet:?xt=...</link>
Is there a way to bypass this?
Here's a way to do it with monkey patching, much cleaner.
I like to create a separate folder "django_patches" for these kinds of things:
myproject/django_patches/__init__.py
from django.contrib.syndication import views
from django.contrib.syndication.views import add_domain
def add_domain_if_we_should(domain, url, secure=False):
if url.startswith('magnet:'):
return url
else:
return add_domain(domain, url, secure=False)
views.add_domain = add_domain_if_we_should
Next, add it to your INSTALLED_APPS so that you can patch the function.
settings.py
INSTALLED_APPS = [
'django_overrides',
...
]
This is a bit gnarly, but here's a potential solution if you don't want to give up on the Django framework:
The problem is that the method add_domain is buried deep in a huge method within syndication framework, and I don't see a clean way to override it. Since this method is used for both the feed URL and the feed items, a monkey patch of add_domain would need to consider this.
Django source:
https://github.com/django/django/blob/master/django/contrib/syndication/views.py#L178
Steps:
1: Subclass the Feed class you're using and do a copy-paste override of the huge method get_feed
2: Modify the line:
link = add_domain(
current_site.domain,
self._get_dynamic_attr('item_link', item),
request.is_secure(),
)
To something like:
link = self._get_dynamic_attr('item_link', item)
I did end up digging through the syndication source code and finding no easy way to override it and did some hacky monkey patching. (Unfortunately I did it before I saw the answers posted here, all of which I assume will work about as well as this one)
Here's how I did it:
def item_link(self, item):
# adding http:// means the internal get_feed won't modify it
return "http://"+item.magnet_link
def get_feed(self, obj, request):
# hacky way to bypass the domain handling
feed = super().get_feed(obj, request)
for item in feed.items:
# strip that http:// we added above
item['link'] = item['link'][7:]
return feed
For future readers, this was as of Django 2.0.1. Hopefully in a future patch they allow support for protocols like magnet.

Django URL reversing NoReverseMatch issue

I'm trying to use reverse to redirect a user to a login page from a third party App I'm using.
The URL config has:
urlpatterns = [
# authentication / association
url(r'^login/(?P<backend>[^/]+){0}$'.format(extra), views.auth,
name='begin'),
How can I accomplish this? I've tried
return redirect(reverse('social:login'), args=('facebook',))
and
return redirect(reverse('social:login'), kwargs={'backend':fb})
(to get to /login/facebook) but I'm getting a NoReverseMatch
The Django URL system and RegExes are confusing me a bit =(
EDIT: All right, it looks like I was making a mess with these URLs.
A simple solution that works (thank you #Alasdair in the comments):
return redirect('social:begin', backend='facebook')

Right way to handle HttpResponseRedirect

I have a Django app I'm trying to deploy. The Apache setting is configured in the way that i access my wsgi app by the following URL:
sitename.com/~amartino
Meaning I have only a wsgi.py file in my public_html directory.
I access my Django site via URL:
sitename.com/~amartino/expofit
For that's the way its been set in urls.py.
urlpatterns = patterns('',
('/param_select/$',session_check(param_select)),
('registration/$',registration),
('result_show/(\d+)',session_check(result_show)),
('^expofit/$',media_clean(start)),
('result_pick/$',session_check(result_pick)),
('mail_report/$',session_check(mail_report)),
('notification/$',session_check(notification)),
However, the problem I'm getting (which didn't show up in development :) ) is that I'm using a hardcoded HttpResponseRedirect in views.py.
...
#If all fields are valid
return HttpResponseRedirect('/expofit/param_select/')
#else reload page
...
Since the production environment doesn't place my site in the root of the URL, i'm having errors now because the upper HttpResponseRedirect translates to
sitename.com/expofit/param_select/
which isn't recognized by Apache.
I suppose I could remove the slash and have:
return HttpResponseRedirect('expofit/param_select/')
which would result in:
sitename.com/~amartino/expofit/registration/expofit/param_select/
but that doesn't seem the right way to do it for I would end up with a huge URL in no time.
Where is the design/configuration flaw here?
"the problem I'm getting that I'm using a hardcoded HttpResponseRedirect"
Well, don't do that then. That's why Django provides the reverse function, which takes your url name and calculates the proper absolute URL.

Tastypie resource name clash

I am currently using tastypie with 2 apps. Each of those apps has a model called Group. They operate very differently, and the only similarity is the name 'Group'.
When only one or the other app is added to the urls file, then it works like a charm. However, as soon as I add both apps, then there's a name clash, and the get_resource_uri() method returns the wrong string. Here is some code:
urls.py
from myapp1.resources import GroupResource as gr_a
from myapp2.resources import GroupResource as gr_b
myapp1_api = Api(api_name='1.0')
myapp1_api.register(gr_a())
myapp2_api = Api(api_name='1.0')
myapp2_api.register(gr_b())
on line 37 of the current api.py file in the tastypie repo I see this code:
if resource_name is None:
raise ImproperlyConfigured("Resource %r must define a 'resource_name'." % resource)
self._registry[resource_name] = resource
Since both of my Group resources have the resource_name of 'group', they get registered on top of each other, even though they are registered at separate urls. Apart from changing the actual resource name, is there a way around this name clash?
Update
The uris would look something like this:
/myapp1/1.0/group/
/myapp2/1.0/group/
Ideally I don't want myapp1 and myapp2 to know about each other (ie the Group class is distinct). The workaround for this is to change myapp2.Group to myapp2.MyGroup (to avoid the name clash), but its really not that elegant.
In all my resources I had the resource_name blank, since I was happy with the default name. Also I wanted a url such as /myapp/1.0/group/ and not /myapp/1.0/myapp/group/
What I've now done is change all the resource_name attributes to the form "myapp/group" and bound them all to an empty url. This gave me a nice such as: /1.0/myapp/group/ while making sure there are no name clashes in the resources.

Decoupling django apps 2 - how to get object information from a slug in the URL

I am trying to de-couple two apps:
Locations - app containing details about some location (town, country, place etc)
Directory - app containing details of places of interest (shop, railway station, pub, etc) - all categorised.
Both locations.Location and directory.Item contain lat/lng coords and I can find items within a certain distance of a specific lat/lng coord.
I'd like to use the following URL structure:
/locations/<location_slug>/directory/<category_slug>/
But I don't want to make my directory app reliant on my location app.
How can I translate this url to use a view like this in my directory app?
items_near(lat, lng, distance, category):
A work around would be to create a new view somewhere that translates this - but where should I put that? if it goes in the Directory app, then I've coupled that with my Location app, and vice versa.
Would a good idea be to place this workaround code inside my project URLs file? Thus keeping clear of both apps? Any issues with doing it like this?
For your urlpattern to work, the view function invoked has to be aware of both Locations and Directories. The short answer is you can put this view function anywhere you want - it's just a python function. But there might be a few logical places for it, outside of your Directory or Location app, that make sense.
First off, I would not put that view code in in your top-level urls.py, as that file is intended for URLconf related code.
A few options of where to put your view:
Create a new view function in a file that lives outside of any particular app. <project_root>/views.py is one possibility. There is nothing wrong with this view calling the item_near(..) view from the Directory app.
# in myproject/urls.py
urlpatterns = (
...
(r'/locations/(?P<location_slug>[\w\-]+)/directory/(?P<category_slug>[\w\-]+)/',
'myproject.views.items_near_from_slug')
)
# in myproject/views.py
from directory.views import items_near
def items_near_from_slug(request, location_slug, category_slug):
location = get_object_or_404(Location, slug=location_slug)
distance = 2 # not sure where this comes from
# And then just invoke the view from your Directory app
return items_near(request, location.lat, location.lng, distance, category_slug)
Create a new app and put the code there, in <my_new_app>/views.py. There is no requirement that a Django app need to have a models.py, urls.py, etc. Just make sure to include the __init__.py if you want Django to load the app properly (if, for instance, you want Django to automatically find templatetags or app specific templates).
Personally, I would go with option 1 only if the project is relatively simple, and <project_root>/views.py is not in danger of becoming cluttered with views for everything. I would go with option 2 otherwise, especially if you anticipate having other code that needs to be aware of both Locations and Directories. With option 2, you can collect the relevant urlpatterns in their own app-specific urls.py as well.
From the django docs here if you're using django >=1.1 then you can pass any captured info into the included application. So splitting across a few files:
# in locations.urls.py
urlpatterns = ('',
(r'location/(?P<location_slug>.*?)/', include('directory.urls')),
#other stuff
)
# in directory.urls.py
urlpatterns = ('',
(r'directory/(?P<directory_slug>.*?)/', 'directory.views.someview'),
#other stuff
)
# in directory.views.py
def someview(request, location_slug = None, directory_slug = None):
#do stuff
Hope that helps. If you're in django < 1.1 I have no idea.
Irrespective of how much ever "re-usable" you make your app, inevitably there is a need for site-specific code.
I think it is logical to create a "site-specific" application that uses the views of the reusable and decoupled apps.