Big rework of Flask and database with new login system
14
app/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
from flask import Flask
|
||||||
|
from config import Config
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
from flask_migrate import Migrate
|
||||||
|
from flask_login import LoginManager
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config.from_object(Config)
|
||||||
|
db = SQLAlchemy(app)
|
||||||
|
migrate = Migrate(app, db)
|
||||||
|
login = LoginManager(app)
|
||||||
|
login.login_view = "login"
|
||||||
|
|
||||||
|
from app import routes, models
|
28
app/forms.py
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import StringField, PasswordField, BooleanField, SubmitField
|
||||||
|
from wtforms.validators import ValidationError, DataRequired, Email, EqualTo
|
||||||
|
from app.models import User
|
||||||
|
|
||||||
|
class LoginForm(FlaskForm):
|
||||||
|
username = StringField("Username", validators=[DataRequired()])
|
||||||
|
password = PasswordField("Password", validators=[DataRequired()])
|
||||||
|
remember_me = BooleanField("Remember Me")
|
||||||
|
submit = SubmitField("Sign In")
|
||||||
|
|
||||||
|
class RegistrationForm(FlaskForm):
|
||||||
|
username = StringField("Username", validators=[DataRequired()])
|
||||||
|
email = StringField("Email", validators=[DataRequired(), Email()])
|
||||||
|
password = PasswordField("Password", validators=[DataRequired()])
|
||||||
|
password2 = PasswordField(
|
||||||
|
"Repeat Password", validators=[DataRequired(), EqualTo("password")])
|
||||||
|
submit = SubmitField("Register")
|
||||||
|
|
||||||
|
def validate_username(self, username):
|
||||||
|
user = User.query.filter_by(username=username.data).first()
|
||||||
|
if user is not None:
|
||||||
|
raise ValidationError("Please use a different username.")
|
||||||
|
|
||||||
|
def validate_email(self, email):
|
||||||
|
user = User.query.filter_by(email=email.data).first()
|
||||||
|
if user is not None:
|
||||||
|
raise ValidationError("Please use a different email address.")
|
36
app/models.py
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
from app import db
|
||||||
|
from werkzeug.security import generate_password_hash, check_password_hash
|
||||||
|
from flask_login import UserMixin
|
||||||
|
from app import login
|
||||||
|
|
||||||
|
class User(UserMixin, db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True, unique=True)
|
||||||
|
username = db.Column(db.String(64), index=True, unique=True)
|
||||||
|
email = db.Column(db.String(120), index=True, unique=True)
|
||||||
|
password_hash = db.Column(db.String(128))
|
||||||
|
twitter_api = db.relationship("TwitterAPI", backref="user", lazy="dynamic")
|
||||||
|
trello_api = db.relationship('TrelloAPI', backref="user", lazy="dynamic")
|
||||||
|
|
||||||
|
def set_password(self, password):
|
||||||
|
self.password_hash = generate_password_hash(password)
|
||||||
|
|
||||||
|
def check_password(self, password):
|
||||||
|
return check_password_hash(self.password_hash, password)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<User {self.username}>"
|
||||||
|
|
||||||
|
class TwitterAPI(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True, unique=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), unique=True)
|
||||||
|
access_token = db.Column(db.String(50), unique=True)
|
||||||
|
access_token_secret = db.Column(db.String(45), unique=True)
|
||||||
|
|
||||||
|
class TrelloAPI(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True, unique=True)
|
||||||
|
user_id = db.Column(db.Integer, db.ForeignKey("user.id"), unique=True)
|
||||||
|
token = db.Column(db.String(64), unique=True)
|
||||||
|
|
||||||
|
@login.user_loader
|
||||||
|
def load_user(id):
|
||||||
|
return User.query.get(int(id))
|
165
app/routes.py
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
from app import app, db
|
||||||
|
from flask import Flask, request, redirect, session, render_template, flash, url_for
|
||||||
|
from flask_login import current_user, login_user, logout_user, login_required
|
||||||
|
from app.forms import LoginForm, RegistrationForm
|
||||||
|
from app.models import User, TwitterAPI, TrelloAPI
|
||||||
|
from werkzeug.urls import url_parse
|
||||||
|
import twitter_credentials, tweepy, tw
|
||||||
|
import trello_credentials, trello, tr
|
||||||
|
|
||||||
|
@app.route("/login", methods=["GET", "POST"])
|
||||||
|
def login():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for("home"))
|
||||||
|
form = LoginForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User.query.filter_by(username=form.username.data).first()
|
||||||
|
if user is None or not user.check_password(form.password.data):
|
||||||
|
flash("Invalid username or password")
|
||||||
|
return redirect(url_for("login"))
|
||||||
|
login_user(user, remember=form.remember_me.data)
|
||||||
|
next_page = request.args.get("next")
|
||||||
|
if not next_page or url_parse(next_page).netloc != "":
|
||||||
|
next_page = url_for("home")
|
||||||
|
return redirect(next_page)
|
||||||
|
return render_template("login.html", form=form)
|
||||||
|
|
||||||
|
@app.route("/register", methods=["GET", "POST"])
|
||||||
|
def register():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
return redirect(url_for("index"))
|
||||||
|
form = RegistrationForm()
|
||||||
|
if form.validate_on_submit():
|
||||||
|
user = User(username=form.username.data, email=form.email.data)
|
||||||
|
user.set_password(form.password.data)
|
||||||
|
db.session.add(user)
|
||||||
|
db.session.commit()
|
||||||
|
flash("Congratulations, you are now a registered user!")
|
||||||
|
return redirect(url_for("login"))
|
||||||
|
return render_template("register.html", form=form)
|
||||||
|
|
||||||
|
@app.route("/logout")
|
||||||
|
def logout():
|
||||||
|
logout_user()
|
||||||
|
return redirect(url_for("home"))
|
||||||
|
|
||||||
|
@app.route("/twlogin")
|
||||||
|
@login_required
|
||||||
|
def twlogin():
|
||||||
|
auth = tweepy.OAuthHandler(twitter_credentials.consumer_key, twitter_credentials.consumer_secret_key, "https://cyberplanificateur.flifloo.fr/twlogin")
|
||||||
|
if request.args.get("oauth_token") and request.args.get("oauth_verifier"):
|
||||||
|
auth.request_token = {"oauth_token" : request.args.get("oauth_token"), "oauth_token_secret" : request.args.get("oauth_verifier")}
|
||||||
|
try:
|
||||||
|
auth.get_access_token(request.args.get("oauth_verifier"))
|
||||||
|
except:
|
||||||
|
return "Error ! Failed to get access token"
|
||||||
|
else:
|
||||||
|
twapi = TwitterAPI(access_token = auth.access_token, access_token_secret = auth.access_token_secret, user = current_user)
|
||||||
|
db.session.add(twapi)
|
||||||
|
db.session.commit()
|
||||||
|
elif not TwitterAPI.query.filter_by(user=current_user).first():
|
||||||
|
return redirect(auth.get_authorization_url())
|
||||||
|
return redirect(url_for("settings"))
|
||||||
|
|
||||||
|
@app.route("/twlogout")
|
||||||
|
@login_required
|
||||||
|
def twlogout():
|
||||||
|
twapi = TwitterAPI.query.filter_by(user=current_user).first()
|
||||||
|
if twapi:
|
||||||
|
db.session.delete(twapi)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("settings"))
|
||||||
|
|
||||||
|
@app.route("/trlogin")
|
||||||
|
@login_required
|
||||||
|
def trlogin():
|
||||||
|
return redirect(f"https://trello.com/1/authorize?expiration=never&name=Cyberplanificateur&scope=read,write&response_type=token&key={trello_credentials.api_key}&return_url=https://cyberplanificateur.flifloo.fr/settings")
|
||||||
|
|
||||||
|
@app.route("/trlogout")
|
||||||
|
@login_required
|
||||||
|
def trlogout():
|
||||||
|
trapi = TrelloAPI.query.filter_by(user=current_user).first()
|
||||||
|
if trapi:
|
||||||
|
db.session.delete(trapi)
|
||||||
|
db.session.commit()
|
||||||
|
return redirect(url_for("settings"))
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def home():
|
||||||
|
return render_template("index.html")
|
||||||
|
|
||||||
|
@app.route("/settings", methods = ["POST", "GET"])
|
||||||
|
@login_required
|
||||||
|
def settings():
|
||||||
|
trloginfail = False
|
||||||
|
if "trtoken" in request.form:
|
||||||
|
try:
|
||||||
|
trello.TrelloClient(api_key = trello_credentials.api_key, token = request.form["trtoken"]).list_boards()
|
||||||
|
except:
|
||||||
|
trloginfail = True
|
||||||
|
else:
|
||||||
|
trapi = TrelloAPI(token = request.form["trtoken"], user = current_user)
|
||||||
|
db.session.add(trapi)
|
||||||
|
db.session.commit()
|
||||||
|
return render_template("settings.html", twlogin = TwitterAPI.query.filter_by(user=current_user).first(), trlogin = TrelloAPI.query.filter_by(user=current_user).first(), trloginfail = trloginfail)
|
||||||
|
|
||||||
|
@app.route("/dashboard", methods = ["POST", "GET"])
|
||||||
|
def dashboard():
|
||||||
|
if not tw.is_login(session):
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
twapi = tw.api_login(session)
|
||||||
|
if request.args.get("twrm"):
|
||||||
|
database.twrm(request.args.get("twrm"))
|
||||||
|
if request.args.get("delet"):
|
||||||
|
twapi.destroy_status(request.args.get["twrm"])
|
||||||
|
elif "tweet" in request.form and "slots" in request.form and "keywords" in request.form:
|
||||||
|
try:
|
||||||
|
slots = int(request.form["slots"])
|
||||||
|
tweet = int(request.form["tweet"])
|
||||||
|
except:
|
||||||
|
formerror = True
|
||||||
|
else:
|
||||||
|
database.twadd(twapi.me().id, tweet, slots, str(request.form["keywords"].split(",")))
|
||||||
|
formerror = False
|
||||||
|
|
||||||
|
tweets = list()
|
||||||
|
idtake = list()
|
||||||
|
for t in database.twlist(twapi.me().id):
|
||||||
|
tw = twapi.get_status(t["id"])
|
||||||
|
keywords = "|"
|
||||||
|
for te in eval(t["keywords"]):
|
||||||
|
keywords += f" {te} |"
|
||||||
|
tweets.append({"text": tw.text, "id": tw.id, "slots": f"{t['slots']}/{t['maxslots']}", "keywords": keywords})
|
||||||
|
idtake.append(tw.id)
|
||||||
|
|
||||||
|
timeline = list()
|
||||||
|
for t in twapi.user_timeline():
|
||||||
|
if not t.in_reply_to_status_id and not t.retweeted and t.id not in idtake:
|
||||||
|
timeline.append({"text": t.text, "id": t.id})
|
||||||
|
|
||||||
|
if tr.is_login(session):
|
||||||
|
trapi = trello.TrelloClient(api_key = trello_credentials.api_key, token = database.trtoken(twapi.me().id))
|
||||||
|
boards = list()
|
||||||
|
for b in trapi.list_boards():
|
||||||
|
boards.append(b.name)
|
||||||
|
|
||||||
|
|
||||||
|
return render_template("dashboard.html", login = True, tweets = tweets, timeline = timeline, boards = boards, columns = None)
|
||||||
|
|
||||||
|
@app.route("/twtoken")
|
||||||
|
def twtoken():
|
||||||
|
return f"{session['access_token']} -- {session['access_secret_token']}"
|
||||||
|
|
||||||
|
@app.route("/twpost")
|
||||||
|
def twpost():
|
||||||
|
if tw.is_login(session):
|
||||||
|
api = twapi_login(session)
|
||||||
|
api.update_status("bloup")
|
||||||
|
return "Send !"
|
||||||
|
else:
|
||||||
|
return "Not login !"
|
||||||
|
|
||||||
|
@app.route("/test")
|
||||||
|
def test():
|
||||||
|
return render_template("elements.html")
|
Before Width: | Height: | Size: 401 B After Width: | Height: | Size: 401 B |
Before Width: | Height: | Size: 396 B After Width: | Height: | Size: 396 B |
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 357 B |
Before Width: | Height: | Size: 434 KiB After Width: | Height: | Size: 434 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
96
app/templates/dashboard.html
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
{% extends "template.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
<article id="main">
|
||||||
|
<header>
|
||||||
|
<h2>Dashboard</h2>
|
||||||
|
<p>Your personal dashboard</p>
|
||||||
|
</header>
|
||||||
|
<section class="wrapper style5">
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<h3>Current automatized tweets</h3>
|
||||||
|
<hr />
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="align-center">Tweet</th>
|
||||||
|
<th class="align-center">Slots</th>
|
||||||
|
<th class="align-center">Keywords</th>
|
||||||
|
<th class="align-center">Actions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for t in tweets %}
|
||||||
|
<tr>
|
||||||
|
<td><blockquote>{{ t['text'] }}</blockquote></td>
|
||||||
|
<td class="align-center">{{ t['slots'] }}</td>
|
||||||
|
<td class="align-center">{{ t['keywords'] }}</td>
|
||||||
|
<td class="align-right"><a href="https://cyberplanificateur.flifloo.fr/dashboard?twrm={{ t['id']}}" class="button primary">Disable</a>
|
||||||
|
<a href="https://cyberplanificateur.flifloo.fr/dashboard?twrm={{ t['id']}}&delet=True" class="button">Delet</a></td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h3>Add an automatized tweet</h3>
|
||||||
|
<hr />
|
||||||
|
<form action = "https://cyberplanificateur.flifloo.fr/dashboard" method = "POST">
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 col-12-medium">
|
||||||
|
{% for t in timeline %}
|
||||||
|
<div class="col-4 col-12-small">
|
||||||
|
<input type="radio" id="{{ t['id'] }}" name="tweet" value="{{ t['id'] }}">
|
||||||
|
<label for="{{ t['id'] }}">{{ t['text'] }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-12-medium">
|
||||||
|
<input type="text" name="slots" placeholder="Slots number" />
|
||||||
|
<input type="text" name="keywords" placeholder="Keywords (separate with a comma)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<input type="submit" value="Connect" class="button primary">
|
||||||
|
</form>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<h3>Trello board</h3>
|
||||||
|
<hr />
|
||||||
|
<section>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-6 col-12-medium">
|
||||||
|
<h4>Choose a board</h4>
|
||||||
|
<form action="https://cyberplanificateur.flifloo.fr/dashboard" method="POST">
|
||||||
|
{% for b in boards %}
|
||||||
|
<div class="col-4 col-12-small">
|
||||||
|
<input type="radio" id="{{ b['id'] }}" name="board" value="{{ b['id'] }}">
|
||||||
|
<label for="{{ b['id'] }}">{{ b['text'] }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="submit" value="Select" class="button primary">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="col-6 col-12-medium">
|
||||||
|
<h4>Choose a column</h4>
|
||||||
|
<form action="https://cyberplanificateur.flifloo.fr/dashboard" method="POST">
|
||||||
|
{% for c in columns %}
|
||||||
|
<div class="col-4 col-12-small">
|
||||||
|
<input type="radio" id="{{ c['id'] }}" name="column" value="{{ c['id'] }}">
|
||||||
|
<label for="{{ c['id'] }}">{{ c['text'] }}</label>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<input type="submit" value="Select" class="button primary">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -7,10 +7,10 @@
|
||||||
<h2>Cyberplanificateur</h2>
|
<h2>Cyberplanificateur</h2>
|
||||||
<p>Manage your commissions was never so easy with with the Cyberplanificateur</p>
|
<p>Manage your commissions was never so easy with with the Cyberplanificateur</p>
|
||||||
<ul class="actions special">
|
<ul class="actions special">
|
||||||
{% if login %}
|
{% if current_user.is_authenticated %}
|
||||||
<li><a href="{{ url_for('dashboard') }}" class="button primary">Dashboard</a></li>
|
<li><a href="{{ url_for('dashboard') }}" class="button primary">Dashboard</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{{ url_for('twlogin') }}" class="button primary">Login !</a></li>
|
<li><a href="{{ url_for('login') }}" class="button primary">Login !</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
31
app/templates/login.html
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{% extends "template.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
<article id="main">
|
||||||
|
<header>
|
||||||
|
<h2>Sign In</h2>
|
||||||
|
</header>
|
||||||
|
<section class="wrapper style5">
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<form action="" method="post" novalidate>
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<p>
|
||||||
|
{{ form.username.label }}<br>
|
||||||
|
{{ form.username(size=32) }}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ form.password.label }}<br>
|
||||||
|
{{ form.password(size=32) }}
|
||||||
|
</p>
|
||||||
|
<p>{{ form.remember_me() }} {{ form.remember_me.label }}</p>
|
||||||
|
<p>{{ form.submit() }}</p>
|
||||||
|
</form>
|
||||||
|
<p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{% endblock %}
|
49
app/templates/register.html
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
{% extends "template.html" %}
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Main -->
|
||||||
|
<article id="main">
|
||||||
|
<header>
|
||||||
|
<h2>Register</h2>
|
||||||
|
</header>
|
||||||
|
<section class="wrapper style5">
|
||||||
|
<div class="inner">
|
||||||
|
|
||||||
|
<form action="" method="post">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
<p>
|
||||||
|
{{ form.username.label }}<br>
|
||||||
|
{{ form.username(size=32) }}<br>
|
||||||
|
{% for error in form.username.errors %}
|
||||||
|
<span style="color: red;">[{{ error }}]</span>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ form.email.label }}<br>
|
||||||
|
{{ form.email(size=64) }}<br>
|
||||||
|
{% for error in form.email.errors %}
|
||||||
|
<span style="color: red;">[{{ error }}]</span>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ form.password.label }}<br>
|
||||||
|
{{ form.password(size=32) }}<br>
|
||||||
|
{% for error in form.password.errors %}
|
||||||
|
<span style="color: red;">[{{ error }}]</span>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{{ form.password2.label }}<br>
|
||||||
|
{{ form.password2(size=32) }}<br>
|
||||||
|
{% for error in form.password2.errors %}
|
||||||
|
<span style="color: red;">[{{ error }}]</span>
|
||||||
|
{% endfor %}
|
||||||
|
</p>
|
||||||
|
<p>{{ form.submit() }}</p>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</article>
|
||||||
|
|
||||||
|
{% endblock %}
|
|
@ -11,16 +11,21 @@
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
|
|
||||||
<h3>Connexions</h3>
|
<h3>Connexions</h3>
|
||||||
{% if trloginfail and not trlogin %}
|
<p>Twitter :
|
||||||
<h4><font color="red">Invalid Trello token !</font></h4>
|
{% if twlogin %}
|
||||||
|
<font color="green">Connected</font></p>
|
||||||
|
<a href="{{ url_for('twlogout') }}" class="button">Disconnect</a>
|
||||||
|
{% else %}
|
||||||
|
<font color="red">Disconnected</font></p>
|
||||||
|
<a href="{{ url_for('twlogin') }}" class="button primary">Connect</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<p>Trello :
|
<p>Trello :
|
||||||
{% if trlogin %}
|
{% if trlogin %}
|
||||||
<font color="green">Connected</font></p>
|
<font color="green">Connected</font></p>
|
||||||
<a href="{{ url_for('trlogout')}}" class="button">Disconnect</a>
|
<a href="{{ url_for('trlogout') }}" class="button">Disconnect</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<font color="red">Disconnected</font></p>
|
<font color="red">Disconnected</font></p>
|
||||||
<p><a href="https://trello.com/1/authorize?expiration=never&name=Cyberplanificateur&scope=read,write&response_type=token&key=4b9aa97a8ec693574d819aeaf9e0eefa&return_url=https://cyberplanificateur.flifloo.fr/settings">Click here</a> and give the token bellow</p>
|
<p><a href="{{ url_for('trlogin') }}">Click here</a> and give the token bellow</p>
|
||||||
<form action = "https://cyberplanificateur.flifloo.fr/settings" method = "POST">
|
<form action = "https://cyberplanificateur.flifloo.fr/settings" method = "POST">
|
||||||
<input type="text" name="trtoken" placeholder="Trello token" />
|
<input type="text" name="trtoken" placeholder="Trello token" />
|
||||||
<input type="submit" value="Connect" class="button primary">
|
<input type="submit" value="Connect" class="button primary">
|
|
@ -27,12 +27,12 @@
|
||||||
<div id="menu">
|
<div id="menu">
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="{{ url_for('home') }}">Home</a></li>
|
<li><a href="{{ url_for('home') }}">Home</a></li>
|
||||||
{% if login %}
|
{% if current_user.is_authenticated %}
|
||||||
<li><a href="{{ url_for('dashboard') }}">Dashboard</a></li>
|
<li><a href="{{ url_for('dashboard') }}">Dashboard</a></li>
|
||||||
<li><a href="{{ url_for('settings')}}">Settings</a></li>
|
<li><a href="{{ url_for('settings')}}">Settings</a></li>
|
||||||
<li><a href="{{ url_for('twlogout')}}">Log Out</a></li>
|
<li><a href="{{ url_for('logout')}}">Log Out</a></li>
|
||||||
{% else %}
|
{% else %}
|
||||||
<li><a href="{{ url_for('twlogin')}}">Log In</a></li>
|
<li><a href="{{ url_for('login')}}">Log In</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
7
config.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import os
|
||||||
|
basedir = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
class Config(object):
|
||||||
|
SECRET_KEY = "*i9uld6u@t!kxl9%o+byxqf14&a&&@y@q=l$!lg4m%b-a*^o(a"
|
||||||
|
SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(basedir, "database.db")
|
||||||
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
9
cyberplanificateur.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
from app import app
|
||||||
|
from app.models import User, TwitterAPI, TrelloAPI
|
||||||
|
|
||||||
|
@app.shell_context_processor
|
||||||
|
def make_shell_context():
|
||||||
|
return {"db": db, "User": User, "TwitterAPI": TwitterAPI, "TrelloAPI": TrelloAPI}
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app.run(debug=True, port=5000, host="0.0.0.0")
|
30
database.py
|
@ -7,7 +7,7 @@ def tradd(id, token):
|
||||||
for u in dbc.execute("SELECT * FROM trello WHERE id=?", (id,)):
|
for u in dbc.execute("SELECT * FROM trello WHERE id=?", (id,)):
|
||||||
sucess = False
|
sucess = False
|
||||||
if sucess:
|
if sucess:
|
||||||
dbc.execute("INSERT INTO trello (id, token) VALUES (?, ?)", (id, token))
|
dbc.execute("INSERT INTO trello (id, token) VALUES (?, ?)", (id, token,))
|
||||||
dbc.close()
|
dbc.close()
|
||||||
db.commit()
|
db.commit()
|
||||||
return sucess
|
return sucess
|
||||||
|
@ -28,6 +28,31 @@ def trtoken(id):
|
||||||
dbc.close()
|
dbc.close()
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
def twadd(user, id, maxslots, keywords):
|
||||||
|
with sqlite3.connect('database.db') as db:
|
||||||
|
dbc = db.cursor()
|
||||||
|
dbc.execute("INSERT INTO tweets (user, id, slots, maxslots, keywords) VALUES (?, ?, ?, ?, ?)", (user, id, 0, maxslots, keywords,))
|
||||||
|
dbc.close()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
def twrm(id):
|
||||||
|
with sqlite3.connect('database.db') as db:
|
||||||
|
dbc = db.cursor()
|
||||||
|
dbc.execute("DELETE FROM tweets WHERE id=?", (id,))
|
||||||
|
dbc.close()
|
||||||
|
db.commit()
|
||||||
|
|
||||||
|
def twlist(user):
|
||||||
|
with sqlite3.connect('database.db') as db:
|
||||||
|
dbc = db.cursor()
|
||||||
|
tweets = list()
|
||||||
|
for u in dbc.execute("SELECT * FROM tweets WHERE user=?", (user,)):
|
||||||
|
tweets.append({"id": u[1], "slots": u[2], "maxslots": u[3], "keywords": u[4]})
|
||||||
|
dbc.close()
|
||||||
|
db.commit()
|
||||||
|
return tweets
|
||||||
|
|
||||||
|
|
||||||
with sqlite3.connect('database.db') as db:
|
with sqlite3.connect('database.db') as db:
|
||||||
Table = False
|
Table = False
|
||||||
dbc = db.cursor()
|
dbc = db.cursor()
|
||||||
|
@ -36,7 +61,8 @@ with sqlite3.connect('database.db') as db:
|
||||||
for t in dbc.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='trello';"):
|
for t in dbc.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='trello';"):
|
||||||
Table = True
|
Table = True
|
||||||
if not Table:
|
if not Table:
|
||||||
dbc.execute('''CREATE TABLE trello (id text, token text)''')
|
dbc.execute('''CREATE TABLE trello (id int, token text)''')
|
||||||
|
dbc.execute('''CREATE TABLE tweets (user int, id int, slots int, maxslots int, keywords text)''')
|
||||||
dbc.close()
|
dbc.close()
|
||||||
db.commit()
|
db.commit()
|
||||||
#register("flifloo", "flifloo@gmail.com", "owo")
|
#register("flifloo", "flifloo@gmail.com", "owo")
|
||||||
|
|
99
site.py
|
@ -1,99 +0,0 @@
|
||||||
from flask import Flask, request, redirect, session, render_template
|
|
||||||
import twitter_credentials, tweepy, database, trello_credentials, trello
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.secret_key = "*i9uld6u@t!kxl9%o+byxqf14&a&&@y@q=l$!lg4m%b-a*^o(a"
|
|
||||||
|
|
||||||
def is_twkeys(session):
|
|
||||||
try:
|
|
||||||
session["access_token"]
|
|
||||||
session["access_secret_token"]
|
|
||||||
except:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
||||||
|
|
||||||
def twapi_login(session):
|
|
||||||
auth = tweepy.OAuthHandler(twitter_credentials.consumer_key, twitter_credentials.consumer_secret_key)
|
|
||||||
auth.set_access_token(session["access_token"], session["access_secret_token"])
|
|
||||||
return tweepy.API(auth)
|
|
||||||
|
|
||||||
def is_twlogin(session):
|
|
||||||
return is_twkeys(session) and twapi_login(session).verify_credentials()
|
|
||||||
|
|
||||||
def is_trlogin(session):
|
|
||||||
return is_twkeys(session) and database.trtoken(twapi_login(session).me().id)
|
|
||||||
|
|
||||||
@app.route("/twlogin")
|
|
||||||
def twlogin():
|
|
||||||
auth = tweepy.OAuthHandler(twitter_credentials.consumer_key, twitter_credentials.consumer_secret_key, "https://cyberplanificateur.flifloo.fr/twlogin")
|
|
||||||
if request.args.get("oauth_token") and request.args.get("oauth_verifier"):
|
|
||||||
auth.request_token = {"oauth_token" : request.args.get("oauth_token"), "oauth_token_secret" : request.args.get("oauth_verifier")}
|
|
||||||
try:
|
|
||||||
auth.get_access_token(request.args.get("oauth_verifier"))
|
|
||||||
except:
|
|
||||||
return "Error ! Failed to get access token"
|
|
||||||
else:
|
|
||||||
session["access_token"] = auth.access_token
|
|
||||||
session["access_secret_token"] = auth.access_token_secret
|
|
||||||
elif not is_twlogin(session):
|
|
||||||
return redirect(auth.get_authorization_url())
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
@app.route("/twlogout")
|
|
||||||
def twlogout():
|
|
||||||
if is_twkeys(session):
|
|
||||||
session.pop("access_token", None)
|
|
||||||
session.pop("access_secret_token", None)
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/trlogout")
|
|
||||||
def trlogout():
|
|
||||||
if not is_twlogin(session):
|
|
||||||
return redirect("/")
|
|
||||||
if is_trlogin(session):
|
|
||||||
database.trrm(twapi_login(session).me().id)
|
|
||||||
return redirect("/settings")
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def home():
|
|
||||||
return render_template("index.html", login = is_twlogin(session))
|
|
||||||
|
|
||||||
@app.route("/settings", methods = ['POST', 'GET'])
|
|
||||||
def settings():
|
|
||||||
if not is_twlogin(session):
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
trloginfail = False
|
|
||||||
if "trtoken" in request.form:
|
|
||||||
try:
|
|
||||||
trello.TrelloClient(api_key = trello_credentials.api_key, token = request.form["trtoken"]).list_boards()
|
|
||||||
except:
|
|
||||||
trloginfail = True
|
|
||||||
else:
|
|
||||||
database.tradd(twapi_login(session).me().id, request.form["trtoken"])
|
|
||||||
return render_template("settings.html", login = True, trlogin = is_trlogin(session), trloginfail = trloginfail)
|
|
||||||
|
|
||||||
@app.route("/dashboard")
|
|
||||||
def dashboard():
|
|
||||||
if not is_twlogin(session):
|
|
||||||
return redirect("/")
|
|
||||||
|
|
||||||
return render_template("dashboard.html", login = True, tweets = ["test1", "test2", "test3"], timeline = [{"id": 1, "text": "test1"}, {"id": 2, "text": "test2"}, {"id": 3, "text": "test3"}, {"id": 4, "text": "test4"}])
|
|
||||||
|
|
||||||
@app.route("/twpost")
|
|
||||||
def twpost():
|
|
||||||
if is_twlogin(session):
|
|
||||||
api = twapi_login(session)
|
|
||||||
api.update_status("bloup")
|
|
||||||
return "Send !"
|
|
||||||
else:
|
|
||||||
return "Not login !"
|
|
||||||
|
|
||||||
@app.route("/test")
|
|
||||||
def test():
|
|
||||||
return render_template("elements.html")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app.run(debug=True, port=5000, host="0.0.0.0")
|
|
|
@ -1,64 +0,0 @@
|
||||||
{% extends "template.html" %}
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
<!-- Main -->
|
|
||||||
<article id="main">
|
|
||||||
<header>
|
|
||||||
<h2>Dashboard</h2>
|
|
||||||
<p>Your personal dashboard</p>
|
|
||||||
</header>
|
|
||||||
<section class="wrapper style5">
|
|
||||||
<div class="inner">
|
|
||||||
|
|
||||||
<h3>Current automatized tweets</h3>
|
|
||||||
<hr />
|
|
||||||
<table>
|
|
||||||
{% for t in tweets %}
|
|
||||||
<tr>
|
|
||||||
<td><blockquote>{{ t }}</blockquote></td>
|
|
||||||
<td><a href="#" class="button primary">Disable</a>
|
|
||||||
<a href="#" class="button">Delet</a></td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</table>
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<h3>Add an automatized tweet</h3>
|
|
||||||
<hr />
|
|
||||||
<section>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6 col-12-medium">
|
|
||||||
{% for t in timeline %}
|
|
||||||
<div class="col-4 col-12-small">
|
|
||||||
<input type="radio" id="{{ t['id'] }}" name="tweet">
|
|
||||||
<label for="{{ t['id'] }}">{{ t['text'] }}</label>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div class="col-6 col-12-medium">
|
|
||||||
<input type="text" name="slots" placeholder="Slots number" />
|
|
||||||
<input type="text" name="keywords" placeholder="Keywords (separate with a comma)" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<input type="submit" value="Connect" class="button primary">
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<h3>Trello board</h3>
|
|
||||||
<hr />
|
|
||||||
<section>
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-6 col-12-medium">
|
|
||||||
<h4>Choose a board</h4>
|
|
||||||
</div>
|
|
||||||
<div class="col-6 col-12-medium">
|
|
||||||
<h4>Choose a columns</h4>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<hr />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</article>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
5
tr.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import trello
|
||||||
|
import tw
|
||||||
|
|
||||||
|
def is_trlogin(session):
|
||||||
|
return is_twkeys(session) and database.trtoken(tw.api_login(session).me().id)
|
18
tw.py
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import tweepy, twitter_credentials
|
||||||
|
|
||||||
|
def api_login(session):
|
||||||
|
auth = tweepy.OAuthHandler(twitter_credentials.consumer_key, twitter_credentials.consumer_secret_key)
|
||||||
|
auth.set_access_token(session["access_token"], session["access_secret_token"])
|
||||||
|
return tweepy.API(auth)
|
||||||
|
|
||||||
|
def is_keys(session):
|
||||||
|
try:
|
||||||
|
session["access_token"]
|
||||||
|
session["access_secret_token"]
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
def is_login(session):
|
||||||
|
return is_keys(session) and api_login(session).verify_credentials()
|