Added Twitter to index page
This commit is contained in:
parent
57fd968fcc
commit
39eb088afc
8 changed files with 197 additions and 35 deletions
|
|
@ -34,3 +34,7 @@ app.register_module(snippets)
|
||||||
app.register_module(extensions)
|
app.register_module(extensions)
|
||||||
|
|
||||||
from flask_website.database import User, db_session
|
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
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,12 @@ blockquote { margin: 0; font-style: italic; color: #444; }
|
||||||
.message { background: #DEEBF3; color: #004B6B; padding: 5px 30px;
|
.message { background: #DEEBF3; color: #004B6B; padding: 5px 30px;
|
||||||
margin: 10px -30px; }
|
margin: 10px -30px; }
|
||||||
.actions { margin-top: 0; }
|
.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;
|
table { border: 1px solid black; border-collapse: collapse;
|
||||||
margin: 15px 0; }
|
margin: 15px 0; }
|
||||||
td, th { border: 1px solid black; padding: 4px 10px;
|
td, th { border: 1px solid black; padding: 4px 10px;
|
||||||
|
|
|
||||||
BIN
flask_website/static/twitter.png
Executable file
BIN
flask_website/static/twitter.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
|
|
@ -5,6 +5,7 @@
|
||||||
h1 { margin: 0 0 30px 0; background: url(/static/logo.png) no-repeat center; height: 165px; }
|
h1 { margin: 0 0 30px 0; background: url(/static/logo.png) no-repeat center; height: 165px; }
|
||||||
h1 span, p.tagline { display: none; }
|
h1 span, p.tagline { display: none; }
|
||||||
</style>
|
</style>
|
||||||
|
<link href="{{ tweets.feed_url }}" rel="alternate" title="Flask on Twitter" type="application/atom+xml">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block body_title %}
|
{% block body_title %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
|
|
@ -67,6 +68,22 @@ def hello():
|
||||||
create a new ticket or fork. If you just want to chat with fellow
|
create a new ticket or fork. If you just want to chat with fellow
|
||||||
developers, go to <code>#pocoo</code> on irc.freenode.net.
|
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>— {{ 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;"
|
<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>
|
src="http://s3.amazonaws.com/github/ribbons/forkme_right_gray_6d6d6d.png" alt="Fork me on GitHub"></a>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@
|
||||||
<dt>From:
|
<dt>From:
|
||||||
<dd class=from>{{ mail.author_name or mail.author_email }}
|
<dd class=from>{{ mail.author_name or mail.author_email }}
|
||||||
<dt>Date:
|
<dt>Date:
|
||||||
<dd>{{ mail.date.strftime('%Y-%m-%d @ %H:%M') }}
|
<dd>{{ mail.date|datetimeformat }}
|
||||||
</dl>
|
</dl>
|
||||||
<pre>{{ mail.rendered_text() }}</pre>
|
<pre>{{ mail.rendered_text() }}</pre>
|
||||||
{% if mail.children %}
|
{% if mail.children %}
|
||||||
|
|
|
||||||
91
flask_website/twitter.py
Normal file
91
flask_website/twitter.py
Normal 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
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import re
|
import re
|
||||||
import creoleparser
|
import creoleparser
|
||||||
|
from datetime import datetime, timedelta
|
||||||
from genshi import builder
|
from genshi import builder
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from creoleparser.elements import PreBlock
|
from creoleparser.elements import PreBlock
|
||||||
|
|
@ -17,6 +18,17 @@ pygments_formatter = HtmlFormatter(style=FlaskyStyle)
|
||||||
_ws_split_re = re.compile(r'(\s+)')
|
_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):
|
class CodeBlock(PreBlock):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
@ -94,3 +106,29 @@ def requires_admin(f):
|
||||||
abort(401)
|
abort(401)
|
||||||
return f(*args, **kwargs)
|
return f(*args, **kwargs)
|
||||||
return requires_login(decorated_function)
|
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''
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,20 @@
|
||||||
from flask import Module, render_template, session, redirect, url_for, \
|
from flask import Module, render_template, session, redirect, url_for, \
|
||||||
request, flash, g, Response
|
request, flash, g, Response
|
||||||
from flask_website import oid
|
from flask_website import oid, twitter
|
||||||
from flask_website.database import db_session, User
|
from flask_website.database import db_session, User
|
||||||
|
|
||||||
general = Module(__name__)
|
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('/')
|
@general.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('general/index.html')
|
return render_template('general/index.html', tweets=tweets)
|
||||||
|
|
||||||
|
|
||||||
@general.route('/logout/')
|
@general.route('/logout/')
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue