Add dependencies

This commit is contained in:
rubenwardy 2018-05-27 21:31:11 +01:00
parent 82159d488d
commit 63af1535b9
No known key found for this signature in database
GPG Key ID: A1E29D52FF81513C
8 changed files with 178 additions and 20 deletions

@ -233,7 +233,6 @@ class PackagePropertyKey(enum.Enum):
else:
return str(value)
provides = db.Table("provides",
db.Column("package_id", db.Integer, db.ForeignKey("package.id"), primary_key=True),
db.Column("metapackage_id", db.Integer, db.ForeignKey("meta_package.id"), primary_key=True)
@ -244,6 +243,74 @@ tags = db.Table("tags",
db.Column("package_id", db.Integer, db.ForeignKey("package.id"), primary_key=True)
)
class Dependency(db.Model):
id = db.Column(db.Integer, primary_key=True)
depender_id = db.Column(db.Integer, db.ForeignKey("package.id"), nullable=True)
package_id = db.Column(db.Integer, db.ForeignKey("package.id"), nullable=True)
package = db.relationship("Package", foreign_keys=[package_id])
meta_package_id = db.Column(db.Integer, db.ForeignKey("meta_package.id"), nullable=True)
optional = db.Column(db.Boolean, nullable=False, default=False)
__table_args__ = (db.UniqueConstraint('depender_id', 'package_id', 'meta_package_id', name='_dependency_uc'), )
def __init__(self, depender=None, package=None, meta=None):
if depender is None:
return
self.depender = depender
packageProvided = package is not None
metaProvided = meta is not None
if packageProvided and not metaProvided:
self.package = package
elif metaProvided and not packageProvided:
self.meta_package = meta
else:
raise Exception("Either meta or package must be given, but not both!")
def __str__(self):
if self.package is not None:
return self.package.author.username + "/" + self.package.name
elif self.meta_package is not None:
return self.meta_package.name
else:
raise Exception("Meta and package are both none!")
@staticmethod
def SpecToList(depender, spec, cache={}):
retval = []
arr = spec.split(",")
import re
pattern1 = re.compile("^([a-z0-9_]+)$")
pattern2 = re.compile("^([A-Za-z0-9_]+)/([a-z0-9_]+)$")
for x in arr:
x = x.strip()
if x == "":
continue
if pattern1.match(x):
meta = MetaPackage.GetOrCreate(x, cache)
retval.append(Dependency(depender, meta=meta))
else:
m = pattern2.match(x)
username = m.group(1)
name = m.group(2)
user = User.query.filter_by(username=username).first()
if user is None:
raise Exception("Unable to find user " + username)
package = Package.query.filter_by(author=user, name=name).first()
if package is None:
raise Exception("Unable to find package " + name + " by " + username)
retval.append(Dependency(depender, package=package))
return retval
class Package(db.Model):
id = db.Column(db.Integer, primary_key=True)
@ -270,6 +337,8 @@ class Package(db.Model):
provides = db.relationship("MetaPackage", secondary=provides, lazy="subquery",
backref=db.backref("packages", lazy=True))
dependencies = db.relationship("Dependency", backref="depender", lazy="dynamic", foreign_keys=[Dependency.depender_id])
tags = db.relationship("Tag", secondary=tags, lazy="subquery",
backref=db.backref("packages", lazy=True))
@ -403,6 +472,7 @@ class Package(db.Model):
class MetaPackage(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
dependencies = db.relationship("Dependency", backref="meta_package", lazy="dynamic")
def __init__(self, name=None):
self.name = name

@ -86,6 +86,18 @@
input = $('input[type=text]', this);
var selected = [];
var lookup = {};
for (var i = 0; i < source.length; i++) {
lookup[source[i].id] = source[i];
}
var selected_raw = result.val().split(",");
for (var i = 0; i < selected_raw.length; i++) {
var raw = selected_raw[i].trim();
if (lookup[raw]) {
selected.push(raw);
}
}
selector.click(function() { input.focus(); })
.delegate('.tag a', 'click', function() {
@ -122,8 +134,8 @@
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);
var value = lookup[selected[i]] || { value: selected[i] };
addTag(selected[i], value.value);
}
result.val(selected.join(","))
}
@ -134,7 +146,9 @@
e.preventDefault();
else if (e.keyCode === $.ui.keyCode.COMMA) {
var item = input.val();
if (item.match(/^([a-z0-9_]+)$/)) {
if (item.length == 0) {
input.data("ui-autocomplete").search("");
} else if (item.match(/^([a-z0-9_]+)$/)) {
selectItem(item);
recreate();
input.val("");
@ -148,6 +162,7 @@
var item = selected[selected.length - 1];
selected.splice(selected.length - 1, 1);
recreate();
if (!(item.indexOf("/") > 0))
input.val(item);
e.preventDefault();
return true;
@ -207,6 +222,12 @@
var input = $(this).parent().children("input[type='text']");
input.hide();
$(this).csvSelector(meta_packages, input.attr("name"), input);
})
});
$(".deps_selector").each(function() {
var input = $(this).parent().children("input[type='text']");
input.hide();
$(this).csvSelector(all_packages, input.attr("name"), input);
});
});
})(jQuery);

