class ExportCountryHistory(BaseSearchModel): """OpenSearch representation of CompanyExportCountryHistory model.""" id = Keyword() history_date = Date(index=False) history_user = fields.id_unindexed_name_field() history_type = Keyword(index=True) country = fields.id_unindexed_name_field() company = fields.id_unindexed_name_field() status = Keyword(index=False) # Adding `date` field, mapping to `history_date` for sorting across entities # export_country_history and interaction. date = Date() MAPPINGS = { 'history_user': dict_utils.id_name_dict, 'country': dict_utils.id_name_dict, 'company': dict_utils.id_name_dict, } COMPUTED_MAPPINGS = { 'id': lambda obj: obj.history_id, # Id required for indexing 'date': lambda obj: obj.history_date, }
def sector_field(): """Sector field.""" ancestors = Object(properties={ 'id': Keyword(), }, ) return Object(properties={ 'id': Keyword(), 'name': NormalizedKeyword(), 'ancestors': ancestors, }, )
def _export_country_field(): return Object(properties={ 'id': Keyword(index=False), 'country': Object(properties={ 'id': Keyword(), 'name': Text(index=False), }, ), 'status': Text(index=False), }, )
def interaction_field(): """Interaction field with id, subject and date.""" return Object(properties={ 'id': Keyword(), 'subject': Text(index=False), 'date': Date(), }, )
class Person(InnerDoc): """Inner doc for a person (e.g. a contact or an adviser).""" id = Keyword() first_name = Text(index=False) last_name = Text(index=False) name = TextWithTrigram()
def _related_investment_project_field(): """Field for a related investment project.""" return Object( properties={ 'id': Keyword(), 'name': fields.NormalizedKeyword(), 'project_code': fields.NormalizedKeyword(), })
def _contact_field(): return Object(properties={ 'id': Keyword(index=False), 'first_name': Text(index=False), 'last_name': Text(index=False), 'name': Text(fields={ 'trigram': TrigramText(), }, ), }, )
def _adviser_field_with_indexed_id(): return Object( properties={ 'id': Keyword(), 'first_name': Text(index=False), 'last_name': Text(index=False), 'name': Text(index=False), }, )
def id_name_partial_field(): """Object field with id and name sub-fields, and with partial matching on name.""" return Object(properties={ 'id': Keyword(), 'name': Text(fields={ 'keyword': NormalizedKeyword(), 'trigram': TrigramText(), }, ), }, )
class SearchRelatedModel(BaseSearchModel): """OpenSearch representation of SimpleModel model.""" id = Keyword() simpleton = fields.id_name_field() MAPPINGS = { 'simpleton': dict_utils.id_name_dict, } SEARCH_FIELDS = ('simpleton.name',)
def company_field(): """Company field with id, name, trading_names and trigrams.""" return Object(properties={ 'id': Keyword(), 'name': Text(fields={ 'trigram': TrigramText(), 'keyword': NormalizedKeyword(), }, ), 'trading_names': TextWithTrigram(), }, )
def contact_or_adviser_field(include_dit_team=False): """Object field for advisers and contacts.""" props = { 'id': Keyword(), 'first_name': NormalizedKeyword(), 'last_name': NormalizedKeyword(), 'name': Text(fields={ 'keyword': NormalizedKeyword(), 'trigram': TrigramText(), }, ), } if include_dit_team: props['dit_team'] = id_name_field() return Object(properties=props)
class SearchSimpleModel(BaseSearchModel): """OpenSearch representation of SimpleModel model.""" id = Keyword() name = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) country = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) address = Text(fields={ 'trigram': fields.TrigramText(), }, ) date = Date() SEARCH_FIELDS = ( 'name', 'name.trigram', 'country.trigram', 'address.trigram', )
def id_name_field(): """Object field with id and name sub-fields.""" return Object(properties={ 'id': Keyword(), 'name': NormalizedKeyword(), }, )
class BaseSearchModel(Document): """Helps convert Django models to dictionaries.""" # This is a replacement for the _type (mapping type name) field which is deprecated in # OpenSearch. It’s required for the aggregations used in global search. _document_type = Keyword() MAPPINGS = {} COMPUTED_MAPPINGS = {} SEARCH_FIELDS = () # Fields that have been renamed in some way, and were used as part of a filter. # While an index migration is in progress, a composite filter must be used so that the # filter works with both the old and new index. # Such fields should be listed in this attribute it make it clear why they're being referenced. # Once the migration is complete in all environments, the composite filter can be updated # and the field removed from here. PREVIOUS_MAPPING_FIELDS = () class Meta: dynamic = MetaField('false') @classmethod def get_app_name(cls): """Get the search app name for this search model.""" return get_search_app_by_search_model(cls).name @classmethod def get_read_alias(cls): """Gets the alias to be used for read operations.""" return f'{settings.OPENSEARCH_INDEX_PREFIX}-{cls.get_app_name()}-read' @classmethod def get_write_alias(cls): """Gets the alias to be used for write operations.""" return f'{settings.OPENSEARCH_INDEX_PREFIX}-{cls.get_app_name()}-write' @classmethod def get_write_index(cls): """Gets the index currently referenced by the write alias.""" indices, = get_indices_for_aliases(cls.get_write_alias()) return _get_write_index(indices) @classmethod def get_read_and_write_indices(cls): """Gets the indices currently referenced by the read and write aliases.""" read_indices, write_indices = get_indices_for_aliases( cls.get_read_alias(), cls.get_write_alias(), ) return read_indices, _get_write_index(write_indices) @classmethod def get_index_prefix(cls): """Gets the prefix used for indices and aliases.""" return f'{settings.OPENSEARCH_INDEX_PREFIX}-{cls.get_app_name()}-' @classmethod def get_target_mapping_hash(cls): """Gets a unique hash digest for mapping (as defined in the code base).""" mapping_data = serialise_mapping(cls._doc_type.mapping.to_dict()) return blake2b(mapping_data, digest_size=16).hexdigest() @classmethod def get_current_mapping_hash(cls): """Extracts and returns the mapping hash from the current index name.""" current_write_index = cls.get_write_index() prefix = cls.get_index_prefix() if not current_write_index.startswith(prefix): logger.warning( f'Unexpected index prefix for search model {cls.get_app_name()} and ' f'index {current_write_index}. It may be a legacy index.', ) return '' return current_write_index[len(prefix):] @classmethod def get_target_index_name(cls): """Generates a unique name for the index based on its mapping.""" mapping_hash = cls.get_target_mapping_hash() prefix = cls.get_index_prefix() return f'{prefix}{mapping_hash}' @classmethod def is_migration_needed(cls): """Returns whether the active mapping is out of date and a migration is needed.""" target_mapping_hash = cls.get_target_mapping_hash() return cls.get_current_mapping_hash() != target_mapping_hash @classmethod def was_migration_started(cls): """ Returns whether a migration was started and has not completed. This could be a a migration still in progress, or an aborted migration. """ read_indices, _ = cls.get_read_and_write_indices() return len(read_indices) != 1 @classmethod def set_up_index_and_aliases(cls): """Creates the index and aliases for this model if they don't already exist.""" if not alias_exists(cls.get_write_alias()): index_name = cls.get_target_index_name() alias_names = (cls.get_write_alias(), cls.get_read_alias()) create_index(index_name, cls._doc_type.mapping, alias_names=alias_names) return True # Should not normally happen if not alias_exists(cls.get_read_alias()): logger.warning( f'Missing read alias {cls.get_read_alias()} detected, recreating the alias...', ) associate_index_with_alias(cls.get_read_alias(), cls.get_write_index()) return False @classmethod def to_document(cls, db_object, index=None, include_index=True, include_source=True): """ Creates a dict representation an OpenSearch document. include_index and include_source can be set to False when the _index and/or _source keys aren't required (e.g. when using `datahub.search.deletion.delete_documents()`). """ doc = { '_id': db_object.pk, } if include_index: doc['_index'] = index or cls.get_write_alias() if include_source: doc['_source'] = cls.db_object_to_dict(db_object) return doc @classmethod def db_object_to_dict(cls, db_object): """Converts a DB model object to a dictionary suitable for OpenSearch.""" mapped_values = ( (col, fn, getattr(db_object, col)) for col, fn in cls.MAPPINGS.items() ) fields = get_model_non_mapped_field_names(cls) result = { **{col: fn(val) if val is not None else None for col, fn, val in mapped_values}, **{col: fn(db_object) for col, fn in cls.COMPUTED_MAPPINGS.items()}, **{field: getattr(db_object, field) for field in fields}, '_document_type': cls.get_app_name(), } return result @classmethod def db_objects_to_documents(cls, db_objects, index=None): """Converts DB model objects to OpenSearch documents.""" for db_object in db_objects: yield cls.to_document(db_object, index=index)
class InvestmentProject(BaseSearchModel): """OpenSearch representation of InvestmentProject.""" id = Keyword() actual_land_date = Date() actual_uk_regions = fields.id_name_field() address_1 = Text() address_2 = Text() address_town = fields.NormalizedKeyword() address_postcode = Text() approved_commitment_to_invest = Boolean() approved_fdi = Boolean() approved_good_value = Boolean() approved_high_value = Boolean() approved_landed = Boolean() approved_non_fdi = Boolean() allow_blank_estimated_land_date = Boolean(index=False) allow_blank_possible_uk_regions = Boolean(index=False) anonymous_description = fields.EnglishText() archived = Boolean() archived_by = fields.contact_or_adviser_field() archived_on = Date() archived_reason = Text() associated_non_fdi_r_and_d_project = _related_investment_project_field() average_salary = fields.id_name_field() business_activities = fields.id_name_field() client_cannot_provide_foreign_investment = Boolean() client_cannot_provide_total_investment = Boolean() client_contacts = fields.contact_or_adviser_field() client_relationship_manager = fields.contact_or_adviser_field( include_dit_team=True) client_requirements = Text(index=False) comments = fields.EnglishText() country_investment_originates_from = fields.id_name_field() country_lost_to = Object(properties={ 'id': Keyword(index=False), 'name': Text(index=False), }, ) created_on = Date() created_by = fields.contact_or_adviser_field(include_dit_team=True) date_abandoned = Date() date_lost = Date() delivery_partners = fields.id_name_field() description = fields.EnglishText() estimated_land_date = Date() export_revenue = Boolean() fdi_type = fields.id_name_field() fdi_value = fields.id_name_field() foreign_equity_investment = Double() government_assistance = Boolean() incomplete_fields = Text() intermediate_company = fields.id_name_field() investor_company = fields.id_name_partial_field() investor_company_country = fields.id_name_field() investment_type = fields.id_name_field() investor_type = fields.id_name_field() level_of_involvement = fields.id_name_field() likelihood_to_land = fields.id_name_field() project_assurance_adviser = fields.contact_or_adviser_field( include_dit_team=True) project_manager = fields.contact_or_adviser_field(include_dit_team=True) name = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) new_tech_to_uk = Boolean() non_fdi_r_and_d_budget = Boolean() number_new_jobs = Integer() number_safeguarded_jobs = Long() modified_on = Date() project_arrived_in_triage_on = Date() project_code = fields.NormalizedKeyword(fields={ 'trigram': fields.TrigramText(), }, ) proposal_deadline = Date() other_business_activity = Text(index=False) quotable_as_public_case_study = Boolean() r_and_d_budget = Boolean() reason_abandoned = Text(index=False) reason_delayed = Text(index=False) reason_lost = Text(index=False) referral_source_activity = fields.id_name_field() referral_source_activity_event = fields.NormalizedKeyword() referral_source_activity_marketing = fields.id_name_field() referral_source_activity_website = fields.id_name_field() referral_source_adviser = Object(properties={ 'id': Keyword(index=False), 'first_name': Text(index=False), 'last_name': Text(index=False), 'name': Text(index=False), }, ) sector = fields.sector_field() site_decided = Boolean() some_new_jobs = Boolean() specific_programme = fields.id_name_field() stage = fields.id_name_field() status = fields.NormalizedKeyword() team_members = fields.contact_or_adviser_field(include_dit_team=True) total_investment = Double() uk_company = fields.id_name_partial_field() uk_company_decided = Boolean() uk_region_locations = fields.id_name_field() will_new_jobs_last_two_years = Boolean() level_of_involvement_simplified = Keyword() latest_interaction = fields.interaction_field() gross_value_added = Double() MAPPINGS = { 'actual_uk_regions': lambda col: [dict_utils.id_name_dict(c) for c in col.all()], 'archived_by': dict_utils.contact_or_adviser_dict, 'associated_non_fdi_r_and_d_project': dict_utils.investment_project_dict, 'average_salary': dict_utils.id_name_dict, 'business_activities': lambda col: [dict_utils.id_name_dict(c) for c in col.all()], 'client_contacts': lambda col: [dict_utils.contact_or_adviser_dict(c) for c in col.all()], 'client_relationship_manager': dict_utils.adviser_dict_with_team, 'country_lost_to': dict_utils.id_name_dict, 'country_investment_originates_from': dict_utils.id_name_dict, 'created_by': dict_utils.adviser_dict_with_team, 'delivery_partners': lambda col: [dict_utils.id_name_dict(c) for c in col.all()], 'fdi_type': dict_utils.id_name_dict, 'fdi_value': dict_utils.id_name_dict, 'intermediate_company': dict_utils.id_name_dict, 'investment_type': dict_utils.id_name_dict, 'investor_company': dict_utils.id_name_dict, 'investor_company_country': dict_utils.id_name_dict, 'investor_type': dict_utils.id_name_dict, 'latest_interaction': dict_utils.interaction_dict, 'level_of_involvement': dict_utils.id_name_dict, 'likelihood_to_land': dict_utils.id_name_dict, 'project_assurance_adviser': dict_utils.adviser_dict_with_team, 'project_code': str, 'project_manager': dict_utils.adviser_dict_with_team, 'referral_source_activity': dict_utils.id_name_dict, 'referral_source_activity_marketing': dict_utils.id_name_dict, 'referral_source_activity_website': dict_utils.id_name_dict, 'referral_source_adviser': dict_utils.contact_or_adviser_dict, 'sector': dict_utils.sector_dict, 'specific_programme': dict_utils.id_name_dict, 'stage': dict_utils.id_name_dict, 'team_members': lambda col: [ dict_utils.contact_or_adviser_dict(c.adviser, include_dit_team=True) for c in col.all() ], 'uk_company': dict_utils.id_name_dict, 'uk_region_locations': lambda col: [dict_utils.id_name_dict(c) for c in col.all()], } SEARCH_FIELDS = ( 'id', 'name', 'name.trigram', 'uk_company.name', 'uk_company.name.trigram', 'investor_company.name', 'investor_company.name.trigram', 'project_code', 'sector.name', )
def ch_company_field(): """Object field with id and company_number sub-fields.""" return Object(properties={ 'id': Keyword(), 'company_number': NormalizedKeyword(), })
def area_field(): """Area field with id, name and trigram.""" return Object(properties={ 'id': Keyword(), 'name': TextWithTrigram(), }, )
def test_creates_index(monkeypatch, mock_connection_for_create_index): """Test creates_index().""" monkeypatch.setattr( 'django.conf.settings.OPENSEARCH_INDEX_SETTINGS', { 'testsetting1': 'testval1', }, ) mapping = Mapping() mapping.field('test-field', Keyword()) index = 'test-index' connection = mock_connection_for_create_index.return_value opensearch_client.create_index(index, mapping, alias_names=('alias1', 'alias2')) connection.indices.create.assert_called_once_with( index='test-index', body={ 'settings': { 'testsetting1': 'testval1', 'analysis': { 'analyzer': { 'trigram_analyzer': { 'tokenizer': 'trigram', 'char_filter': ['special_chars'], 'filter': ['lowercase'], 'type': 'custom', }, 'english_analyzer': { 'tokenizer': 'standard', 'filter': [ 'english_possessive_stemmer', 'lowercase', 'english_stop', 'english_stemmer', ], 'type': 'custom', }, }, 'tokenizer': { 'trigram': { 'min_gram': 3, 'max_gram': 3, 'token_chars': ('letter', 'digit'), 'type': 'nGram', }, }, 'char_filter': { 'special_chars': { 'mappings': ('-=>', ), 'type': 'mapping', }, }, 'filter': { 'english_possessive_stemmer': { 'language': 'possessive_english', 'type': 'stemmer', }, 'english_stop': { 'stopwords': '_english_', 'type': 'stop', }, 'english_stemmer': { 'language': 'english', 'type': 'stemmer', }, }, }, }, 'aliases': { 'alias1': {}, 'alias2': {}, }, 'mappings': { 'properties': { 'test-field': { 'type': 'keyword', }, }, }, }, )
class Company(BaseSearchModel): """ OpenSearch representation of Company model. """ id = Keyword() archived = Boolean() archived_by = fields.contact_or_adviser_field() archived_on = Date() archived_reason = Text() business_type = fields.id_name_field() company_number = fields.NormalizedKeyword() created_on = Date() description = fields.EnglishText() employee_range = fields.id_name_field() export_experience_category = fields.id_name_field() export_to_countries = fields.id_name_field() future_interest_countries = fields.id_name_field() global_headquarters = fields.id_name_field() headquarter_type = fields.id_name_field() modified_on = Date() name = Text( fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) reference_code = fields.NormalizedKeyword() sector = fields.sector_field() address = fields.address_field() registered_address = fields.address_field() one_list_group_global_account_manager = _adviser_field_with_indexed_id() trading_names = fields.TextWithTrigram() turnover_range = fields.id_name_field() uk_region = fields.id_name_field() uk_based = Boolean() uk_address_postcode = fields.PostcodeKeyword() uk_registered_address_postcode = fields.PostcodeKeyword() vat_number = Keyword(index=False) duns_number = Keyword() website = Text() latest_interaction_date = Date() export_segment = Text() export_sub_segment = Text() COMPUTED_MAPPINGS = { 'address': partial(dict_utils.address_dict, prefix='address'), 'registered_address': partial(dict_utils.address_dict, prefix='registered_address'), 'one_list_group_global_account_manager': dict_utils.computed_field_function( 'get_one_list_group_global_account_manager', dict_utils.contact_or_adviser_dict, ), 'export_to_countries': lambda obj: [ dict_utils.id_name_dict(o.country) for o in obj.export_countries.all() if o.status == CompanyExportCountry.Status.CURRENTLY_EXPORTING ], 'future_interest_countries': lambda obj: [ dict_utils.id_name_dict(o.country) for o in obj.export_countries.all() if o.status == CompanyExportCountry.Status.FUTURE_INTEREST ], 'latest_interaction_date': lambda obj: obj.latest_interaction_date, 'uk_address_postcode': lambda obj: obj.address_postcode if obj.uk_based else '', 'uk_registered_address_postcode': lambda obj: obj.registered_address_postcode if obj.uk_based else '', } MAPPINGS = { 'archived_by': dict_utils.contact_or_adviser_dict, 'business_type': dict_utils.id_name_dict, 'employee_range': dict_utils.id_name_dict, 'export_experience_category': dict_utils.id_name_dict, 'global_headquarters': dict_utils.id_name_dict, 'headquarter_type': dict_utils.id_name_dict, 'sector': dict_utils.sector_dict, 'turnover_range': dict_utils.id_name_dict, 'uk_based': bool, 'uk_region': dict_utils.id_name_dict, } SEARCH_FIELDS = ( 'id', 'name', # to find 2-letter words 'name.trigram', 'company_number', 'trading_names', # to find 2-letter words 'trading_names.trigram', 'reference_code', 'sector.name', 'address.line_1.trigram', 'address.line_2.trigram', 'address.town.trigram', 'address.county.trigram', 'address.area.name.trigram', 'address.postcode', 'address.country.name.trigram', 'registered_address.line_1.trigram', 'registered_address.line_2.trigram', 'registered_address.town.trigram', 'registered_address.county.trigram', 'registered_address.area.name.trigram', 'registered_address.postcode', 'registered_address.country.name.trigram', )
class Event(BaseSearchModel): """OpenSearch representation of Event model.""" id = Keyword() address_1 = Text() address_2 = Text() address_town = fields.NormalizedKeyword() address_county = fields.NormalizedKeyword() address_postcode = fields.TextWithTrigram() address_country = fields.id_name_partial_field() created_on = Date() disabled_on = Date() end_date = Date() event_type = fields.id_name_partial_field() lead_team = fields.id_name_field() location_type = fields.id_name_field() modified_on = Date() name = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) notes = fields.EnglishText() organiser = fields.contact_or_adviser_field() related_programmes = fields.id_name_partial_field() service = fields.id_name_field() start_date = Date() teams = fields.id_name_partial_field() uk_region = fields.id_name_partial_field() MAPPINGS = { 'address_country': dict_utils.id_name_dict, 'event_type': dict_utils.id_name_dict, 'lead_team': dict_utils.id_name_dict, 'location_type': dict_utils.id_name_dict, 'organiser': dict_utils.contact_or_adviser_dict, 'related_programmes': lambda col: [dict_utils.id_name_dict(c) for c in col.all()], 'service': dict_utils.id_name_dict, 'teams': lambda col: [dict_utils.id_name_dict(c) for c in col.all()], 'uk_region': dict_utils.id_name_dict, } COMPUTED_MAPPINGS = {} SEARCH_FIELDS = ( 'id', 'name', 'name.trigram', 'address_country.name.trigram', 'address_postcode', 'uk_region.name.trigram', 'organiser.name.trigram', 'teams.name', 'teams.name.trigram', 'related_programmes.name', 'related_programmes.name.trigram', 'event_type.name', 'event_type.name.trigram', )
class LargeInvestorProfile(BaseSearchModel): """OpenSearch representation of LargeInvestorProfile.""" id = Keyword() investor_company = fields.company_field() country_of_origin = fields.country_field() asset_classes_of_interest = fields.id_unindexed_name_field() created_by = fields.contact_or_adviser_field(include_dit_team=True) investor_type = fields.id_unindexed_name_field() global_assets_under_management = Long() investable_capital = Long() required_checks_conducted = fields.id_unindexed_name_field() deal_ticket_sizes = fields.id_unindexed_name_field() investment_types = fields.id_unindexed_name_field() minimum_return_rate = fields.id_unindexed_name_field() time_horizons = fields.id_unindexed_name_field() restrictions = fields.id_unindexed_name_field() construction_risks = fields.id_unindexed_name_field() minimum_equity_percentage = fields.id_unindexed_name_field() desired_deal_roles = fields.id_unindexed_name_field() uk_region_locations = fields.id_unindexed_name_field() other_countries_being_considered = fields.country_field() investor_description = fields.EnglishText() notes_on_locations = fields.EnglishText() created_on = Date() modified_on = Date() _MAIN_FIELD_MAPPINGS = { 'asset_classes_of_interest': _get_many_to_many_list, 'country_of_origin': dict_utils.id_name_dict, 'investor_company': dict_utils.company_dict, 'created_by': dict_utils.adviser_dict_with_team, } _DETAIL_FIELD_MAPPINGS = { 'investor_type': dict_utils.id_name_dict, 'required_checks_conducted': dict_utils.id_name_dict, } _REQUIREMENT_FIELD_MAPPINGS = { 'deal_ticket_sizes': _get_many_to_many_list, 'investment_types': _get_many_to_many_list, 'minimum_return_rate': dict_utils.id_name_dict, 'time_horizons': _get_many_to_many_list, 'restrictions': _get_many_to_many_list, 'construction_risks': _get_many_to_many_list, 'minimum_equity_percentage': dict_utils.id_name_dict, 'desired_deal_roles': _get_many_to_many_list, } _LOCATION_FIELD_MAPPINGS = { 'uk_region_locations': _get_many_to_many_list, 'other_countries_being_considered': _get_many_to_many_list, } MAPPINGS = { **_MAIN_FIELD_MAPPINGS, **_DETAIL_FIELD_MAPPINGS, **_REQUIREMENT_FIELD_MAPPINGS, **_LOCATION_FIELD_MAPPINGS, }
class Contact(BaseSearchModel): """OpenSearch representation of Contact model.""" id = Keyword() address_1 = Text() address_2 = Text() address_town = fields.NormalizedKeyword() address_county = fields.NormalizedKeyword() address_postcode = Text() address_country = fields.id_name_field() address_area = fields.id_name_field() address_same_as_company = Boolean() adviser = fields.contact_or_adviser_field() archived = Boolean() archived_by = fields.contact_or_adviser_field() archived_on = Date() archived_reason = Text() company = fields.company_field() company_sector = fields.sector_field() company_uk_region = fields.id_name_field() created_by = fields.contact_or_adviser_field(include_dit_team=True) created_on = Date() email = fields.NormalizedKeyword() first_name = Text(fields={ 'keyword': fields.NormalizedKeyword(), }, ) job_title = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) last_name = Text(fields={ 'keyword': fields.NormalizedKeyword(), }, ) modified_on = Date() name = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) name_with_title = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) notes = fields.EnglishText() primary = Boolean() full_telephone_number = Keyword() title = fields.id_name_field() MAPPINGS = { 'adviser': dict_utils.contact_or_adviser_dict, 'archived_by': dict_utils.contact_or_adviser_dict, 'company': dict_utils.company_dict, 'created_by': dict_utils.adviser_dict_with_team, 'title': dict_utils.id_name_dict, } COMPUTED_MAPPINGS = { 'address_1': contact_dict_utils.computed_address_field('address_1'), 'address_2': contact_dict_utils.computed_address_field('address_2'), 'address_town': contact_dict_utils.computed_address_field('address_town'), 'address_county': contact_dict_utils.computed_address_field('address_county'), 'address_postcode': contact_dict_utils.computed_address_field('address_postcode'), 'address_country': contact_dict_utils.computed_address_field('address_country'), 'address_area': contact_dict_utils.computed_address_field('address_area'), 'company_sector': dict_utils.computed_nested_sector_dict('company.sector'), 'company_uk_region': dict_utils.computed_nested_id_name_dict('company.uk_region'), } SEARCH_FIELDS = ( 'id', 'name', 'name.trigram', 'name_with_title', 'name_with_title.trigram', 'email', 'company.name', 'company.name.trigram', 'job_title', 'job_title.trigram', 'full_telephone_number', )
def id_unindexed_name_field(): """Object field with id and unindexed name sub-fields.""" return Object(properties={ 'id': Keyword(), 'name': Keyword(index=False), }, )
class Interaction(BaseSearchModel): """OpenSearch representation of Interaction model.""" id = Keyword() company = fields.company_field() companies = fields.company_field() company_sector = fields.sector_field() company_one_list_group_tier = fields.id_unindexed_name_field() communication_channel = fields.id_unindexed_name_field() contacts = _contact_field() created_on = Date() date = Date() dit_participants = Object(_DITParticipant) event = fields.id_name_partial_field() investment_project = fields.id_unindexed_name_field() investment_project_sector = fields.sector_field() is_event = Boolean(index=False) grant_amount_offered = Double(index=False) kind = Keyword() modified_on = Date() net_company_receipt = Double(index=False) notes = fields.Text(index=False) policy_areas = fields.id_unindexed_name_field() policy_issue_types = fields.id_unindexed_name_field() service = fields.id_name_partial_field() service_delivery_status = fields.id_unindexed_name_field() subject = fields.NormalizedKeyword(fields={ 'english': fields.EnglishText(), }, ) was_policy_feedback_provided = Boolean() were_countries_discussed = Boolean() export_countries = _export_country_field() MAPPINGS = { 'company': dict_utils.company_dict, 'companies': _companies_list, 'communication_channel': dict_utils.id_name_dict, 'contacts': dict_utils.contact_or_adviser_list_of_dicts, 'dit_participants': _dit_participant_list, 'export_countries': _export_countries_list, 'event': dict_utils.id_name_dict, 'investment_project': dict_utils.id_name_dict, 'policy_areas': dict_utils.id_name_list_of_dicts, 'policy_issue_types': dict_utils.id_name_list_of_dicts, 'service': dict_utils.id_name_dict, 'service_delivery_status': dict_utils.id_name_dict, } COMPUTED_MAPPINGS = { 'company_sector': dict_utils.computed_nested_sector_dict('company.sector'), 'company_one_list_group_tier': lambda obj: dict_utils.id_name_dict( obj.company.get_one_list_group_tier() if obj.company else None, ), 'investment_project_sector': dict_utils.computed_nested_sector_dict('investment_project.sector', ), 'is_event': attrgetter('is_event'), } SEARCH_FIELDS = ( 'id', 'company.name', 'company.name.trigram', 'companies.name', 'companies.name.trigram', 'contacts.name', # to find 2-letter words 'contacts.name.trigram', 'event.name', 'event.name.trigram', 'service.name', 'service.name.trigram', 'subject.english', 'dit_participants.adviser.name', 'dit_participants.adviser.name.trigram', 'dit_participants.team.name', 'dit_participants.team.name.trigram', )
class LargeCapitalOpportunity(BaseSearchModel): """OpenSearch representation of LargeCapitalOpportunity.""" id = Keyword() name = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) type = fields.id_unindexed_name_field() description = Text(fields={ 'keyword': fields.NormalizedKeyword(), 'trigram': fields.TrigramText(), }, ) uk_region_locations = fields.id_unindexed_name_field() promoters = fields.company_field() required_checks_conducted = fields.id_unindexed_name_field() required_checks_conducted_by = fields.contact_or_adviser_field( include_dit_team=True) required_checks_conducted_on = Date() lead_dit_relationship_manager = fields.contact_or_adviser_field( include_dit_team=True) other_dit_contacts = fields.contact_or_adviser_field(include_dit_team=True) asset_classes = fields.id_unindexed_name_field() opportunity_value_type = fields.id_unindexed_name_field() opportunity_value = Long() construction_risks = fields.id_unindexed_name_field() total_investment_sought = Long() current_investment_secured = Long() investment_types = fields.id_unindexed_name_field() estimated_return_rate = fields.id_unindexed_name_field() time_horizons = fields.id_unindexed_name_field() investment_projects = fields.id_unindexed_name_field() status = fields.id_unindexed_name_field() sources_of_funding = fields.id_unindexed_name_field() dit_support_provided = Boolean() reasons_for_abandonment = fields.id_unindexed_name_field() created_by = fields.contact_or_adviser_field(include_dit_team=True) created_on = Date() modified_on = Date() _MAIN_FIELD_MAPPINGS = { 'type': dict_utils.id_name_dict, 'status': dict_utils.id_name_dict, 'created_by': dict_utils.adviser_dict_with_team, } _DETAIL_FIELD_MAPPINGS = { 'uk_region_locations': _get_many_to_many_list, 'promoters': _get_company_list, 'required_checks_conducted': dict_utils.id_name_dict, 'required_checks_conducted_by': dict_utils.adviser_dict_with_team, 'lead_dit_relationship_manager': dict_utils.adviser_dict_with_team, 'other_dit_contacts': _get_adviser_list, 'asset_classes': _get_many_to_many_list, 'opportunity_value_type': dict_utils.id_name_dict, 'construction_risks': _get_many_to_many_list, 'investment_projects': _get_investment_project_list, 'sources_of_funding': _get_many_to_many_list, 'reasons_for_abandonment': _get_many_to_many_list, } _REQUIREMENT_FIELD_MAPPINGS = { 'investment_types': _get_many_to_many_list, 'estimated_return_rate': dict_utils.id_name_dict, 'time_horizons': _get_many_to_many_list, } MAPPINGS = { **_MAIN_FIELD_MAPPINGS, **_DETAIL_FIELD_MAPPINGS, **_REQUIREMENT_FIELD_MAPPINGS, }
class IDNameTrigram(InnerDoc): """Inner doc for a named object, with a trigram sub-field on name.""" id = Keyword() name = TextWithTrigram()
def country_field(): """Country field with id, name and trigram.""" return Object(properties={ 'id': Keyword(), 'name': TextWithTrigram(), }, )
class Order(BaseSearchModel): """OpenSearch representation of Order model.""" id = Keyword() reference = fields.NormalizedKeyword( fields={ 'trigram': fields.TrigramText(), }, ) status = fields.NormalizedKeyword() company = fields.company_field() contact = fields.contact_or_adviser_field() created_by = fields.contact_or_adviser_field(include_dit_team=True) created_on = Date() modified_on = Date() primary_market = fields.id_name_field() sector = fields.sector_field() uk_region = fields.id_name_field() description = fields.EnglishText() contacts_not_to_approach = Text() further_info = Text() existing_agents = Text(index=False) delivery_date = Date() service_types = fields.id_name_field() contact_email = fields.NormalizedKeyword() contact_phone = Keyword() subscribers = fields.contact_or_adviser_field(include_dit_team=True) assignees = fields.contact_or_adviser_field(include_dit_team=True) po_number = Keyword(index=False) discount_value = Integer(index=False) vat_status = Keyword(index=False) vat_number = Keyword(index=False) vat_verified = Boolean(index=False) net_cost = Integer(index=False) subtotal_cost = Integer( fields={ 'keyword': Keyword(), }, ) vat_cost = Integer(index=False) total_cost = Integer( fields={ 'keyword': Keyword(), }, ) payment_due_date = Date() paid_on = Date() completed_by = fields.contact_or_adviser_field() completed_on = Date() cancelled_by = fields.contact_or_adviser_field() cancelled_on = Date() cancellation_reason = fields.id_name_field() billing_company_name = Text() billing_contact_name = Text() billing_email = fields.NormalizedKeyword() billing_phone = fields.NormalizedKeyword() billing_address_1 = Text() billing_address_2 = Text() billing_address_town = fields.NormalizedKeyword() billing_address_county = fields.NormalizedKeyword() billing_address_postcode = Text() billing_address_country = fields.id_name_field() MAPPINGS = { 'company': dict_utils.company_dict, 'contact': dict_utils.contact_or_adviser_dict, 'created_by': dict_utils.adviser_dict_with_team, 'primary_market': dict_utils.id_name_dict, 'sector': dict_utils.sector_dict, 'uk_region': dict_utils.id_name_dict, 'service_types': lambda col: [dict_utils.id_name_dict(c) for c in col.all()], 'subscribers': lambda col: [ dict_utils.contact_or_adviser_dict(c.adviser, include_dit_team=True) for c in col.all() ], 'assignees': lambda col: [ dict_utils.contact_or_adviser_dict(c.adviser, include_dit_team=True) for c in col.all() ], 'billing_address_country': dict_utils.id_name_dict, 'completed_by': dict_utils.contact_or_adviser_dict, 'cancelled_by': dict_utils.contact_or_adviser_dict, 'cancellation_reason': dict_utils.id_name_dict, } COMPUTED_MAPPINGS = { 'payment_due_date': lambda x: x.invoice.payment_due_date if x.invoice else None, } SEARCH_FIELDS = ( 'id', 'reference.trigram', 'company.name', 'company.name.trigram', 'contact.name', 'contact.name.trigram', 'total_cost.keyword', 'subtotal_cost.keyword', 'sector.name', 'uk_region.name', )