From c1953adaab52b7cfcef74fbbaba70ffebc8347ac Mon Sep 17 00:00:00 2001 From: Ricardo Dahis Date: Mon, 4 Nov 2024 18:04:27 +1100 Subject: [PATCH 1/2] feat: observation_level.order; working except when editing metadata fields on Admin --- backend/apps/api/v1/admin.py | 97 +++++++++++- backend/apps/api/v1/forms/__init__.py | 1 + backend/apps/api/v1/forms/admin_form.py | 13 +- .../forms/reorder_observation_levels_form.py | 8 + .../migrations/0039_observationlevel_order.py | 18 +++ ...0040_initialize_observation_level_order.py | 42 ++++++ ...alter_observationlevel_options_and_more.py | 140 ++++++++++++++++++ backend/apps/api/v1/models.py | 41 ++--- .../admin/reorder_observation_levels.html | 32 ++++ 9 files changed, 369 insertions(+), 23 deletions(-) create mode 100644 backend/apps/api/v1/forms/reorder_observation_levels_form.py create mode 100644 backend/apps/api/v1/migrations/0039_observationlevel_order.py create mode 100644 backend/apps/api/v1/migrations/0040_initialize_observation_level_order.py create mode 100644 backend/apps/api/v1/migrations/0041_alter_observationlevel_options_and_more.py create mode 100644 backend/apps/api/v1/templates/admin/reorder_observation_levels.html diff --git a/backend/apps/api/v1/admin.py b/backend/apps/api/v1/admin.py index 35d2ec57..b875cc0e 100644 --- a/backend/apps/api/v1/admin.py +++ b/backend/apps/api/v1/admin.py @@ -12,7 +12,6 @@ from django.urls import reverse from modeltranslation.admin import TabbedTranslationAdmin, TranslationStackedInline from ordered_model.admin import OrderedInlineModelAdminMixin, OrderedStackedInline - from backend.apps.api.v1.filters import ( DatasetOrganizationListFilter, OrganizationImageListFilter, @@ -32,6 +31,7 @@ PollInlineForm, ReorderColumnsForm, ReorderTablesForm, + ReorderObservationLevelsForm, TableInlineForm, UpdateInlineForm, ) @@ -140,17 +140,23 @@ class CloudTableInline(admin.StackedInline): ] -class ObservationLevelInline(admin.StackedInline): +class ObservationLevelInline(OrderedStackedInline): model = ObservationLevel form = ObservationLevelInlineForm extra = 0 fields = [ - "id", "entity", + "order", + "move_up_down_links", + ] + readonly_fields = [ + "order", + "move_up_down_links", ] autocomplete_fields = [ "entity", ] + ordering = ["order"] class TableInline(OrderedTranslatedInline): @@ -439,6 +445,48 @@ def reset_column_order(modeladmin, request, queryset): reset_column_order.short_description = "Reiniciar ordem das colunas" +def reorder_observation_levels(modeladmin, request, queryset): + """Reorder observation levels in respect to parent""" + if "do_action" in request.POST: + form = ReorderObservationLevelsForm(request.POST) + if form.is_valid(): + if queryset.count() != 1: + messages.error( + request, + "To pass the names manually you must select only one parent.", + ) + return + + parent = queryset.first() + ordered_entities = form.cleaned_data["ordered_entities"].split() + + # Get observation levels for this parent + if hasattr(parent, 'observation_levels'): + obs_levels = parent.observation_levels.all() + + # Create a mapping of entity names to observation levels + obs_by_entity = {ol.entity.name: ol for ol in obs_levels} + + # Update order based on provided entity names + for i, entity_name in enumerate(ordered_entities): + if entity_name in obs_by_entity: + obs_by_entity[entity_name].order = i + obs_by_entity[entity_name].save() + + messages.success(request, "Observation levels reordered successfully") + else: + messages.error(request, "Selected object has no observation levels") + else: + form = ReorderObservationLevelsForm() + return render( + request, + "admin/reorder_observation_levels.html", + {"title": "Reorder observation levels", "parents": queryset, "form": form}, + ) + +reorder_observation_levels.short_description = "Alterar ordem dos níveis de observação" + + ################################################################################ # Model Admins ################################################################################ @@ -564,6 +612,7 @@ class TableAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin): actions = [ reorder_columns, reset_column_order, + reorder_observation_levels, update_table_metadata, update_table_neighbors, update_page_views, @@ -696,7 +745,37 @@ class ColumnOriginalNameAdmin(TabbedTranslationAdmin): inlines = [CoverageInline] +def reset_observation_level_order(modeladmin, request, queryset): + """Reset observation level order in respect to parent""" + # Group observation levels by their parent + by_table = {} + by_raw_data_source = {} + by_information_request = {} + by_analysis = {} + + for obs in queryset: + if obs.table_id: + by_table.setdefault(obs.table_id, []).append(obs) + elif obs.raw_data_source_id: + by_raw_data_source.setdefault(obs.raw_data_source_id, []).append(obs) + elif obs.information_request_id: + by_information_request.setdefault(obs.information_request_id, []).append(obs) + elif obs.analysis_id: + by_analysis.setdefault(obs.analysis_id, []).append(obs) + + # Reset order within each parent group + for parent_levels in [by_table, by_raw_data_source, by_information_request, by_analysis]: + for levels in parent_levels.values(): + sorted_levels = sorted(levels, key=lambda x: x.entity.name) + for i, obs_level in enumerate(sorted_levels): + obs_level.order = i + obs_level.save() + +reset_observation_level_order.short_description = "Reiniciar ordem dos níveis de observação" + + class ObservationLevelAdmin(admin.ModelAdmin): + actions = [reset_observation_level_order] readonly_fields = [ "id", ] @@ -714,6 +793,9 @@ class ObservationLevelAdmin(admin.ModelAdmin): ] list_filter = [ "entity__category__name", + "table", + "raw_data_source", + "information_request", ] list_display = [ "__str__", @@ -724,6 +806,9 @@ class ObservationLevelAdmin(admin.ModelAdmin): class RawDataSourceAdmin(TabbedTranslationAdmin): + actions = [ + reorder_observation_levels, + ] list_display = ["name", "dataset", "created_at", "updated_at"] search_fields = ["name", "dataset__name"] readonly_fields = ["id", "created_at", "updated_at"] @@ -742,6 +827,9 @@ class RawDataSourceAdmin(TabbedTranslationAdmin): class InformationRequestAdmin(TabbedTranslationAdmin): + actions = [ + reorder_observation_levels, + ] list_display = ["__str__", "dataset", "created_at", "updated_at"] search_fields = ["__str__", "dataset__name"] readonly_fields = ["id", "created_at", "updated_at"] @@ -1029,6 +1117,9 @@ class AnalysisTypeAdmin(TabbedTranslationAdmin): class AnalysisAdmin(TabbedTranslationAdmin): + actions = [ + reorder_observation_levels, + ] readonly_fields = [ "id", ] diff --git a/backend/apps/api/v1/forms/__init__.py b/backend/apps/api/v1/forms/__init__.py index 61e03e15..14a90ea4 100644 --- a/backend/apps/api/v1/forms/__init__.py +++ b/backend/apps/api/v1/forms/__init__.py @@ -11,3 +11,4 @@ ) from backend.apps.api.v1.forms.reorder_columns_form import ReorderColumnsForm # noqa: F401 from backend.apps.api.v1.forms.reorder_tables_form import ReorderTablesForm # noqa: F401 +from backend.apps.api.v1.forms.reorder_observation_levels_form import ReorderObservationLevelsForm # noqa: F401 \ No newline at end of file diff --git a/backend/apps/api/v1/forms/admin_form.py b/backend/apps/api/v1/forms/admin_form.py index 222c5f7b..a2783138 100644 --- a/backend/apps/api/v1/forms/admin_form.py +++ b/backend/apps/api/v1/forms/admin_form.py @@ -94,7 +94,18 @@ class Meta(UUIDHiddenIdForm.Meta): class ObservationLevelInlineForm(UUIDHiddenIdForm): class Meta(UUIDHiddenIdForm.Meta): model = ObservationLevel - fields = "__all__" + fields = [ + "id", + "entity", + "table", + "raw_data_source", + "information_request", + "analysis", + ] + readonly_fields = [ + "order", + "move_up_down_links", + ] class CoverageInlineForm(UUIDHiddenIdForm): diff --git a/backend/apps/api/v1/forms/reorder_observation_levels_form.py b/backend/apps/api/v1/forms/reorder_observation_levels_form.py new file mode 100644 index 00000000..46dd4ad9 --- /dev/null +++ b/backend/apps/api/v1/forms/reorder_observation_levels_form.py @@ -0,0 +1,8 @@ +from django import forms + +class ReorderObservationLevelsForm(forms.Form): + ordered_entities = forms.CharField( + required=False, + widget=forms.Textarea(attrs={"rows": 10, "cols": 40}), + help_text="Enter entity names one per line in desired order", + ) \ No newline at end of file diff --git a/backend/apps/api/v1/migrations/0039_observationlevel_order.py b/backend/apps/api/v1/migrations/0039_observationlevel_order.py new file mode 100644 index 00000000..3d96662e --- /dev/null +++ b/backend/apps/api/v1/migrations/0039_observationlevel_order.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +from django.db import migrations, models + +class Migration(migrations.Migration): + dependencies = [ + ("v1", "0038_rename_level_area_administrative_level"), + ] + + operations = [ + migrations.AddField( + model_name="observationlevel", + name="order", + field=models.PositiveIntegerField( + db_index=True, default=0, editable=False, verbose_name="order" + ), + preserve_default=False, + ), + ] \ No newline at end of file diff --git a/backend/apps/api/v1/migrations/0040_initialize_observation_level_order.py b/backend/apps/api/v1/migrations/0040_initialize_observation_level_order.py new file mode 100644 index 00000000..5962db8b --- /dev/null +++ b/backend/apps/api/v1/migrations/0040_initialize_observation_level_order.py @@ -0,0 +1,42 @@ +from django.db import migrations + +def initialize_observation_level_order(apps, schema_editor): + ObservationLevel = apps.get_model('v1', 'ObservationLevel') + + # Group by each possible parent type and set order + for table_id in ObservationLevel.objects.values_list('table_id', flat=True).distinct(): + if table_id: + for i, ol in enumerate(ObservationLevel.objects.filter(table_id=table_id)): + ol.order = i + ol.save() + + for rds_id in ObservationLevel.objects.values_list('raw_data_source_id', flat=True).distinct(): + if rds_id: + for i, ol in enumerate(ObservationLevel.objects.filter(raw_data_source_id=rds_id)): + ol.order = i + ol.save() + + for ir_id in ObservationLevel.objects.values_list('information_request_id', flat=True).distinct(): + if ir_id: + for i, ol in enumerate(ObservationLevel.objects.filter(information_request_id=ir_id)): + ol.order = i + ol.save() + + for analysis_id in ObservationLevel.objects.values_list('analysis_id', flat=True).distinct(): + if analysis_id: + for i, ol in enumerate(ObservationLevel.objects.filter(analysis_id=analysis_id)): + ol.order = i + ol.save() + +def reverse_migration(apps, schema_editor): + ObservationLevel = apps.get_model('v1', 'ObservationLevel') + ObservationLevel.objects.all().update(order=0) + +class Migration(migrations.Migration): + dependencies = [ + ('v1', '0039_observationlevel_order'), + ] + + operations = [ + migrations.RunPython(initialize_observation_level_order, reverse_migration), + ] \ No newline at end of file diff --git a/backend/apps/api/v1/migrations/0041_alter_observationlevel_options_and_more.py b/backend/apps/api/v1/migrations/0041_alter_observationlevel_options_and_more.py new file mode 100644 index 00000000..fdd3d626 --- /dev/null +++ b/backend/apps/api/v1/migrations/0041_alter_observationlevel_options_and_more.py @@ -0,0 +1,140 @@ +# Generated by Django 4.2.16 on 2024-11-04 03:54 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('v1', '0040_initialize_observation_level_order'), + ] + + operations = [ + migrations.AlterModelOptions( + name='observationlevel', + options={'ordering': ['order'], 'verbose_name': 'Observation Level', 'verbose_name_plural': 'Observation Levels'}, + ), + migrations.AlterField( + model_name='analysis', + name='analysis_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='analyses', to='v1.analysistype'), + ), + migrations.AlterField( + model_name='area', + name='administrative_level', + field=models.IntegerField(blank=True, choices=[(0, '0'), (1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5')], null=True), + ), + migrations.AlterField( + model_name='area', + name='entity', + field=models.ForeignKey(blank=True, limit_choices_to={'category__slug': 'spatial'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='areas', to='v1.entity'), + ), + migrations.AlterField( + model_name='area', + name='parent', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='v1.area'), + ), + migrations.AlterField( + model_name='column', + name='bigquery_type', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='columns', to='v1.bigquerytype'), + ), + migrations.AlterField( + model_name='column', + name='directory_primary_key', + field=models.ForeignKey(blank=True, limit_choices_to={'is_primary_key': True, 'table__is_directory': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='columns', to='v1.column'), + ), + migrations.AlterField( + model_name='column', + name='observation_level', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='columns', to='v1.observationlevel'), + ), + migrations.AlterField( + model_name='column', + name='status', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='columns', to='v1.status'), + ), + migrations.AlterField( + model_name='coverage', + name='area', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='coverages', to='v1.area'), + ), + migrations.AlterField( + model_name='dataset', + name='organization', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='datasets', to='v1.organization'), + ), + migrations.AlterField( + model_name='entity', + name='category', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='entities', to='v1.entitycategory'), + ), + migrations.AlterField( + model_name='informationrequest', + name='status', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='information_requests', to='v1.status'), + ), + migrations.AlterField( + model_name='observationlevel', + name='entity', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observation_levels', to='v1.entity'), + ), + migrations.AlterField( + model_name='organization', + name='area', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='organizations', to='v1.area'), + ), + migrations.AlterField( + model_name='poll', + name='entity', + field=models.ForeignKey(blank=True, limit_choices_to={'category__slug': 'datetime'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='polls', to='v1.entity'), + ), + migrations.AlterField( + model_name='qualitycheck', + name='pipeline', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quality_checks', to='v1.pipeline'), + ), + migrations.AlterField( + model_name='rawdatasource', + name='availability', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='raw_data_sources', to='v1.availability'), + ), + migrations.AlterField( + model_name='rawdatasource', + name='license', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='raw_data_sources', to='v1.license'), + ), + migrations.AlterField( + model_name='table', + name='data_cleaned_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tables_cleaned', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='table', + name='license', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tables', to='v1.license'), + ), + migrations.AlterField( + model_name='table', + name='partner_organization', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='partner_tables', to='v1.organization'), + ), + migrations.AlterField( + model_name='table', + name='pipeline', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tables', to='v1.pipeline'), + ), + migrations.AlterField( + model_name='table', + name='published_by', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tables_published', to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='update', + name='entity', + field=models.ForeignKey(blank=True, limit_choices_to={'category__slug': 'datetime'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updates', to='v1.entity'), + ), + ] diff --git a/backend/apps/api/v1/models.py b/backend/apps/api/v1/models.py index 658aa8d4..9c677cc9 100644 --- a/backend/apps/api/v1/models.py +++ b/backend/apps/api/v1/models.py @@ -14,6 +14,9 @@ from backend.custom.storage import OverwriteStorage, upload_to, validate_image from backend.custom.utils import check_kebab_case, check_snake_case +import logging + +logger = logging.getLogger('django.request') class Area(BaseModel): """Area model""" @@ -1672,7 +1675,7 @@ class Meta: ordering = ["slug"] -class ObservationLevel(BaseModel): +class ObservationLevel(BaseModel, OrderedModel): """Model definition for ObservationLevel.""" id = models.UUIDField(primary_key=True, default=uuid4) @@ -1708,6 +1711,8 @@ class ObservationLevel(BaseModel): related_name="observation_levels", ) + order_with_respect_to = ('table', 'raw_data_source', 'information_request', 'analysis') + graphql_nested_filter_fields_whitelist = ["id"] def __str__(self): @@ -1719,25 +1724,23 @@ class Meta: db_table = "observation_level" verbose_name = "Observation Level" verbose_name_plural = "Observation Levels" - ordering = ["id"] + ordering = ["order"] - def clean(self) -> None: - """Assert that only one of "table", "raw_data_source", "information_request" is set""" - count = 0 - if self.table: - count += 1 - if self.raw_data_source: - count += 1 - if self.information_request: - count += 1 - if self.analysis: - count += 1 - if count != 1: - raise ValidationError( - "One and only one of 'table', 'raw_data_source', " - "'information_request', 'analysis' must be set." - ) - return super().clean() + def get_ordering_queryset(self): + """Get queryset for ordering within the appropriate parent""" + qs = super().get_ordering_queryset() + + # Filter by the appropriate parent field + if self.table_id: + return qs.filter(table_id=self.table_id) + elif self.raw_data_source_id: + return qs.filter(raw_data_source_id=self.raw_data_source_id) + elif self.information_request_id: + return qs.filter(information_request_id=self.information_request_id) + elif self.analysis_id: + return qs.filter(analysis_id=self.analysis_id) + + return qs class DateTimeRange(BaseModel): diff --git a/backend/apps/api/v1/templates/admin/reorder_observation_levels.html b/backend/apps/api/v1/templates/admin/reorder_observation_levels.html new file mode 100644 index 00000000..00653b68 --- /dev/null +++ b/backend/apps/api/v1/templates/admin/reorder_observation_levels.html @@ -0,0 +1,32 @@ +{% extends "admin/base_site.html" %} + +{% block content %} +
+ {% csrf_token %} + + + +
+ {{ form.ordered_entities.label_tag}} +
+ {{ form.ordered_entities }} + {{ form.ordered_entities.help_text }} + {{ form.ordered_entities.errors }} +
+
+ +
+ +
+ +

