Skip to content

Commit

Permalink
Add Wagtail 6.0 compatibility (#65)
Browse files Browse the repository at this point in the history
  • Loading branch information
zerolab authored Jan 31, 2024
2 parents 542c9e6 + 44d4196 commit 8f7126b
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 38 deletions.
25 changes: 24 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
15 changes: 13 additions & 2 deletions tests/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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"),
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion tox.ini
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
30 changes: 29 additions & 1 deletion wagtail_airtable/mixins.py
Original file line number Diff line number Diff line change
@@ -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__)
Expand Down Expand Up @@ -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
11 changes: 11 additions & 0 deletions wagtail_airtable/templates/wagtail_airtable/_import_button.html
Original file line number Diff line number Diff line change
@@ -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 %}
<form action="{{ button.url }}" method="post">
{% csrf_token %}
<input type="hidden" name="model" value="{{ model_opts.label }}" />
<input type="hidden" name="next" value="{{ next }}" />
<button type="submit" aria-label="{{ action_label }}" data-controller="w-tooltip" data-w-tooltip-content-value="{{ action_label }}" class="w-header-button" style="background-color: inherit">
{% icon name="cog" %}
</button>
</form>
71 changes: 38 additions & 33 deletions wagtail_airtable/templates/wagtailsnippets/snippets/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
<form action="{% url 'airtable_import_listing' %}" method="post">
{% csrf_token %}
<input type="hidden" name="model" value="{{ model_opts.label }}" />
<input type="hidden" name="next" value="{{ request.path }}" />
<button type="submit" class="button button-longrunning" data-controller="w-progress" data-action="w-progress#activate" data-w-progress-active-value="{% trans "Importing..." %}">
{% icon name="spinner" %}
<em data-w-progress-target="label">{% blocktrans with snippet_type_name=model_opts.verbose_name_plural %}Import {{ snippet_type_name }}{% endblocktrans %}</em>
</button>
</form>
{% 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 %}
<form action="{% url 'airtable_import_listing' %}" method="post">
{% csrf_token %}
<input type="hidden" name="model" value="{{ model_opts.label }}" />
<input type="hidden" name="next" value="{{ request.path }}" />
<button type="submit" class="button button-longrunning" data-controller="w-progress" data-action="w-progress#activate" data-w-progress-active-value="{% trans "Importing..." %}">
{% icon name="spinner" %}
<em data-w-progress-target="label">{% blocktrans with snippet_type_name=model_opts.verbose_name_plural %}Import {{ snippet_type_name }}{% endblocktrans %}</em>
</button>
</form>
{% 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 %}
<div class="nice-padding{% if filters %} filterable{% endif %}">
<div id="listing-results" class="snippets">
{% include "wagtailsnippets/snippets/index_results.html" %}
{% 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 %}
<div class="nice-padding{% if filters %} filterable{% endif %}">
<div id="listing-results" class="snippets">
{% include "wagtailsnippets/snippets/index_results.html" %}
</div>
{% if filters %}
{% include "wagtailadmin/shared/filters.html" %}
{% endif %}
{% trans "Select all snippets in listing" as select_all_text %}
{% include 'wagtailadmin/bulk_actions/footer.html' with select_all_obj_text=select_all_text app_label=model_opts.app_label model_name=model_opts.model_name objects=page_obj %}
</div>
{% if filters %}
{% include "wagtailadmin/shared/filters.html" %}
{% endif %}
{% trans "Select all snippets in listing" as select_all_text %}
{% include 'wagtailadmin/bulk_actions/footer.html' with select_all_obj_text=select_all_text app_label=model_opts.app_label model_name=model_opts.model_name objects=page_obj %}
</div>
{% else %}
{{ block.super }}
{% endif %}
{% endblock %}
11 changes: 11 additions & 0 deletions wagtail_airtable/templatetags/wagtail_airtable_tags.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
from django.conf import settings
from django.urls import reverse

from wagtail import VERSION as WAGTAIL_VERSION


register = template.Library()


Expand Down Expand Up @@ -41,3 +44,11 @@ def wagtail_route(name, label, model, *args) -> str:
url = reverse(name, args=args)

return url


@register.simple_tag
def wagtail_major_version() -> int:
"""
Returns the major version of Wagtail as an integer.
"""
return WAGTAIL_VERSION[0]

0 comments on commit 8f7126b

Please sign in to comment.