Advertisement
beruic

Django query string template tags v2

Jun 7th, 2017
221
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.16 KB | None | 0 0
  1. import six
  2. from django import template
  3. import collections
  4.  
  5. from django.http import QueryDict
  6. from django.utils.encoding import force_text
  7.  
  8. """
  9. This paste is related to https://code.djangoproject.com/ticket/10941
  10. """
  11.  
  12. register = template.Library()
  13.  
  14.  
  15. @register.simple_tag
  16. def build_query(**kwargs):
  17.     """Build a query string"""
  18.     query_dict = QueryDict(mutable=True)
  19.  
  20.     for k, v in kwargs.items():
  21.         if isinstance(v, collections.Iterable) and not isinstance(v, six.string_types):
  22.             query_dict.setlist(k, v)
  23.         else:
  24.             query_dict[k] = v
  25.  
  26.     return query_dict.urlencode()
  27.  
  28.  
  29. @register.simple_tag(takes_context=True)
  30. def modify_query(context, **kwargs):
  31.     """
  32.    Modify the current query.
  33.  
  34.    The following actions are available:
  35.        - ``set``: Overwrites everything for a key. `None` clears the key completely. Cannot be used with append or remove.
  36.        - ``append``: Append to the list of a key. `None` is an invalid value.
  37.        - ``remove``: Remove all occurrences of a specific value for a key. `None` is an invalid value.
  38.  
  39.    Actions are applied by adding multiple key/value arguments defining the action and the query key in the key, and
  40.    the value(s) for the query in the value.
  41.  
  42.    Actions are applied in the order ``remove``, ``append``, ``set``.
  43.  
  44.    Iterables are supported for all actions. ``{% modify_query append_test=1 append_test=2 %}``, is equivalent to
  45.    ``{% modify_query append_test=my_list %}`` if ``my_list`` is the list defined by ``[1, 2]``.
  46.  
  47.    :param context:
  48.    :param kwargs: Key/value pairs defining modifications to the query. The key must have the form ``[action]_[key]``.
  49.    :return: A query string
  50.    """
  51.  
  52.     def validate_action(k):
  53.         if '_' not in k:
  54.             return False
  55.         if k.startswith('set_') or k.startswith('append_') or k.startswith('remove_'):
  56.             if k.count('_') == 1 and k.endswith('_'):
  57.                 return False
  58.             return True
  59.         return False
  60.  
  61.     invalid_keys = {key for key in kwargs if not validate_action(key)}
  62.     for k in kwargs:
  63.         if '_' not in k:
  64.             raise ValueError('The following keys does not define a valid action: {0}'.format(', '.join(invalid_keys)))
  65.  
  66.     set_actions = dict()
  67.     append_actions = dict()
  68.     remove_actions = dict()
  69.  
  70.     # Build action dicts
  71.     for k, value in kwargs.items():
  72.         action, key = k.split('_', 1)
  73.  
  74.         # Set
  75.         if action == 'set':
  76.             if key in set_actions:
  77.                 raise ValueError(
  78.                     'A key can only have one "set" action. The argument "{0}" has been defined more than once'.format(k)
  79.                 )
  80.             if key in append_actions or key in remove_actions:
  81.                 raise ValueError(
  82.                     'A key cannot have both a "set" action and "append" or "remove" actions.'
  83.                     ' The argument "{0}" violates this constraint since "append" and/or "remove" actions also exists'
  84.                     ' for the key "{1}".'.format(k, key)
  85.                 )
  86.  
  87.             value_list = []
  88.  
  89.             if value is not None:
  90.                 if isinstance(value, collections.Iterable) and not isinstance(value, six.string_types):
  91.                     for v in value:
  92.                         value_list.append(force_text(v))
  93.                 else:
  94.                     value_list.append(force_text(value))
  95.  
  96.             set_actions[key] = value_list
  97.  
  98.         # Append
  99.         if action == 'append':
  100.             if key in set_actions:
  101.                 raise ValueError(
  102.                     'A key cannot have both a "set" action and "append" or "remove" actions.'
  103.                     ' The argument "{0}" violates this constraint since a set action also exists'
  104.                     ' for the key "{1}".'.format(k, key)
  105.                 )
  106.             if value is None:
  107.                 raise ValueError(
  108.                     'None is not a permitted append action value.'
  109.                     ' The argument "{0}" has a None value.'.format(k)
  110.                 )
  111.  
  112.             if key not in append_actions:
  113.                 append_actions[key] = []
  114.  
  115.             if isinstance(value, collections.Iterable) and not isinstance(value, six.string_types):
  116.                 for value in value:
  117.                     append_actions[key].append(force_text(value))
  118.             else:
  119.                 append_actions[key].append(force_text(value))
  120.  
  121.         # Remove
  122.         if action == 'remove':
  123.             if key in set_actions:
  124.                 raise ValueError(
  125.                     'A key cannot have both a "set" action and "append" or "remove" actions.'
  126.                     ' The argument "{0}" violates this constraint since a set action also exists'
  127.                     ' for the key "{1}".'.format(k, key)
  128.                 )
  129.             if value is None:
  130.                 raise ValueError(
  131.                     'None is not a permitted remove action value.'
  132.                     ' The argument "{0}" has a None value.'.format(k)
  133.                 )
  134.  
  135.             if key not in remove_actions:
  136.                 remove_actions[key] = []
  137.  
  138.             if isinstance(value, collections.Iterable) and not isinstance(value, six.string_types):
  139.                 for value in value:
  140.                     remove_actions[key].append(force_text(value))
  141.             else:
  142.                 remove_actions[key].append(force_text(value))
  143.  
  144.     # Apply changes
  145.     query_dict = context.request.GET.copy()
  146.  
  147.     # Remove
  148.     for key, value_list in remove_actions.items():
  149.         # Only remove if the key is actually in the current query
  150.         if key in query_dict:
  151.             for value in value_list:
  152.                 # Remove as long as the value is present. A key can have the same value multiple times.
  153.                 while value in query_dict.getlist(key):
  154.                     query_dict.getlist(key).remove(value)
  155.  
  156.     # Append
  157.     for key, value_list in append_actions.items():
  158.         for value in value_list:
  159.             query_dict.appendlist(key, value)
  160.  
  161.     # Set
  162.     for key, value_list in set_actions.items():
  163.         query_dict.setlist(key, value_list)
  164.  
  165.     # Return
  166.     return query_dict.urlencode()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement