1from django import template 2from django.shortcuts import resolve_url 3from django.template.defaulttags import token_kwargs 4from django.template.loader import render_to_string 5from django.utils.encoding import force_str 6from django.utils.html import conditional_escape 7 8from wagtail import VERSION, __version__ 9from wagtail.core.models import Page, Site 10from wagtail.core.rich_text import RichText, expand_db_html 11from wagtail.utils.version import get_main_version 12 13 14register = template.Library() 15 16 17@register.simple_tag(takes_context=True) 18def pageurl(context, page, fallback=None): 19 """ 20 Outputs a page's URL as relative (/foo/bar/) if it's within the same site as the 21 current page, or absolute (http://example.com/foo/bar/) if not. 22 If kwargs contains a fallback view name and page is None, the fallback view url will be returned. 23 """ 24 if page is None and fallback: 25 return resolve_url(fallback) 26 27 if not hasattr(page, 'relative_url'): 28 raise ValueError("pageurl tag expected a Page object, got %r" % page) 29 30 try: 31 site = Site.find_for_request(context['request']) 32 current_site = site 33 except KeyError: 34 # request not available in the current context; fall back on page.url 35 return page.url 36 37 if current_site is None: 38 # request does not correspond to a recognised site; fall back on page.url 39 return page.url 40 41 # Pass page.relative_url the request object, which may contain a cached copy of 42 # Site.get_site_root_paths() 43 # This avoids page.relative_url having to make a database/cache fetch for this list 44 # each time it's called. 45 return page.relative_url(current_site, request=context.get('request')) 46 47 48@register.simple_tag(takes_context=True) 49def slugurl(context, slug): 50 """ 51 Returns the URL for the page that has the given slug. 52 53 First tries to find a page on the current site. If that fails or a request 54 is not available in the context, then returns the URL for the first page 55 that matches the slug on any site. 56 """ 57 58 page = None 59 try: 60 site = Site.find_for_request(context['request']) 61 current_site = site 62 except KeyError: 63 # No site object found - allow the fallback below to take place. 64 pass 65 else: 66 if current_site is not None: 67 page = Page.objects.in_site(current_site).filter(slug=slug).first() 68 69 # If no page is found, fall back to searching the whole tree. 70 if page is None: 71 page = Page.objects.filter(slug=slug).first() 72 73 if page: 74 # call pageurl() instead of page.relative_url() here so we get the ``accepts_kwarg`` logic 75 return pageurl(context, page) 76 77 78@register.simple_tag 79def wagtail_version(): 80 return __version__ 81 82 83@register.simple_tag 84def wagtail_documentation_path(): 85 major, minor, patch, release, num = VERSION 86 if release == 'final': 87 return 'https://docs.wagtail.io/en/v%s' % __version__ 88 else: 89 return 'https://docs.wagtail.io/en/latest' 90 91 92@register.simple_tag 93def wagtail_release_notes_path(): 94 return "%s.html" % get_main_version(VERSION) 95 96 97@register.filter 98def richtext(value): 99 if isinstance(value, RichText): 100 # passing a RichText value through the |richtext filter should have no effect 101 return value 102 elif value is None: 103 html = '' 104 else: 105 if isinstance(value, str): 106 html = expand_db_html(value) 107 else: 108 raise TypeError("'richtext' template filter received an invalid value; expected string, got {}.".format(type(value))) 109 return render_to_string('wagtailcore/shared/richtext.html', {'html': html}) 110 111 112class IncludeBlockNode(template.Node): 113 def __init__(self, block_var, extra_context, use_parent_context): 114 self.block_var = block_var 115 self.extra_context = extra_context 116 self.use_parent_context = use_parent_context 117 118 def render(self, context): 119 try: 120 value = self.block_var.resolve(context) 121 except template.VariableDoesNotExist: 122 return '' 123 124 if hasattr(value, 'render_as_block'): 125 if self.use_parent_context: 126 new_context = context.flatten() 127 else: 128 new_context = {} 129 130 if self.extra_context: 131 for var_name, var_value in self.extra_context.items(): 132 new_context[var_name] = var_value.resolve(context) 133 134 output = value.render_as_block(context=new_context) 135 else: 136 output = value 137 138 if context.autoescape: 139 return conditional_escape(output) 140 else: 141 return force_str(output) 142 143 144@register.tag 145def include_block(parser, token): 146 """ 147 Render the passed item of StreamField content, passing the current template context 148 if there's an identifiable way of doing so (i.e. if it has a `render_as_block` method). 149 """ 150 tokens = token.split_contents() 151 152 try: 153 tag_name = tokens.pop(0) 154 block_var_token = tokens.pop(0) 155 except IndexError: 156 raise template.TemplateSyntaxError("%r tag requires at least one argument" % tag_name) 157 158 block_var = parser.compile_filter(block_var_token) 159 160 if tokens and tokens[0] == 'with': 161 tokens.pop(0) 162 extra_context = token_kwargs(tokens, parser) 163 else: 164 extra_context = None 165 166 use_parent_context = True 167 if tokens and tokens[0] == 'only': 168 tokens.pop(0) 169 use_parent_context = False 170 171 if tokens: 172 raise template.TemplateSyntaxError("Unexpected argument to %r tag: %r" % (tag_name, tokens[0])) 173 174 return IncludeBlockNode(block_var, extra_context, use_parent_context) 175 176 177@register.simple_tag(takes_context=True) 178def wagtail_site(context): 179 """ 180 Returns the Site object for the given request 181 """ 182 try: 183 request = context['request'] 184 except KeyError: 185 return None 186 187 return Site.find_for_request(request=request) 188