Added Melange front page edit view where you can change title, content, feed url. Created SiteSettings and Document models and some logic for them. Added isFeedURLValid function in soc/logic/feed.py. Created some functions for handling datastore updates of different kinds of Models (soc/logic/model.py). Fixed some typos and too long lines of code.
authorPawel Solyga <Pawel.Solyga@gmail.com>
Sat, 13 Sep 2008 22:00:51 +0000
changeset 141 e120c24b89e2
parent 140 c3d098d6fafa
child 142 d88c7dbea0e8
Added Melange front page edit view where you can change title, content, feed url. Created SiteSettings and Document models and some logic for them. Added isFeedURLValid function in soc/logic/feed.py. Created some functions for handling datastore updates of different kinds of Models (soc/logic/model.py). Fixed some typos and too long lines of code. Patch by: Pawel Solyga Review by: to-be-reviewed
app/soc/logic/document.py
app/soc/logic/feed.py
app/soc/logic/model.py
app/soc/logic/site/id_user.py
app/soc/logic/site/settings.py
app/soc/models/document.py
app/soc/models/site_settings.py
app/soc/templates/soc/base.html
app/soc/templates/soc/site/home/edit.html
app/soc/templates/soc/site/home/public.html
app/soc/views/helpers/custom_widgets.py
app/soc/views/helpers/template_helpers.py
app/soc/views/simple.py
app/soc/views/site/home.py
app/soc/views/site/user/profile.py
app/urls.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/logic/document.py	Sat Sep 13 22:00:51 2008 +0000
@@ -0,0 +1,85 @@
+#!/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.
+
+"""Document (Model) query functions.
+"""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+  ]
+
+from google.appengine.ext import db
+
+import soc.models.document
+import soc.logic.model
+
+def getDocumentFromPath(path):
+  """Returns Document entity for a given path, or None if not found.  
+    
+  Args:
+    id: a Google Account (users.User) object
+  """
+  # lookup by Doc:path key name
+  key_name = getDocumentKeyNameForPath(path)
+  
+  if key_name:
+    document = soc.models.document.Document.get_by_key_name(key_name)
+  else:
+    document = None
+  
+  return document
+
+def getDocumentKeyNameForPath(path):
+  """Return a Datastore key_name for a Document from the path.
+  
+  Args:
+    path: a request path of the Document that uniquely identifies it
+  """
+  if not path:
+    return None
+
+  return 'Doc:%s' % path
+
+
+def updateOrCreateDocumentFromPath(path, **document_properties):
+  """Update existing Document entity, or create new one with supplied properties.
+
+  Args:
+    path: a request path of the Document that uniquely identifies it
+    **document_properties: keyword arguments that correspond to Document entity
+      properties and their values
+
+  Returns:
+    the Document entity corresponding to the path, with any supplied
+    properties changed, or a new Document entity now associated with the 
+    supplied path and properties.
+  """
+  # attempt to retrieve the existing Document
+  document = getDocumentFromPath(path)
+
+  if not document:
+    # document did not exist, so create one in a transaction
+    key_name = getDocumentKeyNameForPath(path)
+    document = soc.models.document.Document.get_or_insert(
+      key_name, **document_properties)
+
+  # there is no way to be sure if get_or_insert() returned a new Document or
+  # got an existing one due to a race, so update with document_properties anyway,
+  # in a transaction
+  return soc.logic.model.updateModelProperties(document, **document_properties)
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/logic/feed.py	Sat Sep 13 22:00:51 2008 +0000
@@ -0,0 +1,39 @@
+#!/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.
+
+"""Feeds helpers functions.
+"""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+  ]
+
+from google.appengine.api import urlfetch
+from soc.utils import feedparser
+
+def isFeedURLValid(feed_url=None):
+  """Returns True if provided url is valid ATOM or RSS.
+
+  Args:
+    feed_url: ATOM or RSS feed url
+  """
+  if feed_url:
+    result = urlfetch.fetch(feed_url)
+    if result.status_code == 200:
+      parsed_feed = feedparser.parse(result.content)
+      if parsed_feed.version and (parsed_feed.version != ''):
+        return True
+  return False
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/logic/model.py	Sat Sep 13 22:00:51 2008 +0000
@@ -0,0 +1,56 @@
+#!/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.
+
+"""Helpers functions for updating different kinds of models in datastore.
+"""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+  ]
+
+from google.appengine.ext import db
+
+def updateModelProperties(model, **model_properties):
+  """Update existing model entity using supplied model properties.
+
+  Args:
+    model: a model entity
+    **model_properties: keyword arguments that correspond to model entity
+      properties and their values
+
+  Returns:
+    the original model entity with any supplied properties changed 
+  """
+  def update():
+    return _unsafeUpdateModelProperties(model, **model_properties)
+
+  return db.run_in_transaction(update)
+
+
+def _unsafeUpdateModelProperties(model, **model_properties):
+  """(see updateModelProperties)
+
+  Like updateModelProperties(), but not run within a transaction. 
+  """
+  properties = model.properties()
+
+  for prop in properties.values():
+    if prop.name in model_properties:
+      value = model_properties[prop.name]
+      prop.__set__(model, value)
+
+  model.put()
+  return model
\ No newline at end of file
--- a/app/soc/logic/site/id_user.py	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/soc/logic/site/id_user.py	Sat Sep 13 22:00:51 2008 +0000
@@ -134,7 +134,27 @@
     return True
   else:
     return False
