From 5f5dd275e36ab7a17b3503da237b142d6a44a1cc Mon Sep 17 00:00:00 2001 From: Tim Valenta Date: Wed, 23 Jul 2014 14:20:16 -0700 Subject: [PATCH 1/2] Merge branch 'master' of git://github.com/machalekj/django-datatable-view into feature/genericipaddressfield --- MANIFEST.in | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 796e43e6..4cbf507e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,4 @@ include README.md include requirements.txt -recursive-include datatableview static/* -recursive-include datatableview templates/* +recursive-include datatableview/static * +recursive-include datatableview/templates * From e73a8e1785bac7ec5935c00fac89ba49b9127210 Mon Sep 17 00:00:00 2001 From: Tim Valenta Date: Wed, 23 Jul 2014 14:25:05 -0700 Subject: [PATCH 2/2] Add external dictionary of model field classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should allow external customization of how field classes are treated, particularly for external field from third-party apps, and fields that we need to dynamically detect, such as the overlooked ``GenericIPAddressField``. This closes #53 and address #49, although in the latter case, we may still eventually want a way to register a field type “category” into the dictionary with a set of trailing ORM query bits that can be applied to support unknown extensions to the query language. --- datatableview/helpers.py | 4 ++-- datatableview/utils.py | 23 +++++++++++++++++++++-- datatableview/views.py | 15 +++++++-------- 3 files changed, 30 insertions(+), 12 deletions(-) diff --git a/datatableview/helpers.py b/datatableview/helpers.py index 0823eb64..e852cbb4 100644 --- a/datatableview/helpers.py +++ b/datatableview/helpers.py @@ -16,7 +16,7 @@ import six -from .utils import resolve_orm_path, FIELD_TYPES +from .utils import resolve_orm_path, XEDITABLE_FIELD_TYPES if get_version().split('.') >= ['1', '5']: from django.utils.timezone import localtime @@ -231,7 +231,7 @@ def make_xeditable(instance=None, extra_attrs=[], *args, **kwargs): if field.choices: field_type = 'select' else: - field_type = FIELD_TYPES.get(field.get_internal_type(), 'text') + field_type = XEDITABLE_FIELD_TYPES.get(field.get_internal_type(), 'text') else: field_type = 'text' attrs['data-type'] = field_type diff --git a/datatableview/utils.py b/datatableview/utils.py index 2f1b93ed..6af0a2c0 100644 --- a/datatableview/utils.py +++ b/datatableview/utils.py @@ -8,6 +8,7 @@ except ImportError: from UserDict import UserDict +from django.db import models from django.db.models.fields import FieldDoesNotExist from django.template.loader import render_to_string from django.forms.util import flatatt @@ -46,9 +47,27 @@ 'sort_column_direction': 'sSortDir_%d', } -# Mapping of Django's supported field types to their more generic type names. -# These values are primarily used for the xeditable field type lookups +# Mapping of Django field categories to the set of field classes falling into that category. +# This is used during field searches to know which ORM language queries can be applied to a field, +# such as "__icontains" or "__year". FIELD_TYPES = { + 'text': [models.CharField, models.TextField, models.FileField], + 'date': [models.DateField], + 'boolean': [models.BooleanField], + 'integer': [models.IntegerField, models.AutoField], + 'float': [models.FloatField, models.DecimalField], + + # This is a special type for fields that should be passed up, since there is no intuitive + # meaning for searches done agains the FK field directly. + 'ignored': [models.ForeignKey], +} +if hasattr(models, 'GenericIPAddressField'): + FIELD_TYPES['text'].append(models.GenericIPAddressField) + +# Mapping of Django's supported field types to their more generic type names. +# These values are primarily used for the xeditable field type lookups. +# TODO: Would be nice if we can derive these from FIELD_TYPES so there's less repetition. +XEDITABLE_FIELD_TYPES = { 'AutoField': 'number', 'BooleanField': 'text', 'CharField': 'text', diff --git a/datatableview/views.py b/datatableview/views.py index 6cbb7823..b35cb232 100644 --- a/datatableview/views.py +++ b/datatableview/views.py @@ -22,7 +22,7 @@ import dateutil.parser from .forms import XEditableUpdateForm -from .utils import (ObjectListResult, DatatableOptions, split_real_fields, +from .utils import (FIELD_TYPES, ObjectListResult, DatatableOptions, split_real_fields, filter_real_fields, get_datatable_structure, resolve_orm_path, get_first_orm_bit, get_field_definition) @@ -136,10 +136,9 @@ def apply_queryset_options(self, queryset): field_queries = [] # Queries generated to search this database field for the search term field = resolve_orm_path(self.model, component_name) - - if isinstance(field, (models.CharField, models.TextField, models.FileField)): + if isinstance(field, tuple(FIELD_TYPES['text'])): field_queries = [{component_name + '__icontains': term}] - elif isinstance(field, models.DateField): + elif isinstance(field, tuple(FIELD_TYPES['date'])): try: date_obj = dateutil.parser.parse(term) except ValueError: @@ -163,7 +162,7 @@ def apply_queryset_options(self, queryset): field_queries.append({component_name + '__month': numerical_value}) if 0 < numerical_value <= 31: field_queries.append({component_name + '__day': numerical_value}) - elif isinstance(field, models.BooleanField): + elif isinstance(field, tuple(FIELD_TYPES['boolean'])): if term.lower() in ('true', 'yes'): term = True elif term.lower() in ('false', 'no'): @@ -172,17 +171,17 @@ def apply_queryset_options(self, queryset): continue field_queries = [{component_name: term}] - elif isinstance(field, (models.IntegerField, models.AutoField)): + elif isinstance(field, tuple(FIELD_TYPES['integer'])): try: field_queries = [{component_name: int(term)}] except ValueError: pass - elif isinstance(field, (models.FloatField, models.DecimalField)): + elif isinstance(field, tuple(FIELD_TYPES['float'])): try: field_queries = [{component_name: float(term)}] except ValueError: pass - elif isinstance(field, models.ForeignKey): + elif isinstance(field, tuple(FIELD_TYPES['ignored'])): pass else: raise ValueError("Unhandled field type for %s (%r) in search." % (component_name, type(field)))