I have a multilingual Django project. Every language is a different subdomain.
So we've decided to use the "sites" application and to create one different site for every language.
On that project, I also have a "pages" application, which is quite similar to a CMS. The user can create pages with content and they'll be displayed in the appropriate language site.
Now I'm looking to be able to manage advanced permissions. What I need to do is to allow, in the admin application a user only to create and update pages for one (or many) specific language/site.
What'd be the cleaner way to do something like that ?
Edit : Here is the solution I've adapted, given by Chris
I create a decorator that's checking if the user is appropriately in the group that has access to the lang.
See Chris' accepted answer for an example of this.
In a "normal" view, I do the following :
def view(self):
# Whatever you wanna do
return render_to_response('page.html', {}, RequestContext(request))
view = group_required(view)
If the user is in the group, it'll return the method. Otherwise, it'll return an "Access Denied" error.
And in my admin, I do the following :
class PageAdmin(admin.ModelAdmin):
list_display = ('title', 'published')
fieldsets = [
(None, {'fields': ['title', 'slug', 'whatever_field_you_have']}),
]
def has_add_permission(self, request):
return in_group_required(request)
admin.site.register(Page, PageAdmin)
Where the in_group_required is a similar method to group_required mentionned above. But will return only true or false depending of if we have access or not.
And because we use them quite much in the previous examples, you'll find above here what I have in my in_group and group_required methods.
def group_required(func):
def _decorator(request, *args, **kwargs):
if not in_group(request):
return HttpResponse("Access denied")
return func(*args, **kwargs)
return _decorator
def in_group(request):
language = Language.objects.get(site__domain__exact=request.get_host())
for group in language.group.all():
if request.user in group.user_set.all():
return True
return False
You could create a Group (http://docs.djangoproject.com/en/dev/topics/auth/)
per site / language and add the users to the groups accordingly.
Then, you can check if the request.user.groups belongs to the group.
(You can do this with a decorator:
def group_required(func):
def _decorator(request, *args, **kwargs):
hostname = request.META.get('HTTP_HOST')
lang = hostname.split(".")[0]
if not lang in request.user.groups:
return HttpResponse("Access denied")
return func(*args, **kwargs)
return _decorator
(Correct / modify the code to match your requirements...)
You can override has_add_permission (and related methods) in your ModelAdmin class.
(With similar code like shown above)
If you want to filter the Page objects on the admin index of your page-application,
you can override the method queryset() in ModelAdmin.
This QuerySet returns only those Page objects, that belong to a Site (and therefore Group)
of which the request.user is a member.
Pages.objects.filter(site__name__in=request.user.groups)
Related
So, i have a rather usual "update item" page that is a class-based view which inherits UpdateView. (in views.py it looks like "class ItemUpdateView(UpdateView) and it has method get_success_url(self) defined which contains the redirect url where user will be taken after clicking "Update" button.
My problem is that in my application, there are two different pages that could lead me to this "Update item" page, and depending on the page that user comes from - i want to take the user back to either pageA or pageB upon the successful update of the item.
I wasn't able to find the best-practices of how to handle this anywhere on the web, so - would really appreciate the help.
My guess is that I need to create an additional parameter that will be a part of the url and will contain A or B depending on the pageA or pageB that user came from, i.e. the url itself would be something like '/itemUpdate/int:pk/sourcepage' => '/itemUpdate/45/A'. Does that sound like a correct aproach or is there a better way?
There is a better way that you can check Meta dictionary in request:
write in your views file:
class ItemUpdateView(UpdateView):
previous_url = ''
form_class = UpdateItem
def get(self, request, *args, **kwargs):
self.previous_url = request.META.get('HTTP_REFERER')
print(self.previous_url)
return super().get(request, *args, **kwargs)
def get_initial(self):
initial = super().get_initial()
initial['success_url'] = self.previous_url
return initial
def form_valid(self, form):
self.success_url = form.cleaned_data['success_url']
print(self.success_url)
return super().form_valid(form)
# also you can use get_success_url instead of form_valid()
# def get_success_url(self):
# return super().get_form().cleaned_data['success_url']
and then write a hidden field in your form and name it success_url
class UpdateItem(forms.ModelForm):
success_url = forms.URLField(widget=forms.HiddenInput)
class Meta:
model=Item
fields=['itemName','quantity']
Note you can not use instance in order to get success_url field, because this field belong to form nor your model instance !
refer to documentions
I created two users on the django admin page. When one user enters data the other user can see the data.
So,every users can see and change each other datas.
I want each user can only see and change own datas not others.
How can i prevent it?
I think that you don't have to use django admin as user profile. You can create authorization/authentication system on your website, this is the great post about it.
Then you can create user profile for every users, where they can add/edit/delete their private datas.
You can change the attributes (columns) according to different users (based on permission or some user attribute) but can not filter tuples on basis of that.
Example - (in app_name/admin.py)
def get_readonly_fields(self, request, obj=None):
if request.user.is_superuser:
self.readonly_fields = ()
else:
self.readonly_fields = ('id', 'name', 'attr1', 'attr2')
return self.readonly_fields
But looking on the limited info given, i suppose you want to filter tuples. This can only be achieved by making a custom admin page parallel to django's admin (ex - name it staff panel). In its view method filter (using Querysets) according to the school and return's its related data for edit or whatever you want (for ease you can use Model forms).
EDIT: Sorry, i found exactly what you were asking for(hopefully).
class MyModelAdmin(admin.ModelAdmin):
def get_queryset(self, request):
qs = super(MyModelAdmin, self).get_queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(author=request.user)
This returns only the objects related to logged in user on the admin change page.
I've been trying to create a Django generic deleteview, to delete an instance of a model.
I however have to check whether it is allowed to delete this item. This is done using a method defined in the model.
So far I've created this:
#login_required
def delete_employee(request, pk):
employee = None
try:
employee = Employee.objects.get(pk=pk)
except:
pass
if employee and not employee.empty():
return render(request, "error.html", None)
else:
# Load the generic view here.
Is this a decent way to go? And how can I load the generic view there?
I've tried things like EmployeeDelete.as_view() but those things don't work.
Or is there a way to check this in the generic view itself?
I've tried that as well, but I wasn't able to load an error page, just throw errors.
To do this with a DeleteView you can do this just by overriding the delete method on your inherited view. Here is an example based on what you have said. This is just an example of how you can accomplish it. You might need to tweak it for your exact scenario, specifically the else on can_delete
class EmployeeDeleteView(DeleteView):
success_url = reverse_lazy('index')
def delete(self, request, *args, **kwargs):
self.object = self.get_object()
can_delete = self.object.can_delete()
if can_delete:
return super(EmployeeDeleteView, self).delete(
request, *args, **kwargs)
else:
raise Http404("Object you are looking for doesn't exist")
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.
I'm attempting to override some of the behaviour of the Django UserAdmin model. Particularly, I'd like to hide the 'superuser' field from non-superusers.
So, my approach is this:
class ModelAdmin(BaseModelAdmin):
"Encapsulates all admin options and functionality for a given model."
# ...
def has_change_permission(self, request, obj=None):
"""
Returns True if the given request has permission to change the given
Django model instance.
If `obj` is None, this should return True if the given request has
permission to change *any* object of the given type.
"""
opts = self.opts
return request.user.has_perm(opts.app_label + '.' + opts.get_change_permission())
#...
Based on what I've found in ModelAdmin
class UserAdmin(UserAdmin):
"""
... my customised UserAdmin
"""
# adding a new method
def is_superuser(self, request):
"Returns True if the given user is a superuser."
return request.user.is_superuser
# then elsewhere 'hopefully' show a slightly different fieldset
# the following, of course, doesn't work.
fieldsets = (
(None, {
'fields': (
("first_name", "last_name"),
("email", "password"),
"is_staff",
"is_active",
"is_superuser" if self.is_superuser() else None
)
}),
('Groups', {
'fields': (
'groups',
)
}),
('Meta', {
'classes': ('collapse',),
'fields': (
'username',
"last_login",
"date_joined"
)
})
)
So, my questions are:
How do I create a def within my new custom UserAdmin class, such as above, and how do I call it? (How do I know when I'm in the right context to do so)
Part 2 (bonus): how can I succinctly include/exclude the 'is_superuser' field in the form, as the psuedo code above is suggesting?
Kind thanks fellows!
~ Daryl
Thank you
If you just want to forbid users to promote themselves to superuser, override YourUserAdmin.get_readonly_fields():
class YourUserAdmin(admin.ModelAdmin):
...
def get_readonly_fields(self, request, obj=None):
if request.user.is_superuser:
return None
try:
return self.readonly_fields + ('is_superuser',)
except:
return ('is_superuser',)
You have to unregister the default User/UserAdmin and then register your own.
admin.site.unregister(User)
admin.site.register(User, YourUserAdmin)
However, the ZEN of admin says:
At it's core, Django's admin is designed for a single activity: trusted users editing structured content.
If the user is not trusted, do not give him edit rights to edit user accounts, period. Even if you hide the superadmin option and the "filter by superadmin status" filter, he can just change your password and log in as you. So, if you need some untrusted users to edit user accounts, forget the admin and write your own dumbed down interface.
No experience with Django but thinking a closure might help you in this situation:
class UserAdmin(UserAdmin):
# adding a new method
def is_superuser(self, request):
"Returns True if the given user is a superuser."
return request.user.is_superuser
def gen_fieldset(self, request):
'''
If request.user.is_supeuser == true then return fieldset generator for superuser.
Else return fieldset generator for normal user.
'''
su = is_superuser(request)
def get_fieldset():
if su:
return super_user_fieldset
else:
return normal_user_fieldset
return get_fieldset
Usage would go like this:
obj = UserAdmin()
request = ... #Generate a normal or super-user request.
fieldset = obj.gen_fieldset(request)
# Sometime later when you want to use the fieldset...
fields = fieldset() # Function will decide which fieldset to give based on gen_fieldset() call earlier.
So the basic idea is that you can configure get_fieldset() ahead of time (via the call to gen_fieldset() where the closure is employed) to return the appropriate fieldset for the user's level. Give the user the function object returned by gen_fieldset() it'll generate the appropriate fieldset at a later time whenever the user calls it.
NOTE: If you've never used closures before I'd suggest googling around for other examples and use scenarios. There may be other solutions that'll fit your situation better (again, not familiar with Django) but that was the first thing that came to my mind.