add migration from users_to_feeds table to subscriptions table
This commit is contained in:
parent
3dad2d530e
commit
2e20ead09b
11 changed files with 183 additions and 99 deletions
74
migrations/versions/564f5a5061f_.py
Normal file
74
migrations/versions/564f5a5061f_.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
"""empty message
|
||||
|
||||
Revision ID: 564f5a5061f
|
||||
Revises: 5824a5f06dd
|
||||
Create Date: 2015-04-19 07:49:24.416817
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '564f5a5061f'
|
||||
down_revision = '5824a5f06dd'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from woodwind.models import JsonType
|
||||
|
||||
|
||||
def upgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
subsc = op.create_table(
|
||||
'subscription',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), sa.ForeignKey('user.id'), nullable=False),
|
||||
sa.Column('feed_id', sa.Integer(), sa.ForeignKey('feed.id'), nullable=False),
|
||||
sa.Column('name', sa.String(length=256), nullable=True),
|
||||
sa.Column('tags', JsonType(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('id')
|
||||
)
|
||||
|
||||
feed = sa.table(
|
||||
'feed',
|
||||
sa.column('id', sa.Integer()),
|
||||
sa.column('name', sa.String()))
|
||||
|
||||
u2f = sa.table(
|
||||
'users_to_feeds',
|
||||
sa.column('user_id', sa.Integer()),
|
||||
sa.column('feed_id', sa.Integer()))
|
||||
|
||||
values = sa.select(
|
||||
[u2f.c.user_id, u2f.c.feed_id, feed.c.name]
|
||||
).select_from(
|
||||
u2f.join(feed, feed.c.id == u2f.c.feed_id)
|
||||
)
|
||||
|
||||
op.execute(subsc.insert().from_select(
|
||||
['user_id', 'feed_id', 'name'], values))
|
||||
|
||||
op.drop_table('users_to_feeds')
|
||||
### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
### commands auto generated by Alembic - please adjust! ###
|
||||
u2f = op.create_table(
|
||||
'users_to_feeds',
|
||||
sa.Column('user_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.Column('feed_id', sa.INTEGER(), autoincrement=False, nullable=True),
|
||||
sa.ForeignKeyConstraint(['feed_id'], ['feed.id'], name='users_to_feeds_feed_id_fkey'),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['user.id'], name='users_to_feeds_user_id_fkey')
|
||||
)
|
||||
|
||||
subsc = sa.table(
|
||||
'subscription',
|
||||
sa.Column('user_id', sa.Integer()),
|
||||
sa.Column('feed_id', sa.Integer()),
|
||||
)
|
||||
|
||||
op.execute(u2f.insert().from_select(
|
||||
['user_id', 'feed_id'],
|
||||
sa.select([subsc.c.user_id, subsc.c.feed_id])))
|
||||
|
||||
op.drop_table('subscription')
|
||||
### end Alembic commands ###
|
|
@ -71,7 +71,6 @@ class User(db.Model):
|
|||
|
||||
class Feed(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
users = db.relationship(User, secondary='users_to_feeds', backref='feeds')
|
||||
# the name of this feed
|
||||
name = db.Column(db.String(256))
|
||||
# url that we subscribed to; periodically check if the feed url
|
||||
|
@ -108,12 +107,16 @@ class Feed(db.Model):
|
|||
|
||||
class Subscription(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
users = db.relationship(User, backref='subscriptions')
|
||||
user_id = db.Column(db.Integer, db.ForeignKey(User.id))
|
||||
feed_id = db.Column(db.Integer, db.ForeignKey(Feed.id))
|
||||
|
||||
# user-editable name of this subscribed feed
|
||||
name = db.Column(db.String(256))
|
||||
feed = db.relationship(Feed, backref='subscriptions')
|
||||
tags = db.Column(JsonType)
|
||||
|
||||
user = db.relationship(User, backref='subscriptions')
|
||||
feed = db.relationship(Feed, backref='subscriptions')
|
||||
|
||||
|
||||
class Entry(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
|
|
BIN
woodwind/static/open_in_new_window.png
Normal file
BIN
woodwind/static/open_in_new_window.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
BIN
woodwind/static/open_in_new_window2.png
Normal file
BIN
woodwind/static/open_in_new_window2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 396 B |
|
@ -405,7 +405,7 @@ article {
|
|||
article.reply-context {
|
||||
margin-bottom: 0;
|
||||
background-color: #f3f3f3; }
|
||||
article.reply-context img {
|
||||
article.reply-context .content img {
|
||||
max-height: 240px;
|
||||
max-width: 240px; }
|
||||
article div {
|
||||
|
@ -421,7 +421,7 @@ article {
|
|||
border-bottom: 1px solid #687d77;
|
||||
margin-bottom: 0.5em; }
|
||||
article header img {
|
||||
vertical-align: text-middle;
|
||||
vertical-align: middle;
|
||||
margin: inherit;
|
||||
display: inline;
|
||||
max-width: 1.2em;
|
||||
|
|
|
@ -76,8 +76,7 @@ article {
|
|||
&.reply-context {
|
||||
margin-bottom: 0;
|
||||
background-color: #f3f3f3;
|
||||
|
||||
img {
|
||||
.content img {
|
||||
max-height: 240px;
|
||||
max-width: 240px;
|
||||
}
|
||||
|
@ -100,7 +99,7 @@ article {
|
|||
|
||||
header {
|
||||
img {
|
||||
vertical-align: text-middle;
|
||||
vertical-align: middle;
|
||||
margin: inherit;
|
||||
display: inline;
|
||||
max-width: 1.2em;
|
||||
|
|
|
@ -234,19 +234,21 @@ def notify_feed_updated(session, feed, entries):
|
|||
entries = sorted(entries, key=lambda e: (e.retrieved, e.published),
|
||||
reverse=True)
|
||||
|
||||
for user in feed.users:
|
||||
with flask_app.test_request_context():
|
||||
flask_login.login_user(user, remember=True)
|
||||
message = json.dumps({
|
||||
'user': user.id,
|
||||
'feed': feed.id,
|
||||
'entries': [
|
||||
render_template('_entry.jinja2', feed=feed, entry=e)
|
||||
for e in entries
|
||||
],
|
||||
})
|
||||
for topic in 'user:{}'.format(user.id), 'feed:{}'.format(feed.id):
|
||||
redis.publish('woodwind_notify:{}'.format(topic), message)
|
||||
for s in feed.subscriptions:
|
||||
for user in s.users:
|
||||
with flask_app.test_request_context():
|
||||
flask_login.login_user(user, remember=True)
|
||||
message = json.dumps({
|
||||
'user': user.id,
|
||||
'feed': feed.id,
|
||||
'entries': [
|
||||
render_template('_entry.jinja2', feed=feed, entry=e)
|
||||
for e in entries
|
||||
],
|
||||
})
|
||||
for topic in ('user={}'.format(user.id),
|
||||
'user={}&feed={}'.format(user.id, feed.id)):
|
||||
redis.publish('woodwind_notify:{}'.format(topic), message)
|
||||
|
||||
|
||||
def is_content_equal(e1, e2):
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<h1>{{ context.title }}</h1>
|
||||
{% endif %}
|
||||
{% if context.content %}
|
||||
<div>
|
||||
<div class="content">
|
||||
{{ context.content_cleaned | add_preview }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -42,7 +42,7 @@
|
|||
<h1>{{ entry.title }}</h1>
|
||||
{% endif %}
|
||||
{% if entry.content %}
|
||||
<div>
|
||||
<div class="content">
|
||||
{{ entry.content_cleaned | add_preview }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
@ -50,7 +50,9 @@
|
|||
|
||||
<footer>
|
||||
<a href="{{ entry.permalink }}">{{ entry.published | relative_time }}</a>
|
||||
<a href="{{ url_for('.index', entry=entry.permalink) }}" target="_blank">⇗</a>
|
||||
<a href="{{ url_for('.index', entry=entry.permalink) }}" target="_blank">
|
||||
<img src="{{ url_for('static', filename='open_in_new_window2.png') }}" style="vertical-align:middle;" />
|
||||
</a>
|
||||
|
||||
{% if entry._syndicated_copies %}
|
||||
(also on{% for copy in entry._syndicated_copies %} <a href="{{ copy.permalink }}">{{ copy.permalink | domain_for_url }}</a>{% endfor %})
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
<a href="{{ url_for('.index') }}">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('.feeds') }}">Feeds</a>
|
||||
<a href="{{ url_for('.subscriptions') }}">Subscriptions</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="{{ url_for('.settings') }}">Settings</a>
|
||||
|
|
|
@ -9,34 +9,38 @@
|
|||
{% endblock header %}
|
||||
|
||||
{% block body %}
|
||||
<p>
|
||||
<form action="{{ url_for('.update_all') }}" method="POST">
|
||||
<button type="submit">Poll All</button>
|
||||
</form>
|
||||
</p>
|
||||
|
||||
{% for feed in feeds %}
|
||||
<article>
|
||||
<article>
|
||||
{{ subscriptions | count }} subscriptions
|
||||
|
||||
<form action="{{ url_for('.update_all') }}" method="POST">
|
||||
<button type="submit">Poll All</button>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
{% for s in subscriptions %}
|
||||
|
||||
<article>
|
||||
<div>
|
||||
<a href="{{ url_for('.index', feed=feed.get_feed_code()) }}">View only posts from this feed</a>
|
||||
<a href="{{ url_for('.index', subscription=s.id) }}">View only posts from this feed</a>
|
||||
</div>
|
||||
|
||||
<form action="{{ url_for('.edit_feed') }}" method="POST">
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
<form action="{{ url_for('.edit_subscription') }}" method="POST">
|
||||
<input type="hidden" name="id" value="{{ s.id }}"/>
|
||||
<label>Name</label>
|
||||
<input type="text" name="name" value="{{ feed.name }}"/>
|
||||
<input type="text" name="name" value="{{ s.name }}"/>
|
||||
<label>URL</label>
|
||||
<input type="text" name="feed" value="{{ feed.feed }}"/>
|
||||
<input type="text" name="feed" value="{{ s.feed.feed }}" editable="false" />
|
||||
<button type="submit">Save Edits</button>
|
||||
</form>
|
||||
|
||||
|
||||
<form action="{{ url_for('.update_feed') }}" method="POST">
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
<input type="hidden" name="id" value="{{ s.feed.id }}"/>
|
||||
<button type="submit">Poll Now</button>
|
||||
</form>
|
||||
|
||||
<form action="{{ url_for('.unsubscribe_feed') }}" method="POST">
|
||||
<input type="hidden" name="id" value="{{ feed.id }}"/>
|
||||
|
||||
<form action="{{ url_for('.unsubscribe') }}" method="POST">
|
||||
<input type="hidden" name="id" value="{{ s.id }}"/>
|
||||
<button type="submit">Unsubscribe</button>
|
||||
</form>
|
||||
|
||||
|
@ -46,13 +50,13 @@
|
|||
<div class="feed-details" id="details-{{loop.index}}">
|
||||
<strong>Details</strong>
|
||||
<ul>
|
||||
<li>Last checked: {{feed.last_checked | relative_time}}</li>
|
||||
<li>Last updated: {{feed.last_updated | relative_time}}</li>
|
||||
<li>PuSH hub: {{feed.push_hub}}</li>
|
||||
<li>PuSH topic: {{feed.push_topic}}</li>
|
||||
<li>PuSH verified: {{feed.push_verified}}</li>
|
||||
<li>PuSH last ping: {{feed.last_pinged | relative_time}}</li>
|
||||
<li>PuSH expiry: {{feed.push_expiry | relative_time}}</li>
|
||||
<li>Last checked: {{s.feed.last_checked | relative_time}}</li>
|
||||
<li>Last updated: {{s.feed.last_updated | relative_time}}</li>
|
||||
<li>PuSH hub: {{s.feed.push_hub}}</li>
|
||||
<li>PuSH topic: {{s.feed.push_topic}}</li>
|
||||
<li>PuSH verified: {{s.feed.push_verified}}</li>
|
||||
<li>PuSH last ping: {{s.feed.last_pinged | relative_time}}</li>
|
||||
<li>PuSH expiry: {{s.feed.push_expiry | relative_time}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
|
@ -1,6 +1,6 @@
|
|||
from . import tasks
|
||||
from .extensions import db, login_mgr, micropub
|
||||
from .models import Feed, Entry, User
|
||||
from .models import Feed, Entry, User, Subscription
|
||||
import flask.ext.login as flask_login
|
||||
import binascii
|
||||
import bs4
|
||||
|
@ -28,13 +28,13 @@ def index():
|
|||
if flask_login.current_user.is_authenticated():
|
||||
per_page = flask.current_app.config.get('PER_PAGE', 30)
|
||||
offset = (page - 1) * per_page
|
||||
entry_query = Entry.query
|
||||
|
||||
entry_query = entry_query\
|
||||
entry_query = Entry.query\
|
||||
.options(sqlalchemy.orm.subqueryload(Entry.feed),
|
||||
sqlalchemy.orm.subqueryload(Entry.reply_context))\
|
||||
.join(Entry.feed)\
|
||||
.join(Feed.users)\
|
||||
.join(Feed.subscriptions)\
|
||||
.join(Subscription.user)\
|
||||
.filter(User.id == flask_login.current_user.id)
|
||||
|
||||
if 'entry' in flask.request.args:
|
||||
|
@ -47,17 +47,16 @@ def index():
|
|||
entries = [entry]
|
||||
has_older = False
|
||||
else:
|
||||
if 'feed' in flask.request.args:
|
||||
#feed_hex = flask.request.args.get('feed').encode()
|
||||
#feed_url = binascii.unhexlify(feed_hex).decode('utf-8')
|
||||
feed_url = flask.request.args.get('feed')
|
||||
feed = Feed.query.filter_by(feed=feed_url).first()
|
||||
if not feed:
|
||||
if 'subscription' in flask.request.args:
|
||||
subsc_id = flask.request.args.get('subscription')
|
||||
subsc = Subscription.query.get(subsc_id)
|
||||
if not subsc:
|
||||
flask.abort(404)
|
||||
entry_query = entry_query.filter(Feed.feed == feed_url)
|
||||
ws_topic = 'feed:{}'.format(feed.id)
|
||||
entry_query = entry_query.filter(Subscription.id == subsc_id)
|
||||
ws_topic = 'user={}&feed={}'.format(subsc.user.id,
|
||||
subsc.feed.id)
|
||||
else:
|
||||
ws_topic = 'user:{}'.format(flask_login.current_user.id)
|
||||
ws_topic = 'user={}'.format(flask_login.current_user.id)
|
||||
|
||||
entries = entry_query.order_by(Entry.retrieved.desc(),
|
||||
Entry.published.desc())\
|
||||
|
@ -74,12 +73,13 @@ def install():
|
|||
return 'Success!'
|
||||
|
||||
|
||||
@views.route('/feeds')
|
||||
@views.route('/subscriptions')
|
||||
@flask_login.login_required
|
||||
def feeds():
|
||||
feeds = flask_login.current_user.feeds
|
||||
sorted_feeds = sorted(feeds, key=lambda f: f.name and f.name.lower())
|
||||
return flask.render_template('feeds.jinja2', feeds=sorted_feeds)
|
||||
def subscriptions():
|
||||
subscs = flask_login.current_user.subscriptions
|
||||
sorted_subscs = sorted(subscs, key=lambda s: s.name and s.name.lower())
|
||||
return flask.render_template('subscriptions.jinja2',
|
||||
subscriptions=sorted_subscs)
|
||||
|
||||
|
||||
@views.route('/settings', methods=['GET', 'POST'])
|
||||
|
@ -114,47 +114,44 @@ def settings():
|
|||
def update_feed():
|
||||
feed_id = flask.request.form.get('id')
|
||||
tasks.q.enqueue(tasks.update_feed, feed_id)
|
||||
return flask.redirect(flask.url_for('.feeds'))
|
||||
return flask.redirect(flask.url_for('.subscriptions'))
|
||||
|
||||
|
||||
@views.route('/update_all', methods=['POST'])
|
||||
@flask_login.login_required
|
||||
def update_all():
|
||||
for feed in flask_login.current_user.feeds:
|
||||
tasks.q.enqueue(tasks.update_feed, feed.id)
|
||||
return flask.redirect(flask.url_for('.feeds'))
|
||||
for s in flask_login.current_user.subscriptions:
|
||||
tasks.q.enqueue(tasks.update_feed, s.feed.id)
|
||||
return flask.redirect(flask.url_for('.subscriptions'))
|
||||
|
||||
|
||||
@views.route('/unsubscribe_feed', methods=['POST'])
|
||||
@views.route('/unsubscribe', methods=['POST'])
|
||||
@flask_login.login_required
|
||||
def unsubscribe_feed():
|
||||
feed_id = flask.request.form.get('id')
|
||||
feed = Feed.query.get(feed_id)
|
||||
def unsubscribe():
|
||||
subsc_id = flask.request.form.get('id')
|
||||
subsc = Subscription.query.get(subsc_id)
|
||||
subsc.delete()
|
||||
db.session.commit()
|
||||
flask.flash('Unsubscribed {} ({})'.format(subsc.name))
|
||||
return flask.redirect(flask.url_for('.subscriptions'))
|
||||
|
||||
feeds = flask_login.current_user.feeds
|
||||
feeds.remove(feed)
|
||||
|
||||
@views.route('/edit_subscription', methods=['POST'])
|
||||
@flask_login.login_required
|
||||
def edit_subscription():
|
||||
subsc_id = flask.request.form.get('id')
|
||||
subsc_name = flask.request.form.get('name')
|
||||
#feed_url = flask.request.form.get('feed')
|
||||
|
||||
subsc = Subscription.query.get(subsc_id)
|
||||
if subsc_name:
|
||||
subsc.name = subsc_name
|
||||
#if feed_url:
|
||||
# feed.feed = feed_url
|
||||
|
||||
db.session.commit()
|
||||
flask.flash('Unsubscribed {} ({})'.format(feed.name, feed.feed))
|
||||
return flask.redirect(flask.url_for('.feeds'))
|
||||
|
||||
|
||||
@views.route('/edit_feed', methods=['POST'])
|
||||
@flask_login.login_required
|
||||
def edit_feed():
|
||||
feed_id = flask.request.form.get('id')
|
||||
feed_name = flask.request.form.get('name')
|
||||
feed_url = flask.request.form.get('feed')
|
||||
|
||||
feed = Feed.query.get(feed_id)
|
||||
if feed_name:
|
||||
feed.name = feed_name
|
||||
if feed_url:
|
||||
feed.feed = feed_url
|
||||
|
||||
db.session.commit()
|
||||
flask.flash('Edited {} ({})'.format(feed.name, feed.feed))
|
||||
return flask.redirect(flask.url_for('.feeds'))
|
||||
flask.flash('Edited {} ({})'.format(subsc.name))
|
||||
return flask.redirect(flask.url_for('.subscriptions'))
|
||||
|
||||
|
||||
@views.route('/logout')
|
||||
|
@ -289,7 +286,7 @@ def subscribe():
|
|||
return flask.render_template('subscribe.jinja2')
|
||||
|
||||
|
||||
def add_subscription(origin, feed_url, type):
|
||||
def add_subscription(origin, feed_url, type, tags=['stream']):
|
||||
feed = Feed.query.filter_by(feed=feed_url, type=type).first()
|
||||
if not feed:
|
||||
if type == 'html':
|
||||
|
@ -308,7 +305,10 @@ def add_subscription(origin, feed_url, type):
|
|||
origin=origin, feed=feed_url, type=type)
|
||||
if feed:
|
||||
db.session.add(feed)
|
||||
flask_login.current_user.feeds.append(feed)
|
||||
|
||||
flask_login.current_user.subscriptions.append(
|
||||
Subscription(feed=feed, name=feed.name, tags=tags))
|
||||
|
||||
db.session.commit()
|
||||
# go ahead and update the fed
|
||||
tasks.q.enqueue(tasks.update_feed, feed.id)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue