diff --git a/website/admin/artifact_admin.py b/website/admin/artifact_admin.py index fc84e0e3..2f119856 100644 --- a/website/admin/artifact_admin.py +++ b/website/admin/artifact_admin.py @@ -15,7 +15,9 @@ class ArtifactAdmin(admin.ModelAdmin): # search_fields are used for auto-complete, see: # https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields - search_fields = ['title', 'forum_name'] + # Includes author first/last name so artifacts are findable by who wrote them + # (Django auto-applies DISTINCT for the M2M join). Subclasses may extend this. + search_fields = ['title', 'forum_name', 'authors__first_name', 'authors__last_name'] fieldsets = [ (None, {'fields': ['title', 'authors', 'date']}), diff --git a/website/admin/award_admin.py b/website/admin/award_admin.py index 1e6293dd..162c87c4 100644 --- a/website/admin/award_admin.py +++ b/website/admin/award_admin.py @@ -44,6 +44,8 @@ class AwardAdmin(admin.ModelAdmin): ordering = ('-date',) + date_hierarchy = 'date' # Year/month/day drill-down (awards are browsed by year) + def get_fieldsets(self, request, obj=None): # Built at request time so reverse() can resolve the Publications admin URL. publications_url = reverse('admin:website_publication_changelist') diff --git a/website/admin/grant_admin.py b/website/admin/grant_admin.py index 1599e694..0b249516 100644 --- a/website/admin/grant_admin.py +++ b/website/admin/grant_admin.py @@ -9,7 +9,10 @@ class GrantAdmin(ArtifactAdmin): # search_fields are used for auto-complete, see: # https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields - search_fields = ['title', 'date', 'forum_name'] + # Dropped 'date' (string-searching a DateField is unhelpful); added PI/Co-PI + # (author) and sponsor name so grants are findable by people and funder. + search_fields = ['title', 'forum_name', 'authors__first_name', + 'authors__last_name', 'sponsor__name'] # The list display lets us control what is shown in the default talk table at Home > Website > Grants # See: https://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_display @@ -21,6 +24,7 @@ class GrantAdmin(ArtifactAdmin): autocomplete_fields = ['sponsor'] ordering = ('-date',) # sort by date, most recent first + date_hierarchy = 'date' # Year/month/day drill-down by grant start date fieldsets = [ (None, {'fields': ['title', 'authors']}), diff --git a/website/admin/keyword_admin.py b/website/admin/keyword_admin.py index bb1b748d..a2484ceb 100644 --- a/website/admin/keyword_admin.py +++ b/website/admin/keyword_admin.py @@ -7,6 +7,11 @@ class KeywordAdmin(admin.ModelAdmin): list_display = ['keyword', 'project_count', 'publication_count'] + # The keyword table had no search box; alphabetical ordering also groups + # near-duplicate tags (e.g. "Speech" / "speech") adjacently for cleanup. + search_fields = ['keyword'] + ordering = ['keyword'] + def change_view(self, request, object_id, form_url='', extra_context=None): """Add projects and publications to the context. We then use this extra data in the change_form.html template to display the projects and publications that use this keyword. diff --git a/website/admin/news_admin.py b/website/admin/news_admin.py index 786b8034..0cc7f9be 100644 --- a/website/admin/news_admin.py +++ b/website/admin/news_admin.py @@ -12,8 +12,11 @@ from django.utils.html import format_html # for formatting thumbnails from easy_thumbnails.files import get_thumbnailer # for generating thumbnails import os # for checking if thumbnail file exists +import logging from website.admin.admin_site import ml_admin_site +_logger = logging.getLogger(__name__) + class YearListFilter(admin.SimpleListFilter): title = 'year' # a label for our filter parameter_name = 'year' # you can put anything here @@ -42,6 +45,12 @@ class NewsAdmin(ImageCroppingMixin, admin.ModelAdmin): # Add a filter to the right sidebar that allows us to filter by year list_filter = (YearListFilter, 'project') + # Search by headline or author name (News previously had no search box). + search_fields = ['title', 'author__first_name', 'author__last_name'] + + # Year/month/day drill-down at the top of the changelist (News is date-driven). + date_hierarchy = 'date' + # Define 'author' as an auto-complete field. We must then also define "search_fields" # in PersonAdmin or we'll receive a Django error autocomplete_fields = ['author'] @@ -51,12 +60,18 @@ class NewsAdmin(ImageCroppingMixin, admin.ModelAdmin): def get_display_thumbnail(self, obj): if obj.image and os.path.isfile(obj.image.path): - # Use easy_thumbnails to generate a thumbnail - thumbnailer = get_thumbnailer(obj.image) - thumbnail_options = {'size': (NEWS_THUMBNAIL_SIZE[0], NEWS_THUMBNAIL_SIZE[1]), 'crop': True} - thumbnail_url = thumbnailer.get_thumbnail(thumbnail_options).url - - return format_html('', thumbnail_url) + try: + # Use easy_thumbnails to generate a thumbnail + thumbnailer = get_thumbnailer(obj.image) + thumbnail_options = {'size': (NEWS_THUMBNAIL_SIZE[0], NEWS_THUMBNAIL_SIZE[1]), 'crop': True} + thumbnail_url = thumbnailer.get_thumbnail(thumbnail_options).url + + return format_html('', thumbnail_url) + except Exception: + # A single corrupt/unreadable image must not 500 the entire News + # changelist (the column is rendered for every row). + _logger.warning("Could not generate admin thumbnail for News id=%s", obj.pk, exc_info=True) + return 'No Thumbnail' return 'No Thumbnail' get_display_thumbnail.short_description = 'Thumbnail' diff --git a/website/admin/photo_admin.py b/website/admin/photo_admin.py index 25510a26..f1695cbc 100644 --- a/website/admin/photo_admin.py +++ b/website/admin/photo_admin.py @@ -7,5 +7,8 @@ class PhotoAdmin(ImageCroppingMixin, admin.ModelAdmin): list_display = ('admin_thumbnail', 'caption', 'alt_text', 'get_resolution_as_str', 'cropping', 'picture') - + + # Photos had no search box; search caption/alt text and the owning project. + search_fields = ['caption', 'alt_text', 'project__name'] + list_per_page = 20 # changes how many images to show on a single admin page \ No newline at end of file diff --git a/website/admin/position_admin.py b/website/admin/position_admin.py index 26e8ec30..83dcc320 100644 --- a/website/admin/position_admin.py +++ b/website/admin/position_admin.py @@ -8,6 +8,10 @@ class PositionAdmin(admin.ModelAdmin): """Note: We do not want users to edit positions directly. Instead, we want them to edit people and projects. See PositionInline in PersonAdmin""" + # Needed for autocomplete/search to filter rather than return everything; + # the get_search_results override below builds on this base. + search_fields = ['person__first_name', 'person__last_name', 'title'] + def get_model_perms(self, request): """ Return empty perms dict thus hiding the model from admin index. diff --git a/website/admin/poster_admin.py b/website/admin/poster_admin.py index 4a193e77..fb88cf28 100644 --- a/website/admin/poster_admin.py +++ b/website/admin/poster_admin.py @@ -13,7 +13,12 @@ class PosterAdmin(ArtifactAdmin): # search_fields are used for auto-complete, see: # https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields - search_fields = ['title', 'date'] + # Was ['title', 'date'] — string-searching a DateField is unhelpful; search + # venue and author instead (matches the other artifact admins). + search_fields = ['title', 'forum_name', 'authors__first_name', 'authors__last_name'] + + ordering = ('-date',) # Poster had no default sort; newest first like its siblings + date_hierarchy = 'date' # Year/month/day drill-down def get_changeform_initial_data(self, request): """ diff --git a/website/admin/project_admin.py b/website/admin/project_admin.py index fa15e5f3..a9459984 100644 --- a/website/admin/project_admin.py +++ b/website/admin/project_admin.py @@ -48,7 +48,9 @@ class GrantInline(admin.TabularInline): @admin.register(Project, site=ml_admin_site) class ProjectAdmin(ImageCroppingMixin, admin.ModelAdmin): - search_fields = ['name'] # allows you to search by the name of the project + # Search by name plus the research-area facets editors think in (umbrella, keyword). + search_fields = ['name', 'short_name', 'project_umbrellas__name', 'keywords__name'] + ordering = ('name',) # deterministic alphabetical sort (matched the autocomplete already) inlines = [GrantInline, BannerInline, PhotoInline, ProjectRoleInline] # The list display lets us control what is shown in the Project table at Home > Website > Project diff --git a/website/admin/project_umbrella_admin.py b/website/admin/project_umbrella_admin.py index 63e65e98..ba14d34b 100644 --- a/website/admin/project_umbrella_admin.py +++ b/website/admin/project_umbrella_admin.py @@ -11,6 +11,8 @@ class ProjectInline(admin.TabularInline): # or admin.StackedInline @admin.register(ProjectUmbrella, site=ml_admin_site) class ProjectUmbrellaAdmin(admin.ModelAdmin): list_display = ('name', 'short_name', 'project_count') + search_fields = ['name', 'short_name'] + ordering = ('name',) inlines = [ProjectInline] def formfield_for_manytomany(self, db_field, request=None, **kwargs): diff --git a/website/admin/publication_admin.py b/website/admin/publication_admin.py index 98285fd2..69c00508 100644 --- a/website/admin/publication_admin.py +++ b/website/admin/publication_admin.py @@ -47,6 +47,13 @@ class Media: # default the sort order in table to descending order by date ordering = ('-date',) + # Extend the ArtifactAdmin default (title/forum/authors) with book title and DOI. + search_fields = ['title', 'forum_name', 'book_title', + 'authors__first_name', 'authors__last_name', 'doi'] + + # Year/month/day drill-down for this large, date-ordered table. + date_hierarchy = 'date' + list_filter = (PubVenueTypeListFilter, PubVenueListFilter) # add in auto-complete fields diff --git a/website/admin/sponsor_admin.py b/website/admin/sponsor_admin.py index 4c692acf..91c643b1 100644 --- a/website/admin/sponsor_admin.py +++ b/website/admin/sponsor_admin.py @@ -22,6 +22,8 @@ class SponsorAdmin(ImageCroppingMixin, admin.ModelAdmin): # In this case, the admin will search the 'name' and 'short_name' fields of the Sponsor model. search_fields = ['name', 'short_name'] + ordering = ('name',) # deterministic alphabetical sort (none was defined) + def total_funding(self, obj): return obj.grant_set.aggregate(total_funding=Sum('funding_amount'))['total_funding'] total_funding.short_description = 'Total Funding' diff --git a/website/admin/talk_admin.py b/website/admin/talk_admin.py index 438f6b98..fb774842 100644 --- a/website/admin/talk_admin.py +++ b/website/admin/talk_admin.py @@ -22,7 +22,8 @@ class TalkAdmin(ArtifactAdmin): # search_fields are used for auto-complete, see: # https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields - search_fields = ['title', 'forum_name'] + # Includes speaker (author) name so talks are findable by who gave them. + search_fields = ['title', 'forum_name', 'authors__first_name', 'authors__last_name'] # This auto-complete field is not working # See: https://github.com/makeabilitylab/makeabilitylabwebsite/issues/1093#issuecomment-2423843958 @@ -42,6 +43,7 @@ class TalkAdmin(ArtifactAdmin): ordering = ('-date',) # Sort talks by date in descending order list_filter = ('talk_type',) # Add a filter for the talk type + date_hierarchy = 'date' # Year/month/day drill-down at the top of the list def get_changeform_initial_data(self, request): """ diff --git a/website/admin/video_admin.py b/website/admin/video_admin.py index fab91fb3..c93d6eb0 100644 --- a/website/admin/video_admin.py +++ b/website/admin/video_admin.py @@ -16,11 +16,12 @@ class VideoAdmin(admin.ModelAdmin): # search_fields are used for auto-complete, see: # https://docs.djangoproject.com/en/3.0/ref/contrib/admin/#django.contrib.admin.ModelAdmin.autocomplete_fields # Jon's note (Dec 16, 2025): We cannot use 'get_video_host_str' here because search_fields requires actual model fields. - search_fields = ['title', 'video_url', 'date', 'caption'] - # search_fields = ['title'] + # Dropped 'date' (string-searching a DateField is unhelpful) and added project name. + search_fields = ['title', 'video_url', 'caption', 'projects__name'] # default the sort order in table to descending order by date ordering = ('-date',) + date_hierarchy = 'date' # Year/month/day drill-down def display_projects(self, obj): return ", ".join([project.name for project in obj.projects.all()]) diff --git a/website/templates/website/project.html b/website/templates/website/project.html index 1f2872e5..2b96e736 100644 --- a/website/templates/website/project.html +++ b/website/templates/website/project.html @@ -445,8 +445,8 @@

Related Projects

class="project-sidebar-related-thumbnail-link" aria-label="View project: {{ related_project.name }}"> {{ related_project.name }} + src="{% thumbnail related_project.gallery_image '160x90' box=related_project.cropping crop=True upscale=True %}" + alt="{{ related_project.get_thumbnail_alt_text }}">