Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

s3 storage + tinymce for uploaded images #63

Open
toose opened this issue Jun 4, 2020 · 2 comments
Open

s3 storage + tinymce for uploaded images #63

toose opened this issue Jun 4, 2020 · 2 comments

Comments

@toose
Copy link

toose commented Jun 4, 2020

Hi there--

My application is setup to work with django-tinymce4-lite and it works great when using offline/local media storage. What i mean by that is if I choose to upload an image, the image gets uploaded to media/uploads, including several thumbnail 'versions' that get saved to media/_versions.

When I enable s3 as my media storage location, the image files are getting uploaded to the uploads folder in my bucket correctly (folder is auto created at upload time), however thumbnails are not generated alongside, and in fact, after an upload, the _versions folder does not get created at all.

While I'm able to insert these images after upload using the image insert button, it's a bit frustrating in that unless the image has a descriptive filename, its impossible to tell which image is which without that image preview.

Any help would be greatly appreciated!

Here are the relevant settings regarding tinymce and s3 configuration:

# Media config
USE_S3 = int(os.environ.get('USE_S3', 0))
if USE_S3:
    AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_ID')
    AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_KEY')
    AWS_STORAGE_BUCKET_NAME = os.environ.get('AWS_STORAGE_BUCKET_NAME')
    AWS_DEFAULT_ACL = 'public-read'
    AWS_S3_CUSTOM_DOMAIN = f'{AWS_STORAGE_BUCKET_NAME}.s3.amazonaws.com'
    AWS_S3_OBJECT_PARAMETERS = {'CacheControl': 'max-age=86400'}
    AWS_PRELOAD_METADATA = True
    AWS_QUERYSTRING_AUTH = False
    # s3 media config
    MEDIA_LOCATION = 'media'
    MEDIA_URL = f'https://{AWS_S3_CUSTOM_DOMAIN}/{MEDIA_LOCATION}/'
    DEFAULT_FILE_STORAGE = 'config.storage_backends.MediaStorage'
else:
    MEDIA_URL = '/media/'
    MEDIA_ROOT = os.path.join(BASE_DIR, 'media')


# TinyMCE-lite config
TINYMCE_DEFAULT_CONFIG = {
    'selector': 'textarea',
    'theme': 'modern',
    'plugins': 'link image preview codesample contextmenu table code lists colorpicker textcolor',
    'toolbar1': 'formatselect | bold italic underline | forecolor backcolor | alignleft aligncenter alignright alignjustify '
               '| bullist numlist | outdent indent | table | link image | codesample | preview code | fontsizeselect fontsize ',
    'content_css': 'https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css',
    'contextmenu': 'formats | link image',
    'menubar': False,
    'inline': False,
    'statusbar': True,
    'width': 'auto',
    'height': 360,
}

Here is storage_backends.py:

from storages.backends.s3boto3 import S3Boto3Storage
from django.conf import settings
from django.utils import timezone


class MediaStorage(S3Boto3Storage):
    location = settings.MEDIA_LOCATION
    default_acl = 'private'
    file_overwrite = False
    #custom_domain = False
    isfilecached = {}

    def isdir(self, name):
        if not name:  # Empty name is a directory
            return True

        if self.isfile(name):
            return False

        return True

    def isfile(self, name):
        if len(name.split('.')) > 1:
            return True
        try:
            name = self._normalize_name(self._clean_name(name))
            if self.isfilecached.get(name) is not None:
                return self.isfilecached.get(name)

            f = S3Boto3StorageFile(name, 'rb', self)
            if "directory" in f.obj.content_type:
                isfile = False
            else:
                isfile = True
        except Exception:
            isfile = False
        self.isfilecached[name] = isfile
        return isfile

    def move(self, old_file_name, new_file_name, allow_overwrite=False):

        if self.exists(new_file_name):
            if allow_overwrite:
                self.delete(new_file_name)
            else:
                raise "The destination file '%s' exists and allow_overwrite is False" % new_file_name

        old_key_name = self._encode_name(self._normalize_name(self._clean_name(old_file_name)))
        new_key_name = self._encode_name(self._normalize_name(self._clean_name(new_file_name)))

        k = self.bucket.meta.client.copy(
            {
                'Bucket': self.bucket.name,
                'Key': new_key_name
            },
            self.bucket.name,
            old_key_name
        )

        if not k:
            raise "Couldn't copy '%s' to '%s'" % (old_file_name, new_file_name)

        self.delete(old_file_name)

    def makedirs(self, name):
        name = self._normalize_name(self._clean_name(name))
        return self.bucket.meta.client.put_object(Bucket=self.bucket.name, Key=f'{name}/')

    def rmtree(self, name):
        name = self._normalize_name(self._clean_name(name))
        delete_objects = [{'Key': f"{name}/"}]

        dirlist = self.listdir(self._encode_name(name))
        for item in dirlist:
            for obj in item:
                obj_name = f"{name}/{obj}"
                if self.isdir(obj_name):
                    obj_name = f"{obj_name}/"
                delete_objects.append({'Key': obj_name})
        self.bucket.delete_objects(Delete={'Objects': delete_objects})

    def path(self, name):
        return name

    def listdir(self, name):
        directories, files = super().listdir(name)
        if '.' in files:
            files.remove('.')
        return directories, files

    def exists(self, name):
        if self.isdir(name):
            return True
        else:
            return super().exists(name)

    def get_modified_time(self, name):
        try:
            # S3 boto3 library requires that directorys have the trailing slash
            if self.isdir(name):
                name = f'{name}/'
            modified_date = super().get_modified_time(name)
        except Exception:
            modified_date = timezone.now()
        return modified_date

    def size(self, name):
        try:
            # S3 boto3 library requires that directorys have the trailing slash
            if self.isdir(name):
                name = f'{name}/'
            size = super().size(name)
        except Exception:
            size = 0
        return size
@merwok
Copy link

merwok commented Jun 4, 2020

Have you seen #47 ?

@toose
Copy link
Author

toose commented Jun 4, 2020

I wasn't sure if #47 pertained to my issue. I'll have a go at that later today. Cheers!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants