mirror of
https://github.com/minetest/contentdb.git
synced 2024-11-09 17:13:45 +01:00
Add Content Warnings
This commit is contained in:
parent
0ac2827468
commit
6a674c3c79
@ -19,4 +19,4 @@ from flask import Blueprint
|
|||||||
|
|
||||||
bp = Blueprint("admin", __name__)
|
bp = Blueprint("admin", __name__)
|
||||||
|
|
||||||
from . import admin, licenseseditor, tagseditor, versioneditor, audit
|
from . import admin, audit, licenseseditor, tagseditor, versioneditor, warningseditor
|
||||||
|
59
app/blueprints/admin/warningseditor.py
Normal file
59
app/blueprints/admin/warningseditor.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
# ContentDB
|
||||||
|
# Copyright (C) 2020 rubenwardy
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
from flask import *
|
||||||
|
from flask_user import *
|
||||||
|
from . import bp
|
||||||
|
from app.models import *
|
||||||
|
from flask_wtf import FlaskForm
|
||||||
|
from wtforms import *
|
||||||
|
from wtforms.validators import *
|
||||||
|
from app.utils import rank_required
|
||||||
|
|
||||||
|
@bp.route("/admin/warnings/")
|
||||||
|
@rank_required(UserRank.ADMIN)
|
||||||
|
def warning_list():
|
||||||
|
return render_template("admin/warnings/list.html", warnings=ContentWarning.query.order_by(db.asc(ContentWarning.title)).all())
|
||||||
|
|
||||||
|
class WarningForm(FlaskForm):
|
||||||
|
title = StringField("Title", [InputRequired(), Length(3,100)])
|
||||||
|
name = StringField("Name", [Optional(), Length(1, 20), Regexp("^[a-z0-9_]", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
|
||||||
|
description = TextAreaField("Description", [InputRequired(), Length(0, 500)])
|
||||||
|
submit = SubmitField("Save")
|
||||||
|
|
||||||
|
@bp.route("/admin/warnings/new/", methods=["GET", "POST"])
|
||||||
|
@bp.route("/admin/warnings/<name>/edit/", methods=["GET", "POST"])
|
||||||
|
@rank_required(UserRank.ADMIN)
|
||||||
|
def create_edit_warning(name=None):
|
||||||
|
warning = None
|
||||||
|
if name is not None:
|
||||||
|
warning = ContentWarning.query.filter_by(name=name).first()
|
||||||
|
if warning is None:
|
||||||
|
abort(404)
|
||||||
|
|
||||||
|
form = WarningForm(formdata=request.form, obj=warning)
|
||||||
|
if request.method == "POST" and form.validate():
|
||||||
|
if warning is None:
|
||||||
|
warning = ContentWarning(form.title.data, form.description.data)
|
||||||
|
db.session.add(warning)
|
||||||
|
else:
|
||||||
|
form.populate_obj(warning)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return redirect(url_for("admin.warning_list"))
|
||||||
|
|
||||||
|
return render_template("admin/warnings/edit.html", warning=warning, form=form)
|
@ -198,22 +198,24 @@ def download(package):
|
|||||||
|
|
||||||
|
|
||||||
class PackageForm(FlaskForm):
|
class PackageForm(FlaskForm):
|
||||||
name = StringField("Name (Technical)", [InputRequired(), Length(1, 100), Regexp("^[a-z0-9_]+$", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
|
name = StringField("Name (Technical)", [InputRequired(), Length(1, 100), Regexp("^[a-z0-9_]+$", 0, "Lower case letters (a-z), digits (0-9), and underscores (_) only")])
|
||||||
title = StringField("Title (Human-readable)", [InputRequired(), Length(3, 100)])
|
title = StringField("Title (Human-readable)", [InputRequired(), Length(3, 100)])
|
||||||
short_desc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
|
short_desc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
|
||||||
desc = TextAreaField("Long Description (Markdown)", [Optional(), Length(0,10000)])
|
desc = TextAreaField("Long Description (Markdown)", [Optional(), Length(0,10000)])
|
||||||
type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD)
|
type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD)
|
||||||
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)
|
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)
|
||||||
media_license = QuerySelectField("Media 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)
|
media_license = QuerySelectField("Media 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)
|
||||||
provides_str = StringField("Provides (mods included in package)", [Optional()])
|
provides_str = StringField("Provides (mods included in package)", [Optional()])
|
||||||
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()])
|
content_warnings = QuerySelectMultipleField('Content Warnings', query_factory=lambda: ContentWarning.query.order_by(db.asc(ContentWarning.name)), get_pk=lambda a: a.id, get_label=lambda a: a.title)
|
||||||
softdep_str = StringField("Soft Dependencies", [Optional()])
|
harddep_str = StringField("Hard Dependencies", [Optional()])
|
||||||
repo = StringField("VCS Repository URL", [Optional(), URL()], filters = [lambda x: x or None])
|
softdep_str = StringField("Soft Dependencies", [Optional()])
|
||||||
website = StringField("Website URL", [Optional(), URL()], filters = [lambda x: x or None])
|
repo = StringField("VCS Repository URL", [Optional(), URL()], filters = [lambda x: x or None])
|
||||||
issueTracker = StringField("Issue Tracker URL", [Optional(), URL()], filters = [lambda x: x or None])
|
website = StringField("Website URL", [Optional(), URL()], filters = [lambda x: x or None])
|
||||||
forums = IntegerField("Forum Topic ID", [Optional(), NumberRange(0,999999)])
|
issueTracker = StringField("Issue Tracker URL", [Optional(), URL()], filters = [lambda x: x or None])
|
||||||
submit = SubmitField("Save")
|
forums = IntegerField("Forum Topic ID", [Optional(), NumberRange(0,999999)])
|
||||||
|
submit = SubmitField("Save")
|
||||||
|
|
||||||
|
|
||||||
@bp.route("/packages/new/", methods=["GET", "POST"])
|
@bp.route("/packages/new/", methods=["GET", "POST"])
|
||||||
@bp.route("/packages/<author>/<name>/edit/", methods=["GET", "POST"])
|
@bp.route("/packages/<author>/<name>/edit/", methods=["GET", "POST"])
|
||||||
@ -259,6 +261,7 @@ def create_edit(author=None, name=None):
|
|||||||
form.softdep_str.data = ",".join([str(x) for x in package.getSortedOptionalDependencies() ])
|
form.softdep_str.data = ",".join([str(x) for x in package.getSortedOptionalDependencies() ])
|
||||||
form.provides_str.data = MetaPackage.ListToSpec(package.provides)
|
form.provides_str.data = MetaPackage.ListToSpec(package.provides)
|
||||||
form.tags.data = list(package.tags)
|
form.tags.data = list(package.tags)
|
||||||
|
form.content_warnings.data = list(package.content_warnings)
|
||||||
|
|
||||||
if request.method == "POST" and form.validate():
|
if request.method == "POST" and form.validate():
|
||||||
wasNew = False
|
wasNew = False
|
||||||
@ -320,6 +323,10 @@ def create_edit(author=None, name=None):
|
|||||||
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))
|
||||||
|
|
||||||
|
package.content_warnings.clear()
|
||||||
|
for warning in form.content_warnings.raw_data:
|
||||||
|
package.content_warnings.append(ContentWarning.query.get(warning))
|
||||||
|
|
||||||
db.session.commit() # save
|
db.session.commit() # save
|
||||||
|
|
||||||
next_url = package.getDetailsURL()
|
next_url = package.getDetailsURL()
|
||||||
|
@ -358,6 +358,11 @@ 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)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ContentWarnings = db.Table("content_warnings",
|
||||||
|
db.Column("content_warning_id", db.Integer, db.ForeignKey("content_warning.id"), primary_key=True),
|
||||||
|
db.Column("package_id", db.Integer, db.ForeignKey("package.id"), primary_key=True)
|
||||||
|
)
|
||||||
|
|
||||||
maintainers = db.Table("maintainers",
|
maintainers = db.Table("maintainers",
|
||||||
db.Column("user_id", db.Integer, db.ForeignKey("user.id"), primary_key=True),
|
db.Column("user_id", db.Integer, db.ForeignKey("user.id"), primary_key=True),
|
||||||
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)
|
||||||
@ -488,6 +493,9 @@ class Package(db.Model):
|
|||||||
tags = db.relationship("Tag", secondary=Tags, lazy="select",
|
tags = db.relationship("Tag", secondary=Tags, lazy="select",
|
||||||
backref=db.backref("packages", lazy=True))
|
backref=db.backref("packages", lazy=True))
|
||||||
|
|
||||||
|
content_warnings = db.relationship("ContentWarning", secondary=ContentWarnings, lazy="select",
|
||||||
|
backref=db.backref("packages", lazy=True))
|
||||||
|
|
||||||
releases = db.relationship("PackageRelease", backref="package",
|
releases = db.relationship("PackageRelease", backref="package",
|
||||||
lazy="dynamic", order_by=db.desc("package_release_releaseDate"))
|
lazy="dynamic", order_by=db.desc("package_release_releaseDate"))
|
||||||
|
|
||||||
@ -816,6 +824,23 @@ class MetaPackage(db.Model):
|
|||||||
|
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
|
||||||
|
class ContentWarning(db.Model):
|
||||||
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
|
name = db.Column(db.String(100), unique=True, nullable=False)
|
||||||
|
title = db.Column(db.String(100), nullable=False)
|
||||||
|
description = db.Column(db.String(500), nullable=False)
|
||||||
|
|
||||||
|
def __init__(self, title, description=""):
|
||||||
|
self.title = title
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
import re
|
||||||
|
regex = re.compile("[^a-z_]")
|
||||||
|
self.name = regex.sub("", self.title.lower().replace(" ", "_"))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Tag(db.Model):
|
class Tag(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)
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.tag_list') }}">Tag Editor</a>
|
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.tag_list') }}">Tag Editor</a>
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.license_list') }}">License Editor</a>
|
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.license_list') }}">License Editor</a>
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.version_list') }}">Version Editor</a>
|
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.version_list') }}">Version Editor</a>
|
||||||
|
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.warning_list') }}">Warning Editor</a>
|
||||||
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.switch_user') }}">Sign in as another user</a>
|
<a class="list-group-item list-group-item-action" href="{{ url_for('admin.switch_user') }}">Sign in as another user</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
26
app/templates/admin/warnings/edit.html
Normal file
26
app/templates/admin/warnings/edit.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{% if warning %}
|
||||||
|
Edit {{ warning.title }}
|
||||||
|
{% else %}
|
||||||
|
New warning
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<a class="btn btn-primary float-right" href="{{ url_for('admin.create_edit_warning') }}">New Warning</a>
|
||||||
|
<a class="btn btn-secondary mb-4" href="{{ url_for('admin.warning_list') }}">Back to list</a>
|
||||||
|
|
||||||
|
{% from "macros/forms.html" import render_field, render_submit_field %}
|
||||||
|
<form method="POST" action="" enctype="multipart/form-data">
|
||||||
|
{{ form.hidden_tag() }}
|
||||||
|
|
||||||
|
{{ render_field(form.title) }}
|
||||||
|
{{ render_field(form.description) }}
|
||||||
|
{% if warning %}
|
||||||
|
{{ render_field(form.name) }}
|
||||||
|
{% endif %}
|
||||||
|
{{ render_submit_field(form.submit) }}
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
52
app/templates/admin/warnings/list.html
Normal file
52
app/templates/admin/warnings/list.html
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}
|
||||||
|
{{ _("Warnings") }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<a class="btn btn-primary float-right" href="{{ url_for('admin.create_edit_warning') }}">{{ _("New Warning") }}</a>
|
||||||
|
|
||||||
|
<h1>{{ _("Warnings") }}</h1>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Also see <a href="/help/content_flags/">Package Flags</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="list-group">
|
||||||
|
<div class="list-group-item">
|
||||||
|
<div class="row text-muted">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
{{ _("Name") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm">
|
||||||
|
{{ _("Description") }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-1 text-center">
|
||||||
|
{{ _("Packages") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% for t in warnings %}
|
||||||
|
<a class="list-group-item list-group-item-action"
|
||||||
|
href="{{ url_for('admin.create_edit_warning', name=t.name) }}">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-2">
|
||||||
|
{{ t.title }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm">
|
||||||
|
{{ t.description }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-sm-1 text-center">
|
||||||
|
{{ t.packages | count }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
@ -66,6 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{{ render_field(form.short_desc, class_="pkg_meta") }}
|
{{ render_field(form.short_desc, class_="pkg_meta") }}
|
||||||
{{ render_multiselect_field(form.tags, class_="pkg_meta") }}
|
{{ render_multiselect_field(form.tags, class_="pkg_meta") }}
|
||||||
|
{{ render_multiselect_field(form.content_warnings, class_="pkg_meta") }}
|
||||||
<div class="pkg_meta row">
|
<div class="pkg_meta row">
|
||||||
{{ render_field(form.license, class_="not_txp col-sm-6") }}
|
{{ render_field(form.license, class_="not_txp col-sm-6") }}
|
||||||
{{ render_field(form.media_license, class_="col-sm-6") }}
|
{{ render_field(form.media_license, class_="col-sm-6") }}
|
||||||
|
@ -47,6 +47,13 @@
|
|||||||
{{ package_warning }}
|
{{ package_warning }}
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% for warning in package.content_warnings %}
|
||||||
|
<a class="badge badge-warning" rel="nofollow" href="/help/content_flags/"
|
||||||
|
title="{{ warning.description }}">
|
||||||
|
<i class="fas fa-exclamation-circle" style="margin-right: 0.3em;"></i>
|
||||||
|
{{ warning.title }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
{% for t in package.tags %}
|
{% for t in package.tags %}
|
||||||
<a class="badge badge-primary" rel="nofollow"
|
<a class="badge badge-primary" rel="nofollow"
|
||||||
href="{{ url_for('packages.list_all', tag=t.name) }}">{{ t.title }}</a>
|
href="{{ url_for('packages.list_all', tag=t.name) }}">{{ t.title }}</a>
|
||||||
|
56
migrations/versions/b370c3eb4227_.py
Normal file
56
migrations/versions/b370c3eb4227_.py
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: b370c3eb4227
|
||||||
|
Revises: c5e4213721dd
|
||||||
|
Create Date: 2020-07-17 19:22:15.267179
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy import orm
|
||||||
|
from app.models import ContentWarning
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = 'b370c3eb4227'
|
||||||
|
down_revision = 'c5e4213721dd'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('content_warning',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('name', sa.String(length=100), nullable=False),
|
||||||
|
sa.Column('title', sa.String(length=100), nullable=False),
|
||||||
|
sa.Column('description', sa.String(length=500), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('name')
|
||||||
|
)
|
||||||
|
op.create_table('content_warnings',
|
||||||
|
sa.Column('content_warning_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('package_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['content_warning_id'], ['content_warning.id'], ),
|
||||||
|
sa.ForeignKeyConstraint(['package_id'], ['package.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('content_warning_id', 'package_id')
|
||||||
|
)
|
||||||
|
|
||||||
|
bind = op.get_bind()
|
||||||
|
session = orm.Session(bind=bind)
|
||||||
|
|
||||||
|
session.add(ContentWarning("Violence", "Non-cartoon violence"))
|
||||||
|
session.add(ContentWarning("Drugs", "Drugs or alcohol"))
|
||||||
|
session.add(ContentWarning("Bad Language"))
|
||||||
|
session.add(ContentWarning("Gambling"))
|
||||||
|
session.add(ContentWarning("Horror"))
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('content_warnings')
|
||||||
|
op.drop_table('content_warning')
|
||||||
|
# ### end Alembic commands ###
|
Loading…
Reference in New Issue
Block a user