Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check prepare had been called when project is called #90

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
6 changes: 4 additions & 2 deletions django_readers/specs.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from django_readers import pairs
from django_readers.utils import queries_disabled
from django_readers.utils import with_prepared_checker, with_queries_disabled


def process_item(item):
Expand All @@ -16,7 +16,9 @@ def process_item(item):


def process(spec):
return queries_disabled(pairs.combine(*(process_item(item) for item in spec)))
return with_prepared_checker(
with_queries_disabled(pairs.combine(*(process_item(item) for item in spec)))
)


def relationship(name, relationship_spec, to_attr=None):
Expand Down
20 changes: 19 additions & 1 deletion django_readers/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,30 @@ def none_safe_get_attr(obj):
return none_safe_get_attr


def queries_disabled(pair):
def with_queries_disabled(pair):
prepare, project = pair
decorator = zen_queries.queries_disabled() if zen_queries else lambda fn: fn
return decorator(prepare), decorator(project)


def with_prepared_checker(pair):
prepare, project = pair

is_prepared = False

def wrapped_prepare(qs):
nonlocal is_prepared
is_prepared = True
return prepare(qs)

def wrapped_project(qs):
if not is_prepared:
raise Exception("QuerySet must be prepared before projection")
Copy link
Contributor Author

@pmg-certn pmg-certn Sep 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course I'm not checking that the queryset they called prepare on is actually the same one they're calling project on...

return project(qs)

return (wrapped_prepare, wrapped_project)


class SpecVisitor:
def visit(self, spec):
return [self.visit_item(item) for item in spec]
Expand Down
23 changes: 23 additions & 0 deletions tests/test_rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,29 @@ def test_detail(self):
},
)

def test_override_getqueryset_must_call_prepare(self):
Widget.objects.create()

class WidgetListView(SpecMixin, ListAPIView):
spec = [
"name",
]

def get_queryset(self):
queryset = Widget.objects.all()
# Should call self.prepare(queryset) here
return queryset

request = APIRequestFactory().get("/")
view = WidgetListView.as_view()

with self.assertRaises(Exception) as cm:
view(request)

self.assertEqual(
str(cm.exception), "QuerySet must be prepared before projection"
)


class SpecToSerializerClassTestCase(TestCase):
def test_basic_spec(self):
Expand Down