diff --git a/requirements.txt b/requirements.txt index 6ef90a1..97207da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,3 +7,4 @@ beautifulsoup4==4.9.3 lxml==4.9.0 more-itertools==8.13.0 requests==2.27.1 +wagtail-generic-chooser==0.4.1 diff --git a/website/blog/migrations/0010_bloglistpage_hero_unsplash_photo_and_more.py b/website/blog/migrations/0010_bloglistpage_hero_unsplash_photo_and_more.py new file mode 100644 index 0000000..6e03bda --- /dev/null +++ b/website/blog/migrations/0010_bloglistpage_hero_unsplash_photo_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.0.5 on 2022-07-12 13:04 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("unsplash", "0001_initial"), + ("blog", "0009_alter_bloglistpage_body_alter_blogpostpage_body"), + ] + + operations = [ + migrations.AddField( + model_name="bloglistpage", + name="hero_unsplash_photo", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="unsplash.unsplashphoto", + ), + ), + migrations.AddField( + model_name="blogpostpage", + name="hero_unsplash_photo", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="unsplash.unsplashphoto", + ), + ), + ] diff --git a/website/common/migrations/0011_contentpage_hero_unsplash_photo_and_more.py b/website/common/migrations/0011_contentpage_hero_unsplash_photo_and_more.py new file mode 100644 index 0000000..9f45e66 --- /dev/null +++ b/website/common/migrations/0011_contentpage_hero_unsplash_photo_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 4.0.5 on 2022-07-12 13:04 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("unsplash", "0001_initial"), + ("common", "0010_alter_contentpage_body_alter_listingpage_body"), + ] + + operations = [ + migrations.AddField( + model_name="contentpage", + name="hero_unsplash_photo", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="unsplash.unsplashphoto", + ), + ), + migrations.AddField( + model_name="listingpage", + name="hero_unsplash_photo", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="unsplash.unsplashphoto", + ), + ), + ] diff --git a/website/common/models.py b/website/common/models.py index d9ffb95..722e005 100644 --- a/website/common/models.py +++ b/website/common/models.py @@ -10,6 +10,7 @@ from wagtail.images import get_image_model_string from wagtail.models import Page from website.common.utils import count_words +from website.contrib.unsplash.widgets import UnsplashPhotoChooser from .streamfield import add_heading_anchors, get_blocks, get_content_html from .utils import TocEntry, extract_text, get_table_of_contents, truncate_string @@ -39,11 +40,15 @@ class BaseContentMixin(models.Model): hero_image = models.ForeignKey( get_image_model_string(), null=True, blank=True, on_delete=models.SET_NULL ) + hero_unsplash_photo = models.ForeignKey( + "unsplash.UnsplashPhoto", null=True, blank=True, on_delete=models.SET_NULL + ) body = StreamField(get_blocks(), blank=True, use_json_field=True) content_panels = [ FieldPanel("subtitle"), FieldPanel("hero_image"), + FieldPanel("hero_unsplash_photo", widget=UnsplashPhotoChooser), FieldPanel("body"), ] diff --git a/website/contrib/unsplash/models.py b/website/contrib/unsplash/models.py index fb3c4fc..c1d725a 100644 --- a/website/contrib/unsplash/models.py +++ b/website/contrib/unsplash/models.py @@ -4,3 +4,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() + + def get_description(self) -> str: + return self.data["description"] diff --git a/website/contrib/unsplash/templates/unsplash/results.html b/website/contrib/unsplash/templates/unsplash/results.html new file mode 100644 index 0000000..71f9f66 --- /dev/null +++ b/website/contrib/unsplash/templates/unsplash/results.html @@ -0,0 +1,31 @@ +{# Adapted from generic_chooser/_results.html #} + +{% load i18n %} + + + + + + + + + + + + + {% for row in rows %} + + + + + {% endfor %} + +
{% trans "Unsplash ID" %}{% trans "Description" %}
+

{{ row.item.unsplash_id }}

+
+ {{ row.item.get_description }} +
+ +{% if is_paginated %} + {% include "generic_chooser/_ajax_pagination_nav.html" with items=page %} +{% endif %} diff --git a/website/contrib/unsplash/views.py b/website/contrib/unsplash/views.py new file mode 100644 index 0000000..b97eb80 --- /dev/null +++ b/website/contrib/unsplash/views.py @@ -0,0 +1,39 @@ +from generic_chooser.views import ( + ModelChooserCreateTabMixin, + ModelChooserMixin, + ModelChooserViewSet, +) + +from .models import UnsplashPhoto + + +class UnsplashPhotoCreateTabMixin(ModelChooserCreateTabMixin): + """ + Don't allow creation during creation + """ + + def get_form_class(self) -> None: + return None + + +class UnsplashPhotoChooserMixin(ModelChooserMixin): + results_template = "unsplash/results.html" + + def get_object_string(self, instance: UnsplashPhoto) -> str: + return instance.unsplash_id + + def get_row_data(self, item: UnsplashPhoto) -> dict: + item_data = super().get_row_data(item) + item_data["item"] = item + return item_data + + +class UnsplashPhotoChooserViewSet(ModelChooserViewSet): + icon = "image" + model = UnsplashPhoto + page_title = "Choose a photo" + per_page = 10 + order_by = "unsplash_id" + fields = ["unsplash_id", "data"] + create_tab_mixin_class = UnsplashPhotoCreateTabMixin + chooser_mixin_class = UnsplashPhotoChooserMixin diff --git a/website/contrib/unsplash/wagtail_hooks.py b/website/contrib/unsplash/wagtail_hooks.py index 5feb647..027a2ec 100644 --- a/website/contrib/unsplash/wagtail_hooks.py +++ b/website/contrib/unsplash/wagtail_hooks.py @@ -4,9 +4,11 @@ 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 wagtail.core import hooks from .models import UnsplashPhoto from .utils import get_unsplash_photo +from .views import UnsplashPhotoChooserViewSet class UnsplashPhotoCreateView(CreateView): @@ -45,4 +47,11 @@ class UnsplashPhotoAdmin(ModelAdmin): menu_icon = "image" def description(self, instance: UnsplashPhoto) -> str: - return instance.data["description"] + return instance.get_description() + + +@hooks.register("register_admin_viewset") +def register_person_chooser_viewset() -> UnsplashPhotoChooserViewSet: + return UnsplashPhotoChooserViewSet( + "unsplash_photo_chooser", url_prefix="unsplash-photo-chooser" + ) diff --git a/website/contrib/unsplash/widgets.py b/website/contrib/unsplash/widgets.py new file mode 100644 index 0000000..3cba351 --- /dev/null +++ b/website/contrib/unsplash/widgets.py @@ -0,0 +1,15 @@ +from generic_chooser.widgets import AdminChooser + +from .models import UnsplashPhoto + + +class UnsplashPhotoChooser(AdminChooser): + choose_one_text = "Choose a photo" + choose_another_text = "Choose another photo" + show_edit_link = False + show_create_link = False + model = UnsplashPhoto + choose_modal_url_name = "unsplash_photo_chooser:choose" + + def get_title(self, instance: UnsplashPhoto) -> str: + return instance.unsplash_id diff --git a/website/settings.py b/website/settings.py index f60e3b6..15b07c0 100644 --- a/website/settings.py +++ b/website/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = [ "wagtail", "modelcluster", "taggit", + "generic_chooser", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes",