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

MultipleView with search #1743

Closed
vash-sa opened this issue Nov 20, 2021 · 20 comments
Closed

MultipleView with search #1743

vash-sa opened this issue Nov 20, 2021 · 20 comments

Comments

@vash-sa
Copy link

vash-sa commented Nov 20, 2021

Help me, plz!
Make MultipleView with search.
https://flask-appbuilder.readthedocs.io/en/latest/quickhowto.html?highlight=multiple
We need an independent search for every list on the page. How to do it?

image

If you select filters at the top and click search, then they are reset to the bottom sheet. And vice versa.

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 22, 2021

I faced the same problem.
Have a look at my solution:

custom_multiple_views.html:

{% extends "appbuilder/base.html" %}
{% import 'appbuilder/general/lib.html' as lib %}

{% block content %}

{{ lib.panel_begin(title) }}
    {% block list_list scoped %}
        {% for view_widget in views_widgets %}
            <div>
            {% call lib.accordion_tag(views[loop.index - 1].__class__.__name__,views[loop.index - 1].title, True) %}

                {% block list_search scoped %}
                {% call lib.accordion_tag(tags[loop.index-1],_("Search"), False) %}
                    {{ search_widgets[loop.index-1]()|safe }}
                {% endcall %}
                {% endblock %}
                {{ views_widgets[loop.index-1]()|safe }}
                <!--{{ view_widget()|safe }}-->
            {% endcall %}
            </div>
        {% endfor %}
    {% endblock %}
{{ lib.panel_end() }}

{% endblock %}

custom_multipleviews.py

...
from flask_appbuilder import ModelView, MultipleView, expose, has_access
from flask_appbuilder.urltools import get_filter_args, get_order_args, get_page_args, get_page_size_args
...

class CustomMultipleView(MultipleView):

    views = [...]
    list_template = 'custom_multiple_views.html'

    @expose("/list/")
    @has_access
    def list(self):
        pages = get_page_args()
        page_sizes = get_page_size_args()
        orders = get_order_args()
        views_widgets = list()
        search_widgets = list()
        tags = list()
    
        for index, view in enumerate(self._views):
            if orders.get(view.__class__.__name__):
                order_column, order_direction = orders.get(view.__class__.__name__)
            else:
                order_column, order_direction = "", ""
            page = pages.get(view.__class__.__name__)
            page_size = page_sizes.get(view.__class__.__name__)
            widget = view._get_view_widget(filters=view._base_filters,
                                                       order_column=order_column,
                                                       order_direction=order_direction,
                                                       page=page, page_size=page_size)
            views_widgets.append(widget.get('list'))
            search_widgets.append(widget.get('search'))
            tags.append(index)

        self.update_redirect()
        return self.render_template(
            self.list_template, views=self._views, views_widgets=views_widgets,
            search_widgets=search_widgets,
            tags=tags
        )


@vash-sa
Copy link
Author

vash-sa commented Nov 22, 2021

