mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-22 22:12:24 +01:00
Add dependencies
This commit is contained in:
parent
82159d488d
commit
63af1535b9
@ -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,7 +162,8 @@
|
||||
var item = selected[selected.length - 1];
|
||||
selected.splice(selected.length - 1, 1);
|
||||
recreate();
|
||||
input.val(item);
|
||||
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 %}
|
||||
{{ "Excepted package or meta_package in dep!" | throw }}
|
||||
{% endif %}
|
||||
{% if dep.optional %}
|
||||
[optional]
|
||||
{% endif %}
|
||||
</li>
|
||||
{% else %}
|
||||
{% if not package.softdeps %}
|
||||
<li>No dependencies.</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% for p in package.softdeps %}
|
||||
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }} [optional]</li>
|
||||
<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"])
|
||||
|
5
setup.py
5
setup.py
@ -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"):
|
||||
|
Loading…
Reference in New Issue
Block a user