Подход 1: Прямое использование b2sdk

Этот подход дает вам полный контроль над процессом, но требует написания большего количества кода для взаимодействия с B2 API.

1. Установка SDK:

pip install b2sdk

2. Настройка в Django (settings.py): (Настройки проекта (settings.py))

Крайне не рекомендуется хранить ключи API прямо в settings.py. Используйте переменные окружения или другие методы управления секретами (например, python-dotenv, HashiCorp Vault).

# settings.py
import os
from dotenv import load_dotenv
 
load_dotenv() # Загружает переменные из файла .env
 
B2_ACCOUNT_ID = os.environ.get('B2_ACCOUNT_ID') # Это ваш Key ID
B2_APPLICATION_KEY = os.environ.get('B2_APPLICATION_KEY') # Это ваш Application Key (секретный)
B2_BUCKET_NAME = os.environ.get('B2_BUCKET_NAME') # Имя вашего бакета
 
# Убедитесь, что переменные загружены
if not all([B2_ACCOUNT_ID, B2_APPLICATION_KEY, B2_BUCKET_NAME]):
    # Логирование ошибки или выбрасывание исключения в зависимости от вашей политики
    print("Ошибка: Не все переменные окружения для Backblaze B2 установлены!")
    # raise ImproperlyConfigured("Не все переменные окружения для Backblaze B2 установлены!")
 

Создайте файл .env в корне вашего проекта (добавьте его в .gitignore!):

# .env
B2_ACCOUNT_ID=ВАШ_KEY_ID
B2_APPLICATION_KEY=ВАШ_APPLICATION_KEY
B2_BUCKET_NAME=имя-вашего-бакета

3. Логика загрузки файла (Пример в views.py):

Предположим, у вас есть модель и форма для загрузки изображения.

# models.py
from django.db import models
 
class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    # Храним не сам файл, а его имя (ключ) в B2 или полный URL
    profile_picture_b2_filename = models.CharField(max_length=255, blank=True, null=True)
    # Можно добавить метод для получения URL
    # def get_profile_picture_url(self):
    #    if self.profile_picture_b2_filename:
    #        # Здесь логика получения URL из B2 (см. ниже)
    #        pass
    #    return 'path/to/default/image.jpg'
 
# forms.py
from django import forms
 
class ProfilePictureForm(forms.Form):
    picture = forms.ImageField()
 
# views.py
import uuid
from django.shortcuts import render, redirect
from django.conf import settings
from b2sdk.v2 import B2Api, InMemoryAccountInfo
from .forms import ProfilePictureForm
from .models import UserProfile # Предполагаем, что модель связана с User
 
# --- Вспомогательная функция для инициализации B2 API ---
# (Лучше вынести в отдельный модуль/сервис и кешировать инстанс B2Api)
def get_b2_api():
    info = InMemoryAccountInfo()
    b2_api = B2Api(info)
    b2_api.authorize_account("production", settings.B2_ACCOUNT_ID, settings.B2_APPLICATION_KEY)
    return b2_api
 
def upload_profile_picture(request):
    if request.method == 'POST':
        form = ProfilePictureForm(request.POST, request.FILES)
        if form.is_valid():
            uploaded_file = request.FILES['picture']
            
            # 1. Сгенерировать уникальное имя файла для B2
            # Используем UUID чтобы избежать коллизий
            file_extension = os.path.splitext(uploaded_file.name)[1]
            unique_filename = f"profile_pics/{uuid.uuid4()}{file_extension}"
 
            try:
                # 2. Инициализировать B2 API
                b2_api = get_b2_api()
                bucket = b2_api.get_bucket_by_name(settings.B2_BUCKET_NAME)
 
                # 3. Загрузить файл
                # Читаем содержимое файла в память (для больших файлов могут быть нужны другие подходы)
                file_content = uploaded_file.read()
                content_type = uploaded_file.content_type
 
                uploaded_b2_file = bucket.upload_bytes(
                    data_bytes=file_content,
                    file_name=unique_filename, # Уникальное имя файла в бакете
                    content_type=content_type
                )
 
                # 4. Сохранить имя файла (или ID) в модели Django
                profile, created = UserProfile.objects.get_or_create(user=request.user)
                profile.profile_picture_b2_filename = uploaded_b2_file.file_name # Сохраняем имя файла B2
                profile.save()
 
                # Можно перенаправить пользователя или вернуть успешный ответ
                return redirect('profile_view') # Пример
 
            except Exception as e:
                # Обработка ошибок (логирование, сообщение пользователю)
                print(f"Ошибка загрузки в B2: {e}")
                # form.add_error(None, "Не удалось загрузить изображение.") # Добавить ошибку в форму
 
    else:
        form = ProfilePictureForm()
    
    return render(request, 'upload_form.html', {'form': form})
 

4. Получение URL для отображения в шаблоне:

Вам нужно будет получить URL файла из B2.

# В модели или во view
 
def get_b2_download_url(filename):
    if not filename:
        return None
    try:
        b2_api = get_b2_api() # Получаем инициализированный API
        download_url = b2_api.get_download_url_for_file_name(settings.B2_BUCKET_NAME, filename)
        return download_url
    except Exception as e:
        print(f"Ошибка получения URL из B2: {e}")
        return None
 
# Пример использования в модели
class UserProfile(models.Model):
    # ... поля ...
    profile_picture_b2_filename = models.CharField(max_length=255, blank=True, null=True)
 
    def get_profile_picture_url(self):
        return get_b2_download_url(self.profile_picture_b2_filename) or 'path/to/default/image.jpg'
 
# В шаблоне Django (template.html)
# <img src="{{ user_profile.get_profile_picture_url }}" alt="Profile Picture">

