Skip to content

Commit

Permalink
Allow annotations in browse views (#81)
Browse files Browse the repository at this point in the history
* Render/sort by annotations in browse views
* Update pre-commit hooks
  • Loading branch information
Jdsleppy authored Jun 1, 2022
1 parent dab2820 commit 22c1c17
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 4 deletions.
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.3.0
rev: v4.2.0
hooks:
- id: check-added-large-files
- id: check-case-conflict
Expand All @@ -20,11 +20,11 @@ repos:
- id: black

- repo: https://github.com/pycqa/isort
rev: 5.6.4
rev: 5.10.1
hooks:
- id: isort

- repo: https://gitlab.com/pycqa/flake8
rev: master
rev: 3.9.2
hooks:
- id: flake8
12 changes: 11 additions & 1 deletion bread/bread.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,10 @@ def success_url(self):
# The individual view classes we'll use and customize in the
# omnibus class below:
class BrowseView(BreadViewMixin, ListView):
# Include in any colspec to allow Django ORM annotations.
# Will skip init-time validation for that column.
is_annotation = object()

# Configurable:
columns = []
filterset = None # Class
Expand All @@ -211,7 +215,9 @@ def __init__(self, *args, **kwargs):
if fieldspec:
try:
# In Django 3.1.13+, order_by args are validated here
queryset = self.model.objects.order_by(fieldspec)
queryset = (
super(BrowseView, self).get_queryset().order_by(fieldspec)
)
# Force Django < 3.1.13 to build the query here so it will validate the order_by args
str(queryset.query)
except FieldError:
Expand Down Expand Up @@ -556,6 +562,10 @@ def __init__(self):

if self.browse_view.columns:
for colspec in self.browse_view.columns:
if any(entry == self.browse_view.is_annotation for entry in colspec):
# this column renders an annotation which is not present on the
# model class until querytime.
continue
column = colspec[1]
validate_fieldspec(self.model, column)

Expand Down
60 changes: 60 additions & 0 deletions tests/test_browse.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import json
from unittest.mock import patch

from django.db.models.functions import Upper
from django.urls import reverse

from bread.bread import BrowseView
from tests.models import BreadTestModel

from .base import BreadTestCase
from .factories import BreadTestModelFactory
Expand Down Expand Up @@ -317,3 +319,61 @@ def test_not_sorting_on_column(self):
self.assertEqual(200, rsp.status_code)
rsp.render()
self.assertEqual([], json.loads(rsp.context_data["valid_sorting_columns_json"]))


class AnnotationsBrowseTest(BreadTestCase):
class BrowseClass(BrowseView):
queryset = BreadTestModel.objects.annotate(loud_name=Upper("name"))
columns = [
("Name", "name"),
("Loud Name", "loud_name", BrowseView.is_annotation, "loud_name"),
]

extra_bread_attributes = {
"browse_view": BrowseClass,
}

def test_rendering_annotation(self):
self.set_urls(self.bread)
items = [BreadTestModelFactory() for __ in range(5)]
self.assertTrue(
any(item for item in items if item.name != item.name.upper()),
"all item names already uppercase, this test will not be meaningful",
)
self.give_permission("browse")

url = reverse(self.bread.get_url_name("browse"))
request = self.request_factory.get(url)
request.user = self.user
rsp = self.bread.get_browse_view()(request)
self.assertEqual(200, rsp.status_code)

rsp.render()
body = rsp.content.decode("utf-8")
for item in items:
self.assertIn(item.name, body)
self.assertIn(item.name.upper(), body)

def test_sort_by_annotation(self):
self.set_urls(self.bread)
self.give_permission("browse")
d = BreadTestModelFactory(name="denise")
a = BreadTestModelFactory(name="alice")
e = BreadTestModelFactory(name="elise")
b = BreadTestModelFactory(name="bernice")
c = BreadTestModelFactory(name="clarice")

url = reverse(self.bread.get_url_name("browse")) + "?o=1"
request = self.request_factory.get(url)
request.user = self.user
rsp = self.bread.get_browse_view()(request)
self.assertEqual(200, rsp.status_code)

rsp.render()
self.assertListEqual(
[0, 1],
json.loads(rsp.context_data["valid_sorting_columns_json"]),
"bread did not consider our annotation column sortable",
)
results = rsp.context_data["object_list"]
self.assertListEqual([a, b, c, d, e], list(results), "results were not sorted")

0 comments on commit 22c1c17

Please sign in to comment.