├── README ├── noneurl ├── templatetags │ ├── __init__.py │ ├── url.pyc │ ├── __init__.pyc │ └── url.py ├── views.py ├── models.py ├── __init__.py └── tests.py └── setup.py /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /noneurl/templatetags/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /noneurl/views.py: -------------------------------------------------------------------------------- 1 | # Create your views here. 2 | -------------------------------------------------------------------------------- /noneurl/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /noneurl/__init__.py: -------------------------------------------------------------------------------- 1 | from django.template import add_to_builtins 2 | add_to_builtins('noneurl.templatetags.url') 3 | -------------------------------------------------------------------------------- /noneurl/templatetags/url.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/dango_noneurl/master/noneurl/templatetags/url.pyc -------------------------------------------------------------------------------- /noneurl/templatetags/__init__.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/dango_noneurl/master/noneurl/templatetags/__init__.pyc -------------------------------------------------------------------------------- /noneurl/tests.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file demonstrates writing tests using the unittest module. These will pass 3 | when you run "manage.py test". 4 | 5 | Replace this with more appropriate tests for your application. 6 | """ 7 | 8 | from django.test import TestCase 9 | 10 | 11 | class SimpleTest(TestCase): 12 | def test_basic_addition(self): 13 | """ 14 | Tests that 1 + 1 always equals 2. 15 | """ 16 | self.assertEqual(1 + 1, 2) 17 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | import os 3 | 4 | setup(name='noneurl', 5 | version='0.0.1', 6 | description="Custom django URL tag that strip out empty or null kwargs", 7 | long_description="", 8 | keywords='', 9 | author='Xavier Grangier', 10 | author_email='grangier@gmail.com', 11 | url='', 12 | license='GPL', 13 | packages=find_packages(), 14 | include_package_data=True, 15 | zip_safe=False, 16 | install_requires=[ 17 | 'django', 18 | ] 19 | ) 20 | -------------------------------------------------------------------------------- /noneurl/templatetags/url.py: -------------------------------------------------------------------------------- 1 | """Default tags used by the template system, available to all templates.""" 2 | from django.conf import settings 3 | from django.utils.encoding import smart_str 4 | from django.template.defaulttags import URLNode as _URLNode 5 | from django.template.defaulttags import TemplateSyntaxError, kwarg_re 6 | from django.template.base import Library 7 | 8 | register = Library() 9 | 10 | 11 | class URLNode(_URLNode): 12 | 13 | def render(self, context): 14 | from django.core.urlresolvers import reverse, NoReverseMatch 15 | args = [arg.resolve(context) for arg in self.args] 16 | kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context)) 17 | for k, v in self.kwargs.items()]) 18 | 19 | # remove empty of null kwargs 20 | for k, v in kwargs.items(): 21 | if not v or v == '': 22 | del kwargs[k] 23 | 24 | view_name = self.view_name 25 | if not self.legacy_view_name: 26 | view_name = view_name.resolve(context) 27 | 28 | # Try to look up the URL twice: once given the view name, and again 29 | # relative to what we guess is the "main" app. If they both fail, 30 | # re-raise the NoReverseMatch unless we're using the 31 | # {% url ... as var %} construct in which cause return nothing. 32 | url = '' 33 | try: 34 | url = reverse(view_name, args=args, kwargs=kwargs, current_app=context.current_app) 35 | except NoReverseMatch, e: 36 | if settings.SETTINGS_MODULE: 37 | project_name = settings.SETTINGS_MODULE.split('.')[0] 38 | try: 39 | url = reverse(project_name + '.' + view_name, 40 | args=args, kwargs=kwargs, 41 | current_app=context.current_app) 42 | except NoReverseMatch: 43 | if self.asvar is None: 44 | # Re-raise the original exception, not the one with 45 | # the path relative to the project. This makes a 46 | # better error message. 47 | raise e 48 | else: 49 | if self.asvar is None: 50 | raise e 51 | 52 | if self.asvar: 53 | context[self.asvar] = url 54 | return '' 55 | else: 56 | return url 57 | 58 | 59 | @register.tag 60 | def url(parser, token): 61 | """ 62 | Returns an absolute URL matching given view with its parameters. 63 | 64 | This is a way to define links that aren't tied to a particular URL 65 | configuration:: 66 | 67 | {% url path.to.some_view arg1 arg2 %} 68 | 69 | or 70 | 71 | {% url path.to.some_view name1=value1 name2=value2 %} 72 | 73 | The first argument is a path to a view. It can be an absolute python path 74 | or just ``app_name.view_name`` without the project name if the view is 75 | located inside the project. Other arguments are comma-separated values 76 | that will be filled in place of positional and keyword arguments in the 77 | URL. All arguments for the URL should be present. 78 | 79 | For example if you have a view ``app_name.client`` taking client's id and 80 | the corresponding line in a URLconf looks like this:: 81 | 82 | ('^client/(\d+)/$', 'app_name.client') 83 | 84 | and this app's URLconf is included into the project's URLconf under some 85 | path:: 86 | 87 | ('^clients/', include('project_name.app_name.urls')) 88 | 89 | then in a template you can create a link for a certain client like this:: 90 | 91 | {% url app_name.client client.id %} 92 | 93 | The URL will look like ``/clients/client/123/``. 94 | """ 95 | 96 | import warnings 97 | warnings.warn('The syntax for the url template tag is changing. Load the `url` tag from the `future` tag library to start using the new behavior.', 98 | category=DeprecationWarning) 99 | 100 | bits = token.split_contents() 101 | if len(bits) < 2: 102 | raise TemplateSyntaxError("'%s' takes at least one argument" 103 | " (path to a view)" % bits[0]) 104 | viewname = bits[1] 105 | args = [] 106 | kwargs = {} 107 | asvar = None 108 | bits = bits[2:] 109 | if len(bits) >= 2 and bits[-2] == 'as': 110 | asvar = bits[-1] 111 | bits = bits[:-2] 112 | 113 | # Backwards compatibility: check for the old comma separated format 114 | # {% url urlname arg1,arg2 %} 115 | # Initial check - that the first space separated bit has a comma in it 116 | if bits and ',' in bits[0]: 117 | check_old_format = True 118 | # In order to *really* be old format, there must be a comma 119 | # in *every* space separated bit, except the last. 120 | for bit in bits[1:-1]: 121 | if ',' not in bit: 122 | # No comma in this bit. Either the comma we found 123 | # in bit 1 was a false positive (e.g., comma in a string), 124 | # or there is a syntax problem with missing commas 125 | check_old_format = False 126 | break 127 | else: 128 | # No comma found - must be new format. 129 | check_old_format = False 130 | 131 | if check_old_format: 132 | # Confirm that this is old format by trying to parse the first 133 | # argument. An exception will be raised if the comma is 134 | # unexpected (i.e. outside of a static string). 135 | match = kwarg_re.match(bits[0]) 136 | if match: 137 | value = match.groups()[1] 138 | try: 139 | parser.compile_filter(value) 140 | except TemplateSyntaxError: 141 | bits = ''.join(bits).split(',') 142 | 143 | # Now all the bits are parsed into new format, 144 | # process them as template vars 145 | if len(bits): 146 | for bit in bits: 147 | match = kwarg_re.match(bit) 148 | if not match: 149 | raise TemplateSyntaxError("Malformed arguments to url tag") 150 | name, value = match.groups() 151 | if name: 152 | kwargs[name] = parser.compile_filter(value) 153 | else: 154 | args.append(parser.compile_filter(value)) 155 | 156 | 157 | 158 | return URLNode(viewname, args, kwargs, asvar, legacy_view_name=True) --------------------------------------------------------------------------------