11

I have a model that looks like this:

class Event(models.Model):
    event_dates = ManyToManyField("EventDate")
    #...

class EventDate(models.Model):
    event_date = DateField()
    #...

However, in the django admin MultipleSelect form field that gets show for event_dates in the EventAdmin, I'd like to limit the queryset to event_dates that are not in the past.

The queryset would be something like:

event_date_queryset = EventDate.objects.filter(event_date__gte = datetime.date.today()) 

But where can I set this queryset so that only non-past dates show up in the field?

(I don't currently have a custom form for the EventAdmin but would be happy to add one.)

B Robster
  • 40,605
  • 21
  • 89
  • 122
  • Possible duplicate of [Filter ManyToMany box in Django Admin](http://stackoverflow.com/questions/1226760/filter-manytomany-box-in-django-admin) – Wtower Apr 18 '17 at 05:52

2 Answers2

26

You could try:

event_dates = models.ManyToManyField("EventDate", limit_choices_to={'event_date__gte': date.today()})

taken from https://docs.djangoproject.com/en/dev/ref/models/fields/#django.db.models.ForeignKey.limit_choices_to

but then only dates in the future are shown, even if some dates in the past are still associated to Event.

If you also want all the dates you previously associated to Event you could manipulate ModelForm as following

from datetime import date

from django.contrib import admin
from django import forms
from django.db.models import Q

from models import Event, EventDate

class EventAdminForm(forms.ModelForm):
    class Meta:
        model = Event

    def __init__(self, *args, **kwargs):
        super(EventAdminForm, self).__init__(*args, **kwargs)
        if 'event_dates' in self.initial:
            self.fields['event_dates'].queryset = EventDate.objects.filter(Q(pk__in=self.initial['event_dates']) | Q(event_date__gte=date.today()))
        else:
            self.fields['event_dates'].queryset = EventDate.objects.filter(event_date__gte=date.today())

class EventAdmin(admin.ModelAdmin):
    form = EventAdminForm
    filter_horizontal = ['event_dates']
andrea.ge
  • 1,937
  • 1
  • 18
  • 27
  • yes, i meant event_dates and updated the question to accurately reflect that. Good point on wanting to keep event_dates that are in the past but already selected. The more complete solution is probably the one I'm after, although I learned something new with limit_choices_to. Thanks! – B Robster May 22 '13 at 14:13
  • I ended up using the second (more comprehensive) approach almost verbatim, works great. – B Robster May 25 '13 at 20:08
  • this is awesome. – Mario Sep 25 '18 at 14:12
5

Simplest way is to do it in the admin

Your models

class Event(models.Model):
    event_dates = ManyToManyField("EventDate")
    #...

class EventDate(models.Model):
    event_date = DateField()
    #...

Now in your admin file

This has the added advantage that it only returns event dates only for the current event that you are looking at using the reverse lookup event__id=event_id

class EventAdmin(admin.ModelAdmin):
    def get_field_queryset(self, db, db_field, request):
        """
        If the ModelAdmin specifies ordering, the queryset should respect that
        ordering.  Otherwise don't specify the queryset, let the field decide
        (returns None in that case).
        """
        if db_field.name == 'event_dates':
            event_id = int(request.resolver_match.args[0])

            return db_field.remote_field.model._default_manager.filter(
                              event__id=event_id,
                              event_date__gte = datetime.date.today()
            )

        super().get_field_queryset(db, db_field, request)
Dr Manhattan
  • 13,537
  • 6
  • 45
  • 41
  • 1
    For Django 2 app I just needed to change id get to request.resolver_match.kwargs['object_id'] – farincz Jun 01 '18 at 10:09