Move LINK_ID and SCOPE_PATH regular expressions to soc/models/linkable.py.
authorTodd Larsen <tlarsen@google.com>
Fri, 21 Nov 2008 08:41:23 +0000
changeset 533 ba3309b2fd30
parent 532 3a50bdfb75a9
child 534 c31cfbf1a20f
Move LINK_ID and SCOPE_PATH regular expressions to soc/models/linkable.py. Fix some too-long lines. Change key names back to type:scope_path/link_id (instead of type:scope_path:link_id). Some minor __doc__ string updates. Patch by: Todd Larsen
app/soc/logic/models/base.py
app/soc/logic/models/user.py
app/soc/logic/path_link_name.py
app/soc/logic/validate.py
app/soc/models/group.py
app/soc/models/home_settings.py
app/soc/models/linkable.py
app/soc/models/question.py
app/soc/models/quiz.py
app/soc/models/request.py
app/soc/models/response.py
app/soc/models/review.py
app/soc/models/reviewer.py
app/soc/models/role.py
app/soc/models/task.py
app/soc/models/user.py
app/soc/models/work.py
app/soc/views/models/base.py
--- a/app/soc/logic/models/base.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/logic/models/base.py	Fri Nov 21 08:41:23 2008 +0000
@@ -56,7 +56,7 @@
     if name:
       self._name = name
     else:
-      self._name = self.getModelClassName()
+      self._name =  self._model.__name__
     
     if skip_properties:
       self._skip_properties = skip_properties
@@ -101,18 +101,12 @@
       keyvalues.append(kwargs[key_field_name])
 
     # construct the KeyName in the appropriate format
-    return ":".join([self._name] + keyvalues)
-
-  def getModelClassName(self):
-    """Returns model class name string.
-    """ 
-    return self._model.__name__ 
+    return "%s:%s" % (self._name, '/'.join(keyvalues))
 
   def getFullModelClassName(self):
     """Returns fully-qualified model module.class name string.
     """ 
-    return '%s.%s' % (self._model.__module__,
-                      self.getModelClassName()) 
+    return '%s.%s' % (self._model.__module__, self._model.__name__)
 
   def getKeyValues(self, entity):
     """Exctracts the key values from entity and returns them.
@@ -474,6 +468,10 @@
       supplied key_name and properties.
     """
 
+    import logging
+    logging.info(key_name)
+    logging.info(properties)
+
     entity = self.getFromKeyName(key_name)
 
     if not entity:
--- a/app/soc/logic/models/user.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/logic/models/user.py	Fri Nov 21 08:41:23 2008 +0000
@@ -40,7 +40,8 @@
   def isFormerAccount(self, account):
     """Returns true if account is a former account of some User.
     """
-    # TODO(pawel.solyga): replace 1000 with solution that works for any number of queries
+    # TODO(pawel.solyga): replace 1000 with solution that works for any
+    #   number of queries
     users_with_former_accounts = soc.models.user.User.gql(
         'WHERE former_accounts != :1', None).fetch(1000)
     
@@ -74,28 +75,13 @@
 
     return ['link_id']
 
-  def updateOrCreateFromAccount(self, properties, account):
-    """Like updateOrCreateFromKeyName, but resolves account to key_name first.
-    """
-
-    # attempt to retrieve the existing entity
-    user = soc.models.user.User.gql('WHERE account = :1', account).get()
-    
-    if user:
-      key_name = user.key().name()
-    else:
-      raise
-      key_name  = self.getKeyNameForFields({'link_id': properties['link_id']})
-
-    return self.updateOrCreateFromKeyName(properties, key_name)
-
   def _updateField(self, model, name, value):
     """Special case logic for account.
 
     When the account is changed, the former_accounts field should be appended
     with the old account.
     """
-    if name == 'account' and model.account != value:
+    if (name == 'account') and (model.account != value):
       model.former_accounts.append(model.account)
 
     return True
--- a/app/soc/logic/path_link_name.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/logic/path_link_name.py	Fri Nov 21 08:41:23 2008 +0000
@@ -25,37 +25,7 @@
 
 import re
 
-
-# start with ASCII digit or lowercase
-#   (additional ASCII digit or lowercase
-#     -OR-
-#   underscore and ASCII digit or lowercase)
-#     zero or more of OR group
-LINK_ID_PATTERN_CORE = r'[0-9a-z](?:[0-9a-z]|_[0-9a-z])*'
-LINK_ID_ARG_PATTERN = r'(?P<link_id>%s)' % LINK_ID_PATTERN_CORE
-LINK_ID_PATTERN = r'^%s$' % LINK_ID_PATTERN_CORE
-LINK_ID_REGEX = re.compile(LINK_ID_PATTERN)
-
-# scope path is multiple link_id chunks,
-# each separated by a trailing /
-# (at least 1)
-SCOPE_PATH_ARG_PATTERN = (r'(?P<scope_path>%(link_id)s'
-                             '(?:/%(link_id)s)*)' % {
-                               'link_id': LINK_ID_PATTERN_CORE})
-SCOPE_PATH_PATTERN = r'^%s$' % SCOPE_PATH_ARG_PATTERN
-SCOPE_PATH_REGEX = re.compile(SCOPE_PATH_PATTERN)
-
-# path is multiple link_id chunks,
-#   each separated by a trailing /
-#     (at least 1)
-# followed by a single link_id with no trailing /
-PATH_LINK_ID_ARGS_PATTERN = (
-    r'%(scope_path)s/'
-     '(?P<link_id>%(link_id)s)' % {
-       'scope_path' : SCOPE_PATH_ARG_PATTERN,
-       'link_id': LINK_ID_PATTERN_CORE})
-PATH_LINK_ID_PATTERN = r'^%s$' % PATH_LINK_ID_ARGS_PATTERN
-PATH_LINK_ID_REGEX = re.compile(PATH_LINK_ID_PATTERN)
+from soc.models import linkable
 
 
 def getPartsFromPath(path):
@@ -66,7 +36,7 @@
      'link_id': 'link_id'}
     or {} (empty dict) if string did not match PATH_LINK_ID_PATTERN.
   """
-  path_link_name_match = PATH_LINK_ID_REGEX.match(path)
+  path_link_name_match = linkable.PATH_LINK_ID_REGEX.match(path)
   
   if not path_link_name_match:
     return {}
--- a/app/soc/logic/validate.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/logic/validate.py	Fri Nov 21 08:41:23 2008 +0000
@@ -27,7 +27,7 @@
 
 from google.appengine.api import urlfetch
 
-from soc.logic import path_link_name
+from soc.models import linkable
 
 
 def isFeedURLValid(feed_url=None):
@@ -51,7 +51,7 @@
   Args:
     link_id: link ID used in URLs for identification
   """
-  if path_link_name.LINK_ID_REGEX.match(link_id):
+  if linkable.LINK_ID_REGEX.match(link_id):
     return True
   return False
 
@@ -64,7 +64,7 @@
       used for identification.
   """
    
-  if path_link_name.SCOPE_PATH_REGEX.match(scope_path):
+  if linkable.SCOPE_PATH_REGEX.match(scope_path):
     return True
   
   return False
--- a/app/soc/models/group.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/group.py	Fri Nov 21 08:41:23 2008 +0000
@@ -22,18 +22,17 @@
 ]
 
 
-import polymodel
-
 from google.appengine.ext import db
 
 from django.utils.translation import ugettext_lazy
 
 from soc.models import countries
 
+import soc.models.linkable
 import soc.models.user
 
 
-class Group(polymodel.PolyModel):
+class Group(soc.models.linkable.Linkable):
   """Common data fields for all groups.
   """
 
@@ -42,14 +41,6 @@
       verbose_name=ugettext_lazy('Name'))
   name.help_text = ugettext_lazy('Complete, formal name of the group.')  
   
-  #: Required field storing link_id used in URLs to identify group.
-  #: Lower ASCII characters only.
-  link_id = db.StringProperty(required=True,
-      verbose_name=ugettext_lazy('Link ID'))
-  link_id.help_text = ugettext_lazy(
-      'Field used in URLs to identify group. '
-      'Lower ASCII characters only.')
-  
   #: Required field storing short name of the group.
   #: It can be used for displaying group as sidebar menu item.
   short_name = db.StringProperty(required=True,
--- a/app/soc/models/home_settings.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/home_settings.py	Fri Nov 21 08:41:23 2008 +0000
@@ -22,16 +22,15 @@
 ]
 
 
-import polymodel
-
 from google.appengine.ext import db
 
 from django.utils.translation import ugettext_lazy
 
 import soc.models.document
+import soc.models.linkable
 
 
-class HomeSettings(polymodel.PolyModel):
+class HomeSettings(soc.models.linkable.Linkable):
   """Model that stores settings for various Home pages.
 
   This Model is the basis for more specific "/home" view settings, such as
@@ -51,14 +50,3 @@
   feed_url.help_text = ugettext_lazy(
       'The URL should be a valid ATOM or RSS feed. '
       'Feed entries are shown on the home page.')
-
-  #: Required path, prepended to a "link ID" to form the Setting URL.
-  scope_path = db.StringProperty(required=True,
-      verbose_name=ugettext_lazy('Settings scope path'))
-  scope_path.help_text = ugettext_lazy(
-    'path portion of URLs for Settings, prepended to link ID')
-
-  #: Required link ID, appended to a "path" to form the Setting URL.
-  link_id = db.StringProperty(required=True,
-      verbose_name=ugettext_lazy('Setttings link ID'))
-  link_id.help_text = ugettext_lazy('link ID for Settings used in URLs')
--- a/app/soc/models/linkable.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/linkable.py	Fri Nov 21 08:41:23 2008 +0000
@@ -44,6 +44,27 @@
 LINK_ID_PATTERN = r'^%s$' % LINK_ID_PATTERN_CORE
 LINK_ID_REGEX = re.compile(LINK_ID_PATTERN)
 
+# scope path is multiple link_id chunks,
+# each separated by a trailing /
+# (at least 1)
+SCOPE_PATH_ARG_PATTERN = (r'(?P<scope_path>%(link_id)s'
+                             '(?:/%(link_id)s)*)' % {
+                               'link_id': LINK_ID_PATTERN_CORE})
+SCOPE_PATH_PATTERN = r'^%s$' % SCOPE_PATH_ARG_PATTERN
+SCOPE_PATH_REGEX = re.compile(SCOPE_PATH_PATTERN)
+
+# path is multiple link_id chunks,
+#   each separated by a trailing /
+#     (at least 1)
+# followed by a single link_id with no trailing /
+PATH_LINK_ID_ARGS_PATTERN = (
+    r'%(scope_path)s/'
+     '(?P<link_id>%(link_id)s)' % {
+       'scope_path' : SCOPE_PATH_ARG_PATTERN,
+       'link_id': LINK_ID_PATTERN_CORE})
+PATH_LINK_ID_PATTERN = r'^%s$' % PATH_LINK_ID_ARGS_PATTERN
+PATH_LINK_ID_REGEX = re.compile(PATH_LINK_ID_PATTERN)
+
 
 class Linkable(polymodel.PolyModel):
   """A base class for Model classes that are "linkable".
--- a/app/soc/models/question.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/question.py	Fri Nov 21 08:41:23 2008 +0000
@@ -44,15 +44,15 @@
     work.reviews:  even Questions can be "reviewed" (possibly commented
       on during creation or annotated once put into use).
 
-    work.scope_path:  used to scope (and, when combined with
-      work.link_id, uniquely identify) a Question in the same way the
+    work.content:  the Question text, asked to the respondent
+
+    linkable.scope:  used to scope (and, when combined with
+      linkable.link_id, uniquely identify) a Question in the same way the
       property are used with Documents, etc.
 
-    work.link_id:  used to identify (and, when combined with
-      work.scope_path, *uniquely* identify) a Question in the same way
+    linkable.link_id:  used to identify (and, when combined with
+      linkable.scope, *uniquely* identify) a Question in the same way
       these properties are used with Documents, etc.
-      
-    work.content:  the Question text, asked to the respondent
 
   In addition to any explicit ReferenceProperties in the Question Model
   and those inherited as described above, a Question entity participates
@@ -86,14 +86,14 @@
     when these ideas are implemented in the views and controllers; they
     are here now so that the concepts will not be lost before that time.
 
-  The recommended use for the combination of work.scope_path and
-  work.link_id is to keep the *same* link_id when copying and
+  The recommended use for the combination of linkable.scope and
+  linkable.link_id is to keep the *same* link_id when copying and
   modifying an existing Question for a new Program (or instance of a
-  Group that is per-Program), while changing the work.scope_path to
+  Group that is per-Program), while changing the linkable.scope to
   represent the Program and Group "ownership" of the Question.  For
   example, if a Question asking about prior GSoC participation needed
   to have an additional choice (see the choice_ids and choices properties
-  below), it is desirable to keep the same work.link_id (and also
+  below), it is desirable to keep the same linkable.link_id (and also
   simply append new choice_ids and choices to keep the old answer values
   compatible).  An existing Question in the above example might be identified
   as something like:
@@ -116,7 +116,7 @@
     Question:google/ghop2009/gsoc_past_participation
   To get the combined results, query on a link_id of
   gsoc_past_participation.  For more targeted results, include the
-  scope_path to make the query more specific.
+  scope to make the query more specific.
 
   Question creation to permit use cases like the one above is going to
   be a bit of an "advanced" skill, possibly.  "Doing it wrong" the first
--- a/app/soc/models/quiz.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/quiz.py	Fri Nov 21 08:41:23 2008 +0000
@@ -52,12 +52,13 @@
     work.reviews:  even Quizzes can be "reviewed" (possibly commented
       on during creation or annotated once put into use).
 
-    work.scope_path/work.link_id: used to scope and uniquely identify
-      a Quiz in the same way these properties are used with Documents, etc.
-
     work.content:  the "preface" of the Quiz, displayed before any
       of the Questions, usually containing instructions for the Quiz
 
+    linkable.scope/linkable.link_id: used to scope and uniquely
+      identify a Quiz in the same way these properties are used with
+      Documents, etc.
+
   In addition to any explicit ReferenceProperties in the Quiz Model and
   those inherited as described above, a Quiz entity participates in these
   relationships:
--- a/app/soc/models/request.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/request.py	Fri Nov 21 08:41:23 2008 +0000
@@ -21,8 +21,6 @@
 ]
 
 
-import polymodel
-
 from google.appengine.ext import db
 
 from django.utils.translation import ugettext_lazy
@@ -31,7 +29,7 @@
 import soc.models.group
 
 
-class Request(polymodel.PolyModel):
+class Request(soc.models.linkable.Linkable):
   """A request is made to allow a person to create a new Role entity.
   """
 
--- a/app/soc/models/response.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/response.py	Fri Nov 21 08:41:23 2008 +0000
@@ -21,15 +21,14 @@
 ]
 
 
-import polymodel
-
 from google.appengine.ext import db
 
+import soc.models.linkable
 import soc.models.quiz
 import soc.models.user
 
 
-class Response(polymodel.PolyModel):
+class Response(soc.models.linkable.Linkable):
   """Model of a Response to a Quiz.
 
   A Response is the "collection point" for a set of specific Answers to the
--- a/app/soc/models/review.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/review.py	Fri Nov 21 08:41:23 2008 +0000
@@ -23,13 +23,14 @@
 
 from google.appengine.ext import db
 
+import soc.models.linkable
 # TODO: Uncomment when Survey model is committed
 #import soc.models.survey
+import soc.models.reviewer
 import soc.models.work
-import soc.models.reviewer
 
 
-class Review(db.Model):
+class Review(soc.models.linkable.Linkable):
   """Model of a review of a Proposal or a Task.
 
   A Review entity is a specific instance of a completed Survey, collecting
--- a/app/soc/models/reviewer.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/reviewer.py	Fri Nov 21 08:41:23 2008 +0000
@@ -23,8 +23,8 @@
 
 from google.appengine.ext import db
 
+import soc.models.organization
 import soc.models.role
-import soc.models.organization
 
 
 class Reviewer(soc.models.role.Role):
--- a/app/soc/models/role.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/role.py	Fri Nov 21 08:41:23 2008 +0000
@@ -23,18 +23,17 @@
 ]
 
 
-import polymodel
-
 from google.appengine.ext import db
 
 from django.utils.translation import ugettext_lazy
 
 from soc.models import countries
 
+import soc.models.linkable
 import soc.models.user
 
 
-class Role(polymodel.PolyModel):
+class Role(soc.models.linkable.Linkable):
   """Information common to Program participation for all Roles.
 
   Some details of a Role are considered "public" information, and nearly
@@ -273,4 +272,4 @@
   #: Optional field indicating choice of t-shirt fit; kept private.
   tshirt_style = db.StringProperty(
       verbose_name=ugettext_lazy('T-shirt Style'),
-      choices=('male', 'female'))
\ No newline at end of file
+      choices=('male', 'female'))
--- a/app/soc/models/task.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/task.py	Fri Nov 21 08:41:23 2008 +0000
@@ -25,10 +25,11 @@
 
 from soc.models import base
 
+import soc.models.linkable
 import soc.models.proposal
 
 
