Add release notes and long titles to releases

Fixes #492 and fixes #480
This commit is contained in:
rubenwardy 2024-06-22 15:18:58 +01:00
parent 4147e5edc7
commit 019cd66033
15 changed files with 80 additions and 52 deletions

@ -283,7 +283,7 @@ def markdown():
def list_all_releases(): def list_all_releases():
query = PackageRelease.query.filter_by(approved=True) \ query = PackageRelease.query.filter_by(approved=True) \
.filter(PackageRelease.package.has(state=PackageState.APPROVED)) \ .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: if "author" in request.args:
author = User.query.filter_by(username=request.args["author"]).first() author = User.query.filter_by(username=request.args["author"]).first()
@ -333,7 +333,7 @@ def create_release(token, package):
if option not in data: if option not in data:
error(400, option + " is required in the POST 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: elif request.files:
file = request.files.get("file") file = request.files.get("file")
@ -342,7 +342,7 @@ def create_release(token, package):
commit_hash = data.get("commit") 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: else:
error(400, "Unknown release-creation method. Specify the method or provide a file.") 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) \ updated = db.session.query(Package).select_from(PackageRelease).join(Package) \
.filter_by(state=PackageState.APPROVED) \ .filter_by(state=PackageState.APPROVED) \
.order_by(db.desc(PackageRelease.releaseDate)) \ .order_by(db.desc(PackageRelease.created_at)) \
.limit(20).all() .limit(20).all()
updated = updated[:4] updated = updated[:4]

