parse_placeholder()   F
last analyzed

Complexity

Conditions 16

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 56
rs 3.3916
cc 16

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like parse_placeholder() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""Placeholder module, that's where the smart things happen."""
2
from pages.widgets_registry import get_widget
3
from pages import settings
4
from pages.models import Content, Media
5
from pages.widgets import ImageInput, FileInput
6
from pages.utils import slugify
7
8
from django import forms
9
from django.core.mail import send_mail
10
from django import template
11
from django.template import TemplateSyntaxError
12
from django.core.files.storage import default_storage
13
from django.forms import Textarea, ImageField, CharField, FileField
14
from django.forms import TextInput
15
from django.conf import settings as global_settings
16
from django.utils.translation import ugettext_lazy as _
17
from django.utils.safestring import mark_safe
18
from django.utils.text import unescape_string_literal
19
from django.template.loader import render_to_string
20
from django.template import RequestContext
21
from django.core.files.uploadedfile import UploadedFile
22
import logging
23
import os
24
import time
25
import six
26
import copy
27
import uuid
28
29
logging.basicConfig()
30
logger = logging.getLogger("pages")
31
32
PLACEHOLDER_ERROR = _("[Placeholder %(name)s had syntax error: %(error)s]")
33
34
35
def parse_placeholder(parser, token):
36
    """Parse the `PlaceholderNode` parameters.
37
38
    Return a tuple with the name and parameters."""
39
    params = {}
40
41
    bits = token.split_contents()
42
    count = len(bits)
43
    error_string = '%r tag requires at least one argument' % bits[0]
44
    if count <= 1:
45
        raise TemplateSyntaxError(error_string)
46
    try:
47
        name = unescape_string_literal(bits[1])
48
    except ValueError:
49
        name = bits[1]
50
    remaining = bits[2:]
51
    simple_options = ['parsed', 'inherited', 'untranslated', 'shared', 'block']
52
    param_options = ['as', 'on', 'with', 'section']
53
    all_options = simple_options + param_options
54
    while remaining:
55
        bit = remaining[0]
56
        if bit not in all_options:
57
            raise TemplateSyntaxError(
58
                "%r is not an correct option for a placeholder" % bit)
59
        if bit in param_options:
60
            if len(remaining) < 2:
61
                raise TemplateSyntaxError(
62
                    "Placeholder option '%s' need a parameter" % bit)
63
            if bit == 'as':
64
                params['as_varname'] = remaining[1]
65
            if bit == 'with':
66
                params['widget'] = remaining[1]
67
            if bit == 'on':
68
                params['page'] = remaining[1]
69
            if bit == 'section':
70
                params['section'] = unescape_string_literal(remaining[1])
71
            remaining = remaining[2:]
72
        elif bit == 'parsed':
73
            params['parsed'] = True
74
            remaining = remaining[1:]
75
        elif bit == 'inherited':
76
            params['inherited'] = True
77
            remaining = remaining[1:]
78
        elif bit == 'untranslated':
79
            params['untranslated'] = True
80
            remaining = remaining[1:]
81
        elif bit == 'shared':
82
            params['shared'] = True
83
            remaining = remaining[1:]
84
        elif bit == 'block':
85
            remaining = remaining[1:]
86
            nodelist = parser.parse(('endplaceholder',))
87
            parser.delete_first_token()
88
            params['nodelist'] = nodelist
89
90
    return name, params
91
92
93
class PlaceholderNode(template.Node):
94
    """This template node is used to output and save page content and
95
    dynamically generate input fields in the admin.
96
97
    :param name: the name of the placeholder you want to show/create
98
    :param page: the optional page object
99
    :param widget: the widget you want to use in the admin interface. Take
100
        a look into :mod:`pages.widgets` to see which widgets
101
        are available.
102
    :param parsed: if the ``parsed`` word is given, the content of the
103
        placeholder is evaluated as template code, within the current
104
        context.
105
    :param as_varname: if ``as_varname`` is defined, no value will be
106
        returned. A variable will be created in the context
107
        with the defined name.
108
    :param inherited: inherit content from parent's pages.
109
    :param untranslated: the placeholder's content is the same for
110
        every language.
111
    """
