From e8fee126c42b8775f9e9cce7131739e9b73fb940 Mon Sep 17 00:00:00 2001 From: Oktay Sancak Date: Sun, 12 May 2013 01:49:59 +1000 Subject: [PATCH 1/3] Default queryset support --- flexselect/__init__.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/flexselect/__init__.py b/flexselect/__init__.py index 745a17f..eb2e7c6 100644 --- a/flexselect/__init__.py +++ b/flexselect/__init__.py @@ -43,8 +43,11 @@ def choices_from_instance(instance, widget): for trigger_field in widget.trigger_fields: getattr(instance, trigger_field) except (ObjectDoesNotExist, AttributeError): - return [('', widget.empty_choices_text(instance))] - + try: + return choices_from_queryset(widget.default_queryset(instance)) + except NotImplementedError: + return [('', widget.empty_choices_text(instance))] + return choices_from_queryset(widget.queryset(instance)) def details_from_instance(instance, widget): @@ -175,6 +178,9 @@ def details(self, base_field_instance, instance): def queryset(self, instance): raise NotImplementedError + + def default_queryset(self, instance): + raise NotImplementedError def empty_choices_text(self, instance): raise NotImplementedError From cf858d7228f2a3f730f96ae3111a30a21108a63b Mon Sep 17 00:00:00 2001 From: Oktay Sancak Date: Sun, 12 May 2013 14:05:08 +1000 Subject: [PATCH 2/3] Remove unused import --- flexselect/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/flexselect/__init__.py b/flexselect/__init__.py index eb2e7c6..c9a146a 100644 --- a/flexselect/__init__.py +++ b/flexselect/__init__.py @@ -1,4 +1,3 @@ -from collections import defaultdict from itertools import chain import hashlib import json From 45169c99a3fbc6f594f8fb12a14302eb8aa25d93 Mon Sep 17 00:00:00 2001 From: Oktay Sancak Date: Sun, 12 May 2013 14:12:34 +1000 Subject: [PATCH 3/3] PEP 8 fixes --- flexselect/__init__.py | 117 +++++++++++++++++++++-------------------- flexselect/urls.py | 4 +- flexselect/views.py | 13 ++--- 3 files changed, 70 insertions(+), 64 deletions(-) diff --git a/flexselect/__init__.py b/flexselect/__init__.py index c9a146a..389e340 100644 --- a/flexselect/__init__.py +++ b/flexselect/__init__.py @@ -14,14 +14,18 @@ FLEXSELECT = { 'include_jquery': False, } -try: FLEXSELECT.update(settings.FLEXSELECT) -except AttributeError: pass - + +try: + FLEXSELECT.update(settings.FLEXSELECT) +except AttributeError: + pass + + def choices_from_queryset(queryset): """ - Makes choices from a QuerySet in a format that is usable by the + Makes choices from a QuerySet in a format that is usable by the django.forms.widget.Select widget. - + queryset: An instance of django.db.models.query.QuerySet """ return chain( @@ -29,12 +33,13 @@ def choices_from_queryset(queryset): [(o.pk, smart_unicode(o)) for o in queryset], ) + def choices_from_instance(instance, widget): """ - Builds choices from a model instance using the widgets queryset() method. - If any of the widgets trigger_field fields is not defined on the instance or + Builds choices from a model instance using the widgets queryset() method. + If any of the widgets trigger_field fields is not defined on the instance or the instance itself is None, None is returned. - + instance: An instance of the model used on the current admin page. widget: A widget instance given to the FlexSelectWidget. """ @@ -48,13 +53,14 @@ def choices_from_instance(instance, widget): return [('', widget.empty_choices_text(instance))] return choices_from_queryset(widget.queryset(instance)) - + + def details_from_instance(instance, widget): """ Builds html from a model instance using the widgets details() method. If - any of the widgets trigger_field fields is not defined on the instance or + any of the widgets trigger_field fields is not defined on the instance or the instance itself is None, None is returned. - + instance: An instance of the model used on the current admin page. widget: A widget instance given to the FlexSelectWidget. """ @@ -66,27 +72,29 @@ def details_from_instance(instance, widget): return u'' return widget.details(related_instance, instance) + def instance_from_request(request, widget): - """ - Returns a partial instance of the widgets model loading it with values - from a POST request. - """ - items = dict(request.POST.items()) - values = {} - for f in widget.base_field.model._meta.fields: - if f.name in items: - try: - value = f.formfield().to_python(items[f.name]) - if value is not None: - values[f.name] = value - except ValidationError: - pass - return widget.base_field.model(**values) - + """ + Returns a partial instance of the widgets model loading it with values + from a POST request. + """ + items = dict(request.POST.items()) + values = {} + for f in widget.base_field.model._meta.fields: + if f.name in items: + try: + value = f.formfield().to_python(items[f.name]) + if value is not None: + values[f.name] = value + except ValidationError: + pass + return widget.base_field.model(**values) + + class FlexSelectWidget(Select): + """Instances of widgets with their hashed names as keys.""" instances = {} - """ Instances of widgets with their hashed names as keys.""" - + class Media: js = [] if FLEXSELECT['include_jquery']: @@ -94,30 +102,30 @@ class Media: js.append('%s/jquery/1.6.1/jquery.min.js' % googlecdn) js.append('%s/jqueryui/1.8.13/jquery-ui.min.js' % googlecdn) js.append('flexselect/js/flexselect.js') - - def __init__(self, base_field, modeladmin, request, *args, + + def __init__(self, base_field, modeladmin, request, *args, **kwargs): - + self.base_field = base_field self.modeladmin = modeladmin self.request = request - + self.hashed_name = self._hashed_name() FlexSelectWidget.instances[self.hashed_name] = self super(FlexSelectWidget, self).__init__(*args, **kwargs) - + def _hashed_name(self): """ - Each widget will be unique by the name of the field and the class name + Each widget will be unique by the name of the field and the class name of the model admin. """ salted_string = "".join([ - settings.SECRET_KEY, - self.base_field.name, - self.modeladmin.__class__.__name__, + settings.SECRET_KEY, + self.base_field.name, + self.modeladmin.__class__.__name__, ]) return "_%s" % hashlib.sha1(salted_string).hexdigest() - + def _get_instance(self): """ Returns a model instance from the url in the admin page. @@ -131,7 +139,7 @@ def _get_instance(self): return self.modeladmin.get_object(self.request, object_id) except ValueError: return None - + def _build_js(self): """ Adds the widgets hashed_name as the key with an array of its trigger_fields @@ -143,23 +151,22 @@ def _build_js(self): flexselect.fields = flexselect.fields || {}; flexselect.fields.%s = %s; """ % ( - self.hashed_name, json.dumps({ - 'base_field': self.base_field.name, - 'trigger_fields': self.trigger_fields,}) - ) - - + self.hashed_name, json.dumps({ + 'base_field': self.base_field.name, + 'trigger_fields': self.trigger_fields + })) + def render(self, name, value, attrs=None, choices=(), *args, **kwargs): """ - Overrides. Reduces the choices by calling the widgets queryset() - method and adds a details that is filled with the widgets + Overrides. Reduces the choices by calling the widgets queryset() + method and adds a details that is filled with the widgets details() method. """ instance = self._get_instance() self.choices = choices_from_instance(instance, self) html = [] html.append(super(FlexSelectWidget, self).render( - name, value, attrs=attrs, + name, value, attrs=attrs, *args, **kwargs )) html.append(self._build_js()) @@ -167,21 +174,19 @@ def render(self, name, value, attrs=None, choices=(), *args, **kwargs): html.append(details_from_instance(instance, self)) html.append('') return mark_safe("".join(html)) - + # Methods and properties that must be implemented. - + trigger_fields = [] - + def details(self, base_field_instance, instance): raise NotImplementedError - + def queryset(self, instance): raise NotImplementedError def default_queryset(self, instance): raise NotImplementedError - + def empty_choices_text(self, instance): raise NotImplementedError - - diff --git a/flexselect/urls.py b/flexselect/urls.py index bbbfa3e..130dcdd 100644 --- a/flexselect/urls.py +++ b/flexselect/urls.py @@ -1,5 +1,5 @@ from django.conf.urls.defaults import * -urlpatterns = patterns('flexselect.views', - (r'field_changed', 'field_changed'), +urlpatterns = patterns( + 'flexselect.views', (r'field_changed', 'field_changed'), ) diff --git a/flexselect/views.py b/flexselect/views.py index 1684b23..c01231a 100644 --- a/flexselect/views.py +++ b/flexselect/views.py @@ -4,9 +4,10 @@ from django.forms.widgets import Select from django.contrib.auth.decorators import login_required -from flexselect import (FlexSelectWidget, choices_from_instance, +from flexselect import (FlexSelectWidget, choices_from_instance, details_from_instance, instance_from_request) + @login_required def field_changed(request): """ @@ -14,16 +15,16 @@ def field_changed(request): html for new options and details for the dependent field as json. """ hashed_name = request.POST.__getitem__('hashed_name') - widget = FlexSelectWidget.instances[hashed_name] + widget = FlexSelectWidget.instances[hashed_name] instance = instance_from_request(request, widget) - + if bool(int(request.POST.__getitem__('include_options'))): choices = choices_from_instance(instance, widget) options = Select(choices=choices).render_options([], []) else: options = None - + return HttpResponse(json.dumps({ - 'options' : options, + 'options': options, 'details': details_from_instance(instance, widget), - }), mimetype='application/json') + }), mimetype='application/json')