예제 #1
0
def test_as_html():
    # str case
    assert format_html('{}', as_html(part='foo', context={})) == 'foo'
    assert format_html('{}',
                       as_html(part='<foo>bar</foo>',
                               context={})) == '&lt;foo&gt;bar&lt;/foo&gt;'
    assert format_html('{}',
                       as_html(part=mark_safe('<foo>bar</foo>'),
                               context={})) == '<foo>bar</foo>'

    # Template case
    c = RequestContext(req('get'))
    assert format_html('{}', as_html(part=Template('foo'), context=c)) == 'foo'
    assert format_html('{}', as_html(part=Template('<foo>bar</foo>'),
                                     context=c)) == '<foo>bar</foo>'

    # __html__ attribute case
    assert format_html(
        '{}', as_html(part=Struct(__html__=lambda context: 'foo'),
                      context={})) == 'foo'
    assert format_html(
        '{}',
        as_html(part=Struct(__html__=lambda context: '<foo>bar</foo>'),
                context={})) == '&lt;foo&gt;bar&lt;/foo&gt;'
    assert format_html(
        '{}',
        as_html(
            part=Struct(__html__=lambda context: mark_safe('<foo>bar</foo>')),
            context={})) == '<foo>bar</foo>'
예제 #2
0
파일: part.py 프로젝트: bgrundmann/iommi
def as_html(*, request=None, part: PartType, context):
    if isinstance(part, str):
        return part
    elif isinstance(part, template_types):
        from django.template import RequestContext
        assert not isinstance(context, RequestContext)
        template = part
        return mark_safe(template.render(context=RequestContext(request, context)))
    elif hasattr(part, '__html__'):
        return part.__html__()
    else:
        return str(part)
예제 #3
0
def render_attrs(attrs):
    """
    Render HTML attributes, or return '' if no attributes needs to be rendered.
    """
    if not attrs:
        return ''

    def parts():
        for key, value in sorted(attrs.items()):
            if value is None:
                continue
            if value is True:
                yield f'{key}'
                continue
            if isinstance(value, dict):
                if key == 'class':
                    if not value:
                        continue
                    value = render_class(value)
                    if not value:
                        continue
                elif key == 'style':
                    if not value:
                        continue
                    value = render_style(value)
                    if not value:
                        continue
                else:
                    raise TypeError(
                        f'Only the class and style attributes can be dicts, you sent {value} for key {key}'
                    )
            elif isinstance(value, (list, tuple)):
                raise TypeError(
                    f"Attributes can't be of type {type(value).__name__}, you sent {value} for key {key}"
                )
            elif callable(value):
                from .docs import get_docs_callable_description

                raise TypeError(
                    f"Attributes can't be callable, you sent {get_docs_callable_description(value)} for key {key}"
                )
            v = f'{value}'.replace('"', '&quot;')
            yield f'{key}="{v}"'

    r = mark_safe(' %s' % ' '.join(parts()))
    return '' if r == ' ' else r
