def test_httprequest_repr(self): request = HttpRequest() request.path = '/somepath/' request.method = 'GET' request.GET = {'get-key': 'get-value'} request.POST = {'post-key': 'post-value'} request.COOKIES = {'post-key': 'post-value'} request.META = {'post-key': 'post-value'} self.assertEqual(repr(request), str_prefix("<HttpRequest: GET '/somepath/'>")) self.assertEqual( build_request_repr(request), str_prefix( "<HttpRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>" )) self.assertEqual( build_request_repr(request, path_override='/otherpath/', GET_override={'a': 'b'}, POST_override={'c': 'd'}, COOKIES_override={'e': 'f'}, META_override={'g': 'h'}), str_prefix( "<HttpRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>" ))
def test_httprequest_repr(self): request = HttpRequest() request.path = "/somepath/" request.GET = {"get-key": "get-value"} request.POST = {"post-key": "post-value"} request.COOKIES = {"post-key": "post-value"} request.META = {"post-key": "post-value"} self.assertEqual( repr(request), str_prefix( "<HttpRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>" ), ) self.assertEqual(build_request_repr(request), repr(request)) self.assertEqual( build_request_repr( request, path_override="/otherpath/", GET_override={"a": "b"}, POST_override={"c": "d"}, COOKIES_override={"e": "f"}, META_override={"g": "h"}, ), str_prefix( "<HttpRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>" ), )
def test_unknown_error(self): "An assertion is raised if the field doesn't contain the provided error" post_data = { "text": "Hello World", "email": "not an email address", "value": 37, "single": "b", "multi": ("b", "c", "e"), } response = self.client.post("/test_client/form_view/", post_data) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "Invalid POST Template") try: self.assertFormError(response, "form", "email", "Some error.") except AssertionError as e: self.assertIn( str_prefix( "The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [%(_)s'Enter a valid email address.'])" ), str(e), ) try: self.assertFormError(response, "form", "email", "Some error.", msg_prefix="abc") except AssertionError as e: self.assertIn( str_prefix( "abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [%(_)s'Enter a valid email address.'])" ), str(e), )
def test_wsgirequest_repr(self): request = WSGIRequest({"PATH_INFO": "/somepath/", "REQUEST_METHOD": "get", "wsgi.input": BytesIO(b"")}) request.GET = {"get-key": "get-value"} request.POST = {"post-key": "post-value"} request.COOKIES = {"post-key": "post-value"} request.META = {"post-key": "post-value"} self.assertEqual( repr(request), str_prefix( "<WSGIRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>" ), ) self.assertEqual(build_request_repr(request), repr(request)) self.assertEqual( build_request_repr( request, path_override="/otherpath/", GET_override={"a": "b"}, POST_override={"c": "d"}, COOKIES_override={"e": "f"}, META_override={"g": "h"}, ), str_prefix( "<WSGIRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>" ), )
def test_wsgirequest_repr(self): request = WSGIRequest({ 'PATH_INFO': '/somepath/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'') }) request.GET = {'get-key': 'get-value'} request.POST = {'post-key': 'post-value'} request.COOKIES = {'post-key': 'post-value'} request.META = {'post-key': 'post-value'} self.assertEqual( repr(request), str_prefix( "<WSGIRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>" )) self.assertEqual(build_request_repr(request), repr(request)) self.assertEqual( build_request_repr(request, path_override='/otherpath/', GET_override={'a': 'b'}, POST_override={'c': 'd'}, COOKIES_override={'e': 'f'}, META_override={'g': 'h'}), str_prefix( "<WSGIRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>" ))
def test_message_dict(self): v = ValidationError({'first': ['First Problem']}) self.assertEqual(str(v), str_prefix("{%(_)s'first': [%(_)s'First Problem']}")) self.assertEqual( repr(v), str_prefix( "ValidationError({%(_)s'first': [%(_)s'First Problem']})"))
def test_wsgirequest_repr(self): request = WSGIRequest({'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) self.assertEqual(repr(request), str_prefix("<WSGIRequest: GET '/'>")) request = WSGIRequest({'PATH_INFO': '/somepath/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) request.GET = {'get-key': 'get-value'} request.POST = {'post-key': 'post-value'} request.COOKIES = {'post-key': 'post-value'} request.META = {'post-key': 'post-value'} self.assertEqual(repr(request), str_prefix("<WSGIRequest: GET '/somepath/'>"))
def test_httprequest_repr_invalid_method_and_path(self): request = HttpRequest() self.assertEqual(repr(request), str_prefix("<HttpRequest>")) request = HttpRequest() request.method = "GET" self.assertEqual(repr(request), str_prefix("<HttpRequest>")) request = HttpRequest() request.path = "" self.assertEqual(repr(request), str_prefix("<HttpRequest>"))
def test_wsgirequest_repr(self): request = WSGIRequest({"REQUEST_METHOD": "get", "wsgi.input": BytesIO(b"")}) self.assertEqual(repr(request), str_prefix("<WSGIRequest: GET '/'>")) request = WSGIRequest({"PATH_INFO": "/somepath/", "REQUEST_METHOD": "get", "wsgi.input": BytesIO(b"")}) request.GET = {"get-key": "get-value"} request.POST = {"post-key": "post-value"} request.COOKIES = {"post-key": "post-value"} request.META = {"post-key": "post-value"} self.assertEqual(repr(request), str_prefix("<WSGIRequest: GET '/somepath/'>"))
def test_message_list(self): v = ValidationError(['First Problem', 'Second Problem']) self.assertEqual( str(v), str_prefix("[%(_)s'First Problem', %(_)s'Second Problem']")) self.assertEqual( repr(v), str_prefix( "ValidationError([%(_)s'First Problem', %(_)s'Second Problem'])" ))
def test_wsgirequest_repr(self): request = WSGIRequest({'PATH_INFO': '/somepath/', 'REQUEST_METHOD': 'get', 'wsgi.input': BytesIO(b'')}) request.GET = {'get-key': 'get-value'} request.POST = {'post-key': 'post-value'} request.COOKIES = {'post-key': 'post-value'} request.META = {'post-key': 'post-value'} self.assertEqual(repr(request), str_prefix("<WSGIRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>")) self.assertEqual(build_request_repr(request), repr(request)) self.assertEqual(build_request_repr(request, path_override='/otherpath/', GET_override={'a': 'b'}, POST_override={'c': 'd'}, COOKIES_override={'e': 'f'}, META_override={'g': 'h'}), str_prefix("<WSGIRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>"))
def test_httprequest_repr(self): request = HttpRequest() request.path = '/somepath/' request.GET = {'get-key': 'get-value'} request.POST = {'post-key': 'post-value'} request.COOKIES = {'post-key': 'post-value'} request.META = {'post-key': 'post-value'} self.assertEqual(repr(request), str_prefix("<HttpRequest\npath:/somepath/,\nGET:{%(_)s'get-key': %(_)s'get-value'},\nPOST:{%(_)s'post-key': %(_)s'post-value'},\nCOOKIES:{%(_)s'post-key': %(_)s'post-value'},\nMETA:{%(_)s'post-key': %(_)s'post-value'}>")) self.assertEqual(build_request_repr(request), repr(request)) self.assertEqual(build_request_repr(request, path_override='/otherpath/', GET_override={'a': 'b'}, POST_override={'c': 'd'}, COOKIES_override={'e': 'f'}, META_override={'g': 'h'}), str_prefix("<HttpRequest\npath:/otherpath/,\nGET:{%(_)s'a': %(_)s'b'},\nPOST:{%(_)s'c': %(_)s'd'},\nCOOKIES:{%(_)s'e': %(_)s'f'},\nMETA:{%(_)s'g': %(_)s'h'}>"))
def test_basic_distinct_on(self): """QuerySet.distinct('field', ...) works""" # (qset, expected) tuples qsets = ( (Staff.objects.distinct().order_by("name"), ["<Staff: p1>", "<Staff: p1>", "<Staff: p2>", "<Staff: p3>"]), (Staff.objects.distinct("name").order_by("name"), ["<Staff: p1>", "<Staff: p2>", "<Staff: p3>"]), (Staff.objects.distinct("organisation").order_by("organisation", "name"), ["<Staff: p1>", "<Staff: p1>"]), ( Staff.objects.distinct("name", "organisation").order_by("name", "organisation"), ["<Staff: p1>", "<Staff: p1>", "<Staff: p2>", "<Staff: p3>"], ), ( Celebrity.objects.filter(fan__in=[self.fan1, self.fan2, self.fan3]).distinct("name").order_by("name"), ["<Celebrity: c1>", "<Celebrity: c2>"], ), # Does combining querysets work? ( ( Celebrity.objects.filter(fan__in=[self.fan1, self.fan2]).distinct("name").order_by("name") | Celebrity.objects.filter(fan__in=[self.fan3]).distinct("name").order_by("name") ), ["<Celebrity: c1>", "<Celebrity: c2>"], ), (StaffTag.objects.distinct("staff", "tag"), ["<StaffTag: t1 -> p1>"]), (Tag.objects.order_by("parent__pk", "pk").distinct("parent"), ["<Tag: t2>", "<Tag: t4>", "<Tag: t1>"]), ( StaffTag.objects.select_related("staff").distinct("staff__name").order_by("staff__name"), ["<StaffTag: t1 -> p1>"], ), # Fetch the alphabetically first coworker for each worker ( (Staff.objects.distinct("id").order_by("id", "coworkers__name").values_list("id", "coworkers__name")), [str_prefix("(1, %(_)s'p2')"), str_prefix("(2, %(_)s'p1')"), str_prefix("(3, %(_)s'p1')"), "(4, None)"], ), ) for qset, expected in qsets: self.assertQuerysetEqual(qset, expected) self.assertEqual(qset.count(), len(expected)) # Combining queries with different distinct_fields is not allowed. base_qs = Celebrity.objects.all() self.assertRaisesMessage( AssertionError, "Cannot combine queries with different distinct fields.", lambda: (base_qs.distinct("id") & base_qs.distinct("name")), ) # Test join unreffing c1 = Celebrity.objects.distinct("greatest_fan__id", "greatest_fan__fan_of") self.assertIn("OUTER JOIN", str(c1.query)) c2 = c1.distinct("pk") self.assertNotIn("OUTER JOIN", str(c2.query))
def test_no_interpolation_on_sqlite(self): # Regression for #17158 # This shouldn't raise an exception query = "SELECT strftime('%Y', 'now');" connection.cursor().execute(query) self.assertEqual(connection.queries[-1]['sql'], str_prefix("QUERY = %(_)s\"SELECT strftime('%%Y', 'now');\" - PARAMS = ()"))
def test_httprequest_repr(self): request = HttpRequest() request.path = "/somepath/" request.method = "GET" request.GET = {"get-key": "get-value"} request.POST = {"post-key": "post-value"} request.COOKIES = {"post-key": "post-value"} request.META = {"post-key": "post-value"} self.assertEqual(repr(request), str_prefix("<HttpRequest: GET '/somepath/'>"))
def test_nonexistent_field(self): class SongAdmin(admin.ModelAdmin): readonly_fields = ("title", "nonexistent") self.assertRaisesMessage(ImproperlyConfigured, str_prefix("SongAdmin.readonly_fields[1], %(_)s'nonexistent' is not a callable " "or an attribute of 'SongAdmin' or found in the model 'Song'."), SongAdmin.validate, Song)
def test_load_backend_invalid_name(self): msg = str_prefix( "'foo' isn't an available database backend.\n" "Try using 'django.db.backends.XXX', where XXX is one of:\n" " %(_)s'mysql', %(_)s'oracle', %(_)s'postgresql_psycopg2', %(_)s'sqlite3'\n" "Error was: No module named %%s" ) % "foo.base" if six.PY2 else "'foo'" with self.assertRaisesMessage(ImproperlyConfigured, msg): load_backend('foo')
def test_httprequest_repr(self): request = HttpRequest() request.path = '/somepath/' request.method = 'GET' request.GET = {'get-key': 'get-value'} request.POST = {'post-key': 'post-value'} request.COOKIES = {'post-key': 'post-value'} request.META = {'post-key': 'post-value'} self.assertEqual(repr(request), str_prefix("<HttpRequest: GET '/somepath/'>"))
def test_nonexistent_field_on_inline(self): class CityInline(admin.TabularInline): model = City readonly_fields = ['i_dont_exist'] # Missing attribute self.assertRaisesMessage(ImproperlyConfigured, str_prefix("CityInline.readonly_fields[0], %(_)s'i_dont_exist' is not a callable " "or an attribute of 'CityInline' or found in the model 'City'."), CityInline.validate, City)
def test_nonexistent_field(self): class SongAdmin(admin.ModelAdmin): readonly_fields = ("title", "nonexistent") with warnings.catch_warnings(record=True): warnings.filterwarnings('ignore', module='django.contrib.admin.options') self.assertRaisesMessage(ImproperlyConfigured, str_prefix("SongAdmin.readonly_fields[1], %(_)s'nonexistent' is not a callable " "or an attribute of 'SongAdmin' or found in the model 'Song'."), SongAdmin.validate, Song)
def test_nonexistent_field_on_inline(self): class CityInline(admin.TabularInline): model = City readonly_fields = ['i_dont_exist'] # Missing attribute with warnings.catch_warnings(record=True): warnings.filterwarnings('ignore', module='django.contrib.admin.options') self.assertRaisesMessage(ImproperlyConfigured, str_prefix("CityInline.readonly_fields[0], %(_)s'i_dont_exist' is not a callable " "or an attribute of 'CityInline' or found in the model 'City'."), CityInline.validate, City)
def test_unknown_error(self): "An assertion is raised if the field doesn't contain the provided error" post_data = { 'text': 'Hello World', 'email': 'not an email address', 'value': 37, 'single': 'b', 'multi': ('b','c','e') } response = self.client.post('/test_client/form_view/', post_data) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "Invalid POST Template") try: self.assertFormError(response, 'form', 'email', 'Some error.') except AssertionError as e: self.assertIn(str_prefix("The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [%(_)s'Enter a valid e-mail address.'])"), str(e)) try: self.assertFormError(response, 'form', 'email', 'Some error.', msg_prefix='abc') except AssertionError as e: self.assertIn(str_prefix("abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [%(_)s'Enter a valid e-mail address.'])"), str(e))
def test_unknown_error(self): "An assertion is raised if the field doesn't contain the provided error" post_data = { 'text': 'Hello World', 'email': 'not an email address', 'value': 37, 'single': 'b', 'multi': ('b','c','e') } response = self.client.post('/test_client/form_view/', post_data) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "Invalid POST Template") try: self.assertFormError(response, 'form', 'email', 'Some error.') except AssertionError as e: self.assertIn(str_prefix("The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [%(_)s'Enter a valid email address.'])"), str(e)) try: self.assertFormError(response, 'form', 'email', 'Some error.', msg_prefix='abc') except AssertionError as e: self.assertIn(str_prefix("abc: The field 'email' on form 'form' in context 0 does not contain the error 'Some error.' (actual errors: [%(_)s'Enter a valid email address.'])"), str(e))
def test_list_display_validation(self): class ValidationTestModelAdmin(ModelAdmin): list_display = 10 six.assertRaisesRegex( self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display' must be a list or tuple.", validate, ValidationTestModelAdmin, ValidationTestModel, ) class ValidationTestModelAdmin(ModelAdmin): list_display = ('non_existent_field', ) six.assertRaisesRegex( self, ImproperlyConfigured, str_prefix( "ValidationTestModelAdmin.list_display\[0\], %(_)s'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'." ), validate, ValidationTestModelAdmin, ValidationTestModel, ) class ValidationTestModelAdmin(ModelAdmin): list_display = ('users', ) six.assertRaisesRegex( self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display\[0\]', 'users' is a ManyToManyField which is not supported.", validate, ValidationTestModelAdmin, ValidationTestModel, ) def a_callable(obj): pass class ValidationTestModelAdmin(ModelAdmin): def a_method(self, obj): pass list_display = ('name', 'decade_published_in', 'a_method', a_callable) validate(ValidationTestModelAdmin, ValidationTestModel)
def test_list_display_validation(self): class ValidationTestModelAdmin(ModelAdmin): list_display = 10 six.assertRaisesRegex( self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display' must be a list or tuple.", ValidationTestModelAdmin.validate, ValidationTestModel, ) class ValidationTestModelAdmin(ModelAdmin): list_display = ("non_existent_field",) six.assertRaisesRegex( self, ImproperlyConfigured, str_prefix( "ValidationTestModelAdmin.list_display\[0\], %(_)s'non_existent_field' is not a callable or an attribute of 'ValidationTestModelAdmin' or found in the model 'ValidationTestModel'." ), ValidationTestModelAdmin.validate, ValidationTestModel, ) class ValidationTestModelAdmin(ModelAdmin): list_display = ("users",) six.assertRaisesRegex( self, ImproperlyConfigured, "'ValidationTestModelAdmin.list_display\[0\]', 'users' is a ManyToManyField which is not supported.", ValidationTestModelAdmin.validate, ValidationTestModel, ) def a_callable(obj): pass class ValidationTestModelAdmin(ModelAdmin): def a_method(self, obj): pass list_display = ("name", "decade_published_in", "a_method", a_callable) ValidationTestModelAdmin.validate(ValidationTestModel)
def test_single_message(self): v = ValidationError('Not Valid') self.assertEqual(str(v), str_prefix("[%(_)s'Not Valid']")) self.assertEqual(repr(v), str_prefix("ValidationError([%(_)s'Not Valid'])"))
def test_message_dict(self): v = ValidationError({'first': ['First Problem']}) self.assertEqual(str(v), str_prefix("{%(_)s'first': [%(_)s'First Problem']}")) self.assertEqual(repr(v), str_prefix("ValidationError({%(_)s'first': [%(_)s'First Problem']})"))
def test_message_list(self): v = ValidationError(['First Problem', 'Second Problem']) self.assertEqual(str(v), str_prefix("[%(_)s'First Problem', %(_)s'Second Problem']")) self.assertEqual(repr(v), str_prefix("ValidationError([%(_)s'First Problem', %(_)s'Second Problem'])"))
def get_filter_tests(): now = datetime.now() now_tz = datetime.now(LocalTimezone(now)) now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone today = date.today() # NOTE: \xa0 avoids wrapping between value and unit return { # Default compare with datetime.now() "filter-timesince01": ( "{{ a|timesince }}", {"a": datetime.now() + timedelta(minutes=-1, seconds=-10)}, "1\xa0minute", ), "filter-timesince02": ("{{ a|timesince }}", {"a": datetime.now() - timedelta(days=1, minutes=1)}, "1\xa0day"), "filter-timesince03": ( "{{ a|timesince }}", {"a": datetime.now() - timedelta(hours=1, minutes=25, seconds=10)}, "1\xa0hour, 25\xa0minutes", ), # Compare to a given parameter "filter-timesince04": ( "{{ a|timesince:b }}", {"a": now - timedelta(days=2), "b": now - timedelta(days=1)}, "1\xa0day", ), "filter-timesince05": ( "{{ a|timesince:b }}", {"a": now - timedelta(days=2, minutes=1), "b": now - timedelta(days=2)}, "1\xa0minute", ), # Check that timezone is respected "filter-timesince06": ("{{ a|timesince:b }}", {"a": now_tz - timedelta(hours=8), "b": now_tz}, "8\xa0hours"), # Regression for #7443 "filter-timesince07": ("{{ earlier|timesince }}", {"earlier": now - timedelta(days=7)}, "1\xa0week"), "filter-timesince08": ( "{{ earlier|timesince:now }}", {"now": now, "earlier": now - timedelta(days=7)}, "1\xa0week", ), "filter-timesince09": ("{{ later|timesince }}", {"later": now + timedelta(days=7)}, "0\xa0minutes"), "filter-timesince10": ( "{{ later|timesince:now }}", {"now": now, "later": now + timedelta(days=7)}, "0\xa0minutes", ), # Ensures that differing timezones are calculated correctly "filter-timesince11": ("{{ a|timesince }}", {"a": now}, "0\xa0minutes"), "filter-timesince12": ("{{ a|timesince }}", {"a": now_tz}, "0\xa0minutes"), "filter-timesince13": ("{{ a|timesince }}", {"a": now_tz_i}, "0\xa0minutes"), "filter-timesince14": ("{{ a|timesince:b }}", {"a": now_tz, "b": now_tz_i}, "0\xa0minutes"), "filter-timesince15": ("{{ a|timesince:b }}", {"a": now, "b": now_tz_i}, ""), "filter-timesince16": ("{{ a|timesince:b }}", {"a": now_tz_i, "b": now}, ""), # Regression for #9065 (two date objects). "filter-timesince17": ("{{ a|timesince:b }}", {"a": today, "b": today}, "0\xa0minutes"), "filter-timesince18": ("{{ a|timesince:b }}", {"a": today, "b": today + timedelta(hours=24)}, "1\xa0day"), # Default compare with datetime.now() "filter-timeuntil01": ( "{{ a|timeuntil }}", {"a": datetime.now() + timedelta(minutes=2, seconds=10)}, "2\xa0minutes", ), "filter-timeuntil02": ( "{{ a|timeuntil }}", {"a": (datetime.now() + timedelta(days=1, seconds=10))}, "1\xa0day", ), "filter-timeuntil03": ( "{{ a|timeuntil }}", {"a": (datetime.now() + timedelta(hours=8, minutes=10, seconds=10))}, "8\xa0hours, 10\xa0minutes", ), # Compare to a given parameter "filter-timeuntil04": ( "{{ a|timeuntil:b }}", {"a": now - timedelta(days=1), "b": now - timedelta(days=2)}, "1\xa0day", ), "filter-timeuntil05": ( "{{ a|timeuntil:b }}", {"a": now - timedelta(days=2), "b": now - timedelta(days=2, minutes=1)}, "1\xa0minute", ), # Regression for #7443 "filter-timeuntil06": ("{{ earlier|timeuntil }}", {"earlier": now - timedelta(days=7)}, "0\xa0minutes"), "filter-timeuntil07": ( "{{ earlier|timeuntil:now }}", {"now": now, "earlier": now - timedelta(days=7)}, "0\xa0minutes", ), "filter-timeuntil08": ("{{ later|timeuntil }}", {"later": now + timedelta(days=7, hours=1)}, "1\xa0week"), "filter-timeuntil09": ( "{{ later|timeuntil:now }}", {"now": now, "later": now + timedelta(days=7)}, "1\xa0week", ), # Ensures that differing timezones are calculated correctly "filter-timeuntil10": ("{{ a|timeuntil }}", {"a": now_tz_i}, "0\xa0minutes"), "filter-timeuntil11": ("{{ a|timeuntil:b }}", {"a": now_tz_i, "b": now_tz}, "0\xa0minutes"), # Regression for #9065 (two date objects). "filter-timeuntil12": ("{{ a|timeuntil:b }}", {"a": today, "b": today}, "0\xa0minutes"), "filter-timeuntil13": ("{{ a|timeuntil:b }}", {"a": today, "b": today - timedelta(hours=24)}, "1\xa0day"), "filter-addslash01": ( "{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'", ), "filter-addslash02": ( "{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'", ), "filter-capfirst01": ( "{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}", {"a": "fred>", "b": mark_safe("fred>")}, "Fred> Fred>", ), "filter-capfirst02": ( "{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred>")}, "Fred> Fred>", ), # Note that applying fix_ampsersands in autoescape mode leads to # double escaping. "filter-fix_ampersands01": ( "{% autoescape off %}{{ a|fix_ampersands }} {{ b|fix_ampersands }}{% endautoescape %}", {"a": "a&b", "b": mark_safe("a&b")}, "a&b a&b", ), "filter-fix_ampersands02": ( "{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, "a&amp;b a&b", ), "filter-floatformat01": ( "{% autoescape off %}{{ a|floatformat }} {{ b|floatformat }}{% endautoescape %}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4", ), "filter-floatformat02": ( "{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4", ), # The contents of "linenumbers" is escaped according to the current # autoescape setting. "filter-linenumbers01": ( "{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three", ), "filter-linenumbers02": ( "{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three", ), "filter-lower01": ( "{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, "apple & banana apple & banana", ), "filter-lower02": ( "{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, "apple & banana apple & banana", ), # The make_list filter can destroy existing escaping, so the results are # escaped. "filter-make_list01": ( "{% autoescape off %}{{ a|make_list }}{% endautoescape %}", {"a": mark_safe("&")}, str_prefix("[%(_)s'&']"), ), "filter-make_list02": ("{{ a|make_list }}", {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")), "filter-make_list03": ( '{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}', {"a": mark_safe("&")}, str_prefix("[%(_)s'&']"), ), "filter-make_list04": ( '{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, str_prefix("[%(_)s'&']"), ), # Running slugify on a pre-escaped string leads to odd behavior, # but the result is still safe. "filter-slugify01": ( "{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a & b")}, "a-b a-amp-b", ), "filter-slugify02": ( "{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a & b")}, "a-b a-amp-b", ), # Notice that escaping is applied *after* any filters, so the string # formatting here only needs to deal with pre-escaped characters. "filter-stringformat01": ( '{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "a<b", "b": mark_safe("a<b")}, ". a<b. . a<b.", ), "filter-stringformat02": ( '.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, ". a<b. . a<b.", ), # Test the title filter "filter-title1": ("{{ a|title }}", {"a": "JOE'S CRAB SHACK"}, "Joe's Crab Shack"), "filter-title2": ("{{ a|title }}", {"a": "555 WEST 53RD STREET"}, "555 West 53rd Street"), "filter-truncatewords01": ( '{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, "alpha & ... alpha & ...", ), "filter-truncatewords02": ( '{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, "alpha & ... alpha & ...", ), "filter-truncatechars01": ("{{ a|truncatechars:5 }}", {"a": "Testing, testing"}, "Te..."), "filter-truncatechars02": ("{{ a|truncatechars:7 }}", {"a": "Testing"}, "Testing"), # The "upper" filter messes up entities (which are case-sensitive), # so it's not safe for non-escaping purposes. "filter-upper01": ( "{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a & b")}, "A & B A & B", ), "filter-upper02": ( "{{ a|upper }} {{ b|upper }}", {"a": "a & b", "b": mark_safe("a & b")}, "A & B A &AMP; B", ), "filter-urlize01": ( "{% autoescape off %}{{ a|urlize }} {{ b|urlize }}{% endautoescape %}", {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/?x=&y=" rel="nofollow">http://example.com/?x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>', ), "filter-urlize02": ( "{{ a|urlize }} {{ b|urlize }}", {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/?x=&y=" rel="nofollow">http://example.com/?x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>', ), "filter-urlize03": ( "{% autoescape off %}{{ a|urlize }}{% endautoescape %}", {"a": mark_safe("a & b")}, "a & b", ), "filter-urlize04": ("{{ a|urlize }}", {"a": mark_safe("a & b")}, "a & b"), # This will lead to a nonsense result, but at least it won't be # exploitable for XSS purposes when auto-escaping is on. "filter-urlize05": ( "{% autoescape off %}{{ a|urlize }}{% endautoescape %}", {"a": "<script>alert('foo')</script>"}, "<script>alert('foo')</script>", ), "filter-urlize06": ( "{{ a|urlize }}", {"a": "<script>alert('foo')</script>"}, "<script>alert('foo')</script>", ), # mailto: testing for urlize "filter-urlize07": ( "{{ a|urlize }}", {"a": "Email me at [email protected]"}, 'Email me at <a href="mailto:[email protected]">[email protected]</a>', ), "filter-urlize08": ( "{{ a|urlize }}", {"a": "Email me at <*****@*****.**>"}, 'Email me at <<a href="mailto:[email protected]">[email protected]</a>>', ), "filter-urlizetrunc01": ( '{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe(""Safe" http://example.com?x=&y=")}, '"Unsafe" <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> "Safe" <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>', ), "filter-urlizetrunc02": ( '{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe(""Safe" http://example.com?x=&y=")}, '"Unsafe" <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> "Safe" <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>', ), "filter-wordcount01": ( "{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a & b")}, "3 3", ), "filter-wordcount02": ( "{{ a|wordcount }} {{ b|wordcount }}", {"a": "a & b", "b": mark_safe("a & b")}, "3 3", ), "filter-wordwrap01": ( '{% autoescape off %}{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb", ), "filter-wordwrap02": ( '{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb", ), "filter-ljust01": ( '{% autoescape off %}.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b .", ), "filter-ljust02": ( '.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b .", ), "filter-rjust01": ( '{% autoescape off %}.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b.", ), "filter-rjust02": ( '.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b.", ), "filter-center01": ( '{% autoescape off %}.{{ a|center:"5" }}. .{{ b|center:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b .", ), "filter-center02": ( '.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b .", ), "filter-cut01": ( '{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &y", ), "filter-cut02": ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &y"), "filter-cut03": ( '{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y", ), "filter-cut04": ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y"), # Passing ';' to cut can break existing HTML entities, so those strings # are auto-escaped. "filter-cut05": ( '{% autoescape off %}{{ a|cut:";" }} {{ b|cut:";" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y", ), "filter-cut06": ( '{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&ampy", ), # The "escape" filter works the same whether autoescape is on or off, # but it has no effect on strings already marked as safe. "filter-escape01": ("{{ a|escape }} {{ b|escape }}", {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), "filter-escape02": ( "{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}", {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y", ), # It is only applied once, regardless of the number of times it # appears in a chain. "filter-escape03": ("{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}", {"a": "x&y"}, "x&y"), "filter-escape04": ("{{ a|escape|escape }}", {"a": "x&y"}, "x&y"), # Force_escape is applied immediately. It can be used to provide # double-escaping, for example. "filter-force-escape01": ( "{% autoescape off %}{{ a|force_escape }}{% endautoescape %}", {"a": "x&y"}, "x&y", ), "filter-force-escape02": ("{{ a|force_escape }}", {"a": "x&y"}, "x&y"), "filter-force-escape03": ( "{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}", {"a": "x&y"}, "x&amp;y", ), "filter-force-escape04": ("{{ a|force_escape|force_escape }}", {"a": "x&y"}, "x&amp;y"), # Because the result of force_escape is "safe", an additional # escape filter has no effect. "filter-force-escape05": ( "{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}", {"a": "x&y"}, "x&y", ), "filter-force-escape06": ("{{ a|force_escape|escape }}", {"a": "x&y"}, "x&y"), "filter-force-escape07": ( "{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}", {"a": "x&y"}, "x&y", ), "filter-force-escape08": ("{{ a|escape|force_escape }}", {"a": "x&y"}, "x&y"), # The contents in "linebreaks" and "linebreaksbr" are escaped # according to the current autoescape setting. "filter-linebreaks01": ( "{{ a|linebreaks }} {{ b|linebreaks }}", {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>", ), "filter-linebreaks02": ( "{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}", {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>", ), "filter-linebreaksbr01": ( "{{ a|linebreaksbr }} {{ b|linebreaksbr }}", {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y", ), "filter-linebreaksbr02": ( "{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}", {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y", ), "filter-safe01": ("{{ a }} -- {{ a|safe }}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"), "filter-safe02": ( "{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>", ), "filter-safeseq01": ( '{{ a|join:", " }} -- {{ a|safeseq|join:", " }}', {"a": ["&", "<"]}, "&, < -- &, <", ), "filter-safeseq02": ( '{% autoescape off %}{{ a|join:", " }} -- {{ a|safeseq|join:", " }}{% endautoescape %}', {"a": ["&", "<"]}, "&, < -- &, <", ), "filter-removetags01": ( '{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x <p>y</p> x <p>y</p>", ), "filter-removetags02": ( '{% autoescape off %}{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x <p>y</p> x <p>y</p>", ), "filter-striptags01": ( "{{ a|striptags }} {{ b|striptags }}", {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y", ), "filter-striptags02": ( "{% autoescape off %}{{ a|striptags }} {{ b|striptags }}{% endautoescape %}", {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y", ), "filter-first01": ( "{{ a|first }} {{ b|first }}", {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b", ), "filter-first02": ( "{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}", {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b", ), "filter-last01": ( "{{ a|last }} {{ b|last }}", {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}, "a&b a&b", ), "filter-last02": ( "{% autoescape off %}{{ a|last }} {{ b|last }}{% endautoescape %}", {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}, "a&b a&b", ), "filter-random01": ( "{{ a|random }} {{ b|random }}", {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b", ), "filter-random02": ( "{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}", {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b", ), "filter-slice01": ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), "filter-slice02": ( '{% autoescape off %}{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b", ), "filter-unordered_list01": ( "{{ a|unordered_list }}", {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>", ), "filter-unordered_list02": ( "{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}", {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>", ), "filter-unordered_list03": ( "{{ a|unordered_list }}", {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>", ), "filter-unordered_list04": ( "{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}", {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>", ), "filter-unordered_list05": ( "{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}", {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>", ), # Literal string arguments to the default filter are always treated as # safe strings, regardless of the auto-escaping state. # # Note: we have to use {"a": ""} here, otherwise the invalid template # variable string interferes with the test result. "filter-default01": ('{{ a|default:"x<" }}', {"a": ""}, "x<"), "filter-default02": ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": ""}, "x<"), "filter-default03": ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"), "filter-default04": ( '{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": mark_safe("x>")}, "x>", ), "filter-default_if_none01": ('{{ a|default:"x<" }}', {"a": None}, "x<"), "filter-default_if_none02": ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": None}, "x<"), "filter-phone2numeric01": ( "{{ a|phone2numeric }} {{ b|phone2numeric }}", {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>")}, "<1-800-2255-63> <1-800-2255-63>", ), "filter-phone2numeric02": ( "{% autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{% endautoescape %}", {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>")}, "<1-800-2255-63> <1-800-2255-63>", ), "filter-phone2numeric03": ( "{{ a|phone2numeric }}", {"a": "How razorback-jumping frogs can level six piqued gymnasts!"}, "469 729672225-5867464 37647 226 53835 749 747833 49662787!", ), # Ensure iriencode keeps safe strings: "filter-iriencode01": ("{{ url|iriencode }}", {"url": "?test=1&me=2"}, "?test=1&me=2"), "filter-iriencode02": ( "{% autoescape off %}{{ url|iriencode }}{% endautoescape %}", {"url": "?test=1&me=2"}, "?test=1&me=2", ), "filter-iriencode03": ("{{ url|iriencode }}", {"url": mark_safe("?test=1&me=2")}, "?test=1&me=2"), "filter-iriencode04": ( "{% autoescape off %}{{ url|iriencode }}{% endautoescape %}", {"url": mark_safe("?test=1&me=2")}, "?test=1&me=2", ), # urlencode "filter-urlencode01": ("{{ url|urlencode }}", {"url": '/test&"/me?/'}, "/test%26%22/me%3F/"), "filter-urlencode02": ('/test/{{ urlbit|urlencode:"" }}/', {"urlbit": "escape/slash"}, "/test/escape%2Fslash/"), # Chaining a bunch of safeness-preserving filters should not alter # the safe status either way. "chaining01": ( '{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b ", ), "chaining02": ( '{% autoescape off %}{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b ", ), # Using a filter that forces a string back to unsafe: "chaining03": ( '{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < ", ), "chaining04": ( '{% autoescape off %}{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < ", ), # Using a filter that forces safeness does not lead to double-escaping "chaining05": ("{{ a|escape|capfirst }}", {"a": "a < b"}, "A < b"), "chaining06": ("{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}", {"a": "a < b"}, "A < b"), # Force to safe, then back (also showing why using force_escape too # early in a chain can lead to unexpected results). "chaining07": ('{{ a|force_escape|cut:";" }}', {"a": "a < b"}, "a &lt b"), "chaining08": ( '{% autoescape off %}{{ a|force_escape|cut:";" }}{% endautoescape %}', {"a": "a < b"}, "a < b", ), "chaining09": ('{{ a|cut:";"|force_escape }}', {"a": "a < b"}, "a < b"), "chaining10": ( '{% autoescape off %}{{ a|cut:";"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < b", ), "chaining11": ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "), "chaining12": ('{% autoescape off %}{{ a|cut:"b"|safe }}{% endautoescape %}', {"a": "a < b"}, "a < "), "chaining13": ("{{ a|safe|force_escape }}", {"a": "a < b"}, "a < b"), "chaining14": ("{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}", {"a": "a < b"}, "a < b"), # Filters decorated with stringfilter still respect is_safe. "autoescape-stringfilter01": (r"{{ unsafe|capfirst }}", {"unsafe": UnsafeClass()}, "You & me"), "autoescape-stringfilter02": ( r"{% autoescape off %}{{ unsafe|capfirst }}{% endautoescape %}", {"unsafe": UnsafeClass()}, "You & me", ), "autoescape-stringfilter03": (r"{{ safe|capfirst }}", {"safe": SafeClass()}, "You > me"), "autoescape-stringfilter04": ( r"{% autoescape off %}{{ safe|capfirst }}{% endautoescape %}", {"safe": SafeClass()}, "You > me", ), "escapejs01": ( r"{{ a|escapejs }}", {"a": "testing\r\njavascript 'string\" <b>escaping</b>"}, "testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E", ), "escapejs02": ( r"{% autoescape off %}{{ a|escapejs }}{% endautoescape %}", {"a": "testing\r\njavascript 'string\" <b>escaping</b>"}, "testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E", ), # length filter. "length01": ("{{ list|length }}", {"list": ["4", None, True, {}]}, "4"), "length02": ("{{ list|length }}", {"list": []}, "0"), "length03": ("{{ string|length }}", {"string": ""}, "0"), "length04": ("{{ string|length }}", {"string": "django"}, "6"), # Invalid uses that should fail silently. "length05": ("{{ int|length }}", {"int": 7}, ""), "length06": ("{{ None|length }}", {"None": None}, ""), # length_is filter. "length_is01": ( '{% if some_list|length_is:"4" %}Four{% endif %}', {"some_list": ["4", None, True, {}]}, "Four", ), "length_is02": ( '{% if some_list|length_is:"4" %}Four{% else %}Not Four{% endif %}', {"some_list": ["4", None, True, {}, 17]}, "Not Four", ), "length_is03": ('{% if mystring|length_is:"4" %}Four{% endif %}', {"mystring": "word"}, "Four"), "length_is04": ( '{% if mystring|length_is:"4" %}Four{% else %}Not Four{% endif %}', {"mystring": "Python"}, "Not Four", ), "length_is05": ( '{% if mystring|length_is:"4" %}Four{% else %}Not Four{% endif %}', {"mystring": ""}, "Not Four", ), "length_is06": ("{% with var|length as my_length %}{{ my_length }}{% endwith %}", {"var": "django"}, "6"), # Boolean return value from length_is should not be coerced to a string "length_is07": (r'{% if "X"|length_is:0 %}Length is 0{% else %}Length not 0{% endif %}', {}, "Length not 0"), "length_is08": (r'{% if "X"|length_is:1 %}Length is 1{% else %}Length not 1{% endif %}', {}, "Length is 1"), # Invalid uses that should fail silently. "length_is09": ('{{ var|length_is:"fish" }}', {"var": "django"}, ""), "length_is10": ('{{ int|length_is:"1" }}', {"int": 7}, ""), "length_is11": ('{{ none|length_is:"1" }}', {"none": None}, ""), "join01": (r'{{ a|join:", " }}', {"a": ["alpha", "beta & me"]}, "alpha, beta & me"), "join02": ( r'{% autoescape off %}{{ a|join:", " }}{% endautoescape %}', {"a": ["alpha", "beta & me"]}, "alpha, beta & me", ), "join03": (r'{{ a|join:" & " }}', {"a": ["alpha", "beta & me"]}, "alpha & beta & me"), "join04": ( r'{% autoescape off %}{{ a|join:" & " }}{% endautoescape %}', {"a": ["alpha", "beta & me"]}, "alpha & beta & me", ), # Test that joining with unsafe joiners don't result in unsafe strings (#11377) "join05": (r"{{ a|join:var }}", {"a": ["alpha", "beta & me"], "var": " & "}, "alpha & beta & me"), "join06": ( r"{{ a|join:var }}", {"a": ["alpha", "beta & me"], "var": mark_safe(" & ")}, "alpha & beta & me", ), "join07": (r"{{ a|join:var|lower }}", {"a": ["Alpha", "Beta & me"], "var": " & "}, "alpha & beta & me"), "join08": ( r"{{ a|join:var|lower }}", {"a": ["Alpha", "Beta & me"], "var": mark_safe(" & ")}, "alpha & beta & me", ), "date01": (r'{{ d|date:"m" }}', {"d": datetime(2008, 1, 1)}, "01"), "date02": (r"{{ d|date }}", {"d": datetime(2008, 1, 1)}, "Jan. 1, 2008"), # Ticket 9520: Make sure |date doesn't blow up on non-dates "date03": (r'{{ d|date:"m" }}', {"d": "fail_string"}, ""), # ISO date formats "date04": (r'{{ d|date:"o" }}', {"d": datetime(2008, 12, 29)}, "2009"), "date05": (r'{{ d|date:"o" }}', {"d": datetime(2010, 1, 3)}, "2009"), # Timezone name "date06": (r'{{ d|date:"e" }}', {"d": datetime(2009, 3, 12, tzinfo=FixedOffset(30))}, "+0030"), "date07": (r'{{ d|date:"e" }}', {"d": datetime(2009, 3, 12)}, ""), # Ticket 19370: Make sure |date doesn't blow up on a midnight time object "date08": (r'{{ t|date:"H:i" }}', {"t": time(0, 1)}, "00:01"), "date09": (r'{{ t|date:"H:i" }}', {"t": time(0, 0)}, "00:00"), # Ticket 20693: Add timezone support to built-in time template filter "time01": (r'{{ dt|time:"e:O:T:Z" }}', {"dt": now_tz_i}, "+0315:+0315:+0315:11700"), "time02": (r'{{ dt|time:"e:T" }}', {"dt": now}, ":" + now_tz.tzinfo.tzname(now_tz)), "time03": (r'{{ t|time:"P:e:O:T:Z" }}', {"t": time(4, 0, tzinfo=FixedOffset(30))}, "4 a.m.::::"), "time04": (r'{{ t|time:"P:e:O:T:Z" }}', {"t": time(4, 0)}, "4 a.m.::::"), "time05": (r'{{ d|time:"P:e:O:T:Z" }}', {"d": today}, ""), "time06": (r'{{ obj|time:"P:e:O:T:Z" }}', {"obj": "non-datetime-value"}, ""), # Tests for #11687 and #16676 "add01": (r'{{ i|add:"5" }}', {"i": 2000}, "2005"), "add02": (r'{{ i|add:"napis" }}', {"i": 2000}, ""), "add03": (r"{{ i|add:16 }}", {"i": "not_an_int"}, ""), "add04": (r'{{ i|add:"16" }}', {"i": "not_an_int"}, "not_an_int16"), "add05": (r"{{ l1|add:l2 }}", {"l1": [1, 2], "l2": [3, 4]}, "[1, 2, 3, 4]"), "add06": (r"{{ t1|add:t2 }}", {"t1": (3, 4), "t2": (1, 2)}, "(3, 4, 1, 2)"), "add07": (r"{{ d|add:t }}", {"d": date(2000, 1, 1), "t": timedelta(10)}, "Jan. 11, 2000"), }
def test_make_list04(self): output = self.engine.render_to_string('make_list04', {"a": mark_safe("&")}) self.assertEqual(output, str_prefix("[%(_)s'&']"))
def test_make_list04(self): output = render('make_list04', {"a": mark_safe("&")}) self.assertEqual(output, str_prefix("[%(_)s'&']"))
def test_make_list02(self): output = render('make_list02', {"a": mark_safe("&")}) self.assertEqual(output, str_prefix("[%(_)s'&']"))
def get_filter_tests(): now = datetime.now() now_tz = datetime.now(LocalTimezone(now)) now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone today = date.today() return { # Default compare with datetime.now() 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1 minute'), 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1 day'), 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1 hour, 25 minutes'), # Compare to a given parameter 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1 day'), 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1 minute'), # Check that timezone is respected 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8 hours'), # Regression for #7443 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1 week'), 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1 week'), 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0 minutes'), 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0 minutes'), # Ensures that differing timezones are calculated correctly 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0 minutes'), 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0 minutes'), 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0 minutes'), 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0 minutes'), 'filter-timesince15' : ('{{ a|timesince:b }}', {'a': now, 'b': now_tz_i}, ''), 'filter-timesince16' : ('{{ a|timesince:b }}', {'a': now_tz_i, 'b': now}, ''), # Regression for #9065 (two date objects). 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0 minutes'), 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1 day'), # Default compare with datetime.now() 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2 minutes'), 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1 day'), 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8 hours, 10 minutes'), # Compare to a given parameter 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1 day'), 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1 minute'), # Regression for #7443 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0 minutes'), 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0 minutes'), 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1 week'), 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1 week'), # Ensures that differing timezones are calculated correctly 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0 minutes'), 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0 minutes'), # Regression for #9065 (two date objects). 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0 minutes'), 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1 day'), 'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'"), 'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'"), 'filter-capfirst01': ("{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}", {"a": "fred>", "b": mark_safe("fred>")}, "Fred> Fred>"), 'filter-capfirst02': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred>")}, "Fred> Fred>"), # Note that applying fix_ampsersands in autoescape mode leads to # double escaping. 'filter-fix_ampersands01': ("{% autoescape off %}{{ a|fix_ampersands }} {{ b|fix_ampersands }}{% endautoescape %}", {"a": "a&b", "b": mark_safe("a&b")}, "a&b a&b"), 'filter-fix_ampersands02': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, "a&amp;b a&b"), 'filter-floatformat01': ("{% autoescape off %}{{ a|floatformat }} {{ b|floatformat }}{% endautoescape %}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4"), 'filter-floatformat02': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4"), # The contents of "linenumbers" is escaped according to the current # autoescape setting. 'filter-linenumbers01': ("{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), 'filter-linenumbers02': ("{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), 'filter-lower01': ("{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, "apple & banana apple & banana"), 'filter-lower02': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, "apple & banana apple & banana"), # The make_list filter can destroy existing escaping, so the results are # escaped. 'filter-make_list01': ("{% autoescape off %}{{ a|make_list }}{% endautoescape %}", {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")), 'filter-make_list02': ("{{ a|make_list }}", {"a": mark_safe("&")}, "[u'&']"), 'filter-make_list03': ('{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}', {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")), 'filter-make_list04': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")), # Running slugify on a pre-escaped string leads to odd behavior, # but the result is still safe. 'filter-slugify01': ("{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a & b")}, "a-b a-amp-b"), 'filter-slugify02': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a & b")}, "a-b a-amp-b"), # Notice that escaping is applied *after* any filters, so the string # formatting here only needs to deal with pre-escaped characters. 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "a<b", "b": mark_safe("a<b")}, ". a<b. . a<b."), 'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, ". a<b. . a<b."), # Test the title filter 'filter-title1' : ('{{ a|title }}', {'a' : 'JOE\'S CRAB SHACK'}, 'Joe's Crab Shack'), 'filter-title2' : ('{{ a|title }}', {'a' : '555 WEST 53RD STREET'}, '555 West 53rd Street'), 'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, "alpha & ... alpha & ..."), 'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, "alpha & ... alpha & ..."), 'filter-truncatechars01': ('{{ a|truncatechars:5 }}', {'a': "Testing, testing"}, "Te..."), 'filter-truncatechars02': ('{{ a|truncatechars:7 }}', {'a': "Testing"}, "Testing"), # The "upper" filter messes up entities (which are case-sensitive), # so it's not safe for non-escaping purposes. 'filter-upper01': ('{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "A & B A & B"), 'filter-upper02': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a & b")}, "A & B A &AMP; B"), 'filter-urlize01': ('{% autoescape off %}{{ a|urlize }} {{ b|urlize }}{% endautoescape %}', {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/?x=&y=" rel="nofollow">http://example.com/?x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'), 'filter-urlize02': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/?x=&y=" rel="nofollow">http://example.com/?x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'), 'filter-urlize03': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": mark_safe("a & b")}, 'a & b'), 'filter-urlize04': ('{{ a|urlize }}', {"a": mark_safe("a & b")}, 'a & b'), # This will lead to a nonsense result, but at least it won't be # exploitable for XSS purposes when auto-escaping is on. 'filter-urlize05': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": "<script>alert('foo')</script>"}, "<script>alert('foo')</script>"), 'filter-urlize06': ('{{ a|urlize }}', {"a": "<script>alert('foo')</script>"}, '<script>alert('foo')</script>'), # mailto: testing for urlize 'filter-urlize07': ('{{ a|urlize }}', {"a": "Email me at [email protected]"}, 'Email me at <a href="mailto:[email protected]">[email protected]</a>'), 'filter-urlize08': ('{{ a|urlize }}', {"a": "Email me at <*****@*****.**>"}, 'Email me at <<a href="mailto:[email protected]">[email protected]</a>>'), 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('"Safe" http://example.com?x=&y=')}, '"Unsafe" <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> "Safe" <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('"Safe" http://example.com?x=&y=')}, '"Unsafe" <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> "Safe" <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), 'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), 'filter-wordwrap01': ('{% autoescape off %}{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb"), 'filter-wordwrap02': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb"), 'filter-ljust01': ('{% autoescape off %}.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b ."), 'filter-ljust02': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b ."), 'filter-rjust01': ('{% autoescape off %}.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b."), 'filter-rjust02': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b."), 'filter-center01': ('{% autoescape off %}.{{ a|center:"5" }}. .{{ b|center:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b ."), 'filter-center02': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b ."), 'filter-cut01': ('{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &y"), 'filter-cut02': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &y"), 'filter-cut03': ('{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y"), 'filter-cut04': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y"), # Passing ';' to cut can break existing HTML entities, so those strings # are auto-escaped. 'filter-cut05': ('{% autoescape off %}{{ a|cut:";" }} {{ b|cut:";" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), 'filter-cut06': ('{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&ampy"), # The "escape" filter works the same whether autoescape is on or off, # but it has no effect on strings already marked as safe. 'filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), 'filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), # It is only applied once, regardless of the number of times it # appears in a chain. 'filter-escape03': ('{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"), 'filter-escape04': ('{{ a|escape|escape }}', {"a": "x&y"}, "x&y"), # Force_escape is applied immediately. It can be used to provide # double-escaping, for example. 'filter-force-escape01': ('{% autoescape off %}{{ a|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"), 'filter-force-escape02': ('{{ a|force_escape }}', {"a": "x&y"}, "x&y"), 'filter-force-escape03': ('{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&amp;y"), 'filter-force-escape04': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, "x&amp;y"), # Because the result of force_escape is "safe", an additional # escape filter has no effect. 'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"), 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, "x&y"), 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"), 'filter-force-escape08': ('{{ a|escape|force_escape }}', {"a": "x&y"}, "x&y"), # The contents in "linebreaks" and "linebreaksbr" are escaped # according to the current autoescape setting. 'filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"), 'filter-linebreaks02': ('{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"), 'filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"), 'filter-linebreaksbr02': ('{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"), 'filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"), 'filter-safe02': ("{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"), 'filter-safeseq01': ('{{ a|join:", " }} -- {{ a|safeseq|join:", " }}', {"a": ["&", "<"]}, "&, < -- &, <"), 'filter-safeseq02': ('{% autoescape off %}{{ a|join:", " }} -- {{ a|safeseq|join:", " }}{% endautoescape %}', {"a": ["&", "<"]}, "&, < -- &, <"), 'filter-removetags01': ('{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x <p>y</p> x <p>y</p>"), 'filter-removetags02': ('{% autoescape off %}{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x <p>y</p> x <p>y</p>"), 'filter-striptags01': ('{{ a|striptags }} {{ b|striptags }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"), 'filter-striptags02': ('{% autoescape off %}{{ a|striptags }} {{ b|striptags }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"), 'filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), 'filter-first02': ('{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), 'filter-last01': ('{{ a|last }} {{ b|last }}', {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}, "a&b a&b"), 'filter-last02': ('{% autoescape off %}{{ a|last }} {{ b|last }}{% endautoescape %}', {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}, "a&b a&b"), 'filter-random01': ('{{ a|random }} {{ b|random }}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), 'filter-random02': ('{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), 'filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), 'filter-slice02': ('{% autoescape off %}{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), 'filter-unordered_list01': ('{{ a|unordered_list }}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 'filter-unordered_list02': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 'filter-unordered_list03': ('{{ a|unordered_list }}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 'filter-unordered_list04': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 'filter-unordered_list05': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), # Literal string arguments to the default filter are always treated as # safe strings, regardless of the auto-escaping state. # # Note: we have to use {"a": ""} here, otherwise the invalid template # variable string interferes with the test result. 'filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x<"), 'filter-default02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": ""}, "x<"), 'filter-default03': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"), 'filter-default04': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": mark_safe("x>")}, "x>"), 'filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x<"), 'filter-default_if_none02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": None}, "x<"), 'filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), 'filter-phone2numeric02': ('{% autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{% endautoescape %}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), 'filter-phone2numeric03': ('{{ a|phone2numeric }}', {"a": "How razorback-jumping frogs can level six piqued gymnasts!"}, "469 729672225-5867464 37647 226 53835 749 747833 49662787!"), # Ensure iriencode keeps safe strings: 'filter-iriencode01': ('{{ url|iriencode }}', {'url': '?test=1&me=2'}, '?test=1&me=2'), 'filter-iriencode02': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': '?test=1&me=2'}, '?test=1&me=2'), 'filter-iriencode03': ('{{ url|iriencode }}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'), 'filter-iriencode04': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'), # urlencode 'filter-urlencode01': ('{{ url|urlencode }}', {'url': '/test&"/me?/'}, '/test%26%22/me%3F/'), 'filter-urlencode02': ('/test/{{ urlbit|urlencode:"" }}/', {'urlbit': 'escape/slash'}, '/test/escape%2Fslash/'), # Chaining a bunch of safeness-preserving filters should not alter # the safe status either way. 'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), 'chaining02': ('{% autoescape off %}{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), # Using a filter that forces a string back to unsafe: 'chaining03': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), 'chaining04': ('{% autoescape off %}{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), # Using a filter that forces safeness does not lead to double-escaping 'chaining05': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A < b"), 'chaining06': ('{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}', {"a": "a < b"}, "A < b"), # Force to safe, then back (also showing why using force_escape too # early in a chain can lead to unexpected results). 'chaining07': ('{{ a|force_escape|cut:";" }}', {"a": "a < b"}, "a &lt b"), 'chaining08': ('{% autoescape off %}{{ a|force_escape|cut:";" }}{% endautoescape %}', {"a": "a < b"}, "a < b"), 'chaining09': ('{{ a|cut:";"|force_escape }}', {"a": "a < b"}, "a < b"), 'chaining10': ('{% autoescape off %}{{ a|cut:";"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < b"), 'chaining11': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "), 'chaining12': ('{% autoescape off %}{{ a|cut:"b"|safe }}{% endautoescape %}', {"a": "a < b"}, "a < "), 'chaining13': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a < b"), 'chaining14': ('{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < b"), # Filters decorated with stringfilter still respect is_safe. 'autoescape-stringfilter01': (r'{{ unsafe|capfirst }}', {'unsafe': UnsafeClass()}, 'You & me'), 'autoescape-stringfilter02': (r'{% autoescape off %}{{ unsafe|capfirst }}{% endautoescape %}', {'unsafe': UnsafeClass()}, 'You & me'), 'autoescape-stringfilter03': (r'{{ safe|capfirst }}', {'safe': SafeClass()}, 'You > me'), 'autoescape-stringfilter04': (r'{% autoescape off %}{{ safe|capfirst }}{% endautoescape %}', {'safe': SafeClass()}, 'You > me'), 'escapejs01': (r'{{ a|escapejs }}', {'a': 'testing\r\njavascript \'string" <b>escaping</b>'}, 'testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E'), 'escapejs02': (r'{% autoescape off %}{{ a|escapejs }}{% endautoescape %}', {'a': 'testing\r\njavascript \'string" <b>escaping</b>'}, 'testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E'), # length filter. 'length01': ('{{ list|length }}', {'list': ['4', None, True, {}]}, '4'), 'length02': ('{{ list|length }}', {'list': []}, '0'), 'length03': ('{{ string|length }}', {'string': ''}, '0'), 'length04': ('{{ string|length }}', {'string': 'django'}, '6'), # Invalid uses that should fail silently. 'length05': ('{{ int|length }}', {'int': 7}, ''), 'length06': ('{{ None|length }}', {'None': None}, ''), # length_is filter. 'length_is01': ('{% if some_list|length_is:"4" %}Four{% endif %}', {'some_list': ['4', None, True, {}]}, 'Four'), 'length_is02': ('{% if some_list|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'some_list': ['4', None, True, {}, 17]}, 'Not Four'), 'length_is03': ('{% if mystring|length_is:"4" %}Four{% endif %}', {'mystring': 'word'}, 'Four'), 'length_is04': ('{% if mystring|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'mystring': 'Python'}, 'Not Four'), 'length_is05': ('{% if mystring|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'mystring': ''}, 'Not Four'), 'length_is06': ('{% with var|length as my_length %}{{ my_length }}{% endwith %}', {'var': 'django'}, '6'), # Boolean return value from length_is should not be coerced to a string 'length_is07': (r'{% if "X"|length_is:0 %}Length is 0{% else %}Length not 0{% endif %}', {}, 'Length not 0'), 'length_is08': (r'{% if "X"|length_is:1 %}Length is 1{% else %}Length not 1{% endif %}', {}, 'Length is 1'), # Invalid uses that should fail silently. 'length_is09': ('{{ var|length_is:"fish" }}', {'var': 'django'}, ''), 'length_is10': ('{{ int|length_is:"1" }}', {'int': 7}, ''), 'length_is11': ('{{ none|length_is:"1" }}', {'none': None}, ''), 'join01': (r'{{ a|join:", " }}', {'a': ['alpha', 'beta & me']}, 'alpha, beta & me'), 'join02': (r'{% autoescape off %}{{ a|join:", " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha, beta & me'), 'join03': (r'{{ a|join:" & " }}', {'a': ['alpha', 'beta & me']}, 'alpha & beta & me'), 'join04': (r'{% autoescape off %}{{ a|join:" & " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha & beta & me'), # Test that joining with unsafe joiners don't result in unsafe strings (#11377) 'join05': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': ' & '}, 'alpha & beta & me'), 'join06': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta & me'), 'join07': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': ' & ' }, 'alpha & beta & me'), 'join08': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta & me'), 'date01': (r'{{ d|date:"m" }}', {'d': datetime(2008, 1, 1)}, '01'), 'date02': (r'{{ d|date }}', {'d': datetime(2008, 1, 1)}, 'Jan. 1, 2008'), #Ticket 9520: Make sure |date doesn't blow up on non-dates 'date03': (r'{{ d|date:"m" }}', {'d': 'fail_string'}, ''), # ISO date formats 'date04': (r'{{ d|date:"o" }}', {'d': datetime(2008, 12, 29)}, '2009'), 'date05': (r'{{ d|date:"o" }}', {'d': datetime(2010, 1, 3)}, '2009'), # Timezone name 'date06': (r'{{ d|date:"e" }}', {'d': datetime(2009, 3, 12, tzinfo=FixedOffset(30))}, '+0030'), 'date07': (r'{{ d|date:"e" }}', {'d': datetime(2009, 3, 12)}, ''), # Tests for #11687 and #16676 'add01': (r'{{ i|add:"5" }}', {'i': 2000}, '2005'), 'add02': (r'{{ i|add:"napis" }}', {'i': 2000}, ''), 'add03': (r'{{ i|add:16 }}', {'i': 'not_an_int'}, ''), 'add04': (r'{{ i|add:"16" }}', {'i': 'not_an_int'}, 'not_an_int16'), 'add05': (r'{{ l1|add:l2 }}', {'l1': [1, 2], 'l2': [3, 4]}, '[1, 2, 3, 4]'), 'add06': (r'{{ t1|add:t2 }}', {'t1': (3, 4), 't2': (1, 2)}, '(3, 4, 1, 2)'), 'add07': (r'{{ d|add:t }}', {'d': date(2000, 1, 1), 't': timedelta(10)}, 'Jan. 11, 2000'), }
def test_str(self): self.assertEqual(str_prefix("I am _ComplexObject(%(_)s'joe')"), str(SimpleLazyObject(complex_object)))
def test_basic_distinct_on(self): """QuerySet.distinct('field', ...) works""" # (qset, expected) tuples qsets = ( ( Staff.objects.distinct().order_by('name'), ['<Staff: p1>', '<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], ), ( Staff.objects.distinct('name').order_by('name'), ['<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], ), ( Staff.objects.distinct('organisation').order_by('organisation', 'name'), ['<Staff: p1>', '<Staff: p1>'], ), ( Staff.objects.distinct('name', 'organisation').order_by('name', 'organisation'), ['<Staff: p1>', '<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], ), ( Celebrity.objects.filter(fan__in=[self.fan1, self.fan2, self.fan3]).distinct('name').order_by('name'), ['<Celebrity: c1>', '<Celebrity: c2>'], ), # Does combining querysets work? ( (Celebrity.objects.filter(fan__in=[self.fan1, self.fan2]). distinct('name').order_by('name') | Celebrity.objects.filter(fan__in=[self.fan3]). distinct('name').order_by('name')), ['<Celebrity: c1>', '<Celebrity: c2>'], ), ( StaffTag.objects.distinct('staff', 'tag'), ['<StaffTag: t1 -> p1>'], ), ( Tag.objects.order_by('parent__pk', 'pk').distinct('parent'), ['<Tag: t2>', '<Tag: t4>', '<Tag: t1>'], ), ( StaffTag.objects.select_related('staff').distinct('staff__name').order_by('staff__name'), ['<StaffTag: t1 -> p1>'], ), # Fetch the alphabetically first coworker for each worker ( (Staff.objects.distinct('id').order_by('id', 'coworkers__name'). values_list('id', 'coworkers__name')), [str_prefix("(1, %(_)s'p2')"), str_prefix("(2, %(_)s'p1')"), str_prefix("(3, %(_)s'p1')"), "(4, None)"] ), ) for qset, expected in qsets: self.assertQuerysetEqual(qset, expected) self.assertEqual(qset.count(), len(expected)) # Combining queries with different distinct_fields is not allowed. base_qs = Celebrity.objects.all() self.assertRaisesMessage( AssertionError, "Cannot combine queries with different distinct fields.", lambda: (base_qs.distinct('id') & base_qs.distinct('name')) ) # Test join unreffing c1 = Celebrity.objects.distinct('greatest_fan__id', 'greatest_fan__fan_of') self.assertIn('OUTER JOIN', str(c1.query)) c2 = c1.distinct('pk') self.assertNotIn('OUTER JOIN', str(c2.query))
def get_filter_tests(): now = datetime.now() now_tz = datetime.now(LocalTimezone(now)) now_tz_i = datetime.now(FixedOffset((3 * 60) + 15)) # imaginary time zone today = date.today() # NOTE: \xa0 avoids wrapping between value and unit return { # Default compare with datetime.now() 'filter-timesince01' : ('{{ a|timesince }}', {'a': datetime.now() + timedelta(minutes=-1, seconds = -10)}, '1\xa0minute'), 'filter-timesince02' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(days=1, minutes = 1)}, '1\xa0day'), 'filter-timesince03' : ('{{ a|timesince }}', {'a': datetime.now() - timedelta(hours=1, minutes=25, seconds = 10)}, '1\xa0hour, 25\xa0minutes'), # Compare to a given parameter 'filter-timesince04' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=1)}, '1\xa0day'), 'filter-timesince05' : ('{{ a|timesince:b }}', {'a':now - timedelta(days=2, minutes=1), 'b':now - timedelta(days=2)}, '1\xa0minute'), # Check that timezone is respected 'filter-timesince06' : ('{{ a|timesince:b }}', {'a':now_tz - timedelta(hours=8), 'b':now_tz}, '8\xa0hours'), # Regression for #7443 'filter-timesince07': ('{{ earlier|timesince }}', { 'earlier': now - timedelta(days=7) }, '1\xa0week'), 'filter-timesince08': ('{{ earlier|timesince:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '1\xa0week'), 'filter-timesince09': ('{{ later|timesince }}', { 'later': now + timedelta(days=7) }, '0\xa0minutes'), 'filter-timesince10': ('{{ later|timesince:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '0\xa0minutes'), # Ensures that differing timezones are calculated correctly 'filter-timesince11' : ('{{ a|timesince }}', {'a': now}, '0\xa0minutes'), 'filter-timesince12' : ('{{ a|timesince }}', {'a': now_tz}, '0\xa0minutes'), 'filter-timesince13' : ('{{ a|timesince }}', {'a': now_tz_i}, '0\xa0minutes'), 'filter-timesince14' : ('{{ a|timesince:b }}', {'a': now_tz, 'b': now_tz_i}, '0\xa0minutes'), 'filter-timesince15' : ('{{ a|timesince:b }}', {'a': now, 'b': now_tz_i}, ''), 'filter-timesince16' : ('{{ a|timesince:b }}', {'a': now_tz_i, 'b': now}, ''), # Regression for #9065 (two date objects). 'filter-timesince17' : ('{{ a|timesince:b }}', {'a': today, 'b': today}, '0\xa0minutes'), 'filter-timesince18' : ('{{ a|timesince:b }}', {'a': today, 'b': today + timedelta(hours=24)}, '1\xa0day'), # Default compare with datetime.now() 'filter-timeuntil01' : ('{{ a|timeuntil }}', {'a':datetime.now() + timedelta(minutes=2, seconds = 10)}, '2\xa0minutes'), 'filter-timeuntil02' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(days=1, seconds = 10))}, '1\xa0day'), 'filter-timeuntil03' : ('{{ a|timeuntil }}', {'a':(datetime.now() + timedelta(hours=8, minutes=10, seconds = 10))}, '8\xa0hours, 10\xa0minutes'), # Compare to a given parameter 'filter-timeuntil04' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=1), 'b':now - timedelta(days=2)}, '1\xa0day'), 'filter-timeuntil05' : ('{{ a|timeuntil:b }}', {'a':now - timedelta(days=2), 'b':now - timedelta(days=2, minutes=1)}, '1\xa0minute'), # Regression for #7443 'filter-timeuntil06': ('{{ earlier|timeuntil }}', { 'earlier': now - timedelta(days=7) }, '0\xa0minutes'), 'filter-timeuntil07': ('{{ earlier|timeuntil:now }}', { 'now': now, 'earlier': now - timedelta(days=7) }, '0\xa0minutes'), 'filter-timeuntil08': ('{{ later|timeuntil }}', { 'later': now + timedelta(days=7, hours=1) }, '1\xa0week'), 'filter-timeuntil09': ('{{ later|timeuntil:now }}', { 'now': now, 'later': now + timedelta(days=7) }, '1\xa0week'), # Ensures that differing timezones are calculated correctly 'filter-timeuntil10' : ('{{ a|timeuntil }}', {'a': now_tz_i}, '0\xa0minutes'), 'filter-timeuntil11' : ('{{ a|timeuntil:b }}', {'a': now_tz_i, 'b': now_tz}, '0\xa0minutes'), # Regression for #9065 (two date objects). 'filter-timeuntil12' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today}, '0\xa0minutes'), 'filter-timeuntil13' : ('{{ a|timeuntil:b }}', {'a': today, 'b': today - timedelta(hours=24)}, '1\xa0day'), 'filter-addslash01': ("{% autoescape off %}{{ a|addslashes }} {{ b|addslashes }}{% endautoescape %}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'"), 'filter-addslash02': ("{{ a|addslashes }} {{ b|addslashes }}", {"a": "<a>'", "b": mark_safe("<a>'")}, r"<a>\' <a>\'"), 'filter-capfirst01': ("{% autoescape off %}{{ a|capfirst }} {{ b|capfirst }}{% endautoescape %}", {"a": "fred>", "b": mark_safe("fred>")}, "Fred> Fred>"), 'filter-capfirst02': ("{{ a|capfirst }} {{ b|capfirst }}", {"a": "fred>", "b": mark_safe("fred>")}, "Fred> Fred>"), # Note that applying fix_ampsersands in autoescape mode leads to # double escaping. 'filter-fix_ampersands01': ("{% autoescape off %}{{ a|fix_ampersands }} {{ b|fix_ampersands }}{% endautoescape %}", {"a": "a&b", "b": mark_safe("a&b")}, "a&b a&b"), 'filter-fix_ampersands02': ("{{ a|fix_ampersands }} {{ b|fix_ampersands }}", {"a": "a&b", "b": mark_safe("a&b")}, "a&amp;b a&b"), 'filter-floatformat01': ("{% autoescape off %}{{ a|floatformat }} {{ b|floatformat }}{% endautoescape %}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4"), 'filter-floatformat02': ("{{ a|floatformat }} {{ b|floatformat }}", {"a": "1.42", "b": mark_safe("1.42")}, "1.4 1.4"), # The contents of "linenumbers" is escaped according to the current # autoescape setting. 'filter-linenumbers01': ("{{ a|linenumbers }} {{ b|linenumbers }}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), 'filter-linenumbers02': ("{% autoescape off %}{{ a|linenumbers }} {{ b|linenumbers }}{% endautoescape %}", {"a": "one\n<two>\nthree", "b": mark_safe("one\n<two>\nthree")}, "1. one\n2. <two>\n3. three 1. one\n2. <two>\n3. three"), 'filter-lower01': ("{% autoescape off %}{{ a|lower }} {{ b|lower }}{% endautoescape %}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, "apple & banana apple & banana"), 'filter-lower02': ("{{ a|lower }} {{ b|lower }}", {"a": "Apple & banana", "b": mark_safe("Apple & banana")}, "apple & banana apple & banana"), # The make_list filter can destroy existing escaping, so the results are # escaped. 'filter-make_list01': ("{% autoescape off %}{{ a|make_list }}{% endautoescape %}", {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")), 'filter-make_list02': ("{{ a|make_list }}", {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")), 'filter-make_list03': ('{% autoescape off %}{{ a|make_list|stringformat:"s"|safe }}{% endautoescape %}', {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")), 'filter-make_list04': ('{{ a|make_list|stringformat:"s"|safe }}', {"a": mark_safe("&")}, str_prefix("[%(_)s'&']")), # Running slugify on a pre-escaped string leads to odd behavior, # but the result is still safe. 'filter-slugify01': ("{% autoescape off %}{{ a|slugify }} {{ b|slugify }}{% endautoescape %}", {"a": "a & b", "b": mark_safe("a & b")}, "a-b a-amp-b"), 'filter-slugify02': ("{{ a|slugify }} {{ b|slugify }}", {"a": "a & b", "b": mark_safe("a & b")}, "a-b a-amp-b"), # Notice that escaping is applied *after* any filters, so the string # formatting here only needs to deal with pre-escaped characters. 'filter-stringformat01': ('{% autoescape off %}.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.{% endautoescape %}', {"a": "a<b", "b": mark_safe("a<b")}, ". a<b. . a<b."), 'filter-stringformat02': ('.{{ a|stringformat:"5s" }}. .{{ b|stringformat:"5s" }}.', {"a": "a<b", "b": mark_safe("a<b")}, ". a<b. . a<b."), # Test the title filter 'filter-title1' : ('{{ a|title }}', {'a' : 'JOE\'S CRAB SHACK'}, 'Joe's Crab Shack'), 'filter-title2' : ('{{ a|title }}', {'a' : '555 WEST 53RD STREET'}, '555 West 53rd Street'), 'filter-truncatewords01': ('{% autoescape off %}{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}{% endautoescape %}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, "alpha & ... alpha & ..."), 'filter-truncatewords02': ('{{ a|truncatewords:"2" }} {{ b|truncatewords:"2"}}', {"a": "alpha & bravo", "b": mark_safe("alpha & bravo")}, "alpha & ... alpha & ..."), 'filter-truncatechars01': ('{{ a|truncatechars:5 }}', {'a': "Testing, testing"}, "Te..."), 'filter-truncatechars02': ('{{ a|truncatechars:7 }}', {'a': "Testing"}, "Testing"), # The "upper" filter messes up entities (which are case-sensitive), # so it's not safe for non-escaping purposes. 'filter-upper01': ('{% autoescape off %}{{ a|upper }} {{ b|upper }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "A & B A & B"), 'filter-upper02': ('{{ a|upper }} {{ b|upper }}', {"a": "a & b", "b": mark_safe("a & b")}, "A & B A &AMP; B"), 'filter-urlize01': ('{% autoescape off %}{{ a|urlize }} {{ b|urlize }}{% endautoescape %}', {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/?x=&y=" rel="nofollow">http://example.com/?x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'), 'filter-urlize02': ('{{ a|urlize }} {{ b|urlize }}', {"a": "http://example.com/?x=&y=", "b": mark_safe("http://example.com?x=&y=")}, '<a href="http://example.com/?x=&y=" rel="nofollow">http://example.com/?x=&y=</a> <a href="http://example.com?x=&y=" rel="nofollow">http://example.com?x=&y=</a>'), 'filter-urlize03': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": mark_safe("a & b")}, 'a & b'), 'filter-urlize04': ('{{ a|urlize }}', {"a": mark_safe("a & b")}, 'a & b'), # This will lead to a nonsense result, but at least it won't be # exploitable for XSS purposes when auto-escaping is on. 'filter-urlize05': ('{% autoescape off %}{{ a|urlize }}{% endautoescape %}', {"a": "<script>alert('foo')</script>"}, "<script>alert('foo')</script>"), 'filter-urlize06': ('{{ a|urlize }}', {"a": "<script>alert('foo')</script>"}, '<script>alert('foo')</script>'), # mailto: testing for urlize 'filter-urlize07': ('{{ a|urlize }}', {"a": "Email me at [email protected]"}, 'Email me at <a href="mailto:[email protected]">[email protected]</a>'), 'filter-urlize08': ('{{ a|urlize }}', {"a": "Email me at <*****@*****.**>"}, 'Email me at <<a href="mailto:[email protected]">[email protected]</a>>'), 'filter-urlizetrunc01': ('{% autoescape off %}{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}{% endautoescape %}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('"Safe" http://example.com?x=&y=')}, '"Unsafe" <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> "Safe" <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), 'filter-urlizetrunc02': ('{{ a|urlizetrunc:"8" }} {{ b|urlizetrunc:"8" }}', {"a": '"Unsafe" http://example.com/x=&y=', "b": mark_safe('"Safe" http://example.com?x=&y=')}, '"Unsafe" <a href="http://example.com/x=&y=" rel="nofollow">http:...</a> "Safe" <a href="http://example.com?x=&y=" rel="nofollow">http:...</a>'), 'filter-wordcount01': ('{% autoescape off %}{{ a|wordcount }} {{ b|wordcount }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), 'filter-wordcount02': ('{{ a|wordcount }} {{ b|wordcount }}', {"a": "a & b", "b": mark_safe("a & b")}, "3 3"), 'filter-wordwrap01': ('{% autoescape off %}{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}{% endautoescape %}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb"), 'filter-wordwrap02': ('{{ a|wordwrap:"3" }} {{ b|wordwrap:"3" }}', {"a": "a & b", "b": mark_safe("a & b")}, "a &\nb a &\nb"), 'filter-ljust01': ('{% autoescape off %}.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b ."), 'filter-ljust02': ('.{{ a|ljust:"5" }}. .{{ b|ljust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ".a&b . .a&b ."), 'filter-rjust01': ('{% autoescape off %}.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b."), 'filter-rjust02': ('.{{ a|rjust:"5" }}. .{{ b|rjust:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b. . a&b."), 'filter-center01': ('{% autoescape off %}.{{ a|center:"5" }}. .{{ b|center:"5" }}.{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b ."), 'filter-center02': ('.{{ a|center:"5" }}. .{{ b|center:"5" }}.', {"a": "a&b", "b": mark_safe("a&b")}, ". a&b . . a&b ."), 'filter-cut01': ('{% autoescape off %}{{ a|cut:"x" }} {{ b|cut:"x" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &y"), 'filter-cut02': ('{{ a|cut:"x" }} {{ b|cut:"x" }}', {"a": "x&y", "b": mark_safe("x&y")}, "&y &y"), 'filter-cut03': ('{% autoescape off %}{{ a|cut:"&" }} {{ b|cut:"&" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y"), 'filter-cut04': ('{{ a|cut:"&" }} {{ b|cut:"&" }}', {"a": "x&y", "b": mark_safe("x&y")}, "xy xamp;y"), # Passing ';' to cut can break existing HTML entities, so those strings # are auto-escaped. 'filter-cut05': ('{% autoescape off %}{{ a|cut:";" }} {{ b|cut:";" }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), 'filter-cut06': ('{{ a|cut:";" }} {{ b|cut:";" }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&ampy"), # The "escape" filter works the same whether autoescape is on or off, # but it has no effect on strings already marked as safe. 'filter-escape01': ('{{ a|escape }} {{ b|escape }}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), 'filter-escape02': ('{% autoescape off %}{{ a|escape }} {{ b|escape }}{% endautoescape %}', {"a": "x&y", "b": mark_safe("x&y")}, "x&y x&y"), # It is only applied once, regardless of the number of times it # appears in a chain. 'filter-escape03': ('{% autoescape off %}{{ a|escape|escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"), 'filter-escape04': ('{{ a|escape|escape }}', {"a": "x&y"}, "x&y"), # Force_escape is applied immediately. It can be used to provide # double-escaping, for example. 'filter-force-escape01': ('{% autoescape off %}{{ a|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"), 'filter-force-escape02': ('{{ a|force_escape }}', {"a": "x&y"}, "x&y"), 'filter-force-escape03': ('{% autoescape off %}{{ a|force_escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&amp;y"), 'filter-force-escape04': ('{{ a|force_escape|force_escape }}', {"a": "x&y"}, "x&amp;y"), # Because the result of force_escape is "safe", an additional # escape filter has no effect. 'filter-force-escape05': ('{% autoescape off %}{{ a|force_escape|escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"), 'filter-force-escape06': ('{{ a|force_escape|escape }}', {"a": "x&y"}, "x&y"), 'filter-force-escape07': ('{% autoescape off %}{{ a|escape|force_escape }}{% endautoescape %}', {"a": "x&y"}, "x&y"), 'filter-force-escape08': ('{{ a|escape|force_escape }}', {"a": "x&y"}, "x&y"), # The contents in "linebreaks" and "linebreaksbr" are escaped # according to the current autoescape setting. 'filter-linebreaks01': ('{{ a|linebreaks }} {{ b|linebreaks }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"), 'filter-linebreaks02': ('{% autoescape off %}{{ a|linebreaks }} {{ b|linebreaks }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "<p>x&<br />y</p> <p>x&<br />y</p>"), 'filter-linebreaksbr01': ('{{ a|linebreaksbr }} {{ b|linebreaksbr }}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"), 'filter-linebreaksbr02': ('{% autoescape off %}{{ a|linebreaksbr }} {{ b|linebreaksbr }}{% endautoescape %}', {"a": "x&\ny", "b": mark_safe("x&\ny")}, "x&<br />y x&<br />y"), 'filter-safe01': ("{{ a }} -- {{ a|safe }}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"), 'filter-safe02': ("{% autoescape off %}{{ a }} -- {{ a|safe }}{% endautoescape %}", {"a": "<b>hello</b>"}, "<b>hello</b> -- <b>hello</b>"), 'filter-safeseq01': ('{{ a|join:", " }} -- {{ a|safeseq|join:", " }}', {"a": ["&", "<"]}, "&, < -- &, <"), 'filter-safeseq02': ('{% autoescape off %}{{ a|join:", " }} -- {{ a|safeseq|join:", " }}{% endautoescape %}', {"a": ["&", "<"]}, "&, < -- &, <"), 'filter-removetags01': ('{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x <p>y</p> x <p>y</p>"), 'filter-removetags02': ('{% autoescape off %}{{ a|removetags:"a b" }} {{ b|removetags:"a b" }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x <p>y</p> x <p>y</p>"), 'filter-striptags01': ('{{ a|striptags }} {{ b|striptags }}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"), 'filter-striptags02': ('{% autoescape off %}{{ a|striptags }} {{ b|striptags }}{% endautoescape %}', {"a": "<a>x</a> <p><b>y</b></p>", "b": mark_safe("<a>x</a> <p><b>y</b></p>")}, "x y x y"), 'filter-first01': ('{{ a|first }} {{ b|first }}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), 'filter-first02': ('{% autoescape off %}{{ a|first }} {{ b|first }}{% endautoescape %}', {"a": ["a&b", "x"], "b": [mark_safe("a&b"), "x"]}, "a&b a&b"), 'filter-last01': ('{{ a|last }} {{ b|last }}', {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}, "a&b a&b"), 'filter-last02': ('{% autoescape off %}{{ a|last }} {{ b|last }}{% endautoescape %}', {"a": ["x", "a&b"], "b": ["x", mark_safe("a&b")]}, "a&b a&b"), 'filter-random01': ('{{ a|random }} {{ b|random }}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), 'filter-random02': ('{% autoescape off %}{{ a|random }} {{ b|random }}{% endautoescape %}', {"a": ["a&b", "a&b"], "b": [mark_safe("a&b"), mark_safe("a&b")]}, "a&b a&b"), 'filter-slice01': ('{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), 'filter-slice02': ('{% autoescape off %}{{ a|slice:"1:3" }} {{ b|slice:"1:3" }}{% endautoescape %}', {"a": "a&b", "b": mark_safe("a&b")}, "&b &b"), 'filter-unordered_list01': ('{{ a|unordered_list }}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 'filter-unordered_list02': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 'filter-unordered_list03': ('{{ a|unordered_list }}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 'filter-unordered_list04': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [[mark_safe("<y"), []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), 'filter-unordered_list05': ('{% autoescape off %}{{ a|unordered_list }}{% endautoescape %}', {"a": ["x>", [["<y", []]]]}, "\t<li>x>\n\t<ul>\n\t\t<li><y</li>\n\t</ul>\n\t</li>"), # Literal string arguments to the default filter are always treated as # safe strings, regardless of the auto-escaping state. # # Note: we have to use {"a": ""} here, otherwise the invalid template # variable string interferes with the test result. 'filter-default01': ('{{ a|default:"x<" }}', {"a": ""}, "x<"), 'filter-default02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": ""}, "x<"), 'filter-default03': ('{{ a|default:"x<" }}', {"a": mark_safe("x>")}, "x>"), 'filter-default04': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": mark_safe("x>")}, "x>"), 'filter-default_if_none01': ('{{ a|default:"x<" }}', {"a": None}, "x<"), 'filter-default_if_none02': ('{% autoescape off %}{{ a|default:"x<" }}{% endautoescape %}', {"a": None}, "x<"), 'filter-phone2numeric01': ('{{ a|phone2numeric }} {{ b|phone2numeric }}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), 'filter-phone2numeric02': ('{% autoescape off %}{{ a|phone2numeric }} {{ b|phone2numeric }}{% endautoescape %}', {"a": "<1-800-call-me>", "b": mark_safe("<1-800-call-me>") }, "<1-800-2255-63> <1-800-2255-63>"), 'filter-phone2numeric03': ('{{ a|phone2numeric }}', {"a": "How razorback-jumping frogs can level six piqued gymnasts!"}, "469 729672225-5867464 37647 226 53835 749 747833 49662787!"), # Ensure iriencode keeps safe strings: 'filter-iriencode01': ('{{ url|iriencode }}', {'url': '?test=1&me=2'}, '?test=1&me=2'), 'filter-iriencode02': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': '?test=1&me=2'}, '?test=1&me=2'), 'filter-iriencode03': ('{{ url|iriencode }}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'), 'filter-iriencode04': ('{% autoescape off %}{{ url|iriencode }}{% endautoescape %}', {'url': mark_safe('?test=1&me=2')}, '?test=1&me=2'), # urlencode 'filter-urlencode01': ('{{ url|urlencode }}', {'url': '/test&"/me?/'}, '/test%26%22/me%3F/'), 'filter-urlencode02': ('/test/{{ urlbit|urlencode:"" }}/', {'urlbit': 'escape/slash'}, '/test/escape%2Fslash/'), # Chaining a bunch of safeness-preserving filters should not alter # the safe status either way. 'chaining01': ('{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), 'chaining02': ('{% autoescape off %}{{ a|capfirst|center:"7" }}.{{ b|capfirst|center:"7" }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, " A < b . A < b "), # Using a filter that forces a string back to unsafe: 'chaining03': ('{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), 'chaining04': ('{% autoescape off %}{{ a|cut:"b"|capfirst }}.{{ b|cut:"b"|capfirst }}{% endautoescape %}', {"a": "a < b", "b": mark_safe("a < b")}, "A < .A < "), # Using a filter that forces safeness does not lead to double-escaping 'chaining05': ('{{ a|escape|capfirst }}', {"a": "a < b"}, "A < b"), 'chaining06': ('{% autoescape off %}{{ a|escape|capfirst }}{% endautoescape %}', {"a": "a < b"}, "A < b"), # Force to safe, then back (also showing why using force_escape too # early in a chain can lead to unexpected results). 'chaining07': ('{{ a|force_escape|cut:";" }}', {"a": "a < b"}, "a &lt b"), 'chaining08': ('{% autoescape off %}{{ a|force_escape|cut:";" }}{% endautoescape %}', {"a": "a < b"}, "a < b"), 'chaining09': ('{{ a|cut:";"|force_escape }}', {"a": "a < b"}, "a < b"), 'chaining10': ('{% autoescape off %}{{ a|cut:";"|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < b"), 'chaining11': ('{{ a|cut:"b"|safe }}', {"a": "a < b"}, "a < "), 'chaining12': ('{% autoescape off %}{{ a|cut:"b"|safe }}{% endautoescape %}', {"a": "a < b"}, "a < "), 'chaining13': ('{{ a|safe|force_escape }}', {"a": "a < b"}, "a < b"), 'chaining14': ('{% autoescape off %}{{ a|safe|force_escape }}{% endautoescape %}', {"a": "a < b"}, "a < b"), # Filters decorated with stringfilter still respect is_safe. 'autoescape-stringfilter01': (r'{{ unsafe|capfirst }}', {'unsafe': UnsafeClass()}, 'You & me'), 'autoescape-stringfilter02': (r'{% autoescape off %}{{ unsafe|capfirst }}{% endautoescape %}', {'unsafe': UnsafeClass()}, 'You & me'), 'autoescape-stringfilter03': (r'{{ safe|capfirst }}', {'safe': SafeClass()}, 'You > me'), 'autoescape-stringfilter04': (r'{% autoescape off %}{{ safe|capfirst }}{% endautoescape %}', {'safe': SafeClass()}, 'You > me'), 'escapejs01': (r'{{ a|escapejs }}', {'a': 'testing\r\njavascript \'string" <b>escaping</b>'}, 'testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E'), 'escapejs02': (r'{% autoescape off %}{{ a|escapejs }}{% endautoescape %}', {'a': 'testing\r\njavascript \'string" <b>escaping</b>'}, 'testing\\u000D\\u000Ajavascript \\u0027string\\u0022 \\u003Cb\\u003Eescaping\\u003C/b\\u003E'), # length filter. 'length01': ('{{ list|length }}', {'list': ['4', None, True, {}]}, '4'), 'length02': ('{{ list|length }}', {'list': []}, '0'), 'length03': ('{{ string|length }}', {'string': ''}, '0'), 'length04': ('{{ string|length }}', {'string': 'django'}, '6'), # Invalid uses that should fail silently. 'length05': ('{{ int|length }}', {'int': 7}, ''), 'length06': ('{{ None|length }}', {'None': None}, ''), # length_is filter. 'length_is01': ('{% if some_list|length_is:"4" %}Four{% endif %}', {'some_list': ['4', None, True, {}]}, 'Four'), 'length_is02': ('{% if some_list|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'some_list': ['4', None, True, {}, 17]}, 'Not Four'), 'length_is03': ('{% if mystring|length_is:"4" %}Four{% endif %}', {'mystring': 'word'}, 'Four'), 'length_is04': ('{% if mystring|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'mystring': 'Python'}, 'Not Four'), 'length_is05': ('{% if mystring|length_is:"4" %}Four{% else %}Not Four{% endif %}', {'mystring': ''}, 'Not Four'), 'length_is06': ('{% with var|length as my_length %}{{ my_length }}{% endwith %}', {'var': 'django'}, '6'), # Boolean return value from length_is should not be coerced to a string 'length_is07': (r'{% if "X"|length_is:0 %}Length is 0{% else %}Length not 0{% endif %}', {}, 'Length not 0'), 'length_is08': (r'{% if "X"|length_is:1 %}Length is 1{% else %}Length not 1{% endif %}', {}, 'Length is 1'), # Invalid uses that should fail silently. 'length_is09': ('{{ var|length_is:"fish" }}', {'var': 'django'}, ''), 'length_is10': ('{{ int|length_is:"1" }}', {'int': 7}, ''), 'length_is11': ('{{ none|length_is:"1" }}', {'none': None}, ''), 'join01': (r'{{ a|join:", " }}', {'a': ['alpha', 'beta & me']}, 'alpha, beta & me'), 'join02': (r'{% autoescape off %}{{ a|join:", " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha, beta & me'), 'join03': (r'{{ a|join:" & " }}', {'a': ['alpha', 'beta & me']}, 'alpha & beta & me'), 'join04': (r'{% autoescape off %}{{ a|join:" & " }}{% endautoescape %}', {'a': ['alpha', 'beta & me']}, 'alpha & beta & me'), # Test that joining with unsafe joiners don't result in unsafe strings (#11377) 'join05': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': ' & '}, 'alpha & beta & me'), 'join06': (r'{{ a|join:var }}', {'a': ['alpha', 'beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta & me'), 'join07': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': ' & ' }, 'alpha & beta & me'), 'join08': (r'{{ a|join:var|lower }}', {'a': ['Alpha', 'Beta & me'], 'var': mark_safe(' & ')}, 'alpha & beta & me'), 'date01': (r'{{ d|date:"m" }}', {'d': datetime(2008, 1, 1)}, '01'), 'date02': (r'{{ d|date }}', {'d': datetime(2008, 1, 1)}, 'Jan. 1, 2008'), #Ticket 9520: Make sure |date doesn't blow up on non-dates 'date03': (r'{{ d|date:"m" }}', {'d': 'fail_string'}, ''), # ISO date formats 'date04': (r'{{ d|date:"o" }}', {'d': datetime(2008, 12, 29)}, '2009'), 'date05': (r'{{ d|date:"o" }}', {'d': datetime(2010, 1, 3)}, '2009'), # Timezone name 'date06': (r'{{ d|date:"e" }}', {'d': datetime(2009, 3, 12, tzinfo=FixedOffset(30))}, '+0030'), 'date07': (r'{{ d|date:"e" }}', {'d': datetime(2009, 3, 12)}, ''), # Ticket 19370: Make sure |date doesn't blow up on a midnight time object 'date08': (r'{{ t|date:"H:i" }}', {'t': time(0, 1)}, '00:01'), 'date09': (r'{{ t|date:"H:i" }}', {'t': time(0, 0)}, '00:00'), # Tests for #11687 and #16676 'add01': (r'{{ i|add:"5" }}', {'i': 2000}, '2005'), 'add02': (r'{{ i|add:"napis" }}', {'i': 2000}, ''), 'add03': (r'{{ i|add:16 }}', {'i': 'not_an_int'}, ''), 'add04': (r'{{ i|add:"16" }}', {'i': 'not_an_int'}, 'not_an_int16'), 'add05': (r'{{ l1|add:l2 }}', {'l1': [1, 2], 'l2': [3, 4]}, '[1, 2, 3, 4]'), 'add06': (r'{{ t1|add:t2 }}', {'t1': (3, 4), 't2': (1, 2)}, '(3, 4, 1, 2)'), 'add07': (r'{{ d|add:t }}', {'d': date(2000, 1, 1), 't': timedelta(10)}, 'Jan. 11, 2000'), }
def test_basic_distinct_on(self): """QuerySet.distinct('field', ...) works""" # (qset, expected) tuples qsets = ( ( Staff.objects.distinct().order_by('name'), ['<Staff: p1>', '<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], ), ( Staff.objects.distinct('name').order_by('name'), ['<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], ), ( Staff.objects.distinct('organisation').order_by('organisation', 'name'), ['<Staff: p1>', '<Staff: p1>'], ), ( Staff.objects.distinct('name', 'organisation').order_by('name', 'organisation'), ['<Staff: p1>', '<Staff: p1>', '<Staff: p2>', '<Staff: p3>'], ), ( Celebrity.objects.filter(fan__in=[self.fan1, self.fan2, self.fan3]).distinct('name').order_by('name'), ['<Celebrity: c1>', '<Celebrity: c2>'], ), # Does combining querysets work? ( (Celebrity.objects.filter(fan__in=[self.fan1, self.fan2]). distinct('name').order_by('name') | Celebrity.objects.filter(fan__in=[self.fan3]). distinct('name').order_by('name')), ['<Celebrity: c1>', '<Celebrity: c2>'], ), ( StaffTag.objects.distinct('staff', 'tag'), ['<StaffTag: t1 -> p1>'], ), ( Tag.objects.order_by('parent__pk', 'pk').distinct('parent'), ['<Tag: t2>', '<Tag: t4>', '<Tag: t1>'], ), ( StaffTag.objects.select_related('staff').distinct('staff__name').order_by('staff__name'), ['<StaffTag: t1 -> p1>'], ), # Fetch the alphabetically first coworker for each worker ( (Staff.objects.distinct('id').order_by('id', 'coworkers__name'). values_list('id', 'coworkers__name')), [str_prefix("(1, %(_)s'p2')"), str_prefix("(2, %(_)s'p1')"), str_prefix("(3, %(_)s'p1')"), "(4, None)"] ), ) for qset, expected in qsets: self.assertQuerysetEqual(qset, expected) self.assertEqual(qset.count(), len(expected)) # Combining queries with different distinct_fields is not allowed. base_qs = Celebrity.objects.all() with self.assertRaisesMessage(AssertionError, "Cannot combine queries with different distinct fields."): base_qs.distinct('id') & base_qs.distinct('name') # Test join unreffing c1 = Celebrity.objects.distinct('greatest_fan__id', 'greatest_fan__fan_of') self.assertIn('OUTER JOIN', str(c1.query)) c2 = c1.distinct('pk') self.assertNotIn('OUTER JOIN', str(c2.query))