Comenzando a utilizar SQLAlchemy

Tuve que reescribir bastante para lograrlo, pero ya funciona :3
This commit is contained in:
Daniel Cortes
2019-03-11 20:59:55 -03:00
parent db7e0a4901
commit cb89bf932d
15 changed files with 311 additions and 320 deletions

23
.idea/dataSources.xml generated Normal file
View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="files" uuid="630cc1a9-f960-489a-9dd4-5eb988f04598">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/instance/files.sqlite</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
</data-source>
<data-source source="LOCAL" name="files-test" uuid="8b911962-ca9f-4f64-973c-88617733acd2">
<driver-ref>sqlite.xerial</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.sqlite.JDBC</jdbc-driver>
<jdbc-url>jdbc:sqlite:$PROJECT_DIR$/instance/files-test.sqlite</jdbc-url>
<driver-properties>
<property name="enable_load_extension" value="true" />
</driver-properties>
</data-source>
</component>
</project>

4
.idea/encodings.xml generated Normal file
View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" addBOMForNewFiles="with NO BOM" />
</project>

19
.idea/files.danielcortes.xyz.iml generated Normal file
View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="TemplatesService">
<option name="TEMPLATE_CONFIGURATION" value="Jinja2" />
<option name="TEMPLATE_FOLDERS">
<list>
<option value="$MODULE_DIR$/files/templates" />
</list>
</option>
</component>
<component name="TestRunnerService">
<option name="PROJECT_TEST_RUNNER" value="Unittests" />
</component>
</module>

7
.idea/misc.xml generated Normal file
View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.7 (files.danielcortes.xyz)" project-jdk-type="Python SDK" />
</project>

8
.idea/modules.xml generated Normal file
View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/files.danielcortes.xyz.iml" filepath="$PROJECT_DIR$/.idea/files.danielcortes.xyz.iml" />
</modules>
</component>
</project>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@@ -3,16 +3,17 @@ import os
from flask import Flask, render_template
from werkzeug import SharedDataMiddleware
def create_app():
app = Flask(__name__, instance_relative_config=True)
app.config.from_mapping(
DATABASE = os.path.join(app.instance_path, 'files.sqlite'),
SQLALCHEMY_DATABASE_URI="sqlite:///{}".format(os.path.join(app.instance_path, 'files.sqlite')),
SQLALCHEMY_TRACK_MODIFICATIONS=False,
USERNAME='dev',
PASSWORD='secret',
SECRET_KEY='1337',
UPLOAD_FOLDER = 'uploads',
ALLOWED_EXTENSIONS = set(['png', 'jpg'])
UPLOAD_FOLDER='uploads'
)
app.config.from_pyfile('config.py')
@@ -23,22 +24,24 @@ def create_app():
except OSError:
pass
from . import db
from files.models import db
db.init_app(app)
from . import auth
from files import commands
commands.init_app(app)
from files import auth
app.register_blueprint(auth.bp)
from . import categories
from files import categories
app.register_blueprint(categories.bp)
from . import about
from files import about
app.register_blueprint(about.bp)
app.add_url_rule('/about', endpoint='about')
from . import files
from files import files
app.register_blueprint(files.bp)
app.add_url_rule('/', endpoint='index')
return app

View File

@@ -1,9 +1,9 @@
from files.db import get_db
from flask import Blueprint, request, render_template, redirect, url_for, flash
from files.models import db, Message
bp = Blueprint('about', __name__, url_prefix='/about')
@bp.route('/', methods=('GET', 'POST'))
def about():
if request.method == 'POST':
@@ -11,7 +11,6 @@ def about():
email = request.form['email']
message = request.form['message']
db = get_db()
error = None
if not name:
@@ -24,17 +23,14 @@ def about():
if error is not None:
flash(error)
else:
db.execute(
'INSERT INTO messages'
' (name, email, message)'
' VALUES (?, ?, ?)',
(name, email, message)
)
db.commit()
m = Message(name, email, message)
db.session.add(m)
db.session.commit()
return redirect(url_for('about.thanks'))
return render_template('about/about.html')
@bp.route('/thanks')
def thanks():
return render_template('about/thanks.html')

View File

