mirror of
https://github.com/minetest/contentdb.git
synced 2025-01-03 03:37:28 +01:00
Add release notes and long titles to releases
Fixes #492 and fixes #480
This commit is contained in:
parent
4147e5edc7
commit
019cd66033
@ -283,7 +283,7 @@ def markdown():
|
||||
def list_all_releases():
|
||||
query = PackageRelease.query.filter_by(approved=True) \
|
||||
.filter(PackageRelease.package.has(state=PackageState.APPROVED)) \
|
||||
.order_by(db.desc(PackageRelease.releaseDate))
|
||||
.order_by(db.desc(PackageRelease.created_at))
|
||||
|
||||
if "author" in request.args:
|
||||
author = User.query.filter_by(username=request.args["author"]).first()
|
||||
@ -333,7 +333,7 @@ def create_release(token, package):
|
||||
if option not in data:
|
||||
error(400, option + " is required in the POST data")
|
||||
|
||||
return api_create_vcs_release(token, package, data["title"], data["ref"])
|
||||
return api_create_vcs_release(token, package, data["title"], data["title"], data.get("release_notes"), data["ref"])
|
||||
|
||||
elif request.files:
|
||||
file = request.files.get("file")
|
||||
@ -342,7 +342,7 @@ def create_release(token, package):
|
||||
|
||||
commit_hash = data.get("commit")
|
||||
|
||||
return api_create_zip_release(token, package, data["title"], file, None, None, "API", commit_hash)
|
||||
return api_create_zip_release(token, package, data["title"], data["title"], data.get("release_notes"), file, None, None, "API", commit_hash)
|
||||
|
||||
else:
|
||||
error(400, "Unknown release-creation method. Specify the method or provide a file.")
|
||||
@ -622,7 +622,7 @@ def homepage():
|
||||
|
||||
updated = db.session.query(Package).select_from(PackageRelease).join(Package) \
|
||||
.filter_by(state=PackageState.APPROVED) \
|
||||
.order_by(db.desc(PackageRelease.releaseDate)) \
|
||||
.order_by(db.desc(PackageRelease.created_at)) \
|
||||
.limit(20).all()
|
||||
updated = updated[:4]
|
||||
|
||||
|
@ -14,7 +14,7 @@
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from typing import Optional
|
||||
from flask import jsonify, abort, make_response, url_for, current_app
|
||||
|
||||
from app.logic.packages import do_edit_package
|
||||
@ -38,14 +38,14 @@ def guard(f):
|
||||
return ret
|
||||
|
||||
|
||||
def api_create_vcs_release(token: APIToken, package: Package, title: str, ref: str,
|
||||
def api_create_vcs_release(token: APIToken, package: Package, name: str, title: Optional[str], release_notes: Optional[str], ref: str,
|
||||
min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason="API"):
|
||||
if not token.can_operate_on_package(package):
|
||||
error(403, "API token does not have access to the package")
|
||||
|
||||
reason += ", token=" + token.name
|
||||
|
||||
rel = guard(do_create_vcs_release)(token.owner, package, title, ref, min_v, max_v, reason)
|
||||
rel = guard(do_create_vcs_release)(token.owner, package, name, title, release_notes, ref, min_v, max_v, reason)
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
@ -54,14 +54,14 @@ def api_create_vcs_release(token: APIToken, package: Package, title: str, ref: s
|
||||
})
|
||||
|
||||
|
||||
def api_create_zip_release(token: APIToken, package: Package, title: str, file,
|
||||
def api_create_zip_release(token: APIToken, package: Package, name: str, title: Optional[str], release_notes: Optional[str], file,
|
||||
min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason="API", commit_hash: str = None):
|
||||
if not token.can_operate_on_package(package):
|
||||
error(403, "API token does not have access to the package")
|
||||
|
||||
reason += ", token=" + token.name
|
||||
|
||||
rel = guard(do_create_zip_release)(token.owner, package, title, file, min_v, max_v, reason, commit_hash)
|
||||
rel = guard(do_create_zip_release)(token.owner, package, name, title, release_notes, file, min_v, max_v, reason, commit_hash)
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
|
@ -105,7 +105,7 @@ def home():
|
||||
recent_releases_query = (
|
||||
db.session.query(
|
||||
Package.id,
|
||||
func.max(PackageRelease.releaseDate).label("max_created_at")
|
||||
func.max(PackageRelease.created_at).label("max_created_at")
|
||||
)
|
||||
.join(PackageRelease, Package.releases)
|
||||
.group_by(Package.id)
|
||||
|
@ -44,7 +44,7 @@ def game_hub(package: Package):
|
||||
|
||||
updated = db.session.query(Package).select_from(PackageRelease).join(Package) \
|
||||
.filter(Package.supported_games.any(game=package, supports=True), Package.state==PackageState.APPROVED) \
|
||||
.order_by(db.desc(PackageRelease.releaseDate)) \
|
||||
.order_by(db.desc(PackageRelease.created_at)) \
|
||||
.limit(20).all()
|
||||
updated = updated[:4]
|
||||
|
||||
|
@ -20,6 +20,7 @@ from flask_babel import lazy_gettext, gettext
|
||||
from flask_login import login_required, current_user
|
||||
from flask_wtf import FlaskForm
|
||||
from wtforms import StringField, SubmitField, BooleanField, RadioField, FileField
|
||||
from wtforms.fields.simple import TextAreaField
|
||||
from wtforms.validators import InputRequired, Length, Optional
|
||||
from wtforms_sqlalchemy.fields import QuerySelectField
|
||||
|
||||
@ -28,7 +29,7 @@ from app.models import Package, db, User, PackageState, Permission, UserRank, Pa
|
||||
PackageRelease, PackageUpdateTrigger, PackageUpdateConfig
|
||||
from app.rediscache import has_key, set_temp_key, make_download_key
|
||||
from app.tasks.importtasks import check_update_config
|
||||
from app.utils import is_user_bot, is_package_page, nonempty_or_none
|
||||
from app.utils import is_user_bot, is_package_page, nonempty_or_none, normalize_line_endings
|
||||
from . import bp, get_package_tabs
|
||||
|
||||
|
||||
@ -51,9 +52,12 @@ def get_mt_releases(is_max):
|
||||
|
||||
|
||||
class CreatePackageReleaseForm(FlaskForm):
|
||||
title = StringField(lazy_gettext("Title"), [InputRequired(), Length(1, 30)])
|
||||
uploadOpt = RadioField(lazy_gettext("Method"), choices=[("upload", lazy_gettext("File Upload"))], default="upload")
|
||||
vcsLabel = StringField(lazy_gettext("Git reference (ie: commit hash, branch, or tag)"), default=None)
|
||||
name = StringField(lazy_gettext("Name"), [InputRequired(), Length(1, 30)])
|
||||
title = StringField(lazy_gettext("Title"), [Optional(), Length(1, 100)], filters=[nonempty_or_none])
|
||||
release_notes = TextAreaField(lazy_gettext("Release Notes"), [Optional(), Length(1, 100)],
|
||||
filters=[nonempty_or_none, normalize_line_endings])
|
||||
upload_mode = RadioField(lazy_gettext("Method"), choices=[("upload", lazy_gettext("File Upload"))], default="upload")
|
||||
vcs_label = StringField(lazy_gettext("Git reference (ie: commit hash, branch, or tag)"), default=None)
|
||||
file_upload = FileField(lazy_gettext("File Upload"))
|
||||
min_rel = QuerySelectField(lazy_gettext("Minimum Minetest Version"), [InputRequired()],
|
||||
query_factory=lambda: get_mt_releases(False), get_pk=lambda a: a.id, get_label=lambda a: a.name)
|
||||
@ -63,7 +67,10 @@ class CreatePackageReleaseForm(FlaskForm):
|
||||
|
||||
|
||||
class EditPackageReleaseForm(FlaskForm):
|
||||
title = StringField(lazy_gettext("Title"), [InputRequired(), Length(1, 30)])
|
||||
name = StringField(lazy_gettext("Name"), [InputRequired(), Length(1, 30)])
|
||||
title = StringField(lazy_gettext("Title"), [Optional(), Length(1, 30)], filters=[nonempty_or_none])
|
||||
release_notes = TextAreaField(lazy_gettext("Release Notes"), [Optional(), Length(1, 100)],
|
||||
filters=[nonempty_or_none, normalize_line_endings])
|
||||
url = StringField(lazy_gettext("URL"), [Optional()])
|
||||
task_id = StringField(lazy_gettext("Task ID"), filters = [lambda x: x or None])
|
||||
approved = BooleanField(lazy_gettext("Is Approved"))
|
||||
@ -88,21 +95,21 @@ def create_release(package):
|
||||
# Initial form class from post data and default data
|
||||
form = CreatePackageReleaseForm()
|
||||
if package.repo is not None:
|
||||
form["uploadOpt"].choices = [("vcs", gettext("Import from Git")), ("upload", gettext("Upload .zip file"))]
|
||||
form.upload_mode.choices = [("vcs", gettext("Import from Git")), ("upload", gettext("Upload .zip file"))]
|
||||
if request.method == "GET":
|
||||
form["uploadOpt"].data = "vcs"
|
||||
form.vcsLabel.data = request.args.get("ref")
|
||||
form.upload_mode.data = "vcs"
|
||||
form.vcs_label.data = request.args.get("ref")
|
||||
|
||||
if request.method == "GET":
|
||||
form.title.data = request.args.get("title")
|
||||
|
||||
if form.validate_on_submit():
|
||||
try:
|
||||
if form["uploadOpt"].data == "vcs":
|
||||
rel = do_create_vcs_release(current_user, package, form.title.data,
|
||||
form.vcsLabel.data, form.min_rel.data.get_actual(), form.max_rel.data.get_actual())
|
||||
if form.upload_mode.data == "vcs":
|
||||
rel = do_create_vcs_release(current_user, package, form.name.data, form.title.data, form.release_notes.data,
|
||||
form.vcs_label.data, form.min_rel.data.get_actual(), form.max_rel.data.get_actual())
|
||||
else:
|
||||
rel = do_create_zip_release(current_user, package, form.title.data,
|
||||
rel = do_create_zip_release(current_user, package, form.name.data, form.title.data, form.release_notes.data,
|
||||
form.file_upload.data, form.min_rel.data.get_actual(), form.max_rel.data.get_actual())
|
||||
return redirect(url_for("tasks.check", id=rel.task_id, r=rel.get_edit_url()))
|
||||
except LogicError as e:
|
||||
|
@ -192,7 +192,7 @@ def github_webhook():
|
||||
if package.releases.filter_by(commit_hash=ref).count() > 0:
|
||||
return
|
||||
|
||||
return api_create_vcs_release(token, package, title, ref, reason="Webhook")
|
||||
return api_create_vcs_release(token, package, title, title, None, ref, reason="Webhook")
|
||||
|
||||
return jsonify({
|
||||
"success": False,
|
||||
|
@ -68,7 +68,7 @@ def webhook_impl():
|
||||
if package.releases.filter_by(commit_hash=ref).count() > 0:
|
||||
continue
|
||||
|
||||
return api_create_vcs_release(token, package, title, ref, reason="Webhook")
|
||||
return api_create_vcs_release(token, package, title, title, None, ref, reason="Webhook")
|
||||
|
||||
return jsonify({
|
||||
"success": False,
|
||||
|
@ -224,7 +224,9 @@ Format query parameters:
|
||||
* `maintainer`: Filter by maintainer
|
||||
* Returns array of release dictionaries with keys:
|
||||
* `id`: release ID
|
||||
* `name`: short release name
|
||||
* `title`: human-readable title
|
||||
* `release_notes`: string or null, what's new in this release
|
||||
* `release_date`: Date released
|
||||
* `url`: download URL
|
||||
* `commit`: commit hash or null
|
||||
@ -248,6 +250,7 @@ Format query parameters:
|
||||
* Requires authentication.
|
||||
* Body can be JSON or multipart form data. Zip uploads must be multipart form data.
|
||||
* `title`: human-readable name of the release.
|
||||
* `release_notes`: string or null, what's new in this release.
|
||||
* For Git release creation:
|
||||
* `method`: must be `git`.
|
||||
* `ref`: (Optional) git reference, eg: `master`.
|
||||
|
@ -16,6 +16,7 @@
|
||||
|
||||
import datetime
|
||||
import re
|
||||
from typing import Optional
|
||||
|
||||
from celery import uuid
|
||||
from flask_babel import lazy_gettext
|
||||
@ -32,18 +33,20 @@ def check_can_create_release(user: User, package: Package):
|
||||
raise LogicError(403, lazy_gettext("You don't have permission to make releases"))
|
||||
|
||||
five_minutes_ago = datetime.datetime.now() - datetime.timedelta(minutes=5)
|
||||
count = package.releases.filter(PackageRelease.releaseDate > five_minutes_ago).count()
|
||||
count = package.releases.filter(PackageRelease.created_at > five_minutes_ago).count()
|
||||
if count >= 5:
|
||||
raise LogicError(429, lazy_gettext("You've created too many releases for this package in the last 5 minutes, please wait before trying again"))
|
||||
|
||||
|
||||
def do_create_vcs_release(user: User, package: Package, title: str, ref: str,
|
||||
def do_create_vcs_release(user: User, package: Package, name: str, title: Optional[str], release_notes: Optional[str], ref: str,
|
||||
min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason: str = None):
|
||||
check_can_create_release(user, package)
|
||||
|
||||
rel = PackageRelease()
|
||||
rel.package = package
|
||||
rel.title = title
|
||||
rel.name = name
|
||||
rel.title = title or name
|
||||
rel.release_notes = release_notes
|
||||
rel.url = ""
|
||||
rel.task_id = uuid()
|
||||
rel.min_rel = min_v
|
||||
@ -63,7 +66,7 @@ def do_create_vcs_release(user: User, package: Package, title: str, ref: str,
|
||||
return rel
|
||||
|
||||
|
||||
def do_create_zip_release(user: User, package: Package, title: str, file,
|
||||
def do_create_zip_release(user: User, package: Package, name: str, title: Optional[str], release_notes: Optional[str], file,
|
||||
min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason: str = None,
|
||||
commit_hash: str = None):
|
||||
check_can_create_release(user, package)
|
||||
@ -77,7 +80,9 @@ def do_create_zip_release(user: User, package: Package, title: str, file,
|
||||
|
||||
rel = PackageRelease()
|
||||
rel.package = package
|
||||
rel.title = title
|
||||
rel.name = name
|
||||
rel.title = title or name
|
||||
rel.release_notes = release_notes
|
||||
rel.url = uploaded_url
|
||||
rel.task_id = uuid()
|
||||
rel.commit_hash = commit_hash
|
||||
|
@ -474,7 +474,7 @@ class Package(db.Model):
|
||||
content_warnings = db.relationship("ContentWarning", secondary=ContentWarnings, back_populates="packages")
|
||||
|
||||
releases = db.relationship("PackageRelease", back_populates="package",
|
||||
lazy="dynamic", order_by=db.desc("package_release_releaseDate"), cascade="all, delete, delete-orphan")
|
||||
lazy="dynamic", order_by=db.desc("package_release_created_at"), cascade="all, delete, delete-orphan")
|
||||
|
||||
screenshots = db.relationship("PackageScreenshot", back_populates="package", foreign_keys="PackageScreenshot.package_id",
|
||||
lazy="dynamic", order_by=db.asc("package_screenshot_order"), cascade="all, delete, delete-orphan")
|
||||
@ -1085,13 +1085,15 @@ class PackageRelease(db.Model):
|
||||
package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
|
||||
package = db.relationship("Package", back_populates="releases", foreign_keys=[package_id])
|
||||
|
||||
name = db.Column(db.String(30), nullable=False)
|
||||
title = db.Column(db.String(100), nullable=False)
|
||||
releaseDate = db.Column(db.DateTime, nullable=False)
|
||||
created_at = db.Column(db.DateTime, nullable=False)
|
||||
url = db.Column(db.String(200), nullable=False, default="")
|
||||
approved = db.Column(db.Boolean, nullable=False, default=False)
|
||||
task_id = db.Column(db.String(37), nullable=True)
|
||||
commit_hash = db.Column(db.String(41), nullable=True, default=None)
|
||||
downloads = db.Column(db.Integer, nullable=False, default=0)
|
||||
release_notes = db.Column(db.UnicodeText, nullable=True, default=None)
|
||||
|
||||
min_rel_id = db.Column(db.Integer, db.ForeignKey("minetest_release.id"), nullable=True, server_default=None)
|
||||
min_rel = db.relationship("MinetestRelease", foreign_keys=[min_rel_id])
|
||||
@ -1126,9 +1128,11 @@ class PackageRelease(db.Model):
|
||||
def as_dict(self):
|
||||
return {
|
||||
"id": self.id,
|
||||
"name": self.name,
|
||||
"title": self.title,
|
||||
"release_notes": self.release_notes,
|
||||
"url": self.url if self.url != "" else None,
|
||||
"release_date": self.releaseDate.isoformat(),
|
||||
"release_date": self.created_at.isoformat(),
|
||||
"commit": self.commit_hash,
|
||||
"downloads": self.downloads,
|
||||
"min_minetest_version": self.min_rel and self.min_rel.as_dict(),
|
||||
@ -1141,7 +1145,7 @@ class PackageRelease(db.Model):
|
||||
"id": self.id,
|
||||
"title": self.title,
|
||||
"url": self.url if self.url != "" else None,
|
||||
"release_date": self.releaseDate.isoformat(),
|
||||
"release_date": self.created_at.isoformat(),
|
||||
"commit": self.commit_hash,
|
||||
"downloads": self.downloads,
|
||||
"min_minetest_version": self.min_rel and self.min_rel.as_dict(),
|
||||
@ -1169,7 +1173,7 @@ class PackageRelease(db.Model):
|
||||
id=self.id)
|
||||
|
||||
def __init__(self):
|
||||
self.releaseDate = datetime.datetime.now()
|
||||
self.created_at = datetime.datetime.now()
|
||||
|
||||
def get_download_filename(self):
|
||||
return f"{self.package.name}_{self.id}.zip"
|
||||
|
@ -5,15 +5,15 @@
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
function check_opt() {
|
||||
if (document.querySelector("input[name='uploadOpt']:checked").value === "vcs") {
|
||||
if (document.querySelector("input[name='upload_mode']:checked").value === "vcs") {
|
||||
document.getElementById("file_upload").parentElement.classList.add("d-none");
|
||||
document.getElementById("vcsLabel").parentElement.classList.remove("d-none");
|
||||
document.getElementById("vcs_label").parentElement.classList.remove("d-none");
|
||||
} else {
|
||||
document.getElementById("file_upload").parentElement.classList.remove("d-none");
|
||||
document.getElementById("vcsLabel").parentElement.classList.add("d-none");
|
||||
document.getElementById("vcs_label").parentElement.classList.add("d-none");
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll("input[name='uploadOpt']").forEach(x => x.addEventListener("change", check_opt));
|
||||
document.querySelectorAll("input[name='upload_mode']").forEach(x => x.addEventListener("change", check_opt));
|
||||
check_opt();
|
||||
});
|
||||
|
@ -343,7 +343,7 @@ class QueryBuilder:
|
||||
elif self.order_by == "approved_at" or self.order_by == "date":
|
||||
to_order = Package.approved_at
|
||||
elif self.order_by == "last_release":
|
||||
to_order = PackageRelease.releaseDate
|
||||
to_order = PackageRelease.created_at
|
||||
else:
|
||||
abort(400)
|
||||
|
||||
|
@ -20,7 +20,7 @@
|
||||
[{{ rel.commit_hash | truncate(5, end='') }}]
|
||||
{% endif %}
|
||||
|
||||
{{ _("created %(date)s", date=rel.releaseDate | date) }}.
|
||||
{{ _("created %(date)s", date=rel.created_at | date) }}.
|
||||
</small>
|
||||
</a>
|
||||
{% endfor %}
|
||||
@ -50,7 +50,7 @@
|
||||
[{{ rel.commit_hash | truncate(5, end='') }}]
|
||||
{% endif %}
|
||||
|
||||
{{ _("created %(date)s", date=rel.releaseDate | date) }}.
|
||||
{{ _("created %(date)s", date=rel.created_at | date) }}.
|
||||
</small>
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -96,7 +96,7 @@
|
||||
[{{ rel.commit_hash | truncate(5, end='') }}]
|
||||
{% endif %}
|
||||
|
||||
{{ _("created %(date)s", date=rel.releaseDate | date) }}.
|
||||
{{ _("created %(date)s", date=rel.created_at | date) }}.
|
||||
</small>
|
||||
{% if (package.check_perm(current_user, "MAKE_RELEASE") or rel.check_perm(current_user, "APPROVE_RELEASE")) and rel.task_id %}
|
||||
<a href="{{ url_for('tasks.check', id=rel.task_id, r=package.get_url('packages.view')) }}">
|
||||
|
@ -12,9 +12,17 @@
|
||||
{{ form.hidden_tag() }}
|
||||
|
||||
{% if package.check_perm(current_user, "MAKE_RELEASE") %}
|
||||
{{ render_field(form.title) }}
|
||||
{{ render_field(form.name, hint=_("Release short name. Eg: 1.0.0 or 2018-05-28")) }}
|
||||
{{ render_field(form.title, hint=_("Human-readable name. Eg: 1.0.0 - The Trains Update")) }}
|
||||
{{ render_field(form.release_notes) }}
|
||||
{% else %}
|
||||
<p>
|
||||
{{ _("Name") }}: {{ release.name }}<br>
|
||||
{{ _("Title") }}: {{ release.title }}
|
||||
</p>
|
||||
<p>
|
||||
{{ release.release_notes }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if package.check_perm(current_user, "CHANGE_RELEASE_URL") %}
|
||||
|
@ -39,15 +39,16 @@
|
||||
|
||||
<h3>{{ _("1. Name release") }}</h3>
|
||||
|
||||
{{ render_field(form.title, placeholder=_("Human readable. Eg: 1.0.0 or 2018-05-28")) }}
|
||||
{{ render_field(form.name, hint=_("Release short name. Eg: 1.0.0 or 2018-05-28")) }}
|
||||
{{ render_field(form.title, hint=_("Human-readable name. Eg: 1.0.0 - The Trains Update")) }}
|
||||
{{ render_field(form.release_notes) }}
|
||||
|
||||
<h3 class="mt-5">{{ _("2. Set the content") }}</h3>
|
||||
|
||||
<p class="mb-0">{{ _("Method") }}</p>
|
||||
{{ render_radio_field(form.uploadOpt) }}
|
||||
{{ render_radio_field(form.upload_mode) }}
|
||||
|
||||
{% if package.repo %}
|
||||
{{ render_field(form.vcsLabel, placeholder=_("Leave blank to use default branch"), class_="mt-3",
|
||||
{{ render_field(form.vcs_label, placeholder=_("Leave blank to use default branch"), class_="mt-3",
|
||||
pattern="[A-Za-z0-9/._-]+") }}
|
||||
{% endif %}
|
||||
|
||||
@ -99,5 +100,5 @@
|
||||
|
||||
{% block scriptextra %}
|
||||
<script src="/static/js/release_minmax.js?v=2"></script>
|
||||
<script src="/static/js/release_new.js"></script>
|
||||
<script src="/static/js/release_new.js?v=2"></script>
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user