+    
+def isIdUser(id=None):
+  """Returns True if Google Account has it's soc.models.user.User entity in datastore.
 
+  Args:
+    id: a Google Account (users.User) object; if id is not supplied,
+      the current logged-in user is checked
+  """
+  id = getIdIfMissing(id)
+
+  if not id:
+    # no Google Account was supplied or is logged in
+    return False
+
+  user = getUserFromId(id)
+
+  if not user:
+    # no User entity for this Google Account
+    return False
+  
+  return True
 
 def isIdDeveloper(id=None):
   """Returns True if Google Account is a Developer with special privileges.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/logic/site/settings.py	Sat Sep 13 22:00:51 2008 +0000
@@ -0,0 +1,82 @@
+#!/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.
+
+"""SiteSettings (Model) query functions.
+"""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+  ]
+
+from google.appengine.ext import db
+
+import soc.models.site_settings
+import soc.logic.model
+
+def getSiteSettingsFromPath(path):
+  """Returns SiteSettings entity for a given path, or None if not found.  
+    
+  Args:
+    path: a request path of the SiteSettings that uniquely identifies it
+  """
+  # lookup by Settings:path key name
+  key_name = getSiteSettingsKeyNameForPath(path)
+  
+  if key_name:
+    site_settings = soc.models.site_settings.SiteSettings.get_by_key_name(key_name)
+  else:
+    site_settings = None
+  
+  return site_settings
+
+def getSiteSettingsKeyNameForPath(path):
+  """Return a Datastore key_name for a SiteSettings from the path.
+  
+  Args:
+    path: a request path of the SiteSettings that uniquely identifies it
+  """
+  if not path:
+    return None
+
+  return 'Settings:%s' % path
+
+
+def updateOrCreateSiteSettingsFromPath(path, **site_settings_properties):
+  """Update existing SiteSettings entity, or create new one with supplied properties.
+
+  Args:
+    path: a request path of the SiteSettings that uniquely identifies it
+    **site_settings_properties: keyword arguments that correspond to Document entity
+      properties and their values
+
+  Returns:
+    the SiteSettings entity corresponding to the path, with any supplied
+    properties changed, or a new SiteSettings entity now associated with the 
+    supplied path and properties.
+  """
+  # attempt to retrieve the existing Site Settings
+  site_settings = getSiteSettingsFromPath(path)
+
+  if not site_settings:
+    # site settings did not exist, so create one in a transaction
+    key_name = getSiteSettingsKeyNameForPath(path)
+    site_settings = soc.models.site_settings.SiteSettings.get_or_insert(
+      key_name, **site_settings_properties)
+
+  # there is no way to be sure if get_or_insert() returned a new SiteSettings or
+  # got an existing one due to a race, so update with site_settings_properties anyway,
+  # in a transaction
+  return soc.logic.model.updateModelProperties(site_settings, **site_settings_properties)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/models/document.py	Sat Sep 13 22:00:51 2008 +0000
@@ -0,0 +1,54 @@
+#!/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.
+
+"""This module contains the Document Model."""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+]
+
+from google.appengine.ext import db
+from django.utils.translation import ugettext_lazy
+import soc.models.user
+
+class Document(db.Model):
+  """Model of a Document.
+  
+  Document is used for things like FAQs, front page text etc.
+  """
+
+  title = db.StringProperty(required=True,
+      verbose_name=ugettext_lazy('Title'))
+  title.help_text = ugettext_lazy('Document title displayed on the top of the page')
+
+  link_name = db.StringProperty(required=True,
+      verbose_name=ugettext_lazy('Link name'))
+  link_name.help_text = ugettext_lazy('Document link name used in URLs')
+
+  short_name = db.StringProperty(required=True,
+      verbose_name=ugettext_lazy('Short name'))
+  short_name.help_text = ugettext_lazy('Document short name used for sidebar menu')
+  
+  content = db.TextProperty(
+      verbose_name=ugettext_lazy('Content'))
+  
+  created = db.DateTimeProperty(auto_now_add=True)
+  modified = db.DateTimeProperty(auto_now=True)
+  user = db.ReferenceProperty(reference_class=soc.models.user.User,
+                              required=True, collection_name='documents')
+
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/models/site_settings.py	Sat Sep 13 22:00:51 2008 +0000
@@ -0,0 +1,37 @@
+#!/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.
+
+"""This module contains the SiteSettings Model."""
+
+__authors__ = [
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
+]
+
+from google.appengine.ext import db
+from django.utils.translation import ugettext_lazy
+
+class SiteSettings(db.Model):
+  """Model of a SiteSettings, which stores per site configuration."""
+  
+  #: Valid ATOM or RSS feed url or None if unused. Feed entries are shown 
+  #: on the site page using Google's JavaScript blog widget  
+  feed_url = db.LinkProperty(
+      verbose_name=ugettext_lazy('Feed URL'))
+  feed_url.help_text = ugettext_lazy(
+      'The URL should be a valid ATOM or RSS feed. '
+      'Feed entries are shown on the site page.')
+  
+
--- a/app/soc/templates/soc/base.html	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/soc/templates/soc/base.html	Sat Sep 13 22:00:51 2008 +0000
@@ -65,7 +65,11 @@
 {% block sidebar_menu %}	
     <ul>
      <li>
-<a class="selected" href="/">Google Open Source Programs</a>
+<a class="selected" href="/">
+    {% block sidebar_menu_title %}
+    Google Open Source Programs
+    {% endblock %}
+    </a>
       <ul>
        <li>
 <a href="/user/profile">User (Sign In)</a>
@@ -75,6 +79,20 @@
 
        </li>
       </ul>
+{% if is_admin %}
+	  <ul>
+	      <li>Developer
+                    <ul>
+                        <li>
+              <a href="/site/home/edit">Site Settings</a>
+                       </li>
+                       <li>
+            <a href="/site/user/lookup">Look Up User</a>
+                        </li>
+                    </ul>	          
+	      </li>
+	  </ul>
+{% endif %}
       <ul>
        <li>
 <a href="/program/gsoc2009/home">Google Summer of Code</a>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/app/soc/templates/soc/site/home/edit.html	Sat Sep 13 22:00:51 2008 +0000
@@ -0,0 +1,54 @@
+{% extends "soc/base.html" %}
+{% comment %}
+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.
+{% endcomment %}
+
+{% block scripts %}
+	<script type="text/javascript" src="/soc/content/js/tiny_mce/tiny_mce_src.js"></script>
+{% endblock %}
+
+{% block page_title %}Site Settings{% endblock %}
+{% block header_title %}
+Site Settings
+{% endblock %}
+
+{% block body %}
+<p>
+<p>
+{% block instructions %}
+Please use this form to set basic site settings.
+{% endblock %}
+</p>
+<form method="POST">
+ <table>
+	{{ document_form.as_table }}
+	{{ settings_form.as_table }}
+  <tr>
+   <td colspan="4">&nbsp;</td>
+  </tr>
+  <tr>
+   <td> 
+    <input style="font-weight: bold" type="submit" value="Save Changes"/></span>
+   </td>
+   <td>
+    <input type="button" onclick="location.href='/'" value="Cancel"/>
+   </td>
+   <td>&nbsp;</td>
+   <td>
+    {% if submit_message %}<b><i>{{ submit_message }}</i></b>{% endif %}
+   </td>
+  </tr>
+ </table>
+</form>
+</p>
+{% endblock %}
\ No newline at end of file
--- a/app/soc/templates/soc/site/home/public.html	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/soc/templates/soc/site/home/public.html	Sat Sep 13 22:00:51 2008 +0000
@@ -14,6 +14,7 @@
 {% endcomment %}
 
 {% block scripts %}
+{% if site_settings.feed_url %}
     <script type="text/javascript" src="http://www.google.com/jsapi"></script>
     <script type="text/javascript" src="/soc/content/js/blog.js"></script>
     <script type="text/javascript">
@@ -22,14 +23,37 @@
 
     function initialize() {
       var blog = new BlogPreview(document.getElementById("blog"));
-      blog.show("http://google-opensource.blogspot.com/feeds/posts/default");
+      blog.show("{{ site_settings.feed_url }}");
     }
     google.setOnLoadCallback(initialize);
 
     </script>
+{% endif %}
+{% endblock %}
+
+{% block page_title %}
+{% if site_document %}
+{{ site_document.title }}
+{% else %}
+Google Open Source Programs
+{% endif %}
+{% endblock %}
+
+{% block header_title %}
+{% if site_document %}
+{{ site_document.title }}
+{% else %}
+Google Open Source Programs
+{% endif %}
 {% endblock %}
 
 {% block body %}
+    {% if site_document %}
+    {{ site_document.content|safe }}
+    {% else %}
 	{{ block.super }}
+    {% endif %}
+    {% if site_settings.feed_url %}
     <div id="blog"></div>
+    {% endif %}
 {% endblock %}
\ No newline at end of file
--- a/app/soc/views/helpers/custom_widgets.py	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/soc/views/helpers/custom_widgets.py	Sat Sep 13 22:00:51 2008 +0000
@@ -21,14 +21,14 @@
   '"Pawel Solyga" <pawel.solyga@gmail.com>',
   ]
 
-import django.newforms as forms
+from django import newforms as forms
 from django.newforms.widgets import flatatt
 from django.newforms.util import smart_unicode
 from django.utils.html import escape
 from django.utils import simplejson
 from django.utils.safestring import mark_safe
 
-class TinyMCE(forms.Textarea):
+class TinyMCE(forms.widgets.Textarea):
     """
     TinyMCE widget. requires you include tiny_mce_src.js in your template
     you can customize the mce_settings by overwriting instance mce_settings,
--- a/app/soc/views/helpers/template_helpers.py	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/soc/views/helpers/template_helpers.py	Sat Sep 13 22:00:51 2008 +0000
@@ -19,6 +19,7 @@
 
 __authors__ = [
   '"Todd Larsen" <tlarsen@google.com>',
+  '"Pawel Solyga" <pawel.solyga@gmail.com>'
   ]
 
 
@@ -55,3 +56,13 @@
     '%s/%s' % (t.rsplit('/', 1)[0], new_template_file) for t in templates]
 
   return sibling_templates + default_template
+
+def unescape(html): 
+  "Returns the given HTML with ampersands, quotes and carets decoded" 
+
+  if not isinstance(html, basestring): 
+    html = str(html) 
+  
+  html.replace('&amp;', '&').replace('&lt;', '<')
+  html.replace('&gt;', '>').replace('&quot;', '"').replace('&#39;',"'")
+  return html
--- a/app/soc/views/simple.py	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/soc/views/simple.py	Sat Sep 13 22:00:51 2008 +0000
@@ -15,14 +15,11 @@
 # limitations under the License.
 
 """Simple views that depend entirely on the template and context.
-
-simpleWithLinkName: a simple template view for URLs with a linkname
-
-errorResponse: renders an out_of_band.ErrorResponse page
 """
 
 __authors__ = [
   '"Todd Larsen" <tlarsen@google.com>',
+  '"Pawel Solyga" <pawel.solyga@gmail.com>',
   ]
 
 
