Formalize the concept of a NonPage that can appear in the site-map, useful for
authorTodd Larsen <tlarsen@google.com>
Sat, 18 Oct 2008 01:32:40 +0000
changeset 372 8595c1129c74
parent 371 6dad90b78770
child 373 dcd7013ae0d5
Formalize the concept of a NonPage that can appear in the site-map, useful for non-clickable sidebar menu divisions, for example. Convert "hacky" fake Pages into NonPages in the site-map. Also, pass page=self to every view as a keyword argument when generating the Django urlpatterns. Patch by: Todd Larsen Review by: to-be-reviewed
app/soc/logic/site/map.py
app/soc/logic/site/page.py
--- a/app/soc/logic/site/map.py	Sat Oct 18 01:00:10 2008 +0000
+++ b/app/soc/logic/site/map.py	Sat Oct 18 01:32:40 2008 +0000
@@ -105,27 +105,9 @@
   parent=home)
 
 # Site User Profile views
-site_user_sub_menu = page.Page(
-  page.Url(
-    # not a real Django URL regex, just a unique placeholder
-    # (this can be any unique string that re.compile() will not reject, but
-    # that also contains characters that would be escaped, causing
-    # soc.logic.site.page.Page.makeLinkUrl() to reject it and not make the
-    # menu item into an <A HREF> link; this seems a bit hacky...)
-    #
-    # TODO(tlarsen): formalize this hack by subclassing Page (maybe calling
-    #   it something like NonPage) to add a non-linkable form of page for
-    #   use in dividers just like this
-    # TODO(tlarsen): add an optional keyword parameter that can be used to
-    #   control where the collapsible sub-menus are and whether they are
-    #   collapsed or not by default in the sidebar menu 
-    'site*user*sub*menu',
-    # no view, since this is just a link-less menu divider
-    # (this page will not be placed in urlpatterns)
-    None,
-    # name is alternate string for view when it is not unique
-    name='site-user-sub-menu'),
-  '',
+site_user_sub_menu = page.NonPage(
+  'site-user-sub-menu',
+  'Site: Users Sub-Menu',
   short_name='Site Users',
   parent=site_settings_edit)
 
@@ -170,15 +152,9 @@
   parent=home)
  
 # Site Document views
-site_docs_sub_menu = page.Page(
-  page.Url(
-    # (see site_user_sub_menu above for how this works...) 
-    'site*docs*sub*menu',
-    # no view, since this is just a link-less menu divider
-    None,
-    # name is alternate string for view when it is not unique
-    name='site-docs-sub-menu'),
-  '',
+site_docs_sub_menu = page.NonPage(
+  'site-docs-sub-menu',
+  'Site: Documents Sub-Menu',
   short_name='Site Documents',
   parent=site_settings_edit)
 
@@ -219,19 +195,13 @@
   page.Url(
     r'^sponsor/profile/%s' % path_link_name.LINKNAME_ARG_PATTERN,
     'soc.views.sponsor.profile.public'),
-  'Public Profile',
+  'Sponsor Public Profile',
   parent=home)
     
 # Sponsor Group Site views
-site_sponsor_sub_menu = page.Page(
-  page.Url(
-    # (see site_user_sub_menu above for how this works...) 
-    'site*sponsor*sub*menu',
-    # no view, since this is just a link-less menu divider
-    None,
-    # name is alternate string for view when it is not unique
-    name='site-sponsor-sub-menu'),
-  '',
+site_sponsor_sub_menu = page.NonPage(
+  'site-sponsor-sub-menu',
+  'Site: Sponsors Sub-Menu',
   short_name='Site Sponsors',
   parent=site_settings_edit)
 
--- a/app/soc/logic/site/page.py	Sat Oct 18 01:00:10 2008 +0000
+++ b/app/soc/logic/site/page.py	Sat Oct 18 01:32:40 2008 +0000
@@ -66,10 +66,15 @@
     self.name = name
     self.prefix = prefix
 
-  def makeDjangoUrl(self):
-    """Returns a Django url() used by urlpatterns.
+  def makeDjangoUrl(self, **extra_kwargs):
+    """Returns a Django url() used by urlpatterns, or None if not a view.
     """
-    return defaults.url(self.regex, self.view, kwargs=self.kwargs,
+    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
@@ -154,7 +159,7 @@
   def getChildren(self):
     """Returns an iterator over any child Pages 
     """
-    for page, _ in self.child_by_urls.itervalues():
+    for page in self.child_by_views.itervalues():
       yield page
 
   children = property(getChildren)
@@ -224,15 +229,17 @@
 
     url = page.url
     
-    if not isinstance(url.regex, basestring):
-      raise ValueError('"regex" must be a string, not a compiled regex')
+    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))
+      # 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...)
@@ -289,8 +296,9 @@
     elif name in self.child_views:
       regex = self.child_by_views[name].url.regex
 
-    # regex must refer to an existing Page at this point
-    del self.child_urls[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.
@@ -318,6 +326,9 @@
 
     link = self.url.regex
     
+    if not link:
+      return None
+
     if link.startswith('^'):
       link = link[1:]
     
@@ -360,7 +371,7 @@
   def makeDjangoUrl(self):
     """Returns the Django url() for the underlying self.url.
     """
-    return self.url.makeDjangoUrl()
+    return self.url.makeDjangoUrl(page=self)
 
   def makeDjangoUrls(self):
     """Returns an ordered mapping of unique Django url() objects.
@@ -379,9 +390,11 @@
     Used to implement makeDjangoUrls().  See that method for details.
     """
     urlpatterns = NoOverwriteSortedDict()
+
+    django_url = self.makeDjangoUrl()
     
-    if self.url.view:
-      urlpatterns[self.url.regex] = self.makeDjangoUrl()
+    if django_url:
+      urlpatterns[self.url.regex] = django_url
     
     for child in self.children:
       urlpatterns.update(child._makeDjangoUrlsDict())
@@ -418,3 +431,32 @@
     """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
+    """
+    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):
+    """
+    """
+    non_url = NonUrl(non_url_name)
+    Page.__init__(self, non_url, long_name, **page_kwargs)