Limit queryset on Django 2 autocomplete_field - django

For a long time we've been overriding our ModelAdmin's formfield_for_foreignkey to limit the queryset the field can choose from. Here's a simplified version of what I mean:
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "site":
if not request.user.is_superuser:
kwargs["queryset"] = request.user.site
But I recently added this field to the autocomplete_fields definition (to get some Select2 gravy). The result was I now see no suggestions as a non-superuser account.
Is there a more right way to limit the queryset, or is this a simple bug in Django?

This needs a patch that's still in dev. You can be patient, or you can monkey-patch AutocompleteJsonView.has_perm as I am below. I just stuck this in settings.
If you're also stuck on 2.0.x (as I currently am shakes fist at Wagtail) you'll also need to make sure your ModelAdmins define a has_view_permission.
from django.contrib.admin.views.autocomplete import AutocompleteJsonView
def ac_has_perm(self, request, obj=None):
return self.model_admin.has_view_permission(request, obj=obj)
AutocompleteJsonView.has_perm = ac_has_perm

Related

Django Admin change_list forcing search_form to reject given seach keywords

Problem:
I'd like to enforce the change_list_view when opening the page with "?q=" (that is with no value or whitespace) and simply load the page by not executing queries at all.
Possible ways:
My question is how can I do that by overriding the queryset, get_queryset, or get_search_results methods ?
Any other means are welcomed?
Expectations:
The end goal is to:
start with a page displaying 0 results
no database activity
the search text input focused
underneath a message instructing the user the it may start typing the thing it seeks.
Thanks.
You can't override ModelAdmin.get_queryset() because it is used in the edit/delete views. So you have to inherit ChangeList class and override get_queryset() in it:
from django.contrib import messages
from django.contrib.admin.views.main import ChangeList
class MyChangeList(ChangeList):
def get_queryset(self, request):
queryset = super(MyChangeList, self).get_queryset(request)
if not request.GET.get('q', ''):
messages.add_message(request, messages.INFO, 'Start typing.')
queryset = queryset.none()
return queryset
class MyAdmin(admin.ModelAdmin):
def get_changelist(self, request, **kwargs):
return MyChangeList

Django Admin of multiple databases

According to the 1.4 documentation on using Admin with multiple databases, you only need to implement 5 methods in your ModelAdmin subclass. I've overridden all 5 in the recommended way. Browsing the database works with no problems.
However, attempt to save an existing record and I get an error claiming that the table doesn't exist in the database - the default database for the project, not the one I've specified in the method implementations. In fact, save_model() doesn't get called before the error is thrown, so somewhere before it gets that far there's a reference somewhere that isn't successfully getting the "using" for the right database.
Anyone know what's missing? Here's my ModelAdmin class:
class TransactionAdmin(admin.ModelAdmin):
using = "salesdb"
def save_model(self, request, obj, form, change):
# Tell Django to save objects to the 'other' database.
obj.save(using=self.using)
def delete_model(self, request, obj):
# Tell Django to delete objects from the 'other' database
obj.delete(using=self.using)
def queryset(self, request):
# Tell Django to look for objects on the 'other' database.
return super(TransactionAdmin, self).queryset(request).using(self.using)
def formfield_for_foreignkey(self, db_field, request=None, **kwargs):
# Tell Django to populate ForeignKey widgets using a query
# on the 'other' database.
return super(TransactionAdmin, self).formfield_for_foreignkey(db_field, request=request, using=self.using, **kwargs)
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
# Tell Django to populate ManyToMany widgets using a query
# on the 'other' database.
return super(TransactionAdmin, self).formfield_for_manytomany(db_field, request=request, using=self.using, **kwargs)
And the error I'm getting is:
(1146, "Table 'django_dev._Transactions' doesn't exist")
django_dev is the default database, not the sales database.
It is apparently a bug.
https://code.djangoproject.com/ticket/19747
The bug has been accepted so I guess that means they confirmed it.

Django admin - Is it possible to limit a user's access to only his own inputted data?

