how i can set middleware for some views in django? - python

i have a django project with 50 url (in 1 application).
i would set a custom middleware for 20 urls.
when add middleware to MIDDLEWARE_CLASSES , this set for all urls .
How should this be done?
(i use last version of python and djnago)

You can utilize decorators, view's decorators behave similarly to middlewares, simple ex:
from functools import wraps
def custom_view_decorator(view_function):
#wraps(view_function)
def wrap(request, *args, **kwargs):
# Any preprocessing conditions..etc.
return view_function(request, *args, **kwargs)
return wrap
Usage in views.py:
#custom_view_decorator
def my_view(request, *args, **kwargs):
# view code, return response

you can either add a check condition to your middleware which is not recommended(since you cannot determine whether this middleware is working or not without reading it's code). or create a decorator for those views, decorator job is exactly this.

for starters you could do something like this:
class BlockUrlsMiddleware(MiddlewareMixin):
REDIRECT_URL = reverse("not_allowed")
WHITELISTED_URLS = [
"/your/custom/views/urls",
"/your/custom/apis/urls",
]
def process_request(self, request):
if request.path in self.WHITELISTED_URLS:
return None
else:
return HttpResponseRedirect(self.REDIRECT_URL)
few points to Note:
return None means everything is good and serve this request.
return HttpResponseRedirect means URLs are accessed which are not allowed and block this request by redirecting it to your default URLs (that you need to set) in this case REDIRECT_URL.

Related

process_template_response doesn't get called in Django

In views.py I use render() .
In app -> middleware.py I have this code:
from django.conf import settings
class NoTrackingMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_template_response(self, request, response):
no_tracking = True if request.GET.get("NO_TRACKING", default=False) is not False else False
pub_id = "PUBLISHER_TEST" if no_tracking is True else settings.WFF_PUB_ID
response.context_data["no_tracking"] = no_tracking
response.context_data["pub_id"] = pub_id
return response
In settings.py I have:
MIDDLEWARE = [
...
'app.middleware.NoTrackingMiddleware',
]
Yet if I place a breakpoint at process_template_response it gets ignored and pub_id is always empty.
Why?
From the documentation (emphasis mine):
process_template_response() is called just after the view has finished executing, if the response instance has a render() method, indicating that it is a TemplateResponse or equivalent.
You state that you're using django.http.shortcuts.render, whose documentation reads:
Combines a given template with a given context dictionary and returns an HttpResponse object with that rendered text.
Django does not provide a shortcut function which returns a TemplateResponse because the constructor of TemplateResponse offers the same level of convenience as render().
Thus render returns a HttpResponse, not a TemplateResponse, and as noted above, process_template_response is only called for TemplateResponses.
You either need to change your view to return TemplateResponse, instead of using the render shortcut, or perform your logic elsewhere. I think your logic could be implemented in a context processor instead of middleware.

Django REST Framework strange results

For some reason, I'm getting some weird problems with sending a POST request.
This is my URL settings:
http://host/api/user/1/edit/
http://host/api/address/search/
Where /api/ is the API-Root, user is a model, 1 is user ID, and edit and search are custom functions.
This is inside views.py:
class UserViewSet(viewsets.ModelViewSet):
...
def post(self, request, pk, *args, **kwargs):
... (processing)
#detail_route(methods=['post'])
def edit(self, request, pk, *args, **kwargs):
... (processing)
class AddressViewSet(viewsets.ModelViewSet):
...
def post(self, request, *args, **kwargs):
... (processing)
#detail_route(methods=['post'])
def search(self, request, *args, **kwargs):
... (processing)
This is inside urls.py:
router = DefaultRouter()
router.register(r'user', views.UserViewSet)
router.register(r'address', views.AddressViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
url(r'^authentication/', include('rest_framework.urls', namespace='rest_framework'))
]
The strange thing I'm encountering is this:
If I'm using httpie and sending a POST edit request like this:
http POST http://host/api/user/1/edit/ name="john" address="google.com"
Then the stuff inside def post will never get executed. This is the same if I make the browser send a POST request.
However, if I send a POST search request by doing:
http POST http://host/api/address/search/ name="john"
Then in this case the stuff inside def search will never get executed, while only the stuff inside def post will get executed.
The only difference I can see is that for edit, there's an extra pk (value of 1 for this example), while there are no pk for `search.
Does anyone know why this is happening? It's really confusing for me
Defining post() in a ViewSet doesn't do anything, this is for APIView derived classes. If you want to override default object creation in a ViewSet you can either
def create(self, request, *args, **kwargs):
... do stuff ...
or even better
def perform_create(self, serializer):
... do stuff ...
serializer.save()
As for the address, you need to use list_route decorator instead of detail_route. detail_route is for operating on an individual object and list_route is for lists. So /address/search/ should be list route and /address/1/search/ would be a detail_route. Mind you that I don't think your code in post() runs in either case.
Here is a relevant piece of docs http://www.django-rest-framework.org/api-guide/viewsets/#marking-extra-actions-for-routing
When submitting a post request to rest framework the request is mapped to methods on the ViewSet, unlike Django views that simply call the post method.
When posting to user/{pk}/ your request is mapped to the UserViewSet.create method. A post request to user/{pk}/edit/ maps to your UserViewSet.edit method. In both cases, UserViewSet.post is not called.
It seems that when you post to a custom url that doesn't exist the post method is called instead (probably as a fallback). In your case address/search/ isn't a valid url, and instead you've defined an endpoint as address/{pk}/search/.
If you want to access address/search/ you need to update your search method to use a list_route decorator instead of detail_route:
class AddressViewSet(viewsets.ModelViewSet):
...
#list_route(methods=['post'])
def search(self, request, *args, **kwargs):
... (processing)
Hope this helps to clear things up for you.

Using middleware to add arguments to view functions in django

I have a pretty small, basic site with a handful of views. Pretty much all of the views perform some interaction with an external API.
In each view I instantiate an object of my API class, which obviously is not very DRY. I also have a piece of middleware that adds one or two useful variables to the request object (my middleware runs last in the chain).
I was thinking of instantiating my api class in the middleware, and then passing it to each view as an argument, i.e.
def my_view(request, my_api_class_instance):
and then calling the view from the middleware, something like:
def process_view(self, request, view_func, view_args, view_kwargs):
my_api = api(some, args, here)
response = view_func(request, my_api, *view_args, **view_kwargs)
return response
It seems like a quick and easy way to tidy some code and reduce repetition. Is there anything inherently BAD about this?
If you look at the Django middleware documentation, you'll see;
If process_view returns an HttpResponse object, Django won’t bother calling any other view or exception middleware, or the appropriate view; it’ll apply response middleware to that HttpResponse, and return the result.
So returning an HttpResponse will skip the execution of other middleware classes below this one which in general should be avoided unless your middleware is the last one in settings.MIDDLEWARE_CLASSES list.
However, you still can bind your API object to HttpRequest instance passed on to middleware. It is the same approach to what AuhenticationMiddleware does in order to populate request.user.
def APIMiddleware(object):
def process_request(self, request):
request.api = API(host, port, user, password, extra={"url": request.get_full_path()})
you can just change view_kwargs in middleware
class SomeMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
view_kwargs['my_api'] = api(some, args, here)
return None
def my_view(request, my_api):
# you can use you api there
def my_view(request, args, my_api)
# you can use you api there
document it's there
middleware returns None, Django will continue processing this request, executing any other process_view() middleware.
but, this only applies to every view function can got the keyword argument 'myapi', otherwise will raise the TypeError.
so the best way isn't pass your api by func arguments, like #ozgur pass your api by request.
You can use a middleware but there are two other possibilities too, both more flexible. The first one is to use a decorator and wrap the view:
#my_api_init_decorator
def my_view(request, my_api):
...
This allows you to explicitly select views, check user authorization or permissions before you init your api...
The second solution is to use class based views and create your own view you inherit from.

Nested decorators, raising 404

Suppose I have view
def foo(request)
I'm also using custom user model as follow:
class MyUser(AbstractUser):
field = models.BooleanField()
I'd like to combine 2 django decorators: login_required and user_passed_test, so that anonymous user should be redirected to a login page and user who is not allowed to see the view (user.field == False) should see a proper message (say, something like 'you're not allowed to see this').
So I tried:
my_decor = user_passes_test(lambda user: user.field == True,
login_url='/bar')
def custom_decor(view_func):
decorated_view_func = login_required(my_decor(view_func), login_url='/login')
return decorated_view_func
And I also have to define view:
def bar(request):
return HttpResponse('you are not allowed to see this context.')
and hardcode it in my urls.
The question is: can I do this without creating an additional view and adding it to urls? Is there a way to make 'user_passed_test' decorator raising an 404 error instead of redirecting to a login view?
There is probably a very simple solution and surely I'm just missing it.
I dont remember very well the decorators syntax, but you can do:
def custom_decor(view_func):
def decorator(request, *args, **kwargs)
if request.user.field is True:
return view_func(request, *arg, **kwargs)
raise Http404
return decorator
so...
#login_required(login_url='/login')
#custom_decor
def foo(request)
# ...

