diff --git a/README.md b/README.md index da9d2020..094ce6f7 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,9 @@ Code Yellow backend framework for SPA webapps with REST-like API. ## Running the tests -There are two ways to run the tests: -- Run directly `./setup.py test` (requires you to have python3 and postgres installed) -- Run with docker `docker-compose run binder ./setup.py test` - - Access the test database directly by with `docker-compose run db psql -h db -U postgres`. - - It may be possible to recreate the test database (for example when you added/changed models). One way of achieving this is to just remove all the docker images that were build `docker-compose rm`. The database will be created during the setup in `tests/__init__.py`. +Run with docker `docker compose run binder ./setup.py test` (but you may need to run `docker compose build db binder` first) +- Access the test database directly by with `docker compose run db psql -h db -U postgres`. +- It may be possible to recreate the test database (for example when you added/changed models). One way of achieving this is to just remove all the docker images that were build `docker compose rm`. The database will be created during the setup in `tests/__init__.py`. The tests are set up in such a way that there is no need to keep migration files. The setup procedure in `tests/__init__.py` handles the preparation of the database by directly calling some build-in Django commands. @@ -21,19 +19,4 @@ To only run a selection of the tests, use the `-s` flag like `./setup.py test -s ## MySQL support -MySQL is supported, but only with the goal to replace it with -PostgreSQL. This means it has a few limitations: - -- `where` filtering on `with` relations is not supported. -- Only integer primary keys are supported. -- When fetching large number of records using `with` or the ids are big, be sure to increase `GROUP_CONCAT` max string length by: - -``` -DATABASES = { - 'default': { - 'OPTIONS': { - 'init_command': 'SET SESSION group_concat_max_len = 1000000', - }, - }, -} -``` +MySQL was supported at some point, but not anymore I guess. diff --git a/binder/views.py b/binder/views.py index 21c3d076..0aa2e3a5 100644 --- a/binder/views.py +++ b/binder/views.py @@ -1289,18 +1289,13 @@ def _filter_field(self, field_name, qualifier, value, invert, request, include_a raise FieldDoesNotExist() field = self.model._meta.get_field(field_name) except FieldDoesNotExist: - rel = partial and '.'.join(partial[:-2].split('__')) - annotations = self.annotations(request, {'': include_annotations.get(rel)}) + annotations = self.annotations(request, {'': include_annotations.get('')}) if field_name not in annotations: raise BinderRequestError('Unknown field in filter: {{{}}}.{{{}}}.'.format(self.model.__name__, field_name)) if partial: # NOTE: This creates a subquery; try to avoid this! qs = annotate(self.model.objects.all(), request, annotations) - qs = qs.filter(self._filter_field(field_name, qualifier, value, invert, request, { - rel_[len(rel) + 1:]: annotations - for rel_, annotations in include_annotations.items() - if rel_ == rel or rel_.startswith(rel + '.') - })) + qs = qs.filter(self._filter_field(field_name, qualifier, value, invert, request, include_annotations)) return Q(**{partial + 'in': qs}) field = annotations[field_name]['field'] diff --git a/docker-compose.yml b/docker-compose.yml index b0aeb0c7..efbdbbe6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,8 @@ services: db: - image: postgres:11.5 + image: postgres:13.21 + environment: + - POSTGRES_HOST_AUTH_METHOD=trust # Insecure, but fine for just for running tests. binder: build: . command: tail -f /dev/null diff --git a/tests/plugins/test_csvexport.py b/tests/plugins/test_csvexport.py index 3f04b693..ed1fd37b 100644 --- a/tests/plugins/test_csvexport.py +++ b/tests/plugins/test_csvexport.py @@ -121,9 +121,9 @@ def test_download_extra_params(self): self.assertEqual(data[0], ['ID', 'Name', 'Scary']) # All other data needs to be ordered using the default ordering (by id, asc) - self.assertEqual(data[1], [str(caretaker_1.id), 'Foo', 'boo!']) - self.assertEqual(data[2], [str(caretaker_2.id), 'Bar', 'boo!']) - self.assertEqual(data[3], [str(caretaker_3.id), 'Baz', 'boo!']) + self.assertEqual(data[1], [str(caretaker_1.id), 'Foo', 'boo! Foo']) + self.assertEqual(data[2], [str(caretaker_2.id), 'Bar', 'boo! Bar']) + self.assertEqual(data[3], [str(caretaker_3.id), 'Baz', 'boo! Baz']) def test_context_aware_download_xlsx(self): response = self.client.get('/picture/download/?response_type=xlsx') diff --git a/tests/test_filterable_relations.py b/tests/test_filterable_relations.py index 1e17fec5..6df464ff 100644 --- a/tests/test_filterable_relations.py +++ b/tests/test_filterable_relations.py @@ -456,3 +456,28 @@ def test_where_ignores_commas_in_parens(self): }, EXTRA(): None, }) + + def test_filter_on_relation_with_include_annotations_using_where(self): + zoo = Zoo.objects.create(name='Apenheul') + + carl = Caretaker.objects.create(name="carl") + tim = Caretaker.objects.create(name="tim") + + Animal.objects.create(name='Harambe', zoo=zoo, caretaker=carl) + Animal.objects.create(name='Bokito', zoo=zoo, caretaker=tim) + Animal.objects.create(name='Rafiki', zoo=zoo) + + res = self.client.get('/animal/', data={ + 'with': 'caretaker', + 'include_annotations': 'caretaker.scary', + 'where': 'caretaker(scary=boo! tim)', + }) + self.assertEqual(res.status_code, 200) + data = jsonloads(res.content) + print(data) + + self.assertEqual(1, len(data['with'])) + self.assertEqual(1, len(data['with']['caretaker'])) + with_tim = data['with']['caretaker'][0] + self.assertEqual('tim', with_tim['name']) + self.assertEqual('boo! tim', with_tim['scary']) diff --git a/tests/testapp/models/caretaker.py b/tests/testapp/models/caretaker.py index 9e28a1e1..ab63a454 100644 --- a/tests/testapp/models/caretaker.py +++ b/tests/testapp/models/caretaker.py @@ -25,5 +25,5 @@ class Annotations: bsn = F('ssn') # simple alias last_present = F('last_seen') scary = OptionalAnnotation( - Value('boo!', output_field=models.TextField()) + models.functions.Concat(Value('boo! '), 'name', output_field=models.TextField()) )