@ -58,6 +58,25 @@
</div>
{% endmacro %}
{% macro render_deps_field(field, label=None, label_visible=true, right_url=None, right_label=None) -%}
<div class="form-group {% if field.errors %}has-error{% endif %} {{ kwargs.pop('class_', '') }}">
{% if field.type != 'HiddenField' and label_visible %}
{% if not label %}{% set label=field.label.text %}{% endif %}
<label for="{{ field.id }}" class="control-label">{{ label|safe }}</label>
{% endif %}
<div class="deps_selector bulletselector">
<input type="text" placeholder="Start typing to see suggestions">
<div class="clearboth"></div>
</div>
{{ field(class_='form-control', **kwargs) }}
{% if field.errors %}
{% for e in field.errors %}
<p class="help-block">{{ e }}</p>
{% endfor %}
{% endif %}
</div>
{% endmacro %}
{% macro render_checkbox_field(field, label=None) -%}
{% if not label %}{% set label=field.label.text %}{% endif %}
<div class="checkbox">

@ -21,9 +21,20 @@
},
{% endfor %}
]
all_packages = meta_packages.slice();
{% for p in packages %}
{# This is safe as name can only contain `[a-z0-9_]` #}
all_packages.push({
id: "{{ p.author.username }}/{{ p.name }}",
value: {{ p.title | tojson }} + " by " + {{ p.author.display_name | tojson }},
toString: function() { return {{ p.title | tojson }} + " by " + {{ p.author.display_name | tojson }} + " only"; },
});
{% endfor %}
</script>
{% from "macros/forms.html" import render_field, render_submit_field, form_includes, render_multiselect_field, render_mpackage_field %}
{% from "macros/forms.html" import render_field, render_submit_field, form_includes, render_multiselect_field, render_mpackage_field, render_deps_field %}
{{ form_includes() }}
<form method="POST" action="" class="tableform">
@ -36,6 +47,8 @@
{{ render_field(form.type, class_="pkg_meta") }}
{{ render_field(form.license, class_="pkg_meta") }}
{{ render_mpackage_field(form.provides_str, class_="pkg_meta", placeholder="Comma separated list") }}
{{ render_deps_field(form.harddep_str, class_="pkg_meta", placeholder="Comma separated list") }}
{{ render_deps_field(form.softdep_str, class_="pkg_meta", placeholder="Comma separated list") }}
{{ render_multiselect_field(form.tags, class_="pkg_meta") }}
<div class="pkg_wiz_1">

@ -162,27 +162,33 @@
{% endfor %}
</ul>
<!-- <table class="table-topalign">
<table class="table-topalign">
<tr>
<td>
<h3>Dependencies</h3>
<ul>
{% for p in package.harddeps %}
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }}</li>
{% for dep in package.dependencies %}
<li>
{%- if dep.package %}
<a href="{{ dep.package.getDetailsURL() }}">{{ dep.package.title }}</a> by {{ dep.package.author.display_name }}
{% elif dep.meta_package %}
<a href="{{ url_for('meta_package_page', name=dep.meta_package.name) }}">{{ dep.meta_package.name }}</a>
{% else %}
{% if not package.softdeps %}
<li>No dependencies.</li>
{{ "Excepted package or meta_package in dep!" | throw }}
{% endif %}
{% endfor %}
{% for p in package.softdeps %}
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }} [optional]</li>
{% if dep.optional %}
[optional]
{% endif %}
</li>
{% else %}
<li><i>No dependencies</i></li>
{% endfor %}
</ul>
</td>
<td>
<h3>Required by</h3>
<ul>
{% for p in package.dependents %}
<!-- {% for p in package.dependents %}
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }}</li>
{% else %}
{% if not package.softdependents %}
@ -191,11 +197,11 @@
{% endfor %}
{% for p in package.softdependents %}
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }} [optional]</li>
{% endfor %}
{% endfor %} -->
</ul>
</td>
</tr>
</table> -->
</table>
{% if current_user.is_authenticated or requests %}
<h3>Edit Requests</h3>

