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())
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(), } })
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
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) })
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
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), )
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
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)
def general_layout(obj): return Layout( Column12( Panel( 'info', DescriptionList(*[ f.name for f in models_config.get_config( obj).get_fields() ]))))
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)])
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')
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() } }
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
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}'
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()))
def get_model_config( self, model: Union[str, Type[Model]]) -> Optional[ModelConfig]: """Get model config for given model""" return models_config.get_config(model)
def get_class(model): """Get model class""" return models_config.get_config(get_name(model)).model
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
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
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'])
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, } }
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)