Beispiel #1
0
    def render(self, form, form_style, context, template_pack=TEMPLATE_PACK):
        """Render form"""
        inline_form = form.get_inline_forms()[self.form_name]

        if hasattr(inline_form, 'forms'):
            config = models_config.get_config(inline_form.form._meta.model)
        else:
            config = models_config.get_config(inline_form._meta.model)

        return render_to_string(
            self.template, {
                'inline_form_prefix': self.form_name,
                'inline_form_verbose_name': config.get_verbose_name(),
                'inline_form': inline_form,
            }, utils.get_current_request())
Beispiel #2
0
def ajaxFormModelChoices(request, id):
    """View for select2 ajax data request"""
    if id not in ModelAjaxChoiceField.registered_fields:
        return JsonResponse({})

    field = ModelAjaxChoiceField.registered_fields[id]
    query = field.queryset.order_by('verbose_name')

    search = request.GET.get('q', None)
    if search:
        config = models_config.get_config(query.model)
        if not config.disable_search_index:
            query = watson.filter(query, search, ranking=False)
        else:
            query = query.filter(verbose_name__contains=search)

    pages = Paginator(query, 20)
    page = pages.page(int(request.GET.get('page', 1)))

    result = [{
        'id': '',
        'text': '------'
    }] if not isinstance(field.widget, SelectMultiple
                         ) and not field.required and page.number == 1 else []

    return JsonResponse({
        'results':
        result + [{
            'id': row[0],
            'text': row[1],
        } for row in page.object_list.values_list('id', 'verbose_name')],
        'pagination': {
            'more': page.has_next(),
        }
    })
Beispiel #3
0
 def get_model_alias(self, model_alias):
     """Get model alias if class then convert to alias string"""
     from trionyx.models import BaseModel
     if inspect.isclass(model_alias) and issubclass(model_alias, BaseModel):
         config = models_config.get_config(model_alias)
         return '{}.{}'.format(config.app_label, config.model_name)
     return model_alias
Beispiel #4
0
 def generate_verbose_name(self):
     """Generate verbose name"""
     from trionyx.renderer import LazyFieldRenderer
     app_label = self._meta.app_label
     model_name = type(self).__name__
     verbose_name = models_config.get_config(self).verbose_name
     return verbose_name.format(model_name=model_name,
                                app_label=app_label,
                                **{
                                    field.name:
                                    LazyFieldRenderer(self,
                                                      field.name,
                                                      no_html=True)
                                    for field in models_config.get_config(
                                        self).get_fields(True, True)
                                })
Beispiel #5
0
    def save(self, commit=True):
        """Save form and inline forms"""
        object_updated = False
        config = models_config.get_config(self._meta.model)
        fields = [field.name for field in config.get_fields()]
        with transaction.atomic():
            obj = super().save(commit)

            for key, form in self.get_inline_forms().items():
                form.is_valid()  # Make sure cleaned_data is filled
                fk_name = self.inline_forms[key].get('fk_name', 'instance')
                if fk_name == 'instance':
                    logger.debug('Save inline form {} as FormSet, commit: {}'.format(key, commit))
                    form.instance = obj
                    form.save(commit)
                else:
                    logger.debug('Save inline form {} as Form, commit: {}'.format(key, commit))
                    inline_obj = form.save(False)
                    if inline_obj:
                        setattr(inline_obj, fk_name, obj)
                        if commit:
                            inline_obj.save()

                        if key in fields:
                            logger.debug(' * Set inline form object on parent form as: {}'.format(key))
                            setattr(obj, key, inline_obj)
                            object_updated = True

        if object_updated and commit:
            obj.save()
        return obj
