Skip to content

Commit

Permalink
Merge pull request #213 from 2077-Collective/feat/autosave-image-to-c…
Browse files Browse the repository at this point in the history
…loudinary

chore(feat): autosave thumbnail images to cloudinary
  • Loading branch information
losndu authored Dec 19, 2024
2 parents 174557f + 14014a8 commit e11c332
Show file tree
Hide file tree
Showing 11 changed files with 467 additions and 48 deletions.
108 changes: 106 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
aiodns==3.0.0
aiohttp==3.9.5
aiosignal==1.3.1
amqp==5.2.0
annotated-types==0.7.0
anyio==4.4.0
argcomplete==3.5.1
asgiref==3.8.1
attrs==24.1.0
bcrypt==4.0.1
Beaker==1.12.1
beautifulsoup4==4.12.3
billiard==4.2.0
blivet==3.10.0
blivet-gui==2.6.0
boto3==1.35.72
botocore==1.35.72
Brlapi==0.8.5
Brotli==1.1.0
bs4==0.0.2
celery==5.4.0
certifi==2024.7.4
Expand All @@ -14,61 +26,153 @@ click==8.1.7
click-didyoumean==0.3.1
click-plugins==1.1.1
click-repl==0.3.0
cloudinary==1.41.0
cron-descriptor==1.4.3
cryptography==43.0.0
cupshelpers==1.0
dasbus==1.7
dbus-python==1.3.2
distlib==0.3.8
distro==1.9.0
Django==5.0.8
django-admin-interface==0.28.8
django-celery-beat==2.6.0
django-ckeditor==6.7.1
django-ckeditor-5==0.2.13
django-cloudinary-storage==0.3.0
django-colorfield==0.11.0
django-cors-headers==4.4.0
django-filter==24.2
django-jazzmin==3.0.0
django-js-asset==2.2.0
django-shortcuts==1.6
django-sortedm2m==4.0.0
django-storages==1.14.4
django-timezone-field==7.0
django-tinymce==4.1.0
djangorestframework==3.15.2
dnf==4.22.0
drf-spectacular==0.27.2
fedora-third-party==0.10
file-magic==0.4.0
filelock==3.15.4
fros==1.1
frozenlist==1.4.1
gpg==1.23.2
h11==0.14.0
httpcore==1.0.5
httpx==0.27.0
humanize==3.13.1
idna==3.7
importlib-metadata==6.9.0
inflection==0.5.1
iso639==0.1.4
jaraco.classes==3.3.0
jeepney==0.8.0
Jinja2==3.1.4
jiter==0.8.2
jmespath==1.0.1
jsonschema==4.23.0
jsonschema-specifications==2023.12.1
keyring==24.3.1
kombu==5.4.0
openai==1.57.2
langtable==0.0.68
libcomps==0.1.20
libdnf==0.73.4
louis==3.28.0
lxml==5.1.0
Mako==1.2.3
MarkupSafe==2.1.3
more-itertools==10.1.0
multidict==6.0.5
mutagen==1.47.0
nftables==0.1
olefile==0.47
openai==1.57.4
packaging==23.2
Paste==3.7.1
pexpect==4.9.0
pid==2.2.3
pillow==10.4.0
platformdirs==4.2.2
ply==3.11
productmd==1.41
prompt_toolkit==3.0.47
proton-core==0.4.0
proton-keyring-linux==0.2.0
proton-vpn-api-core==0.38.2
proton-vpn-gtk-app==4.8.1
proton-vpn-network-manager==0.10.1
psutil==5.9.8
ptyprocess==0.7.0
pwquality==1.4.5
pycairo==1.25.1
pycares==4.3.0
pycparser==2.22
pycrypto==2.6.1
pycryptodomex==3.21.0
pycups==2.0.4
pydantic==2.10.3
pydantic_core==2.27.1
pyenchant==3.2.2
PyGObject==3.48.2
pykickstart==3.52
PyNaCl==1.5.0
pyOpenSSL==24.2.1
pyparted==3.13.0
PyQt5==5.15.10
PyQt5-sip==12.13.0
PySocks==1.7.1
python-augeas==1.1.0
python-crontab==3.2.0
python-dateutil==2.9.0.post0
python-decouple==3.8
python-dotenv==1.0.1
python-gnupg==0.5.0
python-meh==0.51
python-pam==2.0.2
python-slugify==8.0.4
pyudev==0.24.1
pyxdg==0.27
PyYAML==6.0.1
redis==5.0.8
referencing==0.35.1
regex==2024.9.11
requests==2.32.3
requests-file==2.0.0
requests-ftp==0.3.1
rpds-py==0.19.1
rpm==4.19.1.1
s3transfer==0.10.4
SecretStorage==3.3.3
selinux @ file:///builddir/build/BUILD/libselinux-3.7/src
sentry-sdk==2.17.0
sepolicy @ file:///builddir/build/BUILD/selinux-3.7/python/sepolicy
setools==4.5.1
setuptools==69.0.3
shtab==1.6.1
simpleaudio==1.0.4
simpleline==1.9.0
six==1.16.0
sniffio==1.3.1
sos==4.7.2
soupsieve==2.6
sqlparse==0.5.1
systemd-python==235
Tempita==0.5.2
text-unidecode==1.3
torbrowser-launcher==0.3.7
tqdm==4.67.1
trash-cli==0.22.10.20
typing_extensions==4.12.2
tzdata==2024.1
uritemplate==4.1.1
urllib3==2.2.2
vine==5.1.0
virtualenv==20.26.3
wcwidth==0.2.13
websockets==12.0
whitenoise==6.7.0
xkbregistry==0.3
yarl==1.9.4
yt-dlp==2024.9.27
zipp==3.17.0
8 changes: 8 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ DB_PORT=5432

