def __init__(self, title, author, text, slug=None, pub_date=None, last_update=None, comments_enabled=True, pings_enabled=True, status=STATUS_PUBLISHED, parser=None, uid=None, content_type='entry', extra=None): app = get_application() self.content_type = content_type self.title = title self.author = author if parser is None: parser = app.cfg['default_parser'] self.parser = parser self.text = text or u'' if extra: self.extra = dict(extra) else: self.extra = {} self.comments_enabled = comments_enabled self.pings_enabled = pings_enabled self.status = status # set times now, they depend on status being set self.touch_times(pub_date) if last_update is not None: self.last_update = last_update # now bind the slug for which we need the times set. self.bind_slug(slug) # generate a UID if none is given if uid is None: uid = build_tag_uri(app, self.pub_date, content_type, self.slug) self.uid = uid
def get_list(self, endpoint=None, page=1, per_page=None, url_args=None, raise_if_empty=True): """Return a dict with pagination, the current posts, number of pages, total posts and all that stuff for further processing. """ if per_page is None: app = get_application() per_page = app.cfg['posts_per_page'] # send the query offset = per_page * (page - 1) postlist = self.order_by(Post.pub_date.desc()) \ .offset(offset).limit(per_page).all() # if raising exceptions is wanted, raise it if raise_if_empty and (page != 1 and not postlist): raise NotFound() pagination = Pagination(endpoint, page, per_page, self.count(), url_args) return { 'pagination': pagination, 'posts': postlist }
def __init__(self, initial=None): self.app = get_application() if initial is None: initial = {} for name in self.fields: initial[name] = self.app.cfg[name] forms.Form.__init__(self, initial)
def get_list(self, endpoint=None, page=1, per_page=None, url_args=None, raise_if_empty=True): """Return a dict with pagination, the current posts, number of pages, total posts and all that stuff for further processing. """ if per_page is None: app = get_application() per_page = app.cfg['posts_per_page'] # send the query offset = per_page * (page - 1) postlist = self.order_by(Post.pub_date.desc()) \ .offset(offset).limit(per_page).all() # if raising exceptions is wanted, raise it if raise_if_empty and (page != 1 and not postlist): raise NotFound() pagination = Pagination(endpoint, page, per_page, self.count(), url_args) return {'pagination': pagination, 'posts': postlist}
def __init__(self, post, author, text, email=None, www=None, parent=None, pub_date=None, submitter_ip='0.0.0.0', parser=None, is_pingback=False, status=COMMENT_MODERATED): self.post = post if isinstance(author, basestring): self.user = None self._author = author self._email = email self._www = www else: assert email is www is None, \ 'email and www can only be provided if the author is ' \ 'an anonymous user' self.user = author if parser is None: parser = get_application().cfg['comment_parser'] self.parser = parser self.text = text or '' self.parent = parent if pub_date is None: pub_date = datetime.utcnow() self.pub_date = pub_date self.blocked_msg = None self.submitter_ip = submitter_ip self.is_pingback = is_pingback self.status = status
def __init__(self, post=None, initial=None): app = get_application() PostForm.__init__(self, post, forms.fill_dict(initial, comments_enabled=app.cfg['comments_enabled'], pings_enabled=app.cfg['pings_enabled'], ping_links=True ))
def gen_timestamped_slug(slug, content_type, pub_date=None): """Generate a timestamped slug, suitable for use as final URL path.""" from zine.application import get_application from zine.i18n import to_blog_timezone cfg = get_application().cfg if pub_date is None: pub_date = datetime.utcnow() pub_date = to_blog_timezone(pub_date) prefix = cfg['blog_url_prefix'].strip(u'/') if prefix: prefix += u'/' if content_type == 'entry': fixed = cfg['fixed_url_date_digits'] def handle_match(match): handler = _slug_parts.get(match.group(1)) if handler is None: return match.group(0) return handler(pub_date, slug, fixed) full_slug = prefix + _placeholder_re.sub( handle_match, cfg['post_url_format']) else: full_slug = u'%s%s' % (prefix, slug) return full_slug
def comments_closed(self): """True if commenting is no longer possible.""" app = get_application() open_for = app.cfg['comments_open_for'] if open_for == 0: return False return self.pub_date + timedelta(days=open_for) < datetime.utcnow()
def gen_timestamped_slug(slug, content_type, pub_date=None): """Generate a timestamped slug, suitable for use as final URL path.""" from zine.application import get_application from zine.i18n import to_blog_timezone cfg = get_application().cfg if pub_date is None: pub_date = datetime.utcnow() pub_date = to_blog_timezone(pub_date) prefix = cfg['blog_url_prefix'].strip(u'/') if prefix: prefix += u'/' if content_type == 'entry': fixed = cfg['fixed_url_date_digits'] def handle_match(match): handler = _slug_parts.get(match.group(1)) if handler is None: return match.group(0) return handler(pub_date, slug, fixed) full_slug = prefix + _placeholder_re.sub(handle_match, cfg['post_url_format']) else: full_slug = u'%s%s' % (prefix, slug) return full_slug
def __getitem__(self, name): locale = str(get_application().locale) if name in self._i18n_values.get(locale, ()): return self._i18n_values[locale][name] if name in self._values: return self._values[name] raise KeyError(name)
def get_engine(): """Return the active database engine (the database engine of the active application). If no application is enabled this has an undefined behavior. If you are not sure if the application is bound to the active thread, use :func:`~zine.application.get_application` and check it for `None`. The database engine is stored on the application object as `database_engine`. """ from zine.application import get_application return get_application().database_engine
def gen_slug(text, delim=u'-'): """Generates a proper slug for the given text. It calls either `gen_ascii_slug` or `gen_unicode_slug` depending on the application configuration. """ from zine.application import get_application if get_application().cfg['ascii_slugs']: return gen_ascii_slug(text, delim) return gen_unicode_slug(text, delim)
def log(message, module=None): try: logger = get_application().log except AttributeError: warn(UnboundLogging('Tried to log %r but no application ' 'was bound to the calling thread' % message), stacklevel=2) return if level >= logger.level: logger.log(name, message, module, currentframe(1))
def parser_missing(self): """If the parser for this post is not available this property will be `True`. If such as post is edited the text area is grayed out and tells the user to reinstall the plugin that provides that parser. Because it doesn't know the name of the plugin, the preferred was is telling it the parser which is available using the `parser` property. """ app = get_application() return self.parser not in app.parsers
def theme_lightweight(self, key): """A query for lightweight settings based on the theme. For example to use the lightweight settings for the author overview page you can use this query:: Post.query.theme_lightweight('author_overview') """ theme_settings = get_application().theme.settings deferred = theme_settings.get('sql.%s.deferred' % key) lazy = theme_settings.get('sql.%s.lazy' % key) return self.lightweight(deferred, lazy)
def __init__(self, tree): self.app = get_application() self.tree = tree self.tags = [] self.categories = [] self.authors = [] self.posts = [] self.blog = None self.extensions = [extension(self.app, self, tree) for extension in self.app.feed_importer_extensions if self.feed_type in extension.feed_types]
def parse_post(self, entry): # parse the dates first. updated = parse_iso8601(entry.findtext(atom.updated)) published = entry.findtext(atom.published) if published is not None: published = parse_iso8601(published) else: published = updated # figure out tags and categories by invoking the # callbacks on the extensions first. If no extension # was able to figure out what to do with it, we treat it # as category. tags, categories = self.parse_categories(entry) link = entry.find(atom.link) if link is not None: link = link.attrib.get('href') post_parser = _pickle(entry.findall(textpress.data)[0].text).get('parser', 'html') if post_parser not in get_application().parsers: post_parser = 'html' post = Post( entry.findtext(textpress.slug), # slug _get_text_content(entry.findall(atom.title)), # title link, # link published, # pub_date self.parse_author(entry), # author # XXX: the Post is prefixing the intro before the actual # content. This is the default Zine behavior and makes sense # for Zine. However nearly every blog works differently and # treats summary completely different from content. We should # think about that. _get_html_content(entry.findall(atom.summary)), # intro _get_html_content(entry.findall(atom.content)), # body tags, # tags categories, # categories parser=post_parser, updated=updated, uid=entry.findtext(atom.id) ) post.element = entry content_type = entry.findtext(textpress.content_type) if content_type not in ('page', 'entry'): post.content_type = 'entry' # now parse the comments for the post self.parse_comments(post) for extension in self.extensions: extension.postprocess_post(post) return post
def __init__(self, initial=None): self.app = app = get_application() self.active_plugins.choices = sorted([(x.name, x.display_name) for x in app.plugins.values()], key=lambda x: x[1].lower()) if initial is None: initial = dict( active_plugins=[x.name for x in app.plugins.itervalues() if x.active], disable_guard=not app.cfg['plugin_guard'] ) forms.Form.__init__(self, initial)
def __init__(self, tree): self.app = get_application() self.tree = tree self.tags = [] self.categories = [] self.authors = [] self.posts = [] self.blog = None self.extensions = [ extension(self.app, self, tree) for extension in self.app.feed_importer_extensions if self.feed_type in extension.feed_types ]
def exception(message=None, module=None, exc_info=None): """Logs an error plus the current or given exc info.""" if exc_info is None: exc_info = sys.exc_info() try: logger = get_application().log except AttributeError: # no application, write the exception to stderr return print_exception(*exc_info) if LEVELS['error'] >= logger.level: message = (message and message + '\n' or '') + \ ''.join(format_exception(*exc_info)) \ .decode('utf-8', 'ignore') logger.log('error', message, module, currentframe(1))
def set_auto_slug(self): """Generate a slug for this post.""" cfg = get_application().cfg slug = gen_slug(self.title) if not slug: slug = to_blog_timezone(self.pub_date).strftime('%H%M') full_slug = gen_timestamped_slug(slug, self.content_type, self.pub_date) if full_slug != self.slug: while Post.query.autoflush(False).filter_by(slug=full_slug) \ .limit(1).count(): full_slug = increment_string(full_slug) self.slug = full_slug
def __init__(self, post=None, initial=None): self.app = get_application() self.post = post if post is not None: initial = forms.fill_dict(initial, title=post.title, text=post.text, status=post.status, pub_date=post.pub_date, slug=post.slug, author=post.author, tags=[x.name for x in post.tags], categories=[x.id for x in post.categories], parser=post.parser, comments_enabled=post.comments_enabled, pings_enabled=post.pings_enabled, ping_links=not post.parser_missing ) else: initial = forms.fill_dict(initial, status=STATUS_DRAFT) # if we have a request, we can use the current user as a default req = get_request() if req and req.user: initial['author'] = req.user initial.setdefault('parser', self.app.cfg['default_parser']) self.author.choices = [x.username for x in User.query.all()] self.parser.choices = self.app.list_parsers() self.parser_missing = post and post.parser_missing if self.parser_missing: self.parser.choices.append((post.parser, _('%s (missing)') % post.parser.title())) self.categories.choices = [(c.id, c.name) for c in Category.query.all()] forms.Form.__init__(self, initial) # if we have have an old post and the parser is not missing and # it was published when the form was created we collect the old # posts so that we don't have to ping them another time. self._old_links = set() if self.post is not None and not self.post.parser_missing and \ self.post.is_published: self._old_links.update(self.post.find_urls())
def requires_moderation(self): """This is `True` if the comment requires moderation with the current moderation settings. This does not check if the comment is already moderated. """ if not self.anonymous: return False moderate = get_application().cfg['moderate_comments'] if moderate == MODERATE_ALL: return True elif moderate == MODERATE_NONE: return False return db.execute(comments.select( (comments.c.author == self._author) & (comments.c.email == self._email) & (comments.c.status == COMMENT_MODERATED) )).fetchone() is None
def requires_moderation(self): """This is `True` if the comment requires moderation with the current moderation settings. This does not check if the comment is already moderated. """ if not self.anonymous: return False moderate = get_application().cfg['moderate_comments'] if moderate == MODERATE_ALL: return True elif moderate == MODERATE_NONE: return False return db.execute( comments.select( (comments.c.author == self._author) & (comments.c.email == self._email) & (comments.c.status == COMMENT_MODERATED))).fetchone() is None
def bind_privileges(container, privileges): """Binds the privileges to the container. The privileges can be a list of privilege names, the container must be a set. This is called for the http roundtrip in the form validation. """ app = get_application() current_map = dict((x.name, x) for x in container) currently_attached = set(x.name for x in container) new_privileges = set(privileges) # remove outdated privileges for name in currently_attached.difference(new_privileges): container.remove(current_map[name]) # add new privileges for name in new_privileges.difference(currently_attached): container.add(app.privileges[name])
def bind_privileges(container, privileges, user=None): """Binds the privileges to the container. The privileges can be a list of privilege names, the container must be a set. This is called for the HTTP round-trip in the form validation. """ if not user: user = get_request().user app = get_application() notification_types = app.notification_manager.notification_types current_map = dict((x.name, x) for x in container) currently_attached = set(x.name for x in container) new_privileges = set(privileges) # remove out-dated privileges for name in currently_attached.difference(new_privileges): container.remove(current_map[name]) # remove any privilege dependencies that are not attached to other # privileges if current_map[name].dependencies: for privilege in current_map[name].dependencies.iter_privileges(): try: container.remove(privilege) except KeyError: # privilege probably already removed pass # remove notification subscriptions that required the privilege # being deleted. for notification in user.notification_subscriptions: privs = notification_types[notification.notification_id].privileges if current_map[name] in privs.iter_privileges(): db.session.delete(notification) break for privilege in current_map[name].dependencies: if privilege in privs.iter_privileges(): db.session.delete(notification) # add new privileges for name in new_privileges.difference(currently_attached): privilege = app.privileges[name] container.add(privilege) # add dependable privileges if privilege.dependencies: for privilege in privilege.dependencies.iter_privileges(): container.add(privilege)
def __init__(self, subject=None, text='', to_addrs=None): self.app = app = get_application() self.subject = u' '.join(subject.splitlines()) self.text = text from_addr = app.cfg['blog_email'] if not from_addr: from_addr = 'noreply@' + urlparse(app.cfg['blog_url']) \ [1].split(':')[0] self.from_addr = u'%s <%s>' % ( app.cfg['blog_title'], from_addr ) self.to_addrs = [] if isinstance(to_addrs, basestring): self.add_addr(to_addrs) else: for addr in to_addrs: self.add_addr(addr)
def open_url(url, data=None, timeout=None, allow_internal_requests=True, **kwargs): """This function parses the URL and opens the connection. The following protocols are supported: - `http` - `https` Per default requests to Zine itself trigger an internal request. This can be disabled by setting `allow_internal_requests` to False. """ app = get_application() if timeout is None: timeout = app.cfg['default_network_timeout'] parts = urlparse.urlsplit(url) if app is not None: blog_url = urlparse.urlsplit(app.cfg['blog_url']) if allow_internal_requests and \ parts.scheme in ('http', 'https') and \ blog_url.netloc == parts.netloc and \ parts.path.startswith(blog_url.path): path = parts.path[len(blog_url.path):].lstrip('/') method = kwargs.pop('method', None) if method is None: method = data is not None and 'POST' or 'GET' make_response = lambda *a: URLResponse(url, *a) return app.perform_subrequest(path.decode('utf-8'), url_decode(parts.query), method, data, timeout=timeout, response_wrapper=make_response, **kwargs) handler = _url_handlers.get(parts.scheme) if handler is None: raise URLError('unsupported URL schema %r' % parts.scheme) if isinstance(data, basestring): data = StringIO(data) try: obj = handler(parts, timeout, **kwargs) return obj.open(data) except Exception, e: if not isinstance(e, NetException): e = NetException('%s: %s' % (e.__class__.__name__, str(e))) raise e
def parse_comments(self, post): comments = {} unresolved_parents = {} for element in post.element.findall(textpress.comment): author = element.find(textpress.author) dependency = author.attrib.get('dependency') if dependency is not None: author = self._get_author(author) email = www = None else: email = author.findtext(textpress.email) www = author.findtext(textpress.uri) author = author.findtext(textpress.name) body = element.findall(textpress.data) if body: pickled = _pickle(body[0].text) body = pickled.get('raw_body', u'') comment_parser = pickled.get('parser', 'html') if comment_parser not in get_application().parsers: comment_parser = 'html' comment = Comment( author, body, email, www, None, parse_iso8601(element.findtext(textpress.published)), element.findtext(textpress.submitter_ip), comment_parser, _to_bool(element.findtext(textpress.is_pingback)), int(element.findtext(textpress.status)), element.findtext(textpress.blocked_msg), _parser_data(element.findtext(textpress.parser_data)) ) comments[int(element.findtext(textpress.id))] = comment parent = element.findtext(textpress.parent) if parent is not None or '': unresolved_parents[comment] = int(parent) for comment, parent_id in unresolved_parents.iteritems(): comment.parent = comments[parent_id] return comments.values()
def redirect(url, code=302, allow_external_redirect=False, force_scheme_change=False): """Return a redirect response. Like Werkzeug's redirect but this one checks for external redirects too. If a redirect to an external target was requested `BadRequest` is raised unless `allow_external_redirect` was explicitly set to `True`. Leading slashes are ignored which makes it unsuitable to redirect to URLs returned from `url_for` and others. Use `redirect_to` to redirect to arbitrary endpoints or `_redirect` to redirect to unchecked resources outside the URL root. By default the redirect will not change the URL scheme of the current request (if there is one). This behavior can be changed by setting the force_scheme_change to False. """ # leading slashes are ignored, if we redirect to "/foo" or "foo" # does not matter, in both cases we want to be below our blog root. url = url.lstrip('/') if not allow_external_redirect: #: check if the url is on the same server #: and make it an external one try: url = check_external_url(get_application(), url) except ValueError: raise BadRequest() # keep the current URL schema if we have an active request if we # should. If https enforcement is set we suppose that the blog_url # is already set to an https value. request = get_request() if request and not force_scheme_change and \ not request.app.cfg['force_https']: url = request.environ['wsgi.url_scheme'] + ':' + url.split(':', 1)[1] return _redirect(url, code)
def make_config_form(): """Returns the form for the configuration editor.""" app = get_application() fields = {} values = {} use_default_label = lazy_gettext(u'Use default value') for category in app.cfg.get_detail_list(): items = {} values[category['name']] = category_values = {} for item in category['items']: items[item['name']] = forms.Mapping( value=item['field'], use_default=forms.BooleanField(use_default_label) ) category_values[item['name']] = { 'value': item['value'], 'use_default': False } fields[category['name']] = forms.Mapping(**items) class _ConfigForm(forms.Form): values = forms.Mapping(**fields) cfg = app.cfg def apply(self): t = self.cfg.edit() for category, items in self.data['values'].iteritems(): for key, d in items.iteritems(): if category != 'zine': key = '%s/%s' % (category, key) if d['use_default']: t.revert_to_default(key) else: t[key] = d['value'] t.commit() return _ConfigForm({'values': values})
def redirect(url, code=302, allow_external_redirect=False): """Return a redirect response. Like Werkzeug's redirect but this one checks for external redirects too. If a redirect to an external target was requested `BadRequest` is raised unless `allow_external_redirect` was explicitly set to `True`. Leading slashes are ignored which makes it unsuitable to redirect to URLs returned from `url_for` and others. Use `redirect_to` to redirect to arbitrary endpoints or `_redirect` to redirect to unchecked resources outside the URL root. """ # leading slashes are ignored, if we redirect to "/foo" or "foo" # does not matter, in both cases we want to be below our blog root. url = url.lstrip('/') if not allow_external_redirect: #: check if the url is on the same server #: and make it an external one try: url = check_external_url(get_application(), url) except ValueError: raise BadRequest() return _redirect(url, code)
def parse(input_data, parser=None, reason='unknown'): """Generate a doc tree out of the data provided. If we are not in unbound mode the `process-doc-tree` event is sent so that plugins can modify the tree in place. The reason is useful for plugins to find out if they want to render it or now. For example a normal blog post would have the reason 'post', a comment 'comment', an isolated page from a plugin maybe 'page' etc. """ input_data = u'\n'.join(input_data.splitlines()) app = get_application() if parser is None: try: parser = app.parsers[app.cfg['default_parser']] except KeyError: # the plugin that provided the default parser is not # longer available. reset the config value to the builtin # parser and parse afterwards. t = app.cfg.edit() t.revert_to_default('default_parser') t.commit() parser = app.parsers[app.cfg['default_parser']] else: try: parser = app.parsers[parser] except KeyError: raise ValueError('parser %r does not exist' % (parser, )) tree = parser.parse(input_data, reason) #! allow plugins to alter the doctree. for callback in iter_listeners('process-doc-tree'): item = callback(tree, input_data, reason) if item is not None: tree = item return tree
def parse(input_data, parser=None, reason='unknown'): """Generate a doc tree out of the data provided. If we are not in unbound mode the `process-doc-tree` event is sent so that plugins can modify the tree in place. The reason is useful for plugins to find out if they want to render it or now. For example a normal blog post would have the reason 'post', a comment 'comment', an isolated page from a plugin maybe 'page' etc. """ input_data = u'\n'.join(input_data.splitlines()) app = get_application() if parser is None: try: parser = app.parsers[app.cfg['default_parser']] except KeyError: # the plugin that provided the default parser is not # longer available. reset the config value to the builtin # parser and parse afterwards. t = app.cfg.edit() t.revert_to_default('default_parser') t.commit() parser = app.parsers[app.cfg['default_parser']] else: try: parser = app.parsers[parser] except KeyError: raise ValueError('parser %r does not exist' % (parser,)) tree = parser.parse(input_data, reason) #! allow plugins to alter the doctree. for callback in iter_listeners('process-doc-tree'): item = callback(tree, input_data, reason) if item is not None: tree = item return tree
def privilege(self): return get_application().privileges.get(self.name)
def for_index(self): """Return all the types for the index.""" types = get_application().cfg['index_content_types'] if len(types) == 1: return self.filter_by(content_type=types[0]) return self.filter(Post.content_type.in_(types))
def _privileges(self): return get_application().content_type_privileges[self.content_type]
def as_dict(self): result = self._values.copy() result.update(self._i18n_values.get(str(get_application().locale), {})) return result
def for_index(self): """Return all the types for the index.""" types = get_application().cfg['index_content_types'] if len(types) == 1: return self.filter_by(content_type=types[0].strip()) return self.filter(Post.content_type.in_([x.strip() for x in types]))
def send_notification(type, message, user=Ellipsis): """Convenience function. Get the application object and deliver the notification to it's NotificationManager. The message must be a valid ZEML formatted message. The following top-level elements are available for marking up the message: title The title of the notification. Some systems may only transmit this part of the message. summary An optional quick summary. If the text is short enough it can be omitted and the system will try to transmit the longtext in that case. The upper limit for the summary should be around 100 chars. details If given this may either contain a paragraph with textual information or an ordered or unordered list of text or links. The general markup rules apply. longtext The full text of this notification. May contain some formattings. actions If given this may contain an unordered list of action links. These links may be transmitted together with the notification. Additionally if there is an associated page with the notification, somewhere should be a link element with a "selflink" class. This can be embedded in the longtext or actions (but any other element too). Example markup:: <title>New comment on "Foo bar baz"</title> <summary>Mr. Miracle wrote a new comment: "This is awesome."</summary> <details> <ul> <li><a href="http://miracle.invalid/">Mr. Miracle</a> <li><a href="mailto:[email protected]">E-Mail</a> </ul> </details> <longtext> <p>This is awesome. Keep it up! <p>Love your work </longtext> <actions> <ul> <li><a href="http://.../link" class="selflink">all comments</a> <li><a href="http://.../?action=delete">delete it</a> <li><a href="http://.../?action=approve">approve it</a> </ul> </actions> Example plaintext rendering (e-mail):: Subject: New comment on "Foo bar baz" Mr. Miracle http://miracle.invalid/ E-Mail [email protected] > This is awesome. Keep it up! > Love your work. Actions: - delete it http://.../?action=delete - approve it http://.../?action=approve Example IM notification rendering (jabber):: New comment on "Foo bar baz." Mr. Miracle wrote anew comment: "This is awesome". http://.../link """ get_application().notification_manager.send( Notification(type, message, user) )
def generate(self, **options): """This method generates the pagination. It accepts some keyword arguments that override the theme pagination settings. These arguments have the same name as the theme setting variables without the `pagination.` prefix. """ from zine.application import url_for, get_application, \ DEFAULT_THEME_SETTINGS if self._skip_theme_defaults: settings = DEFAULT_THEME_SETTINGS else: settings = get_application().theme.settings def _getopt(name): value = options.pop(name, None) if value is not None: return value return settings['pagination.' + name] normal = _getopt('normal') active = _getopt('active') commata = _getopt('commata') ellipsis = _getopt('ellipsis') threshold = _getopt('threshold') left_threshold = _getopt('left_threshold') right_threshold = _getopt('right_threshold') prev_link = _getopt('prev_link') next_link = _getopt('next_link') gray_prev_link = _getopt('gray_prev_link') gray_next_link = _getopt('gray_next_link') simple = _getopt('simple') if options: raise TypeError('generate() got an unexpected keyword ' 'argument %r' % iter(options).next()) was_ellipsis = False result = [] prev = None next = None get_link = lambda x: url_for(self.endpoint, page=x, per_page=self.per_page, post_id=self.post_id, **self.url_args) if simple: result.append(active % { 'url': get_link(self.page), 'page': self.page }) if self.page > 1: prev = self.page - 1 if self.page < self.pages: next = self.page + 1 else: for num in xrange(1, self.pages + 1): if num == self.page: was_ellipsis = False if num - 1 == self.page: next = num if num + 1 == self.page: prev = num if num <= left_threshold or \ num > self.pages - right_threshold or \ abs(self.page - num) < threshold: if result and result[-1] != ellipsis: result.append(commata) link = get_link(num) template = num == self.page and active or normal result.append(template % { 'url': link, 'page': num }) elif not was_ellipsis: was_ellipsis = True result.append(ellipsis) if next_link: if next is not None: result.append(u' <a href="%s" class="next">%s</a>' % (get_link(next), _(u'Next »'))) elif gray_next_link: result.append(u' <span class="disabled next">%s</span>' % _(u'Next »')) if prev_link: if prev is not None: result.insert(0, u'<a href="%s" class="prev">%s</a> ' % (get_link(prev), _(u'« Previous'))) elif gray_prev_link: result.insert(0, u'<span class="disabled prev">%s</span> ' % _(u'« Previous')) return u''.join(result)
def make_external_url(path): """Return an external url for the given path.""" return urljoin(get_application().cfg['blog_url'], path.lstrip('/'))
def _strip_url(url): """Strip an URL so that only the path is left.""" cfg = get_application().cfg if url.startswith(cfg['blog_url']): url = url[len(cfg['blog_url']):] return url.lstrip('/')