-
Notifications
You must be signed in to change notification settings - Fork 202
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
Update media moderation view #4169
Conversation
aab8c29
to
d1d6158
Compare
a9e8e3d
to
bbb7633
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is fantastic! I was able to test this locally and have things behave exactly as expected. I have a few comments about how we're setting the domain that might require changes.
api/api/admin/__init__.py
Outdated
openverse_url = ( | ||
f"https://openverse.org/{self.media_type}/{obj.media_obj.identifier}" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this use settings.CANONICAL_ORIGIN
instead?
https://github.com/WordPress/openverse/blob/520cd7ab15897f33f16cca344b03ae15a687318d/api/conf/settings/security.py#L23-L22
Edit: realizing now this is not the API URL but rather the frontend one - I think it would be good for us to have a similar setting we could use for this, that way we could also point this to a locally running version of the frontend.
api/api/admin/__init__.py
Outdated
) | ||
return mark_safe( | ||
f"<p>{create_link(obj.media_obj.foreign_landing_url, obj.media_obj.provider)}, " | ||
f'{create_link(openverse_url, "openverse.org")}' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same note for here (or different language for the link name might be better).
api/api/admin/__init__.py
Outdated
Use the image thumbnail if available, else replace with the direct image url. | ||
""" | ||
if obj.media_obj.url: | ||
thumb_url = f"https://api.openverse.engineering{obj.media_url()}thumb/" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We will definitely want to use settings.CANONICAL_DOMAIN
here!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should use request.build_absolute_uri
and reverse
, actually. Together they make both settings references and string interpolation completely unnecessary, and inherently validates that routes actually exist (e.g., if they got renamed or something caused a typo). We can avoid build_absolute_uri
if we just use reverse and rely on the relative link, which you've done elsewhere in this PR, Olga. The relative link should theoretically work perfectly fine.
api/api/admin/__init__.py
Outdated
creator_string = f" by {creator}" if creator else "" | ||
return f"{title}{creator_string}" | ||
|
||
def media_display(self, obj): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be an @abstractmethod
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Requesting changes to move the HTML building into the templates, rather than using string concatenation.
Things look good from a functionality perspective, so it's really just moving the HTML code into a place that is easier to read, reason about, and maintain, plus is the canonical "Django-way".
I've left a couple other clarifying comments about some things I didn't understand about the implementation, and +1'd some of Madison's comments, but overall looking very good.
api/api/admin/__init__.py
Outdated
if not reports: | ||
return "" | ||
result = "<table><thead><tr><th>Date</th><th>Report reason</th><th>Status</th><th>Report link</th></tr></thead><tbody>" | ||
for report in reports: | ||
report_link_html = f'<a href="{reverse("admin:api_imagereport_change", args=[report.id])}">{report.id}</a>' | ||
created_at = report.created_at.strftime("%Y-%m-%d %H:%M:%S") | ||
report_row = f"<tr><td>{created_at}</td><td>{report.reason}</td><td>{report.status}</td><td>{report_link_html}</td></tr>" | ||
result += report_row | ||
result += "</tbody></table>" | ||
return mark_safe(result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is really not the right way to do things in Django. Most important issue being that this string concatenation approach does not get any benefit from Django's template cache. A particularly important benefit, regarding maintainability, is the separation of view from model, which is essential for making the data model an understandable and distinctly separate thing from the presentation. Extracting all the presentation logic in the template forces us to write admin pages that cleanly silo the data model into template context, rather than creating functions like this where the two are meshed together such that it makes it unnecessarily harder to understand. That's even the more important benefit, in our context, is to avoid this approach because it is so much harder to read, reason about, and maintain than Django templates.
Please update the change_form template you've added in this PR to render the table in the template. It's also worth checking out Django's form building tools, if you're not familiar with them, as there are canonical ways to do just about everything: https://docs.djangoproject.com/en/5.0/topics/forms/#the-template and https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#overriding-admin-templates (the latter of which you've almost definitely seen, given the template override you've already added, but including it just in case and for other reviewers who might not be familiar with Django, like Madison).
api/api/admin/__init__.py
Outdated
thumb_url = f"https://api.openverse.engineering{obj.media_url()}thumb/" | ||
return mark_safe( | ||
f'<div class="container"><img src="{thumb_url}" alt="Media Image" class="blur-image" height="300" ' | ||
f'onclick="toggleBlur(this)" onerror="replace(this, \'{obj.media_obj.url}\')" style="cursor: pointer;" />' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is replace doing here in the onerror
? The implementation of the function doesn't clear anything up to me. Why would the img
element's src
ever need to change to point to a non-image URL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By default, we use the thumbnail url. However, especially locally, this URL might not contain an image: some of the sample data identifiers do not exist in the production database. So, for local testing, we try loading the thumbnail url as the image src. If that cannot be loaded due to image not being available at that URL, we try loading the image's direct URL, which most likely exists.
This behavior mirrors the behavior of the frontend single result image, where we try to load the direct image URL, and if that is not available - we load the thumbnail.
We should update the sample data to only contain data that exists in production, even if the links are dead - at least we should have the items for all identifiers in the sample data - but that's a different issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ohhhh I see, I totally misread obj.media_obj.url
, and interpreted it as the API route for the image rather than the upstream image URL.
Okay, so I understand this now, thanks for clarifying. The other question is then... do we need to do anything to make sure this works as expected for both images and audio (audio thumbnail doesn't proxy .url
, I think? 🤔)
6737f09
to
e5ecb1a
Compare
Drafting this PR to address @sarayourfriend 's change requests. |
e5ecb1a
to
d9c67c2
Compare
d9c67c2
to
2f37695
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some small issues/bugs I've noticed, but this is really close!
api/api/admin/media_report.py
Outdated
additional_data["thumb_url"] = ( | ||
f"https://api.openverse.engineering{obj.media_url()}thumb/" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just noting that I think we'll still want to use the methods Sara described here for this URL building.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If done in the template, it's a bit less fussy to just use the url
filter: https://docs.djangoproject.com/en/5.0/ref/templates/builtins/#url
<div class="flex-container"> | ||
<label>Tags</label> | ||
<div class="readonly"> | ||
<p>{{ tags }}</div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit of a nit, but this just shares the dictionary that's built up of the tags. Would it be possible to render this in a more friendly manner, separating each group of tags by source?
1791a80
to
38726b3
Compare
I refactored the changes to extract the data in the python code, and render it only in the template. The urls are also built in the template. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two small notes, this is very close!
</tbody> | ||
</table> | ||
{% else %} | ||
<p>No other reports for <a href="{{reverse_media_url}}">{{ identifier }}</a> found.</p> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I click on this for a report, I get redirected to the admin home page with this message 🤔
The URL it's trying to go to is something like http://localhost:50280/admin/api/image/de37812d-2daa-49f6-91ab-e87e780ca653/change/, is it possibly because the /change/
URL isn't available for images? Should something else be used here instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The IP does not require us to add a link to a media admin change page, so I thought it's best to remove it completely.
The URL for image change view is different than what's returned by reverse_media_url
, something like http://localhost:50280/admin/api/image/4347/change/?_changelist_filters=q%3Dde37812d-2daa-49f6-91ab-e87e780ca653
. I have no idea where the 4347
comes from.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That'll be the database id
, primary key column.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've left some comments, and I am approving despite the issues @AetherUnbound identified. My sense about the context using media_obj
being clearer is strong, but it isn't a blocker, and we can refactor this after the fact without issue. I don't want to prolong this PR's review on these problems.
The problems Madison identified are indeed problems, but I feel comfortable saying we can address them in fast follow issues, rather than in this PR, and it won't cause harm to the future issues in this milestone that depend on this baseline implementation existing. In the interest of unblocking that work, and recognising this PR has already been long in review, I'd prefer you created follow-up issues for those, and we merged this either as-is or with only minor modifications (up to you how much).
@AetherUnbound deferring to you on how much you feel are definite blockers vs things that can be fixed in fast-follows.
<div class="flex-container"> | ||
<label>Title and creator</label> | ||
<div class="readonly"> | ||
<p>{{ title }} {% if creator_url %}by <a href="{{creator_url}}">{{creator}}</a> {% else %} {{creator}} {% endif %} </p></div> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we lean on the attribution generation here to make things easier? It already resolves the creator_url/creator resolution, printing the title, source, etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apparently, we don't have a function to get the html attribution, only the text one that does not add creator and work urls.
For some reason, I was under the impression that all the items mentioned in the IP need to be on separate lines. Now, I updated the form to display the media details more compactly. For attribution, I created a new sub-template.
38eb70d
to
24a4a5b
Compare
Signed-off-by: Olga Bulat <obulat@gmail.com>
Signed-off-by: Olga Bulat <obulat@gmail.com>
Signed-off-by: Olga Bulat <obulat@gmail.com>
Signed-off-by: Olga Bulat <obulat@gmail.com>
Co-authored-by: Madison Swain-Bowden <bowdenm@spu.edu>
Signed-off-by: Olga Bulat <obulat@gmail.com>
Signed-off-by: Olga Bulat <obulat@gmail.com>
Signed-off-by: Olga Bulat <obulat@gmail.com>
Signed-off-by: Olga Bulat <obulat@gmail.com>
841e76f
to
f6f942d
Compare
Looks awesome, @obulat |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fantastic, I'm with @sarayourfriend on the other changes, those can be done in follow up if/as necessary. Thanks so much Olga, this looks excellent!
Fixes
Fixes #3637 by @sarayourfriend
Description
This PR updates the reporting view in line with the implementation plan requirements.
The media moderation view is split into 3 sections:
The IP says to use
TabularInline
for the other reports, however, it's not possible to do so because aTabularInline
can be used for displaying the child models on the parent admin page1. The other reports for the media item, on the other hand, display the children of the parent of the admin page model (Child (report) -> Parent (media) -> Children (reports)). That's why I used a separate function to build a table of other reports.Screenshots
Testing Instructions
Run the app using
just up
.Go to
http://localhost:50280/admin
and log in using the local credentials (user:deploy
user, password:deploy
)Add several reports for images and audio. Add several reports for the same item.
Then click on change report. You should see the updated form that contains a blurred image or an audio, as well as other parts of the report.
Check that add report/change report/view report views work correctly.
Checklist
Update index.md
).main
) or a parent feature branch.Developer Certificate of Origin
Developer Certificate of Origin
Footnotes
https://docs.djangoproject.com/en/5.0/ref/contrib/admin/#inlinemodeladmin-objects ↩