Zine

open source content publishing system


Changeset 1070:045c95f337bb


Ignore:
Timestamp:
06/01/09 14:13:11 (3 years ago)
Author:
mitsuhiko
Branch:
default
Message:

Switched to splitted tables for comments, posts and texts.

Files:
4 edited

Legend:

Unmodified
Added
Removed
  • sql/001-add-comment-count-column.sql

    r1063 r1070  
    22 
    33alter table posts add column comment_count integer after comments_enabled not null; 
     4 
     5-- We're missing a migration that splits texts and posts into the old 
     6-- table contents and the new "texts" one.  Migrations should follow 
     7-- soon.  Last revision with old tables is 198cd0bdbc05. 
  • zine/database.py

    r1069 r1070  
    5353 
    5454 
    55 def create_engine(uri, relative_to=None, echo=False): 
     55def create_engine(uri, relative_to=None, echo=True): 
    5656    """Create a new engine.  This works a bit like SQLAlchemy's 
    5757    `create_engine` with the difference that it automaticaly set's MySQL 
     
    252252) 
    253253 
     254texts = db.Table('texts', metadata, 
     255    db.Column('text_id', db.Integer, primary_key=True), 
     256    db.Column('text', db.Text), 
     257    db.Column('parser_data', db.ZEMLParserData), 
     258    db.Column('extra', db.PickleType) 
     259) 
     260 
    254261posts = db.Table('posts', metadata, 
    255262    db.Column('post_id', db.Integer, primary_key=True), 
     
    259266    db.Column('uid', db.String(250)), 
    260267    db.Column('title', db.String(150)), 
    261     db.Column('text', db.Text), 
     268    db.Column('text_id', db.Integer, db.ForeignKey('texts.text_id')), 
    262269    db.Column('author_id', db.Integer, db.ForeignKey('users.user_id')), 
    263     db.Column('parser_data', db.ZEMLParserData), 
    264270    db.Column('comments_enabled', db.Boolean), 
    265271    db.Column('comment_count', db.Integer, nullable=False, default=0), 
    266272    db.Column('pings_enabled', db.Boolean), 
    267273    db.Column('content_type', db.String(40), index=True), 
    268     db.Column('extra', db.PickleType), 
    269     db.Column('status', db.Integer) 
     274    db.Column('status', db.Integer), 
    270275) 
    271276 
     
    304309    db.Column('email', db.String(250)), 
    305310    db.Column('www', db.String(200)), 
    306     db.Column('text', db.Text), 
     311    db.Column('text_id', db.Integer, db.ForeignKey('texts.text_id')), 
    307312    db.Column('is_pingback', db.Boolean, nullable=False), 
    308     db.Column('parser_data', db.ZEMLParserData), 
    309313    db.Column('parent_id', db.Integer, db.ForeignKey('comments.comment_id')), 
    310314    db.Column('pub_date', db.DateTime), 
  • zine/models.py

    r1068 r1070  
    1717from zine.database import users, categories, posts, post_links, \ 
    1818     post_categories, post_tags, tags, comments, groups, group_users, \ 
    19      privileges, user_privileges, group_privileges, db 
     19     privileges, user_privileges, group_privileges, texts, db 
    2020from zine.utils import zeml 
    2121from zine.utils.text import gen_slug, gen_timestamped_slug, build_tag_uri, \ 
     
    303303 
    304304 
    305 class PostQuery(db.Query): 
     305class _PostQueryBase(db.Query): 
    306306    """Add some extra methods to the post model.""" 
    307307 
     
    312312        args = map(db.lazyload, lazy or ()) + map(db.defer, deferred or ()) 
    313313        return self.options(*args) 
    314  
    315     def summary_lightweight(self): 
    316         """A query with lightweight settings for summaries.  (Like widgets 
    317         etc.)  Does not load text or comments or anything related. 
    318         """ 
    319         return self.lightweight(lazy=('comments', 'categories', 'tags'), 
    320                                 deferred=('parser_data', 'text', 'extra')) 
    321  
    322     def theme_lightweight(self, key): 
    323         """A query for lightweight settings based on the theme.  For example 
    324         to use the lightweight settings for the author overview page you can 
    325         use this query:: 
    326  
    327             Post.query.theme_lightweight('author_overview') 
    328         """ 
    329         theme_settings = get_application().theme.settings 
    330         deferred = theme_settings.get('sql.%s.deferred' % key) 
    331         lazy = theme_settings.get('sql.%s.lazy' % key) 
    332         return self.lightweight(deferred, lazy) 
    333314 
    334315    def type(self, content_type): 
     
    513494 
    514495 
    515 class Post(_ZEMLDualContainer): 
     496class _PostBase(object): 
    516497    """Represents one blog post.""" 
     498 
     499    @property 
     500    def _privileges(self): 
     501        return get_application().content_type_privileges[self.content_type] 
     502 
     503    @property 
     504    def EDIT_OWN_PRIVILEGE(self): 
     505        """The edit-own privilege for this content type.""" 
     506        return self._privileges[1] 
     507 
     508    @property 
     509    def EDIT_OTHER_PRIVILEGE(self): 
     510        """The edit-other privilege for this content type.""" 
     511        return self._privileges[2] 
     512 
     513    @property 
     514    def root_comments(self): 
     515        """Return only the comments for this post that don't have a parent.""" 
     516        return [x for x in self.comments if x.parent is None] 
     517 
     518    @property 
     519    def visible_comments(self): 
     520        """Return only the comments for this post that are visible to 
     521        the user. 
     522        """ 
     523        return [x for x in self.comments if x.visible] 
     524 
     525    @property 
     526    def visible_root_comments(self): 
     527        """Return only the comments for this post that are visible to 
     528        the user and that don't have a parent. 
     529        """ 
     530        return [x for x in self.comments if x.visible and x.parent is None] 
     531 
     532    @property 
     533    def comment_count(self): 
     534        """The number of visible comments.""" 
     535        req = get_request() 
     536 
     537        # if the model was loaded with .lightweight() there are no comments 
     538        # but a _comment_count we can use. 
     539        if not db.attribute_loaded(self, 'comments'): 
     540            return self._comment_count 
     541 
     542        # otherwise the comments are already available and we can savely 
     543        # filter it. 
     544        if req and req.user.is_manager: 
     545            return len(self.comments) 
     546        return len([x for x in self.comments if not x.blocked]) 
     547 
     548    @property 
     549    def comment_feed_url(self): 
     550        """The link to the comment feed.""" 
     551        return make_external_url(self.slug.rstrip('/') + '/feed.atom') 
     552 
     553    @property 
     554    def is_draft(self): 
     555        """True if this post is unpublished.""" 
     556        return self.status == STATUS_DRAFT 
     557 
     558    def sync_comment_count(self): 
     559        """Sync the reflected comment count.""" 
     560        self._comment_count = self.query \ 
     561            .published(ignore_privileges=True).count() 
     562 
     563    def set_auto_slug(self): 
     564        """Generate a slug for this post.""" 
     565        cfg = get_application().cfg 
     566        slug = gen_slug(self.title) 
     567        if not slug: 
     568            slug = to_blog_timezone(self.pub_date).strftime('%H%M') 
     569 
     570        full_slug = gen_timestamped_slug(slug, self.content_type, self.pub_date) 
     571 
     572        if full_slug != self.slug: 
     573            while Post.query.autoflush(False).filter_by(slug=full_slug) \ 
     574                      .limit(1).count(): 
     575                full_slug = increment_string(full_slug) 
     576            self.slug = full_slug 
     577 
     578    def touch_times(self, pub_date=None): 
     579        """Touches the times for this post.  If the pub_date is given the 
     580        `pub_date` is changed to the given date.  If it's not given the 
     581        current time is assumed if the post status is set to published, 
     582        otherwise it's set to `None`. 
     583 
     584        Additionally the `last_update` is always set to now. 
     585        """ 
     586        now = datetime.utcnow() 
     587        if pub_date is None and self.status == STATUS_PUBLISHED: 
     588            pub_date = now 
     589        self.pub_date = pub_date 
     590        self.last_update = now 
     591 
     592    def bind_slug(self, slug=None): 
     593        """Binds a new slug to the post.  If the slug is `None`/empty a new 
     594        automatically generated slug is created.  Otherwise that slug is 
     595        used and assigned. 
     596        """ 
     597        if not slug: 
     598            self.set_auto_slug() 
     599        else: 
     600            self.slug = slug 
     601 
     602    def bind_tags(self, tags): 
     603        """Rebinds the tags to a list of tags (strings, not tag objects).""" 
     604        current_map = dict((x.name, x) for x in self.tags) 
     605        currently_attached = set(x.name for x in self.tags) 
     606        new_tags = set(tags) 
     607 
     608        # delete outdated tags 
     609        for name in currently_attached.difference(new_tags): 
     610            self.tags.remove(current_map[name]) 
     611 
     612        # add new tags 
     613        for name in new_tags.difference(currently_attached): 
     614            self.tags.append(Tag.get_or_create(name)) 
     615 
     616    def bind_categories(self, categories): 
     617        """Rebinds the categories to the list passed.  The list of objects 
     618        must be a list of category objects. 
     619        """ 
     620        currently_attached = set(self.categories) 
     621        new_categories = set(categories) 
     622 
     623        # delete outdated categories 
     624        for category in currently_attached.difference(new_categories): 
     625            self.categories.remove(category) 
     626 
     627        # attach new categories 
     628        for category in new_categories.difference(currently_attached): 
     629            self.categories.append(category) 
     630 
     631    def can_edit(self, user=None): 
     632        """Checks if the given user (or current user) can edit this post.""" 
     633        if user is None: 
     634            user = get_request().user 
     635 
     636        return ( 
     637            user.has_privilege(self.EDIT_OTHER_PRIVILEGE) or 
     638            (self.author == user and 
     639             user.has_privilege(self.EDIT_OWN_PRIVILEGE)) 
     640        ) 
     641 
     642    def can_read(self, user=None): 
     643        """Check if the current user or the user provided can read-access 
     644        this post. If there is no user there must be a request object 
     645        for this thread defined. 
     646        """ 
     647        # published posts are always accessible 
     648        if self.status == STATUS_PUBLISHED and self.pub_date is not None and \ 
     649           self.pub_date <= datetime.utcnow(): 
     650            return True 
     651 
     652        if user is None: 
     653            user = get_request().user 
     654 
     655        # users that are allowed to look at drafts may pass 
     656        if user.has_privilege(VIEW_DRAFTS): 
     657            return True 
     658 
     659        # if this is protected and user can view protected, allow them 
     660        if self.status == STATUS_PROTECTED and self.pub_date is not None and \ 
     661            self.pub_date <= datetime.utcnow() and \ 
     662            user.has_privilege(VIEW_PROTECTED): 
     663             return True 
     664 
     665        # if we have the privilege to edit other entries or if we are 
     666        # a blog administrator we can always look at posts. 
     667        if user.has_privilege(self.EDIT_OTHER_PRIVILEGE): 
     668            return True 
     669 
     670        # otherwise if the user has the EDIT_OWN_PRIVILEGE and the 
     671        # author of the post, he may look at it as well 
     672        if user.id == self.author_id and \ 
     673           user.has_privilege(self.EDIT_OWN_PRIVILEGE): 
     674            return True 
     675 
     676        return False 
     677 
     678    @property 
     679    def is_published(self): 
     680        """`True` if the post is visible for everyone.""" 
     681        return self.can_read(AnonymousUser()) 
     682 
     683    @property 
     684    def is_private(self): 
     685        """`True` if the post is marked private.""" 
     686        return self.status == STATUS_PRIVATE 
     687 
     688    @property 
     689    def is_protected(self): 
     690        """`True` if the post is marked protected.""" 
     691        return self.status == STATUS_PROTECTED 
     692 
     693    @property 
     694    def is_scheduled(self): 
     695        """True if the item is scheduled for appearing.""" 
     696        return self.status == STATUS_PUBLISHED and \ 
     697               self.pub_date > datetime.utcnow() 
     698 
     699    def get_url_values(self): 
     700        return self.slug 
     701 
     702    def __repr__(self): 
     703        return '<%s %r>' % ( 
     704            self.__class__.__name__, 
     705            self.title 
     706        ) 
     707 
     708 
     709class PostQuery(_PostQueryBase): 
     710    """Add some extra methods to the post model.""" 
     711 
     712    def theme_lightweight(self, key): 
     713        """A query for lightweight settings based on the theme.  For example 
     714        to use the lightweight settings for the author overview page you can 
     715        use this query:: 
     716 
     717            Post.query.theme_lightweight('author_overview') 
     718        """ 
     719        theme_settings = get_application().theme.settings 
     720        deferred = theme_settings.get('sql.%s.deferred' % key) 
     721        lazy = theme_settings.get('sql.%s.lazy' % key) 
     722        return self.lightweight(deferred, lazy) 
     723 
     724 
     725class SummarizedPostQuery(_PostQueryBase): 
     726    """Add some extra methods to the summarized post model.""" 
     727 
     728 
     729class Post(_PostBase, _ZEMLDualContainer): 
     730    """A full blown post.""" 
    517731 
    518732    query = db.query_property(PostQuery) 
     
    554768        self.uid = uid 
    555769 
    556     @property 
    557     def _privileges(self): 
    558         return get_application().content_type_privileges[self.content_type] 
    559  
    560     @property 
    561     def EDIT_OWN_PRIVILEGE(self): 
    562         """The edit-own privilege for this content type.""" 
    563         return self._privileges[1] 
    564  
    565     @property 
    566     def EDIT_OTHER_PRIVILEGE(self): 
    567         """The edit-other privilege for this content type.""" 
    568         return self._privileges[2] 
    569  
    570     @property 
    571     def root_comments(self): 
    572         """Return only the comments for this post that don't have a parent.""" 
    573         return [x for x in self.comments if x.parent is None] 
    574  
    575     @property 
    576     def visible_comments(self): 
    577         """Return only the comments for this post that are visible to 
    578         the user. 
    579         """ 
    580         return [x for x in self.comments if x.visible] 
    581  
    582     @property 
    583     def visible_root_comments(self): 
    584         """Return only the comments for this post that are visible to 
    585         the user and that don't have a parent. 
    586         """ 
    587         return [x for x in self.comments if x.visible and x.parent is None] 
    588  
    589     @property 
    590     def comment_count(self): 
    591         """The number of visible comments.""" 
    592         req = get_request() 
    593  
    594         # if the model was loaded with .lightweight() there are no comments 
    595         # but a _comment_count we can use. 
    596         if not db.attribute_loaded(self, 'comments'): 
    597             return self._comment_count 
    598  
    599         # otherwise the comments are already available and we can savely 
    600         # filter it. 
    601         if req and req.user.is_manager: 
    602             return len(self.comments) 
    603         return len([x for x in self.comments if not x.blocked]) 
    604  
    605     @property 
    606     def comment_feed_url(self): 
    607         """The link to the comment feed.""" 
    608         return make_external_url(self.slug.rstrip('/') + '/feed.atom') 
    609  
    610     @property 
    611     def is_draft(self): 
    612         """True if this post is unpublished.""" 
    613         return self.status == STATUS_DRAFT 
    614  
    615     def sync_comment_count(self): 
    616         """Sync the reflected comment count.""" 
    617         self._comment_count = self.query \ 
    618             .published(ignore_privileges=True).count() 
    619  
    620     def set_auto_slug(self): 
    621         """Generate a slug for this post.""" 
    622         cfg = get_application().cfg 
    623         slug = gen_slug(self.title) 
    624         if not slug: 
    625             slug = to_blog_timezone(self.pub_date).strftime('%H%M') 
    626  
    627         full_slug = gen_timestamped_slug(slug, self.content_type, self.pub_date) 
    628  
    629         if full_slug != self.slug: 
    630             while Post.query.autoflush(False).filter_by(slug=full_slug) \ 
    631                       .limit(1).count(): 
    632                 full_slug = increment_string(full_slug) 
    633             self.slug = full_slug 
    634  
    635     def touch_times(self, pub_date=None): 
    636         """Touches the times for this post.  If the pub_date is given the 
    637         `pub_date` is changed to the given date.  If it's not given the 
    638         current time is assumed if the post status is set to published, 
    639         otherwise it's set to `None`. 
    640  
    641         Additionally the `last_update` is always set to now. 
    642         """ 
    643         now = datetime.utcnow() 
    644         if pub_date is None and self.status == STATUS_PUBLISHED: 
    645             pub_date = now 
    646         self.pub_date = pub_date 
    647         self.last_update = now 
    648  
    649     def bind_slug(self, slug=None): 
    650         """Binds a new slug to the post.  If the slug is `None`/empty a new 
    651         automatically generated slug is created.  Otherwise that slug is 
    652         used and assigned. 
    653         """ 
    654         if not slug: 
    655             self.set_auto_slug() 
    656         else: 
    657             self.slug = slug 
    658  
    659     def bind_tags(self, tags): 
    660         """Rebinds the tags to a list of tags (strings, not tag objects).""" 
    661         current_map = dict((x.name, x) for x in self.tags) 
    662         currently_attached = set(x.name for x in self.tags) 
    663         new_tags = set(tags) 
    664  
    665         # delete outdated tags 
    666         for name in currently_attached.difference(new_tags): 
    667             self.tags.remove(current_map[name]) 
    668  
    669         # add new tags 
    670         for name in new_tags.difference(currently_attached): 
    671             self.tags.append(Tag.get_or_create(name)) 
    672  
    673     def bind_categories(self, categories): 
    674         """Rebinds the categories to the list passed.  The list of objects 
    675         must be a list of category objects. 
    676         """ 
    677         currently_attached = set(self.categories) 
    678         new_categories = set(categories) 
    679  
    680         # delete outdated categories 
    681         for category in currently_attached.difference(new_categories): 
    682             self.categories.remove(category) 
    683  
    684         # attach new categories 
    685         for category in new_categories.difference(currently_attached): 
    686             self.categories.append(category) 
    687  
    688     def can_edit(self, user=None): 
    689         """Checks if the given user (or current user) can edit this post.""" 
    690         if user is None: 
    691             user = get_request().user 
    692  
    693         return ( 
    694             user.has_privilege(self.EDIT_OTHER_PRIVILEGE) or 
    695             (self.author == user and 
    696              user.has_privilege(self.EDIT_OWN_PRIVILEGE)) 
    697         ) 
    698  
    699     def can_read(self, user=None): 
    700         """Check if the current user or the user provided can read-access 
    701         this post. If there is no user there must be a request object 
    702         for this thread defined. 
    703         """ 
    704         # published posts are always accessible 
    705         if self.status == STATUS_PUBLISHED and self.pub_date is not None and \ 
    706            self.pub_date <= datetime.utcnow(): 
    707             return True 
    708  
    709         if user is None: 
    710             user = get_request().user 
    711  
    712         # users that are allowed to look at drafts may pass 
    713         if user.has_privilege(VIEW_DRAFTS): 
    714             return True 
    715  
    716         # if this is protected and user can view protected, allow them 
    717         if self.status == STATUS_PROTECTED and self.pub_date is not None and \ 
    718             self.pub_date <= datetime.utcnow() and \ 
    719             user.has_privilege(VIEW_PROTECTED): 
    720              return True 
    721  
    722         # if we have the privilege to edit other entries or if we are 
    723         # a blog administrator we can always look at posts. 
    724         if user.has_privilege(self.EDIT_OTHER_PRIVILEGE): 
    725             return True 
    726  
    727         # otherwise if the user has the EDIT_OWN_PRIVILEGE and the 
    728         # author of the post, he may look at it as well 
    729         if user.id == self.author_id and \ 
    730            user.has_privilege(self.EDIT_OWN_PRIVILEGE): 
    731             return True 
    732  
    733         return False 
    734  
    735     @property 
    736     def is_published(self): 
    737         """`True` if the post is visible for everyone.""" 
    738         return self.can_read(AnonymousUser()) 
    739  
    740     @property 
    741     def is_private(self): 
    742         """`True` if the post is marked private.""" 
    743         return self.status == STATUS_PRIVATE 
    744  
    745     @property 
    746     def is_protected(self): 
    747         """`True` if the post is marked protected.""" 
    748         return self.status == STATUS_PROTECTED 
    749  
    750     @property 
    751     def is_scheduled(self): 
    752         """True if the item is scheduled for appearing.""" 
    753         return self.status == STATUS_PUBLISHED and \ 
    754                self.pub_date > datetime.utcnow() 
    755  
    756     def get_url_values(self): 
    757         return self.slug 
    758  
    759     def __repr__(self): 
    760         return '<%s %r>' % ( 
    761             self.__class__.__name__, 
    762             self.title 
    763         ) 
     770 
     771class SummarizedPost(_PostBase): 
     772    """Like a regular post but without text and parser data.""" 
     773 
     774    query = db.query_property(SummarizedPostQuery) 
     775 
     776    def __init__(self): 
     777        raise TypeError('You cannot create %r instance' % type(self).__name__) 
    764778 
    765779 
     
    11811195                                          query_class=PostQuery) 
    11821196}, order_by=categories.c.name) 
    1183 db.mapper(Comment, comments, properties={ 
     1197db.mapper(Comment, db.join(comments, texts), properties={ 
    11841198    'id':           comments.c.comment_id, 
    1185     'text':         db.synonym('_text', map_column=True), 
    1186     'author':       db.synonym('_author', map_column=True), 
    1187     'email':        db.synonym('_email', map_column=True), 
    1188     'www':          db.synonym('_www', map_column=True), 
    1189     'status':       db.synonym('_status', map_column=True), 
     1199    'text_id':      [comments.c.text_id, texts.c.text_id], 
     1200    '_text':        texts.c.text, 
     1201    'text':         db.synonym('_text'), 
     1202    '_author':      comments.c.author, 
     1203    'author':       db.synonym('_author'), 
     1204    '_email':       comments.c.email, 
     1205    'email':        db.synonym('_email'), 
     1206    '_www':         comments.c.www, 
     1207    'www':          db.synonym('_www'), 
     1208    '_status':      comments.c.status, 
     1209    'status':       db.synonym('_status'), 
    11901210    'children':     db.relation(Comment, 
    11911211        primaryjoin=comments.c.parent_id == comments.c.comment_id, 
     
    12041224                                      query_class=PostQuery) 
    12051225}, order_by=tags.c.name) 
    1206 db.mapper(Post, posts, properties={ 
     1226db.mapper(Post, db.join(posts, texts), properties={ 
    12071227    'id':               posts.c.post_id, 
    1208     'text':             db.synonym('_text', map_column=True), 
     1228    'text_id':          [posts.c.text_id, texts.c.text_id], 
     1229    '_text':            texts.c.text, 
     1230    'text':             db.synonym('_text'), 
    12091231    'comments':         db.relation(Comment, backref='post', 
    12101232                                    primaryjoin=posts.c.post_id == 
     
    12201242    'tags':             db.relation(Tag, secondary=post_tags, lazy=False, 
    12211243                                    order_by=[tags.c.name]), 
     1244    '_comment_count':   posts.c.comment_count, 
     1245    'comment_count':    db.synonym('_comment_count') 
     1246}, order_by=posts.c.pub_date.desc()) 
     1247db.mapper(SummarizedPost, posts, properties={ 
     1248    'id':               posts.c.post_id, 
     1249    'comments':         db.relation(Comment, 
     1250                                    primaryjoin=posts.c.post_id == 
     1251                                        comments.c.post_id, 
     1252                                    order_by=[db.asc(comments.c.pub_date)], 
     1253                                    lazy=True, viewonly=True), 
     1254    'links':            db.relation(PostLink, viewonly=True, lazy=True), 
     1255    'categories':       db.relation(Category, secondary=post_categories, lazy=True, 
     1256                                    order_by=[db.asc(categories.c.name)], 
     1257                                    viewonly=True), 
     1258    'tags':             db.relation(Tag, secondary=post_tags, lazy=True, 
     1259                                    viewonly=True, order_by=[tags.c.name]), 
    12221260    'comment_count':    db.synonym('_comment_count', map_column=True) 
    12231261}, order_by=posts.c.pub_date.desc()) 
  • zine/widgets.py

    r1068 r1070  
    1818""" 
    1919from zine.application import render_template 
    20 from zine.models import Post, Category, Tag, Comment 
     20from zine.models import Post, SummarizedPost, Category, Tag, Comment 
    2121 
    2222 
     
    4949 
    5050    def __init__(self, detail='months', limit=6, show_title=False): 
    51         self.__dict__.update(Post.query.summary_lightweight() 
    52                                  .get_archive_summary(detail, limit)) 
     51        self.__dict__.update(SummarizedPost.query 
     52            .get_archive_summary(detail, limit)) 
    5353        self.show_title = show_title 
    5454 
     
    6161 
    6262    def __init__(self, limit=5, show_title=False, content_types=None): 
    63         query = Post.query.summary_lightweight() 
    6463        if content_types is None: 
    65             query = query.for_index() 
     64            query = SummarizedPost.query.for_index() 
    6665        else: 
    67             query = query.filter(Post.content_type.in_(content_types)) 
     66            query = SummarizedPost.query.filter(SummarizedPost 
     67                .content_type.in_(content_types)) 
    6868        self.posts = query.latest().limit(limit).all() 
    6969        self.show_title = show_title 
Note: See TracChangeset for help on using the changeset viewer.