GetPageNode   A
last analyzed

Complexity

Total Complexity 2

Size/Duplication

Total Lines 11
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 2
dl 0
loc 11
rs 10
c 0
b 0
f 0

2 Methods

Rating   Name   Duplication   Size   Complexity  
A __init__() 0 3 1
A render() 0 5 1
1
"""Page CMS page_tags template tags"""
2
from django import template
3
from django.utils.safestring import SafeText
4
from django.template import TemplateSyntaxError
5
from django.conf import settings
6
from django.utils.text import unescape_string_literal
7
from django import forms
8
from django.template.loader import get_template
9
from django.contrib.staticfiles.templatetags.staticfiles import static
10
11
from pages import settings as pages_settings
12
from pages.models import Content, Page
13
from pages.placeholders import (
14
    PlaceholderNode, ImagePlaceholderNode, FilePlaceholderNode
15
)
16
from pages.placeholders import ContactPlaceholderNode, MarkdownPlaceholderNode
17
from pages.placeholders import JsonPlaceholderNode, parse_placeholder
18
from six.moves import urllib
19
import six
20
from pages.utils import get_placeholders
21
22
23
register = template.Library()
24
25
26
def get_page_from_string_or_id(page_string, lang=None):
27
    """Return a Page object from a slug or an id."""
28
    if type(page_string) == int:
29
        return Page.objects.get(pk=int(page_string))
30
    # if we have a string coming from some templates templates
31
    if (isinstance(page_string, SafeText) or
32
            isinstance(page_string, six.string_types)):
33
        if page_string.isdigit():
34
            return Page.objects.get(pk=int(page_string))
35
        return Page.objects.from_path(page_string, lang)
36
    # in any other case we return the input becasue it's probably
37
    # a Page object.
38
    return page_string
39
40
41
def _get_content(context, page, content_type, lang, fallback=True):
42
    """Helper function used by ``PlaceholderNode``."""
43
    if not page:
44
        return ''
45
46
    if not lang and 'lang' in context:
47
        lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
48
49
    page = get_page_from_string_or_id(page, lang)
50
51
    if not page:
52
        return ''
53
54
    content = Content.objects.get_content(page, lang, content_type, fallback)
55
    return content
56
57
58
"""Filters"""
59
60
61
def has_content_in(page, language):
62
    """Fitler that return ``True`` if the page has any content in a
63
    particular language.
64
65
    :param page: the current page
66
    :param language: the language you want to look at
67
    """
68
    if page is None:
69
        return False
70
    return Content.objects.filter(page=page, language=language).count() > 0
71
72
73
register.filter(has_content_in)
74
75
76
"""Inclusion tags"""
77
78
79
def pages_menu(context, page, url='/'):
80
    """Render a nested list of all the descendents of the given page,
81
    including this page.
82
83
    :param page: the page where to start the menu from.
84
    :param url: not used anymore.
85
    """
86
    lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
87
    page = get_page_from_string_or_id(page, lang)
88
    if page:
89
        children = page.get_children_for_frontend()
90
        context.update({'children': children, 'page': page})
91
    return context
92
93
94
pages_menu = register.inclusion_tag('pages/menu.html',
95
                                    takes_context=True)(pages_menu)
96
97
98
def pages_sub_menu(context, page, url='/'):
99
    """Get the root page of the given page and
100
    render a nested list of all root's children pages.
101
    Good for rendering a secondary menu.
102
103
    :param page: the page where to start the menu from.
104
    :param url: not used anymore.
105
    """
106
    lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
107
    page = get_page_from_string_or_id(page, lang)
108
    if page:
109
        root = page.get_root()
110
        children = root.get_children_for_frontend()
111
        context.update({'children': children, 'page': page})
112
    return context
113
114
115
pages_sub_menu = register.inclusion_tag('pages/sub_menu.html',
116
                                        takes_context=True)(pages_sub_menu)
117
118
119
def pages_siblings_menu(context, page, url='/'):
120
    """Get the parent page of the given page and render a nested list of its
121
    child pages. Good for rendering a secondary menu.
122
123
    :param page: the page where to start the menu from.
124
    :param url: not used anymore.
125
    """
126
    lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
127
    page = get_page_from_string_or_id(page, lang)
