# HG changeset patch # User Todd Larsen # Date 1227256883 0 # Node ID ba3309b2fd30b6a65b0efa52e6f7a3f6a018302e # Parent 3a50bdfb75a94854906c777a64fcc452b5d6fa45 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 diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/logic/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: diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/logic/models/user.py --- 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 diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/logic/path_link_name.py --- 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%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%(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)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 {} diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/logic/validate.py --- 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 diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/group.py --- 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, diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/home_settings.py --- 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') diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/linkable.py --- 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%(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)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". diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/question.py --- 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 diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/quiz.py --- 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: diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/request.py --- 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. """ diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/response.py --- 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 diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/review.py --- 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 diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/reviewer.py --- 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): diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/role.py --- 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')) diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/task.py --- 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 diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/user.py --- 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')) diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/models/work.py --- 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')) diff -r 3a50bdfb75a9 -r ba3309b2fd30 app/soc/views/models/base.py --- 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, }