How should I continue a Python Social Auth Partial Pipeline - python

The application I am working has an overwritten endpoint for the Python Social Auth /complete/<backend>/ endpoint.
within our urls.py:
urlspatterns = [
...
# Override of social_auth
url(r'^api/v1/auth/oauth/complete/(?P<backend>[^/]+)/$',
social_auth_complete,
name='social_complete'),
...
]
within views.py:
from social_django.views import complete
def social_auth_complete(request, backend, *args, **kwargs):
"""Overwritten social_auth_complete."""
# some custom logic getting variables from session (Unrelated).
response = complete(request, backend, *args, **kwargs)
# Some custom logic adding args to the redirect (Unrelated).
We are attempting to implement a partial pipeline method. The first time the endpoint is called everything works as expected.
#partial
def required_info(strategy, details, user=None, is_new=False, *args, **kwargs):
"""Verify the user has all the required information before proceeding."""
if not is_new:
return
for field in settings.SOCIAL_USER_REQUIRED_DATA:
if not details.get(field):
data = strategy.request_data().get(field)
if not data:
current_partial = kwargs.get('current_partial')
social_provider = kwargs.get('backend')
return strategy.redirect(f'.../?partial_token={partial_token}&provider={social_provider}'
else:
details[field] = data
This redirects the user to the front end in which they fill out a form which calls a POST request to orginal API api/v1/auth/oauth/complete/(?P<backend>[^/]+)/ with the following in the data:
{
'required_fieldX': 'data',
...
'partial_token': '',
}
Key Issues
Two things go wrong; When I pdb into required_info there is never any data within strategy.request_data(). There is still data within the kwargs['request'].body and I can take the data out there.
However
But I am afraid that the second time around we never get into this block of code from social-core:
partial = partial_pipeline_data(backend, user, *args, **kwargs)
if partial:
user = backend.continue_pipeline(partial)
# clean partial data after usage
backend.strategy.clean_partial_pipeline(partial.token)
else:
user = backend.complete(user=user, *args, **kwargs)
I know this to be true because when I interrogate the database the original Partial object still exists as if backend.strategy.clean_partial_pipeline(partial.token) was never called.
Final Questions
Why is the social_django.views.complete not processing the POST request as expected and as it appears to be in all the example applications. Is there an issue from our overwriting it? Should I just create a separate endpoint to handle the POST request and if so how do mimic all that goes on within #psa such that I can call backend.continue_pipeline(partial)?

I think there's only one issue here, and that's that the Django strategy doesn't look into request.body when loading the request data, you can see the method in charge here. There you can see that it looks for request.GET and/or request.POST, but not body.
You can easily overcome this by defining your custom strategy that extends from the built-in one, and override the request_data method to look for the values in request.body. Then define the SOCIAL_AUTH_STRATEGY to point to your class.

Related

Pass data from the pipeline to views in Django Python Social Auth

I was reading the documentation of Python Social Auth and got curious about the section of Interrupting the Pipeline (and communicating with views).
In there, we see the following pipeline code
In our pipeline code, we would have:
from django.shortcuts import redirect
from django.contrib.auth.models import User
from social_core.pipeline.partial import partial
# partial says "we may interrupt, but we will come back here again"
#partial
def collect_password(strategy, backend, request, details, *args, **kwargs):
# session 'local_password' is set by the pipeline infrastructure
# because it exists in FIELDS_STORED_IN_SESSION
local_password = strategy.session_get('local_password', None)
if not local_password:
# if we return something besides a dict or None, then that is
# returned to the user -- in this case we will redirect to a
# view that can be used to get a password
return redirect("myapp.views.collect_password")
# grab the user object from the database (remember that they may
# not be logged in yet) and set their password. (Assumes that the
# email address was captured in an earlier step.)
user = User.objects.get(email=kwargs['email'])
user.set_password(local_password)
user.save()
# continue the pipeline
return
and the following view
def get_user_password(request):
if request.method == 'POST':
form = PasswordForm(request.POST)
if form.is_valid():
# because of FIELDS_STORED_IN_SESSION, this will get copied
# to the request dictionary when the pipeline is resumed
request.session['local_password'] = form.cleaned_data['secret_word']
# once we have the password stashed in the session, we can
# tell the pipeline to resume by using the "complete" endpoint
return redirect(reverse('social:complete', args=("backend_name,")))
else:
form = PasswordForm()
return render(request, "password_form.html")
Specially interested in the line
return redirect(reverse('social:complete', args=("backend_name,")))
which is used to redirect the user back to the pipeline using an already stablished backend.
We can see earlier in that page a condition that's used to check which backend is being used.
def my_custom_step(strategy, backend, request, details, *args, **kwargs):
if backend.name != 'my_custom_backend':
return
# otherwise, do the special steps for your custom backend
The question is, instead of manually adding it in the args=("backend_name,"), how can the pipeline communicate the correct backend to the view?
One can simply add in the pipeline
request.session['backend'] = backend.name
and then the view becomes
backend_name = request.session['backend']
return redirect(reverse('social:complete', args=(backend_name)))

What Happens When Django-Rest-Framework Receives a Request?

I'm trying to understand how DRF works, specifically the GenericViewSet view.
What is the events sequence when a request is retrieved?
Which component receives the request?
To where the request is passed?
When does validation happen?
Context: All of my field-choices enums are lowercased, so I was trying to lowercase all values that arrive (from uncontrolled 3rd parties). Django's model validation fails before any of the exposed GenericViewSet methods are called. How can I process request data before model validation?
Can anyone shed some light on the topic?
I would suggest overriding the method for which you're trying to debug.
You could then plaec a breakpoint to explore the current scope. Builtin modules such as pdb are great for this! Here is an example using rest_framework.generics.CreateAPIView. Note that you can access the post data using request.data whilst in the trace.
from rest_framework.generics import CreateAPIView
import pdb;
...
class ExampleCreateView(CreateAPIView):
def create(self, request, *args, **kwargs):
pdb.set_trace()
return super(ExampleCreateView, self).create(request, *args, **kwargs)

Django URL with dynamic prefix

I need to have a dynamic URL prefix for all URLs in my app.
I'm familiar with doing a static prefix, such as url(r'^myprefix/app', include('app.urls')).
Instead, I need the myprefixto be dynamic, such as url(r'^(?P<prefix>\w+)/app', include('app.urls')).
That works, but here's the kicker. I don't want that prefix to be sent as a keyword argument to all of the views. I want to be able to capture it and use it in Middleware or something similar.
To give my specific use case, we have software (this Django project) used to manage various testing labs. The app needs knowledge of which lab it is operating on.
Currently I'm doing this with the following:
class LabMiddleware(object):
def process_request(self, request):
request.current_lab = 'lab1' # Note that this is currently hard coded
The requirement states that the users be able to go to a URL such as http://host/<lab_name>/app where the lab_name would then get used in my LabMiddleware. Because of this, I obviously don't want to have to accept the lab_name in every single one of my views as it's cumbersome and overkill.
UPDATE:
Building on what Sohan gave in his answer, I ended up using a custom middleware class:
urls.py
url(r'^(?P<lab_name>\w+)/', include('apps.urls')),
apps/urls.py
url(r'^app1/', include('apps.app1.urls', namespace='app1')),
middleware.py
class LabMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
lab_name = view_kwargs.get('lab_name')
if lab_name:
request.current_lab = Lab.objects.get(name=lab_name)
# Remove the lab name from the kwargs before calling the view
view_kwargs.pop('lab_name')
return view_func(request, *view_args, **view_kwargs)
settings.py
MIDDLEWARE_CLASSES = (
# Default Django middleware classes here...
'raamp.middleware.LabMiddleware',
)
This allowed me to have the lab name in the URL and to add it to the request. Then by removing it from view_kwargs, it doesn't get passed on to the view function and everything works as I intended it.
Also note that the code I have above isn't the most optimized (e.g. I'm querying the database for every request). I stripped out the code I have for caching this as it's not important to showing how this problem was solved, but is worth mentioning that some improvements should be made to this code if you are using it in a production system.
You can create a decorator that wraps each view function. The decorator can take care of any processing work you have on the lab name parameter, and every view won't need to see the lab_name param.
def process_lab_name(request, lab_name):
request.current_lab = lab_name
def lab(view_func):
def _decorator(request, lab_name, *args, **kwargs):
# process the lab_name argument
process_lab_name(request, lab_name)
# when calling the view function, exclude the lab_name argument
response = view_func(request, *args, **kwargs)
return response
return wraps(view_func)(_decorator)
#lab
def some_view(request):
return render(...)
And your route will look like url(r'^(?P<lab_name>\w+)/app'

Django -- Allowing Users To Only View Their Own Page

I'm nearing what I think is the end of development for a Django application I'm building. The key view in this application is a user dashboard to display metrics of some kind. Basically I don't want users to be able to see the dashboards of other users. Right now my view looks like this:
#login_required
#permission_required('social_followup.add_list')
def user_dashboard(request, list_id):
try:
user_list = models.List.objects.get(pk=list_id)
except models.List.DoesNotExist:
raise Http404
return TemplateResponse(request, 'dashboard/view.html', {'user_list': user_list})
the url for this view is like this:
url(r'u/dashboard/(?P<list_id>\d+)/$', views.user_dashboard, name='user_dashboard'),
Right now any logged in user can just change the list_id in the URL and access a different dashboard. How can I make it so a user can only view the dashboard for their own list_id, without removing the list_id parameter from the URL? I'm pretty new to this part of Django and don't really know which direction to go in.
Just pull request.user and make sure this List is theirs.
You haven't described your model, but it should be straight forward.
Perhaps you have a user ID stored in your List model? In that case,
if not request.user == user_list.user:
response = http.HttpResponse()
response.status_code = 403
return response
I solve similiar situations with a reusable mixin. You can add login_required by means of a method decorator for dispatch method or in urlpatterns for the view.
class OwnershipMixin(object):
"""
Mixin providing a dispatch overload that checks object ownership. is_staff and is_supervisor
are considered object owners as well. This mixin must be loaded before any class based views
are loaded for example class SomeView(OwnershipMixin, ListView)
"""
def dispatch(self, request, *args, **kwargs):
self.request = request
self.args = args
self.kwargs = kwargs
# we need to manually "wake up" self.request.user which is still a SimpleLazyObject at this point
# and manually obtain this object's owner information.
current_user = self.request.user._wrapped if hasattr(self.request.user, '_wrapped') else self.request.user
object_owner = getattr(self.get_object(), 'author')
if current_user != object_owner and not current_user.is_superuser and not current_user.is_staff:
raise PermissionDenied
return super(OwnershipMixin, self).dispatch(request, *args, **kwargs)
You need to have some information stored about what list or lists a user can access, and then include that in the user_list lookup. Let's assume the simple case where List has a single owner, a foreign key to the User model. That's a many-to-one relationship between lists and users; no list is owned by more than one user, but a user can have multiple lists. Then you want something like this:
try:
user_list = models.List.objects.get(pk=list_id, owner=request.user)
except models.List.DoesNotExist:
raise Http404
Whether to return 404 or 403 is to some extent a matter of opinion; the definition for 403 says:
If the request method was not HEAD and the server wishes to make public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity. If the server does not wish to make this information available to the client, the status code 404 (Not Found) can be used instead.
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.4
If you do return a 404, you can use the django shortcut function get_object_or_404 instead of the explicit try/except - there's nothing wrong with doing it explicitly, but the need is common enough that there's a convenience function to do it.

Send a file through Django Class Based Views

We use class based views for most of our project. We have run into an issue when we try and create a CSV Mixin that will allow the user to export the information from pretty much any page as a CSV file. Our particular problem deals with CSV files, but I believe my question is generic enough to relate to any file type.
The problem we are having is that the response from the view is trying to go to the template (say like from django.views.generic import TemplateView). We specify the template in the urls.py file.
url(r'^$', MyClassBasedView.as_view(template_name='my_template.html'))
How can you force the response to bypass the template and just return a standard HttpResponse? I'm guessing you'll need to override a method but I'm not sure which one.
Any suggestions?
EDIT1: It appears that I was unclear as to what we are trying to do. I have rendered a page (via a class based view) and the user will see reports of information. I need to put in a button "Export to CSV" for the user to press and it will export the information on their page and download a CSV on to their machine.
It is not an option to rewrite our views as method based views. We deal with almost all class based view types (DetailView, ListView, TemplateView, View, RedirectView, etc.)
This is a generic problem when you need to provide different responses for the same data. The point at which you would want to interject is when the context data has already been resolved but the response hasn't been constructed yet.
Class based views that resolve to the TemplateResponseMixin have several attributes and class methods that control how the response object is constructed. Do not be boxed into thinking that the name implies that only HTML responses or those that need template processing can only be facilitated by this design. Solutions can range from creating custom, reusable response classes which are based on the behavior of the TemplateResponse class or creating a reusable mixin that provides custom behavior for the render_to_response method.
In lieu of writing a custom response class, developers more often provide a custom render_to_response method on the view class or separately in a mixin, as it's pretty simple and straightforward to figure out what's going on. You'll sniff the request data to see if some different kind of response has to be constructed and, if not, you'll simply delegate to the default implementation to render a template response.
Here's how one such implementation might look like:
import csv
from django.http import HttpResponse
from django.utils.text import slugify
from django.views.generic import TemplateView
class CSVResponseMixin(object):
"""
A generic mixin that constructs a CSV response from the context data if
the CSV export option was provided in the request.
"""
def render_to_response(self, context, **response_kwargs):
"""
Creates a CSV response if requested, otherwise returns the default
template response.
"""
# Sniff if we need to return a CSV export
if 'csv' in self.request.GET.get('export', ''):
response = HttpResponse(content_type='text/csv')
response['Content-Disposition'] = 'attachment; filename="%s.csv"' % slugify(context['title'])
writer = csv.writer(response)
# Write the data from the context somehow
for item in context['items']:
writer.writerow(item)
return response
# Business as usual otherwise
else:
return super(CSVResponseMixin, self).render_to_response(context, **response_kwargs):
Here's where you can also see when a more elaborate design with custom response classes might be needed. While this works perfectly for adding ad-hoc support for a custom response type, it doesn't scale well if you wanted to support, say, five different response types.
In that case, you'd create and test separate response classes and write a single CustomResponsesMixin class which would know about all the response classes and provide a custom render_to_response method that only configures self.response_class and delegates everything else to the response classes.
How can you force the response to bypass the template and just return
a standard HttpResponse?
This kinda defeats the point of using a TemplateView. If the thing you're trying to return isn't a templated response, then it should be a different view.
However...
I'm guessing you'll need to override a method but I'm not sure which one.
...if you prefer to hack it into an existing TemplateView, note from the source code...
class TemplateView(TemplateResponseMixin, ContextMixin, View):
"""
A view that renders a template. This view will also pass into the context
any keyword arguments passed by the url conf.
"""
def get(self, request, *args, **kwargs):
context = self.get_context_data(**kwargs)
return self.render_to_response(context)
...so you'd have to override the get() method so it doesn't call render_to_response() when returning your CSV. For example...
class MyClassBasedView(TemplateView):
def get(self, request, *args, **kwargs):
if request.GET['csv'].lower() == 'true':
# Build custom HTTP response
return my_custom_response
else:
return TemplateView.get(request, *args, **kwargs)
If you need a generic mixin for all subclasses of View, I guess you could do something like...
class MyMixin(object):
def dispatch(self, request, *args, **kwargs):
if request.GET['csv'].lower() == 'true':
# Build custom HTTP response
return my_custom_response
else:
return super(MyMixin, self).dispatch(request, *args, **kwargs)
class MyClassBasedView(MyMixin, TemplateView):
pass

Categories