2020-07-12 17:34:25 +02:00
# ContentDB
2021-01-30 17:59:42 +01:00
# Copyright (C) 2018-21 rubenwardy
2019-01-08 18:17:36 +01:00
#
# This program is free software: you can redistribute it and/or modify
2021-01-30 17:59:42 +01:00
# it under the terms of the GNU Affero General Public License as published by
2019-01-08 18:17:36 +01:00
# 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
2021-01-30 17:59:42 +01:00
# GNU Affero General Public License for more details.
2019-01-08 18:17:36 +01:00
#
2021-01-30 17:59:42 +01:00
# You should have received a copy of the GNU Affero General Public License
2019-01-08 18:17:36 +01:00
# along with this program. If not, see <https://www.gnu.org/licenses/>.
2020-12-04 03:23:04 +01:00
from urllib . parse import quote as urlescape
2019-11-16 00:51:42 +01:00
2021-07-25 04:54:08 +02:00
from flask import render_template
2022-01-07 22:46:16 +01:00
from flask_babel import lazy_gettext , gettext
2019-01-08 18:17:36 +01:00
from flask_wtf import FlaskForm
2020-12-04 23:05:10 +01:00
from flask_login import login_required
2020-03-28 19:09:20 +01:00
from sqlalchemy import or_ , func
2020-04-21 21:35:05 +02:00
from sqlalchemy . orm import joinedload , subqueryload
2020-12-04 03:23:04 +01:00
from wtforms import *
2022-01-27 19:21:47 +01:00
from wtforms_sqlalchemy . fields import QuerySelectField , QuerySelectMultipleField
2020-12-04 03:23:04 +01:00
from wtforms . validators import *
2019-01-08 18:17:36 +01:00
2020-12-04 03:23:04 +01:00
from app . querybuilder import QueryBuilder
from app . rediscache import has_key , set_key
2021-07-25 04:54:08 +02:00
from app . tasks . importtasks import importRepoScreenshot
2020-12-04 03:23:04 +01:00
from app . utils import *
2021-05-04 04:15:26 +02:00
from . import bp , get_package_tabs
2021-08-17 22:16:43 +02:00
from app . logic . LogicError import LogicError
from app . logic . packages import do_edit_package
2021-07-25 04:54:08 +02:00
from app . models . packages import PackageProvides
2021-08-17 22:16:43 +02:00
from app . tasks . webhooktasks import post_discord_webhook
2020-07-12 03:22:35 +02:00
2019-01-08 18:17:36 +01:00
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/ " )
def list_all ( ) :
2019-01-08 18:17:36 +01:00
qb = QueryBuilder ( request . args )
query = qb . buildPackageQuery ( )
title = qb . title
2020-12-04 03:23:04 +01:00
query = query . options (
joinedload ( Package . license ) ,
joinedload ( Package . media_license ) ,
2020-04-21 21:35:05 +02:00
subqueryload ( Package . tags ) )
2020-07-16 14:52:18 +02:00
ip = request . headers . get ( " X-Forwarded-For " ) or request . remote_addr
2020-07-16 15:35:12 +02:00
if ip is not None and not is_user_bot ( ) :
2020-07-16 14:52:18 +02:00
edited = False
for tag in qb . tags :
edited = True
key = " tag/ {} / {} " . format ( ip , tag . name )
if not has_key ( key ) :
set_key ( key , " true " )
Tag . query . filter_by ( id = tag . id ) . update ( {
" views " : Tag . views + 1
} )
if edited :
db . session . commit ( )
2020-07-15 20:06:00 +02:00
2019-01-08 18:17:36 +01:00
if qb . lucky :
package = query . first ( )
if package :
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2019-01-08 18:17:36 +01:00
topic = qb . buildTopicQuery ( ) . first ( )
if qb . search and topic :
return redirect ( " https://forum.minetest.net/viewtopic.php?t= " + str ( topic . topic_id ) )
2019-11-17 22:40:32 +01:00
page = get_int_or_abort ( request . args . get ( " page " ) , 1 )
num = min ( 40 , get_int_or_abort ( request . args . get ( " n " ) , 100 ) )
2019-01-08 18:17:36 +01:00
query = query . paginate ( page , num , True )
search = request . args . get ( " q " )
type_name = request . args . get ( " type " )
2020-03-28 19:09:20 +01:00
authors = [ ]
if search :
authors = User . query \
. filter ( or_ ( * [ func . lower ( User . username ) == name . lower ( ) . strip ( ) for name in search . split ( " " ) ] ) ) \
. all ( )
2020-03-28 19:15:15 +01:00
authors = [ ( author . username , search . lower ( ) . replace ( author . username . lower ( ) , " " ) ) for author in authors ]
2020-03-28 19:09:20 +01:00
2019-01-08 18:17:36 +01:00
topics = None
if qb . search and not query . has_next :
2020-01-24 00:56:10 +01:00
qb . show_discarded = True
2019-01-08 18:17:36 +01:00
topics = qb . buildTopicQuery ( ) . all ( )
2020-07-18 04:14:56 +02:00
tags_query = db . session . query ( func . count ( Tags . c . tag_id ) , Tag ) \
. select_from ( Tag ) . join ( Tags ) . join ( Package ) . group_by ( Tag . id ) . order_by ( db . asc ( Tag . title ) )
tags = qb . filterPackageQuery ( tags_query ) . all ( )
2020-07-12 21:10:19 +02:00
selected_tags = set ( qb . tags )
2022-03-16 01:55:39 +01:00
toplevel_tags = get_toplevel_tags ( ) #Tag.query.filter_by(is_toplevel=True).all()
2020-12-04 03:23:04 +01:00
return render_template ( " packages/list.html " ,
2022-01-07 22:15:48 +01:00
query_hint = title , packages = query . items , pagination = query ,
2020-12-04 03:23:04 +01:00
query = search , tags = tags , selected_tags = selected_tags , type = type_name ,
2022-03-16 01:55:39 +01:00
authors = authors , packages_count = query . total , topics = topics , toplevel = toplevel_tags )
2019-01-08 18:17:36 +01:00
def getReleases ( package ) :
if package . checkPerm ( current_user , Permission . MAKE_RELEASE ) :
2019-03-29 22:01:19 +01:00
return package . releases . limit ( 5 )
2019-01-08 18:17:36 +01:00
else :
2019-03-29 22:01:19 +01:00
return package . releases . filter_by ( approved = True ) . limit ( 5 )
2019-01-08 18:17:36 +01:00
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/<author>/<name>/ " )
2019-01-08 18:17:36 +01:00
@is_package_page
2019-11-16 00:51:42 +01:00
def view ( package ) :
2021-07-25 04:54:08 +02:00
show_similar = not package . approved and (
current_user in package . maintainers or
package . checkPerm ( current_user , Permission . APPROVE_NEW ) )
2021-07-29 20:34:47 +02:00
conflicting_modnames = None
2022-03-16 01:55:39 +01:00
if show_similar :
2021-07-29 20:34:47 +02:00
conflicting_modnames = db . session . query ( MetaPackage . name ) \
. filter ( MetaPackage . id . in_ ( [ mp . id for mp in package . provides ] ) ) \
. filter ( MetaPackage . packages . any ( Package . id != package . id ) ) \
2021-07-25 04:54:08 +02:00
. all ( )
2019-01-08 18:17:36 +01:00
2022-02-17 04:46:21 +01:00
# conflicting_modnames += db.session.query(ForumTopic.name) \
# .filter(ForumTopic.name.in_([ mp.name for mp in package.provides ])) \
# .filter(ForumTopic.topic_id != package.forums) \
# .filter(~ db.exists().where(Package.forums==ForumTopic.topic_id)) \
# .order_by(db.asc(ForumTopic.name), db.asc(ForumTopic.title)) \
# .all()
2019-01-08 18:17:36 +01:00
2021-07-29 20:34:47 +02:00
conflicting_modnames = set ( [ x [ 0 ] for x in conflicting_modnames ] )
2021-07-25 04:54:08 +02:00
packages_uses = None
2022-03-16 01:55:39 +01:00
packages_uses = Package . query . filter (
Package . id != package . id ,
Package . state == PackageState . APPROVED ,
Package . dependencies . any (
Dependency . meta_package_id . in_ ( [ p . id for p in package . provides ] ) ) ) \
. order_by ( db . desc ( Package . score ) ) . limit ( 6 ) . all ( )
2021-07-25 04:54:08 +02:00
2019-01-08 18:17:36 +01:00
releases = getReleases ( package )
review_thread = package . review_thread
if review_thread is not None and not review_thread . checkPerm ( current_user , Permission . SEE_THREAD ) :
review_thread = None
topic_error = None
topic_error_lvl = " warning "
2022-02-17 04:46:21 +01:00
# if package.state != PackageState.APPROVED and package.forums is not None:
# errors = []
# if Package.query.filter(Package.forums==package.forums, Package.state!=PackageState.DELETED).count() > 1:
# errors.append("<b>" + gettext("Error: Another package already uses this forum topic!") + "</b>")
# topic_error_lvl = "danger"
2019-01-08 18:17:36 +01:00
2022-02-17 04:46:21 +01:00
# topic = ForumTopic.query.get(package.forums)
# if topic is not None:
# if topic.author != package.author:
# errors.append("<b>" + gettext("Error: Forum topic author doesn't match package author.") + "</b>")
# topic_error_lvl = "danger"
# elif package.type != PackageType.ASSETPACK:
# errors.append(gettext("Warning: Forum topic not found. This may happen if the topic has only just been created."))
2019-01-08 18:17:36 +01:00
2022-02-17 04:46:21 +01:00
# topic_error = "<br />".join(errors)
2019-01-08 18:17:36 +01:00
2020-07-10 20:05:35 +02:00
threads = Thread . query . filter_by ( package_id = package . id , review_id = None )
2019-01-08 18:17:36 +01:00
if not current_user . is_authenticated :
threads = threads . filter_by ( private = False )
2021-08-16 19:57:05 +02:00
elif not current_user . rank . atLeast ( UserRank . APPROVER ) and not current_user == package . author :
2019-01-08 18:17:36 +01:00
threads = threads . filter ( or_ ( Thread . private == False , Thread . author == current_user ) )
2020-07-09 06:34:55 +02:00
has_review = current_user . is_authenticated and PackageReview . query . filter_by ( package = package , author = current_user ) . count ( ) > 0
2019-01-08 18:17:36 +01:00
2022-03-16 01:55:39 +01:00
toplevel_tags = get_toplevel_tags ( ) #Tag.query.filter_by(is_toplevel=True).all()
2020-12-04 03:23:04 +01:00
return render_template ( " packages/view.html " ,
2021-07-25 04:54:08 +02:00
package = package , releases = releases , packages_uses = packages_uses ,
2021-07-29 20:34:47 +02:00
conflicting_modnames = conflicting_modnames ,
2020-12-04 03:23:04 +01:00
review_thread = review_thread , topic_error = topic_error , topic_error_lvl = topic_error_lvl ,
2022-03-16 01:55:39 +01:00
threads = threads . all ( ) , has_review = has_review , toplevel = toplevel_tags )
2019-01-08 18:17:36 +01:00
2020-08-02 18:41:06 +02:00
@bp.route ( " /packages/<author>/<name>/shields/<type>/ " )
@is_package_page
def shield ( package , type ) :
if type == " title " :
2021-06-24 00:57:26 +02:00
url = " https://img.shields.io/static/v1?label=ContentDB&message= {} &color= {} " \
2020-08-02 18:41:06 +02:00
. format ( urlescape ( package . title ) , urlescape ( " #375a7f " ) )
elif type == " downloads " :
#api_url = abs_url_for("api.package", author=package.author.username, name=package.name)
api_url = " https://content.minetest.net " + url_for ( " api.package " , author = package . author . username , name = package . name )
url = " https://img.shields.io/badge/dynamic/json?color= {} &label=ContentDB&query=downloads&suffix=+downloads&url= {} " \
. format ( urlescape ( " #375a7f " ) , urlescape ( api_url ) )
else :
abort ( 404 )
return redirect ( url )
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/<author>/<name>/download/ " )
2019-01-08 18:17:36 +01:00
@is_package_page
2019-11-16 00:51:42 +01:00
def download ( package ) :
2019-01-08 18:17:36 +01:00
release = package . getDownloadRelease ( )
if release is None :
if " application/zip " in request . accept_mimetypes and \
not " text/html " in request . accept_mimetypes :
return " " , 204
else :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " No download available. " ) , " danger " )
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2019-01-08 18:17:36 +01:00
else :
2021-03-02 23:37:37 +01:00
return redirect ( release . getDownloadURL ( ) )
2019-01-08 18:17:36 +01:00
2020-07-17 23:08:34 +02:00
def makeLabel ( obj ) :
if obj . description :
return " {} : {} " . format ( obj . title , obj . description )
else :
return obj . title
2021-12-20 22:07:12 +01:00
2019-01-08 18:17:36 +01:00
class PackageForm ( FlaskForm ) :
2022-03-16 01:55:39 +01:00
try :
toplevel_tags = [ x . title for x in Tag . query . filter_by ( is_toplevel = True ) . all ( ) ]
type = SelectField ( lazy_gettext ( " Type " ) , [ InputRequired ( ) ] , choices = toplevel_tags , default = toplevel_tags [ 0 ] )
except :
toplevel_tags = [ ]
# type = SelectField(lazy_gettext("Type"), [InputRequired()], choices=toplevel_tags, default=toplevel_tags[0])
# type = SelectField(lazy_gettext("Type"), [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.GAME)
2022-01-07 22:18:34 +01:00
title = StringField ( lazy_gettext ( " Title (Human-readable) " ) , [ InputRequired ( ) , Length ( 1 , 100 ) ] )
2022-02-23 07:47:32 +01:00
name = StringField ( lazy_gettext ( " Name (Technical) " ) , [ InputRequired ( ) , Length ( 1 , 100 ) , Regexp ( " ^[a-zA-Z0-9_ \ - \ .]+$ " , 0 , lazy_gettext ( " Lower case letters (a-z), digits (0-9), and underscores (_), dashes and periods only " ) ) ] )
2022-01-07 22:18:34 +01:00
short_desc = StringField ( lazy_gettext ( " Short Description (Plaintext) " ) , [ InputRequired ( ) , Length ( 1 , 200 ) ] )
2021-02-02 23:47:46 +01:00
2022-01-07 22:18:34 +01:00
dev_state = SelectField ( lazy_gettext ( " Maintenance State " ) , [ InputRequired ( ) ] , choices = PackageDevState . choices ( with_none = True ) , coerce = PackageDevState . coerce )
2021-12-20 22:07:12 +01:00
2022-01-07 22:18:34 +01:00
tags = QuerySelectMultipleField ( lazy_gettext ( ' Tags ' ) , query_factory = lambda : Tag . query . order_by ( db . asc ( Tag . name ) ) , get_pk = lambda a : a . id , get_label = makeLabel )
content_warnings = QuerySelectMultipleField ( lazy_gettext ( ' Content Warnings ' ) , query_factory = lambda : ContentWarning . query . order_by ( db . asc ( ContentWarning . name ) ) , get_pk = lambda a : a . id , get_label = makeLabel )
license = QuerySelectField ( lazy_gettext ( " 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 ( lazy_gettext ( " 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 )
2021-02-02 23:47:46 +01:00
2022-01-07 22:18:34 +01:00
desc = TextAreaField ( lazy_gettext ( " Long Description (Markdown) " ) , [ Optional ( ) , Length ( 0 , 10000 ) ] )
2021-02-02 23:47:46 +01:00
2022-01-07 22:18:34 +01:00
repo = StringField ( lazy_gettext ( " VCS Repository URL " ) , [ Optional ( ) , URL ( ) ] , filters = [ lambda x : x or None ] )
website = StringField ( lazy_gettext ( " Website URL " ) , [ Optional ( ) , URL ( ) ] , filters = [ lambda x : x or None ] )
issueTracker = StringField ( lazy_gettext ( " Issue Tracker URL " ) , [ Optional ( ) , URL ( ) ] , filters = [ lambda x : x or None ] )
2022-02-17 04:46:21 +01:00
# forums = IntegerField(lazy_gettext("Forum Topic ID"), [Optional(), NumberRange(0,999999)])
2022-01-25 21:48:37 +01:00
video_url = StringField ( lazy_gettext ( " Video URL " ) , [ Optional ( ) , URL ( ) ] , filters = [ lambda x : x or None ] )
2021-02-02 23:47:46 +01:00
2022-01-07 22:18:34 +01:00
submit = SubmitField ( lazy_gettext ( " Save " ) )
2020-07-17 21:48:51 +02:00
2019-01-08 18:17:36 +01:00
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/new/ " , methods = [ " GET " , " POST " ] )
@bp.route ( " /packages/<author>/<name>/edit/ " , methods = [ " GET " , " POST " ] )
2019-01-08 18:17:36 +01:00
@login_required
2019-11-16 00:51:42 +01:00
def create_edit ( author = None , name = None ) :
2019-01-08 18:17:36 +01:00
package = None
if author is None :
form = PackageForm ( formdata = request . form )
author = request . args . get ( " author " )
if author is None or author == current_user . username :
author = current_user
else :
author = User . query . filter_by ( username = author ) . first ( )
if author is None :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " Unable to find that user " ) , " danger " )
2019-11-16 00:51:42 +01:00
return redirect ( url_for ( " packages.create_edit " ) )
2019-01-08 18:17:36 +01:00
if not author . checkPerm ( current_user , Permission . CHANGE_AUTHOR ) :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " Permission denied " ) , " danger " )
2019-11-16 00:51:42 +01:00
return redirect ( url_for ( " packages.create_edit " ) )
2019-01-08 18:17:36 +01:00
else :
package = getPackageByInfo ( author , name )
2020-08-02 19:03:44 +02:00
if package is None :
abort ( 404 )
2019-01-08 18:17:36 +01:00
if not package . checkPerm ( current_user , Permission . EDIT_PACKAGE ) :
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2019-01-08 18:17:36 +01:00
author = package . author
form = PackageForm ( formdata = request . form , obj = package )
# Initial form class from post data and default data
if request . method == " GET " :
if package is None :
2022-01-27 20:20:45 +01:00
form . name . data = request . args . get ( " bname " )
form . title . data = request . args . get ( " title " )
form . repo . data = request . args . get ( " repo " )
2022-02-17 04:46:21 +01:00
# form.forums.data = request.args.get("forums")
2020-02-23 21:40:14 +01:00
form . license . data = None
form . media_license . data = None
2019-01-08 18:17:36 +01:00
else :
2022-01-27 20:20:45 +01:00
form . tags . data = package . tags
form . content_warnings . data = package . content_warnings
2019-01-08 18:17:36 +01:00
2020-12-05 00:07:19 +01:00
if form . validate_on_submit ( ) :
2019-01-08 18:17:36 +01:00
wasNew = False
if not package :
package = Package . query . filter_by ( name = form [ " name " ] . data , author_id = author . id ) . first ( )
if package is not None :
2020-09-16 18:51:03 +02:00
if package . state == PackageState . READY_FOR_REVIEW :
2019-01-08 18:17:36 +01:00
Package . query . filter_by ( name = form [ " name " ] . data , author_id = author . id ) . delete ( )
else :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " Package already exists! " ) , " danger " )
2019-11-16 00:51:42 +01:00
return redirect ( url_for ( " packages.create_edit " ) )
2019-01-08 18:17:36 +01:00
package = Package ( )
package . author = author
2020-07-11 17:56:36 +02:00
package . maintainers . append ( author )
2019-01-08 18:17:36 +01:00
wasNew = True
2019-07-29 21:29:55 +02:00
2021-02-02 22:35:29 +01:00
try :
2021-07-24 01:43:55 +02:00
do_edit_package ( current_user , package , wasNew , True , {
2021-02-02 23:47:46 +01:00
" type " : form . type . data ,
2021-02-02 22:35:29 +01:00
" title " : form . title . data ,
2021-02-02 23:47:46 +01:00
" name " : form . name . data ,
2021-02-02 22:35:29 +01:00
" short_desc " : form . short_desc . data ,
2021-12-20 22:07:12 +01:00
" dev_state " : form . dev_state . data ,
2021-02-02 22:35:29 +01:00
" tags " : form . tags . raw_data ,
" content_warnings " : form . content_warnings . raw_data ,
2021-02-02 23:47:46 +01:00
" license " : form . license . data ,
" media_license " : form . media_license . data ,
" desc " : form . desc . data ,
2021-02-02 22:35:29 +01:00
" repo " : form . repo . data ,
" website " : form . website . data ,
" issueTracker " : form . issueTracker . data ,
2022-02-17 04:46:21 +01:00
# "forums": form.forums.data,
2022-01-25 21:48:37 +01:00
" video_url " : form . video_url . data ,
2021-02-02 22:35:29 +01:00
} )
if wasNew and package . repo is not None :
importRepoScreenshot . delay ( package . id )
2021-07-24 05:30:14 +02:00
next_url = package . getURL ( " packages.view " )
2021-02-02 22:35:29 +01:00
if wasNew and ( " WTFPL " in package . license . name or " WTFPL " in package . media_license . name ) :
next_url = url_for ( " flatpage " , path = " help/wtfpl " , r = next_url )
elif wasNew :
2021-07-24 05:30:14 +02:00
next_url = package . getURL ( " packages.setup_releases " )
2021-02-02 22:35:29 +01:00
return redirect ( next_url )
except LogicError as e :
flash ( e . message , " danger " )
2019-01-08 18:17:36 +01:00
2020-09-16 18:51:03 +02:00
package_query = Package . query . filter_by ( state = PackageState . APPROVED )
2019-01-08 18:17:36 +01:00
if package is not None :
package_query = package_query . filter ( Package . id != package . id )
enableWizard = name is None and request . method != " POST "
2020-12-04 03:23:04 +01:00
return render_template ( " packages/create_edit.html " , package = package ,
form = form , author = author , enable_wizard = enableWizard ,
packages = package_query . all ( ) ,
2021-05-04 04:15:26 +02:00
mpackages = MetaPackage . query . order_by ( db . asc ( MetaPackage . name ) ) . all ( ) ,
2022-03-16 01:55:39 +01:00
tabs = get_package_tabs ( current_user , package ) , current_tab = " edit " , toplevel = get_toplevel_tags ( ) )
2019-01-08 18:17:36 +01:00
2020-09-16 18:51:03 +02:00
@bp.route ( " /packages/<author>/<name>/state/ " , methods = [ " POST " ] )
2019-01-08 18:17:36 +01:00
@login_required
@is_package_page
2020-09-16 18:51:03 +02:00
def move_to_state ( package ) :
state = PackageState . get ( request . args . get ( " state " ) )
if state is None :
abort ( 400 )
if not package . canMoveToState ( current_user , state ) :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " You don ' t have permission to do that " ) , " danger " )
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2019-01-08 18:17:36 +01:00
2020-09-16 18:51:03 +02:00
package . state = state
msg = " Marked {} as {} " . format ( package . title , state . value )
2019-01-08 18:17:36 +01:00
2020-09-16 18:51:03 +02:00
if state == PackageState . APPROVED :
2020-07-18 02:48:21 +02:00
if not package . approved_at :
2021-08-17 22:16:43 +02:00
post_discord_webhook . delay ( package . author . username ,
" New package {} " . format ( package . getURL ( " packages.view " , absolute = True ) ) , False )
2020-07-18 02:48:21 +02:00
package . approved_at = datetime . datetime . now ( )
2019-01-08 18:17:36 +01:00
screenshots = PackageScreenshot . query . filter_by ( package = package , approved = False ) . all ( )
for s in screenshots :
s . approved = True
2020-07-11 03:32:17 +02:00
msg = " Approved {} " . format ( package . title )
2021-08-17 22:16:43 +02:00
elif state == PackageState . READY_FOR_REVIEW :
post_discord_webhook . delay ( package . author . username ,
" Ready for Review: {} " . format ( package . getURL ( " packages.view " , absolute = True ) ) , True )
2020-09-16 18:51:03 +02:00
2021-07-24 05:30:14 +02:00
addNotification ( package . maintainers , current_user , NotificationType . PACKAGE_APPROVAL , msg , package . getURL ( " packages.view " ) , package )
2020-09-16 18:51:03 +02:00
severity = AuditSeverity . NORMAL if current_user in package . maintainers else AuditSeverity . EDITOR
2021-07-24 05:30:14 +02:00
addAuditLog ( severity , current_user , msg , package . getURL ( " packages.view " ) , package )
2020-09-16 18:51:03 +02:00
db . session . commit ( )
if package . state == PackageState . CHANGES_NEEDED :
2022-01-20 02:18:10 +01:00
flash ( gettext ( " Please comment what changes are needed in the approval thread " ) , " warning " )
2020-09-16 18:51:03 +02:00
if package . review_thread :
return redirect ( package . review_thread . getViewURL ( ) )
else :
return redirect ( url_for ( ' threads.new ' , pid = package . id , title = ' Package approval comments ' ) )
2019-01-08 18:17:36 +01:00
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2019-01-08 18:17:36 +01:00
2019-11-16 00:51:42 +01:00
@bp.route ( " /packages/<author>/<name>/remove/ " , methods = [ " GET " , " POST " ] )
2019-01-08 18:17:36 +01:00
@login_required
@is_package_page
2019-11-16 00:51:42 +01:00
def remove ( package ) :
2019-01-08 18:17:36 +01:00
if request . method == " GET " :
2021-05-04 04:15:26 +02:00
return render_template ( " packages/remove.html " , package = package ,
2022-03-16 01:55:39 +01:00
tabs = get_package_tabs ( current_user , package ) , current_tab = " remove " , toplevel = get_toplevel_tags ( ) )
2019-01-08 18:17:36 +01:00
2022-01-09 22:12:29 +01:00
reason = request . form . get ( " reason " ) or " ? "
2019-01-08 18:17:36 +01:00
if " delete " in request . form :
if not package . checkPerm ( current_user , Permission . DELETE_PACKAGE ) :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " You don ' t have permission to do that. " ) , " danger " )
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2019-01-08 18:17:36 +01:00
2020-09-16 18:51:03 +02:00
package . state = PackageState . DELETED
2019-01-08 18:17:36 +01:00
2019-11-16 00:51:42 +01:00
url = url_for ( " users.profile " , username = package . author . username )
2022-01-09 22:12:29 +01:00
msg = " Deleted {} , reason= {} " . format ( package . title , reason )
2020-12-05 04:44:34 +01:00
addNotification ( package . maintainers , current_user , NotificationType . PACKAGE_EDIT , msg , url , package )
2020-07-11 03:32:17 +02:00
addAuditLog ( AuditSeverity . EDITOR , current_user , msg , url )
2019-01-08 18:17:36 +01:00
db . session . commit ( )
2022-01-07 22:46:16 +01:00
flash ( gettext ( " Deleted package " ) , " success " )
2019-01-08 18:17:36 +01:00
return redirect ( url )
elif " unapprove " in request . form :
if not package . checkPerm ( current_user , Permission . UNAPPROVE_PACKAGE ) :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " You don ' t have permission to do that. " ) , " danger " )
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2019-01-08 18:17:36 +01:00
2020-09-16 19:05:37 +02:00
package . state = PackageState . WIP
2019-01-08 18:17:36 +01:00
2022-01-09 22:12:29 +01:00
msg = " Unapproved {} , reason= {} " . format ( package . title , reason )
2021-07-24 05:30:14 +02:00
addNotification ( package . maintainers , current_user , NotificationType . PACKAGE_APPROVAL , msg , package . getURL ( " packages.view " ) , package )
addAuditLog ( AuditSeverity . EDITOR , current_user , msg , package . getURL ( " packages.view " ) , package )
2020-07-11 03:32:17 +02:00
2019-01-08 18:17:36 +01:00
db . session . commit ( )
2022-01-07 22:46:16 +01:00
flash ( gettext ( " Unapproved package " ) , " success " )
2019-01-08 18:17:36 +01:00
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2019-01-08 18:17:36 +01:00
else :
abort ( 400 )
2020-07-08 23:45:24 +02:00
class PackageMaintainersForm ( FlaskForm ) :
2022-01-07 22:55:33 +01:00
maintainers_str = StringField ( lazy_gettext ( " Maintainers (Comma-separated) " ) , [ Optional ( ) ] )
submit = SubmitField ( lazy_gettext ( " Save " ) )
2020-07-08 23:45:24 +02:00
@bp.route ( " /packages/<author>/<name>/edit-maintainers/ " , methods = [ " GET " , " POST " ] )
@login_required
@is_package_page
def edit_maintainers ( package ) :
if not package . checkPerm ( current_user , Permission . EDIT_MAINTAINERS ) :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " You do not have permission to edit maintainers " ) , " danger " )
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2020-07-08 23:45:24 +02:00
form = PackageMaintainersForm ( formdata = request . form )
if request . method == " GET " :
2020-07-09 00:52:36 +02:00
form . maintainers_str . data = " , " . join ( [ x . username for x in package . maintainers if x != package . author ] )
2020-07-08 23:45:24 +02:00
2020-12-05 00:07:19 +01:00
if form . validate_on_submit ( ) :
2020-07-09 00:00:45 +02:00
usernames = [ x . strip ( ) . lower ( ) for x in form . maintainers_str . data . split ( " , " ) ]
2020-07-08 23:45:24 +02:00
users = User . query . filter ( func . lower ( User . username ) . in_ ( usernames ) ) . all ( )
2020-07-09 00:20:29 +02:00
2021-07-20 00:49:29 +02:00
thread = package . threads . filter_by ( author = get_system_user ( ) ) . first ( )
2020-07-09 00:20:29 +02:00
for user in users :
if not user in package . maintainers :
2021-07-20 00:49:29 +02:00
if thread :
thread . watchers . append ( user )
2020-12-05 04:44:34 +01:00
addNotification ( user , current_user , NotificationType . MAINTAINER ,
2021-07-24 05:30:14 +02:00
" Added you as a maintainer of {} " . format ( package . title ) , package . getURL ( " packages.view " ) , package )
2020-07-09 00:20:29 +02:00
for user in package . maintainers :
2020-07-09 00:52:36 +02:00
if user != package . author and not user in users :
2020-12-05 04:44:34 +01:00
addNotification ( user , current_user , NotificationType . MAINTAINER ,
2021-07-24 05:30:14 +02:00
" Removed you as a maintainer of {} " . format ( package . title ) , package . getURL ( " packages.view " ) , package )
2020-07-09 00:20:29 +02:00
2020-07-08 23:45:24 +02:00
package . maintainers . clear ( )
package . maintainers . extend ( users )
2020-07-11 17:56:36 +02:00
if package . author not in package . maintainers :
package . maintainers . append ( package . author )
2020-07-09 00:20:29 +02:00
2020-07-11 03:32:17 +02:00
msg = " Edited {} maintainers " . format ( package . title )
2021-07-24 05:30:14 +02:00
addNotification ( package . author , current_user , NotificationType . MAINTAINER , msg , package . getURL ( " packages.view " ) , package )
2020-07-11 03:32:17 +02:00
severity = AuditSeverity . NORMAL if current_user == package . author else AuditSeverity . MODERATION
2021-07-24 05:30:14 +02:00
addAuditLog ( severity , current_user , msg , package . getURL ( " packages.view " ) , package )
2020-07-09 00:20:29 +02:00
2020-07-08 23:45:24 +02:00
db . session . commit ( )
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2020-07-08 23:45:24 +02:00
users = User . query . filter ( User . rank > = UserRank . NEW_MEMBER ) . order_by ( db . asc ( User . username ) ) . all ( )
2021-05-04 04:15:26 +02:00
return render_template ( " packages/edit_maintainers.html " , package = package , form = form ,
2022-03-16 01:55:39 +01:00
users = users , tabs = get_package_tabs ( current_user , package ) , current_tab = " maintainers " , toplevel = get_toplevel_tags ( ) )
2020-07-09 00:42:30 +02:00
@bp.route ( " /packages/<author>/<name>/remove-self-maintainer/ " , methods = [ " POST " ] )
@login_required
@is_package_page
def remove_self_maintainers ( package ) :
if not current_user in package . maintainers :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " You are not a maintainer " ) , " danger " )
2020-07-09 00:42:30 +02:00
elif current_user == package . author :
2022-01-07 22:46:16 +01:00
flash ( gettext ( " Package owners cannot remove themselves as maintainers " ) , " danger " )
2020-07-09 00:42:30 +02:00
else :
package . maintainers . remove ( current_user )
2020-12-05 04:44:34 +01:00
addNotification ( package . author , current_user , NotificationType . MAINTAINER ,
2021-07-24 05:30:14 +02:00
" Removed themself as a maintainer of {} " . format ( package . title ) , package . getURL ( " packages.view " ) , package )
2020-07-09 00:42:30 +02:00
db . session . commit ( )
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.view " ) )
2020-07-12 03:22:35 +02:00
2021-02-08 01:46:39 +01:00
@bp.route ( " /packages/<author>/<name>/audit/ " )
@login_required
@is_package_page
def audit ( package ) :
2021-08-16 19:57:05 +02:00
if not ( package . checkPerm ( current_user , Permission . EDIT_PACKAGE ) or
package . checkPerm ( current_user , Permission . APPROVE_NEW ) ) :
2021-02-08 01:46:39 +01:00
abort ( 403 )
page = get_int_or_abort ( request . args . get ( " page " ) , 1 )
num = min ( 40 , get_int_or_abort ( request . args . get ( " n " ) , 100 ) )
query = package . audit_log_entries . order_by ( db . desc ( AuditLogEntry . created_at ) )
pagination = query . paginate ( page , num , True )
2021-07-24 04:56:43 +02:00
return render_template ( " packages/audit.html " , log = pagination . items , pagination = pagination ,
2022-03-16 01:55:39 +01:00
package = package , tabs = get_package_tabs ( current_user , package ) , current_tab = " audit " , toplevel = get_toplevel_tags ( ) )
2021-07-24 03:30:43 +02:00
class PackageAliasForm ( FlaskForm ) :
2022-01-07 22:55:33 +01:00
author = StringField ( lazy_gettext ( " Author Name " ) , [ InputRequired ( ) , Length ( 1 , 50 ) ] )
name = StringField ( lazy_gettext ( " Name (Technical) " ) , [ InputRequired ( ) , Length ( 1 , 100 ) ,
2022-02-23 07:47:32 +01:00
Regexp ( " ^[a-zA-Z0-9_ \ - \ .]+$ " , 0 , lazy_gettext ( " Lower case letters (a-z), digits (0-9), and underscores (_), dashes and periods only " ) ) ] )
2022-01-07 22:55:33 +01:00
submit = SubmitField ( lazy_gettext ( " Save " ) )
2021-07-24 03:30:43 +02:00
@bp.route ( " /packages/<author>/<name>/aliases/ " )
@rank_required ( UserRank . EDITOR )
@is_package_page
def alias_list ( package : Package ) :
2022-03-16 01:55:39 +01:00
return render_template ( " packages/alias_list.html " , package = package , toplevel = get_toplevel_tags ( ) )
2021-07-24 03:30:43 +02:00
@bp.route ( " /packages/<author>/<name>/aliases/new/ " , methods = [ " GET " , " POST " ] )
@bp.route ( " /packages/<author>/<name>/aliases/<int:alias_id>/ " , methods = [ " GET " , " POST " ] )
@rank_required ( UserRank . EDITOR )
@is_package_page
def alias_create_edit ( package : Package , alias_id : int = None ) :
alias = None
if alias_id :
alias = PackageAlias . query . get ( alias_id )
if alias is None or alias . package != package :
abort ( 404 )
form = PackageAliasForm ( request . form , obj = alias )
if form . validate_on_submit ( ) :
if alias is None :
alias = PackageAlias ( )
alias . package = package
db . session . add ( alias )
form . populate_obj ( alias )
db . session . commit ( )
2021-07-24 05:30:14 +02:00
return redirect ( package . getURL ( " packages.alias_list " ) )
2021-07-24 03:30:43 +02:00
2022-03-16 01:55:39 +01:00
return render_template ( " packages/alias_create_edit.html " , package = package , form = form , toplevel = get_toplevel_tags ( ) )
2021-07-24 04:56:43 +02:00
@bp.route ( " /packages/<author>/<name>/share/ " )
@login_required
@is_package_page
def share ( package ) :
return render_template ( " packages/share.html " , package = package ,
2022-03-16 01:55:39 +01:00
tabs = get_package_tabs ( current_user , package ) , current_tab = " share " , toplevel = get_toplevel_tags ( ) )
2021-07-29 20:34:47 +02:00
@bp.route ( " /packages/<author>/<name>/similar/ " )
@is_package_page
def similar ( package ) :
packages_modnames = { }
for metapackage in package . provides :
packages_modnames [ metapackage ] = Package . query . filter ( Package . id != package . id ,
Package . state != PackageState . DELETED ) \
. filter ( Package . provides . any ( PackageProvides . c . metapackage_id == metapackage . id ) ) \
. order_by ( db . desc ( Package . score ) ) \
. all ( )
2022-02-17 04:46:21 +01:00
# similar_topics = ForumTopic.query \
# .filter_by(name=package.name) \
# .filter(ForumTopic.topic_id != package.forums) \
# .filter(~ db.exists().where(Package.forums == ForumTopic.topic_id)) \
# .order_by(db.asc(ForumTopic.name), db.asc(ForumTopic.title)) \
# .all()
2021-07-29 20:34:47 +02:00
return render_template ( " packages/similar.html " , package = package ,
2022-03-16 01:55:39 +01:00
packages_modnames = packages_modnames , similar_topics = [ ] , toplevel = get_toplevel_tags ( ) )