Besides the root url request, which will be sent to a view, I want to have a dispatcher that I will route all the requests to different views which I will chose depending on my custom dispatcher's rules.
How can I create a dispatcher? I have read the docs over and over again yet I can not figure it out.
I want to parse the *remainder of url and then dispatch the request accordingly.
Pyramid will dispatch the request to a view registered via add_view or view_config. If you want to dispatch this again to your own code, just remember that from Pyramid's perspective the renderer/permission defined on the original view will be used... so don't use those. The request effectively enters your Pyramid view, then you have your own mini-framework that will handle it from there.
I suggest thinking about how to dispatch things in the context of Pyramid views, for example the pyramid_rpc package does some cool stuff to dispatch to views based on the content in the body of an RPC request (not just the URL). See the custom predicate within add_jsonrpc_method to know what I'm talking about.
Anyway, assuming you still want to do your own thing, all you have to do is register a view in Pyramid for whatever pattern you want.
config.add_route('my_framework', '/foo/{the_rest:.*}')
#view_config(route_name='my_framework')
def my_framework_view(request):
url = request.matchdict['the_rest']
# do your frameworky stuff here
if url == 'foo/bar':
return some_other_view(request)
return HTTPNotFound()
Anyway, it should be clear that this is a framework on top of a framework and probably a code smell in a lot of cases.
It really depends on the structure or you URLS and your "custom dispatcher rules", but for many cases you can use URL traversal instead of URL dispatch to achieve what you want. Due to the fact that URL traversal uses __getitem__ method of the parent resource, where you can write normal Python code, it may allow you more flexibility.
Example: imagine you have the following URLS:
/food/banana
/food/potato
/food/tomato
/food/apple
and you want to have different views for fruit and vegetables. You can do something like this:
class FoodResource(object):
def __getitem__(self, name):
if name in ["banana", "apple"]:
return FruitResource(self, name)
if name in ["potato", "tomato"]:
return VegetableResource(self, name)
then you can register views for FruitResource and VegetableResource:
#view_config(context=FruitResource):
def view_fruit(context, request):
...
#view_config(context=VegetableResource):
def view_vegetable(context, request):
...
You can have different sets of views registered for Fruits and Vegetables, so /foor/banana/make_jam and /food/apple/make_jam will be vaild URLs for fruits only, and for vegetables you'll have, say, /food/potato/make_soup:
#view_config(context=FruitResource, name="make_jam"):
def view_fruit_make_jam(context, request):
...
#view_config(context=VegetableResource, name="make_soup"):
def view_vegetable_make_soup(context, request):
...
Then your FruitResource and VegetableResource can have their own __getitem__ methods, so you can have potentially different sets of "subitems" - /food/banana/skin, /food/banana/flesh etc, with their own views assigned to them - /food/banana/skin/peel, /food/banana/flesh/eat, where peel and eat are views registered for imaginary FruitSkinResource and FruitFleshResource.
And you can have custom permissions for fruits and vegetables, so accessing /food/apple/make_jam may require one permission and /food/potato/make_soup another.
I think you could use pyramid's event system docs this is looks like hack but I think it is the easiest way.
Related
I am working through the Pyramid authorization tutorial and I have noticed the pattern where
logged_in = request.authenticated_userid
is added to each view dictionary. Can it be avoided? I.e. is there a configuration which automatically ads user id to each view. Or is there a way to create a base, abstract view with the user id and inherit from it?
Part of the code from the tutorial:
#view_config(context='.models.Page', renderer='templates/view.pt', permission='view')
def view_page(context, request):
# not relevant code
return dict(page = context, content = content, edit_url = edit_url,
logged_in = request.authenticated_userid)
#view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt',
permission='edit')
def add_page(context, request):
# not relevant code
return dict(page=page, save_url=save_url,
logged_in=request.authenticated_userid)
It's been awhile since I last looked, but I think logged_in in the samples is just an example to use to conditionally check if there is a logged on user or not. You could probably just as easily refer to request.authenticated_userid within any of your views or templates, too, and get the same behavior and not have to explicitly add a status to the response dict. The request object should be available to be referenced in your view templates, too.
Alternatively, I've used their cookbook to add a user object to the request to make a friendly request.user object that I can use to both check for logged in status where needed, plus get at my other user object details if I need to as well.
I'm new to the web development world, to Django, and to applications that require securing the URL from users that change the foo/bar/pk to access other user data.
Is there a way to prevent this? Or is there a built-in way to prevent this from happening in Django?
E.g.:
foo/bar/22 can be changed to foo/bar/14 and exposes past users data.
I have read the answers to several questions about this topic and I have had little luck in an answer that can clearly and coherently explain this and the approach to prevent this. I don't know a ton about this so I don't know how to word this question to investigate it properly. Please explain this to me like I'm 5.
There are a few ways you can achieve this:
If you have the concept of login, just restrict the URL to:
/foo/bar/
and in the code, user=request.user and display data only for the logged in user.
Another way would be:
/foo/bar/{{request.user.id}}/
and in the view:
def myview(request, id):
if id != request.user.id:
HttpResponseForbidden('You cannot view what is not yours') #Or however you want to handle this
You could even write a middleware that would redirect the user to their page /foo/bar/userid - or to the login page if not logged in.
I'd recommend using django-guardian if you'd like to control per-object access. Here's how it would look after configuring the settings and installing it (this is from django-guardian's docs):
>>> from django.contrib.auth.models import User
>>> boss = User.objects.create(username='Big Boss')
>>> joe = User.objects.create(username='joe')
>>> task = Task.objects.create(summary='Some job', content='', reported_by=boss)
>>> joe.has_perm('view_task', task)
False
If you'd prefer not to use an external library, there's also ways to do it in Django's views.
Here's how that might look:
from django.http import HttpResponseForbidden
from .models import Bar
def view_bar(request, pk):
bar = Bar.objects.get(pk=pk)
if not bar.user == request.user:
return HttpResponseForbidden("You can't view this Bar.")
# The rest of the view goes here...
Just check that the object retrieved by the primary key belongs to the requesting user. In the view this would be
if some_object.user == request.user:
...
This requires that the model representing the object has a reference to the User model.
In my project, for several models/tables, a user should only be able to see data that he/she entered, and not data that other users entered. For these models/tables, there is a user column.
In the list view, that is easy enough to implement, just filter the query set passed to the list view for model.user = loggged_id.user.
But for the detail/update/delete views, seeing the PK up there in the URL, it is conceivable that user could edit the PK in the URL and access another user's row/data.
I'm using Django's built in class based views.
The views with PK in the URL already have the LoginRequiredMixin, but that does not stop a user from changing the PK in the URL.
My solution: "Does Logged In User Own This Row Mixin"
(DoesLoggedInUserOwnThisRowMixin) -- override the get_object method and test there.
from django.core.exceptions import PermissionDenied
class DoesLoggedInUserOwnThisRowMixin(object):
def get_object(self):
'''only allow owner (or superuser) to access the table row'''
obj = super(DoesLoggedInUserOwnThisRowMixin, self).get_object()
if self.request.user.is_superuser:
pass
elif obj.iUser != self.request.user:
raise PermissionDenied(
"Permission Denied -- that's not your record!")
return obj
Voila!
Just put the mixin on the view class definition line after LoginRequiredMixin, and with a 403.html template that outputs the message, you are good to go.
In django, the currently logged in user is available in your views as the property user of the request object.
The idea is to filter your models by the logged in user first, and then if there are any results only show those results.
If the user is trying to access an object that doesn't belong to them, don't show the object.
One way to take care of all of that is to use the get_object_or_404 shortcut function, which will raise a 404 error if an object that matches the given parameters is not found.
Using this, we can just pass the primary key and the current logged in user to this method, if it returns an object, that means the primary key belongs to this user, otherwise it will return a 404 as if the page doesn't exist.
Its quite simple to plug it into your view:
from django.shortcuts import get_object_or_404, render
from .models import YourModel
def some_view(request, pk=None):
obj = get_object_or_404(YourModel, pk=pk, user=request.user)
return render(request, 'details.html', {'object': obj})
Now, if the user tries to access a link with a pk that doesn't belong to them, a 404 is raised.
You're going to want to look into user authentication and authorization, which are both supplied by [Django's Auth package] (https://docs.djangoproject.com/en/4.0/topics/auth/) . There's a big difference between the two things, as well.
Authentication is making sure someone is who they say they are. Think, logging in. You get someone to entire their user name and password to prove they are the owner of the account.
Authorization is making sure that someone is able to access what they are trying to access. So, a normal user for instance, won't be able to just switch PK's.
Authorization is well documented in the link I provided above. I'd start there and run through some of the sample code. Hopefully that answers your question. If not, hopefully it provides you with enough information to come back and ask a more specific question.
This is a recurring question and also implies a serious security flaw. My contribution is this:
There are 2 basic aspects to take care of.
The first is the view:
a) Take care to add a decorator to the function-based view (such as #login_required) or a mixin to the class-based function (such as LoginRequiredMixin). I find the official Django documentation quite helpful on this (https://docs.djangoproject.com/en/4.0/topics/auth/default/).
b) When, in your view, you define the data to be retrieved or inserted (GET or POST methods), the data of the user must be filtered by the ID of that user. Something like this:
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=User.objects.filter(pk=self.request.user.id))
return super().get(request, *args, **kwargs)
The second aspect is the URL:
In the URL you should also limit the URL to the pk that was defined in the view. Something like this:
path('int:pk/blog-add/', AddBlogView.as_view(), name='blog-add'),
In my experience, this prevents that an user sees the data of another user, simply by changing a number in the URL.
Hope it helps.
In django CBV (class based views) you can prevent this by comparing the
user entered pk and the current logged in user:
Note: I tested it in django 4 and python 3.9.
from django.http import HttpResponseForbidden
class UserDetailView(LoginRequiredMixin, DetailView):
model = your_model
def dispatch(self, request, *args, **kwargs):
if kwargs.get('pk') != self.request.user.pk:
return HttpResponseForbidden(_('You do not have permission to view this page'))
return super().dispatch(request, *args, **kwargs)
When defining URL patterns, I am supposed to use a regular expression to acquire a PK from the URL.
What if I want a URL that has no PK, and if it's not provided, it will use the currently logged in user? Examples:
visiting /user will get a DetailView of the currently logged in user
/user/edit will show an UpdateView for the currently logged in user
I tried hard-coding the pk= in the Detail.as_view() call but it reports invalid keyword.
How do I specify that in the URL conf?
My sample code that shows PK required error when visiting /user URL:
urlpatterns = patterns('',
url(r'user/$',
DetailView.as_view(
model=Account,
template_name='user/detail.html')),
)`
An alternative approach would be overriding the get_object method of the DetailView subclass, something along the line of:
class CurrentUserDetailView(UserDetailView):
def get_object(self):
return self.request.user
Much cleaner, simpler and more in the spirit of the class-based views than the mixin approach.
EDIT: To clarify, I believe that two different URL patterns (i.e. one with a pk and the other without) should be defined separately in the urlconf. Therefore they could be served by two different views as well, especially as this makes the code cleaner. In this case the urlconf might look something like:
urlpatterns = patterns('',
url(r"^users/(?P<pk>\d+)/$", UserDetailView.as_view(), name="user_detail"),
url(r"^users/current/$", CurrentUserDetailView.as_view(), name="current_user_detail"),
url(r"^users/$", UserListView.as_view(), name="user_list"),
)
And I've updated my example above to note that it inherits the UserDetailView, which makes it even cleaner, and makes it clear what it really is: a special case of the parent view.
As far as I know, you can't define that on the URL definition, since you don't have access to that information.
However, what you can do is create your own mixin and use it to build views that behave like you want.
Your mixin would look something like this:
class CurrentUserMixin(object):
model = Account
def get_object(self, *args, **kwargs):
try:
obj = super(CurrentUserMixin, self).get_object(*args, **kwargs)
except AttributeError:
# SingleObjectMixin throws an AttributeError when no pk or slug
# is present on the url. In those cases, we use the current user
obj = self.request.user.account
return obj
and then, make your custom views:
class UserDetailView(CurrentUserMixin, DetailView):
pass
class UserUpdateView(CurrentUserMixin, UpdateView):
pass
Generic views uses always RequestContext. And this paragraph in the Django Documentation says that when using RequestContext with auth app, the template gets passed an user variable that represents current user logged in. So, go ahead, and feel free to reference user in your templates.
You can get the details of the current user from the request object. If you'd like to see a different user's details, you can pass the url as parameter. The url would be encoded like:
url(r'user/(?P<user_id>.*)$', 'views.user_details', name='user-details'),
views.user_details 2nd parameter would be user_id which is a string (you can change the regex in the url to restrict integer values, but the parameter would still of type string). Here's a list of other examples for url patterns from the Django documentation.
I'm using Django, and want to store data that is relevant only for the duration of a request, and not on the session.
Is it correct to add something to request.META, like:
request.META['acl'] = acl
In my situation, I am using Tastypie, with a custom authorization class, and need a way to pass data between functions... it seems like storing something on the request would be the right thing to do... I just don't know where to store such information. My class looks something like:
class MyAuthorization(Authorization):
def is_authorized(self, request, object=None):
acl = getMyAccessControlList(request.method,request.session['username'])
for permission in acl:
if permission in self.permissions[request.method]:
request.META['acl'] = acl
return True
return False
def apply_limits(self, request, object_class, rs):
if 'HAS_ALL_ACCESS' in request.META['acl']:
return rs
else if 'HAS_USER_ACCESS' in request.META['acl']:
rs = rs.filter(object_class.user==request.session['username'])
return rs
Futher, Tastypie creates a single REST resource object, with a single authorization class used by all threads, so it's not thread-safe to just put it on the authorization class.
UPDATE
As per Chris Pratt's feedback, no, it doesn't make sense to modify the request. Exploring further, it appears to be appropriate to modify the request initially through custom middleware, and then keep it constant for the rest of the request: https://docs.djangoproject.com/en/1.4/topics/http/middleware
In this case, the middleware will look something like:
class AccessControlListMiddleware(object):
def process_view(self,request,view_func,view_args,view_kwargs):
permissions = set()
for role in request.session['permissions']:
for permission in PERMISSION_LIST[request.method][role]:
permissions.add(permission)
request.acl = list(permissions)
No. Don't mess with the request object. Especially since these are methods on the same class, you should simply assign data to self:
self.acl = getMyAccessControlList(request.method,request.session['username'])
...
if 'HAS_ALL_ACCESS' in self.acl:
Simple question. I have bunch of django views. Is there a way to tell django that for each view, use foo(view) instead? Example:
Instead of writing
#foo
#bar
#baz
def view(request):
# do something
all the time, I'd like to have
def view(request):
markers = ['some', 'markers']
and hook this into django:
for view in all_the_views_in_my_app:
view = do_something_based_on_the_marker(view)
I'd like to have this done at server startup time. Any thoughts?
Depending on what you want to do (or achieve), you can write a custom middelware and implement the method process_view (and/or any other method that you need):
process_view() is called just before Django calls the view. It should return either None or an HttpResponse object. If it returns None, Django will continue processing this request, executing any other process_view() middleware and, then, the appropriate view. If it returns an HttpResponse object, Django won't bother calling ANY other request, view or exception middleware, or the appropriate view; it'll return that HttpResponse. Response middleware is always called on every response.
I don't know why you want to do this. I don't know either why you don't want to use decorators. But you could use this ugly (and likely error prone) hack as a start:
def view(request):
pass
view.markers = ['some', 'markers']
some other place:
from app import views
[x for x in views.__dict__.values() if hasattr(x,'markers')]