app/soc/logic/site/page.py
changeset 517 661ab830e921
parent 516 ec1dcd70b97e
child 518 d9d31d316a74
--- a/app/soc/logic/site/page.py	Thu Nov 20 21:01:18 2008 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,472 +0,0 @@
-#!/usr/bin/python2.5
-#
-# Copyright 2008 the Melange authors.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#   http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""Page properties, used to generate sidebar menus, urlpatterns, etc.
-"""
-
-__authors__ = [
-  '"Todd Larsen" <tlarsen@google.com>',
-  ]
-
-
-import copy
-import re
-
-from django.conf.urls import defaults
-
-from python25src import urllib
-
-from soc.logic import menu
-from soc.logic.no_overwrite_sorted_dict import NoOverwriteSortedDict
-
-
-class Url:
-  """The components of a Django URL pattern.
-  """
-   
-  def __init__(self, regex, view, kwargs=None, name=None, prefix=''):
-    """Collects Django urlpatterns info into a simple object.
-    
-    The arguments to this constructor correspond directly to the items in
-    the urlpatterns tuple, which also correspond to the parameters of
-    django.conf.urls.defaults.url().
-    
-    Args:
-      regex: a Django URL regex pattern, which, for obvious reason, must
-        be unique
-      view: a Django view, either a string or a callable; if a callable,
-        a unique 'name' string must be supplied
-      kwargs: optional dict of extra arguments passed to the view
-        function as keyword arguments, which is copy.deepcopy()'d;
-        default is None, which supplies an empty dict {}
-      name: optional name of the view; used instead of 'view' if supplied;
-        the 'name' or 'view' string, whichever is used, must be unique
-        amongst *all* Url objects supplied to a Page object
-      prefix: optional view prefix
-    """
-    self.regex = regex
-    self.view = view
-    
-    if kwargs:
-      self.kwargs = copy.deepcopy(kwargs)
-    else:
-      self.kwargs = {}
-
-    self.name = name
-    self.prefix = prefix
-
-  def makeDjangoUrl(self, **extra_kwargs):
-    """Returns a Django url() used by urlpatterns, or None if not a view.
-    """
-    if not self.view:
-      return None
-
-    kwargs = copy.deepcopy(self.kwargs)
-    kwargs.update(extra_kwargs)
-    return defaults.url(self.regex, self.view, kwargs=kwargs,
-                        name=self.name, prefix=self.prefix)
-
-  _STR_FMT = '''%(indent)sregex: %(regex)s
-%(indent)sview: %(view)s
-%(indent)skwargs: %(kwargs)s
-%(indent)sname: %(name)s
-%(indent)sprefix: %(prefix)s
-'''
-
-  def asIndentedStr(self, indent=''):
-    """Returns an indented string representation useful for logging.
-    
-    Args:
-      indent: an indentation string that is prepended to each line present
-        in the multi-line string returned by this method.
-    """
-    return self._STR_FMT % {'indent': indent, 'regex': self.regex,
-                            'view': self.view, 'kwargs': self.kwargs,
-                            'name': self.name, 'prefix': self.prefix}
-
-  def __str__(self):
-    """Returns a string representation useful for logging.
-    """
-    return self.asIndentedStr()
-
-
-class Page:
-  """An abstraction that combines a Django view with sidebar menu info.
-  """
-  
-  def __init__(self, url, long_name, short_name=None, selected=False,
-               annotation=None, parent=None, link_url=None,
-               in_breadcrumb=True, force_in_menu=False):
-    """Initializes the menu item attributes from supplied arguments.
-    
-    Args:
-      url: a Url object
-      long_name: title of the Page
-      short_name: optional menu item and breadcrumb name; default is
-        None, in which case long_name is used
-      selected: Boolean indicating if this menu item is selected;
-        default is False
-      annotation: optional annotation associated with the menu item;
-        default is None
-      parent: optional Page that is the logical "parent" of this page;
-        used to construct hierarchical menus and breadcrumb trails
-      link_url: optional alternate URL link; if supplied, it is returned
-        by makeLinkUrl(); default is None, and makeLinkUrl() attempts to
-        create a URL link from url.regex 
-      in_breadcrumb: if True, the Page appears in breadcrumb trails;
-        default is True
-      force_in_menu: if True, the Page appears in menus even if it does
-        not have a usable link_url; default is False, which excludes
-        the Page if makeLinkUrl() returns None
-    """
-    self.url = url
-    self.long_name = long_name
-    self.annotation = annotation
-    self.in_breadcrumb = in_breadcrumb
-    self.force_in_menu = force_in_menu
-    self.link_url = link_url
-    self.selected = selected
-    self.parent = parent
-
-    # create ordered, unique mappings of URLs and view names to Pages
-    self.child_by_urls = NoOverwriteSortedDict()
-    self.child_by_views = NoOverwriteSortedDict()
-    
-    if not short_name:
-      short_name = long_name
-      
-    self.short_name = short_name
-    
-    if parent:
-      # tell parent Page about parent <- child relationship
-      parent.addChild(self)
-      
-    # TODO(tlarsen): build some sort of global Page dictionary to detect
-    #   collisions sooner and to make global queries from URLs to pages
-    #   and views to Pages possible (without requiring a recursive search)
-
-  def getChildren(self):
-    """Returns an iterator over any child Pages 
-    """
-    for page in self.child_by_views.itervalues():
-      yield page
-
-  children = property(getChildren)
-
-  def getChild(self, url=None, regex=None, view=None,
-               name=None, prefix=None, request_path=None):
-    """Returns a child Page if one can be identified; or None otherwise.
-    
-    All of the parameters to this method are optional, but at least one
-    must be supplied to return anything other than None.  The parameters
-    are tried in the order they are listed in the "Args:" section, and
-    this method exits on the first "match".
-    
-    Args:
-      url: a Url object, used to overwrite the regex, view, name, and
-        prefix parameters if present; default is None
-      regex: a regex pattern string, used to return the associated
-        child Page
-      view: a view string, used to return the associated child Page
-      name: a name string, used to return the associated child Page
-      prefix: (currently unused, see TODO below in code)
-      request_path: optional HTTP request path string (request.path)
-        with no query arguments
-    """
-    # this code is yucky; there is probably a better way...
-    if url:
-      regex = url.regex
-      view = url.view
-      name = url.name
-      prefix = url.prefix
-
-    if regex in self.child_by_urls:
-      # regex supplied and Page found, so return that Page
-      return self.child_by_urls[regex][0]
-  
-    # TODO(tlarsen): make this work correctly with prefixes
-
-    if view in self.child_views:
-      # view supplied and Page found, so return that Page
-      return self.child_by_views[view]
-  
-    if name in self.child_views:
-      # name supplied and Page found, so return that Page
-      return self.child_by_views[name]
-
-    if request_path.startswith('/'):
-      request_path = request_path[1:]
-
-    # attempt to match the HTTP request path with a Django URL pattern
-    for pattern, (page, regex) in self.child_by_urls:
-      if regex.match(request_path):
-        return page
-
-    return None
-
-  def addChild(self, page):
-    """Adds a unique Page as a child Page of this parent.
-    
-    Raises:
-      ValueError if page.url.regex is not a string.
-      ValueError if page.url.view is not a string.
-      ValueError if page.url.name is supplied but is not a string.
-      KeyError if page.url.regex is already associated with another Page.
-      KeyError if page.url.view/name is already associated with another Page.
-    """
-    # TODO(tlarsen): see also TODO in __init__() about global Page dictionary
-
-    url = page.url
-    
-    if url.regex:
-      if not isinstance(url.regex, basestring):
-        raise ValueError('"regex" must be a string, not a compiled regex')
-
-      # TODO(tlarsen): see if Django has some way exposed in its API to get
-      #   the view name from the request path matched against urlpatterns;
-      #   if so, there would be no need for child_by_urls, because the
-      #   request path could be converted for us by Django into a view/name,
-      #   and we could just use child_by_views with that string instead
-      self.child_by_urls[url.regex] = (page, re.compile(url.regex))
-    # else: NonUrl does not get indexed by regex, because it has none
-
-    # TODO(tlarsen): make this work correctly if url has a prefix
-    #   (not sure how to make this work with include() views...)
-    if url.name:
-      if not isinstance(url.name, basestring):
-        raise ValueError('"name" must be a string if it is supplied')
-
-      view = url.name
-    elif isinstance(url.view, basestring):
-      view = url.view
-    else:
-      raise ValueError('"view" must be a string if "name" is not supplied')
-
-    self.child_by_views[view] = page
-
-  def delChild(self, url=None, regex=None, view=None, name=None,
-               prefix=None):
-    """Removes a child Page if one can be identified.
-    
-    All of the parameters to this method are optional, but at least one
-    must be supplied in order to remove a child Page.  The parameters
-    are tried in the order they are listed in the "Args:" section, and
-    this method uses the first "match".
-    
-    Args:
-      url: a Url object, used to overwrite the regex, view, name, and
-        prefix parameters if present; default is None
-      regex: a regex pattern string, used to remove the associated
-        child Page
-      view: a view string, used to remove the associated child Page
-      name: a name string, used to remove the associated child Page
-      prefix: (currently unused, see TODO below in code)
-      
-    Raises:
-      KeyError if the child Page could not be definitively identified in
-      order to delete it.
-    """
-    # this code is yucky; there is probably a better way...
-    if url:
-      regex = url.regex
-      view = url.view
-      name = url.name
-      prefix = url.prefix
-
-    # try to find page by regex, view, or name, in turn
-    if regex in self.child_by_urls:
-      url = self.child_by_urls[regex][0].url
-      view = url.view
-      name = url.name
-      prefix = url.prefix
-    elif view in self.child_views:
-      # TODO(tlarsen): make this work correctly with prefixes
-      regex = self.child_by_views[view].url.regex
-    elif name in self.child_views:
-      regex = self.child_by_views[name].url.regex
-
-    if regex:
-      # regex must refer to an existing Page at this point
-      del self.child_urls[regex]
-
-    if not isinstance(view, basestring):
-      # use name if view is callable() or None, etc.
-      view = name
-
-    # TODO(tlarsen): make this work correctly with prefixes
-    del self.child_by_views[view]
-    
-
-  def makeLinkUrl(self):
-    """Makes a URL link suitable for <A HREF> use.
-    
-    Returns:
-      self.link_url if link_url was supplied to the __init__() constructor
-        and it is a non-False value
-       -OR-
-      a suitable URL extracted from the url.regex, if possible
-       -OR-
-      None if url.regex contains quotable characters that have not already
-        been quoted (that is, % is left untouched, so quote suspect
-        characters in url.regex that would otherwise be quoted)
-    """
-    if self.link_url:
-      return self.link_url
-
-    link = self.url.regex
-    
-    if not link:
-      return None
-
-    if link.startswith('^'):
-      link = link[1:]
-    
-    if link.endswith('$'):
-      link = link[:-1]
-
-    if not link.startswith('/'):
-      link = '/' + link
-    
-    # path separators and already-quoted characters are OK
-    if link != urllib.quote(link, safe='/%'):
-      return None
-
-    return link
-
-  def makeMenuItem(self):
-    """Returns a menu.MenuItem for the Page (and any child Pages).
-    """
-    child_items = []
-    
-    for child in self.children:
-      child_item = child.makeMenuItem()
-      if child_item:
-        child_items.append(child_item)
-    
-    if child_items:
-      sub_menu = menu.Menu(items=child_items)
-    else:
-      sub_menu = None
-    
-    link_url = self.makeLinkUrl()
-    
-    if (not sub_menu) and (not link_url) and (not self.force_in_menu):
-      # no sub-menu, no valid link URL, and not forced to be in menu
-      return None
-    
-    return menu.MenuItem(
-      self.short_name, value=link_url, sub_menu=sub_menu)
-
-  def makeDjangoUrl(self):
-    """Returns the Django url() for the underlying self.url.
-    """
-    return self.url.makeDjangoUrl(page_name=self.short_name)
-
-  def makeDjangoUrls(self):
-    """Returns an ordered mapping of unique Django url() objects.
-    
-    Raises:
-      KeyError if more than one Page has the same urlpattern.
-      
-    TODO(tlarsen): this really needs to be detected earlier via a
-      global Page dictionary
-    """
-    return self._makeDjangoUrlsDict().values()
-
-  def _makeDjangoUrlsDict(self):
-    """Returns an ordered mapping of unique Django url() objects.
-    
-    Used to implement makeDjangoUrls().  See that method for details.
-    """
-    urlpatterns = NoOverwriteSortedDict()
-
-    django_url = self.makeDjangoUrl()
-    
-    if django_url:
-      urlpatterns[self.url.regex] = django_url
-    
-    for child in self.children:
-      urlpatterns.update(child._makeDjangoUrlsDict())
-    
-    return urlpatterns
-
-  _STR_FMT = '''%(indent)slong_name: %(long_name)s
-%(indent)sshort_name: %(short_name)s
-%(indent)sselected: %(selected)s
-%(indent)sannotation: %(annotation)s
-%(indent)surl: %(url)s
-'''
-
-  def asIndentedStr(self, indent=''):
-    """Returns an indented string representation useful for logging.
-    
-    Args:
-      indent: an indentation string that is prepended to each line present
-        in the multi-line string returned by this method.
-    """
-    strings = [ 
-        self._STR_FMT % {'indent': indent, 'long_name': self.long_name,
-                         'short_name': self.short_name,
-                         'selected': self.selected,
-                         'annotation': self.annotation,
-                         'url': self.url.asIndentedStr(indent + ' ')}]
-
-    for child in self.children:
-      strings.extend(child.asIndentedStr(indent + '  '))
-
-    return ''.join(strings)
-
-  def __str__(self):
-    """Returns a string representation useful for logging.
-    """
-    return self.asIndentedStr()
-
-
-class NonUrl(Url):
-  """Placeholder for when a site-map entry is not a linkable URL.
-  """
-   
-  def __init__(self, name):
-    """Creates a non-linkable Url placeholder.
-    
-    Args:
-      name: name of the non-view placeholder; see Url.__init__()
-    """
-    Url.__init__(self, None, None, name=name)
-
-  def makeDjangoUrl(self, **extra_kwargs):
-    """Always returns None, since NonUrl is never a Django view.
-    """
-    return None
-
-
-class NonPage(Page):
-  """Placeholder for when a site-map entry is not a displayable page.
-  """
-
-  def __init__(self, non_url_name, long_name, **page_kwargs):
-    """Constructs a NonUrl and passes it to base Page class __init__().
-    
-    Args:
-      non_url_name:  unique (it *must* be) string that does not match
-        the 'name' or 'view' of any other Url or NonUrl object;
-        see Url.__init__() for details
-      long_name:  see Page.__init__()
-      **page_kwargs:  keyword arguments passed directly to the base
-        Page class __init__()
-    """
-    non_url = NonUrl(non_url_name)
-    Page.__init__(self, non_url, long_name, **page_kwargs)