|
3 | 3 | from django.contrib.admin import widgets |
4 | 4 | from django.utils.html import format_html |
5 | 5 | from sortedm2m_filter_horizontal_widget.forms import SortedFilteredSelectMultiple |
| 6 | +from website.utils.upload_validators import PDF_EXTENSIONS, RAW_FILE_EXTENSIONS |
6 | 7 | from easy_thumbnails.files import get_thumbnailer |
7 | 8 | import os |
8 | 9 | import logging |
9 | 10 |
|
10 | 11 | # This retrieves a Python logging instance (or creates it) |
11 | 12 | _logger = logging.getLogger(__name__) |
12 | 13 |
|
| 14 | + |
| 15 | +def _accept_attr(extensions): |
| 16 | + """Build an HTML ``accept`` value (e.g. ``.pdf,.pptx``) from an extension |
| 17 | + allowlist. Used to seed the file inputs so both the OS file picker and the |
| 18 | + client-side check in ``admin_artifact_form.js`` read the allowed types |
| 19 | + straight from the markup — keeping them in sync with the server validators |
| 20 | + (issue #248).""" |
| 21 | + return ",".join(f".{ext}" for ext in extensions) |
| 22 | + |
| 23 | + |
13 | 24 | class ArtifactAdmin(admin.ModelAdmin): |
14 | 25 |
|
| 26 | + # Loaded on every artifact add/change form (Talk/Poster/Publication, which |
| 27 | + # all subclass this). Guards against losing selected files when the form is |
| 28 | + # submitted with a missing required field, plus drag-and-drop (issue #248). |
| 29 | + # PublicationAdmin defines its own Media; Django merges this base in. |
| 30 | + class Media: |
| 31 | + css = {"all": ("website/css/admin_artifact_form.css",)} |
| 32 | + js = ("website/js/admin_artifact_form.js",) |
| 33 | + |
15 | 34 | # The list display lets us control what is shown in the default talk table at Home > Website > Talk |
16 | 35 | # See: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display |
17 | 36 | list_display = ('title', 'date', 'get_first_author_last_name', 'forum_name', 'location') |
@@ -99,9 +118,28 @@ def get_fieldsets(self, request, obj=None): |
99 | 118 | updated.append((name, opts)) |
100 | 119 | return updated |
101 | 120 |
|
| 121 | + def get_form(self, request, obj=None, **kwargs): |
| 122 | + """ |
| 123 | + Seed the ``accept`` attribute on the file inputs from the same extension |
| 124 | + allowlists the server validators enforce (``PDF_EXTENSIONS`` / |
| 125 | + ``RAW_FILE_EXTENSIONS`` in ``website.utils.upload_validators``). This gives |
| 126 | + the OS file picker a native type filter and is the single source of truth |
| 127 | + the client-side check in ``admin_artifact_form.js`` reads back from the |
| 128 | + DOM, so the JS can't drift from the Python rules (issue #248). |
| 129 | +
|
| 130 | + Subclasses (Talk/Publication) call ``super().get_form()`` first and then |
| 131 | + layer their own widget tweaks, so this runs for all artifact admins. |
| 132 | + """ |
| 133 | + form = super().get_form(request, obj, **kwargs) |
| 134 | + if "pdf_file" in form.base_fields: |
| 135 | + form.base_fields["pdf_file"].widget.attrs["accept"] = _accept_attr(PDF_EXTENSIONS) |
| 136 | + if "raw_file" in form.base_fields: |
| 137 | + form.base_fields["raw_file"].widget.attrs["accept"] = _accept_attr(RAW_FILE_EXTENSIONS) |
| 138 | + return form |
| 139 | + |
102 | 140 | def formfield_for_manytomany(self, db_field, request, **kwargs): |
103 | 141 | """ |
104 | | - Overrides the formfield_for_manytomany method of the parent ModelAdmin class to customize the widgets |
| 142 | + Overrides the formfield_for_manytomany method of the parent ModelAdmin class to customize the widgets |
105 | 143 | used for ManyToMany fields in the admin interface. |
106 | 144 |
|
107 | 145 | Parameters: |
|
0 commit comments