import json import re import cloudinary.uploader import cloudinary.utils from cloudinary import CloudinaryResource from django import forms from django.utils.translation import gettext_lazy as _ def cl_init_js_callbacks(form, request): for field in form.fields.values(): if isinstance(field, CloudinaryJsFileField): field.enable_callback(request) class CloudinaryInput(forms.TextInput): input_type = 'file' def render(self, name, value, attrs=None, renderer=None): attrs = dict(self.attrs, **attrs) options = attrs.get('options', {}) attrs["options"] = '' params = cloudinary.utils.build_upload_params(**options) if options.get("unsigned"): params = cloudinary.utils.cleanup_params(params) else: params = cloudinary.utils.sign_request(params, options) if 'resource_type' not in options: options['resource_type'] = 'auto' cloudinary_upload_url = cloudinary.utils.cloudinary_api_url("upload", **options) attrs["data-url"] = cloudinary_upload_url attrs["data-form-data"] = json.dumps(params) attrs["data-cloudinary-field"] = name chunk_size = options.get("chunk_size", None) if chunk_size: attrs["data-max-chunk-size"] = chunk_size attrs["class"] = " ".join(["cloudinary-fileupload", attrs.get("class", "")]) widget = super(CloudinaryInput, self).render("file", None, attrs=attrs) if value: if isinstance(value, CloudinaryResource): value_string = value.get_presigned() else: value_string = value widget += forms.HiddenInput().render(name, value_string) return widget class CloudinaryJsFileField(forms.Field): default_error_messages = { "required": _("No file selected!") } def __init__(self, attrs=None, options=None, autosave=True, *args, **kwargs): if attrs is None: attrs = {} if options is None: options = {} self.autosave = autosave attrs = attrs.copy() attrs["options"] = options.copy() field_options = {'widget': CloudinaryInput(attrs=attrs)} field_options.update(kwargs) super(CloudinaryJsFileField, self).__init__(*args, **field_options) def enable_callback(self, request): from django.contrib.staticfiles.storage import staticfiles_storage self.widget.attrs["options"]["callback"] = request.build_absolute_uri( staticfiles_storage.url("html/cloudinary_cors.html")) def to_python(self, value): """Convert to CloudinaryResource""" if not value: return None m = re.search(r'^([^/]+)/([^/]+)/v(\d+)/([^#]+)#([^/]+)$', value) if not m: raise forms.ValidationError("Invalid format") resource_type = m.group(1) upload_type = m.group(2) version = m.group(3) filename = m.group(4) signature = m.group(5) m = re.search(r'(.*)\.(.*)', filename) if not m: raise forms.ValidationError("Invalid file name") public_id = m.group(1) image_format = m.group(2) return CloudinaryResource(public_id, format=image_format, version=version, signature=signature, type=upload_type, resource_type=resource_type) def validate(self, value): """Validate the signature""" # Use the parent's handling of required fields, etc. super(CloudinaryJsFileField, self).validate(value) if not value: return if not value.validate(): raise forms.ValidationError("Signature mismatch") class CloudinaryUnsignedJsFileField(CloudinaryJsFileField): def __init__(self, upload_preset, attrs=None, options=None, autosave=True, *args, **kwargs): if attrs is None: attrs = {} if options is None: options = {} options = options.copy() options.update({"unsigned": True, "upload_preset": upload_preset}) super(CloudinaryUnsignedJsFileField, self).__init__( attrs, options, autosave, *args, **kwargs) class CloudinaryFileField(forms.FileField): my_default_error_messages = { "required": _("No file selected!") } default_error_messages = forms.FileField.default_error_messages.copy() default_error_messages.update(my_default_error_messages) def __init__(self, options=None, autosave=True, *args, **kwargs): self.autosave = autosave self.options = options or {} super(CloudinaryFileField, self).__init__(*args, **kwargs) def to_python(self, value): """Upload and convert to CloudinaryResource""" value = super(CloudinaryFileField, self).to_python(value) if not value: return None if self.autosave: return cloudinary.uploader.upload_image(value, **self.options) else: return value