|
1 | 1 | # -*- coding: utf-8 -*-
|
2 | 2 | from __future__ import absolute_import
|
3 | 3 |
|
4 |
| -import datetime |
5 |
| -import math |
6 |
| -import uuid |
7 |
| - |
8 | 4 | from flask import abort
|
9 |
| -from flask.sessions import SessionInterface, SessionMixin |
10 |
| -from werkzeug.datastructures import CallbackDict |
11 | 5 |
|
12 | 6 | import mongoengine
|
13 | 7 |
|
14 | 8 | from mongoengine.queryset import MultipleObjectsReturned, DoesNotExist, QuerySet
|
15 | 9 | from mongoengine.base import ValidationError
|
16 | 10 |
|
| 11 | +from .sessions import * |
| 12 | +from .pagination import * |
| 13 | + |
17 | 14 |
|
18 | 15 | def _include_mongoengine(obj):
|
19 | 16 | for module in mongoengine, mongoengine.fields:
|
@@ -104,222 +101,3 @@ class DynamicDocument(mongoengine.DynamicDocument):
|
104 | 101 |
|
105 | 102 | meta = {'abstract': True,
|
106 | 103 | 'queryset_class': BaseQuerySet}
|
107 |
| - |
108 |
| - |
109 |
| -class Pagination(object): |
110 |
| - |
111 |
| - def __init__(self, iterable, page, per_page): |
112 |
| - |
113 |
| - if page < 1: |
114 |
| - abort(404) |
115 |
| - |
116 |
| - self.iterable = iterable |
117 |
| - self.page = page |
118 |
| - self.per_page = per_page |
119 |
| - self.total = len(iterable) |
120 |
| - |
121 |
| - start_index = (page - 1) * per_page |
122 |
| - end_index = page * per_page |
123 |
| - |
124 |
| - self.items = iterable[start_index:end_index] |
125 |
| - if isinstance(self.items, QuerySet): |
126 |
| - self.items = self.items.select_related() |
127 |
| - if not self.items and page != 1: |
128 |
| - abort(404) |
129 |
| - |
130 |
| - @property |
131 |
| - def pages(self): |
132 |
| - """The total number of pages""" |
133 |
| - return int(math.ceil(self.total / float(self.per_page))) |
134 |
| - |
135 |
| - def prev(self, error_out=False): |
136 |
| - """Returns a :class:`Pagination` object for the previous page.""" |
137 |
| - assert self.iterable is not None, ('an object is required ' |
138 |
| - 'for this method to work') |
139 |
| - iterable = self.iterable |
140 |
| - if isinstance(iterable, QuerySet): |
141 |
| - iterable._skip = None |
142 |
| - iterable._limit = None |
143 |
| - iterable = iterable.clone() |
144 |
| - return self.__class__(iterable, self.page - 1, self.per_page) |
145 |
| - |
146 |
| - @property |
147 |
| - def prev_num(self): |
148 |
| - """Number of the previous page.""" |
149 |
| - return self.page - 1 |
150 |
| - |
151 |
| - @property |
152 |
| - def has_prev(self): |
153 |
| - """True if a previous page exists""" |
154 |
| - return self.page > 1 |
155 |
| - |
156 |
| - def next(self, error_out=False): |
157 |
| - """Returns a :class:`Pagination` object for the next page.""" |
158 |
| - assert self.iterable is not None, ('an object is required ' |
159 |
| - 'for this method to work') |
160 |
| - iterable = self.iterable |
161 |
| - if isinstance(iterable, QuerySet): |
162 |
| - iterable._skip = None |
163 |
| - iterable._limit = None |
164 |
| - iterable = iterable.clone() |
165 |
| - return self.__class__(iterable, self.page + 1, self.per_page) |
166 |
| - |
167 |
| - @property |
168 |
| - def has_next(self): |
169 |
| - """True if a next page exists.""" |
170 |
| - return self.page < self.pages |
171 |
| - |
172 |
| - @property |
173 |
| - def next_num(self): |
174 |
| - """Number of the next page""" |
175 |
| - return self.page + 1 |
176 |
| - |
177 |
| - def iter_pages(self, left_edge=2, left_current=2, |
178 |
| - right_current=5, right_edge=2): |
179 |
| - """Iterates over the page numbers in the pagination. The four |
180 |
| - parameters control the thresholds how many numbers should be produced |
181 |
| - from the sides. Skipped page numbers are represented as `None`. |
182 |
| - This is how you could render such a pagination in the templates: |
183 |
| -
|
184 |
| - .. sourcecode:: html+jinja |
185 |
| -
|
186 |
| - {% macro render_pagination(pagination, endpoint) %} |
187 |
| - <div class=pagination> |
188 |
| - {%- for page in pagination.iter_pages() %} |
189 |
| - {% if page %} |
190 |
| - {% if page != pagination.page %} |
191 |
| - <a href="{{ url_for(endpoint, page=page) }}">{{ page }}</a> |
192 |
| - {% else %} |
193 |
| - <strong>{{ page }}</strong> |
194 |
| - {% endif %} |
195 |
| - {% else %} |
196 |
| - <span class=ellipsis>…</span> |
197 |
| - {% endif %} |
198 |
| - {%- endfor %} |
199 |
| - </div> |
200 |
| - {% endmacro %} |
201 |
| - """ |
202 |
| - last = 0 |
203 |
| - for num in xrange(1, self.pages + 1): |
204 |
| - if num <= left_edge or \ |
205 |
| - (num > self.page - left_current - 1 and |
206 |
| - num < self.page + right_current) or \ |
207 |
| - num > self.pages - right_edge: |
208 |
| - if last + 1 != num: |
209 |
| - yield None |
210 |
| - yield num |
211 |
| - last = num |
212 |
| - |
213 |
| - |
214 |
| -class ListFieldPagination(Pagination): |
215 |
| - |
216 |
| - def __init__(self, queryset, field_name, doc_id, page, per_page, |
217 |
| - total=None): |
218 |
| - """Allows an array within a document to be paginated. |
219 |
| -
|
220 |
| - Queryset must contain the document which has the array we're |
221 |
| - paginating, and doc_id should be it's _id. |
222 |
| - Field name is the name of the array we're paginating. |
223 |
| - Page and per_page work just like in Pagination. |
224 |
| - Total is an argument because it can be computed more efficiently |
225 |
| - elsewhere, but we still use array.length as a fallback. |
226 |
| - """ |
227 |
| - if page < 1: |
228 |
| - abort(404) |
229 |
| - |
230 |
| - self.page = page |
231 |
| - self.per_page = per_page |
232 |
| - |
233 |
| - self.queryset = queryset |
234 |
| - self.doc_id = doc_id |
235 |
| - self.field_name = field_name |
236 |
| - |
237 |
| - start_index = (page - 1) * per_page |
238 |
| - |
239 |
| - field_attrs = {field_name: {"$slice": [start_index, per_page]}} |
240 |
| - |
241 |
| - self.items = getattr(queryset().fields(**field_attrs).first(), field_name) |
242 |
| - |
243 |
| - self.total = total or len(self.items) |
244 |
| - |
245 |
| - if not self.items and page != 1: |
246 |
| - abort(404) |
247 |
| - |
248 |
| - def prev(self, error_out=False): |
249 |
| - """Returns a :class:`Pagination` object for the previous page.""" |
250 |
| - assert self.items is not None, ('a query object is required ' |
251 |
| - 'for this method to work') |
252 |
| - return self.__class__(self.queryset, self.doc_id, self.field_name, |
253 |
| - self.page - 1, self.per_page, self.total) |
254 |
| - |
255 |
| - def next(self, error_out=False): |
256 |
| - """Returns a :class:`Pagination` object for the next page.""" |
257 |
| - assert self.iterable is not None, ('a query object is required ' |
258 |
| - 'for this method to work') |
259 |
| - return self.__class__(self.queryset, self.doc_id, self.field_name, |
260 |
| - self.page + 1, self.per_page, self.total) |
261 |
| - |
262 |
| - |
263 |
| -class MongoEngineSession(CallbackDict, SessionMixin): |
264 |
| - |
265 |
| - def __init__(self, initial=None, sid=None): |
266 |
| - def on_update(self): |
267 |
| - self.modified = True |
268 |
| - CallbackDict.__init__(self, initial, on_update) |
269 |
| - self.sid = sid |
270 |
| - self.modified = False |
271 |
| - |
272 |
| - |
273 |
| -class MongoEngineSessionInterface(SessionInterface): |
274 |
| - """SessionInterface for mongoengine""" |
275 |
| - |
276 |
| - def __init__(self, db, collection='session'): |
277 |
| - """ |
278 |
| - The MongoSessionInterface |
279 |
| -
|
280 |
| - :param db: The app's db eg: MongoEngine() |
281 |
| - :param collection: The session collection name defaults to "session" |
282 |
| - """ |
283 |
| - |
284 |
| - if not isinstance(collection, basestring): |
285 |
| - raise ValueError('collection argument should be string or unicode') |
286 |
| - |
287 |
| - class DBSession(db.Document): |
288 |
| - sid = db.StringField(primary_key=True) |
289 |
| - data = db.DictField() |
290 |
| - expiration = db.DateTimeField() |
291 |
| - meta = { |
292 |
| - 'collection': collection, |
293 |
| - 'indexes': [{'fields': ['expiration'], |
294 |
| - 'expireAfterSeconds': 60 * 60 * 24 * 7 * 31}] |
295 |
| - } |
296 |
| - |
297 |
| - self.cls = DBSession |
298 |
| - |
299 |
| - def get_expiration_time(self, app, session): |
300 |
| - if session.permanent: |
301 |
| - return app.permanent_session_lifetime |
302 |
| - return datetime.timedelta(days=1) |
303 |
| - |
304 |
| - def open_session(self, app, request): |
305 |
| - sid = request.cookies.get(app.session_cookie_name) |
306 |
| - if sid: |
307 |
| - stored_session = self.cls.objects(sid=sid).first() |
308 |
| - if stored_session and stored_session.expiration > datetime.datetime.utcnow(): |
309 |
| - return MongoEngineSession(initial=stored_session.data, sid=stored_session.sid) |
310 |
| - return MongoEngineSession(sid=str(uuid.uuid4())) |
311 |
| - |
312 |
| - def save_session(self, app, session, response): |
313 |
| - domain = self.get_cookie_domain(app) |
314 |
| - if not session: |
315 |
| - if session.modified: |
316 |
| - response.delete_cookie(app.session_cookie_name, domain=domain) |
317 |
| - return |
318 |
| - |
319 |
| - expiration = datetime.datetime.now() + self.get_expiration_time(app, session) |
320 |
| - |
321 |
| - if session.modified: |
322 |
| - self.cls(sid=session.sid, data=session, expiration=expiration).save() |
323 |
| - |
324 |
| - response.set_cookie(app.session_cookie_name, session.sid, |
325 |
| - expires=expiration, httponly=True, domain=domain) |
0 commit comments