diff --git a/README.md b/README.md index a00dce9..939021f 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ This package will attempt to match a model object against row in Airtable using * Install the package with `pip install wagtail-airtable` * Add `'wagtail_airtable'` to your project's `INSTALLED_APPS`. - * To enable the snippet-specific import button on the Snippet list view make sure `wagtail_airtable` is above `wagtail.snippets` in your `INSTALLED_APPS` + * On Wagtail 5.x, to enable the snippet-specific import button on the Snippet list view make sure `wagtail_airtable` is above `wagtail.snippets` in your `INSTALLED_APPS` * In your settings you will need to map Django models to Airtable settings. Every model you want to map to an Airtable sheet will need: * An `AIRTABLE_BASE_KEY`. You can find the base key in the [Airtable API docs](https://airtable.com/api) when you're signed in to Airtable.com * An `AIRTABLE_TABLE_NAME` to determine which table to connect to. @@ -252,6 +252,29 @@ The messaging will be off if you do this, so another setting has been made avail `WAGTAIL_AIRTABLE_PUSH_MESSAGE` - set this to whatever you'd like the messaging to be e.g. `WAGTAIL_AIRTABLE_PUSH_MESSAGE='Airtable save is happening in the background'` +### Adding an Import action to the snippet list view (Wagtail 6.x) + +As of Wagtail 6.0, the Import action is no longer automatically shown on the snippet listing view (although it is still available through Settings -> Airtable import). To add it back, first ensure that your snippet model is [registered with an explicit viewset](https://docs.wagtail.org/en/stable/topics/snippets/registering.html#using-register-snippet-as-a-function). Then, ensure that the index view for that viewset inherits from `SnippetImportActionMixin`: + +```python +from wagtail.snippets.models import register_snippet +from wagtail.snippets.views.snippets import IndexView, SnippetViewSet +from wagtail_airtable.mixins import SnippetImportActionMixin +from .models import Advert + + +class AdvertIndexView(SnippetImportActionMixin, IndexView): + pass + + +class AdvertViewSet(SnippetViewSet): + model = Advert + index_view_class = AdvertIndexView + +register_snippet(Advert, viewset=AdvertViewSet) +``` + + ### Trouble Shooting Tips #### Duplicates happening on import Ensure that your serializer matches your field definition *exactly*, and in cases of `CharField`'s that have `blank=True` or `null=True` setting `required=False` on the serializer is also important. diff --git a/tests/models.py b/tests/models.py index d7af988..96028e4 100644 --- a/tests/models.py +++ b/tests/models.py @@ -2,8 +2,9 @@ from wagtail.fields import RichTextField from wagtail.models import Page from wagtail.snippets.models import register_snippet +from wagtail.snippets.views.snippets import IndexView, SnippetViewSet -from wagtail_airtable.mixins import AirtableMixin +from wagtail_airtable.mixins import AirtableMixin, SnippetImportActionMixin class SimplePage(Page): @@ -14,7 +15,6 @@ class Publication(models.Model): title = models.CharField(max_length=30) -@register_snippet class Advert(AirtableMixin, models.Model): STAR_RATINGS = ( (1.0, "1"), @@ -77,6 +77,17 @@ def __str__(self): return self.title +class AdvertIndexView(SnippetImportActionMixin, IndexView): + pass + + +class AdvertViewSet(SnippetViewSet): + model = Advert + index_view_class = AdvertIndexView + +register_snippet(Advert, viewset=AdvertViewSet) + + @register_snippet class SimilarToAdvert(Advert): pass diff --git a/tox.ini b/tox.ini index c0332e0..a49feba 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] envlist = - python{3.8,3.9,3.10,3.11,3.12}-django4.2-wagtail5.2 + python{3.8,3.9,3.10,3.11,3.12}-django{4.2,5.0}-wagtail{5.2,6.0} [testenv] commands = python runtests.py @@ -14,4 +14,6 @@ basepython = deps = django4.2: Django>=4.2,<5.0 + django5.0: Django>=5.0,<5.1 wagtail5.2: wagtail>=5.2,<6.0 + wagtail6.0: wagtail>=6.0rc1,<6.1 diff --git a/wagtail_airtable/mixins.py b/wagtail_airtable/mixins.py index 4a64398..ad48576 100644 --- a/wagtail_airtable/mixins.py +++ b/wagtail_airtable/mixins.py @@ -1,14 +1,16 @@ import sys -from importlib import import_module from ast import literal_eval from logging import getLogger from airtable import Airtable from django.conf import settings from django.db import models +from django.urls import reverse from django.utils.functional import cached_property from requests import HTTPError +from wagtail.admin.widgets.button import Button + from .tests import MockAirtable logger = getLogger(__name__) @@ -362,3 +364,29 @@ def delete(self, *args, **kwargs): class Meta: abstract = True + + +class ImportButton(Button): + template_name = "wagtail_airtable/_import_button.html" + + def get_context_data(self, parent_context): + context = super().get_context_data(parent_context) + context["csrf_token"] = parent_context["csrf_token"] + context["model_opts"] = parent_context["model_opts"] + context["next"] = parent_context["request"].path + return context + + +class SnippetImportActionMixin: + # Add a new action to the snippet listing page to import from Airtable + @cached_property + def header_buttons(self): + buttons = super().header_buttons + if issubclass(self.model, AirtableMixin) and self.add_url: + buttons.append( + ImportButton( + "Import from Airtable", + url=reverse("airtable_import_listing") + ) + ) + return buttons diff --git a/wagtail_airtable/templates/wagtail_airtable/_import_button.html b/wagtail_airtable/templates/wagtail_airtable/_import_button.html new file mode 100644 index 0000000..c68f2c6 --- /dev/null +++ b/wagtail_airtable/templates/wagtail_airtable/_import_button.html @@ -0,0 +1,11 @@ +{% load i18n wagtailadmin_tags %} + +{% blocktranslate asvar action_label with snippet_type_name=model_opts.verbose_name_plural %}Import {{ snippet_type_name }}{% endblocktranslate %} +
diff --git a/wagtail_airtable/templates/wagtailsnippets/snippets/index.html b/wagtail_airtable/templates/wagtailsnippets/snippets/index.html index 43febba..56e984c 100644 --- a/wagtail_airtable/templates/wagtailsnippets/snippets/index.html +++ b/wagtail_airtable/templates/wagtailsnippets/snippets/index.html @@ -2,40 +2,45 @@ {% load i18n wagtailadmin_tags wagtail_airtable_tags %} {% block content %} - {% include 'wagtailadmin/shared/headers/slim_header.html' %} - {% fragment as base_action_locale %}{% if locale %}{% include 'wagtailadmin/shared/locale_selector.html' with theme="large" %}{% endif %}{% endfragment %} - {% fragment as action_url_add_snippet %}{% if can_add_snippet %}{% url view.add_url_name %}{% if locale %}?locale={{ locale.language_code }}{% endif %}{% endif %}{% endfragment %} - {% fragment as action_text_snippet %}{% blocktrans trimmed with snippet_type_name=model_opts.verbose_name %}Add {{ snippet_type_name }}{% endblocktrans %}{% endfragment %} + {% wagtail_major_version as wagtail_major_version %} + {% if wagtail_major_version < 6 %} + {% include 'wagtailadmin/shared/headers/slim_header.html' %} + {% fragment as base_action_locale %}{% if locale %}{% include 'wagtailadmin/shared/locale_selector.html' with theme="large" %}{% endif %}{% endfragment %} + {% fragment as action_url_add_snippet %}{% if can_add_snippet %}{% url view.add_url_name %}{% if locale %}?locale={{ locale.language_code }}{% endif %}{% endif %}{% endfragment %} + {% fragment as action_text_snippet %}{% blocktrans trimmed with snippet_type_name=model_opts.verbose_name %}Add {{ snippet_type_name }}{% endblocktrans %}{% endfragment %} - {% fragment as extra_actions %} - {% if view.list_export %} - {% include view.export_buttons_template_name %} - {% endif %} - {# begin wagtail-airtable customisation for Wagtail 5.2 #} - {% can_import_model model_opts.label as can_import_model %} - {% if can_add_snippet and can_import_model %} - - {% endif %} - {# end wagtail-airtable customisation #} - {% endfragment %} + {% fragment as extra_actions %} + {% if view.list_export %} + {% include view.export_buttons_template_name %} + {% endif %} + {# begin wagtail-airtable customisation for Wagtail 5.2 #} + {% can_import_model model_opts.label as can_import_model %} + {% if can_add_snippet and can_import_model %} + + {% endif %} + {# end wagtail-airtable customisation #} + {% endfragment %} - {% include 'wagtailadmin/shared/header.html' with title=model_opts.verbose_name_plural|capfirst icon=header_icon search_url=search_url base_actions=base_action_locale action_url=action_url_add_snippet action_icon="plus" action_text=action_text_snippet extra_actions=extra_actions search_results_url=index_results_url %} -