Skip to content

Implemented and added media chooser into the edit translation workflow for audio and video files #847

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ basepython = python3.12
deps =
wagtail>=5.2,<6.3
Django>=4.2,<5.2
wagtailmedia>=0.0.0,<2.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to move to dev dependencies

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the "wagtailmedia" dependency from tox.ini into pyproject.toml under the testing label.


commands_pre =
python {toxinidir}/testmanage.py makemigrations
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React, { FunctionComponent } from 'react';
import gettext from 'gettext';

interface MediaAPI {
result: {
id: number
title: string;
edit_url: string;
};
}

interface MediaChooserProps {
adminBaseUrl: string;
mediaId: number | null;
}

const MediaChooser: FunctionComponent<MediaChooserProps> = ({
adminBaseUrl,
mediaId,
}) => {
const [mediaInfo, setMediaInfo] = React.useState<MediaAPI | null>(null);

React.useEffect(() => {
setMediaInfo(null);
if (mediaId) {
fetch(`${adminBaseUrl}media/chooser/${mediaId}/`)
.then((response) => response.json())
.then(setMediaInfo);
}
}, [mediaId]);

// Render
let classNames = ['chooser', 'media-chooser'];
let inner;
if (mediaId) {
if (mediaInfo) {
inner = (
<div className="chosen">
<div className="preview-media">
<strong>{mediaInfo.result.title} (ID: {mediaInfo.result.id})</strong>
</div>

<ul className="actions" style={{ listStyleType: 'none' }}>
<li>
<a
href={`${mediaInfo.result.edit_url}`}
className="edit-link button button-small button-secondary"
target="_blank"
rel="noopener noreferrer"
>
{gettext('Edit this media')}
</a>
</li>
</ul>
</div>
);
} else {
inner = <p>{gettext('Fetching media information...')}</p>;
}
} else {
classNames.push('blank');

inner = (
<div className="unchosen">
<button
type="button"
className="button action-choose button-small button-secondary"
>
{gettext('Choose a media')}
</button>
</div>
);
}

return <div className={classNames.join(' ')}>{inner}</div>;
};

export default MediaChooser;
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export interface SnippetChooserWidget {
}

export interface OtherWidgets {
type: 'text' | 'image_chooser' | 'document_chooser' | 'unknown';
type: 'text' | 'image_chooser' | 'document_chooser' | 'media_chooser' | 'unknown';
}

export interface SegmentCommon {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Avatar from '../../../common/components/Avatar';

import PageChooser from '../../../common/components/PageChooser';
import ImageChooser from '../../../common/components/ImageChooser';
import MediaChooser from '../../../common/components/MediaChooser';
import DocumentChooser from '../../../common/components/DocumentChooser';
import SnippetChooser from '../../../common/components/SnippetChooser';

Expand Down Expand Up @@ -705,7 +706,38 @@ const EditorSynchronisedValueSegment: FunctionComponent<
imageId={(override && override.value) || segment.value}
/>
);
} else if (widget.type == 'document_chooser') {
} else if (widget.type == 'media_chooser') {
const onClickChangeMedia = () => {
return (window as any).ModalWorkflow({
url: (window as any).chooserUrls.mediaChooser,
onload: (window as any).MEDIA_CHOOSER_MODAL_ONLOAD_HANDLERS,
responses: {
mediaChosen: function (responseData: any) {
saveOverride(
segment,
responseData.id,
csrfToken,
dispatch
);
},
},
});
};
if (!isLocked) {
buttons.push(
<ActionButton onClick={onClickChangeMedia}>
{gettext('Change media')}
</ActionButton>
)
}
value = (
<MediaChooser
adminBaseUrl={adminBaseUrl}
mediaId={(override && override.value) || segment.value}
/>
)
}
else if (widget.type == 'document_chooser') {
const onClickChangeDocument = () => {
(window as any).ModalWorkflow({
url: (window as any).chooserUrls.documentChooser,
Expand Down Expand Up @@ -993,7 +1025,6 @@ const EditorSegmentList: FunctionComponent<EditorSegmentListProps> = ({
);
}
);

return <SegmentList>{segmentRendered}</SegmentList>;
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
window.chooserUrls = {
imageChooser: "{% url "wagtailimages_chooser:choose" %}",
documentChooser: "{% url "wagtaildocs_chooser:choose" %}",
mediaChooser: "{% url "wagtailmedia:chooser" %}",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To make this work without the hard dependency, we'll need to move the chooserUrls logic to a template tag and conditionally add mediaChooser.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As suggested, I create a new template tag method which checks the wagtailmedia installation and adds the "wagtailmedia:chooser" conditionally into the translation templates. My local tests did work on test environment instance.

pageChooser: "{% url "wagtailadmin_choose_page" %}",
}
}
Expand All @@ -65,6 +66,8 @@
<script src="{% versioned_static 'wagtaildocs/js/document-chooser-modal.js' %}"></script>
<script src="{% versioned_static 'wagtaildocs/js/document-chooser.js' %}"></script>
<script src="{% versioned_static 'wagtailadmin/js/chooser-modal.js' %}"></script>
<script src="{% versioned_static 'wagtailmedia/js/media-chooser-modal.js' %}"></script>
<script src="{% versioned_static 'wagtailmedia/js/tabs.js' %}"></script>
<script src="{% versioned_static 'wagtailsnippets/js/snippet-chooser.js' %}"></script>
<script src="{% versioned_static 'wagtail_localize/js/wagtail-localize.js' %}"></script>
{% endblock %}
Expand Down
1 change: 1 addition & 0 deletions wagtail_localize/test/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"wagtail.documents",
"wagtail.images",
"wagtail.search",
"wagtailmedia",
"wagtail.admin",
"wagtail.api.v2",
"wagtail.contrib.routable_page",
Expand Down
4 changes: 4 additions & 0 deletions wagtail_localize/views/edit_translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from wagtail.snippets.models import get_snippet_models
from wagtail.snippets.permissions import get_permission_name, user_can_edit_snippet_type
from wagtail.utils.decorators import xframe_options_sameorigin_override
from wagtailmedia.blocks import AudioChooserBlock, VideoChooserBlock

from wagtail_localize.compat import DATE_FORMAT
from wagtail_localize.machine_translators import get_machine_translator
Expand Down Expand Up @@ -366,6 +367,9 @@ def widget_from_block(block, content_components=None):
elif isinstance(block, ImageChooserBlock):
return {"type": "image_chooser"}

elif isinstance(block, AudioChooserBlock) or isinstance(block, VideoChooserBlock):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrap in an if apps.is_installed("wagtail.embeds") and move the block imports inline

if apps.is_installed("wagtail.embeds"):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved the blocks into an inline import if the wagtailmedia has been installed. The code structure didn't change.

return {"type": "media_chooser"}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also, need to check whether the direct field (that is the ForeignKey) is to a media class and set the widget type for it as well in widget_from_field(), a la

elif issubclass(field.related_model, AbstractDocument):
return {"type": "document_chooser"}

Copy link
Author

@catilgan-nextension catilgan-nextension May 28, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment above - same procedure applied here.

elif isinstance(block, SnippetChooserBlock):
chooser_url = reverse(
f"wagtailsnippetchoosers_{block.target_model._meta.app_label}_{block.target_model._meta.model_name}:choose"
Expand Down