112
113
    field = CharField
114
    widget = TextInput
115
116
    def __init__(
117
            self, name, page=None, widget=None, parsed=False,
118
            as_varname=None, inherited=False, untranslated=False,
119
            has_revision=True, section=None, shared=False, nodelist=None):
120
        """Gather parameters for the `PlaceholderNode`.
121
122
        These values should be thread safe and don't change between calls."""
123
        self.page = page or 'current_page'
124
        self.name = name
125
        self.ctype = name.replace(" ", "_")
126
        if widget:
127
            self.widget = widget
128
        self.parsed = parsed
129
        self.inherited = inherited
130
        self.untranslated = untranslated
131
        self.as_varname = as_varname
132
        self.section = section
133
        self.shared = shared
134
        self.nodelist = nodelist or [] # should be an iterable
135
136
        self.found_in_block = None
137
138
    def get_widget(self, page, language, fallback=Textarea):
139
        """Given the name of a placeholder return a `Widget` subclass
140
        like Textarea or TextInput."""
141
        is_str = isinstance(self.widget, six.string_types)
142
        if is_str:
143
            widget = get_widget(self.widget)
144
        else:
145
            widget = self.widget
146
        try:
147
            return widget(page=page, language=language)
148
        except:
149
            pass
150
        return widget()
151
152
    def get_extra_data(self, data):
153
        """Get eventual extra data for this placeholder from the
154
        admin form. This method is called when the Page is
155
        saved in the admin and passed to the placeholder save
156
        method."""
157
        result = {}
158
        for key in list(data.keys()):
159
            if key.startswith(self.ctype + '-'):
160
                new_key = key.replace(self.ctype + '-', '')
161
                result[new_key] = data[key]
162
        return result
163
164
    def get_field(self, page, language, initial=None):
165
        """The field that will be shown within the admin."""
166
        if self.parsed:
167
            help_text = _('Note: This field is evaluated as template code.')
168
        else:
169
            help_text = ''
170
        widget = self.get_widget(page, language)
171
        return self.field(
172
            widget=widget, initial=initial,
173
            help_text=help_text, required=False)
174
175
    def save(self, page, language, data, change, extra_data=None):
176
        """Actually save the placeholder data into the Content object."""
177
        # if this placeholder is untranslated, we save everything
178
        # in the default language
179
        if self.untranslated:
180
            language = settings.PAGE_DEFAULT_LANGUAGE
181
182
        if self.shared:
183
            page = None
184
185
        # the page is being changed
186
        if change:
187
            # we need create a new content if revision is enabled
188
            if(settings.PAGE_CONTENT_REVISION and self.name
189
                    not in settings.PAGE_CONTENT_REVISION_EXCLUDE_LIST):
190
                Content.objects.create_content_if_changed(
191
                    page,
192
                    language,
193
                    self.ctype,
194
                    data
195
                )
196
            else:
197
                Content.objects.set_or_create_content(
198
                    page,
199
                    language,
200
                    self.ctype,
201
                    data
202
                )
203
        # the page is being added
204
        else:
205
            Content.objects.set_or_create_content(
206
                page,
207
                language,
208
                self.ctype,
209
                data
210
            )
211
212
    def get_content(self, page_obj, lang, lang_fallback=True):
213
        if self.untranslated:
214
            lang = settings.PAGE_DEFAULT_LANGUAGE
215
            lang_fallback = False
216
        if self.shared:
217
            return Content.objects.get_content(
218
                None, lang, self.ctype, lang_fallback)
219
        content = Content.objects.get_content(
220
            page_obj, lang, self.ctype, lang_fallback)
