Completed
Push — master ( beb414...ca28b2 )
by Batiste
10s
created

LoadEditMediaNode.render()   A

Complexity

Conditions 3

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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