diff options
| author | Adrian Holovaty <adrian@holovaty.com> | 2007-07-01 01:17:51 +0000 |
|---|---|---|
| committer | Adrian Holovaty <adrian@holovaty.com> | 2007-07-01 01:17:51 +0000 |
| commit | 6f4e933fcc78da11ec43214efd3472192030eced (patch) | |
| tree | d92140118d3afa00bcd909f475dd1a694c15c08f /django/db/models | |
| parent | 7a981380def046f753ff70a7c25efcd781ff6ac6 (diff) | |
newforms-admin: Merged to [5571]
git-svn-id: http://code.djangoproject.com/svn/django/branches/newforms-admin@5572 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Diffstat (limited to 'django/db/models')
| -rw-r--r-- | django/db/models/base.py | 19 | ||||
| -rw-r--r-- | django/db/models/fields/__init__.py | 36 | ||||
| -rw-r--r-- | django/db/models/fields/related.py | 12 | ||||
| -rw-r--r-- | django/db/models/options.py | 7 | ||||
| -rw-r--r-- | django/db/models/query.py | 100 |
5 files changed, 131 insertions, 43 deletions
diff --git a/django/db/models/base.py b/django/db/models/base.py index 899b1086c9..7a1110a718 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -96,9 +96,9 @@ class Model(object): def __init__(self, *args, **kwargs): dispatcher.send(signal=signals.pre_init, sender=self.__class__, args=args, kwargs=kwargs) - + # There is a rather weird disparity here; if kwargs, it's set, then args - # overrides it. It should be one or the other; don't duplicate the work + # overrides it. It should be one or the other; don't duplicate the work # The reason for the kwargs check is that standard iterator passes in by # args, and nstantiation for iteration is 33% faster. args_len = len(args) @@ -122,10 +122,10 @@ class Model(object): # Maintain compatibility with existing calls. if isinstance(field.rel, ManyToOneRel): kwargs.pop(field.attname, None) - + # Now we're left with the unprocessed fields that *must* come from # keywords, or default. - + for field in fields_iter: if kwargs: if isinstance(field.rel, ManyToOneRel): @@ -147,7 +147,7 @@ class Model(object): try: val = getattr(rel_obj, field.rel.get_related_field().attname) except AttributeError: - raise TypeError("Invalid value: %r should be a %s instance, not a %s" % + raise TypeError("Invalid value: %r should be a %s instance, not a %s" % (field.name, field.rel.to, type(rel_obj))) else: val = kwargs.pop(field.attname, field.get_default()) @@ -215,17 +215,18 @@ class Model(object): record_exists = True if pk_set: # Determine whether a record with the primary key already exists. - cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % \ - (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), [pk_val]) + cursor.execute("SELECT COUNT(*) FROM %s WHERE %s=%%s" % \ + (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.pk.column)), + self._meta.pk.get_db_prep_lookup('exact', pk_val)) # If it does already exist, do an UPDATE. - if cursor.fetchone(): + if cursor.fetchone()[0] > 0: db_values = [f.get_db_prep_save(f.pre_save(self, False)) for f in non_pks] if db_values: cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \ (backend.quote_name(self._meta.db_table), ','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]), backend.quote_name(self._meta.pk.column)), - db_values + [pk_val]) + db_values + self._meta.pk.get_db_prep_lookup('exact', pk_val)) else: record_exists = False if not pk_set or not record_exists: diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 9ba3ac96c6..34cf2f0eee 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -72,12 +72,17 @@ class Field(object): maxlength=None, unique=False, blank=False, null=False, db_index=False, core=False, rel=None, default=NOT_PROVIDED, editable=True, serialize=True, 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): + validator_list=None, choices=None, radio_admin=None, help_text='', db_column=None, + db_tablespace=None): self.name = name self.verbose_name = verbose_name self.primary_key = primary_key self.maxlength, self.unique = maxlength, unique self.blank, self.null = blank, null + # Oracle treats the empty string ('') as null, so coerce the null + # option whenever '' is a possible value. + if self.empty_strings_allowed and settings.DATABASE_ENGINE == 'oracle': + self.null = True self.core, self.rel, self.default = core, rel, default self.editable = editable self.serialize = serialize @@ -88,6 +93,7 @@ class Field(object): self.radio_admin = radio_admin self.help_text = help_text self.db_column = db_column + self.db_tablespace = db_tablespace # Set db_index to True if the field has a relationship and doesn't explicitly set db_index. self.db_index = db_index @@ -166,7 +172,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', 'month', 'day', 'search'): + if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'): return [value] elif lookup_type in ('range', 'in'): return value @@ -198,7 +204,7 @@ class Field(object): if callable(self.default): return self.default() return self.default - if not self.empty_strings_allowed or self.null: + if not self.empty_strings_allowed or (self.null and settings.DATABASE_ENGINE != 'oracle'): return None return "" @@ -792,6 +798,7 @@ class IntegerField(Field): return super(IntegerField, self).formfield(**defaults) class IPAddressField(Field): + empty_strings_allowed = False def __init__(self, *args, **kwargs): kwargs['maxlength'] = 15 Field.__init__(self, *args, **kwargs) @@ -803,6 +810,7 @@ class IPAddressField(Field): validators.isValidIPAddress4(field_data, None) class NullBooleanField(Field): + empty_strings_allowed = False def __init__(self, *args, **kwargs): kwargs['null'] = True Field.__init__(self, *args, **kwargs) @@ -877,10 +885,18 @@ class TimeField(Field): Field.__init__(self, verbose_name, name, **kwargs) def get_db_prep_lookup(self, lookup_type, value): + if settings.DATABASE_ENGINE == 'oracle': + # 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 str(value) + else: + prep = str if lookup_type == 'range': - value = [str(v) for v in value] + value = [prep(v) for v in value] else: - value = str(value) + value = prep(value) return Field.get_db_prep_lookup(self, lookup_type, value) def pre_save(self, model_instance, add): @@ -898,7 +914,15 @@ class TimeField(Field): # doesn't support microseconds. if settings.DATABASE_ENGINE == 'mysql' and hasattr(value, 'microsecond'): value = value.replace(microsecond=0) - value = str(value) + if settings.DATABASE_ENGINE == 'oracle': + # 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 = str(value) return Field.get_db_prep_save(self, value) def get_manipulator_field_objs(self): diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 1b5716eb00..347ec8feb8 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -10,9 +10,10 @@ from django import oldforms from django import newforms as forms from django.dispatch import dispatcher -# For Python 2.3 -if not hasattr(__builtins__, 'set'): - from sets import Set as set +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback # Values for Relation.edit_inline. TABULAR, STACKED = 1, 2 @@ -335,10 +336,7 @@ def create_many_related_manager(superclass): (target_col_name, self.join_table, source_col_name, target_col_name, ",".join(['%s'] * len(new_ids))), [self._pk_val] + list(new_ids)) - if cursor.rowcount is not None and cursor.rowcount != 0: - existing_ids = set([row[0] for row in cursor.fetchmany(cursor.rowcount)]) - else: - existing_ids = set() + existing_ids = set([row[0] for row in cursor.fetchall()]) # Add the ones that aren't there already for obj_id in (new_ids - existing_ids): diff --git a/django/db/models/options.py b/django/db/models/options.py index d75bfd495d..d804cc944d 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -12,7 +12,7 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]| DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 'unique_together', 'permissions', 'get_latest_by', - 'order_with_respect_to', 'app_label') + 'order_with_respect_to', 'app_label', 'db_tablespace') class Options(object): def __init__(self, meta): @@ -26,6 +26,7 @@ class Options(object): self.object_name, self.app_label = None, None self.get_latest_by = None self.order_with_respect_to = None + self.db_tablespace = None self.admin = None self.meta = meta self.pk = None @@ -58,6 +59,8 @@ class Options(object): del self.meta def _prepare(self, model): + from django.db import backend + from django.db.backends.util import truncate_name if self.order_with_respect_to: self.order_with_respect_to = self.get_field(self.order_with_respect_to) self.ordering = ('_order',) @@ -72,6 +75,8 @@ class Options(object): # If the db_table wasn't provided, use the app_label + module_name. if not self.db_table: self.db_table = "%s_%s" % (self.app_label, self.module_name) + self.db_table = truncate_name(self.db_table, + backend.get_max_name_length()) def add_field(self, field): # Insert the given field in the order in which it was created, using diff --git a/django/db/models/query.py b/django/db/models/query.py index a6e702be18..92bc9d78ed 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -1,15 +1,18 @@ +from django.conf import settings from django.db import backend, connection, transaction from django.db.models.fields import DateField, FieldDoesNotExist from django.db.models import signals, loading from django.dispatch import dispatcher from django.utils.datastructures import SortedDict from django.contrib.contenttypes import generic +import datetime import operator import re -# For Python 2.3 -if not hasattr(__builtins__, 'set'): - from sets import Set as set +try: + set +except NameError: + from sets import Set as set # Python 2.3 fallback # The string constant used to separate query parts LOOKUP_SEPARATOR = '__' @@ -20,6 +23,7 @@ QUERY_TERMS = ( 'gt', 'gte', 'lt', 'lte', 'in', 'startswith', 'istartswith', 'endswith', 'iendswith', 'range', 'year', 'month', 'day', 'isnull', 'search', + 'regex', 'iregex', ) # Size of each "chunk" for get_iterator calls. @@ -77,7 +81,7 @@ def quote_only_if_word(word): else: return backend.quote_name(word) -class QuerySet(object): +class _QuerySet(object): "Represents a lazy database lookup for a set of objects" def __init__(self, model=None): self.model = model @@ -181,13 +185,18 @@ class QuerySet(object): cursor = connection.cursor() cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) + fill_cache = self._select_related - index_end = len(self.model._meta.fields) + fields = self.model._meta.fields + index_end = len(fields) + has_resolve_columns = hasattr(self, 'resolve_columns') while 1: rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) if not rows: raise StopIteration for row in rows: + if has_resolve_columns: + row = self.resolve_columns(row, fields) if fill_cache: obj, index_end = get_cached_row(klass=self.model, row=row, index_start=0, max_depth=self._max_related_depth) @@ -551,6 +560,12 @@ class QuerySet(object): return select, " ".join(sql), params +# Use the backend's QuerySet class if it defines one, otherwise use _QuerySet. +if hasattr(backend, 'get_query_set_class'): + QuerySet = backend.get_query_set_class(_QuerySet) +else: + QuerySet = _QuerySet + class ValuesQuerySet(QuerySet): def __init__(self, *args, **kwargs): super(ValuesQuerySet, self).__init__(*args, **kwargs) @@ -565,35 +580,38 @@ class ValuesQuerySet(QuerySet): # self._fields is a list of field names to fetch. if self._fields: - #columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields] if not self._select: - columns = [self.model._meta.get_field(f, many_to_many=False).column for f in self._fields] + fields = [self.model._meta.get_field(f, many_to_many=False) for f in self._fields] else: - columns = [] + fields = [] for f in self._fields: if f in [field.name for field in self.model._meta.fields]: - columns.append( self.model._meta.get_field(f, many_to_many=False).column ) + fields.append(self.model._meta.get_field(f, many_to_many=False)) elif not self._select.has_key( f ): raise FieldDoesNotExist, '%s has no field named %r' % ( self.model._meta.object_name, f ) field_names = self._fields else: # Default to all fields. - columns = [f.column for f in self.model._meta.fields] - field_names = [f.attname for f in self.model._meta.fields] + fields = self.model._meta.fields + field_names = [f.attname for f in fields] + columns = [f.column for f in fields] select = ['%s.%s' % (backend.quote_name(self.model._meta.db_table), backend.quote_name(c)) for c in columns] - # Add any additional SELECTs. if self._select: select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), backend.quote_name(s[0])) for s in self._select.items()]) cursor = connection.cursor() cursor.execute("SELECT " + (self._distinct and "DISTINCT " or "") + ",".join(select) + sql, params) + + has_resolve_columns = hasattr(self, 'resolve_columns') while 1: rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) if not rows: raise StopIteration for row in rows: + if has_resolve_columns: + row = self.resolve_columns(row, fields) yield dict(zip(field_names, row)) def _clone(self, klass=None, **kwargs): @@ -604,25 +622,49 @@ class ValuesQuerySet(QuerySet): class DateQuerySet(QuerySet): def iterator(self): from django.db.backends.util import typecast_timestamp + from django.db.models.fields import DateTimeField self._order_by = () # Clear this because it'll mess things up otherwise. if self._field.null: self._where.append('%s.%s IS NOT NULL' % \ (backend.quote_name(self.model._meta.db_table), backend.quote_name(self._field.column))) - try: select, sql, params = self._get_sql_clause() except EmptyResultSet: raise StopIteration - sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \ + table_name = backend.quote_name(self.model._meta.db_table) + field_name = backend.quote_name(self._field.column) + + if backend.allows_group_by_ordinal: + group_by = '1' + else: + group_by = backend.get_date_trunc_sql(self._kind, + '%s.%s' % (table_name, field_name)) + + sql = 'SELECT %s %s GROUP BY %s ORDER BY 1 %s' % \ (backend.get_date_trunc_sql(self._kind, '%s.%s' % (backend.quote_name(self.model._meta.db_table), - backend.quote_name(self._field.column))), sql, self._order) + backend.quote_name(self._field.column))), sql, group_by, self._order) cursor = connection.cursor() cursor.execute(sql, params) - # We have to manually run typecast_timestamp(str()) on the results, because - # MySQL doesn't automatically cast the result of date functions as datetime - # objects -- MySQL returns the values as strings, instead. - return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()] + + has_resolve_columns = hasattr(self, 'resolve_columns') + needs_datetime_string_cast = backend.needs_datetime_string_cast + dates = [] + # It would be better to use self._field here instead of DateTimeField(), + # but in Oracle that will result in a list of datetime.date instead of + # datetime.datetime. + fields = [DateTimeField()] + while 1: + rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE) + if not rows: + return dates + for row in rows: + date = row[0] + if has_resolve_columns: + date = self.resolve_columns([date], fields)[0] + elif needs_datetime_string_cast: + date = typecast_timestamp(str(date)) + dates.append(date) def _clone(self, klass=None, **kwargs): c = super(DateQuerySet, self)._clone(klass, **kwargs) @@ -730,8 +772,17 @@ def get_where_clause(lookup_type, table_prefix, field_name, value): if table_prefix.endswith('.'): table_prefix = backend.quote_name(table_prefix[:-1])+'.' field_name = backend.quote_name(field_name) + if type(value) == datetime.datetime and backend.get_datetime_cast_sql(): + cast_sql = backend.get_datetime_cast_sql() + else: + cast_sql = '%s' + if lookup_type in ('iexact', 'icontains', 'istartswith', 'iendswith') and backend.needs_upper_for_iops: + format = 'UPPER(%s%s) %s' + else: + format = '%s%s %s' try: - return '%s%s %s' % (table_prefix, field_name, (backend.OPERATOR_MAPPING[lookup_type] % '%s')) + return format % (table_prefix, field_name, + backend.OPERATOR_MAPPING[lookup_type] % cast_sql) except KeyError: pass if lookup_type == 'in': @@ -748,6 +799,15 @@ def get_where_clause(lookup_type, table_prefix, field_name, value): return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or '')) elif lookup_type == 'search': return backend.get_fulltext_search_sql(table_prefix + field_name) + elif lookup_type in ('regex', 'iregex'): + if settings.DATABASE_ENGINE == 'oracle': + if lookup_type == 'regex': + match_option = 'c' + else: + match_option = 'i' + return "REGEXP_LIKE(%s%s, %s, '%s')" % (table_prefix, field_name, cast_sql, match_option) + else: + raise NotImplementedError raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type) def get_cached_row(klass, row, index_start, max_depth=0, cur_depth=0): |