Beispiel #6
0
    def auto_load_model_menu(self):
        """
        Auto load model menu entries, can be configured in `trionyx.config.ModelConfig`:

        - menu_name
        - menu_icon
        - menu_order
        """
        from trionyx.trionyx.apps import BaseConfig

        order = 0
        for app in apps.get_app_configs():
            if not isinstance(app, BaseConfig) or getattr(
                    app, 'no_menu', False):
                continue

            app_path = app.name.split('.')[-1]
            model_order = 0
            for model in app.get_models():
                config = models_config.get_config(model)

                if config.menu_exclude:
                    continue

                menu_icon = None
                menu_path = '{}/{}'.format(app_path, config.model_name)
                if config.menu_root:
                    order += 10
                    menu_order = order
                    menu_icon = config.menu_icon
                    menu_path = config.model_name
                else:
                    model_order += 10
                    menu_order = model_order

                self.add_item(
                    path=menu_path,
                    name=config.menu_name if config.menu_name else
                    model._meta.verbose_name_plural.capitalize(),
                    order=config.menu_order
                    if config.menu_order else menu_order,
                    icon=menu_icon,
                    url=reverse("trionyx:model-list",
                                kwargs={
                                    'app': model._meta.app_label,
                                    'model': model._meta.model_name,
                                }),
                    permission='{app_label}.view_{model_name}'.format(
                        app_label=config.app_label,
                        model_name=config.model_name,
                    ).lower())

            if model_order > 0:
                order += 10
                self.add_item(
                    path=app_path,
                    name=getattr(app, 'menu_name', app.verbose_name),
                    icon=getattr(app, 'menu_icon', None),
                    order=getattr(app, 'menu_order', order),
                )
Beispiel #7
0
 def get_model_alias(self, model_alias, rewrite=True):
     """Get model alias if class then convert to alias string"""
     from trionyx.models import Model
     if inspect.isclass(model_alias) and issubclass(model_alias, Model):
         if rewrite:
             models_config.get_model_name(models_config.get_config(model_alias).model)
         return models_config.get_model_name(model_alias)
     return model_alias
Beispiel #8
0
    def get_description(self, *args, **kwargs):
        """Get api description"""
        model = getattr(getattr(self.view, 'queryset', None), 'model', None)
        if model:
            config = models_config.get_config(model)
            if config.api_description:
                return config.api_description

        return super().get_description(*args, **kwargs)
Beispiel #9
0
 def general_layout(obj):
     return Layout(
         Column12(
             Panel(
                 'info',
                 DescriptionList(*[
                     f.name for f in models_config.get_config(
                         obj).get_fields()
                 ]))))
Beispiel #10
0
    def _create_form(self, model, only_required=False):
        """Create form from model"""
        def use_field(field):
            if not only_required:
                return True
            return field.default == NOT_PROVIDED

        config = models_config.get_config(model)
        return modelform_factory(model, fields=[f.name for f in config.get_fields() if use_field(f)])
Beispiel #11
0
    def test_custom_header_button_detailview(self):
        config = models_config.get_config(User)
        config.header_buttons = [{
            'label': 'TestCustom header button',
            'url': 'trionyx:model-list',
        }]

        response = self.client.get(self.get_user_url())

        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'TestCustom header button')
Beispiel #12
0
    def handle_request(self, request, *args, **kwargs):
        """Get filter fields"""
        try:
            modelClass = ContentType.objects.get_for_id(
                request.GET.get('id')).model_class()
        except ContentType.DoesNotExist:
            return {}
        config = models_config.get_config(modelClass)

        return {
            'id': request.GET.get('id'),
            'fields': {
                name: {
                    'name': name,
                    'label': str(field['label']),
                    'type': field['type'],
                    'choices': field['choices'] if field['choices'] else [],
                    'choices_url': field.get('choices_url', None)
                }
                for name, field in config.get_list_fields().items()
            }
        }
Beispiel #13
0
def model_instance_diff(old, new):
    """Create diff of two model instances"""
    diff = {}

    config = models_config.get_config(new if new else old)
    ignore_fields = config.auditlog_ignore_fields if config.auditlog_ignore_fields else []
    fields = [
        field for field in config.get_fields()
        if field.name not in ignore_fields
    ]

    for field in fields:
        old_value = get_field_value(old, field)
        new_value = get_field_value(new, field)

        if old_value != new_value:
            diff[field.name] = (
                get_rendered_value(config.model, field.name, old_value),
                get_rendered_value(config.model, field.name, new_value),
            )

    return diff if diff else None