128
    if page:
129
        siblings = page.get_siblings()
130
        context.update({'children': siblings, 'page': page})
131
    return context
132
133
134
pages_siblings_menu = register.inclusion_tag(
135
    'pages/sub_menu.html',
136
    takes_context=True)(pages_siblings_menu)
137
138
139
def pages_admin_menu(context, page):
140
    """Render the admin table of pages."""
141
    request = context.get('request', None)
142
143
    expanded = False
144
    if request and "tree_expanded" in request.COOKIES:
145
        cookie_string = urllib.parse.unquote(request.COOKIES['tree_expanded'])
146
        if cookie_string:
147
            ids = [
148
                int(id) for id in
149
                urllib.parse.unquote(
150
                    request.COOKIES['tree_expanded']).split(',')
151
            ]
152
            if page.id in ids:
153
                expanded = True
154
    context.update({'expanded': expanded, 'page': page})
155
    return context
156
157
158
pages_admin_menu = register.inclusion_tag(
159
    'admin/pages/page/menu.html', takes_context=True
160
)(pages_admin_menu)
161
162
163
def show_content(context, page, content_type, lang=None, fallback=True):
164
    """Display a content type from a page.
165
166
    Example::
167
168
        {% show_content page_object "title" %}
169
170
    You can also use the slug of a page::
171
172
        {% show_content "my-page-slug" "title" %}
173
174
    Or even the id of a page::
175
176
        {% show_content 10 "title" %}
177
178
    :param page: the page object, slug or id
179
    :param content_type: content_type used by a placeholder
180
    :param lang: the wanted language
181
        (default None, use the request object to know)
182
    :param fallback: use fallback content from other language
183
    """
184
    return {'content': _get_content(
185
        context, page, content_type, lang, fallback)
186
    }
187
188
189
show_content = register.inclusion_tag('pages/content.html',
190
                                      takes_context=True)(show_content)
191
192
193
def show_absolute_url(context, page, lang=None):
194
    """
195
    Show the url of a page in the right language
196
197
    Example ::
198
199
        {% show_absolute_url page_object %}
200
201
    You can also use the slug of a page::
202
203
        {% show_absolute_url "my-page-slug" %}
204
205
    Keyword arguments:
206
    :param page: the page object, slug or id
207
    :param lang: the wanted language \
208
        (defaults to `settings.PAGE_DEFAULT_LANGUAGE`)
209
    """
210
    if not lang:
211
        lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
212
    page = get_page_from_string_or_id(page, lang)
213
    if not page:
214
        return {'content': ''}
215
    url = page.get_url_path(language=lang)
216
    if url:
217
        return {'content': url}
218
    return {'content': ''}
219
220
221
show_absolute_url = register.inclusion_tag(
222
    'pages/content.html',
223
    takes_context=True)(show_absolute_url)
224
225
226
def show_revisions(context, page, content_type, lang=None):
227
    """Render the last 10 revisions of a page content with a list using
228
        the ``pages/revisions.html`` template"""
229
    if (not pages_settings.PAGE_CONTENT_REVISION or
230
            content_type in pages_settings.PAGE_CONTENT_REVISION_EXCLUDE_LIST):
231
        return {'revisions': None}
232
    revisions = Content.objects.filter(
233
        page=page, language=lang,
234
        type=content_type).order_by('-creation_date')
235
    if len(revisions) < 2:
236
        return {'revisions': None}
237
    return {'revisions': revisions[0:10]}
238
239
240
show_revisions = register.inclusion_tag(
241
    'pages/revisions.html',
242
    takes_context=True)(show_revisions)
243
244
245
def pages_dynamic_tree_menu(context, page, url='/'):
246
    """
247
    Render a "dynamic" tree menu, with all nodes expanded which are either
248
    ancestors or the current page itself.
249
250
    Override ``pages/dynamic_tree_menu.html`` if you want to change the
251
    design.
252
253
    :param page: the current page
254
    :param url: not used anymore
255
    """
256
    lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
257
    page = get_page_from_string_or_id(page, lang)
258
    children = None
259
    if page and 'current_page' in context:
260
        current_page = context['current_page']
261
        # if this node is expanded, we also have to render its children
