From 4a49004f942302c4a7740514c3adae334383a410 Mon Sep 17 00:00:00 2001 From: Aleksey Stryukov Date: Mon, 25 Jun 2018 21:34:44 +0300 Subject: [PATCH 1/3] Tender monitorings list --- openprocurement/audit/api/design.py | 29 +++++++ .../api/tests/test_tender_monitorings.py | 87 +++++++++++++++++++ openprocurement/audit/api/views/tender.py | 67 ++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 openprocurement/audit/api/tests/test_tender_monitorings.py create mode 100644 openprocurement/audit/api/views/tender.py diff --git a/openprocurement/audit/api/design.py b/openprocurement/audit/api/design.py index d3f1bf6f..6c3f9554 100644 --- a/openprocurement/audit/api/design.py +++ b/openprocurement/audit/api/design.py @@ -132,3 +132,32 @@ def add_design(): emit(doc._local_seq, data); } }''' % CHANGES_FIELDS) + + +MONITORINGS_BY_TENDER_FIELDS = [ + 'status', +] + +monitorings_by_tender_id_view = ViewDefinition('monitorings', 'by_tender_id', '''function(doc) { + if(doc.doc_type == 'Monitoring' && !doc.mode) { + var fields=%s, data={}; + for (var i in fields) { + if (doc[fields[i]]) { + data[fields[i]] = doc[fields[i]] + } + } + emit([doc.tender_id, doc.dateCreated], data); + } +}''' % MONITORINGS_BY_TENDER_FIELDS) + +test_monitorings_by_tender_id_view = ViewDefinition('monitorings', 'test_by_tender_id', '''function(doc) { + if(doc.doc_type == 'Monitoring' && doc.mode == 'test') { + var fields=%s, data={}; + for (var i in fields) { + if (doc[fields[i]]) { + data[fields[i]] = doc[fields[i]] + } + } + emit([doc.tender_id, doc.dateCreated], data); + } +}''' % MONITORINGS_BY_TENDER_FIELDS) diff --git a/openprocurement/audit/api/tests/test_tender_monitorings.py b/openprocurement/audit/api/tests/test_tender_monitorings.py new file mode 100644 index 00000000..a1ba1ffe --- /dev/null +++ b/openprocurement/audit/api/tests/test_tender_monitorings.py @@ -0,0 +1,87 @@ +from openprocurement.audit.api.tests.base import BaseWebTest +from openprocurement.audit.api.design import MONITORINGS_BY_TENDER_FIELDS +import unittest + + +class TenderMonitoringsResourceTest(BaseWebTest): + + def test_get_empty_list(self): + response = self.app.get('/tender/f9f9f9/monitorings') + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data'], []) + + def test_get(self): + tender_id = "f" * 32 + + ids = [] + for i in range(10): + self.create_monitoring(tender_id=tender_id) + ids.append(self.monitoring_id) + + for i in range(5): # these are not on the list + self.create_monitoring(tender_id="a" * 32) + + response = self.app.get('/tender/{}/monitorings'.format(tender_id)) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual([e["id"] for e in response.json['data']], ids) + self.assertEqual(set(response.json['data'][0].keys()), + {"id", "dateCreated", "status"}) + + def test_get_custom_fields(self): + tender_id = "a" * 32 + + ids = [] + for i in range(10): + self.create_monitoring(tender_id=tender_id) + ids.append(self.monitoring_id) + + response = self.app.get( + '/tender/{}/monitorings?opt_fields=dateModified%2Creasons'.format( + tender_id + ) + ) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + + self.assertEqual([e["id"] for e in response.json['data']], ids) + self.assertEqual(set(response.json['data'][0].keys()), + {"id", "dateCreated", "dateModified", "status", "reasons"}) + + def test_get_test_empty(self): + tender_id = "a" * 32 + for i in range(10): + self.create_monitoring(tender_id=tender_id, mode="test") + + response = self.app.get( + '/tender/{}/monitorings'.format(tender_id) + ) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data'], []) + + def test_get_test(self): + tender_id = "a" * 32 + + ids = [] + for i in range(10): + self.create_monitoring(tender_id=tender_id, mode="test") + ids.append(self.monitoring_id) + + response = self.app.get( + '/tender/{}/monitorings?mode=test'.format(tender_id) + ) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual([e["id"] for e in response.json['data']], ids) + + +def suite(): + s = unittest.TestSuite() + s.addTest(unittest.makeSuite(TenderMonitoringsResourceTest)) + return s + + +if __name__ == '__main__': + unittest.main(defaultTest='suite') diff --git a/openprocurement/audit/api/views/tender.py b/openprocurement/audit/api/views/tender.py new file mode 100644 index 00000000..53b8b80e --- /dev/null +++ b/openprocurement/audit/api/views/tender.py @@ -0,0 +1,67 @@ +from openprocurement.api.utils import json_view +from openprocurement.audit.api.utils import ( + op_resource, + context_unpack, + APIResource, + monitoring_serialize, +) +from openprocurement.audit.api.design import ( + monitorings_by_tender_id_view, + test_monitorings_by_tender_id_view, + MONITORINGS_BY_TENDER_FIELDS, +) +from logging import getLogger + +LOGGER = getLogger(__name__) + + +@op_resource(name='Tender Monitorings', path='/tender/{tender_id}/monitorings') +class TenderMonitoringResource(APIResource): + + def __init__(self, request, context): + super(TenderMonitoringResource, self).__init__(request, context) + self.views = { + "": monitorings_by_tender_id_view, + "test": test_monitorings_by_tender_id_view, + } + self.default_fields = set(MONITORINGS_BY_TENDER_FIELDS) | {"id", "dateCreated"} + + @json_view(permission='view_listing') + def get(self): + tender_id = self.request.matchdict["tender_id"] + + opt_fields = self.request.params.get('opt_fields', '') + opt_fields = set(e for e in opt_fields.split(',') if e) + + mode = self.request.params.get('mode', '') + list_view = self.views.get(mode, "") + + view_kwargs = dict( + limit=500, # TODO: pagination + startkey=[tender_id, None], + endkey=[tender_id, {}], + ) + + if opt_fields - self.default_fields: + self.LOGGER.info( + 'Used custom fields for monitoring list: {}'.format(','.join(sorted(opt_fields))), + extra=context_unpack(self.request, {'MESSAGE_ID': "CUSTOM_MONITORING_LIST"})) + + results = [ + monitoring_serialize(self.request, i[u'doc'], opt_fields | self.default_fields) + for i in list_view(self.db, include_docs=True, **view_kwargs) + ] + else: + results = [ + dict( + id=e.id, + dateCreated=e.key[1], + **e.value + ) + for e in list_view(self.db, **view_kwargs) + ] + + data = { + 'data': results, + } + return data From 326dec678bc741848bf5cec6a9c0669c4b550937 Mon Sep 17 00:00:00 2001 From: Aleksey Stryukov Date: Tue, 26 Jun 2018 07:26:08 +0300 Subject: [PATCH 2/3] Tender monitorings list / improvement --- .../audit/api/tests/test_tender_monitorings.py | 11 +++++------ openprocurement/audit/api/views/tender.py | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/openprocurement/audit/api/tests/test_tender_monitorings.py b/openprocurement/audit/api/tests/test_tender_monitorings.py index a1ba1ffe..04e246ec 100644 --- a/openprocurement/audit/api/tests/test_tender_monitorings.py +++ b/openprocurement/audit/api/tests/test_tender_monitorings.py @@ -1,12 +1,11 @@ from openprocurement.audit.api.tests.base import BaseWebTest -from openprocurement.audit.api.design import MONITORINGS_BY_TENDER_FIELDS import unittest class TenderMonitoringsResourceTest(BaseWebTest): def test_get_empty_list(self): - response = self.app.get('/tender/f9f9f9/monitorings') + response = self.app.get('/tenders/f9f9f9/monitorings') self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') self.assertEqual(response.json['data'], []) @@ -22,7 +21,7 @@ def test_get(self): for i in range(5): # these are not on the list self.create_monitoring(tender_id="a" * 32) - response = self.app.get('/tender/{}/monitorings'.format(tender_id)) + response = self.app.get('/tenders/{}/monitorings'.format(tender_id)) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') self.assertEqual([e["id"] for e in response.json['data']], ids) @@ -38,7 +37,7 @@ def test_get_custom_fields(self): ids.append(self.monitoring_id) response = self.app.get( - '/tender/{}/monitorings?opt_fields=dateModified%2Creasons'.format( + '/tenders/{}/monitorings?opt_fields=dateModified%2Creasons'.format( tender_id ) ) @@ -55,7 +54,7 @@ def test_get_test_empty(self): self.create_monitoring(tender_id=tender_id, mode="test") response = self.app.get( - '/tender/{}/monitorings'.format(tender_id) + '/tenders/{}/monitorings'.format(tender_id) ) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') @@ -70,7 +69,7 @@ def test_get_test(self): ids.append(self.monitoring_id) response = self.app.get( - '/tender/{}/monitorings?mode=test'.format(tender_id) + '/tenders/{}/monitorings?mode=test'.format(tender_id) ) self.assertEqual(response.status, '200 OK') self.assertEqual(response.content_type, 'application/json') diff --git a/openprocurement/audit/api/views/tender.py b/openprocurement/audit/api/views/tender.py index 53b8b80e..a2b89a15 100644 --- a/openprocurement/audit/api/views/tender.py +++ b/openprocurement/audit/api/views/tender.py @@ -15,7 +15,7 @@ LOGGER = getLogger(__name__) -@op_resource(name='Tender Monitorings', path='/tender/{tender_id}/monitorings') +@op_resource(name='Tender Monitorings', path='/tenders/{tender_id}/monitorings') class TenderMonitoringResource(APIResource): def __init__(self, request, context): From 9f114a2cc0f152813dcc3cdbc6a456099db1c714 Mon Sep 17 00:00:00 2001 From: Aleksey Stryukov Date: Tue, 26 Jun 2018 08:05:41 +0300 Subject: [PATCH 3/3] Add risk_indicator role and forbid active monitoring creation --- openprocurement/audit/api/tests/auth.ini | 5 ++- openprocurement/audit/api/tests/base.py | 1 + .../audit/api/tests/test_monitorings.py | 34 +++++++++++++++++++ openprocurement/audit/api/traversal.py | 1 + openprocurement/audit/api/validation.py | 11 +++++- 5 files changed, 50 insertions(+), 2 deletions(-) diff --git a/openprocurement/audit/api/tests/auth.ini b/openprocurement/audit/api/tests/auth.ini index 22491965..1554de2e 100644 --- a/openprocurement/audit/api/tests/auth.ini +++ b/openprocurement/audit/api/tests/auth.ini @@ -18,4 +18,7 @@ test = token test_sas = test_sas_token [contracting] -test_contracting = test_contracting_token \ No newline at end of file +test_contracting = test_contracting_token + +[risk_indicators] +risk_indicator_bot = test_risk_indicator_bot_token \ No newline at end of file diff --git a/openprocurement/audit/api/tests/base.py b/openprocurement/audit/api/tests/base.py index dd2bf1a1..8ba3f0b7 100644 --- a/openprocurement/audit/api/tests/base.py +++ b/openprocurement/audit/api/tests/base.py @@ -44,6 +44,7 @@ def setUp(self): config.read(os.path.join(os.path.dirname(__file__), 'auth.ini')) self.broker_token = config.get("brokers", "broker") self.sas_token = config.get("sas", "test_sas") + self.risk_indicator_token = config.get("risk_indicators", "risk_indicator_bot") def tearDown(self): del self.couchdb_server[self.db.name] diff --git a/openprocurement/audit/api/tests/test_monitorings.py b/openprocurement/audit/api/tests/test_monitorings.py index 17370d7d..803cde03 100644 --- a/openprocurement/audit/api/tests/test_monitorings.py +++ b/openprocurement/audit/api/tests/test_monitorings.py @@ -54,6 +54,40 @@ def test_post_monitoring_sas(self): ) self.assertEqual(response.json["data"]["status"], "draft") + def test_post_monitoring_risk_bot(self): + self.app.authorization = ('Basic', (self.risk_indicator_token, '')) + response = self.app.post_json( + '/monitorings', + {"data": { + "tender_id": "f" * 32, + "reasons": ["public", "fiscal"], + "procuringStages": ["awarding", "contracting"] + }}, + status=201 + ) + + self.assertIn("data", response.json) + self.assertEqual( + set(response.json["data"]), + {"id", "status", "tender_id", "dateModified", + "dateCreated", "reasons", "monitoring_id", "procuringStages"} + ) + self.assertEqual(response.json["data"]["status"], "draft") + + def test_post_active_monitoring_risk_bot(self): + self.app.authorization = ('Basic', (self.risk_indicator_token, '')) + response = self.app.post_json( + '/monitorings', + {"data": { + "tender_id": "f" * 32, + "reasons": ["public", "fiscal"], + "procuringStages": ["awarding", "contracting"], + "status": "active", + }}, + status=422 + ) + self.assertEqual(response.json["errors"][0]["description"], "Can't create a monitoring in 'active' status") + class BaseFeedResourceTest(BaseWebTest): feed = "" diff --git a/openprocurement/audit/api/traversal.py b/openprocurement/audit/api/traversal.py index f076e293..bb819687 100644 --- a/openprocurement/audit/api/traversal.py +++ b/openprocurement/audit/api/traversal.py @@ -14,6 +14,7 @@ class Root(object): (Allow, Everyone, 'view_monitoring'), (Allow, Everyone, 'revision_monitoring'), (Allow, 'g:brokers', 'generate_credentials'), + (Allow, 'g:risk_indicators', 'create_monitoring'), (Allow, 'g:sas', 'create_monitoring'), (Allow, 'g:sas', 'edit_monitoring'), (Allow, 'g:sas', 'upload_monitoring_documents'), diff --git a/openprocurement/audit/api/validation.py b/openprocurement/audit/api/validation.py index ec5632ba..bbd205cf 100644 --- a/openprocurement/audit/api/validation.py +++ b/openprocurement/audit/api/validation.py @@ -12,7 +12,16 @@ def validate_monitoring_data(request): update_logging_context(request, {'MONITOR_ID': '__new__'}) - return validate_data(request, Monitoring) + data = validate_data(request, Monitoring) + + monitoring = request.validated['monitoring'] + if monitoring.status != "draft": + request.errors.add( + 'body', 'status', "Can't create a monitoring in '{}' status".format(monitoring.status) + ) + request.errors.status = 422 + raise error_handler(request.errors) + return data def validate_patch_monitoring_data(request):