@@ -1,47 +1,46 @@
import functools
from files.db import get_db
from files.models import db, User
from werkzeug.exceptions import abort
from werkzeug.security import check_password_hash
from flask import Blueprint, request, flash, render_template, session, g, redirect, url_for
bp = Blueprint('auth', __name__, url_prefix='/auth')
@bp.route('/login', methods=('GET', 'POST'))
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = get_db()
error = None
user = db.execute(
'SELECT * FROM users WHERE username = ?', (username,)
).fetchone()
user = User.query.filter_by(username=username).first()
if user is None:
error = 'Incorrect username.'
elif not check_password_hash(user['password'], password):
elif not check_password_hash(user.password, password):
error = 'Incorrect password.'
if error is None:
session.clear()
session['user_id'] = user['id']
session['user_id'] = user.id
return redirect(url_for('index'))
flash(error)
return render_template('auth/login.html')
@bp.route('/logout')
def logout():
session.clear()
return redirect(url_for('index'))
@bp.before_app_request
def load_logged_in_user():
user_id = session.get('user_id')
@@ -49,9 +48,7 @@ def load_logged_in_user():
if user_id is None:
g.user = None
else:
g.user = get_db().execute(
'SELECT * FROM users WHERE id = ?', (user_id,)
).fetchone()
g.user = User.query.get(user_id)
def admin_required(view):
@@ -63,4 +60,3 @@ def admin_required(view):
return view(**kwargs)
return wrapped_view

View File

@@ -1,49 +1,16 @@
from flask import Flask, Blueprint, flash, request, redirect, url_for, current_app, render_template, jsonify
from werkzeug.exceptions import abort
from files.db import get_db
from files.auth import admin_required
from files.files import get_files_by_category
from files.models import Category, db
bp = Blueprint('categories', __name__, url_prefix='/categories')
def get_categories():
db = get_db()
categories = db.execute(
'SELECT id, name'
' FROM categories'
' ORDER BY name DESC'
).fetchall()
return categories
def get_category(id):
db = get_db()
category = db.execute(
'SELECT id, name'
' FROM categories'
' WHERE id = ?'
' LIMIT 1',
(id,)
).fetchone()
return category
def get_files_by_category(id):
db = get_db()
files = db.execute(
'SELECT id, filename, private'
' FROM files'
' WHERE category = ?'
' ORDER BY filename DESC',
(id,)
).fetchall()
return files
@bp.route('/')
def index():
categories = get_categories()
categories = Category.query.all()
return render_template('categories/index.html', categories=categories)
@bp.route('/create', methods=['GET', 'POST'])
@admin_required
def create():
@@ -57,21 +24,18 @@ def create():
if error is not None:
flash(error)
else:
db = get_db()
db.execute(
'INSERT INTO categories (name)'
' VALUES (?)',
(name,)
)
db.commit()
c = Category(name)
db.session.add(c)
db.session.commit()
return redirect(url_for('categories.index'))
return render_template('categories/create.html')
@bp.route('<int:id>')
def view(id):
category = get_category(id)
files = get_files_by_category(id)
category = Category.query.get(id)
files = category.files
return render_template('categories/view.html', category=category, files=files)
@@ -80,9 +44,8 @@ def view(id):
def update():
pass
@bp.route('/<int:id>/delete')
@admin_required
def delete():
pass

90
files/commands.py Normal file
View File

@@ -0,0 +1,90 @@
import click
import os
from flask import current_app
from flask.cli import with_appcontext
from werkzeug.security import generate_password_hash
from files.models import db, User, Category, File
def init_db():
db.drop_all()
db.create_all()
def generate_admin():
username = current_app.config['USERNAME']
password = current_app.config['PASSWORD']
u = User(username, generate_password_hash(password))
db.session.add(u)
db.session.commit()
def add_defaults():
default_category = Category('Default')
db.session.add(default_category)
db.session.commit()
def add_files():
existing_files = os.listdir(current_app.config['UPLOAD_FOLDER'])
default_category = Category.query.filter_by(name='Default').first()
added = 0
for f in existing_files:
search = File.query.filter_by(filename=f).first()
if search is None:
file = File(f, 0, default_category.id)
db.session.add(file)
added += 1
db.session.commit()
return added
@click.command('init-db')
@with_appcontext
def init_db_command():
"""
Creates the db file
"""
init_db()
@click.command("add-defaults")
@with_appcontext
def add_defaults_command():
"""
Initializes the database with default data
"""
add_defaults()
click.echo("Defaults added")
@click.command('generate-admin')
@with_appcontext
def generate_admin_command():
"""Creates the admin of the system"""
generate_admin()
click.echo('The admin was created')
@click.command('add-files')
@with_appcontext
def add_files_command():
"""
Generates the rows in the database for the files currently uploaded
They all will have default privacy, so, public
"""
added_size= add_files()
click.echo(f'added {added_size} files')
def init_app(app):
app.cli.add_command(init_db_command)
app.cli.add_command(add_defaults_command)
app.cli.add_command(generate_admin_command)
app.cli.add_command(add_files_command)

