magic-removal: Added Manager class. Refactored module-level functions to be methods of Manager. Moved most model-method and module-function creation from the ModelBase metaclass to new a _prepare() method in both Model and Manager.

git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1598 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Adrian Holovaty 2005-12-12 01:32:32 +00:00
parent 5f420e14a9
commit 1926ff4b31
1 changed files with 319 additions and 348 deletions

View File

@ -708,13 +708,8 @@ class ModelBase(type):
custom_methods[k] = v custom_methods[k] = v
del attrs[k] del attrs[k]
# Create the module-level ObjectDoesNotExist exception. # Create the DoesNotExist exception.
dne_exc_name = '%sDoesNotExist' % name attrs['DoesNotExist'] = types.ClassType('DoesNotExist', (ObjectDoesNotExist,), {})
does_not_exist_exception = types.ClassType(dne_exc_name, (ObjectDoesNotExist,), {})
# Explicitly set its __module__ because it will initially (incorrectly)
# be set to the module the code is being executed in.
does_not_exist_exception.__module__ = MODEL_PREFIX + '.' + opts.module_name
setattr(new_mod, dne_exc_name, does_not_exist_exception)
# Create other exceptions. # Create other exceptions.
for exception_name in opts.exceptions: for exception_name in opts.exceptions:
@ -727,10 +722,6 @@ class ModelBase(type):
setattr(new_mod, k, v) setattr(new_mod, k, v)
# Create the default class methods. # Create the default class methods.
if opts.order_with_respect_to:
attrs['get_next_in_order'] = curry(method_get_next_in_order, opts, opts.order_with_respect_to)
attrs['get_previous_in_order'] = curry(method_get_previous_in_order, opts, opts.order_with_respect_to)
for f in opts.fields: for f in opts.fields:
# If the object has a relationship to itself, as designated by # If the object has a relationship to itself, as designated by
# RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally. # RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally.
@ -762,72 +753,15 @@ class ModelBase(type):
# Create the class, because we need it to use in currying. # Create the class, because we need it to use in currying.
new_class = type.__new__(cls, name, bases, attrs) new_class = type.__new__(cls, name, bases, attrs)
# Create the manager.
# TODO: Use weakref because of possible memory leak / circular reference.
# TODO: Allow for overriding of the name, and custom manager subclasses.
new_class.objects = Manager(new_class)
# Give the class a docstring -- its definition. # Give the class a docstring -- its definition.
if new_class.__doc__ is None: if new_class.__doc__ is None:
new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields])) new_class.__doc__ = "%s.%s(%s)" % (opts.module_name, name, ", ".join([f.name for f in opts.fields]))
# Create the standard, module-level API helper functions such
# as get_object() and get_list().
new_mod.get_object = curry(function_get_object, opts, new_class, does_not_exist_exception)
new_mod.get_object.__doc__ = "Returns the %s object matching the given parameters." % name
new_mod.get_list = curry(function_get_list, opts, new_class)
new_mod.get_list.__doc__ = "Returns a list of %s objects matching the given parameters." % name
new_mod.get_iterator = curry(function_get_iterator, opts, new_class)
new_mod.get_iterator.__doc__ = "Returns an iterator of %s objects matching the given parameters." % name
new_mod.get_values = curry(function_get_values, opts, new_class)
new_mod.get_values.__doc__ = "Returns a list of dictionaries matching the given parameters."
new_mod.get_values_iterator = curry(function_get_values_iterator, opts, new_class)
new_mod.get_values_iterator.__doc__ = "Returns an iterator of dictionaries matching the given parameters."
new_mod.get_count = curry(function_get_count, opts)
new_mod.get_count.__doc__ = "Returns the number of %s objects matching the given parameters." % name
new_mod._get_sql_clause = curry(function_get_sql_clause, opts)
new_mod.get_in_bulk = curry(function_get_in_bulk, opts, new_class)
new_mod.get_in_bulk.__doc__ = "Returns a dictionary of ID -> %s for the %s objects with IDs in the given id_list." % (name, name)
if opts.get_latest_by:
new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
for f in opts.fields:
if f.choices:
# Add "get_thingie_display" method to get human-readable value.
func = curry(method_get_display_value, f)
setattr(new_class, 'get_%s_display' % f.name, func)
if isinstance(f, DateField) or isinstance(f, DateTimeField):
# Add "get_next_by_thingie" and "get_previous_by_thingie" methods
# for all DateFields and DateTimeFields that cannot be null.
# EXAMPLES: Poll.get_next_by_pub_date(), Poll.get_previous_by_pub_date()
if not f.null:
setattr(new_class, 'get_next_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, True))
setattr(new_class, 'get_previous_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, False))
# Add "get_thingie_list" for all DateFields and DateTimeFields.
# EXAMPLE: polls.get_pub_date_list()
func = curry(function_get_date_list, opts, f)
func.__doc__ = "Returns a list of days, months or years (as datetime.datetime objects) in which %s objects are available. The first parameter ('kind') must be one of 'year', 'month' or 'day'." % name
setattr(new_mod, 'get_%s_list' % f.name, func)
elif isinstance(f, FileField):
setattr(new_class, 'get_%s_filename' % f.name, curry(method_get_file_filename, f))
setattr(new_class, 'get_%s_url' % f.name, curry(method_get_file_url, f))
setattr(new_class, 'get_%s_size' % f.name, curry(method_get_file_size, f))
func = curry(method_save_file, f)
func.alters_data = True
setattr(new_class, 'save_%s_file' % f.name, func)
if isinstance(f, ImageField):
# Add get_BLAH_width and get_BLAH_height methods, but only
# if the image field doesn't have width and height cache
# fields.
if not f.width_field:
setattr(new_class, 'get_%s_width' % f.name, curry(method_get_image_width, f))
if not f.height_field:
setattr(new_class, 'get_%s_height' % f.name, curry(method_get_image_height, f))
# Add the class itself to the new module we've created. # Add the class itself to the new module we've created.
new_mod.__dict__[name] = new_class new_mod.__dict__[name] = new_class
@ -921,6 +855,10 @@ class ModelBase(type):
for related in model._meta.get_all_related_objects() + model._meta.get_all_related_many_to_many_objects(): for related in model._meta.get_all_related_objects() + model._meta.get_all_related_many_to_many_objects():
related.field.rel.to = opts related.field.rel.to = opts
break break
new_class._prepare()
new_class.objects._prepare()
return new_class return new_class
class Model: class Model:
@ -967,6 +905,34 @@ class Model:
for i, arg in enumerate(args): for i, arg in enumerate(args):
setattr(self, self._meta.fields[i].attname, arg) setattr(self, self._meta.fields[i].attname, arg)
def _prepare(cls):
# Creates some methods once self._meta has been populated.
for f in cls._meta.fields:
if f.choices:
setattr(cls, 'get_%s_display' % f.name, curry(cls.__get_FIELD_display, field=f))
if isinstance(f, DateField):
if not f.null:
setattr(cls, 'get_next_by_%s' % f.name, curry(cls.__get_next_or_previous_by_FIELD, field=f, is_next=True))
setattr(cls, 'get_previous_by_%s' % f.name, curry(cls.__get_next_or_previous_by_FIELD, field=f, is_next=False))
elif isinstance(f, FileField):
setattr(cls, 'get_%s_filename' % f.name, curry(cls.__get_FIELD_filename, f))
setattr(cls, 'get_%s_url' % f.name, curry(cls.__get_FIELD_url, f))
setattr(cls, 'get_%s_size' % f.name, curry(cls.__get_FIELD_size, f))
setattr(cls, 'save_%s_file' % f.name, curry(cls.__save_FIELD_file, f))
if isinstance(f, ImageField):
# Add get_BLAH_width and get_BLAH_height methods, but only
# if the image field doesn't have width and height cache
# fields.
if not f.width_field:
setattr(cls, 'get_%s_width' % f.name, curry(method_get_image_width, f))
if not f.height_field:
setattr(cls, 'get_%s_height' % f.name, curry(method_get_image_height, f))
if cls._meta.order_with_respect_to:
cls.get_next_in_order = curry(cls.__get_next_or_previous_in_order, True)
cls.get_previous_in_order = curry(cls.__get_next_or_previous_in_order, False)
_prepare = classmethod(_prepare)
def save(self): def save(self):
# Run any pre-save hooks. # Run any pre-save hooks.
if hasattr(self, '_pre_save'): if hasattr(self, '_pre_save'):
@ -1068,32 +1034,290 @@ class Model:
delete.alters_data = True delete.alters_data = True
def __get_FIELD_display(self, field):
value = getattr(self, field.attname)
return dict(field.choices).get(value, value)
def __get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
op = is_next and '>' or '<'
kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
(db.db.quote_name(field.column), op, db.db.quote_name(field.column),
db.db.quote_name(self._meta.db_table), db.db.quote_name(self._meta.pk.column), op))
param = str(getattr(self, field.attname))
kwargs.setdefault('params', []).extend([param, param, getattr(self, self._meta.pk.attname)])
kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + self._meta.pk.name]
kwargs['limit'] = 1
return self.objects.get_object(**kwargs)
def __get_next_or_previous_in_order(self, is_next):
cachename = "__%s_order_cache" % is_next
if not hasattr(self, cachename):
op = is_next and '>' or '<'
order_field = self.order_with_respect_to
obj = self.objects.get_object(order_by=('_order',),
where=['%s %s (SELECT %s FROM %s WHERE %s=%%s)' % \
(db.db.quote_name('_order'), op, db.db.quote_name('_order'),
db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
'%s=%%s' % db.db.quote_name(order_field.column)],
limit=1,
params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
setattr(self, cachename, obj)
return getattr(self, cachename)
def __get_FIELD_filename(self, field):
return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
def __get_FIELD_url(self, field):
if getattr(self, field.attname): # value is not blank
import urlparse
return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
return ''
def __get_FIELD_size(self, field):
return os.path.getsize(self.__get_FIELD_filename(field))
def __save_FIELD_file(self, field, filename, raw_contents):
directory = field.get_directory_name()
try: # Create the date-based directory if it doesn't exist.
os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
except OSError: # Directory probably already exists.
pass
filename = field.get_filename(filename)
# If the filename already exists, keep adding an underscore to the name of
# the file until the filename doesn't exist.
while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
try:
dot_index = filename.rindex('.')
except ValueError: # filename has no dot
filename += '_'
else:
filename = filename[:dot_index] + '_' + filename[dot_index:]
# Write the file to disk.
setattr(self, field.attname, filename)
full_filename = self.__get_FIELD_filename(field)
fp = open(full_filename, 'wb')
fp.write(raw_contents)
fp.close()
# Save the width and/or height, if applicable.
if isinstance(field, ImageField) and (field.width_field or field.height_field):
from django.utils.images import get_image_dimensions
width, height = get_image_dimensions(full_filename)
if field.width_field:
setattr(self, field.width_field, width)
if field.height_field:
setattr(self, field.height_field, height)
# Save the object, because it has changed.
self.save()
__save_FIELD_file.alters_data = True
def __get_FIELD_width(self, field):
return self.__get_image_dimensions(field)[0]
def __get_FIELD_height(self, field):
return self.__get_image_dimensions(field)[1]
def __get_image_dimensions(self, field):
cachename = "__%s_dimensions_cache" % field.name
if not hasattr(self, cachename):
from django.utils.images import get_image_dimensions
filename = self.__get_FIELD_filename(field)()
setattr(self, cachename, get_image_dimensions(filename))
return getattr(self, cachename)
class Manager:
def __init__(self, model_class):
self.klass = model_class
def _prepare(self):
# Creates some methods once self.klass._meta has been populated.
if self.klass._meta.get_latest_by:
self.get_latest = self.__get_latest
for f in self.klass._meta.fields:
if isinstance(f, DateField):
setattr(self, 'get_%s_list' % f.name, curry(self.__get_date_list, f))
def _get_sql_clause(self, **kwargs):
def quote_only_if_word(word):
if ' ' in word:
return word
else:
return db.db.quote_name(word)
opts = self.klass._meta
# Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
select = ["%s.%s" % (db.db.quote_name(opts.db_table), db.db.quote_name(f.column)) for f in opts.fields]
tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or [])
tables = [quote_only_if_word(t) for t in tables]
where = kwargs.get('where') and kwargs['where'][:] or []
params = kwargs.get('params') and kwargs['params'][:] or []
# Convert the kwargs into SQL.
tables2, join_where2, where2, params2, _ = _parse_lookup(kwargs.items(), opts)
tables.extend(tables2)
where.extend(join_where2 + where2)
params.extend(params2)
# Add any additional constraints from the "where_constraints" parameter.
where.extend(opts.where_constraints)
# Add additional tables and WHERE clauses based on select_related.
if kwargs.get('select_related') is True:
_fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
# Add any additional SELECTs passed in via kwargs.
if kwargs.get('select'):
select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), db.db.quote_name(s[0])) for s in kwargs['select']])
# ORDER BY clause
order_by = []
for f in handle_legacy_orderlist(kwargs.get('order_by', opts.ordering)):
if f == '?': # Special case.
order_by.append(db.get_random_function_sql())
else:
if f.startswith('-'):
col_name = f[1:]
order = "DESC"
else:
col_name = f
order = "ASC"
if "." in col_name:
table_prefix, col_name = col_name.split('.', 1)
table_prefix = db.db.quote_name(table_prefix) + '.'
else:
# Use the database table as a column prefix if it wasn't given,
# and if the requested column isn't a custom SELECT.
if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]:
table_prefix = db.db.quote_name(opts.db_table) + '.'
else:
table_prefix = ''
order_by.append('%s%s %s' % (table_prefix, db.db.quote_name(orderfield2column(col_name, opts)), order))
order_by = ", ".join(order_by)
# LIMIT and OFFSET clauses
if kwargs.get('limit') is not None:
limit_sql = " %s " % db.get_limit_offset_sql(kwargs['limit'], kwargs.get('offset'))
else:
assert kwargs.get('offset') is None, "'offset' is not allowed without 'limit'"
limit_sql = ""
return select, " FROM " + ",".join(tables) + (where and " WHERE " + " AND ".join(where) or "") + (order_by and " ORDER BY " + order_by or "") + limit_sql, params
def get_iterator(self, **kwargs):
# kwargs['select'] is a dictionary, and dictionaries' key order is
# undefined, so we convert it to a list of tuples internally.
kwargs['select'] = kwargs.get('select', {}).items()
cursor = db.db.cursor()
select, sql, params = self._get_sql_clause(**kwargs)
cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = kwargs.get('select_related')
index_end = len(self.klass._meta.fields)
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
raise StopIteration
for row in rows:
if fill_cache:
obj, index_end = _get_cached_row(opts, row, 0)
else:
obj = self.klass(*row[:index_end])
for i, k in enumerate(kwargs['select']):
setattr(obj, k[0], row[index_end+i])
yield obj
def get_list(self, **kwargs):
return list(self.get_iterator(**kwargs))
def get_count(self, **kwargs):
kwargs['order_by'] = []
kwargs['offset'] = None
kwargs['limit'] = None
kwargs['select_related'] = False
_, sql, params = self._get_sql_clause(**kwargs)
cursor = db.db.cursor()
cursor.execute("SELECT COUNT(*)" + sql, params)
return cursor.fetchone()[0]
def get_object(self, **kwargs):
obj_list = self.get_list(**kwargs)
if len(obj_list) < 1:
raise self.klass.DoesNotExist, "%s does not exist for %s" % (self.klass._meta.object_name, kwargs)
assert len(obj_list) == 1, "get_object() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.klass._meta.object_name, len(obj_list), kwargs)
return obj_list[0]
def get_in_bulk(self, *args, **kwargs):
id_list = args and args[0] or kwargs['id_list']
assert id_list != [], "get_in_bulk() cannot be passed an empty list."
kwargs['where'] = ["%s.%s IN (%s)" % (db.db.quote_name(self.klass._meta.db_table), db.db.quote_name(self.klass._meta.pk.column), ",".join(['%s'] * len(id_list)))]
kwargs['params'] = id_list
obj_list = self.get_list(**kwargs)
return dict([(getattr(o, self.klass._meta.pk.attname), o) for o in obj_list])
def get_values_iterator(self, **kwargs):
# select_related and select aren't supported in get_values().
kwargs['select_related'] = False
kwargs['select'] = {}
# 'fields' is a list of field names to fetch.
try:
fields = [self.klass._meta.get_field(f).column for f in kwargs.pop('fields')]
except KeyError: # Default to all fields.
fields = [f.column for f in self.klass._meta.fields]
cursor = db.db.cursor()
_, sql, params = self._get_sql_clause(**kwargs)
select = ['%s.%s' % (db.db.quote_name(self.klass._meta.db_table), db.db.quote_name(f)) for f in fields]
cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
raise StopIteration
for row in rows:
yield dict(zip(fields, row))
def get_values(self, **kwargs):
return list(self.get_values_iterator(**kwargs))
def __get_latest(self, **kwargs):
kwargs['order_by'] = ('-' + self.klass._meta.get_latest_by,)
kwargs['limit'] = 1
return self.get_object(**kwargs)
def __get_date_list(self, field, *args, **kwargs):
from django.core.db.typecasts import typecast_timestamp
kind = args and args[0] or kwargs['kind']
assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'."
order = 'ASC'
if kwargs.has_key('order'):
order = kwargs['order']
del kwargs['order']
assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
kwargs['order_by'] = () # Clear this because it'll mess things up otherwise.
if field.null:
kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \
(db.db.quote_name(self.klass._meta.db_table), db.db.quote_name(field.column)))
select, sql, params = self._get_sql_clause(**kwargs)
sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1 %s' % \
(db.get_date_trunc_sql(kind, '%s.%s' % (db.db.quote_name(self.klass._meta.db_table),
db.db.quote_name(field.column))), sql, order)
cursor = db.db.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()]
############################################ ############################################
# HELPER FUNCTIONS (CURRIED MODEL METHODS) # # HELPER FUNCTIONS (CURRIED MODEL METHODS) #
############################################ ############################################
# CORE METHODS #############################
def method_get_next_in_order(opts, order_field, self):
if not hasattr(self, '_next_in_order_cache'):
self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',),
where=['%s > (SELECT %s FROM %s WHERE %s=%%s)' % \
(db.db.quote_name('_order'), db.db.quote_name('_order'),
db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
'%s=%%s' % db.db.quote_name(order_field.column)], limit=1,
params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
return self._next_in_order_cache
def method_get_previous_in_order(opts, order_field, self):
if not hasattr(self, '_previous_in_order_cache'):
self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',),
where=['%s < (SELECT %s FROM %s WHERE %s=%%s)' % \
(db.db.quote_name('_order'), db.db.quote_name('_order'),
db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)),
'%s=%%s' % db.db.quote_name(order_field.column)], limit=1,
params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)])
return self._previous_in_order_cache
# RELATIONSHIP METHODS ##################### # RELATIONSHIP METHODS #####################
# Example: Story.get_dateline() # Example: Story.get_dateline()
@ -1241,91 +1465,6 @@ def method_get_order(ordered_obj, self):
cursor.execute(sql, [rel_val]) cursor.execute(sql, [rel_val])
return [r[0] for r in cursor.fetchall()] return [r[0] for r in cursor.fetchall()]
# DATE-RELATED METHODS #####################
def method_get_next_or_previous(get_object_func, opts, field, is_next, self, **kwargs):
op = is_next and '>' or '<'
kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \
(db.db.quote_name(field.column), op, db.db.quote_name(field.column),
db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), op))
param = str(getattr(self, field.attname))
kwargs.setdefault('params', []).extend([param, param, getattr(self, opts.pk.attname)])
kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + opts.pk.name]
kwargs['limit'] = 1
return get_object_func(**kwargs)
# CHOICE-RELATED METHODS ###################
def method_get_display_value(field, self):
value = getattr(self, field.attname)
return dict(field.choices).get(value, value)
# FILE-RELATED METHODS #####################
def method_get_file_filename(field, self):
return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))
def method_get_file_url(field, self):
if getattr(self, field.attname): # value is not blank
import urlparse
return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')
return ''
def method_get_file_size(field, self):
return os.path.getsize(method_get_file_filename(field, self))
def method_save_file(field, self, filename, raw_contents):
directory = field.get_directory_name()
try: # Create the date-based directory if it doesn't exist.
os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))
except OSError: # Directory probably already exists.
pass
filename = field.get_filename(filename)
# If the filename already exists, keep adding an underscore to the name of
# the file until the filename doesn't exist.
while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):
try:
dot_index = filename.rindex('.')
except ValueError: # filename has no dot
filename += '_'
else:
filename = filename[:dot_index] + '_' + filename[dot_index:]
# Write the file to disk.
setattr(self, field.attname, filename)
fp = open(getattr(self, 'get_%s_filename' % field.name)(), 'wb')
fp.write(raw_contents)
fp.close()
# Save the width and/or height, if applicable.
if isinstance(field, ImageField) and (field.width_field or field.height_field):
from django.utils.images import get_image_dimensions
width, height = get_image_dimensions(getattr(self, 'get_%s_filename' % field.name)())
if field.width_field:
setattr(self, field.width_field, width)
if field.height_field:
setattr(self, field.height_field, height)
# Save the object, because it has changed.
self.save()
# IMAGE FIELD METHODS ######################
def method_get_image_width(field, self):
return _get_image_dimensions(field, self)[0]
def method_get_image_height(field, self):
return _get_image_dimensions(field, self)[1]
def _get_image_dimensions(field, self):
cachename = "__%s_dimensions_cache" % field.name
if not hasattr(self, cachename):
from django.utils.images import get_image_dimensions
fname = getattr(self, "get_%s_filename" % field.name)()
setattr(self, cachename, get_image_dimensions(fname))
return getattr(self, cachename)
############################################## ##############################################
# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) # # HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
############################################## ##############################################
@ -1351,13 +1490,6 @@ 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 '')) return "%s%s IS %sNULL" % (table_prefix, field_name, (not value and 'NOT ' or ''))
raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type) raise TypeError, "Got invalid lookup_type: %s" % repr(lookup_type)
def function_get_object(opts, klass, does_not_exist_exception, **kwargs):
obj_list = function_get_list(opts, klass, **kwargs)
if len(obj_list) < 1:
raise does_not_exist_exception, "%s does not exist for %s" % (opts.object_name, kwargs)
assert len(obj_list) == 1, "get_object() returned more than one %s -- it returned %s! Lookup parameters were %s" % (opts.object_name, len(obj_list), kwargs)
return obj_list[0]
def _get_cached_row(opts, row, index_start): def _get_cached_row(opts, row, index_start):
"Helper function that recursively returns an object with cache filled" "Helper function that recursively returns an object with cache filled"
index_end = index_start + len(opts.fields) index_end = index_start + len(opts.fields)
@ -1368,67 +1500,6 @@ def _get_cached_row(opts, row, index_start):
setattr(obj, f.get_cache_name(), rel_obj) setattr(obj, f.get_cache_name(), rel_obj)
return obj, index_end return obj, index_end
def function_get_iterator(opts, klass, **kwargs):
# kwargs['select'] is a dictionary, and dictionaries' key order is
# undefined, so we convert it to a list of tuples internally.
kwargs['select'] = kwargs.get('select', {}).items()
cursor = db.db.cursor()
select, sql, params = function_get_sql_clause(opts, **kwargs)
cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
fill_cache = kwargs.get('select_related')
index_end = len(opts.fields)
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
raise StopIteration
for row in rows:
if fill_cache:
obj, index_end = _get_cached_row(opts, row, 0)
else:
obj = klass(*row[:index_end])
for i, k in enumerate(kwargs['select']):
setattr(obj, k[0], row[index_end+i])
yield obj
def function_get_list(opts, klass, **kwargs):
return list(function_get_iterator(opts, klass, **kwargs))
def function_get_count(opts, **kwargs):
kwargs['order_by'] = []
kwargs['offset'] = None
kwargs['limit'] = None
kwargs['select_related'] = False
_, sql, params = function_get_sql_clause(opts, **kwargs)
cursor = db.db.cursor()
cursor.execute("SELECT COUNT(*)" + sql, params)
return cursor.fetchone()[0]
def function_get_values_iterator(opts, klass, **kwargs):
# select_related and select aren't supported in get_values().
kwargs['select_related'] = False
kwargs['select'] = {}
# 'fields' is a list of field names to fetch.
try:
fields = [opts.get_field(f).column for f in kwargs.pop('fields')]
except KeyError: # Default to all fields.
fields = [f.column for f in opts.fields]
cursor = db.db.cursor()
_, sql, params = function_get_sql_clause(opts, **kwargs)
select = ['%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(f)) for f in fields]
cursor.execute("SELECT " + (kwargs.get('distinct') and "DISTINCT " or "") + ",".join(select) + sql, params)
while 1:
rows = cursor.fetchmany(GET_ITERATOR_CHUNK_SIZE)
if not rows:
raise StopIteration
for row in rows:
yield dict(zip(fields, row))
def function_get_values(opts, klass, **kwargs):
return list(function_get_values_iterator(opts, klass, **kwargs))
def _fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen): def _fill_table_cache(opts, select, tables, where, old_prefix, cache_tables_seen):
""" """
Helper function that recursively populates the select, tables and where (in Helper function that recursively populates the select, tables and where (in
@ -1583,106 +1654,6 @@ def _parse_lookup(kwarg_items, opts, table_count=0):
continue continue
return tables, join_where, where, params, table_count return tables, join_where, where, params, table_count
def function_get_sql_clause(opts, **kwargs):
def quote_only_if_word(word):
if ' ' in word:
return word
else:
return db.db.quote_name(word)
# Construct the fundamental parts of the query: SELECT X FROM Y WHERE Z.
select = ["%s.%s" % (db.db.quote_name(opts.db_table), db.db.quote_name(f.column)) for f in opts.fields]
tables = [opts.db_table] + (kwargs.get('tables') and kwargs['tables'][:] or [])
tables = [quote_only_if_word(t) for t in tables]
where = kwargs.get('where') and kwargs['where'][:] or []
params = kwargs.get('params') and kwargs['params'][:] or []
# Convert the kwargs into SQL.
tables2, join_where2, where2, params2, _ = _parse_lookup(kwargs.items(), opts)
tables.extend(tables2)
where.extend(join_where2 + where2)
params.extend(params2)
# Add any additional constraints from the "where_constraints" parameter.
where.extend(opts.where_constraints)
# Add additional tables and WHERE clauses based on select_related.
if kwargs.get('select_related') is True:
_fill_table_cache(opts, select, tables, where, opts.db_table, [opts.db_table])
# Add any additional SELECTs passed in via kwargs.
if kwargs.get('select'):
select.extend(['(%s) AS %s' % (quote_only_if_word(s[1]), db.db.quote_name(s[0])) for s in kwargs['select']])
# ORDER BY clause
order_by = []
for f in handle_legacy_orderlist(kwargs.get('order_by', opts.ordering)):
if f == '?': # Special case.
order_by.append(db.get_random_function_sql())
else:
if f.startswith('-'):
col_name = f[1:]
order = "DESC"
else:
col_name = f
order = "ASC"
if "." in col_name:
table_prefix, col_name = col_name.split('.', 1)
table_prefix = db.db.quote_name(table_prefix) + '.'
else:
# Use the database table as a column prefix if it wasn't given,
# and if the requested column isn't a custom SELECT.
if "." not in col_name and col_name not in [k[0] for k in kwargs.get('select', [])]:
table_prefix = db.db.quote_name(opts.db_table) + '.'
else:
table_prefix = ''
order_by.append('%s%s %s' % (table_prefix, db.db.quote_name(orderfield2column(col_name, opts)), order))
order_by = ", ".join(order_by)
# LIMIT and OFFSET clauses
if kwargs.get('limit') is not None:
limit_sql = " %s " % db.get_limit_offset_sql(kwargs['limit'], kwargs.get('offset'))
else:
assert kwargs.get('offset') is None, "'offset' is not allowed without 'limit'"
limit_sql = ""
return select, " FROM " + ",".join(tables) + (where and " WHERE " + " AND ".join(where) or "") + (order_by and " ORDER BY " + order_by or "") + limit_sql, params
def function_get_in_bulk(opts, klass, *args, **kwargs):
id_list = args and args[0] or kwargs['id_list']
assert id_list != [], "get_in_bulk() cannot be passed an empty list."
kwargs['where'] = ["%s.%s IN (%s)" % (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), ",".join(['%s'] * len(id_list)))]
kwargs['params'] = id_list
obj_list = function_get_list(opts, klass, **kwargs)
return dict([(getattr(o, opts.pk.attname), o) for o in obj_list])
def function_get_latest(opts, klass, does_not_exist_exception, **kwargs):
kwargs['order_by'] = ('-' + opts.get_latest_by,)
kwargs['limit'] = 1
return function_get_object(opts, klass, does_not_exist_exception, **kwargs)
def function_get_date_list(opts, field, *args, **kwargs):
from django.core.db.typecasts import typecast_timestamp
kind = args and args[0] or kwargs['kind']
assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'."
order = 'ASC'
if kwargs.has_key('_order'):
order = kwargs['_order']
del kwargs['_order']
assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"
kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.
if field.null:
kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \
(db.db.quote_name(opts.db_table), db.db.quote_name(field.column)))
select, sql, params = function_get_sql_clause(opts, **kwargs)
sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % (db.get_date_trunc_sql(kind, '%s.%s' % (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))), sql)
cursor = db.db.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()]
################################### ###################################
# HELPER FUNCTIONS (MANIPULATORS) # # HELPER FUNCTIONS (MANIPULATORS) #
################################### ###################################