Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strange issue on Tables: they appear empty on first load... #27

Open
petasis opened this issue Jan 11, 2024 · 7 comments
Open

Strange issue on Tables: they appear empty on first load... #27

petasis opened this issue Jan 11, 2024 · 7 comments

Comments

@petasis
Copy link

petasis commented Jan 11, 2024

Hi all,

I am observing a strange behaviour, which I haven't found why (I get no exception).

When I restart django (the db is not restarted), all my dashboard tables appear empty the first time they are presented. Reloading the page, and all subsequent reloads work as expected. But the first time a specific table is requested, it is empty.

Simple tables on the other hand, always work fine.

What can the problem be?
How to debug this?

It happens in all tables, not just a specific one.

@petasis
Copy link
Author

petasis commented Jan 11, 2024

Actually I do get an error, its on the bowser console:

Uncaught TypeError: $(...).DataTable is not a function

@petasis
Copy link
Author

petasis commented Jan 11, 2024

I think there is a problem with the Datatable jquery plugin.

If I restart django, the first time I load a dashboard, I see this in the page code:

<!-- dashboard defined js -->
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
<!-- end dashboard defined js -->

If I reload the page, I see this:

!-- dashboard defined js -->
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
<script type="text/javascript" src="/static/dashboards/vendor/js/datatables.min.js"></script>
<!-- end dashboard defined js -->

After the reload, I get an extra .js file. But I cannot figure out why.

@petasis
Copy link
Author

petasis commented Jan 11, 2024

Modifying class Dashboard.get_media() to:

def get_media(self) -> asset_definitions.Media:
        # dashboard level media
        print("=====================================================")
        media = super().get_media()
        print("11111111", media)

        # add any media defined in components
        for key, component in self.components.items():
            media += component.get_media()
            print("++++++++++", key, media)
        print("@@@@@@@@@@@", media)
        return media

Produces during the first render of the dashboard, after django restart:

=====================================================
11111111 <link href="/static/dashboards/css/dashboards.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
++++++++++ value <link href="/static/dashboards/css/dashboards.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
++++++++++ activities_table <link href="/static/dashboards/css/dashboards.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
@@@@@@@@@@@ <link href="/static/dashboards/css/dashboards.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>

Just hitting reload, the printed output changes:

=====================================================
11111111 <link href="/static/dashboards/css/dashboards.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
++++++++++ value <link href="/static/dashboards/css/dashboards.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
++++++++++ activities_table <link href="/static/dashboards/css/dashboards.css" media="all" rel="stylesheet">
<link href="/static/dashboards/vendor/css/datatables.min.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
<script type="text/javascript" src="/static/dashboards/vendor/js/datatables.min.js"></script>
@@@@@@@@@@@ <link href="/static/dashboards/css/dashboards.css" media="all" rel="stylesheet">
<link href="/static/dashboards/vendor/css/datatables.min.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/js/dashboard.js"></script>
<script type="text/javascript" src="/static/dashboards/vendor/js/datatables.min.js"></script>

The component activities_table adds the extra css/js only after the second render.
I do not know why.

@petasis
Copy link
Author

petasis commented Jan 11, 2024

Modifying Component.get_media:

def get_media(self) -> asset_definitions.Media:
        # component level media
        media = self._get_media_from_definition() or asset_definitions.Media()
        print("@@", self.template_name, media, self.value)
        # serializers may have media, if so use that instead of component media
        if callable(self.value) and hasattr(self.value, "get_media"):
            media = self.value().get_media()
            print("@@2", self.template_name, media, self.value)
        elif callable(self.defer) and hasattr(self.defer, "get_media"):
            media = self.defer().get_media()
            print("@@3", self.template_name, media)

        return media

Prints the first time:

@@ dashboards/components/table/index.html <link href="/static/dashboards/vendor/css/datatables.min.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/vendor/js/datatables.min.js"></script> <class 'vast_dashboards.dashboards.ActivitySerializer'>
@@2 dashboards/components/table/index.html  <class 'vast_dashboards.dashboards.ActivitySerializer'>

Thus, self.value is class TableSerializer, causing if callable(self.value) and hasattr(self.value, "get_media") to become true, and call
```` media = self.value().get_media(); print("@@2", self.template_name, media, self.value)```.

Calling it for the second time:

@@ dashboards/components/table/index.html <link href="/static/dashboards/vendor/css/datatables.min.css" media="all" rel="stylesheet">
<script type="text/javascript" src="/static/dashboards/vendor/js/datatables.min.js"></script> <bound method BaseTableSerializer.serialize of <class 'vast_dashboards.dashboards.ActivitySerializer'>>

Now value is <bound method BaseTableSerializer.serialize of <class 'vast_dashboards.dashboards.ActivitySerializer'>>, so its get_media is not called, leading to the expected result.

How to fix this?

@petasis
Copy link
Author

petasis commented Jan 11, 2024

The dashboard that generates all these, is:

@register
class ActivitiesDashboard(Dashboard):
    activities_table = Table(value=ActivitySerializer, css_classes="table table-hover align-middle table-left table-first-w-50 table-not-first-align-center", grid_css_classes="span-12")

    class Meta:
        name = "Activities"

    class Layout(Dashboard.Layout):
        components = ComponentLayout(
            Card("activities_table", 
                 heading = mark_safe("<i class=\"fa-solid fa-building-columns me-3\"></i> VAST Activities"),
                 css_classes="card", grid_css_classes="span-12"
            ),
        )
class ActivitySerializer(TableSerializer):
    @staticmethod
    def get_data(filters, **kwargs):
        if 'object' in kwargs and kwargs['object']:
            objects = Activity.objects.filter(pk=kwargs['object'].pk)
        else:
            objects = Activity.objects.all()
        if filters and "value" in filters and filters["value"] != "all":
            # Select all products having this value...
            objects = Activity.objects.filter(Q(visitor__product__statement__subject=filters["value"]) |
                                              Q(visitor__product__statement__object=filters["value"])  |
                                              Q(visitor__product__ps_subject__object=filters["value"]) |
                                              Q(visitor__product__productannotation__value=filters["value"])
                                             ).distinct()
        return [{
            'name': f'<a href="{o.get_dashboard_absolute_url()}" target="_blank">{o.name} <i class="fa-solid fa-arrow-up-right-from-square ms-3"></a>',
            'events': Event.objects.filter(activity__pk=o.pk).count(),
            'visitors': Visitor.objects.filter(activity__pk=o.pk).count(),
            'products': Product.objects.filter(activity_step__activity__pk=o.pk).count(),
            'statements': Statement.objects.filter(product__activity_step__activity__pk=o.pk).count() + \
                          ProductStatement.objects.filter(subject__activity_step__activity__pk=o.pk).count(),
        } for o in objects]
    class Meta:
        title = "Activities"
        columns = {
            "name": "Name",
            "events": "Events",
            "visitors": "Participants",
            "products": "Products",
            "statements": "Statements",
        }
        order = ["-name"]
        model = Activity

@petasis
Copy link
Author

petasis commented Jan 11, 2024

And probably this happens as TableSerializer inherits asset_definitions.MediaDefiningClass.

@petasis
Copy link
Author

petasis commented Jan 11, 2024

My temporary solution is to define a mixin and use it along TableSerializer:

class VASTTableSerializerMixin:
    class Media:
        js = ("dashboards/vendor/js/datatables.min.js",)
        css = {
            "all": ("dashboards/vendor/css/datatables.min.css",),
        }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant