Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Refactor, cleanup, generally trying to divide the functionality of th…

…e ``ContextBy*`` classes into better units.
  • Loading branch information...
commit d1fe60efe16cfe548087fbbfc7341ed812516606 1 parent 339c708
@malthe malthe authored
Showing with 225 additions and 330 deletions.
  1. +8 −124 lumin/models/user.py
  2. +95 −206 lumin/node.py
  3. +122 −0 lumin/schema.py
View
132 lumin/models/user.py
@@ -1,145 +1,29 @@
-import colander
-from colander import Float
-from colander import SchemaNode
-from colander import String
-
-import deform
-
from pyramid.security import authenticated_userid
from pyramid.security import Allow
from pyramid.security import Everyone
from lumin.node import ContextById
-
-
-@colander.deferred
-def deferred_username_validator(node, kw):
- request = kw['request']
- def validate_username(node, value):
- if len(value) < 4 or len(value) > 24:
- raise colander.Invalid(node,
- "Length of user name must be between 4 and \
- 24 lowercase alphanumeric characters")
- if not value.replace('_', '').isalnum() or not value.islower():
- raise colander.Invalid(node,
- "Only lowercase numbers, letters and \
- underscores are permitted")
- if not value[0].isalpha():
- raise colander.Invalid(node,
- "The username must start with a \
- letter")
- collection = request.context.collection
- available = collection.find({'_id': value}).count()==0
- if not available:
- raise colander.Invalid(node, "Username is not available")
- return validate_username
-
-email_widget = deform.widget.CheckedInputWidget(
- subject="Email",
- confirm_subject="Confirm Email",
- size=40
- )
-
-class EmailSchema(colander.Schema):
- email = SchemaNode(String(),
- title="email",
- description='Type your email address and confirm it',
- validator=colander.Email(),
- widget=email_widget)
-
-
-class PasswordSchema(colander.Schema):
- password = SchemaNode(String(),
- validator=colander.Length(min=6),
- widget = deform.widget.CheckedPasswordWidget(size=40),
- description="Type your password and confirm it")
-
-
-class UserSchema(colander.MappingSchema):
- _id = SchemaNode(String(),
- title="Username",
- description="The name of the participant",
- validator=deferred_username_validator)
- given_name = SchemaNode(String(), missing='',
- title="Given Name")
- surname = SchemaNode(String(), missing='',
- title="Surname")
- street_address = SchemaNode(String(), missing='',
- title="Street Address",
- description='Address info (number, street, unit)')
- locality = SchemaNode(String(), missing='',
- title='City',
- description="City or township name")
- ## TODO: There must be an ISO list for this
- region = SchemaNode(String(), missing='',
- title='Locality',
- description='State, Province, Township or equivalent')
- postal_code = SchemaNode(String(), missing='',
- title='Postal Code',
- description='ZIP or postal code')
- ## TODO: make this oneOf ISO countries
- country_name = SchemaNode(String(), missing='',
- title='Country',
- description='Country')
- telephone = SchemaNode(String(), missing='',
- title='Telephone Number')
- fax = SchemaNode(String(), missing='',
- title='Fax number')
- website_url = SchemaNode(String(), missing='',
- title='Website URL',
- description='I.e. http://example.com')
- latitude = SchemaNode(Float(), missing=colander.null,
- title='Latitude')
- longitude = SchemaNode(Float(), missing=colander.null,
- title='Longitude')
- email = SchemaNode(String(),
- title="email",
- description='Type your email address and confirm it',
- validator=colander.Email(),
- widget=email_widget)
- password = SchemaNode(String(),
- validator=colander.Length(min=6),
- widget = deform.widget.CheckedPasswordWidget(size=40),
- description="Type your password and confirm it")
-
-class SimpleUserSchema(colander.MappingSchema):
- _id = SchemaNode(String(),
- title="Username",
- description="The name of the participant",
- validator=deferred_username_validator)
- display_name = SchemaNode(String(), missing=colander.null,
- title="Display Name",
- widget=deform.widget.TextInputWidget(size=40))
- email = SchemaNode(String(),
- title="email",
- description='Type your email address and confirm it',
- validator=colander.Email(),
- widget=email_widget)
- password = SchemaNode(String(),
- validator=colander.Length(min=6),
- widget = deform.widget.CheckedPasswordWidget(size=40),
- description="Type your password and confirm it")
-
+from lumin.schema import UserSchema
class User(ContextById):
__acl__ = [
- (Allow, Everyone, 'view'), ## Really?
- (Allow, Everyone, ('add')),
- (Allow, 'group:users', ('add', 'edit')),
- (Allow, 'group:managers', ('add', 'edit', 'delete')),
+ (Allow, Everyone, 'view'), # Really?
+ (Allow, 'group:managers', 'edit'),
]
- __parent__ = __collection__ = 'users'
- __schema__ = UserSchema
- button_name = 'Create User'
+
+ collection = 'users'
+ schema = UserSchema
def __init__(self, request):
super(User, self).__init__(request)
self.logged_in = authenticated_userid(request)
self._id = request.matchdict.get('slug')
+
if self._id == self.logged_in:
if (Allow, self._id, ('edit', 'delete')) not in self.__acl__:
self.__acl__.append((Allow, self._id, ('edit', 'delete')))
+
if self._id != self.logged_in:
if (Allow, self.logged_in, ('edit', 'delete')) in self.__acl__:
self.__acl__.remove((Allow, self.logged_in, ('edit', 'delete')))
View
301 lumin/node.py
@@ -16,32 +16,105 @@
from lumin.util import normalize
-class RootFactory(object):
- __acl__ = [ (Allow, Everyone, 'view'),]
+class Factory(object):
+ """Pyramid context factory base class."""
+
+ __acl__ = [
+ (Allow, Everyone, 'view'),
+ ]
+
__name__ = __parent__ = None
- __collection__ = None
- def __init__(self, request, collection=None):
- self.db = request.db
- self.fs = request.fs
- if request.get('mc', None):
- self.mc = request.mc
+ def __init__(self, request):
+ self.request = request
+
+
+class Collection(Factory):
+ """Represents a collection context."""
+
+ # Database collection name
+ collection = None
+
+ def __init__(self, request):
+ super(Collection, self).__init__(request)
+ self._collection = request.db[self.collection]
+
+ def find(self, **kwargs):
+ return self._collection.find(**kwargs)
+
+ def insert(self, doc, title_or_id, increment=True, seperator=u'-'):
+ """
+ Insert ``doc`` into the :term:`collection`.
+
+ :param doc: A dictionary to be stored in the DB
+ :param title_or_id: a string to be normalized for a URL and used as the _id for the document.
+ :param increment: Whether to increment ``title_or_id`` if it already exists in the DB. **Default: ``True``**
+ :param seperator: carachter to separate ``title_or_id`` incremental id. **Default: ``u"-"``**
+ """
+
+ ctime = mtime = datetime.datetime.utcnow().strftime(TS_FORMAT)
+ doc['ctime'] = ctime
+ doc['mtime'] = mtime
+ doc['_id'] = normalize(title_or_id)
+
+ if increment:
+ suffix = 0
+ _id = doc['_id']
+ while True:
+ try:
+ oid = self._collection.insert(doc, safe=True)
+ break
+ except DuplicateKeyError:
+ suffix += 1
+ _id_suffixed = u','.join([_id, unicode(suffix)])
+ doc['_id'] = _id_suffixed
+ else:
+ oid = self._collection.insert(doc, safe=True)
+
+ return oid
-class ContextById(RootFactory):
+ def update(self):
+ """
+ Update the item this ``context`` represents in its
+ :term:`collection`
+ """
+ self.data['mtime'] = datetime.datetime.utcnow().strftime(TS_FORMAT)
- __acl__ = [] ## this should become _default__acl__
+ result = self._collection.update(
+ {"_id": self.data["_id"]},
+ self.data,
+ manipulate=True,
+ safe=True
+ )
+
+ return result
+
+ def delete(self, safe=False):
+ """
+ Remove the entry represented by this ``context`` from this
+ :term:`collection`
+ """
+ result = self._collection.remove(self.data["_id"], safe=safe)
+ if safe and result['err']:
+ raise result['err']
+
+
+class ContextById(Collection):
+ __acl__ = [] # this should become _default__acl__
#: the collection name we will use in the DB
- __collection__ = None #'root'
- __name__ = __parent__ = None
+ __collection__ = None
+
+ __name__ = __parent__ = None
__schema__ = colander.Schema
+
button_name = "Submit"
def __init__(self, request, _id=None):
super(ContextById, self).__init__(request)
self.request = request
self.environ = request.environ
- self.data={}
+ self.data = {}
## These next two can prolly use the setters below, maybe...
## but this way you can set it as a class variable and then
## override it live with another coll/schema and then get the
@@ -50,12 +123,11 @@ def __init__(self, request, _id=None):
## dilemma. Use a non-validating (all colander.null) schema
## while filling shit out then self.schema = ValidatingSchema
## when finalizing and submitting.
- self._collection = self.db[self.__collection__]
self._schema = self.__schema__().bind(request=self.request)
self._id = _id if _id else request.matchdict.get('slug')
if self._id:
- cursor = self.collection.find(
- {'_id' : self._id}
+ cursor = self._collection.find(
+ {'_id': self._id}
)
try:
assert cursor.count() < 2
@@ -69,51 +141,6 @@ def __init__(self, request, _id=None):
def __name__(self):
return self._id
- @property
- def collection(self):
- """
- returns the :term:`context` factory's :term:`collection` name
- """
- return self._collection
-
- @collection.setter
- def collection(self, coll):
- """
- sets the context factory's collection
-
- :param coll: The :term:`collection` name as ``unicode``, ``str``
- """
- if not isinstance(coll, (unicode, str)):
- raise TypeError("{} is not unicode, str")
- self._collection = self.db[coll]
-
- @property
- def schema(self):
- """
- returns the context factory's schema
- """
- return self._schema
-
- @schema.setter
- def schema(self, schema, bind=True):
- """
- sets the context factory's schema
-
- :param schema: an instance of ``colander.MappingSchema``
- :param bind: whether the request should be bound to the
- schema, defaults to True. This is necessary for
- colander.deferred to work with the db which is attached to the
- request.
- """
- if not issubclass(schema, colander.Schema):
- raise TypeError("{} is not a colander.MappingSchema")
- if bind:
- self._schema = schema().bind(request=self.request)
- else:
- self._schema = schema()
-
-
-
def add_form(self):
"""
:rtype: a tuple consisting of the form and and required static resources
@@ -127,7 +154,7 @@ def add_form(self):
title = self.button_name
),
cancel)
- form = deform.Form(self.schema, buttons=buttons)
+ form = deform.Form(self._schema, buttons=buttons)
resources = form.get_widget_resources()
return (form, resources)
@@ -143,67 +170,12 @@ def edit_form(self):
title = "Update"
),
cancel)
- form = deform.Form(self.schema, buttons=buttons)
+ form = deform.Form(self._schema, buttons=buttons)
resources = form.get_widget_resources()
return (form, resources)
- def insert(self,
- doc,
- title_or_id,
- increment=True,
- seperator=u'-'):
- """
- Insert the item this ``context`` represents into the
- :term:`collection`.
-
- :param doc: A dictionary to be stored in the DB
- :param title_or_id: a string to be normalized for a URL and used as the _id for the document.
- :param increment: Whether to increment ``title_or_id`` if it already exists in the DB. **Default: ``True``**
- :param seperator: carachter to separate ``title_or_id`` incremental id. **Default: ``u"-"``**
- """
- ctime = mtime = datetime.datetime.utcnow().strftime(TS_FORMAT)
- doc['ctime'] = ctime
- doc['mtime'] = mtime
- doc['_id'] = normalize(title_or_id)
- if increment:
- suffix=0
- _id = doc['_id']
- while True:
- try:
- oid=self.collection.insert(doc, safe=True)
- break
- except DuplicateKeyError as e:
- suffix+=1
- _id_suffixed = u','.join([_id, unicode(suffix)])
- doc['_id'] = _id_suffixed
- else:
- oid = self.collection.insert(doc, safe=True)
- return oid
-
- def update(self):
- """
- Update the item this ``context`` represents in its
- :term:`collection`
- """
- self.data['mtime'] = datetime.datetime.utcnow().strftime(TS_FORMAT)
- result = self.collection.update({"_id" : self.data["_id"] },
- self.data,
- manipulate=True,
- safe=True)
- return result
-
- def delete(self, safe=False):
- """
- Remove the entry represented by this ``context`` from this
- :term:`collection`
- """
- result = self.collection.remove(self.data["_id"],
- safe=safe)
- if safe and result['err']:
- raise result['err']
-
-class ContextBySpec(RootFactory):
+class ContextBySpec(Collection):
"""
Like ContextById but takes a *spec*ifying dictionary instead.
@@ -225,14 +197,13 @@ def __init__(self, request, spec=None, unique=True):
self.environ = request.environ
self.spec = spec
self.unique = unique
- self.data={}
- self._collection = self.db[self.__collection__]
+ self.data = {}
self._schema = self.__schema__().bind(request=self.request)
for item in self._default__acl__:
if item not in self.__acl__:
self.__acl__.append(item)
if self.spec:
- cursor = self.collection.find(spec)
+ cursor = self._collection.find(spec)
if self.unique:
try:
assert cursor.count() < 2
@@ -252,51 +223,6 @@ def __name__(self):
## this is probably wrong, but maybe not need to think.
return self._id
- @property
- def collection(self):
- """
- returns the :term:`context` factory's :term:`collection` name
- """
- return self._collection
-
- @collection.setter
- def collection(self, coll):
- """
- sets the context factory's collection
-
- :param coll: The :term:`collection` name as ``unicode``, ``str``
- """
- if not isinstance(coll, (unicode, str)):
- raise TypeError("{} is not unicode, str")
- self._collection = self.db[coll]
-
- @property
- def schema(self):
- """
- returns the context factory's schema
- """
- return self._schema
-
- @schema.setter
- def schema(self, schema, bind=True):
- """
- sets the context factory's schema
-
- :param schema: an instance of ``colander.MappingSchema``
- :param bind: whether the request should be bound to the
- schema, defaults to True. This is necessary for
- colander.deferred to work with the db which is attached to the
- request.
- """
- if not issubclass(schema, colander.Schema):
- raise TypeError("{} is not a colander.MappingSchema")
- if bind:
- self._schema = schema().bind(request=self.request)
- else:
- self._schema = schema()
-
-
-
def add_form(self):
"""
:rtype: a tuple consisting of the form and and required static resources
@@ -310,7 +236,7 @@ def add_form(self):
title = self.button_name
),
cancel)
- form = deform.Form(self.schema, buttons=buttons)
+ form = deform.Form(self._schema, buttons=buttons)
resources = form.get_widget_resources()
return (form, resources)
@@ -326,43 +252,6 @@ def edit_form(self):
title = "Update"
),
cancel)
- form = deform.Form(self.schema, buttons=buttons)
+ form = deform.Form(self._schema, buttons=buttons)
resources = form.get_widget_resources()
return (form, resources)
-
-
- def insert(self, doc):
- """
- Insert the item this ``context`` represents into the
- :term:`collection`. It generates the _id since we don't want
- to ask for docs by this attribute. The OID is returned.
-
- :param doc: A dictionary to be stored in the DB
- """
- ctime = mtime = datetime.datetime.utcnow().strftime(TS_FORMAT)
- doc['ctime'] = ctime
- doc['mtime'] = mtime
- oid = self.collection.insert(doc, safe=True)
- return oid
-
- def update(self):
- """
- Update the item this ``context`` represents in its
- :term:`collection`
- """
- self.data['mtime'] = datetime.datetime.utcnow().strftime(TS_FORMAT)
- oid = self.collection.update({"_id" : self.data["_id"] },
- self.data,
- manipulate=True,
- safe=True)
- return oid
-
- def delete(self, safe=False):
- """
- Remove the entry represented by this ``context`` from this
- :term:`collection`
- """
- result = self.collection.remove(self.data["_id"],
- safe=safe)
- if safe and result['err']:
- raise result['err']
View
122 lumin/schema.py
@@ -0,0 +1,122 @@
+import colander
+from colander import Float
+from colander import SchemaNode
+from colander import String
+
+import deform
+
+
+@colander.deferred
+def deferred_username_validator(node, kw):
+ request = kw['request']
+
+ def validate_username(node, value):
+ if len(value) < 4 or len(value) > 24:
+ raise colander.Invalid(node,
+ "Length of user name must be between 4 and \
+ 24 lowercase alphanumeric characters")
+ if not value.replace('_', '').isalnum() or not value.islower():
+ raise colander.Invalid(node,
+ "Only lowercase numbers, letters and \
+ underscores are permitted")
+ if not value[0].isalpha():
+ raise colander.Invalid(node,
+ "The username must start with a \
+ letter")
+
+ query = request.context.find(_id=value)
+
+ if query.count() > 0:
+ raise colander.Invalid(node, "Username is not available")
+
+ return validate_username
+
+
+email_widget = deform.widget.CheckedInputWidget(
+ subject="Email",
+ confirm_subject="Confirm Email",
+ size=40
+ )
+
+
+class EmailSchema(colander.Schema):
+ email = SchemaNode(String(),
+ title="email",
+ description='Type your email address and confirm it',
+ validator=colander.Email(),
+ widget=email_widget)
+
+
+class PasswordSchema(colander.Schema):
+ password = SchemaNode(String(),
+ validator=colander.Length(min=6),
+ widget=deform.widget.CheckedPasswordWidget(size=40),
+ description="Type your password and confirm it")
+
+
+class UserSchema(colander.MappingSchema):
+ _id = SchemaNode(String(),
+ title="Username",
+ description="The name of the participant",
+ validator=deferred_username_validator)
+ given_name = SchemaNode(String(), missing='',
+ title="Given Name")
+ surname = SchemaNode(String(), missing='',
+ title="Surname")
+ street_address = SchemaNode(
+ String(), missing='',
+ title="Street Address",
+ description='Address info (number, street, unit)')
+ locality = SchemaNode(String(), missing='',
+ title='City',
+ description="City or township name")
+ ## TODO: There must be an ISO list for this
+ region = SchemaNode(String(), missing='',
+ title='Locality',
+ description='State, Province, Township or equivalent')
+ postal_code = SchemaNode(String(), missing='',
+ title='Postal Code',
+ description='ZIP or postal code')
+ ## TODO: make this oneOf ISO countries
+ country_name = SchemaNode(String(), missing='',
+ title='Country',
+ description='Country')
+ telephone = SchemaNode(String(), missing='',
+ title='Telephone Number')
+ fax = SchemaNode(String(), missing='',
+ title='Fax number')
+ website_url = SchemaNode(String(), missing='',
+ title='Website URL',
+ description='I.e. http://example.com')
+ latitude = SchemaNode(Float(), missing=colander.null,
+ title='Latitude')
+ longitude = SchemaNode(Float(), missing=colander.null,
+ title='Longitude')
+ email = SchemaNode(String(),
+ title="email",
+ description='Type your email address and confirm it',
+ validator=colander.Email(),
+ widget=email_widget)
+ password = SchemaNode(String(),
+ validator=colander.Length(min=6),
+ widget=deform.widget.CheckedPasswordWidget(size=40),
+ description="Type your password and confirm it")
+
+
+class SimpleUserSchema(colander.MappingSchema):
+ _id = SchemaNode(String(),
+ title="Username",
+ description="The name of the participant",
+ validator=deferred_username_validator)
+ display_name = SchemaNode(String(), missing=colander.null,
+ title="Display Name",
+ widget=deform.widget.TextInputWidget(size=40))
+ email = SchemaNode(String(),
+ title="email",
+ description='Type your email address and confirm it',
+ validator=colander.Email(),
+ widget=email_widget)
+ password = SchemaNode(String(),
+ validator=colander.Length(min=6),
+ widget=deform.widget.CheckedPasswordWidget(size=40),
+ description="Type your password and confirm it")
Please sign in to comment.
Something went wrong with that request. Please try again.