Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 4 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,14 @@ 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.

To only run a selection of the tests, use the `-s` flag like `./setup.py test -s tests.test_some_specific_test`.

## 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.
9 changes: 2 additions & 7 deletions binder/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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']

Expand Down
4 changes: 3 additions & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 3 additions & 3 deletions tests/plugins/test_csvexport.py
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
25 changes: 25 additions & 0 deletions tests/test_filterable_relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
2 changes: 1 addition & 1 deletion tests/testapp/models/caretaker.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())
)