Reorder observation levels for:

+ +
+{% endblock %} \ No newline at end of file From fc58d723f85b270b62c32f65df4f17c46bf081ef Mon Sep 17 00:00:00 2001 From: Ricardo Dahis Date: Wed, 6 Nov 2024 14:28:14 +1100 Subject: [PATCH 2/2] fix: observation level inline working after edits --- backend/apps/api/v1/admin.py | 38 +++-- ...alter_observationlevel_options_and_more.py | 140 ------------------ ...rder.py => 0046_observationlevel_order.py} | 2 +- ...047_initialize_observation_level_order.py} | 2 +- ...alter_observationlevel_options_and_more.py | 20 +++ .../admin/observation_level_inline.html | 15 ++ 6 files changed, 65 insertions(+), 152 deletions(-) delete mode 100644 backend/apps/api/v1/migrations/0041_alter_observationlevel_options_and_more.py rename backend/apps/api/v1/migrations/{0039_observationlevel_order.py => 0046_observationlevel_order.py} (87%) rename backend/apps/api/v1/migrations/{0040_initialize_observation_level_order.py => 0047_initialize_observation_level_order.py} (97%) create mode 100644 backend/apps/api/v1/migrations/0048_alter_observationlevel_options_and_more.py create mode 100644 backend/apps/api/v1/templates/admin/observation_level_inline.html diff --git a/backend/apps/api/v1/admin.py b/backend/apps/api/v1/admin.py index cd1d4caf..bd34b8cf 100644 --- a/backend/apps/api/v1/admin.py +++ b/backend/apps/api/v1/admin.py @@ -165,20 +165,37 @@ class ObservationLevelInline(OrderedStackedInline): model = ObservationLevel form = ObservationLevelInlineForm extra = 0 - fields = [ - "entity", - "order", - "move_up_down_links", - ] + show_change_link = True readonly_fields = [ + "entity", "order", "move_up_down_links", ] - autocomplete_fields = [ - "entity", - ] + fields = readonly_fields + template = 'admin/observation_level_inline.html' ordering = ["order"] + def get_formset(self, request, obj=None, **kwargs): + self.parent_obj = obj + return super().get_formset(request, obj, **kwargs) + + def get_ordering_prefix(self): + """Return the appropriate ordering prefix based on parent model""" + if isinstance(self.parent_obj, Table): + return 'table' + elif isinstance(self.parent_obj, RawDataSource): + return 'rawdatasource' + elif isinstance(self.parent_obj, InformationRequest): + return 'informationrequest' + elif isinstance(self.parent_obj, Analysis): + return 'analysis' + return super().get_ordering_prefix() + + def has_add_permission(self, request, obj=None): + return False + + def has_change_permission(self, request, obj=None): + return False class TableInline(OrderedTranslatedInline): model = Table @@ -888,7 +905,7 @@ class ObservationLevelAdmin(admin.ModelAdmin): ] -class RawDataSourceAdmin(TabbedTranslationAdmin): +class RawDataSourceAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin): actions = [ reorder_observation_levels, ] @@ -905,11 +922,12 @@ class RawDataSourceAdmin(TabbedTranslationAdmin): ] inlines = [ CoverageInline, + ObservationLevelInline, PollInline, ] -class InformationRequestAdmin(TabbedTranslationAdmin): +class InformationRequestAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin): actions = [ reorder_observation_levels, ] diff --git a/backend/apps/api/v1/migrations/0041_alter_observationlevel_options_and_more.py b/backend/apps/api/v1/migrations/0041_alter_observationlevel_options_and_more.py deleted file mode 100644 index fdd3d626..00000000 --- a/backend/apps/api/v1/migrations/0041_alter_observationlevel_options_and_more.py +++ /dev/null @@ -1,140 +0,0 @@ -# Generated by Django 4.2.16 on 2024-11-04 03:54 - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('v1', '0040_initialize_observation_level_order'), - ] - - operations = [ - migrations.AlterModelOptions( - name='observationlevel', - options={'ordering': ['order'], 'verbose_name': 'Observation Level', 'verbose_name_plural': 'Observation Levels'}, - ), - migrations.AlterField( - model_name='analysis', - name='analysis_type', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='analyses', to='v1.analysistype'), - ), - migrations.AlterField( - model_name='area', - name='administrative_level', - field=models.IntegerField(blank=True, choices=[(0, '0'), (1, '1'), (2, '2'), (3, '3'), (4, '4'), (5, '5')], null=True), - ), - migrations.AlterField( - model_name='area', - name='entity', - field=models.ForeignKey(blank=True, limit_choices_to={'category__slug': 'spatial'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='areas', to='v1.entity'), - ), - migrations.AlterField( - model_name='area', - name='parent', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='v1.area'), - ), - migrations.AlterField( - model_name='column', - name='bigquery_type', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='columns', to='v1.bigquerytype'), - ), - migrations.AlterField( - model_name='column', - name='directory_primary_key', - field=models.ForeignKey(blank=True, limit_choices_to={'is_primary_key': True, 'table__is_directory': True}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='columns', to='v1.column'), - ), - migrations.AlterField( - model_name='column', - name='observation_level', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='columns', to='v1.observationlevel'), - ), - migrations.AlterField( - model_name='column', - name='status', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='columns', to='v1.status'), - ), - migrations.AlterField( - model_name='coverage', - name='area', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='coverages', to='v1.area'), - ), - migrations.AlterField( - model_name='dataset', - name='organization', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='datasets', to='v1.organization'), - ), - migrations.AlterField( - model_name='entity', - name='category', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='entities', to='v1.entitycategory'), - ), - migrations.AlterField( - model_name='informationrequest', - name='status', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='information_requests', to='v1.status'), - ), - migrations.AlterField( - model_name='observationlevel', - name='entity', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='observation_levels', to='v1.entity'), - ), - migrations.AlterField( - model_name='organization', - name='area', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='organizations', to='v1.area'), - ), - migrations.AlterField( - model_name='poll', - name='entity', - field=models.ForeignKey(blank=True, limit_choices_to={'category__slug': 'datetime'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='polls', to='v1.entity'), - ), - migrations.AlterField( - model_name='qualitycheck', - name='pipeline', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quality_checks', to='v1.pipeline'), - ), - migrations.AlterField( - model_name='rawdatasource', - name='availability', - field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='raw_data_sources', to='v1.availability'), - ), - migrations.AlterField( - model_name='rawdatasource', - name='license', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='raw_data_sources', to='v1.license'), - ), - migrations.AlterField( - model_name='table', - name='data_cleaned_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tables_cleaned', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='table', - name='license', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tables', to='v1.license'), - ), - migrations.AlterField( - model_name='table', - name='partner_organization', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='partner_tables', to='v1.organization'), - ), - migrations.AlterField( - model_name='table', - name='pipeline', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tables', to='v1.pipeline'), - ), - migrations.AlterField( - model_name='table', - name='published_by', - field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='tables_published', to=settings.AUTH_USER_MODEL), - ), - migrations.AlterField( - model_name='update', - name='entity', - field=models.ForeignKey(blank=True, limit_choices_to={'category__slug': 'datetime'}, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='updates', to='v1.entity'), - ), - ] diff --git a/backend/apps/api/v1/migrations/0039_observationlevel_order.py b/backend/apps/api/v1/migrations/0046_observationlevel_order.py similarity index 87% rename from backend/apps/api/v1/migrations/0039_observationlevel_order.py rename to backend/apps/api/v1/migrations/0046_observationlevel_order.py index 3d96662e..fbc194ef 100644 --- a/backend/apps/api/v1/migrations/0039_observationlevel_order.py +++ b/backend/apps/api/v1/migrations/0046_observationlevel_order.py @@ -3,7 +3,7 @@ class Migration(migrations.Migration): dependencies = [ - ("v1", "0038_rename_level_area_administrative_level"), + ("v1", "0045_add_measurement_categories_and_units"), ] operations = [ diff --git a/backend/apps/api/v1/migrations/0040_initialize_observation_level_order.py b/backend/apps/api/v1/migrations/0047_initialize_observation_level_order.py similarity index 97% rename from backend/apps/api/v1/migrations/0040_initialize_observation_level_order.py rename to backend/apps/api/v1/migrations/0047_initialize_observation_level_order.py index 5962db8b..b8425838 100644 --- a/backend/apps/api/v1/migrations/0040_initialize_observation_level_order.py +++ b/backend/apps/api/v1/migrations/0047_initialize_observation_level_order.py @@ -34,7 +34,7 @@ def reverse_migration(apps, schema_editor): class Migration(migrations.Migration): dependencies = [ - ('v1', '0039_observationlevel_order'), + ('v1', '0046_observationlevel_order'), ] operations = [ diff --git a/backend/apps/api/v1/migrations/0048_alter_observationlevel_options_and_more.py b/backend/apps/api/v1/migrations/0048_alter_observationlevel_options_and_more.py new file mode 100644 index 00000000..4ae3ed76 --- /dev/null +++ b/backend/apps/api/v1/migrations/0048_alter_observationlevel_options_and_more.py @@ -0,0 +1,20 @@ +# Generated by Django 4.2.16 on 2024-11-04 03:54 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('v1', '0047_initialize_observation_level_order'), + ] + + operations = [ + migrations.AlterModelOptions( + name='observationlevel', + options={'ordering': ['order'], 'verbose_name': 'Observation Level', 'verbose_name_plural': 'Observation Levels'}, + ), + ] diff --git a/backend/apps/api/v1/templates/admin/observation_level_inline.html b/backend/apps/api/v1/templates/admin/observation_level_inline.html new file mode 100644 index 00000000..f95ee531 --- /dev/null +++ b/backend/apps/api/v1/templates/admin/observation_level_inline.html @@ -0,0 +1,15 @@ +{% load i18n admin_urls %} + +{# Include the standard stacked inline template #} +{% include "admin/edit_inline/stacked.html" %} + +{# Add the custom add button #} +{% if inline_admin_formset.formset.instance.pk %} + +{% endif %} \ No newline at end of file