Skip to content

Commit 65e37fd

Browse files
fix: add fields and fix order in table and dataset admin page
1 parent 01bee91 commit 65e37fd

File tree

3 files changed

+235
-34
lines changed

3 files changed

+235
-34
lines changed

backend/apps/api/v1/admin.py

Lines changed: 228 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
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 modeltranslation.admin import TabbedTranslationAdmin, TranslationStackedInline
1617
from ordered_model.admin import OrderedInlineModelAdminMixin, OrderedStackedInline
1718

1819
from backend.apps.api.v1.filters import (
1920
AreaAdministrativeLevelFilter,
2021
AreaParentFilter,
21-
DatasetOrganizationListFilter,
2222
OrganizationImageListFilter,
2323
TableCoverageListFilter,
2424
TableDirectoryListFilter,
@@ -611,35 +611,22 @@ class DatasetAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
611611
"full_slug",
612612
"spatial_coverage",
613613
"temporal_coverage",
614-
"contains_tables",
615-
"contains_raw_data_sources",
616-
"contains_information_requests",
617-
"contains_closed_data",
618-
"contains_direct_download_free",
619-
"contains_direct_download_paid",
620-
"contains_temporalcoverage_free",
621-
"contains_temporalcoverage_paid",
622614
"page_views",
623615
"created_at",
624616
"updated_at",
625-
"related_objects",
626617
]
627618
search_fields = ["name", "slug", "organizations__name"]
628619
filter_horizontal = [
629620
"tags",
630621
"themes",
631622
"organizations",
632623
]
633-
list_filter = [
634-
DatasetOrganizationListFilter,
635-
]
636624
list_display = [
637625
"name",
638626
"get_organizations",
639-
"spatial_coverage",
640627
"temporal_coverage",
641-
"related_objects",
642-
"created_at",
628+
"related_tables",
629+
"related_raw_data_sources",
643630
"updated_at",
644631
]
645632
ordering = ["-updated_at"]
@@ -650,7 +637,7 @@ def get_organizations(self, obj):
650637

651638
get_organizations.short_description = "Organizations"
652639

653-
def related_objects(self, obj):
640+
def related_tables(self, obj):
654641
return format_html(
655642
"<a class='related-widget-wrapper-link add-related' "
656643
"href='/admin/v1/table/add/?dataset={0}&_to_field=id&_popup=1'>{1} {2}</a>",
@@ -659,7 +646,18 @@ def related_objects(self, obj):
659646
"tables" if obj.tables.count() > 1 else "table",
660647
)
661648

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

664662

665663
class CustomUserAdmin(UserAdmin):
@@ -673,6 +671,31 @@ class CustomUserAdmin(UserAdmin):
673671

674672
class TableAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
675673
form = TableForm
674+
fieldsets = (
675+
(
676+
None,
677+
{
678+
"fields": (
679+
"dataset",
680+
"get_table_url",
681+
"status",
682+
"name",
683+
"slug",
684+
"description",
685+
"get_datetime_ranges_display",
686+
"number_columns",
687+
"number_rows",
688+
"get_update_display",
689+
"raw_data_source",
690+
"published_by",
691+
"data_cleaned_by",
692+
"auxiliary_files_url",
693+
"created_at",
694+
"updated_at",
695+
)
696+
},
697+
),
698+
)
676699
actions = [
677700
reorder_columns,
678701
reset_column_order,
@@ -689,7 +712,8 @@ class TableAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
689712
UpdateInline,
690713
]
691714
readonly_fields = [
692-
"id",
715+
"get_table_url",
716+
"get_datetime_ranges_display",
693717
"partitions",
694718
"created_at",
695719
"updated_at",
@@ -700,21 +724,15 @@ class TableAdmin(OrderedInlineModelAdminMixin, TabbedTranslationAdmin):
700724
"number_columns",
701725
"uncompressed_file_size",
702726
"compressed_file_size",
703-
"contains_open_data",
704-
"contains_direct_download_free",
705-
"contains_direct_download_paid",
706-
"contains_temporalcoverage_free",
707-
"contains_temporalcoverage_paid",
708-
"contains_closed_data",
709727
"page_views",
728+
"get_update_display",
710729
]
711730
search_fields = [
712731
"name",
713732
"dataset__name",
714733
]
715734
autocomplete_fields = [
716735
"dataset",
717-
"partner_organization",
718736
"published_by",
719737
"data_cleaned_by",
720738
]
@@ -757,6 +775,188 @@ def get_data_cleaners(self, obj):
757775

758776
get_data_cleaners.short_description = "Data Cleaners"
759777

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

761961
class TableNeighborAdmin(admin.ModelAdmin):
762962
search_fields = [
@@ -1054,9 +1254,9 @@ def datetime_ranges_display(self, obj):
10541254

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

1059-
return format_html("<br>".join(links))
1259+
return mark_safe("<br>".join(links))
10601260

10611261
datetime_ranges_display.short_description = "DateTime Ranges"
10621262

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
@@ -895,6 +895,8 @@ class Update(BaseModel):
895895
graphql_nested_filter_fields_whitelist = ["id"]
896896

897897
def __str__(self):
898+
if self.latest:
899+
return f"{self.latest.strftime('%Y-%m-%d')}: {str(self.frequency)} {str(self.entity)}"
898900
return f"{str(self.frequency)} {str(self.entity)}"
899901

900902
class Meta:
@@ -964,6 +966,8 @@ class Poll(BaseModel):
964966
graphql_nested_filter_fields_whitelist = ["id"]
965967

966968
def __str__(self):
969+
if self.latest:
970+
return f"{self.latest.strftime('%Y-%m-%d')}: {str(self.frequency)} {str(self.entity)}"
967971
return f"{str(self.frequency)} {str(self.entity)}"
968972

969973
class Meta:

0 commit comments

Comments
 (0)