Error((
AttributeError: 'ListWidget' object has no attribute 'get'

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 22, 2021

I'm sorry I missed something.
You have to extend your Views you wan't to show together using CustomMultipleView.

class TestView(ModelView):

    #--------------------------------------------------------------------------
    def _get_view_widget(self, **kwargs):
        """
         overriding method
            :return:
                returns widgets dict

        necessary for custommultipleview with search functionality
        """
        
        return self._list()

    #--------------------------------------------------------------------------
    def _list(self):
        """
            list function logic, override to implement different logic
            returns list and search widget
        """
        if get_order_args().get(self.__class__.__name__):
            order_column, order_direction = get_order_args().get(
                self.__class__.__name__
            )
        else:
            order_column, order_direction = "", ""
        page = get_page_args().get(self.__class__.__name__)
        page_size = get_page_size_args().get(self.__class__.__name__)

        # prevent unknown filters (on custommultipleview)
        try:
            get_filter_args(self._filters)
        except:
            pass
        widgets = self._get_list_widget(
            filters=self._filters,
            order_column=order_column,
            order_direction=order_direction,
            page=page,
            page_size=page_size,
        )
        form = self.search_form.refresh()
        self.update_redirect()
        return self._get_search_widget(form=form, widgets=widgets)

@vash-sa
Copy link
Author

vash-sa commented Nov 22, 2021

Sorry, i did not understand. How do I use this code? To make the error go away.

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 22, 2021

Each class you referenced in CustomModelView has to be extended like above.

class TestView(ModelView):

    #--------------------------------------------------------------------------
    def _get_view_widget(self, **kwargs):
        ...

    #--------------------------------------------------------------------------
    def _list(self):
        ...

class TestView2(ModelView):

    #--------------------------------------------------------------------------
    def _get_view_widget(self, **kwargs):
        ...

    #--------------------------------------------------------------------------
    def _list(self):
        ...

class CustomMultipleView(MultipleView):

    views = [TestView,TestView2]
    list_template = 'custom_multiple_views.html'

    ...

@vash-sa
Copy link
Author

vash-sa commented Nov 22, 2021

image
Is this your result?

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 22, 2021

I can't reproduce your results, because I know nothing about your models and views!
Which version of flask_appbuilder do you use?

I think we have to customize the template for the search widget.
#1432

template/widgets/custom_search.html

{% import 'appbuilder/general/lib.html' as lib %}

{% macro random_int() %}{% for n in [0,1,2,3,4,5] %}{{ [0,1,2,3,4,5,6,7,8,9]|random }}{% endfor %}{% endmacro %}

<!-- You have to manually call the macros in a list. -->
{% set parts = [random_int(), random_int(), random_int(), random_int(), random_int()] %}
{% set unique_id = parts|join('-') %}

<form id="filter_form_{{ unique_id }}" class="form-search" method="get">

    <div class="btn-group">
        <button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
        {{_("Add Filter")}}
      <span class="caret"></span>
        </button>
        <ul class="dropdown-menu">
            {% for col in include_cols %}
            <li><a href="javascript:void(0)" name="{{col}}" class="filter" onclick="return false;">
                {{ label_columns[col] }}</a>
            </li>
            {% endfor %}
        </ul>
    </div>

    <table class="table table-responsive table-hover filters">
        <tbody>

        </tbody>
    </table>

{{ lib.btn_search() }}
</form>

<script>
	(function($) {
	var filter = new AdminFilters(
                    '#filter_form_{{ unique_id }}',
                    {{ label_columns | tojson }},
                    {{ form_fields | tojson }},
                    {{ search_filters | tojson }},
                    {{ active_filters | tojson }}
                );
	})(jQuery);

</script>

customsearchwidget.py

from flask_appbuilder.widgets import SearchWidget

class CustomSearchWidget(SearchWidget):

    template = "widgets/custom_search.html"

view.py

from .customsearchwidget import CustomSearchWidget

class TestView(ModelView):

    search_widget = CustomSearchWidget
    ...

class TestView2(ModelView):

    search_widget = CustomSearchWidget
    ...

class CustomMultipleView(MultipleView):

    views = [TestView,TestView2]
    list_template = 'custom_multiple_views.html'

@vash-sa
Copy link
Author

vash-sa commented Nov 23, 2021

I do not understand where the parameter is formed "?_flt_0_Age=60" in http://0.0.0.0:8080/procedurespatientsdirectchart_tl/chart/?_flt_0_Age=60 when pressed "Search"

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 23, 2021

I don't have enough information to answer your question!
Where do you use a chart view?

@vash-sa
Copy link
Author

vash-sa commented Nov 23, 2021

If you write 0.0.0.0:8080 /.../?_ flt_0 _... = ... & _ flt_0 _... = ... from different tables to the browser command line, the result will be perfect. Question. How does it do Flask appbuilder?

@vash-sa
Copy link
Author

vash-sa commented Nov 23, 2021

image

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 23, 2021

MultipleView is quit complicated. Each SearchWidget contains a separate Form.
On the client side (browser) each of this forms tries to use (render) the filter parameters,
because the filter syntax does not allow to do a unique mapping.

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 23, 2021

image

This is the problem on the server side.
If a view does not know one of the usesd filter parameters, an exception is thrown and not all filter conditions are applied!

If you have the same field ("age") in your models, each view will filter by this field!

@vash-sa
Copy link
Author

vash-sa commented Nov 23, 2021

{{ lib.btn_search() }} Where is the handler for this button?

@ThomasP0815
Copy link
Contributor

There is no handler, it is a submit button!

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 23, 2021

Now I have a solution to apply conditions from different SearchWidgets.
The "submit" handler extends the submit form with conditions from all other forms.

custom_multiple_views.html:

{% extends "appbuilder/base.html" %}
{% import 'appbuilder/general/lib.html' as lib %}

{% block content %}

   {{ lib.panel_begin(title) }}
    {% block list_list scoped %}
        {% for view_widget in views_widgets %}
            <div>
            {% call lib.accordion_tag(views[loop.index - 1].__class__.__name__,views[loop.index - 1].title, True) %}

                {% block list_search scoped %}
                {% call lib.accordion_tag(tags[loop.index-1],_("Search"), False) %}
                    {{ search_widgets[loop.index-1]()|safe }}
                {% endcall %}
                {% endblock %}
                {{ views_widgets[loop.index-1]()|safe }}
                <!--{{ view_widget()|safe }}-->
            {% endcall %}
            </div>
        {% endfor %}
    {% endblock %}
{{ lib.panel_end() }}

{% endblock %}

<!-- changed -->
{% block tail_js %}
   {{ super() }}
   <script type="text/javascript">
   
      document.onsubmit = function(event) {

        var formsearchList = eval($('.form-search'));
        
        for(f = 0; f < formsearchList.length; f++)
        {
            form = formsearchList[f]
            if (form == event.srcElement)
                continue

            for(var element of form.elements)
            {
                if (element.name.startsWith("_flt"))
                {
                    event.srcElement.append(element)
                }
            }
        }    
    }

   </script>
{% endblock %}
<!-- end changed -->

views.py

def get_known_filter_args(filters):
    filters.clear_filters()
    for arg in request.args:
        re_match = re.findall(r"_flt_(\d)_(.*)", arg)
        if re_match:
            try:
                filters.add_filter_index(
                    re_match[0][1], int(re_match[0][0]), request.args.get(arg))
            except:
                pass

class TestView(ModelView):

    #--------------------------------------------------------------------------
    def _get_view_widget(self, **kwargs):
        """
         overriding method
            :return:
                returns widgets dict

        necessary for custommultipleview with search functionality
        """
        
        return self._list()

    #--------------------------------------------------------------------------
    def _list(self):
        """
            list function logic, override to implement different logic
            returns list and search widget
        """
        if get_order_args().get(self.__class__.__name__):
            order_column, order_direction = get_order_args().get(
                self.__class__.__name__
            )
        else:
            order_column, order_direction = "", ""
        page = get_page_args().get(self.__class__.__name__)
        page_size = get_page_size_args().get(self.__class__.__name__)

        # prevent unknown filters (on custommultipleview)
        try:
            get_known_filter_args(self._filters)                           #  <= changed
        except:
            pass
        widgets = self._get_list_widget(
            filters=self._filters,
            order_column=order_column,
            order_direction=order_direction,
            page=page,
            page_size=page_size,
        )
        form = self.search_form.refresh()
        self.update_redirect()
        return self._get_search_widget(form=form, widgets=widgets)


@vash-sa
Copy link
Author

vash-sa commented Nov 23, 2021

Did it work for you? Can I get your example by email? vashk-sa@yandex.ru

@ThomasP0815
Copy link
Contributor

ThomasP0815 commented Nov 23, 2021

Yes it works, but I have no compact example.
Follow the last post and you will succeed!

@vash-sa
Copy link
Author

vash-sa commented Nov 23, 2021

Good. I'll try to do it all over again tomorrow.

@vash-sa
Copy link
Author

vash-sa commented Nov 24, 2021

Super!!! Works. I am very happy. Thank you very much!

@vash-sa vash-sa closed this as completed Nov 24, 2021
@vash-sa vash-sa reopened this Nov 24, 2021
@vash-sa vash-sa closed this as completed Nov 27, 2021
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

2 participants