View File

@@ -1,77 +0,0 @@
import sqlite3
import click
from flask import current_app, g
from flask.cli import with_appcontext
from werkzeug.security import generate_password_hash
def get_db():
if 'db' not in g:
g.db = sqlite3.connect(
current_app.config['DATABASE'],
detect_types = sqlite3.PARSE_DECLTYPES
)
g.db.row_factory = sqlite3.Row
return g.db
def close_db(e=None):
db = g.pop('db', None)
if db is not None:
db.close()
def init_db():
db = get_db()
with current_app.open_resource('schema.sql') as f:
db.executescript(f.read().decode('utf8'))
def generate_admin():
db = get_db()
username = current_app.config['USERNAME']
password = current_app.config['PASSWORD']
db.execute(
'INSERT INTO users (username, password) VALUES (?, ?)',
(username, generate_password_hash(password),)
)
db.commit()
def generate_files():
pass
@click.command('init-db')
@with_appcontext
def init_db_command():
"""Inits the database with it's schema"""
init_db()
click.echo('The database was initialized')
@click.command('generate-admin')
@with_appcontext
def generate_admin_command():
"""Creates the admin of the system"""
generate_admin()
click.echo('The admin was created')
@click.command('generate-files')
@with_appcontext
def generate_files_command():
"""
Generates the rows in the database for the files currently uploaded
They all will have default privacy, so, public
"""
generate_files()
click.echo('files generated')
def init_app(app):
app.teardown_appcontext(close_db)
app.cli.add_command(init_db_command)
app.cli.add_command(generate_admin_command)

View File

@@ -1,150 +1,69 @@
import os
import random
from flask import Flask, Blueprint, flash, request, redirect, url_for, current_app, render_template, send_from_directory, g
from flask import Flask, Blueprint, flash, request, redirect, url_for, current_app, render_template, \
send_from_directory, g
from werkzeug.utils import secure_filename
from werkzeug.exceptions import abort
from files.models import db, File, Category, User
from files.auth import admin_required
from files.db import get_db
bp = Blueprint('files', __name__)
bp.add_url_rule('/uploads/<path:filename>', 'uploaded_file', build_only=True)
def get_extension(filename):
def _get_extension(filename):
return filename.rsplit('.', 1)[1].lower()
def get_path_in_upload(filename):
def _get_path_in_upload(filename):
return os.path.join(current_app.config['UPLOAD_FOLDER'], filename)
def save_file(file, private, category):
def _save_file(file, private, category):
if private is None:
is_private = 0
else:
is_private = 1
db = get_db()
filename = secure_filename(file.filename)
db.execute(
'INSERT INTO files (filename, private, category)'
' VALUES (?,?,?)',
(filename, is_private, category['id'],)
)
file.save(get_path_in_upload(filename))
file.save(_get_path_in_upload(filename))
f = File(filename, is_private, category.id)
db.commit()
db.session.add(f)
db.session.commit()
return filename
def _rename_file(file, new_name):
db = get_db()
new_name = secure_filename(new_name)
db.execute(
'UPDATE files'
' SET filename = ?'
' WHERE id = ?',
(new_name, file['id'],)
)
os.rename(get_path_in_upload(file['filename']), get_path_in_upload(new_name))
os.rename(_get_path_in_upload(file.filename), _get_path_in_upload(new_name))
file.filename = new_name
db.commit()
return new_name
db.session.commit()
def _delete_file(file):
db = get_db()
db.execute(
'DELETE from files'
' WHERE id = ?',
(file['id'],)
)
os.remove(_get_path_in_upload(file.filename))
db.session.delete(file)
db.session.commit()
os.remove(get_path_in_upload(file['filename']))
db.commit()
def get_files():
db = get_db()
files = db.execute(
'SELECT id, filename, private, category'
' FROM files'
' ORDER BY filename'
).fetchall()
return files
def get_public_files():
db = get_db()
files = db.execute(
'SELECT id, filename, private, category'
' FROM files'
' WHERE private = false'
' ORDER BY filename'
).fetchall()
return files
def get_files_by_category(id):
db = get_db()
files = db.execute(
'SELECT id, filename, private'
' FROM files'
' WHERE category = ?'
' ORDER BY filename',
(id,)
).fetchall()
def get_file(id):
db = get_db()
file = db.execute(
'SELECT id, filename, private, category'
' FROM files'
' WHERE id = ?'
' LIMIT 1',
(id,)
).fetchone()
return file
def get_categories():
db = get_db()
categories = db.execute(
'SELECT id, name'
' FROM categories'
' ORDER BY name'
).fetchall()
return categories
def get_category(id):
db = get_db()
category = db.execute(
'SELECT id, name'
' FROM categories'
' WHERE id = ?'
' LIMIT 1',
(id,)
).fetchone()
return category
@bp.route('/', methods=['GET', 'POST'])
def index():
if g.user is None:
files = get_public_files()
files = File.query.filter_by(private=0).all()
else:
files = get_files()
files = File.query.all()
return render_template('files/index.html', files=files)
@bp.route('/upload', methods=['GET', 'POST'])
@admin_required
def upload_file():
db = get_db()
if request.method == 'POST':
if 'file' not in request.files:
flash('No file part')
@@ -160,23 +79,27 @@ def upload_file():
if 'category' not in request.form:
flash('No category selected')
return redirect(request.url)
else:
category = get_category(request.form['category'])
category = Category.query.get(request.form['category'])
if category is None:
flash('The category selected won\'t exists')
return redirect(request.url)
if file.filename == '':
flash('No seleted file')
return redirect(request.url)
if file:
filename = save_file(file, private, category)
_save_file(file, private, category)
return redirect(url_for('files.index'))
return render_template('files/upload.html', categories=get_categories())
return render_template('files/upload.html', categories=Category.query.all())
@bp.route('/preview/<int:id>')
def preview_file(id):
file = get_file(id)
if (file['private'] == 1 and g.user is not None) or (file['private'] == 0):
file = File.query.get(id)
if (file.private == 1 and g.user is not None) or (file.private == 0):
return render_template('files/preview.html', file=file)
else:
return abort(404)
@@ -186,28 +109,27 @@ def preview_file(id):
@admin_required
def rename_file(id):
if request.method == 'POST':
file = get_file(id)
file = File.query.get(id)
new_name = request.form['new_name'].lower()
extension = file['filename'].rsplit('.', 1)[1].lower()
extension = file.filename.rsplit('.', 1)[1].lower()
if "." in new_name and get_extension(new_name):
if "." in new_name and _get_extension(new_name):
new_name = new_name.rsplit('.', 1)[0] + '.' + extension
else:
new_name = new_name + '.' + extension
_rename_file(file, new_name)
return redirect(url_for('files.preview_file', id=file['id']))
return redirect(url_for('files.preview_file', id=file.id))
@bp.route('/delete/<int:id>', methods=['POST'])
@admin_required
def delete_file(id):
if request.method == 'POST':
file = get_file(id)
print(file)
file = File.query.get(id)
_delete_file(file)
return redirect(url_for('index'))
else:
abort(404)

