Add request header before redirection - django

I am retrofitting a Django web app for a new client. To this end I have added a url pattern that redirects requests from new client to old url patterns.
from:-
(('api/(?P<phone>\w+)/MessageA', handle_a_message),
('api/(?P<phone>\w+)/MessageB', handle_b_message),
...)
to:-
(('api/(?P<phone>\w+)/MessageA', handle_a_message),
('api/(?P<phone>\w+)/MessageB', handle_b_message),
('api/newclient', handle_newclient)
...)
views.handle_newclient
def handle_newclient(request):
return redirect('/api/%(phone)s/%(msg)s' % request.GET)
This somewhat works. However the new client doesn't do basic auth which those url's need. Also the default output is json where the new client needs plain text. Is there a way I can tweak the headers before redirecting to the existing url's?

Django FBV's should return an HTTPResponse object (or subclass thereof). The Django shorcut redirect returns HttpResponseRedirect which is a subclass of HTTPResponse. This means we can set the headers for redirect() the way we will set headers for a typical HTTPResponse object. We can do that like so:
def my_view(request):
response = redirect('http://www.gamefaqs.com')
# Set 'Test' header and then delete
response['Test'] = 'Test'
del response['Test']
# Set 'Test Header' header
response['Test Header'] = 'Test Header'
return response
Relevant docs here and here.

Related

What is the correctly way to pass an orientdb #rid as a parameter, to DELETE request in django rest framework?

