first commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
from .api import Navigation
|
||||
|
||||
|
||||
__all__ = ['Navigation']
|
||||
__version__ = '0.2.0'
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
59
hypenv/lib/python3.11/site-packages/flask_navigation/api.py
Normal file
59
hypenv/lib/python3.11/site-packages/flask_navigation/api.py
Normal file
@@ -0,0 +1,59 @@
|
||||
from flask.signals import appcontext_pushed
|
||||
|
||||
from .navbar import NavigationBar
|
||||
from .item import Item, ItemReference
|
||||
from .utils import BoundTypeProperty
|
||||
from .signals import navbar_created
|
||||
|
||||
|
||||
class Navigation(object):
|
||||
"""The navigation extension API."""
|
||||
|
||||
#: The subclass of :class:`~flask.ext.navigation.navbar.NavigationBar`. It
|
||||
#: bound with the the current instance.
|
||||
#:
|
||||
#: :type: :class:`~flask.ext.navigation.utils.BoundTypeProperty`
|
||||
Bar = BoundTypeProperty('Bar', NavigationBar)
|
||||
|
||||
#: The subclass of :class:`~flask.ext.navigation.item.Item`. It bound with
|
||||
#: the the current instance.
|
||||
#:
|
||||
#: :type: :class:`~flask.ext.navigation.utils.BoundTypeProperty`
|
||||
Item = BoundTypeProperty('Item', Item)
|
||||
|
||||
ItemReference = ItemReference
|
||||
|
||||
def __init__(self, app=None):
|
||||
self.bars = {}
|
||||
if app is not None:
|
||||
self.init_app(app)
|
||||
# connects ext-level signals
|
||||
navbar_created.connect(self.bind_bar, self.Bar)
|
||||
|
||||
def __getitem__(self, name):
|
||||
"""Gets a bound navigation bar by its name."""
|
||||
return self.bars[name]
|
||||
|
||||
def init_app(self, app):
|
||||
"""Initializes an app to work with this extension.
|
||||
|
||||
The app-context signals will be subscribed and the template context
|
||||
will be initialized.
|
||||
|
||||
:param app: the :class:`flask.Flask` app instance.
|
||||
"""
|
||||
# connects app-level signals
|
||||
appcontext_pushed.connect(self.initialize_bars, app)
|
||||
# integrate with jinja template
|
||||
app.add_template_global(self, 'nav')
|
||||
|
||||
def initialize_bars(self, sender=None, **kwargs):
|
||||
"""Calls the initializers of all bound navigation bars."""
|
||||
for bar in self.bars.values():
|
||||
for initializer in bar.initializers:
|
||||
initializer(self)
|
||||
|
||||
def bind_bar(self, sender=None, **kwargs):
|
||||
"""Binds a navigation bar into this extension instance."""
|
||||
bar = kwargs.pop('bar')
|
||||
self.bars[bar.name] = bar
|
||||
210
hypenv/lib/python3.11/site-packages/flask_navigation/item.py
Normal file
210
hypenv/lib/python3.11/site-packages/flask_navigation/item.py
Normal file
@@ -0,0 +1,210 @@
|
||||
import collections
|
||||
|
||||
from flask import url_for, request, Markup
|
||||
|
||||
from .utils import freeze_dict, join_html_attrs
|
||||
|
||||
|
||||
class Item(object):
|
||||
"""The navigation item object.
|
||||
|
||||
:param label: the display label of this navigation item.
|
||||
:param endpoint: the unique name of this navigation item.
|
||||
If this item point to a internal url, this parameter
|
||||
should be acceptable for ``url_for`` which will generate
|
||||
the target url.
|
||||
:param args: optional. If this parameter be provided, it will be passed to
|
||||
the ``url_for`` with ``endpoint`` together.
|
||||
Maybe this arguments need to be decided in the Flask app
|
||||
context, then this parameter could be a function to delay the
|
||||
execution.
|
||||
:param url: optional. If this parameter be provided, the target url of
|
||||
this navigation will be it. The ``endpoint`` and ``args`` will
|
||||
not been used to generate url.
|
||||
:param html_attrs: optional. This :class:`dict` will be used for
|
||||
representing html.
|
||||
|
||||
The ``endpoint`` is the identity name of this navigation item. It will be
|
||||
unique in whole application. In mostly situation, it should be a endpoint
|
||||
name of a Flask view function.
|
||||
"""
|
||||
|
||||
def __init__(self, label, endpoint, args=None, url=None, html_attrs=None,
|
||||
items=None):
|
||||
self.label = label
|
||||
self.endpoint = endpoint
|
||||
self._args = args
|
||||
self._url = url
|
||||
self.html_attrs = {} if html_attrs is None else html_attrs
|
||||
self.items = ItemCollection(items or None)
|
||||
|
||||
def __html__(self):
|
||||
attrs = dict(self.html_attrs)
|
||||
|
||||
# adds ``active`` to class list
|
||||
html_class = attrs.get('class', [])
|
||||
if self.is_active:
|
||||
html_class.append('active')
|
||||
|
||||
# joins class list
|
||||
attrs['class'] = ' '.join(html_class)
|
||||
if not attrs['class']:
|
||||
del attrs['class']
|
||||
attrs['href'] = self.url
|
||||
attrs_template, attrs_values = join_html_attrs(attrs)
|
||||
|
||||
return Markup('<a %s>{label}</a>' % attrs_template).format(
|
||||
*attrs_values, label=self.label)
|
||||
|
||||
def __html_format__(self, format_spec):
|
||||
if format_spec == 'li':
|
||||
li_attrs = Markup(' class="active"') if self.is_active else ''
|
||||
return Markup('<li{0}>{1}</li>').format(li_attrs, self.__html__())
|
||||
elif format_spec:
|
||||
raise ValueError('Invalid format spec')
|
||||
return self.__html__()
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
"""The arguments which will be passed to ``url_for``.
|
||||
|
||||
:type: :class:`dict`
|
||||
"""
|
||||
if self._args is None:
|
||||
return {}
|
||||
if callable(self._args):
|
||||
return dict(self._args())
|
||||
return dict(self._args)
|
||||
|
||||
@property
|
||||
def url(self):
|
||||
"""The final url of this navigation item.
|
||||
|
||||
By default, the value is generated by the :attr:`self.endpoint` and
|
||||
:attr:`self.args`.
|
||||
|
||||
.. note::
|
||||
|
||||
The :attr:`url` property require the app context without a provided
|
||||
config value :const:`SERVER_NAME`, because of :func:`flask.url_for`.
|
||||
|
||||
:type: :class:`str`
|
||||
"""
|
||||
if self.is_internal:
|
||||
return url_for(self.endpoint, **self.args)
|
||||
return self._url
|
||||
|
||||
@property
|
||||
def is_active(self):
|
||||
"""``True`` if the item should be presented as active, and ``False``
|
||||
always if the request context is not bound.
|
||||
"""
|
||||
return bool(request and self.is_current)
|
||||
|
||||
@property
|
||||
def is_internal(self):
|
||||
"""``True`` if the target url is internal of current app."""
|
||||
return self._url is None
|
||||
|
||||
@property
|
||||
def is_current(self):
|
||||
"""``True`` if current request has same endpoint with the item.
|
||||
|
||||
The property should be used in a bound request context, or the
|
||||
:class:`RuntimeError` may be raised.
|
||||
"""
|
||||
if not self.is_internal:
|
||||
return False # always false for external url
|
||||
has_same_endpoint = (request.endpoint == self.endpoint)
|
||||
has_same_args = (request.view_args == self.args)
|
||||
return has_same_endpoint and has_same_args # matches the endpoint
|
||||
|
||||
@property
|
||||
def ident(self):
|
||||
"""The identity of this item.
|
||||
|
||||
:type: :class:`~flask.ext.navigation.Navigation.ItemReference`
|
||||
"""
|
||||
return ItemReference(self.endpoint, self.args)
|
||||
|
||||
|
||||
class ItemCollection(collections.MutableSequence,
|
||||
collections.Iterable):
|
||||
"""The collection of navigation items.
|
||||
|
||||
This collection is a mutable sequence. All items have order index, and
|
||||
could be found by its endpoint name. e.g.::
|
||||
|
||||
c = ItemCollection()
|
||||
c.append(Item(endpoint='doge'))
|
||||
|
||||
print(c['doge']) # output: Item(endpoint='doge')
|
||||
print(c[0]) # output: Item(endpoint='doge')
|
||||
print(c) # output: ItemCollection([Item(endpoint='doge')])
|
||||
print(len(c)) # output: 1
|
||||
|
||||
c.append(Item(endpoint='lumpy', args={'num': 4}))
|
||||
|
||||
print(c[1]) # output: Item(endpoint='lumpy', args={'num': 4})
|
||||
assert c['lumpy', {'num': 4}] is c[1]
|
||||
"""
|
||||
|
||||
def __init__(self, iterable=None):
|
||||
#: the item collection
|
||||
self._items = []
|
||||
#: the mapping collection of endpoint -> item
|
||||
self._items_mapping = {}
|
||||
#: initial extending
|
||||
self.extend(iterable or [])
|
||||
|
||||
def __repr__(self):
|
||||
return 'ItemCollection(%r)' % self._items
|
||||
|
||||
def __getitem__(self, index):
|
||||
if isinstance(index, int):
|
||||
return self._items[index]
|
||||
|
||||
if isinstance(index, tuple):
|
||||
endpoint, args = index
|
||||
else:
|
||||
endpoint, args = index, {}
|
||||
ident = ItemReference(endpoint, args)
|
||||
return self._items_mapping[ident]
|
||||
|
||||
def __setitem__(self, index, item):
|
||||
# remove the old reference
|
||||
old_item = self._items[index]
|
||||
del self._items_mapping[old_item.ident]
|
||||
|
||||
self._items[index] = item
|
||||
self._items_mapping[item.ident] = item
|
||||
|
||||
def __delitem__(self, index):
|
||||
item = self[index]
|
||||
del self._items[index]
|
||||
del self._items_mapping[item.ident]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._items)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._items)
|
||||
|
||||
def insert(self, index, item):
|
||||
self._items.insert(index, item)
|
||||
self._items_mapping[item.ident] = item
|
||||
|
||||
|
||||
class ItemReference(collections.namedtuple('ItemReference', 'endpoint args')):
|
||||
"""The identity tuple of navigation item.
|
||||
|
||||
:param endpoint: the endpoint of view function.
|
||||
:type endpoint: ``str``
|
||||
:param args: the arguments of view function.
|
||||
:type args: ``dict``
|
||||
"""
|
||||
|
||||
def __new__(cls, endpoint, args=()):
|
||||
if isinstance(args, dict):
|
||||
args = freeze_dict(args)
|
||||
return super(cls, ItemReference).__new__(cls, endpoint, args)
|
||||
@@ -0,0 +1,54 @@
|
||||
import collections
|
||||
|
||||
from .item import ItemCollection
|
||||
from .signals import navbar_created
|
||||
|
||||
|
||||
class NavigationBar(collections.Iterable):
|
||||
"""The navigation bar object."""
|
||||
|
||||
def __init__(self, name, items=None, alias=None):
|
||||
self.name = name
|
||||
self.items = ItemCollection(items or [])
|
||||
self.initializers = []
|
||||
self.alias = alias or {}
|
||||
|
||||
# sends signal
|
||||
navbar_created.send(self.__class__, bar=self)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.items)
|
||||
|
||||
def initializer(self, fn):
|
||||
"""Adds a initializer function.
|
||||
|
||||
If you want to initialize the navigation bar within a Flask app
|
||||
context, you can use this decorator.
|
||||
|
||||
The decorated function should nave one paramater ``nav`` which is the
|
||||
bound navigation extension instance.
|
||||
"""
|
||||
self.initializers.append(fn)
|
||||
return fn
|
||||
|
||||
def alias_item(self, alias):
|
||||
"""Gets an item by its alias."""
|
||||
ident = self.alias[alias]
|
||||
return self.items[ident]
|
||||
|
||||
@property
|
||||
def current_item(self):
|
||||
"""Get the current active navigation Item if any.
|
||||
|
||||
.. versionadded:: 0.2.0
|
||||
"""
|
||||
return self._get_current_item(self)
|
||||
|
||||
def _get_current_item(self, items):
|
||||
for item in items:
|
||||
if item.is_active:
|
||||
return item
|
||||
else:
|
||||
nested = self._get_current_item(item.items)
|
||||
if nested:
|
||||
return nested
|
||||
@@ -0,0 +1,7 @@
|
||||
from flask.signals import Namespace
|
||||
|
||||
|
||||
signals = Namespace()
|
||||
|
||||
|
||||
navbar_created = signals.signal('navbar-created')
|
||||
@@ -0,0 +1,80 @@
|
||||
import operator
|
||||
import collections
|
||||
|
||||
|
||||
def freeze_dict(dict_):
|
||||
"""Freezes ``dict`` into ``tuple``.
|
||||
|
||||
A typical usage is packing ``dict`` into hashable.
|
||||
|
||||
e.g.::
|
||||
|
||||
>>> freeze_dict({'a': 1, 'b': 2})
|
||||
(('a', 1), ('b', 2))
|
||||
"""
|
||||
pairs = dict_.items()
|
||||
key_getter = operator.itemgetter(0)
|
||||
return tuple(sorted(pairs, key=key_getter))
|
||||
|
||||
|
||||
def join_html_attrs(attrs):
|
||||
"""Joins the map structure into HTML attributes.
|
||||
|
||||
The return value is a 2-tuple ``(template, ordered_values)``. It should be
|
||||
passed into :class:`markupsafe.Markup` to prevent XSS attacked.
|
||||
|
||||
e.g.::
|
||||
|
||||
>>> join_html_attrs({'href': '/', 'data-active': 'true'})
|
||||
('data-active="{0}" href="{1}"', ['true', '/'])
|
||||
"""
|
||||
attrs = collections.OrderedDict(freeze_dict(attrs or {}))
|
||||
template = ' '.join('%s="{%d}"' % (k, i) for i, k in enumerate(attrs))
|
||||
return template, list(attrs.values())
|
||||
|
||||
|
||||
class BoundTypeProperty(object):
|
||||
"""This kind of property creates subclasses of given class for each
|
||||
instance.
|
||||
|
||||
Those subclasses means "bound type" which be used for identifying
|
||||
themselves with blinker/Flask signals.
|
||||
|
||||
e.g.::
|
||||
|
||||
>>> class Foo(object):
|
||||
... pass
|
||||
>>> class Bar(object):
|
||||
... Foo = BoundTypeProperty('Foo', Foo)
|
||||
>>>
|
||||
>>> Bar.Foo
|
||||
BoundTypeProperty('Foo', Foo)
|
||||
>>> bar = Bar()
|
||||
>>> bar.Foo is Foo
|
||||
False
|
||||
>>> issubclass(bar.Foo, Foo)
|
||||
True
|
||||
>>> egg = Bar()
|
||||
>>> egg.Foo is bar.Foo
|
||||
False
|
||||
>>> egg.Foo.__bases__ == bar.Foo.__bases__ == (Foo,)
|
||||
True
|
||||
|
||||
:param name: the name of this property.
|
||||
:param cls: the base class of all bound classes.
|
||||
"""
|
||||
|
||||
def __init__(self, name, cls):
|
||||
self.name = name
|
||||
self.cls = cls
|
||||
|
||||
def __repr__(self):
|
||||
return 'BoundTypeProperty(%r, %s)' % (self.name, self.cls.__name__)
|
||||
|
||||
def __get__(self, instance, owner):
|
||||
if instance is None:
|
||||
return self
|
||||
ns = vars(instance) # the instance namespace
|
||||
if self.name not in ns:
|
||||
ns[self.name] = type(self.name, (self.cls,), {})
|
||||
return ns[self.name]
|
||||
Reference in New Issue
Block a user