code cleanups, added first "show private pastes only" implementation, added filterable interface to show_all view
This commit is contained in:
parent
6dd43db2e2
commit
17823ac36e
2
TODO
2
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
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -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]
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
80
lodgeit/lib/filterable.py
Normal 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
|
@ -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
|
||||
*/
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
2
lodgeit/views/utils/filterable.html
Normal file
2
lodgeit/views/utils/filterable.html
Normal file
@ -0,0 +1,2 @@
|
||||
{% from 'utils/macros.html' import filterable %}
|
||||
{{ filterable(filters, fields, actions, args, inline) }}
|
66
lodgeit/views/utils/macros.html
Normal file
66
lodgeit/views/utils/macros.html
Normal 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 %}
|
Loading…
x
Reference in New Issue
Block a user