Плюсы:

  • Полный контроль над API B2.
  • Можно использовать все специфичные функции b2sdk.

Минусы:

  • Больше кода для написания и поддержки.
  • Нужно вручную обрабатывать загрузку в views.py.
  • Нужно реализовать получение URL.

Подход 2: Использование django-storages

Это более “Django-way”. Пакет django-storages предоставляет бэкенды для различных облачных хранилищ, включая Backblaze B2, и интегрируется с полями FileField и ImageField Django.

1. Установка:

pip install django-storages[b2]
# '[b2]' автоматически установит b2sdk как зависимость

2. Настройка в settings.py:

Добавьте storages в INSTALLED_APPS. Настройте параметры B2 и укажите django-storages использовать B2 как бэкенд для хранения файлов по умолчанию (или для конкретного поля).

# settings.py
INSTALLED_APPS = [
    # ... другие приложения
    'storages',
    # ... ваши приложения
]
 
STORAGES = {  
    "default": {  
        "BACKEND": "storages.backends.s3.S3Storage",  
        "OPTIONS": {  
            # --- Аутентификация ---  
            # Используем ключи из .env            # (Используем альтернативные имена из документации django-storages)            "access_key": B2_KEY_ID,  
            "secret_key": B2_APPLICATION_KEY,  
  
            # --- Настройки бакета и эндпоинта ---  
            "bucket_name": B2_BUCKET_NAME,  
            # endpoint_url КРАЙНЕ ВАЖЕН для B2 и других S3-совместимых!  
            "endpoint_url": "https://s3.us-east-005.backblazeb2.com",  # Например, 's3.us-east-005.backblazeb2.com'  
  
            # --- Настройки генерации URL и доступа ---            "querystring_auth": True,  # <-- Генерировать подписанные URL (для приватных бакетов)  
            "querystring_expire": 3600,  # Время жизни URL в секундах (1 час по умолчанию)  
            "signature_version": 's3v4',  # <-- ВАЖНО для B2!  
            "default_acl": None,  # B2 обычно не использует ACL как S3, лучше None или private  
  
            # --- Опционально, но полезно ---            # Имя региона может помочь boto3, хотя endpoint_url важнее            "region_name": 'us-east-005',  # Укажите ваш регион B2  
            "object_parameters": {  # Доп. параметры для ВСЕХ загружаемых файлов  
                'CacheControl': 'max-age=86400',  # Кэшировать на 1 день  
            },  
            "file_overwrite": False,  # Не перезаписывать файлы с тем же именем  
            "location": "media",  # Префикс (папка) внутри бакета для медиафайлов  
            "use_ssl": True,  
        },  
    },  
    "staticfiles": {  
        "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",  
    },  
}

3. Модель Django:

Теперь вы можете использовать стандартные FileField или ImageField. django-storages автоматически перехватит сохранение файла и загрузит его в B2.

# models.py
from django.db import models
from django.contrib.auth.models import User
 
class UserProfile(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    # Используем стандартный ImageField. Django-storages позаботится об остальном.
    profile_picture = models.ImageField(upload_to='profile_pics/', blank=True, null=True)
    # upload_to будет использоваться как префикс внутри AWS_LOCATION (если он задан)
    # или внутри корня бакета.

4. Формы и Представления (Forms & Views):

Ваш код в forms.py и views.py становится намного проще, как при работе с локальным хранилищем.

# forms.py
from django import forms
from .models import UserProfile
 
class ProfilePictureModelForm(forms.ModelForm): # Используем ModelForm
    class Meta:
        model = UserProfile
        fields = ['profile_picture']
 
# views.py
from django.shortcuts import render, redirect
from .forms import ProfilePictureModelForm
from .models import UserProfile
 
def upload_profile_picture_django_storages(request):
    profile, created = UserProfile.objects.get_or_create(user=request.user)
    if request.method == 'POST':
        form = ProfilePictureModelForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():
            form.save() # Django-storages автоматически загрузит файл в B2 здесь!
            return redirect('profile_view')
    else:
        form = ProfilePictureModelForm(instance=profile)
    
    return render(request, 'upload_form.html', {'form': form})

5. Отображение в шаблоне:

Доступ к URL файла осуществляется через стандартный атрибут .url поля ImageField или FileField.

<!-- template.html -->
{% if user_profile.profile_picture %}
    <img src="{{ user_profile.profile_picture.url }}" alt="Profile Picture">
{% else %}
    <img src="path/to/default/image.jpg" alt="Default Picture">
{% endif %}

django-storages автоматически сгенерирует правильный (возможно, подписанный, если AWS_QUERYSTRING_AUTH = True) URL для доступа к файлу в B2.

Плюсы:

  • Гораздо меньше кода для написания.
  • Используются стандартные механизмы Django (FileField, ImageField, ModelForm).
  • Легче переключиться на другое хранилище в будущем (просто изменив настройки).

Минусы:

  • Меньше прямого контроля над специфичными функциями B2 API (если они вам нужны).
  • Зависимость от сторонней библиотеки django-storages.

Рекомендация:

Для большинства случаев использования, особенно для стандартной загрузки пользовательских файлов, использование django-storages является предпочтительным подходом. Он значительно упрощает код и лучше интегрируется с Django. Прямое использование SDK может понадобиться для более сложных сценариев или если вам нужны функции B2, не поддерживаемые django-storages.

Не забудьте обработать ошибки, настроить CORS на вашем бакете B2 (если нужно загружать файлы напрямую из браузера) и следить за безопасностью ваших ключей API.

〰〰〰 𓆝 𓆟 𓆞 𓆝 𓆟 𓆝 𓆟 𓆞 〰〰〰