from __future__ import absolute_import import logging import re import urllib import urlparse import inspect from django.core.urlresolvers import NoReverseMatch, resolve, reverse from django.template import VariableDoesNotExist, Variable from django.template import (VariableDoesNotExist, Variable) from django.utils.encoding import smart_str, smart_unicode from django.utils.http import urlquote, urlencode from django.utils.text import unescape_string_literal from django.utils.translation import ugettext_lazy as _ logger = logging.getLogger(__name__) class ResolvedLink(object): active = False url = '#' text = _('Unnamed link') class Link(object): bound_links = {} @classmethod def bind_links(cls, sources, links, menu_name=None, position=0): """ Associate a link to a model, a view, or an url """ cls.bound_links.setdefault(menu_name, {}) try: for source in sources: cls.bound_links[menu_name].setdefault(source, {'links': []}) try: cls.bound_links[menu_name][source]['links'].extend(links) except TypeError: # Try to see if links is a single link cls.bound_links[menu_name][source]['links'].append(links) except TypeError: raise Exception('The bind_links source argument must be a list, even for single element sources.') def __init__(self, text, view, klass=None, args=None, icon=None, permissions=None, condition=None, conditional_disable=None, description=None, dont_mark_active=False, children_view_regex=None, keep_query=False, children_classes=None, children_views=None, conditional_highlight=None): self.text = text self.view = view self.args = args or {} #self.kwargs = kwargs or {} self.icon = icon self.permissions = permissions or [] self.condition = condition self.conditional_disable = conditional_disable self.description = description self.dont_mark_active = dont_mark_active self.klass = klass self.keep_query = keep_query self.conditional_highlight = conditional_highlight # Used by dynamic sources self.children_views = children_views or [] self.children_classes = children_classes or [] self.children_view_regex = children_view_regex def resolve(self, context, request=None, current_path=None, current_view=None, resolved_object=None): # Don't calculate these if passed in an argument request = request or Variable('request').resolve(context) current_path = current_path or request.META['PATH_INFO'] if not current_view: match = resolve(current_path) if match.namespace: current_view = '{}:{}'.format(match.namespace, match.url_name) else: current_view = match.url_name # Preserve unicode data in URL query previous_path = smart_unicode(urllib.unquote_plus(smart_str(request.get_full_path()) or smart_str(request.META.get('HTTP_REFERER', u'/')))) query_string = urlparse.urlparse(previous_path).query parsed_query_string = urlparse.parse_qs(query_string) logger.debug('condition: %s', self.condition) if resolved_object: context['resolved_object'] = resolved_object # Check to see if link has conditional display if self.condition: self.condition_result = self.condition(context) else: self.condition_result = True logger.debug('self.condition_result: %s', self.condition_result) if self.condition_result: resolved_link = ResolvedLink() resolved_link.text = self.text resolved_link.icon = self.icon resolved_link.permissions = self.permissions resolved_link.condition_result = self.condition_result try: #args, kwargs = resolve_arguments(context, self.get('args', {})) args, kwargs = Link.resolve_arguments(context, self.args) except VariableDoesNotExist: args = [] kwargs = {} if self.view: if not self.dont_mark_active: resolved_link.active = self.view == current_view try: if kwargs: resolved_link.url = reverse(self.view, kwargs=kwargs) else: resolved_link.url = reverse(self.view, args=args) if self.keep_query: resolved_link.url = u'%s?%s' % (urlquote(resolved_link.url), urlencode(parsed_query_string, doseq=True)) except NoReverseMatch, exc: resolved_link.url = '#' resolved_link.error = exc elif self.url: if not self.dont_mark_active: resolved_link.url.active = self.url == current_path if kwargs: resolved_link.url = self.url % kwargs else: resolved_link.url = self.url % args if self.keep_query: resolved_link.url = u'%s?%s' % (urlquote(resolved_link.url), urlencode(parsed_query_string, doseq=True)) else: resolved_link.active = False if self.conditional_highlight: resolved_link.active = self.conditional_highlight(context) if self.conditional_disable: resolved_link.disabled = self.conditional_disable(context) else: resolved_link.disabled = False if current_view in self.children_views: resolved_link.active = True # TODO: add tree base main menu support to auto activate parent links if self.children_view_regex and re.compile(self.children_view_regex).match(current_view): resolved_link.active = True return resolved_link @classmethod def get_context_navigation_links(cls, context, menu_name=None, links_dict=None): request = Variable('request').resolve(context) current_path = request.META['PATH_INFO'] match = resolve(current_path) if match.namespace: current_view = '{}:{}'.format(match.namespace, match.url_name) else: current_view = match.url_name context_links = {} if not links_dict: links_dict = Link.bound_links # Don't fudge with the original global dictionary # TODO: fix this links_dict = links_dict.copy() # Dynamic sources # TODO: improve name to 'injected...' # TODO: remove, only used by staging files try: """ Check for and inject a temporary navigation dictionary """ temp_navigation_links = Variable('temporary_navigation_links').resolve(context) if temp_navigation_links: links_dict.update(temp_navigation_links) except VariableDoesNotExist: pass # Get view only links try: view_links = links_dict[menu_name][current_view]['links'] except KeyError: pass else: context_links[None] = [] for link in view_links: context_links[None].append(link.resolve(context, request=request, current_path=current_path, current_view=current_view)) # Get object links for resolved_object in Link.get_navigation_objects(context).keys(): for source, data in links_dict.get(menu_name, {}).items(): if inspect.isclass(source) and isinstance(resolved_object, source) or Combined(obj=type(resolved_object), view=current_view) == source: context_links[resolved_object] = [] for link in data['links']: context_links[resolved_object].append(link.resolve(context, request=request, current_path=current_path, current_view=current_view, resolved_object=resolved_object)) break # No need for further content object match testing return context_links @classmethod def get_navigation_objects(cls, context): objects = {} try: object_list = Variable('navigation_object_list').resolve(context) except VariableDoesNotExist: pass else: logger.debug('found: navigation_object_list') for obj in object_list: objects.setdefault(obj, {}) # Legacy try: indirect_reference_list = Variable('navigation_object_list_ref').resolve(context) except VariableDoesNotExist: pass else: logger.debug('found: navigation_object_list_ref') for indirect_reference in indirect_reference_list: try: resolved_object = Variable(indirect_reference['object']).resolve(context) except VariableDoesNotExist: resolved_object = None else: objects.setdefault(resolved_object, {}) objects[resolved_object]['label'] = indirect_reference.get('object_name') try: resolved_object = Variable('object').resolve(context) except VariableDoesNotExist: pass else: logger.debug('found single object') try: object_label = Variable('object_name').resolve(context) except VariableDoesNotExist: object_label = None finally: objects.setdefault(resolved_object, {}) objects[resolved_object]['label'] = object_label #logger.debug('objects: %s' % objects) return objects @classmethod def resolve_template_variable(cls, context, name): try: return unescape_string_literal(name) except ValueError: #return Variable(name).resolve(context) #TODO: Research if should return always as a str return str(Variable(name).resolve(context)) except TypeError: return name @classmethod def resolve_arguments(cls, context, src_args): args = [] kwargs = {} if type(src_args) == type([]): for i in src_args: try: # Try to execute as a function val = i(context=context) except TypeError: val = Link.resolve_template_variable(context, i) if val: args.append(val) else: args.append(val) elif type(src_args) == type({}): for key, value in src_args.items(): try: # Try to execute as a function val = i(context=context) except TypeError: val = Link.resolve_template_variable(context, value) if val: kwargs[key] = val else: kwargs[key] = val else: val = Link.resolve_template_variable(context, src_args) if val: args.append(val) return args, kwargs class ModelListColumn(object): _model_list_columns = {} @classmethod def get_model(cls, model): return cls._model_list_columns.get(model) def __init__(self, model, name, attribute): self.__class__._model_list_columns.setdefault(model, []) self.__class__._model_list_columns[model].extend(columns) class Combined(object): """ Class that binds a link to a combination of an object and a view. This is used to show links relating to a specific object type but only in certain views. Used by the PageDocument class to show rotatio and zoom link only on certain views """ def __init__(self, obj, view): self.obj = obj self.view = view def __hash__(self): return hash((self.obj, self.view)) def __eq__(self, other): return hash(self) == hash(other)