Beispiel #14
0
    def _get_operation_summary(self, path, method):
        """Get operation id"""
        method_name = getattr(self.view, 'action', method.lower())
        if is_list_view(path, method, self.view):
            action = _('List')
        elif method_name not in self.translate_mapping:
            action = method_name
        else:
            action = self.translate_mapping[method.lower()]

        model = getattr(getattr(self.view, 'queryset', None), 'model', None)
        if model:
            config = models_config.get_config(model)
            name = config.get_verbose_name_plural(
                title=True) if action == 'list' else config.get_verbose_name(
                    title=True)
        else:
            if hasattr(self.view, 'get_serializer_class'):
                name = self.view.get_serializer_class().__name__
                if name.endswith('Serializer'):
                    name = name[:-10]

                # Fallback to the view name
            else:
                name = self.view.__class__.__name__
                if name.endswith('APIView'):
                    name = name[:-7]
                elif name.endswith('View'):
                    name = name[:-4]

                # Due to camel-casing of classes and `action` being lowercase, apply title in order to find if action truly
                # comes at the end of the name
                if name.endswith(action.title()
                                 ):  # ListView, UpdateAPIView, ThingDelete ...
                    name = name[:-len(action)]

        return f'{action} {name}'
Beispiel #15
0
def model_url(model, view_name=None, code=None, params=None):
    """Shortcut function for getting model url"""
    from trionyx.config import models_config
    view_name = 'trionyx:model-{}'.format(view_name if view_name else 'view')
    config = models_config.get_config(model)
    kwargs = {
        'app': config.app_label,
        'model': config.model_name,
    }
    if code:
        kwargs['code'] = code

    if not inspect.isclass(model) and not isinstance(model, str):
        try:
            kwargs['pk'] = model.pk
            return reverse(view_name, kwargs=kwargs)
        except NoReverseMatch:
            kwargs.pop('pk')

    url = reverse(view_name, kwargs=kwargs)
    return url if not params else '{url}?{params}'.format(
        url=url,
        params='&'.join('{}={}'.format(key, value)
                        for key, value in params.items()))
Beispiel #16
0
 def get_model_config(
         self, model: Union[str, Type[Model]]) -> Optional[ModelConfig]:
     """Get model config for given model"""
     return models_config.get_config(model)
Beispiel #17
0
def get_class(model):
    """Get model class"""
    return models_config.get_config(get_name(model)).model
Beispiel #18
0
def filter_queryset_with_user_filters(queryset,
                                      filters,
                                      request=None,
                                      raise_exception=False):
    """Apply user provided filters on queryset"""
    config = models_config.get_config(queryset.model)

    field_indexed = {
        name: {
            'name': name,
            'label': field['label'],
            'type': field['type'],
            'choices': field['choices'],
        }
        for name, field in config.get_list_fields().items()
    }
    grouped_filter = defaultdict(list)
    for filter in filters:
        field = field_indexed.get(filter['field'])

        if not field:
            continue

        try:
            if filter['operator'] == 'null':
                queryset = queryset.filter(
                    **{'{}__isnull'.format(filter['field']): filter['value']})
            elif field['type'] == 'datetime':
                filter['value'] = timezone.make_aware(
                    timezone.datetime.strptime(
                        filter['value'], utils.get_datetime_input_format()))
            elif field['type'] == 'date':
                filter['value'] = timezone.make_aware(
                    timezone.datetime.strptime(
                        filter['value'],
                        utils.get_datetime_input_format(date_only=True)))

            if filter['operator'] == '==':
                grouped_filter[filter['field']].append(filter['value'])
            elif filter['operator'] == '!=':
                if field['type'] == 'text':
                    queryset = queryset.exclude(**{
                        '{}__icontains'.format(filter['field']):
                        filter['value']
                    })
                else:
                    queryset = queryset.exclude(
                        **{filter['field']: filter['value']})
            elif filter['operator'] == '<':
                queryset = queryset.filter(
                    **{'{}__lt'.format(filter['field']): filter['value']})
            elif filter['operator'] == '<=':
                queryset = queryset.filter(
                    **{'{}__lte'.format(filter['field']): filter['value']})
            elif filter['operator'] == '>':
                queryset = queryset.filter(
                    **{'{}__gt'.format(filter['field']): filter['value']})
            elif filter['operator'] == '>=':
                queryset = queryset.filter(
                    **{'{}__gte'.format(filter['field']): filter['value']})
        except Exception as e:
            if raise_exception:
                raise e

            if request:
                messages.add_message(
                    request, messages.ERROR,
                    "Could not apply filter ({} {} {})".format(
                        filter['field'], filter['operator'], filter['value']))
    for name, values in grouped_filter.items():
        field = field_indexed[name]
        if field['type'] == 'text':
            or_queries = [
                Q(**{'{}__icontains'.format(name): value}) for value in values
            ]
            queryset = queryset.filter(reduce(operator.or_, or_queries))
        else:
            or_queries = [Q(**{name: value}) for value in values]
            queryset = queryset.filter(reduce(operator.or_, or_queries))

    return queryset