262
        # a node is expanded if it is the current node or one of its ancestors
263
        if(
264
            page.tree_id == current_page.tree_id and
265
            page.lft <= current_page.lft and
266
            page.rght >= current_page.rght
267
        ):
268
            children = page.get_children_for_frontend()
269
    context.update({'children': children, 'page': page})
270
    return context
271
272
273
pages_dynamic_tree_menu = register.inclusion_tag(
274
    'pages/dynamic_tree_menu.html',
275
    takes_context=True
276
)(pages_dynamic_tree_menu)
277
278
279
def pages_breadcrumb(context, page, url='/'):
280
    """
281
    Render a breadcrumb like menu.
282
283
    Override ``pages/breadcrumb.html`` if you want to change the
284
    design.
285
286
    :param page: the current page
287
    :param url: not used anymore
288
    """
289
    lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
290
    page = get_page_from_string_or_id(page, lang)
291
    pages_navigation = None
292
    if page:
293
        pages_navigation = page.get_ancestors()
294
    context.update({'pages_navigation': pages_navigation, 'page': page})
295
    return context
296
297
298
pages_breadcrumb = register.inclusion_tag(
299
    'pages/breadcrumb.html',
300
    takes_context=True
301
)(pages_breadcrumb)
302
303
304
"""Tags"""
305
306
307
class GetPageNode(template.Node):
308
    """get_page Node"""
309
    def __init__(self, page_filter, varname):
310
        self.page_filter = page_filter
311
        self.varname = varname
312
313
    def render(self, context):
314
        page_or_id = self.page_filter.resolve(context)
315
        page = get_page_from_string_or_id(page_or_id)
316
        context[self.varname] = page
317
        return ''
318
319
320
def do_get_page(parser, token):
321
    """Retrieve a page and insert into the template's context.
322
323
    Example::
324
325
        {% get_page "news" as news_page %}
326
327
    :param page: the page object, slug or id
328
    :param name: name of the context variable to store the page in
329
    """
330
    bits = token.split_contents()
331
    if 4 != len(bits):
332
        raise TemplateSyntaxError('%r expects 4 arguments' % bits[0])
333
    if bits[-2] != 'as':
334
        raise TemplateSyntaxError(
335
            '%r expects "as" as the second argument' % bits[0])
336
    page_filter = parser.compile_filter(bits[1])
337
    varname = bits[-1]
338
    return GetPageNode(page_filter, varname)
339
340
341
do_get_page = register.tag('get_page', do_get_page)
342
343
344
class GetContentNode(template.Node):
345
    """Get content node"""
346
    def __init__(self, page, content_type, varname, lang, lang_filter):
347
        self.page = page
348
        self.content_type = content_type
349
        self.varname = varname
350
        self.lang = lang
351
        self.lang_filter = lang_filter
352
353
    def render(self, context):
354
        if self.lang_filter:
355
            self.lang = self.lang_filter.resolve(context)
356
        context[self.varname] = _get_content(
357
            context,
358
            self.page.resolve(context),
359
            self.content_type.resolve(context),
360
            self.lang
361
        )
362
        return ''
363
364
365
def do_get_content(parser, token):
366
    """Retrieve a Content object and insert it into the template's context.
367
368
    Example::
369
370
        {% get_content page_object "title" as content %}
371
372
    You can also use the slug of a page::
373
374
        {% get_content "my-page-slug" "title" as content %}
375
376
    Syntax::
377
378
        {% get_content page type [lang] as name %}
379
380
    :param page: the page object, slug or id
381
    :param type: content_type used by a placeholder
382
    :param name: name of the context variable to store the content in
383
    :param lang: the wanted language
384
    """
385
    bits = token.split_contents()
386
    if not 5 <= len(bits) <= 6:
387
        raise TemplateSyntaxError('%r expects 4 or 5 arguments' % bits[0])
388
    if bits[-2] != 'as':
389
        raise TemplateSyntaxError(
390
            '%r expects "as" as the second last argument' % bits[0])
391
    page = parser.compile_filter(bits[1])
392
    content_type = parser.compile_filter(bits[2])
393
    varname = bits[-1]
394
    lang = None
395
    lang_filter = None
396
    if len(bits) == 6:
