"""A custom list that manages index/position information for its children.
-``orderinglist`` is a custom list collection implementation for mapped relations
-that keeps an arbitrary "position" attribute on contained objects in sync with
-each object's position in the Python list.
+``orderinglist`` is a custom list collection implementation for mapped
+relations that keeps an arbitrary "position" attribute on contained objects in
+sync with each object's position in the Python list.
The collection acts just like a normal Python ``list``, with the added
behavior that as you manipulate the list (via ``insert``, ``pop``, assignment,
to reflect its position. This is very useful for managing ordered relations
which have a user-defined, serialized order::
- from sqlalchemy.ext.orderinglist import ordering_list
-
- users = Table('users', metadata,
- Column('id', Integer, primary_key=True))
- blurbs = Table('user_top_ten_list', metadata,
- Column('id', Integer, primary_key=True),
- Column('user_id', Integer, ForeignKey('users.id')),
- Column('position', Integer),
- Column('blurb', String(80)))
-
- class User(object): pass
- class Blurb(object):
- def __init__(self, blurb):
- self.blurb = blurb
-
- mapper(User, users, properties={
- 'topten': relation(Blurb, collection_class=ordering_list('position'),
- order_by=[blurbs.c.position])
- })
- mapper(Blurb, blurbs)
-
- u = User()
- u.topten.append(Blurb('Number one!'))
- u.topten.append(Blurb('Number two!'))
-
- # Like magic.
- assert [blurb.position for blurb in u.topten] == [0, 1]
-
- # The objects will be renumbered automaticaly after any list-changing
- # operation, for example an insert:
- u.topten.insert(1, Blurb('I am the new Number Two.'))
-
- assert [blurb.position for blurb in u.topten] == [0, 1, 2]
- assert u.topten[1].blurb == 'I am the new Number Two.'
- assert u.topten[1].position == 1
+ >>> from sqlalchemy import MetaData, Table, Column, Integer, String, ForeignKey
+ >>> from sqlalchemy.orm import mapper, relation
+ >>> from sqlalchemy.ext.orderinglist import ordering_list
+
+A simple model of users their "top 10" things::
+
+ >>> metadata = MetaData()
+ >>> users = Table('users', metadata,
+ ... Column('id', Integer, primary_key=True))
+ >>> blurbs = Table('user_top_ten_list', metadata,
+ ... Column('id', Integer, primary_key=True),
+ ... Column('user_id', Integer, ForeignKey('users.id')),
+ ... Column('position', Integer),
+ ... Column('blurb', String(80)))
+ >>> class User(object):
+ ... pass
+ ...
+ >>> class Blurb(object):
+ ... def __init__(self, blurb):
+ ... self.blurb = blurb
+ ...
+ >>> mapper(User, users, properties={
+ ... 'topten': relation(Blurb, collection_class=ordering_list('position'),
+ ... order_by=[blurbs.c.position])})
+ <Mapper ...>
+ >>> mapper(Blurb, blurbs)
+ <Mapper ...>
+
+Acts just like a regular list::
+
+ >>> u = User()
+ >>> u.topten.append(Blurb('Number one!'))
+ >>> u.topten.append(Blurb('Number two!'))
+
+But the ``.position`` attibute is set automatically behind the scenes::
+
+ >>> assert [blurb.position for blurb in u.topten] == [0, 1]
+
+The objects will be renumbered automaticaly after any list-changing operation,
+for example an ``insert()``::
+
+ >>> u.topten.insert(1, Blurb('I am the new Number Two.'))
+ >>> assert [blurb.position for blurb in u.topten] == [0, 1, 2]
+ >>> assert u.topten[1].blurb == 'I am the new Number Two.'
+ >>> assert u.topten[1].position == 1
Numbering and serialization are both highly configurable. See the docstrings
in this module and the main SQLAlchemy documentation for more information and
examples.
-The [sqlalchemy.ext.orderinglist#ordering_list] function is the ORM-compatible
-constructor for OrderingList instances.
+The [sqlalchemy.ext.orderinglist#ordering_list] factory function is the
+ORM-compatible constructor for `OrderingList` instances.
+
"""
+from sqlalchemy.orm.collections import collection
__all__ = [ 'ordering_list' ]
"""A custom list that manages position information for its children.
See the module and __init__ documentation for more details. The
- ``ordering_list`` function is used to configure ``OrderingList``
+ ``ordering_list`` factory function is used to configure ``OrderingList``
collections in ``mapper`` relation definitions.
+
"""
def __init__(self, ordering_attr=None, ordering_func=None,
so be **sure** to put an ``order_by`` on your relation.
ordering_attr
- Name of the attribute that stores the object's order in the relation.
+ Name of the attribute that stores the object's order in the
+ relation.
ordering_func
Optional. A function that maps the position in the Python list to a
- value to store in the ``ordering_attr``. Values returned are usually
- (but need not be!) integers.
+ value to store in the ``ordering_attr``. Values returned are
+ usually (but need not be!) integers.
An ``ordering_func`` is called with two positional parameters: the
index of the element in the list, and the list itself.
concurrent modification error. Spooky action at a distance.
Recommend leaving this with the default of False, and just call
- ``_reorder()`` if you're doing ``append()`` operations with
+ ``reorder()`` if you're doing ``append()`` operations with
previously ordered instances or when doing some housekeeping after
manual sql operations.
- """
+ """
self.ordering_attr = ordering_attr
if ordering_func is None:
ordering_func = count_from_0
def _set_order_value(self, entity, value):
setattr(entity, self.ordering_attr, value)
- def _reorder(self):
- """Sweep through the list and ensure that each object has accurate
- ordering information set."""
+ def reorder(self):
+ """Synchronize ordering for the entire collection.
+ Sweeps through the list and ensures that each object has accurate
+ ordering information set.
+
+ """
for index, entity in enumerate(self):
self._order_entity(index, entity, True)
+ # As of 0.5, _reorder is no longer semi-private
+ _reorder = reorder
+
def _order_entity(self, index, entity, reorder=True):
have = self._get_order_value(entity)
super(OrderingList, self).append(entity)
self._order_entity(len(self) - 1, entity, self.reorder_on_append)
+ @collection.adds(1)
def _raw_append(self, entity):
"""Append without any ordering behavior."""
def __delslice__(self, start, end):
super(OrderingList, self).__delslice__(start, end)
self._reorder()
+
+ for func_name, func in locals().items():
+ if (callable(func) and func.func_name == func_name and
+ not func.__doc__ and hasattr(list, func_name)):
+ func.__doc__ = getattr(list, func_name).__doc__
+ del func_name, func
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod(optionflags=doctest.ELLIPSIS)
+