Example #1
0
    def get_field_type(self, field_name, path=""):
        """ Get field type for given field name.
        field_name is the full path of the field
        path is optional
        """
        model = get_model_from_path_string(
            self.root_model_class, path + field_name)

        # Is it a ORM field?
        try:
            return model._meta.get_field_by_name(
                field_name)[0].get_internal_type()
        except FieldDoesNotExist:
            pass
        # Is it a property?
        field_attr = getattr(model, field_name, None)
        if isinstance(field_attr, (property, cached_property)):
            return "Property"
        # Is it a custom field?
        try:
            model().get_custom_field(field_name)
            return "Custom Field"
        except (ObjectDoesNotExist, AttributeError):
            pass
        return "Invalid"
    def get_field_type(self, field_name, path=""):
        """ Get field type for given field name.
        field_name is the full path of the field
        path is optional
        """
        model = get_model_from_path_string(self.root_model_class,
                                           path + field_name)

        # Is it a ORM field?
        try:
            return model._meta.get_field_by_name(
                field_name)[0].get_internal_type()
        except FieldDoesNotExist:
            pass
        # Is it a property?
        field_attr = getattr(model, field_name, None)
        if isinstance(field_attr, (property, cached_property)):
            return "Property"
        # Is it a custom field?
        try:
            model().get_custom_field(field_name)
            return "Custom Field"
        except (ObjectDoesNotExist, AttributeError):
            pass
        return "Invalid"
