How add an public API to an intranet-like site? - django

I run a Pinax-site for collaborative purposes. I added 'account.middleware.AuthenticatedMiddleware' to 'MIDDLEWARE_CLASSES' in order to not allow anonymous access to anything on the site.
But now I need public APIs to be enabled. Is there any solutions besides adding 'login_required'-decorator at all the views that still need to be private?
edit
Gregor Müllegger answer doesn't work. settings.AUTHENTICATED_EXEMPT_URLS seems to get overwritten somewhere in the code
class AuthenticatedMiddleware(object):
def __init__(self, login_url=None, redirect_field_name=REDIRECT_FIELD_NAME):
if login_url is None:
login_url = settings.LOGIN_URL
self.redirect_field_name = redirect_field_name
self.login_url = login_url
self.exemptions = [
r"^%s" % settings.MEDIA_URL,
r"^%s" % settings.STATIC_URL,
r"^%s$" % login_url,
]
print "settings.AUTHENTICATED_EXEMPT_URLS ",settings.AUTHENTICATED_EXEMPT_URLS
if ( settings.AUTHENTICATED_EXEMPT_URLS):
self.exemptions += settings.AUTHENTICATED_EXEMPT_URLS
print "settings.AUTHENTICATED_EXEMPT_URLS ",settings.AUTHENTICATED_EXEMPT_URLS
doesn't print my settings but this:
settings.AUTHENTICATED_EXEMPT_URLS ['^/account/signup/$', '^/account/password_reset', '^/account/confirm_email', '^/openid']
I will try to fix it.

Have a look at the source code of AuthenticatedMiddleware.
It reveals that there is a setting called AUTHENTICATED_EXEMPT_URLS. It can contain regular expressions that are left public. Set it to something like this in your settings.py:
AUTHENTICATED_EXEMPT_URLS = (r"^api/",)
This will make any URLs below /api/ available without being logged in.t

Related

Django rest framework: How to reuse an app using different settings?

