Adam Coldrick 983bb8e2d7 Unpin webob and hacking to fix compatibility issues
The pinned versions of webob and hacking weren't functioning properly
with modern Python, so they are unpinned.

Unpinning hacking brought a number of extra rules into use, so this
commit also contains a number of code style fixes.

There was also an incompatibility in the test_setup.sh script, which
meant it doesn't work with MySQL 8.0 (which is in Ubuntu 20.04). This
is also fixed in this commit.

Change-Id: I0889bc35f8babfeec42f6f577d302bb5fbce1c95
2020-09-11 12:26:47 +01:00

206 lines
8.2 KiB
Python

# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied. See the License for the specific language governing permissions and
# limitations under the License.
import json
import storyboard.db.api.base as db_api
from storyboard.db.api import subscriptions as sub_api
from storyboard.db.api import timeline_events as events_api
import storyboard.db.models as models
from storyboard.plugin.event_worker import WorkerTaskBase
def get_preference(name, user):
if name not in user.preferences:
return None
return user.preferences[name].cast_value
class Subscription(WorkerTaskBase):
def enabled(self):
"""This plugin is always enabled.
:return: True
"""
return True
def handle(self, session, author, method, url, path, query_string, status,
resource, resource_id, sub_resource=None, sub_resource_id=None,
resource_before=None, resource_after=None):
"""This worker handles API events and attempts to determine whether
they correspond to user subscriptions.
:param session: An event-specific SQLAlchemy session.
:param author: The author's user record.
:param method: The HTTP Method.
:param url: The Referer header from the request.
:param path: The full HTTP Path requested.
:param query_string: The HTTP query string from the request.
:param status: The returned HTTP Status of the response.
:param resource: The resource type.
:param resource_id: The ID of the resource.
:param sub_resource: The subresource type.
:param sub_resource_id: The ID of the subresource.
:param resource_before: The resource state before this event occurred.
:param resource_after: The resource state after this event occurred.
"""
if resource == 'timeline_event':
story_id = resource_after.get('story_id')
worklist_id = resource_after.get('worklist_id')
if story_id is not None:
subscribers = sub_api.subscription_get_all_subscriber_ids(
'story', story_id, session=session)
self.handle_timeline_events(
session, resource_after, author, subscribers)
if worklist_id is not None:
subscribers = sub_api.subscription_get_all_subscriber_ids(
'worklist', worklist_id, session=session)
self.handle_timeline_events(
session, resource_after, author, subscribers)
elif resource == 'project_group':
subscribers = sub_api.subscription_get_all_subscriber_ids(
resource, resource_id, session=session)
self.handle_resources(session=session,
method=method,
resource_id=resource_id,
sub_resource_id=sub_resource_id,
author=author,
subscribers=subscribers)
if method == 'DELETE' and not (sub_resource_id or sub_resource):
self.handle_deletions(session, resource, resource_id)
def handle_deletions(self, session, resource_name, resource_id):
target_subs = []
sub_ids = set()
resource_name = resource_name[:-1]
target_sub = db_api.entity_get_all(models.Subscription,
target_type=resource_name,
target_id=resource_id,
session=session)
target_subs.extend(target_sub)
for sub in target_subs:
sub_ids.add(sub.id)
for sub_id in sub_ids:
db_api.entity_hard_delete(models.Subscription,
sub_id,
session=session)
def handle_timeline_events(self, session, resource, author, subscribers):
for user_id in subscribers:
user = db_api.entity_get(models.User, user_id, session=session)
send_notification = get_preference(
'receive_notifications_worklists', user)
if (send_notification != 'true'
and resource.get('worklist_id') is not None):
continue
if resource['event_type'] == 'user_comment':
event_info = json.dumps(
self.resolve_comments(session=session, event=resource)
)
else:
event_info = resource['event_info']
# Don't send a notification if the user isn't allowed to see the
# thing this event is about.
event = events_api.event_get(
resource['id'], current_user=user_id, session=session)
if not events_api.is_visible(event, user_id, session=session):
continue
db_api.entity_create(models.SubscriptionEvents, {
"author_id": author.id,
"subscriber_id": user_id,
"event_type": resource['event_type'],
"event_info": event_info
}, session=session)
def handle_resources(self, session, method, resource_id, sub_resource_id,
author, subscribers):
if sub_resource_id:
for user_id in subscribers:
if method == 'DELETE':
event_type = 'project removed from project_group'
event_info = json.dumps({'project_group_id': resource_id,
'project_id': sub_resource_id})
else:
event_type = 'project added to project_group'
event_info = json.dumps({'project_group_id': resource_id,
'project_id': sub_resource_id})
db_api.entity_create(models.SubscriptionEvents, {
"author_id": author.id,
"subscriber_id": user_id,
"event_type": event_type,
"event_info": event_info
}, session=session)
else:
if method == 'DELETE':
# Handling project_group targeted.
for user_id in subscribers:
db_api.entity_create(models.SubscriptionEvents, {
"author_id": author.id,
"subscriber_id": user_id,
"event_type": 'project_group deleted',
"event_info": json.dumps(
{'project_group_id': resource_id})
}, session=session)
def resolve_comments(self, session, event):
# Sanity check. If there's no comment_id, exit.
if 'comment_id' not in event:
return None
# Retrieve the content of the comment.
comment = db_api.entity_get(models.Comment,
event['comment_id'],
session=session)
if not comment:
return None
# Retrieve the timeline events.
timeline_event = session.query(models.TimeLineEvent) \
.filter(models.TimeLineEvent.comment_id == event['comment_id']) \
.first()
if not timeline_event:
return None
# Retrieve the story from the timeline event.
story = db_api.entity_get(models.Story,
timeline_event.story_id,
session=session)
if not story:
return None
# Construct and return the comment's event_info object.
return {
'comment_id': comment.id,
'comment_content': comment.content,
'story_id': story.id,
'story_title': story.title
}