اتصال به فضای ذخیره‌سازی ابری در برنامه‌های Django


پروژه و کدهای مورد استفاده در ویدیوی فوق در اینجا قابل مشاهده و دسترسی هستند.

برای استفاده از Object Storage در برنامه‌های Django، می‌توانید از پکیج boto3 استفاده کنید که بایستی با دستور زیر، آن را در پروژه خود، نصب کنید:

کپی
pip install boto3

پس از آن، کافیست تا طبق مستندات ایجاد کلید، یک کلید جدید برای باکت خود بسازید. اطلاعات مربوط به ENDPOINT و نام باکت نیز در صفحه تنظیمات، در بخش دسترسی با SDK، برای شما قرار گرفته است. در نهایت نیز، بایستی اطلاعات مربوط به Object Storage خود را به متغیرهای محیطی برنامه خود، اضافه کنید؛ به عنوان مثال:

کپی
LIARA_ENDPOINT_URL=https://storage.c2.liara.site
LIARA_ACCESS_KEY=nieiou08cnbod58p
LIARA_SECRET_KEY=20b71a4c-1168-4945-8ed3-4724dbf9e997
BUCKET_NAME=my-bucket

در مرحله‌ی آخر باید قطعه کد زیر را به فایل settings.py برنامه‌تان، اضافه کنید:

کپی
# AWS S3 Settings for Liara
AWS_ACCESS_KEY_ID = os.getenv('LIARA_ACCESS_KEY')
AWS_SECRET_ACCESS_KEY = os.getenv('LIARA_SECRET_KEY')
AWS_STORAGE_BUCKET_NAME = os.getenv('BUCKET_NAME')
AWS_S3_ENDPOINT_URL = os.getenv('LIARA_ENDPOINT_URL')

DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage'

تمامی کارها انجام شده است و می‌توانید از Object Storage در برنامه خود، استفاده کنید؛ در نظر داشته باشید که با انجام کار فوق، فایل های آپلود شده به طور خودکار در ذخیره‌سازی ابری لیارا قرار می‌گیرند. در ادامه، مثال‌هایی برای استفاده از Object Storage در Django آورده شده است.

در ابتدا، با استفاده از دستور زیر، یک app جدید برای کار با Object Storage بسازید:

کپی
python manage.py startapp object_storage

سپس، بایستی در فایل views.py، قطعه کد زیر را، قرار دهید:

کپی
import boto3
from django.conf import settings
from django.shortcuts import render
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

# Initialize S3 Client
def get_s3_client():
  return boto3.client(
      's3',
      endpoint_url=settings.AWS_S3_ENDPOINT_URL,
      aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
      aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY
  )

اکنون، می‌توانید در فایل views.py، بنا به نیاز خود، قطعه کدهای زیر را، به قطعه کد فعلی، اضافه کنید.

نمونه کد آپلود فایل

کپی
@csrf_exempt
def upload_file(request):
  if request.method == 'POST' and request.FILES.get('file'):
      s3_client = get_s3_client()
      file = request.FILES['file']
      try:
          s3_client.upload_fileobj(file, settings.AWS_STORAGE_BUCKET_NAME, file.name)
          return JsonResponse({'message': f'{file.name} uploaded successfully.'})
      except Exception as e:
          return JsonResponse({'error': str(e)}, status=500)
  return JsonResponse({'error': 'Invalid request'}, status=400)

نمونه کد لیست کردن فایل‌ها

کپی
def list_files(request):
  s3_client = get_s3_client()
  try:
      response = s3_client.list_objects_v2(Bucket=settings.AWS_STORAGE_BUCKET_NAME)
      files = [obj['Key'] for obj in response.get('Contents', [])]
      return JsonResponse({'files': files})
  except Exception as e:
      return JsonResponse({'error': str(e)}, status=500)

نمونه کد حذف فایل

کپی
@csrf_exempt
def delete_file(request):
  if request.method == 'POST':
      file_name = request.POST.get('file_name')
      if not file_name:
          return JsonResponse({'error': 'File name is required'}, status=400)
      s3_client = get_s3_client()
      try:
          s3_client.delete_object(Bucket=settings.AWS_STORAGE_BUCKET_NAME, Key=file_name)
          return JsonResponse({'message': f'{file_name} deleted successfully.'})
      except Exception as e:
          return JsonResponse({'error': str(e)}, status=500)

نمونه کد ساخت لینک موقت دانلود فایل

کپی
def generate_presigned_url(request):
  file_name = request.GET.get('file_name')
  if not file_name:
      return JsonResponse({'error': 'File name is required'}, status=400)
  s3_client = get_s3_client()
  try:
      url = s3_client.generate_presigned_url(
          'get_object',
          Params={'Bucket': settings.AWS_STORAGE_BUCKET_NAME, 'Key': file_name},
          ExpiresIn=3600  # URL valid for 1 hour
      )
      return JsonResponse({'url': url})
  except Exception as e:
      return JsonResponse({'error': str(e)}, status=500)

نمونه کد دریافت لینک موقت دانلود فایل با دامنه اختصاصی

اگر طبق مستندات اتصال دامنه، یک دامنه اختصاصی به باکت خود متصل کرده‌اید، ساده‌ترین روش برای دریافت لینک موقت (Presigned) با دامنه اختصاصی این است که لینک را با Endpoint اصلی لیارا تولید کنید و سپس مقدار Host آن را به دامنه اختصاصی تغییر دهید.

