__init__.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #!/usr/bin/env python
  2. # coding=utf8
  3. import re
  4. from flask import Blueprint, current_app, url_for
  5. try:
  6. from wtforms.fields import HiddenField
  7. except ImportError:
  8. def is_hidden_field_filter(field):
  9. raise RuntimeError('WTForms is not installed.')
  10. else:
  11. def is_hidden_field_filter(field):
  12. return isinstance(field, HiddenField)
  13. from .forms import render_form
  14. __version__ = '3.3.7.0'
  15. BOOTSTRAP_VERSION = re.sub(r'^(\d+\.\d+\.\d+).*', r'\1', __version__)
  16. JQUERY_VERSION = '1.12.4'
  17. HTML5SHIV_VERSION = '3.7.3'
  18. RESPONDJS_VERSION = '1.4.2'
  19. class CDN(object):
  20. """Base class for CDN objects."""
  21. def get_resource_url(self, filename):
  22. """Return resource url for filename."""
  23. raise NotImplementedError
  24. class StaticCDN(object):
  25. """A CDN that serves content from the local application.
  26. :param static_endpoint: Endpoint to use.
  27. :param rev: If ``True``, honor ``BOOTSTRAP_QUERYSTRING_REVVING``.
  28. """
  29. def __init__(self, static_endpoint='static', rev=False):
  30. self.static_endpoint = static_endpoint
  31. self.rev = rev
  32. def get_resource_url(self, filename):
  33. extra_args = {}
  34. if self.rev and current_app.config['BOOTSTRAP_QUERYSTRING_REVVING']:
  35. extra_args['bootstrap'] = __version__
  36. return url_for(self.static_endpoint, filename=filename, **extra_args)
  37. class WebCDN(object):
  38. """Serves files from the Web.
  39. :param baseurl: The baseurl. Filenames are simply appended to this URL.
  40. """
  41. def __init__(self, baseurl):
  42. self.baseurl = baseurl
  43. def get_resource_url(self, filename):
  44. return self.baseurl + filename
  45. class ConditionalCDN(object):
  46. """Serves files from one CDN or another, depending on whether a
  47. configuration value is set.
  48. :param confvar: Configuration variable to use.
  49. :param primary: CDN to use if the configuration variable is ``True``.
  50. :param fallback: CDN to use otherwise.
  51. """
  52. def __init__(self, confvar, primary, fallback):
  53. self.confvar = confvar
  54. self.primary = primary
  55. self.fallback = fallback
  56. def get_resource_url(self, filename):
  57. if current_app.config[self.confvar]:
  58. return self.primary.get_resource_url(filename)
  59. return self.fallback.get_resource_url(filename)
  60. def bootstrap_find_resource(filename, cdn, use_minified=None, local=True):
  61. """Resource finding function, also available in templates.
  62. Tries to find a resource, will force SSL depending on
  63. ``BOOTSTRAP_CDN_FORCE_SSL`` settings.
  64. :param filename: File to find a URL for.
  65. :param cdn: Name of the CDN to use.
  66. :param use_minified': If set to ``True``/``False``, use/don't use
  67. minified. If ``None``, honors
  68. ``BOOTSTRAP_USE_MINIFIED``.
  69. :param local: If ``True``, uses the ``local``-CDN when
  70. ``BOOTSTRAP_SERVE_LOCAL`` is enabled. If ``False``, uses
  71. the ``static``-CDN instead.
  72. :return: A URL.
  73. """
  74. config = current_app.config
  75. if None == use_minified:
  76. use_minified = config['BOOTSTRAP_USE_MINIFIED']
  77. if use_minified:
  78. filename = '%s.min.%s' % tuple(filename.rsplit('.', 1))
  79. cdns = current_app.extensions['bootstrap']['cdns']
  80. resource_url = cdns[cdn].get_resource_url(filename)
  81. if resource_url.startswith('//') and config['BOOTSTRAP_CDN_FORCE_SSL']:
  82. resource_url = 'https:%s' % resource_url
  83. return resource_url
  84. class Bootstrap(object):
  85. def __init__(self, app=None):
  86. if app is not None:
  87. self.init_app(app)
  88. def init_app(self, app):
  89. app.config.setdefault('BOOTSTRAP_USE_MINIFIED', True)
  90. app.config.setdefault('BOOTSTRAP_CDN_FORCE_SSL', False)
  91. app.config.setdefault('BOOTSTRAP_QUERYSTRING_REVVING', True)
  92. app.config.setdefault('BOOTSTRAP_SERVE_LOCAL', False)
  93. app.config.setdefault('BOOTSTRAP_LOCAL_SUBDOMAIN', None)
  94. blueprint = Blueprint(
  95. 'bootstrap',
  96. __name__,
  97. template_folder='templates',
  98. static_folder='static',
  99. static_url_path=app.static_url_path + '/bootstrap',
  100. subdomain=app.config['BOOTSTRAP_LOCAL_SUBDOMAIN'])
  101. # add the form rendering template filter
  102. blueprint.add_app_template_filter(render_form)
  103. app.register_blueprint(blueprint)
  104. app.jinja_env.globals['bootstrap_is_hidden_field'] =\
  105. is_hidden_field_filter
  106. app.jinja_env.globals['bootstrap_find_resource'] =\
  107. bootstrap_find_resource
  108. if not hasattr(app, 'extensions'):
  109. app.extensions = {}
  110. local = StaticCDN('bootstrap.static', rev=True)
  111. static = StaticCDN()
  112. def lwrap(cdn, primary=static):
  113. return ConditionalCDN('BOOTSTRAP_SERVE_LOCAL', primary, cdn)
  114. bootstrap = lwrap(
  115. WebCDN('//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/%s/' %
  116. BOOTSTRAP_VERSION), local)
  117. jquery = lwrap(
  118. WebCDN('//cdnjs.cloudflare.com/ajax/libs/jquery/%s/' %
  119. JQUERY_VERSION), local)
  120. html5shiv = lwrap(
  121. WebCDN('//cdnjs.cloudflare.com/ajax/libs/html5shiv/%s/' %
  122. HTML5SHIV_VERSION))
  123. respondjs = lwrap(
  124. WebCDN('//cdnjs.cloudflare.com/ajax/libs/respond.js/%s/' %
  125. RESPONDJS_VERSION))
  126. app.extensions['bootstrap'] = {
  127. 'cdns': {
  128. 'local': local,
  129. 'static': static,
  130. 'bootstrap': bootstrap,
  131. 'jquery': jquery,
  132. 'html5shiv': html5shiv,
  133. 'respond.js': respondjs,
  134. },
  135. }
  136. # setup support for flask-nav
  137. renderers = app.extensions.setdefault('nav_renderers', {})
  138. renderer_name = (__name__ + '.nav', 'BootstrapRenderer')
  139. renderers['bootstrap'] = renderer_name
  140. # make bootstrap the default renderer
  141. renderers[None] = renderer_name