Django – Iframe – Internt explorer : problems SEC7111


I’m recently making the use of Iframe and postmessages for running a project. I run into problems while testing it for Internet Explore (not a news).

The fact is, IE is pretty bad also at stating errors, the only things that it says is that the form where blocked for security reason saying

SEC7111

I initially thought of the X-Frame-Option, and with django you can fix it by annotating the view with @xframe_options_exempt. This works but not when you POST to a view within the Iframe. This beacuse Django uses CSRF cookie while IE blocks cookie of  a third party.

The soluition is pretty easy: THERE’S NO SOLUTION. As explained in this ticket.  The best one seems to be the one of not using Iframe. Or to remove CSRF for that specific view.

A thing that took me forever to solve this problem is the fact that django can’t show you the page 403 since it’s protected for Iframing (you need to rewrite the 403handler, maybe the 403csrfhandler if existsts) and then IE tells you that the page can’t be displayed for security reasons, which at first sights it’s impossible to grasp the reason.

 

 

“Beautiful” Django widget for Multi Selection


widtget.png
Left: django default widget / Right: final result

To be honest django is terrific, but in order to be general enough it lacks some look and feel and other stylistics things. One of the problem with forms, which generally work great, is with multi selections. You can have an item list or a checkbox list, like in the 90s. I decided to build a widget to render in a nice fashion the multi-selection case. It took me more than expected, roughly an afternoon, but I run into various problem and I’d to hack a bit the widgets. One of the biggest problem was to access the model.object in the widget since i want to display more data than just the label. Another problem that stucked me for a while was the fact that with crispy form the widget_template overiding seems not to be working (issue here).

Since I want to write less code as possible, the ingredients are:

  • use Class Based view
  • use Model Forms

And the final solution I made allowed me to cut ~50% of the code. Less code you write less bug you make.

The code, once made, is not complex. However, getting there took some time. Let’s start from the view.

class MyVIew(CreateView):
template_name = "my_template.html"
form_class = MultipleChoiceForm
def get_form_kwargs(self):
ctx = super().get_form_kwargs()
ctx.update({'qs':THE_QUERYSET})
return ctx
view raw views.py hosted with ❤ by GitHub

https://gist.github.com/esseti/441f9a4da1b69f1b6c6061432bcc640e.js

rewriting the get_context  function is done for being able to pass a queryset to the formThis allows us to have two benefits:

  • we are able to display in the form field only the data that we want and not the entire list of items present in the database
  • we can pass the queryset into the widget part, usually widgets do not have access to the context

To do so, we have to modify a bit the Form

class MultipleChoiceForm(ModelForm):
class Meta:
model = MyModel
fields = ('myChoices',)
widgets = {
'myChoices': MyWidget,
}
def __init__(self, qs, *args, **kwargs):
# this is set in get_form_kwargs of the view
super().__init__(*args, **kwargs)
#this is needed to display only the data that matches the query
self.fields['myChoices'].queryset = qs
#this is needed for the rendering
self.fields['myChoices'].widget.qs = qs
view raw forms.py hosted with ❤ by GitHub

As you can see we set the queryset of a inner field and also to as widget.qs value. Note that the widget of the field is linked to the brand new widget I just made.

class MyWidget(forms.widgets.CheckboxSelectMultiple):
template_name = 'web_admin/partial/checkbox.html'
option_template_name = 'web_admin/partial/panel_item_p.html'
def get_context(self, name, value, attrs):
ctx = super().get_context(name, value, attrs)
# update the context adding the qs
ctx.update(dict(qs=self.qs))
return ctx
view raw widget.py hosted with ❤ by GitHub

For the widget, I had to extend and overwrite the get_context function in order to load a specifc value from the context. This is a bit of hack, since widget should not know the request or context data, but I need it!

Finally, in the templates (that you see in the form variable) I made the bootstrap panels where I displayed the widget and other information (directly from the object).

The first template is pretty standard

{% with id=widget.attrs.id %}
{% for group, options, index in widget.optgroups %}
{% for option in options %}
{% with widget=option %}
{% include widget.template_name%}
{% endwith %}
{% endfor %}
{% endfor %}
{% endwith %}
view raw checkbox.html hosted with ❤ by GitHub

The second one has a piece of code to load from the qs  variable the correct item that is displayed within the widget.

{% load consenta_utils %}
{% with item=qs|index:widget.index %}
<div class="col-md-3 ">
<div class="panel purpose panel-default {% if widget.attrs.checked %}panel-primary{% endif %}"
style="cursor: pointer;">
<div class="panel-heading ">
<!-- this is the standard for the checkbox from django . i suggest to hide this in the class-->
<!-- the click on the div .panel checks the checbox (see js) -->
<input type="checkbox"
name="{{ widget.name }}"
{% if widget.value != None %}
value="{{ widget.value|stringformat:'s' }}"
{% endif %}
{% for name, value in widget.attrs.items %}{% if value is not False %}
{{ name }}{% if value is not True %}="{{ value|stringformat:'s' }}"{% endif %}
{% endif %}{% endfor %}/>
</div>
<div class="panel-body">
{{ item.description }}
</div>
</div>
</div>
{% endwith %}
view raw panel.html hosted with ❤ by GitHub

To do so I had to create a filter to get the item from the list

from django import template
register = template.Library()
@register.filter
def index(List, i):
return List[int(i)]

I also add some JS to make the whole panel green when selected.

$(document).ready(function () {
//this makes all the panels of the same size
function resize_panel(selector, what) {
if (what == undefined)
what = '.panel-body';
var objs = $(selector).find(what);
var o_h = 0;
objs.each(function () {
if (o_h < $(this).height()) {
o_h = $(this).height();
}
});
objs.each(function () {
$(this).height(o_h);
});
};
resize_panel('.panel', '.panel-heading');
resize_panel('.panel', '.panel-body');
resize_panel('.panel', '.panel-footer');
//this adds panel-primary (green stuff) to the panel when checkbox is selected
$('.purpose').click(function () {
var checkbox = $(this).find('input[type=checkbox]');
console.log($(this));
checkbox.prop("checked", !checkbox.prop("checked"));
if (checkbox.prop("checked")) {
$(this).addClass('panel-primary');
} else {
$(this).removeClass('panel-primary');
}
});
});
view raw script.js hosted with ❤ by GitHub

For full code write a comment here and I’ll provide it.

A full set of the gist is here

Final

This is the result

final.png

  • It works
  • It’s (somehow) better than the plain one and quite reusable
  • It took longer than expected to implement it