{"id":30,"date":"2015-05-09T00:52:07","date_gmt":"2015-05-08T22:52:07","guid":{"rendered":"http:\/\/mickael.bucas.name\/blog\/?p=30"},"modified":"2019-01-31T12:39:42","modified_gmt":"2019-01-31T11:39:42","slug":"a-new-django-widget","status":"publish","type":"post","link":"https:\/\/mickael.bucas.name\/blog\/2015\/05\/a-new-django-widget\/","title":{"rendered":"A new Django widget"},"content":{"rendered":"<p>I&#8217;m building websites with Bootstrap and Django. I&#8217;ve already created a form on one of them with a file input. The standard file input can&#8217;t really be styled with CSS apparently, so I&#8217;ve searched for a way to have a nice Bootstrap file input and found <a href=\"http:\/\/www.abeautifulsite.net\/whipping-file-inputs-into-shape-with-bootstrap-3\/\" target=\"_blank\">this<\/a>. In short, it uses a button and a text input for display, and a transparent file input above them to use the browser access to local files. Some Javascript makes those two parts communicate.<\/p>\n<p>First I&#8217;ve used this manually in a form directly in the template. Today I discovered that you can write your own Django widgets, so I tried to reorganize this file input.<\/p>\n<h3>Creating the widget<\/h3>\n<p>First I created a <code>widgets<\/code> directory in my app, with the following files : <code>__init__.py<\/code> (empty) and <code>styledfileinput.py<\/code>.<\/p>\n<p><code>styledfileinput.py<\/code> contains :<\/p>\n<pre class=\"brush: python; collapse: false; title: ; wrap-lines: false; notranslate\" title=\"\"># StyledFileInput\n# coding: utf-8\n\nfrom django.forms.widgets import FileInput\nfrom django.utils.html import format_html\nfrom django.forms.utils import flatatt\n\nclass StyledFileInput(FileInput):\n\nclass Media:\ncss = { 'all': ('styledfileinput.css', ) }\njs = ('styledfileinput.js', )\n\ndef render(self, name, value, attrs=None):\nif value is None:\nvalue = ''\nfinal_attrs = self.build_attrs(attrs, type=self.input_type, name=name)\nif value != '':\n# Only add the 'value' attribute if a value is non-empty.\nfinal_attrs&#x5B;'value'] = force_text(self._format_value(value))\nreturn format_html(\n'&lt;div class=\"input-group\"&gt;\\n'\n+ '&lt;span class=\"input-group-btn\"&gt;\\n'\n+ '&lt;span class=\"btn btn-default btn-file\"&gt;\\n'\n+ 'Browse... \\n'\n+ '&lt;input multiple=\"\" {} &gt;\\n'\n+ '&lt;\/span&gt;\\n'\n+ '&lt;\/span&gt;\\n'\n+ '&lt;input class=\"form-control\" readonly=\"\" type=\"text\"&gt;\\n'\n+ '&lt;\/div&gt;\\n', flatatt(final_attrs)\n)\n<\/pre>\n<p>I&#8217;ve created a <code>StyledFileInput<\/code> class inheriting from <code>FileInput<\/code>, and redefined the <code>render<\/code> function with the whole HTML block. I&#8217;ve kept the attributes and value handling, so that Django can use it properly.<\/p>\n<p>There is an inner <code>Media<\/code> class to define the required static files. This two files are placed in the <code>static<\/code> directory of my app.<\/p>\n<p>Here is the content of <code>styledfileinput.css<\/code> :<\/p>\n<pre class=\"brush: css; collapse: false; title: ; wrap-lines: false; notranslate\" title=\"\">\/* file buttons should be styled *\/\n.btn-file {\nposition: relative;\noverflow: hidden;\n}\n\n.btn-file input&#x5B;type=file] {\nposition: absolute;\ntop: 0;\nright: 0;\nmin-width: 100%;\nmin-height: 100%;\nfont-size: 999px;\ntext-align: right;\nfilter: alpha(opacity=0);\nopacity: 0;\nbackground: red;\ncursor: inherit;\ndisplay: block;\n}\n<\/pre>\n<p>Then <code>styledfileinput.js<\/code> :<\/p>\n<pre class=\"brush: jscript; collapse: false; title: ; wrap-lines: false; notranslate\" title=\"\">(function($){\n$(document)\n.on('change', '.btn-file :file', function() {\nvar input = $(this),\nnumFiles = input.get(0).files ? input.get(0).files.length : 1,\nlabel = input.val().replace(\/\\\\\/g, '\/').replace(\/.*\\\/\/, '');\ninput.trigger('fileselect', &#x5B;numFiles, label]);\n});\n\n$(document).ready( function() {\n$('.btn-file :file').on('fileselect', function(event, numFiles, label) {\n\nvar input = $(this).parents('.input-group').find(':text'),\nlog = numFiles &gt; 1 ? numFiles + ' files selected' : label;\n\nif( input.length ) {\ninput.val(log);\n} else {\nif( log ) alert(log);\n}\n\n});\n});\n})(django.jQuery);\n<\/pre>\n<p>You can see that this code is wrapped in a function called with <code>django.jQuery<\/code> for <code>$<\/code>. This is necessary as I also use the AdminDateWidget, which uses the following JavaScript :<\/p>\n<pre class=\"brush: jscript; collapse: false; title: ; wrap-lines: false; notranslate\" title=\"\">\/* Puts the included jQuery into our own namespace using noConflict and passing\n* it 'true'. This ensures that the included jQuery doesn't pollute the global\n* namespace (i.e. this preserves pre-existing values for both window.$ and\n* window.jQuery).\n*\/\nvar django = django || {};\ndjango.jQuery = jQuery.noConflict(true);\n<\/pre>\n<h3>Using the widget<\/h3>\n<p>Now I can use it in a Django form :<\/p>\n<pre class=\"brush: python; collapse: false; title: ; wrap-lines: false; notranslate\" title=\"\">from app.widgets.styledfileinput import StyledFileInput\n\nclass DocumentForm(forms.ModelForm):\nclass Meta:\nmodel = Document\nfields = \"__all__\"\nwidgets = {\n'date': admin.widgets.AdminDateWidget(),\n'filename' : StyledFileInput(),\n}\n<\/pre>\n<p>In the template, nothing changes in the form itself :<\/p>\n<pre class=\"brush: xml; collapse: false; title: ; wrap-lines: false; notranslate\" title=\"\">\n&lt;form method=\"post\" enctype=\"multipart\/form-data\"&gt;\n{% csrf_token %}\n&lt;div class=\"panel panel-default\"&gt;\n&lt;div class=\"panel-body\"&gt;\n{{ doc_form|bootstrap_horizontal }}\n&lt;button class=\"btn btn-default\" type=\"submit\"&gt;Envoyer&lt;\/button&gt;\n&lt;\/div&gt;\n&lt;\/div&gt;\n&lt;\/form&gt;\n<\/pre>\n<p>You just have to be sure to put somewhere in your template <code>{{ doc_form.media }}<\/code> so that the required static files are included.<\/p>\n<h3>Conclusion<\/h3>\n<p>Now that the Bootstrap file input is redefined as a widget, it&#8217;s easy to attach to any classic Django forms, and it&#8217;s easy to reuse in any project.<\/p>\n<p>If you have any suggestion to improve it, feel free to comment.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m building websites with Bootstrap and Django. I&#8217;ve already created a form on one of them with a file input. The standard file input can&#8217;t really be styled with CSS apparently, so I&#8217;ve searched for a way to have a nice Bootstrap file input and found this. In short, it uses a button and a&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10],"tags":[],"class_list":["post-30","post","type-post","status-publish","format-standard","hentry","category-python"],"_links":{"self":[{"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/posts\/30","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/comments?post=30"}],"version-history":[{"count":21,"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/posts\/30\/revisions"}],"predecessor-version":[{"id":105,"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/posts\/30\/revisions\/105"}],"wp:attachment":[{"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/media?parent=30"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/categories?post=30"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/mickael.bucas.name\/blog\/wp-json\/wp\/v2\/tags?post=30"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}