diff --git a/dataedit/migrations/0046_alter_view_unique_together_remove_view_is_default.py b/dataedit/migrations/0046_alter_view_unique_together_remove_view_is_default.py new file mode 100644 index 000000000..81d704279 --- /dev/null +++ b/dataedit/migrations/0046_alter_view_unique_together_remove_view_is_default.py @@ -0,0 +1,57 @@ +# Generated by Django 5.1.15 on 2026-01-26 10:58 +__license__ = """ +SPDX-FileCopyrightText: Christian Winger +SPDX-License-Identifier: AGPL-3.0-or-later +""" # noqa: 501 + +import logging + +from django.db import migrations +from django.db.models import Count, Max + +logger = logging.getLogger("oeplatform") + + +def remove_duplicate_views(apps, schema_editor): + """remove duplicates of (table, type, name). + + But keep last instance + """ + + View = apps.get_model("dataedit", "View") + + duplicate_groups = ( + View.objects.values("table", "type", "name") # group by + .annotate(keep_id=Max("id"), count=Count("id")) + .filter(count__gt=1) + ) + + for d in duplicate_groups: + for view in View.objects.filter( + table=d["table"], + type=d["type"], + name=d["name"], + ).exclude(id=d["keep_id"]): + logging.warning(f"removing view: {view}") + view.delete() + + +def remove_duplicate_views_reverse(apps, schema_editor): + # do nothing + pass + + +class Migration(migrations.Migration): + atomic = False + + dependencies = [ + ("dataedit", "0045_alter_embargo_table"), + ] + + operations = [ + migrations.RunPython(remove_duplicate_views, remove_duplicate_views_reverse), + migrations.AlterUniqueTogether( + name="view", + unique_together={("table", "type", "name")}, + ), + ] diff --git a/dataedit/models.py b/dataedit/models.py index 119fd266c..c386a0c24 100644 --- a/dataedit/models.py +++ b/dataedit/models.py @@ -25,7 +25,7 @@ from django.contrib.postgres.search import SearchVectorField from django.core.exceptions import ValidationError -from django.db import models +from django.db import models, transaction from django.db.models import ( BooleanField, CharField, @@ -370,7 +370,6 @@ def get_readable_table_name(self) -> str: ) except Exception: - return self.name def validate_open_data_license( @@ -511,6 +510,33 @@ class View(models.Model): def __str__(self): return '{}--"{}"({})'.format(self.table, self.name, self.type.upper()) + class Meta: + unique_together = [("table", "type", "name")] + + @classmethod + def get_or_create_default( + cls, table: str, type: str = "table", name: str = "default" + ) -> "View": + with transaction.atomic(): + # technically, multiple views per table can be default (or none). + view = View.objects.filter(is_default=True, table=table).last() + if view: + return view + # its possible that we have a view named "default", that is not + # marked as is_default=True + view = View.objects.filter(table=table, type=type, name=name).last() + if view is not None: + # make it default for next time + view.is_default = True + view.save() + return view + + # create a new one + view = View.objects.create( + table=table, type=type, name=name, is_default=True + ) + return view + class Filter(models.Model): column = CharField(max_length=100, null=False) @@ -708,13 +734,12 @@ def set_version_of_metadata_for_review(self, table: str, *args, **kwargs): return ( True, - f"Set current version of table's: '{table}' " "oemetadata for review.", + f"Set current version of table's: '{table}' oemetadata for review.", ) return ( False, - f"This tables (name: {table}) review " - "already got a version of oemetadata.", + f"This tables (name: {table}) review already got a version of oemetadata.", ) def update_all_table_peer_reviews_after_table_moved(self, *args, topic, **kwargs): diff --git a/dataedit/urls.py b/dataedit/urls.py index bda0f5b2a..8275a39be 100644 --- a/dataedit/urls.py +++ b/dataedit/urls.py @@ -68,14 +68,18 @@ name="table-view-save", ), re_path( - r"^(?P{qual})/view/set-default".format(qual=pgsql_qualifier), + # TODO: not used yet + r"^(?P
{qual})/view/(?P{qual})/set-default".format( + qual="[0-9]+" + ), table_view_set_default_view, - name="table-view-set-default", # TODO: should be POST, but is GET? + name="table-view-set-default", ), re_path( - r"^(?P
{qual})/view/delete".format(qual=pgsql_qualifier), + # TODO: not used yet + r"^(?P
{qual})/view/(?P{qual})/delete".format(qual="[0-9]+"), table_view_delete_view, - name="table-view-delete-default", # TODO: should be POST, but is GET? + name="table-view-delete-default", ), re_path( r"^(?P
{qual})/graph/new".format(qual=pgsql_qualifier), diff --git a/dataedit/views.py b/dataedit/views.py index 18871983b..21835b126 100644 --- a/dataedit/views.py +++ b/dataedit/views.py @@ -37,7 +37,6 @@ import re from collections import defaultdict from io import TextIOWrapper -from itertools import chain from django.contrib import messages from django.contrib.auth.decorators import login_required @@ -529,14 +528,14 @@ def table_view_save_view(request: HttpRequest, table: str) -> HttpResponse: ) -def table_view_set_default_view(request: HttpRequest, table: str) -> HttpResponse: +@require_POST +def table_view_set_default_view( + request: HttpRequest, table: str, view_id: int +) -> HttpResponse: table_obj = table_or_404(table=table) - # TODO: shouldnt this be POST only? - post_id = request.GET.get("id") - for view in DBView.objects.filter(table=table_obj.name): - if str(view.pk) == post_id: + if str(view.pk) == view_id: view.is_default = True else: view.is_default = False @@ -544,13 +543,13 @@ def table_view_set_default_view(request: HttpRequest, table: str) -> HttpRespons return redirect("dataedit:view", table=table_obj.name) -def table_view_delete_view(request: HttpRequest, table: str) -> HttpResponse: +@require_POST +def table_view_delete_view( + request: HttpRequest, table: str, view_id: int +) -> HttpResponse: table_obj = table_or_404(table=table) - # TODO: shouldnt this be POST only? - post_id = request.GET.get("id") - - view = DBView.objects.get(id=post_id, table=table_obj.name) + view = DBView.objects.get(pk=view_id, table=table_obj.name) view.delete() return redirect("dataedit:view", table=table_obj.name) @@ -684,7 +683,7 @@ def iter_oem_key_order(metadata: dict): table_label = table_obj.human_readable_name table_views = DBView.objects.filter(table=table) - default = DBView(name="default", type="table", table=table) + default_view = DBView.get_or_create_default(table=table) view_id = request.GET.get("view") embargo = Embargo.objects.filter(table=table_obj).first() @@ -697,18 +696,13 @@ def iter_oem_key_order(metadata: dict): else: embargo_time_left = "No embargo data available" - if view_id == "default": - current_view = default - current_view.save() - else: - try: - # at first, try to use the view, that is passed as get argument - current_view = table_views.get(id=view_id) - except ObjectDoesNotExist: - current_view = default - current_view.save() - - table_views = list(chain((default,), table_views)) + try: + # at first, try to use the view, that is passed as get argument + current_view = table_views.get(id=view_id) + except ObjectDoesNotExist: + current_view = default_view + + table_views = [default_view, *table_views.exclude(pk=default_view.pk)] ######################################################### # Get open peer review process related metadata #