Last active
August 29, 2015 14:09
-
-
Save pzrq/6d70ef3f501d7a759885 to your computer and use it in GitHub Desktop.
Django Lazy ModelChoiceField
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
""" | |
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) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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