Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: pivotal-energy-solutions/django-datatable-view
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: master
Choose a base ref
...
head repository: CloudNcodeInc/django-datatable-view
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: master
Choose a head ref
Can’t automatically merge. Don’t worry, you can still create the pull request.

Commits on Jul 28, 2014

  1. Copy the full SHA
    ef3a248 View commit details

Commits on Jul 30, 2014

  1. Copy the full SHA
    329ac4f View commit details

Commits on Sep 2, 2014

  1. Copy the full SHA
    3a9dbe3 View commit details
  2. Copy the full SHA
    d511129 View commit details
  3. Copy the full SHA
    cf75af7 View commit details

Commits on Sep 3, 2014

  1. Copy the full SHA
    afb6e86 View commit details
  2. Copy the full SHA
    f70aea3 View commit details
  3. Copy the full SHA
    f4ac9f9 View commit details
  4. Copy the full SHA
    148c5a9 View commit details
  5. Merge branch 'master' into dev

    Conflicts:
    	datatableview/views.py
    davidfischer-ch committed Sep 3, 2014
    Copy the full SHA
    3dadf06 View commit details
  6. Copy the full SHA
    aade972 View commit details
  7. Copy the full SHA
    87989f9 View commit details
  8. Copy the full SHA
    13aa99a View commit details
  9. Copy the full SHA
    12f783d View commit details

Commits on Nov 21, 2014

  1. Copy the full SHA
    92ede4a View commit details
  2. Copy the full SHA
    85ea27f View commit details
  3. Copy the full SHA
    fdb40f4 View commit details
  4. Copy the full SHA
    ed7d7c0 View commit details
  5. Copy the full SHA
    c1d7b8c View commit details
  6. Copy the full SHA
    e6a8590 View commit details
  7. Copy the full SHA
    f1acab2 View commit details
  8. Merge branch 'dev'

    davidfischer-ch committed Nov 21, 2014
    Copy the full SHA
    a1e4181 View commit details

Commits on Jan 29, 2015

  1. Merge remote-tracking branch 'upstream/master'

    Conflicts:
    	datatableview/helpers.py
    	datatableview/views.py
    davidfischer-ch committed Jan 29, 2015
    Copy the full SHA
    65fed42 View commit details
  2. Set execution bit

    davidfischer-ch committed Jan 29, 2015
    Copy the full SHA
    2d22405 View commit details

Commits on Sep 1, 2015

  1. Merge remote-tracking branch 'upstream/master'

    Conflicts:
    	datatableview/compat.py
    	datatableview/tests/example_project/example_project/example_app/views.py
    	datatableview/tests/example_project/example_project/settings.py
    	datatableview/tests/example_project/example_project/wsgi.py
    	datatableview/tests/test_helpers.py
    	datatableview/tests/test_views.py
    	datatableview/tests/testcase.py
    	datatableview/utils.py
    	datatableview/views.py
    davidfischer-ch committed Sep 1, 2015
    Copy the full SHA
    3a307e1 View commit details
  2. Copy the full SHA
    0a80fa9 View commit details
  3. Copy the full SHA
    2a48e45 View commit details
71 changes: 71 additions & 0 deletions datatableview/handlers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -*- encoding: utf-8 -*-

import datetime
import dateutil.parser

__all__ = (
'boolean_field_handler', 'date_field_handler', 'float_field_handler', 'ignored_field_handler',
'integer_field_handler', 'text_field_handler'
)


def boolean_field_handler(field, component_name, term):
if term.lower() in ('true', 'yes'):
return [{component_name: True}]
elif term.lower() in ('false', 'no'):
return [{component_name: False}]


def date_field_handler(field, component_name, term):
field_queries = []
try:
date_obj = dateutil.parser.parse(term)
except ValueError:
# This exception is theoretical, but it doesn't seem to raise.
pass
except TypeError:
# Failed conversions can lead to the parser adding ints to None.
pass
except OverflowError:
# Catches OverflowError: signed integer is greater than maximum
pass
else:
field_queries.append({component_name: date_obj})

