aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrubenwardy <rw@rubenwardy.com>2018-05-27 21:31:11 +0100
committerrubenwardy <rw@rubenwardy.com>2018-05-27 21:31:11 +0100
commit63af1535b90358f1a26248cae217f4a74fdc1f84 (patch)
tree55ce4339a5bea144b7b25ecb6b8e97486b94ed6b
parent82159d488d87d204390dc58fdd30ca2167156b79 (diff)
downloadcheatdb-63af1535b90358f1a26248cae217f4a74fdc1f84.tar.xz
Add dependencies
-rw-r--r--app/models.py72
-rw-r--r--app/public/static/tagselector.js31
-rw-r--r--app/templates/macros/forms.html19
-rw-r--r--app/templates/packages/create_edit.html15
-rw-r--r--app/templates/packages/view.html30
-rw-r--r--app/views/__init__.py4
-rw-r--r--app/views/packages/__init__.py22
-rw-r--r--setup.py5
8 files changed, 178 insertions, 20 deletions
diff --git a/app/models.py b/app/models.py
index bfd8a31..3283595 100644
--- a/app/models.py
+++ b/app/models.py
@@ -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
diff --git a/app/public/static/tagselector.js b/app/public/static/tagselector.js
index 2e90657..32e4882 100644
--- a/app/public/static/tagselector.js
+++ b/app/public/static/tagselector.js
@@ -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);
diff --git a/app/templates/macros/forms.html b/app/templates/macros/forms.html
index 430c4e8..940c4a2 100644
--- a/app/templates/macros/forms.html
+++ b/app/templates/macros/forms.html
@@ -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">
diff --git a/app/templates/packages/create_edit.html b/app/templates/packages/create_edit.html
index c7c2dbb..666d4cd 100644
--- a/app/templates/packages/create_edit.html
+++ b/app/templates/packages/create_edit.html
@@ -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">
diff --git a/app/templates/packages/view.html b/app/templates/packages/view.html
index 1623f1c..834ea63 100644
--- a/app/templates/packages/view.html
+++ b/app/templates/packages/view.html
@@ -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>
diff --git a/app/views/__init__.py b/app/views/__init__.py
index 8fff788..2559969 100644
--- a/app/views/__init__.py
+++ b/app/views/__init__.py
@@ -28,6 +28,10 @@ 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
diff --git a/app/views/packages/__init__.py b/app/views/packages/__init__.py
index 3c3acf0..07f62a9 100644
--- a/app/views/packages/__init__.py
+++ b/app/views/packages/__init__.py
@@ -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"])
diff --git a/setup.py b/setup.py
index 22b80cf..5873067 100644
--- a/setup.py
+++ b/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"):