221
        if self.inherited and not content:
222
            for ancestor in page_obj.get_ancestors():
223
                content = Content.objects.get_content(
224
                    ancestor, lang,
225
                    self.ctype, lang_fallback)
226
                if content:
227
                    break
228
        return content
229
230
    def get_lang(self, context):
231
        if self.untranslated:
232
            lang = settings.PAGE_DEFAULT_LANGUAGE
233
        else:
234
            lang = context.get('lang', settings.PAGE_DEFAULT_LANGUAGE)
235
        return lang
236
237
    def get_content_from_context(self, context):
238
        if self.untranslated:
239
            lang_fallback = False
240
        else:
241
            lang_fallback = True
242
243
        if self.shared:
244
            return self.get_content(
245
                None,
246
                self.get_lang(context),
247
                lang_fallback)
248
        if self.page not in context:
249
            return ''
250
        # current_page can be set to None
251
        if not context[self.page]:
252
            return ''
253
254
        return self.get_content(
255
            context[self.page],
256
            self.get_lang(context),
257
            lang_fallback)
258
259
    def get_render_content(self, context):
260
        if self.nodelist:
261
            with context.push():
262
                context['content'] = self.get_content_from_context(context)
263
                output = self.nodelist.render(context)
264
            return mark_safe(output)
265
        return mark_safe(self.get_content_from_context(context))
266
267
    def render_parsed(self, context, content):
268
        try:
269
            content_template = template.Template(content, name=self.name)
270
            new_content = mark_safe(content_template.render(context))
271
        except TemplateSyntaxError as error:
272
            if global_settings.DEBUG:
273
                new_content = PLACEHOLDER_ERROR % {
274
                    'name': self.name,
275
                    'error': error,
276
                }
277
            else:
278
                new_content = ''
279
        return new_content
280
281
    def edit_tag(self):
282
        return u"""<!--placeholder ;{};-->""".format(self.name)
283
284
    def render(self, context):
285
        """Output the content of the `PlaceholdeNode` as a template."""
286
        content = self.get_render_content(context)
287
        request = context.get('request')
288
        render_edit_tag = False
289
        if request and request.user.is_staff and request.COOKIES.get('enable_edit_mode'):
290
            render_edit_tag = True
291
292
        if not content:
293
            if not render_edit_tag:
294
                return ''
295
            return self.edit_tag()
296
297
        if self.parsed:
298
            content = self.render_parsed(context, content)
299
300
        if self.as_varname is None:
301
            if not render_edit_tag:
302
                return content
303
            return content + self.edit_tag()
304
        context[self.as_varname] = content
305
        return ''
306
307
    def __repr__(self):
308
        return "<Placeholder Node: %s>" % self.name
309
310
311 View Code Duplication
def get_filename(page, content_type, data):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
312
    """
313
    Generate a stable filename using the original filename of the type.
314
315
316
    """
317
    avoid_collision = uuid.uuid4().hex[:8]
318
319
    name_parts = data.name.split('.')
320
    if len(name_parts) > 1:
321
        name = slugify('.'.join(name_parts[:-1]), allow_unicode=True)
322
        ext = slugify(name_parts[-1])
323
        name = name + '.' + ext
324
    else:
325
        name = slugify(data.name)
326
    filename = os.path.join(
327
        settings.PAGE_UPLOAD_ROOT,
328
        'page_' + str(page.id),
329
        content_type + '-' + avoid_collision + '-' + name
330
    )
331
    return filename
332
333
334
class FilePlaceholderNode(PlaceholderNode):
335
    """A `PlaceholderNode` that saves one file on disk.
336
337
    `PAGE_UPLOAD_ROOT` setting define where to save the file.
338
    """
339
340
    def get_field(self, page, language, initial=None):
341
        help_text = ""
342
        widget = FileInput(page, language)
