From 17c3b6486b0d5ed5a9248010b5f8ec98cb343ffc Mon Sep 17 00:00:00 2001 From: Ryan Cross Date: Thu, 21 Feb 2013 22:12:19 +0000 Subject: [PATCH] move form_utils - Legacy-Id: 5446 --- ietf/form_utils/__init__.py | 0 ietf/form_utils/admin.py | 12 + ietf/form_utils/fields.py | 51 ++++ ietf/form_utils/forms.py | 278 ++++++++++++++++++ .../media/form_utils/js/autoresize.js | 3 + .../media/form_utils/js/jquery.autogrow.js | 132 +++++++++ ietf/form_utils/models.py | 0 ietf/form_utils/settings.py | 12 + .../templates/form_utils/better_form.html | 16 + .../templates/form_utils/fields_as_lis.html | 11 + .../form_utils/templates/form_utils/form.html | 13 + ietf/form_utils/templatetags/CVS/Entries | 3 + ietf/form_utils/templatetags/CVS/Repository | 1 + ietf/form_utils/templatetags/CVS/Root | 1 + ietf/form_utils/templatetags/__init__.py | 6 + .../templatetags/form_utils_tags.py | 42 +++ ietf/form_utils/utils.py | 20 ++ ietf/form_utils/widgets.py | 112 +++++++ ietf/settings.py | 3 +- 19 files changed, 715 insertions(+), 1 deletion(-) create mode 100644 ietf/form_utils/__init__.py create mode 100644 ietf/form_utils/admin.py create mode 100644 ietf/form_utils/fields.py create mode 100644 ietf/form_utils/forms.py create mode 100644 ietf/form_utils/media/form_utils/js/autoresize.js create mode 100644 ietf/form_utils/media/form_utils/js/jquery.autogrow.js create mode 100644 ietf/form_utils/models.py create mode 100644 ietf/form_utils/settings.py create mode 100644 ietf/form_utils/templates/form_utils/better_form.html create mode 100644 ietf/form_utils/templates/form_utils/fields_as_lis.html create mode 100644 ietf/form_utils/templates/form_utils/form.html create mode 100644 ietf/form_utils/templatetags/CVS/Entries create mode 100644 ietf/form_utils/templatetags/CVS/Repository create mode 100644 ietf/form_utils/templatetags/CVS/Root create mode 100644 ietf/form_utils/templatetags/__init__.py create mode 100644 ietf/form_utils/templatetags/form_utils_tags.py create mode 100644 ietf/form_utils/utils.py create mode 100644 ietf/form_utils/widgets.py diff --git a/ietf/form_utils/__init__.py b/ietf/form_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ietf/form_utils/admin.py b/ietf/form_utils/admin.py new file mode 100644 index 000000000..b074bdb99 --- /dev/null +++ b/ietf/form_utils/admin.py @@ -0,0 +1,12 @@ +from django.contrib import admin +from django import forms + +from form_utils.fields import ClearableFileField + +class ClearableFileFieldsAdmin(admin.ModelAdmin): + def formfield_for_dbfield(self, db_field, **kwargs): + field = super(ClearableFileFieldsAdmin, self).formfield_for_dbfield( + db_field, **kwargs) + if isinstance(field, forms.FileField): + field = ClearableFileField(field) + return field diff --git a/ietf/form_utils/fields.py b/ietf/form_utils/fields.py new file mode 100644 index 000000000..d1d00ecf7 --- /dev/null +++ b/ietf/form_utils/fields.py @@ -0,0 +1,51 @@ +from django import forms + +from form_utils.widgets import ClearableFileInput + +class FakeEmptyFieldFile(object): + """ + A fake FieldFile that will convice a FileField model field to + actually replace an existing file name with an empty string. + + FileField.save_form_data only overwrites its instance data if the + incoming form data evaluates to True in a boolean context (because + an empty file input is assumed to mean "no change"). We want to be + able to clear it without requiring the use of a model FileField + subclass (keeping things at the form level only). In order to do + this we need our form field to return a value that evaluates to + True in a boolean context, but to the empty string when coerced to + unicode. This object fulfills that requirement. + + It also needs the _committed attribute to satisfy the test in + FileField.pre_save. + + This is, of course, hacky and fragile, and depends on internal + knowledge of the FileField and FieldFile classes. But it will + serve until Django FileFields acquire a native ability to be + cleared (ticket 7048). + + """ + def __unicode__(self): + return u'' + _committed = True + +class ClearableFileField(forms.MultiValueField): + default_file_field_class = forms.FileField + widget = ClearableFileInput + + def __init__(self, file_field=None, template=None, *args, **kwargs): + file_field = file_field or self.default_file_field_class(*args, + **kwargs) + fields = (file_field, forms.BooleanField(required=False)) + kwargs['required'] = file_field.required + kwargs['widget'] = self.widget(file_widget=file_field.widget, + template=template) + super(ClearableFileField, self).__init__(fields, *args, **kwargs) + + def compress(self, data_list): + if data_list[1] and not data_list[0]: + return FakeEmptyFieldFile() + return data_list[0] + +class ClearableImageField(ClearableFileField): + default_file_field_class = forms.ImageField diff --git a/ietf/form_utils/forms.py b/ietf/form_utils/forms.py new file mode 100644 index 000000000..be21ea2c8 --- /dev/null +++ b/ietf/form_utils/forms.py @@ -0,0 +1,278 @@ +""" +forms for django-form-utils + +Time-stamp: <2010-04-28 02:57:16 carljm forms.py> + +""" +from copy import deepcopy + +from django import forms +from django.forms.util import flatatt, ErrorDict +from django.utils.safestring import mark_safe + +class Fieldset(object): + """ + An iterable Fieldset with a legend and a set of BoundFields. + + """ + def __init__(self, form, name, boundfields, legend='', classes='', description=''): + self.form = form + self.boundfields = boundfields + if legend is None: legend = name + self.legend = legend and mark_safe(legend) + self.classes = classes + self.description = mark_safe(description) + self.name = name + + + def _errors(self): + return ErrorDict(((k, v) for (k, v) in self.form.errors.iteritems() + if k in [f.name for f in self.boundfields])) + errors = property(_errors) + + def __iter__(self): + for bf in self.boundfields: + yield _mark_row_attrs(bf, self.form) + + def __repr__(self): + return "%s('%s', %s, legend='%s', classes='%s', description='%s')" % ( + self.__class__.__name__, self.name, + [f.name for f in self.boundfields], self.legend, self.classes, self.description) + +class FieldsetCollection(object): + def __init__(self, form, fieldsets): + self.form = form + self.fieldsets = fieldsets + self._cached_fieldsets = [] + + def __len__(self): + return len(self.fieldsets) or 1 + + def __iter__(self): + if not self._cached_fieldsets: + self._gather_fieldsets() + for field in self._cached_fieldsets: + yield field + + def __getitem__(self, key): + if not self._cached_fieldsets: + self._gather_fieldsets() + for field in self._cached_fieldsets: + if field.name == key: + return field + raise KeyError + + def _gather_fieldsets(self): + if not self.fieldsets: + self.fieldsets = (('main', {'fields': self.form.fields.keys(), + 'legend': ''}),) + for name, options in self.fieldsets: + try: + field_names = [n for n in options['fields'] + if n in self.form.fields] + except KeyError: + raise ValueError("Fieldset definition must include 'fields' option." ) + boundfields = [forms.forms.BoundField(self.form, self.form.fields[n], n) + for n in field_names] + self._cached_fieldsets.append(Fieldset(self.form, name, + boundfields, options.get('legend', None), + ' '.join(options.get('classes', ())), + options.get('description', ''))) + +def _get_meta_attr(attrs, attr, default): + try: + ret = getattr(attrs['Meta'], attr) + except (KeyError, AttributeError): + ret = default + return ret + +def _set_meta_attr(attrs, attr, value): + try: + setattr(attrs['Meta'], attr, value) + return True + except KeyError: + return False + +def get_fieldsets(bases, attrs): + """ + Get the fieldsets definition from the inner Meta class. + + """ + fieldsets = _get_meta_attr(attrs, 'fieldsets', None) + if fieldsets is None: + #grab the fieldsets from the first base class that has them + for base in bases: + fieldsets = getattr(base, 'base_fieldsets', None) + if fieldsets is not None: + break + fieldsets = fieldsets or [] + return fieldsets + +def get_fields_from_fieldsets(fieldsets): + """ + Get a list of all fields included in a fieldsets definition. + + """ + fields = [] + try: + for name, options in fieldsets: + fields.extend(options['fields']) + except (TypeError, KeyError): + raise ValueError('"fieldsets" must be an iterable of two-tuples, ' + 'and the second tuple must be a dictionary ' + 'with a "fields" key') + return fields + +def get_row_attrs(bases, attrs): + """ + Get the row_attrs definition from the inner Meta class. + + """ + return _get_meta_attr(attrs, 'row_attrs', {}) + +def _mark_row_attrs(bf, form): + row_attrs = deepcopy(form._row_attrs.get(bf.name, {})) + if bf.field.required: + req_class = 'required' + else: + req_class = 'optional' + if 'class' in row_attrs: + row_attrs['class'] = row_attrs['class'] + ' ' + req_class + else: + row_attrs['class'] = req_class + bf.row_attrs = mark_safe(flatatt(row_attrs)) + return bf + +class BetterFormBaseMetaclass(type): + def __new__(cls, name, bases, attrs): + attrs['base_fieldsets'] = get_fieldsets(bases, attrs) + fields = get_fields_from_fieldsets(attrs['base_fieldsets']) + if (_get_meta_attr(attrs, 'fields', None) is None and + _get_meta_attr(attrs, 'exclude', None) is None): + _set_meta_attr(attrs, 'fields', fields) + attrs['base_row_attrs'] = get_row_attrs(bases, attrs) + new_class = super(BetterFormBaseMetaclass, + cls).__new__(cls, name, bases, attrs) + return new_class + +class BetterFormMetaclass(BetterFormBaseMetaclass, + forms.forms.DeclarativeFieldsMetaclass): + pass + +class BetterModelFormMetaclass(BetterFormBaseMetaclass, + forms.models.ModelFormMetaclass): + pass + +class BetterBaseForm(object): + """ + ``BetterForm`` and ``BetterModelForm`` are subclasses of Form + and ModelForm that allow for declarative definition of fieldsets + and row_attrs in an inner Meta class. + + The row_attrs declaration is a dictionary mapping field names to + dictionaries of attribute/value pairs. The attribute/value + dictionaries will be flattened into HTML-style attribute/values + (i.e. {'style': 'display: none'} will become ``style="display: + none"``), and will be available as the ``row_attrs`` attribute of + the ``BoundField``. Also, a CSS class of "required" or "optional" + will automatically be added to the row_attrs of each + ``BoundField``, depending on whether the field is required. + + There is no automatic inheritance of ``row_attrs``. + + The fieldsets declaration is a list of two-tuples very similar to + the ``fieldsets`` option on a ModelAdmin class in + ``django.contrib.admin``. + + The first item in each two-tuple is a name for the fieldset, and + the second is a dictionary of fieldset options. + + Valid fieldset options in the dictionary include: + + ``fields`` (required): A tuple of field names to display in this + fieldset. + + ``classes``: A list of extra CSS classes to apply to the fieldset. + + ``legend``: This value, if present, will be the contents of a ``legend`` + tag to open the fieldset. + + ``description``: A string of optional extra text to be displayed + under the ``legend`` of the fieldset. + + When iterated over, the ``fieldsets`` attribute of a + ``BetterForm`` (or ``BetterModelForm``) yields ``Fieldset``s. + Each ``Fieldset`` has a ``name`` attribute, a ``legend`` + attribute, , a ``classes`` attribute (the ``classes`` tuple + collapsed into a space-separated string), and a description + attribute, and when iterated over yields its ``BoundField``s. + + Subclasses of a ``BetterForm`` will inherit their parent's + fieldsets unless they define their own. + + A ``BetterForm`` or ``BetterModelForm`` can still be iterated over + directly to yield all of its ``BoundField``s, regardless of + fieldsets. + + """ + def __init__(self, *args, **kwargs): + self._fieldsets = deepcopy(self.base_fieldsets) + self._row_attrs = deepcopy(self.base_row_attrs) + self._fieldset_collection = None + super(BetterBaseForm, self).__init__(*args, **kwargs) + + @property + def fieldsets(self): + if not self._fieldset_collection: + self._fieldset_collection = FieldsetCollection(self, + self._fieldsets) + return self._fieldset_collection + + def __iter__(self): + for bf in super(BetterBaseForm, self).__iter__(): + yield _mark_row_attrs(bf, self) + + def __getitem__(self, name): + bf = super(BetterBaseForm, self).__getitem__(name) + return _mark_row_attrs(bf, self) + +class BetterForm(BetterBaseForm, forms.Form): + __metaclass__ = BetterFormMetaclass + __doc__ = BetterBaseForm.__doc__ + +class BetterModelForm(BetterBaseForm, forms.ModelForm): + __metaclass__ = BetterModelFormMetaclass + __doc__ = BetterBaseForm.__doc__ + + +class BasePreviewForm (object): + """ + Mixin to add preview functionality to a form. If the form is submitted with + the following k/v pair in its ``data`` dictionary: + + 'submit': 'preview' (value string is case insensitive) + + Then ``PreviewForm.preview`` will be marked ``True`` and the form will + be marked invalid (though this invalidation will not put an error in + its ``errors`` dictionary). + + """ + def __init__(self, *args, **kwargs): + super(BasePreviewForm, self).__init__(*args, **kwargs) + self.preview = self.check_preview(kwargs.get('data', None)) + + def check_preview(self, data): + if data and data.get('submit', '').lower() == u'preview': + return True + return False + + def is_valid(self, *args, **kwargs): + if self.preview: + return False + return super(BasePreviewForm, self).is_valid() + +class PreviewModelForm(BasePreviewForm, BetterModelForm): + pass + +class PreviewForm(BasePreviewForm, BetterForm): + pass diff --git a/ietf/form_utils/media/form_utils/js/autoresize.js b/ietf/form_utils/media/form_utils/js/autoresize.js new file mode 100644 index 000000000..8c269b866 --- /dev/null +++ b/ietf/form_utils/media/form_utils/js/autoresize.js @@ -0,0 +1,3 @@ +$(document).ready(function() { + $('textarea.autoresize').autogrow(); + }); diff --git a/ietf/form_utils/media/form_utils/js/jquery.autogrow.js b/ietf/form_utils/media/form_utils/js/jquery.autogrow.js new file mode 100644 index 000000000..aeae46545 --- /dev/null +++ b/ietf/form_utils/media/form_utils/js/jquery.autogrow.js @@ -0,0 +1,132 @@ +/* + * Auto Expanding Text Area (1.2.2) + * by Chrys Bader (www.chrysbader.com) + * chrysb@gmail.com + * + * Special thanks to: + * Jake Chapa - jake@hybridstudio.com + * John Resig - jeresig@gmail.com + * + * Copyright (c) 2008 Chrys Bader (www.chrysbader.com) + * Licensed under the GPL (GPL-LICENSE.txt) license. + * + * + * NOTE: This script requires jQuery to work. Download jQuery at www.jquery.com + * + */ + +(function(jQuery) { + + var self = null; + + jQuery.fn.autogrow = function(o) + { + return this.each(function() { + new jQuery.autogrow(this, o); + }); + }; + + + /** + * The autogrow object. + * + * @constructor + * @name jQuery.autogrow + * @param Object e The textarea to create the autogrow for. + * @param Hash o A set of key/value pairs to set as configuration properties. + * @cat Plugins/autogrow + */ + + jQuery.autogrow = function (e, o) + { + this.options = o || {}; + this.dummy = null; + this.interval = null; + this.line_height = this.options.lineHeight || parseInt(jQuery(e).css('line-height')); + this.min_height = this.options.minHeight || parseInt(jQuery(e).css('min-height')); + this.max_height = this.options.maxHeight || parseInt(jQuery(e).css('max-height'));; + this.textarea = jQuery(e); + + if(this.line_height == NaN) + this.line_height = 0; + + // Only one textarea activated at a time, the one being used + this.init(); + }; + + jQuery.autogrow.fn = jQuery.autogrow.prototype = { + autogrow: '1.2.2' + }; + + jQuery.autogrow.fn.extend = jQuery.autogrow.extend = jQuery.extend; + + jQuery.autogrow.fn.extend({ + + init: function() { + var self = this; + this.textarea.css({overflow: 'hidden', display: 'block'}); + this.textarea.bind('focus', function() { self.startExpand() } ).bind('blur', function() { self.stopExpand() }); + this.checkExpand(); + }, + + startExpand: function() { + var self = this; + this.interval = window.setInterval(function() {self.checkExpand()}, 400); + }, + + stopExpand: function() { + clearInterval(this.interval); + }, + + checkExpand: function() { + + if (this.dummy == null) + { + this.dummy = jQuery('
'); + this.dummy.css({ + 'font-size' : this.textarea.css('font-size'), + 'font-family': this.textarea.css('font-family'), + 'width' : this.textarea.css('width'), + 'padding' : this.textarea.css('padding'), + 'line-height': this.line_height + 'px', + 'overflow-x' : 'hidden', + 'position' : 'absolute', + 'top' : 0, + 'left' : -9999 + }).appendTo('body'); + } + + // Strip HTML tags + var html = this.textarea.val().replace(/(<|>)/g, ''); + + // IE is different, as per usual + if ($.browser.msie) + { + html = html.replace(/\n/g, '
new'); + } + else + { + html = html.replace(/\n/g, '
new'); + } + + if (this.dummy.html() != html) + { + this.dummy.html(html); + + if (this.max_height > 0 && (this.dummy.height() + this.line_height > this.max_height)) + { + this.textarea.css('overflow-y', 'auto'); + } + else + { + this.textarea.css('overflow-y', 'hidden'); + if (this.textarea.height() < this.dummy.height() + this.line_height || (this.dummy.height() < this.textarea.height())) + { + this.textarea.animate({height: (this.dummy.height() + this.line_height) + 'px'}, 100); + } + } + } + } + + }); +})(jQuery); \ No newline at end of file diff --git a/ietf/form_utils/models.py b/ietf/form_utils/models.py new file mode 100644 index 000000000..e69de29bb diff --git a/ietf/form_utils/settings.py b/ietf/form_utils/settings.py new file mode 100644 index 000000000..a8462e62f --- /dev/null +++ b/ietf/form_utils/settings.py @@ -0,0 +1,12 @@ +import posixpath + +from django.conf import settings + +JQUERY_URL = getattr( + settings, 'JQUERY_URL', + 'http://ajax.googleapis.com/ajax/libs/jquery/1.4/jquery.min.js') + +if not ((':' in JQUERY_URL) or (JQUERY_URL.startswith('/'))): + JQUERY_URL = posixpath.join(settings.MEDIA_URL, JQUERY_URL) + +FORM_UTILS_MEDIA_URL = getattr(settings, 'FORM_UTILS_MEDIA_URL', settings.MEDIA_URL) diff --git a/ietf/form_utils/templates/form_utils/better_form.html b/ietf/form_utils/templates/form_utils/better_form.html new file mode 100644 index 000000000..d25e429b1 --- /dev/null +++ b/ietf/form_utils/templates/form_utils/better_form.html @@ -0,0 +1,16 @@ +{% extends "form_utils/form.html" %} + +{% block fields %} +{% for fieldset in form.fieldsets %} +
+{% if fieldset.legend %} +{{ fieldset.legend }} +{% endif %} + +
+{% endfor %} +{% endblock %} diff --git a/ietf/form_utils/templates/form_utils/fields_as_lis.html b/ietf/form_utils/templates/form_utils/fields_as_lis.html new file mode 100644 index 000000000..99c79719f --- /dev/null +++ b/ietf/form_utils/templates/form_utils/fields_as_lis.html @@ -0,0 +1,11 @@ +{% for field in fields %} +{% if field.is_hidden %} +{{ field }} +{% else %} + + {{ field.errors }} + {{ field.label_tag }} + {{ field }} + +{% endif %} +{% endfor %} diff --git a/ietf/form_utils/templates/form_utils/form.html b/ietf/form_utils/templates/form_utils/form.html new file mode 100644 index 000000000..4b235d533 --- /dev/null +++ b/ietf/form_utils/templates/form_utils/form.html @@ -0,0 +1,13 @@ +{% block errors %} +{% if form.non_field_errors %}{{ form.non_field_errors }}{% endif %} +{% endblock %} + +{% block fields %} +
+
    +{% with form as fields %} +{% include "form_utils/fields_as_lis.html" %} +{% endwith %} +