-class Task(base.ModelWithFieldAttributes):
+class Task(soc.models.linkable.Linkable):
   """Model of a Task, which is a Proposal to be completed by Contributors.
 
   A Task brings along a Proposal that was used to initiate the Task.  A Task
--- a/app/soc/models/user.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/user.py	Fri Nov 21 08:41:23 2008 +0000
@@ -28,10 +28,10 @@
 
 from django.utils.translation import ugettext_lazy
 
-from soc.models import base
+import soc.models.linkable
 
 
-class User(base.ModelWithFieldAttributes):
+class User(soc.models.linkable.Linkable):
   """A user and associated login credentials, the fundamental identity entity.
 
   User is a separate Model class from Person because the same login 
@@ -80,14 +80,6 @@
   public_name = db.StringProperty(required=True,
       verbose_name=ugettext_lazy('Public name'))
       
-  #: Required field storing link_id used in URLs to identify user.
-  #: Lower ASCII characters only.
-  link_id = db.StringProperty(required=True,
-      verbose_name=ugettext_lazy('Link ID'))
-  link_id.help_text = ugettext_lazy(
-      'Field used in URLs to identify user. '
-      'Lower ASCII characters only.')
-
   #: field storing whether User is a Developer with site-wide access.
   is_developer = db.BooleanProperty(
       verbose_name=ugettext_lazy('Is Developer'))
--- a/app/soc/models/work.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/models/work.py	Fri Nov 21 08:41:23 2008 +0000
@@ -22,16 +22,15 @@
 ]
 
 
-import polymodel
-
 from google.appengine.ext import db
 
 from django.utils.translation import ugettext_lazy
 
+import soc.models.linkable
 import soc.models.user
 
 
-class Work(polymodel.PolyModel):
+class Work(soc.models.linkable.Linkable):
   """Model of a Work created by one or more Persons in Roles.
 
   Work is a "base entity" of other more specific "works" created by Persons
@@ -57,24 +56,6 @@
   title.help_text = ugettext_lazy(
       'title of the document; often used in the window title')
 
-  #: Required path, prepended to a "link ID" to form the document URL.
-  #: The combined path and link ID must be globally unique on the
-  #: site.  Except in /site/document (Developer) forms, this field is not
-  #: usually directly editable by the User, but is instead set by controller
-  #: logic to match the "scope" of the document.
-  scope_path = db.StringProperty(required=True,
-      verbose_name=ugettext_lazy('Scope path'))
-  scope_path.help_text = ugettext_lazy(
-    'path portion of URLs, prepended to link ID')
-
-  #: Required link ID, appended to a "path" to form the document URL.
-  #: The combined path and link ID must be globally unique on the
-  #: site (but, unlike some link IDs, a Work link ID can be reused,
-  #: as long as the combination with the preceding path is unique).
-  link_id = db.StringProperty(required=True,
-      verbose_name=ugettext_lazy('Link ID'))
-  link_id.help_text = ugettext_lazy('link ID used in URLs')
-
   #: short name used in places such as the sidebar menu and breadcrumb trail
   #: (optional: title will be used if short_name is not present)
   short_name = db.StringProperty(verbose_name=ugettext_lazy('Short name'))
--- a/app/soc/views/models/base.py	Fri Nov 21 08:38:53 2008 +0000
+++ b/app/soc/views/models/base.py	Fri Nov 21 08:41:23 2008 +0000
@@ -36,7 +36,7 @@
 
 from soc.logic import dicts
 from soc.logic import models
-from soc.logic import path_link_name
+from soc.models import linkable
 from soc.views import simple
 from soc.views import helper
 from soc.views.helper import access
@@ -532,7 +532,7 @@
     patterns = params['key_fields_prefix']
 
     for name in names:
-      pattern = r'(?P<%s>%s)' % (name, path_link_name.LINK_ID_PATTERN_CORE)
+      pattern = r'(?P<%s>%s)' % (name, linkable.LINK_ID_PATTERN_CORE)
       patterns.append(pattern)
 
     result = '/'.join(patterns)
@@ -620,8 +620,8 @@
 
       url = url % {
           'url_name': params['url_name'],
-          'lnp': path_link_name.LINK_ID_ARG_PATTERN,
-          'ulnp': path_link_name.LINK_ID_PATTERN_CORE,
+          'lnp': linkable.LINK_ID_ARG_PATTERN,
+          'ulnp': linkable.LINK_ID_PATTERN_CORE,
           'key_fields': key_fields_pattern,
           }