Social Profiles

My Projects

Flask framework - использование url converters

В процессе разработки этого блога возникла небольшая проблема, при использовании схемы роутинга урлов в виде:

/<string:val1>/<string:val2>/

возникает конфликт, view с которым связана подобная схема начинает еще и обрабатывать то, что лежит в /static/ и веб-сервер отдает ожидаемую 404 ошибку. Так вот, чтобы избегать подобных конфликтов во Flask (а точнее в его wsgi слое - werkzeug) можно создать кастомный url converter.

Т.е. если есть например такая вьюха:

@app.route('/<string:name>/<string:page_name>/')
def page(name, page_name):
    flat_page = static_blog.get_page_by_name_for(name, page_name)
    return render_template(getattr(flat_page, 'template'), flat_page=flat_page)

Да еще и такая:

@app.route('/<string:name>/<string:lang>/')
def blog(name, lang):
    articles = static_blog.get_articles(name, language=lang)
    return render_template('blog.html', articles=articles, language=lang)

То эти вьюхи мало того, что будут "перекрывать" друг друга, так еще и весьма вероятно мешать раздаче статики из static_folder. Как решать? Достаточно просто, нужно написать кастомные url converter для того, чтобы обеспечить правильный url mapping.

Делается это таким образом:

'''
Url converters, to avoid wrong url pattern matching
'''
from werkzeug.routing import BaseConverter, ValidationError


class NoSomethingConverter(BaseConverter):
    restriction = []

    def __ini__(self, url_map):
        super(NoSomethingConverter, self).__init__(url_map)

    def to_python(self, value):
        # в классах наследниках нужно определить функцию
        # которая будет возвращать урлы которые нужно отклонить
        # в виде списка
        if value in self.restriction():
            raise ValidationError()
        return value

class NoStaticConverter(NoSomethingConverter):
    # собственно определяем функцию self.restriction()
    restriction = lambda x: ['static']
# добавим конвертер в url_map фласка
app.url_map.converters['no_static'] = NoStaticConverter

class NoBlogsConverter(NoSomethingConverter):
    restriction = static_blog.get_blogs_names
app.url_map.converters['no_blogs'] = NoBlogsConverter

class NoPagesConverter(NoSomethingConverter):
    restriction = static_blog.get_pages_names
app.url_map.converters['no_pages'] = NoPagesConverter

И прописываются в route для конфликтных view:

@app.route('/<no_blogs:name>/<string:page_name>/')
def page(name, page_name):
    flat_page = static_blog.get_page_by_name_for(name, page_name)
    return render_template(getattr(flat_page, 'template'), flat_page=flat_page)

@app.route('/<no_pages:name>/<string:lang>/')
def blog(name, lang):
    articles = static_blog.get_articles(name, language=lang)
    return render_template('blog.html', articles=articles, language=lang)

@app.route('/<no_static:name>/<string:lang>/<string:article_name>/')
def post(name, lang, article_name):
    article = static_blog.get_article_by_name(article_name)
    return render_template('post.html', article=article, language=lang)

Надеюсь, кому-то поможет. Чтобы разобраться в деталях можно сходить по ссылкам:

comments powered by Disqus