diff options
Diffstat (limited to 'django/db/models/fields/__init__.py')
-rw-r--r-- | django/db/models/fields/__init__.py | 244 |
1 files changed, 129 insertions, 115 deletions
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 78f75aea35..494c42cc54 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -22,7 +22,6 @@ from django.utils.itercompat import tee from django.utils.text import capfirst from django.utils.translation import ugettext_lazy, ugettext as _ from django.utils.encoding import smart_unicode, force_unicode, smart_str -from django.utils.maxlength import LegacyMaxlength from django.utils import datetime_safe class NOT_PROVIDED: @@ -62,10 +61,6 @@ def manipulator_validator_unique(f, opts, self, field_data, all_data): # getattr(obj, opts.pk.attname) class Field(object): - # Provide backwards compatibility for the maxlength attribute and - # argument for this class and all subclasses. - __metaclass__ = LegacyMaxlength - # Designates whether empty strings fundamentally are allowed at the # database level. empty_strings_allowed = True @@ -191,7 +186,8 @@ class Field(object): def set_attributes_from_name(self, name): self.name = name self.attname, self.column = self.get_attname_column() - self.verbose_name = self.verbose_name or (name and name.replace('_', ' ')) + if self.verbose_name is None and name: + self.verbose_name = name.replace('_', ' ') def contribute_to_class(self, cls, name): self.set_attributes_from_name(name) @@ -217,19 +213,30 @@ class Field(object): "Returns field's value just before saving." return getattr(model_instance, self.attname) + def get_db_prep_value(self, value): + """Returns field's value prepared for interacting with the database + backend. + + Used by the default implementations of ``get_db_prep_save``and + `get_db_prep_lookup``` + """ + return value + def get_db_prep_save(self, value): "Returns field's value prepared for saving into a database." - return value + return self.get_db_prep_value(value) def get_db_prep_lookup(self, lookup_type, value): "Returns field's value prepared for database lookup." if hasattr(value, 'as_sql'): sql, params = value.as_sql() return QueryWrapper(('(%s)' % sql), params) - if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'): + if lookup_type in ('regex', 'iregex', 'month', 'day', 'search'): return [value] + elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): + return [self.get_db_prep_value(value)] elif lookup_type in ('range', 'in'): - return value + return [self.get_db_prep_value(v) for v in value] elif lookup_type in ('contains', 'icontains'): return ["%%%s%%" % connection.ops.prep_for_like_query(value)] elif lookup_type == 'iexact': @@ -245,19 +252,12 @@ class Field(object): value = int(value) except ValueError: raise ValueError("The __year lookup type requires an integer argument") - if settings.DATABASE_ENGINE == 'sqlite3': - first = '%s-01-01' - second = '%s-12-31 23:59:59.999999' - elif not connection.features.date_field_supports_time_value and self.get_internal_type() == 'DateField': - first = '%s-01-01' - second = '%s-12-31' - elif not connection.features.supports_usecs: - first = '%s-01-01 00:00:00' - second = '%s-12-31 23:59:59.99' + + if self.get_internal_type() == 'DateField': + return connection.ops.year_lookup_bounds_for_date_field(value) else: - first = '%s-01-01 00:00:00' - second = '%s-12-31 23:59:59.999999' - return [first % value, second % value] + return connection.ops.year_lookup_bounds(value) + raise TypeError("Field has invalid lookup: %s" % lookup_type) def has_default(self): @@ -288,7 +288,7 @@ class Field(object): if self.choices: field_objs = [oldforms.SelectField] - params['choices'] = self.flatchoices + params['choices'] = self.get_flatchoices() else: field_objs = self.get_manipulator_field_objs() return (field_objs, params) @@ -362,7 +362,8 @@ class Field(object): return val def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH): - "Returns a list of tuples used as SelectField choices for this field." + """Returns choices with a default blank choices included, for use + as SelectField choices for this field.""" first_choice = include_blank and blank_choice or [] if self.choices: return first_choice + list(self.choices) @@ -376,6 +377,11 @@ class Field(object): def get_choices_default(self): return self.get_choices() + def get_flatchoices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH): + "Returns flattened choices with a default blank choice included." + first_choice = include_blank and blank_choice or [] + return first_choice + list(self.flatchoices) + def _get_val_from_obj(self, obj): if obj: return getattr(obj, self.attname) @@ -408,15 +414,16 @@ class Field(object): choices = property(_get_choices) def _get_flatchoices(self): + """Flattened version of choices tuple.""" flat = [] - for choice, value in self.get_choices_default(): + for choice, value in self.choices: if type(value) in (list, tuple): flat.extend(value) else: flat.append((choice,value)) return flat flatchoices = property(_get_flatchoices) - + def save_form_data(self, instance, data): setattr(instance, self.name, data) @@ -449,6 +456,11 @@ class AutoField(Field): except (TypeError, ValueError): raise validators.ValidationError, _("This value must be an integer.") + def get_db_prep_value(self, value): + if value is None: + return None + return int(value) + def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): if not rel: return [] # Don't add a FormField unless it's in a related context. @@ -477,6 +489,8 @@ class AutoField(Field): class BooleanField(Field): def __init__(self, *args, **kwargs): kwargs['blank'] = True + if 'default' not in kwargs and not kwargs.get('null'): + kwargs['default'] = False Field.__init__(self, *args, **kwargs) def get_internal_type(self): @@ -488,6 +502,11 @@ class BooleanField(Field): if value in ('f', 'False', '0'): return False raise validators.ValidationError, _("This value must be either True or False.") + def get_db_prep_value(self, value): + if value is None: + return None + return bool(value) + def get_manipulator_field_objs(self): return [oldforms.CheckboxField] @@ -549,15 +568,6 @@ class DateField(Field): except ValueError: raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.') - def get_db_prep_lookup(self, lookup_type, value): - if lookup_type in ('range', 'in'): - value = [smart_unicode(v) for v in value] - elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'): - value = datetime_safe.new_date(value).strftime('%Y-%m-%d') - else: - value = smart_unicode(value) - return Field.get_db_prep_lookup(self, lookup_type, value) - def pre_save(self, model_instance, add): if self.auto_now or (self.auto_now_add and add): value = datetime.datetime.now() @@ -581,16 +591,9 @@ class DateField(Field): else: return self.editable or self.auto_now or self.auto_now_add - def get_db_prep_save(self, value): - # Casts dates into string format for entry into database. - if value is not None: - try: - value = datetime_safe.new_date(value).strftime('%Y-%m-%d') - except AttributeError: - # If value is already a string it won't have a strftime method, - # so we'll just let it pass through. - pass - return Field.get_db_prep_save(self, value) + def get_db_prep_value(self, value): + # Casts dates into the format expected by the backend + return connection.ops.value_to_db_date(self.to_python(value)) def get_manipulator_field_objs(self): return [oldforms.DateField] @@ -619,33 +622,37 @@ class DateTimeField(DateField): return value if isinstance(value, datetime.date): return datetime.datetime(value.year, value.month, value.day) + + # Attempt to parse a datetime: + value = smart_str(value) + # split usecs, because they are not recognized by strptime. + if '.' in value: + try: + value, usecs = value.split('.') + usecs = int(usecs) + except ValueError: + raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.') + else: + usecs = 0 + kwargs = {'microsecond': usecs} try: # Seconds are optional, so try converting seconds first. - return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6]) + return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6], + **kwargs) + except ValueError: try: # Try without seconds. - return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5]) + return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5], + **kwargs) except ValueError: # Try without hour/minutes/seconds. try: - return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3]) + return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3], + **kwargs) except ValueError: - raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.') + raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.') - def get_db_prep_save(self, value): - # Casts dates into string format for entry into database. - if value is not None: - # MySQL will throw a warning if microseconds are given, because it - # doesn't support microseconds. - if not connection.features.supports_usecs and hasattr(value, 'microsecond'): - value = value.replace(microsecond=0) - value = smart_unicode(value) - return Field.get_db_prep_save(self, value) - - def get_db_prep_lookup(self, lookup_type, value): - if lookup_type in ('range', 'in'): - value = [smart_unicode(v) for v in value] - else: - value = smart_unicode(value) - return Field.get_db_prep_lookup(self, lookup_type, value) + def get_db_prep_value(self, value): + # Casts dates into the format expected by the backend + return connection.ops.value_to_db_datetime(self.to_python(value)) def get_manipulator_field_objs(self): return [oldforms.DateField, oldforms.TimeField] @@ -710,26 +717,18 @@ class DecimalField(Field): 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 u"%.*f" % (self.decimal_places, value) - - def get_db_prep_save(self, value): - value = self._format(value) - return super(DecimalField, self).get_db_prep_save(value) + # Method moved to django.db.backends.util. + # + # It is preserved because it is used by the oracle backend + # (django.db.backends.oracle.query), and also for + # backwards-compatibility with any external code which may have used + # this method. + from django.db.backends import util + return util.format_number(value, self.max_digits, self.decimal_places) - def get_db_prep_lookup(self, lookup_type, value): - if lookup_type in ('range', 'in'): - 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_db_prep_value(self, value): + return connection.ops.value_to_db_decimal(self.to_python(value), + self.max_digits, self.decimal_places) def get_manipulator_field_objs(self): return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)] @@ -768,7 +767,7 @@ class FileField(Field): def get_internal_type(self): return "FileField" - def get_db_prep_save(self, value): + def get_db_prep_value(self, value): "Returns field's value prepared for saving into a database." # Need to convert UploadedFile objects provided via a form to unicode for database insertion if hasattr(value, 'name'): @@ -909,6 +908,11 @@ class FilePathField(Field): class FloatField(Field): empty_strings_allowed = False + def get_db_prep_value(self, value): + if value is None: + return None + return float(value) + def get_manipulator_field_objs(self): return [oldforms.FloatField] @@ -956,6 +960,11 @@ class ImageField(FileField): class IntegerField(Field): empty_strings_allowed = False + def get_db_prep_value(self, value): + if value is None: + return None + return int(value) + def get_manipulator_field_objs(self): return [oldforms.IntegerField] @@ -1003,6 +1012,11 @@ class NullBooleanField(Field): if value in ('f', 'False', '0'): return False raise validators.ValidationError, _("This value must be either None, True or False.") + def get_db_prep_value(self, value): + if value is None: + return None + return bool(value) + def get_manipulator_field_objs(self): return [oldforms.NullBooleanField] @@ -1015,7 +1029,7 @@ class NullBooleanField(Field): defaults.update(kwargs) return super(NullBooleanField, self).formfield(**defaults) -class PhoneNumberField(IntegerField): +class PhoneNumberField(Field): def get_manipulator_field_objs(self): return [oldforms.PhoneNumberField] @@ -1097,20 +1111,34 @@ class TimeField(Field): def get_internal_type(self): return "TimeField" - def get_db_prep_lookup(self, lookup_type, value): - if connection.features.time_field_needs_date: - # Oracle requires a date in order to parse. - def prep(value): - if isinstance(value, datetime.time): - value = datetime.datetime.combine(datetime.date(1900, 1, 1), value) - return smart_unicode(value) - else: - prep = smart_unicode - if lookup_type in ('range', 'in'): - value = [prep(v) for v in value] + def to_python(self, value): + if value is None: + return None + if isinstance(value, datetime.time): + return value + + # Attempt to parse a datetime: + value = smart_str(value) + # split usecs, because they are not recognized by strptime. + if '.' in value: + try: + value, usecs = value.split('.') + usecs = int(usecs) + except ValueError: + raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.') else: - value = prep(value) - return Field.get_db_prep_lookup(self, lookup_type, value) + usecs = 0 + kwargs = {'microsecond': usecs} + + try: # Seconds are optional, so try converting seconds first. + return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6], + **kwargs) + except ValueError: + try: # Try without seconds. + return datetime.time(*time.strptime(value, '%H:%M')[3:5], + **kwargs) + except ValueError: + raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.') def pre_save(self, model_instance, add): if self.auto_now or (self.auto_now_add and add): @@ -1120,23 +1148,9 @@ class TimeField(Field): else: return super(TimeField, self).pre_save(model_instance, add) - def get_db_prep_save(self, value): - # Casts dates into string format for entry into database. - if value is not None: - # MySQL will throw a warning if microseconds are given, because it - # doesn't support microseconds. - if not connection.features.supports_usecs and hasattr(value, 'microsecond'): - value = value.replace(microsecond=0) - if connection.features.time_field_needs_date: - # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field. - if isinstance(value, datetime.time): - value = datetime.datetime(1900, 1, 1, value.hour, value.minute, - value.second, value.microsecond) - elif isinstance(value, basestring): - value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6])) - else: - value = smart_unicode(value) - return Field.get_db_prep_save(self, value) + def get_db_prep_value(self, value): + # Casts times into the format expected by the backend + return connection.ops.value_to_db_time(self.to_python(value)) def get_manipulator_field_objs(self): return [oldforms.TimeField] |