397
        lang = bits[3]
398
    else:
399
        lang_filter = parser.compile_filter("lang")
400
    return GetContentNode(page, content_type, varname, lang, lang_filter)
401
402
403
do_get_content = register.tag('get_content', do_get_content)
404
405
406
class LoadPagesNode(template.Node):
407
    """Load page node."""
408
    def render(self, context):
409
        if 'pages_navigation' not in context:
410
            pages = Page.objects.navigation().order_by("tree_id")
411
            context.update({'pages_navigation': pages})
412
        if 'current_page' not in context:
413
            context.update({'current_page': None})
414
        return ''
415
416
417
def do_load_pages(parser, token):
418
    """Load the navigation pages, lang, and current_page variables into the
419
    current context.
420
421
    Example::
422
423
        <ul>
424
            {% load_pages %}
425
            {% for page in pages_navigation %}
426
                {% pages_menu page %}
427
            {% endfor %}
428
        </ul>
429
    """
430
    return LoadPagesNode()
431
432
433
do_load_pages = register.tag('load_pages', do_load_pages)
434
435
436
class LoadEditNode(template.Node):
437
    """Load edit node."""
438
439
    def render(self, context):
440
        request = context.get('request')
441
        if not request.user.is_staff:
442
            return ''
443
        template_name = context.get('template_name')
444
        placeholders = get_placeholders(template_name)
445
        page = context.get('current_page')
446
        if not page:
447
            return ''
448
        lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
449
        form = forms.Form()
450
        for p in placeholders:
451
            field = p.get_field(
452
                page, lang, initial=p.get_content_from_context(context))
453
            form.fields[p.name] = field
454
455
        template = get_template('pages/inline-edit.html')
456
        with context.push():
457
            context['form'] = form
458
            context['edit_enabled'] = request.COOKIES.get('enable_edit_mode')
459
            content = template.render(context.flatten())
460
461
        return content
462
463
464
def do_load_edit(parser, token):
465
    """
466
    """
467
    return LoadEditNode()
468
469
470
do_load_edit = register.tag('pages_edit_init', do_load_edit)
471
472
473
class LoadEditMediaNode(template.Node):
474
    """Load edit node."""
475
476
    def render(self, context):
477
        request = context.get('request')
478
        if not request.user.is_staff:
479
            return ''
480
        template_name = context.get('template_name')
481
        placeholders = get_placeholders(template_name)
482
        page = context.get('current_page')
483
        lang = context.get('lang', pages_settings.PAGE_DEFAULT_LANGUAGE)
484
        form = forms.Form()
485
        for p in placeholders:
486
            field = p.get_field(page, lang)
487
            form.fields[p.name] = field
488
489
        link = '<link href="{}" type="text/css" media="all" rel="stylesheet" />'.format(
490
            static('pages/css/inline-edit.css')
491
            )
492
493
        return "{}{}".format(form.media, link)
494
495
496
def do_load_edit_media(parser, token):
497
    """
498
    """
499
    return LoadEditMediaNode()
500
501
502
do_load_edit = register.tag('pages_edit_media', do_load_edit_media)
503
504
505
def do_placeholder(parser, token):
506
    """
507
    Method that parse the placeholder template tag.
508
509
    Syntax::
510
511
        {% placeholder <name> [on <page>] [with <widget>] \
512
[parsed] [as <varname>] %}
513
514
    Example usage::
515
516
        {% placeholder about %}
517
        {% placeholder body with TextArea as body_text %}
518
        {% placeholder welcome with TextArea parsed as welcome_text %}
519
        {% placeholder teaser on next_page with TextArea parsed %}
520
    """
521
    name, params = parse_placeholder(parser, token)
522
523
    return PlaceholderNode(name, **params)
524
register.tag('placeholder', do_placeholder)
525
526
527
def do_markdownlaceholder(parser, token):
528
    """
529
    Method that parse the markdownplaceholder template tag.
530
    """
531
    name, params = parse_placeholder(parser, token)
532
    return MarkdownPlaceholderNode(name, **params)
533
register.tag('markdownplaceholder', do_markdownlaceholder)
534
535
536
def do_imageplaceholder(parser, token):
537
    """
538
    Method that parse the imageplaceholder template tag.
539
    """