@ -14,7 +14,7 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # 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 flask import jsonify, abort, make_response, url_for, current_app
from app.logic.packages import do_edit_package from app.logic.packages import do_edit_package
@ -38,14 +38,14 @@ def guard(f):
return ret 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"): min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason="API"):
if not token.can_operate_on_package(package): if not token.can_operate_on_package(package):
error(403, "API token does not have access to the package") error(403, "API token does not have access to the package")
reason += ", token=" + token.name 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({ return jsonify({
"success": True, "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): min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason="API", commit_hash: str = None):
if not token.can_operate_on_package(package): if not token.can_operate_on_package(package):
error(403, "API token does not have access to the package") error(403, "API token does not have access to the package")
reason += ", token=" + token.name 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({ return jsonify({
"success": True, "success": True,

@ -105,7 +105,7 @@ def home():
recent_releases_query = ( recent_releases_query = (
db.session.query( db.session.query(
Package.id, Package.id,
func.max(PackageRelease.releaseDate).label("max_created_at") func.max(PackageRelease.created_at).label("max_created_at")
) )
.join(PackageRelease, Package.releases) .join(PackageRelease, Package.releases)
.group_by(Package.id) .group_by(Package.id)

@ -44,7 +44,7 @@ def game_hub(package: Package):
updated = db.session.query(Package).select_from(PackageRelease).join(Package) \ updated = db.session.query(Package).select_from(PackageRelease).join(Package) \
.filter(Package.supported_games.any(game=package, supports=True), Package.state==PackageState.APPROVED) \ .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() .limit(20).all()
updated = updated[:4] updated = updated[:4]

@ -20,6 +20,7 @@ from flask_babel import lazy_gettext, gettext
from flask_login import login_required, current_user from flask_login import login_required, current_user
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, SubmitField, BooleanField, RadioField, FileField from wtforms import StringField, SubmitField, BooleanField, RadioField, FileField
from wtforms.fields.simple import TextAreaField
from wtforms.validators import InputRequired, Length, Optional from wtforms.validators import InputRequired, Length, Optional
from wtforms_sqlalchemy.fields import QuerySelectField from wtforms_sqlalchemy.fields import QuerySelectField
@ -28,7 +29,7 @@ from app.models import Package, db, User, PackageState, Permission, UserRank, Pa
PackageRelease, PackageUpdateTrigger, PackageUpdateConfig PackageRelease, PackageUpdateTrigger, PackageUpdateConfig
from app.rediscache import has_key, set_temp_key, make_download_key from app.rediscache import has_key, set_temp_key, make_download_key
from app.tasks.importtasks import check_update_config 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 from . import bp, get_package_tabs
@ -51,19 +52,25 @@ def get_mt_releases(is_max):
class CreatePackageReleaseForm(FlaskForm): class CreatePackageReleaseForm(FlaskForm):
title = StringField(lazy_gettext("Title"), [InputRequired(), Length(1, 30)]) name = StringField(lazy_gettext("Name"), [InputRequired(), Length(1, 30)])
uploadOpt = RadioField(lazy_gettext("Method"), choices=[("upload", lazy_gettext("File Upload"))], default="upload") title = StringField(lazy_gettext("Title"), [Optional(), Length(1, 100)], filters=[nonempty_or_none])
vcsLabel = StringField(lazy_gettext("Git reference (ie: commit hash, branch, or tag)"), default=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")) file_upload = FileField(lazy_gettext("File Upload"))
min_rel = QuerySelectField(lazy_gettext("Minimum Minetest Version"), [InputRequired()], 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) query_factory=lambda: get_mt_releases(False), get_pk=lambda a: a.id, get_label=lambda a: a.name)
max_rel = QuerySelectField(lazy_gettext("Maximum Minetest Version"), [InputRequired()], max_rel = QuerySelectField(lazy_gettext("Maximum Minetest Version"), [InputRequired()],
query_factory=lambda: get_mt_releases(True), get_pk=lambda a: a.id, get_label=lambda a: a.name) query_factory=lambda: get_mt_releases(True), get_pk=lambda a: a.id, get_label=lambda a: a.name)
submit = SubmitField(lazy_gettext("Save")) submit = SubmitField(lazy_gettext("Save"))
class EditPackageReleaseForm(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()]) url = StringField(lazy_gettext("URL"), [Optional()])
task_id = StringField(lazy_gettext("Task ID"), filters = [lambda x: x or None]) task_id = StringField(lazy_gettext("Task ID"), filters = [lambda x: x or None])
approved = BooleanField(lazy_gettext("Is Approved")) approved = BooleanField(lazy_gettext("Is Approved"))
@ -88,21 +95,21 @@ def create_release(package):
# Initial form class from post data and default data # Initial form class from post data and default data
form = CreatePackageReleaseForm() form = CreatePackageReleaseForm()
if package.repo is not None: 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": if request.method == "GET":
form["uploadOpt"].data = "vcs" form.upload_mode.data = "vcs"
form.vcsLabel.data = request.args.get("ref") form.vcs_label.data = request.args.get("ref")
if request.method == "GET": if request.method == "GET":
form.title.data = request.args.get("title") form.title.data = request.args.get("title")
if form.validate_on_submit(): if form.validate_on_submit():
try: try:
if form["uploadOpt"].data == "vcs": if form.upload_mode.data == "vcs":
rel = do_create_vcs_release(current_user, package, form.title.data, rel = do_create_vcs_release(current_user, package, form.name.data, form.title.data, form.release_notes.data,
form.vcsLabel.data, form.min_rel.data.get_actual(), form.max_rel.data.get_actual()) form.vcs_label.data, form.min_rel.data.get_actual(), form.max_rel.data.get_actual())
else: 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()) 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())) return redirect(url_for("tasks.check", id=rel.task_id, r=rel.get_edit_url()))
except LogicError as e: except LogicError as e:

@ -192,7 +192,7 @@ def github_webhook():
if package.releases.filter_by(commit_hash=ref).count() > 0: if package.releases.filter_by(commit_hash=ref).count() > 0:
return 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({ return jsonify({
"success": False, "success": False,

@ -68,7 +68,7 @@ def webhook_impl():
if package.releases.filter_by(commit_hash=ref).count() > 0: if package.releases.filter_by(commit_hash=ref).count() > 0:
continue 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({ return jsonify({
"success": False, "success": False,

@ -224,7 +224,9 @@ Format query parameters:
* `maintainer`: Filter by maintainer * `maintainer`: Filter by maintainer
* Returns array of release dictionaries with keys: * Returns array of release dictionaries with keys:
* `id`: release ID * `id`: release ID
* `name`: short release name
* `title`: human-readable title * `title`: human-readable title
* `release_notes`: string or null, what's new in this release
* `release_date`: Date released * `release_date`: Date released
* `url`: download URL * `url`: download URL
* `commit`: commit hash or null * `commit`: commit hash or null
@ -248,6 +250,7 @@ Format query parameters:
* Requires authentication. * Requires authentication.
* Body can be JSON or multipart form data. Zip uploads must be multipart form data. * Body can be JSON or multipart form data. Zip uploads must be multipart form data.
* `title`: human-readable name of the release. * `title`: human-readable name of the release.
* `release_notes`: string or null, what's new in this release.
* For Git release creation: * For Git release creation:
* `method`: must be `git`. * `method`: must be `git`.
* `ref`: (Optional) git reference, eg: `master`. * `ref`: (Optional) git reference, eg: `master`.

@ -16,6 +16,7 @@
import datetime import datetime
import re import re
from typing import Optional
from celery import uuid from celery import uuid
from flask_babel import lazy_gettext 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")) raise LogicError(403, lazy_gettext("You don't have permission to make releases"))
five_minutes_ago = datetime.datetime.now() - datetime.timedelta(minutes=5) 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: 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")) 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): min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason: str = None):
check_can_create_release(user, package) check_can_create_release(user, package)
rel = PackageRelease() rel = PackageRelease()
rel.package = package rel.package = package
rel.title = title rel.name = name
rel.title = title or name
rel.release_notes = release_notes
rel.url = "" rel.url = ""
rel.task_id = uuid() rel.task_id = uuid()
rel.min_rel = min_v rel.min_rel = min_v
@ -63,7 +66,7 @@ def do_create_vcs_release(user: User, package: Package, title: str, ref: str,
return rel 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, min_v: MinetestRelease = None, max_v: MinetestRelease = None, reason: str = None,
commit_hash: str = None): commit_hash: str = None):
check_can_create_release(user, package) 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 = PackageRelease()
rel.package = package rel.package = package
rel.title = title rel.name = name
rel.title = title or name
rel.release_notes = release_notes
rel.url = uploaded_url rel.url = uploaded_url
rel.task_id = uuid() rel.task_id = uuid()
rel.commit_hash = commit_hash rel.commit_hash = commit_hash

@ -474,7 +474,7 @@ class Package(db.Model):
content_warnings = db.relationship("ContentWarning", secondary=ContentWarnings, back_populates="packages") content_warnings = db.relationship("ContentWarning", secondary=ContentWarnings, back_populates="packages")
releases = db.relationship("PackageRelease", back_populates="package", 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", 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") 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_id = db.Column(db.Integer, db.ForeignKey("package.id"))
package = db.relationship("Package", back_populates="releases", foreign_keys=[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) 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="") url = db.Column(db.String(200), nullable=False, default="")
approved = db.Column(db.Boolean, nullable=False, default=False) approved = db.Column(db.Boolean, nullable=False, default=False)
task_id = db.Column(db.String(37), nullable=True) task_id = db.Column(db.String(37), nullable=True)
commit_hash = db.Column(db.String(41), nullable=True, default=None) commit_hash = db.Column(db.String(41), nullable=True, default=None)
downloads = db.Column(db.Integer, nullable=False, default=0) 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_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]) min_rel = db.relationship("MinetestRelease", foreign_keys=[min_rel_id])
@ -1126,9 +1128,11 @@ class PackageRelease(db.Model):
def as_dict(self): def as_dict(self):
return { return {
"id": self.id, "id": self.id,
"name": self.name,
"title": self.title, "title": self.title,
"release_notes": self.release_notes,
"url": self.url if self.url != "" else None, "url": self.url if self.url != "" else None,
"release_date": self.releaseDate.isoformat(), "release_date": self.created_at.isoformat(),
"commit": self.commit_hash, "commit": self.commit_hash,
"downloads": self.downloads, "downloads": self.downloads,
"min_minetest_version": self.min_rel and self.min_rel.as_dict(), "min_minetest_version": self.min_rel and self.min_rel.as_dict(),
@ -1141,7 +1145,7 @@ class PackageRelease(db.Model):
"id": self.id, "id": self.id,
"title": self.title, "title": self.title,
"url": self.url if self.url != "" else None, "url": self.url if self.url != "" else None,
"release_date": self.releaseDate.isoformat(), "release_date": self.created_at.isoformat(),
"commit": self.commit_hash, "commit": self.commit_hash,
"downloads": self.downloads, "downloads": self.downloads,
"min_minetest_version": self.min_rel and self.min_rel.as_dict(), "min_minetest_version": self.min_rel and self.min_rel.as_dict(),
@ -1169,7 +1173,7 @@ class PackageRelease(db.Model):
id=self.id) id=self.id)
def __init__(self): def __init__(self):
self.releaseDate = datetime.datetime.now() self.created_at = datetime.datetime.now()
def get_download_filename(self): def get_download_filename(self):
return f"{self.package.name}_{self.id}.zip" return f"{self.package.name}_{self.id}.zip"

@ -5,15 +5,15 @@
window.addEventListener("load", () => { window.addEventListener("load", () => {
function check_opt() { 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("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 { } else {
document.getElementById("file_upload").parentElement.classList.remove("d-none"); 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(); check_opt();
}); });

@ -343,7 +343,7 @@ class QueryBuilder:
elif self.order_by == "approved_at" or self.order_by == "date": elif self.order_by == "approved_at" or self.order_by == "date":
to_order = Package.approved_at to_order = Package.approved_at
elif self.order_by == "last_release": elif self.order_by == "last_release":
to_order = PackageRelease.releaseDate to_order = PackageRelease.created_at
else: else:
abort(400) abort(400)

@ -20,7 +20,7 @@
[{{ rel.commit_hash | truncate(5, end='') }}] [{{ rel.commit_hash | truncate(5, end='') }}]
{% endif %} {% endif %}
{{ _("created %(date)s", date=rel.releaseDate | date) }}. {{ _("created %(date)s", date=rel.created_at | date) }}.
</small> </small>
</a> </a>
{% endfor %} {% endfor %}
@ -50,7 +50,7 @@
[{{ rel.commit_hash | truncate(5, end='') }}] [{{ rel.commit_hash | truncate(5, end='') }}]
{% endif %} {% endif %}
{{ _("created %(date)s", date=rel.releaseDate | date) }}. {{ _("created %(date)s", date=rel.created_at | date) }}.
</small> </small>
</a> </a>
{% endif %} {% endif %}
@ -96,7 +96,7 @@
[{{ rel.commit_hash | truncate(5, end='') }}] [{{ rel.commit_hash | truncate(5, end='') }}]
{% endif %} {% endif %}
{{ _("created %(date)s", date=rel.releaseDate | date) }}. {{ _("created %(date)s", date=rel.created_at | date) }}.
</small> </small>
{% if (package.check_perm(current_user, "MAKE_RELEASE") or rel.check_perm(current_user, "APPROVE_RELEASE")) and rel.task_id %} {% 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')) }}"> <a href="{{ url_for('tasks.check', id=rel.task_id, r=package.get_url('packages.view')) }}">

@ -12,9 +12,17 @@
{{ form.hidden_tag() }} {{ form.hidden_tag() }}
{% if package.check_perm(current_user, "MAKE_RELEASE") %} {% 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 %} {% else %}
{{ _("Title") }}: {{ release.title }} <p>
{{ _("Name") }}: {{ release.name }}<br>
{{ _("Title") }}: {{ release.title }}
</p>
<p>
{{ release.release_notes }}
</p>
{% endif %} {% endif %}
{% if package.check_perm(current_user, "CHANGE_RELEASE_URL") %} {% if package.check_perm(current_user, "CHANGE_RELEASE_URL") %}

@ -39,15 +39,16 @@
<h3>{{ _("1. Name release") }}</h3> <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> <h3 class="mt-5">{{ _("2. Set the content") }}</h3>
<p class="mb-0">{{ _("Method") }}</p> {{ render_radio_field(form.upload_mode) }}
{{ render_radio_field(form.uploadOpt) }}
{% if package.repo %} {% 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/._-]+") }} pattern="[A-Za-z0-9/._-]+") }}
{% endif %} {% endif %}
@ -99,5 +100,5 @@
{% block scriptextra %} {% block scriptextra %}
<script src="/static/js/release_minmax.js?v=2"></script> <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 %} {% endblock %}