pallets-eco / pallets-eco/flask-admin

[Feature Request] Add a Cloudinary FileAdmin like S3

Open
#2,158 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
Python
Stars
6.1k
Forks
1.6k
Avg merge
6d 14h
Merged PRs (30d)
1

Description

I'm trying to implement a custom FileAdmin, so I can use it with Cloudinary's free CDN to host images (jpg, png, gif). They have a Python SDK (Github) that provides the upload, search, and management aspects of everything. However, I'm struggling to implement the methods from the LocalFileStorage (per the documentation). I've also tried to refer to the existing s3 module, but wasn't able to get much from it.

Current skeleton of the CloudinaryStorage class

try:
    import cloudinary
    import cloudinary.uploader
    import cloudinary.api
except ImportError:
    cloudinary = None

from flask_admin.contrib.fileadmin import BaseFileAdmin


class CloudinaryStorage(object):
    def __init__(self, cloud_name, api_key, api_secret):
        """Cloudinary Storage instance constructor.

            :param cloud_name: Base file storage location
            :param api_key: Base file storage location
            :param api_secret: Base file storage location
        """
        if not cloudinary:
            raise ValueError(
                "Could not import cloudinary. "
                "You can install cloudinary by using pip install cloudinary"
            )

        cloudinary.config(
            cloud_name=cloud_name,
            api_key=api_key,
            api_secret=api_secret
        )

    def get_base_path(self):
        """Return base path.

        Override to customize behavior (per-user directories, etc).
        """
        return ""

    def make_dir(self, path, directory):
        """Creates a new directory.

            :param path: The remote path under which to create the new directory.
            :param directory: The name of the new directory to create within ``path``.
        """
        directory = directory.strip("/")
        cloudinary.api.create_folder(f"{path}/{directory}/")

    def get_files(self, path, directory):
        """Gets a list of tuples (files) in the ``directory`` under the ``path``.

            :param path: The path up to the directory.
            :param directory: The directory that will have its files listed.

            Each tuple represents a file and it should contain the file name,
            the relative path, a flag signifying if it is a directory, the file
            size in bytes, and the time last modified in seconds since the epoch.
        """
        files = []
        directories = []
        path = path.rstrip("/")
        results = cloudinary.Search() \
            .expression(f"folder:{directory}/*") \
            .execute()
        for resource in results.get("resources", []):
            pass

    def delete_tree(self, directory):
        """Deletes the directory ``directory`` and all its files & subdirectories.

            :param directory: The directory to delete.
        """
        cloudinary.api.delete_folder(directory)

    def delete_file(self, file_path):
        """Deletes the file located at ``file_path``.

            :param file_path: The path to the file to delete.
        """

    def path_exists(self, path) -> bool:
        """Check if ``path`` exists.

            :param path: The path to the file or directory to verify exists.
            :returns: ``True``/``False - Whether ``path`` exists or not.
            :rtype: ``bool``
        """

    def rename_path(self, src, dst):
        """Rename ``src`` to ``dst``.

            :param src: The path to the file or directory to rename.
            :param dst: The new name to assign to the file or directory.
        """

    def is_dir(self, path) -> bool:
        """Check if ``path`` is a directory.

            :param path: The path to the file or directory to check.
            :returns: ``True``/``False - Whether ``path`` is a directory or not.
            :rtype: ``bool``
        """

    def send_file(self, path):
        """Sends the file located at ``path`` to the user.

            :param path: The remote path to the file to send to the user.
        """

    def read_file(self, path):
        """Reads the content of the file located at ``path``.

            :param path: The remote path to the file to read.
        """

    def write_file(self, path, content):
        """Writes the ``content`` to the file located at ``path``.

            :param path: The remote path to the file to write to.
            :param content: The content to write to the remote file.
        """

    def save_file(self, path, file_data):
        """Save uploaded file to the remote path.

            :param path: Path to save ``file_data`` to.
            :param file_data: Werkzeug ``FileStorage`` object.
        """
        cloudinary.uploader.upload(file_data.stream, resource_type="image")


class CloudinaryFileAdmin(BaseFileAdmin):
    def __init__(self, cloud_name, api_key, api_secret, *args, **kwargs):
        storage = CloudinaryStorage(cloud_name, api_key, api_secret)
        super().__init__(*args, storage=storage, **kwargs)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with flask_admin/contrib/fileadmin/init.py's LocalFileStorage and the existing flask_admin/contrib/fileadmin/s3.py, then compare their methods with the CloudinaryStorage skeleton in the issue. Review the Cloudinary Python SDK's upload, search, and management APIs. Done means a usable CloudinaryFileAdmin implementing the listed storage operations.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
backend, cloud
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
30/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.