I have an application that has it's own urls and uses specific settings to access another api.
I would like to use this same app again within the same project, but with different urls and using a seperate endpoint.
So just setup new urls, and point to the same views from the original app but inject different settings.
For example one of my views is:
class SummaryVMsList(ListAPIView):
'''
VM Summary
'''
def list(self, request, *args, **kwargs):
'''
Return a list of processed vm's
'''
v_token = settings.VTOKEN
base_url = settings.VURL
v_password = settings.VPASSWORD
v_username = settings.VUSERNAME
session = Session()
session.headers.update({
'v_token': v_token
})
client = VClient(
url=base_url,
v_username=v_username,
v_password=v_password,
session=session
)
try:
repos = client.get_summary_vms()
return Response(data=repos, status=status.HTTP_200_OK)
except VError as err:
return Response(
data={'error': str(err)},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
# log the error
finally:
client.logout()
How would I be able to change the setting values: settings.VTOKEN, settings.VURL, settings.VPASSWORD and settings.VUSERNAME
Based on whick url is used:
In urls-site1.py
app_name = 'v_site1'
urlpatterns = [
path('vm-summary', views.SummaryVMsList.as_view(), name='vms_list'),
]
In urls-site2.py:
app_name = 'v_site2'
urlpatterns = [
path('vm-summary', views.SummaryVMsList.as_view(), name='vms_list'),
]
In this case, a better idea would be to store such params in constants instead of settings. The settings in Django used for different environments, in the end, it can be hard to work with it.
Also, take note that there are two exactly the same URL vm-summary and it could cause a name collision.

Django signup/login not directing users to ?next=/my/url

Setup
I use [django-allauth][1] for user accounts.
# urls.py
url(r'^login$', allauth.account.views.login, name="account_login"),
url(r'^join$', allauth.account.views.signup, name="account_signup"),
.
# settings.py
LOGIN_REDIRECT_URL = '/me'
LOGIN_URL = '/join' # users sent here if they run into #login_required decorator
# To collect additional info if user signs up by email:
ACCOUNT_SIGNUP_FORM_CLASS = 'allauth.account.forms.WinnerSignupForm'
.
That custom signup form:
# account/forms.py
from .models import Winner, FriendCode
class WinnerSignupForm(forms.ModelForm):
"""
This is the additional custom form to accompany the default fields email/password (and maybe username)
"""
class Meta:
model = Winner
fields = ('author_display_name','signupcode',)
widgets = {'author_display_name':forms.TextInput(attrs={
'placeholder': _('Display Name'), # 'Display Name',
'autofocus': 'autofocus',
})
,
'signupcode': forms.TextInput(attrs={
'placeholder': _('Invite code (optional)'),
'autofocus': 'autofocus'
})
}
def signup(self, request, user):
# custom code that performs some account setup for the user
# just runs a procedure; there's no "return" at end of this block
I don't think my custom WinnerSignupForm is causing the issue, because the problem persists even if I disable it (i.e., I comment out this line from settings.py: ACCOUNT_SIGNUP_FORM_CLASS = 'allauth.account.forms.WinnerSignupForm')
Behaviour
0. Without ?next=/some/url parameter:
Thanks to LOGIN_REDIRECT_URL in settings.py, if I visit example.com/join or example.com/login, I'll wind up on example.com/me
That is fine.
1. If I am already logged in, everything works as expected:
A) If I visit https://example.com/login?next=/some/url,
I'm immediately forwarded to https://example.com/some/url (without being asked to log in, since I am already logged in).
I conclude the /login view is correctly reading the next=/some/url argument.
B) Similarly, if I visit https://example.com/join?next=/some/url, I'm immediately forwarded to https://example.com/some/url.
I conclude the /join view is also correctly reading the next=/some/url argument.
2. If I log in or sign up by social account, everything works as expected
This uses allauth/socialaccount
After I sign up or log in, I'm forwarded to https://example.com/some/url
However, here's the problem:
3. But! If I log in by email, ?next=/some/url is being ignored:
A) If I visit https://example.com/login?next=/some/url, I'm brought first to the /login page.
If I log in by email, I'm then forwarded to https://example.com/me
For some reason now, the ?next= is not over-riding the default LOGIN_REDIRECT_URL in settings.
(If I log in via Twitter, the ?next= paramter is correctly read, and I'm brought to https://example.com/some/url.)
B) Similarly, if I visit https://example.com/join?next=/some/url, I'm brought first to the /join (signup) page, and after successful login by email, I'm brought to /me, i.e., the fallback LOGIN_REDIRECT_URL defined in settings.py.
Inspecting the POST data in the signup/login form, the "next" parameter is there alright: {"next": "/some/url", "username": "myusername", "password": "..."}
More context
Extracts from django-allauth:
# allauth/account/views.py
from .utils import (get_next_redirect_url, complete_signup,
get_login_redirect_url, perform_login,
passthrough_next_redirect_url)
...
class SignupView(RedirectAuthenticatedUserMixin, CloseableSignupMixin,
AjaxCapableProcessFormViewMixin, FormView):
template_name = "account/signup.html"
form_class = SignupForm
redirect_field_name = "next"
success_url = None
def get_form_class(self):
return get_form_class(app_settings.FORMS, 'signup', self.form_class)
def get_success_url(self):
# Explicitly passed ?next= URL takes precedence
ret = (get_next_redirect_url(self.request,
self.redirect_field_name)
or self.success_url)
return ret
...
.
# allauth/account/utils.py
def get_next_redirect_url(request, redirect_field_name="next"):
"""
Returns the next URL to redirect to, if it was explicitly passed
via the request.
"""
redirect_to = request.GET.get(redirect_field_name)
if not is_safe_url(redirect_to):
redirect_to = None
return redirect_to
def get_login_redirect_url(request, url=None, redirect_field_name="next"):
redirect_url \
= (url
or get_next_redirect_url(request,
redirect_field_name=redirect_field_name)
or get_adapter().get_login_redirect_url(request))
return redirect_url
_user_display_callable = None
...
I'm pretty sure it was originally working when I installed [django-allauth][1] out of the box. I must have somehow interfered to break this ?next=/some/url functionality, though I can't remember the last time it was working or find out what I've done to mess things up.
Any tips on troubleshooting would be greatly appreciated.
(In case relevant -- perhaps settings are not being read correctly;
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True in settings.py seems to be ignored, users have to log in after resetting their password.)
#Akshay, the following work-around worked for me.
I added the following lines to allauth/account/adapter.py, within the get_login_redirect_url sub-function.
goto = request.POST.get('next', '')
if goto:
return goto
To clarify, the result looks like this:
class DefaultAccountAdapter(object):
# no change to stash_verified_email, unstash_verified_email, etc.
# edit only get_login_redirect_url as follows
def get_login_redirect_url(self, request):
"""
Returns the default URL to redirect to after logging in. Note
that URLs passed explicitly (e.g. by passing along a `next`
GET parameter) take precedence over the value returned here.
"""
assert request.user.is_authenticated()
url = getattr(settings, "LOGIN_REDIRECT_URLNAME", None)
if url:
warnings.warn("LOGIN_REDIRECT_URLNAME is deprecated, simply"
" use LOGIN_REDIRECT_URL with a URL name",
DeprecationWarning)
else:
url = settings.LOGIN_REDIRECT_URL
# Added 20170301 - look again for ?next parameter, as work-around fallback
goto = request.POST.get('next', '')
if goto:
return goto
print "found next url in adapter.py"
else:
print "no sign of next in adapter.py"
# end of work-around manually added bit
return resolve_url(url)
# leave remainder of fn untouched
# get_logout_redirect_url, get_email_confirmation_redirect_url, etc.
I still don't know how I broke this functionality in the first place, so I won't mark my answer as accepted/best answer. It does, however, resolve the issue I had, so I am happy. Hope this is useful to others.