@ -27,6 +27,10 @@ from werkzeug.contrib.cache import SimpleCache
from urllib.parse import urlparse
cache = SimpleCache()
@app.template_filter()
def throw(err):
raise Exception(err)
@app.template_filter()
def domain(url):
return urlparse(url).netloc

@ -108,6 +108,8 @@ class PackageForm(FlaskForm):
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", [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)
harddep_str = StringField("Hard Dependencies", [Optional(), Length(0,1000)])
softdep_str = StringField("Soft Dependencies", [Optional(), Length(0,1000)])
repo = StringField("Repo URL", [Optional(), URL()])
website = StringField("Website URL", [Optional(), URL()])
issueTracker = StringField("Issue Tracker URL", [Optional(), URL()])
@ -146,6 +148,9 @@ def create_edit_package_page(author=None, name=None):
# Initial form class from post data and default data
if request.method == "GET" and package is not None:
deps = package.dependencies
form.harddep_str.data = ",".join([str(x) for x in deps if not x.optional])
form.softdep_str.data = ",".join([str(x) for x in deps if x.optional])
form.provides_str.data = MetaPackage.ListToSpec(package.provides)
if request.method == "POST" and form.validate():
@ -174,11 +179,21 @@ def create_edit_package_page(author=None, name=None):
for m in mpackages:
package.provides.append(m)
Dependency.query.filter_by(depender=package).delete()
deps = Dependency.SpecToList(package, form.harddep_str.data, mpackage_cache)
for dep in deps:
dep.optional = False
db.session.add(dep)
deps = Dependency.SpecToList(package, form.softdep_str.data, mpackage_cache)
for dep in deps:
dep.optional = True
db.session.add(dep)
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))
@ -191,9 +206,14 @@ def create_edit_package_page(author=None, name=None):
return redirect(package.getDetailsURL())
package_query = Package.query.filter_by(approved=True, soft_deleted=False)
if package is not None:
package_query = package_query.filter(Package.id != package.id)
enableWizard = name is None and request.method != "POST"
return render_template("packages/create_edit.html", package=package, \
form=form, author=author, enable_wizard=enableWizard, \
packages=package_query.all(), \
mpackages=MetaPackage.query.order_by(db.asc(MetaPackage.name)).all())
@app.route("/packages/<author>/<name>/approve/", methods=["POST"])

@ -262,6 +262,7 @@ No warranty is provided, express or implied, for any part of the project.
mod.forums = 9039
mod.shortDesc = "Adds sweet food"
mod.desc = "This is the long desc"
food_sweet = mod
db.session.add(mod)
game1 = Package()
@ -326,6 +327,10 @@ Uses the CTF PvP Engine.
metas[package.name] = meta
package.provides.append(meta)
dep = Dependency(food_sweet, meta=metas["food"])
db.session.add(dep)
delete_db = len(sys.argv) >= 2 and sys.argv[1].strip() == "-d"
if delete_db and os.path.isfile("db.sqlite"):