From 82159d488d87d204390dc58fdd30ca2167156b79 Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sun, 27 May 2018 20:15:35 +0100 Subject: [PATCH] Add meta package selector --- README.md | 8 +- app/models.py | 24 +++-- app/public/static/tagselector.js | 116 +++++++++++++++++++++++- app/scss/components.scss | 16 ++-- app/templates/macros/forms.html | 4 +- app/templates/packages/create_edit.html | 13 +++ app/views/packages/__init__.py | 10 +- 7 files changed, 163 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index c5203e1..8dc588b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ the current session: If you need to, reset the db like so: - python3 setup.py -d + python3 setup.py -t Then run the server: @@ -43,6 +43,12 @@ Then view in your web browser: http://localhost:5000/ ## How-tos +### Start celery worker + +```sh +FLASK_CONFIG=../config.cfg celery -A app.tasks.celery worker +``` + ### Create migration ```sh diff --git a/app/models.py b/app/models.py index ea8bb18..bfd8a31 100644 --- a/app/models.py +++ b/app/models.py @@ -414,6 +414,19 @@ class MetaPackage(db.Model): def ListToSpec(list): return ",".join([str(x) for x in list]) + @staticmethod + def GetOrCreate(name, cache={}): + mp = cache.get(name) + if mp is None: + mp = MetaPackage.query.filter_by(name=name).first() + + if mp is None: + mp = MetaPackage(name) + db.session.add(mp) + + cache[name] = mp + return mp + @staticmethod def SpecToList(spec, cache={}): retval = [] @@ -430,16 +443,7 @@ class MetaPackage(db.Model): if not pattern.match(x): continue - mp = cache.get(x) - if mp is None: - mp = MetaPackage.query.filter_by(name=x).first() - - if mp is None: - mp = MetaPackage(x) - db.session.add(mp) - - cache[x] = mp - retval.append(mp) + retval.append(MetaPackage.GetOrCreate(x, cache)) return retval diff --git a/app/public/static/tagselector.js b/app/public/static/tagselector.js index d5895bf..2e90657 100644 --- a/app/public/static/tagselector.js +++ b/app/public/static/tagselector.js @@ -5,7 +5,7 @@ * https://petprojects.googlecode.com/svn/trunk/GPL-LICENSE.txt */ (function($) { - $.fn.tagSelector = function(source, name, select) { + $.fn.selectSelector = function(source, name, select) { return this.each(function() { var selector = $(this), input = $('input[type=text]', this); @@ -80,15 +80,115 @@ }); } + $.fn.csvSelector = function(source, name, result, allowSlash) { + return this.each(function() { + var selector = $(this), + input = $('input[type=text]', this); + + var selected = []; + + selector.click(function() { input.focus(); }) + .delegate('.tag a', 'click', function() { + var id = $(this).parent().data("id"); + for (var i = 0; i < selected.length; i++) { + if (selected[i] == id) { + selected.splice(i, 1); + } + } + recreate(); + }); + + + function selectItem(id) { + for (var i = 0; i < selected.length; i++) { + if (selected[i] == id) { + return false; + } + } + selected.push(id); + return true; + } + + function addTag(id, value) { + var tag = $('') + .text(value) + .data("id", id) + .append(' x') + .insertBefore(input); + + input.attr("placeholder", null); + } + + function recreate() { + selector.find("span").remove(); + for (var i = 0; i < selected.length; i++) { + var value = source[selected[i]] || selected[i]; + addTag(selected[i], value); + } + result.val(selected.join(",")) + } + recreate(); + + input.keydown(function(e) { + if (e.keyCode === $.ui.keyCode.TAB && $(this).data('ui-autocomplete').menu.active) + e.preventDefault(); + else if (e.keyCode === $.ui.keyCode.COMMA) { + var item = input.val(); + if (item.match(/^([a-z0-9_]+)$/)) { + selectItem(item); + recreate(); + input.val(""); + } else { + alert("Only lowercase alphanumeric and number names allowed."); + } + e.preventDefault(); + return true; + } else if (e.keyCode === $.ui.keyCode.BACKSPACE) { + if (input.val() == "") { + var item = selected[selected.length - 1]; + selected.splice(selected.length - 1, 1); + recreate(); + input.val(item); + e.preventDefault(); + return true; + } + } + }) + .autocomplete({ + minLength: 0, + source: source, + select: function(event, ui) { + selectItem(ui.item.id); + recreate(); + input.val(""); + return false; + } + }); + + input.data('ui-autocomplete')._renderItem = function(ul, item) { + return $('
  • ') + .data('item.autocomplete', item) + .append($('').text(item.toString())) + .appendTo(ul); + }; + + input.data('ui-autocomplete')._resizeMenu = function(ul, item) { + var ul = this.menu.element; + ul.outerWidth(Math.max( + ul.width('').outerWidth(), + selector.outerWidth() + )); + }; + }); + } + $(function() { $(".multichoice_selector").each(function() { var ele = $(this); var sel = ele.parent().find("select"); - console.log(sel.attr("name")); - sel.css("display", "none"); + sel.hide(); var options = []; - sel.find("option").each(function() { var text = $(this).text(); options.push({ @@ -100,7 +200,13 @@ }); console.log(options); - ele.tagSelector(options, sel.attr("name"), sel); + ele.selectSelector(options, sel.attr("name"), sel); + }); + + $(".metapackage_selector").each(function() { + var input = $(this).parent().children("input[type='text']"); + input.hide(); + $(this).csvSelector(meta_packages, input.attr("name"), input); }) }); })(jQuery); diff --git a/app/scss/components.scss b/app/scss/components.scss index 2cf8af4..a8ec31a 100644 --- a/app/scss/components.scss +++ b/app/scss/components.scss @@ -87,7 +87,7 @@ a:hover { } .button, .buttonset li a, input[type=submit], input[type=text], - input[type=password], textarea, select, .multichoice_selector { + input[type=password], textarea, select, .bulletselector { text-align: center; display: inline-block; padding: 0.4em 1em; @@ -99,7 +99,7 @@ a:hover { font-size: 100%; } -input[type=text], input[type=password], textarea, select, .multichoice_selector { +input[type=text], input[type=password], textarea, select, .bulletselector { text-align: left; } @@ -147,13 +147,13 @@ select:not([multiple]) { padding: 0 8px 8px 0; } -.form-group input, .form-group textarea, .form-group .multichoice_selector { +.form-group input, .form-group textarea, .form-group .bulletselector { display: block; min-width: 100%; max-width: 100%; } -.box .form-group input, .box .form-group textarea, .form-group .multichoice_selector { +.box .form-group input, .box .form-group textarea, .form-group .bulletselector { min-width: 95%; max-width: 95%; } @@ -197,7 +197,7 @@ select:not([multiple]) { } -.multichoice_selector input { +.bulletselector input { border: none; border-radius: 0; -moz-border-radius: 0; @@ -211,7 +211,7 @@ select:not([multiple]) { white-space: nowrap; background: transparent; } -.multichoice_selector .tag { +.bulletselector .tag { background: #375D81; border-radius: 3px; -moz-border-radius: 3px; @@ -223,11 +223,11 @@ select:not([multiple]) { margin-bottom: 0.3em; vertical-align: baseline; } -.multichoice_selector .tag a { +.bulletselector .tag a { color: #FFF; cursor: pointer; } -.multichoice_selector .tag a:hover { +.bulletselector .tag a:hover { color: #0099CC; text-decoration: none; } diff --git a/app/templates/macros/forms.html b/app/templates/macros/forms.html index e0ad2de..430c4e8 100644 --- a/app/templates/macros/forms.html +++ b/app/templates/macros/forms.html @@ -26,7 +26,7 @@ {% if not label %}{% set label=field.label.text %}{% endif %} {% endif %} -
    +
    @@ -45,7 +45,7 @@ {% if not label %}{% set label=field.label.text %}{% endif %} {% endif %} -
    +
    diff --git a/app/templates/packages/create_edit.html b/app/templates/packages/create_edit.html index 8eb7213..c7c2dbb 100644 --- a/app/templates/packages/create_edit.html +++ b/app/templates/packages/create_edit.html @@ -10,6 +10,19 @@ {% block content %}

    Create Package

    + + {% from "macros/forms.html" import render_field, render_submit_field, form_includes, render_multiselect_field, render_mpackage_field %} {{ form_includes() }} diff --git a/app/views/packages/__init__.py b/app/views/packages/__init__.py index e5a515d..3c3acf0 100644 --- a/app/views/packages/__init__.py +++ b/app/views/packages/__init__.py @@ -106,7 +106,7 @@ class PackageForm(FlaskForm): desc = TextAreaField("Long Description", [Optional(), Length(0,10000)]) type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD) license = QuerySelectField("License", [InputRequired()], query_factory=lambda: License.query, get_pk=lambda a: a.id, get_label=lambda a: a.name) - provides_str = StringField("Provides", [InputRequired(), Length(1,1000)]) + provides_str = StringField("Provides", [Optional(), Length(0,1000)]) tags = QuerySelectMultipleField('Tags', query_factory=lambda: Tag.query.order_by(db.asc(Tag.name)), get_pk=lambda a: a.id, get_label=lambda a: a.title) repo = StringField("Repo URL", [Optional(), URL()]) website = StringField("Website URL", [Optional(), URL()]) @@ -174,6 +174,11 @@ def create_edit_package_page(author=None, name=None): for m in mpackages: package.provides.append(m) + if wasNew and package.type == PackageType.MOD and not package.name in mpackage_cache: + m = MetaPackage.GetOrCreate(package.name, mpackage_cache) + package.provides.append(m) + + package.tags.clear() for tag in form.tags.raw_data: package.tags.append(Tag.query.get(tag)) @@ -188,7 +193,8 @@ def create_edit_package_page(author=None, name=None): enableWizard = name is None and request.method != "POST" return render_template("packages/create_edit.html", package=package, \ - form=form, author=author, enable_wizard=enableWizard) + form=form, author=author, enable_wizard=enableWizard, \ + mpackages=MetaPackage.query.order_by(db.asc(MetaPackage.name)).all()) @app.route("/packages///approve/", methods=["POST"]) @login_required