From 765abe8f7076d749d4dc763b35cb1023379a63c3 Mon Sep 17 00:00:00 2001 From: Robert Wittams Date: Wed, 14 Dec 2005 21:30:49 +0000 Subject: [PATCH] magic-removal: related classes methods now added git-svn-id: http://code.djangoproject.com/svn/django/branches/magic-removal@1650 bcc190cf-cafb-0310-a4f2-bffc1f526a37 --- django/db/models/__init__.py | 110 ++++++++++------------ django/db/models/fields.py | 38 +++++++- tests/modeltests/custom_methods/models.py | 3 +- 3 files changed, 88 insertions(+), 63 deletions(-) diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index 5bb8571261..90faee9f46 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -166,8 +166,9 @@ class RelatedObject(object): self.opts = model._meta self.field = field self.edit_inline = field.rel.edit_inline - self.name = opts.module_name - self.var_name = opts.object_name.lower() + self.name = self.opts.module_name + self.var_name = self.opts.object_name.lower() + def flatten_data(self, follow, obj=None): new_data = {} @@ -738,6 +739,7 @@ class Manager(object): # objects -- MySQL returns the values as strings, instead. return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()] + class ModelBase(type): "Metaclass for all models" def __new__(cls, name, bases, attrs): @@ -839,6 +841,16 @@ class ModelBase(type): new_class._prepare() + + for field in fields: + if field.rel: + other = field.rel.to + if isinstance(other, basestring): + print "string lookup" + else: + related = RelatedObject(other._meta, new_class, field) + field.contribute_to_related_class(other, related) + return new_class class Model(object): @@ -935,34 +947,7 @@ class Model(object): cls.get_next_in_order = curry(cls.__get_next_or_previous_in_order, is_next=True) cls.get_previous_in_order = curry(cls.__get_next_or_previous_in_order, is_next=False) - # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods - # for all related objects. - for related in cls._meta.get_all_related_objects(): - # Determine whether this related object is in another app. - # If it's in another app, the method names will have the app - # label prepended, and the add_BLAH() method will not be - # generated. - rel_obj_name = related.get_method_name_part() - if isinstance(related.field.rel, meta.OneToOne): - # Add "get_thingie" methods for one-to-one related objects. - # EXAMPLE: Place.get_restaurants_restaurant() - setattr(cls, 'get_%s' % rel_obj_name, curry(cls.__get_related, method_name='get_object', rel_class=related.model, rel_field=related.field)) - elif isinstance(related.field.rel, meta.ManyToOne): - # Add "get_thingie" methods for many-to-one related objects. - # EXAMPLE: Poll.get_choice() - setattr(cls, 'get_%s' % rel_obj_name, curry(cls.__get_related, method_name='get_object', rel_class=related.model, rel_field=related.field)) - # Add "get_thingie_count" methods for many-to-one related objects. - # EXAMPLE: Poll.get_choice_count() - setattr(cls, 'get_%s_count' % rel_obj_name, curry(cls.__get_related, method_name='get_count', rel_class=related.model, rel_field=related.field)) - # Add "get_thingie_list" methods for many-to-one related objects. - # EXAMPLE: Poll.get_choice_list() - setattr(cls, 'get_%s_list' % rel_obj_name, curry(cls.__get_related, method_name='get_list', rel_class=related.model, rel_field=related.field)) - # Add "add_thingie" methods for many-to-one related objects, - # but only for related objects that are in the same app. - # EXAMPLE: Poll.add_choice() - if related.opts.app_label == cls._meta.app_label: - setattr(cls, 'add_%s' % rel_obj_name, curry(cls.__add_related, rel_class=related.model, rel_field=related.field)) - + _prepare = classmethod(_prepare) def save(self): @@ -1079,7 +1064,7 @@ class Model(object): 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._default_manager.get_object(**kwargs) + return self.__class__._default_manager.get_object(**kwargs) def __get_next_or_previous_in_order(self, is_next): cachename = "__%s_order_cache" % is_next @@ -1193,7 +1178,7 @@ class Model(object): setattr(self, cache_var, [field_with_rel.rel.to(*row) for row in cursor.fetchall()]) return getattr(self, cache_var) - def __set_many_to_many_objects(self, field_with_rel, id_list): + def __set_many_to_many_objects(self, id_list, field_with_rel): current_ids = [obj.id for obj in self.__get_many_to_many_objects(field_with_rel)] ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), [] for current_id in current_ids: @@ -1230,12 +1215,12 @@ class Model(object): __set_many_to_many_objects.alters_data = True - def __get_related(self, method_name, rel_class, rel_field, **kwargs): + def _get_related(self, method_name, rel_class, rel_field, **kwargs): kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to._meta.pk.name)] = getattr(self, rel_field.rel.get_related_field().attname) kwargs.update(rel_field.rel.lookup_overrides) return getattr(rel_class._default_manager, method_name)(**kwargs) - def __add_related(self, rel_class, rel_field, *args, **kwargs): + def _add_related(self, rel_class, rel_field, *args, **kwargs): init_kwargs = dict(zip([f.attname for f in rel_class._meta.fields if f != rel_field and not isinstance(f, AutoField)], args)) init_kwargs.update(kwargs) for f in rel_class._meta.fields: @@ -1246,38 +1231,41 @@ class Model(object): obj.save() return obj - __add_related.alters_data = True + _add_related.alters_data = True + + + + + + # Handles related many-to-many object retrieval. + # Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count() + def _get_related_many_to_many(self, method_name, rel_class, rel_field, **kwargs): + kwargs['%s__%s__exact' % (rel_field.name, rel_class._meta.pk.name)] = getattr(self, rel_class._meta.pk.attname) + return getattr(rel_class._default_manager, method_name)(**kwargs) + + # Handles setting many-to-many related objects. + # Example: Album.set_songs() + def _set_related_many_to_many(self, rel_class, rel_field, 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.attname) + cursor = connection.cursor() + cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ + (backend.quote_name(m2m_table), + backend.quote_name(rel.object_name.lower() + '_id')), [this_id]) + sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ + (backend.quote_name(m2m_table), + backend.quote_name(rel.object_name.lower() + '_id'), + backend.quote_name(rel_opts.object_name.lower() + '_id')) + cursor.executemany(sql, [(this_id, i) for i in id_list]) + connection.commit() + ############################################ # HELPER FUNCTIONS (CURRIED MODEL METHODS) # ############################################ -# RELATIONSHIP METHODS ##################### - -# 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.attname) - return getattr(rel_mod.Klass._default_manager, method_name)(**kwargs) - -# Handles setting many-to-many related objects. -# Example: Album.set_songs() -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.attname) - cursor = connection.cursor() - cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ - (backend.quote_name(m2m_table), - backend.quote_name(rel.object_name.lower() + '_id')), [this_id]) - sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ - (backend.quote_name(m2m_table), - backend.quote_name(rel.object_name.lower() + '_id'), - backend.quote_name(rel_opts.object_name.lower() + '_id')) - cursor.executemany(sql, [(this_id, i) for i in id_list]) - connection.commit() - # ORDERING METHODS ######################### def method_set_order(ordered_obj, self, id_list): diff --git a/django/db/models/fields.py b/django/db/models/fields.py index 212dec036d..f9f6ca045c 100644 --- a/django/db/models/fields.py +++ b/django/db/models/fields.py @@ -741,6 +741,25 @@ class ForeignKey(Field): return {self.attname: choice_list[1][0]} return Field.flatten_data(self, follow, obj) + def contribute_to_related_class(self, cls, related): + rel_obj_name = related.get_method_name_part() + + # Add "get_thingie" methods for many-to-one related objects. + # EXAMPLE: Poll.get_choice() + setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related, method_name='get_object', rel_class=related.model, rel_field=related.field)) + # Add "get_thingie_count" methods for many-to-one related objects. + # EXAMPLE: Poll.get_choice_count() + setattr(cls, 'get_%s_count' % rel_obj_name, curry(cls._get_related, method_name='get_count', rel_class=related.model, rel_field=related.field)) + # Add "get_thingie_list" methods for many-to-one related objects. + # EXAMPLE: Poll.get_choice_list() + setattr(cls, 'get_%s_list' % rel_obj_name, curry(cls._get_related, method_name='get_list', rel_class=related.model, rel_field=related.field)) + # Add "add_thingie" methods for many-to-one related objects, + # but only for related objects that are in the same app. + # EXAMPLE: Poll.add_choice() + if related.opts.app_label == cls._meta.app_label: + setattr(cls, 'add_%s' % rel_obj_name, curry(cls._add_related, rel_class=related.model, rel_field=related.field)) + + class ManyToManyField(Field): def __init__(self, to, **kwargs): kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural) @@ -800,9 +819,18 @@ class ManyToManyField(Field): if not self.blank and not self.rel.edit_inline and not self.rel.raw_id_admin: choices_list = self.get_choices_default() if len(choices_list) == 1: - print self.name, choices_list[0][0] new_data[self.name] = [choices_list[0][0]] return new_data + + def contribute_to_related_class(self, cls, related): + rel_obj_name = related.get_method_name_part() + setattr(cls, 'get_%s' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_object', rel_class=related.model , rel_field=related.field)) + setattr(cls, 'get_%s_count' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_count', rel_class=related.model, rel_field=related.field)) + setattr(cls, 'get_%s_list' % rel_obj_name, curry(cls._get_related_many_to_many, method_name='get_list', rel_class=related.model, rel_field=related.field)) + if related.opts.app_label == cls._meta.app_label: + func = curry(cls._set_related_many_to_many, cls, related.field) + func.alters_data = True + setattr(cls, 'set_%s' % related.opts.module_name, func) class OneToOneField(IntegerField): def __init__(self, to, to_field=None, **kwargs): @@ -824,6 +852,14 @@ class OneToOneField(IntegerField): kwargs['primary_key'] = True IntegerField.__init__(self, **kwargs) + def contribute_to_related_class(self, cls, related): + rel_obj_name = related.get_method_name_part() + # Add "get_thingie" methods for one-to-one related objects. + # EXAMPLE: Place.get_restaurants_restaurant() + setattr(cls, 'get_%s' % rel_obj_name, + curry(cls._get_related, method_name='get_object', + rel_class=related.model, rel_field=related.field)) + class ManyToOne: def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, diff --git a/tests/modeltests/custom_methods/models.py b/tests/modeltests/custom_methods/models.py index bd1c5f7303..bd36562183 100644 --- a/tests/modeltests/custom_methods/models.py +++ b/tests/modeltests/custom_methods/models.py @@ -5,6 +5,7 @@ Any method you add to a model will be available to instances. """ from django.db import models +import datetime class Article(models.Model): headline = models.CharField(maxlength=100) @@ -17,7 +18,7 @@ class Article(models.Model): return self.pub_date == datetime.date.today() def get_articles_from_same_day_1(self): - return self.objects.get_list(id__ne=self.id, pub_date__exact=self.pub_date) + return Article.objects.get_list(id__ne=self.id, pub_date__exact=self.pub_date) def get_articles_from_same_day_2(self): """