F(backend): add public media derivatives pipeline
Some checks failed
Backend CI/CD / test (push) Has been cancelled
Backend CI/CD / deploy (push) Has been cancelled

This commit is contained in:
2026-05-20 14:26:51 +03:30
parent 88b793ed9f
commit b4903f7cb1
18 changed files with 710 additions and 53 deletions

View File

@@ -3,6 +3,7 @@
from ninja import Schema, ModelSchema
from typing import Optional
from core.media import PREVIEW_VARIANT, THUMBNAIL_VARIANT, derivative_url
from apps.users.models import User
@@ -23,6 +24,8 @@ class UserLoginSchema(Schema):
class UserProfileSchema(ModelSchema):
profile_picture: Optional[str] = None
profile_picture_thumbnail_url: Optional[str] = None
profile_picture_preview_url: Optional[str] = None
student_id: Optional[str] = None
major: Optional[str] = None
university: Optional[str] = None
@@ -68,6 +71,18 @@ class UserProfileSchema(ModelSchema):
return request.build_absolute_uri(obj.profile_picture.url)
return None
@staticmethod
def resolve_profile_picture_thumbnail_url(obj, context):
request = context["request"]
url = derivative_url(obj.profile_picture, THUMBNAIL_VARIANT)
return request.build_absolute_uri(url) if url else None
@staticmethod
def resolve_profile_picture_preview_url(obj, context):
request = context["request"]
url = derivative_url(obj.profile_picture, PREVIEW_VARIANT)
return request.build_absolute_uri(url) if url else None
class UserListSchema(ModelSchema):
major: Optional[str] = None

View File

@@ -5,13 +5,13 @@ from django.contrib.auth import authenticate
from django.db.models import Q
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.core.files.storage import default_storage
from django.core.files.base import ContentFile
import uuid
import jwt
from ninja import Query, Router
from core.media import delete_image_derivatives
from apps.users.models import User, Major, University
from apps.users.tasks import send_verification_email, send_password_reset_email
from apps.users.api.schemas import (
@@ -254,10 +254,6 @@ def upload_profile_picture(request):
user = request.auth
# Delete old profile picture if exists
if user.profile_picture:
default_storage.delete(user.profile_picture.name)
# Save new profile picture
filename = f"profile_pictures/{user.id}_{uuid.uuid4().hex}.{file.name.split('.')[-1]}"
user.profile_picture.save(filename, ContentFile(file.read()))
@@ -270,7 +266,7 @@ def delete_profile_picture(request):
user = request.auth
if user.profile_picture:
default_storage.delete(user.profile_picture.name)
delete_image_derivatives(user.profile_picture, "profile_picture", delete_original=True)
user.profile_picture = None
user.save(update_fields=['profile_picture'])
@@ -400,4 +396,3 @@ def check_username_availability(request, username: str):
"""Check if a username is available for registration"""
exists = User.objects.filter(username=username).exists()
return {"exists": exists}

View File

@@ -5,6 +5,11 @@ from django.db import models
import uuid
from datetime import timedelta
from core.media import (
delete_image_derivatives_by_name,
get_image_previous_name,
safe_process_public_image,
)
from core.models import BaseModel
@@ -95,6 +100,8 @@ class User(AbstractUser, BaseModel):
self.save(update_fields=['password_reset_token', 'password_reset_token_expires_at'])
def save(self, *args, **kwargs):
previous_image_name = get_image_previous_name(self, "profile_picture")
current_image_name = self.profile_picture.name if self.profile_picture else None
send_verified_success = False
if self.pk is not None:
@@ -104,6 +111,17 @@ class User(AbstractUser, BaseModel):
super().save(*args, **kwargs)
if previous_image_name != current_image_name and previous_image_name:
delete_image_derivatives_by_name(
self.profile_picture.storage if self.profile_picture else None,
previous_image_name,
"profile_picture",
delete_original=True,
)
if previous_image_name != current_image_name and self.profile_picture:
safe_process_public_image(self.profile_picture, "profile_picture")
if send_verified_success:
try:
from apps.users.tasks import send_email_verified_success