SITE_URL='http://localhost:8000'

# OpenAI
OPENAI_API_KEY=xxxxxx

# Cloudinary
CLOUDINARY_CLOUD_NAME=xxxx
CLOUDINARY_API_KEY=123456789
CLOUDINARY_API_SECRET=xxxxxxxxxxxxx

# Django smtp
EMAIL_HOST = 'smtp.gmail.com' # Example using Gmail
EMAIL_HOST_USER = 'enter your email'
Expand Down
59 changes: 59 additions & 0 deletions server/apps/research/migrations/0018_alter_article_thumb.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import apps.research.models.article
import cloudinary.models
import cloudinary.uploader
from django.db import migrations
from django.core.files.storage import default_storage
import os


def transfer_to_cloudinary(apps, schema_editor):
Article = apps.get_model("research", "Article")

for instance in Article.objects.all():
if instance.thumb:
try:
if hasattr(instance.thumb, "public_id"):
continue

if hasattr(instance.thumb, "url"):
file_path = instance.thumb.path

if default_storage.exists(file_path):
with open(file_path, "rb") as file:
upload_result = cloudinary.uploader.upload(
file, folder="coverImage", resource_type="image"
)

instance.thumb = upload_result["public_id"]
instance.save()

# delete the local file
# default_storage.delete(file_path)

except Exception as e:
print(f"Info for Article {instance.id}: {str(e)}")


def reverse_transfer(apps, schema_editor):
pass


class Migration(migrations.Migration):

dependencies = [
("research", "0017_article_gpt_summary_alter_article_summary"),
]

operations = [
migrations.AlterField(
model_name="article",
name="thumb",
field=cloudinary.models.CloudinaryField(
blank=True,
default=apps.research.models.article.get_default_thumb,
max_length=255,
verbose_name="image",
),
),
migrations.RunPython(transfer_to_cloudinary, reverse_transfer),
]
14 changes: 11 additions & 3 deletions server/apps/research/models/article.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@
from bs4 import BeautifulSoup
import uuid
from django.db import transaction
from cloudinary.models import CloudinaryField

def get_default_thumb():
return f"{settings.MEDIA_URL}images/2077-Collective.png"
return "v1734517759/v4_article_cover_slashing_hhf6tz"

class Article(BaseModel):
"""Model for articles."""
Expand All @@ -32,7 +33,7 @@ class Article(BaseModel):
authors = models.ManyToManyField(Author, blank=True, related_name='articles')
slug = models.SlugField(max_length=255, blank=True, db_index=True)
categories = models.ManyToManyField(Category, blank=True, related_name='articles')
thumb = models.ImageField(upload_to='images/', default=get_default_thumb, blank=True)
thumb = CloudinaryField('image', folder='coverImage', default=get_default_thumb, blank=True)
views = models.PositiveBigIntegerField(default=0)
status = models.CharField(max_length=10, choices=options, default='draft', db_index=True)
scheduled_publish_time = models.DateTimeField(null=True, blank=True, db_index=True)
Expand Down Expand Up @@ -120,7 +121,7 @@ def save(self, *args, **kwargs):
self.slug = self.generate_unique_slug()

"""Override the save method to track slug changes."""
if self.pk: # If this is an existing article
if self.pk:
try:
old_instance = Article.objects.get(pk=self.pk)
# Generate new slug first
Expand All @@ -143,6 +144,13 @@ def save(self, *args, **kwargs):

if self.scheduled_publish_time and self.status == 'draft' and timezone.now() >= self.scheduled_publish_time:
self.status = 'ready'

if self.thumb and hasattr(self.thumb, 'public_id'):
try:
if not self.thumb.public_id:
raise ValidationError("Failed to upload image to Cloudinary")
except Exception as e:
raise ValidationError(f"Image upload failed: {str(e)}") from e

super().save(*args, **kwargs)

Expand Down
Loading

0 comments on commit e11c332

Please sign in to comment.