azure_storage.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import mimetypes
  2. import os.path
  3. import uuid
  4. from azure.common import AzureMissingResourceHttpError
  5. from azure.storage.blob import BlobServiceClient
  6. from django.conf import settings
  7. from django.core.exceptions import ImproperlyConfigured
  8. from django.core.files.base import ContentFile
  9. from django.core.files.storage import Storage
  10. from django.utils.deconstruct import deconstructible
  11. from django.utils.timezone import localtime
  12. from datetime import datetime
  13. from pac.blobs.utils import AttachedFile
  14. try:
  15. import azure # noqa
  16. except ImportError:
  17. raise ImproperlyConfigured(
  18. "Could not load Azure bindings. "
  19. "See https://github.com/WindowsAzure/azure-sdk-for-python")
  20. def clean_name(name):
  21. return os.path.normpath(name).replace("\\", "/")
  22. def setting(name, default=None):
  23. """
  24. Helper function to get a Django setting by name. If setting doesn't exists
  25. it will return a default.
  26. :param name: Name of setting
  27. :type name: str
  28. :param default: Value if setting is unfound
  29. :returns: Setting's value
  30. """
  31. return getattr(settings, name, default)
  32. @deconstructible
  33. class AzureStorage(Storage):
  34. # account_name = setting("AZURE_ACCOUNT_NAME")
  35. # account_key = setting("AZURE_ACCOUNT_KEY")
  36. azure_container_url = setting("AZURE_CONTAINER_URL")
  37. azure_ssl = setting("AZURE_SSL")
  38. def __init__(self, container=None, *args, **kwargs):
  39. super(AzureStorage, self).__init__(*args, **kwargs)
  40. self._connection = None
  41. if container is None:
  42. self.azure_container = setting("AZURE_CONTAINER")
  43. else:
  44. self.azure_container = container
  45. def get_available_name(self, name, *args, **kwargs):
  46. return {"original_name": name, "uuid": str(uuid.uuid4())}
  47. @property
  48. def connection(self):
  49. if self._connection is None:
  50. connect_str = setting("AZURE_STORAGE_CONNECTION_STRING")
  51. # Create the BlobServiceClient object which will be used to create a container client
  52. blob_service_client = BlobServiceClient.from_connection_string(connect_str)
  53. # Create a unique name for the container
  54. container_name = "pac-files"
  55. # Create a blob client using the local file name as the name for the blob
  56. self._connection = blob_service_client
  57. return self._connection
  58. @property
  59. def azure_protocol(self):
  60. """
  61. :return: http | https | None
  62. :rtype: str | None
  63. """
  64. if self.azure_ssl:
  65. return 'https'
  66. return 'http' if self.azure_ssl is not None else None
  67. def __get_blob_properties(self, name):
  68. """
  69. :param name: Filename
  70. :rtype: azure.storage.blob.models.Blob | None
  71. """
  72. try:
  73. return self.connection.get_blob_properties(
  74. self.azure_container,
  75. name
  76. )
  77. except AzureMissingResourceHttpError:
  78. return None
  79. def _open(self, container, name, mode="rb"):
  80. """
  81. :param str name: Filename
  82. :param str mode:
  83. :rtype: ContentFile
  84. """
  85. print(f'Retrieving blob: container={self.azure_container}, blob={name}')
  86. blob_client = self.connection.get_blob_client(container=container, blob=name)
  87. contents = blob_client.download_blob().readall()
  88. return ContentFile(contents)
  89. def exists(self, name):
  90. """
  91. :param name: File name
  92. :rtype: bool
  93. """
  94. return False # self.__get_blob_properties(name) is not None
  95. def delete(self, name):
  96. """
  97. :param name: File name
  98. :return: None
  99. """
  100. try:
  101. self.connection.delete_blob(self.azure_container, name)
  102. except AzureMissingResourceHttpError:
  103. pass
  104. def size(self, name):
  105. """
  106. :param name:
  107. :rtype: int
  108. """
  109. blob = self.connection.get_blob_properties(self.azure_container, name)
  110. return blob.properties.content_length
  111. def _save(self, name, content):
  112. """
  113. :param name:
  114. :param File content:
  115. :return:
  116. """
  117. original_name = name.get("original_name")
  118. blob_file_name = datetime.now().strftime("%Y%m%d-%H:%M:%S.%f_") + original_name
  119. # blob_name = "{}.{}".format(name.get("uuid"), original_name.partition(".")[-1])
  120. if hasattr(content.file, 'content_type'):
  121. content_type = content.file.content_type
  122. else:
  123. content_type = mimetypes.guess_type(original_name)
  124. if hasattr(content, 'chunks'):
  125. content_data = b''.join(chunk for chunk in content.chunks())
  126. else:
  127. content_data = content.read()
  128. print(f'Saving blob: container={self.azure_container}, blob={blob_file_name}')
  129. blob_client = self.connection.get_blob_client(container=self.azure_container, blob=blob_file_name)
  130. obj = blob_client.upload_blob(content_data)
  131. # create_blob_from_bytes(self.azure_container, name, content_data,
  132. #
  133. # content_settings=ContentSettings(content_type=content_type))
  134. af = AttachedFile(original_name, self.azure_container, blob_file_name)
  135. return af
  136. def url(self, name):
  137. """
  138. :param str name: Filename
  139. :return: path
  140. """
  141. return self.connection.make_blob_url(
  142. container_name=self.azure_container,
  143. blob_name=name,
  144. protocol=self.azure_protocol,
  145. )
  146. def modified_time(self, name):
  147. """
  148. :param name:
  149. :rtype: datetime.datetime
  150. """
  151. blob = self.__get_blob_properties(name)
  152. return localtime(blob.properties.last_modified).replace(tzinfo=None)