Commits

murarth committed 68afb0a

Added Community visibility checks

Comments (0)

Files changed (7)

reddish/lib/base.py

         try:
             off = int(request.GET.get('offset', 0))
             # Completely arbitrary item limit
-            if off > 0 and off < (MAX_ITEMS - c.prefs.n_items):
+            if off > 0 and off <= (MAX_ITEMS - c.prefs.n_items):
                 c.offset = off
         except ValueError:
             pass

reddish/lib/helpers.py

         d.strftime(TIME_FORMAT), s)
 
 def page(results):
-    c.num_items = min(len(results), c.MAX_ITEMS)
+    if results.filtered():
+        c.num_items = -1
+    else:
+        c.num_items = min(len(results), c.MAX_ITEMS)
     if c.offset:
         results.skip(c.offset)
     return results.limit(c.prefs.n_items)

reddish/lib/mongo.py

         self.kws = kws
         kws.setdefault('fields', get_hidden_fields(typ))
         self._cursor = None
+        self.cursor_access = None
         self.data = []
+        self._skip = self._limit = None
+        self.filter_callbacks = []
 
     def __getattr__(self, key):
         value = getattr(self.cursor, key)
         '''
         data = self.data
         n += 1
+
+        if self.cursor_access is None:
+            self._finalize_cursor()
+
         if n > len(data):
             try:
                 for i in xrange(n - len(data)):
-                    data.append(self.create(self.cursor.next()))
+                    data.append(self.cursor_access.next())
             except StopIteration:
                 return False
 
         return True
 
+    def fetch_all(self):
+        if self.cursor_access is None:
+            self._finalize_cursor()
+
+        self.data.extend(self.cursor_access)
+
+    def _finalize_cursor(self):
+        if not self.filter_callbacks:
+            c = self.cursor
+
+            if self._skip:
+                c.skip(self._skip)
+            if self._limit is not None:
+                c.limit(self._limit)
+
+            self.cursor_access = self._create_results()
+        else:
+            self.cursor_access = self._filter_results()
+
+    def _create_results(self):
+        for i in self.cursor:
+            yield self.create(i)
+
+    def _filter_results(self):
+        skip = self._skip or 0
+        limit = self._limit
+        callbacks = self.filter_callbacks
+
+        if limit == 0:
+            return
+
+        n = 0
+
+        if limit:
+            self.cursor.batch_size(min(limit + skip, 100))
+
+        for i in self.cursor:
+            obj = self.create(i)
+            if all(cb(obj) for cb in callbacks):
+                if skip:
+                    skip -= 1
+                else:
+                    yield obj
+                    n += 1
+                    if limit and n >= limit:
+                        break
+
+    def _modify(self, op):
+        if self.cursor_access is not None:
+            raise RuntimeError(op + ' operation on executed ResultSet')
+
     def filter(self, query):
-        if self.data:
-            raise RuntimeError('filter operation on executed ResultSet')
+        self._modify('filter')
 
-        if self.query is None:
-            self.query = query
+        if isinstance(query, dict):
+            if self.query is None:
+                self.query = query
+            else:
+                h.merge(self.query, query)
         else:
-            h.merge(self.query, query)
+            self.filter_callbacks.append(query)
+
+        return self
+
+    def filtered(self):
+        return bool(self.filter_callbacks)
+
+    def limit(self, n):
+        self._modify('limit')
+        n = int(n)
+        if n < 0:
+            raise ValueError('negative limit value')
+        self._limit = n
+        return self
+
+    def skip(self, n):
+        self._modify('skip')
+        n = int(n)
+        if n < 0:
+            raise ValueError('negative skip value')
+        self._skip = n
         return self
 
     def where(self, code):
         return ResultIter(self)
 
     def __len__(self):
+        if self.filtered():
+            raise RuntimeError('Length operation on filtered ResultSet')
+
         return self.cursor.count(True)
 
     def __nonzero__(self):
         if self.data:
             return True
+        elif self.filtered():
+            return self.fetch(0)
         return len(self) != 0
 
 class ResultIter(object):

reddish/lib/renderers.py

 
 def render_json(*args):
     obj = render_object(args[-1], c.user)
-    if c.num_items is not None:
+    if c.num_items is not None and c.num_items != -1:
         obj['num_items'] = c.num_items
     return json.dumps(obj, default = json_default).replace('/', r'\/')
 

reddish/model/__init__.py

     Session.configure(engine, cfg['database'])
 
 def post_sort(res, user = None, sort = 'latest'):
-    # TODO: Hide posts from banned and inaccessible communities
-    # in a way that isn't horrendously slow.
-
-    if sort == 'top':
-        res.sort([('total_value', -1), ('created', -1)])
+    if user is None:
+        def post_filter(p):
+            cmty = p.get_community()
+            return not cmty.banned and cmty.access != 'private'
+        res.filter(post_filter)
+    elif not user.admin:
+        def post_filter(p):
+            cmty = p.get_community()
+            return not cmty.banned and (cmty.access != 'private' or
+                user._id in cmty.contributors)
+        res.filter(post_filter)
+
+    if sort == 'latest':
+        res.sort([('last_reply', -1), ('created', -1)])
     elif sort == 'new':
         res.sort('created', -1)
-    else: # elif sort == 'latest':
-        res.sort([('last_reply', -1), ('created', -1)])
+    else: # elif sort == 'top':
+        res.sort([('total_value', -1), ('created', -1)])
 
     return res
 
     def get_user(self):
         return User.get(self.user)
 
-mongo.Index(ResetToken, 'key', unique = True)
-
 class Email(ModelBase, mongo.Document):
     _collection = 'email'
     _polymorphic = 'type'
         except exc.NotFound:
             return False
 
-mongo.Index(Captcha, 'key', unique = True)
-
 class Moderator(ModelBase, mongo.Document, Renderable):
     _collection = 'moderator'
 
 mongo.Index(Community, 'created')
 mongo.Index(Community, 'creator')
 mongo.Index(Community, 'num_subscribers')
+mongo.Index(Community, [('num_subscribers', -1), ('created', 1)])
 
 class CommunityBan(ModelBase, mongo.Document, Renderable):
     _collection = 'community_ban'
 mongo.Index(Post, 'community')
 mongo.Index(Post, 'created')
 mongo.Index(Post, 'keywords', sparse = True)
+mongo.Index(Post, 'last_reply')
 mongo.Index(Post, 'link', sparse = True)
 mongo.Index(Post, 'r_domain', sparse = True)
+mongo.Index(Post, 'total_value')
+mongo.Index(Post, [('last_reply', -1), ('created', -1)])
+mongo.Index(Post, [('total_value', -1), ('created', -1)])
 
 class Comment(ModelBase, mongo.Document, Renderable):
     _collection = 'comment'
 mongo.Index(Comment, 'created')
 mongo.Index(Comment, 'depth')
 mongo.Index(Comment, 'parents')
+mongo.Index(Comment, 'total_value')
+mongo.Index(Comment, [('total_value', -1), ('created', 1)])
 
 class Report(ModelBase, mongo.Document, Renderable):
 

reddish/public/robots.txt

 Disallow: /*.json
 Disallow: /*.rss
 Disallow: /*.xml
+Disallow: /*?offset=
 Disallow: /api/
 Disallow: /captcha/
 Disallow: /g/*/submit

reddish/templates/base.mako

 <div id="content">
     ${next.body(**context.kwargs)}
 
-    % if c.num_items and c.num_items > c.prefs.n_items:
+    % if c.num_items == -1 or c.num_items > c.prefs.n_items:
     <% n = c.prefs.n_items %>\
     more:
     %   if c.offset > 0:
     <a href="${url.current(offset = c.offset - n)}">prev</a> | \
     %   endif
-    %   if c.num_items - c.offset > n:
+    %   if c.num_items == -1 or c.num_items - c.offset > n:
     <a href="${url.current(offset = c.offset + n)}">next</a>
     %   else:
     next