Changeset 533

Show
Ignore:
Timestamp:
08/14/07 15:33:18 (1 year ago)
Author:
brian
Message:

trying out generalized revision maker

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • pagoda/trunk/Pagoda/pagoda/models/revision_mapper.py

    r514 r533  
    99from pagoda.models.template import Template 
    1010 
    11 __all__ = ['revisioned_table', 'RevisionableMapperExtension', 
     11__all__ = ['RevisionSelectable', 'RevisionMapperExtension', 
    1212           'revision_mapper'] 
    1313 
     
    2323    pass 
    2424 
    25 def revisioned_table(name, generic_table, localized_table, locale_column=None): 
    26     """ 
    27     Create an aliased `Select` against which a mapper can be created that 
    28     selects revisions of a content type composed of independently 
    29     revisioned tables, according to the `Pagoda revision model`_. 
    30      
    31     `name` is the alias of the resulting `Select`. 
    32      
    33     `generic_table` is a `Table` or `Selectable` containing the 
    34     locale-independent columns for this content type. 
    35      
    36     `localized_table` is a `Table` or `Selectable` containing the 
    37     locale-dependent columns for this content type. 
    38      
    39     `locale_column` is the name or `Column` from `localized_table` where 
    40     the locale name (e.g. 'en_US') is stored. If None (the default), a column 
    41     named 'content_locale' or 'locale' is assumed (in that order), and if not 
    42     found, ValueError is raised. 
    43      
    44     .. _Pagoda revision model: http://exogen.case.edu/revisions.png 
    45      
    46     """ 
    47     if locale_column is None: 
    48         locale_columns = ['content_locale', 'locale'] 
    49         for col in locale_columns: 
    50             if localized_table.c.has_key(col): 
    51                 locale_column = localized_table.c[col] 
    52                 break 
    53         else: 
    54             raise ValueError( 
    55                 "Locale column not specified or detected (tried %s). " 
    56                 "Indicate the locale column with the `locale_column` " 
    57                 "argument." % ', '.join(locale_columns) 
    58             ) 
    59     if isinstance(locale_column, basestring): 
    60         locale_column = localized_table.c[locale_column] 
    61      
    62     content_table = follow_foreign_key(revision_table.c.content_id).table 
    63      
    64     def revision_subselect(revisioned_table, group_by=None, join_alias=None): 
     25class RevisionSelectable(object): 
     26    def __init__(self, revision_table=revision_table, 
     27                 content_table=content_table): 
     28        self.revision_table = revision_table 
     29        self.content_table = content_table 
     30        self.tables = util.OrderedSet() 
     31        self.id_labels = {} 
     32        self.subselects = {} 
     33     
     34    def revision_subselect(self, table, group_by=None, join_alias=None): 
    6535        """ 
    6636        Generate a `Select` that retrieves matching revisions from 
     
    7848        """ 
    7949        if join_alias is None: 
    80             join_alias = "%s_revision" % revisioned_table.name 
    81          
    82         revision_alias = revision_table.alias(join_alias) 
     50            join_alias = '%s_revision' % table.name 
     51         
     52        revision_alias = self.revision_table.alias(join_alias) 
    8353 
    8454        if group_by is not None: 
     
    8959                    group_by[i] = revision_alias.c[column] 
    9060         
    91         max_revision_id = func.max(revisioned_table.c.revision_id) 
     61        max_revision_id = func.max(table.c.revision_id) 
    9262         
    9363        revisions = select( 
    9464            [max_revision_id], 
    9565            and_( 
    96                 revisioned_table.c.revision_id <= revision_table.c.revision_id, 
    97                 revisioned_table.c.revision_id == revision_alias.c.revision_id, 
    98                 revision_table.c.content_id == revision_alias.c.content_id 
     66                table.c.revision_id <= self.revision_table.c.revision_id, 
     67                table.c.revision_id == revision_alias.c.revision_id, 
     68                self.revision_table.c.content_id == revision_alias.c.content_id 
    9969            ), 
    100             from_obj=[revisioned_table, revision_alias], 
     70            from_obj=[table, revision_alias], 
    10171            group_by=group_by, 
    10272            scalar=True, 
    10373            correlate=False 
    10474        ) 
    105         revisions.correlate(revision_table) 
    106          
     75        revisions.correlate(self.revision_table) 
    10776        return revisions 
    108          
    109     node_revisions = revision_subselect(node_table) 
    110     generic_revisions = revision_subselect(generic_table) 
    111     localized_revisions = revision_subselect(localized_table, 
    112         group_by=locale_column 
    113     ) 
    114      
    115     # XXX: Danger! Alert! Caution! Hey! 
    116     # 
    117     # The order of these tables is important! We want to ensure that the 
    118     # columns from `content_table` and `revision_table` are selected before 
    119     # others! Not only should their names have precedence, but SQLite's 
    120     # query planner actually returns different results if `revision_table` 
    121     # does not come first in the FROM clause. 
    122     # 
    123     select_tables = [content_table, revision_table, node_table, generic_table, 
    124                      localized_table] 
    125      
    126     select_columns = ColumnCollection() 
    127      
    128     for select_table in select_tables: 
    129         # It would be nice to use `select_columns.extend` here. 
    130         # However, `ColumnCollection.extend` and `ColumnCollection.add` allow 
    131         # later additions to overwrite (and change the order of) existing 
    132         # columns with the same name. We want existing columns to take 
    133         # precedence instead. 
    134         for column in select_table.c: 
    135             if not select_columns.has_key(column.name): 
    136                 select_columns.add(column) 
    137      
    138     select_columns.extend([ 
    139         node_table.c.revision_id.label('node_revision_id'), 
    140         generic_table.c.revision_id.label('generic_revision_id'), 
    141         localized_table.c.revision_id.label('localized_revision_id') 
    142     ]) 
    143      
    144     content_revisions = select( 
    145         select_columns, 
    146         and_( 
    147             content_table.c.content_id == revision_table.c.content_id, 
    148             node_table.c.revision_id.in_(node_revisions), 
    149             generic_table.c.revision_id.in_(generic_revisions), 
    150             localized_table.c.revision_id.in_(localized_revisions), 
    151             # Ensure that the `revision_id` from `revision_table` matches 
    152             # the `revision_id` from one of the content tables, to prevent 
    153             # a higher `revision_id` from matching this query but not being 
    154             # used. 
    155             or_( 
    156                 revision_table.c.revision_id == node_table.c.revision_id, 
    157                 revision_table.c.revision_id == generic_table.c.revision_id, 
    158                 revision_table.c.revision_id == localized_table.c.revision_id 
    159             ) 
    160         ), 
    161         correlate=False, 
    162         # Group by the `revision_id` from each content table to prevent 
    163         # duplicates! 
    164         group_by=[node_table.c.revision_id, generic_table.c.revision_id, 
    165                   localized_table.c.revision_id], 
    166         order_by=[revision_table.c.revision_id, node_table.c.revision_id, 
    167                   generic_table.c.revision_id, localized_table.c.revision_id] 
    168     ) 
    169      
    170     return content_revisions.alias(name) 
    171  
    172 class RevisionableMapperExtension(MapperExtension): 
     77 
     78    def add_table(self, table, id_label=None, locale_column=None, 
     79                  join_alias=None): 
     80        if id_label is None: 
     81            id_label = '%s_revision_id' % table.name 
     82 
     83        if isinstance(locale_column, basestring): 
     84            locale_column = table.c[locale_column] 
     85 
     86        subselect = self.revision_subselect(table, locale_column, join_alias) 
     87 
     88        self.tables.add(table) 
     89        self.id_labels[table] = id_label 
     90        self.subselects[table] = subselect 
     91 
     92    def get_selectable(self, alias): 
     93        # XXX: Danger! Alert! Caution! Hey! 
     94        # 
     95        # The order of these tables is important! We want to ensure that the 
     96        # columns from `content_table` and `revision_table` are selected before 
     97        # others! Not only should their names have precedence, but SQLite's 
     98        # query planner actually returns different results if `revision_table` 
     99        # does not come first in the FROM clause. 
     100        # 
     101        select_tables = [self.content_table, self.revision_table] 
     102        select_tables += list(self.tables) 
     103         
     104        select_columns = ColumnCollection() 
     105        for select_table in select_tables: 
     106            # It would be nice to use `select_columns.extend` here. 
     107            # However, `ColumnCollection.extend` and `ColumnCollection.add` allow 
     108            # later additions to overwrite (and change the order of) existing 
     109            # columns with the same name. We want existing columns to take 
     110            # precedence instead. 
     111            for column in select_table.c: 
     112                if not select_columns.has_key(column.name): 
     113                    select_columns.add(column) 
     114         
     115        for table in self.tables: 
     116            id_column = table.c.revision_id 
     117            id_label = self.id_labels[table] 
     118            select_columns.add(id_column.label(id_label)) 
     119         
     120        content_table = self.content_table 
     121        revision_table = self.revision_table 
     122         
     123        in_clauses = [table.c.revision_id.in_(self.subselects[table]) for 
     124                      table in self.tables] 
     125         
     126        id_clauses = [revision_table.c.revision_id == table.c.revision_id for 
     127                      table in self.tables] 
     128 
     129        selectable = select( 
     130            select_columns, 
     131            and_( 
     132                content_table.c.content_id == revision_table.c.content_id, 
     133                # Ensure that the `revision_id` from `revision_table` matches 
     134                # the `revision_id` from one of the content tables, to prevent 
     135                # a higher `revision_id` from matching this query but not being 
     136                # used. 
     137                or_(*id_clauses), 
     138                *in_clauses 
     139            ), 
     140            correlate=False, 
     141            # Group by the `revision_id` from each content table to prevent 
     142            # duplicates! 
     143            group_by=[table.c.revision_id for table in self.tables], 
     144            order_by=[revision_table.c.revision_id] + [ 
     145                table.c.revision_id for table in self.tables 
     146            ] 
     147        ) 
     148        return selectable.alias(alias) 
     149 
     150class RevisionMapperExtension(MapperExtension): 
    173151    """ 
    174152    Extension to modify the behavior of queries performed by a class 
     
    249227    # added by the caller. 
    250228    for extension in extensions: 
    251         if isinstance(ext, RevisionableMapperExtension): 
     229        if isinstance(ext, RevisionMapperExtension): 
    252230            break 
    253231    else: 
    254         extension = RevisionableMapperExtension() 
     232        extension = RevisionMapperExtension() 
    255233        extensions.append(extension) # XXX: Testing! 
    256234    kwargs['extension'] = extensions 
  • pagoda/trunk/Pagoda/pagoda/plugins/page/models.py

    r449 r533  
    22from sqlalchemy.ext.assignmapper import assign_mapper 
    33from turbogears.database import metadata, session 
    4 from pagoda.models import Node, Revision 
     4from pagoda.models import node_table, Node, Revision 
    55from pagoda.models.revision_mapper import * 
    66 
     
    2020) 
    2121 
    22 page_table = revisioned_table( 
    23     'page', page_generic_table, page_localized_table 
    24 
     22page_selectable = RevisionedSelectable() 
     23page_selectable.add_table(node_table) 
     24page_selectable.add_table(page_generic_table, 'generic_revision_id') 
     25page_selectable.add_table(page_localized_table, 'localized_revision_id', 'content_locale') 
     26 
     27page_table = page_selectable.get_selectable('page') 
    2528 
    2629class Page(Node): 

Log in as guest/pagoda to create tickets