Esempio n. 1
0
    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
Esempio n. 2
0
    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