540
    name, params = parse_placeholder(parser, token)
541
    return ImagePlaceholderNode(name, **params)
542
register.tag('imageplaceholder', do_imageplaceholder)
543
544
545
def do_fileplaceholder(parser, token):
546
    """
547
    Method that parse the fileplaceholder template tag.
548
    """
549
    name, params = parse_placeholder(parser, token)
550
    return FilePlaceholderNode(name, **params)
551
register.tag('fileplaceholder', do_fileplaceholder)
552
553
554
def do_contactplaceholder(parser, token):
555
    """
556
    Method that parse the contactplaceholder template tag.
557
    """
558
    name, params = parse_placeholder(parser, token)
559
    return ContactPlaceholderNode(name, **params)
560
register.tag('contactplaceholder', do_contactplaceholder)
561
562
563
def do_jsonplaceholder(parser, token):
564
    """
565
    Method that parse the contactplaceholder template tag.
566
    """
567
    name, params = parse_placeholder(parser, token)
568
    return JsonPlaceholderNode(name, **params)
569
570
571
register.tag('jsonplaceholder', do_jsonplaceholder)
572
573
574
def language_content_up_to_date(page, language):
575
    """Tell if all the page content has been updated since the last
576
    change of the official version (settings.LANGUAGE_CODE)
577
578
    This is approximated by comparing the last modified date of any
579
    content in the page, not comparing each content block to its
580
    corresponding official language version.  That allows users to
581
    easily make "do nothing" changes to any content block when no
582
    change is required for a language.
583
    """
584
    lang_code = getattr(settings, 'LANGUAGE_CODE', None)
585
    if lang_code == language:
586
        # official version is always "up to date"
587
        return True
588
    # get the last modified date for the official version
589
    last_modified = Content.objects.filter(
590
        language=lang_code,
591
        page=page).order_by('-creation_date')
592
    if not last_modified:
593
        # no official version
594
        return True
595
    lang_modified = Content.objects.filter(
596
        language=language,
597
        page=page).order_by('-creation_date')[0].creation_date
598
    return lang_modified > last_modified[0].creation_date
599
600
601
register.filter(language_content_up_to_date)
602
603
604
@register.assignment_tag
605
def get_pages_with_tag(tag):
606
    """
607
    Return Pages with given tag
608
609
    Syntax::
610
611
        {% get_pages_with_tag <tag name> as <varname> %}
612
613
    Example use::
614
        {% get_pages_with_tag "footer" as pages %}
615
    """
616
    return Page.objects.filter(tags__name__in=[tag])
617
618
619
def do_page_has_content(parser, token):
620
    """
621
    Conditional tag that only renders its nodes if the page
622
    has content for a particular content type. By default the
623
    current page is used.
624
625
    Syntax::
626
627
        {% page_has_content <content_type> [<page var name>] %}
628
            ...
629
        {%_end page_has_content %}
630
631
    Example use::
632
633
        {% page_has_content 'header-image' %}
634
            <img src="{{ MEDIA_URL }}{% imageplaceholder 'header-image' %}">
635
        {% end_page_has_content %}
636
637
    """
638
    nodelist = parser.parse(('end_page_has_content',))
639
    parser.delete_first_token()
640
    args = token.split_contents()
641
    try:
642
        content_type = unescape_string_literal(args[1])
643
    except IndexError:
644
        raise template.TemplateSyntaxError(
645
            "%r tag requires the argument content_type" % args[0]
646
        )
647
    if len(args) > 2:
648
        page = args[2]
649
    else:
650
        page = None
651
    return PageHasContentNode(page, content_type, nodelist)
652
653
654
register.tag('page_has_content', do_page_has_content)
655
656
657
class PageHasContentNode(template.Node):
658
659
    def __init__(self, page, content_type, nodelist):
660
        self.page = page or 'current_page'
661
        self.content_type = content_type
662
        self.nodelist = nodelist
663
664
    def render(self, context):
665
        page = context.get(self.page)
666
        if not page:
667
            return ''
668
        content = page.get_content(
669
            context.get('lang', None), self.content_type)
670
        if(content):
671
            output = self.nodelist.render(context)
672
            return output
673
        return ''
674