-
Notifications
You must be signed in to change notification settings - Fork 14
feat(user): add user profile picture update functionality #306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| from rest_framework import serializers | ||
|
|
||
|
|
||
| ALLOWED_IMAGE_TYPES = ["image/jpeg", "image/png", "image/webp"] | ||
| MAX_FILE_SIZE = 5 * 1024 * 1024 | ||
|
|
||
|
|
||
| class UpdateProfileSerializer(serializers.Serializer): | ||
| picture = serializers.FileField(required=True) | ||
|
|
||
| def validate_picture(self, value): | ||
| if value.content_type not in ALLOWED_IMAGE_TYPES: | ||
| raise serializers.ValidationError(f"Invalid file type. Allowed: {', '.join(ALLOWED_IMAGE_TYPES)}") | ||
|
|
||
| if value.size > MAX_FILE_SIZE: | ||
| raise serializers.ValidationError("File size must be under 5MB") | ||
|
|
||
| return value |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| import io | ||
| import os | ||
|
|
||
| from cloudinary import uploader | ||
| import cloudinary | ||
|
|
||
| from todo.exceptions.auth_exceptions import APIException | ||
|
|
||
|
|
||
| class CloudinaryService: | ||
| @staticmethod | ||
| def _require_config() -> tuple[str, str, str]: | ||
| cloud_name = os.getenv("CLOUDINARY_CLOUD_NAME") | ||
| api_key = os.getenv("CLOUDINARY_API_KEY") | ||
| api_secret = os.getenv("CLOUDINARY_API_SECRET") | ||
|
|
||
| if not cloud_name or not api_key or not api_secret: | ||
| raise APIException( | ||
| "Cloudinary is not configured. Set CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET." | ||
| ) | ||
|
|
||
| return str(cloud_name), str(api_key), str(api_secret) | ||
|
Comment on lines
+4
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: In the Cloudinary Python SDK, credentials can be configured in two primary ways: using the CLOUDINARY_URL environment variable or explicitly via cloudinary.config. The CLOUDINARY_URL environment variable is the recommended and most convenient method, especially for security. It follows the format cloudinary://api_key:api_secret@cloud_name and can include additional query parameters for other config options (e.g., ?secure=true). The SDK automatically detects and parses this variable upon import/initialization if present, populating the config with cloud_name, api_key, api_secret, and other params. No explicit config call is needed for basic setup. Use tools like python-dotenv to load it from a .env file. Example: # .env CLOUDINARY_URL=cloudinary://your_api_key:your_api_secret@your_cloud_name # In code from dotenv import load_dotenv load_dotenv import cloudinary # Credentials are auto-loaded; verify with: print(cloudinary.config.cloud_name) Alternatively, use cloudinary.config to set credentials programmatically, either directly or from other env vars. This overrides or supplements the auto-loaded config. Example: import cloudinary import os cloudinary.config( cloud_name="your_cloud_name", api_key=os.getenv("CLOUDINARY_API_KEY"), api_secret=os.getenv("CLOUDINARY_API_SECRET"), secure=True ) Both methods support all config params (e.g., secure, private_cdn). CLOUDINARY_URL is preferred for production as it centralizes secrets in env vars. Call config before importing uploader/api modules for full functionality. The global config is a singleton updated by both approaches. Citations:
🏁 Script executed: cat -n todo/services/cloudinary_service.pyRepository: RealDevSquad/todo-backend Length of output: 1764 🏁 Script executed: rg "cloudinary\.config" --type pyRepository: RealDevSquad/todo-backend Length of output: 51 🏁 Script executed: rg "CLOUDINARY_URL" --type pyRepository: RealDevSquad/todo-backend Length of output: 51 Critical: Cloudinary is not configured —
At line 32, Proposed fix: Call Example fix `@staticmethod`
- def _require_config() -> tuple[str, str, str]:
+ def _require_config() -> None:
cloud_name = os.getenv("CLOUDINARY_CLOUD_NAME")
api_key = os.getenv("CLOUDINARY_API_KEY")
api_secret = os.getenv("CLOUDINARY_API_SECRET")
if not cloud_name or not api_key or not api_secret:
raise APIException(
"Cloudinary is not configured. Set CLOUDINARY_CLOUD_NAME, CLOUDINARY_API_KEY, and CLOUDINARY_API_SECRET."
)
-
- return str(cloud_name), str(api_key), str(api_secret)
+ cloudinary.config(
+ cloud_name=cloud_name,
+ api_key=api_key,
+ api_secret=api_secret,
+ secure=True,
+ )Alternatively, move the configuration call to application startup to avoid reconfiguring on every upload. 🧰 Tools🪛 GitHub Actions: Tests[error] 5-5: ruff check reported unused import: 🤖 Prompt for AI Agents✅ Confirmed as addressed by @AnujChhikara
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. check now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: Length of output: 2016
One optional improvement: consider adding cloudinary.config(
cloud_name=cloud_name,
api_key=api_key,
api_secret=api_secret,
secure=True, # ensures secure_url is always HTTPS
)But that's a non-critical enhancement — the authentication issue is fully addressed. ✅ If you found this review helpful, would you consider giving us a shout-out on X? Thank you for using CodeRabbit!
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @AnujChhikara can you try adding secure=True as well in config? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| @staticmethod | ||
| def _configure() -> None: | ||
| cloud_name, api_key, api_secret = CloudinaryService._require_config() | ||
| cloudinary.config( | ||
| cloud_name=cloud_name, | ||
| api_key=api_key, | ||
| api_secret=api_secret, | ||
| secure=True, | ||
| ) | ||
|
|
||
| @classmethod | ||
| def upload_image( | ||
| cls, | ||
| *, | ||
| file_data: bytes, | ||
| user_id: str, | ||
| image_name: str, | ||
| ) -> str: | ||
| cls._configure() | ||
|
|
||
| if not image_name.strip(): | ||
| raise APIException("imageName must be a non-empty string") | ||
|
|
||
| upload_folder = f"todo/users/{user_id}" | ||
| public_id = image_name.strip() | ||
|
|
||
| file_obj = io.BytesIO(file_data) | ||
|
|
||
| result = uploader.upload( | ||
| file_obj, | ||
| public_id=public_id, | ||
| folder=upload_folder, | ||
| overwrite=True, | ||
| resource_type="image", | ||
| ) | ||
|
|
||
| return result["secure_url"] | ||
Uh oh!
There was an error while loading. Please reload this page.