# Copyright (C) 2017-2024 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
# Generated from:
# cd swh_deposit && \
#    python3 -m manage inspectdb
import datetime
from typing import Optional
from django.contrib.auth.models import User, UserManager
from django.contrib.postgres.fields import ArrayField
from django.db import models
from django.utils.timezone import now
try:
    from django.db.models import JSONField as OrigJSONField
except ImportError:
    from django.contrib.postgres.fields import JSONField as OrigJSONField
from swh.auth.django.models import OIDCUser
from swh.deposit.config import (
    ARCHIVE_TYPE,
    DEPOSIT_STATUS_DEPOSITED,
    DEPOSIT_STATUS_LOAD_FAILURE,
    DEPOSIT_STATUS_LOAD_SUCCESS,
    DEPOSIT_STATUS_PARTIAL,
    DEPOSIT_STATUS_REJECTED,
    DEPOSIT_STATUS_VERIFIED,
    METADATA_TYPE,
)
[docs]
class JSONField(OrigJSONField):
[docs]
    def deconstruct(self):
        # the original path was 'django.db.models.JSONField' or
        # 'django.contrib.postgres.fields....'
        name, path, args, kwargs = super().deconstruct()
        # Substitute 'my_app' by your application name everywhere.
        path = "swh.deposit.models.JSONField"
        return name, path, args, kwargs 
 
[docs]
class Dbversion(models.Model):
    """Db version"""
    version = models.IntegerField(primary_key=True)
    release = models.DateTimeField(default=now, null=True)
    description = models.TextField(blank=True, null=True)
    class Meta:
        db_table = "dbversion"
        app_label = "deposit"
    def __str__(self):
        return str(
            {
                "version": self.version,
                "release": self.release,
                "description": self.description,
            }
        ) 
"""Possible status"""
DEPOSIT_STATUS = [
    (DEPOSIT_STATUS_PARTIAL, DEPOSIT_STATUS_PARTIAL),
    ("expired", "expired"),
    (DEPOSIT_STATUS_DEPOSITED, DEPOSIT_STATUS_DEPOSITED),
    (DEPOSIT_STATUS_VERIFIED, DEPOSIT_STATUS_VERIFIED),
    (DEPOSIT_STATUS_REJECTED, DEPOSIT_STATUS_REJECTED),
    ("loading", "loading"),
    (DEPOSIT_STATUS_LOAD_SUCCESS, DEPOSIT_STATUS_LOAD_SUCCESS),
    (DEPOSIT_STATUS_LOAD_FAILURE, DEPOSIT_STATUS_LOAD_FAILURE),
]
"""Possible status and the detailed meaning."""
DEPOSIT_STATUS_DETAIL = {
    DEPOSIT_STATUS_PARTIAL: "Deposit is partially received. To finalize it, "
    "In-Progress header should be false",
    "expired": "Deposit has been there too long and is now "
    "deemed ready to be garbage collected",
    DEPOSIT_STATUS_DEPOSITED: "Deposit is ready for additional checks "
    "(tarball ok, metadata, etc...)",
    DEPOSIT_STATUS_VERIFIED: "Deposit is fully received, checked, and "
    "ready for loading",
    DEPOSIT_STATUS_REJECTED: "Deposit failed the checks",
    "loading": "Loading is ongoing on swh's side",
    DEPOSIT_STATUS_LOAD_SUCCESS: "The deposit has been successfully "
    "loaded into the Software Heritage archive",
    DEPOSIT_STATUS_LOAD_FAILURE: "The deposit loading into the "
    "Software Heritage archive failed",
}
[docs]
class DepositClient(User):
    """Deposit client"""
    collections = ArrayField(models.IntegerField(), null=True)
    objects = UserManager()  # type: ignore
    # this typing hint is due to a mypy/django-stubs limitation,
    # see https://github.com/typeddjango/django-stubs/issues/174
    provider_url = models.TextField(null=False)
    domain = models.TextField(null=False)
    oidc_user: Optional[OIDCUser] = None
    class Meta:
        db_table = "deposit_client"
        app_label = "deposit"
    def __str__(self):
        return str(
            {
                "id": self.id,
                "collections": self.collections,
                "username": super().username,
                "domain": self.domain,
                "provider_url": self.provider_url,
            }
        ) 
