Skip to content

Commit f3eeaf8

Browse files
Merge pull request #844 from basedosdados/fix-admin-pages
fix: table admin page
2 parents 6a06504 + ed596e8 commit f3eeaf8

File tree

3 files changed

+223
-46
lines changed

3 files changed

+223
-46
lines changed

backend/apps/api/v1/admin.py

Lines changed: 216 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,15 @@
1212
from django.shortcuts import render
1313
from django.urls import reverse
1414
from django.utils.html import format_html
15+
from django.utils.safestring import mark_safe
1516
from django_admin_inline_paginator_plus.admin import StackedInlinePaginated
1617
from modeltranslation.admin import TabbedTranslationAdmin, TranslationStackedInline
1718
from ordered_model.admin import OrderedInlineModelAdminMixin, OrderedStackedInline
1819

1920
from backend.apps.api.v1.filters import (
2021
AreaAdministrativeLevelFilter,
2122
AreaParentFilter,
22-
DatasetOrganizationListFilter,
2323
OrganizationImageListFilter,
24-
TableCoverageListFilter,
25-
TableDirectoryListFilter,
26-
TableObservationListFilter,
27-
TableOrganizationListFilter,
2824
)
2925
from backend.apps.api.v1.forms import (
3026
ColumnInlineForm,
@@ -617,35 +613,22 @@ class DatasetAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
617613
"full_slug",
618614
"spatial_coverage",
619615
"temporal_coverage",
620-
"contains_tables",
621-
"contains_raw_data_sources",
622-
"contains_information_requests",
623-
"contains_closed_data",
624-
"contains_direct_download_free",
625-
"contains_direct_download_paid",
626-
"contains_temporalcoverage_free",
627-
"contains_temporalcoverage_paid",
628616
"page_views",
629617
"created_at",
630618
"updated_at",
631-
"related_objects",
632619
]
633620
search_fields = ["name", "slug", "organizations__name"]
634621
filter_horizontal = [
635622
"tags",
636623
"themes",
637624
"organizations",
638625
]
639-
list_filter = [
640-
DatasetOrganizationListFilter,
641-
]
642626
list_display = [
643627
"name",
644628
"get_organizations",
645-
"spatial_coverage",
646629
"temporal_coverage",
647-
"related_objects",
648-
"created_at",
630+
"related_tables",
631+
"related_raw_data_sources",
649632
"updated_at",
650633
]
651634
ordering = ["-updated_at"]
@@ -656,7 +639,7 @@ def get_organizations(self, obj):
656639

657640
get_organizations.short_description = "Organizations"
658641

659-
def related_objects(self, obj):
642+
def related_tables(self, obj):
660643
return format_html(
661644
"<a class='related-widget-wrapper-link add-related' "
662645
"href='/admin/v1/table/add/?dataset={0}&_to_field=id&_popup=1'>{1} {2}</a>",
@@ -665,7 +648,18 @@ def related_objects(self, obj):
665648
"tables" if obj.tables.count() > 1 else "table",
666649
)
667650

668-
related_objects.short_description = "Tables"
651+
related_tables.short_description = "Tables"
652+
653+
def related_raw_data_sources(self, obj):
654+
return format_html(
655+
"<a class='related-widget-wrapper-link add-related' "
656+
"href='/admin/v1/table/add/?dataset={0}&_to_field=id&_popup=1'>{1} {2}</a>",
657+
obj.id,
658+
obj.raw_data_sources.count(),
659+
"sources" if obj.raw_data_sources.count() > 1 else "sources",
660+
)
661+
662+
related_raw_data_sources.short_description = "Sources"
669663

670664

671665
class CustomUserAdmin(UserAdmin):
@@ -679,6 +673,31 @@ class CustomUserAdmin(UserAdmin):
679673

680674
class TableAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
681675
form = TableForm
676+
fieldsets = (
677+
(
678+
None,
679+
{
680+
"fields": (
681+
"dataset",
682+
"get_table_url",
683+
"status",
684+
"name",
685+
"slug",
686+
"description",
687+
"get_datetime_ranges_display",
688+
"number_columns",
689+
"number_rows",
690+
"get_update_display",
691+
"raw_data_source",
692+
"published_by",
693+
"data_cleaned_by",
694+
"auxiliary_files_url",
695+
"created_at",
696+
"updated_at",
697+
)
698+
},
699+
),
700+
)
682701
actions = [
683702
reorder_columns,
684703
reset_column_order,
@@ -695,7 +714,8 @@ class TableAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
695714
UpdateInline,
696715
]
697716
readonly_fields = [
698-
"id",
717+
"get_table_url",
718+
"get_datetime_ranges_display",
699719
"partitions",
700720
"created_at",
701721
"updated_at",
@@ -706,21 +726,15 @@ class TableAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
706726
"number_columns",
707727
"uncompressed_file_size",
708728
"compressed_file_size",
709-
"contains_open_data",
710-
"contains_direct_download_free",
711-
"contains_direct_download_paid",
712-
"contains_temporalcoverage_free",
713-
"contains_temporalcoverage_paid",
714-
"contains_closed_data",
715729
"page_views",
730+
"get_update_display",
716731
]
717732
search_fields = [
718733
"name",
719734
"dataset__name",
720735
]
721736
autocomplete_fields = [
722737
"dataset",
723-
"partner_organization",
724738
"published_by",
725739
"data_cleaned_by",
726740
]
@@ -730,17 +744,9 @@ class TableAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
730744
list_display = [
731745
"name",
732746
"dataset",
733-
"get_publishers",
734-
"get_data_cleaners",
735747
"created_at",
736748
"updated_at",
737749
]
738-
list_filter = [
739-
TableOrganizationListFilter,
740-
TableCoverageListFilter,
741-
TableObservationListFilter,
742-
TableDirectoryListFilter,
743-
]
744750
ordering = ["-updated_at"]
745751