Example #3
0
    def report_to_list(self, queryset, display_fields, user, property_filters=[], preview=False):
        """ Create list from a report with all data filtering.

        queryset: initial queryset to generate results
        display_fields: list of field references or DisplayField models
        user: requesting user
        property_filters: ???
        preview: return only first 50 rows

        Returns list, message in case of issues.
        """
        model_class = queryset.model
        #print 'IN REPORT TO LIST'

        def can_change_or_view(model):
            """ Return True iff `user` has either change or view permission
            for `model`.
            """
            try:
                model_name = model._meta.model_name
            except AttributeError:
                # Needed for Django 1.4.* (LTS).
                model_name = model._meta.module_name

            app_label = model._meta.app_label
            can_change = user.has_perm(app_label + '.change_' + model_name)
            can_view = user.has_perm(app_label + '.view_' + model_name)

            return can_change or can_view

        if not can_change_or_view(model_class):
            return [], 'Permission Denied'

        if isinstance(display_fields, list):
            # Convert list of strings to DisplayField objects.
            #print 'CHECKPOINT 1'

            new_display_fields = []

            for display_field in display_fields:
                #print display_field
                field_list = display_field.split('__')
                field = field_list[-1]
                path = '__'.join(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

        # Build group-by field list.

        group = [df.path + df.field for df in display_fields if df.group]

        # To support group-by with multiple fields, we turn all the other
        # fields into aggregations. The default aggregation is `Max`.

        if group:
            for field in display_fields:
                if (not field.group) and (not field.aggregate):
                    field.aggregate = 'Max'

        message = ""
        objects = self.add_aggregates(queryset, display_fields)

        # Display Values

        display_field_paths = []
        property_list = {}
        custom_list = {}
        display_totals = {}
        #print 'CHECKPOINT 2'
        for i, display_field in enumerate(display_fields):
            model = get_model_from_path_string(model_class, display_field.path)

            if display_field.field_type == "Invalid":
                continue

            #print display_field
            #print i
            if not model or can_change_or_view(model):
                display_field_key = display_field.path + display_field.field

                if display_field.field_type == "Property":
                    property_list[i] = display_field_key
                elif display_field.field_type == "Custom Field":
                    custom_list[i] = display_field_key
                elif display_field.aggregate == "Avg":
                    display_field_key += '__avg'
                elif display_field.aggregate == "Max":
                    display_field_key += '__max'
                elif display_field.aggregate == "Min":
                    display_field_key += '__min'
                elif display_field.aggregate == "Count":
                    display_field_key += '__count'
                elif display_field.aggregate == "Sum":
                    display_field_key += '__sum'

                if display_field.field_type not in ('Property', 'Custom Field'):
                    display_field_paths.append(display_field_key)

                if display_field.total:
                    display_totals[display_field_key] = Decimal(0)

            else:
                message += 'Error: Permission denied on access to {0}.'.format(
                    display_field.name
                )

        def increment_total(display_field_key, val):
            """ Increment display total by `val` if given `display_field_key` in
            `display_totals`.
            """
            if display_field_key in display_totals:
                if isinstance(val, bool):
                    # True: 1, False: 0
                    display_totals[display_field_key] += Decimal(val)
                elif isinstance(val, Number):
                    display_totals[display_field_key] += Decimal(str(val))
                elif val:
                    display_totals[display_field_key] += Decimal(1)

        # Select pk for primary and m2m relations in order to retrieve objects
        # for adding properties to report rows. Group-by queries do not support
        # Property nor Custom Field filters.

        if not group:
            display_field_paths.insert(0, 'pk')

            m2m_relations = []
            #print 'm2m_relations'
            for position, property_path in property_list.items():
                property_root = property_path.split('__')[0]
                root_class = model_class

                try:
                    property_root_class = getattr(root_class, property_root)
                except AttributeError: # django-hstore schema compatibility
                    continue

                if type(property_root_class) == ReverseManyRelatedObjectsDescriptor:
                    display_field_paths.insert(1, '%s__pk' % property_root)
                    m2m_relations.append(property_root)

        if group:
            values = objects.values(*group)
            values = self.add_aggregates(values, display_fields)
            filtered_report_rows = [
                [row[field] for field in display_field_paths]
                for row in values
            ]
            #print 'row in filtered'
            for row in filtered_report_rows:
                for pos, field in enumerate(display_field_paths):
                    increment_total(field, row[pos])
        else:
            filtered_report_rows = []
            values_and_properties_list = []

            values_list = objects.values_list(*display_field_paths)
            #print 'CHECKPOINT 3'
            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 property_filter.field_type == 'Custom Field':
                            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 property_filter.filter_property(val):
                        remove_row = True
                        values_and_properties_list.pop()
                        break
                if not remove_row:
                    for i, field in enumerate(display_field_paths[1:]):
                        increment_total(field, row[i + 1])

                    for position, display_property in property_list.items():
                        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:
                            # Could error if a related field doesn't exist
                            try:
                                val = reduce(getattr, relations, obj)
                            except AttributeError:
                                val = None
                        values_and_properties_list[-1].insert(position, val)
                        increment_total(display_property, val)

                    for position, display_custom in custom_list.items():
                        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, val)

                    filtered_report_rows.append(values_and_properties_list[-1])

                if preview and len(filtered_report_rows) == 50:
                    break

        # Sort results if requested.

        if hasattr(display_fields, 'filter'):
            defaults = {
                None: text_type,
                datetime.date: lambda: datetime.date(datetime.MINYEAR, 1, 1),
                datetime.datetime: lambda: datetime.datetime(datetime.MINYEAR, 1, 1),
            }

            # Order sort fields in reverse order so that ascending, descending
            # sort orders work together (based on Python's stable sort). See
            # http://stackoverflow.com/questions/6666748/ for details.

            sort_fields = display_fields.filter(sort__gt=0).order_by('-sort')
            sort_values = sort_fields.values_list('position', 'sort_reverse')

            for pos, reverse in sort_values:
                column = (row[pos] for row in filtered_report_rows)
                type_col = (type(val) for val in column if val is not None)
                field_type = next(type_col, None)
                default = defaults.get(field_type, field_type)()

                filtered_report_rows = sorted(
                    filtered_report_rows,
                    key=lambda row: self.sort_helper(row[pos], default),
                    reverse=reverse,
                )

        values_and_properties_list = filtered_report_rows

        # Build mapping from display field position to choices list.

        choice_lists = {}
        #print ' display fields'
        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[df.position] = df_choices

        # Build mapping from display field position to format.

        display_formats = {}

        for df in display_fields:
            if hasattr(df, 'display_format') and df.display_format:
                display_formats[df.position] = df.display_format

        def formatter(value, style):
            # Convert value to Decimal to apply numeric formats.
            try:
                value = Decimal(value)
            except Exception:
                pass

            try:
                return style.string.format(value)
            except ValueError:
                return value

        # Iterate rows and convert values by choice lists and field formats.

        final_list = []
        #print 'iterate rows and conver values by choice lists'
        for row in values_and_properties_list:
            row = list(row)

            for position, choice_list in choice_lists.items():
                try:
                    row[position] = text_type(choice_list[row[position]])
                except Exception:
                    row[position] = text_type(row[position])

            for pos, style in display_formats.items():
                row[pos] = formatter(row[pos], style)

            final_list.append(row)

        values_and_properties_list = final_list

        if display_totals:
            display_totals_row = []

            fields_and_properties = list(display_field_paths[0 if group else 1:])

            for position, value in property_list.items():
                fields_and_properties.insert(position, value)

            for field in fields_and_properties:
                display_totals_row.append(display_totals.get(field, ''))

            # Add formatting to display totals.

            for pos, style in display_formats.items():
                display_totals_row[pos] = formatter(display_totals_row[pos], style)

            values_and_properties_list.append(
                ['TOTALS'] + (len(fields_and_properties) - 1) * ['']
            )
            values_and_properties_list.append(display_totals_row)
        #print values_and_properties_list
        #print message
        return values_and_properties_list, message
