Skip to content

Commit

Permalink
feat: add ?tags and ?content filters to wagtail pages API
Browse files Browse the repository at this point in the history
  • Loading branch information
ngurenyaga committed Nov 23, 2021
1 parent cf31375 commit aae4710
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 12 deletions.
7 changes: 3 additions & 4 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
from rest_framework.schemas import get_schema_view
from wagtail.admin import urls as wagtailadmin_urls
from wagtail.api.v2.router import WagtailAPIRouter
from wagtail.api.v2.views import PagesAPIViewSet
from wagtail.core import urls as wagtail_urls
from wagtail.documents import urls as wagtaildocs_urls
from wagtail.documents.api.v2.views import DocumentsAPIViewSet
from wagtail.images.api.v2.views import ImagesAPIViewSet
from wagtail.images.views.serve import ServeView

from mycarehub.common.views import AboutView, HomeView
from mycarehub.content.views import CustomPageAPIViewset

from .graphql_auth import DRFAuthenticatedGraphQLView

Expand Down Expand Up @@ -111,14 +111,13 @@
urlpatterns = [path("__debug__/", include(debug_toolbar.urls))] + urlpatterns


# content management via wagtail
wagtail_api_router = WagtailAPIRouter("wagtailapi")
wagtail_api_router.register_endpoint("pages", PagesAPIViewSet)
wagtail_api_router.register_endpoint("pages", CustomPageAPIViewset)
wagtail_api_router.register_endpoint("images", ImagesAPIViewSet)
wagtail_api_router.register_endpoint("documents", DocumentsAPIViewSet)

urlpatterns += [
path("contentapi/", wagtail_api_router.urls),
path("contentapi/", wagtail_api_router.urls, name="wagtail_content_api"),
path("admin/", include(wagtailadmin_urls)),
path("documents/", include(wagtaildocs_urls)),
re_path(
Expand Down
7 changes: 7 additions & 0 deletions mycarehub/clients/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from django.db.models.enums import TextChoices
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from wagtail.snippets.models import register_snippet

from mycarehub.common.models import AbstractBase
from mycarehub.common.models.common_models import Address, Contact, Facility
Expand All @@ -15,6 +16,7 @@ class FlavourChoices(TextChoices):
CONSUMER = "CONSUMER", _("CONSUMER")


@register_snippet
class Identifier(AbstractBase):
class IdentifierType(models.TextChoices):
CCC = "CCC", _("Comprehensive Care Clinic Number")
Expand Down Expand Up @@ -44,6 +46,7 @@ def __str__(self):
return f"{self.identifier_value} ({self.identifier_type}, {self.identifier_use})"


@register_snippet
class SecurityQuestion(AbstractBase):
class ResponseType(models.TextChoices):
TEXT = "TEXT", _("Text Response")
Expand All @@ -58,6 +61,7 @@ class ResponseType(models.TextChoices):
flavour = models.CharField(choices=FlavourChoices.choices, max_length=32, null=True)


@register_snippet
class SecurityQuestionResponse(AbstractBase):

user = models.ForeignKey(get_user_model(), on_delete=models.PROTECT)
Expand All @@ -73,6 +77,7 @@ class Meta:
)


@register_snippet
class RelatedPerson(AbstractBase):
class RelationshipType(TextChoices):
SPOUSE = "SPOUSE", _("Spouse")
Expand Down Expand Up @@ -102,6 +107,7 @@ def __str__(self):
return f"{self.first_name} {self.other_name} {self.last_name} ({self.relationship_type})"


@register_snippet
class Client(AbstractBase):
"""
A client is a patient or non-professional end user.
Expand Down Expand Up @@ -245,6 +251,7 @@ def __str__(self):
)


@register_snippet
class ClientFacility(AbstractBase):
"""
ClientFacility tracks a client's assigned facilities and changes over time.
Expand Down
5 changes: 2 additions & 3 deletions mycarehub/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,10 @@ def user_with_group(user, group_with_all_permissions) -> User:


@pytest.fixture
def request_with_user(rf, django_user_model):
def request_with_user(rf, django_user_model, user_with_all_permissions):
url = settings.ADMIN_URL + "/common/organisation/add/"
request = rf.get(url)
user = baker.make(django_user_model)
request.user = user
request.user = user_with_all_permissions
return request


Expand Down
38 changes: 38 additions & 0 deletions mycarehub/content/filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from django.db.models import Q
from rest_framework.filters import BaseFilterBackend

from mycarehub.content.models import ContentItem


class TagFilter(BaseFilterBackend):
"""
Implements the ?tag filter which returns only pages that match a tag.
This filter accepts a single tag.
"""

def filter_queryset(self, request, queryset, view):
query_params = request.query_params
tag = query_params.get("tag", "")

if tag and queryset.model is ContentItem:
queryset = queryset.filter(Q(tags__name=tag) | Q(tags__slug=tag))

return queryset


class CategoryFilter(BaseFilterBackend):
"""
Implements the ?category filter which returns only pages that match a
category ID.
This filter accepts a single category ID.
"""

def filter_queryset(self, request, queryset, view):
query_params = request.query_params
category_id = query_params.get("category", "")
if category_id and queryset.model is ContentItem:
queryset = queryset.filter(categories__id=category_id)

return queryset
5 changes: 0 additions & 5 deletions mycarehub/content/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,8 +563,3 @@ class Meta:
"page",
# "document",
)


# TODO Theme for articles, tags, blog home
# TODO Implement questionnaires and questionnaire responses...pre or post
# TODO API: query categories with at least one article...
155 changes: 155 additions & 0 deletions mycarehub/content/tests/test_filters.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import pytest
from django.urls import reverse
from django.utils import timezone
from model_bakery import baker
from rest_framework import status
from taggit.models import Tag
from wagtail.core.models import Page, Site

