`__.
46 |
47 | Installation
48 | ------------
49 |
50 | Either checkout ``spurl`` from GitHub, or install using pip:
51 |
52 | .. code:: shell
53 |
54 | pip install django-spurl
55 |
56 | Add ``spurl`` to your ``INSTALLED_APPS``:
57 |
58 | .. code:: python
59 |
60 | INSTALLED_APPS = (
61 | ...
62 | 'spurl',
63 | )
64 |
65 | Finally, whenever you want to use Spurl in a template, you need to load
66 | its template library:
67 |
68 | .. code:: html+django
69 |
70 | {% load spurl %}
71 |
72 | Usage
73 | -----
74 |
75 | Spurl is **not** a replacement for Django's built-in ``{% url %}``
76 | template tag. It is a general-purpose toolkit for manipulating URL
77 | components in templates. You can use it alongside ``{% url %}`` if you
78 | like (see below).
79 |
80 | Spurl provides a single template tag, called (surprisingly enough),
81 | ``spurl``. You call it with a set of ``key=value`` keyword arguments,
82 | which are described fully below.
83 |
84 | To show some of the features of Spurl, we'll go over a couple of simple
85 | example use cases.
86 |
87 | Adding query parameters to URLs
88 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
89 |
90 | Say you have a list of external URLs in your database. When you create
91 | links to these URLs in a template, you need to add a
92 | ``referrer=mysite.com`` query parameter to each. The simple way to do
93 | this might be:
94 |
95 | .. code:: html+django
96 |
97 | {% for url, title in list_of_links %}
98 | {{ title }}
99 | {% endfor %}
100 |
101 | The problem here is that you don't know in advance if the URLs stored in
102 | your database *already* have query parameters. If they do, you'll
103 | generate malformed links like
104 | ``http://www.example.com?foo=bar?referrer=mysite.com``.
105 |
106 | Spurl can fix this. Because it knows about the components of a URL, it
107 | can add parameters onto an existing query, if there is one.
108 |
109 | .. code:: html+django
110 |
111 | {% for url, title in list_of_links %}
112 | {{ title }}
113 | {% endfor %}
114 |
115 | Note that **when you pass a literal string to Spurl, you have to wrap it
116 | in double quotes**. If you don't, Spurl will assume it's a variable name
117 | and try to look it up in the template's context.
118 |
119 | SSL-sensitive external URLs.
120 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
121 |
122 | Suppose your site needs to display a gallery of images, the URLs of
123 | which have come from some third-party web API. Additionally, imagine
124 | your site needs to run both in secure and non-secure mode - the same
125 | content is available at both ``https`` or ``http`` URLs (depending on
126 | whether a visitor is logged in, say). Some browsers will complain loudly
127 | (displaying "Mixed content warnings" to the user) if the page being
128 | displayed is ``https`` but some of the assets are ``http``. Spurl can
129 | fix this.
130 |
131 | .. code:: html+django
132 |
133 | {% for image_url in list_of_image_urls %}
134 |
135 | {% endfor %}
136 |
137 | This will take the image URL you supply and replace the scheme component
138 | (the ``http`` or ``https`` bit) with the correct version, depending on
139 | the return value of ``request.is_secure()``. Note that the above assumes
140 | you're using a ``RequestContext`` so that ``request`` is available in
141 | your template.
142 |
143 | Using alongside ``{% url %}``
144 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
145 |
146 | Notice that Spurl's functionality doesn't overlap with Django's built-in
147 | ``{% url %}`` tag. Spurl doesn't know about your urlconf, and doesn't do
148 | any URL reversing. In fact, Spurl is mostly useful for manipulating
149 | **external** URLs, rather than URLs on your own site. However, you can
150 | easily use Spurl with ``{% url %}`` if you need to. You just have to use
151 | the ``as`` keyword to put your reversed URL in a template variable, and
152 | then pass this to Spurl. As it's a relative path (rather than a full
153 | URL) you should pass it using the ``path`` argument. For example, say
154 | you want to append some query parameters to a URL on your site:
155 |
156 | .. code:: html+django
157 |
158 | {% url your_url_name as my_url %}
159 | Click here!
160 |
161 | There is another way to use Spurl with ``{% url %}``, see *Embedding
162 | template tags* below.
163 |
164 | Available arguments
165 | ~~~~~~~~~~~~~~~~~~~
166 |
167 | Below is a full list of arguments that Spurl understands.
168 |
169 | base
170 | ^^^^
171 |
172 | If you pass a ``base`` argument to Spurl, it will parse its contents and
173 | use this as the base URL upon which all other arguments will operate. If
174 | you *don't* pass a ``base`` argument, Spurl will generate a URL from
175 | scratch based on the components that you pass in separately.
176 |
177 | scheme
178 | ^^^^^^
179 |
180 | Set the scheme component of the URL. Example:
181 |
182 | .. code:: html+django
183 |
184 | {% spurl base="http://example.com" scheme="ftp" %}
185 |
186 | This will return ``ftp://example.com``
187 |
188 | See also: ``scheme_from``, below.
189 |
190 | host
191 | ^^^^
192 |
193 | Set the host component of the URL. Example:
194 |
195 | .. code:: html+django
196 |
197 | {% spurl base="http://example.com/some/path/" host="google.com" %}
198 |
199 | This will return ``http://google.com/some/path/``
200 |
201 | See also: ``host_from``, below.
202 |
203 | auth
204 | ^^^^
205 |
206 | Handle HTTP Basic authentication, username and password can be passed in
207 | URL. Example:
208 |
209 | .. code:: html+django
210 |
211 | {% spurl base="https://example.com" auth="user:pass" %}
212 |
213 | This will return ``https://user:pass@example.com``
214 |
215 | path
216 | ^^^^
217 |
218 | Set the path component of the URL. Example:
219 |
220 | .. code:: html+django
221 |
222 | {% spurl base="http://example.com/some/path/" path="/different/" %}
223 |
224 | This will return ``http://example.com/different/``
225 |
226 | See also: ``path_from``, below.
227 |
228 | add\_path
229 | ^^^^^^^^^
230 |
231 | Append a path component to the existing path. You can add multiple
232 | ``add_path`` calls, and the results of each will be combined. Example:
233 |
234 | .. code:: html+django
235 |
236 | {% spurl base=STATIC_URL add_path="javascript" add_path="lib" add_path="jquery.js" %}
237 |
238 | This will return ``http://cdn.example.com/javascript/lib/jquery.js``
239 | (assuming ``STATIC_URL`` is set to ``http://cdn.example.com``)
240 |
241 | See also: ``add_path_from``, below.
242 |
243 | fragment
244 | ^^^^^^^^
245 |
246 | Set the fragment component of the URL. Example:
247 |
248 | .. code:: html+django
249 |
250 | {% spurl base="http://example.com" fragment="myfragment" %}
251 |
252 | This will return ``http://example.com/#myfragment``
253 |
254 | See also: ``fragment_from``, below.
255 |
256 | port
257 | ^^^^
258 |
259 | Set the port component of the URL. Example:
260 |
261 | .. code:: html+django
262 |
263 | {% spurl base="http://example.com/some/path/" port="8080" %}
264 |
265 | This will return ``http://example.com:8080/some/path/``
266 |
267 | See also: ``port_from``, below.
268 |
269 | query
270 | ^^^^^
271 |
272 | Set the query component of the URL. Example:
273 |
274 | .. code:: html+django
275 |
276 | {% spurl base="http://example.com/" query="foo=bar&bar=baz" %}
277 |
278 | This will return ``http://example.com/?foo=bar&bar=baz``
279 |
280 | The ``query`` argument can also be passed a dictionary from your
281 | template's context.
282 |
283 | .. code:: python
284 |
285 | # views.py
286 | def my_view(request):
287 | my_query_params = {'foo': 'bar', 'bar': 'baz'}
288 | return render(request, 'path/to/template.html', {'my_query_params': my_query_params})
289 |
290 | .. code:: html+django
291 |
292 |
293 | {% spurl base="http://example.com/" query=my_query_params %}
294 |
295 | This will return ``http://example.com/?foo=bar&bar=baz``
296 |
297 | Finally, you can pass individual template variables to the query. To do
298 | this, Spurl uses Django's template system. For example:
299 |
300 | .. code:: html+django
301 |
302 | {% spurl base="http://example.com/" query="foo={{ variable_name }}" %}
303 |
304 | See also: ``query_from``, below.
305 |
306 | add\_query
307 | ^^^^^^^^^^
308 |
309 | Append a set of parameters to an existing query. If your base URL might
310 | already have a query component, this will merge the existing parameters
311 | with your new ones. Example:
312 |
313 | .. code:: html+django
314 |
315 | {% spurl base="http://example.com/?foo=bar" add_query="bar=baz" %}
316 |
317 | This will return ``http://example.com?foo=bar&bar=baz``
318 |
319 | You can add multiple ``add_query`` calls, and the results of each will
320 | be combined:
321 |
322 | .. code:: html+django
323 |
324 | {% spurl base="http://example.com/" add_query="foo=bar" add_query="bar=baz" %}
325 |
326 | This will return ``http://example.com?foo=bar&bar=baz``
327 |
328 | Like the ``query`` argument above, the values passed to ``add_query``
329 | can also be dictionaries, and they can contain Django template
330 | variables.
331 |
332 | See also: ``add_query_from``, below.
333 |
334 | set\_query
335 | ^^^^^^^^^^
336 |
337 | Appends a set of parameters to an existing query, overwriting existing
338 | parameters with the same name. Otherwise uses the exact same syntax as
339 | ``add_query``.
340 |
341 | See also: ``set_query_from``, below.
342 |
343 | toggle\_query
344 | ^^^^^^^^^^^^^
345 |
346 | Toggle the value of one or more query parameters between two possible
347 | values. Useful when reordering list views. Example:
348 |
349 | .. code:: html+django
350 |
351 | {% spurl base=request.get_full_path toggle_query="sort=ascending,descending" %}
352 |
353 | If the value of ``request.get_full_path()`` doesn't have a ``sort``
354 | parameter, one will be added with a value of ``ascending`` (the first
355 | item in the list is the default). If it already has a ``sort``
356 | parameter, and it is currently set to ``ascending``, it will be set to
357 | ``descending``. If it's already set to ``descending``, it will be set to
358 | ``ascending``.
359 |
360 | You can also specify the options as a dictionary, mapping the parameter
361 | name to a two-tuple containing the values to toggle. Example:
362 |
363 | .. code:: python
364 |
365 | # views.py
366 |
367 | SORT_PARAM = 'sort'
368 | ASCENDING = 'ascending'
369 | DESCENDING = 'descending'
370 |
371 | def my_view(request):
372 |
373 | if request.GET.get(SORT_PARAM, ASCENDING) == DESCENDING:
374 | object_list = MyModel.objects.order_by('-somefield')
375 | else:
376 | object_list = MyModel.objects.order_by('somefield')
377 |
378 | return render(request, 'path/to/template.html', {
379 | 'object_list': object_list,
380 | 'sort_params': {SORT_PARAM: (ASCENDING, DESCENDING)},
381 | })
382 |
383 | .. code:: html+django
384 |
385 |
386 | Reverse order
387 |
388 | remove\_query\_param
389 | ^^^^^^^^^^^^^^^^^^^^
390 |
391 | Remove a query parameter from an existing query:
392 |
393 | .. code:: html+django
394 |
395 | {% spurl base="http://example.com/?foo=bar&bar=baz" remove_query_param="foo" %}
396 |
397 | This will return ``http://example.com?bar=baz``
398 |
399 | Again, you can add multiple ``remove_query_param`` calls, and the
400 | results will be combined:
401 |
402 | .. code:: html+django
403 |
404 | {% spurl base="http://example.com/?foo=bar&bar=baz" remove_query_param="foo" remove_query_param="bar" %}
405 |
406 | This will return ``http://example.com/``
407 |
408 | You can also remove parameters with specific values:
409 |
410 | .. code:: html+django
411 |
412 | {% spurl base="http://example.com/?foo=bar&bar=baz&foo=baz" remove_query_param="foo" remove_query_param="foo=baz" %}
413 |
414 | This will return ``http://example.com/?bar=baz``
415 |
416 | Finally, you can pass individual template variables to the
417 | ``remove_query_param`` calls. To do this, Spurl uses Django's template
418 | system. For example:
419 |
420 | .. code:: html+django
421 |
422 | {% spurl base="http://example.com/?foo=bar&bar=baz" remove_query_param="{{ variable_name }}" %}
423 |
424 | secure
425 | ^^^^^^
426 |
427 | Control whether the generated URL starts with ``http`` or ``https``. The
428 | value of this argument can be a boolean (``True`` or ``False``), if
429 | you're using a context variable. If you're using a literal argument
430 | here, it must be a quoted string. The strings ``"True"`` or ``"on"``
431 | (case-insensitive) will be converted to ``True``, any other string will
432 | be converted to ``False``. Example:
433 |
434 | .. code:: html+django
435 |
436 | {% spurl base="http://example.com/" secure="True" %}
437 |
438 | This will return ``https://example.com/``
439 |
440 | autoescape
441 | ^^^^^^^^^^
442 |
443 | By default, Spurl will escape its output in the same way as Django's
444 | template system. For example, an ``&`` character in a URL will be
445 | rendered as ``&``. You can override this behaviour by passing an
446 | ``autoescape`` argument, which must be either a boolean (if passed from
447 | a template variable) or a string. The strings ``"True"`` or ``"on"``
448 | (case-insensitive) will be converted to ``True``, any other string will
449 | be converted to ``False``.
450 |
451 | Added bonus: ``_from`` parameters
452 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
453 |
454 | As well as those listed above, Spurl provides a family of parameters for
455 | *combining* URLs. Given a base URL to start with, you can copy a
456 | component from another URL. These arguments expect to be passed a full
457 | URL (or anything that can be understood by ``URLObject.parse``). This
458 | URL will be parsed, and then the component in question will be extracted
459 | and combined with the base URL.
460 |
461 | Below is a full list of the available ``_from`` methods. They have
462 | identical semantics to their counterparts above (except they expect a
463 | full URL, not just a URL component).
464 |
465 | - ``query_from``
466 | - ``add_query_from``
467 | - ``set_query_from``
468 | - ``scheme_from``
469 | - ``host_from``
470 | - ``path_from``
471 | - ``add_path_from``
472 | - ``fragment_from``
473 | - ``port_from``
474 |
475 | Example:
476 |
477 | .. code:: html+django
478 |
479 | {% spurl base="http://example.com/foo/bar/?foo=bar path_from="http://another.com/something/?bar=foo" %}
480 |
481 | This will return ``http://example.com/something/?foo=bar``
482 |
483 | Building a URL without displaying it
484 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
485 |
486 | Like Django's ``{% url %}`` tag, Spurl allows you to insert the
487 | generated URL into the template's context for later use. Example:
488 |
489 | .. code:: html+django
490 |
491 | {% spurl base="http://example.com" secure="True" as secure_url %}
492 | The secure version of the url is {{ secure_url }}
493 |
494 | Embedding template tags
495 | ~~~~~~~~~~~~~~~~~~~~~~~
496 |
497 | As mentioned above, Spurl uses Django's template system to individually
498 | parse any arguments which can be passed strings. This allows the use of
499 | syntax such as:
500 |
501 | .. code:: html+django
502 |
503 | {% spurl base="http://example.com" add_query="foo={{ bar }}" %}
504 |
505 | This works fine for variable and filters, but what if we want to use
506 | other template tags *inside* our Spurl tag? We can't nest ``{%`` and
507 | ``%}`` tokens inside each other, because Django's template parser would
508 | get very confused. Instead, we have to escape the inner set of tag
509 | markers with backslashes:
510 |
511 | .. code:: html+django
512 |
513 | {% spurl base="http://example.com" add_query="next={\% url home %\}" %}
514 |
515 | Note that any tags or filters loaded in your template are automatically
516 | available in the nested templates used to render each variable. This
517 | means we can do:
518 |
519 | .. code:: html+django
520 |
521 | {% load url from future %}
522 | {% spurl base="{\% url 'home' %\}" %}
523 |
524 | Be careful with your quotation marks! If you use double-quotes to
525 | surround the nested template, you have to use single quotes inside it.
526 |
527 | **Warning!** This functionality only exists to serve the most complex of
528 | use cases, and is extremely magical (and probably a bad idea). You may
529 | prefer to use:
530 |
531 | .. code:: html+django
532 |
533 | {% url "home" as my_url %}
534 | {% spurl base=my_url %}
535 |
536 | Development
537 | -----------
538 |
539 | To contribute, fork the repository, make your changes, add some tests,
540 | commit, push, and open a pull request.
541 |
542 | How to run the tests
543 | ~~~~~~~~~~~~~~~~~~~~
544 |
545 | Spurl is tested with `nose `__. Clone the
546 | repository, then run ``pip install -r requirements.txt`` to install nose
547 | and Django into your virtualenv. Then, simply type ``nosetests`` to find
548 | and run all the tests.
549 |
550 | (Un)license
551 | -----------
552 |
553 | This is free and unencumbered software released into the public domain.
554 |
555 | Anyone is free to copy, modify, publish, use, compile, sell, or
556 | distribute this software, either in source code form or as a compiled
557 | binary, for any purpose, commercial or non-commercial, and by any means.
558 |
559 | In jurisdictions that recognize copyright laws, the author or authors of
560 | this software dedicate any and all copyright interest in the software to
561 | the public domain. We make this dedication for the benefit of the public
562 | at large and to the detriment of our heirs and successors. We intend
563 | this dedication to be an overt act of relinquishment in perpetuity of
564 | all present and future rights to this software under copyright law.
565 |
566 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
567 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
568 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
569 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
570 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
571 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
572 | DEALINGS IN THE SOFTWARE.
573 |
574 | For more information, please refer to http://unlicense.org/
575 |
576 | Artwork credit
577 | --------------
578 |
579 | Superman ASCII art comes from http://ascii.co.uk/art/superman
580 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | django >= 1.4
2 | coverage
3 | nose
4 | six
5 | urlobject >= 2.0.0
6 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [metadata]
2 | name = django-spurl
3 | version = 0.6.8
4 | description = A Django template library for manipulating URLs.
5 | long_description = file: README.rst, CHANGES.rst
6 | long_description_content_type = text/x-rst
7 | author = Jamie Matthews
8 | author_email = jamie.matthews@jamesturk.net
9 | maintainer = Basil Shubin
10 | maintainer_email = basil.shubin@gmail.com
11 | url = https://github.com/j4mie/django-spurl/
12 | download_url = https://github.com/j4mie/django-spurl/zipball/master
13 | license = Public Domain
14 | classifiers =
15 | Development Status :: 5 - Production/Stable
16 | Environment :: Web Environment
17 | Intended Audience :: Developers
18 | License :: Public Domain
19 | Operating System :: OS Independent
20 | Programming Language :: Python
21 | Programming Language :: Python :: 3 :: Only
22 | Programming Language :: Python :: 3.6
23 | Programming Language :: Python :: 3.7
24 | Programming Language :: Python :: 3.8
25 | Programming Language :: Python :: 3.9
26 | Framework :: Django
27 | Framework :: Django :: 2.2
28 | Framework :: Django :: 3.0
29 | Framework :: Django :: 3.1
30 | Framework :: Django :: 3.2
31 |
32 | [options]
33 | zip_safe = False
34 | include_package_data = True
35 | packages = find:
36 | install_requires =
37 | urlobject>=2.4.0
38 | six
39 |
40 | [options.extras_require]
41 | develop =
42 | tox
43 | django
44 | nose
45 | test =
46 | coverage
47 | nose
48 |
49 | [bdist_wheel]
50 | # No longer universal (Python 3 only) but leaving this section in here will
51 | # trigger zest to build a wheel.
52 | universal = 0
53 |
54 | [flake8]
55 | # Some sane defaults for the code style checker flake8
56 | # black compatibility
57 | max-line-length = 88
58 | # E203 and W503 have edge cases handled by black
59 | extend-ignore = E203, W503
60 | exclude =
61 | .tox
62 | build
63 | dist
64 | .eggs
65 |
66 | [nosetests]
67 | verbosity = 1
68 | detailed-errors = 1
69 | with-coverage = 1
70 | cover-package = spurl
71 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | from setuptools import setup
3 |
4 | setup()
5 |
--------------------------------------------------------------------------------
/spurl/__init__.py:
--------------------------------------------------------------------------------
1 | __version__ = "0.6.7"
2 |
--------------------------------------------------------------------------------
/spurl/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j4mie/django-spurl/3a3f5a2dcad52c8fd48d4200bf3828ced86329b8/spurl/models.py
--------------------------------------------------------------------------------
/spurl/templatetags/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/j4mie/django-spurl/3a3f5a2dcad52c8fd48d4200bf3828ced86329b8/spurl/templatetags/__init__.py
--------------------------------------------------------------------------------
/spurl/templatetags/spurl.py:
--------------------------------------------------------------------------------
1 | import re
2 |
3 | import django
4 | from django.conf import settings
5 | from django.template import Library, Node, Origin, Template, TemplateSyntaxError
6 | from django.template.base import Lexer, Parser
7 | from django.template.defaulttags import kwarg_re
8 | from django.utils.encoding import smart_str
9 | from django.utils.html import escape
10 | from urlobject import URLObject
11 | from urlobject.query_string import QueryString
12 |
13 | register = Library()
14 |
15 | TRUE_RE = re.compile(r"^(true|on)$", flags=re.IGNORECASE)
16 |
17 |
18 | def convert_to_boolean(string_or_boolean):
19 | if isinstance(string_or_boolean, bool):
20 | return string_or_boolean
21 | if isinstance(string_or_boolean, str):
22 | return bool(TRUE_RE.match(string_or_boolean))
23 |
24 |
25 | class SpurlURLBuilder:
26 | def __init__(self, args, context, tags, filters):
27 | self.args = args
28 | self.context = context
29 | self.tags = tags
30 | self.filters = filters
31 | self.autoescape = self.context.autoescape
32 | self.url = URLObject()
33 |
34 | def build(self):
35 | for argument, value in self.args:
36 | self.handle_argument(argument, value)
37 |
38 | self.set_sensible_defaults()
39 |
40 | url = str(self.url)
41 |
42 | if self.autoescape:
43 | url = escape(url)
44 |
45 | return url
46 |
47 | def handle_argument(self, argument, value):
48 | argument = smart_str(argument, "ascii")
49 | handler_name = "handle_%s" % argument
50 | handler = getattr(self, handler_name, None)
51 |
52 | if handler is not None:
53 | value = value.resolve(self.context)
54 | handler(value)
55 |
56 | def handle_base(self, value):
57 | base = self.prepare_value(value)
58 | self.url = URLObject(base)
59 |
60 | def handle_auth(self, value):
61 | auth = self.prepare_value(value)
62 | self.url = self.url.with_auth(*auth.split(":", 1))
63 |
64 | def handle_secure(self, value):
65 | is_secure = convert_to_boolean(value)
66 | scheme = "https" if is_secure else "http"
67 | self.url = self.url.with_scheme(scheme)
68 |
69 | def handle_query(self, value):
70 | query = self.prepare_value(value)
71 | if isinstance(query, dict):
72 | query = QueryString().set_params(**query)
73 | self.url = self.url.with_query(QueryString(query))
74 |
75 | def handle_query_from(self, value):
76 | url = URLObject(value)
77 | self.url = self.url.with_query(url.query)
78 |
79 | def handle_add_query(self, value):
80 | query_to_add = self.prepare_value(value)
81 | if isinstance(query_to_add, str):
82 | query_to_add = QueryString(query_to_add).dict
83 | self.url = self.url.add_query_params(**query_to_add)
84 |
85 | def handle_add_query_from(self, value):
86 | url = URLObject(value)
87 | self.url = self.url.add_query_params(**url.query.dict)
88 |
89 | def handle_set_query(self, value):
90 | query_to_set = self.prepare_value(value)
91 | if isinstance(query_to_set, str):
92 | query_to_set = QueryString(query_to_set).dict
93 | self.url = self.url.set_query_params(**query_to_set)
94 |
95 | def handle_set_query_from(self, value):
96 | url = URLObject(value)
97 | self.url = self.url.set_query_params(**url.query.dict)
98 |
99 | def handle_remove_query_param(self, value):
100 | query_to_remove = self.prepare_value(value)
101 | if "=" in query_to_remove:
102 | k, v = query_to_remove.split("=", 1)
103 | self.url = self.url.del_query_param_value(k, v)
104 | else:
105 | self.url = self.url.del_query_param(query_to_remove)
106 |
107 | def handle_remove_query_params_except(self, value):
108 | params_to_keep = self.prepare_value(value).split(" ")
109 | params_to_remove = [pair[0] for pair in self.url.query_list if pair[0] not in params_to_keep]
110 | self.url = self.url.with_query(self.url.query.del_params(params_to_remove))
111 |
112 | def handle_toggle_query(self, value):
113 | query_to_toggle = self.prepare_value(value)
114 | if isinstance(query_to_toggle, str):
115 | query_to_toggle = QueryString(query_to_toggle).dict
116 | current_query = self.url.query.dict
117 | for key, value in list(query_to_toggle.items()):
118 | if isinstance(value, str):
119 | value = value.split(",")
120 | first, second = value
121 | if key in current_query and first == current_query[key]:
122 | self.url = self.url.set_query_param(key, second)
123 | else:
124 | self.url = self.url.set_query_param(key, first)
125 |
126 | def handle_scheme(self, value):
127 | self.url = self.url.with_scheme(value)
128 |
129 | def handle_scheme_from(self, value):
130 | url = URLObject(value)
131 | self.url = self.url.with_scheme(url.scheme)
132 |
133 | def handle_host(self, value):
134 | host = self.prepare_value(value)
135 | self.url = self.url.with_hostname(host)
136 |
137 | def handle_host_from(self, value):
138 | url = URLObject(value)
139 | self.url = self.url.with_hostname(url.hostname)
140 |
141 | def handle_path(self, value):
142 | path = self.prepare_value(value)
143 | self.url = self.url.with_path(path)
144 |
145 | def handle_path_from(self, value):
146 | url = URLObject(value)
147 | self.url = self.url.with_path(url.path)
148 |
149 | def handle_add_path(self, value):
150 | path_to_add = self.prepare_value(value)
151 | self.url = self.url.add_path(path_to_add)
152 |
153 | def handle_add_path_from(self, value):
154 | url = URLObject(value)
155 | path_to_add = url.path
156 | if path_to_add.startswith("/"):
157 | path_to_add = path_to_add[1:]
158 | self.url = self.url.add_path(path_to_add)
159 |
160 | def handle_fragment(self, value):
161 | fragment = self.prepare_value(value)
162 | self.url = self.url.with_fragment(fragment)
163 |
164 | def handle_fragment_from(self, value):
165 | url = URLObject(value)
166 | self.url = self.url.with_fragment(url.fragment)
167 |
168 | def handle_port(self, value):
169 | self.url = self.url.with_port(int(value))
170 |
171 | def handle_port_from(self, value):
172 | url = URLObject(value)
173 | self.url = self.url.with_port(url.port)
174 |
175 | def handle_autoescape(self, value):
176 | self.autoescape = convert_to_boolean(value)
177 |
178 | def set_sensible_defaults(self):
179 | if self.url.hostname and not self.url.scheme:
180 | self.url = self.url.with_scheme("http")
181 |
182 | def prepare_value(self, value):
183 | """Prepare a value by unescaping embedded template tags
184 | and rendering through Django's template system"""
185 | if isinstance(value, str):
186 | value = self.render_template(self.unescape_tags(value))
187 | return value
188 |
189 | def unescape_tags(self, template_string):
190 | r"""Spurl allows the use of templatetags inside templatetags, if
191 | the inner templatetags are escaped - {\% and %\}"""
192 | return template_string.replace(r"{\%", "{%").replace(r"%\}", "%}")
193 |
194 | def compile_string(self, template_string, origin, template_debug=False):
195 | """Re-implementation of django.template.base.compile_string
196 | that takes into account the tags and filter of the parser
197 | that rendered the parent template"""
198 | if template_debug is True:
199 | if django.VERSION < (1, 9):
200 | from django.template.debug import DebugLexer, DebugParser
201 |
202 | lexer_class, parser_class = DebugLexer, DebugParser
203 | else:
204 | from django.template.base import DebugLexer
205 |
206 | lexer_class, parser_class = DebugLexer, Parser
207 | else:
208 | lexer_class, parser_class = Lexer, Parser
209 | if django.VERSION < (1, 9):
210 | lexer = lexer_class(template_string, origin)
211 | else:
212 | lexer = lexer_class(template_string)
213 | parser = parser_class(lexer.tokenize())
214 |
215 | # Attach the tags and filters from the parent parser
216 | parser.tags = self.tags
217 | parser.filters = self.filters
218 |
219 | return parser.parse()
220 |
221 | def render_template(self, template_string):
222 | """Used to render an "inner" template, ie one which
223 | is passed as an argument to spurl"""
224 | original_autoescape = self.context.autoescape
225 | self.context.autoescape = False
226 |
227 | template = Template("")
228 | template_debug = getattr(
229 | settings, "TEMPLATE_DEBUG", template.engine.debug if hasattr(template, "engine") else False
230 | )
231 | if template_debug is True:
232 | origin = Origin(template_string)
233 | else:
234 | origin = None
235 |
236 | template.nodelist = self.compile_string(template_string, origin, template_debug)
237 |
238 | rendered = template.render(self.context)
239 | self.context.autoescape = original_autoescape
240 | return rendered
241 |
242 |
243 | class SpurlNode(Node):
244 | def __init__(self, args, tags, filters, asvar=None):
245 | self.args = args
246 | self.asvar = asvar
247 | self.tags = tags
248 | self.filters = filters
249 |
250 | def render(self, context):
251 | builder = SpurlURLBuilder(self.args, context, self.tags, self.filters)
252 | url = builder.build()
253 |
254 | if self.asvar:
255 | context[self.asvar] = url
256 | return ""
257 |
258 | return url
259 |
260 |
261 | @register.tag
262 | def spurl(parser, token):
263 | bits = token.split_contents()
264 | if len(bits) < 2:
265 | raise TemplateSyntaxError("'spurl' takes at least one argument")
266 |
267 | args = []
268 | asvar = None
269 | bits = bits[1:]
270 |
271 | if len(bits) >= 2 and bits[-2] == "as":
272 | asvar = bits[-1]
273 | bits = bits[:-2]
274 |
275 | for bit in bits:
276 | name, value = kwarg_re.match(bit).groups()
277 | if not (name and value):
278 | raise TemplateSyntaxError("Malformed arguments to spurl tag")
279 | args.append((name, parser.compile_filter(value)))
280 | return SpurlNode(args, parser.tags, parser.filters, asvar)
281 |
--------------------------------------------------------------------------------
/spurl/tests.py:
--------------------------------------------------------------------------------
1 | import django
2 | import nose
3 | from django.conf import settings
4 | from django.http import HttpResponse
5 | from django.template import Context, Template, TemplateSyntaxError
6 | from django.urls import path
7 |
8 | from spurl.templatetags.spurl import convert_to_boolean
9 |
10 | # This file acts as a urlconf
11 | urlpatterns = [path("test/", lambda r: HttpResponse("ok"), name="test")]
12 |
13 | # bootstrap django
14 | configure_kwargs = {
15 | "ROOT_URLCONF": "spurl.tests",
16 | "INSTALLED_APPS": ["spurl.tests"],
17 | }
18 | configure_kwargs["TEMPLATES"] = [
19 | {
20 | "BACKEND": "django.template.backends.django.DjangoTemplates",
21 | "OPTIONS": {
22 | "builtins": ["spurl.templatetags.spurl"],
23 | },
24 | }
25 | ]
26 | settings.configure(**configure_kwargs)
27 |
28 | if django.VERSION >= (1, 7):
29 | django.setup()
30 |
31 |
32 | def render(template_string, dictionary=None, autoescape=False):
33 | """
34 | Render a template from the supplied string, with optional context data.
35 |
36 | This differs from Django's normal template system in that autoescaping
37 | is disabled by default. This is simply to make the tests below easier
38 | to read and write. You can re-enable the default behavior by passing True
39 | as the value of the autoescape parameter
40 | """
41 | context = Context(dictionary, autoescape=autoescape)
42 | return Template(template_string).render(context)
43 |
44 |
45 | def test_convert_argument_value_to_boolean():
46 | assert convert_to_boolean(True) is True
47 | assert convert_to_boolean(False) is False
48 | assert convert_to_boolean("True") is True
49 | assert convert_to_boolean("true") is True
50 | assert convert_to_boolean("On") is True
51 | assert convert_to_boolean("on") is True
52 | assert convert_to_boolean("False") is False
53 | assert convert_to_boolean("false") is False
54 | assert convert_to_boolean("Off") is False
55 | assert convert_to_boolean("off") is False
56 | assert convert_to_boolean("randomstring") is False
57 |
58 |
59 | @nose.tools.raises(TemplateSyntaxError)
60 | def test_noargs_raises_exception():
61 | render("""{% spurl %}""")
62 |
63 |
64 | @nose.tools.raises(TemplateSyntaxError)
65 | def test_malformed_args_raises_exception():
66 | render("""{% spurl something %}""")
67 |
68 |
69 | def test_passthrough():
70 | template = """{% spurl base="http://www.google.com" %}"""
71 | rendered = render(template)
72 | assert rendered == "http://www.google.com"
73 |
74 |
75 | def test_url_in_variable():
76 | template = """{% spurl base=myurl %}"""
77 | data = {"myurl": "http://www.google.com"}
78 | rendered = render(template, data)
79 | assert rendered == "http://www.google.com"
80 |
81 |
82 | def test_make_secure():
83 | template = """{% spurl base="http://www.google.com" secure="True" %}"""
84 | rendered = render(template)
85 | assert rendered == "https://www.google.com"
86 |
87 |
88 | def test_make_secure_with_variable():
89 | template = """{% spurl base=myurl secure=is_secure %}"""
90 | data = {"myurl": "http://www.google.com", "is_secure": True}
91 | rendered = render(template, data)
92 | assert rendered == "https://www.google.com"
93 |
94 |
95 | def test_make_insecure():
96 | template = """{% spurl base="https://www.google.com" secure="False" %}"""
97 | rendered = render(template)
98 | assert rendered == "http://www.google.com"
99 |
100 |
101 | def test_make_insecure_with_variable():
102 | template = """{% spurl base=myurl secure=is_secure %}"""
103 | data = {"myurl": "https://www.google.com", "is_secure": False}
104 | rendered = render(template, data)
105 | assert rendered == "http://www.google.com"
106 |
107 |
108 | def test_set_query_from_string():
109 | template = """{% spurl base="http://www.google.com" query="foo=bar&bar=foo" %}"""
110 | rendered = render(template)
111 | assert rendered == "http://www.google.com?foo=bar&bar=foo"
112 |
113 |
114 | def test_set_query_from_string_with_variable():
115 | template = """{% spurl base=myurl query=myquery %}"""
116 | data = {"myurl": "http://www.google.com", "myquery": "foo=bar&bar=foo"}
117 | rendered = render(template, data)
118 | assert rendered == "http://www.google.com?foo=bar&bar=foo"
119 |
120 |
121 | def test_set_query_from_dict_with_variable():
122 | template = """{% spurl base=myurl query=myquery %}"""
123 | data = {
124 | "myurl": "http://www.google.com",
125 | "myquery": {"foo": "bar", "bar": "foo"},
126 | }
127 | rendered = render(template, data)
128 | if "http://www.google.com?" not in rendered:
129 | raise AssertionError
130 | assert "foo=bar" in rendered and "bar=foo" in rendered
131 |
132 |
133 | def test_set_query_from_template_variables():
134 | template = """{% spurl base=myurl query="foo={{ first_var }}&bar={{ second_var }}" %}"""
135 | data = {
136 | "myurl": "http://www.google.com",
137 | "first_var": "bar",
138 | "second_var": "foo",
139 | }
140 | rendered = render(template, data)
141 | if "http://www.google.com?" not in rendered:
142 | raise AssertionError
143 | assert "foo=bar" in rendered and "bar=foo" in rendered
144 |
145 |
146 | def test_set_query_from_template_variables_not_double_escaped():
147 | template = """{% spurl base="http://www.google.com" query="{{ query }}" %}"""
148 | data = {"query": "foo=bar&bar=foo"}
149 | rendered = render(template, data)
150 | assert rendered == "http://www.google.com?foo=bar&bar=foo"
151 |
152 |
153 | def test_set_query_removes_existing_query():
154 | template = """{% spurl base="http://www.google.com?something=somethingelse" query="foo=bar&bar=foo" %}"""
155 | rendered = render(template)
156 | assert rendered == "http://www.google.com?foo=bar&bar=foo"
157 |
158 |
159 | def test_query_from():
160 | template = """{% spurl base="http://www.google.com/" query_from=url %}"""
161 | data = {"url": "http://example.com/some/path/?foo=bar&bar=foo"}
162 | rendered = render(template, data)
163 | assert rendered == "http://www.google.com/?foo=bar&bar=foo"
164 |
165 |
166 | def test_add_to_query_from_string():
167 | template = """{% spurl base="http://www.google.com?something=somethingelse" add_query="foo=bar&bar=foo" %}"""
168 | rendered = render(template)
169 | if "http://www.google.com?something=somethingelse" not in rendered:
170 | raise AssertionError
171 | assert "&foo=bar" in rendered and "&bar=foo" in rendered
172 |
173 |
174 | def test_add_to_query_from_dict_with_variable():
175 | template = """{% spurl base=myurl add_query=myquery %}"""
176 | data = {
177 | "myurl": "http://www.google.com?something=somethingelse",
178 | "myquery": {"foo": "bar", "bar": "foo"},
179 | }
180 | rendered = render(template, data)
181 | if "http://www.google.com?something=somethingelse" not in rendered:
182 | raise AssertionError
183 | assert "&foo=bar" in rendered and "&bar=foo" in rendered
184 |
185 |
186 | def test_multiple_add_query():
187 | template = """{% spurl base="http://www.google.com/" add_query="foo=bar" add_query="bar=baz" %}"""
188 | rendered = render(template)
189 | assert rendered == "http://www.google.com/?foo=bar&bar=baz"
190 |
191 |
192 | def test_add_to_query_from_template_variables():
193 | template = """{% spurl base="http://www.google.com/?foo=bar" add_query="bar={{ var }}" %}"""
194 | data = {"var": "baz"}
195 | rendered = render(template, data)
196 | assert rendered == "http://www.google.com/?foo=bar&bar=baz"
197 |
198 |
199 | def test_add_query_from():
200 | template = """{% spurl base="http://www.google.com/?bla=bla&flub=flub" add_query_from=url %}"""
201 | data = {"url": "http://example.com/some/path/?foo=bar&bar=foo"}
202 | rendered = render(template, data)
203 | if "http://www.google.com/?bla=bla&flub=flub" not in rendered:
204 | raise AssertionError
205 | assert "&foo=bar" in rendered and "&bar=foo" in rendered
206 |
207 |
208 | def test_set_query_param_from_string():
209 | template = """{% spurl base="http://www.google.com?something=somethingelse" set_query="something=foo&somethingelse=bar" %}"""
210 | rendered = render(template)
211 | if "http://www.google.com?" not in rendered:
212 | raise AssertionError
213 | assert "somethingelse=bar" in rendered and "something=foo" in rendered
214 |
215 |
216 | def test_set_query_param_from_dict_with_variable():
217 | template = """{% spurl base=myurl set_query=myquery %}"""
218 | data = {
219 | "myurl": "http://www.google.com?something=somethingelse",
220 | "myquery": {"something": "foo", "somethingelse": "bar"},
221 | }
222 | rendered = render(template, data)
223 | if "http://www.google.com?" not in rendered:
224 | raise AssertionError
225 | assert "somethingelse=bar" in rendered and "something=foo" in rendered
226 |
227 |
228 | def test_toggle_query():
229 | template = """{% spurl base="http://www.google.com/?foo=bar" toggle_query="bar=first,second" %}"""
230 | rendered = render(template)
231 | assert rendered == "http://www.google.com/?foo=bar&bar=first"
232 |
233 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=first" toggle_query="bar=first,second" %}"""
234 | rendered = render(template)
235 | assert rendered == "http://www.google.com/?foo=bar&bar=second"
236 |
237 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=second" toggle_query="bar=first,second" %}"""
238 | rendered = render(template)
239 | assert rendered == "http://www.google.com/?foo=bar&bar=first"
240 |
241 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=first" toggle_query=to_toggle %}"""
242 | data = {"to_toggle": {"bar": ("first", "second")}}
243 | rendered = render(template, data)
244 | assert rendered == "http://www.google.com/?foo=bar&bar=second"
245 |
246 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=second" toggle_query=to_toggle %}"""
247 | data = {"to_toggle": {"bar": ("first", "second")}}
248 | rendered = render(template, data)
249 | assert rendered == "http://www.google.com/?foo=bar&bar=first"
250 |
251 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=javascript" toggle_query=to_toggle %}"""
252 | data = {"to_toggle": {"bar": ("java", "javascript")}}
253 | rendered = render(template, data)
254 | assert rendered == "http://www.google.com/?foo=bar&bar=java"
255 |
256 |
257 | def test_multiple_set_query():
258 | template = """{% spurl base="http://www.google.com/?foo=test" set_query="foo=bar" set_query="bar=baz" %}"""
259 | rendered = render(template)
260 | assert rendered == "http://www.google.com/?foo=bar&bar=baz"
261 |
262 |
263 | def test_set_query_param_from_template_variables():
264 | template = """{% spurl base="http://www.google.com/?foo=bar" set_query="foo={{ var }}" %}"""
265 | data = {"var": "baz"}
266 | rendered = render(template, data)
267 | assert rendered == "http://www.google.com/?foo=baz"
268 |
269 |
270 | def test_empty_parameters_preserved():
271 | template = """{% spurl base="http://www.google.com/?foo=bar" set_query="bar={{ emptyvar }}" %}"""
272 | data = {} # does not contain and "emptyvar" key
273 | rendered = render(template, data)
274 | assert rendered == "http://www.google.com/?foo=bar&bar="
275 |
276 |
277 | def test_none_values_are_removed_when_setting_query():
278 | template = """{% spurl base="http://www.google.com/?foo=bar" set_query="bar={{ nonevar|default_if_none:'' }}" %}"""
279 | data = {"nonevar": None}
280 | rendered = render(template, data)
281 | assert rendered == "http://www.google.com/?foo=bar&bar="
282 |
283 |
284 | def test_set_query_from():
285 | template = """{% spurl base="http://www.google.com?bla=bla&foo=something" set_query_from=url %}"""
286 | data = {"url": "http://example.com/some/path?foo=bar&bar=foo"}
287 | rendered = render(template, data)
288 | if "http://www.google.com?" not in rendered:
289 | raise AssertionError
290 | assert "bla=bla" in rendered and "foo=bar" in rendered and "bar=foo" in rendered
291 |
292 |
293 | def test_none_values_are_removed_when_adding_query():
294 | template = """{% spurl base="http://www.google.com/?foo=bar" add_query="bar={{ nonevar|default_if_none:'' }}" %}"""
295 | data = {"nonevar": None}
296 | rendered = render(template, data)
297 | assert rendered == "http://www.google.com/?foo=bar&bar="
298 |
299 |
300 | def test_remove_from_query():
301 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=baz" remove_query_param="foo" %}"""
302 | rendered = render(template)
303 | assert rendered == "http://www.google.com/?bar=baz"
304 |
305 |
306 | def test_remove_except_from_query():
307 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=baz&baz=foo" remove_query_params_except="baz" %}"""
308 | rendered = render(template)
309 | assert rendered == "http://www.google.com/?baz=foo"
310 |
311 |
312 | def test_remove_except_from_query_with_template_variable():
313 | template = (
314 | """{% spurl base="http://www.google.com/?foo=bar&bar=baz&baz=foo" remove_query_params_except="{{ baz }}" %}"""
315 | )
316 | data = {"baz": "baz"}
317 | rendered = render(template, data)
318 | assert rendered == "http://www.google.com/?baz=foo"
319 |
320 |
321 | def test_remove_from_query_with_value():
322 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=baz&bar=foo" remove_query_param="bar=foo" %}"""
323 | rendered = render(template)
324 | assert rendered == "http://www.google.com/?foo=bar&bar=baz"
325 |
326 |
327 | def test_remove_multiple_from_query_with_value():
328 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=baz&bar=foo&foo=baz" remove_query_param="bar=foo" remove_query_param="foo=baz" %}"""
329 | rendered = render(template)
330 | assert rendered == "http://www.google.com/?foo=bar&bar=baz"
331 |
332 |
333 | def test_remove_multiple_params():
334 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=baz" remove_query_param="foo" remove_query_param="bar" %}"""
335 | rendered = render(template)
336 | assert rendered == "http://www.google.com/"
337 |
338 |
339 | def test_remove_from_query_with_value_from_template_variable():
340 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=baz&bar=foo&foo=baz" remove_query_param="{{ foo }}={{ baz }}" remove_query_param="{{ bar }}={{ foo }}" %}"""
341 | data = {"foo": "foo", "bar": "bar", "baz": "baz"}
342 | rendered = render(template, data)
343 | assert rendered == "http://www.google.com/?foo=bar&bar=baz"
344 |
345 |
346 | def test_remove_param_from_template_variable():
347 | template = """{% spurl base="http://www.google.com/?foo=bar&bar=baz" remove_query_param="{{ foo }}" remove_query_param="{{ bar }}" %}"""
348 | data = {"foo": "foo", "bar": "bar"}
349 | rendered = render(template, data)
350 | assert rendered == "http://www.google.com/"
351 |
352 |
353 | def test_override_scheme():
354 | template = """{% spurl base="http://google.com" scheme="ftp" %}"""
355 | rendered = render(template)
356 | assert rendered == "ftp://google.com"
357 |
358 |
359 | def test_scheme_from():
360 | template = """{% spurl base="http://www.google.com/?bla=bla&foo=bar" scheme_from=url %}"""
361 | data = {"url": "https://example.com/some/path/?foo=bar&bar=foo"}
362 | rendered = render(template, data)
363 | assert rendered == "https://www.google.com/?bla=bla&foo=bar"
364 |
365 |
366 | def test_override_host():
367 | template = """{% spurl base="http://www.google.com/some/path/" host="www.example.com" %}"""
368 | rendered = render(template)
369 | assert rendered == "http://www.example.com/some/path/"
370 |
371 |
372 | def test_host_from():
373 | template = """{% spurl base="http://www.google.com/?bla=bla&foo=bar" host_from=url %}"""
374 | data = {"url": "https://example.com/some/path/?foo=bar&bar=foo"}
375 | rendered = render(template, data)
376 | assert rendered == "http://example.com/?bla=bla&foo=bar"
377 |
378 |
379 | def test_override_path():
380 | template = """{% spurl base="http://www.google.com/some/path/" path="/another/different/one/" %}"""
381 | rendered = render(template)
382 | assert rendered == "http://www.google.com/another/different/one/"
383 |
384 |
385 | def test_path_from():
386 | template = """{% spurl base="http://www.google.com/original/?bla=bla&foo=bar" path_from=url %}"""
387 | data = {"url": "https://example.com/some/path/?foo=bar&bar=foo"}
388 | rendered = render(template, data)
389 | assert rendered == "http://www.google.com/some/path/?bla=bla&foo=bar"
390 |
391 |
392 | def test_add_path():
393 | template = """{% spurl base="http://www.google.com/some/path/" add_path="another/" %}"""
394 | rendered = render(template)
395 | assert rendered == "http://www.google.com/some/path/another/"
396 |
397 |
398 | def test_multiple_add_path():
399 | template = """{% spurl base="http://www.google.com/" add_path="some" add_path="another/" %}"""
400 | rendered = render(template)
401 | assert rendered == "http://www.google.com/some/another/"
402 |
403 |
404 | def test_multiple_add_path_from_template_variables():
405 | """Usage example for building media urls"""
406 | template = """{% spurl base=STATIC_URL add_path="javascript" add_path="lib" add_path="jquery.js" %}"""
407 | data = {"STATIC_URL": "http://cdn.example.com"}
408 | rendered = render(template, data)
409 | assert rendered == "http://cdn.example.com/javascript/lib/jquery.js"
410 |
411 |
412 | def test_add_path_from():
413 | template = """{% spurl base="http://www.google.com/original/?bla=bla&foo=bar" add_path_from=url %}"""
414 | data = {"url": "https://example.com/some/path/?foo=bar&bar=foo"}
415 | rendered = render(template, data)
416 | assert rendered == "http://www.google.com/original/some/path/?bla=bla&foo=bar"
417 |
418 |
419 | def test_override_fragment():
420 | template = """{% spurl base="http://www.google.com/#somefragment" fragment="someotherfragment" %}"""
421 | rendered = render(template)
422 | assert rendered == "http://www.google.com/#someotherfragment"
423 |
424 |
425 | def test_fragment_from():
426 | template = """{% spurl base="http://www.google.com/?bla=bla&foo=bar#fragment" fragment_from=url %}"""
427 | data = {"url": "https://example.com/some/path/?foo=bar&bar=foo#newfragment"}
428 | rendered = render(template, data)
429 | assert rendered == "http://www.google.com/?bla=bla&foo=bar#newfragment"
430 |
431 |
432 | def test_override_port():
433 | template = """{% spurl base="http://www.google.com:80" port="8080" %}"""
434 | rendered = render(template)
435 | assert rendered == "http://www.google.com:8080"
436 |
437 |
438 | def test_port_from():
439 | template = """{% spurl base="http://www.google.com:8000/?bla=bla&foo=bar" port_from=url %}"""
440 | data = {"url": "https://example.com:8888/some/path/?foo=bar&bar=foo"}
441 | rendered = render(template, data)
442 | assert rendered == "http://www.google.com:8888/?bla=bla&foo=bar"
443 |
444 |
445 | def test_build_complete_url():
446 | template = (
447 | """{% spurl scheme="http" host="www.google.com" path="/some/path/" port="8080" fragment="somefragment" %}"""
448 | )
449 | rendered = render(template)
450 | assert rendered == "http://www.google.com:8080/some/path/#somefragment"
451 |
452 |
453 | def test_sensible_defaults():
454 | template = """{% spurl path="/some/path/" %}"""
455 | rendered = render(template)
456 | assert rendered == "/some/path/"
457 |
458 | template = """{% spurl path="/some/path/" host="www.google.com" %}"""
459 | rendered = render(template)
460 | assert rendered == "http://www.google.com/some/path/"
461 |
462 |
463 | def test_autoescaping():
464 | template = (
465 | """{% spurl base="http://www.google.com" query="a=b" add_query="c=d" add_query="e=f" fragment="frag" %}"""
466 | )
467 | rendered = render(template, autoescape=True) # Ordinarily, templates will be autoescaped by default
468 | assert rendered == "http://www.google.com?a=b&c=d&e=f#frag"
469 |
470 |
471 | def test_disable_autoescaping_with_parameter():
472 | template = """{% spurl base="http://www.google.com" query="a=b" add_query="c=d" autoescape="False" %}"""
473 | rendered = render(template, autoescape=True)
474 | assert rendered == "http://www.google.com?a=b&c=d"
475 |
476 |
477 | def test_url_as_template_variable():
478 | template = """{% spurl base="http://www.google.com" as foo %}The url is {{ foo }}"""
479 | rendered = render(template)
480 | assert rendered == "The url is http://www.google.com"
481 |
482 |
483 | def test_reversing_inside_spurl_tag():
484 | template = r"""{% spurl base="http://www.google.com/" path="{\% url 'test' %\}" %}"""
485 | if django.VERSION < (1, 9):
486 | template = r"""{% load url from future %}{% spurl base="http://www.google.com/" path="{\% url 'test' %\}" %}"""
487 | else:
488 | template = r"""{% spurl base="http://www.google.com/" path="{\% url 'test' %\}" %}"""
489 | rendered = render(template)
490 | assert rendered == "http://www.google.com/test/"
491 |
492 | if django.VERSION < (1, 9):
493 | template = (
494 | r"""{% load url from future %}{% spurl base="http://www.google.com/" query="next={\% url 'test' %\}" %}"""
495 | )
496 | else:
497 | template = r"""{% spurl base="http://www.google.com/" query="next={\% url 'test' %\}" %}"""
498 | rendered = render(template)
499 | assert rendered == "http://www.google.com/?next=/test/"
500 |
501 |
502 | def test_xzibit():
503 | template = r"""Yo dawg, the URL is: {% spurl base="http://www.google.com/" query="foo={\% spurl base='http://another.com' secure='true' %\}" %}"""
504 | rendered = render(template)
505 | assert rendered == "Yo dawg, the URL is: http://www.google.com/?foo=https://another.com"
506 |
507 |
508 | def test_auth_with_username_and_password():
509 | template = """{% spurl base=myurl auth=auth %}"""
510 | data = {"myurl": "https://www.google.com", "auth": "user:pass"}
511 | rendered = render(template, data)
512 | assert rendered == "https://user:pass@www.google.com"
513 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | distribute = False
3 | envlist =
4 | py{36,37,38,39}-dj{22,30,31,32}
5 | skip_missing_interpreters = True
6 |
7 | [travis]
8 | python =
9 | 3.6: py36
10 | 3.7: py37
11 | 3.8: py38
12 | 3.9: py39
13 |
14 | [testenv]
15 | usedevelop = True
16 | extras = test
17 | deps =
18 | dj22: Django>=2.2,<3.0
19 | dj30: Django>=3.0,<3.1
20 | dj31: Django>=3.1,<3.2
21 | dj32: Django>=3.2,<3.3
22 | commands = python setup.py nosetests
23 |
--------------------------------------------------------------------------------