Example #4
0
    def report_to_list(self,
                       queryset,
                       display_fields,
                       user,
                       property_filters=[],
                       preview=False):
        """ Create list from a report with all data filtering.

        queryset: initial queryset to generate results
        display_fields: list of field references or DisplayField models
        user: requesting user
        property_filters: ???
        preview: return only first 50 rows

        Returns list, message in case of issues.
        """
        model_class = queryset.model

        def can_change_or_view(model):
            """ Return True iff `user` has either change or view permission
            for `model`.
            """
            try:
                model_name = model._meta.model_name
            except AttributeError:
                # Needed for Django 1.4.* (LTS).
                model_name = model._meta.module_name

            app_label = model._meta.app_label
            can_change = user.has_perm(app_label + '.change_' + model_name)
            can_view = user.has_perm(app_label + '.view_' + model_name)

            return can_change or can_view

        if not can_change_or_view(model_class):
            return [], 'Permission Denied'

        if isinstance(display_fields, list):
            # Convert list of strings to DisplayField objects.

            new_display_fields = []

            for display_field in display_fields:
                field_list = display_field.split('__')
                field = field_list[-1]
                path = '__'.join(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

        # Build group-by field list.

        group = [df.path + df.field for df in display_fields if df.group]

        # To support group-by with multiple fields, we turn all the other
        # fields into aggregations. The default aggregation is `Max`.

        if group:
            for field in display_fields:
                if (not field.group) and (not field.aggregate):
                    field.aggregate = 'Max'

        message = ""
        objects = self.add_aggregates(queryset, display_fields)

        # Display Values

        display_field_paths = []
        property_list = {}
        custom_list = {}
        display_totals = {}

        for i, display_field in enumerate(display_fields):
            model = get_model_from_path_string(model_class, display_field.path)

            if display_field.field_type == "Invalid":
                continue

            if not model or can_change_or_view(model):
                display_field_key = display_field.path + display_field.field

                if display_field.field_type == "Property":
                    property_list[i] = display_field_key
                elif display_field.field_type == "Custom Field":
                    custom_list[i] = display_field_key
                elif display_field.aggregate == "Avg":
                    display_field_key += '__avg'
                elif display_field.aggregate == "Max":
                    display_field_key += '__max'
                elif display_field.aggregate == "Min":
                    display_field_key += '__min'
                elif display_field.aggregate == "Count":
                    display_field_key += '__count'
                elif display_field.aggregate == "Sum":
                    display_field_key += '__sum'

                if display_field.field_type not in ('Property',
                                                    'Custom Field'):
                    display_field_paths.append(display_field_key)

                if display_field.total:
                    display_totals[display_field_key] = Decimal(0)

            else:
                message += 'Error: Permission denied on access to {0}.'.format(
                    display_field.name)

        def increment_total(display_field_key, val):
            """ Increment display total by `val` if given `display_field_key` in
            `display_totals`.
            """
            if display_field_key in display_totals:
                if isinstance(val, bool):
                    # True: 1, False: 0
                    display_totals[display_field_key] += Decimal(val)
                elif isinstance(val, Number):
                    display_totals[display_field_key] += Decimal(str(val))
                elif val:
                    display_totals[display_field_key] += Decimal(1)

        # Select pk for primary and m2m relations in order to retrieve objects
        # for adding properties to report rows. Group-by queries do not support
        # Property nor Custom Field filters.

        if not group:
            display_field_paths.insert(0, 'pk')

            m2m_relations = []
            for position, property_path in property_list.items():
                property_root = property_path.split('__')[0]
                root_class = model_class

                try:
                    property_root_class = getattr(root_class, property_root)
                except AttributeError:  # django-hstore schema compatibility
                    continue

                if type(property_root_class) == ManyToManyDescriptor:
                    display_field_paths.insert(1, '%s__pk' % property_root)
                    m2m_relations.append(property_root)

        if group:
            values = objects.values(*group)
            values = self.add_aggregates(values, display_fields)
            filtered_report_rows = [
                [row[field] for field in display_field_paths] for row in values
            ]
            for row in filtered_report_rows:
                for pos, field in enumerate(display_field_paths):
                    increment_total(field, row[pos])
        else:
            filtered_report_rows = []
            values_and_properties_list = []

            values_list = objects.values_list(*display_field_paths)

            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 property_filter.field_type == 'Custom Field':
                            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 property_filter.filter_property(val):
                        remove_row = True
                        values_and_properties_list.pop()
                        break
                if not remove_row:
                    for i, field in enumerate(display_field_paths[1:]):
                        increment_total(field, row[i + 1])

                    for position, display_property in property_list.items():
                        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:
                            # Could error if a related field doesn't exist
                            try:
                                val = reduce(getattr, relations, obj)
                            except AttributeError:
                                val = None
                        values_and_properties_list[-1].insert(position, val)
                        increment_total(display_property, val)

                    for position, display_custom in custom_list.items():
                        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, val)

                    filtered_report_rows.append(values_and_properties_list[-1])

                if preview and len(filtered_report_rows) == 50:
                    break

        # Sort results if requested.

        if hasattr(display_fields, 'filter'):
            defaults = {
                None:
                text_type,
                datetime.date:
                lambda: datetime.date(datetime.MINYEAR, 1, 1),
                datetime.datetime:
                lambda: datetime.datetime(datetime.MINYEAR, 1, 1),
            }

            # Order sort fields in reverse order so that ascending, descending
            # sort orders work together (based on Python's stable sort). See
            # http://stackoverflow.com/questions/6666748/ for details.

            sort_fields = display_fields.filter(sort__gt=0).order_by('-sort')
            sort_values = sort_fields.values_list('position', 'sort_reverse')

            for pos, reverse in sort_values:
                column = (row[pos] for row in filtered_report_rows)
                type_col = (type(val) for val in column if val is not None)
                field_type = next(type_col, None)
                default = defaults.get(field_type, field_type)()

                filtered_report_rows = sorted(
                    filtered_report_rows,
                    key=lambda row: self.sort_helper(row[pos], default),
                    reverse=reverse,
                )

        values_and_properties_list = filtered_report_rows

        # Build mapping from display field position to choices list.

        choice_lists = {}
        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[df.position] = df_choices

        # Build mapping from display field position to format.

        display_formats = {}

        for df in display_fields:
            if hasattr(df, 'display_format') and df.display_format:
                display_formats[df.position] = df.display_format

        def formatter(value, style):
            # Convert value to Decimal to apply numeric formats.
            try:
                value = Decimal(value)
            except Exception:
                pass

            try:
                return style.string.format(value)
            except ValueError:
                return value

        # Iterate rows and convert values by choice lists and field formats.

        final_list = []

        for row in values_and_properties_list:
            row = list(row)

            for position, choice_list in choice_lists.items():
                try:
                    row[position] = text_type(choice_list[row[position]])
                except Exception:
                    row[position] = text_type(row[position])

            for pos, style in display_formats.items():
                row[pos] = formatter(row[pos], style)

            final_list.append(row)

        values_and_properties_list = final_list

        if display_totals:
            display_totals_row = []

            fields_and_properties = list(
                display_field_paths[0 if group else 1:])

            for position, value in property_list.items():
                fields_and_properties.insert(position, value)

            for field in fields_and_properties:
                display_totals_row.append(display_totals.get(field, ''))

            # Add formatting to display totals.

            for pos, style in display_formats.items():
                display_totals_row[pos] = formatter(display_totals_row[pos],
                                                    style)

            values_and_properties_list.append(
                ['TOTALS'] + (len(fields_and_properties) - 1) * [''])
            values_and_properties_list.append(display_totals_row)

        return values_and_properties_list, message
