If you need to, reset the db like so:
- python3 setup.py -d
+ python3 setup.py -t
Then run the server:
## How-tos
+### Start celery worker
+
+```sh
+FLASK_CONFIG=../config.cfg celery -A app.tasks.celery worker
+```
+
### Create migration
```sh
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 = []
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
* 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);
});
}
+ $.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 = $('<span class="tag"/>')
+ .text(value)
+ .data("id", id)
+ .append(' <a>x</a>')
+ .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 $('<li/>')
+ .data('item.autocomplete', item)
+ .append($('<a/>').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({
});
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);
}
.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;
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;
}
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%;
}
}
-.multichoice_selector input {
+.bulletselector input {
border: none;
border-radius: 0;
-moz-border-radius: 0;
white-space: nowrap;
background: transparent;
}
-.multichoice_selector .tag {
+.bulletselector .tag {
background: #375D81;
border-radius: 3px;
-moz-border-radius: 3px;
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;
}
{% if not label %}{% set label=field.label.text %}{% endif %}
<label for="{{ field.id }}" class="control-label">{{ label|safe }}</label>
{% endif %}
- <div class="multichoice_selector">
+ <div class="multichoice_selector bulletselector">
<input type="text" placeholder="Start typing to see suggestions">
<div class="clearboth"></div>
</div>
{% if not label %}{% set label=field.label.text %}{% endif %}
<label for="{{ field.id }}" class="control-label">{{ label|safe }}</label>
{% endif %}
- <div class="metapackage_selector">
+ <div class="metapackage_selector bulletselector">
<input type="text" placeholder="Start typing to see suggestions">
<div class="clearboth"></div>
</div>
{% block content %}
<h2>Create Package</h2>
+ <script>
+ meta_packages = [
+ {% for m in mpackages %}
+ {# This is safe as name can only contain `[a-z0-9_]` #}
+ {
+ id: "{{ m.name }}",
+ value: "{{ m.name }}",
+ toString: function() { return "{{ m.name }}"; },
+ },
+ {% endfor %}
+ ]
+ </script>
+
{% from "macros/forms.html" import render_field, render_submit_field, form_includes, render_multiselect_field, render_mpackage_field %}
{{ form_includes() }}
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()])
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))
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/<author>/<name>/approve/", methods=["POST"])
@login_required