diff --git a/swh/web/add_forge_now/__init__.py b/swh/web/add_forge_now/__init__.py new file mode 100644 diff --git a/swh/web/add_forge_now/apps.py b/swh/web/add_forge_now/apps.py new file mode 100644 --- /dev/null +++ b/swh/web/add_forge_now/apps.py @@ -0,0 +1,10 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from django.apps import AppConfig + + +class AddForgeNowConfig(AppConfig): + name = "add_forge_now" diff --git a/swh/web/add_forge_now/migrations/0001_initial.py b/swh/web/add_forge_now/migrations/0001_initial.py new file mode 100644 --- /dev/null +++ b/swh/web/add_forge_now/migrations/0001_initial.py @@ -0,0 +1,109 @@ +# Generated by Django 2.2.24 on 2022-03-08 10:25 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] # type: ignore + + operations = [ + migrations.CreateModel( + name="Request", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "status", + models.TextField( + choices=[ + ("PENDING", "Pending"), + ("WAITING_FOR_FEEDBACK", "Waiting for feedback"), + ("FEEDBACK_TO_HANDLE", "Feedback to handle"), + ("ACCEPTED", "Accepted"), + ("SCHEDULED", "Scheduled"), + ("FIRST_LISTING_DONE", "First listing done"), + ("FIRST_ORIGIN_LOADED", "First origin loaded"), + ("REJECTED", "Rejected"), + ("SUSPENDED", "Suspended"), + ("DENIED", "Denied"), + ], + default="PENDING", + ), + ), + ("submission_date", models.DateTimeField(auto_now_add=True)), + ("submitter_name", models.TextField()), + ("submitter_email", models.TextField()), + ("forge_type", models.TextField()), + ("forge_url", models.TextField()), + ("forge_contact_email", models.EmailField(max_length=254)), + ("forge_contact_name", models.TextField()), + ( + "forge_contact_comment", + models.TextField( + help_text=( + "Where did you find this contact information (url, ...)" + ) + ), + ), + ], + ), + migrations.CreateModel( + name="RequestHistory", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("text", models.TextField()), + ("actor", models.TextField()), + ( + "actor_role", + models.TextField( + choices=[("MODERATOR", "moderator"), ("SUBMITTER", "submitter")] + ), + ), + ("date", models.DateTimeField(auto_now_add=True)), + ( + "new_status", + models.TextField( + choices=[ + ("PENDING", "Pending"), + ("WAITING_FOR_FEEDBACK", "Waiting for feedback"), + ("FEEDBACK_TO_HANDLE", "Feedback to handle"), + ("ACCEPTED", "Accepted"), + ("SCHEDULED", "Scheduled"), + ("FIRST_LISTING_DONE", "First listing done"), + ("FIRST_ORIGIN_LOADED", "First origin loaded"), + ("REJECTED", "Rejected"), + ("SUSPENDED", "Suspended"), + ("DENIED", "Denied"), + ], + null=True, + ), + ), + ( + "request", + models.ForeignKey( + on_delete=django.db.models.deletion.DO_NOTHING, + to="add_forge_now.Request", + ), + ), + ], + ), + ] diff --git a/swh/web/add_forge_now/migrations/__init__.py b/swh/web/add_forge_now/migrations/__init__.py new file mode 100644 diff --git a/swh/web/add_forge_now/models.py b/swh/web/add_forge_now/models.py new file mode 100644 --- /dev/null +++ b/swh/web/add_forge_now/models.py @@ -0,0 +1,71 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU Affero General Public License version 3, or any later version +# See top-level LICENSE file for more information + +import enum + +from django.db import models + + +class RequestStatus(enum.Enum): + """Request statuses. + + Values are used in the ui. + + """ + + PENDING = "Pending" + WAITING_FOR_FEEDBACK = "Waiting for feedback" + FEEDBACK_TO_HANDLE = "Feedback to handle" + ACCEPTED = "Accepted" + SCHEDULED = "Scheduled" + FIRST_LISTING_DONE = "First listing done" + FIRST_ORIGIN_LOADED = "First origin loaded" + REJECTED = "Rejected" + SUSPENDED = "Suspended" + DENIED = "Denied" + + @classmethod + def choices(cls): + return tuple((variant.name, variant.value) for variant in cls) + + +class RequestActorRole(enum.Enum): + MODERATOR = "moderator" + SUBMITTER = "submitter" + + @classmethod + def choices(cls): + return tuple((variant.name, variant.value) for variant in cls) + + +class RequestHistory(models.Model): + """Comment or status change. This is commented or changed by either submitter or + moderator. + + """ + + request = models.ForeignKey("Request", models.DO_NOTHING) + text = models.TextField() + actor = models.TextField() + actor_role = models.TextField(choices=RequestActorRole.choices()) + date = models.DateTimeField(auto_now_add=True) + new_status = models.TextField(choices=RequestStatus.choices(), null=True) + + +class Request(models.Model): + status = models.TextField( + choices=RequestStatus.choices(), default=RequestStatus.PENDING.name, + ) + submission_date = models.DateTimeField(auto_now_add=True) + submitter_name = models.TextField() + submitter_email = models.TextField() + # FIXME: shall we do create a user model inside the webapp instead? + forge_type = models.TextField() + forge_url = models.TextField() + forge_contact_email = models.EmailField() + forge_contact_name = models.TextField() + forge_contact_comment = models.TextField( + help_text="Where did you find this contact information (url, ...)", + ) diff --git a/swh/web/add_forge_now/tests/test_migration.py b/swh/web/add_forge_now/tests/test_migration.py new file mode 100644 --- /dev/null +++ b/swh/web/add_forge_now/tests/test_migration.py @@ -0,0 +1,62 @@ +# Copyright (C) 2022 The Software Heritage developers +# See the AUTHORS file at the top-level directory of this distribution +# License: GNU General Public License version 3, or any later version +# See top-level LICENSE file for more information + +from datetime import datetime, timezone + +APP_NAME = "add_forge_now" + +MIGRATION_0001 = "0001_initial" + + +def now() -> datetime: + return datetime.now(tz=timezone.utc) + + +def test_add_forge_now_initial_migration(migrator): + """Basic migration test to check the model is fine""" + + state = migrator.apply_tested_migration((APP_NAME, MIGRATION_0001)) + request = state.apps.get_model(APP_NAME, "Request") + request_history = state.apps.get_model(APP_NAME, "RequestHistory") + + from swh.web.add_forge_now.models import RequestActorRole, RequestStatus + + date_now = now() + + req = request( + status=RequestStatus.PENDING, + submitter_name="dudess", + submitter_email="dudess@orga.org", + forge_type="cgit", + forge_url="https://example.org/forge", + forge_contact_email="forge@//example.org", + forge_contact_name="forge", + forge_contact_comment=( + "Discovered on the main forge homepag, following contact link." + ), + ) + req.save() + + assert req.submission_date > date_now + + req_history = request_history( + request=req, + text="some comment from the moderator", + actor="moderator", + actor_role=RequestActorRole.MODERATOR, + new_status=None, + ) + req_history.save() + assert req_history.date > req.submission_date + + req_history2 = request_history( + request=req, + text="some answer from the user", + actor="user", + actor_role=RequestActorRole.SUBMITTER, + new_status=None, + ) + req_history2.save() + assert req_history2.date > req_history.date diff --git a/swh/web/settings/common.py b/swh/web/settings/common.py --- a/swh/web/settings/common.py +++ b/swh/web/settings/common.py @@ -46,6 +46,7 @@ "swh.web.api", "swh.web.auth", "swh.web.browse", + "swh.web.add_forge_now", "webpack_loader", "django_js_reverse", "corsheaders",