I'm creating a delete method in a DRF API, by passing parameters, but I don't know how to pass correctly an orientdb #rid.
I have a relationship in orientdb called "worksat", in OrientDB Studio i can see the #rid with the structure name like #:, i.e: "#33:1" is the #rid of a worksat relationship record.
So I need to pass that string in my DRF URL api relationship:
http://127.0.0.1:8000/api/oworksat/
But passing like:
http://127.0.0.1:8000/api/oworksat/#33:1
I see GET request, with the message below (I expect to see DELETE):
Allow: GET, POST, HEAD, OPTIONS
If a pass a simple number:
http://127.0.0.1:8000/api/oworksat/1
Then I see DELETE request (obviously "1" doesn't exist):
HTTP 404 Not Found
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
api.py:
class OWorksAtViewSet(viewsets.ModelViewSet):
queryset = graph.oworksat.query()
serializer_class = OWorksAtSerializer
permission_classes = [
permissions.AllowAny
]
def destroy(self, request, *args, **kwargs):
print ("destroy")
import pdb;pdb.set_trace()
urls.py:
from django.conf.urls import include, url
from rest_framework import routers
from .api import (OWorksAtViewSet)
from rest_framework_swagger.views import get_swagger_view
router = routers.DefaultRouter()
router.register('api/oworksat', OWorksAtViewSet, 'oworksat')
schema_view = get_swagger_view(title='Swagger Documentation')
urlpatterns = [
url(r'^swagger/$', schema_view)
]
urlpatterns += router.urls
The interesting thing is that by accesing from swagger api, in the DELETE method, if I a pass in the ID of the request "#33:1", it works, the api call to my destroy method and recieve in kwargs: kwargs = {'pk': '#33:1'}.
How can I reach that behavior from DRF api?
Edited:
This is my temporal solution to implement my destroy method, but obviously this only works in Swagger UI, by passing #rid in the request.
from rest_framework import status
from rest_framework.response import Response
from core.pyorient_client import *
class OFriendsViewSet(viewsets.ModelViewSet):
def destroy(self, request, *args, **kwargs):
client = orientdbConnection()
client.command("delete edge ofriends where #rid = '" + kwargs['pk'] + "'")
return Response(status=status.HTTP_204_NO_CONTENT)
I assume that when speaking about not being able to delete or see "Delete" button, you talk about Django Rest Framework browsable API.
When you access your API through DRF browsable API you get list of objects when you navigate to for example http://127.0.0.1:8000/api/oworksat/. This is the "list endpoint" and it doesn't support DELETE.
Delete button will be there when you access "detail endpoint" of single object, for example: http://127.0.0.1:8000/api/oworksat/123.
In your case however when you try to pass OrientDB #RID as object ID, browser thinks you want to get to list endpoint. This is because in urls everything after # is called fragment and this is not passed to server. So when you navigate to http://127.0.0.1:8000/api/oworksat/#1:23 browser actually requests page from http://127.0.0.1:8000/api/oworksat/ and thus gives you the list endpoint without delete button.
Why Swagger then works?
Swagger works probably because the request is not made the same way as browsers normally load pages. Swagger makes Ajax request to your API when you click the Delete button and thus the fragment part is not stripped from your url. Swagger also probably url encodes the value you type into UI and this would transform #1:22 to %231%3A22 and thus remove # which causes our problem. DRF then knows how to url decode the url automatically and ends up with correct looking ID.
Example of request Swagger probably does:
function deleteData(url, item) {
return fetch(url + '/' + item, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => console.log(response.json()));
}
deleteData("http://127.0.0.1:8000/api/oworksat/", encodeURIComponent("#1:23"));
How to fix this?
Your API probably works correctly when #RID is url encoded and Ajax request is made. It just doesn't work with DRF browsable API.
However to make it nicer to work with for your users and also to make DRF Browsable API to work I would remove # from the ID's when they are serialized through your API. Users would then make requests to urls like http://127.0.0.1:8000/api/oworksat/1:23. Of course by doing it like this you would then need to prepend the client provided id with # before passing it to OrientDB query.

Allow users to download files without revealing link and without buffering

I am hosting some video files in rackspace cloud files, and each user is allowed to download the files that are assigned to them.
Because of the file sizes it is not feasible to buffer the object in the webserver(webfaction)
I tried a redirect to the file, with Content-Disposition set to attachment, but to no avail.
What kind of options do I have, if any?
Ideally the file download would pop as coming from my domain after clicking a link that points to something like example.com/video/42/download/ so I can handle authentication ect. but im not sure how to structure my view for that to happen.
You are probably best served by using an HttpResponseRedirect unless there is something I am misunderstanding...?
# urls.py
from django.http import HttpResponseRedirect
url(r'^applications/(?P<id>\d+)/image\.png$', 'core.views.serve_image', name='image'),
This will serve a view at http://localhost/application/12345/image.png.
# core/views.py
def serve_application_image(request, id):
# redirect to temp_url
application = Application.objects.get(id=id)
return HttpResponseRedirect(application.image.temp_url)
And this will redirect users that hit that URL to the Rackspace URL. It can work for embedding videos, images, etc, in html <img> tags and such. Browser clients will be able to see the redirected URL (at rackcdn.com).
I have configured my apps to serve a temp_url property that expires after 15 minutes. The temporary URL is created for the CDN at Rackspace.com and their documentation may be out of the scope for this question so I'll leave it off for now... but the code I use to sub-class ImageField to serve image attributes with the .temp_url code follows:
import hmac
from hashlib import sha1
from time import time
class ImageFieldFile_With_Temp_Url(ImageFieldFile):
#property
def temp_url(self):
container_name, file_name = (self.storage.container.name, self.name)
key = settings.CUMULUS['CUSTOM__X_ACCOUNT_META_TEMP_URL_KEY']
public_url = settings.CUMULUS['CUSTOM__X_STORAGE_URL']
method = 'GET'
expires = int(time() + settings.CUMULUS['CUSTOM__X_TEMP_URL_TIMEOUT'])
url = '%s/%s/%s' % (public_url, container_name, file_name)
base_url, object_path = url.split('/v1/')
object_path = '/v1/' + object_path
hmac_body = '%s\n%s\n%s' % (method, expires, object_path)
sig = hmac.new(key, hmac_body, sha1).hexdigest()
return '%s%s?temp_url_sig=%s&temp_url_expires=%s' % (base_url, object_path, sig, expires)
class ImageField_With_Temp_Url(models.ImageField):
attr_class = ImageFieldFile_With_Temp_Url
models.ImageField = ImageField_With_Temp_Url
Note that I am using the django-cumulus project in this approach.
Importing this function anywhere at the top of your models.py will extend ImageField with a new temp_url property (since I assign it to models.ImageField ...).

Django - Ajax registration

I am trying to allow registration (using this django-registration register view) to one of my applications from a modal dialog.
Since this form is in a modal box, I'd like to get an json reponse on success (instead of the default redirection)
How can I use this view (django-registration register) to manage the registration and send back a json response on success ?
I know how to make ajax/json responses, the question is how to use the django-registration view without the redirection behavior or wrap it into an other view to manage the response.
First you need to change the urls.py to wrap the existing view with another functionality. To do that you have to create a new backend package in backends folder and change urls.py there while keeping everything else intact, or you could just go ahead and modify the existing urls.py in the backend package.
I have not tested this, but it should work.
Point url to the new view:
# urls.py
url(r'^register/$', register_wrap,
{'backend': 'registration.backends.default.DefaultBackend'},
name='registration_register'),
# your new view that wraps the existing one
def register_wrap(request, *args, **kwargs):
# call the standard view here
response = register(request, *args, **kwargs)
# check if response is a redirect
if response.status_code == 302:
# this was redirection, send json response instead
else:
# just return as it is
return response
If you are going to need this for more views you can just create a decorator using this.
Why I would do is to check if request.is_ajax() in your normal after-successfull-registration-redirect view and return json response there.
You ask how you can use the existing view to manage the registration and send back a json response on success. Since the HttpResponseRedirect is pretty much hard coded in the view, you can't use the view as it is. Instead, either fork it, or write your own view and change the urls.py so that r'^register/$' directs to your new view.
As far as the json response is concerned, on success you can do something like this:
from django.utils import simplejson as json
def register_ajax(request):
...
return HttpResponse(json.dumps(dict(success=True, **dict_containing_data)))
Hope this helps

How to convert a Django HttpResponse to a Django render call

I have the following code
def ajax_login_request(request):
try:
request.POST[u'login']
dictionary = request.POST
except:
dictionary = request.GET
user = authenticate(username = dictionary[u'login'], password = dictionary[u'password'])
if user and user.is_active:
login(request, user)
result = True
else:
result = False
response = HttpResponse(json.dumps(result), mimetype = u'application/json')
return response
which is being called via ajax. I'm a noob and this is from an example in a book. Unfortunately the version of Django I'm using throws a CSRF error on this. I've done the other CSRF bits, but I don't know how to change the HttpResponse bit to a render call. I do not want to use CSRF_exempt, because I have no idea when that is appropriate. Can someone please provide me the equivalent render call for the HttpResponse above.
Thanks
To make your original code work, you need to get a RequestContext object and pass it along with your response, something like this:
from django.http import HttpResponse
from django.template import RequestContext, Template
def ajax_login_request(request):
# ...
# This bit of code adds the CSRF bits to your request.
c = RequestContext(request,{'result':json.dumps(result)})
t = Template("{{result}}") # A dummy template
response = HttpResponse(t.render(c), mimetype = u'application/json')
return response
Do read up on the CSRF documentation as you might run into strange errors if you don't understand all the ways CSRF is "wired" in your app. The page also has a javascript snippet to make sure CSRF cookies are sent with your ajax requests if you are sending them without a form.
You can also use the render_to_response() shortcut, but you would need to have an actual template to load (in your case, you don't need a template, hence the "dummy" template in my example).
Ok, I'm going to re-draft this answer so you understand where I'm coming from. The CSRF middleware works like this:
You make request -------> request hits csrf --(invalid/no token)--> render 403
middleware
|
(valid token)
|
\ /
Call view
|
\ /
middleware sets
csrf cookie
|
\ /
Response appears
In other words, if you are seeing a 403 csrf page, your view was never called. You can confirm this by sticking a spurious print statement in the view and watching the output from runserver when you make your request.
To solve this, you either need to disable csrf (not good) or use one of the ajax methods available to you. If the required token is passed in your view will actually be executed.
The reason your view is not called is to prevent the action from the forged site from actually ever taking place - for example, if you denied the template at response time the user would already be logged in. The same behaviour occurs with the function decorators.
As for the middleware setting the cookie, that does not alter or depend on the render function at all - this sets the HTTP header Cookie: ... in the response. All responses in Django are HttpResponse objects until it finally converts them to output; render functions are helpers, but that's not what's causing your problem here.
Edit I'll convert what you've got to a render call. You could do this:
return render_to_response(`ajax_templates/login_response.html`,
{'loginresponse': json.dumps(result)})
Where ajax_templates/login_response.html is just:
{% loginresponse %}
That's it. HttpResponse has a main default argument which is the string to return (literally, the html of the web page); that's what you're doing initially. render_to_response and render are shortcuts to this which do this:
render_to_response called ----> open template asked for --> substitute arguments
|
\ /
django instructs web server <--- return this from view <-- create HttpResponse
to send to client object

How to find the location URL in a Django response object?

Let's say I have a Django response object.
I want to find the URL (location).
However, the response header does not actually contain a Location or Content-Location field.
How do I determine, from this response object, the URL it is showing?
This is old, but I ran into a similar issue when doing unit tests. Here is how I solved the problem.
You can use the response.redirect_chain and/or the response.request['PATH_INFO'] to grab redirect urls.
Check out the documentation as well! Django Testing Tools: assertRedirects
from django.core.urlresolvers import reverse
from django.test import TestCase
class MyTest(TestCase)
def test_foo(self):
foo_path = reverse('foo')
bar_path = reverse('bar')
data = {'bar': 'baz'}
response = self.client.post(foo_path, data, follow=True)
# Get last redirect
self.assertGreater(len(response.redirect_chain), 0)
# last_url will be something like 'http://testserver/.../'
last_url, status_code = response.redirect_chain[-1]
self.assertIn(bar_path, last_url)
self.assertEqual(status_code, 302)
# Get the exact final path from the response,
# excluding server and get params.
last_path = response.request['PATH_INFO']
self.assertEqual(bar_path, last_path)
# Note that you can also assert for redirects directly.
self.assertRedirects(response, bar_path)
The response does not decide the url, the request does.
The response gives you the content of the response, not the url of it.