343
        return FileField(
344
            widget=widget,
345
            initial=initial,
346
            help_text=help_text,
347
            required=False
348
        )
349
350
    def save(self, page, language, data, change, extra_data=None):
351
        if self.shared:
352
            page = None
353
354
        if extra_data and 'delete' in extra_data:
355
            return super(FilePlaceholderNode, self).save(
356
                page,
357
                language,
358
                "",
359
                change
360
            )
361
            return
362
        if extra_data and 'revision' in extra_data:
363
            return super(FilePlaceholderNode, self).save(
364
                page,
365
                language,
366
                extra_data['revision'],
367
                change
368
            )
369
            return
370
        if extra_data and 'selected' in extra_data and extra_data['selected']:
371
            return super(FilePlaceholderNode, self).save(
372
                page,
373
                language,
374
                extra_data['selected'],
375
                change
376
            )
377
            return
378
379
        filename = ''
380
        if change and data:
381
            # the image URL is posted if not changed
382
            if not isinstance(data, UploadedFile):
383
                return
384
385
            filename = get_filename(page, self.ctype, data)
386
            filename = default_storage.save(filename, data)
387
            media = Media(url=filename)
388
            media.save()
389
            return super(FilePlaceholderNode, self).save(
390
                page,
391
                language,
392
                filename,
393
                change
394
            )
395
396
397
class ImagePlaceholderNode(FilePlaceholderNode):
398
    """A `PlaceholderNode` that saves one image on disk.
399
400
    `PAGE_UPLOAD_ROOT` setting define where to save the image.
401
    """
402
403
    def get_field(self, page, language, initial=None):
404
        help_text = ""
405
        widget = ImageInput(page, language)
406
        return ImageField(
407
            widget=widget,
408
            initial=initial,
409
            help_text=help_text,
410
            required=False
411
        )
412
413
414
class ContactForm(forms.Form):
415
    """
416
    Simple contact form
417
    """
418
    email = forms.EmailField(label=_('Your email'))
419
    subject = forms.CharField(
420
        label=_('Subject'), max_length=150)
421
    message = forms.CharField(
422
        widget=forms.Textarea(), label=_('Your message'))
423
424
425
class ContactPlaceholderNode(PlaceholderNode):
426
    """A contact `PlaceholderNode` example."""
427
428
    def render(self, context):
429
        request = context.get('request', None)
430
        if not request:
431
            raise ValueError('request not available in the context.')
432
        if request.method == 'POST':
433
            form = ContactForm(request.POST)
434
            if form.is_valid():
435
                data = form.cleaned_data
436
                recipients = [adm[1] for adm in global_settings.ADMINS]
437
                try:
438
                    send_mail(
439
                        data['subject'], data['message'],
440
                        data['email'], recipients, fail_silently=False)
441
                    return _("Your email has been sent. Thank you.")
442
                except:
443
                    return _("An error as occured: your email has not been sent.")
444
        else:
445
            form = ContactForm()
446
        renderer = render_to_string(
447
            'pages/contact.html', {'form': form}, RequestContext(request))
448
        return mark_safe(renderer)
449
450
451
class JsonPlaceholderNode(PlaceholderNode):
452
    """
453
    A `PlaceholderNode` that try to return a deserialized JSON object
454
    in the template.
455
    """
456
457
    def get_render_content(self, context):
458
        import json
459
        content = self.get_content_from_context(context)
460
        try:
461
            return json.loads(str(content))
462
        except:
463
            logger.warning("JsonPlaceholderNode: coudn't decode json")
464
        return content
465
466
467
class MarkdownPlaceholderNode(PlaceholderNode):
468
    """
469
    A `PlaceholderNode` that return HTML from MarkDown format
470
    """
471
472
    widget = Textarea
473
474
    def render(self, context):
475
        """Render markdown."""
476
        import markdown
477
        content = self.get_content_from_context(context)
478
        return markdown.markdown(content)
479