1

This is a followup on this thread: How to filter ModelAdmin autocomplete_fields results with the context of limit_choices_to

Uberdude proposed a solution which works great to customize the autoselect queryset based on the field which triggered the request, but I would also need to filter based on input from the client side, most specifically a checkbox which is not a model field, and only for some of the fields in the form as in this form excerpt.

Form

I managed to apply the checkbox to the widget by overriding your AutocompleteSelect widget as:

class AutocompleteSelectCb(AutocompleteSelect):
def render(self, name, value, attrs=None):
    s = super().render(name, value, attrs)
    return mark_safe('<div style="margin-bottom:10px;"><input type="checkbox" id="parent1"\
         name="parentx" value="1">Search among all kennels</div>' + s)

and use that widget only when the fields are present in a autocomplete_cb_fields attribute in the admin:

 autocomplete_fields = ['breed']
 autocomplete_cb_fields = ['father', 'mother']

However, I am not sure how to get my AutocompleteSelectCb widget to send the status of the checkbox so that it can be processed in the get_search_results method. I assume with some js, but how? Any idea?

jphilip
  • 136
  • 2
  • 9

1 Answers1

1

I was finally able to resolve the problem by using the approach laid out in this post, that is adding querystring keys to the referrer URL because select2 does not accept modification of the request URL after being initialized. I am also adding other information about the dog being edited such as its id and birth year to further filter the queryset (A dog cannot be its own parent or have a parent younger than self, mothers can only be females...).

//this get passes to the select2:opening event listener
function expand_ajax_location_search($, fieldId) {
    if (!$) {
        $ = django.jQuery;
    }
    var pageURL = $(location).attr("href");
    var match = pageURL.match(/\/.*\/(\d+)\/change/);
    return `&birth_year=${$('#id_birth_year').val()}&dog_id=${match[1]}&father_cb=${$('#father-cb').prop( "checked" )}&mother_cb=${$('#mother-cb').prop( "checked" )}`
}

Finally doing the filtering on the server side was challenging because the dog admin is normally filtered by kennel so that each kennel owner can only edit their dogs, although the checkbox allows them to use dogs of other kennels as parents. The challenge in the get_search_results method was to unfilter the queryset when the box is checked, which cannot be done, so create a new one before passing it to super().get_search_results. Also the base get_search_results method seems to traverse all the relations even if they are not needed resulting in over 100 queries, so using select_related on all relation produces just one query.

def get_search_results(self, request, queryset, search_term):
    if request.is_ajax and '/autocomplete/' in request.path and request.GET.get('model_name') == 'dog':
        url = urllib.parse.urlparse(request.headers['Referer'])
        referer = url.path
        # (parse_qs results are lists)
        qs = urllib.parse.parse_qs(url.query)
        if request.GET.get('field_name') == 'father':
            if qs.get('father_cb')[0] == 'true':
                # default is filetered by kennel so need new qs
                queryset = Dog.objects.select_related(
                    'kenn', 'img', 'father', 'mother', 'breeder').order_by('name')
            queryset, use_distinct = super().get_search_results(request, queryset, search_term)
            queryset = queryset.exclude(
                sex='F').exclude(id=qs.get('dog_id')[0])
        elif request.GET.get('field_name') == 'mother':
            if qs.get('mother_cb')[0] == 'true':
                # default is filetered by kennel so need new qs
                queryset = Dog.objects.select_related(
                    'kenn', 'img', 'father', 'mother', 'breeder').order_by('name')
            queryset, use_distinct = super().get_search_results(request, queryset, search_term)
            queryset = queryset.exclude(
                sex='M').exclude(id=qs.get('dog_id')[0])
        birth_year = qs.get('birth_year')[0]
        if birth_year:
            queryset = queryset.exclude(birth_year__gt=birth_year)
    else:
        queryset, use_distinct = super().get_search_results(request, queryset, search_term)
    return queryset, use_distinct
jphilip
  • 136
  • 2
  • 9