64
files/models.py Normal file
View File

@@ -0,0 +1,64 @@
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
db = SQLAlchemy()
class User(db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String, unique=True, nullable=False)
password = db.Column(db.String, nullable=False)
def __init__(self, username=None, password=None):
self.username = username
self.password = password
def __repr__(self):
return f'<User {self.username}>'
class Message(db.Model):
__tablename__ = 'messages'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
email = db.Column(db.String, nullable=False)
message = db.Column(db.Text, nullable=False)
sended = db.Column(db.DateTime, default=datetime.utcnow())
def __init__(self, name=None, email=None, message=None):
self.name = name
self.email = email
self.message = message
def __repr__(self):
return f'<Message from:{self.name} message:{self.message}>'
class File(db.Model):
__tablename__ = 'files'
id = db.Column(db.Integer, primary_key=True)
filename = db.Column(db.String, nullable=False)
private = db.Column(db.Integer, nullable=False)
category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=False)
def __init__(self, filename=None, private=None, category_id=None):
self.filename = filename
self.private = private
self.category_id = category_id
def __repr__(self):
return f'<File name:{self.filename}>'
class Category(db.Model):
__tablename__ = 'categories'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String, nullable=False)
files = db.relationship('File', backref='category', lazy=True)
def __init__(self, name=None):
self.name = name
def __repr__(self):
return f'<Category name:{self.name}>'

View File

@@ -1,33 +0,0 @@
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS messages;
DROP TABLE IF EXISTS files;
DROP TABLE IF EXISTS categories;
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL
);
CREATE TABLE messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
message TEXT NOT NULL,
sended TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
filename TEXT NOT NULL,
private INTEGER NOT NULL DEFAULT 0,
category INTEGER NOT NULL,
FOREIGN KEY(category) REFERENCES categories(id)
);
CREATE TABLE categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);