Skip to content

Commit

Permalink
Convert impress header table to senaite.app.listing (#134)
Browse files Browse the repository at this point in the history
* Moved static folder -> browser/static

* Moved resources viewlet class to viewlets package

* Refactored publish view to viewlets

* Webpack config

* Removed senaite.app.listing

* Changelog updated

* Added needed JS libraries for listing

* Registered viewlet for senaite.app.listing resources

* Added methods to retrieve listing view

* Added listing view for impress contents

* WIP for content listing

* Removed package include

* Changelog updated

* Changed layer interface

* Removed i18n attributes

* Converted content table to senaite.app.listing

* Updated view class

* Changelog updated
  • Loading branch information
ramonski committed Feb 8, 2023
1 parent 8428525 commit 30c1466
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 96 deletions.
1 change: 1 addition & 0 deletions docs/Changelog.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
2.4.0 (unreleased)
------------------

- #134 Convert impress header table to senaite.app.listing
- #133 Refactor publish view controls and content table to viewlets
- #132 Add custom action provider for direct PDF sharing via email
- #131 Hookable action providers
Expand Down
1 change: 1 addition & 0 deletions src/senaite/impress/browser/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:plone="http://namespaces.plone.org/plone"
xmlns:browser="http://namespaces.zope.org/browser">

<include package=".publish" />
<include package=".viewlets" />

<!-- Static directory for js, css and image resources -->
Expand Down
Empty file.
14 changes: 14 additions & 0 deletions src/senaite/impress/browser/publish/configure.zcml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:browser="http://namespaces.zope.org/browser">

<!-- Listing view for publish contents -->
<browser:page
for="*"
name="publish_content_listing"
class=".content.ContentListingView"
permission="zope2.View"
layer="senaite.impress.interfaces.ILayer"
/>

</configure>
234 changes: 234 additions & 0 deletions src/senaite/impress/browser/publish/content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
# -*- coding: utf-8 -*-

from collections import OrderedDict

from six.moves import urllib

from bika.lims import api
from bika.lims import senaiteMessageFactory as _
from bika.lims.interfaces import IAnalysisRequest
from bika.lims.utils import get_link
from bika.lims.utils import get_link_for
from bika.lims.utils import t
from senaite.app.listing import ListingView
from senaite.core.api import dtime


class ContentListingView(ListingView):
"""Listing table of selected UIDs
"""
def __init__(self, context, request):
super(ContentListingView, self).__init__(context, request)

self.pagesize = 9999
self.context_actions = {}
self.show_search = False
self.show_select_column = False
self.show_workflow_action_buttons = False
self.show_table_footer = False
self.omit_form = True

# Show categories
self.categories = []
self.show_categories = True
self.expand_all_categories = False

self.columns = OrderedDict((
# Although 'created' column is not displayed in the list (see
# review_states to check the columns that will be rendered), this
# column is needed to sort the list by create date
("id", {
"title": _("ID"),
"sortable": False,
"toggle": True}),
("title", {
"title": _("Title"),
"sortable": False,
"toggle": True}),
("SampleType", {
"title": _("Sample Type"),
"sortable": False,
"toggle": True}),
("created", {
"title": _("Registered"),
"sortable": False,
"toggle": False}),
("DateSampled", {
"title": _("Date Sampled"),
"sortable": False,
"toggle": True}),
("Client", {
"title": _("Client"),
"sortable": False,
"toggle": True}),
("ClientID", {
"title": _("Client ID"),
"sortable": False,
"toggle": True}),
("Contact", {
"title": _("Contact"),
"sortable": False,
"toggle": True}),
("BatchID", {
"title": _("Batch ID"),
"sortable": False,
"toggle": True}),
("review_state", {
"title": _("Workflow State ID"),
"sortable": False,
"toggle": False}),
("state", {
"title": _("Workflow State"),
"sortable": False,
"toggle": True}),
))

self.review_states = [
{
"id": "default",
"title": _("All"),
"contentFilter": {},
"transitions": [],
"custom_transitions": [],
"columns": self.columns.keys()
}
]

def get_uids(self):
"""Parse the UIDs from the query string
NOTE:
This listing view is called asynchronously with a new HTTP POST request
from senaite.app.listing (see JS: api.get_json)
Therefore, the original `?items` request parameter is contained only in
the request QUERY_STRING, but no longer in the form data, because there
we have only the payload from the POST request
XXX: This might be better done in senaite.app.listing
"""
uids = []
qs = self.request.get_header("query_string", "")
params = urllib.parse.parse_qs(qs)
items = params.get("items", [])
for item in items:
uids.extend(filter(api.is_uid, item.split(",")))
return uids

def make_empty_item(self, **kw):
"""Create a new empty item
"""
item = {
"uid": None,
"before": {},
"after": {},
"replace": {},
"allow_edit": [],
"disabled": False,
"state_class": "state-active",
}
item.update(**kw)
return item

def folderitems(self):
items = []
for num, uid in enumerate(self.get_uids()):
obj = api.get_object(uid)
# create base folderitem
item = self.make_empty_item(**{
"uid": uid,
"id": api.get_id(obj),
"title": api.get_title(obj),
"replace": {
"id": get_link_for(obj),
}
})

# append workflow info
self._folder_item_workflow(obj, item)
# append sample specific info
self._folder_item_sample(obj, item)

items.append(self.folderitem(obj, item, num))

return items

def folderitem(self, obj, item, index):
"""Render a row in the listing
"""
return item

def _folder_item_workflow(self, obj, item):
"""Add workflow information to the item
"""
state = "Active"
review_state = "active"

wf_tool = api.get_tool("portal_workflow")
wfs = wf_tool.getWorkflowsFor(obj)

for wf in wfs:
review_state = wf.getInfoFor(obj, wf.state_var, "")
sdef = wf.states.get(review_state)
state = sdef.title
break

item["state"] = t(state)
item["review_state"] = review_state
item["state_class"] = "state-{}".format(review_state)

def _folder_item_sample(self, obj, item):
"""Add sample specific information
"""
if not IAnalysisRequest.providedBy(obj):
return
item["SampleType"] = obj.getSampleTypeTitle()

client = obj.getClient()
client_url = api.get_url(client)
client_id = client.getClientID()
client_name = client.getName()

# Categorize objects by client name
item["category"] = client_name
if client_name not in self.categories:
self.categories.append(client_name)

# Client Name
item["Client"] = client_name
item["replace"]["Client"] = get_link(
client_url, value=client_name, target="_blank")

# Client ID
item["ClientID"] = client.getClientID()
item["replace"]["ClientID"] = get_link(
client_url, value=client_id, target="_blank")

# Client Contact
contact = obj.getContact()
if contact:
contact_url = api.get_url(contact)
contact_name = contact.getFullname()
item["Contact"] = contact.getFullname()
item["replace"]["Contact"] = get_link(
contact_url, value=contact_name, target="_blank")

# Date Sampled
date_created = obj.created()
date_sampled = obj.getDateSampled()
item["created"] = dtime.to_localized_time(
date_created, long_format=True,
context=self.context, request=self.request)
item["DateSampled"] = dtime.to_localized_time(
date_sampled, long_format=True,
context=self.context, request=self.request)

# Batch
batch = obj.getBatch()
if batch:
batch_id = batch.getId()
batch_url = api.get_url(batch)
item["BatchID"] = batch.getId()
item["replace"]["BatchID"] = get_link(
batch_url, value=batch_id, target="_blank")
9 changes: 9 additions & 0 deletions src/senaite/impress/browser/viewlets/configure.zcml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@
layer="senaite.impress.interfaces.ISenaiteImpressLayer"
/>

<!-- Static Resources for senaite.app.listing -->
<browser:viewlet
name="senaite.app.listing.static"
manager=".interfaces.IPublishCustomHtmlHeadViewlets"
class="senaite.app.listing.browser.viewlets.resources.ResourcesViewlet"
permission="zope2.View"
layer="senaite.impress.interfaces.ILayer"
/>

<!-- Favicon viewlet -->
<browser:viewlet
name="plone.links.favicon"
Expand Down
23 changes: 10 additions & 13 deletions src/senaite/impress/browser/viewlets/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@ def __init__(self, context, request, view, manager=None):
self.request = request
self.view = view

@property
def publishview(self):
return api.get_view(
"publish", context=self.context, request=self.request)
def get_listing_view(self):
request = api.get_request()
view_name = "publish_content_listing"
view = api.get_view(view_name, context=self.context, request=request)
return view

def get_uids(self):
"""Parse the UIDs from the request `items` parameter
"""
return self.publishview.get_uids()

def get_collection(self, uids, group_by=None):
"""Wraps the given UIDs into a collection of SuperModels
"""
return self.publishview.get_collection(uids, group_by=group_by)
def contents_table(self):
view = self.get_listing_view()
view.update()
view.before_render()
return view.ajax_contents_table()

0 comments on commit 30c1466

Please sign in to comment.