Beispiel #19
0
def create_permission_jstree(selected=None, disabled=False):
    """Create permission jstree"""
    selected = selected if selected else []
    jstree = []

    added_apps = ['auth']
    added_models = []
    for permission in Permission.objects.select_related('content_type').all():
        if not permission.content_type.model_class():
            continue

        model_config = models_config.get_config(
            permission.content_type.model_class())

        if model_config.hide_permissions:
            continue

        if permission.codename == 'view_{}'.format(
                model_config.model_name) and (model_config.disable_view
                                              or model_config.admin_view_only):
            continue

        if permission.codename == 'add_{}'.format(
                model_config.model_name) and (model_config.disable_add
                                              or model_config.admin_add_only):
            continue

        if permission.codename == 'change_{}'.format(
                model_config.model_name) and (model_config.disable_change or
                                              model_config.admin_change_only):
            continue

        if permission.codename == 'delete_{}'.format(
                model_config.model_name) and (model_config.disable_delete or
                                              model_config.admin_delete_only):
            continue

        parent = ['jstree']

        if model_config.app_label not in added_apps:
            jstree.append({
                'id': '.'.join([*parent, model_config.app_label]),
                'parent': '#',
                'text': model_config.get_app_verbose_name(),
                'state': {
                    'disabled': disabled,
                }
            })
            added_apps.append(model_config.app_label)

        parent.append(model_config.app_label
                      if model_config.app_label != 'auth' else 'trionyx')

        if model_config.model_name not in added_models:
            jstree.append({
                'id': '.'.join([*parent, model_config.model_name]),
                'parent': '.'.join(parent),
                'text': model_config.get_verbose_name_plural(),
                'state': {
                    'disabled': disabled,
                }
            })
            added_models.append(model_config.model_name)

        parent.append(model_config.model_name)
        name = {
            'view_{}'.format(model_config.model_name): _('View'),
            'add_{}'.format(model_config.model_name): _('Add'),
            'change_{}'.format(model_config.model_name): _('Change'),
            'delete_{}'.format(model_config.model_name): _('Delete'),
        }.get(permission.codename, permission.name)

        jstree.append({
            'id': '.'.join([*parent, permission.codename]),
            'parent': '.'.join(parent),
            'text': str(name),
            'state': {
                'selected': permission in selected,
                'disabled': disabled,
            },
            'permission_id': permission.id,
        })

    return jstree
