Feature: Save posts + Better error handling
This commit is contained in:
parent
62003cb39d
commit
cda0b18c10
@ -11,4 +11,4 @@ migrate = Migrate(app, db)
|
|||||||
login = LoginManager(app)
|
login = LoginManager(app)
|
||||||
login.login_view = 'login'
|
login.login_view = 'login'
|
||||||
|
|
||||||
from app import routes, models
|
from app import routes, models, errors
|
||||||
|
16
app/errors.py
Normal file
16
app/errors.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
from flask import render_template
|
||||||
|
from app import app, db
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def not_found_error(error):
|
||||||
|
return render_template('404.html'), 404
|
||||||
|
|
||||||
|
@app.errorhandler(500)
|
||||||
|
def internal_error(error):
|
||||||
|
db.session.rollback()
|
||||||
|
return render_template('500.html'), 500
|
||||||
|
|
||||||
|
@app.errorhandler(405)
|
||||||
|
def internal_error(error):
|
||||||
|
db.session.rollback()
|
||||||
|
return render_template('405.html'), 405
|
@ -39,9 +39,10 @@ class User(UserMixin, db.Model):
|
|||||||
def following_list(self):
|
def following_list(self):
|
||||||
return self.followed.all()
|
return self.followed.all()
|
||||||
|
|
||||||
def followed_posts(self):
|
def saved_posts(self):
|
||||||
return "Soon.."
|
return Post.query.filter_by(user_id=self.id)
|
||||||
|
|
||||||
|
|
||||||
followed = db.relationship(
|
followed = db.relationship(
|
||||||
'User', secondary=followers,
|
'User', secondary=followers,
|
||||||
primaryjoin=(followers.c.follower_id == id),
|
primaryjoin=(followers.c.follower_id == id),
|
||||||
@ -70,8 +71,10 @@ class twitterPost():
|
|||||||
class Post(db.Model):
|
class Post(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
body = db.Column(db.String(140))
|
body = db.Column(db.String(140))
|
||||||
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
|
timestamp = db.Column(db.String(100))
|
||||||
|
url = db.Column(db.String(100), unique=True)
|
||||||
|
username = db.Column(db.String(24))
|
||||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<Post {}>'.format(self.body)
|
return '<Post {}>'.format(self.body)
|
@ -3,7 +3,7 @@ from flask import render_template, flash, redirect, url_for, request
|
|||||||
from app.forms import LoginForm, RegistrationForm, EmptyForm, SearchForm
|
from app.forms import LoginForm, RegistrationForm, EmptyForm, SearchForm
|
||||||
from requests_futures.sessions import FuturesSession
|
from requests_futures.sessions import FuturesSession
|
||||||
from concurrent.futures import as_completed
|
from concurrent.futures import as_completed
|
||||||
from app.models import User, twitterPost
|
from app.models import User, twitterPost, Post
|
||||||
from werkzeug.urls import url_parse
|
from werkzeug.urls import url_parse
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from flask import Markup
|
from flask import Markup
|
||||||
@ -80,10 +80,38 @@ def register():
|
|||||||
@app.route('/savePost/<url>', methods=['POST'])
|
@app.route('/savePost/<url>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def savePost(url):
|
def savePost(url):
|
||||||
print("SAVEPOST")
|
savedUrl = url.replace('~', '/')
|
||||||
print("Saved {}.".format(url.replace('~', '/')))
|
r = requests.get(savedUrl)
|
||||||
|
html = BeautifulSoup(str(r.content), "lxml")
|
||||||
|
post = html.body.find_all('div', attrs={'class':'tweet-content'})
|
||||||
|
|
||||||
|
newPost = Post()
|
||||||
|
newPost.url = savedUrl
|
||||||
|
newPost.body = html.body.find_all('div', attrs={'class':'main-tweet'})[0].find_all('div', attrs={'class':'tweet-content'})[0].text
|
||||||
|
newPost.username = html.body.find('a','username').text.replace("@","")
|
||||||
|
newPost.timestamp = html.body.find_all('p', attrs={'class':'tweet-published'})[0].text
|
||||||
|
newPost.user_id = current_user.id
|
||||||
|
try:
|
||||||
|
db.session.add(newPost)
|
||||||
|
db.session.commit()
|
||||||
|
except:
|
||||||
|
flash("Post could not be saved. Either it was already saved or there was an error.")
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
@app.route('/saved')
|
||||||
|
@login_required
|
||||||
|
def saved():
|
||||||
|
savedPosts = current_user.saved_posts().all()
|
||||||
|
return render_template('saved.html', title='Saved', savedPosts=savedPosts)
|
||||||
|
|
||||||
|
@app.route('/deleteSaved/<id>', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def deleteSaved(id):
|
||||||
|
savedPost = Post.query.filter_by(id=id).first()
|
||||||
|
db.session.delete(savedPost)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for('saved'))
|
||||||
|
|
||||||
@app.route('/follow/<username>', methods=['POST'])
|
@app.route('/follow/<username>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def follow(username):
|
def follow(username):
|
||||||
@ -109,7 +137,6 @@ def follow(username):
|
|||||||
else:
|
else:
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
|
||||||
@app.route('/unfollow/<username>', methods=['POST'])
|
@app.route('/unfollow/<username>', methods=['POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def unfollow(username):
|
def unfollow(username):
|
||||||
@ -177,9 +204,9 @@ def search():
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/notfound')
|
@app.route('/error/<errno>')
|
||||||
def notfound():
|
def error(errno):
|
||||||
return render_template('404.html')
|
return render_template('{}.html'.format(str(errno)))
|
||||||
|
|
||||||
@app.route('/user/<username>')
|
@app.route('/user/<username>')
|
||||||
@login_required
|
@login_required
|
||||||
@ -194,7 +221,7 @@ def user(username):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
elif not isTwitter and user is None:
|
elif not isTwitter and user is None:
|
||||||
return redirect(url_for('notfound'))
|
return redirect( url_for('error', errno="404"))
|
||||||
|
|
||||||
posts = []
|
posts = []
|
||||||
posts.extend(getPosts(username))
|
posts.extend(getPosts(username))
|
||||||
|
BIN
app/static/img/405.png
Normal file
BIN
app/static/img/405.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 64 KiB |
BIN
app/static/img/500.png
Normal file
BIN
app/static/img/500.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 45 KiB |
10
app/templates/405.html
Normal file
10
app/templates/405.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div style="margin-top: 2em;" class="ui one column centered grid">
|
||||||
|
<img class="ui medium circular image" src="{{ url_for('static',filename='img/405.png') }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin: 1.5em;" class="ui one column centered grid">
|
||||||
|
<h2 class="ui header">You are not allowed to do this!</h2>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
10
app/templates/500.html
Normal file
10
app/templates/500.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div style="margin-top: 2em;" class="ui one column centered grid">
|
||||||
|
<img class="ui medium circular image" src="{{ url_for('static',filename='img/500.png') }}">
|
||||||
|
</div>
|
||||||
|
<div style="margin: 1.5em;" class="ui one column centered grid">
|
||||||
|
<h2 class="ui header">Something went wrong... But it's not your fault!</h2>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
35
app/templates/_post_saved.html
Normal file
35
app/templates/_post_saved.html
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<div class="column"> <!--Start tweet-->
|
||||||
|
<div class="ui centered card">
|
||||||
|
<div class="content">
|
||||||
|
<div class="extra content">
|
||||||
|
<div class="left floated author">
|
||||||
|
<img class="ui avatar image" src="{{ url_for('static',filename='img/avatars/')}}{{range(1, 12) | random}}.png">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="header" id="author"><a href="user/{{post.username}}">@{{ post.username }}</a></div>
|
||||||
|
<div class="meta">
|
||||||
|
<span class="category" id="time"><i class="clock icon"></i> {{post.timestamp.replace("\xc2\xb7", "")}} </span>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
<p>{{post.body}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="extra center aligned content">
|
||||||
|
<p>
|
||||||
|
<form class="ui form" action="{{ url_for('deleteSaved', id=post.id) }}" method="post">
|
||||||
|
<a href="{{ post.url }}"><button type="button" class="ui blue left aligned icon button">
|
||||||
|
<i class="share icon"></i>
|
||||||
|
</button></a>
|
||||||
|
<button type="submit" class="ui red right aligned icon button">
|
||||||
|
<i class="trash icon"></i>
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!--newPost.url = savedUrl
|
||||||
|
newPost.body = html.body.find_all('div', attrs={'class':'main-tweet'})[0].find_all('div', attrs={'class':'tweet-content'})[0].text
|
||||||
|
newPost.username = html.body.find('a','username').text.replace("@","")
|
||||||
|
newPost.timestamp = html.body.find_all('p', attrs={'class':'tweet-published'})[0].text
|
||||||
|
newPost.user_id = current_user.id-->
|
||||||
|
</div>
|
||||||
|
</div> <!--End tweet-->
|
@ -21,7 +21,7 @@
|
|||||||
{% else %}
|
{% else %}
|
||||||
<a href="{{ url_for('search') }}" class="item">Search</a>
|
<a href="{{ url_for('search') }}" class="item">Search</a>
|
||||||
<a href="{{ url_for('following') }}" class="item">Following</a>
|
<a href="{{ url_for('following') }}" class="item">Following</a>
|
||||||
<a href="#" class="item">Saved</a>
|
<a href="{{ url_for('saved') }}" class="item">Saved</a>
|
||||||
<a href="{{ url_for('logout') }}" class="item">Logout</a>
|
<a href="{{ url_for('logout') }}" class="item">Logout</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
27
app/templates/saved.html
Normal file
27
app/templates/saved.html
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div style="padding: 1.3em;" class="ui one column centered grid">
|
||||||
|
<h2 class="ui blue header">
|
||||||
|
Your saved posts
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<hr>
|
||||||
|
<div class="ui one column grid" id="card-container">
|
||||||
|
{% if not savedPosts %}
|
||||||
|
<div style="margin-top: 2em;" class="ui one column centered grid">
|
||||||
|
<div style="margin: 1.5em;" class="ui row">
|
||||||
|
<img class="ui medium circular image" src="{{ url_for('static',filename='img/empty.png') }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="margin: 1.5em;" class="ui row">
|
||||||
|
<h2 class="ui header">No saved posts yet.</h2>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
{% for post in savedPosts %}
|
||||||
|
{% include '_post_saved.html' %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -1,4 +1,3 @@
|
|||||||
aiohttp==3.6.2
|
|
||||||
alembic==1.4.2
|
alembic==1.4.2
|
||||||
async-timeout==3.0.1
|
async-timeout==3.0.1
|
||||||
attrs==19.3.0
|
attrs==19.3.0
|
||||||
@ -19,7 +18,6 @@ Flask-WTF==0.14.3
|
|||||||
future==0.18.2
|
future==0.18.2
|
||||||
gevent==20.6.2
|
gevent==20.6.2
|
||||||
greenlet==0.4.16
|
greenlet==0.4.16
|
||||||
grequests==0.6.0
|
|
||||||
idna==2.10
|
idna==2.10
|
||||||
itsdangerous==1.1.0
|
itsdangerous==1.1.0
|
||||||
Jinja2==2.11.2
|
Jinja2==2.11.2
|
||||||
@ -29,6 +27,7 @@ Mako==1.1.3
|
|||||||
MarkupSafe==1.1.1
|
MarkupSafe==1.1.1
|
||||||
multidict==4.7.6
|
multidict==4.7.6
|
||||||
numpy==1.19.0
|
numpy==1.19.0
|
||||||
|
PyMySQL==0.9.3
|
||||||
python-dateutil==2.8.1
|
python-dateutil==2.8.1
|
||||||
python-dotenv==0.14.0
|
python-dotenv==0.14.0
|
||||||
python-editor==1.0.4
|
python-editor==1.0.4
|
||||||
|
Reference in New Issue
Block a user