Commits

Anonymous committed 5f258c7

initial commit

Comments (0)

Files changed (8)

+# use glob syntax.
+syntax: glob
+
+*.pyc
+*.swp
+*.orig
+*.DS_Store
+*.log
+*.cache
+.svn*
+.git*
+Thumbs.db
+Copyright (c) 2012 Jason Christa and contributors
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    1. Redistributions of source code must retain the above copyright notice,
+       this list of conditions and the following disclaimer.
+
+    2. Redistributions in binary form must reproduce the above copyright
+       notice, this list of conditions and the following disclaimer in the
+       documentation and/or other materials provided with the distribution.
+
+    3. Neither the name of this project nor the names of its contributors may
+       be used to endorse or promote products derived from this software without
+       specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Empty file added.

form_extensions/__init__.py

+__version_info__ = {
+    'major': 0,
+    'minor': 1,
+    'micro': 0,
+    'releaselevel': 'alpha',
+    'serial': 1
+}
+
+def get_version(short=False):
+    assert __version_info__['releaselevel'] in ('alpha', 'beta', 'final')
+    vers = ["%(major)i.%(minor)i" % __version_info__, ]
+    if __version_info__['micro']:
+        vers.append(".%(micro)i" % __version_info__)
+    if __version_info__['releaselevel'] != 'final' and not short:
+        vers.append('%s%i' % (__version_info__['releaselevel'][0], __version_info__['serial']))
+    return ''.join(vers)
+
+__version__ = get_version()

form_extensions/fields.py

+from django.forms.fields import Field, CharField, FileField, ImageField
+from form_extensions.widgets import HoneypotWidget, MultiFileInput
+from django.core.exceptions import ValidationError
+from django.core import validators
+import re
+
+
+EMPTY_VALUES = (None, '')
+CURRENCY_RE = re.compile(r'^\$?\d+(,\d{3})*(\.\d{0,2})?$')
+CREDIT_CARD_PATTERNS = {
+    'Visa': '^4([0-9]{12,15})$',
+    'MasterCard': '^5[12345]([0-9]{14})$',
+    'American Express': '^3[47][0-9]{13}$',
+    'Discover': '^6(?:011|5[0-9]{2})[0-9]{12}$',
+    'Diners Club': '^3(?:0[0-5]|[68][0-9])[0-9]{11}$',
+    'JCB': '^(?:2131|1800|35\d{3})\d{11}$',
+}
+
+
+class UnicodeSafeCharField(CharField):
+    def clean(self, value):
+        try:
+            return super(UnicodeSafeCharField, self).clean(value)
+        except UnicodeDecodeError, e:
+            raise ValidationError(e)
+
+
+class HoneypotField(Field):
+    widget = HoneypotWidget
+
+    def clean(self, value):
+        if self.initial in EMPTY_VALUES and value in EMPTY_VALUES or value == self.initial:
+            return value
+        raise ValidationError('Anti-spam field changed in value.')
+
+
+class USCurrencyField(CharField):
+    def clean(self, value):
+        if value in validators.EMPTY_VALUES:
+            return
+        if not re.match(CURRENCY_RE, value):
+            raise ValidationError('Enter a valid amount in U.S. dollars.')
+        value = value.replace('$', '').replace(',', '')
+        return super(USCurrencyField, self).clean(value)
+
+
+class CreditCardField(CharField):
+    def init(self, max_length=19, *args, **kwargs):
+        super(CreditCardField, self).__init__(max_length, *args, **kwargs)
+
+    def clean(self, value):
+        if value in validators.EMPTY_VALUES:
+            return
+        value = value.replace(' ', '').replace('-', '')
+        num = [int(digit) for digit in str(value)]
+        valid = not sum(num[::-2] + [sum(divmod(d * 2, 10)) for d in num[-2::-2]]) % 10
+        if not valid:
+            raise ValidationError('Enter a valid credit card number.')
+        return super(CreditCardField, self).clean(value)
+
+
+
+class MultiFileField(FileField):
+    widget = MultiFileInput
+    default_error_messages = {
+        'min_num': u"Ensure at least %(min_num)s files are uploaded (received %(num_files)s).",
+        'max_num': u"Ensure at most %(max_num)s files are uploaded (received %(num_files)s).",
+    }
+
+    def __init__(self, *args, **kwargs):
+        self.min_num = kwargs.pop('min_num', 0)
+        self.max_num = kwargs.pop('max_num', None)
+        super(MultiFileField, self).__init__(*args, **kwargs)
+
+    def to_python(self, data):
+        ret = []
+        for item in data:
+            ret.append(super(MultiFileField, self).to_python(item))
+        return ret
+
+    def validate(self, data):
+        super(MultiFileField, self).validate(data)
+        num_files = len(data)
+        if num_files < self.min_num:
+            raise ValidationError(self.error_messages['min_num'] % {'min_num': self.min_num, 'num_files': num_files})
+        elif self.max_num and  num_files > self.max_num:
+            raise ValidationError(self.error_messages['max_num'] % {'max_num': self.max_num, 'num_files': num_files})
+
+
+class MultiImageField(ImageField):
+    widget = MultiFileInput
+    default_error_messages = {
+        'min_num': u"Ensure at least %(min_num)s files are uploaded (received %(num_files)s).",
+        'max_num': u"Ensure at most %(max_num)s files are uploaded (received %(num_files)s).",
+    }
+
+    def __init__(self, *args, **kwargs):
+        self.min_num = kwargs.pop('min_num', 0)
+        self.max_num = kwargs.pop('max_num', None)
+        super(MultiImageField, self).__init__(*args, **kwargs)
+
+    def to_python(self, data):
+        ret = []
+        for item in data:
+            ret.append(super(MultiImageField, self).to_python(item))
+        return ret
+
+    def validate(self, data):
+        super(MultiImageField, self).validate(data)
+        num_files = len(data)
+        if num_files < self.min_num:
+            raise ValidationError(self.error_messages['min_num'] % {'min_num': self.min_num, 'num_files': num_files})
+        elif self.max_num and  num_files > self.max_num:
+            raise ValidationError(self.error_messages['max_num'] % {'max_num': self.max_num, 'num_files': num_files})

