Compare commits

..

33 Commits

Author SHA1 Message Date
rubenwardy
ed409df323 Update scoring algorithm to take licenses and screenshots into account 2018-09-03 01:50:53 +01:00
rubenwardy
b8decafd75 Add WTFPL warning on new packages 2018-09-03 01:40:48 +01:00
rubenwardy
5aaee010c1 Fix accidental regression in phpbbparser 2018-08-25 21:25:12 +01:00
rubenwardy
a01fe4043e Fix owner not seeing create link in 'more content' list 2018-08-25 19:12:42 +01:00
rubenwardy
e0ef0e018d Fix permissions check in 'more content' list 2018-08-25 19:10:11 +01:00
rubenwardy
0210a3e601 Add I'm feeling lucky 2018-08-25 18:50:05 +01:00
rubenwardy
36000b1592 Add list of relevant forum topics to last page of results 2018-08-25 18:20:45 +01:00
rubenwardy
b296b9b299 Fix two bugs 2018-07-30 00:42:11 +01:00
rubenwardy
dd6257a0a0 Add flask-admin 2018-07-30 00:16:22 +01:00
rubenwardy
23b324cc9c Update policy: remote too much detail about name exceptions 2018-07-29 17:34:06 +01:00
rubenwardy
f61f9e8654 Fix typo in template path for tags list 2018-07-28 19:20:49 +01:00
rubenwardy
286207ffa2 Add release specific download URL 2018-07-28 18:33:36 +01:00
rubenwardy
a3e82ad42f Add support for multiple types in packages list 2018-07-28 18:12:22 +01:00
rubenwardy
404200b8f0 Fix license editor setting is_foss to true on edit 2018-07-28 17:47:08 +01:00
rubenwardy
dfecf470fa Redirect to license list on save 2018-07-28 17:34:00 +01:00
rubenwardy
c737f58fc0 Update policy 2018-07-28 17:31:27 +01:00
rubenwardy
ab59b7f4ba Prevent approval of packages with an 'Other' license 2018-07-28 17:30:43 +01:00
rubenwardy
514a24e2c4 Add license editor 2018-07-28 17:26:28 +01:00
rubenwardy
742a327cbb Add warning on other license 2018-07-28 16:46:46 +01:00
rubenwardy
864e067412 Fix typo in running task link on edit release page 2018-07-28 16:06:23 +01:00
rubenwardy
1c7a192854 Add link to original screenshot in edit screenshot page 2018-07-28 16:05:09 +01:00
rubenwardy
c298f64295 Fix thumbnails
Fixes #97
2018-07-28 16:03:48 +01:00
rubenwardy
e82166f87e Add subscribe/unsubscribe button 2018-07-28 15:30:59 +01:00
rubenwardy
909a2b4ce9 Add support for post-approval threads 2018-07-28 15:19:30 +01:00
rubenwardy
df8d05f09d Add thread list to package view 2018-07-28 15:08:08 +01:00
rubenwardy
8c3b1c8c95 Add commit hash to releases 2018-07-28 14:48:03 +01:00
rubenwardy
ecdb755dd3 Remove unused release approval checklist 2018-07-28 14:29:40 +01:00
rubenwardy
901e115a21 Prevent trusted users from approving their own packages 2018-07-28 14:25:51 +01:00
rubenwardy
d4c2166019 Add default title to screenshots 2018-07-28 14:13:26 +01:00
rubenwardy
cbc98ef624 Enable markdown in comments 2018-07-28 14:07:29 +01:00
nOOb3167
794bc8a018 Add default password to admin user 2018-07-24 20:39:48 +01:00
nOOb3167
34900222dc Add upper version limit to Flask requirement 2018-07-24 20:39:29 +01:00
rubenwardy
f9a1d25c57 Fix unreadable dropdown text
Fixes #74
2018-07-24 20:33:26 +01:00
37 changed files with 628 additions and 127 deletions

View File

@@ -34,6 +34,8 @@ title: Ranks and Permissions
<th>N</th>
<th>Y</th>
<th>N</th>
<th>Y</th>
<th>N</th>
</tr>
</thead>
<tbody>
@@ -58,7 +60,7 @@ title: Ranks and Permissions
<th></th>
<th></th> <!-- member -->
<th></th>
<th></th> <!-- trusted member -->
<th></th> <!-- trusted member -->
<th></th>
<th></th> <!-- editor -->
<th></th>
@@ -101,7 +103,7 @@ title: Ranks and Permissions
<td>Approve Screenshot</td>
<th></th> <!-- new -->
<th></th>
<th></th> <!-- member -->
<th></th> <!-- member -->
<th></th>
<th></th> <!-- trusted member -->
<th></th>
@@ -187,6 +189,21 @@ title: Ranks and Permissions
<th></th> <!-- admin -->
<th></th>
</tr>
<tr>
<td>See Private Thread</td>
<th></th> <!-- new -->
<th></th>
<th></th> <!-- member -->
<th></th>
<th></th> <!-- trusted member -->
<th></th>
<th></th> <!-- editor -->
<th></th>
<th></th> <!-- moderator -->
<th></th>
<th></th> <!-- admin -->
<th></th>
</tr>
<tr>
<td>Set Email</td>
<th></th> <!-- new -->

View File

