Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python >= 3 compatible + small fix #24

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
34 changes: 28 additions & 6 deletions django_sorting/middleware.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,44 @@
try:
from django.utils.deprecation import MiddlewareMixin
except ImportError:
MiddlewareMixin = object


def get_field(self):
try:
field = self.REQUEST['sort']
except (KeyError, ValueError, TypeError):
get = self.GET
if 'sort' in get:
field = get['sort']
else:
post = self.POST
if 'sort' in post:
field = post['sort']
else:
field = ''
except AttributeError:
field = ''
return (self.direction == 'desc' and '-' or '') + field

def get_direction(self):
try:
return self.REQUEST['dir']
except (KeyError, ValueError, TypeError):
get = self.GET
if 'dir' in get:
return get['dir']
else:
post = self.POST
if 'dir' in post:
return post['sort']
else:
return 'desc'
except AttributeError:
return 'desc'

class SortingMiddleware(object):
class SortingMiddleware(MiddlewareMixin):
"""
Inserts a variable representing the field (with direction of sorting)
onto the request object if it exists in either **GET** or **POST**
portions of the request.
"""
def process_request(self, request):
request.__class__.field = property(get_field)
request.__class__.direction = property(get_direction)
request.__class__.direction = property(get_direction)
82 changes: 54 additions & 28 deletions django_sorting/templatetags/sorting_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,34 +6,33 @@

DEFAULT_SORT_UP = getattr(settings, 'DEFAULT_SORT_UP' , '↑')
DEFAULT_SORT_DOWN = getattr(settings, 'DEFAULT_SORT_DOWN' , '↓')
INVALID_FIELD_RAISES_404 = getattr(settings,
INVALID_FIELD_RAISES_404 = getattr(settings,
'SORTING_INVALID_FIELD_RAISES_404' , False)

sort_directions = {
'asc': {'icon':DEFAULT_SORT_UP, 'inverse': 'desc'},
'desc': {'icon':DEFAULT_SORT_DOWN, 'inverse': 'asc'},
'': {'icon':DEFAULT_SORT_DOWN, 'inverse': 'asc'},
'asc': {'icon':DEFAULT_SORT_UP, 'inverse': 'desc'},
'desc': {'icon':DEFAULT_SORT_DOWN, 'inverse': 'asc'},
'': {'icon':DEFAULT_SORT_DOWN, 'inverse': 'asc'},
}

def anchor(parser, token):
"""
Parses a tag that's supposed to be in this format: {% anchor field title %}
"""
bits = [b.strip('"\'') for b in token.split_contents()]
if len(bits) < 2:
raise TemplateSyntaxError, "anchor tag takes at least 1 argument"
Parses a tag that's supposed to be in this format: {% anchor field title %}
"""
try:
title = bits[2]

tag_name, field, title = token.split_contents()
except IndexError:
title = bits[1].capitalize()
return SortAnchorNode(bits[1].strip(), title.strip())

tag_name, field = token.split_contents()
title = field.capitalize()
return SortAnchorNode(field, title)


class SortAnchorNode(template.Node):
"""
Renders an <a> HTML tag with a link which href attribute
Renders an <a> HTML tag with a link which href attribute
includes the field on which we sort and the direction.
and adds an up or down arrow if the field is the one
and adds an up or down arrow if the field is the one
currently being sorted on.

Eg.
Expand All @@ -42,10 +41,21 @@ class SortAnchorNode(template.Node):

"""
def __init__(self, field, title):
self.field = field
self.title = title
self.field = template.Variable(field)
self.title = template.Variable(title)

def render(self, context):
try:
self.rendered_field = self.field.resolve(context)
except template.VariableDoesNotExist:
self.rendered_field = str(self.field)
try:
self.rendered_title = self.title.resolve(context)
except template.VariableDoesNotExist:
self.rendered_title = str(self.title)
except AttributeError:
self.rendered_title = str(self.title)

request = context['request']
getvars = request.GET.copy()
if 'sort' in getvars:
Expand All @@ -58,7 +68,7 @@ def render(self, context):
del getvars['dir']
else:
sortdir = ''
if sortby == self.field:
if sortby == self.rendered_field:
getvars['dir'] = sort_directions[sortdir]['inverse']
icon = sort_directions[sortdir]['icon']
else:
Expand All @@ -67,20 +77,34 @@ def render(self, context):
urlappend = "&%s" % getvars.urlencode()
else:
urlappend = ''

if icon:
title = "%s %s" % (self.title, icon)
title = "%s %s" % (self.rendered_title, icon)
else:
title = self.title
title = self.rendered_title

url = '%s?sort=%s%s' % (request.path, self.rendered_field, urlappend)
return '<a href="%s" title="%s">%s</a>' % (url, self.rendered_title, title)

url = '%s?sort=%s%s' % (request.path, self.field, urlappend)
return '<a href="%s" title="%s">%s</a>' % (url, self.title, title)


def autosort(parser, token):
bits = [b.strip('"\'') for b in token.split_contents()]
if len(bits) != 2:
raise TemplateSyntaxError, "autosort tag takes exactly one argument"
return SortedDataNode(bits[1])
bits = token.split_contents()
if len(bits) not in (2, 4):
raise template.TemplateSyntaxError("autosort tag takes exactly one argument")
try:
if bits[2] != 'as':
raise template.TemplateSyntaxError(
"Context variable assignment must take the form of {%% %s"
" queryset as context_var_name %%}" % bits[0]
)
except IndexError:
pass
try:
return SortedDataNode(bits[1], bits[-1])
except IndexError:
return SortedDataNode(bits[1])


class SortedDataNode(template.Node):
"""
Expand All @@ -91,7 +115,10 @@ def __init__(self, queryset_var, context_var=None):
self.context_var = context_var

def render(self, context):
key = self.queryset_var.var
if self.context_var is None:
key = self.queryset_var.var
else:
key = self.context_var
value = self.queryset_var.resolve(context)
order_by = context['request'].field
if len(order_by) > 1:
Expand All @@ -109,4 +136,3 @@ def render(self, context):

anchor = register.tag(anchor)
autosort = register.tag(autosort)