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