From 73a79d5eefdc02a942edfdb0c48cefc507f81427 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Fri, 23 Mar 2018 17:05:07 +0000 Subject: [PATCH] Add file upload for releases --- .gitignore | 2 +- app/templates/packages/release_new.html | 2 +- app/views/__init__.py | 9 +++- app/views/packages.py | 69 ++++++++++++++++--------- config.example.cfg | 2 + 5 files changed, 56 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index db3f938..ef0fa99 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ config.cfg *.sqlite main.css - +tmp # Created by https://www.gitignore.io/api/linux,macos,python,windows diff --git a/app/templates/packages/release_new.html b/app/templates/packages/release_new.html index 29be43f..207cf03 100644 --- a/app/templates/packages/release_new.html +++ b/app/templates/packages/release_new.html @@ -6,7 +6,7 @@ {% block content %} {% from "macros/forms.html" import render_field, render_submit_field %} -
+ {{ form.hidden_tag() }} {{ render_field(form.title) }} diff --git a/app/views/__init__.py b/app/views/__init__.py index 09ce16f..3c8c009 100644 --- a/app/views/__init__.py +++ b/app/views/__init__.py @@ -12,13 +12,18 @@ cache = SimpleCache() @app.template_filter() def domain(url): - return urlparse(url).netloc + return urlparse(url).netloc -# TODO: remove on production! +# Use nginx to serve files on production instead @app.route("/static/") def send_static(path): return send_from_directory("static", path) +@app.route("/uploads/") +def send_upload(path): + import os + return send_from_directory(os.path.abspath(app.config["UPLOAD_FOLDER"]), path) + @app.route("/") @menu.register_menu(app, ".", "Home") def home_page(): diff --git a/app/views/packages.py b/app/views/packages.py index 1eee29c..4e221a5 100644 --- a/app/views/packages.py +++ b/app/views/packages.py @@ -8,8 +8,12 @@ from flask_wtf import FlaskForm from wtforms import * from wtforms.validators import * +def isFilenameAllowed(filename, exts): + return "." in filename and \ + filename.rsplit(".", 1)[1].lower() in exts -# TODO: the following could be made into one route, except I'm not sure how + +# TODO: the following could be made into one route, except I"m not sure how # to do the menu def doPackageList(type): @@ -115,16 +119,16 @@ def package_download_page(type, author, name): class PackageForm(FlaskForm): - name = StringField("Name", [InputRequired(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")]) - title = StringField("Title", [InputRequired(), Length(3, 50)]) - shortDesc = StringField("Short Description", [InputRequired(), Length(1,200)]) - desc = TextAreaField("Long Description", [Optional(), Length(0,10000)]) - type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD) - repo = StringField("Repo URL", [Optional(), URL()]) - website = StringField("Website URL", [Optional(), URL()]) + name = StringField("Name", [InputRequired(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")]) + title = StringField("Title", [InputRequired(), Length(3, 50)]) + shortDesc = StringField("Short Description", [InputRequired(), Length(1,200)]) + desc = TextAreaField("Long Description", [Optional(), Length(0,10000)]) + type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD) + repo = StringField("Repo URL", [Optional(), URL()]) + website = StringField("Website URL", [Optional(), URL()]) issueTracker = StringField("Issue Tracker URL", [Optional(), URL()]) - forums = IntegerField("Forum Topic ID", [InputRequired(), NumberRange(0,999999)]) - submit = SubmitField("Save") + forums = IntegerField("Forum Topic ID", [InputRequired(), NumberRange(0,999999)]) + submit = SubmitField("Save") @menu.register_menu(app, ".new", "Create", order=21, visible_when=lambda: current_user.is_authenticated) @app.route("/new/", methods=["GET", "POST"]) @@ -175,19 +179,19 @@ def approve_package_page(type=None, author=None, name=None): return redirect(package.getDetailsURL()) class CreatePackageReleaseForm(FlaskForm): - name = StringField("Name") - title = StringField("Title") - uploadOpt = RadioField ("File", choices=[("vcs", "From VCS Commit or Branch"), ("upload", "File Upload")]) - vcsLabel = StringField("VCS Commit or Branch", default="master") + name = StringField("Name") + title = StringField("Title") + uploadOpt = RadioField ("File", choices=[("vcs", "From VCS Commit or Branch"), ("upload", "File Upload")]) + vcsLabel = StringField("VCS Commit or Branch", default="master") fileUpload = FileField("File Upload") - submit = SubmitField("Save") + submit = SubmitField("Save") class EditPackageReleaseForm(FlaskForm): - name = StringField("Name") - title = StringField("Title") - url = StringField("URL", [URL]) - approved = BooleanField("Is Approved") - submit = SubmitField("Save") + name = StringField("Name") + title = StringField("Title") + url = StringField("URL", [URL]) + approved = BooleanField("Is Approved") + submit = SubmitField("Save") @app.route("/s///releases/new/", methods=["GET", "POST"]) @login_required @@ -197,8 +201,10 @@ def create_release_page(type, author, name): return redirect(package.getDetailsURL()) # Initial form class from post data and default data - form = CreatePackageReleaseForm(formdata=request.form) + form = CreatePackageReleaseForm() if request.method == "POST" and form.validate(): + for key, value in request.files.items() : + print (key, value) if form["uploadOpt"].data == "vcs": rel = PackageRelease() rel.package = package @@ -206,9 +212,24 @@ def create_release_page(type, author, name): rel.url = form["vcsLabel"].data # TODO: get URL to commit from branch name db.session.commit() - return redirect(package.getDetailsURL()) # redirect + return redirect(package.getDetailsURL()) else: - raise Exception("Unimplemented option = file upload") + file = form.fileUpload.data + if not file or file.filename == "": + flash("No selected file", "error") + elif not isFilenameAllowed(file.filename, ["zip"]): + flash("Please select a zip file", "error") + else: + import random, string, os + filename = ''.join(random.choice(string.ascii_lowercase + string.ascii_uppercase + string.digits) for _ in range(10)) + ".zip" + file.save(os.path.join(app.config["UPLOAD_FOLDER"], filename)) + + rel = PackageRelease() + rel.package = package + rel.title = form["title"].data + rel.url = "/uploads/" + filename + db.session.commit() + return redirect(package.getDetailsURL()) return render_template("packages/release_new.html", package=package, form=form) @@ -227,7 +248,7 @@ def edit_release_page(type, author, name, id): if package.name != name or package.type != PackageType[type.upper()]: abort(404) - canEdit = package.checkPerm(current_user, Permission.MAKE_RELEASE) + canEdit = package.checkPerm(current_user, Permission.MAKE_RELEASE) canApprove = package.checkPerm(current_user, Permission.APPROVE_RELEASE) if not (canEdit or canApprove): return redirect(package.getDetailsURL()) diff --git a/config.example.cfg b/config.example.cfg index 05fa9be..efb4c4f 100644 --- a/config.example.cfg +++ b/config.example.cfg @@ -7,3 +7,5 @@ SQLALCHEMY_DATABASE_URI = "sqlite:///../db.sqlite" GITHUB_CLIENT_ID = "" GITHUB_CLIENT_SECRET = "" + +UPLOAD_FOLDER="tmp"