Skip to content

Instantly share code, notes, and snippets.

@pzrq
Last active August 29, 2015 14:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pzrq/6d70ef3f501d7a759885 to your computer and use it in GitHub Desktop.
Save pzrq/6d70ef3f501d7a759885 to your computer and use it in GitHub Desktop.
Django Lazy ModelChoiceField
"""
In practice with four subclasses of LazyModelChoiceField
across a large project, we found existing __deepcopy__ based
alternatives such as
https://djangosnippets.org/snippets/2973/
simply didn't work in all the circumstances we needed them to.
Tested against Python 2.7 and Python 3.4 with Django 1.6.8
Hopefully this works for you too :)
"""
from django import forms
from django.utils.functional import lazy
from .form_widgets import LazyChoicesMixin, LazySelect
class LazyModelChoiceField(LazyChoicesMixin, forms.ModelChoiceField):
"""
A form ModelChoiceField that respects choices being a lazy object.
Inspired by https://github.com/SmileyChris/django-countries/commit/ed870d76
"""
widget = LazySelect
def grouped_choices(self):
"""
Allows constructing a list of choices grouped by headings.
i.e. add some structured HTML <optgroup> headings.
If you just need the laziness, delete this hook and update
_set_queryset appropriately.
"""
raise NotImplementedError('See subclasses')
def _set_choices(self, value):
"""
Also update the widget's choices.
"""
super(LazyModelChoiceField, self)._set_choices(value)
self.widget.choices = value
def _set_queryset(self, queryset):
"""
Overridden so we can set the choices lazily.
"""
self._queryset = queryset
self.choices = lazy(self.grouped_choices, list)()
queryset = property(forms.ModelChoiceField._get_queryset, _set_queryset)
from collections import defaultdict
from .form_fields import LazyModelChoiceField
class TopicChoiceField(LazyModelChoiceField):
"""
For a Topic model with a ForeignKey to an Area model
where the Area has a unique title.
"""
def grouped_choices(self):
areas = defaultdict(list)
for topic in self.queryset.all():
label = self.label_from_instance(topic)
areas[topic.area.title].append((topic.id, label))
choices = list(sorted(areas.iteritems()))
return choices
from django.forms import widgets
from django.utils.functional import Promise
class LazyChoicesMixin(object):
"""
See https://github.com/SmileyChris/django-countries/commit/ed870d76
"""
@property
def choices(self):
"""
When it's time to get the choices, if it was a lazy then figure it out
now and memoize the result.
"""
if isinstance(self._choices, Promise):
self._choices = list(self._choices)
return self._choices
@choices.setter
def choices(self, value):
self._set_choices(value)
def _set_choices(self, value):
self._choices = value
class LazySelect(LazyChoicesMixin, widgets.Select):
"""
A form Select widget that respects choices being a lazy object.
"""
def __init__(self, attrs=None):
# Deliberately bypass direct superclass constructor
# so we can set choices later and lazily
super(widgets.Select, self).__init__(attrs)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment