I'm using Django for an application, and wondering about an option in the admin.
Is it possible for django admin to redirect to the details page of an object, if only one exists in the list view?
For example,
if only this object exists:
redirect immediately to the change view on this object, without needing the user to click on the object.
I'm not using any custom view. I couldn't find any solution after 2 hours of search.
Thanks!
You can try this
def changelist_view(self, request, extra_context=None):
if self.model.objects.all().count() == 1:
obj = self.model.objects.all()[0]
return HttpResponseRedirect(reverse("admin:%s_%s_change" %(self.model._meta.app_label, self.model._meta.model_name), args=(obj.id,)))
return super(ItemAdmin, self).changelist_view(request=request, extra_context=extra_context)
Also check changelist_view parameters based on django version.
I try something with readonly field in Django 1.8.7, let say I have some code like the following:
class MyAdmin(admin.ModelAdmin):
readonly_fields = ('a', 'b')
def get_readonly_fields(self, request, obj=None):
if not request.user.is_superuser:
self.readonly_fields += ('c')
return super(MyAdmin, self).get_readonly_fields(request, obj)
first I login with super admin and access that admin page change_form,
the code is works well, then I login with staff user, then still works well, again, I try login with superadmin, but the read only fields rendered is for the non-superadmin user,
again I clear the browser cache, try again with super admin, but still not work correctly. I try restart the server, then it work normally until I repeat the same step above I do, this weird thing come again.
Anyone know why this happen ? I think this is looks like some bug but not sure.
Thanks in Advance.
The bug is not in Django, but in your code. In your get_readonly_fields method you modify the readonly_fields attribute; those modifications persist, since the admin object lives for the lifetime of the process.
Don't do that. get_readonly_fields is supposed to return a value, not modify the attribute. Just do:
def get_readonly_fields(self, request, obj=None):
rfo = super(MyAdmin, self).get_readonly_fields(request, obj)
if not request.user.is_superuser:
rfo += ('c')
return rfo
I'm new to django.
I want to run a script(for ex. zipping a file) after it gets uploaded to a server through "admin panel",i.e when user hits Save "in" from admin panel,it should get zipped(or some other manipulation that i may want to implement) after it gets uploaded.
Or can you just tell me which function is called when user hits the save button.
Signals might work, but it seems like the OP wants to do something only when an object is created or changed from the admin panel.
I think the best way to do this is to use the ModelAdmin method save_model().
From the Django docs:
ModelAdmin.save_model(self, request, obj, form, change)
You can overwrite this method in your definition of an admin class, as follows:
class SomeObjectAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# do any pre-save stuff here
obj.save()
The change arg is a Boolean value that is True if the object is being changed, and false if the object is being created for the first time. So if you want to execute some function only on object creation:
def save_model(self, request, obj, form, change):
if not change:
# do your compression here
# do any other pre-save stuff here
obj.save()
# do any post-save stuff here
You may use signals : https://docs.djangoproject.com/en/dev/topics/signals/
to detect the save action.
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)
I have a model (UserProfile) with which I want to record a user's IP address when new user accounts are created.
I've begin the process of overriding the Django model's save() method, but I am totally uncertain how to properly access HttpRequest attributes from within a model. Can any of you guys help? Google was unable to provide a specific answer for me.
You can get to the request from within the admin, but you can't do it in general code. If you need it then you'll have to assign it manually in the view.
You always need to pass on request information from the view to your save-method.
Consider that saving an instance doesn't always have to be invoked from a http request (for a simple example think of calling save in the python shell).
If you need to access the request object within the admin while saving, you can override it's save_model method:
def save_model(self, request, obj, form, change):
# do something with obj and request here....
obj.save()
But otherwise you always have to pass it on from the view:
def my_view(request):
obj = MyClass(ip_address = request.META['REMOTE_ADDR'])
Or to make this easier to re-use, make a method on the model like:
def foo(self, request):
self.ip_address = request.META['REMOTE_ADDR']
self..... = request.....
and call it from your view with obj.foo(request).