diff --git a/README.org b/README.org new file mode 100644 index 0000000..0ba1be6 --- /dev/null +++ b/README.org @@ -0,0 +1,127 @@ +* PyConES16: Flask-note > Clone of Evernote with Flask + +** [[https://flasknote.fenollosa.work][DEMO]] + +#+CAPTION: Flask-note +#+NAME: Flask-note +[[file:sketching/PNG/dashboard.png]] + +** Install + +*** Flask + +#+BEGIN_SRC bash +mkdir flask-note +cd flask-note +pip install virtualenv +virtualenv ENV +source ENV/bin/activate +pip install Flask +pip install markdown +#+END_SRC + +*** SQLite + +**** OS X +#+BEGIN_SRC bash +brew install sqlite +#+END_SRC + +**** Ubuntu +#+BEGIN_SRC bash +sudo apt-get update +sudo apt-get install sqlite3 libsqlite3-dev +#+END_SRC + +*** Flask_sqlalchemy + +#+BEGIN_SRC bash +pip install flask_sqlalchemy +#+END_SRC + +** Create Flask + +Create *app.py* + +#+BEGIN_SRC python +from flask import Flask +app = Flask(__name__) + +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + session.clear() + return redirect(url_for('index')) + return f(*args, **kwargs) + return decorated_function + +@app.route('/') +def hello(): + return 'Hello PyConES!' + +if __name__ == "__main__": + app.run() +#+END_SRC + +#+BEGIN_SRC bash +python app.py +#+END_SRC + +Open in your browser + +http://127.0.0.1:5000/ + +** Create Database + +Create *database.py* + +#+BEGIN_SRC python +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.sqlite' +db = SQLAlchemy(app) + + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(100), unique=True) + password = db.Column(db.String(32)) + + def __init__(self, email, password): + self.email = email + self.password = password + + def __repr__(self): + return ''.format(email=self.email) + + +class Note(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(100), unique=True) + text = db.Column(db.Text()) + + def __init__(self, title, text): + self.title = title + self.text = text + + def __repr__(self): + return ''.format(title=self.title) + +#+END_SRC + +#+BEGIN_SRC bash +python +from database import db +db.create_all() +exit() +sqlite3 database.sqlite +#+END_SRC + +#+BEGIN_SRC sql +INSERT INTO user VALUES (NULL, 'py@con.es', '2016'); +.exit +#+END_SRC + diff --git a/sketching/HTML/Resources/SampleStyle.css b/sketching/HTML/Resources/SampleStyle.css new file mode 100644 index 0000000..dfe04b2 --- /dev/null +++ b/sketching/HTML/Resources/SampleStyle.css @@ -0,0 +1,7 @@ +/* + place extra stylesheet here +*/ + +.ImageContainer img { + border: solid 2px #999; +} diff --git a/sketching/HTML/index.html b/sketching/HTML/index.html new file mode 100644 index 0000000..92ee28a --- /dev/null +++ b/sketching/HTML/index.html @@ -0,0 +1 @@ +sketching.ep

Flask-note

Login

Dashboard

New

Edit

Delete

\ No newline at end of file diff --git a/sketching/HTML/pages/dashboard.png b/sketching/HTML/pages/dashboard.png new file mode 100644 index 0000000..aae1f2d Binary files /dev/null and b/sketching/HTML/pages/dashboard.png differ diff --git a/sketching/HTML/pages/delete.png b/sketching/HTML/pages/delete.png new file mode 100644 index 0000000..42dd3a7 Binary files /dev/null and b/sketching/HTML/pages/delete.png differ diff --git a/sketching/HTML/pages/edit.png b/sketching/HTML/pages/edit.png new file mode 100644 index 0000000..5b74e4e Binary files /dev/null and b/sketching/HTML/pages/edit.png differ diff --git a/sketching/HTML/pages/login.png b/sketching/HTML/pages/login.png new file mode 100644 index 0000000..46ddd64 Binary files /dev/null and b/sketching/HTML/pages/login.png differ diff --git a/sketching/HTML/pages/new.png b/sketching/HTML/pages/new.png new file mode 100644 index 0000000..ef82dc6 Binary files /dev/null and b/sketching/HTML/pages/new.png differ diff --git a/sketching/PNG/dashboard.png b/sketching/PNG/dashboard.png new file mode 100644 index 0000000..aae1f2d Binary files /dev/null and b/sketching/PNG/dashboard.png differ diff --git a/sketching/PNG/delete.png b/sketching/PNG/delete.png new file mode 100644 index 0000000..42dd3a7 Binary files /dev/null and b/sketching/PNG/delete.png differ diff --git a/sketching/PNG/edit.png b/sketching/PNG/edit.png new file mode 100644 index 0000000..5b74e4e Binary files /dev/null and b/sketching/PNG/edit.png differ diff --git a/sketching/PNG/login.png b/sketching/PNG/login.png new file mode 100644 index 0000000..46ddd64 Binary files /dev/null and b/sketching/PNG/login.png differ diff --git a/sketching/PNG/new.png b/sketching/PNG/new.png new file mode 100644 index 0000000..ef82dc6 Binary files /dev/null and b/sketching/PNG/new.png differ diff --git a/sketching/sketching.ep b/sketching/sketching.ep new file mode 100644 index 0000000..6c908ed --- /dev/null +++ b/sketching/sketching.ep @@ -0,0 +1,313 @@ + +Login1473225761798_6183749884true#FFFFFFFFlogin + + + + + + + + Flask-note]]> + + +
Flask-note
+
+
+ + + + E-mail + + + + + Password + + + + + Login +
Dashboard1473719769736_2313749884true#FFFFFFFFdashboard + + + + + + + + Flask-note]]> + + +
Flask-note
+
+
PyConES16]]> + + + + + + + + ]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + New + + + + + Delete + + + + + Edit + + + + + Logout + + + + + Search... +
New1473720033604_586749884true#FFFFFFFFtransparentnew + + + + + + + + Flask-note]]> + + +
Flask-note
+
+
+ + + + + + + + + + + + + New + + + + + Preview + + + + + + + + + + MarkDown + + + + + Logout +
Edit1473720371952_2384749884true#FFFFFFFFtransparentedit + + + + + + + + Flask-note]]> + + +
Flask-note
+
+
+ + + + + + + + + + + + + Save + + + + + Preview + + + + + + + + + + MarkDown + + + + + Logout +
Delete1473720471843_2494749884true#FFFFFFFFtransparentdelete + + + + + + + + Flask-note]]> + + +
Flask-note
+
+
+ + + + + + + + + + + + + Yes + + + + + No please! + + + + + + +
\ No newline at end of file diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..38d46bc --- /dev/null +++ b/src/app.py @@ -0,0 +1,164 @@ +from flask import Flask, render_template, request, url_for, redirect, session +from functools import wraps +from sqlalchemy import or_ +from markdown import markdown +from database import db, User, Note + +app = Flask(__name__) + +# Decoration: check login in session + + +def login_required(f): + @wraps(f) + def decorated_function(*args, **kwargs): + if 'user_id' not in session: + session.clear() + return redirect(url_for('index')) + return f(*args, **kwargs) + return decorated_function + + +@app.route('/') +def index(data=None): + return render_template('items/login.html', data=data) + + +@app.route('/login', methods=['POST']) +def login(): + data = dict() + if request.form['email'] and request.form['password']: + # Checks if the user exists + data['email'] = request.form['email'] + data['password'] = request.form['password'] + my_user = User.query.filter_by( + email=data['email'], password=data['password']).first() + if my_user: + # Create user session + session['user_id'] = my_user.id + session['user_email'] = my_user.email + return redirect(url_for('dashboard')) + else: + data['error'] = True + else: + data['error'] = True + + return index(data) + + +@app.route('/logout') +@login_required +def logout(): + # Clear sessions + session.clear() + + return redirect(url_for('index')) + + +@app.route('/dashboard') +@login_required +def dashboard(my_param_note=None): + my_main_note = None + + # Searchs + my_notes = my_param_note + if not my_notes: + # Nothing found + my_notes = Note.query.order_by(Note.id.desc()).all() + if my_notes: + # Show first result + my_main_note = my_notes[0] + + # Show first note + if request.args.get('id'): + my_note_temp = Note.query.filter_by(id=request.args.get('id')).first() + # Is there any note in the database? + if my_note_temp: + my_main_note = my_note_temp + + # Data for template + data = dict() + data['notes'] = my_notes + data['main_note'] = my_main_note + data['markdown'] = markdown + + return render_template('items/dashboard.html', data=data) + + +@app.route('/search') +@login_required +def search(): + q = request.args.get('q') + + return dashboard(Note.query.filter( + or_(Note.title.like('%' + q + '%'), Note.text.like('%' + q + '%') + )).all()) + + +@app.route('/new') +@login_required +def new(): + return render_template('items/new.html') + + +@app.route('/new/save', methods=['POST']) +@login_required +def save_note(): + myNote = Note(request.form['title'], request.form['text']) + # Create + db.session.add(myNote) + db.session.commit() + + return redirect(url_for('dashboard')) + + +@app.route('/edit') +@login_required +def edit(data=None): + id = request.args.get('id') + my_note = Note.query.filter_by(id=id).first() + data = dict() + data['main_note'] = my_note + return render_template('items/edit.html', data=data) + + +@app.route('/edit_note', methods=['POST']) +@login_required +def edit_note(data=None): + if request.form['id']: + # Update + my_note = Note.query.filter_by(id=request.form['id']).first() + my_note.title = request.form['title'] + my_note.text = request.form['text'] + db.session.commit() + + return redirect(url_for('dashboard')) + + +@app.route('/delete') +@login_required +def delete(): + id = request.args.get('id') + my_note = Note.query.filter_by(id=id).first() + data = dict() + data['main_note'] = my_note + data['markdown'] = markdown + + return render_template('items/delete.html', data=data) + + +@app.route('/delete_note') +@login_required +def delete_note(): + id = request.args.get('id') + # Delete + my_note = Note.query.filter_by(id=id).first() + db.session.delete(my_note) + db.session.commit() + + return redirect(url_for('dashboard', id=id)) + +# App +if __name__ == "__main__": + app.secret_key = 'secret' + app.run() diff --git a/src/database.py b/src/database.py new file mode 100644 index 0000000..fdefc5c --- /dev/null +++ b/src/database.py @@ -0,0 +1,32 @@ +from flask import Flask +from flask_sqlalchemy import SQLAlchemy + +app = Flask(__name__) +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///database.sqlite' +db = SQLAlchemy(app) + + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.String(100), unique=True) + password = db.Column(db.String(32)) + + def __init__(self, email, password): + self.email = email + self.password = password + + def __repr__(self): + return ''.format(email=self.email) + + +class Note(db.Model): + id = db.Column(db.Integer, primary_key=True) + title = db.Column(db.String(100), unique=True) + text = db.Column(db.Text()) + + def __init__(self, title, text): + self.title = title + self.text = text + + def __repr__(self): + return ''.format(title=self.title) diff --git a/src/templates/items/dashboard.html b/src/templates/items/dashboard.html new file mode 100644 index 0000000..53f9879 --- /dev/null +++ b/src/templates/items/dashboard.html @@ -0,0 +1,62 @@ +{% extends 'layouts/master.html' %} +{% block contain %} +
+
+
+
+ + + + +
+
+
+
+ {{ session['user_email'] }} + Logout +
+
+
+
+
+ New +
+
+ {%- if data.main_note -%} + Edit + Delete + {% endif %} +
+
+
+
+ +
+ {%- if data.main_note -%} +
+
+

