From 17823ac36eb9ebcb641ba891d757e553f22b69d8 Mon Sep 17 00:00:00 2001 From: EnTeQuAk Date: Thu, 24 Jul 2008 00:05:04 +0200 Subject: [PATCH] code cleanups, added first "show private pastes only" implementation, added filterable interface to show_all view --- TODO | 2 +- lodgeit/application.py | 12 ++--- lodgeit/controllers/pastes.py | 22 ++++++-- lodgeit/database.py | 10 ++-- lodgeit/i18n/__init__.py | 4 -- lodgeit/lib/captcha.py | 2 +- lodgeit/lib/diff.py | 10 +++- lodgeit/lib/filterable.py | 80 +++++++++++++++++++++++++++++ lodgeit/static/lodgeit.js | 10 ++++ lodgeit/static/style.css | 44 +++++++++++++++- lodgeit/utils.py | 8 ++- lodgeit/views/show_all.html | 14 +++++ lodgeit/views/utils/filterable.html | 2 + lodgeit/views/utils/macros.html | 66 ++++++++++++++++++++++++ 14 files changed, 261 insertions(+), 25 deletions(-) create mode 100644 lodgeit/lib/filterable.py create mode 100644 lodgeit/views/utils/filterable.html create mode 100644 lodgeit/views/utils/macros.html diff --git a/TODO b/TODO index ee8eb5f..162ec36 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,6 @@ * Make it possible to tag and find pastes * Add a button to find all the personal (and private) pastes - (cookie bound) + (cookie bound) (yet only the button is needed. Feature is implemented in show_all filterable view) * Improve i18n support * Improve LodgeIt Interface (null-interface + star) * use udiff module instead of pygments-diff-highlighter for diff highlighning diff --git a/lodgeit/application.py b/lodgeit/application.py index 28efc78..56f9aa8 100644 --- a/lodgeit/application.py +++ b/lodgeit/application.py @@ -11,14 +11,14 @@ """ import os from datetime import datetime, timedelta -import sqlalchemy from babel import Locale from werkzeug import SharedDataMiddleware, ClosingIterator from werkzeug.exceptions import HTTPException, NotFound +from sqlalchemy import create_engine from lodgeit import i18n, local from lodgeit.urls import urlmap from lodgeit.utils import COOKIE_NAME, Request, jinja_environment -from lodgeit.database import metadata, session, Paste +from lodgeit.database import metadata, session from lodgeit.controllers import get_controller @@ -28,10 +28,10 @@ class LodgeIt(object): def __init__(self, dburi, secret_key): self.secret_key = secret_key - #: database engine - self.engine = sqlalchemy.create_engine(dburi, convert_unicode=True) - #: make sure all tables exist. - metadata.create_all(self.engine) + #: bind metadata, create engine and create all tables + self.engine = engine = create_engine(dburi, convert_unicode=True) + metadata.bind = engine + metadata.create_all(engine) #: 18n setup self.locale = Locale('en') diff --git a/lodgeit/controllers/pastes.py b/lodgeit/controllers/pastes.py index 2c8df52..1e46d95 100644 --- a/lodgeit/controllers/pastes.py +++ b/lodgeit/controllers/pastes.py @@ -18,6 +18,7 @@ from lodgeit.database import session, Paste from lodgeit.lib.highlighting import LANGUAGES, STYLES, get_style from lodgeit.lib.pagination import generate_pagination from lodgeit.lib.captcha import check_hashed_solution, Captcha +from lodgeit.lib.filterable import Filterable class PasteController(object): @@ -109,13 +110,26 @@ class PasteController(object): def show_all(self, page=1): """Paginated list of pages.""" - def link(page): if page == 1: return '/all/' return '/all/%d' % page - all = Paste.find_all() + form_args = local.request.args + if not 'show_private' in form_args: + query = Paste.find_all() + else: + query = Paste.query.filter_by( + user_hash=local.request.user_hash + ) + + filterable = Filterable(Paste, query, { + 'paste_id': (_(u'identifier'), 'int'), + 'pub_date': (_(u'published'), 'date'), + 'language': (_(u'language'), 'str'), + }, form_args, True) + all = filterable.get_objects() + pastes = all.limit(10).offset(10 * (page -1)).all() if not pastes and page != 1: raise NotFound() @@ -123,7 +137,9 @@ class PasteController(object): return render_template('show_all.html', pastes=pastes, pagination=generate_pagination(page, 10, all.count(), link), - css=get_style(local.request)[1] + css=get_style(local.request)[1], + filterable=filterable, + show_private='show_private' in form_args ) def compare_paste(self, new_id=None, old_id=None): diff --git a/lodgeit/database.py b/lodgeit/database.py index d63c33a..3b5d2aa 100644 --- a/lodgeit/database.py +++ b/lodgeit/database.py @@ -10,20 +10,20 @@ """ import time import difflib -from cgi import escape from datetime import datetime from werkzeug import cached_property from sqlalchemy import MetaData, Integer, Text, DateTime, ForeignKey, \ String, Boolean, Table, Column, select, and_, func -from sqlalchemy.orm import create_session, mapper, backref, relation +from sqlalchemy.orm import scoped_session, create_session, backref, relation from sqlalchemy.orm.scoping import ScopedSession from lodgeit import local from lodgeit.utils import generate_paste_hash from lodgeit.lib.highlighting import highlight, LANGUAGES -session = ScopedSession(lambda: create_session(bind=local.application.engine), - scopefunc=local._local_manager.get_ident) +session = scoped_session(lambda: create_session(local.application.engine), + scopefunc=local._local_manager.get_ident) + metadata = MetaData() pastes = Table('pastes', metadata, @@ -79,7 +79,7 @@ class Paste(object): @staticmethod def count(): - """COunt all pastes.""" + """Count all pastes.""" s = select([func.count(pastes.c.paste_id)]) return session.execute(s).fetchone()[0] diff --git a/lodgeit/i18n/__init__.py b/lodgeit/i18n/__init__.py index 442f82f..5a53ec5 100644 --- a/lodgeit/i18n/__init__.py +++ b/lodgeit/i18n/__init__.py @@ -9,15 +9,11 @@ :license: GNU GPL. """ import os -from datetime import datetime from babel import Locale, dates, UnknownLocaleError from babel.support import Translations from lodgeit import local -from lodgeit.utils import jinja_environment -__all__ = ['_', 'gettext', 'ngettext'] - def load_translations(locale): """Load the translation for a locale.""" diff --git a/lodgeit/lib/captcha.py b/lodgeit/lib/captcha.py index a34ab7d..e7a835b 100644 --- a/lodgeit/lib/captcha.py +++ b/lodgeit/lib/captcha.py @@ -114,7 +114,7 @@ class Captcha(object): response = Response(mimetype='image/png') self.render_image(size=None).save(response.stream, 'PNG') if set_cookie: - request.session['captcha_id'] = self.hash_solution() + local.request.session['captcha_id'] = self.hash_solution() return response diff --git a/lodgeit/lib/diff.py b/lodgeit/lib/diff.py index 0c5a68a..508daf9 100644 --- a/lodgeit/lib/diff.py +++ b/lodgeit/lib/diff.py @@ -121,7 +121,15 @@ class DiffRenderer(object): affects_old = True action = 'del' else: - raise RuntimeError() + # this happens sometimes if it's a diff from + # a po/pot file with `"` at one line. + # No idea how to handle that a better way... + if command == '"': + affects_old = affects_new = True + action = 'unmod' + line = '"' + else: + raise RuntimeError() old_line += affects_old new_line += affects_new diff --git a/lodgeit/lib/filterable.py b/lodgeit/lib/filterable.py new file mode 100644 index 0000000..b68fa92 --- /dev/null +++ b/lodgeit/lib/filterable.py @@ -0,0 +1,80 @@ +#-*- coding: utf-8 -*- +""" + lodgeit.libs.filterable + ~~~~~~~~~~~~~~~~~~~~~~~ + + Small library that adds filterable support to some parts of lodgeit. + + :copyright: 2008 by Christopher Grebs. + :license: BSD. +""" +from lodgeit.i18n import _ +from lodgeit.utils import render_template + + +ACTIONS = { + 'str': { + 'is': _(u'is'), + 'contains': _(u'contains'), + 'startswith': _('startswith'), + }, + 'int': { + 'is': _(u'is'), + 'greater': _(u'greater'), + 'lower': _(u'lower'), + }, + 'date': { + 'is': _(u'same date'), + 'greater': _(u'later'), + 'lower': _(u'earlier'), + } +} +ACTIONS_MAP = { + 'is': lambda f, v: f == v, + 'contains': lambda f, v: f.contains(v), + 'startswith': lambda f, v: f.startswith(v), + 'greater': lambda f, v: f > v, + 'lower': lambda f, v: f < v, + 'bool': lambda f, v: f == (v == 'true'), +} + + +class Filterable(object): + def __init__(self, model, objects, fields, args, inline=False): + self.model = model + self.fields = fields + self.objects = objects + self.args = args + self.filters = {} + for field in fields: + action = args.get('%s_action' % field) + value = args.get('%s_value' % field) + if action and value and action in ACTIONS_MAP \ + and not field == args.get('remove_filter'): + self.filters[field] = action, value + + new_filter = args.get('new_filter') + if 'add_filter' in args and new_filter and new_filter in fields: + self.filters[new_filter] = 'is', '' + + self.inline = inline + + + def get_html(self): + ret = render_template('utils/filterable.html', plain=True, **{ + 'filters': self.filters, + 'fields': self.fields, + 'actions': ACTIONS, + 'args': {'order': self.args.get('order')}, + 'inline': self.inline + }) + return ret + + def get_objects(self): + for field, filter in self.filters.iteritems(): + action, value = filter + if value: + func = ACTIONS_MAP[action] + criterion = (getattr(self.model, field), value) + self.objects = self.objects.filter(func(*criterion)) + return self.objects diff --git a/lodgeit/static/lodgeit.js b/lodgeit/static/lodgeit.js index 9276beb..cc3d475 100644 --- a/lodgeit/static/lodgeit.js +++ b/lodgeit/static/lodgeit.js @@ -27,6 +27,9 @@ var LodgeIt = { /* hide all related blocks if in js mode */ $('div.related div.content').hide(); + + /* hide all filter related blocks if in js mode */ + $('div.paste_filter form').hide(); /** * links marked with "autoclose" inside the related div @@ -89,6 +92,13 @@ var LodgeIt = { } }, + /** + * slide-toggle a box + */ + toggleFilterBox : function() { + $('div.paste_filter form').slideToggle(500); + }, + /** * fade the line numbers in and out */ diff --git a/lodgeit/static/style.css b/lodgeit/static/style.css index e77de08..17cdddc 100644 --- a/lodgeit/static/style.css +++ b/lodgeit/static/style.css @@ -144,6 +144,7 @@ div.text { } div.related, +div.paste_filter, div.notification { margin: 0 0 10px 0; border: 1px solid #84BCCA; @@ -152,6 +153,7 @@ div.notification { } div.related h3, +div.paste_filter h3, div.notification h3 { margin: 0; padding: 0; @@ -160,6 +162,7 @@ div.notification h3 { } div.notification h3, +div.paste_filter h3 a, div.related h3 a { display: block; padding: 5px; @@ -180,12 +183,14 @@ div.notification div.captcha img { margin: 8px 0 8px 0; } -div.related h3 a:hover { +div.related h3 a:hover, +div.paste_filter h3 a:hover { background-color: #1d89a4; color: white; } -div.related h3 a:focus { +div.related h3 a:focus, +div.paste_filter h3 a:focus { outline: none; } @@ -435,3 +440,38 @@ ul.xmlrpc-method-list li p.docstring { padding: 5px 0 0 20px; font-size: 0.95em; } + + +/* Filterable */ +table.filterable { + width: 100%; + margin-bottom: 0.7em; + margin-top: 0.2em; + border-collapse: collapse; + border-spacing: 0px; + border: solid 1px #ccc; +} + +table.filterable td { + border-color: #ccc; + border-style: solid; + border-width: 1px 0 1px 0; + vertical-align: middle; +} + +table.filterable td.value { + width: 100%; +} + +table.filterable div.add { + float: right; +} + +table.filterable div.add.left { + float: left; +} + +input.update_filterable { + margin: 5px 10px 5px 5px; + display: block; +} diff --git a/lodgeit/utils.py b/lodgeit/utils.py index c1e7fe2..1b815a1 100644 --- a/lodgeit/utils.py +++ b/lodgeit/utils.py @@ -72,7 +72,7 @@ class Request(RequestBase): local.request = self -def render_template(template_name, **tcontext): +def render_template(template_name, plain=False, **tcontext): """Render a template to a response. This automatically fetches the list of new replies for the layout template. It also adds the current request to the context. This is used for the @@ -86,4 +86,8 @@ def render_template(template_name, **tcontext): if local.application: tcontext['active_language'] = local.application.locale.language t = jinja_environment.get_template(template_name) - return Response(t.render(tcontext), mimetype='text/html; charset=utf-8') + if not plain: + resp = Response(t.render(tcontext), mimetype='text/html; charset=utf-8') + else: + resp = t.render(tcontext) + return resp diff --git a/lodgeit/views/show_all.html b/lodgeit/views/show_all.html index 10a3e17..7634e73 100644 --- a/lodgeit/views/show_all.html +++ b/lodgeit/views/show_all.html @@ -1,7 +1,18 @@ {% extends "layout.html" %} +{% import 'utils/macros.html' as macros %} {% set page_title = _('All Pastes') %} {% set active_page = 'all' %} {% block body %} +
+

{%- trans -%}Filter pastes{%- endtrans -%}

+
+ {{ filterable.get_html() }} + + + +
+
+ {% if pastes %}