Ticket #1020: mutually-referential.diff
File mutually-referential.diff, 67.6 KB (added by , 19 years ago) |
---|
-
django/contrib/admin/views/main.py
=== django/contrib/admin/views/main.py ==================================================================
552 552 nh = _nest_help # Bind to local variable for performance 553 553 if current_depth > 16: 554 554 return # Avoid recursing too deep. 555 o bjects_seen = []555 opts_seen = [] 556 556 for related in opts.get_all_related_objects(): 557 if related.opts in o bjects_seen:557 if related.opts in opts_seen: 558 558 continue 559 o bjects_seen.append(related.opts)559 opts_seen.append(related.opts) 560 560 rel_opts_name = related.get_method_name_part() 561 561 if isinstance(related.field.rel, meta.OneToOne): 562 562 try: … … 600 600 if not user.has_perm(p): 601 601 perms_needed.add(rel_opts.verbose_name) 602 602 for related in opts.get_all_related_many_to_many_objects(): 603 if related.opts in o bjects_seen:603 if related.opts in opts_seen: 604 604 continue 605 o bjects_seen.append(related.opts)605 opts_seen.append(related.opts) 606 606 rel_opts_name = related.get_method_name_part() 607 607 has_related_objs = False 608 608 for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)(): … … 625 625 if not user.has_perm(p): 626 626 perms_needed.add(related.opts.verbose_name) 627 627 628 from pprint import * 628 629 def delete_stage(request, app_label, module_name, object_id): 629 630 import sets 630 631 mod, opts = _get_mod_opts(app_label, module_name) … … 637 638 deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, strip_tags(str(obj))), []] 638 639 perms_needed = sets.Set() 639 640 _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1) 640 641 pprint(deleted_objects) 641 642 if request.POST: # The user has already confirmed the deletion. 642 643 if perms_needed: 643 644 raise PermissionDenied -
django/models/__init__.py
=== django/models/__init__.py ==================================================================
1 1 from django.core import meta 2 2 from django.utils.functional import curry 3 from django.core.meta.fields import RECURSIVE_RELATIONSHIP_CONSTANT 3 4 4 5 __all__ = ['auth', 'core'] 5 6 … … 12 13 # First, import all models so the metaclasses run. 13 14 modules = meta.get_installed_model_modules(__all__) 14 15 15 # Now, create the extra methods that we couldn't create earlier because16 # relationships hadn't been known until now.17 for mod in modules:18 for klass in mod._MODELS:19 16 20 # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods 21 # for all related objects. 22 for related in klass._meta.get_all_related_objects(): 23 # Determine whether this related object is in another app. 24 # If it's in another app, the method names will have the app 25 # label prepended, and the add_BLAH() method will not be 26 # generated. 27 rel_mod = related.opts.get_model_module() 28 rel_obj_name = related.get_method_name_part() 29 if isinstance(related.field.rel, meta.OneToOne): 30 # Add "get_thingie" methods for one-to-one related objects. 31 # EXAMPLE: Place.get_restaurants_restaurant() 32 func = curry(meta.method_get_related, 'get_object', rel_mod, related.field) 33 func.__doc__ = "Returns the associated `%s.%s` object." % (related.opts.app_label, related.opts.module_name) 34 setattr(klass, 'get_%s' % rel_obj_name, func) 35 elif isinstance(related.field.rel, meta.ManyToOne): 36 # Add "get_thingie" methods for many-to-one related objects. 37 # EXAMPLE: Poll.get_choice() 38 func = curry(meta.method_get_related, 'get_object', rel_mod, related.field) 39 func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % \ 40 (related.opts.app_label, related.opts.module_name) 41 setattr(klass, 'get_%s' % rel_obj_name, func) 42 # Add "get_thingie_count" methods for many-to-one related objects. 43 # EXAMPLE: Poll.get_choice_count() 44 func = curry(meta.method_get_related, 'get_count', rel_mod, related.field) 45 func.__doc__ = "Returns the number of associated `%s.%s` objects." % \ 46 (related.opts.app_label, related.opts.module_name) 47 setattr(klass, 'get_%s_count' % rel_obj_name, func) 48 # Add "get_thingie_list" methods for many-to-one related objects. 49 # EXAMPLE: Poll.get_choice_list() 50 func = curry(meta.method_get_related, 'get_list', rel_mod, related.field) 51 func.__doc__ = "Returns a list of associated `%s.%s` objects." % \ 52 (related.opts.app_label, related.opts.module_name) 53 setattr(klass, 'get_%s_list' % rel_obj_name, func) 54 # Add "add_thingie" methods for many-to-one related objects, 55 # but only for related objects that are in the same app. 56 # EXAMPLE: Poll.add_choice() 57 if related.opts.app_label == klass._meta.app_label: 58 func = curry(meta.method_add_related, related.opts, rel_mod, related.field) 59 func.alters_data = True 60 setattr(klass, 'add_%s' % rel_obj_name, func) 61 del func 62 del rel_obj_name, rel_mod, related # clean up 17 def fixup_modules(): 18 #resolve string referencing models 19 for mod in modules: 20 for klass in mod._MODELS: 21 for f in klass._meta.fields + klass._meta.many_to_many: 22 if f.rel and isinstance(f.rel.to, basestring): 23 #find the class 24 other = None 25 26 if f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT: 27 other = klass 28 else: 29 for cls in mod._MODELS: 30 if cls.__name__ == f.rel.to: 31 other = cls 32 break 33 assert other != None, "'to' must be either a model, the string '%s', or the name of a model" \ 34 % RECURSIVE_RELATIONSHIP_CONSTANT 35 f.rel.to = other._meta 36 f.name = f.name or (f.rel.to.object_name.lower() + '_' + f.rel.to.pk.name) 37 f.verbose_name = f.verbose_name or f.rel.to.verbose_name 38 f.rel.field_name = f.rel.field_name or f.rel.to.pk.name 63 39 64 # Do the same for all related many-to-many objects. 65 for related in klass._meta.get_all_related_many_to_many_objects(): 66 rel_mod = related.opts.get_model_module() 67 rel_obj_name = related.get_method_name_part() 68 setattr(klass, 'get_%s' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, related.field)) 69 setattr(klass, 'get_%s_count' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, related.field)) 70 setattr(klass, 'get_%s_list' % rel_obj_name, curry(meta.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, related.field)) 71 if related.opts.app_label == klass._meta.app_label: 72 func = curry(meta.method_set_related_many_to_many, related.opts, related.field) 40 # Now, create the extra methods that we couldn't create earlier because 41 # relationships might not have been known until now. 42 for mod in modules: 43 for klass in mod._MODELS: 44 #Allow fields to add stuff to the class and module 45 for f in klass._meta.fields + klass._meta.many_to_many: 46 f.contribute_to_class(klass._meta, klass._meta.get_model_module() , klass) 47 48 # Add "get_thingie", "get_thingie_count" and "get_thingie_list" methods 49 # for all related objects. 50 for related in klass._meta.get_all_related_objects(): 51 related.field.contribute_to_related_class( related, klass) 52 53 # Do the same for all related many-to-many objects. 54 for related in klass._meta.get_all_related_many_to_many_objects(): 55 related.field.contribute_to_related_class( related, klass) 56 57 # Add "set_thingie_order" and "get_thingie_order" methods for objects 58 # that are ordered with respect to this. 59 for obj in klass._meta.get_ordered_objects(): 60 func = curry(meta.method_set_order, obj) 61 func.__doc__ = "Sets the order of associated `%s.%s` objects to the given ID list." % (obj.app_label, obj.module_name) 73 62 func.alters_data = True 74 setattr(klass, 'set_%s' % related.opts.module_name, func) 75 del func 76 del rel_obj_name, rel_mod, related # clean up 63 setattr(klass, 'set_%s_order' % obj.object_name.lower(), func) 64 65 func = curry(meta.method_get_order, obj) 66 func.__doc__ = "Returns the order of associated `%s.%s` objects as a list of IDs." % (obj.app_label, obj.module_name) 67 setattr(klass, 'get_%s_order' % obj.object_name.lower(), func) 77 68 78 # Add "set_thingie_order" and "get_thingie_order" methods for objects 79 # that are ordered with respect to this. 80 for obj in klass._meta.get_ordered_objects(): 81 func = curry(meta.method_set_order, obj) 82 func.__doc__ = "Sets the order of associated `%s.%s` objects to the given ID list." % (obj.app_label, obj.module_name) 83 func.alters_data = True 84 setattr(klass, 'set_%s_order' % obj.object_name.lower(), func) 69 fixup_modules() 85 70 86 func = curry(meta.method_get_order, obj)87 func.__doc__ = "Returns the order of associated `%s.%s` objects as a list of IDs." % (obj.app_label, obj.module_name)88 setattr(klass, 'get_%s_order' % obj.object_name.lower(), func)89 del func, obj # clean up90 del klass # clean up91 del mod92 del modules93 94 71 # Expose get_app and get_module. 95 72 from django.core.meta import get_app, get_module -
django/core/meta/__init__.py
=== django/core/meta/__init__.py ==================================================================
167 167 self.edit_inline = field.rel.edit_inline 168 168 self.name = opts.module_name 169 169 self.var_name = opts.object_name.lower() 170 self.klass = None 170 171 172 def get_class(self): 173 if not self.klass: 174 self.klass = self.opts.get_model_module().Klass 175 return self.klass 176 171 177 def flatten_data(self, follow, obj=None): 172 178 new_data = {} 173 179 rel_instances = self.get_list(obj) … … 739 745 attrs['get_next_in_order'] = curry(method_get_next_in_order, opts, opts.order_with_respect_to) 740 746 attrs['get_previous_in_order'] = curry(method_get_previous_in_order, opts, opts.order_with_respect_to) 741 747 742 for f in opts.fields: 743 # If the object has a relationship to itself, as designated by 744 # RECURSIVE_RELATIONSHIP_CONSTANT, create that relationship formally. 745 if f.rel and f.rel.to == RECURSIVE_RELATIONSHIP_CONSTANT: 746 f.rel.to = opts 747 f.name = f.name or (f.rel.to.object_name.lower() + '_' + f.rel.to.pk.name) 748 f.verbose_name = f.verbose_name or f.rel.to.verbose_name 749 f.rel.field_name = f.rel.field_name or f.rel.to.pk.name 750 # Add "get_thingie" methods for many-to-one related objects. 751 # EXAMPLES: Choice.get_poll(), Story.get_dateline() 752 if isinstance(f.rel, ManyToOne): 753 func = curry(method_get_many_to_one, f) 754 func.__doc__ = "Returns the associated `%s.%s` object." % (f.rel.to.app_label, f.rel.to.module_name) 755 attrs['get_%s' % f.name] = func 756 757 for f in opts.many_to_many: 758 # Add "get_thingie" methods for many-to-many related objects. 759 # EXAMPLES: Poll.get_site_list(), Story.get_byline_list() 760 func = curry(method_get_many_to_many, f) 761 func.__doc__ = "Returns a list of associated `%s.%s` objects." % (f.rel.to.app_label, f.rel.to.module_name) 762 attrs['get_%s_list' % f.rel.singular] = func 763 # Add "set_thingie" methods for many-to-many related objects. 764 # EXAMPLES: Poll.set_sites(), Story.set_bylines() 765 func = curry(method_set_many_to_many, f) 766 func.__doc__ = "Resets this object's `%s.%s` list to the given list of IDs. Note that it doesn't check whether the given IDs are valid." % (f.rel.to.app_label, f.rel.to.module_name) 767 func.alters_data = True 768 attrs['set_%s' % f.name] = func 769 748 770 749 # Create the class, because we need it to use in currying. 771 750 new_class = type.__new__(cls, name, bases, attrs) 772 751 … … 802 781 if opts.get_latest_by: 803 782 new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception) 804 783 805 for f in opts.fields:806 784 #TODO : change this into a virtual function so that user defined fields will be able to add methods to module or class. 807 if f.choices:808 # Add "get_thingie_display" method to get human-readable value.809 func = curry(method_get_display_value, f)810 setattr(new_class, 'get_%s_display' % f.name, func)811 if isinstance(f, DateField) or isinstance(f, DateTimeField):812 # Add "get_next_by_thingie" and "get_previous_by_thingie" methods813 # for all DateFields and DateTimeFields that cannot be null.814 # EXAMPLES: Poll.get_next_by_pub_date(), Poll.get_previous_by_pub_date()815 if not f.null:816 setattr(new_class, 'get_next_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, True))817 setattr(new_class, 'get_previous_by_%s' % f.name, curry(method_get_next_or_previous, new_mod.get_object, opts, f, False))818 # Add "get_thingie_list" for all DateFields and DateTimeFields.819 # EXAMPLE: polls.get_pub_date_list()820 func = curry(function_get_date_list, opts, f)821 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'." % name822 setattr(new_mod, 'get_%s_list' % f.name, func)823 785 824 elif isinstance(f, FileField): 825 setattr(new_class, 'get_%s_filename' % f.name, curry(method_get_file_filename, f)) 826 setattr(new_class, 'get_%s_url' % f.name, curry(method_get_file_url, f)) 827 setattr(new_class, 'get_%s_size' % f.name, curry(method_get_file_size, f)) 828 func = curry(method_save_file, f) 829 func.alters_data = True 830 setattr(new_class, 'save_%s_file' % f.name, func) 831 if isinstance(f, ImageField): 832 # Add get_BLAH_width and get_BLAH_height methods, but only 833 # if the image field doesn't have width and height cache 834 # fields. 835 if not f.width_field: 836 setattr(new_class, 'get_%s_width' % f.name, curry(method_get_image_width, f)) 837 if not f.height_field: 838 setattr(new_class, 'get_%s_height' % f.name, curry(method_get_image_height, f)) 839 786 840 787 # Add the class itself to the new module we've created. 841 788 new_mod.__dict__[name] = new_class 842 789 … … 938 885 def __repr__(self): 939 886 return '<%s object>' % self.__class__.__name__ 940 887 888 941 889 ############################################ 942 890 # HELPER FUNCTIONS (CURRIED MODEL METHODS) # 943 891 ############################################ 944 892 893 894 945 895 # CORE METHODS ############################# 946 896 947 897 def method_init(opts, self, *args, **kwargs): … … 966 916 try: 967 917 val = getattr(rel_obj, f.rel.get_related_field().attname) 968 918 except AttributeError: 969 raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj) )919 raise TypeError, "Invalid value: %r should be a %s instance, not a %s" % (f.name, f.rel.to, type(rel_obj) ) 970 920 setattr(self, f.attname, val) 971 921 else: 972 922 val = kwargs.pop(f.attname, f.get_default()) … … 1031 981 if hasattr(self, '_post_save'): 1032 982 self._post_save() 1033 983 1034 def method_delete(opts, self): 1035 assert getattr(self, opts.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID." 1036 # Run any pre-delete hooks. 1037 if hasattr(self, '_pre_delete'): 1038 self._pre_delete() 1039 cursor = db.db.cursor() 984 985 def add_sub_objects(opts, instance, seen_objs): 986 pk_val = str(getattr(instance, opts.pk.attname)) 987 988 if (opts,pk_val) in seen_objs: 989 return 990 seen_objs[(opts,pk_val)] = instance 991 1040 992 for related in opts.get_all_related_objects(): 1041 993 rel_opts_name = related.get_method_name_part() 1042 994 if isinstance(related.field.rel, OneToOne): 1043 995 try: 1044 sub_obj = getattr( self, 'get_%s' % rel_opts_name)()996 sub_obj = getattr(instance, 'get_%s' % rel_opts_name)() 1045 997 except ObjectDoesNotExist: 1046 998 pass 1047 999 else: 1048 sub_obj.delete()1000 add_sub_objects(related.opts, sub_obj, seen_objs) 1049 1001 else: 1050 for sub_obj in getattr(self, 'get_%s_list' % rel_opts_name)(): 1051 sub_obj.delete() 1052 for related in opts.get_all_related_many_to_many_objects(): 1002 for sub_obj in getattr(instance, 'get_%s_list' % rel_opts_name)(): 1003 add_sub_objects(related.opts, sub_obj, seen_objs) 1004 1005 def cmp_opts(x, y): 1006 for field in x.fields: 1007 if field.rel and field.null and field.rel.to == y: 1008 return -1 1009 for field in y.fields: 1010 if field.rel and field.null and field.rel.to == x: 1011 return 1 1012 return 0 1013 1014 def method_delete(opts, self, seen_objs = None): 1015 assert getattr(self, opts.pk.attname) is not None, "%r can't be deleted because it doesn't have an ID." 1016 1017 if seen_objs == None: 1018 seen_objs = {} 1019 cursor = db.db.cursor() 1020 add_sub_objects(opts, self, seen_objs) 1021 1022 seen_opts = set([opts for opts,pk in seen_objs.keys()]) 1023 opts_order = list(seen_opts) 1024 opts_order.sort(cmp=cmp_opts) 1025 1026 seen_tups = [ (s_opts, pk_val, instance) for (s_opts, pk_val),instance in seen_objs.items() ] 1027 seen_tups.sort(key=lambda x: opts_order.index(x[0])) 1028 1029 for s_opts, pk_val, instance in seen_tups: 1030 1031 # Run any pre-delete hooks. 1032 if hasattr(instance, '_pre_delete'): 1033 instance._pre_delete() 1034 1035 for related in s_opts.get_all_related_many_to_many_objects(): 1036 cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ 1037 (db.db.quote_name(related.field.get_m2m_db_table(related.opts)), 1038 db.db.quote_name(s_opts.object_name.lower() + '_id')), 1039 [pk_val]) 1040 for f in s_opts.many_to_many: 1041 cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ 1042 (db.db.quote_name(f.get_m2m_db_table(s_opts)), 1043 db.db.quote_name(s_opts.object_name.lower() + '_id')), 1044 [pk_val]) 1045 1046 for field in s_opts.fields: 1047 if field.rel and field.null and field.rel.to in seen_opts: 1048 cursor.execute("UPDATE %s SET %s = NULL WHERE %s =%%s" % \ 1049 ( db.db.quote_name(s_opts.db_table), 1050 db.db.quote_name(field.column), 1051 db.db.quote_name(s_opts.pk.column)), 1052 [pk_val] ) 1053 1054 seen_tups.reverse() 1055 1056 for s_opts, pk_val, instance in seen_tups: 1053 1057 cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ 1054 (db.db.quote_name(related.field.get_m2m_db_table(related.opts)), 1055 db.db.quote_name(self._meta.object_name.lower() + '_id')), [getattr(self, opts.pk.attname)]) 1056 for f in opts.many_to_many: 1057 cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ 1058 (db.db.quote_name(f.get_m2m_db_table(opts)), 1059 db.db.quote_name(self._meta.object_name.lower() + '_id')), 1060 [getattr(self, opts.pk.attname)]) 1061 cursor.execute("DELETE FROM %s WHERE %s=%%s" % \ 1062 (db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column)), 1063 [getattr(self, opts.pk.attname)]) 1058 (db.db.quote_name(s_opts.db_table), db.db.quote_name(s_opts.pk.column)), 1059 [pk_val]) 1060 1061 1062 setattr(self, opts.pk.attname, None) 1063 for f in s_opts.fields: 1064 if isinstance(f, FileField) and getattr(self, f.attname): 1065 file_name = getattr(instance, 'get_%s_filename' % f.name)() 1066 # If the file exists and no other object of this type references it, 1067 # delete it from the filesystem. 1068 if os.path.exists(file_name) and not s_opts.get_model_module().get_list(**{'%s__exact' % f.name: getattr(self, f.name)}): 1069 os.remove(file_name) 1070 # Run any post-delete hooks. 1071 if hasattr(instance, '_post_delete'): 1072 instance._post_delete() 1073 1064 1074 db.db.commit() 1065 setattr(self, opts.pk.attname, None) 1066 for f in opts.fields: 1067 if isinstance(f, FileField) and getattr(self, f.attname): 1068 file_name = getattr(self, 'get_%s_filename' % f.name)() 1069 # If the file exists and no other object of this type references it, 1070 # delete it from the filesystem. 1071 if os.path.exists(file_name) and not opts.get_model_module().get_list(**{'%s__exact' % f.name: getattr(self, f.name)}): 1072 os.remove(file_name) 1073 # Run any post-delete hooks. 1074 if hasattr(self, '_post_delete'): 1075 self._post_delete() 1075 1076 1076 1077 1077 def method_get_next_in_order(opts, order_field, self): 1078 1078 if not hasattr(self, '_next_in_order_cache'): … … 1113 1113 setattr(self, cache_var, retrieved_obj) 1114 1114 return getattr(self, cache_var) 1115 1115 1116 # Handles getting many-to-many related objects.1117 # Example: Poll.get_site_list()1118 def method_get_many_to_many(field_with_rel, self):1119 rel = field_with_rel.rel.to1120 cache_var = '_%s_cache' % field_with_rel.name1121 if not hasattr(self, cache_var):1122 mod = rel.get_model_module()1123 sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \1124 (','.join(['a.%s' % db.db.quote_name(f.column) for f in rel.fields]),1125 db.db.quote_name(rel.db_table),1126 db.db.quote_name(field_with_rel.get_m2m_db_table(self._meta)),1127 db.db.quote_name(rel.pk.column),1128 db.db.quote_name(rel.object_name.lower() + '_id'),1129 db.db.quote_name(self._meta.object_name.lower() + '_id'), rel.get_order_sql('a'))1130 cursor = db.db.cursor()1131 cursor.execute(sql, [getattr(self, self._meta.pk.attname)])1132 setattr(self, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()])1133 return getattr(self, cache_var)1134 1135 # Handles setting many-to-many relationships.1136 # Example: Poll.set_sites()1137 def method_set_many_to_many(rel_field, self, id_list):1138 current_ids = [obj.id for obj in method_get_many_to_many(rel_field, self)]1139 ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), []1140 for current_id in current_ids:1141 if current_id in id_list:1142 del ids_to_add[current_id]1143 else:1144 ids_to_delete.append(current_id)1145 ids_to_add = ids_to_add.keys()1146 # Now ids_to_add is a list of IDs to add, and ids_to_delete is a list of IDs to delete.1147 if not ids_to_delete and not ids_to_add:1148 return False # No change1149 rel = rel_field.rel.to1150 m2m_table = rel_field.get_m2m_db_table(self._meta)1151 cursor = db.db.cursor()1152 this_id = getattr(self, self._meta.pk.attname)1153 if ids_to_delete:1154 sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \1155 (db.db.quote_name(m2m_table),1156 db.db.quote_name(self._meta.object_name.lower() + '_id'),1157 db.db.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete)))1158 cursor.execute(sql, [this_id])1159 if ids_to_add:1160 sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \1161 (db.db.quote_name(m2m_table),1162 db.db.quote_name(self._meta.object_name.lower() + '_id'),1163 db.db.quote_name(rel.object_name.lower() + '_id'))1164 cursor.executemany(sql, [(this_id, i) for i in ids_to_add])1165 db.db.commit()1166 try:1167 delattr(self, '_%s_cache' % rel_field.name) # clear cache, if it exists1168 except AttributeError:1169 pass1170 return True1171 1172 # Handles related-object retrieval.1173 # Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count()1174 def method_get_related(method_name, rel_mod, rel_field, self, **kwargs):1175 if self._meta.has_related_links and rel_mod.Klass._meta.module_name == 'relatedlinks':1176 kwargs['object_id__exact'] = getattr(self, rel_field.rel.field_name)1177 else:1178 kwargs['%s__%s__exact' % (rel_field.name, rel_field.rel.to.pk.name)] = getattr(self, rel_field.rel.get_related_field().attname)1179 kwargs.update(rel_field.rel.lookup_overrides)1180 return getattr(rel_mod, method_name)(**kwargs)1181 1182 # Handles adding related objects.1183 # Example: Poll.add_choice()1184 def method_add_related(rel_obj, rel_mod, rel_field, self, *args, **kwargs):1185 init_kwargs = dict(zip([f.attname for f in rel_obj.fields if f != rel_field and not isinstance(f, AutoField)], args))1186 init_kwargs.update(kwargs)1187 for f in rel_obj.fields:1188 if isinstance(f, AutoField):1189 init_kwargs[f.attname] = None1190 init_kwargs[rel_field.name] = self1191 obj = rel_mod.Klass(**init_kwargs)1192 obj.save()1193 return obj1194 1195 # Handles related many-to-many object retrieval.1196 # Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count()1197 def method_get_related_many_to_many(method_name, opts, rel_mod, rel_field, self, **kwargs):1198 kwargs['%s__%s__exact' % (rel_field.name, opts.pk.name)] = getattr(self, opts.pk.attname)1199 return getattr(rel_mod, method_name)(**kwargs)1200 1201 # Handles setting many-to-many related objects.1202 # Example: Album.set_songs()1203 def method_set_related_many_to_many(rel_opts, rel_field, self, id_list):1204 id_list = map(int, id_list) # normalize to integers1205 rel = rel_field.rel.to1206 m2m_table = rel_field.get_m2m_db_table(rel_opts)1207 this_id = getattr(self, self._meta.pk.attname)1208 cursor = db.db.cursor()1209 cursor.execute("DELETE FROM %s WHERE %s = %%s" % \1210 (db.db.quote_name(m2m_table),1211 db.db.quote_name(rel.object_name.lower() + '_id')), [this_id])1212 sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \1213 (db.db.quote_name(m2m_table),1214 db.db.quote_name(rel.object_name.lower() + '_id'),1215 db.db.quote_name(rel_opts.object_name.lower() + '_id'))1216 cursor.executemany(sql, [(this_id, i) for i in id_list])1217 db.db.commit()1218 1219 1116 # ORDERING METHODS ######################### 1220 1117 1221 1118 def method_set_order(ordered_obj, self, id_list): … … 1241 1138 cursor.execute(sql, [rel_val]) 1242 1139 return [r[0] for r in cursor.fetchall()] 1243 1140 1244 # DATE-RELATED METHODS #####################1245 1141 1246 def method_get_next_or_previous(get_object_func, opts, field, is_next, self, **kwargs):1247 op = is_next and '>' or '<'1248 kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \1249 (db.db.quote_name(field.column), op, db.db.quote_name(field.column),1250 db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), op))1251 param = str(getattr(self, field.attname))1252 kwargs.setdefault('params', []).extend([param, param, getattr(self, opts.pk.attname)])1253 kwargs['order_by'] = [(not is_next and '-' or '') + field.name, (not is_next and '-' or '') + opts.pk.name]1254 kwargs['limit'] = 11255 return get_object_func(**kwargs)1256 1257 # CHOICE-RELATED METHODS ###################1258 1259 def method_get_display_value(field, self):1260 value = getattr(self, field.attname)1261 return dict(field.choices).get(value, value)1262 1263 # FILE-RELATED METHODS #####################1264 1265 def method_get_file_filename(field, self):1266 return os.path.join(settings.MEDIA_ROOT, getattr(self, field.attname))1267 1268 def method_get_file_url(field, self):1269 if getattr(self, field.attname): # value is not blank1270 import urlparse1271 return urlparse.urljoin(settings.MEDIA_URL, getattr(self, field.attname)).replace('\\', '/')1272 return ''1273 1274 def method_get_file_size(field, self):1275 return os.path.getsize(method_get_file_filename(field, self))1276 1277 def method_save_file(field, self, filename, raw_contents):1278 directory = field.get_directory_name()1279 try: # Create the date-based directory if it doesn't exist.1280 os.makedirs(os.path.join(settings.MEDIA_ROOT, directory))1281 except OSError: # Directory probably already exists.1282 pass1283 filename = field.get_filename(filename)1284 1285 # If the filename already exists, keep adding an underscore to the name of1286 # the file until the filename doesn't exist.1287 while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)):1288 try:1289 dot_index = filename.rindex('.')1290 except ValueError: # filename has no dot1291 filename += '_'1292 else:1293 filename = filename[:dot_index] + '_' + filename[dot_index:]1294 1295 # Write the file to disk.1296 setattr(self, field.attname, filename)1297 fp = open(getattr(self, 'get_%s_filename' % field.name)(), 'wb')1298 fp.write(raw_contents)1299 fp.close()1300 1301 # Save the width and/or height, if applicable.1302 if isinstance(field, ImageField) and (field.width_field or field.height_field):1303 from django.utils.images import get_image_dimensions1304 width, height = get_image_dimensions(getattr(self, 'get_%s_filename' % field.name)())1305 if field.width_field:1306 setattr(self, field.width_field, width)1307 if field.height_field:1308 setattr(self, field.height_field, height)1309 1310 # Save the object, because it has changed.1311 self.save()1312 1313 # IMAGE FIELD METHODS ######################1314 1315 def method_get_image_width(field, self):1316 return _get_image_dimensions(field, self)[0]1317 1318 def method_get_image_height(field, self):1319 return _get_image_dimensions(field, self)[1]1320 1321 def _get_image_dimensions(field, self):1322 cachename = "__%s_dimensions_cache" % field.name1323 if not hasattr(self, cachename):1324 from django.utils.images import get_image_dimensions1325 fname = getattr(self, "get_%s_filename" % field.name)()1326 setattr(self, cachename, get_image_dimensions(fname))1327 return getattr(self, cachename)1328 1329 1142 ############################################## 1330 1143 # HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) # 1331 1144 ############################################## … … 1659 1472 kwargs['limit'] = 1 1660 1473 return function_get_object(opts, klass, does_not_exist_exception, **kwargs) 1661 1474 1662 def function_get_date_list(opts, field, *args, **kwargs):1663 from django.core.db.typecasts import typecast_timestamp1664 kind = args and args[0] or kwargs['kind']1665 assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'."1666 order = 'ASC'1667 if kwargs.has_key('_order'):1668 order = kwargs['_order']1669 del kwargs['_order']1670 assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'"1671 kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise.1672 if field.null:1673 kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \1674 (db.db.quote_name(opts.db_table), db.db.quote_name(field.column)))1675 select, sql, params = function_get_sql_clause(opts, **kwargs)1676 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)1677 cursor = db.db.cursor()1678 cursor.execute(sql, params)1679 # We have to manually run typecast_timestamp(str()) on the results, because1680 # MySQL doesn't automatically cast the result of date functions as datetime1681 # objects -- MySQL returns the values as strings, instead.1682 return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]1683 1684 1475 ################################### 1685 1476 # HELPER FUNCTIONS (MANIPULATORS) # 1686 1477 ################################### … … 1892 1683 # If, in the change stage, all of the core fields were blank and 1893 1684 # the primary key (ID) was provided, delete the item. 1894 1685 if change and all_cores_blank and old_rel_obj: 1895 new_rel_obj.delete( )1686 new_rel_obj.delete(seen_objs=[(opts, str(self.obj_key))]) 1896 1687 self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj)) 1897 1688 1898 1689 # Save the order, if applicable. -
django/core/meta/fields.py
=== django/core/meta/fields.py ==================================================================
55 55 old_obj = opts.get_model_module().get_object(**{lookup_type: field_data}) 56 56 except ObjectDoesNotExist: 57 57 return 58 if hasattr(self, 'original_object') and getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname): 58 if hasattr(self, 'original_object') and \ 59 getattr(self.original_object, opts.pk.attname) == getattr(old_obj, opts.pk.attname): 59 60 return 60 raise validators.ValidationError, _("%(optname)s with this %(fieldname)s already exists.") % {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name} 61 raise validators.ValidationError, \ 62 _("%(optname)s with this %(fieldname)s already exists.") % \ 63 {'optname': capfirst(opts.verbose_name), 'fieldname': f.verbose_name} 61 64 62 65 class BoundField(object): 63 66 def __init__(self, field, field_mapping, original): … … 282 285 core_field_names.extend(f.get_manipulator_field_names(name_prefix)) 283 286 # Now, if there are any, add the validator to this FormField. 284 287 if core_field_names: 285 params['validator_list'].append(validators.RequiredIfOtherFieldsGiven(core_field_names, gettext_lazy("This field is required."))) 288 params['validator_list'].append( 289 validators.RequiredIfOtherFieldsGiven(core_field_names, 290 gettext_lazy("This field is required."))) 286 291 287 292 # BooleanFields (CheckboxFields) are a special case. They don't take 288 293 # is_required or validator_list. … … 344 349 def bind(self, fieldmapping, original, bound_field_class=BoundField): 345 350 return bound_field_class(self, fieldmapping, original) 346 351 352 def contribute_to_class(self, opts, new_mod, new_class ): 353 if self.choices: 354 # Add "get_thingie_display" method to get human-readable value. 355 setattr(new_class, 'get_%s_display' % self.name, self.method_get_display_value) 356 357 def method_get_display_value(self, instance): 358 value = getattr(instance, self.attname) 359 return dict(self.choices).get(value, value) 347 360 class AutoField(Field): 348 361 empty_strings_allowed = False 349 362 def __init__(self, *args, **kwargs): … … 378 391 class CommaSeparatedIntegerField(CharField): 379 392 def get_manipulator_field_objs(self): 380 393 return [formfields.CommaSeparatedIntegerField] 381 394 382 395 class DateField(Field): 383 396 empty_strings_allowed = False 384 397 def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs): … … 421 434 val = self._get_val_from_obj(obj) 422 435 return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')} 423 436 437 def contribute_to_class(self, opts, new_mod, new_class ): 438 super(DateField,self).contribute_to_class(opts, new_mod, new_class) 439 # Add "get_next_by_thingie" and "get_previous_by_thingie" methods 440 # for all DateFields and DateTimeFields that cannot be null. 441 # EXAMPLES: Poll.get_next_by_pub_date(), Poll.get_previous_by_pub_date() 442 if not self.null: 443 setattr(new_class, 'get_next_by_%s' % self.name, 444 curry(self.method_get_next, new_mod.get_object, opts)) 445 setattr(new_class, 'get_previous_by_%s' % self.name, 446 curry(self.method_get_previous, new_mod.get_object, opts)) 447 # Add "get_thingie_list" for all DateFields and DateTimeFields. 448 # EXAMPLE: polls.get_pub_date_list() 449 func = curry(self.function_get_date_list, opts) 450 func.__doc__ = "Returns a list of days, months or years (as datetime.datetime objects)" \ 451 "in which %s objects are available. The first parameter ('kind') must be" \ 452 " one of 'year', 'month' or 'day'." % opts.object_name 453 setattr(new_mod, 'get_%s_list' % self.name, func) 454 455 def function_get_date_list(self, opts, *args, **kwargs): 456 from django.core.db.typecasts import typecast_timestamp 457 from django.core import db 458 kind = args and args[0] or kwargs['kind'] 459 assert kind in ("month", "year", "day"), "'kind' must be one of 'year', 'month' or 'day'." 460 order = 'ASC' 461 if kwargs.has_key('_order'): 462 order = kwargs['_order'] 463 del kwargs['_order'] 464 assert order in ('ASC', 'DESC'), "'order' must be either 'ASC' or 'DESC'" 465 kwargs['order_by'] = [] # Clear this because it'll mess things up otherwise. 466 if field.null: 467 kwargs.setdefault('where', []).append('%s.%s IS NOT NULL' % \ 468 (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))) 469 select, sql, params = function_get_sql_clause(opts, **kwargs) 470 sql = 'SELECT %s %s GROUP BY 1 ORDER BY 1' % \ 471 (db.get_date_trunc_sql(kind, '%s.%s' % \ 472 (db.db.quote_name(opts.db_table), db.db.quote_name(field.column))), sql) 473 cursor = db.db.cursor() 474 cursor.execute(sql, params) 475 # We have to manually run typecast_timestamp(str()) on the results, because 476 # MySQL doesn't automatically cast the result of date functions as datetime 477 # objects -- MySQL returns the values as strings, instead. 478 return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()] 479 480 def method_get_next(self, get_object_func, opts, instance, **kwargs ): 481 return self.method_get_next_or_previous(get_object_func, opts, True, instance, **kwargs) 482 483 def method_get_previous(self, get_object_func, opts, instance, **kwargs ): 484 return self.method_get_next_or_previous(get_object_func, opts, False, instance, **kwargs) 485 486 def method_get_next_or_previous(self, get_object_func, opts, is_next, instance, **kwargs): 487 from django.core import db 488 op = is_next and '>' or '<' 489 kwargs.setdefault('where', []).append('(%s %s %%s OR (%s = %%s AND %s.%s %s %%s))' % \ 490 (db.db.quote_name(self.column), op, db.db.quote_name(self.column), 491 db.db.quote_name(opts.db_table), db.db.quote_name(opts.pk.column), op)) 492 param = str(getattr(instance, self.attname)) 493 kwargs.setdefault('params', []).extend([param, param, getattr(instance, opts.pk.attname)]) 494 kwargs['order_by'] = [(not is_next and '-' or '') + self.name, (not is_next and '-' or '') + opts.pk.name] 495 kwargs['limit'] = 1 496 497 498 obj = get_object_func(**kwargs) 499 return obj 500 424 501 class DateTimeField(DateField): 425 502 def get_db_prep_save(self, value): 426 503 # Casts dates into string format for entry into database. … … 485 562 self.always_test = True 486 563 def __call__(self, field_data, all_data): 487 564 if not all_data.get(self.other_file_field_name, False): 488 c = validators.RequiredIfOtherFieldsGiven(self.other_field_names, gettext_lazy("This field is required.")) 565 c = validators.RequiredIfOtherFieldsGiven( 566 self.other_field_names, gettext_lazy("This field is required.")) 489 567 c(field_data, all_data) 490 568 # First, get the core fields, if any. 491 569 core_field_names = [] … … 496 574 if core_field_names: 497 575 field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name)) 498 576 else: 499 v = validators.RequiredIfOtherFieldNotGiven(field_list[1].field_name, gettext_lazy("This field is required.")) 577 v = validators.RequiredIfOtherFieldNotGiven( 578 field_list[1].field_name, gettext_lazy("This field is required.")) 500 579 v.always_test = True 501 580 field_list[0].validator_list.append(v) 502 581 field_list[0].is_required = field_list[1].is_required = False … … 518 597 def save_file(self, new_data, new_object, original_object, change, rel): 519 598 upload_field_name = self.get_manipulator_field_names('')[0] 520 599 if new_data.get(upload_field_name, False): 600 func = getattr(new_object, 'save_%s_file' % self.name) 521 601 if rel: 522 getattr(new_object, 'save_%s_file' % self.name)(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"])602 func(new_data[upload_field_name][0]["filename"], new_data[upload_field_name][0]["content"]) 523 603 else: 524 getattr(new_object, 'save_%s_file' % self.name)(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"])604 func(new_data[upload_field_name]["filename"], new_data[upload_field_name]["content"]) 525 605 526 606 def get_directory_name(self): 527 607 return os.path.normpath(datetime.datetime.now().strftime(self.upload_to)) … … 531 611 f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename))) 532 612 return os.path.normpath(f) 533 613 614 def contribute_to_class(self, opts, new_mod, new_class ): 615 super(FileField, self).contribute_to_class(opts, new_mod, new_class) 616 617 setattr(new_class, 'get_%s_filename' % self.name, self.method_get_file_filename) 618 setattr(new_class, 'get_%s_url' % self.name, self.method_get_file_url) 619 setattr(new_class, 'get_%s_size' % self.name, self.method_get_file_size) 620 setattr(new_class, 'save_%s_file' % self.name, self.method_save_file) 621 622 623 def method_get_file_filename(self, instance): 624 return os.path.join(settings.MEDIA_ROOT, getattr(instance, self.attname)) 625 626 def method_get_file_url(self, instance): 627 if getattr(instance, self.attname): # value is not blank 628 import urlparse 629 return urlparse.urljoin(settings.MEDIA_URL, getattr(instance, self.attname)).replace('\\', '/') 630 return '' 631 632 def method_get_file_size(self, instance): 633 return os.path.getsize(method_get_file_filename(self, instance)) 634 635 def write_file(self, instance, filename, raw_contents): 636 directory = self.get_directory_name() 637 try: # Create the date-based directory if it doesn't exist. 638 os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) 639 except OSError: # Directory probably already exists. 640 pass 641 filename = self.get_filename(filename) 642 643 # If the filename already exists, keep adding an underscore to the name of 644 # the file until the filename doesn't exist. 645 while os.path.exists(os.path.join(settings.MEDIA_ROOT, filename)): 646 try: 647 dot_index = filename.rindex('.') 648 except ValueError: # filename has no dot 649 filename += '_' 650 else: 651 filename = filename[:dot_index] + '_' + filename[dot_index:] 652 653 # Write the file to disk. 654 setattr(instance, self.attname, filename) 655 fp = open(getattr(instance, 'get_%s_filename' % self.name)(), 'wb') 656 fp.write(raw_contents) 657 fp.close() 658 659 def method_save_file(self, instance, filename, raw_contents): 660 self.write_file(instance, filename, raw_contents) 661 662 # Save the object, because it has changed. 663 instance.save() 664 method_save_file.alters_data = True 665 534 666 class FilePathField(Field): 535 667 def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs): 536 668 self.path, self.match, self.recursive = path, match, recursive … … 568 700 setattr(new_object, self.height_field, getattr(original_object, self.height_field)) 569 701 new_object.save() 570 702 703 704 def contribute_to_class(self, opts, new_mod, new_class ): 705 super(ImageField,self).contribute_to_class(opts, new_mod, new_class) 706 707 # Add get_BLAH_width and get_BLAH_height methods, but only 708 # if the image field doesn't have width and height cache 709 # fields. 710 if not self.width_field: 711 setattr(new_class, 'get_%s_width' % self.name, self.method_get_image_width) 712 if not self.height_field: 713 setattr(new_class, 'get_%s_height' % self.name, self.method_get_image_height) 714 715 def method_save_file(self, instance, filename, raw_contents): 716 self.write_file(instance, filename, raw_contents) 717 # Save the width and/or height, if applicable. 718 if (self.width_field or self.height_field): 719 from django.utils.images import get_image_dimensions 720 width, height = get_image_dimensions(getattr(instance, 'get_%s_filename' % self.name)()) 721 if self.width_field: 722 setattr(instance, field.width_field, width) 723 if self.height_field: 724 setattr(instance, field.height_field, height) 725 # Save the object, because it has changed. 726 instance.save() 727 728 def method_get_image_width(self, instance): 729 return self._get_image_dimensions(instance)[0] 730 731 def method_get_image_height(self, instance): 732 return self._get_image_dimensions(instance)[1] 733 734 def _get_image_dimensions(self, instance): 735 cachename = "__%s_dimensions_cache" % self.name 736 if not hasattr(instance, cachename): 737 from django.utils.images import get_image_dimensions 738 fname = getattr(instance, "get_%s_filename" % field.name)() 739 setattr(instance, cachename, get_image_dimensions(fname)) 740 return getattr(instance, cachename) 741 571 742 class IntegerField(Field): 572 743 empty_strings_allowed = False 573 744 def get_manipulator_field_objs(self): … … 682 853 def get_manipulator_field_objs(self): 683 854 return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)] 684 855 685 class ForeignKey(Field): 856 class RelatedField(Field): 857 # Handles related-object retrieval. 858 # Examples: Poll.get_choice(), Poll.get_choice_list(), Poll.get_choice_count() 859 def method_get_related(self, method_name, rel_mod, instance, **kwargs): 860 if instance._meta.has_related_links and rel_mod.Klass._meta.module_name == 'relatedlinks': 861 kwargs['object_id__exact'] = getattr(instance, self.rel.field_name) 862 else: 863 kwargs['%s__%s__exact' % (self.name, self.rel.to.pk.name)] = getattr(instance, self.rel.get_related_field().attname) 864 kwargs.update(self.rel.lookup_overrides) 865 return getattr(rel_mod, method_name)(**kwargs) 866 867 # Example: Story.get_dateline() 868 def method_get_many_to_one(self, instance): 869 cache_var = self.get_cache_name() 870 if not hasattr(instance, cache_var): 871 val = getattr(instance, self.attname) 872 mod = self.rel.to.get_model_module() 873 if val is None: 874 raise getattr(mod, '%sDoesNotExist' % self.rel.to.object_name) 875 other_field = self.rel.get_related_field() 876 if other_field.rel: 877 params = {'%s__%s__exact' % (self.rel.field_name, other_field.rel.field_name): val} 878 else: 879 params = {'%s__exact'% self.rel.field_name: val} 880 retrieved_obj = mod.get_object(**params) 881 setattr(instance, cache_var, retrieved_obj) 882 return getattr(instance, cache_var) 883 884 class ForeignKey(RelatedField): 686 885 empty_strings_allowed = False 687 886 def __init__(self, to, to_field=None, **kwargs): 688 887 try: 689 888 to_name = to._meta.object_name.lower() 690 889 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT 691 assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT) 890 #assert to == 'self', "ForeignKey(%r) is invalid. First parameter to ForeignKey" \ 891 # " must be either a model or the string %r" % (to, RECURSIVE_RELATIONSHIP_CONSTANT) 892 assert isinstance(to, basestring) , "ForeignKey(%r) is invalid. First parameter to ForeignKey " \ 893 "must be either a model, the string %r, " \ 894 "or the name of a model" % (to, RECURSIVE_RELATIONSHIP_CONSTANT) 692 895 kwargs['verbose_name'] = kwargs.get('verbose_name', '') 693 896 else: 694 897 to_field = to_field or to._meta.pk.name … … 722 925 if value == '' or value == None: 723 926 return None 724 927 else: 725 return int(value)928 return value 726 929 727 930 def flatten_data(self, follow, obj = None): 728 931 if not obj: … … 736 939 return { self.attname : choice_list[1][0] } 737 940 return Field.flatten_data(self, follow, obj) 738 941 942 def contribute_to_related_class(self, related, klass): 943 " Add the 'get_thingie', 'get_thingie_count' and 'get_thingie_list' methods " 944 rel_mod = related.opts.get_model_module() 945 rel_obj_name = related.get_method_name_part() 946 947 # Add "get_thingie" methods for many-to-one related objects. 948 # EXAMPLE: Poll.get_choice() 949 func = curry(self.method_get_related, 'get_object', rel_mod) 950 func.__doc__ = "Returns the associated `%s.%s` object matching the given criteria." % \ 951 (related.opts.app_label, related.opts.module_name) 952 setattr(klass, 'get_%s' % rel_obj_name, func) 953 # Add "get_thingie_count" methods for many-to-one related objects. 954 # EXAMPLE: Poll.get_choice_count() 955 func = curry(self.method_get_related, 'get_count', rel_mod) 956 func.__doc__ = "Returns the number of associated `%s.%s` objects." % \ 957 (related.opts.app_label, related.opts.module_name) 958 setattr(klass, 'get_%s_count' % rel_obj_name, func) 959 # Add "get_thingie_list" methods for many-to-one related objects. 960 # EXAMPLE: Poll.get_choice_list() 961 func = curry(self.method_get_related, 'get_list', rel_mod) 962 func.__doc__ = "Returns a list of associated `%s.%s` objects." % \ 963 (related.opts.app_label, related.opts.module_name) 964 setattr(klass, 'get_%s_list' % rel_obj_name, func) 965 # Add "add_thingie" methods for many-to-one related objects, 966 # but only for related objects that are in the same app. 967 # EXAMPLE: Poll.add_choice() 968 if related.opts.app_label == klass._meta.app_label: 969 func = curry(self.method_add_related, related) 970 func.alters_data = True 971 setattr(klass, 'add_%s' % rel_obj_name, func) 972 del func, rel_obj_name, rel_mod # clean up 973 974 # Handles adding related objects. 975 # Example: Poll.add_choice() 976 def method_add_related(self, related, instance, *args, **kwargs): 977 init_kwargs = dict(zip([f.attname for f in related.opts.fields if f != related.field and not isinstance(f, AutoField)], args)) 978 init_kwargs.update(kwargs) 979 for f in related.opts.fields: 980 if isinstance(f, AutoField): 981 init_kwargs[f.attname] = None 982 init_kwargs[related.field.name] = instance 983 obj = related.get_class()(**init_kwargs) 984 obj.save() 985 return obj 986 987 def contribute_to_class(self, opts, new_mod, new_class): 988 # Add "get_thingie" methods for many-to-one related objects. 989 # EXAMPLES: Choice.get_poll(), Story.get_dateline() 990 func = lambda instance : self.method_get_many_to_one(instance) 991 func.__doc__ = "Returns the associated `%s.%s` object." % (self.rel.to.app_label, self.rel.to.module_name) 992 setattr(new_class, 'get_%s' % self.name, func) 993 994 739 995 class ManyToManyField(Field): 740 996 def __init__(self, to, **kwargs): 741 997 kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural) … … 799 1055 new_data[self.name] = [choices_list[0][0]] 800 1056 return new_data 801 1057 802 class OneToOneField(IntegerField): 1058 def contribute_to_related_class(self, related, klass): 1059 " Add 'get_thingie', 'get_thingie_count' and 'get_thingie_list' methods" 1060 rel_mod = related.opts.get_model_module() 1061 rel_obj_name = related.get_method_name_part() 1062 setattr(klass, 'get_%s' % rel_obj_name, curry(self.__class__.method_get_related_many_to_many, 'get_object', klass._meta, rel_mod, self)) 1063 setattr(klass, 'get_%s_count' % rel_obj_name, curry(self.__class__.method_get_related_many_to_many, 'get_count', klass._meta, rel_mod, self)) 1064 setattr(klass, 'get_%s_list' % rel_obj_name, curry(self.__class__.method_get_related_many_to_many, 'get_list', klass._meta, rel_mod, self)) 1065 if related.opts.app_label == klass._meta.app_label: 1066 func = curry(self.__class__.method_set_related_many_to_many, related.opts, self) 1067 func.alters_data = True 1068 setattr(klass, 'set_%s' % related.opts.module_name, func) 1069 1070 # Handles related many-to-many object retrieval. 1071 # Examples: Album.get_song(), Album.get_song_list(), Album.get_song_count() 1072 def method_get_related_many_to_many(method_name, opts, rel_mod, rel_field, self, **kwargs): 1073 kwargs['%s__%s__exact' % (rel_field.name, opts.pk.name)] = getattr(self, opts.pk.attname) 1074 return getattr(rel_mod, method_name)(**kwargs) 1075 method_get_related_many_to_many = staticmethod(method_get_related_many_to_many) 1076 1077 # Handles setting many-to-many related objects. 1078 # Example: Album.set_songs() 1079 def method_set_related_many_to_many(rel_opts, rel_field, self, id_list): 1080 from django.core import db 1081 id_list = map(int, id_list) # normalize to integers 1082 rel = rel_field.rel.to 1083 m2m_table = rel_field.get_m2m_db_table(rel_opts) 1084 this_id = getattr(self, self._meta.pk.attname) 1085 cursor = db.db.cursor() 1086 cursor.execute("DELETE FROM %s WHERE %s = %%s" % \ 1087 (db.db.quote_name(m2m_table), 1088 db.db.quote_name(rel.object_name.lower() + '_id')), 1089 [this_id]) 1090 sql = "INSERT INTO %s (%s_id, %s_id) VALUES (%%s, %%s)" % \ 1091 (db.db.quote_name(m2m_table), 1092 db.db.quote_name(rel.object_name.lower() + '_id'), 1093 db.db.quote_name(rel_opts.object_name.lower() + '_id')) 1094 cursor.executemany(sql, [(this_id, i) for i in id_list]) 1095 db.db.commit() 1096 method_set_related_many_to_many = staticmethod(method_set_related_many_to_many) 1097 1098 def contribute_to_class(self, opts, new_mod, new_class ): 1099 # Add "get_thingie" methods for many-to-many related objects. 1100 # EXAMPLES: Poll.get_site_list(), Story.get_byline_list() 1101 func = curry(self.method_get_many_to_many) 1102 func.__doc__ = "Returns a list of associated `%s.%s` objects." %\ 1103 (self.rel.to.app_label, self.rel.to.module_name) 1104 setattr(new_class, 'get_%s_list' % self.rel.singular, func) 1105 # Add "set_thingie" methods for many-to-many related objects. 1106 # EXAMPLES: Poll.set_sites(), Story.set_bylines() 1107 func = curry(self.method_set_many_to_many) 1108 func.__doc__ = "Resets this object's `%s.%s` list to the given list of IDs." \ 1109 "Note that it doesn't check whether the given IDs are valid." % \ 1110 (self.rel.to.app_label, self.rel.to.module_name) 1111 func.alters_data = True 1112 setattr(new_class,'set_%s' % self.name , func) 1113 1114 # Handles getting many-to-many related objects. 1115 # Example: Poll.get_site_list() 1116 def method_get_many_to_many(self, instance): 1117 from django.core import db 1118 rel = self.rel.to 1119 cache_var = '_%s_cache' % self.name 1120 if not hasattr(instance, cache_var): 1121 mod = rel.get_model_module() 1122 sql = "SELECT %s FROM %s a, %s b WHERE a.%s = b.%s AND b.%s = %%s %s" % \ 1123 (','.join(['a.%s' % db.db.quote_name(f.column) for f in rel.fields]), 1124 db.db.quote_name(rel.db_table), 1125 db.db.quote_name(self.get_m2m_db_table(instance._meta)), 1126 db.db.quote_name(rel.pk.column), 1127 db.db.quote_name(rel.object_name.lower() + '_id'), 1128 db.db.quote_name(instance._meta.object_name.lower() + '_id'), rel.get_order_sql('a')) 1129 cursor = db.db.cursor() 1130 cursor.execute(sql, [getattr(instance, instance._meta.pk.attname)]) 1131 setattr(instance, cache_var, [getattr(mod, rel.object_name)(*row) for row in cursor.fetchall()]) 1132 return getattr(instance, cache_var) 1133 1134 # Handles setting many-to-many relationships. 1135 # Example: Poll.set_sites() 1136 def method_set_many_to_many(self, instance, id_list): 1137 from django.core import db 1138 current_ids = [obj.id for obj in self.method_get_many_to_many(instance)] 1139 ids_to_add, ids_to_delete = dict([(i, 1) for i in id_list]), [] 1140 for current_id in current_ids: 1141 if current_id in id_list: 1142 del ids_to_add[current_id] 1143 else: 1144 ids_to_delete.append(current_id) 1145 ids_to_add = ids_to_add.keys() 1146 # Now ids_to_add is a list of IDs to add, and ids_to_delete is a list of IDs to delete. 1147 if not ids_to_delete and not ids_to_add: 1148 return False # No change 1149 rel = self.rel.to 1150 m2m_table = self.get_m2m_db_table(instance._meta) 1151 cursor = db.db.cursor() 1152 this_id = getattr(instance, instance._meta.pk.attname) 1153 if ids_to_delete: 1154 sql = "DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ 1155 (db.db.quote_name(m2m_table), 1156 db.db.quote_name(instance._meta.object_name.lower() + '_id'), 1157 db.db.quote_name(rel.object_name.lower() + '_id'), ','.join(map(str, ids_to_delete))) 1158 cursor.execute(sql, [this_id]) 1159 if ids_to_add: 1160 sql = "INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ 1161 (db.db.quote_name(m2m_table), 1162 db.db.quote_name(instance._meta.object_name.lower() + '_id'), 1163 db.db.quote_name(rel.object_name.lower() + '_id')) 1164 cursor.executemany(sql, [(this_id, i) for i in ids_to_add]) 1165 db.db.commit() 1166 try: 1167 delattr(instance, '_%s_cache' % self.name) # clear cache, if it exists 1168 except AttributeError: 1169 pass 1170 return True 1171 1172 1173 class OneToOneField(IntegerField,RelatedField): 803 1174 def __init__(self, to, to_field=None, **kwargs): 804 1175 kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID') 805 1176 to_field = to_field or to._meta.pk.name … … 819 1190 kwargs['primary_key'] = True 820 1191 IntegerField.__init__(self, **kwargs) 821 1192 1193 #HACK 1194 def contribute_to_class(self, opts, new_mod, new_class ): 1195 # Add "get_thingie" methods for one-to-one related objects. 1196 # EXAMPLES: Choice.get_poll(), Story.get_dateline() 1197 func = lambda instance: self.method_get_many_to_one(instance) 1198 func.__doc__ = "Returns the associated `%s.%s` object." % (self.rel.to.app_label, self.rel.to.module_name) 1199 setattr(new_class, 'get_%s' % self.name, func) 1200 1201 def contribute_to_related_class(self, related, klass): 1202 " Add the 'get_thingie' " 1203 rel_mod = related.opts.get_model_module() 1204 rel_obj_name = related.get_method_name_part() 1205 # Add "get_thingie" methods for one-to-one related objects. 1206 # EXAMPLE: Place.get_restaurants_restaurant() 1207 func = curry(self.method_get_related, 'get_object', rel_mod) 1208 func.__doc__ = "Returns the associated `%s.%s` object." % (related.opts.app_label, related.opts.module_name) 1209 setattr(klass, 'get_%s' % rel_obj_name, func) 1210 822 1211 class ManyToOne: 823 1212 def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, 824 1213 max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, 825 1214 related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False): 826 1215 try: 827 1216 self.to = to._meta 828 except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT829 assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT1217 except AttributeError: # to._meta doesn't exist, so it must be a string 1218 #assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT 830 1219 self.to = to 831 1220 self.field_name = field_name 832 1221 self.num_in_admin, self.edit_inline = num_in_admin, edit_inline … … 840 1229 "Returns the Field in the 'to' object to which this relationship is tied." 841 1230 return self.to.get_field(self.field_name) 842 1231 1232 843 1233 class ManyToMany: 844 1234 def __init__(self, to, singular=None, num_in_admin=0, related_name=None, 845 1235 filter_interface=None, limit_choices_to=None, raw_id_admin=False): … … 864 1254 self.lookup_overrides = lookup_overrides or {} 865 1255 self.raw_id_admin = raw_id_admin 866 1256 1257 867 1258 class BoundFieldLine(object): 868 1259 def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField): 869 1260 self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line] -
django/core/management.py
=== django/core/management.py ==================================================================
62 62 "Returns a list of the CREATE TABLE SQL statements for the given module." 63 63 from django.core import db, meta 64 64 final_output = [] 65 opts_output = set() 66 pending_references = {} 65 67 for klass in mod._MODELS: 66 68 opts = klass._meta 67 69 table_output = [] … … 81 83 if f.primary_key: 82 84 field_output.append('PRIMARY KEY') 83 85 if f.rel: 84 field_output.append('REFERENCES %s (%s)' % \ 85 (db.db.quote_name(f.rel.to.db_table), 86 db.db.quote_name(f.rel.to.get_field(f.rel.field_name).column))) 86 if f.rel.to in opts_output: 87 field_output.append('REFERENCES %s (%s)' % \ 88 (db.db.quote_name(f.rel.to.db_table), 89 db.db.quote_name(f.rel.to.get_field(f.rel.field_name).column))) 90 else: 91 pr = pending_references.get(f.rel.to, []) 92 pr.append( (opts, f) ) 93 pending_references[f.rel.to] = pr 87 94 table_output.append(' '.join(field_output)) 88 95 if opts.order_with_respect_to: 89 96 table_output.append('%s %s NULL' % (db.db.quote_name('_order'), db.DATA_TYPES['IntegerField'])) … … 96 103 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) 97 104 full_statement.append(');') 98 105 final_output.append('\n'.join(full_statement)) 106 if opts in pending_references: 107 for (rel_opts, f) in pending_references[opts]: 108 r_table = rel_opts.db_table 109 r_col = f.column 110 table = opts.db_table 111 col = opts.get_field(f.rel.field_name).column 112 final_output.append( 'ALTER TABLE %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s);' % \ 113 (db.db.quote_name(r_table), 114 db.db.quote_name("%s_referencing_%s_%s" % (r_col,table,col)), 115 db.db.quote_name(r_col), db.db.quote_name(table),db.db.quote_name(col)) 116 ) 117 del pending_references[opts] 118 opts_output.add(opts) 99 119 100 120 for klass in mod._MODELS: 101 121 opts = klass._meta … … 145 165 output = [] 146 166 147 167 # Output DROP TABLE statements for standard application tables. 168 to_delete = set() 169 170 references_to_delete = {} 148 171 for klass in mod._MODELS: 149 172 try: 150 173 if cursor is not None: … … 154 177 # The table doesn't exist, so it doesn't need to be dropped. 155 178 db.db.rollback() 156 179 else: 180 opts = klass._meta 181 for f in opts.fields: 182 if f.rel and f.rel.to not in to_delete: 183 refs = references_to_delete.get(f.rel.to, []) 184 refs.append( (opts, f) ) 185 references_to_delete[f.rel.to] = refs 186 187 to_delete.add(opts) 188 189 for klass in mod._MODELS: 190 try: 191 if cursor is not None: 192 # Check whether the table exists. 193 cursor.execute("SELECT 1 FROM %s LIMIT 1" % db.db.quote_name(klass._meta.db_table)) 194 except: 195 # The table doesn't exist, so it doesn't need to be dropped. 196 db.db.rollback() 197 else: 157 198 output.append("DROP TABLE %s;" % db.db.quote_name(klass._meta.db_table)) 158 199 if references_to_delete.has_key(klass._meta): 200 for opts, f in references_to_delete[klass._meta]: 201 col = f.column 202 table = opts.db_table 203 r_table = f.rel.to.db_table 204 r_col = f.rel.to.get_field(f.rel.field_name).column 205 206 output.append( 'ALTER TABLE %s DROP CONSTRAINT %s;' % \ 207 (db.db.quote_name(table), 208 db.db.quote_name("%s_referencing_%s_%s" % (col,r_table,r_col)) 209 )) 210 211 212 213 214 215 159 216 # Output DROP TABLE statements for many-to-many tables. 160 217 for klass in mod._MODELS: 161 218 opts = klass._meta