12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394 |
- import base64
- import logging
- import pathlib
- import uuid
- from django.conf import settings
- from django.utils.functional import cached_property
- from storages.utils import safe_join
- from s3file.middleware import S3FileMiddleware
- from s3file.storages import get_aws_location, storage
- logger = logging.getLogger("s3file")
- class S3FileInputMixin:
- """FileInput that uses JavaScript to directly upload to Amazon S3."""
- needs_multipart_form = False
- upload_path = safe_join(
- str(get_aws_location()),
- str(
- getattr(
- settings, "S3FILE_UPLOAD_PATH", pathlib.PurePosixPath("tmp", "s3file")
- )
- ),
- )
- expires = settings.SESSION_COOKIE_AGE
- @property
- def bucket_name(self):
- return storage.bucket.name
- @property
- def client(self):
- return storage.connection.meta.client
- def build_attrs(self, *args, **kwargs):
- attrs = super().build_attrs(*args, **kwargs)
- accept = attrs.get("accept")
- response = self.client.generate_presigned_post(
- self.bucket_name,
- str(pathlib.PurePosixPath(self.upload_folder, "${filename}")),
- Conditions=self.get_conditions(accept),
- ExpiresIn=self.expires,
- )
- defaults = {
- "data-fields-%s" % key: value for key, value in response["fields"].items()
- }
- defaults["data-url"] = response["url"]
- # we sign upload location, and will only accept files within the same folder
- defaults["data-s3f-signature"] = S3FileMiddleware.sign_s3_key_prefix(
- self.upload_folder
- )
- defaults.update(attrs)
- try:
- defaults["class"] += " s3file"
- except KeyError:
- defaults["class"] = "s3file"
- return defaults
- def get_conditions(self, accept):
- conditions = [
- {"bucket": self.bucket_name},
- ["starts-with", "$key", str(self.upload_folder)],
- {"success_action_status": "201"},
- ]
- if accept and "," not in accept:
- top_type, sub_type = accept.split("/", 1)
- if sub_type == "*":
- conditions.append(["starts-with", "$Content-Type", "%s/" % top_type])
- else:
- conditions.append({"Content-Type": accept})
- else:
- conditions.append(["starts-with", "$Content-Type", ""])
- return conditions
- @cached_property
- def upload_folder(self):
- return str(
- pathlib.PurePosixPath(
- self.upload_path,
- base64.urlsafe_b64encode(uuid.uuid4().bytes)
- .decode("utf-8")
- .rstrip("=\n"),
- )
- ) # S3 uses POSIX paths
- class Media:
- js = ("s3file/js/s3file.js" if settings.DEBUG else "s3file/js/s3file.min.js",)
|