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

CHORE: Refactor RecordChooser to use built-in Wagtail components #865

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
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
1 change: 0 additions & 1 deletion config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@
"wagtailmedia",
"wagtail.contrib.settings",
"wagtail.contrib.styleguide",
"generic_chooser",
"wagtailmetadata",
"modelcluster",
"taggit",
Expand Down
4 changes: 4 additions & 0 deletions etna/records/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,10 @@ def iaid(self) -> str:
return candidate
return ""

@property
def pk(self):
return self.iaid

@cached_property
def reference_number(self) -> str:
"""
Expand Down
2 changes: 1 addition & 1 deletion etna/records/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .choosers import KongChosenView, KongModelChooserMixinIn, RecordChooserViewSet
from .choosers import RecordChooserViewSet
from .images import image_browse, image_serve, image_viewer
from .records import record_detail_view, record_disambiguation_view
121 changes: 54 additions & 67 deletions etna/records/views/choosers.py
Original file line number Diff line number Diff line change
@@ -1,106 +1,93 @@
from django.core.exceptions import ObjectDoesNotExist
from django.core.paginator import Page
from django.shortcuts import Http404
from django.urls import re_path

from generic_chooser.views import BaseChosenView, ChooserMixin, ChooserViewSet
from wagtail.admin.forms.choosers import BaseFilterForm, SearchFilterMixin
from wagtail.admin.views.generic import chooser as chooser_views
from wagtail.admin.viewsets.chooser import ChooserViewSet

from ...ciim.client import Stream
from ...ciim.exceptions import KongAPIError
from ...ciim.paginator import APIPaginator
from ..api import records_client
from ..models import Record
from ..widgets import RecordChooser


class KongModelChooserMixinIn(ChooserMixin):
"""Chooser source to allow filtering and selection of Kong model data.
class SearchForm(SearchFilterMixin, BaseFilterForm):
pass

Similar to the DFRDRFChooserMixin:

https://github.com/wagtail/wagtail-generic-chooser/blob/9ec9db937fe40311c67ed055e1b3f0dcd1b86908/generic_chooser/views.py#L223
"""
class RecordMixin:
model = Record
filter_form_class = SearchForm
is_searchable = True

def get_object(self, pk):
try:
return records_client.fetch(iaid=pk)
except KongAPIError:
raise ObjectDoesNotExist

# Model belonging to this chooser, set via <model>ChooserViewSet.
model: Record = None
def get_objects(self, pks):
return records_client.fetch_all(iaids=pks)

# Allow models to be searched via chooser. Hides the search box if False
is_searchable = True
def get_object_id(self, instance):
return instance.iaid

def get_paginated_object_list(self, page_number, search_term="", **kwargs):
offset = ((page_number - 1) * self.per_page,)
count = 0
results = []
def get_edit_item_url(self, instance):
# Avoid trying to show 'edit' links for records
return None

if search_term:
def can_create(self):
# Avoid showing 'create' options for records
return False

def get_results_page(self, request):
page_number = int(request.GET.get("p", 1))

query = request.GET.get("q", "")
if query:
results = records_client.search_unified(
q=search_term,
q=query,
stream=Stream.EVIDENTIAL,
size=self.per_page,
offset=offset,
offset=((page_number - 1) * self.per_page,),
)
count = results.total_count
else:
results = []
count = 0

paginator = APIPaginator(count, self.per_page)
page = Page(results, page_number, paginator)
return (page, paginator)
return Page(results, page_number, paginator)

def get_object(self, pk):
"""Fetch selected object"""
return records_client.fetch(iaid=pk)

def get_object_id(self, instance):
"""Return selected object's ID, used when resolving a link to this item.
class RecordChooseView(RecordMixin, chooser_views.ChooseView):
pass

see RecordChooserViewSet.get_urlpatterns for overridden pattern for selected item.
"""
return instance.iaid

def user_can_create(self, user):
"""Records cannot be created in Wagtail.

Hides the "create" tab in chooser.
"""
return False
class RecordChooseResultsView(RecordMixin, chooser_views.ChooseResultsView):
pass


class KongChosenView(BaseChosenView):
"""View to handle fetching a selected item."""
class RecordChosenView(RecordMixin, chooser_views.ChosenView):
pass

def get(self, request, pk):
"""Fetch selected item by its pk (in our case IAID)

Override parent to handle any errors from the Kong API.
"""
try:
return super().get(request, pk)
except KongAPIError:
raise Http404
class RecordChosenMultipleView(RecordMixin, chooser_views.ChosenMultipleView):
pass


class RecordChooserViewSet(ChooserViewSet):
"""Custom chooser to allow users to filter and select records."""

base_chosen_view_class = KongChosenView
chooser_mixin_class = KongModelChooserMixinIn
icon = "form"
model = Record
choose_view_class = RecordChooseView
choose_results_view_class = RecordChooseResultsView
chosen_view_class = RecordChosenView
chosen_multiple_view_class = RecordChosenMultipleView
widget_class = RecordChooser
page_title = "Choose a record"
per_page = 10

def get_choose_view_attrs(self):
attrs = super().get_choose_view_attrs()
attrs.update(model=self.model)
return attrs

def get_chosen_view_attrs(self):
attrs = super().get_chosen_view_attrs()
attrs.update(model=self.model)
return attrs

def get_urlpatterns(self):
"""Define patterns for chooser and chosen views.

Overridden to allow IAID to be used as an ID for chosen view"""
return super().get_urlpatterns() + [
re_path(r"^$", self.choose_view, name="choose"),
re_path(r"^([\w-]+)/$", self.chosen_view, name="chosen"),
]
per_page = 15
register_widget = False
58 changes: 17 additions & 41 deletions etna/records/widgets.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,32 @@
import logging

from generic_chooser.widgets import AdminChooser
from wagtail.admin.widgets.chooser import BaseChooser

from .api import records_client
from .models import Record

logger = logging.getLogger(__name__)


class RecordChooser(AdminChooser):
class RecordChooser(BaseChooser):
choose_one_text = "Choose a record"
choose_another_text = "Choose another record"
link_to_chosen_text = "Edit this record"
choose_modal_url_name = "record_chooser:choose"
chooser_modal_url_name = "record_chooser:choose"
show_edit_link = False

def get_value_data(self, value):
# Given a data value (which may be None or an value such as a pk to pass to get_instance),
# extract the necessary data for rendering the widget with that value.
# In the case of StreamField (in Wagtail >=2.13), this data will be serialised via
# telepath https://wagtail.github.io/telepath/ to be rendered client-side, which means it
# cannot include model instances. Instead, we return the raw values used in rendering -
# namely: value, title and edit_item_url

instance = None

if isinstance(value, Record):
instance = value
value = value.iaid
elif value:
try:
instance = self.get_instance(value)
except Exception:
logger.exception(f"Error fetching Record '{value}'. Using dummy value.")
return {
"value": value,
"title": "Record currently unavailable",
"edit_item_url": None,
}

if instance is None:
return {
"value": None,
"title": "",
"edit_item_url": None,
}
def get_instance(self, pk):
"""Fetch related instance on edit form."""
try:
return records_client.fetch(iaid=pk)
except Exception:
logger.exception(f"Error fetching Record '{pk}'.")
return None

def get_value_data_from_instance(self, instance):
return {
"value": value,
"title": self.get_title(instance),
"edit_item_url": self.get_edit_item_url(instance),
"id": instance.pk,
self.display_title_key: instance.summary_title,
"description": instance.listing_description,
"reference_number": instance.reference_number,
"edit_item_url": None,
}

def get_instance(self, pk):
"""Fetch related instance on edit form."""
return records_client.fetch(iaid=pk)
23 changes: 4 additions & 19 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ wagtail = "~5.0"
django-taggit = "~3.1"
platformshconfig = "^2.4.0"
pyquery = "^2.0.0"
wagtail-generic-chooser = "~0.5.1"
psycopg2 = "^2.9.1"
wagtailmedia = "~0.14"
django-allauth = "~0.54"
Expand Down