DEPOSIT_METADATA_ONLY = "meta"
DEPOSIT_CODE = "code"
DEPOSIT_TYPES = [
    (DEPOSIT_METADATA_ONLY, DEPOSIT_METADATA_ONLY),
    (DEPOSIT_CODE, DEPOSIT_CODE),
]
[docs]
class Deposit(models.Model):
    """Deposit reception table"""
    id = models.BigAutoField(primary_key=True)
    # First deposit reception date
    reception_date = models.DateTimeField(auto_now_add=True)
    # Date when the deposit is deemed complete and ready for loading
    complete_date = models.DateTimeField(null=True)
    # collection concerned by the deposit
    collection = models.ForeignKey("DepositCollection", models.DO_NOTHING)
    # Deprecated: Deposit's external identifier
    external_id = models.TextField(null=True)
    # URL of the origin of this deposit, null if this is a metadata-only deposit
    origin_url = models.TextField(null=True)
    # Deposit client
    client = models.ForeignKey("DepositClient", models.DO_NOTHING)
    # SWH's loading result identifier
    swhid = models.TextField(blank=True, null=True)
    swhid_context = models.TextField(blank=True, null=True)
    # Deposit's status regarding loading
    status = models.TextField(choices=DEPOSIT_STATUS, default=DEPOSIT_STATUS_PARTIAL)
    status_detail = JSONField(null=True)
    # deposit can have one parent
    parent = models.ForeignKey("self", on_delete=models.PROTECT, null=True)
    check_task_id = models.TextField(
        blank=True, null=True, verbose_name="Scheduler's associated checking task id"
    )
    load_task_id = models.TextField(
        blank=True, null=True, verbose_name="Scheduler's associated loading task id"
    )
    type = models.CharField(max_length=4, choices=DEPOSIT_TYPES, default=DEPOSIT_CODE)
    # Release data
    software_version = models.TextField(default="")
    release_notes = models.TextField(default="")
    raw_metadata: Optional[str] = None
    class Meta:
        db_table = "deposit"
        app_label = "deposit"
    def __str__(self):
        d = {
            "id": self.id,
            "type": self.type,
            "status": self.status,
            "reception_date": self.reception_date,
            "complete_date": self.complete_date,
            "collection": self.collection.name,
            "external_id": self.external_id,
            "origin_url": self.origin_url,
            "client": self.client.username,
        }
        if self.status in (DEPOSIT_STATUS_REJECTED):
            d["status_detail"] = self.status_detail
        return str(d)
 
[docs]
def client_directory_path(instance: "DepositRequest", filename: str) -> str:
    """Callable to determine the upload archive path. This defaults to
     MEDIA_ROOT/client_<user_id>/%Y%m%d-%H%M%S.%f/<filename>.
    The format "%Y%m%d-%H%M%S.%f" is the reception date of the associated deposit
    formatted using strftime.
    Args:
        instance: DepositRequest concerned by the upload
        filename: Filename of the uploaded file
    Returns:
        The upload archive path.
    """
    reception_date = instance.deposit.reception_date
    assert isinstance(reception_date, datetime.datetime)
    folder = reception_date.strftime("%Y%m%d-%H%M%S.%f")
    return f"client_{instance.deposit.client.id}/{folder}/{filename}" 
REQUEST_TYPES = [(ARCHIVE_TYPE, ARCHIVE_TYPE), (METADATA_TYPE, METADATA_TYPE)]
[docs]
class DepositRequest(models.Model):
    """Deposit request associated to one deposit."""
    id = models.BigAutoField(primary_key=True)
    # Deposit concerned by the request
    deposit = models.ForeignKey(Deposit, models.DO_NOTHING)
    date = models.DateTimeField(auto_now_add=True)
    # Deposit request information on the data to inject
    # this can be null when type is 'archive'
    metadata = JSONField(null=True)
    raw_metadata = models.TextField(null=True)
    # this can be null when type is 'metadata'
    archive = models.FileField(null=True, upload_to=client_directory_path)
    type = models.CharField(max_length=8, choices=REQUEST_TYPES, null=True)
    class Meta:
        db_table = "deposit_request"
        app_label = "deposit"
    def __str__(self):
        meta = None
        if self.metadata:
            from json import dumps
            meta = dumps(self.metadata)
        archive_name = None
        if self.archive:
            archive_name = self.archive.name
        return str(
            {
                "id": self.id,
                "deposit": self.deposit,
                "metadata": meta,
                "archive": archive_name,
            }
        ) 
[docs]
class DepositCollection(models.Model):
    id = models.BigAutoField(primary_key=True)
    # Human readable name for the collection type e.g HAL, arXiv, etc...
    name = models.TextField()
    class Meta:
        db_table = "deposit_collection"
        app_label = "deposit"
    def __str__(self):
        return str({"id": self.id, "name": self.name})