Beispiel #20
0
    def get_data(self, request: HttpRequest, config: dict) -> str:
        """Get data"""
        if config.get('source', '__custom__') != '__custom__':
            func = widget_data.get_data(self, config.get('source')).get(
                'function', lambda config: '-')
            return func(config)

        try:
            ModelClass = ContentType.objects.get_for_id(config.get(
                'model', -1)).model_class()
        except ContentType.DoesNotExist:
            return '-'

        if not ModelClass:
            return ''

        model_config = models_config.get_config(ModelClass)
        if not model_config.has_permission('view'):
            return '-'

        query = ModelClass.objects.get_queryset()

        if config.get('filters'):
            query = filter_queryset_with_user_filters(
                query, json.loads(config['filters']))

        if config.get('period', 'all') != 'all':
            today = timezone.now().replace(hour=0,
                                           minute=0,
                                           second=0,
                                           microsecond=0)
            query = query.filter(
                **{
                    'year': {
                        '{}__gte'.format(config['period_field']):
                        today.replace(month=1, day=1)
                    },
                    'month': {
                        '{}__gte'.format(config['period_field']):
                        today.replace(day=1)
                    },
                    'week': {
                        '{}__gte'.format(config['period_field']):
                        today - timezone.timedelta(today.weekday())
                    },
                    'day': {
                        '{}__gte'.format(config['period_field']): today
                    },
                    '365days': {
                        '{}__gte'.format(config['period_field']):
                        today - timezone.timedelta(days=365)
                    },
                    '30days': {
                        '{}__gte'.format(config['period_field']):
                        today - timezone.timedelta(days=30)
                    },
                    '7days': {
                        '{}__gte'.format(config['period_field']):
                        today - timezone.timedelta(days=7)
                    },
                }.get(config['period'], {}))

        if config.get('field', '__count__') == '__count__':
            return renderer.render_value(query.count())
        else:
            result = query.aggregate(sum=Sum(config['field']))
            return renderer.render_field(
                ModelClass(**{config['field']: result['sum']}),
                config['field'])
