From aae546a08ec0f6530435eb006e51ee0bed2b27ef Mon Sep 17 00:00:00 2001 From: rubenwardy Date: Sat, 31 Jul 2021 21:03:45 +0100 Subject: [PATCH] Import licenses from SPDX Fixes #326 --- app/blueprints/admin/actions.py | 48 ++++++++++++++++++++++++++ app/blueprints/admin/licenseseditor.py | 4 ++- app/blueprints/users/account.py | 2 +- app/models/packages.py | 4 ++- app/templates/admin/licenses/edit.html | 5 +-- app/templates/packages/view.html | 16 ++++++--- migrations/versions/725ff70ea316_.py | 28 +++++++++++++++ 7 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 migrations/versions/725ff70ea316_.py diff --git a/app/blueprints/admin/actions.py b/app/blueprints/admin/actions.py index 52e0573..fb53f77 100644 --- a/app/blueprints/admin/actions.py +++ b/app/blueprints/admin/actions.py @@ -18,6 +18,7 @@ import os from typing import List +import requests from celery import group from flask import * from sqlalchemy import or_ @@ -232,3 +233,50 @@ def remind_outdated(): url_for('todo.view_user', username=user.username)) db.session.commit() + +@action("Import licenses from SPDX") +def import_licenses(): + renames = { + "GPLv2" : "GPL-2.0-only", + "GPLv3" : "GPL-3.0-only", + "AGPLv2" : "AGPL-2.0-only", + "AGPLv3" : "AGPL-3.0-only", + "LGPLv2.1" : "LGPL-2.1-only", + "LGPLv3" : "LGPL-3.0-only", + "Apache 2.0" : "Apache-2.0", + "BSD 2-Clause / FreeBSD": "BSD-2-Clause-FreeBSD", + "BSD 3-Clause" : "BSD-3-Clause", + "CC0": "CC0-1.0", + "CC BY 3.0": "CC-BY-3.0", + "CC BY 4.0": "CC-BY-4.0", + "CC BY-NC-SA 3.0": "CC-BY-NC-SA-3.0", + "CC BY-SA 3.0": "CC-BY-SA-3.0", + "CC BY-SA 4.0": "CC-BY-SA-4.0", + "NPOSLv3": "NPOSL-3.0", + "MPL 2.0": "MPL-2.0", + "EUPLv1.2": "EUPL-1.2", + "SIL Open Font License v1.1": "OFL-1.1", + } + + for old_name, new_name in renames.items(): + License.query.filter_by(name=old_name).update({ "name": new_name }) + + r = requests.get( + "https://raw.githubusercontent.com/spdx/license-list-data/master/json/licenses.json") + licenses = r.json()["licenses"] + + existing_licenses = {} + for license in License.query.all(): + assert license.name not in renames.keys() + existing_licenses[license.name.lower()] = license + + for license in licenses: + obj = existing_licenses.get(license["licenseId"].lower()) + if obj: + obj.url = license["reference"] + elif license.get("isOsiApproved") and license.get("isFsfLibre") and \ + not license["isDeprecatedLicenseId"]: + obj = License(license["licenseId"], True, license["reference"]) + db.session.add(obj) + + db.session.commit() diff --git a/app/blueprints/admin/licenseseditor.py b/app/blueprints/admin/licenseseditor.py index 695c258..40ef3c6 100644 --- a/app/blueprints/admin/licenseseditor.py +++ b/app/blueprints/admin/licenseseditor.py @@ -18,10 +18,11 @@ from flask import * from flask_wtf import FlaskForm from wtforms import * +from wtforms.fields.html5 import URLField from wtforms.validators import * from app.models import * -from app.utils import rank_required +from app.utils import rank_required, nonEmptyOrNone from . import bp @@ -33,6 +34,7 @@ def license_list(): class LicenseForm(FlaskForm): name = StringField("Name", [InputRequired(), Length(3,100)]) is_foss = BooleanField("Is FOSS") + url = URLField("URL", [Optional], filters=[nonEmptyOrNone]) submit = SubmitField("Save") @bp.route("/licenses/new/", methods=["GET", "POST"]) diff --git a/app/blueprints/users/account.py b/app/blueprints/users/account.py index f6d830a..256b4c6 100644 --- a/app/blueprints/users/account.py +++ b/app/blueprints/users/account.py @@ -98,7 +98,7 @@ def logout(): class RegisterForm(FlaskForm): - display_name = StringField("Display Name", [Optional(), Length(1, 20)], filters=[lambda x: nonEmptyOrNone(x)]) + display_name = StringField("Display Name", [Optional(), Length(1, 20)], filters=[nonEmptyOrNone]) username = StringField("Username", [InputRequired(), Regexp("^[a-zA-Z0-9._-]+$", message="Only a-zA-Z0-9._ allowed")]) email = StringField("Email", [InputRequired(), Email()]) diff --git a/app/models/packages.py b/app/models/packages.py index ea31559..3354c94 100644 --- a/app/models/packages.py +++ b/app/models/packages.py @@ -35,10 +35,12 @@ class License(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50), nullable=False, unique=True) is_foss = db.Column(db.Boolean, nullable=False, default=True) + url = db.Column(db.String(128), nullable=True, default=None) - def __init__(self, v, is_foss=True): + def __init__(self, v: str, is_foss: bool = True, url: str = None): self.name = v self.is_foss = is_foss + self.url = url def __str__(self): return self.name diff --git a/app/templates/admin/licenses/edit.html b/app/templates/admin/licenses/edit.html index e488c56..056145f 100644 --- a/app/templates/admin/licenses/edit.html +++ b/app/templates/admin/licenses/edit.html @@ -12,12 +12,13 @@ New License Back to list - {% from "macros/forms.html" import render_field, render_submit_field %} + {% from "macros/forms.html" import render_field, render_checkbox_field, render_submit_field %}
{{ form.hidden_tag() }} {{ render_field(form.name) }} - {{ render_field(form.is_foss) }} + {{ render_checkbox_field(form.is_foss) }} + {{ render_field(form.url) }} {{ render_submit_field(form.submit) }}
{% endblock %} diff --git a/app/templates/packages/view.html b/app/templates/packages/view.html index 33c891b..ea01742 100644 --- a/app/templates/packages/view.html +++ b/app/templates/packages/view.html @@ -16,6 +16,14 @@ {% endif %} {% endblock %} +{% macro render_license(license) %} + {% if license.url %} + {{ license.name }} + {% else %} + {{ license.name }} + {% endif %} +{% endmacro %} + {% block container %} {% if not package.license.is_foss and not package.media_license.is_foss and package.type != package.type.TXP %} {% set package_warning="Non-free code and media" %} @@ -363,12 +371,12 @@
{{ _("License") }}
{% if package.license == package.media_license %} - {{ package.license.name }} + {{ render_license(package.license) }} {% elif package.type == package.type.TXP %} - {{ package.media_license.name }} + {{ render_license(package.media_license) }} {% else %} - {{ package.license.name }} for code,
- {{ package.media_license.name }} for media. + {{ render_license(package.license) }} for code,
+ {{ render_license(package.media_license) }} for media. {% endif %}
Added
diff --git a/migrations/versions/725ff70ea316_.py b/migrations/versions/725ff70ea316_.py new file mode 100644 index 0000000..ac73932 --- /dev/null +++ b/migrations/versions/725ff70ea316_.py @@ -0,0 +1,28 @@ +"""empty message + +Revision ID: 725ff70ea316 +Revises: 51be0401bb85 +Create Date: 2021-07-31 19:10:36.683434 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = '725ff70ea316' +down_revision = '51be0401bb85' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('license', sa.Column('url', sa.String(length=128), nullable=True, default=None)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('license', 'url') + # ### end Alembic commands ###