def _get_inheritance_filters_for_single_instance(self): """ Return queryset filter for CustomFieldValue with inheritance for single instance. Final format of queryset will look similar to: (Q(content_type_id=X) & Q(object_id=Y)) | (Q(content_type_id=A) & Q(object_id=B)) | ... # noqa """ inheritance_filters = [ # custom field of instance models.Q( **{self.content_type_field_name: self.content_type.id}) & # object id of instance models.Q(**{self.object_id_field_name: self.instance.id}) ] # for each related field (foreign key), add it's content_type # and object_id to queryset filter for field_path in self.instance.custom_fields_inheritance: content_type = _get_content_type_from_field_path( self.instance, field_path) value = getattr_dunder(self.instance, field_path) # filter only if related field has some value if value: inheritance_filters.append( models.Q( **{self.content_type_field_name: content_type.id}) & models.Q(**{self.object_id_field_name: value.pk})) return reduce(operator.or_, inheritance_filters)
def prepare(self, model, *args, **kwargs): queryset = BaseObjectsSupport.objects.select_related( 'support', 'baseobject__asset__property_of', 'baseobject__service_env__service' ).prefetch_related( # AttachmentItem is attached to BaseObject (by content type), # so we need to prefetch attachments through base object of support 'support__baseobject_ptr__attachments__attachment', 'support__baseobjectssupport_set', ) headers = [] select_related = [] if model._meta.object_name == 'DataCenterAsset': headers = self.dc_headers select_related = self.dc_select_related queryset = queryset.filter( baseobject__content_type=ContentType.objects.get_for_model( DataCenterAsset ) ) elif model._meta.object_name == 'BackOfficeAsset': headers = self.bo_headers select_related = self.bo_select_related queryset = queryset.filter( baseobject__content_type=ContentType.objects.get_for_model( BackOfficeAsset ) ) yield headers + self.extra_headers for bos in queryset.select_related(*select_related): row = [str(getattr_dunder(bos, column)) for column in headers] row += self.get_extra_columns(bos) yield row
def get_field_value(self, item, field): """ Returns the value for the given field name. Looking in: If the field is type Choices returns choice name else returns the value of row :param item: row from dict :param field: field name """ value = None if hasattr(self, field): value = getattr(self, field)(item) else: value = getattr_dunder(item, field) try: choice_class = get_field_by_relation_path( item._meta.model, field ).choices except FieldDoesNotExist: choice_class = None if ( use_choices and choice_class and isinstance(choice_class, Choices) ): value = choice_class.name_from_id(value) return value
def report_failure(self, item): item_dict = model_to_dict(item) url = settings.MY_EQUIPMENT_REPORT_FAILURE_URL if url: placeholders = [ k[1] for k in Formatter().parse(url) if k[1] is not None ] item_dict.update({ k: getattr_dunder(item, k) for k in placeholders }) if self.request and 'username' not in item_dict: item_dict['username'] = self.request.user.username def escape_param(p): """ Escape URL param and replace quotation by unicode inches sign """ return quote(str(p).replace('"', '\u2033')) return '<a href="{}" target="_blank">{}</a><br />'.format( url.format( **{k: escape_param(v) for (k, v) in item_dict.items()} ), _('Report failure') ) return ''
def test_getattr_dunder(self): """getattr_dunder works recursively""" class A(): pass a = A() a.b = A() a.b.name = 'spam' self.assertEqual(getattr_dunder(a, 'b__name'), 'spam')
def clean(self): bo_asset = getattr_dunder( self.base_object, 'asset__backofficeasset' ) if ( bo_asset and self.licence and self.licence.region_id != bo_asset.region_id ): raise ValidationError( _('Asset region is in a different region than licence.') )
def prepare(self, model, *args, **kwargs): queryset = model.objects.prefetch_related('tags') headers = self.bo_headers select_related = self.bo_select_related if model._meta.object_name == 'DataCenterAsset': headers = self.dc_headers select_related = self.dc_select_related yield headers + self.extra_headers for asset in queryset.select_related(*select_related): row = [str(getattr_dunder(asset, column)) for column in headers] row += self.get_extra_columns(asset) yield row
def create_report_link(self, url, url_title, item): item_dict = model_to_dict(item) if url: placeholders = [ k[1] for k in Formatter().parse(url) if k[1] is not None ] item_dict.update( {k: getattr_dunder(item, k) for k in placeholders}) if self.request and 'username' not in item_dict: item_dict['username'] = self.request.user.username def escape_param(p): """ Escape URL param and replace quotation by unicode inches sign """ return quote_plus(quotation_to_inches(str(p))) return '<a href="{}" target="_blank">{}</a><br />'.format( url.format( **{k: escape_param(v) for (k, v) in item_dict.items()}), _(url_title)) return ''
def prepare(self, model, *args, **kwargs): queryset = Licence.objects.select_related('region', 'software') asset_related = [None] if model._meta.object_name == 'BackOfficeAsset': queryset = queryset.filter( software__asset_type__in=( ObjectModelType.back_office, ObjectModelType.all ) ) asset_related = [ 'base_object__asset', 'base_object__asset__backofficeasset', 'base_object__asset__backofficeasset__user', 'base_object__asset__backofficeasset__owner', 'base_object__asset__backofficeasset__region' ] if model._meta.object_name == 'DataCenterAsset': queryset = queryset.filter( software__asset_type=ObjectModelType.data_center ) asset_related = [ 'base_object__asset', 'base_object__asset__backofficeasset' ] fill_empty_assets = [''] * len(self.licences_asset_headers) fill_empty_licences = [''] * len(self.licences_users_headers) headers = self.licences_headers + self.licences_asset_headers + \ self.licences_users_headers + ['single_cost'] yield headers queryset = queryset.select_related( 'software' ).prefetch_related( Prefetch( 'licenceuser_set', queryset=LicenceUser.objects.select_related('user') ), Prefetch( 'baseobjectlicence_set', queryset=BaseObjectLicence.objects.select_related( *asset_related ) ) ) for licence in queryset: row = [ smart_str(getattr_dunder(licence, column)) for column in self.licences_headers ] base_row = row row = row + fill_empty_assets + fill_empty_licences + [''] yield row if licence.number_bought > 0 and licence.price: single_licence_cost = str( licence.price.amount / licence.number_bought ) else: single_licence_cost = '' for asset in licence.baseobjectlicence_set.all(): row = [ smart_str( getattr_dunder(asset.base_object, column), ) for column in self.licences_asset_headers ] yield base_row + row + fill_empty_licences + [ single_licence_cost ] for user in licence.licenceuser_set.all(): row = [ smart_str(getattr_dunder(user, column)) for column in self.licences_users_headers ] yield base_row + fill_empty_assets + row + [ single_licence_cost ]
def get_prefetch_queryset(self, instances, queryset=None): """ Prefetch custom fields with inheritance of values for multiple instances. This implementation is one-big-workaround for Django's handling of prefetch_related (especially in django.db.models.query:prefetch_one_level) """ if queryset is None: queryset = super().get_queryset().select_related( 'custom_field') queryset._add_hints(instance=instances[0]) queryset = queryset.using(queryset._db or self._db) content_type = ContentType.objects.get_for_model(instances[0]) # store possible content types of CustomFieldValue content_types = set([content_type]) # store possible values of object id objects_ids = set() # mapping from instance id to content_type and object id of # dependent fields for inheritance instances_cfs = defaultdict(set) # process every instance (no inheritance here right now) for instance in instances: objects_ids.add(instance.pk) instances_cfs[instance.pk].add((content_type.pk, instance.pk)) # process each dependent field from `custom_fields_inheritance` for field_path in self.instance.custom_fields_inheritance: # assume that field is foreign key content_type = _get_content_type_from_field_path( self.instance, field_path) content_types.add(content_type) # for each instance, get value of this dependent field for instance in instances: value = getattr_dunder(instance, field_path) if value: objects_ids.add(value.pk) # store mapping from instance to content type and value # of dependent field to know, which CustomFieldValue # assign later to instance instances_cfs[instance.pk].add( (content_type.pk, value.pk)) # filter by possible content types and objects ids # notice that thus this filter is not perfect (filter separately # for content types and for objects ids, without correlation # between particular content type and value), this simplify # (and speedup) query # alternative solution is to do something similar to fetching # custom fields for single instance: correlate content type with # possible values for this single content type, for example: # (Q(content_type_id=A) & Q(object_id__in=[B, C, D])) | (Q(content_type_id=X) & Q(object_id__in=[Y, Z])) | ... # noqa query = { '%s__in' % self.content_type_field_name: content_types, '%s__in' % self.object_id_field_name: set(objects_ids) } qs = list(queryset.filter(**query)) # this fragment of code (at least similar one) is normally done in # Django's `prefetch_one_level`, but it does NOT handle M2M case # (that single prefetched object (in this case CustomFieldValue) # could be assigned to multiple instances - it works only with # many-to-one case) # mapping from content_type and object_id to `CustomFieldValue`s rel_obj_cache = defaultdict(list) for rel_obj in qs: rel_obj_cache[(rel_obj.content_type_id, rel_obj.object_id)].append(rel_obj) # for each instance reconstruct it's CustomFieldValues # using `instances_cfs` mapping (from instance pk to content_type # and object_id of possible CustomFieldValue) for obj in instances: vals = [] # fetch `CustomFieldsValue`s for this instance - use mapping # from instance pk to content type id and object id of it's # dependent fields for content_type_id, obj_id in instances_cfs[obj.id]: try: vals.extend(rel_obj_cache[(content_type_id, obj_id)]) except KeyError: # ignore if there is no CustomFieldValue for such # content_type_id and object_id pass vals = [ v[1] for v in _prioritize_custom_field_values( vals, self.instance, self.content_type) ] # store `CustomFieldValue`s of instance in cache instance_custom_fields_queryset = obj.custom_fields.all() instance_custom_fields_queryset._result_cache = vals instance_custom_fields_queryset._prefetch_done = True obj._prefetched_objects_cache[ self.prefetch_cache_name] = instance_custom_fields_queryset # main "hack" for Django - since all querysets are already # prefetched, `prefetch_one_level` has nothing to do, thus # `rel_obj_attr` and `instance_attr` (second and third item) # returns constant value, to not assign it to any object. return ( qs, lambda relobj: None, lambda obj: -1, # most important part of this workaround - return single=True, # to force `prefetch_one_level` to just do setattr to instance # under returned cache_name (suffixed with '__empty' below) True, # cache_name is also changed to not assign empty result # to `custom_fields` (and overwrite prefetched custom fields # assigned above) self.prefetch_cache_name + '__empty')