+
+{% endblock %} diff --git a/ietf/form_utils/templatetags/CVS/Entries b/ietf/form_utils/templatetags/CVS/Entries new file mode 100644 index 000000000..6cfab49ff --- /dev/null +++ b/ietf/form_utils/templatetags/CVS/Entries @@ -0,0 +1,3 @@ +/__init__.py/1.1/Fri Jan 28 21:08:54 2011// +/form_utils_tags.py/1.1/Fri Jan 28 21:08:54 2011// +D diff --git a/ietf/form_utils/templatetags/CVS/Repository b/ietf/form_utils/templatetags/CVS/Repository new file mode 100644 index 000000000..da7c7547a --- /dev/null +++ b/ietf/form_utils/templatetags/CVS/Repository @@ -0,0 +1 @@ +ietfsec/form_utils/templatetags diff --git a/ietf/form_utils/templatetags/CVS/Root b/ietf/form_utils/templatetags/CVS/Root new file mode 100644 index 000000000..85344fb37 --- /dev/null +++ b/ietf/form_utils/templatetags/CVS/Root @@ -0,0 +1 @@ +/a/cvs diff --git a/ietf/form_utils/templatetags/__init__.py b/ietf/form_utils/templatetags/__init__.py new file mode 100644 index 000000000..f5d2536b7 --- /dev/null +++ b/ietf/form_utils/templatetags/__init__.py @@ -0,0 +1,6 @@ +""" +__init__.py for django-form-utils - templatetags + +Time-stamp: <2008-10-13 12:14:37 carljm __init__.py> + +""" diff --git a/ietf/form_utils/templatetags/form_utils_tags.py b/ietf/form_utils/templatetags/form_utils_tags.py new file mode 100644 index 000000000..4be8c8f0d --- /dev/null +++ b/ietf/form_utils/templatetags/form_utils_tags.py @@ -0,0 +1,42 @@ +""" +templatetags for django-form-utils + +Time-stamp: <2009-03-26 12:32:08 carljm form_utils_tags.py> + +""" +from django import template + +from form_utils.forms import BetterForm, BetterModelForm +from form_utils.utils import select_template_from_string + +register = template.Library() + +@register.filter +def render(form, template_name=None): + """ + Renders a ``django.forms.Form`` or + ``form_utils.forms.BetterForm`` instance using a template. + + The template name(s) may be passed in as the argument to the + filter (use commas to separate multiple template names for + template selection). + + If not provided, the default template name is + ``form_utils/form.html``. + + If the form object to be rendered is an instance of + ``form_utils.forms.BetterForm`` or + ``form_utils.forms.BetterModelForm``, the template + ``form_utils/better_form.html`` will be used instead if present. + + """ + default = 'form_utils/form.html' + if isinstance(form, (BetterForm, BetterModelForm)): + default = ','.join(['form_utils/better_form.html', default]) + tpl = select_template_from_string(template_name or default) + + return tpl.render(template.Context({'form': form})) + + + + diff --git a/ietf/form_utils/utils.py b/ietf/form_utils/utils.py new file mode 100644 index 000000000..79fd76bce --- /dev/null +++ b/ietf/form_utils/utils.py @@ -0,0 +1,20 @@ +""" +utility functions for django-form-utils + +Time-stamp: <2009-03-26 12:32:41 carljm utils.py> + +""" +from django.template import loader + +def select_template_from_string(arg): + """ + Select a template from a string, which can include multiple + template paths separated by commas. + + """ + if ',' in arg: + tpl = loader.select_template( + [tn.strip() for tn in arg.split(',')]) + else: + tpl = loader.get_template(arg) + return tpl diff --git a/ietf/form_utils/widgets.py b/ietf/form_utils/widgets.py new file mode 100644 index 000000000..92bf9ca72 --- /dev/null +++ b/ietf/form_utils/widgets.py @@ -0,0 +1,112 @@ +""" +widgets for django-form-utils + +parts of this code taken from http://www.djangosnippets.org/snippets/934/ + - thanks baumer1122 + +""" +import os +import posixpath + +from django import forms +from django.conf import settings +from django.utils.functional import curry +from django.utils.safestring import mark_safe +from django.core.files.uploadedfile import SimpleUploadedFile as UploadedFile + +from form_utils.settings import JQUERY_URL, FORM_UTILS_MEDIA_URL + +try: + from sorl.thumbnail.main import DjangoThumbnail + def thumbnail(image_path, width, height): + t = DjangoThumbnail(relative_source=image_path, requested_size=(width,height)) + return u'%s' % (t.absolute_url, image_path) +except ImportError: + def thumbnail(image_path, width, height): + absolute_url = posixpath.join(settings.MEDIA_URL, image_path) + return u'%s' % (absolute_url, image_path) + +class ImageWidget(forms.FileInput): + template = '%(input)s
%(image)s' + + def __init__(self, attrs=None, template=None, width=200, height=200): + if template is not None: + self.template = template + self.width = width + self.height = height + super(ImageWidget, self).__init__(attrs) + + def render(self, name, value, attrs=None): + input_html = super(forms.FileInput, self).render(name, value, attrs) + if hasattr(value, 'width') and hasattr(value, 'height'): + image_html = thumbnail(value.name, self.width, self.height) + output = self.template % {'input': input_html, + 'image': image_html} + else: + output = input_html + return mark_safe(output) + +class ClearableFileInput(forms.MultiWidget): + default_file_widget_class = forms.FileInput + template = '%(input)s Clear: %(checkbox)s' + + def __init__(self, file_widget=None, + attrs=None, template=None): + if template is not None: + self.template = template + file_widget = file_widget or self.default_file_widget_class() + super(ClearableFileInput, self).__init__( + widgets=[file_widget, forms.CheckboxInput()], + attrs=attrs) + + def render(self, name, value, attrs=None): + if isinstance(value, list): + self.value = value[0] + else: + self.value = value + return super(ClearableFileInput, self).render(name, value, attrs) + + def decompress(self, value): + # the clear checkbox is never initially checked + return [value, None] + + def format_output(self, rendered_widgets): + if self.value: + return self.template % {'input': rendered_widgets[0], + 'checkbox': rendered_widgets[1]} + return rendered_widgets[0] + +root = lambda path: posixpath.join(FORM_UTILS_MEDIA_URL, path) + +class AutoResizeTextarea(forms.Textarea): + """ + A Textarea widget that automatically resizes to accomodate its contents. + + """ + class Media: + + js = (JQUERY_URL, + root('form_utils/js/jquery.autogrow.js'), + root('form_utils/js/autoresize.js')) + + def __init__(self, *args, **kwargs): + attrs = kwargs.setdefault('attrs', {}) + try: + attrs['class'] = "%s autoresize" % (attrs['class'],) + except KeyError: + attrs['class'] = 'autoresize' + attrs.setdefault('cols', 80) + attrs.setdefault('rows', 5) + super(AutoResizeTextarea, self).__init__(*args, **kwargs) + +class InlineAutoResizeTextarea(AutoResizeTextarea): + def __init__(self, *args, **kwargs): + attrs = kwargs.setdefault('attrs', {}) + try: + attrs['class'] = "%s inline" % (attrs['class'],) + except KeyError: + attrs['class'] = 'inline' + attrs.setdefault('cols', 40) + attrs.setdefault('rows', 2) + super(InlineAutoResizeTextarea, self).__init__(*args, **kwargs) + diff --git a/ietf/settings.py b/ietf/settings.py index c38e7061e..b226771ed 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -174,6 +174,7 @@ INSTALLED_APPS = ( 'ietf.secr.announcement', 'ietf.secr.areas', 'ietf.secr.drafts', + 'ietf.secr.form_utils', 'ietf.secr.groups', 'ietf.secr.ipradmin', 'ietf.secr.meetings', @@ -338,7 +339,7 @@ SEC_AUTH_UNRESTRICTED_URLS = ( #(r'^/proceedings/'), (r'^/secr/sreq/'), ) -SECR_STATIC_URL = '/secr/static/' +SECR_STATIC_URL = '/secr-static/' # Put SECRET_KEY in here, or any other sensitive or site-specific # changes. DO NOT commit settings_local.py to svn.