Remove unused legacy grid column types

This commit is contained in:
guerler
2024-02-11 08:20:18 -05:00
parent f7c3e856cc
commit f5862dbddd
2 changed files with 1 additions and 497 deletions
-496
View File
@@ -129,13 +129,6 @@ class GridColumn:
return query
class ReverseSortColumn(GridColumn):
"""Column that reverses sorting; this is useful when the natural sort is descending."""
def sort(self, trans, query, ascending, column_name=None):
return GridColumn.sort(self, trans, query, (not ascending), column_name=column_name)
class TextColumn(GridColumn):
"""Generic column that employs freetext and, hence, supports freetext, case-independent filtering."""
@@ -182,495 +175,6 @@ class TextColumn(GridColumn):
return query
class DateTimeColumn(TextColumn):
def sort(self, trans, query, ascending, column_name=None):
"""Sort query using this column."""
return GridColumn.sort(self, trans, query, ascending, column_name=column_name)
class BooleanColumn(TextColumn):
def sort(self, trans, query, ascending, column_name=None):
"""Sort query using this column."""
return GridColumn.sort(self, trans, query, ascending, column_name=column_name)
def get_single_filter(self, user, a_filter):
if self.key.find(".") > -1:
a_key = self.key.split(".")[1]
else:
a_key = self.key
model_class_key_field = getattr(self.model_class, a_key)
return model_class_key_field == a_filter
class IntegerColumn(TextColumn):
"""
Integer column that employs freetext, but checks that the text is an integer,
so support filtering on integer values.
IMPORTANT NOTE: grids that use this column type should not include the column
in the cols_to_filter list of MulticolFilterColumn ( i.e., searching on this
column type should not be performed in the grid's standard search - it won't
throw exceptions, but it also will not find what you're looking for ). Grids
that search on this column should use 'filterable="advanced"' so that searching
is only performed in the advanced search component, restricting the search to
the specific column.
This is useful for searching on object ids or other integer columns. See the
JobIdColumn column in the SpecifiedDateListGrid class in the jobs controller of
the reports webapp for an example.
"""
def get_single_filter(self, user, a_filter):
model_class_key_field = getattr(self.model_class, self.key)
assert int(a_filter), "The search entry must be an integer"
return model_class_key_field == int(a_filter)
def sort(self, trans, query, ascending, column_name=None):
"""Sort query using this column."""
return GridColumn.sort(self, trans, query, ascending, column_name=column_name)
class CommunityRatingColumn(GridColumn, UsesItemRatings):
"""Column that displays community ratings for an item."""
def get_value(self, trans, grid, item):
if not hasattr(item, "average_rating"):
# No prefetched column property, generate it on the fly.
ave_item_rating, num_ratings = self.get_ave_item_rating_data(
trans.sa_session, item, webapp_model=trans.model
)
else:
ave_item_rating = item.average_rating
num_ratings = 2 # just used for pluralization
if not ave_item_rating:
ave_item_rating = 0
return trans.fill_template(
"tool_shed_rating.mako",
ave_item_rating=ave_item_rating,
num_ratings=num_ratings,
item_id=trans.security.encode_id(item.id),
)
def sort(self, trans, query, ascending, column_name=None):
# Get the columns that connect item's table and item's rating association table.
item_rating_assoc_class = getattr(trans.model, f"{self.model_class.__name__}RatingAssociation")
foreign_key = get_foreign_key(item_rating_assoc_class, self.model_class)
fk_col = foreign_key.parent
referent_col = foreign_key.get_referent(self.model_class.table)
# Do sorting using a subquery.
# Subquery to get average rating for each item.
ave_rating_subquery = (
trans.sa_session.query(fk_col, func.avg(item_rating_assoc_class.table.c.rating).label("avg_rating"))
.group_by(fk_col)
.subquery()
)
# Integrate subquery into main query.
query = query.outerjoin((ave_rating_subquery, referent_col == ave_rating_subquery.columns[fk_col.name]))
# Sort using subquery results; use coalesce to avoid null values.
if (
not ascending
): # TODO: for now, reverse sorting b/c first sort is ascending, and that should be the natural sort.
query = query.order_by(func.coalesce(ave_rating_subquery.c.avg_rating, 0).asc())
else:
query = query.order_by(func.coalesce(ave_rating_subquery.c.avg_rating, 0).desc())
return query
class OwnerAnnotationColumn(TextColumn, UsesAnnotations):
"""Column that displays and filters item owner's annotations."""
def __init__(self, col_name, key, model_class=None, model_annotation_association_class=None, filterable=None):
GridColumn.__init__(self, col_name, key=key, model_class=model_class, filterable=filterable)
self.sortable = False
self.model_annotation_association_class = model_annotation_association_class
def get_value(self, trans, grid, item):
"""Returns first 150 characters of annotation."""
annotation = self.get_item_annotation_str(trans.sa_session, item.user, item)
if annotation:
ann_snippet = annotation[:155]
if len(annotation) > 155:
ann_snippet = ann_snippet[: ann_snippet.rfind(" ")]
ann_snippet += "..."
else:
ann_snippet = ""
return escape(ann_snippet)
def get_single_filter(self, user, a_filter):
"""Filter by annotation and annotation owner."""
return self.model_class.annotations.any(
and_(
func.lower(self.model_annotation_association_class.annotation).like(f"%{a_filter.lower()}%"),
# TODO: not sure why, to filter by owner's annotations, we have to do this rather than
# 'self.model_class.user==self.model_annotation_association_class.user'
self.model_annotation_association_class.table.c.user_id == self.model_class.table.c.user_id,
)
)
class CommunityTagsColumn(TextColumn):
"""Column that supports community tags."""
def __init__(
self, col_name, key, model_class=None, model_tag_association_class=None, filterable=None, grid_name=None
):
GridColumn.__init__(
self, col_name, key=key, model_class=model_class, nowrap=True, filterable=filterable, sortable=False
)
self.model_tag_association_class = model_tag_association_class
# Column-specific attributes.
self.grid_name = grid_name
def get_value(self, trans, grid, item):
return trans.fill_template(
"/tagging_common.mako",
tag_type="community",
trans=trans,
user=trans.get_user(),
tagged_item=item,
elt_context=self.grid_name,
tag_click_fn="add_tag_to_grid_filter",
use_toggle_link=True,
)
def filter(self, trans, user, query, column_filter):
"""Modify query to filter model_class by tag. Multiple filters are ANDed."""
if column_filter == "All":
pass
elif column_filter:
query = query.filter(self.get_filter(trans, user, column_filter))
return query
def get_filter(self, trans, user, column_filter):
# Parse filter to extract multiple tags.
if isinstance(column_filter, list):
# Collapse list of tags into a single string; this is redundant but effective. TODO: fix this by iterating over tags.
column_filter = ",".join(column_filter)
raw_tags = trans.app.tag_handler.parse_tags(column_filter)
clause_list = []
for name, value in raw_tags:
if name:
# Filter by all tags.
clause_list.append(
self.model_class.tags.any(
func.lower(self.model_tag_association_class.user_tname).like(f"%{name.lower()}%")
)
)
if value:
# Filter by all values.
clause_list.append(
self.model_class.tags.any(
func.lower(self.model_tag_association_class.user_value).like(f"%{value.lower()}%")
)
)
return and_(*clause_list)
class IndividualTagsColumn(CommunityTagsColumn):
"""Column that supports individual tags."""
def get_value(self, trans, grid, item):
return trans.fill_template(
"/tagging_common.mako",
tag_type="individual",
user=trans.user,
tagged_item=item,
elt_context=self.grid_name,
tag_click_fn="add_tag_to_grid_filter",
use_toggle_link=True,
)
def get_filter(self, trans, user, column_filter):
# Parse filter to extract multiple tags.
if isinstance(column_filter, list):
# Collapse list of tags into a single string; this is redundant but effective. TODO: fix this by iterating over tags.
column_filter = ",".join(column_filter)
raw_tags = trans.app.tag_handler.parse_tags(column_filter)
clause_list = []
for name, value in raw_tags:
if name:
# Filter by individual's tag names.
clause_list.append(
self.model_class.tags.any(
and_(
func.lower(self.model_tag_association_class.user_tname).like(f"%{name.lower()}%"),
self.model_tag_association_class.user == user,
)
)
)
if value:
# Filter by individual's tag values.
clause_list.append(
self.model_class.tags.any(
and_(
func.lower(self.model_tag_association_class.user_value).like(f"%{value.lower()}%"),
self.model_tag_association_class.user == user,
)
)
)
return and_(*clause_list)
class MulticolFilterColumn(TextColumn):
"""Column that performs multicolumn filtering."""
def __init__(self, col_name, cols_to_filter, key, visible, filterable="default"):
GridColumn.__init__(self, col_name, key=key, visible=visible, filterable=filterable)
self.cols_to_filter = cols_to_filter
def filter(self, trans, user, query, column_filter):
"""Modify query to filter model_class by tag. Multiple filters are ANDed."""
if column_filter == "All":
return query
if isinstance(column_filter, list):
clause_list = []
for filter in column_filter:
part_clause_list = []
for column in self.cols_to_filter:
part_clause_list.append(column.get_filter(trans, user, filter))
clause_list.append(or_(*part_clause_list))
complete_filter = and_(*clause_list)
else:
clause_list = []
for column in self.cols_to_filter:
clause_list.append(column.get_filter(trans, user, column_filter))
complete_filter = or_(*clause_list)
return query.filter(complete_filter)
class OwnerColumn(TextColumn):
"""Column that lists item's owner."""
def get_value(self, trans, grid, item):
return item.user.username
def sort(self, trans, query, ascending, column_name=None):
"""Sort column using case-insensitive alphabetical sorting on item's username."""
if ascending:
query = query.order_by(func.lower(self.model_class.username).asc())
else:
query = query.order_by(func.lower(self.model_class.username).desc())
return query
class PublicURLColumn(TextColumn):
"""Column displays item's public URL based on username and slug."""
def get_link(self, trans, grid, item):
if item.user.username and item.slug:
return dict(action="display_by_username_and_slug", username=item.user.username, slug=item.slug)
elif not item.user.username:
# TODO: provide link to set username.
return None
elif not item.user.slug:
# TODO: provide link to set slug.
return None
class DeletedColumn(GridColumn):
"""Column that tracks and filters for items with deleted attribute."""
def get_accepted_filters(self):
"""Returns a list of accepted filters for this column."""
accepted_filter_labels_and_vals = {"active": "False", "deleted": "True", "all": "All"}
accepted_filters = []
for label, val in accepted_filter_labels_and_vals.items():
args = {self.key: val}
accepted_filters.append(GridColumnFilter(label, args))
return accepted_filters
def filter(self, trans, user, query, column_filter):
"""Modify query to filter self.model_class by state."""
if column_filter == "All":
pass
elif column_filter in ["True", "False"]:
query = query.filter(self.model_class.deleted == (column_filter == "True"))
return query
class PurgedColumn(GridColumn):
"""Column that tracks and filters for items with purged attribute."""
def get_accepted_filters(self):
"""Returns a list of accepted filters for this column."""
accepted_filter_labels_and_vals = {"nonpurged": "False", "purged": "True", "all": "All"}
accepted_filters = []
for label, val in accepted_filter_labels_and_vals.items():
args = {self.key: val}
accepted_filters.append(GridColumnFilter(label, args))
return accepted_filters
def filter(self, trans, user, query, column_filter):
"""Modify query to filter self.model_class by state."""
if column_filter == "All":
pass
elif column_filter in ["True", "False"]:
query = query.filter(self.model_class.purged == (column_filter == "True"))
return query
class StateColumn(GridColumn):
"""
Column that tracks and filters for items with state attribute.
IMPORTANT NOTE: self.model_class must have a states Bunch or dict if
this column type is used in the grid.
"""
def get_value(self, trans, grid, item):
return item.state
def filter(self, trans, user, query, column_filter):
"""Modify query to filter self.model_class by state."""
if column_filter == "All":
pass
elif column_filter in [v for k, v in self.model_class.states.items()]:
query = query.filter(self.model_class.state == column_filter)
return query
def get_accepted_filters(self):
"""Returns a list of accepted filters for this column."""
all = GridColumnFilter("all", {self.key: "All"})
accepted_filters = [all]
for v in self.model_class.states.values():
args = {self.key: v}
accepted_filters.append(GridColumnFilter(v, args))
return accepted_filters
class SharingStatusColumn(GridColumn):
"""Grid column to indicate sharing status."""
def __init__(self, *args, **kwargs):
self.use_shared_with_count = kwargs.pop("use_shared_with_count", False)
super().__init__(*args, **kwargs)
def get_value(self, trans, grid, item):
# Delete items cannot be shared.
if item.deleted:
return ""
# Build a list of sharing for this item.
sharing_statuses = []
if self._is_shared(item):
sharing_statuses.append("Shared")
if item.importable:
sharing_statuses.append("Accessible")
if item.published:
sharing_statuses.append("Published")
return ", ".join(sharing_statuses)
def _is_shared(self, item):
if self.use_shared_with_count:
# optimization to skip join for users_shared_with and loading in that data.
return item.users_shared_with_count > 0
return item.users_shared_with
def filter(self, trans, user, query, column_filter):
"""Modify query to filter histories by sharing status."""
if column_filter == "All":
pass
elif column_filter:
if column_filter == "private":
query = query.filter(self.model_class.users_shared_with == null())
query = query.filter(self.model_class.importable == false())
elif column_filter == "shared":
query = query.filter(self.model_class.users_shared_with != null())
elif column_filter == "accessible":
query = query.filter(self.model_class.importable == true())
elif column_filter == "published":
query = query.filter(self.model_class.published == true())
return query
def get_accepted_filters(self):
"""Returns a list of accepted filters for this column."""
accepted_filter_labels_and_vals = {}
accepted_filter_labels_and_vals["private"] = "private"
accepted_filter_labels_and_vals["shared"] = "shared"
accepted_filter_labels_and_vals["accessible"] = "accessible"
accepted_filter_labels_and_vals["published"] = "published"
accepted_filter_labels_and_vals["all"] = "All"
accepted_filters = []
for label, val in accepted_filter_labels_and_vals.items():
args = {self.key: val}
accepted_filters.append(GridColumnFilter(label, args))
return accepted_filters
class GridOperation:
def __init__(
self,
label,
key=None,
condition=None,
allow_multiple=True,
allow_popup=True,
target=None,
url_args=None,
async_compatible=False,
confirm=None,
global_operation=None,
):
self.label = label
self.key = key
self.allow_multiple = allow_multiple
self.allow_popup = allow_popup
self.condition = condition
self.target = target
self.url_args = url_args
self.async_compatible = async_compatible
# if 'confirm' is set, then ask before completing the operation
self.confirm = confirm
# specify a general operation that acts on the full grid
# this should be a function returning a dictionary with parameters
# to pass to the URL, similar to GridColumn links:
# global_operation=(lambda: dict(operation="download")
self.global_operation = global_operation
def get_url_args(self, item):
if self.url_args:
if callable(self.url_args):
url_args = self.url_args(item)
else:
url_args = dict(self.url_args)
url_args["id"] = item.id
return url_args
else:
return dict(operation=self.label, id=item.id)
def allowed(self, item):
if self.condition:
return bool(self.condition(item))
else:
return True
class DisplayByUsernameAndSlugGridOperation(GridOperation):
"""Operation to display an item by username and slug."""
def get_url_args(self, item):
return {"action": "display_by_username_and_slug", "username": item.user.username, "slug": item.slug}
class GridAction:
def __init__(self, label=None, url_args=None, target=None):
self.label = label
self.url_args = url_args
self.target = target
class GridColumnFilter:
def __init__(self, label, args=None):
self.label = label
self.args = args
def get_url_args(self):
rval = {}
for k, v in self.args.items():
rval[f"f-{k}"] = v
return rval
class GridData:
"""
Specifies the content a grid (data table).
@@ -245,7 +245,7 @@ class GroupListGrid(grids.GridData):
grids.GridColumn("Name", key="name"),
UsersColumn("Users", key="users"),
RolesColumn("Roles", key="roles"),
grids.DeletedColumn("Deleted", key="deleted", escape=False),
grids.GridColumn("Deleted", key="deleted", escape=False),
grids.GridColumn("Last Updated", key="update_time"),
]