Beispiel #21
0
    def get_data(self, request: HttpRequest, config: dict):
        """Get graph data"""
        if config.get('source', '__custom__') != '__custom__':
            func = widget_data.get_data(self, config.get('source')).get(
                'function', lambda config: None)
            return func(config)

        try:
            ModelClass = ContentType.objects.get_for_id(config.get(
                'model', -1)).model_class()
        except ContentType.DoesNotExist:
            return None

        if not ModelClass:
            return None

        from django.db.models.functions import ExtractMinute, ExtractHour, ExtractDay, ExtractWeek, ExtractMonth, ExtractYear
        interval_field = config.get('interval_field', 'created_at')
        query = ModelClass.objects.get_queryset().annotate(
            widget_minute=ExtractMinute(interval_field),
            widget_hour=ExtractHour(interval_field),
            widget_day=ExtractDay(interval_field),
            widget_week=ExtractWeek(interval_field),
            widget_month=ExtractMonth(interval_field),
            widget_year=ExtractYear(interval_field))

        if config.get('filters'):
            query = filter_queryset_with_user_filters(
                query, json.loads(config['filters']))

        if config.get('interval_period') == 'minute':
            query = query.values('widget_minute', 'widget_hour', 'widget_day',
                                 'widget_month', 'widget_year').order_by(
                                     '-widget_year', '-widget_month',
                                     '-widget_day', '-widget_hour',
                                     '-widget_minute')
        elif config.get('interval_period') == 'hour':
            query = query.values('widget_hour', 'widget_day', 'widget_month',
                                 'widget_year').order_by(
                                     '-widget_year', '-widget_month',
                                     '-widget_day', '-widget_hour')
        elif config.get('interval_period') == 'day':
            query = query.values('widget_day', 'widget_month',
                                 'widget_year').order_by(
                                     '-widget_year', '-widget_month',
                                     '-widget_day')
        elif config.get('interval_period') == 'week':
            query = query.values('widget_week', 'widget_year').order_by(
                '-widget_year', '-widget_week')
        elif config.get('interval_period') == 'month':
            query = query.values('widget_month', 'widget_year').order_by(
                '-widget_year', '-widget_month')
        elif config.get('interval_period') == 'year':
            query = query.values('widget_year').order_by('-widget_year')

        query = query.annotate(widget_count=Count('id'))

        model_config = models_config.get_config(ModelClass)
        only_count = config.get('field', '__count__') == '__count__'
        if only_count:
            label = model_config.get_verbose_name() + ' ' + str(_('Count'))
        else:
            query = query.annotate(widget_value=Sum(config['field']))
            label = _('Sum of {objects} {field}'.format(
                objects=model_config.get_verbose_name_plural(),
                field=model_config.get_field(config['field']).verbose_name))

        results = list(reversed(query[:30]))

        if not results:
            return False

        def row_to_date(row):
            """Based on row generate a date"""
            import datetime

            if config.get('interval_period') == 'week':
                return datetime.datetime.strptime(
                    '{}-W{}-1'.format(
                        row.get('widget_year'),
                        row.get('widget_week', 1) - 1,
                    ), "%Y-W%W-%w").strftime('%Y-%m-%d %H:%M:%S')

            return datetime.datetime(
                year=row.get('widget_year'),
                month=row.get('widget_month', 1),
                day=row.get('widget_day', 1),
                hour=row.get('widget_hour', 0),
                minute=row.get('widget_minute',
                               0)).strftime('%Y-%m-%d %H:%M:%S')

        datasets = []
        y_axes = [{
            'id': 'y-axis-2',
            'type': 'linear',
            'position': 'right' if not only_count else 'left',
            'gridLines': {
                'drawOnChartArea': False,
            },
            'ticks': {
                'suggestedMax':
                float(max([row['widget_count'] for row in results])) *
                (1.5 if not only_count else 1.10),
                'suggestedMin':
                0,
            }
        }]
        if not only_count:
            field_renderer = renderer.renderers.get(
                type(model_config.get_field(config['field'])),
                lambda x: str(x))
            datasets.append({
                'label':
                label,
                'backgroundColor':
                self.get_color(config.get('color'), 'fill'),
                'borderColor':
                self.get_color(config.get('color'), 'stroke'),
                'pointBorderColor':
                self.get_color(config.get('color'), 'stroke'),
                'pointBackgroundColor':
                self.get_color(config.get('color'), 'stroke'),
                'fill':
                True,
                'pointRadius':
                4,
                'data': [{
                    'x': row_to_date(row),
                    'y': row.get('widget_value'),
                    'label': field_renderer(row.get('widget_value')),
                } for row in results],
                'yAxisID':
                'y-axis-1',
            })
            y_axes.append({
                'id': 'y-axis-1',
                'type': 'linear',
                'position': 'left',
                'gridLines': {
                    'drawOnChartArea': False,
                },
                'ticks': {
                    'suggestedMax':
                    float(max([row['widget_value']
                               for row in results])) * 1.10,
                    'suggestedMin':
                    0,
                }
            })

        datasets.append({
            'label':
            str(_('Number of {objects}')).format(
                objects=model_config.get_verbose_name_plural()),
            'backgroundColor':
            self.get_color(config.get('color'), 'fill')
            if only_count else 'rgba(211, 211, 211, 0.2)',
            'borderColor':
            self.get_color(config.get('color'), 'stroke')
            if only_count else 'rgba(211, 211, 211, 1)',
            'pointBorderColor':
            self.get_color(config.get('color'), 'stroke')
            if only_count else 'rgba(211, 211, 211, 1)',
            'pointBackgroundColor':
            self.get_color(config.get('color'), 'stroke')
            if only_count else 'rgba(211, 211, 211, 1)',
            'fill':
            True,
            'pointRadius':
            4,
            'data': [row.get('widget_count') for row in results],
            'yAxisID':
            'y-axis-2',
        })

        return {
            'scales': {
                'xAxes': [{
                    'type': 'time',
                    'autoSkip': True,
                    'distribution': 'linear',
                    'time': {
                        'unit':
                        config.get('interval_period', 'day'),
                        'stepSize':
                        1,
                        'tooltipFormat':
                        utils.datetime_format_to_momentjs(
                            utils.get_datetime_input_format(
                                date_only=config.get('interval_period') not in
                                ['minute', 'hour']))
                    },
                }],
                'yAxes':
                y_axes,
            },
            'data': {
                'labels': [row_to_date(row) for row in results],
                'datasets': datasets,
            }
        }
Beispiel #22
0
 def handle_request(self, request, app, model, pk, code=None):
     """Return given sidebar"""
     from trionyx.views import sidebars
     config = models_config.get_config(f'{app}.{model}')
     obj = config.model.objects.get(id=pk)
     return sidebars.get_sidebar(config.model, code)(request, obj)