+ {{ data.markdown(data.main_note.title)|safe }} +

+ {{ data.markdown(data.main_note.text)|safe }} +
+
+ {% endif %} +
+
+{% endblock %} diff --git a/src/templates/items/delete.html b/src/templates/items/delete.html new file mode 100644 index 0000000..8b0eb01 --- /dev/null +++ b/src/templates/items/delete.html @@ -0,0 +1,21 @@ +{% extends 'layouts/master.html' %} +{% block contain %} +
+

Do you want to delete this note?

+ +
+
+
+
+
+

+ {{ data.markdown(data.main_note.title)|safe }} +

+ {{ data.markdown(data.main_note.text)|safe }} +
+
+
+{% endblock %} diff --git a/src/templates/items/edit.html b/src/templates/items/edit.html new file mode 100644 index 0000000..ab95a10 --- /dev/null +++ b/src/templates/items/edit.html @@ -0,0 +1,24 @@ +{% extends 'layouts/master.html' %} +{% block contain %} +
+ {{ session['user_email'] }} + Logout +
+
+
+ +
+
+ Back +
+
+ +
+
+
+
+ + +
+
+{% endblock %} diff --git a/src/templates/items/login.html b/src/templates/items/login.html new file mode 100644 index 0000000..305bf99 --- /dev/null +++ b/src/templates/items/login.html @@ -0,0 +1,21 @@ +{% extends 'layouts/master.html' %} +{% block contain %} +{% if data['error'] %} +
+ +
+{% endif %} +
+
+
+ +
+
+ +
+ +
+
+{% endblock %} diff --git a/src/templates/items/new.html b/src/templates/items/new.html new file mode 100644 index 0000000..e50d5cb --- /dev/null +++ b/src/templates/items/new.html @@ -0,0 +1,23 @@ +{% extends 'layouts/master.html' %} +{% block contain %} +
+ {{ session['user_email'] }} + Logout +
+
+
+
+
+ Back +
+
+ +
+
+
+
+ + +
+
+{% endblock %} diff --git a/src/templates/layouts/master.html b/src/templates/layouts/master.html new file mode 100644 index 0000000..7ec88c5 --- /dev/null +++ b/src/templates/layouts/master.html @@ -0,0 +1,22 @@ + + + + + + + Flask-note + + +
+ +
+ {% block contain %} + {% endblock %} +
+
+ +