code cleanups, added first "show private pastes only" implementation, added filterable interface to show_all view

This commit is contained in:
EnTeQuAk 2008-07-24 00:05:04 +02:00
parent 6dd43db2e2
commit 17823ac36e
14 changed files with 261 additions and 25 deletions

2
TODO
View File

@ -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

View File

@ -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')

View File

@ -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):

View File

@ -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]

View File

@ -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."""

View File

@ -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

View File

@ -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

80
lodgeit/lib/filterable.py Normal file
View File

@ -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

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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

View File

@ -1,7 +1,18 @@
{% extends "layout.html" %}
{% import 'utils/macros.html' as macros %}
{% set page_title = _('All Pastes') %}
{% set active_page = 'all' %}
{% block body %}
<div class="paste_filter">
<h3><a href="javascript:LodgeIt.toggleFilterBox()">{%- trans -%}Filter pastes{%- endtrans -%}</a></h3>
<form action="" method="get">
{{ filterable.get_html() }}
<input type="checkbox" name="show_private" id="show_private"{% if show_private %} checked="checked"{% endif %} />
<label for="show_private">{% trans %}show <em>only</em> private pastes{% endtrans %}</label>
<input type="submit" value="{% trans %}update{% endtrans %}" class="update_filterable" />
</form>
</div>
{% if pastes %}
<ul class="paste_list">
{% for paste in pastes %}
<li class="{{ loop.cycle('even', 'odd') }}"><p><a href="{{ paste.url|e
@ -10,6 +21,9 @@
{{ paste.render_preview() }}</li>
{%- endfor %}
</ul>
{% else %}
<strong>{% trans %}No pastes found{% endtrans %}</strong>
{% endif %}
<div class="pagination">
{{ pagination }}
</div>

View File

@ -0,0 +1,2 @@
{% from 'utils/macros.html' import filterable %}
{{ filterable(filters, fields, actions, args, inline) }}

View File

@ -0,0 +1,66 @@
{% macro filterable(filters, fields, actions, args, inline=False) %}
{% if not inline %}
<form action="" method="get">
{% endif %}
{%- for k, v in args.iteritems() %}
{%- if v %}
<input type="hidden" name="{{ k|e }}" value="{{ v|e }}" />
{%- endif %}
{%- endfor %}
<table class="filterable">
{%- for field, _ in filters.iteritems() %}
{%- set action, value = _ %}
{%- set label, type = fields[field] %}
<tr class="filter_value">
{%- if type == 'bool' %}
<td class="field value" colspan="3">
<input type="hidden" name="{{ field }}_action" value="bool" />
<select name="{{ field }}_value">
<option {% if value == 'true' %}selected="selected" {% endif %}value="true">{% trans %}is{% endtrans %}</option>
<option {% if value == 'false' %}selected="selected" {% endif %}value="false">{% trans %}is not{% endtrans %}</option>
</select> {{ label|e }}
</td>
{%- else %}
<td class="field">{{ label|e }}</td>
<td class="filter">
<select name="{{ field }}_action">
{%- for k, v in actions[type].iteritems() %}:
<option value="{{ k }}"{% if k == action %} selected="selected"{% endif %}>
{{- v|e -}}
</option>
{%- endfor %}
</select>
</td>
<td class="value">
<input type="{{ fields[field][1] == 'date' and 'date' or 'text' }}"
name="{{ field }}_value" value="{{ value|e }}" />
</td>
{%- endif %}
<td class="action">
<button type="submit" name="remove_filter" value="{{ field }}">-</button>
</td>
</tr>
{%- endfor %}
<tr>
<td colspan="4">
<div class="add{% if not filters %} left{% endif %}">
{% trans %}new filter: {% endtrans %}<select name="new_filter">
<option></option>
{%- for k, v in fields.iteritems() %}
{%- if k not in filters %}
<option value="{{ k }}">{{ v[0]|e }}</option>
{%- endif %}
{%- endfor %}
</select>
<input type="submit" name="add_filter" value="{% trans %}ok{% endtrans %}" />
</div>
{%- if filters and not inline %}
<input type="submit" value="{% trans %}update{% endtrans %}">
{%- endif %}
</td>
</tr>
</table>
{% if not inline %}
</form>
{% endif %}
{% endmacro %}