Skip to content

Commit c203d77

Browse files
authored
Merge pull request #809 from marwoodandrew/sdesk-4882
[SDESK-4882] Collate golf results
2 parents 5de42f1 + 79eb261 commit c203d77

File tree

3 files changed

+709
-0
lines changed

3 files changed

+709
-0
lines changed

server/aap/macros/golf_collation.py

+197
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
# -*- coding: utf-8; -*-
2+
#
3+
# This file is part of Superdesk.
4+
#
5+
# Copyright 2013, 2014 Sourcefabric z.u. and contributors.
6+
#
7+
# For the full copyright and license information, please see the
8+
# AUTHORS and LICENSE files distributed with this source code, or
9+
# at https://www.sourcefabric.org/superdesk/license
10+
11+
from superdesk import get_resource_service
12+
import logging
13+
from eve.utils import ParsedRequest
14+
import json
15+
from datetime import datetime
16+
import pytz
17+
from flask import current_app as app
18+
from apps.prepopulate.app_initialize import get_filepath
19+
20+
logger = logging.getLogger(__name__)
21+
22+
23+
def golf_collation(item, **kwargs):
24+
"""
25+
Collates a number of Golf results into a single story.
26+
It uses the location of the input item to filter the included stories.
27+
It expects the name of the golf course (links) to be in the slugline
28+
Stories will be included based on the order of the slugline
29+
If grouping result into regions it expect the region name to be in the anpa_take_key of the input item
30+
:param item:
31+
:param kwargs:
32+
:return:
33+
"""
34+
35+
def get_desk():
36+
"""
37+
Search for a desk on the system with the name "Copytakers"
38+
:return:
39+
"""
40+
logger.info('Fetching the ObjectID for the desk "Copytakers".')
41+
query = {'name': 'Copytakers'}
42+
req = ParsedRequest()
43+
req.where = json.dumps(query)
44+
45+
desk_service = get_resource_service('desks')
46+
desk_item = list(desk_service.get_from_mongo(req=req, lookup=None))
47+
if not desk_item:
48+
raise('Failed to find the a desk called "Copytakers".')
49+
50+
desk_id = desk_item[0]['_id']
51+
logger.info('ObjectID for the desk Copytakers is {}.'.format(desk_id))
52+
return desk_item[0]
53+
54+
def get_hold_stages(desk_id):
55+
"""
56+
Get any stages on the passed desk that have the word Hold in their name
57+
:param desk_id:
58+
:return:
59+
"""
60+
lookup = {'$and': [{'name': {'$regex': 'Hold', '$options': 'i'}}, {'desk': str(desk_id)}]}
61+
stages = get_resource_service('stages').get(req=None, lookup=lookup)
62+
return stages
63+
64+
def get_result_items(location, desk_id, stage_ids, midnight_utc):
65+
"""
66+
Need to find all stories the need to be collated
67+
The subject should be golf
68+
The place should match that of the story the macro is being run against
69+
The slugline should not start with 'Golf Results' (output story will have this slugline)
70+
The story should be updated/created since midnight
71+
Should be on the copy takers desk maybe hold stage?
72+
Not spiked
73+
Not already a collated story
74+
:param location:
75+
:param desk_id:
76+
:param stage_ids:
77+
:param midnight_utc:
78+
:return:
79+
"""
80+
query = {
81+
"query": {
82+
"filtered": {
83+
"filter": {
84+
"bool": {
85+
"must": [
86+
{"term": {"place.qcode": location.get("qcode")}},
87+
{"term": {"subject.qcode": "15027000"}},
88+
{"term": {"task.desk": str(desk_id)}},
89+
{"terms": {"task.stage": stage_ids}},
90+
{
91+
"range": {
92+
"versioncreated": {
93+
"gte": midnight_utc
94+
}
95+
}
96+
}
97+
],
98+
"must_not": [
99+
{"term": {"state": "spiked"}},
100+
{"query": {
101+
"match_phrase_prefix": {
102+
"slugline": "Golf Results"
103+
}
104+
}}
105+
]
106+
}
107+
}
108+
}
109+
},
110+
"sort": [{"slugline": "asc"}],
111+
"size": 200
112+
}
113+
114+
req = ParsedRequest()
115+
repos = 'archive'
116+
req.args = {'source': json.dumps(query), 'repo': repos}
117+
return get_resource_service('search').get(req=req, lookup=None)
118+
119+
if 'place' not in item or len(item.get('place')) != 1:
120+
raise Exception('The story you''re running the macro on must have a single place defined')
121+
location = item.get('place')[0]
122+
123+
# Read the file that groups golf courses into regions
124+
path = get_filepath('golf_links.json')
125+
try:
126+
with path.open('r') as f:
127+
regions = json.load(f)
128+
except Exception as ex:
129+
logger.error('Exception loading golf_links.json : {}'.format(ex))
130+
131+
copytakers_desk = get_desk()
132+
133+
# Attempt to get the hold stages for the Copytakers desk
134+
stages = get_hold_stages(copytakers_desk.get('_id'))
135+
stage_ids = [str(s.get('_id')) for s in stages]
136+
if len(stage_ids) == 0:
137+
raise Exception('No hold stages found on desk "{}"'.format(copytakers_desk.get('name')))
138+
139+
# Get the local midnight in UTC
140+
midnight_utc = datetime.now(pytz.timezone(app.config['DEFAULT_TIMEZONE']))\
141+
.replace(hour=0, minute=0, second=0, microsecond=0).astimezone(pytz.utc).isoformat()[:19] + 'z'
142+
143+
# List of golf courses to include, if grouping by region
144+
links = None
145+
# A flag that indicates if all regions are to be included
146+
collated_grouped = False
147+
148+
# Get any any entry from the golf links file for the state defined in the location of the item story
149+
state_regions = [s for s in regions.get('states') if s.get('state') == location.get('qcode')]
150+
if len(state_regions):
151+
state_region = state_regions[0]
152+
# Match the value in the take key to any region in the links file
153+
region = [r for r in state_region.get('regions') if
154+
item.get('anpa_take_key', '') and r.get('name', '').lower() == item.get('anpa_take_key', '').lower()]
155+
if len(region):
156+
links = region[0].get('links', [])
157+
else:
158+
# If no match is found then it is assumed that a collated story of all regions is to be produced.
159+
collated_grouped = True
160+
161+
items = list(get_result_items(location, copytakers_desk.get('_id'), stage_ids, midnight_utc))
162+
body = ''
163+
if collated_grouped:
164+
# keep a set of the golf links that have been include so as not to include them multiple times
165+
include_links = set()
166+
for region in state_region.get('regions'):
167+
body += '<p>' + region.get('name') + '</p>'
168+
for i in items:
169+
for l in region.get('links'):
170+
if l.lower().startswith(i.get('slugline', '').lower()) and l not in include_links:
171+
body += i.get('body_html')
172+
include_links.add(l)
173+
else:
174+
for i in items:
175+
if links:
176+
for l in links:
177+
if l.lower().startswith(i.get('slugline', '').lower()):
178+
body += i.get('body_html')
179+
else:
180+
body += i.get('body_html')
181+
182+
if not links:
183+
dayname = datetime.now(pytz.timezone(app.config['DEFAULT_TIMEZONE'])).strftime('%A')
184+
item['anpa_take_key'] = location.get('state', '') + ' ' + dayname
185+
186+
item['body_html'] = body
187+
item['slugline'] = 'Golf Results'
188+
189+
return item
190+
191+
192+
name = 'Golf collation'
193+
label = 'Golf collation'
194+
callback = golf_collation
195+
access_type = 'frontend'
196+
action_type = 'direct'
197+
group = 'Copytakers'
+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# -*- coding: utf-8; -*-
2+
#
3+
# This file is part of Superdesk.
4+
#
5+
# Copyright 2013, 2014 Sourcefabric z.u. and contributors.
6+
#
7+
# For the full copyright and license information, please see the
8+
# AUTHORS and LICENSE files distributed with this source code, or
9+
# at https://www.sourcefabric.org/superdesk/license
10+
11+
from apps.publish import init_app
12+
from .golf_collation import golf_collation
13+
from bson import ObjectId
14+
from datetime import timedelta
15+
from superdesk.utc import utcnow
16+
from unittests import AAPTestCase
17+
18+
19+
class TestGolfCollation(AAPTestCase):
20+
def setUp(self):
21+
init_app(self.app)
22+
self.app.config['DEFAULT_TIMEZONE'] = 'Australia/Sydney'
23+
self.app.data.insert('archive', [
24+
{"place": [{"qcode": "WA"}],
25+
"task": {
26+
"desk": ObjectId("5e1e9474d70421b46535ebe6"),
27+
"stage": ObjectId("5e1e9474d70421b46535ebe4")
28+
},
29+
"headline": "Included 1",
30+
"slugline": "The links A",
31+
"subject": [{"qcode": "15027000"}],
32+
"state": "submitted",
33+
"versioncreated": utcnow() - timedelta(hours=5),
34+
"body_html": "<p>LINKS A: somebody something</p>"
35+
},
36+
{"place": [{"qcode": "WA"}],
37+
"task": {
38+
"desk": ObjectId("5e1e9474d70421b46535ebe6"),
39+
"stage": ObjectId("5e1e9474d70421b46535ebe4")
40+
},
41+
"headline": "Excluded due to slugline",
42+
"slugline": "Golf Results",
43+
"subject": [{"qcode": "15027000"}],
44+
"state": "submitted",
45+
"versioncreated": utcnow() - timedelta(hours=5),
46+
"body_html": "<p>LINKS A: somebody something</p>"
47+
},
48+
{"place": [{"qcode": "WA"}],
49+
"task": {
50+
"desk": ObjectId("5e1e9474d70421b46535ebe6"),
51+
"stage": ObjectId("5e1e9474d70421b46535ebe4")
52+
},
53+
"headline": "Excluded due to age",
54+
"slugline": "The links A",
55+
"subject": [{"qcode": "15027000"}],
56+
"state": "submitted",
57+
"versioncreated": utcnow() - timedelta(hours=25),
58+
"body_html": "<p>LINKS A: somebody something</p>"
59+
},
60+
{"place": [{"qcode": "SA"}],
61+
"task": {
62+
"desk": ObjectId("5e1e9474d70421b46535ebe6"),
63+
"stage": ObjectId("5e1e9474d70421b46535ebe4")
64+
},
65+
"headline": "Included 1",
66+
"slugline": "Echung",
67+
"subject": [{"qcode": "15027000"}],
68+
"state": "submitted",
69+
"versioncreated": utcnow() - timedelta(hours=5),
70+
"body_html": "<p>ECHUNGA: somebody something</p>"
71+
},
72+
{"place": [{"qcode": "SA"}],
73+
"task": {
74+
"desk": ObjectId("5e1e9474d70421b46535ebe6"),
75+
"stage": ObjectId("5e1e9474d70421b46535ebe4")
76+
},
77+
"headline": "Included 2",
78+
"slugline": "Gawler",
79+
"subject": [{"qcode": "15027000"}],
80+
"state": "submitted",
81+
"versioncreated": utcnow() - timedelta(hours=5),
82+
"body_html": "<p>GAWLER: somebody something</p>"
83+
},
84+
{"place": [{"qcode": "SA"}],
85+
"task": {
86+
"desk": ObjectId("5e1e9474d70421b46535ebe6"),
87+
"stage": ObjectId("5e1e9474d70421b46535ebe4")
88+
},
89+
"headline": "Included 3",
90+
"slugline": "Penola",
91+
"subject": [{"qcode": "15027000"}],
92+
"state": "submitted",
93+
"versioncreated": utcnow() - timedelta(hours=5),
94+
"body_html": "<p>PENOLA: somebody something</p>"
95+
},
96+
{"place": [{"qcode": "SA"}],
97+
"task": {
98+
"desk": ObjectId("5e1e9474d70421b46535ebe6"),
99+
"stage": ObjectId("5e1e9474d70421b46535ebe4")
100+
},
101+
"headline": "Excluded due to slugline",
102+
"slugline": "Golf Results",
103+
"subject": [{"qcode": "15027000"}],
104+
"state": "submitted",
105+
"versioncreated": utcnow() - timedelta(hours=5),
106+
"body_html": "<p>LINKS A: somebody something</p>"
107+
},
108+
{"place": [{"qcode": "SA"}],
109+
"task": {
110+
"desk": ObjectId("5e1e9474d70421b46535ebe6"),
111+
"stage": ObjectId("5e1e9474d70421b46535ebe4")
112+
},
113+
"headline": "Excluded due to age",
114+
"slugline": "The links A",
115+
"subject": [{"qcode": "15027000"}],
116+
"state": "submitted",
117+
"versioncreated": utcnow() - timedelta(hours=25),
118+
"body_html": "<p>LINKS A: somebody something</p>"
119+
}
120+
])
121+
self.app.data.insert('desks', [
122+
{'_id': ObjectId("5e1e9474d70421b46535ebe6"), "name": "Copytakers"},
123+
{'_id': ObjectId("123456789009876543221123"), "name": "Nothing to see here"}
124+
])
125+
self.app.data.insert('stages', [
126+
{"_id": ObjectId("5e1e9474d70421b46535ebe4"), "name": "Some Hold stage",
127+
"desk": ObjectId("5e1e9474d70421b46535ebe6")},
128+
{"_id": ObjectId("abababababababababababab"), "name": "Nothing to see here",
129+
"desk": ObjectId("5e1e9474d70421b46535ebe6")}
130+
])
131+
132+
def testWAGolf(self):
133+
item = {'place': [{'qcode': 'WA'}]}
134+
golf_collation(item)
135+
self.assertEqual(item.get('body_html'), '<p>LINKS A: somebody something</p>')
136+
137+
def testSAGolf(self):
138+
item = {'place': [{'qcode': 'SA'}], 'anpa_take_key': 'Country'}
139+
golf_collation(item)
140+
self.assertEqual(item.get('body_html'), '<p>ECHUNGA: somebody something</p>')
141+
142+
def testSACollatedGolf(self):
143+
item = {'place': [{'qcode': 'SA'}]}
144+
golf_collation(item)
145+
self.assertTrue(1)
146+
self.assertEqual(item.get('body_html'), '<p>Metropolitan</p><p>ECHUNGA: somebody something</p>'
147+
'<p>GAWLER: somebody something</p><p>Country</p>'
148+
'<p>Country</p>')

0 commit comments

Comments
 (0)