예제 #4
0
def live_edit_view(request, view, args, kwargs):
    view = get_wrapped_view(view)
    # Read the old code
    try:
        # view is a function based view
        filename = view.__globals__['__file__']
    except AttributeError:
        # view is an iommi class
        from iommi.debug import filename_and_line_num_from_part

        filename, _ = filename_and_line_num_from_part(view)

    with open(filename) as f:
        entire_file = f.read()
        ast_of_entire_file = parso.parse(entire_file)

    is_unix_line_endings = '\r\n' not in entire_file

    ast_of_old_code = find_view(view, ast_of_entire_file)
    assert ast_of_old_code is not None

    flow_direction = request.GET.get('_iommi_live_edit') or 'column'
    assert flow_direction in ('column', 'row')

    if request.method == 'POST':
        try:
            code = request.POST['data'].replace('\t', '    ')
            if is_unix_line_endings:
                code = code.replace('\r\n', '\n')
            final_result = dangerous_execute_code(code, request, view, args,
                                                  kwargs)

            if orig_reload is not None:
                # A little monkey patch dance to avoid one reload of the runserver when it's just us writing the code to disk
                # This only works in django 2.2+
                def restore_auto_reload(filename):
                    from django.utils import autoreload

                    print('Skipped reload')
                    autoreload.trigger_reload = orig_reload

                autoreload.trigger_reload = restore_auto_reload

            if isinstance(view, Part):
                ast_of_new_code = find_node(name=view.__class__.__name__,
                                            node=parso.parse(code),
                                            node_type='classdef')
            else:
                ast_of_new_code = find_node(name=view.__name__,
                                            node=parso.parse(code),
                                            node_type='funcdef')
            ast_of_old_code.children[:] = ast_of_new_code.children
            new_code = ast_of_entire_file.get_code()
            with open(filename, 'w') as f:
                f.write(new_code)

            return final_result
        except Exception as e:
            import traceback

            traceback.print_exc()
            error = str(e)
            if not error:
                error = str(e.__class__)
            return HttpResponse(json.dumps(dict(error=error)))

    return LiveEditPage(
        title='iommi live edit',
        h_tag__include=False,
        assets__code_editor=Asset.js(
            attrs=dict(
                src='https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js',
                integrity=
                'sha512-GZ1RIgZaSc8rnco/8CXfRdCpDxRCphenIiZ2ztLy3XQfCbQUSCuk8IudvNHxkRA3oUg6q0qejgN/qqyG1duv5Q==',
                crossorigin='anonymous',
            ),
            after=-1,
        ),
        assets__live_edit_page_custom=Asset(
            tag='style',
            text='''
                .container {
                    padding: 0 !important;
                    margin: 0 !important;
                    max-width: 100%;
                }

                html,
                body {
                    height: 100%;
                    margin: 0;
                }

                .container {
                    display: flex;
                    flex-flow: <<flow_direction>>;
                    height: 100%;
                }

                .container iframe {
                    flex: 1 1 auto;
                }

                .container #editor {
                    flex: 2 1 auto;
                }
            '''.replace('<<flow_direction>>', flow_direction),
        ),
        parts__result=html.iframe(attrs__id='result'),
        parts__editor=html.div(
            ast_of_old_code.get_code(),
            attrs__id='editor',
        ),
        parts__script=html.script(
            mark_safe('''
                    function iommi_debounce(func, wait) {
                        let timeout;

                        return (...args) => {
                            const fn = () => func.apply(this, args);

                            clearTimeout(timeout);
                            timeout = setTimeout(() => fn(), wait);
                        };
                    }

                    var editor = ace.edit("editor");
                    editor.setTheme("ace/theme/cobalt");
                    editor.session.setMode("ace/mode/python");
                    editor.setShowPrintMargin(false);

                    async function update() {
                        let form_data = new FormData();
                        form_data.append('data', editor.getValue());

                        let response = await fetch('', {
                            method: 'POST',
                            body: form_data
                        });
                        let foo = await response.json();
                        if (foo.page) {
                            // TODO: get scroll position and restore it
                            document.getElementById('result').srcdoc = foo.page;
                        }
                    }

                    function foo() {
                        iommi_debounce(update, 200)();
                    }

                    editor.session.on('change', foo);
                    editor.setFontSize(14);
                    editor.session.setUseWrapMode(true);

                    foo();
        ''')),
    )
예제 #5
0
파일: debug.py 프로젝트: tltx/iommi
def iommi_debug_panel(part):
    filename, lineno = filename_and_line_num_from_part(part)

    if filename is None or filename.endswith('urls.py'):
        import inspect
        if not inspect.getmodule(type(part)).__name__.startswith('iommi.'):
            filename = inspect.getsourcefile(type(part))
            lineno = inspect.getsourcelines(type(part))[-1]

    if filename is not None:
        source_url = src_debug_url_builder(filename, lineno)
    else:
        source_url = None
    script = r"""
        window.iommi_start_pick = function() {
            window.iommi_pick_stack = [];
            
            function create(html) {
                let r = document.createElement('div');
                r.innerHTML = html;
                return r.firstChild;
            }
            
            window.iommi_close_pick_toolbar = function() {
                window.iommi_pick_stack.forEach(function(el) {
                    el[3].style.backgroundColor = el[2];
                });
                document.getElementById('iommi-pick-toolbar').remove()
            };

            function update_toolbar() {
                let toolbar = document.getElementById('iommi-pick-toolbar');
                if (!toolbar) {
                    return;
                }
                
                while(toolbar.firstChild) {
                    toolbar.removeChild(toolbar.firstChild);
                }
                
                toolbar.append(create('<div style="float: right" onclick="iommi_close_pick_toolbar()">close</div>'));
                for (let i in window.iommi_pick_stack) {
                    let x = window.iommi_pick_stack[i];
                    toolbar.append(create('<div style="background-color: ' + getColor(i) + '">' + x[0] + ' <a href="https://docs.iommi.rocks/en/latest/' + x[1] + '.html">' + x[1] + '</a></div>'));
                }
            }
        
            let with_iommi_path = document.querySelectorAll('*[data-iommi-path]');
            let colors = [
'rgb(255, 255, 191)',
'rgb(254, 224, 139)',
'rgb(253, 174,  97)',
'rgb(244, 109,  67)',
'rgb(213,  62,  79)',
'rgb(158,   1,  66)',
'rgb(230, 245, 152)',
'rgb(171, 221, 164)',
'rgb(102, 194, 165)',
'rgb( 50, 136, 189)',
'rgb( 94,  79, 162)',
            ];
            function getColor(index) {
                return colors[Math.min(index, colors.length - 1)]
            } 
            
            function mouseenter() {
                window.iommi_pick_stack.push([this.getAttribute('data-iommi-path'), this.getAttribute('data-iommi-type'), this.style.backgroundColor, this])
                this.style.backgroundColor = getColor(window.iommi_pick_stack.length-1);
                update_toolbar();
            }
            function mouseleave() {
                if (window.iommi_pick_stack.length) {
                    this.style.backgroundColor = window.iommi_pick_stack.pop()[2];
                    update_toolbar();
                }
            }
            function click() {
                document.querySelectorAll('*[data-iommi-path]').forEach(function (e) {
                    e.removeEventListener('mouseenter', mouseenter)
                    e.removeEventListener('mouseleave', mouseleave)
                    e.removeEventListener('click', click)
                });
            }
            
            with_iommi_path.forEach(function (e) {
                e.addEventListener('mouseenter', mouseenter);
                e.addEventListener('mouseleave', mouseleave);
                setTimeout(function(){
                    e.addEventListener('click', click);
                });
            });
            
            
            let toolbar = create('<div id="iommi-pick-toolbar" style="position: fixed; left: 0; bottom: 0; width: 100%; background-color: white; color: black; padding: 4px; border-top: 2px solid #1084ff; z-index: 200">');
            
            document.getElementsByTagName('body')[0].append(toolbar);
        };
    """


    from iommi.menu import DebugMenu
    return DebugMenu(sub_menu__code__url=source_url).bind(request=part.get_request()).__html__() + mark_safe(f'<script>{script}</script>')