Most appropriate way to redirect page after successful POST request in Django

I have build a view and a form in Django1.5. If the POST request is successful (based on some values I set) then I need the page to redirect to another URL which is created simultaneously.
Otherwise, if the POST was not successful I need to stay on the same page. Right now I have solved the problem as following but I am quite sure this is not the best way to do it:
This is a part of my view:
def layer_create(request, template='layers/layer_create.html'):
if request.method == 'GET':
....
elif request.method == 'POST':
out = {}
...
new_table = 'something that comes from the form'
if form.is_valid():
...
try:
...
out['success'] = True
except:
...
out['success'] = False
finally:
if out['success']:
status_code = 200
# THIS IS THE PART WHICH I THINK I CAN IMPROVE
template = '/something/workspace:' + new_table + '/metadata'
else: # if form not valid
out['success'] = False
return render_to_response(template, RequestContext(request, {'form': form}))
This part of the code:
template = '/something/workspace:' + new_table + '/metadata'
seems very ugly to me. But as I am quite new in Django I am not sure how to approach this matter.
A side note first about Django 1.5 - you're highly advised to upgrade to a supported version like 1.8.
Redirecting
For redirecting you can use the redirect shortcut. (Or HttpResponseRedirect)
from django.shortcuts import redirect
# out of a view context
return redirect('/url/to/redirect/to/')
Building URLs
Indeed - as you did mention, your attempt with template = '/something/workspace:' + new_table + '/metadata' is not the cleanest way :)
Django provides a really nice way with the URL dispatcher.
A complete solution here would go too far (or definitely would require more detailed information about your project structure) - I would recommend you to dive into the Django URL dispatcher.
In short you would do something like:
# app/urls.py
urlpatterns = [
#...
url(r'^workspace/(?P<id>[0-9]+)/metadata/$', views.workspace_detail, name='workspace-detail-metadata'),
#...
]
Then you are able to reverse your URL patterns:
from django.core.urlresolvers import reverse
url = reverse('workspace-detail-metadata', kwargs={'id': 123})
# would result in:
# .../workspace/123/metadata/
After all, I have used the "reverse" method as follows:
layer = 'geonode:' + new_table
return HttpResponseRedirect(
reverse(
'layer_metadata',
args=(
layer,
)))
Where my urls.py file includes:
url(r'^(?P<layername>[^/]*)/metadata$', 'layer_metadata', name="layer_metadata"),
As described here this is the most appropriate way to do it.

