From 88e9f71b95ab4a2f60eec2ff2dba768bdd5dbfe5 Mon Sep 17 00:00:00 2001 From: andrey Date: Thu, 24 Jan 2019 18:17:03 +0200 Subject: [PATCH 1/4] Change document_of field choices --- openprocurement/auctions/rubble/models.py | 24 ++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/openprocurement/auctions/rubble/models.py b/openprocurement/auctions/rubble/models.py index bfb5e12..880fe38 100644 --- a/openprocurement/auctions/rubble/models.py +++ b/openprocurement/auctions/rubble/models.py @@ -72,6 +72,16 @@ def validator(klass, data, value): return validator +class RubbleDocument(dgfCDB2Document): + documentOf = StringType( + required=True, + choices=[ + 'auction', + 'item', + 'lot'], + default='auction') + + class Bid(BaseBid): class Options: roles = { @@ -79,7 +89,7 @@ class Options: } status = StringType(choices=['active', 'draft', 'invalid'], default='active') - documents = ListType(ModelType(dgfCDB2Document), default=list()) + documents = ListType(ModelType(RubbleDocument), default=list()) qualified = BooleanType(required=True, choices=[True]) @bids_validation_wrapper @@ -88,7 +98,7 @@ def validate_value(self, data, value): class Cancellation(dgfCancellation): - documents = ListType(ModelType(dgfCDB2Document), default=list()) + documents = ListType(ModelType(RubbleDocument), default=list()) def rounding_shouldStartAfter(start_after, auction, use_from=datetime(2016, 6, 1, tzinfo=TZ)): @@ -191,7 +201,7 @@ def __local_roles__(self): complaints = ListType(ModelType(dgfCDB2Complaint), default=list()) contracts = ListType(ModelType(Contract), default=list()) dgfID = StringType() - documents = ListType(ModelType(dgfCDB2Document), default=list()) # All documents and attachments related to the auction. + documents = ListType(ModelType(RubbleDocument), default=list()) # All documents and attachments related to the auction. enquiryPeriod = ModelType(Period) # The period during which enquiries may be made and will be answered. rectificationPeriod = ModelType(RectificationPeriod) # The period during which editing of main procedure fields are allowed tenderPeriod = ModelType(Period) # The period when the auction is open for submissions. The end date is the closing date for auction submissions. @@ -302,7 +312,7 @@ def next_check(self): # Rubble Financial models -class dgfFinCDB2Document(dgfCDB2Document): +class RubbleFinancialDocument(RubbleDocument): documentType = StringType(choices=[ 'auctionNotice', 'awardNotice', 'contractNotice', 'notice', 'biddingDocuments', 'technicalSpecifications', @@ -318,7 +328,7 @@ class dgfFinCDB2Document(dgfCDB2Document): 'x_presentation', 'x_nda', ]) -dgfFinCDB2Document.__name__ = 'Document' +RubbleFinancialDocument.__name__ = 'Document' class Bid(Bid): @@ -326,7 +336,7 @@ class Options: roles = { 'create': whitelist('value', 'tenderers', 'parameters', 'lotValues', 'status', 'qualified', 'eligible'), } - documents = ListType(ModelType(dgfFinCDB2Document), default=list()) + documents = ListType(ModelType(RubbleFinancialDocument), default=list()) tenderers = ListType(ModelType(FinancialOrganization), required=True, min_size=1, max_size=1) eligible = BooleanType(required=True, choices=[True]) @@ -335,7 +345,7 @@ class Options: class Auction(RubbleOther): """Data regarding auction process - publicly inviting prospective contractors to submit bids for evaluation and selecting a winner or winners.""" _internal_type = "rubbleFinancial" - documents = ListType(ModelType(dgfFinCDB2Document), default=list()) # All documents and attachments related to the auction. + documents = ListType(ModelType(RubbleFinancialDocument), default=list()) # All documents and attachments related to the auction. bids = ListType(ModelType(Bid), default=list()) procurementMethodType = StringType() eligibilityCriteria = StringType(default=u"До участі допускаються лише ліцензовані фінансові установи.") From 05db8b7e625b8c0d55fdcde3ae532388fa47b6a8 Mon Sep 17 00:00:00 2001 From: andrey484 Date: Tue, 26 Feb 2019 13:11:39 +0200 Subject: [PATCH 2/4] Add migrations and tests for it --- openprocurement/auctions/rubble/migration.py | 43 +++++- openprocurement/auctions/rubble/models.py | 20 +-- .../rubble/tests/blanks/migration_blanks.py | 125 ++++++++++++++++++ .../auctions/rubble/tests/migration.py | 45 ++++++- 4 files changed, 221 insertions(+), 12 deletions(-) diff --git a/openprocurement/auctions/rubble/migration.py b/openprocurement/auctions/rubble/migration.py index 97ab5ac..30cb061 100644 --- a/openprocurement/auctions/rubble/migration.py +++ b/openprocurement/auctions/rubble/migration.py @@ -6,12 +6,22 @@ ) from openprocurement.auctions.core.traversal import Root from openprocurement.auctions.core.utils import ( - get_plugins, get_procurement_method_types, get_now + get_plugins, get_procurement_method_types, get_now, migrate_all_document_of_tender +) + +from openprocurement.api.migration import ( + BaseMigrationsRunner, + BaseMigrationStep, + MigrationResourcesDTO, + AliasesInfoDTO ) LOGGER = logging.getLogger(__name__) SCHEMA_VERSION = 1 SCHEMA_DOC = 'openprocurement_auctions_dgf_schema' +PACKAGE_ALIASES = { + 'openprocurement.auctions.rubble': ['rubbleOther', 'rubbleFinancial'] +} def get_db_schema_version(db): @@ -74,3 +84,34 @@ def __init__(self, registry): docs = [] if docs: registry.db.update(docs) + + +class RubbleMigrationsRunner(BaseMigrationsRunner): + + SCHEMA_VERSION = 1 + SCHEMA_DOC = SCHEMA_DOC + + +class DocumentOfStep(BaseMigrationStep): + + def setUp(self): + self.view = 'auctions/all' + self.procurement_method_types = self.resources.aliases_info.get_package_aliases( + 'openprocurement.auctions.rubble' + ) + + def migrate_document(self, auction): + if auction['procurementMethodType'] in self.procurement_method_types: + changed = migrate_all_document_of_tender(auction) + return auction if changed else None + return None + + +MIGRATION_STEPS = (DocumentOfStep, ) + + +def migrate(db): + aliases_info_dto = AliasesInfoDTO(PACKAGE_ALIASES) + migration_resource_dto = MigrationResourcesDTO(db, aliases_info_dto) + runner = RubbleMigrationsRunner(migration_resource_dto) + runner.migrate(MIGRATION_STEPS) diff --git a/openprocurement/auctions/rubble/models.py b/openprocurement/auctions/rubble/models.py index 880fe38..2a9a5ae 100644 --- a/openprocurement/auctions/rubble/models.py +++ b/openprocurement/auctions/rubble/models.py @@ -32,8 +32,8 @@ validate_lots_uniq, validate_not_available, ) -from openprocurement.auctions.core.plugins.awarding.v2_1.models import Award -from openprocurement.auctions.core.plugins.contracting.v2_1.models import Contract +from openprocurement.auctions.core.plugins.awarding.v2_1.models import Award as BaseAward +from openprocurement.auctions.core.plugins.contracting.v2_1.models import Contract as BaseContract from openprocurement.auctions.core.utils import ( AUCTIONS_COMPLAINT_STAND_STILL_TIME as COMPLAINT_STAND_STILL_TIME, SANDBOX_MODE, @@ -73,13 +73,7 @@ def validator(klass, data, value): class RubbleDocument(dgfCDB2Document): - documentOf = StringType( - required=True, - choices=[ - 'auction', - 'item', - 'lot'], - default='auction') + documentOf = StringType(required=True, choices=['auction', 'item', 'lot', 'tender'], default='auction') class Bid(BaseBid): @@ -97,6 +91,14 @@ def validate_value(self, data, value): BaseBid._validator_functions['value'](self, data, value) +class Award(BaseAward): + documents = ListType(ModelType(RubbleDocument), default=list()) + + +class Contract(BaseContract): + documents = ListType(ModelType(RubbleDocument), default=list()) + + class Cancellation(dgfCancellation): documents = ListType(ModelType(RubbleDocument), default=list()) diff --git a/openprocurement/auctions/rubble/tests/blanks/migration_blanks.py b/openprocurement/auctions/rubble/tests/blanks/migration_blanks.py index 5cac833..7794fdb 100644 --- a/openprocurement/auctions/rubble/tests/blanks/migration_blanks.py +++ b/openprocurement/auctions/rubble/tests/blanks/migration_blanks.py @@ -1217,3 +1217,128 @@ def migrate_unsuccessful_unsuccessful_active(self): self.assertEqual(auction['awards'][1]['status'], 'unsuccessful') self.assertEqual(auction['awards'][2]['status'], 'active') self.assertEqual(auction['contracts'][0]['status'], 'pending') + + +def migrate_document_of_auction_documents(self): + auction = self.db.get(self.auction_id) + self.assertIsNone(auction.get('documents')) + auction['documents'] = [self.test_document_data] + + auction.update(auction) + self.db.save(auction) + + response = self.app.get('/auctions/{}'.format(self.auction_id)) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data']['documents'][0]['documentOf'], 'tender') + + self.runner.migrate(self.steps) + + response = self.app.get('/auctions/{}'.format(self.auction_id)) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data']['documents'][0]['documentOf'], 'auction') + + +def migrate_document_of_bids_documents(self): + auction = self.db.get(self.auction_id) + bid, bid_id = self.initial_bids[0], self.initial_bids[0]['id'] + + auction['bids'] = [self.initial_bids[0]] + auction['status'] = 'complete' + auction['bids'][0]['documents'] = [self.test_document_data] + auction.update(auction) + self.db.save(auction) + + response = self.app.get( + '/auctions/{}/bids/{}/documents?acc_token={}'.format(self.auction_id, bid_id, self.initial_bids_tokens[bid_id])) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data'][0]['documentOf'], 'tender') + + self.runner.migrate(self.steps) + + response = self.app.get( + '/auctions/{}/bids/{}/documents?acc_token={}'.format(self.auction_id, bid_id, self.initial_bids_tokens[bid_id])) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data'][0]['documentOf'], 'auction') + + +def migrate_document_of_awards_documents(self): + auction = self.db.get(self.auction_id) + _id = uuid4().hex + award = { + 'id': _id, + 'bid_id': self.initial_bids[0]['id'], + "documents": [self.test_document_data] + } + + auction['bids'] = [self.initial_bids[0]] + auction['awards'] = [award] + auction.update(auction) + self.db.save(auction) + + response = self.app.get('/auctions/{}/awards/{}/documents'.format(self.auction_id, _id)) + self.assertEqual(response.json['data'][0]['documentOf'], 'tender') + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + + self.runner.migrate(self.steps) + + response = self.app.get('/auctions/{}/awards/{}/documents'.format(self.auction_id, _id)) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data'][0]['documentOf'], 'auction') + + +def migrate_document_of_contracts_documents(self): + auction = self.db.get(self.auction_id) + _id = uuid4().hex + contract = { + 'id': _id, + "documents": [self.test_document_data] + } + + auction['contracts'] = [contract] + auction.update(auction) + self.db.save(auction) + + response = self.app.get('/auctions/{}/contracts/{}/documents'.format(self.auction_id, _id)) + self.assertEqual(response.json['data'][0]['documentOf'], 'tender') + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + + self.runner.migrate(self.steps) + + response = self.app.get('/auctions/{}/contracts/{}/documents'.format(self.auction_id, _id)) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data'][0]['documentOf'], 'auction') + + +def migrate_document_of_cancellations_documents(self): + + auction = self.db.get(self.auction_id) + id = uuid4().hex + + auction['cancellations'] = [{ + "reason": "cancellation reason", + "id": id, + "documents": [self.test_document_data], + }] + + auction.update(auction) + self.db.save(auction) + + response = self.app.get('/auctions/{}/cancellations/{}/documents'.format(self.auction_id, id)) + self.assertEqual(response.json['data'][0]['documentOf'], 'tender') + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + + self.runner.migrate(self.steps) + + response = self.app.get('/auctions/{}/cancellations/{}/documents'.format(self.auction_id, id)) + self.assertEqual(response.status, '200 OK') + self.assertEqual(response.content_type, 'application/json') + self.assertEqual(response.json['data'][0]['documentOf'], 'auction') diff --git a/openprocurement/auctions/rubble/tests/migration.py b/openprocurement/auctions/rubble/tests/migration.py index 54bb770..8545172 100644 --- a/openprocurement/auctions/rubble/tests/migration.py +++ b/openprocurement/auctions/rubble/tests/migration.py @@ -7,7 +7,7 @@ from openprocurement.auctions.core.tests.base import snitch from openprocurement.auctions.rubble.migration import migrate_data, get_db_schema_version, set_db_schema_version, SCHEMA_VERSION -from openprocurement.auctions.rubble.tests.base import BaseWebTest, BaseAuctionWebTest, test_bids +from openprocurement.auctions.rubble.tests.base import BaseWebTest, BaseAuctionWebTest, test_bids, test_auction_data from openprocurement.auctions.rubble.tests.blanks.migration_blanks import ( # MigrateTestFrom1To2Bids migrate_one_pending, @@ -28,9 +28,20 @@ migrate_awards_number, # MigrateTestFrom1To2WithThreeBids migrate_unsuccessful_unsuccessful_pending, - migrate_unsuccessful_unsuccessful_active + migrate_unsuccessful_unsuccessful_active, + # MigrateDocumentOf + migrate_document_of_auction_documents, + migrate_document_of_bids_documents, + migrate_document_of_awards_documents, + migrate_document_of_contracts_documents, + migrate_document_of_cancellations_documents ) +from openprocurement.auctions.rubble.migration import ( + RubbleMigrationsRunner, DocumentOfStep, PACKAGE_ALIASES +) +from openprocurement.api.tests.fixtures.mocks import MigrationResourcesDTO_mock + class MigrateTest(BaseWebTest): @@ -99,12 +110,42 @@ def setUp(self): self.db.save(auction) +class MigrateTestDocumentOf(BaseAuctionWebTest): + initial_data = test_auction_data + initial_bids = test_bids + test_document_data = { + 'title': 'auction_protocol.pdf', + 'hash': 'md5:' + '0' * 32, + 'format': 'application/msword', + "description": "auction protocol", + "documentType": 'auctionProtocol', + 'url': 'http://localhost/test', + 'documentOf': 'tender' + + } + test_migrate_document_of_auction_documents = snitch(migrate_document_of_auction_documents) + test_migrate_document_of_bids_documents = snitch(migrate_document_of_bids_documents) + test_migrate_document_of_awards_documents = snitch(migrate_document_of_awards_documents) + test_migrate_document_of_contracts_documents = snitch(migrate_document_of_contracts_documents) + test_migrate_document_of_cancellations_documents = snitch(migrate_document_of_cancellations_documents) + + def setUp(self): + super(MigrateTestDocumentOf, self).setUp() + self.runner = RubbleMigrationsRunner( + MigrationResourcesDTO_mock( + self.db, PACKAGE_ALIASES + ) + ) + self.steps = (DocumentOfStep, ) + + def suite(): suite = unittest.TestSuite() suite.addTest(unittest.makeSuite(MigrateTest)) suite.addTest(unittest.makeSuite(MigrateTestFrom1To2Bids)) suite.addTest(unittest.makeSuite(MigrateTestFrom1To2WithTwoBids)) suite.addTest(unittest.makeSuite(MigrateTestFrom1To2WithThreeBids)) + suite.addTest(unittest.makeSuiter(MigrateTestDocumentOf)) return suite From cbd6aab84351d248508abd17a4cc95175c621e5f Mon Sep 17 00:00:00 2001 From: andrey484 Date: Mon, 4 Mar 2019 17:35:48 +0200 Subject: [PATCH 3/4] Relocate base document model to core package --- openprocurement/auctions/rubble/models.py | 24 ++++++++--------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/openprocurement/auctions/rubble/models.py b/openprocurement/auctions/rubble/models.py index 2a9a5ae..bef7d5c 100644 --- a/openprocurement/auctions/rubble/models.py +++ b/openprocurement/auctions/rubble/models.py @@ -23,9 +23,9 @@ auction_embedded_role, calc_auction_end_time, dgfCDB2Complaint, - dgfCDB2Document, + AuctionDocument, dgfCDB2Item, - dgfCancellation, + AuctionCancellation, edit_role, get_auction, validate_items_uniq, @@ -72,10 +72,6 @@ def validator(klass, data, value): return validator -class RubbleDocument(dgfCDB2Document): - documentOf = StringType(required=True, choices=['auction', 'item', 'lot', 'tender'], default='auction') - - class Bid(BaseBid): class Options: roles = { @@ -83,7 +79,7 @@ class Options: } status = StringType(choices=['active', 'draft', 'invalid'], default='active') - documents = ListType(ModelType(RubbleDocument), default=list()) + documents = ListType(ModelType(AuctionDocument), default=list()) qualified = BooleanType(required=True, choices=[True]) @bids_validation_wrapper @@ -92,15 +88,11 @@ def validate_value(self, data, value): class Award(BaseAward): - documents = ListType(ModelType(RubbleDocument), default=list()) + documents = ListType(ModelType(AuctionDocument), default=list()) class Contract(BaseContract): - documents = ListType(ModelType(RubbleDocument), default=list()) - - -class Cancellation(dgfCancellation): - documents = ListType(ModelType(RubbleDocument), default=list()) + documents = ListType(ModelType(AuctionDocument), default=list()) def rounding_shouldStartAfter(start_after, auction, use_from=datetime(2016, 6, 1, tzinfo=TZ)): @@ -199,11 +191,11 @@ def __local_roles__(self): _internal_type = "rubbleOther" awards = ListType(ModelType(Award), default=list()) bids = ListType(ModelType(Bid), default=list()) # A list of all the companies who entered submissions for the auction. - cancellations = ListType(ModelType(Cancellation), default=list()) + cancellations = ListType(ModelType(AuctionCancellation), default=list()) complaints = ListType(ModelType(dgfCDB2Complaint), default=list()) contracts = ListType(ModelType(Contract), default=list()) dgfID = StringType() - documents = ListType(ModelType(RubbleDocument), default=list()) # All documents and attachments related to the auction. + documents = ListType(ModelType(AuctionDocument), default=list()) # All documents and attachments related to the auction. enquiryPeriod = ModelType(Period) # The period during which enquiries may be made and will be answered. rectificationPeriod = ModelType(RectificationPeriod) # The period during which editing of main procedure fields are allowed tenderPeriod = ModelType(Period) # The period when the auction is open for submissions. The end date is the closing date for auction submissions. @@ -314,7 +306,7 @@ def next_check(self): # Rubble Financial models -class RubbleFinancialDocument(RubbleDocument): +class RubbleFinancialDocument(AuctionDocument): documentType = StringType(choices=[ 'auctionNotice', 'awardNotice', 'contractNotice', 'notice', 'biddingDocuments', 'technicalSpecifications', From 8b7ede60164b65cd449d12e6606678c40f6a3dd8 Mon Sep 17 00:00:00 2001 From: andrey484 Date: Mon, 11 Mar 2019 09:39:37 +0200 Subject: [PATCH 4/4] Refactor migrations --- openprocurement/auctions/rubble/migration.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/openprocurement/auctions/rubble/migration.py b/openprocurement/auctions/rubble/migration.py index 30cb061..fa7b293 100644 --- a/openprocurement/auctions/rubble/migration.py +++ b/openprocurement/auctions/rubble/migration.py @@ -11,9 +11,7 @@ from openprocurement.api.migration import ( BaseMigrationsRunner, - BaseMigrationStep, - MigrationResourcesDTO, - AliasesInfoDTO + BaseMigrationStep ) LOGGER = logging.getLogger(__name__) @@ -110,8 +108,6 @@ def migrate_document(self, auction): MIGRATION_STEPS = (DocumentOfStep, ) -def migrate(db): - aliases_info_dto = AliasesInfoDTO(PACKAGE_ALIASES) - migration_resource_dto = MigrationResourcesDTO(db, aliases_info_dto) - runner = RubbleMigrationsRunner(migration_resource_dto) +def migrate(resources): + runner = RubbleMigrationsRunner(resources) runner.migrate(MIGRATION_STEPS)