@@ -0,0 +1,42 @@
title: WTFPL is a terrible license
no_h1: true
<div id="warning" class="box box_grey alert alert-warning">
<span class="icon_message"></span>
Please reconsider the choice of WTFPL as a license.
<script src="/static/jquery.min.js"></script>
<script>
// @author rubenwardy
// @license magnet:?xt=urn:btih:1f739d935676111cfff4b4693e3816e664797050&dn=gpl-3.0.txt GPL-v3-or-Later
var params = new URLSearchParams(location.search);
var r = params.get("r");
if (r)
document.write("<a class='alert_right button' href='" + r + "'>Okay</a>");
else
$("#warning").hide();
</script>
</div>
# WTFPL is a terrible license
The use of WTFPL as a license is discouraged for multiple reasons.
* **No Warranty disclaimer:** This could open you up to being sued.<sup>[1]</sup>
* **Swearing:** This prevents settings like schools from using your content.
* **Not OSI Approved:** Same as public domain?
The Open Source Initiative chose not to approve the license as an open-source
license, saying:<sup>[3]</sup>
> It's no different from dedication to the public domain.
> Author has submitted license approval request author is free to make public domain dedication.
> Although he agrees with the recommendation, Mr. Michlmayr notes that public domain doesn't exist in Europe. Recommend: Reject.
## Sources
1. [WTFPL is harmful to software developers](https://cubicspot.blogspot.com/2017/04/wtfpl-is-harmful-to-software-developers.html)
2. [FSF](https://www.gnu.org/licenses/license-list.en.html)
3. [OSI](https://opensource.org/minutes20090304)

View File

@@ -4,6 +4,20 @@ title: Package Inclusion Policy and Guidance
<b>Note:</b> This is a draft
</div>
## 0. Overview
ContentDB is for the community, and as such listings should be useful to the
community. To help with this, there are a few rules to improve the quality of
the listings and to combat abuse.
* No inappropriate content.
* Content must be playable/useful, but not necessarily finished.
* Don't use the name of another mod unless your mod is a fork or reimplementation.
* Licenses must allow derivatives, redistribution, and must not discriminate.
* Don't put promotions are advertisements in package listings, except for
donation and personal website links which are permitted in the long description.
## 1. General
It is not permitted to submit abusive, obscene, vulgar, slanderous, hateful,
@@ -22,14 +36,17 @@ The submission of malware is strictly prohibited. This includes software which
does not do as it advertises, for example if it posts telemetry without stating
clearly that it does in the package meta.
ContentDB should only currently contain playable content, ie: stuff that would
be in Mod Releases and Game Releases. Please don't upload any Work In Progress (WIP)
things. This will probably change in future if/when an "early access" feature is
added.
ContentDB should only currently contain playable content - content which is
sufficiently complete to be useful to end users. It's fine to add stuff which
is still a work in progress (WIP) as long as it adds sufficient value -
Mineclone 2 is a good example of a WIP package which may break between releases
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.
Adding non-player facing mods, such as libraries and server tools, is perfectly fine.
ContentDB isn't just for player-facing things, and adding libraries allows them to be
installed when a mod depends on it.
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.
## 3. Technical Names
@@ -48,6 +65,8 @@ package and give the name to the correct one.
If you submit a package where you don't have the right to the name you will be asked
to change the name of the package, or your package won't be accepted.
We reserve the right to issue exceptions for this where we feel necessary.
### 3.2 Mod Forks and Reimplementations
An exception to the above is that mods are allowed to have the same name as a
@@ -66,24 +85,31 @@ Please ensure that you correctly credit any resources (code, assets, or otherwis
that you have used in your package.
**The use of licenses which do not allow derivatives or redistribution is not
permitted. This includes CC-ND (No-Derivatives) and lots of closed source licenses.**
permitted. This includes CC-ND (No-Derivatives) and lots of closed source licenses.
The use of licenses which discriminate between groups of people or forbid the use
of the content on servers or singleplayer is also not permitted.**
However, closed sourced licenses are allowed if they allow the above.
If the license you use is not on the list then please choose the correct "Other"
option.
If the license you use is not on the list then please select "Other", and we'll
get around to adding it.
Please note that the definitions of "free" and "non-free" is the same as that
of the [Free Software Foundation](https://www.gnu.org/philosophy/free-sw.en.html).
### 4.2 Recommended Licenses
It is highly recommended that you use a free and open source software license.
FOSS licenses result in a sharing community and will increase the number of potential users your package has.
Using a closed source license will result in your package being massively penalised in the search results and package lists.
It is recommended that you use a proper license for code with a warranty
disclaimer, such as the (L)GPL or MIT. You should also use a proper media license
for media, such as a Creative Commons license.
The use of WTFPL is discouraged as it doesn't contain a valid warranty disclaimer,
and also includes swearing which dissuades teachers from using your content.
The use of WTFPL is discouraged as it doesn't contain a [valid warranty disclaimer](https://cubicspot.blogspot.com/2017/04/wtfpl-is-harmful-to-software-developers.html),
and also includes swearing which prevents settings like schools from using your content.
[Read more](/help/wtfpl/).
Public domain is not a valid license in many countries, please use CC0 or MIT instead.

View File

@@ -77,6 +77,7 @@ class Permission(enum.Enum):
CHANGE_EMAIL = "CHANGE_EMAIL"
EDIT_EDITREQUEST = "EDIT_EDITREQUEST"
SEE_THREAD = "SEE_THREAD"
CREATE_THREAD = "CREATE_THREAD"
# Only return true if the permission is valid for *all* contexts
# See Package.checkPerm for package-specific contexts
@@ -210,6 +211,13 @@ class PackageType(enum.Enum):
def __str__(self):
return self.name
@classmethod
def get(cls, name):
try:
return PackageType[name.upper()]
except KeyError:
return None
@classmethod
def choices(cls):
return [(choice, choice.value) for choice in cls]
@@ -480,7 +488,7 @@ class Package(db.Model):
isOwner = user == self.author
# Members can edit their own packages, and editors can edit any packages
if perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS:
if perm == Permission.MAKE_RELEASE or perm == Permission.ADD_SCREENSHOTS or perm == Permission.CREATE_THREAD:
return isOwner or user.rank.atLeast(UserRank.EDITOR)
if perm == Permission.EDIT_PACKAGE or perm == Permission.APPROVE_CHANGES:
@@ -489,12 +497,11 @@ class Package(db.Model):
else:
return user.rank.atLeast(UserRank.EDITOR)
# Editors can change authors
elif perm == Permission.CHANGE_AUTHOR:
# Editors can change authors and approve new packages
elif perm == Permission.APPROVE_NEW or perm == Permission.CHANGE_AUTHOR:
return user.rank.atLeast(UserRank.EDITOR)
elif perm == Permission.APPROVE_NEW or perm == Permission.APPROVE_RELEASE \
or perm == Permission.APPROVE_SCREENSHOT:
elif perm == Permission.APPROVE_RELEASE or perm == Permission.APPROVE_SCREENSHOT:
return user.rank.atLeast(UserRank.TRUSTED_MEMBER if isOwner else UserRank.EDITOR)
# Moderators can delete packages
@@ -507,17 +514,21 @@ class Package(db.Model):
def recalcScore(self):
import datetime
self.score = 0
self.score = 10
if self.forums is None:
return
if self.forums is not None:
topic = ForumTopic.query.get(self.forums)
if topic:
days = (datetime.datetime.now() - topic.created_at).days
months = days / 30
years = days / 365
self.score = topic.views / max(years, 0.0416) + 80*min(max(months, 0.5), 6)
topic = ForumTopic.query.get(self.forums)
if topic:
days = (datetime.datetime.now() - topic.created_at).days
months = days / 30
years = days / 365
self.score = topic.views / years + 80*min(6, months)
if self.getMainScreenshotURL() is None:
self.score *= 0.8
if not self.license.is_foss or not self.media_license.is_foss:
self.score *= 0.1
class MetaPackage(db.Model):
id = db.Column(db.Integer, primary_key=True)
@@ -592,6 +603,7 @@ class PackageRelease(db.Model):
url = db.Column(db.String(200), nullable=False)
approved = db.Column(db.Boolean, nullable=False, default=False)
task_id = db.Column(db.String(37), nullable=True)
commit_hash = db.Column(db.String(41), nullable=True, default=None)
def getEditURL(self):
@@ -600,9 +612,24 @@ class PackageRelease(db.Model):
name=self.package.name,
id=self.id)
def getDownloadURL(self):
return url_for("download_release_page",
author=self.package.author.username,
name=self.package.name,
id=self.id)
def __init__(self):
self.releaseDate = datetime.now()
class PackageReview(db.Model):
id = db.Column(db.Integer, primary_key=True)
package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
thread_id = db.Column(db.Integer, db.ForeignKey("thread.id"), nullable=False)
recommend = db.Column(db.Boolean, nullable=False, default=True)
class PackageScreenshot(db.Model):
id = db.Column(db.Integer, primary_key=True)
package_id = db.Column(db.Integer, db.ForeignKey("package.id"))
@@ -730,6 +757,15 @@ class Thread(db.Model):
watchers = db.relationship("User", secondary=watchers, lazy="subquery", \
backref=db.backref("watching", lazy=True))
def getSubscribeURL(self):
return url_for("thread_subscribe_page",
id=self.id)
def getUnsubscribeURL(self):
return url_for("thread_unsubscribe_page",
id=self.id)
def checkPerm(self, user, perm):
if not user.is_authenticated:
return not self.private
@@ -755,10 +791,6 @@ class ThreadReply(db.Model):
created_at = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
REPO_BLACKLIST = [".zip", "mediafire.com", "dropbox.com", "weebly.com", \
"minetest.net", "dropboxusercontent.com", "4shared.com", \
"digitalaudioconcepts.com", "hg.intevation.org", "www.wtfpl.net", \

View File

@@ -99,6 +99,10 @@ a:hover {
font-size: 100%;
}
select > * {
color: #222;
}
input[type=text], input[type=password], textarea, select, .bulletselector {
text-align: left;
}

View File

@@ -344,9 +344,10 @@ def makeVCSReleaseFromGithub(id, branch, release, url):
if len(commits) == 0 or not "sha" in commits[0]:
raise TaskError("No commits found")
release.url = urlmaker.getCommitDownload(commits[0]["sha"])
release.url = urlmaker.getCommitDownload(commits[0]["sha"])
release.task_id = None
release.commit_hash = commits[0]["sha"]
print(release.url)
release.task_id = None
db.session.commit()
return release.url
@@ -374,9 +375,10 @@ def makeVCSRelease(id, branch):
with open(destPath, "wb") as fp:
repo.archive(fp, format="zip")
release.url = "/uploads/" + filename
release.url = "/uploads/" + filename
release.task_id = None
release.commit_hash = repo.head.object.hexsha
print(release.url)
release.task_id = None
db.session.commit()
return release.url

View File

@@ -0,0 +1,25 @@
{% extends "base.html" %}
{% block title %}
{% if license %}
Edit {{ license.name }}
{% else %}
New license
{% endif %}
{% endblock %}
{% block content %}
<p>
<a href="{{ url_for('license_list_page') }}">Back to list</a> |
<a href="{{ url_for('createedit_license_page') }}">New License</a>
</p>
{% from "macros/forms.html" import render_field, render_submit_field %}
<form method="POST" action="" enctype="multipart/form-data">
{{ form.hidden_tag() }}
{{ render_field(form.name) }}
{{ render_field(form.is_foss) }}
{{ render_submit_field(form.submit) }}
</form>
{% endblock %}

View File

@@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block title %}
Licenses
{% endblock %}
{% block content %}
<p>
<a href="{{ url_for('createedit_license_page') }}">New Tag</a>
</p>
<ul>
{% for l in licenses %}
<li><a href="{{ url_for('createedit_license_page', name=l.name) }}">{{ l.name }}</a> [{{ l.is_foss and "Free" or "Non-free"}}]</li>
{% endfor %}
</ul>
{% endblock %}

View File

@@ -1,13 +1,15 @@
{% extends "base.html" %}
{% block title %}
Admin Tools
Admin
{% endblock %}
{% block content %}
<ul>
<li><a href="db/">Database</a></li>
<li><a href="{{ url_for('user_list_page') }}">User list</a></li>
<li><a href="{{ url_for('tag_list_page') }}">Tag Editor</a></li>
<li><a href="{{ url_for('license_list_page') }}">License Editor</a></li>
<li><a href="{{ url_for('switch_user_page') }}">Sign in as another user</a></li>
</ul>
@@ -17,12 +19,12 @@
<form method="post" action="" class="box-body">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<select name="action">
<option value="importmodlist">Import forum topics</option>
<option value="importscreenshots" selected>Import screenshots from VCS</option>
<option value="importdepends">Import dependencies from downloads</option>
<option value="modprovides">Set provides to mod name</option>
<option value="recalcscores">Recalc package scores</option>
<option value="vcsrelease">Create VCS releases</option>
<option value="importmodlist" selected>Import forum topics</option>
<option value="recalcscores">Recalculate package scores</option>
<!-- <option value="importscreenshots">Import screenshots from VCS</option> -->
<!-- <option value="importdepends">Import dependencies from downloads</option> -->
<!-- <option value="modprovides">Set provides to mod name</option> -->
<!-- <option value="vcsrelease">Create VCS releases</option> -->
</select>
<input type="submit" value="Perform" />
</form>

View File

@@ -65,6 +65,7 @@
{% endif %}
{% if current_user.rank == current_user.rank.MODERATOR %}
<li><a href="{{ url_for('tag_list_page') }}">Tag Editor</a></li>
<li><a href="{{ url_for('license_list_page') }}">License Editor</a></li>
{% endif %}
<li><a href="{{ url_for('user.logout') }}">Sign out</a></li>
</ul>

View File

@@ -5,7 +5,7 @@
{% endblock %}
{% block content %}
<h1>{{ page['title'] }}</h1>
{% if not page["no_h1"] %}<h1>{{ page['title'] }}</h1>{% endif %}
{{ page.html | safe }}
{% endblock %}

View File

@@ -10,7 +10,7 @@
<div class="clearboth"></div>
</div>
<div class="msg">
{{ r.comment }}
{{ r.comment | markdown }}
</div>
</li>
{% endfor %}
@@ -19,7 +19,7 @@
{% if current_user.is_authenticated %}
<form method="post" action="{{ url_for('thread_page', id=thread.id)}}" class="comment_form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<textarea required maxlength=500 name="comment"></textarea><br />
<textarea required maxlength=500 name="comment" placeholder="Markdown supported"></textarea><br />
<input type="submit" value="Comment" />
</form>
{% endif %}
@@ -28,7 +28,7 @@
{% macro render_threadlist(threads) -%}
<ul>
{% for t in threads %}
<li><a href="{{ url_for('thread_page', id=t.id) }}">{{ t.title }}</a> by {{ t.author.display_name }}</li>
<li>{% if t.private %}&#x1f512; {% endif %}<a href="{{ url_for('thread_page', id=t.id) }}">{{ t.title }}</a> by {{ t.author.display_name }}</li>
{% else %}
<li><i>No threads found</i></li>
{% endfor %}

View File

@@ -1,4 +1,4 @@
{% macro render_topictable(topics, show_author=True) -%}
{% macro render_topics_table(topics, show_author=True) -%}
<table>
<tr>
<th>Id</th>
@@ -31,3 +31,22 @@
{% endfor %}
</table>
{% endmacro %}
{% macro render_topics(topics, current_user, show_author=True) -%}
<ul>
{% for topic in topics %}
<li{% if topic.wip %} class="wiptopic"{% endif %}>
<a href="https://forum.minetest.net/viewtopic.php?t={{ topic.topic_id}}">{{ topic.title }}</a>
{% if topic.wip %}[WIP]{% endif %}
{% if topic.name %}[{{ topic.name }}]{% endif %}
{% if show_author %}
by <a href="{{ url_for('user_profile_page', username=topic.author.username) }}">{{ topic.author.display_name }}</a>
{% endif %}
{% if topic.author == current_user or topic.author.checkPerm(current_user, "CHANGE_AUTHOR") %}
| <a href="{{ url_for('create_edit_package_page', author=topic.author.username, repo=topic.getRepoURL(), forums=topic.topic_id, title=topic.title, bname=topic.name) }}">Create</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% endmacro %}

View File

@@ -9,6 +9,7 @@
{% if type %}<input type="hidden" name="type" value="{{ type }}" />{% endif %}
<input type="text" name="q" value="{{ query or ''}}" />
<input type="submit" value="Search" />
<input type="submit" name="lucky" value="I'm feeling lucky" />
<p>
Found {{ packages_count }} packages.
@@ -37,4 +38,12 @@
<li>{{ page }} / {{ page_max }}</li>
{% if next_url %}<li><a href="{{ next_url }}">Next</a></li> {% endif %}
</ul>
{% if topics %}
<h2 style="margin-top:2em;">More content from the forums</h2>
{% from "macros/topics.html" import render_topics %}
{{ render_topics(topics, current_user) }}
{% endif %}
{% endblock %}

View File

@@ -21,8 +21,12 @@
Url: <a href="{{ release.url }}">{{ release.url }}</a><br />
{% endif %}
{% if release.commit_hash %}
Commit Hash: {{ release.commit_hash }}<br />
{% endif %}
{% if release.task_id %}
Importing... <a href="{ url_for('check_task', id=release.task_id, r=release.getEditURL()) }}">view task</a><br />
Importing... <a href="{{ url_for('check_task', id=release.task_id, r=release.getEditURL()) }}">view task</a><br />
{% if package.checkPerm(current_user, "CHANGE_RELEASE_URL") %}
{{ render_field(form.task_id) }}
{% endif %}
@@ -38,23 +42,4 @@
{{ render_submit_field(form.submit) }}
</form>
{% if package.checkPerm(current_user, "APPROVE_RELEASE") %}
<div class="box box_grey">
<h2>Approval Checklist</h2>
<ul>
<li>Link leads to a valid download, ie: is a zip file which
has either init.lua or modpack.txt if a mod, mods/ if a game, or textures if a texture pack.
It's okay if they're inside an immediate folder, like so:
<pre>
example.zip/
└── example
└── init.lua
</pre>
</li>
<li>There's no obfuscated code.</li>
</ul>
</div>
{% endif %}
{% endblock %}

View File

@@ -5,7 +5,9 @@
{% endblock %}
{% block content %}
<img src="{{ screenshot.getThumbnailURL() }}" alt="{{ screenshot.title }}" />
<a href="{{ screenshot.url }}">
<img src="{{ screenshot.getThumbnailURL() }}" alt="{{ screenshot.title }}" />
</a>
{% from "macros/forms.html" import render_field, render_submit_field %}
<form method="POST" action="" enctype="multipart/form-data">

View File

@@ -27,6 +27,9 @@
{% elif topic_error_lvl == "error" %}
Please fix the below topic issue(s).
{% elif "Other" in package.license.name or "Other" in package.media_license.name %}
Please wait for the license to be added to CDB.
{% else %}
{% if package.screenshots.count() == 0 %}
<b>You should add at least one screenshot, but this isn't required.</b><br />
@@ -57,11 +60,13 @@
{% if package.author == current_user or package.checkPerm(current_user, "APPROVE_NEW") %}
{% if review_thread %}
<h2>&#x1f512; {{ review_thread.title }}</h2>
<p><i>
This thread is only visible to the package owner and users of
Editor rank or above.
</i></p>
<h2>{% if review_thread.private %}&#x1f512;{% endif %} {{ review_thread.title }}</h2>
{% if review_thread.private %}
<p><i>
This thread is only visible to the package owner and users of
Editor rank or above.
</i></p>
{% endif %}
{% from "macros/threads.html" import render_thread %}
{{ render_thread(review_thread, current_user) }}
@@ -169,6 +174,9 @@
{% if package.checkPerm(current_user, "MAKE_RELEASE") %}
<li><a href="{{ package.getCreateReleaseURL() }}">Create Release</a></li>
{% endif %}
{% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %}
<li><a href="{{ url_for('new_thread_page', pid=package.id) }}">Open Thread</a></li>
{% endif %}
{% if package.checkPerm(current_user, "DELETE_PACKAGE") %}
<li><a href="{{ package.getDeleteURL() }}">Delete</a></li>
{% endif %}
@@ -188,7 +196,8 @@
<li>
{% if not rel.approved %}<i>{% endif %}
<a href="{{ rel.url }}">{{ rel.title }}</a>,
<a href="{{ rel.getDownloadURL() }}">{{ rel.title }}</a>{% if rel.commit_hash %}
[{{ rel.commit_hash | truncate(5, end='') }}]{% endif %},
created {{ rel.releaseDate | datetime }}.
{% if rel.task_id %}
<a href="{{ url_for('check_task', id=rel.task_id, r=package.getDetailsURL()) }}">Importing...</a>
@@ -312,4 +321,15 @@
{% endfor %}
</ul>
{% endif %}
{% if threads %}
<h3>Threads</h3>
{% if package.approved and package.checkPerm(current_user, "CREATE_THREAD") %}
<p><a href="{{ url_for('new_thread_page', pid=package.id) }}">Open Thread</a></p>
{% endif %}
{% from "macros/threads.html" import render_threadlist %}
{{ render_threadlist(threads) }}
{% endif %}
{% endblock %}

View File

@@ -6,11 +6,24 @@ Threads
{% block content %}
<h1>{% if thread.private %}&#x1f512; {% endif %}{{ thread.title }}</h1>
{% if thread.package or current_user.is_authenticated %}
{% if thread.package %}
<p>Package: <a href="{{ thread.package.getDetailsURL() }}">{{ thread.package.title }}</a></p>
{% endif %}
{% if thread.package %}
<p>
Package: <a href="{{ thread.package.getDetailsURL() }}">{{ thread.package.title }}</a>
</p>
{% if current_user.is_authenticated %}
{% if current_user in thread.watchers %}
<form method="post" action="{{ thread.getUnsubscribeURL() }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" value="Unsubscribe" />
</form>
{% else %}
<form method="post" action="{{ thread.getSubscribeURL() }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}" />
<input type="submit" value="Subscribe" />
</form>
{% endif %}
{% endif %}
{% endif %}
{% if thread.private %}

View File

@@ -12,6 +12,6 @@ Topics to be Added
{{ topics | count }} remaining.
</p>
{% from "macros/topictable.html" import render_topictable %}
{{ render_topictable(topics) }}
{% from "macros/topics.html" import render_topics_table %}
{{ render_topics_table(topics) }}
{% endblock %}

View File

@@ -107,8 +107,8 @@
List of your forum topics which do not have a matching package.
</p>
{% from "macros/topictable.html" import render_topictable %}
{{ render_topictable(topics_to_add, show_author=False) }}
{% from "macros/topics.html" import render_topics_table %}
{{ render_topics_table(topics_to_add, show_author=False) }}
</div>
</div>
{% endif %}

View File

@@ -50,6 +50,25 @@ def doFileUpload(file, allowedExtensions, fileTypeName):
file.save(os.path.join("app/public/uploads", filename))
return "/uploads/" + filename
def make_flask_user_password(plaintext_str):
# http://passlib.readthedocs.io/en/stable/modular_crypt_format.html
# http://passlib.readthedocs.io/en/stable/lib/passlib.hash.bcrypt.html#format-algorithm
# Flask_User stores passwords in the Modular Crypt Format.
# https://github.com/lingthio/Flask-User/blob/master/flask_user/user_manager__settings.py#L166
# Note that Flask_User allows customizing password algorithms.
# USER_PASSLIB_CRYPTCONTEXT_SCHEMES defaults to bcrypt but if
# default changes or is customized, the code below needs adapting.
# Individual password values will look like:
# $2b$12$.az4S999Ztvy/wa3UdQvMOpcki1Qn6VYPXmEFMIdWQyYs7ULnH.JW
# $XX$RR$SSSSSSSSSSSSSSSSSSSSSSHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH
# $XX : Selects algorithm (2b is bcrypt).
# $RR : Selects bcrypt key expansion rounds (12 is 2**12 rounds).
# $SSS... : 22 chars of (random, per-password) salt
# HHH... : 31 remaining chars of password hash (note no dollar sign)
import bcrypt
plaintext = plaintext_str.encode("UTF-8")
password = bcrypt.hashpw(plaintext, bcrypt.gensalt())
return password.decode("UTF-8")
def _do_login_user(user, remember_me=False):
def _call_or_get(v):

View File

@@ -53,7 +53,8 @@ def home_page():
return render_template("index.html", new=new, popular=popular, count=count)
from . import users, githublogin, packages, meta, threads, api
from . import sass, tasks, admin, notifications, tagseditor, thumbnails
from . import tasks, admin, notifications, tagseditor, licenseseditor
from . import sass, thumbnails
@menu.register_menu(app, ".help", "Help", order=19, endpoint_arguments_constructor=lambda: { 'path': 'help' })
@app.route('/<path:path>/')

View File

@@ -27,6 +27,37 @@ from flask_wtf import FlaskForm
from wtforms import *
from app.utils import loginUser, rank_required, triggerNotif
import datetime
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
class MyModelView(ModelView):
def is_accessible(self):
return current_user.is_authenticated and current_user.rank.atLeast(UserRank.ADMIN)
def inaccessible_callback(self, name, **kwargs):
# redirect to login page if user doesn't have access
if current_user.is_authenticated:
abort(403)
else:
return redirect(url_for('user.login', next=request.url))
admin = Admin(app, name='ContentDB', template_mode='bootstrap3', url="/admin/db")
admin.add_view(MyModelView(User, db.session))
admin.add_view(MyModelView(Package, db.session))
admin.add_view(MyModelView(Dependency, db.session))
admin.add_view(MyModelView(EditRequest, db.session))
admin.add_view(MyModelView(EditRequestChange, db.session))
admin.add_view(MyModelView(ForumTopic, db.session))
admin.add_view(MyModelView(License, db.session))
admin.add_view(MyModelView(MetaPackage, db.session))
admin.add_view(MyModelView(Notification, db.session))
admin.add_view(MyModelView(PackageRelease, db.session))
admin.add_view(MyModelView(PackageScreenshot, db.session))
admin.add_view(MyModelView(Tag, db.session))
admin.add_view(MyModelView(Thread, db.session))
admin.add_view(MyModelView(ThreadReply, db.session))
admin.add_view(MyModelView(UserEmailVerification, db.session))
@app.route("/admin/", methods=["GET", "POST"])
@rank_required(UserRank.ADMIN)
@@ -79,8 +110,8 @@ def admin_page():
rel = PackageRelease()
rel.package = package
rel.title = datetime.date.today().isoformat()
rel.url = ""
rel.title = datetime.date.today().isoformat()
rel.url = ""
rel.task_id = uuid()
rel.approved = True
db.session.add(rel)
@@ -98,6 +129,7 @@ def admin_page():
deleted_packages = Package.query.filter_by(soft_deleted=True).all()
return render_template("admin/list.html", deleted_packages=deleted_packages)
class SwitchUserForm(FlaskForm):
username = StringField("Username")
submit = SubmitField("Switch")

View File

@@ -20,11 +20,13 @@ from flask_user import *
from app import app
from app.models import *
from app.utils import is_package_page
from .packages import build_packages_query
from .packages import QueryBuilder
@app.route("/api/packages/")
def api_packages_page():
query, _ = build_packages_query()
qb = QueryBuilder()
query = qb.buildPackageQuery()
pkgs = [package.getAsDictionaryShort(app.config["BASE_URL"]) \
for package in query.all() if package.getDownloadRelease() is not None]
return jsonify(pkgs)

View File

@@ -51,9 +51,9 @@ def github_authorized(oauth_token):
if current_user and current_user.is_authenticated:
if userByGithub is None:
current_user.github_username = username
db.session.add(auth)
db.session.commit()
return redirect(url_for("gitAccount", id=auth.id))
flash("Linked github to account", "success")
return redirect(url_for("home_page"))
else:
flash("Github account is already associated with another user", "danger")
return redirect(url_for("home_page"))

View File

@@ -0,0 +1,62 @@
# Content DB
# Copyright (C) 2018 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 app import app
from app.models import *
from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
from app.utils import rank_required
@app.route("/licenses/")
@rank_required(UserRank.MODERATOR)
def license_list_page():
return render_template("admin/licenses/list.html", licenses=License.query.order_by(db.asc(License.name)).all())
class LicenseForm(FlaskForm):
name = StringField("Name", [InputRequired(), Length(3,100)])
is_foss = BooleanField("Is FOSS")
submit = SubmitField("Save")
@app.route("/licenses/new/", methods=["GET", "POST"])
@app.route("/licenses/<name>/edit/", methods=["GET", "POST"])
@rank_required(UserRank.MODERATOR)
def createedit_license_page(name=None):
license = None
if name is not None:
license = License.query.filter_by(name=name).first()
if license is None:
abort(404)
form = LicenseForm(formdata=request.form, obj=license)
if request.method == "GET" and license is None:
form.is_foss.data = True
elif request.method == "POST" and form.validate():
if license is None:
license = License(form.name.data)
db.session.add(license)
flash("Created license " + form.name.data, "success")
else:
flash("Updated license " + form.name.data, "success")
form.populate_obj(license)
db.session.commit()
return redirect(url_for("license_list_page"))
return render_template("admin/licenses/edit.html", license=license, form=form)

View File

@@ -29,27 +29,59 @@ from flask_wtf import FlaskForm
from wtforms import *
from wtforms.validators import *
from wtforms.ext.sqlalchemy.fields import QuerySelectField, QuerySelectMultipleField
from sqlalchemy import or_, any_
def build_packages_query():
type_name = request.args.get("type")
type = None
if type_name is not None:
type = PackageType[type_name.upper()]
title = "Packages"
query = Package.query.filter_by(soft_deleted=False, approved=True)
class QueryBuilder:
title = None
types = None
search = None
if type is not None:
title = type.value + "s"
query = query.filter_by(type=type)
def __init__(self):
title = "Packages"
search = request.args.get("q")
if search is not None and search.strip() != "":
query = query.filter(Package.title.ilike('%' + search + '%'))
# Get request types
types = request.args.getlist("type")
types = [PackageType.get(tname) for tname in types]
types = [type for type in types if type is not None]
if len(types) > 0:
title = ", ".join([type.value + "s" for type in types])
query = query.order_by(db.desc(Package.score))
self.title = title
self.types = types
self.search = request.args.get("q")
self.lucky = "lucky" in request.args
self.limit = 1 if self.lucky else None
return query, title
def buildPackageQuery(self):
query = Package.query.filter_by(soft_deleted=False, approved=True)
if len(self.types) > 0:
query = query.filter(Package.type.in_(self.types))
if self.search is not None and self.search.strip() != "":
query = query.filter(Package.title.ilike('%' + self.search + '%'))
query = query.order_by(db.desc(Package.score))
if self.limit:
query = query.limit(self.limit)
return query
def buildTopicQuery(self):
topics = ForumTopic.query \
.filter(~ db.exists().where(Package.forums==ForumTopic.topic_id)) \
.order_by(db.asc(ForumTopic.wip), db.asc(ForumTopic.name), db.asc(ForumTopic.title)) \
.filter(ForumTopic.title.ilike('%' + self.search + '%'))
if len(self.types) > 0:
topics = topics.filter(ForumTopic.type.in_(self.types))
if self.limit:
topics = topics.limit(self.limit)
return topics
@menu.register_menu(app, ".mods", "Mods", order=11, endpoint_arguments_constructor=lambda: { 'type': 'mod' })
@menu.register_menu(app, ".games", "Games", order=12, endpoint_arguments_constructor=lambda: { 'type': 'game' })
@@ -59,7 +91,19 @@ def packages_page():
if shouldReturnJson():
return redirect(url_for("api_packages_page"))
query, title = build_packages_query()
qb = QueryBuilder()
query = qb.buildPackageQuery()
title = qb.title
if qb.lucky:
package = query.first()
if package:
return redirect(package.getDetailsURL())
topic = qb.buildTopicQuery().first()
if topic:
return redirect("https://forum.minetest.net/viewtopic.php?t=" + str(topic.topic_id))
page = int(request.args.get("page") or 1)
num = min(42, int(request.args.get("n") or 100))
query = query.paginate(page, num, True)
@@ -72,8 +116,13 @@ def packages_page():
prev_url = url_for("packages_page", type=type_name, q=search, page=query.prev_num) \
if query.has_prev else None
topics = None
if qb.search and not query.has_next:
topics = qb.buildTopicQuery().all()
tags = Tag.query.all()
return render_template("packages/list.html", title=title, packages=query.items, \
return render_template("packages/list.html", \
title=title, packages=query.items, topics=topics, \
query=search, tags=tags, type=type_name, \
next_url=next_url, prev_url=prev_url, page=page, page_max=query.pages, packages_count=query.total)
@@ -112,7 +161,7 @@ def package_page(package):
releases = getReleases(package)
requests = [r for r in package.requests if r.status == 0]
review_thread = Thread.query.filter_by(package_id=package.id, private=True).first()
review_thread = package.review_thread
if review_thread is not None and not review_thread.checkPerm(current_user, Permission.SEE_THREAD):
review_thread = None
@@ -137,10 +186,19 @@ def package_page(package):
topic_error = "<br />".join(errors)
threads = Thread.query.filter_by(package_id=package.id)
if not current_user.is_authenticated:
threads = threads.filter_by(private=False)
elif not current_user.rank.atLeast(UserRank.EDITOR) and not current_user == package.author:
threads = threads.filter(or_(Thread.private == False, Thread.author == current_user))
return render_template("packages/view.html", \
package=package, releases=releases, requests=requests, \
alternatives=alternatives, similar_topics=similar_topics, \
review_thread=review_thread, topic_error=topic_error, topic_error_lvl=topic_error_lvl)
review_thread=review_thread, topic_error=topic_error, topic_error_lvl=topic_error_lvl, \
threads=threads.all())
@app.route("/packages/<author>/<name>/download/")
@@ -165,8 +223,8 @@ class PackageForm(FlaskForm):
shortDesc = StringField("Short Description (Plaintext)", [InputRequired(), Length(1,200)])
desc = TextAreaField("Long Description (Markdown)", [Optional(), Length(0,10000)])
type = SelectField("Type", [InputRequired()], choices=PackageType.choices(), coerce=PackageType.coerce, default=PackageType.MOD)
license = QuerySelectField("License", [InputRequired()], query_factory=lambda: License.query, get_pk=lambda a: a.id, get_label=lambda a: a.name)
media_license = QuerySelectField("Media License", [InputRequired()], query_factory=lambda: License.query, get_pk=lambda a: a.id, get_label=lambda a: a.name)
license = QuerySelectField("License", [InputRequired()], 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", [InputRequired()], 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(), Length(0,1000)])
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(), Length(0,1000)])
@@ -267,11 +325,15 @@ def create_edit_package_page(author=None, name=None):
db.session.commit() # save
next_url = package.getDetailsURL()
if wasNew and package.repo is not None:
task = importRepoScreenshot.delay(package.id)
return redirect(url_for("check_task", id=task.id, r=package.getDetailsURL()))
next_url = url_for("check_task", id=task.id, r=next_url)
return redirect(package.getDetailsURL())
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)
return redirect(next_url)
package_query = Package.query.filter_by(approved=True, soft_deleted=False)
if package is not None:

View File

@@ -91,6 +91,23 @@ def create_release_page(package):
return render_template("packages/release_new.html", package=package, form=form)
@app.route("/packages/<author>/<name>/releases/<id>/download/")
@is_package_page
def download_release_page(package, id):
release = PackageRelease.query.get(id)
if release is None or release.package != package:
abort(404)
if release is None:
if "application/zip" in request.accept_mimetypes and \
not "text/html" in request.accept_mimetypes:
return "", 204
else:
flash("No download available.", "error")
return redirect(package.getDetailsURL())
else:
return redirect(release.url, code=300)
@app.route("/packages/<author>/<name>/releases/<id>/", methods=["GET", "POST"])
@login_required
@is_package_page

View File

@@ -54,7 +54,7 @@ def create_screenshot_page(package, id=None):
if uploadedPath is not None:
ss = PackageScreenshot()
ss.package = package
ss.title = form["title"].data
ss.title = form["title"].data or "Untitled"
ss.url = uploadedPath
db.session.add(ss)
@@ -91,7 +91,7 @@ def edit_screenshot_page(package, id):
wasApproved = screenshot.approved
if canEdit:
screenshot.title = form["title"].data
screenshot.title = form["title"].data or "Untitled"
if canApprove:
screenshot.approved = form["approved"].data

View File

@@ -27,7 +27,7 @@ from app.utils import rank_required
@app.route("/tags/")
@rank_required(UserRank.MODERATOR)
def tag_list_page():
return render_template("tags/list.html", tags=Tag.query.order_by(db.asc(Tag.title)).all())
return render_template("admin/tags/list.html", tags=Tag.query.order_by(db.asc(Tag.title)).all())
class TagForm(FlaskForm):
title = StringField("Title", [InputRequired(), Length(3,100)])
@@ -54,4 +54,4 @@ def createedit_tag_page(name=None):
db.session.commit()
return redirect(url_for("createedit_tag_page", name=tag.name))
return render_template("tags/edit.html", tag=tag, form=form)
return render_template("admin/tags/edit.html", tag=tag, form=form)

View File

@@ -32,6 +32,41 @@ def threads_page():
query = query.filter_by(private=False)
return render_template("threads/list.html", threads=query.all())
@app.route("/threads/<int:id>/subscribe/", methods=["POST"])
@login_required
def thread_subscribe_page(id):
thread = Thread.query.get(id)
if thread is None or not thread.checkPerm(current_user, Permission.SEE_THREAD):
abort(404)
if current_user in thread.watchers:
flash("Already subscribed!", "success")
else:
flash("Subscribed to thread", "success")
thread.watchers.append(current_user)
db.session.commit()
return redirect(url_for("thread_page", id=id))
@app.route("/threads/<int:id>/unsubscribe/", methods=["POST"])
@login_required
def thread_unsubscribe_page(id):
thread = Thread.query.get(id)
if thread is None or not thread.checkPerm(current_user, Permission.SEE_THREAD):
abort(404)
if current_user in thread.watchers:
flash("Unsubscribed!", "success")
thread.watchers.remove(current_user)
db.session.commit()
else:
flash("Not subscribed to thread", "success")
return redirect(url_for("thread_page", id=id))
@app.route("/threads/<int:id>/", methods=["GET", "POST"])
def thread_page(id):
clearNotifications(url_for("thread_page", id=id))
@@ -92,7 +127,7 @@ def new_thread_page():
flash("Unable to find that package!", "error")
# Don't allow making threads on approved packages for now
if package is None or package.approved:
if package is None:
abort(403)
def_is_private = request.args.get("private") or False
@@ -102,8 +137,7 @@ def new_thread_page():
is_review_thread = package is not None and not package.approved
# Check that user can make the thread
if is_review_thread and not (package.author == current_user or \
package.checkPerm(current_user, Permission.APPROVE_NEW)):
if not package.checkPerm(current_user, Permission.CREATE_THREAD):
flash("Unable to create thread!", "error")
return redirect(url_for("home_page"))

View File

@@ -18,7 +18,7 @@
from flask import *
from app import app
import glob, os
import os
from PIL import Image
ALLOWED_RESOLUTIONS=[(350,233)]
@@ -29,6 +29,33 @@ def mkdir(path):
mkdir("app/public/thumbnails/")
def resize_and_crop(img_path, modified_path, size):
img = Image.open(img_path)
# Get current and desired ratio for the images
img_ratio = img.size[0] / float(img.size[1])
ratio = size[0] / float(size[1])
# Is more portrait than target, scale and crop
if ratio > img_ratio:
img = img.resize((int(size[0]), int(size[0] * img.size[1] / img.size[0])),
Image.BICUBIC)
box = (0, (img.size[1] - size[1]) / 2, img.size[0], (img.size[1] + size[1]) / 2)
img = img.crop(box)
# Is more landscape than target, scale and crop
elif ratio < img_ratio:
img = img.resize((int(size[1] * img.size[0] / img.size[1]), int(size[1])),
Image.BICUBIC)
box = ((img.size[0] - size[0]) / 2, 0, (img.size[0] + size[0]) / 2, img.size[1])
img = img.crop(box)
# Is exactly the same ratio as target
else:
img = img.resize(size, Image.BICUBIC)
img.save(modified_path)
@app.route("/thumbnails/<img>")
@app.route("/thumbnails/<int:w>x<int:h>/<img>")
def make_thumbnail(img, w=350, h=233):
@@ -40,7 +67,5 @@ def make_thumbnail(img, w=350, h=233):
cache_filepath = "public/thumbnails/{}x{}/{}".format(w, h, img)
source_filepath = "public/uploads/" + img
im = Image.open("app/" + source_filepath)
im.thumbnail((w, h), Image.ANTIALIAS)
im.save("app/" + cache_filepath, optimize=True)
resize_and_crop("app/" + source_filepath, "app/" + cache_filepath, (w, h))
return send_file(cache_filepath)

View File

@@ -0,0 +1,28 @@
"""empty message
Revision ID: 44e138485931
Revises: 9e2ac631efb0
Create Date: 2018-07-28 14:45:28.879331
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '44e138485931'
down_revision = '9e2ac631efb0'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('package_release', sa.Column('commit_hash', sa.String(length=41), nullable=True, server_default=None))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('package_release', 'commit_hash')
# ### end Alembic commands ###

View File

@@ -1,4 +1,4 @@
Flask>=0.12.2
Flask>=0.12.2,<1.0
Flask-SQLAlchemy>=2.3
Flask-Login>=0.4.1
Flask-User==0.6.19
@@ -14,3 +14,4 @@ Flask-FlatPages==0.6
Flask-Migrate==2.1.1
pillow==5.1.0
GitPython==2.1.10
flask-admin==1.5.1

View File

@@ -23,6 +23,7 @@ if not "FLASK_CONFIG" in os.environ:
test_data = len(sys.argv) >= 2 and sys.argv[1].strip() == "-t"
from app.models import *
from app.utils import make_flask_user_password
def defineDummyData(licenses, tags, ruben):
ez = User("Shara")
@@ -342,6 +343,8 @@ db.create_all()
print("Filling database...")
ruben = User("rubenwardy")
ruben.active = True
ruben.password = make_flask_user_password("tuckfrump")
ruben.github_username = "rubenwardy"
ruben.forums_username = "rubenwardy"
ruben.rank = UserRank.ADMIN
@@ -359,12 +362,12 @@ for tag in ["Inventory", "Mapgen", "Building", \
licenses = {}
for license in ["GPLv2.1", "GPLv3", "LGPLv2.1", "LGPLv3", "AGPLv2.1", "AGPLv3",
"Apache", "BSD 3-Clause", "BSD 2-Clause", "CC0", "CC-BY-SA",
"CC-BY", "MIT", "ZLib"]:
"CC-BY", "MIT", "ZLib", "Other (Free)"]:
row = License(license)
licenses[row.name] = row
db.session.add(row)
for license in ["CC-BY-NC-SA"]:
for license in ["CC-BY-NC-SA", "Other (Non-free)"]:
row = License(license, False)
licenses[row.name] = row
db.session.add(row)