def report_to_list(self, queryset, display_fields, user, property_filters=[], preview=False): """ Create list from a report with all data filtering preview: Return only first 50 objects: Provide objects for list, instead of running filters display_fields: a list of fields or a report_builder display field model Returns list, message in case of issues """ model_class = queryset.model if isinstance(display_fields, QuerySet): # Make it a report_builder.models.DisplayField like object new_display_fields = [] for display_field in display_fields: field_list = display_field.field.split("__") field = field_list[-1] new_model = get_model_from_path_string(model_class, display_field.path) model_field = new_model._meta.get_field_by_name(field)[0] choices = model_field.choices # path path_verbose field field_verbose aggregate total group display_format position choices new_display_fields.append( DisplayField( display_field.path, "", field, "", display_field.aggregate, display_field.total, display_field.group, display_field.display_format, display_field.position, choices, ) ) display_fields = new_display_fields message = "" # Display Values display_field_paths = [] select_fields = [] property_list = {} custom_list = {} display_totals = {} def append_display_total(display_totals, display_field, display_field_key): if display_field.total: display_totals[display_field_key] = {"val": Decimal("0.00")} for i, display_field in enumerate(display_fields): model = get_model_from_path_string(model_class, display_field.path) if ( user.has_perm(model._meta.app_label + ".change_" + model._meta.module_name) or user.has_perm(model._meta.app_label + ".view_" + model._meta.module_name) or not model ): # TODO: clean this up a bit display_field_key = display_field.path + display_field.field if "[property]" in display_field.field_verbose: property_list[i] = display_field_key append_display_total(display_totals, display_field, display_field_key) elif "[custom" in display_field.field_verbose: custom_list[i] = display_field_key append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Avg": display_field_key += "__avg" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Max": display_field_key += "__max" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Min": display_field_key += "__min" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Count": display_field_key += "__count" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Sum": display_field_key += "__sum" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) else: display_field_paths += [display_field_key] select_fields += [display_field_key] append_display_total(display_totals, display_field, display_field_key) else: message += "You don't have permission to " + display_field.name if user.has_perm(model_class._meta.app_label + ".change_" + model_class._meta.model_name) or user.has_perm( model_class._meta.app_label + ".view_" + model_class._meta.model_name ): values_and_properties_list = [] filtered_report_rows = [] # compose the list of values for the queryset group = [f.path + f.field for f in display_fields if f.group] objects = queryset.values(*select_fields) objects = self.add_aggregates(objects, display_fields) values_list = objects.order_by(*group) for row in values_list: values_and_properties_list.append(row.values()) if preview and len(filtered_report_rows) == 50: break if hasattr(display_fields, "filter"): sort_fields = ( display_fields.filter(sort__gt=0).order_by("-sort").values_list("position", "sort_reverse") ) for sort_field in sort_fields: try: filtered_report_rows = sorted( filtered_report_rows, key=lambda x: self.sort_helper(x, sort_field[0] - 1), reverse=sort_field[1], ) except TypeError: # Sorry crappy way to determine if date is being sorted filtered_report_rows = sorted( filtered_report_rows, key=lambda x: self.sort_helper(x, sort_field[0] - 1, date_field=True), reverse=sort_field[1], ) values_and_properties_list = filtered_report_rows else: values_and_properties_list = [] message = "Permission Denied on %s" % report.root_model.name # add choice list display and display field formatting choice_lists = {} display_formats = {} for df in display_fields: if df.choices and hasattr(df, "choices_dict"): df_choices = df.choices_dict # Insert blank and None as valid choices df_choices[""] = "" df_choices[None] = "" choice_lists.update({df.position: df_choices}) if hasattr(df, "display_format") and df.display_format: display_formats.update({df.position: df.display_format}) if display_totals: display_totals_row = [] fields_and_properties = list(display_field_paths[1:]) for position, value in property_list.iteritems(): fields_and_properties.insert(position, value) for i, field in enumerate(fields_and_properties): if field in display_totals.keys(): display_totals_row += [display_totals[field]["val"]] else: display_totals_row += [""] # add formatting to display totals for df in display_fields: if df.display_format: try: value = Decimal(display_totals_row[df.position - 1]) except: value = display_totals_row[df.position - 1] # Fall back to original value if format string and value # aren't compatible, e.g. a numerically-oriented format # string with value which is not numeric. try: value = df.display_format.string.format(value) except ValueError: pass display_totals_row[df.position - 1] = value values_and_properties_list = ( values_and_properties_list + [["TOTALS"] + (len(fields_and_properties) - 1) * [""]] + [display_totals_row] ) return values_and_properties_list, message
def report_to_list_old(self, queryset, display_fields, user, property_filters=[], preview=False): """ Create list from a report with all data filtering preview: Return only first 50 objects: Provide objects for list, instead of running filters display_fields: a list of fields or a report_builder display field model Returns list, message in case of issues """ model_class = queryset.model if isinstance(display_fields, list): # Make it a report_builder.models.DisplayField like object new_display_fields = [] for display_field in display_fields: field_list = display_field.split("__") field = field_list[-1] path = "__".join([str(x) for x in field_list[:-1]]) if path: path += "__" # Legacy format to append a __ here. new_model = get_model_from_path_string(model_class, path) model_field = new_model._meta.get_field_by_name(field)[0] choices = model_field.choices new_display_fields.append(DisplayField(path, "", field, "", "", None, None, choices)) display_fields = new_display_fields message = "" objects = self.add_aggregates(queryset, display_fields) # Display Values display_field_paths = [] property_list = {} custom_list = {} display_totals = {} def append_display_total(display_totals, display_field, display_field_key): if display_field.total: display_totals[display_field_key] = {"val": Decimal("0.00")} for i, display_field in enumerate(display_fields): model = get_model_from_path_string(model_class, display_field.path) if ( user.has_perm(model._meta.app_label + ".change_" + model._meta.module_name) or user.has_perm(model._meta.app_label + ".view_" + model._meta.module_name) or not model ): # TODO: clean this up a bit display_field_key = display_field.path + display_field.field if "[property]" in display_field.field_verbose: property_list[i] = display_field_key append_display_total(display_totals, display_field, display_field_key) elif "[custom" in display_field.field_verbose: custom_list[i] = display_field_key append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Avg": display_field_key += "__avg" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Max": display_field_key += "__max" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Min": display_field_key += "__min" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Count": display_field_key += "__count" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) elif display_field.aggregate == "Sum": display_field_key += "__sum" display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) else: display_field_paths += [display_field_key] append_display_total(display_totals, display_field, display_field_key) else: message += "You don't have permission to " + display_field.name if user.has_perm(model_class._meta.app_label + ".change_" + model_class._meta.model_name) or user.has_perm( model_class._meta.app_label + ".view_" + model_class._meta.model_name ): def increment_total(display_field_key, display_totals, val): if display_totals.has_key(display_field_key): # Booleans are Numbers - blah if isinstance(val, Number) and not isinstance(val, bool): # do decimal math for all numbers display_totals[display_field_key]["val"] += Decimal(str(val)) else: display_totals[display_field_key]["val"] += Decimal("1.00") # get pk for primary and m2m relations in order to retrieve objects # for adding properties to report rows display_field_paths.insert(0, "pk") m2m_relations = [] for position, property_path in property_list.iteritems(): property_root = property_path.split("__")[0] root_class = model_class property_root_class = getattr(root_class, property_root) if type(property_root_class) == ReverseManyRelatedObjectsDescriptor: display_field_paths.insert(1, "%s__pk" % property_root) m2m_relations.append(property_root) values_and_properties_list = [] filtered_report_rows = [] group = None for df in display_fields: if df.group: group = df.path + df.field break if group: filtered_report_rows = self.add_aggregates(objects.values_list(group), display_fields) else: values_list = objects.values_list(*display_field_paths) if not group: for row in values_list: row = list(row) values_and_properties_list.append(row[1:]) obj = None # we will get this only if needed for more complex processing # related_objects remove_row = False # filter properties (remove rows with excluded properties) for property_filter in property_filters: if not obj: obj = model_class.objects.get(pk=row.pop(0)) root_relation = property_filter.path.split("__")[0] if root_relation in m2m_relations: pk = row[0] if pk is not None: # a related object exists m2m_obj = getattr(obj, root_relation).get(pk=pk) val = reduce(getattr, [property_filter.field], m2m_obj) else: val = None else: if "[custom" in property_filter.field_verbose: for relation in property_filter.path.split("__"): if hasattr(obj, root_relation): obj = getattr(obj, root_relation) val = obj.get_custom_value(property_filter.field) else: val = reduce(getattr, (property_filter.path + property_filter.field).split("__"), obj) if filter_property(property_filter, val): remove_row = True values_and_properties_list.pop() break if not remove_row: # increment totals for fields for i, field in enumerate(display_field_paths[1:]): if field in display_totals.keys(): increment_total(field, display_totals, row[i]) for position, display_property in property_list.iteritems(): if not obj: obj = model_class.objects.get(pk=row.pop(0)) relations = display_property.split("__") root_relation = relations[0] if root_relation in m2m_relations: pk = row.pop(0) if pk is not None: # a related object exists m2m_obj = getattr(obj, root_relation).get(pk=pk) val = reduce(getattr, relations[1:], m2m_obj) else: val = None else: try: # Could error if a related field doesn't exist val = reduce(getattr, relations, obj) except AttributeError: val = None values_and_properties_list[-1].insert(position, val) increment_total(display_property, display_totals, val) for position, display_custom in custom_list.iteritems(): if not obj: obj = model_class.objects.get(pk=row.pop(0)) val = obj.get_custom_value(display_custom) values_and_properties_list[-1].insert(position, val) increment_total(display_custom, display_totals, val) filtered_report_rows += [values_and_properties_list[-1]] if preview and len(filtered_report_rows) == 50: break if hasattr(display_fields, "filter"): sort_fields = ( display_fields.filter(sort__gt=0).order_by("-sort").values_list("position", "sort_reverse") ) for sort_field in sort_fields: try: filtered_report_rows = sorted( filtered_report_rows, key=lambda x: self.sort_helper(x, sort_field[0] - 1), reverse=sort_field[1], ) except TypeError: # Sorry crappy way to determine if date is being sorted filtered_report_rows = sorted( filtered_report_rows, key=lambda x: self.sort_helper(x, sort_field[0] - 1, date_field=True), reverse=sort_field[1], ) values_and_properties_list = filtered_report_rows else: values_and_properties_list = [] message = "Permission Denied on %s" % report.root_model.name # add choice list display and display field formatting choice_lists = {} display_formats = {} final_list = [] for df in display_fields: if df.choices and hasattr(df, "choices_dict"): df_choices = df.choices_dict # Insert blank and None as valid choices df_choices[""] = "" df_choices[None] = "" choice_lists.update({df.position: df_choices}) if hasattr(df, "display_format") and df.display_format: display_formats.update({df.position: df.display_format}) for row in values_and_properties_list: # add display totals for grouped result sets # TODO: dry this up, duplicated logic in non-grouped total routine if group: # increment totals for fields for i, field in enumerate(display_field_paths[1:]): if field in display_totals.keys(): increment_total(field, display_totals, row[i]) row = list(row) for position, choice_list in choice_lists.iteritems(): row[position - 1] = unicode(choice_list[row[position - 1]]) for position, display_format in display_formats.iteritems(): # convert value to be formatted into Decimal in order to apply # numeric formats try: value = Decimal(row[position - 1]) except: value = row[position - 1] # Try to format the value, let it go without formatting for ValueErrors try: row[position - 1] = display_format.string.format(value) except ValueError: row[position - 1] = value final_list.append(row) values_and_properties_list = final_list if display_totals: display_totals_row = [] fields_and_properties = list(display_field_paths[1:]) for position, value in property_list.iteritems(): fields_and_properties.insert(position, value) for i, field in enumerate(fields_and_properties): if field in display_totals.keys(): display_totals_row += [display_totals[field]["val"]] else: display_totals_row += [""] # add formatting to display totals for df in display_fields: if df.display_format: try: value = Decimal(display_totals_row[df.position - 1]) except: value = display_totals_row[df.position - 1] # Fall back to original value if format string and value # aren't compatible, e.g. a numerically-oriented format # string with value which is not numeric. try: value = df.display_format.string.format(value) except ValueError: pass display_totals_row[df.position - 1] = value values_and_properties_list = ( values_and_properties_list + [["TOTALS"] + (len(fields_and_properties) - 1) * [""]] + [display_totals_row] ) return values_and_properties_list, message