برای این کار، لینک باید به‌صورت virtual-hosted ساخته شود؛ یعنی نام باکت در ابتدای Host قرار بگیرد (مانند bucket-name.storage.c2.liara.site) و نه در مسیر لینک. کلاینت boto3 هنگام استفاده از endpoint_url به‌صورت پیش‌فرض لینک را به‌صورت path-style می‌سازد؛ به همین دلیل، در قطعه کد زیر یک کلاینت با addressing_style برابر با virtual برای ساخت لینک ایجاد شده است:

کپی
import boto3
from botocore.config import Config
from urllib.parse import urlsplit, urlunsplit

CUSTOM_DOMAIN = "example.com"  # change this to your custom domain

presign_client = boto3.client(
  "s3",
  endpoint_url=settings.AWS_S3_ENDPOINT_URL,
  aws_access_key_id=settings.AWS_ACCESS_KEY_ID,
  aws_secret_access_key=settings.AWS_SECRET_ACCESS_KEY,
  config=Config(signature_version="s3v4", s3={"addressing_style": "virtual"}),
)

def generate_custom_domain_url(request):
  file_name = request.GET.get('file_name')
  if not file_name:
      return JsonResponse({'error': 'File name is required'}, status=400)
  try:
      signed = presign_client.generate_presigned_url(
          'get_object',
          Params={'Bucket': settings.AWS_STORAGE_BUCKET_NAME, 'Key': file_name},
          ExpiresIn=3600  # URL valid for 1 hour
      )
      parts = urlsplit(signed)
      return JsonResponse({'url': urlunsplit(parts._replace(netloc=CUSTOM_DOMAIN))})
  except Exception as e:
      return JsonResponse({'error': str(e)}, status=500)

برای استفاده از این تابع، کافیست مانند تابع generate_presigned_url، یک مسیر برای آن در urls.py تعریف کنید.

فقط مقدار host را تغییر دهید و پارامترهای *-X-Amz موجود در لینک را دستکاری نکنید؛ در غیر این صورت امضای لینک نامعتبر می‌شود و دسترسی به فایل با خطا مواجه خواهد شد.

نمونه کد برای مدیریت فایل‌ها

کپی
def manage_files(request):
  context = {
      'AWS_STORAGE_BUCKET_NAME': settings.AWS_STORAGE_BUCKET_NAME,
      'AWS_S3_ENDPOINT_URL': settings.AWS_S3_ENDPOINT_URL,
  }
  return render(request, 'manage_files.html', context)

تجمیع قابلیت‌ها در یک فایل

برای استفاده از تمامی قابلیت‌های فوق، بایستی در ابتدا، به فایل urls.py، قطعه کد زیر را اضافه کنید:

کپی
from django.urls import path
from object_storage import views

urlpatterns = [
  path('', views.manage_files, name='manage_files'),
  path('api/files/list/', views.list_files, name='list_files'),
  path('api/files/upload/', views.upload_file, name='upload_file'),
  path('api/files/delete/', views.delete_file, name='delete_file'),
  path('api/files/presigned-url/', views.generate_presigned_url, name='generate_presigned_url'),
]

در ادامه، در app ساخته شده، یک دایرکتوری به نام templates بسازید و درون آن، یک فایل به نام manage_files.html ایجاد کنید و قطعه کد زیر را در آن قرار دهید:

کپی
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Manage Files</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
  <h1>File Management</h1>

  <!-- Upload Form -->
  <h2>Upload File</h2>
  <form id="uploadForm" enctype="multipart/form-data">
      <input type="file" name="file" required>
      <button type="submit">Upload</button>
  </form>
  <div id="uploadMessage"></div>

  <!-- File List -->
  <h2>File List</h2>
  <ul id="fileList"></ul>

  <script>
      $(document).ready(function () {
          // Load file list
          function loadFiles() {
              $.get('/api/files/list/', function (data) {
                  $('#fileList').empty();
                  data.files.forEach(function (file) {
                      const listItem = `
                          <li>
                              ${file}
                              <button class="delete-btn" data-file="${file}">Delete</button>
                              <button class="presigned-btn" data-file="${file}">Pre-Signed URL</button>
                              <a href="${generatePermanentUrl(file)}" target="_blank">Permanent URL</a>
                          </li>
                      `;
                      $('#fileList').append(listItem);
                  });
              });
          }

          // Generate permanent URL
          function generatePermanentUrl(fileName) {
              const bucketName = "{{ AWS_STORAGE_BUCKET_NAME }}";
              const endpointUrl = "{{ AWS_S3_ENDPOINT_URL }}";
              return `${endpointUrl}/${bucketName}/${fileName}`;
          }

          // Upload file
          $('#uploadForm').on('submit', function (e) {
              e.preventDefault();
              var formData = new FormData(this);
              $.ajax({
                  url: '/api/files/upload/',
                  type: 'POST',
                  data: formData,
                  processData: false,
                  contentType: false,
                  success: function (response) {
                      $('#uploadMessage').text(response.message);
                      loadFiles();
                  },
                  error: function (xhr) {
                      $('#uploadMessage').text('Error: ' + xhr.responseJSON.error);
                  }
              });
          });

          // Delete file
          $(document).on('click', '.delete-btn', function () {
              const fileName = $(this).data('file');
              $.post('/api/files/delete/', { file_name: fileName }, function (response) {
                  alert(response.message);
                  loadFiles();
              }).fail(function (xhr) {
                  alert('Error: ' + xhr.responseJSON.error);
              });
          });

          // Generate pre-signed URL
          $(document).on('click', '.presigned-btn', function () {
              const fileName = $(this).data('file');
              $.get('/api/files/presigned-url/', { file_name: fileName }, function (response) {
                  window.open(response.url, '_blank');
              }).fail(function (xhr) {
                  alert('Error: ' + xhr.responseJSON.error);
              });
          });

          // Initial file load
          loadFiles();
      });
  </script>
</body>
</html>