@@ -174,12 +171,44 @@
   return requestLogin(request, template, context,
                       login_message_fmt=login_message_fmt)
 
+DEF_NO_USER_LOGIN_MSG_FMT = ugettext_lazy(
+    'Please create <a href="/user/profile">User Profile</a>'
+    ' in order to view this page.')
+
+def getAltResponseIfNotUser(request, context=None,
+                                template=DEF_LOGIN_TMPL, id=None,
+                                login_message_fmt=DEF_LOGIN_MSG_FMT):
+  """Returns an alternate HTTP response if Google Account has no User entity.
+
+  Args:
+    request: the standard django request object
+    context: the context supplied to the template (implements dict)
+    template: the "sibling" template (or a search list of such templates)
+      from which to construct the public.html template name (or names)
+    id: a Google Account (users.User) object, or None, in which case
+      the current logged-in user is used
+
+  Returns:
+    None if User exists for id, or a subclass of django.http.HttpResponse
+    which contains the alternate response that should be returned by the
+    calling view.
+  """
+  user_exist = id_user.isIdUser(id)
+
+  if user_exist:
+    return None
+
+  # if missing, create default template context for use with any templates
+  context = response_helpers.getUniversalContext(request, context=context)
+
+  return requestLogin(request, template, context,
+                      login_message_fmt=DEF_NO_USER_LOGIN_MSG_FMT)
 
 DEF_DEV_LOGIN_MSG_FMT = ugettext_lazy(
     'Please <a href="%(sign_in)s">sign in</a>'
     ' as a site developer to view this page.')
 
-DEF_DEF_LOGOUT_LOGIN_MSG_FMT = ugettext_lazy(
+DEF_DEV_LOGOUT_LOGIN_MSG_FMT = ugettext_lazy(
     'Please <a href="%(sign_out)s">sign out</a>'
     ' and <a href="%(sign_in)s">sign in</a>'
     ' again as a site developer to view this page.')
@@ -212,7 +241,7 @@
 
   if not id_user.isIdDeveloper(id=id):
     return requestLogin(request, template, context,
-        login_message_fmt=DEF_DEF_LOGOUT_LOGIN_MSG_FMT)
+        login_message_fmt=DEF_DEV_LOGOUT_LOGIN_MSG_FMT)
 
   # Google Account is logged in and is a Developer, so no need for sign in
   return None
--- a/app/soc/views/site/home.py	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/soc/views/site/home.py	Sat Sep 13 22:00:51 2008 +0000
@@ -20,7 +20,7 @@
   site
   
 There may (eventually) be different views of the site home page for
-logged-in Users (such as a debug() view for logged-in Developers). 
+logged-in Users (such as a edit() view for logged-in Developers). 
 """
 
 __authors__ = [
@@ -29,10 +29,64 @@
 
 
 from google.appengine.api import users
+
+from django import http
+from django import shortcuts
+from django import newforms as forms
+
+from soc.logic import out_of_band
+from soc.logic import feed
+from soc.logic.site import id_user
+from soc.views import simple
+from soc.views.helpers import forms_helpers
 from soc.views.helpers import response_helpers
+from soc.views.helpers import template_helpers
+from soc.views.helpers import widgets
+
+import soc.models.site_settings
+import soc.models.document
+import soc.logic.document
+import soc.logic.site.settings
 
 
-def public(request, template='soc/site/home/public.html'):
+class DocumentForm(forms_helpers.DbModelForm):
+  content = forms.fields.CharField(widget=widgets.TinyMCE())
+  #link_name = forms.CharField(widget=forms.TextInput(
+  #                                attrs={'readonly':'true'}))
+  class Meta:
+    """Inner Meta class that defines some behavior for the form.
+    """
+    #: db.Model subclass for which the form will gather information
+    model = soc.models.document.Document
+    
+    #: list of model fields which will *not* be gathered by the form
+    exclude = ['user','modified','created','link_name']
+
+class SiteSettingsForm(forms_helpers.DbModelForm):
+  """Django form displayed when creating or editing Site Settings.
+  """
+  class Meta:
+    """Inner Meta class that defines some behavior for the form.
+    """
+    #: db.Model subclass for which the form will gather information
+    model = soc.models.site_settings.SiteSettings
+
+  def clean_feed_url(self):
+    feed_url = self.cleaned_data.get('feed_url')
+
+    if feed_url == '':
+      # feed url not supplied (which is OK), so do not try to validate it
+      return None
+    
+    if not feed.isFeedURLValid(feed_url):
+      raise forms.ValidationError('This URL is not a valid ATOM or RSS feed.')
+
+    return feed_url
+
+DEF_SITE_HOME_PATH = 'site/home'
+DEF_SITE_HOME_PUBLIC_TMPL = 'soc/site/home/public.html'
+
+def public(request, template=DEF_SITE_HOME_PUBLIC_TMPL):
   """How the "general public" sees the Melange site home page.
 
   Args:
@@ -42,5 +96,109 @@
   Returns:
     A subclass of django.http.HttpResponse with generated template.
   """
-  return response_helpers.respond(request,
-      template, {'template': template})
+  # create default template context for use with any templates
+  context = response_helpers.getUniversalContext(request)
+  
+  document = soc.logic.document.getDocumentFromPath(DEF_SITE_HOME_PATH)
+  site_settings = soc.logic.site.settings.getSiteSettingsFromPath(DEF_SITE_HOME_PATH)
+  
+  if document:
+    document.content = template_helpers.unescape(document.content)
+    context.update({'site_document': document})
+  
+  if site_settings:
+    context.update({'site_settings': site_settings})
+    
+  return response_helpers.respond(request, template, context)
+
+
+DEF_SITE_HOME_EDIT_TMPL = 'soc/site/home/edit.html'
+
+def edit(request, template=DEF_SITE_HOME_EDIT_TMPL):
+  """View for Developer to edit content of Melange site home page.
+
+  Args:
+    request: the standard django request object.
+    template: the template path to use for rendering the template.
+
+  Returns:
+    A subclass of django.http.HttpResponse with generated template.
+  """
+  # create default template context for use with any templates
+  context = response_helpers.getUniversalContext(request)
+  
+  logged_in_id = users.get_current_user()
+  
+  alt_response = simple.getAltResponseIfNotDeveloper(request, context, 
+                                                        id = logged_in_id)
+  if alt_response:
+    # not a developer
+    return alt_response
+  
+  alt_response = simple.getAltResponseIfNotLoggedIn(request, context, 
+                                                        id = logged_in_id)
+  if alt_response:
+    # not logged in
+    return alt_response
+  
+  alt_response = simple.getAltResponseIfNotUser(request, context, 
+                                                        id = logged_in_id)
+  if alt_response:
+    # no existing User entity for logged in Google Account. User entity is 
+    # required for creating Documents
+    return alt_response
+                             
+  settings_form = None
+  document_form = None
+
+  if request.method == 'POST':
+    document_form = DocumentForm(request.POST)
+    settings_form = SiteSettingsForm(request.POST)
+
+    if document_form.is_valid() and settings_form.is_valid():
+      title = document_form.cleaned_data.get('title')
+      link_name = DEF_SITE_HOME_PATH
+      short_name = document_form.cleaned_data.get('short_name')
+      content = document_form.cleaned_data.get('content')
+      
+      feed_url = settings_form.cleaned_data.get('feed_url')
+      
+      document = soc.logic.document.updateOrCreateDocumentFromPath(
+                                  DEF_SITE_HOME_PATH,
+                                  link_name = link_name,
+                                  title = title,
+                                  short_name = short_name,
+                                  content = content,
+                                  user = id_user.getUserFromId(logged_in_id))
+
+      site_settings = soc.logic.site.settings.updateOrCreateSiteSettingsFromPath(
+                                  DEF_SITE_HOME_PATH,
+                                  feed_url = feed_url)
+      
+      context.update({'submit_message': 'Site Settings saved.'})
+  else: # request.method == 'GET'
+    # try to fetch Document entity by unique key_name   
+    document = soc.logic.document.getDocumentFromPath(DEF_SITE_HOME_PATH)
+
+    if document:
+      # populate form with the existing Document entity
+      document_form = DocumentForm(instance=document)
+    else:
+      # no Document entity exists for this key_name, so show a blank form
+      document_form = DocumentForm()
+      
+    # try to fetch SiteSettings entity by unique key_name   
+    site_settings = soc.logic.site.settings.getSiteSettingsFromPath(
+                                                        DEF_SITE_HOME_PATH)
+
+    if site_settings:
+      # populate form with the existing SiteSettings entity
+      settings_form = SiteSettingsForm(instance=site_settings)
+    else:
+      # no SiteSettings entity exists for this key_name, so show a blank form
+      settings_form = SiteSettingsForm()
+    
+  context.update({'document_form': document_form,
+                  'settings_form': settings_form })
+  
+  return response_helpers.respond(request, template, context)
--- a/app/soc/views/site/user/profile.py	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/soc/views/site/user/profile.py	Sat Sep 13 22:00:51 2008 +0000
@@ -92,16 +92,17 @@
 
   logged_in_id = users.get_current_user()
 
-  if not logged_in_id:
-    return simple.requestLogin(request, template, context,
-        login_message_fmt='Please <a href="%(sign_in)s">sign in</a>'
-                           ' as a site developer to view this page.')
-
-  if not id_user.isIdDeveloper(id=logged_in_id):
-    return simple.requestLogin(request, template, context,
-        login_message_fmt='Please <a href="%(sign_out)s">sign out</a>'
-                         ' and <a href="%(sign_in)s">sign in</a>'
-                         ' again as a site developer to view this page.')
+  alt_response = simple.getAltResponseIfNotDeveloper(request, context, 
+                                                        id = logged_in_id)
+  if alt_response:
+    # not a developer
+    return alt_response
+  
+  alt_response = simple.getAltResponseIfNotLoggedIn(request, context, 
+                                                        id = logged_in_id)
+  if alt_response:
+    # not logged in
+    return alt_response
 
   user = None  # assume that no User entity will be found
   form = None  # assume blank form needs to be displayed
--- a/app/urls.py	Sat Sep 13 21:27:17 2008 +0000
+++ b/app/urls.py	Sat Sep 13 22:00:51 2008 +0000
@@ -24,6 +24,8 @@
 urlpatterns = patterns(
     '',
     (r'^$', 'soc.views.site.home.public'),
+    (r'^site/home$', 'soc.views.site.home.public'),
+    (r'^site/home/edit$', 'soc.views.site.home.edit'),
 
     # TODO(tlarsen): uncomment these when the view functions are committed
     # attempt to send User to their dashboard
@@ -35,7 +37,9 @@
     #  'soc.views.user.roles.dashboard'),
 
     (r'^site/user/lookup$', 'soc.views.site.user.profile.lookup'),
-
+    (r'^site/user/profile/(?P<linkname>[_0-9a-z]+)$',
+     'soc.views.site.user.profile.edit'),
+     
     # TODO(tlarsen): uncomment these when the view functions are committed
     # (r'^site/user/profile$', 'soc.views.site.user.profile.create'),
     # (r'^site/user/profile/(?P<linkname>[_0-9a-z]+)$',