forms.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394
  1. import base64
  2. import logging
  3. import pathlib
  4. import uuid
  5. from django.conf import settings
  6. from django.utils.functional import cached_property
  7. from storages.utils import safe_join
  8. from s3file.middleware import S3FileMiddleware
  9. from s3file.storages import get_aws_location, storage
  10. logger = logging.getLogger("s3file")
  11. class S3FileInputMixin:
  12. """FileInput that uses JavaScript to directly upload to Amazon S3."""
  13. needs_multipart_form = False
  14. upload_path = safe_join(
  15. str(get_aws_location()),
  16. str(
  17. getattr(
  18. settings, "S3FILE_UPLOAD_PATH", pathlib.PurePosixPath("tmp", "s3file")
  19. )
  20. ),
  21. )
  22. expires = settings.SESSION_COOKIE_AGE
  23. @property
  24. def bucket_name(self):
  25. return storage.bucket.name
  26. @property
  27. def client(self):
  28. return storage.connection.meta.client
  29. def build_attrs(self, *args, **kwargs):
  30. attrs = super().build_attrs(*args, **kwargs)
  31. accept = attrs.get("accept")
  32. response = self.client.generate_presigned_post(
  33. self.bucket_name,
  34. str(pathlib.PurePosixPath(self.upload_folder, "${filename}")),
  35. Conditions=self.get_conditions(accept),
  36. ExpiresIn=self.expires,
  37. )
  38. defaults = {
  39. "data-fields-%s" % key: value for key, value in response["fields"].items()
  40. }
  41. defaults["data-url"] = response["url"]
  42. # we sign upload location, and will only accept files within the same folder
  43. defaults["data-s3f-signature"] = S3FileMiddleware.sign_s3_key_prefix(
  44. self.upload_folder
  45. )
  46. defaults.update(attrs)
  47. try:
  48. defaults["class"] += " s3file"
  49. except KeyError:
  50. defaults["class"] = "s3file"
  51. return defaults
  52. def get_conditions(self, accept):
  53. conditions = [
  54. {"bucket": self.bucket_name},
  55. ["starts-with", "$key", str(self.upload_folder)],
  56. {"success_action_status": "201"},
  57. ]
  58. if accept and "," not in accept:
  59. top_type, sub_type = accept.split("/", 1)
  60. if sub_type == "*":
  61. conditions.append(["starts-with", "$Content-Type", "%s/" % top_type])
  62. else:
  63. conditions.append({"Content-Type": accept})
  64. else:
  65. conditions.append(["starts-with", "$Content-Type", ""])
  66. return conditions
  67. @cached_property
  68. def upload_folder(self):
  69. return str(
  70. pathlib.PurePosixPath(
  71. self.upload_path,
  72. base64.urlsafe_b64encode(uuid.uuid4().bytes)
  73. .decode("utf-8")
  74. .rstrip("=\n"),
  75. )
  76. ) # S3 uses POSIX paths
  77. class Media:
  78. js = ("s3file/js/s3file.js" if settings.DEBUG else "s3file/js/s3file.min.js",)