Skip to content

Commit

Permalink
Update media moderation view (#4169)
Browse files Browse the repository at this point in the history
* Update media moderation view

Signed-off-by: Olga Bulat <[email protected]>

* Add has_sensitive_text

Signed-off-by: Olga Bulat <[email protected]>

* Move media report admins to a separate file

Signed-off-by: Olga Bulat <[email protected]>

* Move form html to the template

Signed-off-by: Olga Bulat <[email protected]>

* Update api/api/admin/media_report.py

Co-authored-by: Madison Swain-Bowden <[email protected]>

* Remove left-over templates from python file

Signed-off-by: Olga Bulat <[email protected]>

* Fix thumb and report url; tags display

Signed-off-by: Olga Bulat <[email protected]>

* Reorder code

Signed-off-by: Olga Bulat <[email protected]>

* Add changes from code review

Signed-off-by: Olga Bulat <[email protected]>

---------

Signed-off-by: Olga Bulat <[email protected]>
Co-authored-by: Madison Swain-Bowden <[email protected]>
  • Loading branch information
obulat and AetherUnbound authored Apr 30, 2024
1 parent 575f529 commit 1d875c8
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 45 deletions.
53 changes: 9 additions & 44 deletions api/api/admin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
from oauth2_provider.models import AccessToken

from api.admin.forms import UserPreferencesAdminForm
from api.admin.media_report import (
AudioReportAdmin,
ImageReportAdmin,
MediaReportAdmin,
MediaSubreportAdmin,
)
from api.admin.site import openverse_admin
from api.models import (
PENDING,
Audio,
AudioReport,
ContentProvider,
Expand Down Expand Up @@ -40,50 +45,10 @@ class AudioAdmin(admin.ModelAdmin):
search_fields = ("identifier",)


class MediaReportAdmin(admin.ModelAdmin):
list_display = ("id", "reason", "is_pending", "description", "created_at", "url")
list_filter = (
("decision", admin.EmptyFieldListFilter), # ~status, i.e. pending or moderated
"reason",
)
list_display_links = ("id",)
search_fields = ("description", "media_obj__identifier")
autocomplete_fields = ("media_obj",)
actions = None

def get_exclude(self, request, obj=None):
# ``identifier`` cannot be edited on an existing report.
if request.path.endswith("/change/"):
return ["media_obj"]

def get_readonly_fields(self, request, obj=None):
if obj is None:
return []
readonly_fields = [
"reason",
"description",
"media_obj_id",
"created_at",
]
# ``status`` cannot be changed on a finalised report.
if obj.status != PENDING:
readonly_fields.append("status")
return readonly_fields


admin.site.register(AudioReport, MediaReportAdmin)
admin.site.register(ImageReport, MediaReportAdmin)


class MediaSubreportAdmin(admin.ModelAdmin):
exclude = ("media_obj",)
search_fields = ("media_obj__identifier",)
readonly_fields = ("media_obj_id",)

def has_add_permission(self, *args, **kwargs):
"""Create ``_Report`` instances instead."""
return False
# Register the MediaReportAdmin classes and its subclasses

admin.site.register(AudioReport, AudioReportAdmin)
admin.site.register(ImageReport, ImageReportAdmin)

for klass in [
*AbstractSensitiveMedia.__subclasses__(),
Expand Down
163 changes: 163 additions & 0 deletions api/api/admin/media_report.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import logging

from django.conf import settings
from django.contrib import admin

from elasticsearch import NotFoundError
from elasticsearch_dsl import Search
from openverse_attribution.license import License

from api.models import PENDING


class MediaReportAdmin(admin.ModelAdmin):
change_form_template = "admin/api/media_report/change_form.html"
list_display = ("id", "reason", "is_pending", "description", "created_at", "url")
list_filter = (
("decision", admin.EmptyFieldListFilter), # ~status, i.e. pending or moderated
"reason",
)
list_display_links = ("id",)
list_select_related = ("media_obj",)
search_fields = ("description", "media_obj__identifier")
autocomplete_fields = ("media_obj",)
actions = None
media_type = None

def get_fieldsets(self, request, obj=None):
if obj is None:
return [
(
"Report details",
{"fields": ["status", "decision", "reason", "description"]},
),
("Media details", {"fields": ["media_obj"]}),
]
return [
(
"Report details",
{
"fields": [
"created_at",
"status",
"decision",
"reason",
"description",
"has_sensitive_text",
],
},
),
]

def get_exclude(self, request, obj=None):
# ``identifier`` cannot be edited on an existing report.
if request.path.endswith("/change/"):
return ["media_obj"]

def get_readonly_fields(self, request, obj=None):
if obj is None:
return []
readonly_fields = [
"created_at",
"reason",
"description",
"has_sensitive_text",
"media_obj_id",
]
# ``status`` cannot be changed on a finalised report.
if obj.status != PENDING:
readonly_fields.append("status")
return readonly_fields

@admin.display(description="Has sensitive text")
def has_sensitive_text(self, obj):
"""
Return `True` if the item cannot be found in the filtered index - which means the item
was filtered out due to text sensitivity.
"""
if not self.media_type or not obj:
return None

filtered_index = f"{settings.MEDIA_INDEX_MAPPING[self.media_type]}-filtered"
try:
search = (
Search(index=filtered_index)
.query("term", identifier=obj.media_obj.identifier)
.execute()
)
if search.hits:
return False
except NotFoundError:
logging.error(f"Could not resolve index {filtered_index}")
return None
return True

def get_other_reports(self, obj):
if not self.media_type or not obj:
return []

reports = (
self.model.objects.filter(media_obj__identifier=obj.media_obj.identifier)
.exclude(id=obj.id)
.order_by("created_at")
)
return reports

def _get_media_obj_data(self, obj):
tags_by_provider = {}
if obj.media_obj.tags:
for tag in obj.media_obj.tags:
tags_by_provider.setdefault(tag["provider"], []).append(tag["name"])
additional_data = {
"other_reports": self.get_other_reports(obj),
"media_obj": obj.media_obj,
"license": License(obj.media_obj.license).name(
obj.media_obj.license_version
),
"tags": tags_by_provider,
"description": obj.media_obj.meta_data.get("description", ""),
}
logging.info(f"Additional data: {additional_data}")
return additional_data

def change_view(self, request, object_id, form_url="", extra_context=None):
extra_context = extra_context or {}
extra_context["media_type"] = self.media_type

obj = self.get_object(request, object_id)
if obj and obj.media_obj:
additional_data = self._get_media_obj_data(obj)
extra_context = {**extra_context, **additional_data}

return super().change_view(
request,
object_id,
form_url,
extra_context=extra_context,
)

def render_change_form(
self, request, context, add=False, change=False, form_url="", obj=None
):
context.update({"add": add, "change": change})
return super().render_change_form(
request, context, add=add, change=change, form_url=form_url, obj=obj
)


class ImageReportAdmin(MediaReportAdmin):
media_type = "image"


class AudioReportAdmin(MediaReportAdmin):
media_type = "audio"


class MediaSubreportAdmin(admin.ModelAdmin):
exclude = ("media_obj",)
search_fields = ("media_obj__identifier",)
readonly_fields = ("media_obj_id",)

def has_add_permission(self, *args, **kwargs):
"""Create ``_Report`` instances instead."""
return False
6 changes: 5 additions & 1 deletion api/api/models/media.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ def clean(self):
f"with identifier '{self.media_obj_id}'."
)

def url(self, request=None) -> str:
def media_url(self, request=None) -> str:
"""
Build the URL of the media item. This uses ``reverse`` and
``request.build_absolute_uri`` to build the URL without having to worry
Expand All @@ -227,6 +227,10 @@ def url(self, request=None) -> str:
)
if request is not None:
url = request.build_absolute_uri(url)
return url

def url(self, request=None) -> str:
url = self.media_url(request)
return format_html(f"<a href={url}>{url}</a>")

@property
Expand Down
25 changes: 25 additions & 0 deletions api/api/templates/admin/api/media_report/attribution.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<p>
{% if media_obj.title %}
"{% if media_obj.url %}<a href="{{ media_obj.url }}">{% endif %}{{ media_obj.title }}{% if media_obj.url %}</a>{% endif %}"
{% else %}
{% if media_obj.url %}<a href="{{ media_obj.url }}">{% endif %}
This work
{% if media_obj.url %}</a>{% endif %}
{% endif %}
<span> by </span>
{% if media_obj.creator %}
{% if media_obj.creator_url %}
<a href="{{ media_obj.creator_url }}">{{ media_obj.creator }}</a>
{% else %}
{{ media_obj.creator }}
{% endif %}
{% endif %}
{% if media_obj.license %}
{% if media_obj.license.is_pd %}
is marked with
{% else %}
is licensed under
{% endif %}
{{ license }}
{% endif %}
</p>
Loading

0 comments on commit 1d875c8

Please sign in to comment.