# Add queries for more granular date field lookups
try:
numerical_value = int(term)
except ValueError:
pass
else:
from . import utils # Fix a recursive import
if utils.MINIMUM_YEAR < numerical_value < datetime.MAXYEAR - 1:
field_queries.append({component_name + '__year': numerical_value})
if 0 < numerical_value <= 12:
field_queries.append({component_name + '__month': numerical_value})
if 0 < numerical_value <= 31:
field_queries.append({component_name + '__day': numerical_value})

return field_queries


def float_field_handler(field, component_name, term):
try:
return [{component_name: float(term)}]
except ValueError:
pass


def ignored_field_handler(field, component_name, term):
pass


def integer_field_handler(field, component_name, term):
try:
return [{component_name: int(term)}]
except ValueError:
pass


def text_field_handler(field, component_name, term):
return [{component_name + '__icontains': term}]
Original file line number Diff line number Diff line change
@@ -1037,7 +1037,6 @@ def get_datatable(self, type=None):

return datatable


def get_context_data(self, **kwargs):
context = super(MultipleTablesDatatableView, self).get_context_data(**kwargs)

2 changes: 0 additions & 2 deletions datatableview/tests/example_project/example_project/wsgi.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
# -*- encoding: utf-8 -*-
"""
WSGI config for test_project project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.6/howto/deployment/wsgi/
"""
13 changes: 6 additions & 7 deletions datatableview/tests/test_helpers.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@
test_data_fixture = 'test_data.json'



class HelpersTests(DatatableViewTestCase):
fixtures = [test_data_fixture]

@@ -31,7 +30,7 @@ def test_link_to_model(self):
with self.assertRaises(AttributeError) as cm:
helper(related)
self.assertEqual(str(cm.exception), "'RelatedM2MModel' object has no attribute 'get_absolute_url'")

# Verify simple use
instance = ExampleModel.objects.get(pk=1)
output = helper(instance)
@@ -136,7 +135,7 @@ def test_itemgetter(self):
secondary_helper = helper(slice(0, 5), ellipsis=True)
output = secondary_helper(data)
self.assertEqual(output, data[:5] + "...")

# Verify ellipsis can be customized
secondary_helper = helper(slice(0, 5), ellipsis="custom")
output = secondary_helper(data)
@@ -167,7 +166,7 @@ def test_attrgetter(self):
def test_make_xeditable(self):
""" Verifies that make_xeditable works. """
helper = helpers.make_xeditable

# Items that the helper normally expects in a callback context
internals = {'field_data': ("Name", 'name', None)}

@@ -189,8 +188,8 @@ def test_make_xeditable(self):
self.assertEqual(str(cm.exception), "'make_xeditable' cannot determine a value for 'url'.")

# Verify kwargs accumulate
kwargs1 = { 'type': 'textarea' }
kwargs2 = { 'other_arg': True }
kwargs1 = {'type': 'textarea'}
kwargs2 = {'other_arg': True}
secondary_helper = helper(**kwargs1)
expected_kwargs = dict(kwargs1, extra_attrs=[])
self.assertEqual(secondary_helper.keywords, expected_kwargs)
@@ -207,7 +206,7 @@ def test_make_xeditable(self):
'source': "SOURCE DATA",
'title': "TITLE DATA",
'placeholder': "PLACEHOLDER DATA",

# Extra stuff not in anticipated to appear in rendered string
'special': "SPECIAL DATA",
'data_custom': "DATA-CUSTOM DATA",
6 changes: 4 additions & 2 deletions datatableview/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -4,9 +4,11 @@
from .test_app import models
from .. import utils


def get_structure(columns, opts):
return utils.get_datatable_structure('/', dict(opts, columns=columns), model=models.ExampleModel)


class UtilsTests(DatatableViewTestCase):
def test_get_first_orm_bit(self):
""" """
@@ -55,12 +57,12 @@ def test_split_real_fields(self):
real, fake = utils.split_real_fields(model, ['name', 'fake'])
self.assertEqual(real, ['name'])
self.assertEqual(fake, ['fake'])

# Fake first, real last
real, fake = utils.split_real_fields(model, ['fake', 'name'])
self.assertEqual(real, [])
self.assertEqual(fake, ['fake', 'name'])

def test_filter_real_fields(self):
model = models.ExampleModel
fields = [
1 change: 0 additions & 1 deletion datatableview/tests/test_views.py
Original file line number Diff line number Diff line change
@@ -157,7 +157,6 @@ def test_embedded_table_datatable_view(self):
len(view.datatable_options['columns'])
)


# Straightforward views that call on procedural logic not worth testing. We would effectively
# be proving that Python strings concatenate, etc.
# Instead of proving details of the callbacks we've written, we'll just ask for the views, to
1 change: 1 addition & 0 deletions datatableview/tests/testcase.py
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@
from django.test.utils import override_settings
from django.db.models import loading
initial_data_fixture = 'initial_data_legacy'

def clear_app_cache():
loading.cache.loaded = False

16 changes: 13 additions & 3 deletions datatableview/utils.py
Original file line number Diff line number Diff line change
@@ -26,6 +26,8 @@

import six

from . import handlers

# Sane boundary constants
MINIMUM_PAGE_LENGTH = 5

@@ -73,6 +75,15 @@
if hasattr(models, 'GenericIPAddressField'):
FIELD_TYPES['text'].append(models.GenericIPAddressField)

FIELD_HANDLERS = {
'text': handlers.text_field_handler,
'date': handlers.date_field_handler,
'boolean': handlers.boolean_field_handler,
'integer': handlers.integer_field_handler,
'float': handlers.float_field_handler,
'ignored': handlers.ignored_field_handler
}

# 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.
@@ -109,6 +120,7 @@
ColumnOrderingTuple = namedtuple('ColumnOrderingTuple', ['order', 'column_index', 'direction'])
ColumnInfoTuple = namedtuple('ColumnInfoTuple', ['pretty_name', 'attrs'])


def resolve_orm_path(model, orm_path):
"""
Follows the queryset-style query path of ``orm_path`` starting from ``model`` class. If the
@@ -127,7 +139,6 @@ def get_model_at_related_field(model, attr):
"""
Looks up ``attr`` as a field of ``model`` and returns the related model class. If ``attr`` is
not a relationship field, ``ValueError`` is raised.
"""

try:
@@ -150,7 +161,7 @@ def get_model_at_related_field(model, attr):
return field.field.rel.to

raise ValueError("{0}.{1} ({2}) is not a relationship field.".format(model.__name__, attr,
field.__class__.__name__))
field.__class__.__name__))


def get_first_orm_bit(field_definition):
@@ -474,4 +485,3 @@ def filter_real_fields(model, fields, key=None):
else:
virtual_fields.append(field)
return db_fields, virtual_fields

82 changes: 18 additions & 64 deletions datatableview/views.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# -*- encoding: utf-8 -*-

import datetime
import json
from django.utils.encoding import force_text
import re
@@ -15,25 +14,23 @@
from django.http import HttpResponse, HttpResponseBadRequest
from django.core.exceptions import ObjectDoesNotExist
from django.db.models import Model, Manager, Q
from django.utils.encoding import force_text
from django.utils.text import smart_split
from django.views.decorators.csrf import ensure_csrf_cookie
from django.conf import settings
from django import get_version

import six
import dateutil.parser

from .forms import XEditableUpdateForm
from .utils import (FIELD_TYPES, ObjectListResult, DatatableOptions, DatatableStructure,
split_real_fields, filter_real_fields, resolve_orm_path, get_first_orm_bit,
get_field_definition, MINIMUM_YEAR)
from .utils import (FIELD_TYPES, FIELD_HANDLERS, ObjectListResult, DatatableOptions,
DatatableStructure, split_real_fields, filter_real_fields, resolve_orm_path,
get_first_orm_bit, get_field_definition)

log = logging.getLogger(__name__)


CAN_UPDATE_FIELDS = get_version().split('.') >= ['1', '5']


class DatatableMixin(MultipleObjectMixin):
"""
Converts a view into an AJAX interface for obtaining records.
@@ -146,12 +143,13 @@ def apply_queryset_options(self, queryset):

