mirror of
https://github.com/minetest/contentdb.git
synced 2024-11-08 16:43:47 +01:00
parent
a800685947
commit
5d32d7922f
@ -385,7 +385,7 @@ def list_all_reviews():
|
||||
query = query.options(joinedload(PackageReview.author), joinedload(PackageReview.package))
|
||||
|
||||
if request.args.get("author"):
|
||||
query = query.join(User).filter(User.username == request.args.get("author"))
|
||||
query = query.filter(PackageReview.author.has(User.username == request.args.get("author")))
|
||||
|
||||
if request.args.get("is_positive"):
|
||||
query = query.filter(PackageReview.recommends == isYes(request.args.get("is_positive")))
|
||||
|
@ -228,12 +228,20 @@ def makeLabel(obj):
|
||||
else:
|
||||
return obj.title
|
||||
|
||||
|
||||
def NotNullOption(_form, field):
|
||||
if field.data is None or field.data.name == "__None":
|
||||
raise ValidationError("This field is required")
|
||||
|
||||
|
||||
class PackageForm(FlaskForm):
|
||||
type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD)
|
||||
title = StringField("Title (Human-readable)", [InputRequired(), Length(1, 100)])
|
||||
name = StringField("Name (Technical)", [InputRequired(), Length(1, 100), Regexp("^[a-z0-9_]+$", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
|
||||
short_desc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
|
||||
|
||||
dev_state = SelectField("Maintenance State", [InputRequired(), NotNullOption], choices=PackageDevState.choices(with_none=True), coerce=PackageDevState.coerce)
|
||||
|
||||
tags = QuerySelectMultipleField('Tags', query_factory=lambda: Tag.query.order_by(db.asc(Tag.name)), get_pk=lambda a: a.id, get_label=makeLabel)
|
||||
content_warnings = QuerySelectMultipleField('Content Warnings', query_factory=lambda: ContentWarning.query.order_by(db.asc(ContentWarning.name)), get_pk=lambda a: a.id, get_label=makeLabel)
|
||||
license = QuerySelectField("License", [DataRequired()], allow_blank=True, query_factory=lambda: License.query.order_by(db.asc(License.name)), get_pk=lambda a: a.id, get_label=lambda a: a.name)
|
||||
@ -318,6 +326,7 @@ def create_edit(author=None, name=None):
|
||||
"title": form.title.data,
|
||||
"name": form.name.data,
|
||||
"short_desc": form.short_desc.data,
|
||||
"dev_state": form.dev_state.data,
|
||||
"tags": form.tags.raw_data,
|
||||
"content_warnings": form.content_warnings.raw_data,
|
||||
"license": form.license.data,
|
||||
|
@ -61,6 +61,8 @@ Tokens can be attained by visiting [Settings > API Tokens](/user/tokens/).
|
||||
* `title`: Human-readable title.
|
||||
* `name`: Technical name (needs permission if already approved).
|
||||
* `short_description`
|
||||
* `dev_state`: One of `WIP`, `BETA`, `ACTIVELY_DEVELOPED`, `MAINTENANCE_ONLY`, `AS_IS`, `DEPRECATED`,
|
||||
`LOOKING_FOR_MAINTAINER`.
|
||||
* `tags`: List of [tag](#tags) names.
|
||||
* `content_warnings`: List of [content warning](#content-warnings) names.
|
||||
* `license`: A [license](#licenses) name.
|
||||
|
@ -17,9 +17,14 @@ A flag can be:
|
||||
|
||||
* `nonfree` - can be used to hide packages which do not qualify as
|
||||
'free software', as defined by the Free Software Foundation.
|
||||
* `wip` - packages marked as Work in Progress
|
||||
* `deprecated` - packages marked as Deprecated
|
||||
* A content warning, given below.
|
||||
* `android_default` - meta-flag that filters out any content with a content warning.
|
||||
* `desktop_default` - meta-flag that doesn't filter anything out for now.
|
||||
* `android_default` - meta-flag that filters out any content with a content warning and WIP packages
|
||||
* `desktop_default` - meta-flag that filters out WIP packages.
|
||||
|
||||
The `_default` flags are designed so that we can change how different platforms filter the package list
|
||||
based on
|
||||
|
||||
## Content Warnings
|
||||
|
||||
|
@ -50,6 +50,8 @@ It should be a JSON dictionary with one or more of the following optional keys:
|
||||
* `title`: Human-readable title.
|
||||
* `name`: Technical name (needs permission if already approved).
|
||||
* `short_description`
|
||||
* `dev_state`: One of `WIP`, `BETA`, `ACTIVELY_DEVELOPED`, `MAINTENANCE_ONLY`, `AS_IS`, `DEPRECATED`,
|
||||
`LOOKING_FOR_MAINTAINER`.
|
||||
* `tags`: List of tag names, see [/api/tags/](/api/tags/).
|
||||
* `content_warnings`: List of content warning names, see [/api/content_warnings/](/api/content_warnings/).
|
||||
* `license`: A license name, see [/api/licenses/](/api/licenses/).
|
||||
|
@ -46,6 +46,9 @@ but still has value. Note that this doesn't mean that you should add a thing
|
||||
you started working on yesterday, it's worth adding all the basic stuff to
|
||||
make your package useful.
|
||||
|
||||
You should make sure to mark Work in Progress stuff as such in the "maintenance status" column,
|
||||
as this will help advise players.
|
||||
|
||||
Adding non-player facing mods, such as libraries and server tools, is perfectly fine
|
||||
and encouraged. ContentDB isn't just for player-facing things, and adding
|
||||
libraries allows them to be installed when a mod depends on it.
|
||||
|
@ -19,7 +19,8 @@ import re
|
||||
import validators
|
||||
|
||||
from app.logic.LogicError import LogicError
|
||||
from app.models import User, Package, PackageType, MetaPackage, Tag, ContentWarning, db, Permission, AuditSeverity, License, UserRank
|
||||
from app.models import User, Package, PackageType, MetaPackage, Tag, ContentWarning, db, Permission, AuditSeverity, \
|
||||
License, UserRank, PackageDevState
|
||||
from app.utils import addAuditLog
|
||||
|
||||
|
||||
@ -47,6 +48,7 @@ ALLOWED_FIELDS = {
|
||||
"name": str,
|
||||
"short_description": str,
|
||||
"short_desc": str,
|
||||
"dev_state": AnyType,
|
||||
"tags": list,
|
||||
"content_warnings": list,
|
||||
"license": AnyType,
|
||||
@ -116,13 +118,18 @@ def do_edit_package(user: User, package: Package, was_new: bool, was_web: bool,
|
||||
if "type" in data:
|
||||
data["type"] = PackageType.coerce(data["type"])
|
||||
|
||||
if "dev_state" in data:
|
||||
data["dev_state"] = PackageDevState.coerce(data["dev_state"])
|
||||
if data["dev_state"] is None:
|
||||
raise LogicError(400, "dev_state cannot be null")
|
||||
|
||||
if "license" in data:
|
||||
data["license"] = get_license(data["license"])
|
||||
|
||||
if "media_license" in data:
|
||||
data["media_license"] = get_license(data["media_license"])
|
||||
|
||||
for key in ["name", "title", "short_desc", "desc", "type", "license", "media_license",
|
||||
for key in ["name", "title", "short_desc", "desc", "type", "dev_state", "license", "media_license",
|
||||
"repo", "website", "issueTracker", "forums"]:
|
||||
if key in data:
|
||||
setattr(package, key, data[key])
|
||||
|
@ -73,6 +73,65 @@ class PackageType(enum.Enum):
|
||||
return item if type(item) == PackageType else PackageType[item.upper()]
|
||||
|
||||
|
||||
class PackageDevState(enum.Enum):
|
||||
WIP = "Work in Progress"
|
||||
BETA = "Beta"
|
||||
ACTIVELY_DEVELOPED = "Actively Developed"
|
||||
MAINTENANCE_ONLY = "Maintenance Only"
|
||||
AS_IS = "As-Is"
|
||||
DEPRECATED = "Deprecated"
|
||||
LOOKING_FOR_MAINTAINER = "Looking for Maintainer"
|
||||
|
||||
def toName(self):
|
||||
return self.name.lower()
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def get_desc(self):
|
||||
if self == PackageDevState.WIP:
|
||||
return "Under active development, and may break worlds/things without warning"
|
||||
elif self == PackageDevState.BETA:
|
||||
return "Fully playable, but with some breakages/changes expected"
|
||||
elif self == PackageDevState.MAINTENANCE_ONLY:
|
||||
return "Finished, with bug fixes being made as needed"
|
||||
elif self == PackageDevState.AS_IS:
|
||||
return "Finished, the maintainer doesn't intend to continue working on it or provide support"
|
||||
elif self == PackageDevState.DEPRECATED:
|
||||
return "The maintainer doesn't recommend this package. See the description for more info"
|
||||
else:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get(cls, name):
|
||||
try:
|
||||
return PackageDevState[name.upper()]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def choices(cls, with_none):
|
||||
def build_label(choice):
|
||||
desc = choice.get_desc()
|
||||
if desc is None:
|
||||
return choice.value
|
||||
else:
|
||||
return f"{choice.value}: {desc}"
|
||||
|
||||
ret = [(choice, build_label(choice)) for choice in cls]
|
||||
|
||||
if with_none:
|
||||
ret.insert(0, ("__None", ""))
|
||||
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def coerce(cls, item):
|
||||
if item is None or (isinstance(item, str) and item.upper() == "__NONE"):
|
||||
return None
|
||||
return item if type(item) == PackageDevState else PackageDevState[item.upper()]
|
||||
|
||||
|
||||
class PackageState(enum.Enum):
|
||||
WIP = "Draft"
|
||||
CHANGES_NEEDED = "Changes Needed"
|
||||
@ -293,6 +352,7 @@ class Package(db.Model):
|
||||
media_license = db.relationship("License", foreign_keys=[media_license_id])
|
||||
|
||||
state = db.Column(db.Enum(PackageState), nullable=False, default=PackageState.WIP)
|
||||
dev_state = db.Column(db.Enum(PackageDevState), nullable=True, default=None)
|
||||
|
||||
@property
|
||||
def approved(self):
|
||||
|
@ -3,7 +3,8 @@ from sqlalchemy import or_
|
||||
from sqlalchemy.orm import subqueryload
|
||||
from sqlalchemy.sql.expression import func
|
||||
|
||||
from .models import db, PackageType, Package, ForumTopic, License, MinetestRelease, PackageRelease, User, Tag, ContentWarning, PackageState
|
||||
from .models import db, PackageType, Package, ForumTopic, License, MinetestRelease, PackageRelease, User, Tag, \
|
||||
ContentWarning, PackageState, PackageDevState
|
||||
from .utils import isYes, get_int_or_abort
|
||||
|
||||
|
||||
@ -30,7 +31,6 @@ class QueryBuilder:
|
||||
# Hide
|
||||
hide_flags = args.getlist("hide")
|
||||
|
||||
|
||||
self.title = title
|
||||
self.types = types
|
||||
self.tags = tags
|
||||
@ -41,9 +41,16 @@ class QueryBuilder:
|
||||
self.order_by = args.get("sort")
|
||||
self.order_dir = args.get("order") or "desc"
|
||||
|
||||
use_platform_defaults = "android_default" in hide_flags or "desktop_default" in hide_flags
|
||||
|
||||
self.hide_nonfree = "nonfree" in hide_flags
|
||||
self.hide_wip = "wip" in hide_flags or use_platform_defaults
|
||||
self.hide_deprecated = "deprecated" in hide_flags or use_platform_defaults
|
||||
|
||||
self.hide_flags = set(hide_flags)
|
||||
self.hide_flags.discard("nonfree")
|
||||
self.hide_flags.discard("wip")
|
||||
self.hide_flags.discard("deprecated")
|
||||
|
||||
# Filters
|
||||
self.search = args.get("q")
|
||||
@ -136,6 +143,11 @@ class QueryBuilder:
|
||||
query = query.filter(Package.license.has(License.is_foss == True))
|
||||
query = query.filter(Package.media_license.has(License.is_foss == True))
|
||||
|
||||
if self.hide_wip:
|
||||
query = query.filter(or_(Package.dev_state == None, Package.dev_state != PackageDevState.WIP))
|
||||
if self.hide_deprecated:
|
||||
query = query.filter(or_(Package.dev_state == None, Package.dev_state != PackageDevState.DEPRECATED))
|
||||
|
||||
if self.version:
|
||||
query = query.join(Package.releases) \
|
||||
.filter(PackageRelease.approved == True) \
|
||||
|
@ -77,6 +77,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{{ render_field(form.short_desc, class_="pkg_meta") }}
|
||||
{{ render_field(form.dev_state, class_="pkg_meta", hint=_("Please choose 'Work in Progress' if your package is unstable, and shouldn't be recommended to all players")) }}
|
||||
{{ render_multiselect_field(form.tags, class_="pkg_meta") }}
|
||||
{{ render_multiselect_field(form.content_warnings, class_="pkg_meta") }}
|
||||
<div class="pkg_meta row">
|
||||
|
@ -71,6 +71,12 @@
|
||||
</p>
|
||||
|
||||
<p>
|
||||
{% if package.dev_state.name == "LOOKING_FOR_MAINTAINER" or package.dev_state.name == "DEPRECATED" %}
|
||||
<span class="badge badge-warning" title="{{ package.dev_state.get_desc() }}">
|
||||
<i class="fas fa-exclamation-circle" style="margin-right: 0.3em;"></i>
|
||||
{{ package.dev_state.value }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% if package_warning %}
|
||||
<a class="badge badge-danger" href="/help/non_free/">
|
||||
<i class="fas fa-exclamation-circle" style="margin-right: 0.3em;"></i>
|
||||
@ -84,6 +90,12 @@
|
||||
{{ warning.title }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
{% if package.dev_state.name == "WIP" %}
|
||||
<span class="badge badge-info" title="{{ package.dev_state.get_desc() }}">
|
||||
<i class="fas fa-tools" style="margin-right: 0.3em;"></i>
|
||||
{{ _("Work in Progress") }}
|
||||
</span>
|
||||
{% endif %}
|
||||
{% for t in package.tags %}
|
||||
<a class="badge badge-primary" rel="nofollow"
|
||||
title="{{ t.description or '' }}"
|
||||
@ -379,7 +391,13 @@
|
||||
{{ render_license(package.media_license) }} for media.
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>Added</dt>
|
||||
<dt>{{ _("Maintenance State") }}</dt>
|
||||
{% if package.dev_state %}
|
||||
<dd title="{{ package.dev_state.get_desc() }}">{{ package.dev_state.value }}</dd>
|
||||
{% else %}
|
||||
<dd><i>Unknown</i></dd>
|
||||
{% endif %}
|
||||
<dt>{{ _("Added") }}</dt>
|
||||
<dd>{{ package.created_at | datetime }}</dd>
|
||||
<dt>Maintainers</dt>
|
||||
<dd>
|
||||
|
27
migrations/versions/17b303f33f68_.py
Normal file
27
migrations/versions/17b303f33f68_.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""empty message
|
||||
|
||||
Revision ID: 17b303f33f68
|
||||
Revises: 96a01fe23389
|
||||
Create Date: 2021-12-20 19:48:58.571336
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '17b303f33f68'
|
||||
down_revision = '96a01fe23389'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
status = postgresql.ENUM('WIP', 'BETA', 'ACTIVELY_DEVELOPED', 'MAINTENANCE_ONLY', 'AS_IS', 'DEPRECATED', 'LOOKING_FOR_MAINTAINER', name='packagedevstate')
|
||||
status.create(op.get_bind())
|
||||
|
||||
op.add_column('package', sa.Column('dev_state', sa.Enum('WIP', 'BETA', 'ACTIVELY_DEVELOPED', 'MAINTENANCE_ONLY', 'AS_IS', 'DEPRECATED', 'LOOKING_FOR_MAINTAINER', name='packagedevstate'), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('package', 'dev_state')
|
Loading…
Reference in New Issue
Block a user