wagtail/wagtail/images/views/chooser.py

341 wiersze
12 KiB
Python

from django.conf import settings
from django.shortcuts import get_object_or_404
from django.template.loader import render_to_string
from django.urls import path, reverse
from django.utils.functional import cached_property
from django.utils.http import urlencode
from django.utils.translation import gettext_lazy as _
from django.views.generic.base import View
from wagtail.admin.auth import PermissionPolicyChecker
from wagtail.admin.modal_workflow import render_modal_workflow
from wagtail.admin.models import popular_tags_for_model
from wagtail.admin.views.generic.chooser import (
BaseChooseView,
ChooseResultsViewMixin,
ChooseViewMixin,
ChosenMultipleViewMixin,
ChosenResponseMixin,
ChosenViewMixin,
CreateViewMixin,
CreationFormMixin,
PreserveURLParametersMixin,
)
from wagtail.admin.viewsets.chooser import ChooserViewSet
from wagtail.images import get_image_model
from wagtail.images.formats import get_image_format
from wagtail.images.forms import ImageInsertionForm, get_image_form
from wagtail.images.permissions import permission_policy
from wagtail.images.utils import find_image_duplicates
permission_checker = PermissionPolicyChecker(permission_policy)
class ImageChosenResponseMixin(ChosenResponseMixin):
def get_chosen_response_data(self, image, preview_image_filter="max-165x165"):
"""
Given an image, return the json data to pass back to the image chooser panel
"""
response_data = super().get_chosen_response_data(image)
preview_image = image.get_rendition(preview_image_filter)
response_data["preview"] = {
"url": preview_image.url,
"width": preview_image.width,
"height": preview_image.height,
}
return response_data
class ImageCreationFormMixin(CreationFormMixin):
creation_tab_id = "upload"
create_action_label = _("Upload")
create_action_clicked_label = _("Uploading…")
permission_policy = permission_policy
def get_creation_form_class(self):
return get_image_form(self.model)
def get_creation_form_kwargs(self):
kwargs = super().get_creation_form_kwargs()
kwargs.update(
{
"user": self.request.user,
"prefix": "image-chooser-upload",
}
)
if self.request.method in ("POST", "PUT"):
kwargs["instance"] = self.model(uploaded_by_user=self.request.user)
return kwargs
class BaseImageChooseView(BaseChooseView):
template_name = "wagtailimages/chooser/chooser.html"
results_template_name = "wagtailimages/chooser/results.html"
per_page = 12
ordering = "-created_at"
construct_queryset_hook_name = "construct_image_chooser_queryset"
def get_object_list(self):
return (
permission_policy.instances_user_has_any_permission_for(
self.request.user, ["choose"]
)
.select_related("collection")
.prefetch_renditions("max-165x165")
)
def filter_object_list(self, objects):
tag_name = self.request.GET.get("tag")
if tag_name:
objects = objects.filter(tags__name=tag_name)
return super().filter_object_list(objects)
def get_filter_form(self):
FilterForm = self.get_filter_form_class()
return FilterForm(self.request.GET, collections=self.collections)
@cached_property
def collections(self):
collections = self.permission_policy.collections_user_has_permission_for(
self.request.user, "choose"
)
if len(collections) < 2:
return None
return collections
def get(self, request):
self.model = get_image_model()
return super().get(request)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
chosen_url_name = (
"wagtailimages_chooser:select_format"
if self.request.GET.get("select_format")
else "wagtailimages_chooser:chosen"
)
for image in context["results"]:
image.chosen_url = self.append_preserved_url_parameters(
reverse(chosen_url_name, args=(image.id,))
)
context["collections"] = self.collections
return context
class ImageChooseViewMixin(ChooseViewMixin):
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["popular_tags"] = popular_tags_for_model(self.model)
return context
class ImageChooseView(
ImageChooseViewMixin, ImageCreationFormMixin, BaseImageChooseView
):
pass
class ImageChooseResultsView(
ChooseResultsViewMixin, ImageCreationFormMixin, BaseImageChooseView
):
pass
class ImageChosenView(ChosenViewMixin, ImageChosenResponseMixin, View):
def get(self, request, *args, pk, **kwargs):
self.model = get_image_model()
return super().get(request, *args, pk, **kwargs)
class ImageChosenMultipleView(ChosenMultipleViewMixin, ImageChosenResponseMixin, View):
def get(self, request, *args, **kwargs):
self.model = get_image_model()
return super().get(request, *args, **kwargs)
class SelectFormatResponseMixin(PreserveURLParametersMixin):
def render_select_format_response(self, image, form):
action_url = self.append_preserved_url_parameters(
reverse("wagtailimages_chooser:select_format", args=(image.id,))
)
return render_modal_workflow(
self.request,
"wagtailimages/chooser/select_format.html",
None,
{"image": image, "form": form, "select_format_action_url": action_url},
json_data={"step": "select_format"},
)
class ImageUploadViewMixin(SelectFormatResponseMixin, CreateViewMixin):
def get(self, request):
self.model = get_image_model()
return super().get(request)
def post(self, request):
self.model = get_image_model()
self.form = self.get_creation_form()
if self.form.is_valid():
image = self.save_form(self.form)
duplicates = find_image_duplicates(
image=image,
user=request.user,
permission_policy=permission_policy,
)
existing_image = duplicates.first()
if existing_image:
return self.render_duplicate_found_response(
request, image, existing_image
)
if request.GET.get("select_format"):
insertion_form = ImageInsertionForm(
initial={"alt_text": image.default_alt_text},
prefix="image-chooser-insertion",
)
return self.render_select_format_response(image, insertion_form)
else:
# not specifying a format; return the image details now
return self.get_chosen_response(image)
else: # form is invalid
return self.get_reshow_creation_form_response()
def render_duplicate_found_response(self, request, new_image, existing_image):
next_step_url = (
"wagtailimages_chooser:select_format"
if request.GET.get("select_format")
else "wagtailimages_chooser:chosen"
)
choose_new_image_url = self.append_preserved_url_parameters(
reverse(next_step_url, args=(new_image.id,))
)
choose_existing_image_url = self.append_preserved_url_parameters(
reverse(next_step_url, args=(existing_image.id,))
)
cancel_duplicate_upload_action = (
f"{reverse('wagtailimages:delete', args=(new_image.id,))}?"
f"{urlencode({'next': choose_existing_image_url})}"
)
duplicate_upload_html = render_to_string(
"wagtailimages/chooser/confirm_duplicate_upload.html",
{
"new_image": new_image,
"existing_image": existing_image,
"confirm_duplicate_upload_action": choose_new_image_url,
"cancel_duplicate_upload_action": cancel_duplicate_upload_action,
},
request,
)
return render_modal_workflow(
request,
None,
None,
None,
json_data={
"step": "duplicate_found",
"htmlFragment": duplicate_upload_html,
},
)
class ImageUploadView(
ImageUploadViewMixin, ImageCreationFormMixin, ImageChosenResponseMixin, View
):
pass
class ImageSelectFormatView(SelectFormatResponseMixin, ImageChosenResponseMixin, View):
model = None
def get(self, request, image_id):
image = get_object_or_404(self.model, id=image_id)
initial = {"alt_text": image.default_alt_text}
initial.update(request.GET.dict())
# If you edit an existing image, and there is no alt text, ensure that
# "image is decorative" is ticked when you open the form
initial["image_is_decorative"] = initial["alt_text"] == ""
form = ImageInsertionForm(initial=initial, prefix="image-chooser-insertion")
return self.render_select_format_response(image, form)
def get_chosen_response_data(self, image):
format = get_image_format(self.form.cleaned_data["format"])
alt_text = self.form.cleaned_data["alt_text"]
response_data = super().get_chosen_response_data(
image, preview_image_filter=format.filter_spec
)
response_data.update(
{
"format": format.name,
"alt": alt_text,
"class": format.classname,
"html": format.image_to_editor_html(image, alt_text),
}
)
return response_data
def post(self, request, image_id):
image = get_object_or_404(get_image_model(), id=image_id)
self.form = ImageInsertionForm(
request.POST,
initial={"alt_text": image.default_alt_text},
prefix="image-chooser-insertion",
)
if self.form.is_valid():
return self.get_chosen_response(image)
else:
return self.render_select_format_response(image, self.form)
class ImageChooserViewSet(ChooserViewSet):
choose_view_class = ImageChooseView
choose_results_view_class = ImageChooseResultsView
chosen_view_class = ImageChosenView
chosen_multiple_view_class = ImageChosenMultipleView
create_view_class = ImageUploadView
select_format_view_class = ImageSelectFormatView
permission_policy = permission_policy
register_widget = False
preserve_url_parameters = ChooserViewSet.preserve_url_parameters + ["select_format"]
icon = "image"
per_page = getattr(settings, "WAGTAILIMAGES_CHOOSER_PAGE_SIZE", 10)
choose_one_text = _("Choose an image")
create_action_label = _("Upload")
create_action_clicked_label = _("Uploading…")
choose_another_text = _("Choose another image")
edit_item_text = _("Edit this image")
@property
def select_format_view(self):
return self.select_format_view_class.as_view(
model=self.model,
preserve_url_parameters=self.preserve_url_parameters,
)
def get_urlpatterns(self):
return super().get_urlpatterns() + [
path(
"<int:image_id>/select_format/",
self.select_format_view,
name="select_format",
),
]
viewset = ImageChooserViewSet(
"wagtailimages_chooser",
model=get_image_model(),
url_prefix="images/chooser",
)