Commit c3683f94 authored by Open Science Conservation Fund's avatar Open Science Conservation Fund
Browse files

fix for resource can_view permission check; refactored data package publishing task

parent 565e9c6b
Loading
Loading
Loading
Loading
+104 −51
Original line number Diff line number Diff line
@@ -411,86 +411,139 @@ class ResultsDataPackageGenerator:

class PublishDataPackage:
    """
    TODO: docstrings
    Publishes a data package to external repositories (Dataverse or Zenodo).
    """

    def __init__(self, data, user):
        self.user = user
        self.data = data
        self.token = data["api_token"]
        self.host = data["host_url"]
        self.secure = data.get("secure_connection", True)
        self.data_hub = data["data_hub"]
        self.data_package = data["data_package"]

    def _get_error_message(self, response):
        """Extract error message from response with fallback handling."""
        try:
            return response.json().get("message", response.text)
        except (json.JSONDecodeError, ValueError):
            return f"HTTP {response.status_code}: {response.text[:200]}"

    def run(self):
        # connection settings
        token = self.data["api_token"]
        host = self.data["host_url"]
        secure = self.data["secure_connection"]
        # get zipped data package
        data_package = self.data["data_package"]
        zipped = zipfile.ZipFile(open(data_package.package.path, "rb"))
        # process package descriptor
    def _open_package(self):
        """Open and validate the data package."""
        try:
            zipped = zipfile.ZipFile(open(self.data_package.package.path, "rb"))
            descriptor = json.loads(zipped.read("datapackage.json"))

        if self.data["data_hub"] == "Dataverse":
            return zipped, descriptor
        except (zipfile.BadZipFile, KeyError) as e:
            raise ValueError(f"Invalid data package: {e}")
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid package descriptor JSON: {e}")

    def _publish_to_dataverse(self, zipped, descriptor):
        """Publish package to Dataverse."""
        dv_meta, dfiles = mapper.package2dataverse(descriptor, taxonomic_cov=False)
            dv_meta_json = json.dumps(dv_meta)
            # create dataset
        dataverse = self.data["container"]
            url = f"{host}/api/dataverses/{dataverse}/datasets"
            headers = {"X-Dataverse-key": token, "Content-Type": "application/json"}
            resp = requests.post(url, data=dv_meta_json, headers=headers, verify=secure)
            if resp.status_code == 201:

        # Create dataset
        url = f"{self.host}/api/dataverses/{dataverse}/datasets"
        headers = {"X-Dataverse-key": self.token, "Content-Type": "application/json"}

        resp = requests.post(
            url, data=json.dumps(dv_meta), headers=headers, verify=self.secure
        )

        if resp.status_code != 201:
            raise Exception(
                f"Failed to create dataset: {self._get_error_message(resp)}"
            )

        dataset_data = resp.json()["data"]
        dataset_id = dataset_data["id"]
                # upload files
                url = f"{host}/api/datasets/{dataset_id}/add"

        # Upload files
        url = f"{self.host}/api/datasets/{dataset_id}/add"
        for fdesc in dfiles:
            try:
                files = {"file": zipped.open(fdesc["filename"])}
                    payload = dict(jsonData=json.dumps(fdesc))
                payload = {"jsonData": json.dumps(fdesc)}

                resp = requests.post(
                    url,
                    data=payload,
                    files=files,
                        params={"key": token},
                        verify=secure,
                    params={"key": self.token},
                    verify=self.secure,
                )

                if resp.status_code not in (200, 201):
                    raise Exception(
                        f"Failed to upload {fdesc['filename']}: {self._get_error_message(resp)}"
                    )
                time.sleep(2)
            else:
                raise Exception(resp.json()["message"])
            msg = _(
                f"You have successfully published your data package {data_package.filename()} at "
                f"{host} ! Details of the published dataset: {dataset_data}"
            except KeyError as e:
                raise Exception(
                    f"File {fdesc.get('filename', 'unknown')} not found in package: {e}"
                )
            return msg

        elif self.data["data_hub"] == "Zenodo":
            # get package metadata in zenodo-friendly format and files list
            meta, datafiles = mapper.package2zenodo(descriptor)
        return _(
            f"Successfully published data package {self.data_package.filename()} to {self.host}. "
            f"Dataset ID: {dataset_id}"
        )

            params = {"access_token": token}
    def _publish_to_zenodo(self, zipped, descriptor):
        """Publish package to Zenodo."""
        meta, datafiles = mapper.package2zenodo(descriptor)
        params = {"access_token": self.token}

            # upload files
        # Upload files
        for fname in datafiles:
                fcontent = zipped.open(fname)
                r = requests.put(
                    "%s/%s" % (host, fname),
                    data=fcontent,
                    params=params,
            try:
                with zipped.open(fname) as fcontent:
                    url = f"{self.host}/{fname}"
                    resp = requests.put(url, data=fcontent, params=params)

                    if resp.status_code != 200:
                        raise Exception(
                            f"Failed to upload {fname}: {self._get_error_message(resp)}"
                        )
                if r.status_code != 200:
                    raise Exception(r.json()["message"])
            except KeyError as e:
                raise Exception(f"File {fname} not found in package: {e}")

            # send metadata
        # Send metadata
        headers = {"Content-Type": "application/json"}
        metadata_url = self.data["metadata_url"]
            r = requests.put(
                metadata_url, params=params, data=json.dumps(meta), headers=headers

        resp = requests.put(
            metadata_url,
            params=params,
            data=json.dumps(meta),
            headers=headers,
        )
            if r.status_code != 200:
                raise Exception(r.json()["message"])

            msg = _(
                "You have successfully sent data package to Zenodo. It can be published manually from your "
                "Zenodo account."
        if resp.status_code != 200:
            raise Exception(
                f"Failed to update metadata: {self._get_error_message(resp)}"
            )
            return msg

        raise NotImplementedError
        return _(
            "Successfully sent data package to Zenodo. "
            "It can be published manually from your Zenodo account."
        )

    def run(self):
        """Execute the publishing workflow."""
        zipped, descriptor = self._open_package()

        try:
            if self.data_hub == "Dataverse":
                return self._publish_to_dataverse(zipped, descriptor)
            elif self.data_hub == "Zenodo":
                return self._publish_to_zenodo(zipped, descriptor)
            else:
                raise NotImplementedError(
                    f"Publishing to {self.data_hub} is not supported"
                )
        finally:
            zipped.close()
+2 −7
Original line number Diff line number Diff line
@@ -324,13 +324,8 @@ class Resource(AccessModelMixin, models.Model):
        if Deployment.objects.filter(pk=self.deployment_id, managers=user).exists():
            return True

        # Membership in any collection that contains this resource, excluding owner/manager roles
        if (
            self.collection_set.exclude(owner=user)
            .exclude(managers=user)
            .filter(members=user)
            .exists()
        ):
        # Membership in any collection that contains this resource
        if self.collection_set.filter(members=user).exists():
            return True

        # Membership in any CS Team that contains this resource's deployment