for term in search_terms:
term_queries = [] # Queries generated to search all fields for this term
# Every concrete database lookup string in 'columns' is followed to its trailing field descriptor. For example, "subdivision__name" terminates in a CharField. The field type determines how it is probed for search.
# Every concrete database lookup string in 'columns' is followed to its trailing
# field descriptor. For example, "subdivision__name" terminates in a CharField.
# The field type determines how it is probed for search.
for column in db_fields:
column = get_field_definition(column)
for component_name in column.fields:
field_queries = [] # Queries generated to search this database field for the search term

field = resolve_orm_path(self.get_model(), component_name)
if field.choices:
# Query the database for the database value rather than display value
@@ -167,64 +165,20 @@ def apply_queryset_options(self, queryset):
for i in range(length):
if term.lower() in display_values[i]:
field_queries = [{component_name + '__iexact': database_values[i]}]

elif isinstance(field, tuple(FIELD_TYPES['text'])):
field_queries = [{component_name + '__icontains': term}]
elif isinstance(field, tuple(FIELD_TYPES['date'])):
try:
date_obj = dateutil.parser.parse(term)
except ValueError:
# This exception is theoretical, but it doesn't seem to raise.
pass
except TypeError:
# Failed conversions can lead to the parser adding ints to None.
pass
except OverflowError:
# Catches OverflowError: signed integer is greater than maximum
pass
else:
field_queries.append({component_name: date_obj})

# Add queries for more granular date field lookups
try:
numerical_value = int(term)
except ValueError:
pass
else:
if MINIMUM_YEAR < numerical_value < datetime.MAXYEAR - 1:
field_queries.append({component_name + '__year': numerical_value})
if 0 < numerical_value <= 12:
field_queries.append({component_name + '__month': numerical_value})
if 0 < numerical_value <= 31:
field_queries.append({component_name + '__day': numerical_value})
elif isinstance(field, tuple(FIELD_TYPES['boolean'])):
if term.lower() in ('true', 'yes'):
term = True
elif term.lower() in ('false', 'no'):
term = False
else:
continue

field_queries = [{component_name: term}]
elif isinstance(field, tuple(FIELD_TYPES['integer'])):
try:
field_queries = [{component_name: int(term)}]
except ValueError:
pass
elif isinstance(field, tuple(FIELD_TYPES['float'])):
try:
field_queries = [{component_name: float(term)}]
except ValueError:
pass
elif isinstance(field, tuple(FIELD_TYPES['ignored'])):
pass
else:
raise ValueError("Unhandled field type for %s (%r) in search." % (component_name, type(field)))

# print field_queries
for label, field_types in FIELD_TYPES.items():
if isinstance(field, tuple(field_types)):
# Queries generated to search this database field for the search term
handler = FIELD_HANDLERS.get(label)
if not handler:
raise ValueError("Unhandled field type %s. Please update "
"FIELD_HANDLERS." % label)
field_queries = handler(field, component_name, term)
break

# Append each field inspection for this term
term_queries.extend(map(lambda q: Q(**q), field_queries))
if field_queries:
term_queries.extend(map(lambda q: Q(**q), field_queries))
# Append the logical OR of all field inspections for this term
if len(term_queries):
queries.append(reduce(operator.or_, term_queries))
Empty file modified setup.py
100644 → 100755
Empty file.