예제 #6
0
파일: live_edit.py 프로젝트: tltx/iommi
def live_edit_view(request, view_func):
    # Read the old code
    filename = view_func.__globals__['__file__']
    with open(filename) as f:
        entire_file = f.read()
        ast_of_entire_file = parso.parse(entire_file)

    is_unix_line_endings = '\r\n' not in entire_file

    ast_of_old_code = find_function(name=view_func.__name__,
                                    node=ast_of_entire_file)

    flow_direction = request.GET.get('_iommi_live_edit') or 'column'
    assert flow_direction in ('column', 'row')

    if request.method == 'POST':
        try:
            code = request.POST['data'].replace('\t', '    ')
            if is_unix_line_endings:
                code = code.replace('\r\n', '\n')

            local_variables = {}
            exec(code, view_func.__globals__, local_variables)
            assert len(local_variables) == 1
            request.method = 'GET'
            response = list(local_variables.values())[0](request)
            response = render_if_needed(request, response)
            final_result = HttpResponse(
                json.dumps(dict(page=response.content.decode())))

            ast_of_new_code = find_function(name=view_func.__name__,
                                            node=parso.parse(code))

            ast_of_old_code.children[:] = ast_of_new_code.children

            # This only works in django 2.2+
            if orig_reload is not None:
                # A little monkey patch dance to avoid one reload of the runserver when it's just us writing the code to disk
                def restore_auto_reload(filename):
                    from django.utils import autoreload
                    print('Skipped reload')
                    autoreload.trigger_reload = orig_reload

                autoreload.trigger_reload = restore_auto_reload

            new_code = ast_of_entire_file.get_code()
            with open(filename, 'w') as f:
                f.write(new_code)

            return final_result
        except Exception as e:
            return HttpResponse(json.dumps(dict(error=str(e))))

    # This class exists just to provide a way to style the page
    class LiveEditPage(Page):
        pass

    return LiveEditPage(
        assets__code_editor=Asset.js(
            attrs=dict(
                src='https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.12/ace.js',
                integrity=
                'sha512-GZ1RIgZaSc8rnco/8CXfRdCpDxRCphenIiZ2ztLy3XQfCbQUSCuk8IudvNHxkRA3oUg6q0qejgN/qqyG1duv5Q==',
                crossorigin='anonymous',
            ),
            after=-1,
        ),
        assets__custom=Asset(tag='style',
                             text='''
            .container {
                padding: 0 !important;           
                margin: 0 !important;
                max-width: 100%;
            }

            html,
            body {
                height: 100%;
                margin: 0;
            }

            .container {
                display: flex;
                flex-flow: <<flow_direction>>;
                height: 100%;
            }

            .container iframe {
                flex: 1 1 auto;
            }
            .container #editor {
                flex: 2 1 auto;
            }
            '''.replace('<<flow_direction>>', flow_direction)),
        parts__result=html.iframe(attrs__id='result'),
        parts__editor=html.div(
            ast_of_old_code.get_code(),
            attrs__id='editor',
        ),
        parts__script=html.script(
            mark_safe('''
        function iommi_debounce(func, wait) {
            let timeout;

            return (...args) => {
                const fn = () => func.apply(this, args);

                clearTimeout(timeout);
                timeout = setTimeout(() => fn(), wait);
            };
        }

        var editor = ace.edit("editor");
        editor.setTheme("ace/theme/cobalt");
        editor.session.setMode("ace/mode/python");
        editor.setShowPrintMargin(false);

        async function update() {
            let form_data = new FormData();
            form_data.append('data', editor.getValue());

            let response = await fetch('', {
                method: 'POST',
                body: form_data
            });
            let foo = await response.json();
            if (foo.page) {
                // TODO: get scroll position and restore it
                document.getElementById('result').srcdoc = foo.page;
            }
        }


        function foo() {
            iommi_debounce(update, 200)();
        }

        editor.session.on('change', foo);
        editor.setFontSize(14);
        editor.session.setUseWrapMode(true);
        
        foo();
        ''')),
    )