Search code examples
flaskflask-admin

Flask Admin - is there a way to store current url (with custom filters applied) of table view?


I am working on ticketing system in Flask Admin. The Flask Admin enviroment will be the main one for all the users. For creating or editing tickets I go out from Flask-Admin and use wtforms to implement backend logic. After creation or editing the ticket (validate_on_submit) I want to redirect back to Flask Admin, so I use redirect(url_for(ticket.index_view)). It works fine.

Is there a way to redirect to flask admin, but also with specific filters which were applied before user left Flask admin enviroment? (it is basiccaly GET parameters of url - but in FLASK)

I was trying to use:

  • referrer = request.referrer
    
  • get_url()

But I am probably missing something crucial and don´t know how to implement it (where to put it so I can call the arguments)

Thank you so much.

EDIT : adding more context:

I have a flask admin customized to different roles of users. The main ModelView is the one showing the TICKETS : the specifics of the Class are not vital to my current problem but here its how it looks:

class TicketModelView(ModelView):
    column_list = ['id', 'title', 'osoba', 'content', 'povod_vmc_kom', 'dateVMC','zodpovedni', 'deadline', 'odpoved', 'solution', 'is_finished']
    column_searchable_list = ['osoba']

    column_filters = [ 'povod_vmc_kom', 'dateVMC', 'osoba', 'zodpovedni']
    column_labels = dict(povod_vmc_kom='VMČ / Komisia', dateVMC='Dátum VMČ / komisie', zodpovedni = "Zodpovední")
    column_display_actions = True
    column_filters = [
        FilterEqual(column=Ticket.povod_vmc_kom, name='Výbor/komisia', options=(('VMČ Juh','VMČ Juh'), ('UM','UM'), ('Kom dopravy','Kom dopravy'))),
        'zodpovedni', 'is_finished',

        'dateVMC', 'osoba'
        ]

    def is_accessible(self):

        #práva pre vedenie mesta - môže len nazerať
        if current_user.is_authenticated and current_user.role == 0:
            self.can_export=True
            self.can_delete = False
            self.can_edit = False
            self.can_create = False
            self._refresh_form_rules_cache()
            self._refresh_forms_cache()
            return True
        #práva pre super admina (ostatné práva sú defaultne zapnuté)
        if current_user.is_authenticated and current_user.role == 1:
            self.can_export=True
            self.can_delete=True
            self.form_edit_rules = ('zodpovedni', 'is_finished' )
            self.column_editable_list = ['is_finished']
            self._refresh_form_rules_cache()
            self._refresh_forms_cache()
            return True
        #práva pre garantov
        if current_user.is_authenticated and current_user.role == 2:
            self.can_delete = False
            self.can_create = False
            self.can_edit = False
            self.can_export=True
            self.column_searchable_list = ['title']


            self._refresh_form_rules_cache()
            self._refresh_forms_cache()

            return True
        #práva pre veducich utvarov
        if current_user.is_authenticated and current_user.role == 3:
            self.can_create = False
            self.can_delete = False
            self.can_export=True
            self.column_searchable_list = ['title']
            self.column_editable_list = ['odpoved', 'date_odpoved', 'solution', 'date_solution' ]
            self.form_edit_rules = ('odpoved', 'date_odpoved', 'solution', 'date_solution')
            self._refresh_form_rules_cache()
            self._refresh_forms_cache()
            return True
        return False
            

    def _solution_formatter(view, context, model, name):
        # Format your string here e.g show first 20 characters
        # can return any valid HTML e.g. a link to another view to show the detail or a popup window
        if model.solution:
            return model.solution[:50]
        pass
    def _content_formatter(view, context, model, name):
        # Format your string here e.g show first 20 characters
        # can return any valid HTML e.g. a link to another view to show the detail or a popup window
        if len(model.content) > 100:
            markupstring = "<a href= '%s'>%s</a>" % (url_for('ticket', ticket_id=model.id), "...")
            return model.content[:100] + Markup(markupstring)
        return model.content
    def _user_formatter(view, context, model, name):
        if model.id:
           markupstring = "<a href= '%s'>%s</a>" % (url_for('ticket', ticket_id=model.id), model.id)
           return Markup(markupstring)
        else:
           return ""
    column_formatters = {
        'content': _content_formatter,
        'solution': _solution_formatter,
        'id': _user_formatter
    }

When user viewing the TicketView in Flask Admin, he can apply various filters which is vital to the user experience of the whole web app. The filters work fine and they are stored in URL as GET arguments. When he wants to create or edit a ticket, I am not allowing him to do it in Flask Admin (I edited Flask-Admin layout.html template and added a button to navbar which redirects to my new_ticket url with wtforms.) because of backend logic I want to be applied. For example when he edits field "solution" : I want the value in field "date_of_solution" be generated automatically (date.today()). So I am using wtforms and flask routing : example is bellow:

@app.route("/ticket/<int:ticket_id>/solution", methods = ['GET', 'POST'])
@login_required
def solution(ticket_id):
    if current_user.role != 3:
        flash("Pre zadanie riešenia alebo odpovede musíte byť prihlásený ako vedúci útvaru", "danger")
        return redirect(url_for('ticket', ticket_id=ticket_id))
    ticket = Ticket.query.get_or_404(ticket_id)
    form = AdminPanelForm()

    if form.validate_on_submit():
        print("1")
        if not ticket.date_solution:
            print("2")
            ticket.date_solution= datetime.now()
        if not ticket.date_odpoved:
            print("3")
            if form.odpoved.data != ticket.odpoved:
                print("4")
                ticket.date_odpoved= datetime.now()
        ticket.solution = form.solution.data
        ticket.odpoved = form.odpoved.data
        ticket.is_finished = True
        db.session.commit()
        flash("Ticket bol updatenutý", "success")
        **return redirect(url_for('ticketmod.index_view'))**
    elif request.method == 'GET':

        form.solution.data = ticket.solution
        form.odpoved.data = ticket.odpoved
    return render_template("admin_ticket.html", form=form, ticket = ticket)

Now you can see that after succesful updating the ticket, user is redirected to Ticket model View where he came from, return redirect(url_for('ticketmod.index_view')) but without filters applied. I am looking for the solution, how can you store the url GET parameters (the filters) and then use them when redirecting back to ModelView. I tried function get_url() or request.referrer but I wasn´t succesful.

As I said in my original post, maybe I am missing something crucial in web architecture - if you have in mind some learning material I shoul be looking at : thanks for any advice.


Solution

  • Within the formatter method you can get a view's url including the applied filters/sorting criteria using the following:

    _view_url = view.get_url('.index_view', **request.args)
    

    Now pass this along to route request, either as a parameter or some other means. For example:

    class TicketModelView(ModelView):
    
        #  blah blah
    
         def _user_formatter(view, context, model, name):
            if model.id:
                #  This is the current url of the view including filters
                _view_url = view.get_url('.index_view', **request.args)
                # Pass this as a parameter to your route
               markupstring = "<a href= '%s'>%s</a>" % (url_for('ticket', ticket_id=model.id, return_url=_view_url), model.id)
               return Markup(markupstring)
    

    At the route you can now pull out the return_url from the request arg and add it as a hidden field in the form. Then in the post back retrieve the value from the form and redirect.

    @app.route("/ticket/<int:ticket_id>/solution", methods = ['GET', 'POST'])
    @login_required
    def solution(ticket_id):
        #  Get the return_url from the request
        _return_url = request.args.get('return_url'):
    
        #  Add the return_url to the form as a hidden field
        form.return_url.data = _return_url
    
        #  blah blah
    
        if form.validate_on_submit():
            #  get return value from form
            _return_url = form.return_url.data
    
            return redirect(_return_url) if _return_url else redirect(url_for('ticketmod.index_view'))