开发者

Django admin, filter objects by ManyToMany reference

There's photologue application, simple photo gallery for django, implementing Photo and Ga开发者_StackOverflow社区llery objects. Gallery object has ManyToMany field, which references Photo objects.

I need to be able to get list of all Photos for a given Gallery. Is it possible to add Gallery filter to Photo's admin page? If it's possible, how to do it best?


You need to write a custom FilterSpec! Custom Filter in Django Admin on Django 1.3 or below
It'll look like this:

from django.contrib.admin.filterspecs import RelatedFilterSpec, FilterSpec
from models import Gallery

class GalleryFilterSpec(RelatedFilterSpec):
    def __init__(self, f, request, params, model, model_admin):
        self.lookup_kwarg = f.name
        self._lookup_model = f.rel.to
        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
        self.user = request.user
        self.lookup_choices = [(g.pk, g.name) for g in Gallery.objects.all()]

    def has_output(self):
        return len(self.lookup_choices) > 1

    def title(self):
        return self._lookup_model._meta.verbose_name

FilterSpec.filter_specs.insert(0, 
        (lambda f: f.rel.to == Gallery, GalleryFilterSpec))

Put it in a module filters.py in your app package and import it in you admin.py (it's important to import it, so that the filter becomes registered on the admin site!)

EDIT: "f" is the field instance, in this case models.ManyToManyField The last line registers the FilterSpec for all fields that have a relation to the Gallery model. This will not work as you mentioned if the field is defined on the Gallery model, since django.contrib.admin.views.main.ChangeList.get_filters checks if the field you define in the list really exist on the model (doesnt work for related_name either). I think the easiest way around is that you could make a custom template for that changelist and hardcode your filter in there, the FilterSpec itself isn't need for the filtering itself, django uses just the url get parameters for that!


Well, that's how I've done it.

I made custom admin template "change_list.html". Custom template tag creates a list of all existing galleries. Filtering is made like this:


class PhotoAdmin(admin.ModelAdmin):
    ...
    def queryset(self, request):
        if request.COOKIES.has_key("gallery"):
            gallery = Gallery.objects.filter(title_slug=request.COOKIES["gallery"])
            if len(gallery)>0:
            return gallery[0].photos.all()
            return super(PhotoAdmin, self).queryset(request)

Cookie is set with javascript.


For future reference for others to find, if you have a relationship it's bi-directional, so you can get the photos for galleries or the galleries for a photo via a ModelAdmin.

Let's say you have a changelist view for your Photo model:

from django.contrib import admin
from yourapp.models import Photo

class PhotoAdmin(admin.ModelAdmin):
    list_filter = ('galleries', )

admin.site.register(Photo, PhotoAdmin)

Then in the admin you'll see a filter showing all of the galleries and if you click one it'll filter the list to show you only photos for that gallery.

Of course, this may not be practical if there are a LOT of galleries, but you can get there just by using the well-documented ModelAdmin rather than hacking together a template or filterspec.

http://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects


@Jough Dempsey pointed out you maybe don't need a custom FilterSpec just for m2m fields.

However today I found I wanted one for a django-taggit tag field. The tags are basically an m2m relation but it complains that 'TaggableManager' object has no attribute 'get_choices' if you try and add the tag field into list_filter.

In this case it was @lazerscience's code to the rescue...

However it didn't work when used against Django 1.3, needed a couple of new lines added, compare my version below which works:

class TagFilterSpec(RelatedFilterSpec):
    def __init__(self, f, request, params, model, model_admin, field_path=None):
        super(RelatedFilterSpec, self).__init__(
            f, request, params, model, model_admin, field_path=field_path)

        self.lookup_title = f.verbose_name # use field name
        self.lookup_kwarg = f.name
        self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path)
        self._lookup_model = f.rel.to
        self.lookup_val = request.GET.get(self.lookup_kwarg, None)
        self.lookup_val_isnull = request.GET.get(
                                      self.lookup_kwarg_isnull, None)
        self.user = request.user
        self.lookup_choices = [(g.pk, g.name) for g in Tag.objects.all()]

    def has_output(self):
        return len(self.lookup_choices) > 1

    def title(self):
        return self._lookup_model._meta.verbose_name

FilterSpec.filter_specs.insert(0, 
        (lambda f: f.rel.to == Tag, TagFilterSpec))
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