Opened 9 years ago

Closed 9 years ago

#1502 closed enhancement (wontfix)

Make (?P) url parameters available to filter a generic view's queryset.

Reported by: benjaminlyu@… Owned by: jacob
Component: Generic views Version: magic-removal
Severity: normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: UI/UX:

Description

Example Problem:

1) http://example.com/users/ - lists all the users.

2) http://example.com/users/(?P<mystr>[a-z]+)/ - For given string, list all the users with a username beginning with that string.

(1) is a simple generic view. However, one currently must write a custom view to come up with a QuerySet for (2). What we could do is just take the generic view used for (1) and filter that QuerySet down more.

The code listed here is an example solution using a custom wrapper to the object_list generic view. A real solution would patch the generic views. 'myproject.object_list' (shown here) differs from the generic object_list by two items in its method signature: extra_lookup_kwargs={} and kwargs; some of its code can be used to filter the queryset.

myproject/urls.py

info_dict = {
  'queryset': MyModel.objects.all(),
  'extra_lookup_kwargs': { 'mystr': 'name__istartswith'}
}

urlpatterns = patterns('',
    (r'^users/$', 'myproject.views.object_list', info_dict),
    (r'^users/(?P<mystr>.+)/$', 'myproject.views.object_list', info_dict),
)

myproject/views.py:

from django.template import loader
from django.views.generic import list_detail

def object_list(request, queryset, paginate_by=None, allow_empty=False,
  template_name=None, template_loader=loader,
  extra_context={}, context_processors=None, template_object_name='object',
  extra_lookup_kwargs={}, **kwargs):

  extra_lookup_dict = {}
  for param in kwargs:
    if param in extra_lookup_kwargs:
      extra_lookup_dict[extra_lookup_kwargs[param]] = kwargs[param]
  if len(extra_lookup_dict) > 0:
    newqueryset = queryset.filter(**extra_lookup_dict)
  else:
    newqueryset = queryset

  return list_detail.object_list(request=request, queryset=newqueryset,
    paginate_by=paginate_by, allow_empty=allow_empty,
    template_name=template_name, template_loader=template_loader,
    extra_context=extra_context, context_processors=context_processors,
    template_object_name=template_object_name)

Change History (3)

comment:1 Changed 9 years ago by benjaminlyu@…

Thinking about the above... Instead of hardcoding logic for the filter, it may be better to allow the end user to use their own queryset_modifier method.

def object_list(request, queryset, paginate_by=None, allow_empty=False,
  template_name=None, template_loader=loader,
  extra_context={}, context_processors=None, template_object_name='object',
  queryset_modifier=None, **kwargs):

  if callable(queryset_modifier):
    finalQs = queryset_modifier(request, queryset, **kwargs)
  else:
    finalQs = queryset

  return list_detail.object_list(request=request, queryset=finalQs,
    paginate_by=paginate_by, allow_empty=allow_empty,
    template_name=template_name, template_loader=template_loader,
    extra_context=extra_context, context_processors=context_processors,
    template_object_name=template_object_name)

def my_queryset_modifier(request, queryset, p_names, **kwargs):
  extra_lookup_dict = {}

  # Pull it out from the query
  val = request.GET.get('q', '')
  if len(val) > 0:
    extra_lookup_dict['name__exact'] = val

  # use the /path/
  for param in kwargs:
    if param in p_names:
      extra_lookup_dict[p_names[param]] = kwargs[param]
  if len(extra_lookup_dict) > 0:
    return queryset.filter(**extra_lookup_dict)
  else:
    return queryset

info_dict = {
  'queryset': MyModel.objects.all(),
  'queryset_modifier': my_queryset_modifier,
  'p_names': { 'mystr': 'name__istartswith'}
}

urlpatterns = patterns('',
    (r'^users/$', 'myproject.views.object_list', info_dict),
    (r'^users/(?P<mystr>.+)/$', 'myproject.views.object_list', info_dict),
)

#The developer will have to coordinate the ?P<names> and the extra keys in info_dict so they don't overlap, since they'll both be passed in as kwargs to the object_list. This will be done when the queryset_modifier function is defined, since the conflicts will be settled in that function's parameter listing.

comment:2 Changed 9 years ago by lukeplant

The custom view code for your specific case looks like this:

def my_object_list(request, mystr):
    mymodels = MyModel.objects.filter(name__istartswith=mystr)
    return list_detail.object_list(request, mymodels,
        extra_context={'foo':'bar'},
        paginate_by=10, allow_empty='True',
        template_name='mymodels/index')

Is that so hard? Remember, you don't have to make the function have the same interface as the generic view -- you simply take the parameters that were in your urls.py and put them in your view function.

Trying to stuff everything into generic views makes them less useful, since the interface becomes more and more complex. There are lots of tickets similar to this one, trying to get generic views to do one more thing (i.e. a custom tweak). If you added them all it would be ridiculous. Generic views are already very easy to wrap, with very few lines of code. Your my_queryset_modifier() function is almost twice as long as the code above.

So I would be -1 on this.

comment:3 Changed 9 years ago by adrian

  • Resolution set to wontfix
  • Status changed from new to closed

Marking this as a wontfix for the reasons Luke mentioned in his previous comment.

Note: See TracTickets for help on using tickets.
Back to Top