BrotherJack
BrotherJack

Reputation: 329

Django Concatenate Two Querysets IN ORDER

So I have a ModelForm with a select field. In this select field there are about 100 entries. Of these entries I have a number of entries like "Organization Colorado - Denver", which I would like to have at the top of the list, such that all the entries with "Organization Colorado" are at the top of the list, and everything else is sorted in lexicographical order.

I've tried making two separate querysets (this seems like a bad idea, but manageable with only 100 or so entries). There seems to be a lot of ways of combining these two query sets, but without maintaining the order (which is the point). I've tried this:

class CreateContactForm(ModelForm):
    ...

    def __init__(self, *args, **kwargs):
        super(CreateContactForm, self).__init__(*args, **kwargs)
        p = models.ConstantContactList.objects.filter(
            name__startswith=settings.PREF_ORGANIZATION_PREFIX
        )
        np = models.ConstantContactList.objects.filter(
            name__regex=r'^(?!{})'.format(settings.PREF_ORGANIZATION_PREFIX)
        ).order_by('-name')

        self.fields['cc_lists'].queryset = list(p) + list(np)

This doesn't work, although it might, if there was some way to convert that list back into a queryset, or if there is a way to go around the queryset maybe? I'm not sure. Can anyone provide a clue as to what I should do?

Upvotes: 3

Views: 1227

Answers (2)

BrotherJack
BrotherJack

Reputation: 329

OK. I've come up with a solution that works here, and I'm providing it to anyone else who has the same need.

from django.forms.widgets import Select
import re

class CustomOrderingWidget(Select):
    def __init__(self, priority_regex, sort='+', attrs=None):
        super(CustomOrderingWidget, self).__init__(attrs)
        self.regex = re.compile(priority_regex)
        self.sort = sort
        self.template_name = 'django/forms/widgets/select.html'

    def render(self, name, value, attrs=None, renderer=None):
        context = self.get_context(name, value, attrs)
        optgroups = context.get('widget').get('optgroups')
        firsts, others = [], []
        for grp in optgroups:
            if self.regex.search(grp[1][0].get('label')):
                firsts.append(grp)
            else:
                others.append(grp)
        if self.sort == '+':
            kfn = lambda x: x[1][0].get('label')
            context['widget']['optgroups'] = sorted(firsts, key=kfn) +\
                                               sorted(others, key=kfn)
        elif self.sort == '-':
            kfn = lambda x: x[1][0].get('label')
            context['widget']['optgroups'] =\
                sorted(firsts, key=kfn, reverse=True) +\
                sorted(others, key=kfn, reverse=True)
        else:
            context['widget']['optgroups'] = firsts + others
        return self._render(self.template_name, context, renderer)

Then you can plug it into a ModelForm like this...

import settings # YOUR personal stuffz!

class CreateContactForm(ModelForm):
    ...
    class Meta:
       ...
        widgets = {
            # Just an example, make your own regex string!  
            'cc_lists': CustomOrderingWidget("^{0}".format(
                settings.PREF_ORGANIZATION_PREFIX
            ))
        }

Upvotes: 0

Exelian
Exelian

Reputation: 5888

I would recommend against trying to order the querysets and just handle sorting in the rendering layer (templates or forms). This way if you want to localize your code you won't have to change your queries.

Assuming you use a forms.Select widget. You may want to inherit from this widget and override the render_menu logic in order to construct it yourself and handle the ordering yourself. You'll have access to the rendered or unrendered options, so it shouldn't be an issues from that point on.

Upvotes: 3

Related Questions