In other words, I would like to disallow users from editing or viewing anything but their own inputted data, throughout all applications.
I read here that this may be impossible with the built in admin application. If so is there an extension available?
Thanks
It can be done.
You need to create the appropriate modelAdmin in your admin.py first.
For list "display" filtering modify the queryset method:
class MyModelAdmin(admin.ModelAdmin):
def queryset(self, request):
return Entry.objects.filter(owner=request.user)
For field filtering, depending on the field type you want to limit you override the appropriate method.
Related django documentation is here:
https://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.formfield_for_foreignkey
To limit foreignkey field output you can do something like this:
(from the django documentation)
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car": # The name of the field you want to limit
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

Django Admin - Specific user (admin) content

I'm starting to organize a new project and let's say i'll have a few models like products and catalogs.
I will allow my clients (not visitors, only specific clients) to login on Django Admin site to create, edit and delete their own catalogs.
Lets say I create a model called "Shop", create every shop (name, address, logo, contact info and etc.) and create an admin user binded to that shop.
Now I want this new admin (who's not a site admin, but a shop admin -- probably an user group) to see and edit only the catalogs linked with his shop.
Is that possible?
Should I do this inside the Django Admin or should I create a new "shop admin" app?
First, the cautionary warning: The Django admin design philosophy is that any user with access to the admin (is_staff==True) is a trusted user, e.g. an employee, hence the "staff" designation to even gain access to the admin. While you can customize the admin to restrict areas, allowing anyone not within your organization access to your admin is considered risky, and Django makes no guarantees about any sort of security at that point.
Now, if you still want to proceed, you can restrict most everything but the shops right off the bat by simply not assigning those privileges to the user. You'll have to give all the shop owners rights to edit any of the shop models they'll need access to, but everything else should be left off their permissions list.
Then, for each model that needs to be limited to the owner's eyes only, you'll need to add a field to store the "owner", or user allowed access to it. You can do this with the save_model method on ModelAdmin, which has access to the request object:
class MyModelAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
obj.user = request.user
super(MyModelAdmin, self).save_model(request, obj, form, change)
Then you'll also need to limit the ModelAdmin's queryset to only those items own by the current user:
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(owner=request.user)
However, that will only limit what gets listed, the user could still play with the URL to access other objects they don't have access to, so you'll need to override each of the ModelAdmin's vulnerable views to redirect if the user is not the owner:
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
class MyModelAdmin(admin.ModelAdmin):
def change_view(self, request, object_id, form_url='', extra_context=None):
if not self.queryset(request).filter(id=object_id).exists():
return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))
return super(MyModelAdmin, self).change_view(request, object_id, form_url, extra_context)
def delete_view(self, request, object_id, extra_context=None):
if not self.queryset(request).filter(id=object_id).exists():
return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))
return super(MyModelAdmin, self).delete_view(request, object_id, extra_context)
def history_view(self, request, object_id, extra_context=None):
if not self.queryset(request).filter(id=object_id).exists():
return HttpResponseRedirect(reverse('admin:myapp_mymodel_changelist'))
return super(MyModelAdmin, self).history_view(request, object_id, extra_context)
UPDATE 06/05/12
Thanks #christophe31 for pointing out that since the ModelAdmin's queryset is already limited by user, you can just use self.queryset() in the change, delete and history views. This nicely abstracts away the model classname making the code less fragile. I've also changed to using filter and exists instead of a try...except block with get. It's more streamlined that way, and actually results in a simpler query, as well.
I'm just posting this here since the top comment is no longer the most up to date answer. I'm using Django 1.9, I'm not sure when this is change took place.
For example, you have different Venues and different users associated with each Venue, the model will look something like this:
class Venue(models.Model):
user = models.ForeignKey(User)
venue_name = models.CharField(max_length=255)
area = models.CharField(max_length=255)
Now, staff status for the user must be true if he allowed to log in through the django admin panel.
The admin.py looks something like:
class FilterUserAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
if getattr(obj, 'user', None) is None:
obj.user = request.user
obj.save()
def get_queryset(self, request):
qs = super(FilterUserAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
return qs.filter(user=request.user)
def has_change_permission(self, request, obj=None):
if not obj:
return True
return obj.user == request.user or request.user.is_superuser
#admin.register(Venue)
class VenueAdmin(admin.ModelAdmin):
pass
function name has changed from queryset to get_queryset.
EDIT: I wanted to extend my answer. There's another way to return filtered objects without using the queryset function. I do want to emphasise that I don't know if this method is more efficient or less efficient.
An alternative implementation for the get_queryset method is as follows:
def get_queryset(self, request):
if request.user.is_superuser:
return Venue.objects.all()
else:
return Venue.objects.filter(user=request.user)
Furthermore, we can also filter content is the relationships are more deeper.
class VenueDetails(models.Model):
venue = models.ForeignKey(Venue)
details = models.TextField()
Now, if I want to filter this model which has Venue as foreignkey but does not have User, my query changes as follows:
def get_queryset(self, request):
if request.user.is_superuser:
return VenueDetails.objects.all()
else:
return VenueDetails.objects.filter(venue__user=request.user)
Django ORM allows us to access different kinds of relationships which can be as deep as we want via '__'
Here's a link to the offical docs for the above.
I am sorry, I know it is late ,but maybe it would help anyone else. I guess that the django-permission app could help to perform the purpose.
I think RelatedOnlyFieldListFilter should help you.
Here the link to Django Doc : RelatedOnlyFieldListFilter
list_filter can be : a tuple, where the first element is a field
name and the second element is a class inheriting from
django.contrib.admin.FieldListFilter, for example:
class PersonAdmin(admin.ModelAdmin):
list_filter = (
('is_staff', admin.BooleanFieldListFilter),
)
You can limit the choices of a related model to the objects
involved in that relation using RelatedOnlyFieldListFilter: (Vous
pouvez limiter les choix d’un modèle lié aux objets concernés par la
relation en utilisant RelatedOnlyFieldListFilter:)
class BookAdmin(admin.ModelAdmin):
list_filter = (
('author', admin.RelatedOnlyFieldListFilter),
)
Assuming author is a ForeignKey to a User model, this will limit the list_filter choices to the users who have written a book
instead of listing all users. (En supposant que author est une clé
ForeignKey vers un modèle User, cela va limiter les choix de
list_filter aux utilisateurs qui ont écrit un livre au lieu d’énumérer
tous les utilisateurs.)

How to filter queryset in changelist_view in django admin?

Let's say I have a site where Users can add Entries through admin panel. Each User has his own Category he is responsible for (each Category has an Editor assigned through ForeingKey/ManyToManyField).
When User adds Entry, I limit the choices by using EntryAdmin like this:
class EntryAdmin(admin.ModelAdmin):
(...)
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'category':
if request.user.is_superuser:
kwargs['queryset'] = Category.objects.all()
else:
kwargs['queryset'] = Category.objects.filter(editors=request.user)
return db_field.formfield(**kwargs)
return super(EntryAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This way I can limit the categories to which a User can add Entry and it works perfect.
Now the tricky part: On the Entry changelist/action page I want to show only those Entries which belong to current User's Category. I tried to do this using this method:
def changelist_view(self, request, extra_context=None):
if not request.user.is_superuser:
self.queryset = self.queryset.filter(editors=request.user)
But I get this error:
AttributeError: 'function' object has no attribute 'filter'
This is strange, because I thought it should be a typical QuerySet. Basically such methods are not well documented and digging through tons of Django code is not my favourite sport.
Any ideas how can I achieve my goal?
Warning: This answer is from 2010, and is not useful for Django >= 1.8.
queryset is a method on ModelAdmin which returns a queryset. You need to override it on your EntryAdmin class.
def queryset(self, request):
qs = super(EntryAdmin, self).queryset(request)
if request.user.is_superuser:
return qs
else:
return qs.filter(editors=request.user)
Changing the queryset will limit the Entries shown in the list view. You also need to override has_change_permission to ensure that the user has permission to edit the object on the individual object editing page. See the following blog post by James Bennett for further details:
http://www.b-list.org/weblog/2008/dec/24/admin/