form_extensions/widgets.py

+from django import forms
+from itertools import chain
+from django.utils.html import conditional_escape
+from django.utils.encoding import force_unicode
+from django.utils.safestring import mark_safe
+from django.forms.util import flatatt
+from django.conf import settings
+
+
+EMPTY_VALUES = (None, '')
+
+
+class HoneypotWidget(forms.TextInput):
+    is_hidden = True
+    def __init__(self, attrs=None, html_comment=False, *args, **kwargs):
+        self.html_comment = html_comment
+        super(HoneypotWidget, self).__init__(attrs, *args, **kwargs)
+        if not self.attrs.has_key('class'):
+            self.attrs['style'] = 'display:none'
+    def render(self, *args, **kwargs):
+        value = super(HoneypotWidget, self).render(*args, **kwargs)
+        if self.html_comment:
+            value = '<!-- %s -->' % value
+        return value
+
+
+class DataList(forms.TextInput):
+    def __init__(self, attrs=None, choices=()):
+        super(DataList, self).__init__(attrs)
+        self.choices = list(choices)
+
+    def render(self, name, value, attrs={}, choices=()):
+        attrs['list'] = u'id_%s_list' % name
+        output = super(DataList, self).render(name, value, attrs)
+        output += u'\n' + self.render_options(name, choices)
+        return output
+
+    def render_options(self, name, choices):
+        output = []
+        output.append(u'<datalist id="id_%s_list" style="display:none">' % name)
+        output.append(u'<select name="%s_select"' % name)
+        for option in chain(self.choices, choices):
+            output.append(u'<option value="%s" />' % conditional_escape(force_unicode(option)))
+        output.append(u'</select>')
+        output.append(u'</datalist>')
+        return u'\n'.join(output)
+
+
+class MultiFileInput(forms.FileInput):
+    def render(self, name, value, attrs={}):
+        attrs['multiple'] = 'multiple'
+        return super(MultiFileInput, self).render(name, None, attrs=attrs)
+
+    def value_from_datadict(self, data, files, name):
+        if hasattr(files, 'getlist'):
+            return files.getlist(name)
+        else:
+            return [files.get(name)]
+
+
+class FileMultiInput(forms.FileInput):
+    def value_from_datadict(self, data, files, name):
+        if hasattr(files, 'getlist'):
+            return files.getlist(name)
+        else:
+            return [files.get(name)]
+
+
+class ImageInput(forms.FileInput):
+    def __init__(self, size, attrs=None):
+        self.size = size
+        super(ImageInput, self).__init__(attrs)
+
+    def render(self, name, value, attrs=None):
+        if value:
+            if hasattr(value, 'get_thumbnail') and value.storage.exists(value):
+                thumbnail = value.get_thumbnail({'size': self.size})
+                image_attrs = {
+                    'class': u'image-preview',
+                    'src': thumbnail.url,
+                    'height': thumbnail.height,
+                    'width': thumbnail.width,
+                    'style': 'max-width: %spx; max-height: %spx;' % self.size,
+                }
+            else:
+                image_attrs = {
+                    'class': u'image-preview',
+                    'src': settings.MEDIA_URL + str(value),
+                    'style': 'max-width: %spx; max-height: %spx;' % self.size,
+                }
+            image = u'<img%s />' % flatatt(image_attrs)
+        else:
+            image_attrs = {
+                'class': u'image-preview',
+                'src': settings.STATIC_URL + 'images/blank-image.jpg',
+                'style': 'max-width: %spx; max-height: %spx;' % self.size,
+            }
+            image = u'<img%s />' % flatatt(image_attrs)
+        return mark_safe(image) + super(ImageInput, self).render(name, value, attrs)
+Django>=1.1
+import os
+try:
+    from setuptools import setup, find_packages
+except ImportError:
+    from distutils.core import setup, find_packages
+
+
+def read_file(filename):
+    """Read a file into a string"""
+    path = os.path.abspath(os.path.dirname(__file__))
+    filepath = os.path.join(path, filename)
+    try:
+        return open(filepath).read()
+    except IOError:
+        return ''
+
+
+def get_readme():
+    """Return the README file contents. Supports text,rst, and markdown"""
+    for name in ('README', 'README.rst', 'README.md'):
+        if os.path.exists(name):
+            return read_file(name)
+    return ''
+
+setup(
+    name = 'Django Form Extensions',
+    version = __import__('form_extensions').get_version().replace(' ', '-'),
+    url = 'https://bitbucket.org/nextscreenlabs/django-form-extensions',
+    author = 'Jason Christa',
+    author_email = 'jason@zeitcode.com',
+    description = 'Useful fields and widgets for Django forms.',
+    long_description = get_readme(),
+    packages = find_packages(exclude=['tests']),
+    include_package_data = True,
+    install_requires = read_file('requirements.txt'),
+    license = read_file('LICENSE'),
+    classifiers = [
+        'Environment :: Web Environment',
+        'License :: OSI Approved :: BSD Liscense',
+        'Framework :: Django',
+        'Programming Language :: Python',
+    ],
+    zip_safe = False,
+)