diff --git a/src/core/admin.py b/src/core/admin.py index 761d3ff0..ceedbab5 100644 --- a/src/core/admin.py +++ b/src/core/admin.py @@ -33,6 +33,18 @@ def create_link_to_related_artworks(obj, artworks_list): return format_html(HTML_LINK, link, obj._artworks_count) +@admin.action(description="Highlight selected content") +def highlight_content(modeladmin, request, queryset): + updated = queryset.update(highlighted=True) + modeladmin.message_user(request, f"{updated} item(s) highlighted.") + + +@admin.action(description="Remove highlight from selected content") +def unhighlight_content(modeladmin, request, queryset): + updated = queryset.update(highlighted=False) + modeladmin.message_user(request, f"{updated} item(s) no longer highlighted.") + + class BaseMarkerObjectAdmin(admin.ModelAdmin): list_display = [ "title", @@ -45,6 +57,7 @@ class BaseMarkerObjectAdmin(admin.ModelAdmin): "created", "modified", "filesize", + "highlighted", ] search_fields = ["title", "id"] ordering = ["-created"] @@ -124,7 +137,10 @@ def remove_border(modeladmin, request, queryset): @admin.register(Marker) class MarkerAdmin(BaseMarkerObjectAdmin): + list_filter = ["highlighted"] actions = [ + highlight_content, + unhighlight_content, regenerate_marker_white_border, regenerate_marker_no_inner_border, remove_border, @@ -211,8 +227,13 @@ class ObjectAdmin(BaseMarkerObjectAdmin): "has_spritesheet", ] search_fields = ["title", "id"] - list_filter = ["file_extension", SpritesheetFilter] - actions = [generate_spritesheets, "populate_dimensions"] + list_filter = ["file_extension", SpritesheetFilter, "highlighted"] + actions = [ + generate_spritesheets, + "populate_dimensions", + highlight_content, + unhighlight_content, + ] def image_preview(self, obj): return format_html(obj.as_html(height=64, width=64), "") @@ -254,6 +275,8 @@ def populate_dimensions(self, request, queryset): @admin.register(Artwork) class ArtworkAdmin(admin.ModelAdmin): + list_filter = ["highlighted"] + actions = [highlight_content, unhighlight_content] list_display = [ "title", "id", @@ -330,7 +353,8 @@ class ExhibitAdmin(admin.ModelAdmin): "created", "modified", ] - list_filter = ["exhibit_type"] + list_filter = ["exhibit_type", "highlighted"] + actions = [highlight_content, unhighlight_content] search_fields = ["name", "slug"] ordering = ["-created"] @@ -377,6 +401,8 @@ def _owner(self, obj): @admin.register(Sound) class SoundAdmin(admin.ModelAdmin): + list_filter = ["highlighted"] + actions = [highlight_content, unhighlight_content] list_display = [ "title", "preview", diff --git a/src/core/migrations/0047_remove_artwork_insert_insert_and_more.py b/src/core/migrations/0047_remove_artwork_insert_insert_and_more.py new file mode 100644 index 00000000..4535a3f1 --- /dev/null +++ b/src/core/migrations/0047_remove_artwork_insert_insert_and_more.py @@ -0,0 +1,280 @@ +# Generated by Django 6.1.1 on 2026-09-17 15:02 + +import pgtrigger.compiler +import pgtrigger.migrations +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0046_alter_sound_file_alter_soundevent_file"), + ] + + operations = [ + pgtrigger.migrations.RemoveTrigger( + model_name="artwork", + name="insert_insert", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="artwork", + name="update_update", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="exhibit", + name="insert_insert", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="exhibit", + name="update_update", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="marker", + name="insert_insert", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="marker", + name="update_update", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="object", + name="insert_insert", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="object", + name="update_update", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="sound", + name="insert_insert", + ), + pgtrigger.migrations.RemoveTrigger( + model_name="sound", + name="update_update", + ), + migrations.AddField( + model_name="artwork", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="artworkevent", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="exhibit", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="exhibitevent", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="marker", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="markerevent", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="object", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="objectevent", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="sound", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + migrations.AddField( + model_name="soundevent", + name="highlighted", + field=models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="artwork", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "core_artworkevent" ("augmented_id", "author_id", "created", "description", "highlighted", "id", "marker_id", "modified", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "position_x", "position_y", "scale_x", "scale_y", "sound_id", "title") VALUES (NEW."augmented_id", NEW."author_id", NEW."created", NEW."description", NEW."highlighted", NEW."id", NEW."marker_id", NEW."modified", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."position_x", NEW."position_y", NEW."scale_x", NEW."scale_y", NEW."sound_id", NEW."title"); RETURN NULL;', + hash="19c3d0f5fa153df403ea6d9efdfa40f8448783c7", + operation="INSERT", + pgid="pgtrigger_insert_insert_65595", + table="core_artwork", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="artwork", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "core_artworkevent" ("augmented_id", "author_id", "created", "description", "highlighted", "id", "marker_id", "modified", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "position_x", "position_y", "scale_x", "scale_y", "sound_id", "title") VALUES (NEW."augmented_id", NEW."author_id", NEW."created", NEW."description", NEW."highlighted", NEW."id", NEW."marker_id", NEW."modified", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."position_x", NEW."position_y", NEW."scale_x", NEW."scale_y", NEW."sound_id", NEW."title"); RETURN NULL;', + hash="65d3976398df2f07d031dba8df1ee23297e8e511", + operation="UPDATE", + pgid="pgtrigger_update_update_1ac7c", + table="core_artwork", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="exhibit", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "core_exhibitevent" ("created", "exhibit_type", "highlighted", "id", "modified", "name", "owner_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "slug") VALUES (NEW."created", NEW."exhibit_type", NEW."highlighted", NEW."id", NEW."modified", NEW."name", NEW."owner_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."slug"); RETURN NULL;', + hash="7994abf3dc51dd495ac5568442432d5ef20598e1", + operation="INSERT", + pgid="pgtrigger_insert_insert_aaa01", + table="core_exhibit", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="exhibit", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "core_exhibitevent" ("created", "exhibit_type", "highlighted", "id", "modified", "name", "owner_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "slug") VALUES (NEW."created", NEW."exhibit_type", NEW."highlighted", NEW."id", NEW."modified", NEW."name", NEW."owner_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."slug"); RETURN NULL;', + hash="0ab4b5f065f85e2316e8b99b7e4acec3391c9d9e", + operation="UPDATE", + pgid="pgtrigger_update_update_7b6f5", + table="core_exhibit", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="marker", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "core_markerevent" ("author", "created", "file_size", "highlighted", "id", "in_use", "is_used_by_other_user", "marker_img", "modified", "owner_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "print_img", "source", "thumb_img", "title") VALUES (NEW."author", NEW."created", NEW."file_size", NEW."highlighted", NEW."id", NEW."in_use", NEW."is_used_by_other_user", NEW."marker_img", NEW."modified", NEW."owner_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."print_img", NEW."source", NEW."thumb_img", NEW."title"); RETURN NULL;', + hash="0186ac7b719cc1c0438b6cb618b1ab74e943cf3d", + operation="INSERT", + pgid="pgtrigger_insert_insert_719b2", + table="core_marker", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="marker", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "core_markerevent" ("author", "created", "file_size", "highlighted", "id", "in_use", "is_used_by_other_user", "marker_img", "modified", "owner_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "print_img", "source", "thumb_img", "title") VALUES (NEW."author", NEW."created", NEW."file_size", NEW."highlighted", NEW."id", NEW."in_use", NEW."is_used_by_other_user", NEW."marker_img", NEW."modified", NEW."owner_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."print_img", NEW."source", NEW."thumb_img", NEW."title"); RETURN NULL;', + hash="d6e72e9e8a89ea13d3f9f05a22bdc298a058cc6f", + operation="UPDATE", + pgid="pgtrigger_update_update_9a032", + table="core_marker", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="object", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "core_objectevent" ("audio_description", "author", "created", "file_extension", "file_name_original", "file_size", "height", "highlighted", "id", "in_use", "is_used_by_other_user", "modified", "owner_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "sound_id", "source", "spritesheet_file", "spritesheet_metadata", "thumbnail", "title", "width") VALUES (NEW."audio_description", NEW."author", NEW."created", NEW."file_extension", NEW."file_name_original", NEW."file_size", NEW."height", NEW."highlighted", NEW."id", NEW."in_use", NEW."is_used_by_other_user", NEW."modified", NEW."owner_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."sound_id", NEW."source", NEW."spritesheet_file", NEW."spritesheet_metadata", NEW."thumbnail", NEW."title", NEW."width"); RETURN NULL;', + hash="52e77a20b6ec1d20afc7eb36de85058af9d18b67", + operation="INSERT", + pgid="pgtrigger_insert_insert_25a7e", + table="core_object", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="object", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "core_objectevent" ("audio_description", "author", "created", "file_extension", "file_name_original", "file_size", "height", "highlighted", "id", "in_use", "is_used_by_other_user", "modified", "owner_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "sound_id", "source", "spritesheet_file", "spritesheet_metadata", "thumbnail", "title", "width") VALUES (NEW."audio_description", NEW."author", NEW."created", NEW."file_extension", NEW."file_name_original", NEW."file_size", NEW."height", NEW."highlighted", NEW."id", NEW."in_use", NEW."is_used_by_other_user", NEW."modified", NEW."owner_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."sound_id", NEW."source", NEW."spritesheet_file", NEW."spritesheet_metadata", NEW."thumbnail", NEW."title", NEW."width"); RETURN NULL;', + hash="edda8e37dd9070a9e49c587769dc29a5fdd978bf", + operation="UPDATE", + pgid="pgtrigger_update_update_27852", + table="core_object", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="sound", + trigger=pgtrigger.compiler.Trigger( + name="insert_insert", + sql=pgtrigger.compiler.UpsertTriggerSql( + func='INSERT INTO "core_soundevent" ("author", "created", "file", "file_extension", "file_name_original", "file_size", "highlighted", "id", "in_use", "is_used_by_other_user", "modified", "owner_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "title") VALUES (NEW."author", NEW."created", NEW."file", NEW."file_extension", NEW."file_name_original", NEW."file_size", NEW."highlighted", NEW."id", NEW."in_use", NEW."is_used_by_other_user", NEW."modified", NEW."owner_id", _pgh_attach_context(), NOW(), \'insert\', NEW."id", NEW."title"); RETURN NULL;', + hash="d84c6cc11998e37bd67e88d23d2fd23f485c3853", + operation="INSERT", + pgid="pgtrigger_insert_insert_fe2cc", + table="core_sound", + when="AFTER", + ), + ), + ), + pgtrigger.migrations.AddTrigger( + model_name="sound", + trigger=pgtrigger.compiler.Trigger( + name="update_update", + sql=pgtrigger.compiler.UpsertTriggerSql( + condition="WHEN (OLD.* IS DISTINCT FROM NEW.*)", + func='INSERT INTO "core_soundevent" ("author", "created", "file", "file_extension", "file_name_original", "file_size", "highlighted", "id", "in_use", "is_used_by_other_user", "modified", "owner_id", "pgh_context_id", "pgh_created_at", "pgh_label", "pgh_obj_id", "title") VALUES (NEW."author", NEW."created", NEW."file", NEW."file_extension", NEW."file_name_original", NEW."file_size", NEW."highlighted", NEW."id", NEW."in_use", NEW."is_used_by_other_user", NEW."modified", NEW."owner_id", _pgh_attach_context(), NOW(), \'update\', NEW."id", NEW."title"); RETURN NULL;', + hash="ee3f6515af0a2c3ec9e2ab2e058b38f6d2ec86cf", + operation="UPDATE", + pgid="pgtrigger_update_update_8c5ab", + table="core_sound", + when="AFTER", + ), + ), + ), + ] diff --git a/src/core/models.py b/src/core/models.py index 3399d0d1..ca591981 100644 --- a/src/core/models.py +++ b/src/core/models.py @@ -98,6 +98,11 @@ class Sound(ContentMixin, models.Model): created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True, db_index=True) + highlighted = models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ) + @property def date(self): return self.created.strftime("%d/%m/%Y") @@ -173,6 +178,10 @@ class Marker(ContentMixin, models.Model): in_use = models.BooleanField(default=False) is_used_by_other_user = models.BooleanField(default=False) + highlighted = models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ) created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True, db_index=True) @@ -289,6 +298,11 @@ class Object(ContentMixin, models.Model): created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True, db_index=True) + highlighted = models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ) + def __str__(self): return self.source.name @@ -480,6 +494,10 @@ class Artwork(ContentMixin, models.Model): position_x = models.FloatField(default=0.0) position_y = models.FloatField(default=0.0) + highlighted = models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ) created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True, db_index=True) @@ -538,6 +556,11 @@ class Exhibit(ContentMixin, models.Model): created = models.DateTimeField(auto_now_add=True, db_index=True) modified = models.DateTimeField(auto_now=True, db_index=True) + highlighted = models.BooleanField( + default=False, + help_text="Editor-curated content is listed before everything else.", + ) + def __str__(self): return self.name diff --git a/src/core/tests/test_highlight_content.py b/src/core/tests/test_highlight_content.py new file mode 100644 index 00000000..37785b24 --- /dev/null +++ b/src/core/tests/test_highlight_content.py @@ -0,0 +1,46 @@ +from django.test import TestCase +from django.urls import reverse + +from core.models import Artwork, Exhibit, Marker, Object, Sound +from core.tests.factory import MarkerFactory + +HIGHLIGHTABLE = (Marker, Object, Artwork, Exhibit, Sound) + + +class TestHighlightField(TestCase): + def test_every_listed_content_type_can_be_highlighted(self): + for model in HIGHLIGHTABLE: + with self.subTest(model=model.__name__): + field = model._meta.get_field("highlighted") + assert field.default is False + + def test_content_is_not_highlighted_by_default(self): + marker = MarkerFactory() + + assert marker.highlighted is False + + +class TestHighlightOrdering(TestCase): + """Highlighted content must lead the listings, regardless of age.""" + + def test_highlighted_marker_is_listed_before_newer_ones(self): + old = MarkerFactory() + old.highlighted = True + old.save() + newest = MarkerFactory() + + response = self.client.get(reverse("collection")) + listed = list(response.context["markers"]) + + assert listed[0] == old, "the highlighted marker should lead the list" + assert newest in listed + assert listed.index(old) < listed.index(newest) + + def test_ordering_falls_back_to_recency_within_the_same_flag(self): + first = MarkerFactory() + second = MarkerFactory() + + response = self.client.get(reverse("collection")) + listed = list(response.context["markers"]) + + assert listed.index(second) < listed.index(first) diff --git a/src/core/views/views.py b/src/core/views/views.py index 61a8e8eb..3a9a62e2 100644 --- a/src/core/views/views.py +++ b/src/core/views/views.py @@ -70,23 +70,23 @@ def collection(request): .prefetch_related("artworks") .filter(exhibit_type=ExhibitTypes.AR) .all() - .order_by("-created")[:4] + .order_by("-highlighted", "-created")[:4] ) mr_exhibits = ( Exhibit.objects.select_related("owner", "owner__user") .prefetch_related("artworks") .filter(exhibit_type=ExhibitTypes.MR) .all() - .order_by("-created")[:4] + .order_by("-highlighted", "-created")[:4] ) artworks = ( Artwork.objects.select_related("author", "author__user", "marker", "augmented") .all() - .order_by("-created")[:6] + .order_by("-highlighted", "-created")[:6] ) - markers = Marker.objects.all().order_by("-created")[:8] - objects = Object.objects.all().order_by("-created")[:8] - sounds = Sound.objects.all().order_by("-created")[:8] + markers = Marker.objects.all().order_by("-highlighted", "-created")[:8] + objects = Object.objects.all().order_by("-highlighted", "-created")[:8] + sounds = Sound.objects.all().order_by("-highlighted", "-created")[:8] ctx = { "artworks": artworks, @@ -127,22 +127,22 @@ def see_all(request, which=""): page = 1 data_types = { - "object": Object.objects.all().order_by("-created"), - "marker": Marker.objects.all().order_by("-created"), + "object": Object.objects.all().order_by("-highlighted", "-created"), + "marker": Marker.objects.all().order_by("-highlighted", "-created"), "artwork": Artwork.objects.prefetch_related("marker", "augmented") .all() - .order_by("-created"), + .order_by("-highlighted", "-created"), "ar-exhibit": Exhibit.objects.select_related("owner", "owner__user") .prefetch_related("artworks") .filter(exhibit_type=ExhibitTypes.AR) .all() - .order_by("-created"), + .order_by("-highlighted", "-created"), "mr-exhibit": Exhibit.objects.select_related("owner", "owner__user") .prefetch_related("artworks") .filter(exhibit_type=ExhibitTypes.MR) .all() - .order_by("-created"), - "sound": Sound.objects.all().order_by("-created"), + .order_by("-highlighted", "-created"), + "sound": Sound.objects.all().order_by("-highlighted", "-created"), } data = data_types.get(request_type) @@ -294,7 +294,7 @@ def object_upload(request): else: form = UploadObjectForm() - sounds = Sound.objects.all().order_by("-created") + sounds = Sound.objects.all().order_by("-highlighted", "-created") paginator_sounds = Paginator(sounds, settings.OBJECT_MODAL_PAGE_SIZE) return render( request, @@ -469,7 +469,7 @@ def edit_object(request): else: form = UploadObjectForm(initial=model_data) - sounds = Sound.objects.all().order_by("-created") + sounds = Sound.objects.all().order_by("-highlighted", "-created") paginator_sounds = Paginator(sounds, settings.OBJECT_MODAL_PAGE_SIZE) return render( @@ -513,13 +513,13 @@ def _handle_artwork_form(request, user_profile, artwork_instance=None): def _get_artwork_context_data(form, artwork_instance=None): """Helper function to prepare context data for artwork templates.""" - marker_list = Marker.objects.all().order_by("-created") + marker_list = Marker.objects.all().order_by("-highlighted", "-created") object_list = ( Object.objects.exclude(file_extension=ObjectExtensions.GLB) .all() - .order_by("-created") + .order_by("-highlighted", "-created") ) - sound_list = Sound.objects.all().order_by("-created") + sound_list = Sound.objects.all().order_by("-highlighted", "-created") paginator_marker = Paginator(marker_list, settings.MODAL_PAGE_SIZE) paginator_object = Paginator(object_list, settings.OBJECT_MODAL_PAGE_SIZE) paginator_sound = Paginator(sound_list, settings.OBJECT_MODAL_PAGE_SIZE) @@ -597,13 +597,13 @@ def get_element(request): search = request.GET.get("search", "").strip() match element_type := request.GET.get("element_type"): case "object": - qs = Object.objects.all().order_by("-created") + qs = Object.objects.all().order_by("-highlighted", "-created") if request.GET.get("exclude_glb", "false") == "true": qs = qs.exclude(file_extension=ObjectExtensions.GLB) case "marker": - qs = Marker.objects.all().order_by("-created") + qs = Marker.objects.all().order_by("-highlighted", "-created") case "sound": - qs = Sound.objects.all().order_by("-created") + qs = Sound.objects.all().order_by("-highlighted", "-created") case _: raise Http404("Invalid element type") @@ -675,8 +675,8 @@ def _handle_exhibit_form( def _get_mr_exhibit_context_data(form, edit=False): """Helper method to prepare context data for exhibit templates.""" - objects = Object.objects.all().order_by("-created") - sounds = Sound.objects.all().order_by("-created") + objects = Object.objects.all().order_by("-highlighted", "-created") + sounds = Sound.objects.all().order_by("-highlighted", "-created") paginator_objects = Paginator(objects, settings.OBJECT_MODAL_PAGE_SIZE) paginator_sounds = Paginator(sounds, settings.OBJECT_MODAL_PAGE_SIZE)