From ba87f474dc1883aff039c5fbd659bd70e749ee71 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 9 Nov 2005 20:36:23 +0000 Subject: [PATCH 1/8] Changed dateformat unit tests so that they use the en-us language. They were failing because they depended on the current user's LANGUAGE_CODE setting git-svn-id: http://code.djangoproject.com/svn/django/trunk@1145 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/othertests/dateformat.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/othertests/dateformat.py b/tests/othertests/dateformat.py index fe8bc8256d..c4cfd943aa 100644 --- a/tests/othertests/dateformat.py +++ b/tests/othertests/dateformat.py @@ -63,11 +63,13 @@ NotImplementedError '1979 188 CET' """ -from django.utils import dateformat -format = dateformat.format +from django.utils import dateformat, translation import datetime, os, time +format = dateformat.format os.environ['TZ'] = 'Europe/Copenhagen' +translation.activate('en-us') + time.tzset() my_birthday = datetime.datetime(1979, 7, 7, 22, 00) From 2a149e29b862a153263169c991584784d01e6e2f Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 9 Nov 2005 20:41:18 +0000 Subject: [PATCH 2/8] Fixed dateformat unit test that was failing -- the test was incorrect. git-svn-id: http://code.djangoproject.com/svn/django/trunk@1146 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/othertests/dateformat.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tests/othertests/dateformat.py b/tests/othertests/dateformat.py index c4cfd943aa..3350a1f8ab 100644 --- a/tests/othertests/dateformat.py +++ b/tests/othertests/dateformat.py @@ -30,9 +30,7 @@ >>> format(my_birthday, 'S') 'th' >>> format(my_birthday, 't') -Traceback (most recent call last): - ... -NotImplementedError +'31' >>> format(my_birthday, 'T') 'CET' >>> format(my_birthday, 'U') From 133e9e9639413ca5a115a2fd8a7218e4065c345e Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 9 Nov 2005 21:56:05 +0000 Subject: [PATCH 3/8] Added unit tests to confirm #683 -- a new custom_columns model example git-svn-id: http://code.djangoproject.com/svn/django/trunk@1147 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/testapp/models/__init__.py | 2 +- tests/testapp/models/custom_columns.py | 53 ++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/testapp/models/custom_columns.py diff --git a/tests/testapp/models/__init__.py b/tests/testapp/models/__init__.py index 0fab083922..baa048d123 100644 --- a/tests/testapp/models/__init__.py +++ b/tests/testapp/models/__init__.py @@ -1,4 +1,4 @@ __all__ = ['basic', 'repr', 'custom_methods', 'many_to_one', 'many_to_many', 'ordering', 'lookup', 'get_latest', 'm2m_intermediary', 'one_to_one', 'm2o_recursive', 'm2o_recursive2', 'save_delete_hooks', 'custom_pk', - 'subclassing', 'many_to_one_null'] + 'subclassing', 'many_to_one_null', 'custom_columns'] diff --git a/tests/testapp/models/custom_columns.py b/tests/testapp/models/custom_columns.py new file mode 100644 index 0000000000..c900091b64 --- /dev/null +++ b/tests/testapp/models/custom_columns.py @@ -0,0 +1,53 @@ +""" +17. Custom column names + +If your database column name is different than your model attribute, use the +``db_column`` parameter. Note that you'll use the field's name, not its column +name, in API usage. +""" + +from django.core import meta + +class Person(meta.Model): + first_name = meta.CharField(maxlength=30, db_column='firstname') + last_name = meta.CharField(maxlength=30, db_column='last') + + def __repr__(self): + return '%s %s' % (self.first_name, self.last_name) + +API_TESTS = """ +# Create a Person. +>>> p = persons.Person(first_name='John', last_name='Smith') +>>> p.save() + +>>> p.id +1 + +>>> persons.get_list() +[John Smith] + +>>> persons.get_list(first_name__exact='John') +[John Smith] + +>>> persons.get_object(first_name__exact='John') +John Smith + +>>> persons.get_list(firstname__exact='John') +Traceback (most recent call last): + ... +TypeError: got unexpected keyword argument 'firstname__exact' + +>>> p = persons.get_object(last_name__exact='Smith') +>>> p.first_name +'John' +>>> p.last_name +'Smith' +>>> p.firstname +Traceback (most recent call last): + ... +AttributeError: 'Person' object has no attribute 'firstname' +>>> p.last +Traceback (most recent call last): + ... +AttributeError: 'Person' object has no attribute 'last' +""" From 95cc5772c34742c45f7fc5d98d9bb593a658398b Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 9 Nov 2005 22:40:02 +0000 Subject: [PATCH 4/8] Factored FORM_FIELD_ID_PREFIX out into a get_id() method in formfields.py -- thanks for the idea, rjwittams git-svn-id: http://code.djangoproject.com/svn/django/trunk@1148 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/formfields.py | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/django/core/formfields.py b/django/core/formfields.py index 48238cf050..0333d09304 100644 --- a/django/core/formfields.py +++ b/django/core/formfields.py @@ -210,6 +210,10 @@ class FormField: def render(self, data): raise NotImplementedError + def get_id(self): + "Returns the HTML 'id' attribute for this form field." + return FORM_FIELD_ID_PREFIX + self.field_name + #################### # GENERIC WIDGETS # #################### @@ -239,7 +243,7 @@ class TextField(FormField): if isinstance(data, unicode): data = data.encode(DEFAULT_CHARSET) return '' % \ - (self.input_type, FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', + (self.input_type, self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', self.field_name, self.length, escape(data), maxlength) def html2python(data): @@ -264,7 +268,7 @@ class LargeTextField(TextField): if isinstance(data, unicode): data = data.encode(DEFAULT_CHARSET) return '' % \ - (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, self.is_required and ' required' or '', + (self.get_id(), self.__class__.__name__, self.is_required and ' required' or '', self.field_name, self.rows, self.cols, escape(data)) class HiddenField(FormField): @@ -274,7 +278,7 @@ class HiddenField(FormField): def render(self, data): return '' % \ - (FORM_FIELD_ID_PREFIX + self.field_name, self.field_name, escape(data)) + (self.get_id(), self.field_name, escape(data)) class CheckboxField(FormField): def __init__(self, field_name, checked_by_default=False): @@ -287,7 +291,7 @@ class CheckboxField(FormField): if data or (data is '' and self.checked_by_default): checked_html = ' checked="checked"' return '' % \ - (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, + (self.get_id(), self.__class__.__name__, self.field_name, checked_html) def html2python(data): @@ -306,8 +310,8 @@ class SelectField(FormField): def render(self, data): output = ['' % \ - (FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), self.field_name, value, selected_html), + (self.get_id() + '_' + str(i), self.field_name, value, selected_html), 'label': '' % \ - (FORM_FIELD_ID_PREFIX + self.field_name + '_' + str(i), display_name), + (self.get_id() + '_' + str(i), display_name), }) return RadioFieldRenderer(datalist, self.ul_class) @@ -412,7 +416,7 @@ class SelectMultipleField(SelectField): requires_data_list = True def render(self, data): output = [' ' % \ - (FORM_FIELD_ID_PREFIX, field_name, self.__class__.__name__, field_name, checked_html, - FORM_FIELD_ID_PREFIX, field_name, choice)) + output.append('
  • ' % \ + (self.get_id(), self.__class__.__name__, field_name, checked_html, + self.get_id(), choice)) output.append('') return '\n'.join(output) @@ -488,8 +492,7 @@ class FileUploadField(FormField): def render(self, data): return '' % \ - (FORM_FIELD_ID_PREFIX + self.field_name, self.__class__.__name__, - self.field_name) + (self.get_id(), self.__class__.__name__, self.field_name) def html2python(data): if data is None: From aae50fcce00ca37cf655e364faf7bbc78ea52f0b Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 9 Nov 2005 23:04:26 +0000 Subject: [PATCH 5/8] Fixed #758 -- HTML-escaped admin log items in admin index template. Thanks, Tom Tobin git-svn-id: http://code.djangoproject.com/svn/django/trunk@1149 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/templates/admin/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/django/contrib/admin/templates/admin/index.html b/django/contrib/admin/templates/admin/index.html index 9822ed92c2..03ae272909 100644 --- a/django/contrib/admin/templates/admin/index.html +++ b/django/contrib/admin/templates/admin/index.html @@ -57,7 +57,7 @@ {% else %} {% endif %} From 2ef937d0fdd607900f67ecc932309d87167ba89a Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 9 Nov 2005 23:37:41 +0000 Subject: [PATCH 6/8] Fixed #683 -- Lightly refactored meta.fields.Field to add an attname attribute. git-svn-id: http://code.djangoproject.com/svn/django/trunk@1150 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/contrib/admin/views/main.py | 20 +++--- django/core/meta/__init__.py | 108 ++++++++++++++--------------- django/core/meta/fields.py | 35 +++++++--- django/models/auth.py | 4 +- django/models/core.py | 4 +- 5 files changed, 94 insertions(+), 77 deletions(-) diff --git a/django/contrib/admin/views/main.py b/django/contrib/admin/views/main.py index c4bbee633f..726ffa5eb2 100644 --- a/django/contrib/admin/views/main.py +++ b/django/contrib/admin/views/main.py @@ -214,7 +214,7 @@ def change_list(request, app_label, module_name): ((lookup_val is None and ' class="selected"' or ''), get_query_string(params, {}, [lookup_kwarg]))) for val in lookup_choices: - pk_val = getattr(val, f.rel.to.pk.column) + pk_val = getattr(val, f.rel.to.pk.attname) filter_template.append('%r\n' % \ ((lookup_val == str(pk_val) and ' class="selected"' or ''), get_query_string(params, {lookup_kwarg: pk_val}), val)) @@ -378,7 +378,7 @@ def change_list(request, app_label, module_name): capfirst(f.verbose_name))) raw_template.append('\n\n') # Result rows. - pk = lookup_opts.pk.name + pk = lookup_opts.pk.attname for i, result in enumerate(result_list): raw_template.append('\n' % (i % 2 + 1)) for j, field_name in enumerate(lookup_opts.admin.list_display): @@ -393,7 +393,7 @@ def change_list(request, app_label, module_name): except ObjectDoesNotExist: result_repr = EMPTY_CHANGELIST_VALUE else: - field_val = getattr(result, f.column) + field_val = getattr(result, f.attname) # Foreign-key fields are special: Use the repr of the # related object. if isinstance(f.rel, meta.ManyToOne): @@ -785,7 +785,7 @@ def add_stage(request, app_label, module_name, show_delete=False, form_url='', p new_data.setlist(f.name, new_data[f.name].split(",")) manipulator.do_html2python(new_data) new_object = manipulator.save(new_data) - pk_value = getattr(new_object, opts.pk.column) + pk_value = getattr(new_object, opts.pk.attname) log.log_action(request.user.id, opts.get_content_type_id(), pk_value, repr(new_object), log.ADDITION) msg = 'The %s "%s" was added successfully.' % (opts.verbose_name, new_object) # Here, we distinguish between different save types by checking for @@ -887,7 +887,7 @@ def change_stage(request, app_label, module_name, object_id): new_data.setlist(f.name, new_data[f.name].split(",")) manipulator.do_html2python(new_data) new_object = manipulator.save(new_data) - pk_value = getattr(new_object, opts.pk.column) + pk_value = getattr(new_object, opts.pk.attname) # Construct the change message. change_message = [] @@ -925,19 +925,19 @@ def change_stage(request, app_label, module_name, object_id): new_data = {} obj = manipulator.original_object for f in opts.fields: - new_data.update(_get_flattened_data(f, getattr(obj, f.column))) + new_data.update(_get_flattened_data(f, getattr(obj, f.attname))) for f in opts.many_to_many: get_list_func = getattr(obj, 'get_%s_list' % f.rel.singular) if f.rel.raw_id_admin: - new_data[f.name] = ",".join([str(getattr(i, f.rel.to.pk.column)) for i in get_list_func()]) + new_data[f.name] = ",".join([str(getattr(i, f.rel.to.pk.attname)) for i in get_list_func()]) elif not f.rel.edit_inline: - new_data[f.name] = [getattr(i, f.rel.to.pk.column) for i in get_list_func()] + new_data[f.name] = [getattr(i, f.rel.to.pk.attname) for i in get_list_func()] for rel_obj, rel_field in inline_related_objects: var_name = rel_obj.object_name.lower() for i, rel_instance in enumerate(getattr(obj, 'get_%s_list' % opts.get_rel_object_method_name(rel_obj, rel_field))()): for f in rel_obj.fields: if f.editable and f != rel_field: - for k, v in _get_flattened_data(f, getattr(rel_instance, f.column)).items(): + for k, v in _get_flattened_data(f, getattr(rel_instance, f.attname)).items(): new_data['%s.%d.%s' % (var_name, i, k)] = v for f in rel_obj.many_to_many: new_data['%s.%d.%s' % (var_name, i, f.column)] = [j.id for j in getattr(rel_instance, 'get_%s_list' % f.rel.singular)()] @@ -1026,7 +1026,7 @@ def _get_deleted_objects(deleted_objects, perms_needed, user, obj, opts, current # Display a link to the admin page. nh(deleted_objects, current_depth, ['%s: %r' % \ (capfirst(rel_opts.verbose_name), rel_opts.app_label, rel_opts.module_name, - getattr(sub_obj, rel_opts.pk.column), sub_obj), []]) + getattr(sub_obj, rel_opts.pk.attname), sub_obj), []]) _get_deleted_objects(deleted_objects, perms_needed, user, sub_obj, rel_opts, current_depth+2) else: has_related_objs = False diff --git a/django/core/meta/__init__.py b/django/core/meta/__init__.py index 93f5aa4d0d..7fe23a4884 100644 --- a/django/core/meta/__init__.py +++ b/django/core/meta/__init__.py @@ -748,7 +748,7 @@ def method_init(opts, self, *args, **kwargs): except KeyError: try: # Object instance wasn't passed in -- must be an ID. - val = kwargs.pop(f.column) + val = kwargs.pop(f.attname) except KeyError: val = f.get_default() else: @@ -760,17 +760,17 @@ def method_init(opts, self, *args, **kwargs): val = getattr(rel_obj, f.rel.field_name) except AttributeError: raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj)) - setattr(self, f.column, val) + setattr(self, f.attname, val) else: - val = kwargs.pop(f.name, f.get_default()) - setattr(self, f.name, val) + val = kwargs.pop(f.attname, f.get_default()) + setattr(self, f.attname, val) if kwargs: raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0] for i, arg in enumerate(args): - setattr(self, opts.fields[i].column, arg) + setattr(self, opts.fields[i].attname, arg) def method_eq(opts, self, other): - return isinstance(other, self.__class__) and getattr(self, opts.pk.column) == getattr(other, opts.pk.column) + return isinstance(other, self.__class__) and getattr(self, opts.pk.attname) == getattr(other, opts.pk.attname) def method_save(opts, self): # Run any pre-save hooks. @@ -780,7 +780,7 @@ def method_save(opts, self): cursor = db.db.cursor() # First, try an UPDATE. If that doesn't update anything, do an INSERT. - pk_val = getattr(self, opts.pk.column) + pk_val = getattr(self, opts.pk.attname) pk_set = bool(pk_val) record_exists = True if pk_set: @@ -788,33 +788,33 @@ def method_save(opts, self): cursor.execute("SELECT 1 FROM %s WHERE %s=%%s LIMIT 1" % (opts.db_table, opts.pk.column), [pk_val]) # If it does already exist, do an UPDATE. if cursor.fetchone(): - db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), False)) for f in non_pks] + db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), False)) for f in non_pks] cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % (opts.db_table, - ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.column), + ','.join(['%s=%%s' % f.column for f in non_pks]), opts.pk.attname), db_values + [pk_val]) else: record_exists = False if not pk_set or not record_exists: field_names = [f.column for f in opts.fields if not isinstance(f, AutoField)] placeholders = ['%s'] * len(field_names) - db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.column), True)) for f in opts.fields if not isinstance(f, AutoField)] + db_values = [f.get_db_prep_save(f.pre_save(getattr(self, f.attname), True)) for f in opts.fields if not isinstance(f, AutoField)] if opts.order_with_respect_to: field_names.append('_order') # TODO: This assumes the database supports subqueries. placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \ (opts.db_table, opts.order_with_respect_to.column)) - db_values.append(getattr(self, opts.order_with_respect_to.column)) + db_values.append(getattr(self, opts.order_with_respect_to.attname)) cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % (opts.db_table, ','.join(field_names), ','.join(placeholders)), db_values) if opts.has_auto_field: - setattr(self, opts.pk.column, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column)) + setattr(self, opts.pk.attname, db.get_last_insert_id(cursor, opts.db_table, opts.pk.column)) db.db.commit() # Run any post-save hooks. if hasattr(self, '_post_save'): self._post_save() def method_delete(opts, self): - assert getattr(self, opts.pk.column) is not None, "%r can't be deleted because it doesn't have an ID." + assert getattr(self, opts.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID." # Run any pre-delete hooks. if hasattr(self, '_pre_delete'): self._pre_delete() @@ -833,15 +833,15 @@ def method_delete(opts, self): sub_obj.delete() for rel_opts, rel_field in opts.get_all_related_many_to_many_objects(): cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (rel_field.get_m2m_db_table(rel_opts), - self._meta.object_name.lower()), [getattr(self, opts.pk.column)]) + self._meta.object_name.lower()), [getattr(self, opts.pk.attname)]) for f in opts.many_to_many: cursor.execute("DELETE FROM %s WHERE %s_id=%%s" % (f.get_m2m_db_table(opts), self._meta.object_name.lower()), - [getattr(self, opts.pk.column)]) - cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.column), [getattr(self, opts.pk.column)]) + [getattr(self, opts.pk.attname)]) + cursor.execute("DELETE FROM %s WHERE %s=%%s" % (opts.db_table, opts.pk.column), [getattr(self, opts.pk.attname)]) db.db.commit() - setattr(self, opts.pk.column, None) + setattr(self, opts.pk.attname, None) for f in opts.fields: - if isinstance(f, FileField) and getattr(self, f.column): + if isinstance(f, FileField) and getattr(self, f.attname): file_name = getattr(self, 'get_%s_filename' % f.name)() # If the file exists and no other object of this type references it, # delete it from the filesystem. @@ -856,7 +856,7 @@ def method_get_next_in_order(opts, order_field, self): self._next_in_order_cache = opts.get_model_module().get_object(order_by=('_order',), where=['_order > (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column), '%s=%%s' % order_field.column], limit=1, - params=[getattr(self, opts.pk.column), getattr(self, order_field.column)]) + 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): @@ -864,7 +864,7 @@ def method_get_previous_in_order(opts, order_field, self): self._previous_in_order_cache = opts.get_model_module().get_object(order_by=('-_order',), where=['_order < (SELECT _order FROM %s WHERE %s=%%s)' % (opts.db_table, opts.pk.column), '%s=%%s' % order_field.column], limit=1, - params=[getattr(self, opts.pk.column), getattr(self, order_field.column)]) + params=[getattr(self, opts.pk.attname), getattr(self, order_field.attname)]) return self._previous_in_order_cache # RELATIONSHIP METHODS ##################### @@ -873,7 +873,7 @@ def method_get_previous_in_order(opts, order_field, self): def method_get_many_to_one(field_with_rel, self): cache_var = field_with_rel.get_cache_name() if not hasattr(self, cache_var): - val = getattr(self, field_with_rel.column) + val = getattr(self, field_with_rel.attname) mod = field_with_rel.rel.to.get_model_module() if val is None: raise getattr(mod, '%sDoesNotExist' % field_with_rel.rel.to.object_name) @@ -893,7 +893,7 @@ def method_get_many_to_many(field_with_rel, self): field_with_rel.get_m2m_db_table(self._meta), rel.pk.column, rel.object_name.lower(), self._meta.object_name.lower(), rel.get_order_sql('a')) cursor = db.db.cursor() - cursor.execute(sql, [getattr(self, self._meta.pk.column)]) + cursor.execute(sql, [getattr(self, self._meta.pk.attname)]) setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()]) return getattr(self, cache_var) @@ -914,7 +914,7 @@ def method_set_many_to_many(rel_field, self, id_list): rel = rel_field.rel.to m2m_table = rel_field.get_m2m_db_table(self._meta) cursor = db.db.cursor() - this_id = getattr(self, self._meta.pk.column) + this_id = getattr(self, self._meta.pk.attname) if ids_to_delete: sql = "DELETE FROM %s WHERE %s_id = %%s AND %s_id IN (%s)" % (m2m_table, self._meta.object_name.lower(), rel.object_name.lower(), ','.join(map(str, ids_to_delete))) cursor.execute(sql, [this_id]) @@ -941,11 +941,11 @@ def method_get_related(method_name, rel_mod, rel_field, self, **kwargs): # Handles adding related objects. # Example: Poll.add_choice() def method_add_related(rel_obj, rel_mod, rel_field, self, *args, **kwargs): - init_kwargs = dict(zip([f.name for f in rel_obj.fields if f != rel_field and not isinstance(f, AutoField)], args)) + init_kwargs = dict(zip([f.attname for f in rel_obj.fields if f != rel_field and not isinstance(f, AutoField)], args)) init_kwargs.update(kwargs) for f in rel_obj.fields: if isinstance(f, AutoField): - init_kwargs[f.name] = None + init_kwargs[f.attname] = None init_kwargs[rel_field.name] = self obj = rel_mod.Klass(**init_kwargs) obj.save() @@ -954,7 +954,7 @@ def method_add_related(rel_obj, rel_mod, rel_field, self, *args, **kwargs): # Handles related many-to-many object retrieval. # Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count() def method_get_related_many_to_many(method_name, opts, rel_mod, rel_field, self, **kwargs): - kwargs['%s__%s__exact' % (rel_field.name, opts.pk.name)] = getattr(self, opts.pk.column) + kwargs['%s__%s__exact' % (rel_field.name, opts.pk.name)] = getattr(self, opts.pk.attname) return getattr(rel_mod, method_name)(**kwargs) # Handles setting many-to-many related objects. @@ -963,7 +963,7 @@ def method_set_related_many_to_many(rel_opts, rel_field, self, id_list): id_list = map(int, id_list) # normalize to integers rel = rel_field.rel.to m2m_table = rel_field.get_m2m_db_table(rel_opts) - this_id = getattr(self, self._meta.pk.column) + this_id = getattr(self, self._meta.pk.attname) cursor = db.db.cursor() cursor.execute("DELETE FROM %s WHERE %s_id = %%s" % (m2m_table, rel.object_name.lower()), [this_id]) sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % (m2m_table, rel.object_name.lower(), rel_opts.object_name.lower()) @@ -992,7 +992,7 @@ def method_get_order(ordered_obj, self): def method_get_next_or_previous(get_object_func, field, is_next, self, **kwargs): kwargs.setdefault('where', []).append('%s %s %%s' % (field.column, (is_next and '>' or '<'))) - kwargs.setdefault('params', []).append(str(getattr(self, field.name))) + kwargs.setdefault('params', []).append(str(getattr(self, field.attname))) kwargs['order_by'] = [(not is_next and '-' or '') + field.name] kwargs['limit'] = 1 return get_object_func(**kwargs) @@ -1000,18 +1000,18 @@ def method_get_next_or_previous(get_object_func, field, is_next, self, **kwargs) # CHOICE-RELATED METHODS ################### def method_get_display_value(field, self): - value = getattr(self, field.column) + 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.name)) + return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname)) def method_get_file_url(field, self): - if getattr(self, field.name): # value is not blank + if getattr(self, field.attname): # value is not blank import urlparse - return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.name)).replace('\\', '/') + return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/') return '' def method_get_file_size(field, self): @@ -1036,7 +1036,7 @@ def method_save_file(field, self, filename, raw_contents): filename = filename[:dot_index] + '_' + filename[dot_index:] # Write the file to disk. - setattr(self, field.name, filename) + setattr(self, field.attname, filename) fp = open(getattr(self, 'get_%s_filename' % field.name)(), 'wb') fp.write(raw_contents) fp.close() @@ -1363,7 +1363,7 @@ def function_get_in_bulk(opts, klass, *args, **kwargs): kwargs['where'] = ["%s.%s IN (%s)" % (opts.db_table, 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.column), o) for o in obj_list]) + 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,) @@ -1433,8 +1433,8 @@ def manipulator_init(opts, add, change, self, obj_key=None): lookup_kwargs = opts.one_to_one_field.rel.limit_choices_to lookup_kwargs['%s__exact' % opts.one_to_one_field.rel.field_name] = obj_key _ = opts.one_to_one_field.rel.to.get_model_module().get_object(**lookup_kwargs) - params = dict([(f.column, f.get_default()) for f in opts.fields]) - params[opts.pk.column] = obj_key + params = dict([(f.attname, f.get_default()) for f in opts.fields]) + params[opts.pk.attname] = obj_key self.original_object = opts.get_model_module().Klass(**params) else: raise @@ -1470,12 +1470,12 @@ def manipulator_save(opts, klass, add, change, self, new_data): # Fields with auto_now_add are another special case; they should keep # their original value in the change stage. if change and getattr(f, 'auto_now_add', False): - params[f.column] = getattr(self.original_object, f.name) + params[f.attname] = getattr(self.original_object, f.attname) else: - params[f.column] = f.get_manipulator_new_data(new_data) + params[f.attname] = f.get_manipulator_new_data(new_data) if change: - params[opts.pk.column] = self.obj_key + params[opts.pk.attname] = self.obj_key # First, save the basic object itself. new_object = klass(**params) @@ -1490,7 +1490,7 @@ def manipulator_save(opts, klass, add, change, self, new_data): if change: self.fields_added, self.fields_changed, self.fields_deleted = [], [], [] for f in opts.fields: - if not f.primary_key and str(getattr(self.original_object, f.column)) != str(getattr(new_object, f.column)): + if not f.primary_key and str(getattr(self.original_object, f.attname)) != str(getattr(new_object, f.attname)): self.fields_changed.append(f.verbose_name) # Save many-to-many objects. Example: Poll.set_sites() @@ -1526,7 +1526,7 @@ def manipulator_save(opts, klass, add, change, self, new_data): old_rel_obj = None if rel_new_data[rel_opts.pk.name][0]: try: - old_rel_obj = getattr(self.original_object, 'get_%s' % opts.get_rel_object_method_name(rel_opts, rel_field))(**{'%s__exact' % rel_opts.pk.name: rel_new_data[rel_opts.pk.name][0]}) + old_rel_obj = getattr(self.original_object, 'get_%s' % opts.get_rel_object_method_name(rel_opts, rel_field))(**{'%s__exact' % rel_opts.pk.name: rel_new_data[rel_opts.pk.attname][0]}) except ObjectDoesNotExist: pass @@ -1540,16 +1540,16 @@ def manipulator_save(opts, klass, add, change, self, new_data): # given, use a default value. FileFields are also a special # case, because they'll be dealt with later. if change and (isinstance(f, FileField) or not f.editable): - if rel_new_data.get(rel_opts.pk.name, False) and rel_new_data[rel_opts.pk.name][0]: - params[f.column] = getattr(old_rel_obj, f.column) + if rel_new_data.get(rel_opts.pk.attname, False) and rel_new_data[rel_opts.pk.attname][0]: + params[f.attname] = getattr(old_rel_obj, f.attname) else: - params[f.column] = f.get_default() + params[f.attname] = f.get_default() elif f == rel_field: - params[f.column] = getattr(new_object, rel_field.rel.field_name) + params[f.attname] = getattr(new_object, rel_field.rel.field_name) elif add and isinstance(f, AutoField): - params[f.column] = None + params[f.attname] = None else: - params[f.column] = f.get_manipulator_new_data(rel_new_data, rel=True) + params[f.attname] = f.get_manipulator_new_data(rel_new_data, rel=True) # Related links are a special case, because we have to # manually set the "content_type_id" and "object_id" fields. if opts.has_related_links and rel_opts.module_name == 'relatedlinks': @@ -1566,7 +1566,7 @@ def manipulator_save(opts, klass, add, change, self, new_data): # Save any uploaded files. for f in rel_opts.fields: - if isinstance(f, FileField) and rel_new_data.get(f.name, False): + if isinstance(f, FileField) and rel_new_data.get(f.attname, False): f.save_file(rel_new_data, new_rel_obj, change and old_rel_obj or None, old_rel_obj is not None, rel=True) # Calculate whether any fields have changed. @@ -1575,19 +1575,19 @@ def manipulator_save(opts, klass, add, change, self, new_data): self.fields_added.append('%s "%r"' % (rel_opts.verbose_name, new_rel_obj)) else: for f in rel_opts.fields: - if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.column)) != str(getattr(new_rel_obj, f.column)): + if not f.primary_key and f != rel_field and str(getattr(old_rel_obj, f.attname)) != str(getattr(new_rel_obj, f.attname)): self.fields_changed.append('%s for %s "%r"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj)) # Save many-to-many objects. for f in rel_opts.many_to_many: if not f.rel.edit_inline: - was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.name]) + was_changed = getattr(new_rel_obj, 'set_%s' % f.name)(rel_new_data[f.attname]) if change and was_changed: self.fields_changed.append('%s for %s "%s"' % (f.verbose_name, rel_opts.verbose_name, new_rel_obj)) # If, in the change stage, all of the core fields were blank and # the primary key (ID) was provided, delete the item. - if change and all_cores_blank and rel_new_data.has_key(rel_opts.pk.name) and rel_new_data[rel_opts.pk.name][0]: + if change and all_cores_blank and rel_new_data.has_key(rel_opts.pk.attname) and rel_new_data[rel_opts.pk.attname][0]: new_rel_obj.delete() self.fields_deleted.append('%s "%r"' % (rel_opts.verbose_name, old_rel_obj)) @@ -1606,7 +1606,7 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat else: kwargs = {'%s__iexact' % field_name_list[0]: field_data} for f in field_list[1:]: - field_val = all_data.get(f.column, None) + field_val = all_data.get(f.attname, None) if field_val is None: # This will be caught by another validator, assuming the field # doesn't have blank=True. @@ -1620,7 +1620,7 @@ def manipulator_validator_unique_together(field_name_list, opts, self, field_dat old_obj = mod.get_object(**kwargs) except ObjectDoesNotExist: return - if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column): + if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname): pass else: raise validators.ValidationError, "%s with this %s already exists for the given %s." % \ @@ -1646,7 +1646,7 @@ def manipulator_validator_unique_for_date(from_field, date_field, opts, lookup_t except ObjectDoesNotExist: return else: - if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column): + if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname): pass else: format_string = (lookup_type == 'date') and '%B %d, %Y' or '%B %Y' diff --git a/django/core/meta/fields.py b/django/core/meta/fields.py index e23e6cda88..06403f0a31 100644 --- a/django/core/meta/fields.py +++ b/django/core/meta/fields.py @@ -55,10 +55,26 @@ def manipulator_validator_unique(f, opts, self, field_data, all_data): old_obj = opts.get_model_module().get_object(**{'%s__%s' % (f.name, lookup_type): field_data}) except ObjectDoesNotExist: return - if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.column) == getattr(old_obj, opts.pk.column): + if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname): return raise validators.ValidationError, "%s with this %s already exists." % (capfirst(opts.verbose_name), f.verbose_name) + +# A guide to Field parameters: +# +# * name: The name of the field specifed in the model. +# * attname: The attribute to use on the model object. This is the same as +# "name", except in the case of ForeignKeys, where "_id" is +# appended. +# * db_column: The db_column specified in the model (or None). +# * column: The database column for this field. This is the same as +# "attname", except if db_column is specified. +# +# Code that introspects values, or does other dynamic things, should use +# attname. For example, this gets the primary key value of object "obj": +# +# getattr(obj, opts.pk.attname) + class Field(object): # Designates whether empty strings fundamentally are allowed at the @@ -110,19 +126,20 @@ class Field(object): self.creation_counter = Field.creation_counter Field.creation_counter += 1 - # Set the name of the database column. - self.column = self.get_db_column() + self.attname, self.column = self.get_attname_column() def set_name(self, name): self.name = name self.verbose_name = self.verbose_name or name.replace('_', ' ') - self.column = self.get_db_column() + self.attname, self.column = self.get_attname_column() - def get_db_column(self): - if self.db_column: return self.db_column + def get_attname_column(self): if isinstance(self.rel, ManyToOne): - return '%s_id' % self.name - return self.name + attname = '%s_id' % self.name + else: + attname = self.name + column = self.db_column or attname + return attname, column def get_cache_name(self): return '_%s_cache' % self.name @@ -277,7 +294,7 @@ class Field(object): if self.choices: return first_choice + list(self.choices) rel_obj = self.rel.to - return first_choice + [(getattr(x, rel_obj.pk.column), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)] + return first_choice + [(getattr(x, rel_obj.pk.attname), repr(x)) for x in rel_obj.get_model_module().get_list(**self.rel.limit_choices_to)] class AutoField(Field): empty_strings_allowed = False diff --git a/django/models/auth.py b/django/models/auth.py index f06822b145..ef13fbea69 100644 --- a/django/models/auth.py +++ b/django/models/auth.py @@ -13,7 +13,7 @@ class Permission(meta.Model): ordering = ('package', 'codename') def __repr__(self): - return "%s | %s" % (self.package, self.name) + return "%s | %s" % (self.package_id, self.name) class Group(meta.Model): name = meta.CharField(_('name'), maxlength=80, unique=True) @@ -103,7 +103,7 @@ class User(meta.Model): def get_all_permissions(self): if not hasattr(self, '_perm_cache'): import sets - self._perm_cache = sets.Set(["%s.%s" % (p.package, p.codename) for p in self.get_permission_list()]) + self._perm_cache = sets.Set(["%s.%s" % (p.package_id, p.codename) for p in self.get_permission_list()]) self._perm_cache.update(self.get_group_permissions()) return self._perm_cache diff --git a/django/models/core.py b/django/models/core.py index 2c4399bfa7..1c52affdfd 100644 --- a/django/models/core.py +++ b/django/models/core.py @@ -48,11 +48,11 @@ class ContentType(meta.Model): unique_together = (('package', 'python_module_name'),) def __repr__(self): - return "%s | %s" % (self.package, self.name) + return "%s | %s" % (self.package_id, self.name) def get_model_module(self): "Returns the Python model module for accessing this type of content." - return __import__('django.models.%s.%s' % (self.package, self.python_module_name), '', '', ['']) + return __import__('django.models.%s.%s' % (self.package_id, self.python_module_name), '', '', ['']) def get_object_for_this_type(self, **kwargs): """ From 68397a3654caa4fd347b718cbd1b2abb08b6a7b7 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Wed, 9 Nov 2005 23:50:32 +0000 Subject: [PATCH 7/8] Fixed #686 -- django-admin.py sqlreset and sqlclear no longer assume the admin app is installed. git-svn-id: http://code.djangoproject.com/svn/django/trunk@1151 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/core/management.py | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/django/core/management.py b/django/core/management.py index ca94683cff..8a9fd4020d 100644 --- a/django/core/management.py +++ b/django/core/management.py @@ -108,7 +108,23 @@ def get_sql_delete(mod): cursor = db.db.cursor() except: cursor = None + + # Determine whether the admin log table exists. It only exists if the + # person has installed the admin app. + try: + if cursor is not None: + # Check whether the table exists. + cursor.execute("SELECT 1 FROM django_admin_log LIMIT 1") + except: + # The table doesn't exist, so it doesn't need to be dropped. + db.db.rollback() + admin_log_exists = False + else: + admin_log_exists = True + output = [] + + # Output DROP TABLE statements for standard application tables. for klass in mod._MODELS: try: if cursor is not None: @@ -119,6 +135,8 @@ def get_sql_delete(mod): db.db.rollback() else: output.append("DROP TABLE %s;" % klass._meta.db_table) + + # Output DROP TABLE statements for many-to-many tables. for klass in mod._MODELS: opts = klass._meta for f in opts.many_to_many: @@ -140,8 +158,9 @@ def get_sql_delete(mod): # Delete from the admin log. if cursor is not None: cursor.execute("SELECT id FROM content_types WHERE package = %s", [app_label]) - for row in cursor.fetchall(): - output.append("DELETE FROM django_admin_log WHERE content_type_id = %s;" % row[0]) + if admin_log_exists: + for row in cursor.fetchall(): + output.append("DELETE FROM django_admin_log WHERE content_type_id = %s;" % row[0]) # Close database connection explicitly, in case this output is being piped # directly into a database client, to avoid locking issues. From 28bce49e59a4dac2ef0fdb14af4ff5b09c5efc95 Mon Sep 17 00:00:00 2001 From: Adrian Holovaty Date: Thu, 10 Nov 2005 00:03:53 +0000 Subject: [PATCH 8/8] Added unit tests to disprove #724 git-svn-id: http://code.djangoproject.com/svn/django/trunk@1152 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- tests/testapp/models/lookup.py | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/tests/testapp/models/lookup.py b/tests/testapp/models/lookup.py index bb8f4aaad1..deb5d807ef 100644 --- a/tests/testapp/models/lookup.py +++ b/tests/testapp/models/lookup.py @@ -18,18 +18,24 @@ class Article(meta.Model): API_TESTS = """ # Create a couple of Articles. >>> from datetime import datetime ->>> a1 = articles.Article(id=None, headline='Article 1', pub_date=datetime(2005, 7, 26)) +>>> a1 = articles.Article(headline='Article 1', pub_date=datetime(2005, 7, 26)) >>> a1.save() ->>> a2 = articles.Article(id=None, headline='Article 2', pub_date=datetime(2005, 7, 27)) +>>> a2 = articles.Article(headline='Article 2', pub_date=datetime(2005, 7, 27)) >>> a2.save() ->>> a3 = articles.Article(id=None, headline='Article 3', pub_date=datetime(2005, 7, 27)) +>>> a3 = articles.Article(headline='Article 3', pub_date=datetime(2005, 7, 27)) >>> a3.save() ->>> a4 = articles.Article(id=None, headline='Article 4', pub_date=datetime(2005, 7, 28)) +>>> a4 = articles.Article(headline='Article 4', pub_date=datetime(2005, 7, 28)) >>> a4.save() +>>> a5 = articles.Article(headline='Article 5', pub_date=datetime(2005, 8, 1, 9, 0)) +>>> a5.save() +>>> a6 = articles.Article(headline='Article 6', pub_date=datetime(2005, 8, 1, 8, 0)) +>>> a6.save() # get_iterator() is just like get_list(), but it's a generator. >>> for a in articles.get_iterator(): ... print a.headline +Article 5 +Article 6 Article 4 Article 2 Article 3 @@ -42,7 +48,7 @@ Article 4 # get_count() returns the number of objects matching search criteria. >>> articles.get_count() -4L +6L >>> articles.get_count(pub_date__exact=datetime(2005, 7, 27)) 2L >>> articles.get_count(headline__startswith='Blah blah') @@ -61,10 +67,10 @@ Article 4 # dictionaries instead of object instances -- and you can specify which fields # you want to retrieve. >>> articles.get_values(fields=['headline']) -[{'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 1'}] +[{'headline': 'Article 5'}, {'headline': 'Article 6'}, {'headline': 'Article 4'}, {'headline': 'Article 2'}, {'headline': 'Article 3'}, {'headline': 'Article 1'}] >>> articles.get_values(pub_date__exact=datetime(2005, 7, 27), fields=['id']) [{'id': 2}, {'id': 3}] ->>> articles.get_values(fields=['id', 'headline']) == [{'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 1, 'headline': 'Article 1'}] +>>> articles.get_values(fields=['id', 'headline']) == [{'id': 5, 'headline': 'Article 5'}, {'id': 6, 'headline': 'Article 6'}, {'id': 4, 'headline': 'Article 4'}, {'id': 2, 'headline': 'Article 2'}, {'id': 3, 'headline': 'Article 3'}, {'id': 1, 'headline': 'Article 1'}] True # get_values_iterator() is just like get_values(), but it's a generator. @@ -72,6 +78,8 @@ True ... i = d.items() ... i.sort() ... i +[('headline', 'Article 5'), ('id', 5)] +[('headline', 'Article 6'), ('id', 6)] [('headline', 'Article 4'), ('id', 4)] [('headline', 'Article 2'), ('id', 2)] [('headline', 'Article 3'), ('id', 3)] @@ -84,4 +92,15 @@ Article 4 >>> a2.get_previous_by_pub_date() Article 1 +# get_next_by_FOO() and get_previous_by_FOO() take the time into account. +>>> a4.get_next_by_pub_date() +Article 6 +>>> a5.get_next_by_pub_date() +Traceback (most recent call last): + ... +ArticleDoesNotExist: Article does not exist for ... +>>> a6.get_previous_by_pub_date() +Article 4 +>>> a5.get_previous_by_pub_date() +Article 6 """