746752
def get_queryset(self, request):
@@ -763,6 +769,176 @@ def get_data_cleaners(self, obj):
763769

764770
get_data_cleaners.short_description = "Data Cleaners"
765771

772+
def get_table_url(self, obj):
773+
"""Get the clickable URL for the table"""
774+
website_url = f"https://basedosdados.org/dataset/{obj.dataset.id}?table={obj.id}"
775+
website_html = format_html(
776+
'<a href="{}" target="_blank">🖥️ Ver tabela no site</a>', website_url
777+
)
778+
779+
cloud_tables = obj.cloud_tables.all()
780+
781+
if len(cloud_tables) == 0:
782+
add_cloud_table_url = reverse("admin:v1_cloudtable_add") + f"?table={obj.id}"
783+
gcp_html = format_html(
784+
'No cloud table found. <a href="{}">Create here</a>', add_cloud_table_url
785+
)
786+
787+
elif len(cloud_tables) > 1:
788+
cloud_table_tab = reverse("admin:v1_table_change") + "/#cloud-tables-tab"
789+
gcp_html = format_html(
790+
'More than 1 cloud table found. <a href="{}">Fix it here</a>', cloud_table_tab
791+
)
792+
793+
else:
794+
cloud_table = cloud_tables[0]
795+
gcp_dev_url = f"https://console.cloud.google.com/bigquery?p=basedosdados-dev&d={cloud_table.gcp_dataset_id}&t={cloud_table.gcp_table_id}&page=table"
796+
gcp_prod_url = f"https://console.cloud.google.com/bigquery?p=basedosdados&d={cloud_table.gcp_dataset_id}&t={cloud_table.gcp_table_id}&page=table"
797+
798+
# Gerando o HTML
799+
gcp_html = format_html(
800+
'<a href="{}" target="_blank">🧩 Ver tabela em BigQuery-dev</a><br>'
801+
'<a href="{}" target="_blank">🧊 Ver tabela em BigQuery-prod</a>',
802+
gcp_dev_url,
803+
gcp_prod_url,
804+
)
805+
806+
return format_html("{}<br>{}", website_html, gcp_html)
807+
808+
get_table_url.short_description = "Table URLs"
809+
810+
def get_datetime_ranges_display(self, obj):
811+
"""Display datetime ranges with links to their admin pages"""
812+
coverages = list(obj.coverages.all())
813+
links = []
814+
815+
if len(coverages) == 0:
816+
add_coverage_url = reverse("admin:v1_coverage_add") + f"?table={obj.id}"
817+
return format_html("No coverages found. <a href='{}'>Create here</a>", add_coverage_url)
818+
819+
for cov in coverages:
820+
url_coverage = cov.admin_url
821+
add_date_time_range_url = reverse("admin:v1_datetimerange_add") + f"?coverage={cov.id}"
822+
status = "Closed" if cov.is_closed else "Open"
823+
824+
if cov.datetime_ranges.count() == 0:
825+
links.append(
826+
format_html(
827+
"⚠️ <a href='{}'>{} coverage</a> found, but no Datetime Range."
828+
"<a href='{}'>Create here</a>",
829+
add_date_time_range_url,
830+
status,
831+
add_date_time_range_url,
832+
)
833+
)
834+
835+
ranges = sorted(cov.datetime_ranges.all(), key=lambda dt: str(dt))
836+
for dt_range in ranges:
837+
url_dt_range = reverse("admin:v1_datetimerange_change", args=[dt_range.id])
838+
links.append(
839+
format_html(
840+
'<a href="{}">{}</a> - <a href="{}">{} coverage</a>',
841+
url_dt_range,
842+
str(dt_range),
843+
url_coverage,
844+
status,
845+
)
846+
)
847+
848+
return format_html("<br>".join(links))
849+
850+
get_datetime_ranges_display.short_description = "DateTime Ranges"
851+
852+
def get_update_display(self, table_obj):
853+
"""Display update info"""
854+
855+
def check_if_there_is_only_one_object_connected(attr_label, connection_obj):
856+
campos = [f.name for f in connection_obj._meta.get_fields()]
857+
858+
if attr_label not in campos:
859+
return format_html(
860+
"The {} label was not found in {} model",
861+
attr_label,
862+
connection_obj._meta.verbose_name,
863+
)
864+
865+
obj_list = getattr(connection_obj, attr_label).all()
866+
change_url = connection_obj.admin_url + "#" + attr_label + "-tab"
867+
868+
# Se não houver objetos
869+
if len(obj_list) == 0:
870+
return format_html(
871+
"No {} found in {}. <a href='{}'>Create one</a>",
872+
attr_label,
873+
connection_obj._meta.verbose_name,
874+
change_url,
875+
)
876+
877+
# Se houver mais de 1 objeto
878+
elif len(obj_list) > 1:
879+
return format_html(
880+
"More than 1 {} found in {}. <a href='{}'>Fix it</a>",
881+
obj_list[0]._meta.verbose_name,
882+
connection_obj._meta.verbose_name,
883+
change_url,
884+
)
885+
886+
# Se houver exatamente 1 objeto
887+
else:
888+
selected_obj = obj_list[0]
889+
html = format_html(
890+
"<a href='{}'>{}</a> {} found in <a href='{}'>{}</a> ",
891+
selected_obj.admin_url,
892+
str(selected_obj),
893+
selected_obj._meta.verbose_name,
894+
change_url,
895+
connection_obj._meta.verbose_name,
896+
)
897+
return html
898+
899+
def check_if_there_is_only_one_raw_data_source_connected(table_object):
900+
"""Specific function to check Raw Data Source
901+
the instructions and conditionals are different from updates and polls"""
902+
raw_data_source_obj_list = getattr(table_object, "raw_data_source").all()
903+
if len(raw_data_source_obj_list) == 0:
904+
return None, format_html("No Raw Data Source found. Add one in the box bellow")
905+
906+
elif len(raw_data_source_obj_list) > 1:
907+
return None, format_html("More than 1 Raw Data Source found. Fix in the box bellow")
908+
909+
else:
910+
selected_obj = raw_data_source_obj_list[0]
911+
html = format_html(
912+
"<a href='{}'>Raw Data Source</a> found",
913+
selected_obj.admin_url,
914+
str(selected_obj),
915+
)
916+
return raw_data_source_obj_list[0], html
917+
918+
update_html = check_if_there_is_only_one_object_connected("updates", table_obj)
919+
920+
(
921+
raw_data_source_obj,
922+
raw_data_source_html,
923+
) = check_if_there_is_only_one_raw_data_source_connected(table_obj)
924+
925+
if raw_data_source_obj:
926+
raw_data_source_update_html = check_if_there_is_only_one_object_connected(
927+
"updates", raw_data_source_obj
928+
)
929+
print(f"{raw_data_source_update_html = }")
930+
poll_raw_data_source_html = check_if_there_is_only_one_object_connected(
931+
"polls", raw_data_source_obj
932+
)
933+
934+
raw_data_source_html = format_html(
935+
raw_data_source_update_html + "<br>" + poll_raw_data_source_html
936+
)
937+
938+
return format_html(update_html + "<br>" + raw_data_source_html)
939+
940+
get_update_display.short_description = "Update and Poll Info"
941+
766942

