djk2 / django-tables2-column-shifter

Simple extension for django-tables2 can dynamically show or hide columns. Using JQuery, Bootstrap 3, Bootstrap 4, Bootstrap 5 and Django >=1.9
BSD 3-Clause "New" or "Revised" License
21 stars 8 forks source link

[Help] exclude_columns parameter doesn't appear in url #34

Closed ChloeQuignot closed 8 months ago

ChloeQuignot commented 11 months ago

Hello,

Thank you for this great package, it's just what I was looking for and it was quite simple to set up thanks to your detailed README! I am now able to show/hide columns dynamically while exploring the table and I am able to select the columns that I want to show by default.

I was just wandering if there's also a way to save the table with just the columns that are showing in the web page (according to what columns were selected/deselected by the user). I've implemented the hack that was described in the README in which you add the exclude_columns=request.GET.get('exclude_columns').split(",") parameter to TableExport in the view function and it seems to work if I manually add &exclude_columns=species to the download url (this excludes the species column in the download) but I have trouble making it work without my intervention. I noticed, when I play around with showing/hiding columns in the web page, that it doesn't actually change the url (it doesn't add the exclude_columns parameter to the url with the columns that aren't showing) so I guess it's normal that request.GET.get('exclude_columns').split(",") isn't able to pick up the excluded columns since they don't appear in the url? Is this expected? Is there something that I forgot to add to make it work?

Thank you in advance for your help and advice, Chloe

PS: below, a simplified version of the model, table, filter, view and template that I used.

app/models.py

from django.db import models

class MyModel(models.Model):

    species = models.CharField(max_length=100, verbose_name="Species", default=None)
    name = models.CharField(max_length=100, verbose_name="Name", default=None)
    score = models.FloatField(verbose_name="Score", default=None, null=True)

app/tables.py

import django_tables2 as tables
from django_tables2_column_shifter.tables import ColumnShiftTableBootstrap3

from .models import MyModel

class NumberColumn(tables.Column):
    """To show floats rounded off at 0.01 in the django table view"""
    def render(self, value):
        return '{:0.2f}'.format(value)

class MyModelTable(ColumnShiftTableBootstrap3):

    score = NumberColumn()

    class Meta:
        model = MyModel
        template_name = "django_tables2/bootstrap.html" # shoud this be changed to "django_tables2_column_shifter/table.html"?
        per_page = 10 # set default number of entries per page to 10
        exclude = ('id',)

    def get_column_default_show(self): # set the default showing columns
        self.column_default_show = ['species', 'name']
        return super(MyModelTable, self).get_column_default_show()

app/filters.py

import django_filters
from django import forms

from .models import MyModel

class MyRangeWidget(django_filters.widgets.RangeWidget):
    """
    To define different placeholders for "from" and "to" boxes in a Range Filter
    https://etuannv.com/en/django-set-different-placeholders-for-rangefilter-or-datefromtorangefilter/
    """
    def __init__(self, from_attrs=None, to_attrs=None, attrs=None):
        super(MyRangeWidget, self).__init__(attrs)
        if from_attrs:
            self.widgets[0].attrs.update(from_attrs)
        if to_attrs:
            self.widgets[1].attrs.update(to_attrs)

class MyModelFilter(django_filters.FilterSet):
    species = django_filters.AllValuesMultipleFilter()

    # add placeholder: https://stackoverflow.com/questions/53924154/adding-placeholder-to-form-in-django-when-using-django-filter
    name = django_filters.CharFilter(lookup_expr='contains',widget = forms.widgets.TextInput(attrs={'placeholder':'Enter sequence name'}))
    score = django_filters.RangeFilter(label='Score range', widget=MyRangeWidget(from_attrs={'placeholder':'from'},to_attrs={'placeholder':'to'},))

    class Meta:
        model = MyModel
        fields = {}

    def __init__(self, *args, **kwargs):
        super(MyModelFilter, self).__init__(*args, **kwargs)

app/views.py

from django.shortcuts import render
from django_tables2.config import RequestConfig
from django_tables2.export.export import TableExport

from .models import MyModel
from .tables import MyModelTable
from .filters import MyModelFilter

def explore_db(request):
    filter = MyModelFilter(request.GET, queryset=MyModel.objects.all())
    table = MyModelTable=filter.qs)

    RequestConfig(request).configure(table)

    export_format = request.GET.get("_export", None)
    if TableExport.is_valid_format(export_format):
        exclude_columns = request.GET.get('exclude_columns')
        if exclude_columns:
            exclude_columns = exclude_columns.split(",")
        print(exclude_columns)
        exporter = TableExport(export_format, table, exclude_columns=exclude_columns)
        return exporter.response(f"table.{export_format}")

    return render(request, "db_explore.html", {
        "table": table, "filter": filter
    })

app/urls.py

from django.urls import path

from . import views

app_name='app'

urlpatterns = [
    path('db-explore/', views.explore_db, name="db_explore"),
]

app/templates/app/db_explore.html

{% load static %}
{% load bootstrap3 %}
{% load render_table from django_tables2 %}
{% load export_url from django_tables2 %}

<!DOCTYPE html>

<html lang="en">

    <head>
        <meta charset="utf-8" name="viewport" content="width=device-width, initial-scale=1">
        <title>MyApp</title>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"></script>
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
        <link rel="stylesheet" href="{% static 'global/css/styles.css' %}" />
        <link rel="stylesheet" href="{% static 'app/css/styles.css' %}" />
        <link rel="shortcut icon" type="image/png" href="{% static 'app/img/favicon.png' %}" />
    </head>

    <body class="mybody">

       <!-- print filters -->
        <form action="" method="get" style="display: grid;" class="form">
              {% bootstrap_form filter.form %}
             <button type="submit" class="btn btn-dark mb-3 mt-2">Filter</button> &nbsp; 
             <a class="text-dark text-sm mb-1" href="{% url 'app:db_explore' %}?{{request.GET.urlencode}}">x Clear Filters</a><br>
       </form>

       <!-- show table -->
       {% render_table table %}

       <!-- add download link -->
       <a href="{% export_url 'csv' %}">Download table</a>

    </body>

</html>
djk2 commented 11 months ago

Hi. I will look on you issue in next week. Sorry for delay, however I have a lot of work.

djk2 commented 10 months ago

Hi. Today, I started looking on your issue. Unfortunately, I meet some troubles.

  1. I tried to reproduce your case base on code snipes passed by you, but, I do not have your settings, requirements and structure of whole project. It will be easier for me if you could send me whole your demo project compressed by zip. I do not have enough time to guess how launch your application.
  2. JS API was prepared not by me. Some time ago JS API was added by @spapas and next extended by @mpibpc-mroose

As I understand, you expect that after column manipulation (hide or show) URL in browser window will change. I don't think manipulating the URL in the browser is a good idea. Nevertheless, I understand your need and suggest using a slightly different approach.

I suggest next steps:

  1. User click on download button
  2. You grab this event
  3. You retrieve the invisible columns using django_tables2_column_shifter_hidden()
  4. You create new url
  5. Open new location

Please see on below example:

    <!-- download button -->
    <div id="button" class="btn" data-link="{% export_url 'csv' %}">Download table</a>

    <script type="text/javascript"
        src="{% static "django_tables2_column_shifter/js/django_tables2_column_shifter.min.js" %}">
    </script>

    <script type="text/javascript">
        $(document).ready(function(){
            $.django_tables2_column_shifter_init()
            var button = $("#button");
            button.click(function(){
                // 1. get hidden columns
                var hidden_columns = $.django_tables2_column_shifter_hidden();
                console.log(hidden_columns);

                // 2. add hidden columns to url
                var url = button.data("link") + "?hidden_columns=" + hidden_columns;
                console.log(url);

                // 3. Open url in current window
                window.location.replace(url);
            });
        });
    </script>
ChloeQuignot commented 9 months ago

Hi! Thank you so much for your time and sorry for my late reply, September was a bit of a rush...

I followed your suggestion and it seems to be working. I'm not very familiar with javascript so your code snippet was really useful to me. I have the impression though that adding it to the html somehow interferes with the column selection. What I observe is that the dropdown menu opens up but the column boxes in the menu aren't clickable. I'm not sure what could be causing this, do you have some ideas that come to mind?

I played around a bit and found that opening the new link in a new tab makes it (sort of) work (clickable columns in the menu + hidden columns taken into account at download) although the "frozen column" syndrome still appears just after the download. But refreshing the page makes it work again so I think I can live with it like that.

    <a href="#" onclick="create_link('csv');return false;"><img src="{% static 'static/img/download_icon.png' %}" alt="logo_download" style="width:50px"/></a>

    <script type="text/javascript"
        src="{% static "django_tables2_column_shifter/js/django_tables2_column_shifter.min.js" %}">
    </script>

    <script type="text/javascript">

        function create_link(format) {

            var url = window.location.origin+window.location.pathname;
            var params = window.location.search;

            $.django_tables2_column_shifter_init()

            // 1. get hidden columns
            var hidden_columns = $.django_tables2_column_shifter_hidden();
            console.log(hidden_columns);

            // 2. add hidden columns to url
            var newurl = url + "?" + params + "&exclude_columns=" + hidden_columns + "&_export=" + format;
            console.log(newurl);

            // 3. Open url in new tab
            window.open(newurl,"_blank");
        }
    </script>
djk2 commented 8 months ago

Which columns are not clickable ? Are you able prepare some short video presenting this issue? You can also send me a zip archive with your code, and then I will look into.