Added Twitter to index page

This commit is contained in:
Armin Ronacher 2010-05-16 01:06:37 +02:00
parent 57fd968fcc
commit 39eb088afc
8 changed files with 197 additions and 35 deletions

View file

@ -34,3 +34,7 @@ app.register_module(snippets)
app.register_module(extensions)
from flask_website.database import User, db_session
from flask_website import utils
app.jinja_env.filters['datetimeformat'] = utils.format_datetime
app.jinja_env.filters['timedeltaformat'] = utils.format_timedelta

View file

@ -1,38 +1,44 @@
body { font-family: 'Georgia', serif; font-size: 17px; color: #000; }
a { color: #004B6B; }
a:hover { color: #6D4100; }
.box { width: 540px; margin: 40px auto; }
h2, h3 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; }
h1 { margin: 0 0 30px 0; background: url(/static/flask.png) no-repeat center;
height: 165px; }
h1 span { display: none; }
h2 { font-size: 28px; margin: 15px 0 5px 0; }
h3 { font-size: 22px; margin: 15px 0 5px 0; }
body { font-family: 'Georgia', serif; font-size: 17px; color: #000; }
a { color: #004B6B; }
a:hover { color: #6D4100; }
.box { width: 540px; margin: 40px auto; }
h2, h3 { font-family: 'Garamond', 'Georgia', serif; font-weight: normal; }
h1 { margin: 0 0 30px 0; background: url(/static/flask.png) no-repeat center;
height: 165px; }
h1 span { display: none; }
h2 { font-size: 28px; margin: 15px 0 5px 0; }
h3 { font-size: 22px; margin: 15px 0 5px 0; }
textarea, code,
pre { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono',
monospace!important; font-size: 15px; background: #eee; }
pre { padding: 7px 30px; margin: 15px -30px; line-height: 1.3; }
.ig { color: #888; }
p { line-height: 1.4; }
ul { margin: 15px 0 15px 0; padding: 0; list-style: none; line-height: 1.4; }
pre { font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono',
monospace!important; font-size: 15px; background: #eee; }
pre { padding: 7px 30px; margin: 15px -30px; line-height: 1.3; }
.ig { color: #888; }
p { line-height: 1.4; }
ul { margin: 15px 0 15px 0; padding: 0; list-style: none; line-height: 1.4; }
ul li:before { content: "\00BB \0020"; color: #888; position: absolute; margin-left: -19px; }
ol { line-height: 1.4; margin: 15px 0 15px 30px; padding: 0; }
blockquote { margin: 0; font-style: italic; color: #444; }
.footer { font-size: 13px; color: #888; text-align: right; margin-top: 25px; }
.nav { text-align: center; }
.nav a { font-style: italic; }
.backnav { float: right; color: #444; font-style: italic;
margin: 5px 0 0 0; font-size: 0.9em; }
.message { background: #DEEBF3; color: #004B6B; padding: 5px 30px;
margin: 10px -30px; }
.actions { margin-top: 0; }
table { border: 1px solid black; border-collapse: collapse;
margin: 15px 0; }
td, th { border: 1px solid black; padding: 4px 10px;
text-align: left; }
th { background: #eee; font-weight: normal; }
ol { line-height: 1.4; margin: 15px 0 15px 30px; padding: 0; }
blockquote { margin: 0; font-style: italic; color: #444; }
.footer { font-size: 13px; color: #888; text-align: right; margin-top: 25px; }
.nav { text-align: center; }
.nav a { font-style: italic; }
.backnav { float: right; color: #444; font-style: italic;
margin: 5px 0 0 0; font-size: 0.9em; }
.message { background: #DEEBF3; color: #004B6B; padding: 5px 30px;
margin: 10px -30px; }
.actions { margin-top: 0; }
.twitter:before { background: url(twitter.png) no-repeat; content: " ";
float: right; width: 64px; height: 64px;
margin: -25px 0 0 0; padding: 0 0 10px 10px; }
.twitter li { margin: 15px 0; line-height: 1.2; font-size: 15px; }
.twitter li:before { content: "♯"; padding-left: 4px; }
.twitter .meta, .twitter .meta a { color: #888; font-size: 13px; }
table { border: 1px solid black; border-collapse: collapse;
margin: 15px 0; }
td, th { border: 1px solid black; padding: 4px 10px;
text-align: left; }
th { background: #eee; font-weight: normal; }
td input { border: none; padding: 0; }
td input { border: none; padding: 0; }
/* forms */
input, textarea, select { border: 1px solid black; padding: 2px; background: white;

BIN
flask_website/static/twitter.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View file

@ -5,6 +5,7 @@
h1 { margin: 0 0 30px 0; background: url(/static/logo.png) no-repeat center; height: 165px; }
h1 span, p.tagline { display: none; }
</style>
<link href="{{ tweets.feed_url }}" rel="alternate" title="Flask on Twitter" type="application/atom+xml">
{% endblock %}
{% block body_title %}
{{ super() }}
@ -67,6 +68,22 @@ def hello():
create a new ticket or fork. If you just want to chat with fellow
developers, go to <code>#pocoo</code> on irc.freenode.net.
{% if tweets %}
<h2>Recent Tweets</h2>
<div class=twitter>
<p>
What people say about Flask on <a href=http://twitter.com/>Twitter</a>:
<ul>
{%- for tweet in tweets %}
<li>
<a href="http://twitter.com/{{ tweet.user }}">{{ tweet.user }}</a>:
{{ tweet.text|urlize }}
<span class=meta>&mdash; {{ tweet.pub_date|timedeltaformat }} ago via {{ tweet.via }}</span>
{%- endfor %}
</ul>
</div>
{% endif %}
<a href="http://github.com/mitsuhiko/flask"><img style="position: fixed; top: 0; right: 0; border: 0;"
src="http://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
{% endblock %}

View file

@ -25,7 +25,7 @@
<dt>From:
<dd class=from>{{ mail.author_name or mail.author_email }}
<dt>Date:
<dd>{{ mail.date.strftime('%Y-%m-%d @ %H:%M') }}
<dd>{{ mail.date|datetimeformat }}
</dl>
<pre>{{ mail.rendered_text() }}</pre>
{% if mail.children %}

91
flask_website/twitter.py Normal file
View file

@ -0,0 +1,91 @@
from __future__ import with_statement
import urllib2
import time
import threading
from flask import json, Markup
from werkzeug import url_encode, parse_date
class SearchResult(object):
def __init__(self, result):
self.text = Markup(result['text']).unescape()
self.user = result['from_user']
self.via = Markup(Markup(result['source']).unescape())
self.pub_date = parse_date(result['created_at'])
self.profile_image = result['profile_image_url']
self.type = result['metadata']['result_type']
self.retweets = result['metadata'].get('recent_retweets') or 0
class SearchQuery(object):
fetch_timeout = 10
def __init__(self, required=None, optional=None, limit=5, timeout=60, lang=None):
self.required = set(x.lower() for x in (required or ()))
self.optional = set(x.lower() for x in (optional or ()))
self.limit = limit
self.lang = lang
self.timeout = timeout
self._last_fetch = 0
self._last_scheduled_fetch = 0
self._cached = None
@property
def query(self):
def _quote_if(x):
if len(x.split()) != 1:
return u'"%s"' % x
return x
q = u' '.join(map(_quote_if, self.required))
q += u' ' + u' OR '.join(map(_quote_if, self.optional))
return q
@property
def feed_url(self):
return self.get_url(kind='atom')
def get_url(self, kind='json'):
return 'http://search.twitter.com/search.%s?%s' % (kind, url_encode({
'q': self.query,
'result_type': 'mixed',
'lang': self.lang
}))
def fetch(self):
def _accept(text):
text = text.lower()
for word in self.required:
if word not in text:
return False
for word in self.optional:
if word in text:
return True
return False
rv = json.load(urllib2.urlopen(self.get_url()))
return [SearchResult(x) for x in rv['results'] if
_accept(x['text'])]
@property
def up_to_date(self):
return time.time() < self._last_fetch + self.timeout
def _try_refresh(self):
if self.up_to_date:
return
if time.time() > self._last_scheduled_fetch + self.fetch_timeout:
self._last_scheduled_fetch = time.time()
threading.Thread(target=self._fetch_and_store).start()
def _fetch_and_store(self):
self._cached = self.fetch()
self._last_fetch = time.time()
def __len__(self):
self._try_refresh()
return len(self._cached or ())
def __iter__(self):
self._try_refresh()
for item in (self._cached or ())[:self.limit]:
yield item

View file

@ -1,5 +1,6 @@
import re
import creoleparser
from datetime import datetime, timedelta
from genshi import builder
from functools import wraps
from creoleparser.elements import PreBlock
@ -17,6 +18,17 @@ pygments_formatter = HtmlFormatter(style=FlaskyStyle)
_ws_split_re = re.compile(r'(\s+)')
TIMEDELTA_UNITS = (
('year', 3600 * 24 * 365),
('month', 3600 * 24 * 30),
('week', 3600 * 24 * 7),
('day', 3600 * 24),
('hour', 3600),
('minute', 60),
('second', 1)
)
class CodeBlock(PreBlock):
def __init__(self):
@ -94,3 +106,29 @@ def requires_admin(f):
abort(401)
return f(*args, **kwargs)
return requires_login(decorated_function)
def format_datetime(dt):
return dt.strftime('%Y-%m-%d @ %H:%M')
def format_timedelta(delta, granularity='second', threshold=.85):
if isinstance(delta, datetime):
delta = datetime.utcnow() - delta
if isinstance(delta, timedelta):
seconds = int((delta.days * 86400) + delta.seconds)
else:
seconds = delta
for unit, secs_per_unit in TIMEDELTA_UNITS:
value = abs(seconds) / secs_per_unit
if value >= threshold or unit == granularity:
if unit == granularity and value > 0:
value = max(1, value)
value = str(int(round(value)))
value += u' ' + unit
if value != 1:
value += u's'
return value
return u''

View file

@ -1,14 +1,20 @@
from flask import Module, render_template, session, redirect, url_for, \
request, flash, g, Response
from flask_website import oid
from flask_website import oid, twitter
from flask_website.database import db_session, User
general = Module(__name__)
tweets = twitter.SearchQuery(required=['flask'],
optional=['code', 'web', 'python', 'py',
'pocoo', 'micro', 'mitsuhiko',
'framework', 'django', 'jinja',
'werkzeug', 'pylons'],
lang='en')
@general.route('/')
def index():
return render_template('general/index.html')
return render_template('general/index.html', tweets=tweets)
@general.route('/logout/')