Ticket #7560: 7560_get_db_prep_refactor.diff

File 7560_get_db_prep_refactor.diff, 30.7 KB (added by Leo Soto M., 16 years ago)
  • django/db/backends/__init__.py

    diff -r 403793bcdc1d -r 0fe9bb68aded django/db/backends/__init__.py
    a b  
    44except ImportError:
    55    # Import copy of _thread_local.py from Python 2.4
    66    from django.utils._threading_local import local
     7
     8from django.db.backends import util
    79
    810class BaseDatabaseWrapper(local):
    911    """
     
    3638        return cursor
    3739
    3840    def make_debug_cursor(self, cursor):
    39         from django.db.backends import util
    4041        return util.CursorDebugWrapper(cursor, self)
    4142
    4243class BaseDatabaseFeatures(object):
    4344    allows_group_by_ordinal = True
    4445    inline_fk_references = True
    45     needs_datetime_string_cast = True
     46    needs_datetime_string_cast = True # uses
     47                                      # django.db.backend.utils.typecast_timetamp
     48                                      # on returned values from dates()?
    4649    supports_constraints = True
    4750    supports_tablespaces = False
    4851    uses_case_insensitive_names = False
    4952    uses_custom_query_class = False
    5053    empty_fetchmany_value = []
    5154    update_can_self_select = True
    52     supports_usecs = True
    53     time_field_needs_date = False
    5455    interprets_empty_strings_as_nulls = False
    55     date_field_supports_time_value = True
    5656    can_use_chunked_reads = True
    5757
    5858class BaseDatabaseOperations(object):
     
    263263        """Prepares a value for use in a LIKE query."""
    264264        from django.utils.encoding import smart_unicode
    265265        return smart_unicode(x).replace("\\", "\\\\").replace("%", "\%").replace("_", "\_")
     266
     267    def value_to_db_date(self, value):
     268        """
     269        Transform a date value to an object compatible with what is expected
     270        by the backend driver for date columns.
     271        """
     272        if value is None:
     273            return None
     274        return value.strftime('%Y-%m-%d')
     275
     276    def value_to_db_datetime(self, value):
     277        """
     278        Transform a datetime value to an object compatible with what is expected
     279        by the backend driver for date columns.
     280        """
     281        if value is None:
     282            return None
     283        return unicode(value) # XXX: Why not just str(value)?
     284
     285    def value_to_db_time(self, value):
     286        """
     287        Transform a datetime value to an object compatible with what is expected
     288        by the backend driver for date columns.
     289        """
     290        if value is None:
     291            return None
     292        return unicode(value) # XXX: Why not just str(value)?
     293
     294    def value_to_db_decimal(self, value, max_digits, decimal_places):
     295        """
     296        Transform a decimal.Decimal value to an object compatible with what is
     297        expected by the backend driver for decimal (numeric) columns.
     298        """
     299        if value is None:
     300            return None
     301        return util.format_number(value, max_digits, decimal_places)
     302
     303    def year_lookup_bounds(self, value):
     304        """
     305        Returns a two-elements list with the lower and upper bound to be used
     306        with a BETWEEN operator to query a field value using a year lookup
     307
     308        `value` is an int, containing the looked-up year.
     309        """
     310        first = '%s-01-01 00:00:00'
     311        second = '%s-12-31 23:59:59.999999'
     312        return [first % value, second % value]
     313
     314    def year_lookup_bounds_for_date_field(self, value):
     315        """
     316        Returns a two-elements list with the lower and upper bound to be used
     317        with a BETWEEN operator to query a DateField value using a year lookup
     318
     319        `value` is an int, containing the looked-up year.
     320
     321        By default, it just calls `self.year_lookup_bounds`. Some backends need
     322        this hook because on their DB date fields can't be compared to values
     323        which include a time part.
     324        """
     325        return self.year_lookup_bounds(value)
     326
  • django/db/backends/mysql/base.py

    diff -r 403793bcdc1d -r 0fe9bb68aded django/db/backends/mysql/base.py
    a b  
    6363    inline_fk_references = False
    6464    empty_fetchmany_value = ()
    6565    update_can_self_select = False
    66     supports_usecs = False
    6766
    6867class DatabaseOperations(BaseDatabaseOperations):
    6968    def date_extract_sql(self, lookup_type, field_name):
     
    123122            return sql
    124123        else:
    125124            return []
     125
     126    def value_to_db_datetime(self, value):
     127        # MySQL doesn't support microseconds
     128        if value is None:
     129            return None
     130        # XXX: Why not just str(value)?
     131        return unicode(value.replace(microsecond=0))
     132
     133    def value_to_db_time(self, value):
     134        # MySQL doesn't support microseconds
     135        if value is None:
     136            return None
     137        # XXX: Why not just str(value)?
     138        return unicode(value.replace(microsecond=0))
     139
     140    def year_lookup_bounds(self, value):
     141        # Again, no microseconds
     142        first = '%s-01-01 00:00:00'
     143        second = '%s-12-31 23:59:59.99'
     144        return [first % value, second % value]
    126145
    127146class DatabaseWrapper(BaseDatabaseWrapper):
    128147    features = DatabaseFeatures()
  • django/db/backends/mysql_old/base.py

    diff -r 403793bcdc1d -r 0fe9bb68aded django/db/backends/mysql_old/base.py
    a b  
    6767    inline_fk_references = False
    6868    empty_fetchmany_value = ()
    6969    update_can_self_select = False
    70     supports_usecs = False
    7170
    7271class DatabaseOperations(BaseDatabaseOperations):
    7372    def date_extract_sql(self, lookup_type, field_name):
     
    127126            return sql
    128127        else:
    129128            return []
     129
     130    def value_to_db_datetime(self, value):
     131        # MySQL doesn't support microseconds
     132        if value is None:
     133            return None
     134        # XXX: Why not just str(value)?
     135        return unicode(value.replace(microsecond=0))
     136
     137    def value_to_db_time(self, value):
     138        # MySQL doesn't support microseconds
     139        if value is None:
     140            return None
     141        # XXX: Why not just str(value)?
     142        return unicode(value.replace(microsecond=0))
     143
     144
     145    def year_lookup_bounds(self, value):
     146        # Again, no microseconds
     147        first = '%s-01-01 00:00:00'
     148        second = '%s-12-31 23:59:59.99'
     149        return [first % value, second % value]
     150
    130151
    131152class DatabaseWrapper(BaseDatabaseWrapper):
    132153    features = DatabaseFeatures()
  • django/db/backends/oracle/base.py

    diff -r 403793bcdc1d -r 0fe9bb68aded django/db/backends/oracle/base.py
    a b  
    55"""
    66
    77import os
     8import datetime
     9import time
    810
    911from django.db.backends import BaseDatabaseWrapper, BaseDatabaseFeatures, BaseDatabaseOperations, util
    1012from django.db.backends.oracle import query
     
    2931    supports_tablespaces = True
    3032    uses_case_insensitive_names = True
    3133    uses_custom_query_class = True
    32     time_field_needs_date = True
    3334    interprets_empty_strings_as_nulls = True
    34     date_field_supports_time_value = False
    3535
    3636class DatabaseOperations(BaseDatabaseOperations):
    3737    def autoinc_sql(self, table, column):
     
    180180
    181181    def tablespace_sql(self, tablespace, inline=False):
    182182        return "%sTABLESPACE %s" % ((inline and "USING INDEX " or ""), self.quote_name(tablespace))
     183
     184    def value_to_db_time(self, value):
     185        if value is None:
     186            return None
     187        if isinstance(value, basestring):
     188            return datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
     189        return datetime.datetime(1900, 1, 1, value.hour, value.minute,
     190                                 value.second, value.microsecond)
     191
     192    def year_lookup_bounds_for_date_field(self, value):
     193        first = '%s-01-01'
     194        second = '%s-12-31'
     195        return [first % value, second % value]
     196
     197
    183198
    184199class DatabaseWrapper(BaseDatabaseWrapper):
    185200    features = DatabaseFeatures()
  • django/db/backends/sqlite3/base.py

    diff -r 403793bcdc1d -r 0fe9bb68aded django/db/backends/sqlite3/base.py
    a b  
    8484        # sql_flush() implementations). Just return SQL at this point
    8585        return sql
    8686
     87    def year_lookup_bounds(self, value):
     88        first = '%s-01-01'
     89        second = '%s-12-31 23:59:59.999999'
     90        return [first % value, second % value]
     91
     92
    8793class DatabaseWrapper(BaseDatabaseWrapper):
    8894    features = DatabaseFeatures()
    8995    ops = DatabaseOperations()
     
    156162        dt = util.typecast_timestamp(dt)
    157163    except (ValueError, TypeError):
    158164        return None
    159     return str(getattr(dt, lookup_type))
     165    return getattr(dt, lookup_type)
    160166
    161167def _sqlite_date_trunc(lookup_type, dt):
    162168    try:
  • django/db/backends/util.py

    diff -r 403793bcdc1d -r 0fe9bb68aded django/db/backends/util.py
    a b  
    117117    hash = md5.md5(name).hexdigest()[:4]
    118118
    119119    return '%s%s' % (name[:length-4], hash)
     120
     121def format_number(value, max_digits, decimal_places):
     122    """
     123    Formats a number into a string with the requisite number of digits and
     124    decimal places.
     125    """
     126    return u"%.*f" % (decimal_places, value)
  • django/db/models/fields/__init__.py

    diff -r 403793bcdc1d -r 0fe9bb68aded django/db/models/fields/__init__.py
    a b  
    224224        "Returns field's value just before saving."
    225225        return getattr(model_instance, self.attname)
    226226
     227    def get_db_prep_value(self, value):
     228        """Returns field's value prepared for interacting with the database
     229        backend.
     230
     231        Used by the default implementations of ``get_db_prep_save``and
     232        `get_db_prep_lookup```
     233        """
     234        return value
     235
    227236    def get_db_prep_save(self, value):
    228237        "Returns field's value prepared for saving into a database."
    229         return value
     238        return self.get_db_prep_value(value)
    230239
    231240    def get_db_prep_lookup(self, lookup_type, value):
    232241        "Returns field's value prepared for database lookup."
    233242        if hasattr(value, 'as_sql'):
    234243            sql, params = value.as_sql()
    235244            return QueryWrapper(('(%s)' % sql), params)
    236         if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'):
     245        if lookup_type in ('regex', 'iregex', 'month', 'day', 'search'):
    237246            return [value]
     247        elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'):
     248            return [self.get_db_prep_value(value)]
    238249        elif lookup_type in ('range', 'in'):
    239             return value
     250            return [self.get_db_prep_value(v) for v in value]
    240251        elif lookup_type in ('contains', 'icontains'):
    241252            return ["%%%s%%" % connection.ops.prep_for_like_query(value)]
    242253        elif lookup_type == 'iexact':
     
    252263                value = int(value)
    253264            except ValueError:
    254265                raise ValueError("The __year lookup type requires an integer argument")
    255             if settings.DATABASE_ENGINE == 'sqlite3':
    256                 first = '%s-01-01'
    257                 second = '%s-12-31 23:59:59.999999'
    258             elif not connection.features.date_field_supports_time_value and self.get_internal_type() == 'DateField':
    259                 first = '%s-01-01'
    260                 second = '%s-12-31'
    261             elif not connection.features.supports_usecs:
    262                 first = '%s-01-01 00:00:00'
    263                 second = '%s-12-31 23:59:59.99'
     266
     267            if self.get_internal_type() == 'DateField':
     268                return connection.ops.year_lookup_bounds_for_date_field(value)
    264269            else:
    265                 first = '%s-01-01 00:00:00'
    266                 second = '%s-12-31 23:59:59.999999'
    267             return [first % value, second % value]
     270                return connection.ops.year_lookup_bounds(value)
     271
    268272        raise TypeError("Field has invalid lookup: %s" % lookup_type)
    269273
    270274    def has_default(self):
     
    453457        except (TypeError, ValueError):
    454458            raise validators.ValidationError, _("This value must be an integer.")
    455459
     460    def get_db_prep_value(self, value):
     461        if value is None:
     462            return None
     463        return int(value)
     464
    456465    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True):
    457466        if not rel:
    458467            return [] # Don't add a FormField unless it's in a related context.
     
    491500        if value in ('t', 'True', '1'): return True
    492501        if value in ('f', 'False', '0'): return False
    493502        raise validators.ValidationError, _("This value must be either True or False.")
     503
     504    def get_db_prep_value(self, value):
     505        if value is None:
     506            return None
     507        return bool(value)
    494508
    495509    def get_manipulator_field_objs(self):
    496510        return [oldforms.CheckboxField]
     
    553567        except ValueError:
    554568            raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.')
    555569
    556     def get_db_prep_lookup(self, lookup_type, value):
    557         if lookup_type in ('range', 'in'):
    558             value = [smart_unicode(v) for v in value]
    559         elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'):
    560             value = value.strftime('%Y-%m-%d')
    561         else:
    562             value = smart_unicode(value)
    563         return Field.get_db_prep_lookup(self, lookup_type, value)
    564 
    565570    def pre_save(self, model_instance, add):
    566571        if self.auto_now or (self.auto_now_add and add):
    567572            value = datetime.datetime.now()
     
    585590        else:
    586591            return self.editable or self.auto_now or self.auto_now_add
    587592
    588     def get_db_prep_save(self, value):
    589         # Casts dates into string format for entry into database.
    590         if value is not None:
    591             try:
    592                 value = value.strftime('%Y-%m-%d')
    593             except AttributeError:
    594                 # If value is already a string it won't have a strftime method,
    595                 # so we'll just let it pass through.
    596                 pass
    597         return Field.get_db_prep_save(self, value)
     593    def get_db_prep_value(self, value):
     594        # Casts dates into the format expected by the backend
     595        return connection.ops.value_to_db_date(self.to_python(value))
    598596
    599597    def get_manipulator_field_objs(self):
    600598        return [oldforms.DateField]
     
    619617            return value
    620618        if isinstance(value, datetime.date):
    621619            return datetime.datetime(value.year, value.month, value.day)
     620
     621        # Attempt to parse a datetime:
     622        value = smart_str(value)
     623        # split usecs, because they are not recognized by strptime.
     624        if '.' in value:
     625            try:
     626                value, usecs = value.split('.')
     627                usecs = int(usecs)
     628            except ValueError:
     629                raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.')
     630        else:
     631            usecs = 0
     632        kwargs = {'microsecond': usecs}
    622633        try: # Seconds are optional, so try converting seconds first.
    623             return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6])
     634            return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6],
     635                                     **kwargs)
     636
    624637        except ValueError:
    625638            try: # Try without seconds.
    626                 return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5])
     639                return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5],
     640                                         **kwargs)
    627641            except ValueError: # Try without hour/minutes/seconds.
    628642                try:
    629                     return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3])
     643                    return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3],
     644                                             **kwargs)
    630645                except ValueError:
    631                     raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.')
     646                    raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.')
    632647
    633     def get_db_prep_save(self, value):
    634         # Casts dates into string format for entry into database.
    635         if value is not None:
    636             # MySQL will throw a warning if microseconds are given, because it
    637             # doesn't support microseconds.
    638             if not connection.features.supports_usecs and hasattr(value, 'microsecond'):
    639                 value = value.replace(microsecond=0)
    640             value = smart_unicode(value)
    641         return Field.get_db_prep_save(self, value)
    642 
    643     def get_db_prep_lookup(self, lookup_type, value):
    644         if lookup_type in ('range', 'in'):
    645             value = [smart_unicode(v) for v in value]
    646         else:
    647             value = smart_unicode(value)
    648         return Field.get_db_prep_lookup(self, lookup_type, value)
     648    def get_db_prep_value(self, value):
     649        # Casts dates into the format expected by the backend
     650        return connection.ops.value_to_db_datetime(self.to_python(value))
    649651
    650652    def get_manipulator_field_objs(self):
    651653        return [oldforms.DateField, oldforms.TimeField]
     
    705707        Formats a number into a string with the requisite number of digits and
    706708        decimal places.
    707709        """
    708         num_chars = self.max_digits
    709         # Allow for a decimal point
    710         if self.decimal_places > 0:
    711             num_chars += 1
    712         # Allow for a minus sign
    713         if value < 0:
    714             num_chars += 1
     710        # Method moved to django.db.backends.util.
     711        #
     712        # It is preserved because it is used by the oracle backend
     713        # (django.db.backends.oracle.query), and also for
     714        # backwards-compatibility with any external code which may have used
     715        # this method.
     716        from django.db.backends import util
     717        return util.format_number(value, self.max_digits, self.decimal_places)
    715718
    716         return u"%.*f" % (self.decimal_places, value)
    717 
    718     def get_db_prep_save(self, value):
    719         value = self._format(value)
    720         return super(DecimalField, self).get_db_prep_save(value)
    721 
    722     def get_db_prep_lookup(self, lookup_type, value):
    723         if lookup_type in ('range', 'in'):
    724             value = [self._format(v) for v in value]
    725         else:
    726             value = self._format(value)
    727         return super(DecimalField, self).get_db_prep_lookup(lookup_type, value)
     719    def get_db_prep_value(self, value):
     720        return connection.ops.value_to_db_decimal(value, self.max_digits,
     721                                                  self.decimal_places)
    728722
    729723    def get_manipulator_field_objs(self):
    730724        return [curry(oldforms.DecimalField, max_digits=self.max_digits, decimal_places=self.decimal_places)]
     
    763757    def get_internal_type(self):
    764758        return "FileField"
    765759
    766     def get_db_prep_save(self, value):
     760    def get_db_prep_value(self, value):
    767761        "Returns field's value prepared for saving into a database."
    768762        # Need to convert UploadedFile objects provided via a form to unicode for database insertion
    769763        if hasattr(value, 'name'):
     
    904898class FloatField(Field):
    905899    empty_strings_allowed = False
    906900
     901    def get_db_prep_value(self, value):
     902        if value is None:
     903            return None
     904        return float(value)
     905
    907906    def get_manipulator_field_objs(self):
    908907        return [oldforms.FloatField]
    909908
     
    954953
    955954class IntegerField(Field):
    956955    empty_strings_allowed = False
     956    def get_db_prep_value(self, value):
     957        if value is None:
     958            return None
     959        return int(value)
     960
    957961    def get_manipulator_field_objs(self):
    958962        return [oldforms.IntegerField]
    959963
     
    10011005        if value in ('f', 'False', '0'): return False
    10021006        raise validators.ValidationError, _("This value must be either None, True or False.")
    10031007
     1008    def get_db_prep_value(self, value):
     1009        if value is None:
     1010            return None
     1011        return bool(value)
     1012
    10041013    def get_manipulator_field_objs(self):
    10051014        return [oldforms.NullBooleanField]
    10061015
     
    10091018        defaults.update(kwargs)
    10101019        return super(NullBooleanField, self).formfield(**defaults)
    10111020
    1012 class PhoneNumberField(IntegerField):
     1021class PhoneNumberField(Field):
    10131022    def get_manipulator_field_objs(self):
    10141023        return [oldforms.PhoneNumberField]
    10151024
     
    10911100    def get_internal_type(self):
    10921101        return "TimeField"
    10931102
    1094     def get_db_prep_lookup(self, lookup_type, value):
    1095         if connection.features.time_field_needs_date:
    1096             # Oracle requires a date in order to parse.
    1097             def prep(value):
    1098                 if isinstance(value, datetime.time):
    1099                     value = datetime.datetime.combine(datetime.date(1900, 1, 1), value)
    1100                 return smart_unicode(value)
     1103    def to_python(self, value):
     1104        if value is None:
     1105            return None
     1106        if isinstance(value, datetime.time):
     1107            return value
     1108
     1109        # Attempt to parse a datetime:
     1110        value = smart_str(value)
     1111        # split usecs, because they are not recognized by strptime.
     1112        if '.' in value:
     1113            try:
     1114                value, usecs = value.split('.')
     1115                usecs = int(usecs)
     1116            except ValueError:
     1117                raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')
    11011118        else:
    1102             prep = smart_unicode
    1103         if lookup_type in ('range', 'in'):
    1104             value = [prep(v) for v in value]
    1105         else:
    1106             value = prep(value)
    1107         return Field.get_db_prep_lookup(self, lookup_type, value)
     1119            usecs = 0
     1120        kwargs = {'microsecond': usecs}
     1121
     1122        try: # Seconds are optional, so try converting seconds first.
     1123            return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6],
     1124                                 **kwargs)
     1125        except ValueError:
     1126            try: # Try without seconds.
     1127                return datetime.datetime(*time.strptime(value, '%H:%M')[:5],
     1128                                         **kwargs)
     1129            except ValueError:
     1130                raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.')
    11081131
    11091132    def pre_save(self, model_instance, add):
    11101133        if self.auto_now or (self.auto_now_add and add):
     
    11141137        else:
    11151138            return super(TimeField, self).pre_save(model_instance, add)
    11161139
    1117     def get_db_prep_save(self, value):
    1118         # Casts dates into string format for entry into database.
    1119         if value is not None:
    1120             # MySQL will throw a warning if microseconds are given, because it
    1121             # doesn't support microseconds.
    1122             if not connection.features.supports_usecs and hasattr(value, 'microsecond'):
    1123                 value = value.replace(microsecond=0)
    1124             if connection.features.time_field_needs_date:
    1125                 # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field.
    1126                 if isinstance(value, datetime.time):
    1127                     value = datetime.datetime(1900, 1, 1, value.hour, value.minute,
    1128                                               value.second, value.microsecond)
    1129                 elif isinstance(value, basestring):
    1130                     value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6]))
    1131             else:
    1132                 value = smart_unicode(value)
    1133         return Field.get_db_prep_save(self, value)
     1140    def get_db_prep_value(self, value):
     1141        # Casts times into the format expected by the backend
     1142        return connection.ops.value_to_db_time(self.to_python(value))
    11341143
    11351144    def get_manipulator_field_objs(self):
    11361145        return [oldforms.TimeField]
  • docs/custom_model_fields.txt

    diff -r 403793bcdc1d -r 0fe9bb68aded docs/custom_model_fields.txt
    a b  
    386386called when it is created, you should be using `The SubfieldBase metaclass`_
    387387mentioned earlier. Otherwise ``to_python()`` won't be called automatically.
    388388
    389 ``get_db_prep_save(self, value)``
    390 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     389``get_db_prep_value(self, value)``
     390~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    391391
    392392This is the reverse of ``to_python()`` when working with the database backends
    393393(as opposed to serialization). The ``value`` parameter is the current value of
     
    400400    class HandField(models.Field):
    401401        # ...
    402402
    403         def get_db_prep_save(self, value):
     403        def get_db_prep_value(self, value):
    404404            return ''.join([''.join(l) for l in (value.north,
    405405                    value.east, value.south, value.west)])
     406
     407``get_db_prep_save(self, value)``
     408~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     409
     410Same as the above, but called when the Field value must be *saved* to the
     411database. As the default implementation just calls ``get_db_prep_value``, you
     412shouldn't need to implement this method unless your custom field need a special
     413conversion when being saved that is not the same as the used for normal query
     414parameters (which is implemented by ``get_db_prep_value``).
     415
    406416
    407417``pre_save(self, model_instance, add)``
    408418~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     
    441451and pass the rest of the ``get_db_prep_lookup()`` method of the parent class.
    442452
    443453If you needed to implement ``get_db_prep_save()``, you will usually need to
    444 implement ``get_db_prep_lookup()``. The usual reason is because of the
    445 ``range``  and ``in`` lookups. In these case, you will passed a list of
    446 objects (presumably of the right type) and will need to convert them to a list
    447 of things of the right type for passing to the database. Sometimes you can
    448 reuse ``get_db_prep_save()``, or at least factor out some common pieces from
    449 both methods into a help function.
     454implement ``get_db_prep_lookup()``. If you don't, ``get_db_prep_value`` will be
     455called by the default implementation, to manage ``exact``, ``gt``, ``gte``,
     456``lt``, ``lte``, ``in`` and ``range`` lookups.
    450457
    451 For example::
     458You may also want to implement this method to limit the lookup types that could
     459be used with your custom field type.
     460
     461Note that, for ``range`` and ``in`` lookups, ``get_db_prep_lookup`` will receive
     462a list of objects (presumably of the right type) and will need to convert them
     463to a list of things of the right type for passing to the database. Most of the
     464time, you can reuse ``get_db_prep_value()``, or at least factor out some common
     465pieces.
     466
     467For example, the following code implements ``get_db_prep_lookup`` to limit the
     468accepted lookup types to ``exact`` and ``in``::
    452469
    453470    class HandField(models.Field):
    454471        # ...
     
    456473        def get_db_prep_lookup(self, lookup_type, value):
    457474            # We only handle 'exact' and 'in'. All others are errors.
    458475            if lookup_type == 'exact':
    459                 return self.get_db_prep_save(value)
     476                return self.get_db_prep_value(value)
    460477            elif lookup_type == 'in':
    461                 return [self.get_db_prep_save(v) for v in value]
     478                return [self.get_db_prep_value(v) for v in value]
    462479            else:
    463480                raise TypeError('Lookup type %r not supported.' % lookup_type)
    464481
     
    558575
    559576        def flatten_data(self, follow, obj=None):
    560577            value = self._get_val_from_obj(obj)
    561             return {self.attname: self.get_db_prep_save(value)}
     578            return {self.attname: self.get_db_prep_value(value)}
    562579
    563580Some general advice
    564581--------------------
  • tests/modeltests/custom_methods/models.py

    diff -r 403793bcdc1d -r 0fe9bb68aded tests/modeltests/custom_methods/models.py
    a b  
    3131            SELECT id, headline, pub_date
    3232            FROM custom_methods_article
    3333            WHERE pub_date = %s
    34                 AND id != %s""", [str(self.pub_date), self.id])
     34                AND id != %s""", [connection.ops.value_to_db_date(self.pub_date),
     35                                  self.id])
    3536        # The asterisk in "(*row)" tells Python to expand the list into
    3637        # positional arguments to Article().
    3738        return [self.__class__(*row) for row in cursor.fetchall()]
  • tests/regressiontests/model_fields/tests.py

    diff -r 403793bcdc1d -r 0fe9bb68aded tests/regressiontests/model_fields/tests.py
    a b  
    2020>>> x = f.to_python(2)
    2121>>> y = f.to_python('2.6')
    2222
    23 >>> f.get_db_prep_save(x)
     23>>> f._format(x)
    2424u'2.0'
    25 >>> f.get_db_prep_save(y)
     25>>> f._format(y)
    2626u'2.6'
    27 >>> f.get_db_prep_save(None)
    28 >>> f.get_db_prep_lookup('exact', x)
    29 [u'2.0']
    30 >>> f.get_db_prep_lookup('exact', y)
    31 [u'2.6']
     27>>> f._format(None)
    3228>>> f.get_db_prep_lookup('exact', None)
    3329[None]
    3430
     31# DateTimeField and TimeField to_python should support usecs:
     32>>> f = DateTimeField()
     33>>> f.to_python('2001-01-02 03:04:05.000006')
     34datetime.datetime(2001, 1, 2, 3, 4, 5, 6)
     35>>> f.to_python('2001-01-02 03:04:05.999999')
     36datetime.datetime(2001, 1, 2, 3, 4, 5, 999999)
     37
     38>>> f = TimeField()
     39>>> f.to_python('01:02:03.000004')
     40datetime.time(1, 2, 3, 4)
     41>>> f.to_python('01:02:03.999999')
     42datetime.time(1, 2, 3, 999999)
     43
     44
    3545"""
  • tests/regressiontests/model_regress/models.py

    diff -r 403793bcdc1d -r 0fe9bb68aded tests/regressiontests/model_regress/models.py
    a b  
    2828
    2929class Party(models.Model):
    3030    when = models.DateField()
     31
     32class Event(models.Model):
     33    when = models.DateTimeField()
    3134
    3235__test__ = {'API_TESTS': """
    3336(NOTE: Part of the regression test here is merely parsing the model
     
    6871>>> [p.when for p in Party.objects.filter(when__year = 1998)]
    6972[datetime.date(1998, 12, 31)]
    7073
     74# Check that get_next_by_FIELD and get_previous_by_FIELD don't crash when we
     75# have usecs values stored on the database
     76#
     77# [It crashed after the Field.get_db_prep_* refactor, because on most backends
     78#  DateTimeFields supports usecs, but DateTimeField.to_python didn't recognize
     79#  them. (Note that Model._get_next_or_previous_by_FIELD coerces values to
     80#  strings)]
     81#
     82>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 16, 0, 0))
     83>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 6, 1, 1))
     84>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 13, 1, 1))
     85>>> e = Event.objects.create(when = datetime.datetime(2000, 1, 1, 12, 0, 20, 24))
     86>>> e.get_next_by_when().when
     87datetime.datetime(2000, 1, 1, 13, 1, 1)
     88>>> e.get_previous_by_when().when
     89datetime.datetime(2000, 1, 1, 6, 1, 1)
    7190"""
    7291}
Back to Top