diff --git a/dev-requirements.txt b/dev-requirements.txt index da77ef2..ddd3914 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -7,3 +7,4 @@ isort==5.10.1 black==22.3.0 django-browser-reload==1.6.0 django-debug-toolbar==3.4.0 +types-requests==2.27.1 diff --git a/requirements.txt b/requirements.txt index 4b7df25..6ef90a1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,3 +6,4 @@ pygments==2.12.0 beautifulsoup4==4.9.3 lxml==4.9.0 more-itertools==8.13.0 +requests==2.27.1 diff --git a/website/contrib/unsplash/__init__.py b/website/contrib/unsplash/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website/contrib/unsplash/migrations/0001_initial.py b/website/contrib/unsplash/migrations/0001_initial.py new file mode 100644 index 0000000..17b14ad --- /dev/null +++ b/website/contrib/unsplash/migrations/0001_initial.py @@ -0,0 +1,32 @@ +# Generated by Django 4.0.5 on 2022-07-12 08:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [] # type: ignore + + operations = [ + migrations.CreateModel( + name="UnsplashPhoto", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "unsplash_id", + models.CharField(db_index=True, max_length=11, unique=True), + ), + ("data", models.JSONField()), + ], + ), + ] diff --git a/website/contrib/unsplash/migrations/__init__.py b/website/contrib/unsplash/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website/contrib/unsplash/models.py b/website/contrib/unsplash/models.py new file mode 100644 index 0000000..fb3c4fc --- /dev/null +++ b/website/contrib/unsplash/models.py @@ -0,0 +1,6 @@ +from django.db import models + + +class UnsplashPhoto(models.Model): + unsplash_id = models.CharField(unique=True, max_length=11, db_index=True) + data = models.JSONField() diff --git a/website/contrib/unsplash/utils.py b/website/contrib/unsplash/utils.py new file mode 100644 index 0000000..5c6c026 --- /dev/null +++ b/website/contrib/unsplash/utils.py @@ -0,0 +1,19 @@ +import requests +from django.conf import settings + + +def get_unsplash_photo(image_id: str) -> dict: + response = requests.get( + f"https://api.unsplash.com/photos/{image_id}", + headers={ + "Accept-Version": "v1", + "Authorization": f"Client-ID {settings.UNSPLASH_CLIENT_ID}", + }, + ) + + if response.status_code == 404: + raise ValueError(f"Unknown image {image_id}") + + response.raise_for_status() + + return response.json() diff --git a/website/contrib/unsplash/wagtail_hooks.py b/website/contrib/unsplash/wagtail_hooks.py new file mode 100644 index 0000000..5feb647 --- /dev/null +++ b/website/contrib/unsplash/wagtail_hooks.py @@ -0,0 +1,48 @@ +from typing import Type + +from django.core.exceptions import ValidationError +from wagtail.admin.forms.models import WagtailAdminModelForm +from wagtail.contrib.modeladmin.options import ModelAdmin, modeladmin_register +from wagtail.contrib.modeladmin.views import CreateView + +from .models import UnsplashPhoto +from .utils import get_unsplash_photo + + +class UnsplashPhotoCreateView(CreateView): + def get_form_class(self) -> WagtailAdminModelForm: + """ + Modify form class to validate unsplash id and save data to model1 + """ + EditHandlerForm: Type[ + WagtailAdminModelForm + ] = self.edit_handler.get_form_class() + + class CreateFormClass(EditHandlerForm): # type: ignore[valid-type,misc] + def clean(self) -> None: + cleaned_data = super().clean() + try: + self._unsplash_photo_data = get_unsplash_photo( + cleaned_data["unsplash_id"] + ) + except ValueError as e: + raise ValidationError(str(e)) + + def save(self, commit: bool = True) -> UnsplashPhoto: + self.instance.data = self._unsplash_photo_data + return super().save(commit) + + return CreateFormClass + + +@modeladmin_register +class UnsplashPhotoAdmin(ModelAdmin): + model = UnsplashPhoto + list_display = ["unsplash_id", "description"] + form_fields_exclude = ["data"] + search_fields = ["unsplash_id", "data__description"] + create_view_class = UnsplashPhotoCreateView + menu_icon = "image" + + def description(self, instance: UnsplashPhoto) -> str: + return instance.data["description"] diff --git a/website/settings.py b/website/settings.py index 4b0c793..f60e3b6 100644 --- a/website/settings.py +++ b/website/settings.py @@ -4,7 +4,11 @@ import environ BASE_DIR = Path(__file__).parent.parent -env = environ.Env(DEBUG=(bool, False), BASE_HOSTNAME=(str, "example.com")) +env = environ.Env( + DEBUG=(bool, False), + BASE_HOSTNAME=(str, "example.com"), + UNSPLASH_CLIENT_ID=(str, ""), +) # Read local secrets environ.Env.read_env(BASE_DIR / ".env") @@ -25,6 +29,7 @@ INSTALLED_APPS = [ "website.blog", "website.images", "website.contrib.code_block", + "website.contrib.unsplash", "wagtail.contrib.forms", "wagtail.contrib.redirects", "wagtail.contrib.modeladmin", @@ -153,6 +158,8 @@ WAGTAILEMBEDS_FINDERS = [ }, ] +UNSPLASH_CLIENT_ID = env("UNSPLASH_CLIENT_ID") + if DEBUG: # Add django-browser-reload