Restrict so users only view a certain part of website-Django

I am using Django, and require certain 'trial' users only to activate a certain part of the website-any ideas on an efficient way to do this?
I was thinking about giving a paying customer a certain ID and linking this to the URL of the sites for permission.
Thanks,
Tom
I'd use a view decorator like this:
def paying_only(view):
def _decorated(request, *args, **kwargs):
if not is_paying(request.user):
redirect('page_explaining_that_this_is_for_paying_users_only')
return view(request, *args, **kwargs)
return _decorated
#paying_only
def some_view(request):
...
I decided to post my solution, I might even get some feedback. I have a middleware blocking request/responses on certain paths defined in settings, first the middleware:
import re
from django.conf import settings
from django.shortcuts import redirect
class InvitationRequired(object):
def process_response(self, request, response):
if not settings.CLOSED_BETA_ACTIVE:
return response
if (hasattr(request, 'user')
and hasattr(request.user, 'is_authenticated')
and request.user.is_authenticated()):
return response
elif (request.path in
settings.CLOSED_BETA_INVITATION_MIDDLEWARE_EXCEPTED_URIS):
return response
elif response.status_code < 200 or response.status_code >= 300:
return response
else:
for regex in \
settings.CLOSED_BETA_INVITATION_MIDDLEWARE_EXCEPTED_PATTERNS:
if re.compile(regex).match(request.path):
return response
return redirect(settings.CLOSED_BETA_INVITATION_MIDDLEWARE_REDIRECT)
In settings.py I have something like this:
CLOSED_BETA_ACTIVE = True
CLOSED_BETA_INVITATION_URL = '/invitation/'
CLOSED_BETA_INVITATION_MIDDLEWARE_REDIRECT = CLOSED_BETA_INVITATION_URL
CLOSED_BETA_INVITATION_MIDDLEWARE_EXCEPTED_PATTERNS = (
r'^/api/v1/',
r'^/static/',
r'^/media/',
r'^/admin/',
r'^/registration/',
r'^/',
)
Hope it's clear, at least it can give you a a different approach.
This is a very wide-ranging question. One solution would be to store a trial flag on each user. On an authenticated request, check for User.trial in your controller (and probably view) and selectively allow/deny access to the endpoint or selectively render parts of the page.
If you wish to use built-in capabilities of Django, you could view 'trial' as a permission, or a user group.

Categories

Resources