mirror of
https://github.com/minetest/contentdb.git
synced 2024-12-23 06:22:24 +01:00
Add dependencies
This commit is contained in:
parent
82159d488d
commit
63af1535b9
@ -233,7 +233,6 @@ class PackagePropertyKey(enum.Enum):
|
|||||||
else:
|
else:
|
||||||
return str(value)
|
return str(value)
|
||||||
|
|
||||||
|
|
||||||
provides = db.Table("provides",
|
provides = db.Table("provides",
|
||||||
db.Column("package_id", db.Integer, db.ForeignKey("package.id"), primary_key=True),
|
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)
|
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)
|
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):
|
class Package(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
|
||||||
@ -270,6 +337,8 @@ class Package(db.Model):
|
|||||||
provides = db.relationship("MetaPackage", secondary=provides, lazy="subquery",
|
provides = db.relationship("MetaPackage", secondary=provides, lazy="subquery",
|
||||||
backref=db.backref("packages", lazy=True))
|
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",
|
tags = db.relationship("Tag", secondary=tags, lazy="subquery",
|
||||||
backref=db.backref("packages", lazy=True))
|
backref=db.backref("packages", lazy=True))
|
||||||
|
|
||||||
@ -403,6 +472,7 @@ class Package(db.Model):
|
|||||||
class MetaPackage(db.Model):
|
class MetaPackage(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
name = db.Column(db.String(100), unique=True, nullable=False)
|
name = db.Column(db.String(100), unique=True, nullable=False)
|
||||||
|
dependencies = db.relationship("Dependency", backref="meta_package", lazy="dynamic")
|
||||||
|
|
||||||
def __init__(self, name=None):
|
def __init__(self, name=None):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
@ -86,6 +86,18 @@
|
|||||||
input = $('input[type=text]', this);
|
input = $('input[type=text]', this);
|
||||||
|
|
||||||
var selected = [];
|
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(); })
|
selector.click(function() { input.focus(); })
|
||||||
.delegate('.tag a', 'click', function() {
|
.delegate('.tag a', 'click', function() {
|
||||||
@ -122,8 +134,8 @@
|
|||||||
function recreate() {
|
function recreate() {
|
||||||
selector.find("span").remove();
|
selector.find("span").remove();
|
||||||
for (var i = 0; i < selected.length; i++) {
|
for (var i = 0; i < selected.length; i++) {
|
||||||
var value = source[selected[i]] || selected[i];
|
var value = lookup[selected[i]] || { value: selected[i] };
|
||||||
addTag(selected[i], value);
|
addTag(selected[i], value.value);
|
||||||
}
|
}
|
||||||
result.val(selected.join(","))
|
result.val(selected.join(","))
|
||||||
}
|
}
|
||||||
@ -134,7 +146,9 @@
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
else if (e.keyCode === $.ui.keyCode.COMMA) {
|
else if (e.keyCode === $.ui.keyCode.COMMA) {
|
||||||
var item = input.val();
|
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);
|
selectItem(item);
|
||||||
recreate();
|
recreate();
|
||||||
input.val("");
|
input.val("");
|
||||||
@ -148,7 +162,8 @@
|
|||||||
var item = selected[selected.length - 1];
|
var item = selected[selected.length - 1];
|
||||||
selected.splice(selected.length - 1, 1);
|
selected.splice(selected.length - 1, 1);
|
||||||
recreate();
|
recreate();
|
||||||
input.val(item);
|
if (!(item.indexOf("/") > 0))
|
||||||
|
input.val(item);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -207,6 +222,12 @@
|
|||||||
var input = $(this).parent().children("input[type='text']");
|
var input = $(this).parent().children("input[type='text']");
|
||||||
input.hide();
|
input.hide();
|
||||||
$(this).csvSelector(meta_packages, input.attr("name"), input);
|
$(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);
|
})(jQuery);
|
||||||
|
@ -58,6 +58,25 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% 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) -%}
|
{% macro render_checkbox_field(field, label=None) -%}
|
||||||
{% if not label %}{% set label=field.label.text %}{% endif %}
|
{% if not label %}{% set label=field.label.text %}{% endif %}
|
||||||
<div class="checkbox">
|
<div class="checkbox">
|
||||||
|
@ -21,9 +21,20 @@
|
|||||||
},
|
},
|
||||||
{% endfor %}
|
{% 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>
|
</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_includes() }}
|
||||||
|
|
||||||
<form method="POST" action="" class="tableform">
|
<form method="POST" action="" class="tableform">
|
||||||
@ -36,6 +47,8 @@
|
|||||||
{{ render_field(form.type, class_="pkg_meta") }}
|
{{ render_field(form.type, class_="pkg_meta") }}
|
||||||
{{ render_field(form.license, class_="pkg_meta") }}
|
{{ render_field(form.license, class_="pkg_meta") }}
|
||||||
{{ render_mpackage_field(form.provides_str, class_="pkg_meta", placeholder="Comma separated list") }}
|
{{ 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") }}
|
{{ render_multiselect_field(form.tags, class_="pkg_meta") }}
|
||||||
|
|
||||||
<div class="pkg_wiz_1">
|
<div class="pkg_wiz_1">
|
||||||
|
@ -162,27 +162,33 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- <table class="table-topalign">
|
<table class="table-topalign">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<h3>Dependencies</h3>
|
<h3>Dependencies</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{% for p in package.harddeps %}
|
{% for dep in package.dependencies %}
|
||||||
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }}</li>
|
<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 %}
|
{% else %}
|
||||||
{% if not package.softdeps %}
|
<li><i>No dependencies</i></li>
|
||||||
<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>
|
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<h3>Required by</h3>
|
<h3>Required by</h3>
|
||||||
<ul>
|
<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>
|
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }}</li>
|
||||||
{% else %}
|
{% else %}
|
||||||
{% if not package.softdependents %}
|
{% if not package.softdependents %}
|
||||||
@ -191,11 +197,11 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% for p in package.softdependents %}
|
{% for p in package.softdependents %}
|
||||||
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }} [optional]</li>
|
<li><a href="{{ p.getDetailsURL() }}">{{ p.title }}</a> by {{ p.author.display_name }} [optional]</li>
|
||||||
{% endfor %}
|
{% endfor %} -->
|
||||||
</ul>
|
</ul>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table> -->
|
</table>
|
||||||
|
|
||||||
{% if current_user.is_authenticated or requests %}
|
{% if current_user.is_authenticated or requests %}
|
||||||
<h3>Edit Requests</h3>
|
<h3>Edit Requests</h3>
|
||||||
|
@ -27,6 +27,10 @@ from werkzeug.contrib.cache import SimpleCache
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
cache = SimpleCache()
|
cache = SimpleCache()
|
||||||
|
|
||||||
|
@app.template_filter()
|
||||||
|
def throw(err):
|
||||||
|
raise Exception(err)
|
||||||
|
|
||||||
@app.template_filter()
|
@app.template_filter()
|
||||||
def domain(url):
|
def domain(url):
|
||||||
return urlparse(url).netloc
|
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)
|
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)])
|
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)
|
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()])
|
repo = StringField("Repo URL", [Optional(), URL()])
|
||||||
website = StringField("Website URL", [Optional(), URL()])
|
website = StringField("Website URL", [Optional(), URL()])
|
||||||
issueTracker = StringField("Issue Tracker 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
|
# Initial form class from post data and default data
|
||||||
if request.method == "GET" and package is not None:
|
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)
|
form.provides_str.data = MetaPackage.ListToSpec(package.provides)
|
||||||
|
|
||||||
if request.method == "POST" and form.validate():
|
if request.method == "POST" and form.validate():
|
||||||
@ -174,11 +179,21 @@ def create_edit_package_page(author=None, name=None):
|
|||||||
for m in mpackages:
|
for m in mpackages:
|
||||||
package.provides.append(m)
|
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:
|
if wasNew and package.type == PackageType.MOD and not package.name in mpackage_cache:
|
||||||
m = MetaPackage.GetOrCreate(package.name, mpackage_cache)
|
m = MetaPackage.GetOrCreate(package.name, mpackage_cache)
|
||||||
package.provides.append(m)
|
package.provides.append(m)
|
||||||
|
|
||||||
|
|
||||||
package.tags.clear()
|
package.tags.clear()
|
||||||
for tag in form.tags.raw_data:
|
for tag in form.tags.raw_data:
|
||||||
package.tags.append(Tag.query.get(tag))
|
package.tags.append(Tag.query.get(tag))
|
||||||
@ -191,9 +206,14 @@ def create_edit_package_page(author=None, name=None):
|
|||||||
|
|
||||||
return redirect(package.getDetailsURL())
|
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"
|
enableWizard = name is None and request.method != "POST"
|
||||||
return render_template("packages/create_edit.html", package=package, \
|
return render_template("packages/create_edit.html", package=package, \
|
||||||
form=form, author=author, enable_wizard=enableWizard, \
|
form=form, author=author, enable_wizard=enableWizard, \
|
||||||
|
packages=package_query.all(), \
|
||||||
mpackages=MetaPackage.query.order_by(db.asc(MetaPackage.name)).all())
|
mpackages=MetaPackage.query.order_by(db.asc(MetaPackage.name)).all())
|
||||||
|
|
||||||
@app.route("/packages/<author>/<name>/approve/", methods=["POST"])
|
@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.forums = 9039
|
||||||
mod.shortDesc = "Adds sweet food"
|
mod.shortDesc = "Adds sweet food"
|
||||||
mod.desc = "This is the long desc"
|
mod.desc = "This is the long desc"
|
||||||
|
food_sweet = mod
|
||||||
db.session.add(mod)
|
db.session.add(mod)
|
||||||
|
|
||||||
game1 = Package()
|
game1 = Package()
|
||||||
@ -326,6 +327,10 @@ Uses the CTF PvP Engine.
|
|||||||
metas[package.name] = meta
|
metas[package.name] = meta
|
||||||
package.provides.append(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"
|
delete_db = len(sys.argv) >= 2 and sys.argv[1].strip() == "-d"
|
||||||
if delete_db and os.path.isfile("db.sqlite"):
|
if delete_db and os.path.isfile("db.sqlite"):
|
||||||
|
Loading…
Reference in New Issue
Block a user