summaryrefslogtreecommitdiff
path: root/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py
diff options
context:
space:
mode:
Diffstat (limited to 'yocto-poky/bitbake/lib/toaster/toastergui/widgets.py')
-rw-r--r--yocto-poky/bitbake/lib/toaster/toastergui/widgets.py411
1 files changed, 411 insertions, 0 deletions
diff --git a/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py b/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py
new file mode 100644
index 000000000..eb2914d87
--- /dev/null
+++ b/yocto-poky/bitbake/lib/toaster/toastergui/widgets.py
@@ -0,0 +1,411 @@
+#
+# ex:ts=4:sw=4:sts=4:et
+# -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
+#
+# BitBake Toaster Implementation
+#
+# Copyright (C) 2015 Intel Corporation
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+from django.views.generic import View, TemplateView
+from django.shortcuts import HttpResponse
+from django.http import HttpResponseBadRequest
+from django.core import serializers
+from django.core.cache import cache
+from django.core.paginator import Paginator, EmptyPage
+from django.db.models import Q
+from orm.models import Project, ProjectLayer, Layer_Version
+from django.template import Context, Template
+from django.core.serializers.json import DjangoJSONEncoder
+from django.core.exceptions import FieldError
+from django.conf.urls import url, patterns
+
+import types
+import json
+import collections
+import operator
+import re
+
+from toastergui.views import objtojson
+
+class ToasterTable(TemplateView):
+ def __init__(self, *args, **kwargs):
+ super(ToasterTable, self).__init__()
+ if 'template_name' in kwargs:
+ self.template_name = kwargs['template_name']
+ self.title = None
+ self.queryset = None
+ self.columns = []
+ self.filters = {}
+ self.total_count = 0
+ self.static_context_extra = {}
+ self.filter_actions = {}
+ self.empty_state = "Sorry - no data found"
+ self.default_orderby = ""
+
+ # add the "id" column, undisplayable, by default
+ self.add_column(title="Id",
+ displayable=False,
+ orderable=True,
+ field_name="id")
+
+
+ def get(self, request, *args, **kwargs):
+ if request.GET.get('format', None) == 'json':
+
+ self.setup_queryset(*args, **kwargs)
+ # Put the project id into the context for the static_data_template
+ if 'pid' in kwargs:
+ self.static_context_extra['pid'] = kwargs['pid']
+
+ cmd = request.GET.get('cmd', None)
+ if cmd and 'filterinfo' in cmd:
+ data = self.get_filter_info(request, **kwargs)
+ else:
+ # If no cmd is specified we give you the table data
+ data = self.get_data(request, **kwargs)
+
+ return HttpResponse(data, content_type="application/json")
+
+ return super(ToasterTable, self).get(request, *args, **kwargs)
+
+ def get_filter_info(self, request, **kwargs):
+ data = None
+
+ self.setup_filters(**kwargs)
+
+ search = request.GET.get("search", None)
+ if search:
+ self.apply_search(search)
+
+ name = request.GET.get("name", None)
+ if name is None:
+ data = json.dumps(self.filters,
+ indent=2,
+ cls=DjangoJSONEncoder)
+ else:
+ for actions in self.filters[name]['filter_actions']:
+ actions['count'] = self.filter_actions[actions['name']](count_only=True)
+
+ # Add the "All" items filter action
+ self.filters[name]['filter_actions'].insert(0, {
+ 'name' : 'all',
+ 'title' : 'All',
+ 'count' : self.queryset.count(),
+ })
+
+ data = json.dumps(self.filters[name],
+ indent=2,
+ cls=DjangoJSONEncoder)
+
+ return data
+
+ def setup_columns(self, *args, **kwargs):
+ """ function to implement in the subclass which sets up the columns """
+ pass
+ def setup_filters(self, *args, **kwargs):
+ """ function to implement in the subclass which sets up the filters """
+ pass
+ def setup_queryset(self, *args, **kwargs):
+ """ function to implement in the subclass which sets up the queryset"""
+ pass
+
+ def add_filter(self, name, title, filter_actions):
+ """Add a filter to the table.
+
+ Args:
+ name (str): Unique identifier of the filter.
+ title (str): Title of the filter.
+ filter_actions: Actions for all the filters.
+ """
+ self.filters[name] = {
+ 'title' : title,
+ 'filter_actions' : filter_actions,
+ }
+
+ def make_filter_action(self, name, title, action_function):
+ """ Utility to make a filter_action """
+
+ action = {
+ 'title' : title,
+ 'name' : name,
+ }
+
+ self.filter_actions[name] = action_function
+
+ return action
+
+ def add_column(self, title="", help_text="",
+ orderable=False, hideable=True, hidden=False,
+ field_name="", filter_name=None, static_data_name=None,
+ displayable=True, computation=None,
+ static_data_template=None):
+ """Add a column to the table.
+
+ Args:
+ title (str): Title for the table header
+ help_text (str): Optional help text to describe the column
+ orderable (bool): Whether the column can be ordered.
+ We order on the field_name.
+ hideable (bool): Whether the user can hide the column
+ hidden (bool): Whether the column is default hidden
+ field_name (str or list): field(s) required for this column's data
+ static_data_name (str, optional): The column's main identifier
+ which will replace the field_name.
+ static_data_template(str, optional): The template to be rendered
+ as data
+ """
+
+ self.columns.append({'title' : title,
+ 'help_text' : help_text,
+ 'orderable' : orderable,
+ 'hideable' : hideable,
+ 'hidden' : hidden,
+ 'field_name' : field_name,
+ 'filter_name' : filter_name,
+ 'static_data_name': static_data_name,
+ 'static_data_template': static_data_template,
+ 'displayable': displayable,
+ 'computation': computation,
+ })
+
+ def render_static_data(self, template, row):
+ """Utility function to render the static data template"""
+
+ context = {
+ 'extra' : self.static_context_extra,
+ 'data' : row,
+ }
+
+ context = Context(context)
+ template = Template(template)
+
+ return template.render(context)
+
+ def apply_filter(self, filters, **kwargs):
+ self.setup_filters(**kwargs)
+
+ try:
+ filter_name, filter_action = filters.split(':')
+ except ValueError:
+ return
+
+ if "all" in filter_action:
+ return
+
+ try:
+ self.filter_actions[filter_action]()
+ except KeyError:
+ # pass it to the user - programming error here
+ raise
+
+ def apply_orderby(self, orderby):
+ # Note that django will execute this when we try to retrieve the data
+ self.queryset = self.queryset.order_by(orderby)
+
+ def apply_search(self, search_term):
+ """Creates a query based on the model's search_allowed_fields"""
+
+ if not hasattr(self.queryset.model, 'search_allowed_fields'):
+ raise Exception("Err Search fields aren't defined in the model")
+
+ search_queries = []
+ for st in search_term.split(" "):
+ q_map = [Q(**{field + '__icontains': st})
+ for field in self.queryset.model.search_allowed_fields]
+
+ search_queries.append(reduce(operator.or_, q_map))
+
+ search_queries = reduce(operator.and_, search_queries)
+
+ self.queryset = self.queryset.filter(search_queries)
+
+
+ def get_data(self, request, **kwargs):
+ """Returns the data for the page requested with the specified
+ parameters applied"""
+
+ page_num = request.GET.get("page", 1)
+ limit = request.GET.get("limit", 10)
+ search = request.GET.get("search", None)
+ filters = request.GET.get("filter", None)
+ orderby = request.GET.get("orderby", None)
+
+ # Make a unique cache name
+ cache_name = self.__class__.__name__
+
+ for key, val in request.GET.iteritems():
+ cache_name = cache_name + str(key) + str(val)
+
+ for key, val in kwargs.iteritems():
+ cache_name = cache_name + str(key) + str(val)
+
+ # No special chars allowed in the cache name apart from dash
+ cache_name = re.sub(r'[^A-Za-z0-9-]', "", cache_name)
+ data = cache.get(cache_name)
+
+ if data:
+ return data
+
+ self.setup_columns(**kwargs)
+
+ if search:
+ self.apply_search(search)
+ if filters:
+ self.apply_filter(filters, **kwargs)
+ if orderby:
+ self.apply_orderby(orderby)
+
+ paginator = Paginator(self.queryset, limit)
+
+ try:
+ page = paginator.page(page_num)
+ except EmptyPage:
+ page = paginator.page(1)
+
+ data = {
+ 'total' : self.queryset.count(),
+ 'default_orderby' : self.default_orderby,
+ 'columns' : self.columns,
+ 'rows' : [],
+ 'error' : "ok",
+ }
+
+ try:
+ for row in page.object_list:
+ #Use collection to maintain the order
+ required_data = collections.OrderedDict()
+
+ for col in self.columns:
+ field = col['field_name']
+ if not field:
+ field = col['static_data_name']
+ if not field:
+ raise Exception("Must supply a field_name or static_data_name for column %s.%s" % (self.__class__.__name__,col))
+ # Check if we need to process some static data
+ if "static_data_name" in col and col['static_data_name']:
+ required_data["static:%s" % col['static_data_name']] = self.render_static_data(col['static_data_template'], row)
+
+ # Overwrite the field_name with static_data_name
+ # so that this can be used as the html class name
+
+ col['field_name'] = col['static_data_name']
+
+ # compute the computation on the raw data if needed
+ model_data = row
+ if col['computation']:
+ model_data = col['computation'](row)
+ else:
+ # Traverse to any foriegn key in the object hierachy
+ for subfield in field.split("__"):
+ if hasattr(model_data, subfield):
+ model_data = getattr(model_data, subfield)
+ # The field could be a function on the model so check
+ # If it is then call it
+ if isinstance(model_data, types.MethodType):
+ model_data = model_data()
+
+ required_data[col['field_name']] = model_data
+
+ data['rows'].append(required_data)
+
+ except FieldError:
+ # pass it to the user - programming-error here
+ raise
+ data = json.dumps(data, indent=2, default=objtojson)
+ cache.set(cache_name, data, 60*30)
+
+ return data
+
+
+class ToasterTemplateView(TemplateView):
+ # renders a instance in a template, or returns the context as json
+ # the class-equivalent of the _template_renderer decorator for views
+
+ def __init__(self, *args, **kwargs):
+ super(ToasterTemplateView, self).__init__(*args, **kwargs)
+ self.context_entries = []
+
+ def get(self, *args, **kwargs):
+ if self.request.GET.get('format', None) == 'json':
+ from django.core.urlresolvers import reverse
+ from django.shortcuts import HttpResponse
+ from views import objtojson
+ from toastergui.templatetags.projecttags import json as jsonfilter
+
+ context = self.get_context_data(**kwargs)
+
+ for x in context.keys():
+ if x not in self.context_entries:
+ del context[x]
+
+ context["error"] = "ok"
+
+ return HttpResponse(jsonfilter(context, default=objtojson ),
+ content_type = "application/json; charset=utf-8")
+
+ return super(ToasterTemplateView, self).get(*args, **kwargs)
+
+class ToasterTypeAhead(View):
+ """ A typeahead mechanism to support the front end typeahead widgets """
+ MAX_RESULTS = 6
+
+ class MissingFieldsException(Exception):
+ pass
+
+ def __init__(self, *args, **kwargs):
+ super(ToasterTypeAhead, self).__init__()
+
+ def get(self, request, *args, **kwargs):
+ def response(data):
+ return HttpResponse(json.dumps(data,
+ indent=2,
+ cls=DjangoJSONEncoder),
+ content_type="application/json")
+
+ error = "ok"
+
+ search_term = request.GET.get("search", None)
+ if search_term == None:
+ # We got no search value so return empty reponse
+ return response({'error' : error , 'results': []})
+
+ try:
+ prj = Project.objects.get(pk=kwargs['pid'])
+ except KeyError:
+ prj = None
+
+ results = self.apply_search(search_term, prj, request)[:ToasterTypeAhead.MAX_RESULTS]
+
+ if len(results) > 0:
+ try:
+ self.validate_fields(results[0])
+ except MissingFieldsException as e:
+ error = e
+
+ data = { 'results' : results,
+ 'error' : error,
+ }
+
+ return response(data)
+
+ def validate_fields(self, result):
+ if 'name' in result == False or 'detail' in result == False:
+ raise MissingFieldsException("name and detail are required fields")
+
+ def apply_search(self, search_term, prj):
+ """ Override this function to implement search. Return an array of
+ dictionaries with a minium of a name and detail field"""
+ pass