767943
class TableNeighborAdmin(admin.ModelAdmin):
768944
search_fields = [
@@ -1060,9 +1236,9 @@ def datetime_ranges_display(self, obj):
10601236

10611237
# Add link to add new datetime range
10621238
add_url = reverse("admin:v1_datetimerange_add") + f"?coverage={obj.id}"
1063-
links.append(format_html('<a class="addlink" href="{}">Add DateTime Range</a>', add_url))
1239+
links.append(mark_safe(f'<a class="addlink" href="{add_url}">Add DateTime Range</a>'))
10641240

1065-
return format_html("<br>".join(links))
1241+
return mark_safe("<br>".join(links))
10661242

10671243
datetime_ranges_display.short_description = "DateTime Ranges"
10681244

backend/apps/api/v1/forms/admin_form.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,12 @@ class Meta(UUIDHiddenIdForm.Meta):
8787
fields = [
8888
"id",
8989
"name",
90-
"name_staging",
91-
"description",
9290
"bigquery_type",
93-
"is_closed",
94-
"status",
95-
"is_primary_key",
96-
"table",
91+
"description",
92+
"covered_by_dictionary",
9793
"observation_level",
9894
"directory_primary_key",
95+
"is_primary_key",
9996
]
10097
readonly_fields = [
10198
"order",

backend/apps/api/v1/models.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -941,6 +941,8 @@ class Update(BaseModel):
941941
graphql_nested_filter_fields_whitelist = ["id"]
942942

943943
def __str__(self):
944+
if self.latest:
945+
return f"{self.latest.strftime('%Y-%m-%d')}: {str(self.frequency)} {str(self.entity)}"
944946
return f"{str(self.frequency)} {str(self.entity)}"
945947

946948
class Meta:
@@ -1010,6 +1012,8 @@ class Poll(BaseModel):
10101012
graphql_nested_filter_fields_whitelist = ["id"]
10111013

10121014
def __str__(self):
1015+
if self.latest:
1016+
return f"{self.latest.strftime('%Y-%m-%d')}: {str(self.frequency)} {str(self.entity)}"
10131017
return f"{str(self.frequency)} {str(self.entity)}"
10141018

10151019
class Meta:

0 commit comments

Comments
 (0)