mbr / flask-nav

Easily create navigation for Flask applications.
http://pythonhosted.org/flask-nav/
MIT License
62 stars 37 forks source link

Problem with endpoints which have multiple routes #20

Open hieu-n opened 7 years ago

hieu-n commented 7 years ago

My packages' version:

Flask (0.12)
Flask-Bootstrap (3.3.7.1)
flask-nav (0.6)

I have a function with multiple routes as below:

from flask_nav.elements import Navbar, View
from flask import Flask
from flask_nav import Nav

nav = Nav()
app = Flask(__name__)
nav.init_app(app)

@app.route('/<profile>/trang-<int:page>/order-<order>-<order_dir>/', )
@app.route('/<profile>/', defaults={'page': 1, 'order': 'sequence', 'order_dir': 'asc'}, )
def index(profile, page, order, order_dir):
    ...

@nav.navigation()
def mk_navbar():
    ...

    return Navbar(
        ...
        View(u'Home', 'index', profile=pf),
        ...
    )

When I tried to access http://localhost:5000/kkm/trang-1/order-list_price-asc/, I got the exception as below:

Traceback (most recent call last):
...
  File "/miniconda2/envs/flecom/lib/python2.7/site-packages/flask_nav/elements.py", line 24, in render
    self))
  File "/miniconda2/envs/flecom/lib/python2.7/site-packages/visitor/__init__.py", line 48, in visit
    return meth(node)
  File "/miniconda2/envs/flecom/lib/python2.7/site-packages/flask_bootstrap/nav.py", line 53, in visit_Navbar
    bar_list.add(self.visit(item))
  File "/miniconda2/envs/flecom/lib/python2.7/site-packages/visitor/__init__.py", line 48, in visit
    return meth(node)
  File "/miniconda2/envs/flecom/lib/python2.7/site-packages/flask_bootstrap/nav.py", line 99, in visit_View
    if node.active:
  File "/miniconda2/envs/flecom/lib/python2.7/site-packages/flask_nav/elements.py", line 89, in active
    append_unknown=not self.ignore_query)
  File "/miniconda2/envs/flecom/lib/python2.7/site-packages/werkzeug/routing.py", line 806, in build
    add(self._converters[data].to_url(values[data]))
KeyError: 'page'

As I tried to debug, I find the problem may lie in active method in flask_nav/elements.py (line 89):

    @property
    def active(self):
        if not request.endpoint == self.endpoint:
            return False

        # rebuild the url and compare results. we can't rely on using get_url()
        # because whether or not an external url is created depends on factors
        # outside our control

        _, url = request.url_rule.build(self.url_for_kwargs,
                                        append_unknown=not self.ignore_query)

        if self.ignore_query:
            return url == request.path

        # take query string into account.
        # FIXME: ensure that the order of query parameters is consistent
        return url == request.full_path

I set a breakpoint at _, url = request.url_rule.build(self.url_for_kwargs, and inspected some variables:

>>>  request.endpoint
'index'
>>> self.endpoint
'index'
>>> request.url_rule
'<Rule \\'/<profile>/trang-<page>/order-<order>-<order_dir>/\\' (HEAD, OPTIONS, GET) -> index>'       
>>> self.url_for_kwargs
{'profile': u'kkm'}

So the rule requires profile, page, order and order_dir. But url_for_kwargs only has profile. If I change:

        _, url = request.url_rule.build(self.url_for_kwargs,
                                        append_unknown=not self.ignore_query)

to:

       url = url_for(request.endpoint, **self.url_for_kwargs, )

It would work:

>>> url = url_for(request.endpoint, **self.url_for_kwargs)
>>> url
'/kkm/'

That resolved the issue for me. However, I do not know if that would break something else.

Regards, Hieu