[#7188] Add has_direct_subprojects attr to Project

Signed-off-by: Tim Van Steenburgh <tvansteenburgh@gmail.com>
diff --git a/Allura/allura/lib/plugin.py b/Allura/allura/lib/plugin.py
index cd0ed23..eb08d67 100644
--- a/Allura/allura/lib/plugin.py
+++ b/Allura/allura/lib/plugin.py
@@ -662,17 +662,24 @@
                 sp.install_app('admin', 'admin', ordinal=1)
                 sp.install_app('search', 'search', ordinal=2)
             g.post_event('project_created')
+        project.has_direct_subprojects = True
         return sp
 
     def delete_project(self, project, user):
         for sp in project.subprojects:
             self.delete_project(sp, user)
         project.deleted = True
+        if not project.is_root:
+            project.parent_project.has_direct_subprojects = bool(
+                    project.parent_project.direct_subprojects)
+
 
     def undelete_project(self, project, user):
         project.deleted = False
         for sp in project.subprojects:
             self.undelete_project(sp, user)
+        if not project.is_root:
+            project.parent_project.has_direct_subprojects = True
 
     def best_download_url(self, project):
         '''This is the url needed to render a download button.
diff --git a/Allura/allura/model/project.py b/Allura/allura/model/project.py
index 21b4792..327059e 100644
--- a/Allura/allura/model/project.py
+++ b/Allura/allura/model/project.py
@@ -223,6 +223,7 @@
     trove_environment = FieldProperty([S.ObjectId])
     tracking_id = FieldProperty(str, if_missing='')
     is_nbhd_project = FieldProperty(bool, if_missing=False)
+    has_direct_subprojects = FieldProperty(bool)
 
     # transient properties
     notifications_disabled = False
@@ -254,12 +255,11 @@
             p = self.parent_project
             result.append(SitemapEntry('Parent Project'))
             result.append(SitemapEntry(p.name or p.script_name, p.script_name))
-        sps = self.direct_subprojects
-        if sps:
+        if self.has_direct_subprojects:
             result.append(SitemapEntry('Child Projects'))
             result += [
                 SitemapEntry(sp.name or sp.script_name, sp.script_name)
-                for sp in sps]
+                for sp in self.direct_subprojects]
         return result
 
     def troves_by_type(self, trove_type):
@@ -481,12 +481,13 @@
         delta_ordinal = i
         max_ordinal = i
 
-        for sub in self.direct_subprojects:
-            ordinal = sub.ordinal + delta_ordinal
-            if ordinal > max_ordinal:
-                max_ordinal = ordinal
-            entries.append({'ordinal': sub.ordinal + delta_ordinal,
-                           'entry': SitemapEntry(sub.name, sub.url())})
+        if self.has_direct_subprojects:
+            for sub in self.direct_subprojects:
+                ordinal = sub.ordinal + delta_ordinal
+                if ordinal > max_ordinal:
+                    max_ordinal = ordinal
+                entries.append({'ordinal': sub.ordinal + delta_ordinal,
+                               'entry': SitemapEntry(sub.name, sub.url())})
         for ac in self.app_configs + [a.config for a in new_tools]:
             if excluded_tools and ac.tool_name in excluded_tools:
                 continue
@@ -700,9 +701,10 @@
         i = len(anchored_tools)
         self.install_anchored_tools()
 
-        for sub in self.direct_subprojects:
-            result.append(
-                {'ordinal': int(sub.ordinal + i), 'sub': sub, 'rank': 1})
+        if self.has_direct_subprojects:
+            for sub in self.direct_subprojects:
+                result.append(
+                    {'ordinal': int(sub.ordinal + i), 'sub': sub, 'rank': 1})
         for ac in self.app_configs:
             App = g.entry_points['tool'].get(ac.tool_name)
             if include_hidden or App and not App.hidden: