Ticket #1020: mutually-referential.diff

File mutually-referential.diff, 67.6 KB (added by rjwittams, 19 years ago)
  • django/contrib/admin/views/main.py

    === django/contrib/admin/views/main.py
    ==================================================================
     
    552552    nh = _nest_help # Bind to local variable for performance
    553553    if current_depth > 16:
    554554        return # Avoid recursing too deep.
    555     objects_seen = []
     555    opts_seen = []
    556556    for related in opts.get_all_related_objects():
    557         if related.opts in objects_seen:
     557        if related.opts in opts_seen:
    558558            continue
    559         objects_seen.append(related.opts)
     559        opts_seen.append(related.opts)
    560560        rel_opts_name = related.get_method_name_part()
    561561        if isinstance(related.field.rel, meta.OneToOne):
    562562            try:
     
    600600                if not user.has_perm(p):
    601601                    perms_needed.add(rel_opts.verbose_name)
    602602    for related in opts.get_all_related_many_to_many_objects():
    603         if related.opts in objects_seen:
     603        if related.opts in opts_seen:
    604604            continue
    605         objects_seen.append(related.opts)
     605        opts_seen.append(related.opts)
    606606        rel_opts_name = related.get_method_name_part()
    607607        has_related_objs = False
    608608        for sub_obj in getattr(obj, 'get_%s_list' % rel_opts_name)():
     
    625625            if not user.has_perm(p):
    626626                perms_needed.add(related.opts.verbose_name)
    627627
     628from pprint import *
    628629def delete_stage(request, app_label, module_name, object_id):
    629630    import sets
    630631    mod, opts = _get_mod_opts(app_label, module_name)
     
    637638    deleted_objects = ['%s: <a href="../../%s/">%s</a>' % (capfirst(opts.verbose_name), object_id, strip_tags(str(obj))), []]
    638639    perms_needed = sets.Set()
    639640    _get_deleted_objects(deleted_objects, perms_needed, request.user, obj, opts, 1)
    640 
     641    pprint(deleted_objects)
    641642    if request.POST: # The user has already confirmed the deletion.
    642643        if perms_needed:
    643644            raise PermissionDenied
  • django/models/__init__.py

    === django/models/__init__.py
    ==================================================================
     
    11from django.core import meta
    22from django.utils.functional import curry
     3from django.core.meta.fields import RECURSIVE_RELATIONSHIP_CONSTANT
    34
    45__all__ = ['auth', 'core']
    56
     
    1213# First, import all models so the metaclasses run.
    1314modules = meta.get_installed_model_modules(__all__)
    1415
    15 # Now, create the extra methods that we couldn't create earlier because
    16 # relationships hadn't been known until now.
    17 for mod in modules:
    18     for klass in mod._MODELS:
    1916
    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
     17def 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
    6339
    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)
    7362                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)
    7768
    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)
     69fixup_modules()
    8570
    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 up
    90         del klass # clean up
    91     del mod
    92 del modules
    93 
    9471# Expose get_app and get_module.
    9572from django.core.meta import get_app, get_module
  • django/core/meta/__init__.py

    === django/core/meta/__init__.py
    ==================================================================
     
    167167        self.edit_inline = field.rel.edit_inline
    168168        self.name = opts.module_name
    169169        self.var_name = opts.object_name.lower()
     170        self.klass = None
    170171
     172    def get_class(self):
     173        if not self.klass:
     174            self.klass = self.opts.get_model_module().Klass
     175        return self.klass
     176           
    171177    def flatten_data(self, follow, obj=None):
    172178        new_data = {}
    173179        rel_instances = self.get_list(obj)
     
    739745            attrs['get_next_in_order'] = curry(method_get_next_in_order, opts, opts.order_with_respect_to)
    740746            attrs['get_previous_in_order'] = curry(method_get_previous_in_order, opts, opts.order_with_respect_to)
    741747
    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               
    770749        # Create the class, because we need it to use in currying.
    771750        new_class = type.__new__(cls, name, bases, attrs)
    772751
     
    802781        if opts.get_latest_by:
    803782            new_mod.get_latest = curry(function_get_latest, opts, new_class, does_not_exist_exception)
    804783
    805         for f in opts.fields:
    806784            #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" methods
    813                 # 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'." % name
    822                 setattr(new_mod, 'get_%s_list' % f.name, func)
    823785
    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           
    840787        # Add the class itself to the new module we've created.
    841788        new_mod.__dict__[name] = new_class
    842789
     
    938885    def __repr__(self):
    939886        return '<%s object>' % self.__class__.__name__
    940887
     888
    941889############################################
    942890# HELPER FUNCTIONS (CURRIED MODEL METHODS) #
    943891############################################
    944892
     893
     894
    945895# CORE METHODS #############################
    946896
    947897def method_init(opts, self, *args, **kwargs):
     
    966916                        try:
    967917                            val = getattr(rel_obj, f.rel.get_related_field().attname)
    968918                        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) )
    970920                setattr(self, f.attname, val)
    971921            else:
    972922                val = kwargs.pop(f.attname, f.get_default())
     
    1031981    if hasattr(self, '_post_save'):
    1032982        self._post_save()
    1033983
    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
     985def 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   
    1040992    for related in opts.get_all_related_objects():
    1041993        rel_opts_name = related.get_method_name_part()
    1042994        if isinstance(related.field.rel, OneToOne):
    1043995            try:
    1044                 sub_obj = getattr(self, 'get_%s' % rel_opts_name)()
     996                sub_obj = getattr(instance, 'get_%s' % rel_opts_name)()
    1045997            except ObjectDoesNotExist:
    1046998                pass
    1047999            else:
    1048                 sub_obj.delete()
     1000                add_sub_objects(related.opts, sub_obj, seen_objs)
    10491001        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
     1005def 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   
     1014def 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:
    10531057        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       
    10641074    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   
    10761076
    10771077def method_get_next_in_order(opts, order_field, self):
    10781078    if not hasattr(self, '_next_in_order_cache'):
     
    11131113        setattr(self, cache_var, retrieved_obj)
    11141114    return getattr(self, cache_var)
    11151115
    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.to
    1120     cache_var = '_%s_cache' % field_with_rel.name
    1121     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 change
    1149     rel = rel_field.rel.to
    1150     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 exists
    1168     except AttributeError:
    1169         pass
    1170     return True
    1171 
    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] = None
    1190     init_kwargs[rel_field.name] = self
    1191     obj = rel_mod.Klass(**init_kwargs)
    1192     obj.save()
    1193     return obj
    1194 
    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 integers
    1205     rel = rel_field.rel.to
    1206     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 
    12191116# ORDERING METHODS #########################
    12201117
    12211118def method_set_order(ordered_obj, self, id_list):
     
    12411138    cursor.execute(sql, [rel_val])
    12421139    return [r[0] for r in cursor.fetchall()]
    12431140
    1244 # DATE-RELATED METHODS #####################
    12451141
    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'] = 1
    1255     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 blank
    1270         import urlparse
    1271         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         pass
    1283     filename = field.get_filename(filename)
    1284 
    1285     # If the filename already exists, keep adding an underscore to the name of
    1286     # 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 dot
    1291             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_dimensions
    1304         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.name
    1323     if not hasattr(self, cachename):
    1324         from django.utils.images import get_image_dimensions
    1325         fname = getattr(self, "get_%s_filename" % field.name)()
    1326         setattr(self, cachename, get_image_dimensions(fname))
    1327     return getattr(self, cachename)
    1328 
    13291142##############################################
    13301143# HELPER FUNCTIONS (CURRIED MODEL FUNCTIONS) #
    13311144##############################################
     
    16591472    kwargs['limit'] = 1
    16601473    return function_get_object(opts, klass, does_not_exist_exception, **kwargs)
    16611474
    1662 def function_get_date_list(opts, field, *args, **kwargs):
    1663     from django.core.db.typecasts import typecast_timestamp
    1664     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, because
    1680     # MySQL doesn't automatically cast the result of date functions as datetime
    1681     # objects -- MySQL returns the values as strings, instead.
    1682     return [typecast_timestamp(str(row[0])) for row in cursor.fetchall()]
    1683 
    16841475###################################
    16851476# HELPER FUNCTIONS (MANIPULATORS) #
    16861477###################################
     
    18921683                # If, in the change stage, all of the core fields were blank and
    18931684                # the primary key (ID) was provided, delete the item.
    18941685                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))])
    18961687                    self.fields_deleted.append('%s "%s"' % (related.opts.verbose_name, old_rel_obj))
    18971688
    18981689    # Save the order, if applicable.
  • django/core/meta/fields.py

    === django/core/meta/fields.py
    ==================================================================
     
    5555        old_obj = opts.get_model_module().get_object(**{lookup_type: field_data})
    5656    except ObjectDoesNotExist:
    5757        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):
    5960        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}
    6164
    6265class BoundField(object):
    6366    def __init__(self, field, field_mapping, original):
     
    282285                    core_field_names.extend(f.get_manipulator_field_names(name_prefix))
    283286            # Now, if there are any, add the validator to this FormField.
    284287            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.")))
    286291
    287292        # BooleanFields (CheckboxFields) are a special case. They don't take
    288293        # is_required or validator_list.
     
    344349    def bind(self, fieldmapping, original, bound_field_class=BoundField):
    345350        return bound_field_class(self, fieldmapping, original)
    346351
     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)
    347360class AutoField(Field):
    348361    empty_strings_allowed = False
    349362    def __init__(self, *args, **kwargs):
     
    378391class CommaSeparatedIntegerField(CharField):
    379392    def get_manipulator_field_objs(self):
    380393        return [formfields.CommaSeparatedIntegerField]
    381 
     394   
    382395class DateField(Field):
    383396    empty_strings_allowed = False
    384397    def __init__(self, verbose_name=None, name=None, auto_now=False, auto_now_add=False, **kwargs):
     
    421434        val = self._get_val_from_obj(obj)
    422435        return {self.attname: (val is not None and val.strftime("%Y-%m-%d") or '')}
    423436
     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
    424501class DateTimeField(DateField):
    425502    def get_db_prep_save(self, value):
    426503        # Casts dates into string format for entry into database.
     
    485562                        self.always_test = True
    486563                    def __call__(self, field_data, all_data):
    487564                        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."))
    489567                            c(field_data, all_data)
    490568                # First, get the core fields, if any.
    491569                core_field_names = []
     
    496574                if core_field_names:
    497575                    field_list[0].validator_list.append(RequiredFileField(core_field_names, field_list[1].field_name))
    498576            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."))
    500579                v.always_test = True
    501580                field_list[0].validator_list.append(v)
    502581                field_list[0].is_required = field_list[1].is_required = False
     
    518597    def save_file(self, new_data, new_object, original_object, change, rel):
    519598        upload_field_name = self.get_manipulator_field_names('')[0]
    520599        if new_data.get(upload_field_name, False):
     600            func = getattr(new_object, 'save_%s_file' % self.name)
    521601            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"])
    523603            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"])
    525605
    526606    def get_directory_name(self):
    527607        return os.path.normpath(datetime.datetime.now().strftime(self.upload_to))
     
    531611        f = os.path.join(self.get_directory_name(), get_valid_filename(os.path.basename(filename)))
    532612        return os.path.normpath(f)
    533613
     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
    534666class FilePathField(Field):
    535667    def __init__(self, verbose_name=None, name=None, path='', match=None, recursive=False, **kwargs):
    536668        self.path, self.match, self.recursive = path, match, recursive
     
    568700                setattr(new_object, self.height_field, getattr(original_object, self.height_field))
    569701            new_object.save()
    570702
     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
    571742class IntegerField(Field):
    572743    empty_strings_allowed = False
    573744    def get_manipulator_field_objs(self):
     
    682853    def get_manipulator_field_objs(self):
    683854        return [curry(formfields.XMLLargeTextField, schema_path=self.schema_path)]
    684855
    685 class ForeignKey(Field):
     856class 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
     884class ForeignKey(RelatedField):
    686885    empty_strings_allowed = False
    687886    def __init__(self, to, to_field=None, **kwargs):
    688887        try:
    689888            to_name = to._meta.object_name.lower()
    690889        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)
    692895            kwargs['verbose_name'] = kwargs.get('verbose_name', '')
    693896        else:
    694897            to_field = to_field or to._meta.pk.name
     
    722925        if value == '' or value == None:
    723926           return None
    724927        else:
    725            return int(value)
     928           return value
    726929
    727930    def flatten_data(self, follow, obj = None):
    728931        if not obj:
     
    736939                  return { self.attname : choice_list[1][0] }
    737940        return Field.flatten_data(self, follow, obj)
    738941
     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
    739995class ManyToManyField(Field):
    740996    def __init__(self, to, **kwargs):
    741997        kwargs['verbose_name'] = kwargs.get('verbose_name', to._meta.verbose_name_plural)
     
    7991055                   new_data[self.name] = [choices_list[0][0]]
    8001056        return new_data
    8011057
    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
     1173class OneToOneField(IntegerField,RelatedField):
    8031174    def __init__(self, to, to_field=None, **kwargs):
    8041175        kwargs['verbose_name'] = kwargs.get('verbose_name', 'ID')
    8051176        to_field = to_field or to._meta.pk.name
     
    8191190        kwargs['primary_key'] = True
    8201191        IntegerField.__init__(self, **kwargs)
    8211192
     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
    8221211class ManyToOne:
    8231212    def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None,
    8241213        max_num_in_admin=None, num_extra_on_change=1, edit_inline=False,
    8251214        related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False):
    8261215        try:
    8271216            self.to = to._meta
    828         except AttributeError: # to._meta doesn't exist, so it must be RECURSIVE_RELATIONSHIP_CONSTANT
    829             assert to == RECURSIVE_RELATIONSHIP_CONSTANT, "'to' must be either a model or the string '%s'" % RECURSIVE_RELATIONSHIP_CONSTANT
     1217        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
    8301219            self.to = to
    8311220        self.field_name = field_name
    8321221        self.num_in_admin, self.edit_inline = num_in_admin, edit_inline
     
    8401229        "Returns the Field in the 'to' object to which this relationship is tied."
    8411230        return self.to.get_field(self.field_name)
    8421231
     1232
    8431233class ManyToMany:
    8441234    def __init__(self, to, singular=None, num_in_admin=0, related_name=None,
    8451235        filter_interface=None, limit_choices_to=None, raw_id_admin=False):
     
    8641254        self.lookup_overrides = lookup_overrides or {}
    8651255        self.raw_id_admin = raw_id_admin
    8661256
     1257
    8671258class BoundFieldLine(object):
    8681259    def __init__(self, field_line, field_mapping, original, bound_field_class=BoundField):
    8691260        self.bound_fields = [field.bind(field_mapping, original, bound_field_class) for field in field_line]
  • django/core/management.py

    === django/core/management.py
    ==================================================================
     
    6262    "Returns a list of the CREATE TABLE SQL statements for the given module."
    6363    from django.core import db, meta
    6464    final_output = []
     65    opts_output = set()
     66    pending_references = {}
    6567    for klass in mod._MODELS:
    6668        opts = klass._meta
    6769        table_output = []
     
    8183                if f.primary_key:
    8284                    field_output.append('PRIMARY KEY')
    8385                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
    8794                table_output.append(' '.join(field_output))
    8895        if opts.order_with_respect_to:
    8996            table_output.append('%s %s NULL' % (db.db.quote_name('_order'), db.DATA_TYPES['IntegerField']))
     
    96103            full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
    97104        full_statement.append(');')
    98105        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)
    99119
    100120    for klass in mod._MODELS:
    101121        opts = klass._meta
     
    145165    output = []
    146166
    147167    # Output DROP TABLE statements for standard application tables.
     168    to_delete = set()
     169   
     170    references_to_delete = {}
    148171    for klass in mod._MODELS:
    149172        try:
    150173            if cursor is not None:
     
    154177            # The table doesn't exist, so it doesn't need to be dropped.
    155178            db.db.rollback()
    156179        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:
    157198            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           
    159216    # Output DROP TABLE statements for many-to-many tables.
    160217    for klass in mod._MODELS:
    161218        opts = klass._meta
Back to Top