diff options
Diffstat (limited to 'django/db/models/fields/__init__.py')
| -rw-r--r-- | django/db/models/fields/__init__.py | 205 |
1 files changed, 166 insertions, 39 deletions
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index fe317ac24f..136ce31b8b 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -10,6 +10,10 @@ from django.utils.itercompat import tee from django.utils.text import capfirst from django.utils.translation import gettext, gettext_lazy import datetime, os, time +try: + import decimal +except ImportError: + from django.utils import _decimal as decimal # for Python 2.3 class NOT_PROVIDED: pass @@ -67,7 +71,7 @@ class Field(object): def __init__(self, verbose_name=None, name=None, primary_key=False, maxlength=None, unique=False, blank=False, null=False, db_index=False, - core=False, rel=None, default=NOT_PROVIDED, editable=True, + core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, prepopulate_from=None, unique_for_date=None, unique_for_month=None, unique_for_year=None, validator_list=None, choices=None, radio_admin=None, help_text='', db_column=None): @@ -78,6 +82,7 @@ class Field(object): self.blank, self.null = blank, null self.core, self.rel, self.default = core, rel, default self.editable = editable + self.serialize = serialize self.validator_list = validator_list or [] self.prepopulate_from = prepopulate_from self.unique_for_date, self.unique_for_month = unique_for_date, unique_for_month @@ -164,7 +169,7 @@ class Field(object): def get_db_prep_lookup(self, lookup_type, value): "Returns field's value prepared for database lookup." - if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'year', 'month', 'day', 'search'): + if lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'): return [value] elif lookup_type in ('range', 'in'): return value @@ -178,7 +183,13 @@ class Field(object): return ["%%%s" % prep_for_like_query(value)] elif lookup_type == 'isnull': return [] - raise TypeError, "Field has invalid lookup: %s" % lookup_type + elif lookup_type == 'year': + try: + value = int(value) + except ValueError: + raise ValueError("The __year lookup type requires an integer argument") + return ['%s-01-01 00:00:00' % value, '%s-12-31 23:59:59.999999' % value] + raise TypeError("Field has invalid lookup: %s" % lookup_type) def has_default(self): "Returns a boolean of whether this field has a default value." @@ -334,10 +345,17 @@ class Field(object): return self._choices choices = property(_get_choices) - def formfield(self): + def formfield(self, form_class=forms.CharField, **kwargs): "Returns a django.newforms.Field instance for this database Field." - # TODO: This is just a temporary default during development. - return forms.CharField(required=not self.blank, label=capfirst(self.verbose_name)) + defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name), 'help_text': self.help_text} + if self.choices: + defaults['widget'] = forms.Select(choices=self.get_choices()) + defaults.update(kwargs) + return form_class(**defaults) + + def value_from_object(self, obj): + "Returns the value of this field in the given model instance." + return getattr(obj, self.attname) class AutoField(Field): empty_strings_allowed = False @@ -375,7 +393,7 @@ class AutoField(Field): super(AutoField, self).contribute_to_class(cls, name) cls._meta.has_auto_field = True - def formfield(self): + def formfield(self, **kwargs): return None class BooleanField(Field): @@ -392,8 +410,10 @@ class BooleanField(Field): def get_manipulator_field_objs(self): return [oldforms.CheckboxField] - def formfield(self): - return forms.BooleanField(required=not self.blank, label=capfirst(self.verbose_name)) + def formfield(self, **kwargs): + defaults = {'form_class': forms.BooleanField} + defaults.update(kwargs) + return super(BooleanField, self).formfield(**defaults) class CharField(Field): def get_manipulator_field_objs(self): @@ -409,8 +429,10 @@ class CharField(Field): raise validators.ValidationError, gettext_lazy("This field cannot be null.") return str(value) - def formfield(self): - return forms.CharField(max_length=self.maxlength, required=not self.blank, label=capfirst(self.verbose_name)) + def formfield(self, **kwargs): + defaults = {'max_length': self.maxlength} + defaults.update(kwargs) + return super(CharField, self).formfield(**defaults) # TODO: Maybe move this into contrib, because it's specialized. class CommaSeparatedIntegerField(CharField): @@ -428,6 +450,8 @@ class DateField(Field): Field.__init__(self, verbose_name, name, **kwargs) def to_python(self, value): + if value is None: + return value if isinstance(value, datetime.datetime): return value.date() if isinstance(value, datetime.date): @@ -479,15 +503,19 @@ class DateField(Field): def get_manipulator_field_objs(self): return [oldforms.DateField] - def flatten_data(self, follow, obj = None): + def flatten_data(self, follow, obj=None): val = self._get_val_from_obj(obj) return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')} - def formfield(self): - return forms.DateField(required=not self.blank, label=capfirst(self.verbose_name)) + def formfield(self, **kwargs): + defaults = {'form_class': forms.DateField} + defaults.update(kwargs) + return super(DateField, self).formfield(**defaults) class DateTimeField(DateField): def to_python(self, value): + if value is None: + return value if isinstance(value, datetime.datetime): return value if isinstance(value, datetime.date): @@ -544,8 +572,69 @@ class DateTimeField(DateField): return {date_field: (val is not None and val.strftime("%Y-%m-%d") or ''), time_field: (val is not None and val.strftime("%H:%M:%S") or '')} - def formfield(self): - return forms.DateTimeField(required=not self.blank, label=capfirst(self.verbose_name)) + def formfield(self, **kwargs): + defaults = {'form_class': forms.DateTimeField} + defaults.update(kwargs) + return super(DateTimeField, self).formfield(**defaults) + +class DecimalField(Field): + empty_strings_allowed = False + def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): + self.max_digits, self.decimal_places = max_digits, decimal_places + Field.__init__(self, verbose_name, name, **kwargs) + + def to_python(self, value): + if value is None: + return value + try: + return decimal.Decimal(value) + except decimal.InvalidOperation: + raise validators.ValidationError, gettext("This value must be a decimal number.") + + def _format(self, value): + if isinstance(value, basestring): + return value + else: + return self.format_number(value) + + def format_number(self, value): + """ + Formats a number into a string with the requisite number of digits and + decimal places. + """ + num_chars = self.max_digits + # Allow for a decimal point + if self.decimal_places > 0: + num_chars += 1 + # Allow for a minus sign + if value < 0: + num_chars += 1 + + return "%.*f" % (self.decimal_places, value) + + def get_db_prep_save(self, value): + if value is not None: + value = self._format(value) + return super(DecimalField, self).get_db_prep_save(value) + + def get_db_prep_lookup(self, lookup_type, value): + if lookup_type == 'range': + value = [self._format(v) for v in value] + else: + value = self._format(value) + return super(DecimalField, self).get_db_prep_lookup(lookup_type, value) + + def get_manipulator_field_objs(self): + return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)] + + def formfield(self, **kwargs): + defaults = { + 'max_digits': self.max_digits, + 'decimal_places': self.decimal_places, + 'form_class': forms.DecimalField, + } + defaults.update(kwargs) + return super(DecimalField, self).formfield(**defaults) class EmailField(CharField): def __init__(self, *args, **kwargs): @@ -561,8 +650,10 @@ class EmailField(CharField): def validate(self, field_data, all_data): validators.isValidEmail(field_data, all_data) - def formfield(self): - return forms.EmailField(required=not self.blank, label=capfirst(self.verbose_name)) + def formfield(self, **kwargs): + defaults = {'form_class': forms.EmailField} + defaults.update(kwargs) + return super(EmailField, self).formfield(**defaults) class FileField(Field): def __init__(self, verbose_name=None, name=None, upload_to='', **kwargs): @@ -610,7 +701,7 @@ class FileField(Field): setattr(cls, 'get_%s_filename' % self.name, curry(cls._get_FIELD_filename, field=self)) setattr(cls, 'get_%s_url' % self.name, curry(cls._get_FIELD_url, field=self)) setattr(cls, 'get_%s_size' % self.name, curry(cls._get_FIELD_size, field=self)) - setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents: instance._save_FIELD_file(self, filename, raw_contents)) + setattr(cls, 'save_%s_file' % self.name, lambda instance, filename, raw_contents, save=True: instance._save_FIELD_file(self, filename, raw_contents, save)) dispatcher.connect(self.delete_file, signal=signals.post_delete, sender=cls) def delete_file(self, instance): @@ -628,14 +719,14 @@ class FileField(Field): def get_manipulator_field_names(self, name_prefix): return [name_prefix + self.name + '_file', name_prefix + self.name] - def save_file(self, new_data, new_object, original_object, change, rel): + def save_file(self, new_data, new_object, original_object, change, rel, save=True): upload_field_name = self.get_manipulator_field_names('')[0] if new_data.get(upload_field_name, False): func = getattr(new_object, 'save_%s_file' % self.name) if rel: - func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"]) + func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"], save) else: - func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"]) + func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"], save) def get_directory_name(self): return os.path.normpath(datetime.datetime.now().strftime(self.upload_to)) @@ -655,12 +746,14 @@ class FilePathField(Field): class FloatField(Field): empty_strings_allowed = False - def __init__(self, verbose_name=None, name=None, max_digits=None, decimal_places=None, **kwargs): - self.max_digits, self.decimal_places = max_digits, decimal_places - Field.__init__(self, verbose_name, name, **kwargs) def get_manipulator_field_objs(self): - return [curry(oldforms.FloatField, max_digits=self.max_digits, decimal_places=self.decimal_places)] + return [oldforms.FloatField] + + def formfield(self, **kwargs): + defaults = {'form_class': forms.FloatField} + defaults.update(kwargs) + return super(FloatField, self).formfield(**defaults) class ImageField(FileField): def __init__(self, verbose_name=None, name=None, width_field=None, height_field=None, **kwargs): @@ -679,12 +772,12 @@ class ImageField(FileField): if not self.height_field: setattr(cls, 'get_%s_height' % self.name, curry(cls._get_FIELD_height, field=self)) - def save_file(self, new_data, new_object, original_object, change, rel): - FileField.save_file(self, new_data, new_object, original_object, change, rel) + def save_file(self, new_data, new_object, original_object, change, rel, save=True): + FileField.save_file(self, new_data, new_object, original_object, change, rel, save) # If the image has height and/or width field(s) and they haven't # changed, set the width and/or height field(s) back to their original # values. - if change and (self.width_field or self.height_field): + if change and (self.width_field or self.height_field) and save: if self.width_field: setattr(new_object, self.width_field, getattr(original_object, self.width_field)) if self.height_field: @@ -696,8 +789,10 @@ class IntegerField(Field): def get_manipulator_field_objs(self): return [oldforms.IntegerField] - def formfield(self): - return forms.IntegerField(required=not self.blank, label=capfirst(self.verbose_name)) + def formfield(self, **kwargs): + defaults = {'form_class': forms.IntegerField} + defaults.update(kwargs) + return super(IntegerField, self).formfield(**defaults) class IPAddressField(Field): def __init__(self, *args, **kwargs): @@ -715,6 +810,13 @@ class NullBooleanField(Field): kwargs['null'] = True Field.__init__(self, *args, **kwargs) + def to_python(self, value): + if value in (None, True, False): return value + if value in ('None'): return None + if value in ('t', 'True', '1'): return True + if value in ('f', 'False', '0'): return False + raise validators.ValidationError, gettext("This value must be either None, True or False.") + def get_manipulator_field_objs(self): return [oldforms.NullBooleanField] @@ -725,6 +827,12 @@ class PhoneNumberField(IntegerField): def validate(self, field_data, all_data): validators.isValidPhone(field_data, all_data) + def formfield(self, **kwargs): + from django.contrib.localflavor.us.forms import USPhoneNumberField + defaults = {'form_class': USPhoneNumberField} + defaults.update(kwargs) + return super(PhoneNumberField, self).formfield(**defaults) + class PositiveIntegerField(IntegerField): def get_manipulator_field_objs(self): return [oldforms.PositiveIntegerField] @@ -738,7 +846,7 @@ class SlugField(Field): kwargs['maxlength'] = kwargs.get('maxlength', 50) kwargs.setdefault('validator_list', []).append(validators.isSlug) # Set db_index=True unless it's been set manually. - if not kwargs.has_key('db_index'): + if 'db_index' not in kwargs: kwargs['db_index'] = True Field.__init__(self, *args, **kwargs) @@ -753,6 +861,11 @@ class TextField(Field): def get_manipulator_field_objs(self): return [oldforms.LargeTextField] + def formfield(self, **kwargs): + defaults = {'widget': forms.Textarea} + defaults.update(kwargs) + return super(TextField, self).formfield(**defaults) + class TimeField(Field): empty_strings_allowed = False def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): @@ -781,7 +894,7 @@ class TimeField(Field): if value is not None: # MySQL will throw a warning if microseconds are given, because it # doesn't support microseconds. - if settings.DATABASE_ENGINE == 'mysql': + if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): value = value.replace(microsecond=0) value = str(value) return Field.get_db_prep_save(self, value) @@ -793,26 +906,40 @@ class TimeField(Field): val = self._get_val_from_obj(obj) return {self.attname: (val is not None and val.strftime("%H:%M:%S") or '')} - def formfield(self): - return forms.TimeField(required=not self.blank, label=capfirst(self.verbose_name)) + def formfield(self, **kwargs): + defaults = {'form_class': forms.TimeField} + defaults.update(kwargs) + return super(TimeField, self).formfield(**defaults) -class URLField(Field): +class URLField(CharField): def __init__(self, verbose_name=None, name=None, verify_exists=True, **kwargs): + kwargs['maxlength'] = kwargs.get('maxlength', 200) if verify_exists: kwargs.setdefault('validator_list', []).append(validators.isExistingURL) self.verify_exists = verify_exists - Field.__init__(self, verbose_name, name, **kwargs) + CharField.__init__(self, verbose_name, name, **kwargs) def get_manipulator_field_objs(self): return [oldforms.URLField] - def formfield(self): - return forms.URLField(required=not self.blank, verify_exists=self.verify_exists, label=capfirst(self.verbose_name)) + def get_internal_type(self): + return "CharField" + + def formfield(self, **kwargs): + defaults = {'form_class': forms.URLField, 'verify_exists': self.verify_exists} + defaults.update(kwargs) + return super(URLField, self).formfield(**defaults) class USStateField(Field): def get_manipulator_field_objs(self): return [oldforms.USStateField] + def formfield(self, **kwargs): + from django.contrib.localflavor.us.forms import USStateSelect + defaults = {'widget': USStateSelect} + defaults.update(kwargs) + return super(USStateField, self).formfield(**defaults) + class XMLField(TextField): def __init__(self, verbose_name=None, name=None, schema_path=None, **kwargs): self.schema_path = schema_path |