Django: How can I localize my urls?

How can I localize my urls in Django?
url(u'^{0}/$'.format(_('register')), RegisterView.as_view(), name='register'),
I tried the above but it only seems to work when I stop and start the server. I guess that the urls are translated when the application start.
So if there a way to solve this?
It's a bit more complicated than just throwing _() in urls.py. You have spotted the reason yourself: The URLs are evaluated once, when Django starts, and not for every request. Therefore you will have to
a) put every possible translation in urls.py, or
b) implement routing yourself
A. All in urls.py
url('hello', hello, name="hello_en"),
url('hallo', hello, name="hello_de"),
url('buenos_dias', hello, name="hello_es"),
Obviously not a nice solution, but it works for small projects.
B. Implementing routing
That has it's own drawback, especially when it comes to use reverse(). However, it works in principle:
urls.py:
#...
url('(?<path>.+)', dispatcher),
#...
views.py:
def dispatcher(request, path):
if path == "hallo":
lang = "de"
elif path == "buenos_dias":
lang = "de"
else:
lang = "en"
Of course, you can make the lookup more intelligent, but then you have to make preassumptions:
# if request.session['language'] can be trusted:
def dispatcher(request, path):
list_of_views = ['contact', 'about', 'foo']
v = None
for view in list_of_views:
if _(view) == path:
v = view
break
if v is None:
# return 404 or the home page
Internationalization of URLs has been introduced in django 1.4
see https://docs.djangoproject.com/en/dev/topics/i18n/translation/#url-internationalization
this is exactly what you are looking for

Invalidating Memcached Keys on save() in Django

I've got a view in Django that uses memcached to cache data for the more highly trafficked views that rely on a relatively static set of data. The key word is relatively: I need invalidate the memcached key for that particular URL's data when it's changed in the database. To be as clear as possible, here's the meat an' potatoes of the view (Person is a model, cache is django.core.cache.cache):
def person_detail(request, slug):
if request.is_ajax():
cache_key = "%s_ABOUT_%s" % settings.SITE_PREFIX, slug
# Check the cache to see if we've already got this result made.
json_dict = cache.get(cache_key)
# Was it a cache hit?
if json_dict is None:
# That's a negative Ghost Rider
person = get_object_or_404(Person, display = True, slug = slug)
json_dict = {
'name' : person.name,
'bio' : person.bio_html,
'image' : person.image.extra_thumbnails['large'].absolute_url,
}
cache.set(cache_key)
# json_dict will now exist, whether it's from the cache or not
response = HttpResponse()
response['Content-Type'] = 'text/javascript'
response.write(simpljson.dumps(json_dict)) # Make sure it's all properly formatted for JS by using simplejson
return response
else:
# This is where the fully templated response is generated
What I want to do is get at that cache_key variable in it's "unformatted" form, but I'm not sure how to do this--if it can be done at all.
Just in case there's already something to do this, here's what I want to do with it (this is from the Person model's hypothetical save method)
def save(self):
# If this is an update, the key will be cached, otherwise it won't, let's see if we can't find me
try:
old_self = Person.objects.get(pk=self.id)
cache_key = # Voodoo magic to get that variable
old_key = cache_key.format(settings.SITE_PREFIX, old_self.slug) # Generate the key currently cached
cache.delete(old_key) # Hit it with both barrels of rock salt
# Turns out this doesn't already exist, let's make that first request even faster by making this cache right now
except DoesNotExist:
# I haven't gotten to this yet.
super(Person, self).save()
I'm thinking about making a view class for this sorta stuff, and having functions in it like remove_cache or generate_cache since I do this sorta stuff a lot. Would that be a better idea? If so, how would I call the views in the URLconf if they're in a class?
URLConf should point to any callable. There's no strict requirement to make it point to function exactly. You could implement base class with your cache methods then extend it:
class RealView(BaseViewWithCacheMethods):
def __call__(self, request):
if request.is_ajax():
return self.ajax_view()
return self.html_view()
URLConf definition would be something like that:
from django.conf.urls.defaults import *
from views import RealView
urlpattrens = patterns('',
(r'^$', RealView()),
)