Example #5
0
 def test_get_model_from_path_string(self):
     result = get_model_from_path_string(Restaurant, 'waiter__name')
     self.assertEqual(result, Waiter)
 def choices(self):
     if self.pk:
         model = get_model_from_path_string(
             self.report.root_model.model_class(), self.path)
         return self.get_choices(model, self.field)
Example #7
0
 def choices(self):
     if self.pk:
         model = get_model_from_path_string(
             self.report.root_model.model_class(), self.path)
         return self.get_choices(model, self.field)
Example #8
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, 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.items():
                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.items(
                        ):
                            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.items():
                            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"

        # 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.items():
                row[position - 1] = text_type(choice_list[row[position - 1]],
                                              'UTF-8')
            for position, display_format in display_formats.items():
                # 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
Example #9
0
 def test_get_model_from_path_string(self):
     result = get_model_from_path_string(Restaurant, 'waiter__name')
     self.assertEqual(result, Waiter)
Example #10
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, 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.items():
                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.items():
                            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.items():
                            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"

        # 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.items():
                row[position-1] = text_type(choice_list[row[position-1]], 'UTF-8')
            for position, display_format in display_formats.items():
                # 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
Example #11
0
 def list_to_csv_file(self, data, title='report', header=None, widths=None): """ Make a list into a csv response for download. """        wb = self.list_to_workbook(data, title, header, widths) if not title.endswith('.csv'):            title += '.csv'        myfile = StringIO()        sh = wb.get_active_sheet()        c = csv.writer(myfile) for r in sh.rows:            c.writerow([cell.value for cell in r]) return myfile
 def list_to_xlsx_response(self, data, title='report', header=None, widths=None): """ Make 2D list into a xlsx response for download        data can be a 2d array or a dict of 2d arrays        like {'sheet_1': [['A1', 'B1']]} """        wb = self.list_to_workbook(data, title, header, widths) return self.build_xlsx_response(wb, title=title)
 def list_to_csv_response(self, data, title='report', header=None, widths=None): """ Make 2D list into a csv response for download data. """        wb = self.list_to_workbook(data, title, header, widths) return self.build_csv_response(wb, title=title)
 def add_aggregates(self, queryset, display_fields):        agg_funcs = { 'Avg': Avg, 'Min': Min, 'Max': Max, 'Count': Count, 'Sum': Sum        }
 for display_field in display_fields: if display_field.aggregate:                func = agg_funcs[display_field.aggregate]                full_name = display_field.path + display_field.field                queryset = queryset.annotate(func(full_name))
 return queryset
 def report_to_list(self, queryset, display_fields, user, property_filters=[], preview=False): """ Create list from a report with all data filtering.        queryset: initial queryset to generate results        display_fields: list of field references or DisplayField models        user: requesting user        property_filters: ???        preview: return only first 50 rows        Returns list, message in case of issues. """        model_class = queryset.model
 def can_change_or_view(model): """ Return True iff `user` has either change or view permission            for `model`. """ try:                model_name = model._meta.model_name except AttributeError: # Needed for Django 1.4.* (LTS).                model_name = model._meta.module_name
            app_label = model._meta.app_label            can_change = user.has_perm(app_label + '.change_' + model_name)            can_view = user.has_perm(app_label + '.view_' + model_name)
 return can_change or can_view
 if not can_change_or_view(model_class): return [], 'Permission Denied'
 if isinstance(display_fields, list): # Convert list of strings to DisplayField objects.
            new_display_fields = []
 for display_field in display_fields:                field_list = display_field.split('__')                field = field_list[-1]                path = '__'.join(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
 # Build group-by field list.
        group = [df.path + df.field for df in display_fields if df.group]
 # To support group-by with multiple fields, we turn all the other # fields into aggregations. The default aggregation is `Max`.
 if group: for field in display_fields: if (not field.group) and (not field.aggregate):                    field.aggregate = 'Max'
        message = ""        objects = self.add_aggregates(queryset, display_fields)
 # Display Values
        display_field_paths = []        property_list = {}        custom_list = {}        display_totals = {}
 for i, display_field in enumerate(display_fields):            model = get_model_from_path_string(model_class, display_field.path)
 if display_field.field_type == "Invalid": continue
 if not model or can_change_or_view(model):                display_field_key = display_field.path + display_field.field
 if display_field.field_type == "Property":                    property_list[i] = display_field_key elif display_field.field_type == "Custom Field":                    custom_list[i] = display_field_key elif display_field.aggregate == "Avg":                    display_field_key += '__avg' elif display_field.aggregate == "Max":                    display_field_key += '__max' elif display_field.aggregate == "Min":                    display_field_key += '__min' elif display_field.aggregate == "Count":                    display_field_key += '__count' elif display_field.aggregate == "Sum":                    display_field_key += '__sum'
 if display_field.field_type not in ('Property', 'Custom Field'):                    display_field_paths.append(display_field_key)
 if display_field.total:                    display_totals[display_field_key] = Decimal(0)
 else:                message += 'Error: Permission denied on access to {0}.'.format(                    display_field.name                )