diff --git a/tests/filters/test_datetime_filters.py b/tests/filters/test_datetime_filters.py
index e6f9cd1e..e2a50c5e 100644
--- a/tests/filters/test_datetime_filters.py
+++ b/tests/filters/test_datetime_filters.py
@@ -140,6 +140,29 @@ def test_datetime_filter_syntax_variations(self):
 		response = self.client.get('/caretaker/', data={'.last_seen:range': '2017-03-23T00:00:00Z,2017-03-24', 'order_by': 'last_seen'})
 		self.assertEqual(response.status_code, 418)
 
+	def test_datetime_filter_syntax_variations_with_chained_qualifiers(self):
+		# Implicitly we add T23:59:59Z here to make this correct.
+		response = self.client.get(
+			'/caretaker/', data={'.last_seen:date:gt': '2017-03-23', 'order_by': 'last_seen'})
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		self.assertEqual(1, len(result['data']))
+
+		# Same as above, but to the range start we add T00:00:00Z
+		response = self.client.get(
+			'/caretaker/', data={'.last_seen:date:range': '2017-03-23,2017-03-23', 'order_by': 'last_seen'})
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		self.assertEqual(1, len(result['data']))
+
+		# Just a sanity check
+		response = self.client.get('/caretaker/', data={'.last_seen:date:range': '2017-03-23,2017-03-24', 'order_by': 'last_seen'})
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		self.assertEqual(2, len(result['data']))
 
 	def test_datetime_filter_syntax_errors_cause_error_response(self):
 		response = self.client.get('/caretaker/', data={'.last_seen': '1838-05'})
diff --git a/tests/filters/test_text_filters.py b/tests/filters/test_text_filters.py
new file mode 100644
index 00000000..3a482cbb
--- /dev/null
+++ b/tests/filters/test_text_filters.py
@@ -0,0 +1,460 @@
+import unittest
+import os
+from django.test import TestCase, Client
+from binder.json import jsonloads
+from django.contrib.auth.models import User
+
+from ..testapp.models import Caretaker
+
+class TextFiltersTest(TestCase):
+	def setUp(self):
+		super().setUp()
+		u = User(username='testuser', is_active=True, is_superuser=True)
+		u.set_password('test')
+		u.save()
+		self.client = Client()
+		r = self.client.login(username='testuser', password='test')
+		self.assertTrue(r)
+
+		Caretaker(name='Peter').save()
+		Caretaker(name='Stefan').save()
+
+
+	def test_text_filter_exact_match(self):
+		response = self.client.get('/caretaker/', data={'.name': 'Stefan'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name': 'Stefa'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		self.assertEqual(0, len(result['data']))
+
+	def test_text_filter_iexact(self):
+		response = self.client.get('/caretaker/', data={'.name:iexact': 'stefan'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name:iexact': 'sTEfaN'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+	def test_text_filter_contains(self):
+		response = self.client.get('/caretaker/', data={'.name:contains': 'stef'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get('/caretaker/', data={'.name:contains': 'Stef'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name:contains': 'e'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(2, len(result['data']))
+
+	def test_text_filter_icontains(self):
+		response = self.client.get('/caretaker/', data={'.name:icontains': 'stefi'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get('/caretaker/', data={'.name:icontains': 'sTEf'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name:icontains': 'E'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(2, len(result['data']))
+
+	def test_text_filter_startswith(self):
+		response = self.client.get('/caretaker/', data={'.name:startswith': 'tef'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get('/caretaker/', data={'.name:startswith': 'Stef'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name:startswith': 'ste'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+	def test_text_filter_istartswith(self):
+		response = self.client.get('/caretaker/', data={'.name:istartswith': 'tef'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get('/caretaker/', data={'.name:istartswith': 'stef'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name:istartswith': 'sTEF'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+	def test_text_filter_endswith(self):
+		response = self.client.get('/caretaker/', data={'.name:endswith': 'efa'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get('/caretaker/', data={'.name:endswith': 'efan'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name:endswith': 'efaN'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+	def test_text_filter_iendswith(self):
+		response = self.client.get('/caretaker/', data={'.name:iendswith': 'efa'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get('/caretaker/', data={'.name:iendswith': 'EfAn'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name:iendswith': 'efaN'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+	# Unaccent extension tests
+	@unittest.skipIf(
+		os.environ.get('BINDER_TEST_MYSQL', '0') != '0',
+		"Only available with PostgreSQL"
+	)
+	def test_text_filter_exact_match_unaccent(self):
+		response = self.client.get('/caretaker/', data={'.name:unaccent': 'Śtefan'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get('/caretaker/', data={'.name:unaccent': 'Śtefa'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		self.assertEqual(0, len(result['data']))
+
+	@unittest.skipIf(
+		os.environ.get('BINDER_TEST_MYSQL', '0') != '0',
+		"Only available with PostgreSQL"
+	)
+	def test_text_filter_iexact_unaccent(self):
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:iexact': 'stęfan'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:iexact': 'sTĘfaN'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+	@unittest.skipIf(
+		os.environ.get('BINDER_TEST_MYSQL', '0') != '0',
+		"Only available with PostgreSQL"
+	)
+	def test_text_filter_contains(self):
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:contains': 'stęf'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:contains': 'Stęf'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:contains': 'ę'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(2, len(result['data']))
+
+	@unittest.skipIf(
+		os.environ.get('BINDER_TEST_MYSQL', '0') != '0',
+		"Only available with PostgreSQL"
+	)
+	def test_text_filter_icontains(self):
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:icontains': 'stęfi'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:icontains': 'sTĘf'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:icontains': 'Ę'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(2, len(result['data']))
+
+	@unittest.skipIf(
+		os.environ.get('BINDER_TEST_MYSQL', '0') != '0',
+		"Only available with PostgreSQL"
+	)
+	def test_text_filter_startswith(self):
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:startswith': 'tęf'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:startswith': 'Śtęf'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:startswith': 'śtę'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+	@unittest.skipIf(
+		os.environ.get('BINDER_TEST_MYSQL', '0') != '0',
+		"Only available with PostgreSQL"
+	)
+	def test_text_filter_istartswith(self):
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:istartswith': 'tęf'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:istartswith': 'stęf'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:istartswith': 'sTĘF'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+	@unittest.skipIf(
+		os.environ.get('BINDER_TEST_MYSQL', '0') != '0',
+		"Only available with PostgreSQL"
+	)
+	def test_text_filter_endswith(self):
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:endswith': 'efą'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:endswith': 'efań'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:endswith': 'efaŃ'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+	@unittest.skipIf(
+		os.environ.get('BINDER_TEST_MYSQL', '0') != '0',
+		"Only available with PostgreSQL"
+	)
+	def test_text_filter_iendswith(self):
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:iendswith': 'ęfa'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(0, len(result['data']))
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:iendswith': 'EfĄn'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])
+
+		response = self.client.get(
+			'/caretaker/', data={'.name:unaccent:iendswith': 'efąN'})
+
+		self.assertEqual(response.status_code, 200)
+
+		result = jsonloads(response.content)
+		print(result)
+		self.assertEqual(1, len(result['data']))
+		self.assertEqual('Stefan', result['data'][0]['name'])