from mycarehub.content.models import Author, ContentItem, ContentItemCategory, ContentItemIndexPage
from mycarehub.home.models import HomePage

pytestmark = pytest.mark.django_db


@pytest.fixture
def content_item_with_tag_and_category(request_with_user):
# get the root page
site = Site.find_for_request(request_with_user)
assert site is not None
root = site.root_page
assert root is not None

# set up a home page
home = HomePage(
title="Home",
slug="index",
)
root.add_child(instance=home)
root.save_revision().publish()

# set up a content item index page
content_item_index = ContentItemIndexPage(
title="Content Item Index",
slug="articles",
intro="content",
)
home.add_child(instance=content_item_index)
home.save_revision().publish()

# set up a content item
author = baker.make(Author)
content_item = ContentItem(
title="An article",
slug="article-1",
intro="intro",
body="body",
item_type="ARTICLE",
date=timezone.now().date(),
author=author,
)
content_item_index.add_child(instance=content_item)
content_item_index.save_revision().publish()

# add a category
icon = baker.make("wagtailimages.Image", _create_files=True)
cat = baker.make(ContentItemCategory, id=999_999, name="a valid category", icon=icon)
content_item.categories.add(cat)
content_item.save()
assert ContentItem.objects.filter(categories__id=cat.pk).count() == 1

# add a tag
tag = baker.make(Tag, name="a-valid-tag") # expect slug a-valid-tag
content_item.tags.add(tag)
content_item.save()
assert ContentItem.objects.filter(tags__name="a-valid-tag").count() == 1

# sanity checks
assert (
Page.objects.all().public().live().count() >= 4
) # root, home, content index, content item
assert ContentItem.objects.all().public().live().count() == 1

# return the initialized content item
content_item.save_revision().publish()
return content_item


def test_tag_filter_found_tags(
content_item_with_tag_and_category,
request_with_user,
client,
):
assert content_item_with_tag_and_category is not None
assert content_item_with_tag_and_category.tags.count() == 1
assert content_item_with_tag_and_category.tags.filter(name="a-valid-tag").count() == 1

client.force_login(request_with_user.user)
url = (
reverse("wagtailapi:pages:listing")
+ "?type=content.ContentItem&fields=*&order=-first_published_at&tag=a-valid-tag"
)
response = client.get(url)
assert response.status_code == status.HTTP_200_OK
response_data = response.json()
assert response_data["meta"]["total_count"] == 1


def test_tag_filter_absent_tags(
content_item_with_tag_and_category,
request_with_user,
client,
):
assert content_item_with_tag_and_category is not None
assert content_item_with_tag_and_category.tags.count() == 1
assert content_item_with_tag_and_category.tags.filter(name="not-a-valid-tag").count() == 0

client.force_login(request_with_user.user)
url = (
reverse("wagtailapi:pages:listing")
+ "?type=content.ContentItem&fields=*&order=-first_published_at&tag=not-a-valid-tag"
)
response = client.get(url)
assert response.status_code == status.HTTP_200_OK
response_data = response.json()
assert response_data["meta"]["total_count"] == 0


def test_category_filter_found_categories(
content_item_with_tag_and_category,
request_with_user,
client,
):
assert content_item_with_tag_and_category is not None
assert content_item_with_tag_and_category.categories.count() == 1
assert content_item_with_tag_and_category.categories.filter(id=999_999).count() == 1

client.force_login(request_with_user.user)
url = (
reverse("wagtailapi:pages:listing")
+ "?type=content.ContentItem&fields=*&order=-first_published_at&category=999999"
)
response = client.get(url)
assert response.status_code == status.HTTP_200_OK
response_data = response.json()
assert response_data["meta"]["total_count"] == 1


def test_category_filter_absent_categories(
content_item_with_tag_and_category,
request_with_user,
client,
):
assert content_item_with_tag_and_category is not None
assert content_item_with_tag_and_category.categories.count() == 1
assert content_item_with_tag_and_category.categories.filter(id=999_999).count() == 1

client.force_login(request_with_user.user)
url = (
reverse("wagtailapi:pages:listing")
+ "?type=content.ContentItem&fields=*&order=-first_published_at&category=87654321"
)
response = client.get(url)
assert response.status_code == status.HTTP_200_OK
response_data = response.json()
assert response_data["meta"]["total_count"] == 0
30 changes: 30 additions & 0 deletions mycarehub/content/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from wagtail.api.v2.filters import (
AncestorOfFilter,
ChildOfFilter,
DescendantOfFilter,
FieldsFilter,
LocaleFilter,
OrderingFilter,
SearchFilter,
TranslationOfFilter,
)
from wagtail.api.v2.views import PagesAPIViewSet

from .filters import CategoryFilter, TagFilter


class CustomPageAPIViewset(PagesAPIViewSet):
# the order is important...wagtail filters come last
filter_backends = [
TagFilter,
CategoryFilter,
FieldsFilter,
ChildOfFilter,
AncestorOfFilter,
DescendantOfFilter,
OrderingFilter,
TranslationOfFilter,
LocaleFilter,
SearchFilter, # must be last
]
known_query_parameters = PagesAPIViewSet.known_query_parameters.union(["tag", "category"])
2 changes: 2 additions & 0 deletions mycarehub/users/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
from phonenumber_field.modelfields import PhoneNumberField
from wagtail.snippets.models import register_snippet

DEFAULT_ORG_CODE = 1

Expand Down Expand Up @@ -59,6 +60,7 @@ def default_organisation():
return uuid.UUID(settings.DEFAULT_ORG_ID)


@register_snippet
class TermsOfService(Model):
text = TextField()
valid_from = DateTimeField(default=timezone.now)
Expand Down

0 comments on commit aae4710

Please sign in to comment.