Django

Code

Show
Ignore:
Timestamp:
08/05/08 12:15:33 (5 months ago)
Author:
jbronn
Message:

gis: Merged revisions 7981-8001,8003-8011,8013-8033,8035-8036,8038-8039,8041-8063,8065-8076,8078-8139,8141-8154,8156-8214 via svnmerge from trunk.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • django/branches/gis

    • Property svnmerge-integrated changed from /django/trunk:1-7978 to /django/trunk:1-8214
  • django/branches/gis/django/db/models/base.py

    r7979 r8215  
    1313from django.core import validators 
    1414from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned, FieldError 
    15 from django.db.models.fields import AutoField, ImageField, FieldDoesNotExist 
     15from django.db.models.fields import AutoField, ImageField 
    1616from django.db.models.fields.related import OneToOneRel, ManyToOneRel, OneToOneField 
    1717from django.db.models.query import delete_objects, Q, CollectedObjects 
     
    2121from django.db.models.loading import register_models, get_model 
    2222from django.dispatch import dispatcher 
    23 from django.utils.datastructures import SortedDict 
    2423from django.utils.functional import curry 
    2524from django.utils.encoding import smart_str, force_unicode, smart_unicode 
     
    202201 
    203202        for field in fields_iter: 
     203            rel_obj = None 
    204204            if kwargs: 
    205205                if isinstance(field.rel, ManyToOneRel): 
     
    218218                        if rel_obj is None and field.null: 
    219219                            val = None 
    220                         else: 
    221                             try: 
    222                                 val = getattr(rel_obj, field.rel.get_related_field().attname) 
    223                             except AttributeError: 
    224                                 raise TypeError("Invalid value: %r should be a %s instance, not a %s" % 
    225                                     (field.name, field.rel.to, type(rel_obj))) 
    226220                else: 
    227221                    val = kwargs.pop(field.attname, field.get_default()) 
    228222            else: 
    229223                val = field.get_default() 
    230             setattr(self, field.attname, val) 
     224            # If we got passed a related instance, set it using the field.name 
     225            # instead of field.attname (e.g. "user" instead of "user_id") so 
     226            # that the object gets properly cached (and type checked) by the 
     227            # RelatedObjectDescriptor. 
     228            if rel_obj: 
     229                setattr(self, field.name, rel_obj) 
     230            else: 
     231                setattr(self, field.attname, val) 
    231232 
    232233        if kwargs: 
     
    300301        if not raw: 
    301302            for parent, field in meta.parents.items(): 
     303                # At this point, parent's primary key field may be unknown 
     304                # (for example, from administration form which doesn't fill 
     305                # this field). If so, fill it. 
     306                if getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None: 
     307                    setattr(self, parent._meta.pk.attname, getattr(self, field.attname)) 
     308 
    302309                self.save_base(raw, parent) 
    303310                setattr(self, field.attname, self._get_pk_val(parent._meta)) 
     
    473480 
    474481    def _save_FIELD_file(self, field, filename, raw_field, save=True): 
    475         directory = field.get_directory_name() 
    476         try: # Create the date-based directory if it doesn't exist. 
    477             os.makedirs(os.path.join(settings.MEDIA_ROOT, directory)) 
    478         except OSError: # Directory probably already exists. 
    479             pass 
     482        # Create the upload directory if it doesn't already exist 
     483        directory = os.path.join(settings.MEDIA_ROOT, field.get_directory_name()) 
     484        if not os.path.exists(directory): 
     485            os.makedirs(directory) 
     486        elif not os.path.isdir(directory): 
     487            raise IOError('%s exists and is not a directory' % directory)         
    480488 
    481489        # Check for old-style usage (files-as-dictionaries). Warn here first 
     
    495503            import warnings 
    496504            warnings.warn( 
    497                 message = "Representing uploaded files as dictionaries is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.", 
     505                message = "Representing uploaded files as strings is deprecated. Use django.core.files.uploadedfile.SimpleUploadedFile instead.", 
    498506                category = DeprecationWarning, 
    499507                stacklevel = 2 
  • django/branches/gis/django/db/models/fields/__init__.py

    r8002 r8215  
    2323from django.utils.translation import ugettext_lazy, ugettext as _ 
    2424from django.utils.encoding import smart_unicode, force_unicode, smart_str 
    25 from django.utils.maxlength import LegacyMaxlength 
    2625from django.utils import datetime_safe 
    2726 
     
    6362 
    6463class Field(object): 
    65     # Provide backwards compatibility for the maxlength attribute and 
    66     # argument for this class and all subclasses. 
    67     __metaclass__ = LegacyMaxlength 
    68  
    6964    # Designates whether empty strings fundamentally are allowed at the 
    7065    # database level. 
     
    192187        self.name = name 
    193188        self.attname, self.column = self.get_attname_column() 
    194         self.verbose_name = self.verbose_name or (name and name.replace('_', ' ')) 
     189        if self.verbose_name is None and name: 
     190            self.verbose_name = name.replace('_', ' ') 
    195191 
    196192    def contribute_to_class(self, cls, name): 
     
    218214        return getattr(model_instance, self.attname) 
    219215 
     216    def get_db_prep_value(self, value): 
     217        """Returns field's value prepared for interacting with the database 
     218        backend. 
     219 
     220        Used by the default implementations of ``get_db_prep_save``and 
     221        `get_db_prep_lookup``` 
     222        """ 
     223        return value 
     224 
    220225    def get_db_prep_save(self, value): 
    221226        "Returns field's value prepared for saving into a database." 
    222         return value 
     227        return self.get_db_prep_value(value) 
    223228 
    224229    def get_db_prep_lookup(self, lookup_type, value): 
     
    227232            sql, params = value.as_sql() 
    228233            return QueryWrapper(('(%s)' % sql), params) 
    229         if lookup_type in ('exact', 'regex', 'iregex', 'gt', 'gte', 'lt', 'lte', 'month', 'day', 'search'): 
     234        if lookup_type in ('regex', 'iregex', 'month', 'day', 'search'): 
    230235            return [value] 
     236        elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte'): 
     237            return [self.get_db_prep_value(value)] 
    231238        elif lookup_type in ('range', 'in'): 
    232             return value 
     239            return [self.get_db_prep_value(v) for v in value] 
    233240        elif lookup_type in ('contains', 'icontains'): 
    234241            return ["%%%s%%" % connection.ops.prep_for_like_query(value)] 
     
    246253            except ValueError: 
    247254                raise ValueError("The __year lookup type requires an integer argument") 
    248             if settings.DATABASE_ENGINE == 'sqlite3': 
    249                 first = '%s-01-01' 
    250                 second = '%s-12-31 23:59:59.999999' 
    251             elif not connection.features.date_field_supports_time_value and self.get_internal_type() == 'DateField': 
    252                 first = '%s-01-01' 
    253                 second = '%s-12-31' 
    254             elif not connection.features.supports_usecs: 
    255                 first = '%s-01-01 00:00:00' 
    256                 second = '%s-12-31 23:59:59.99' 
     255 
     256            if self.get_internal_type() == 'DateField': 
     257                return connection.ops.year_lookup_bounds_for_date_field(value) 
    257258            else: 
    258                 first = '%s-01-01 00:00:00' 
    259                 second = '%s-12-31 23:59:59.999999' 
    260             return [first % value, second % value] 
     259                return connection.ops.year_lookup_bounds(value) 
     260 
    261261        raise TypeError("Field has invalid lookup: %s" % lookup_type) 
    262262 
     
    289289            field_objs = [oldforms.SelectField] 
    290290 
    291             params['choices'] = self.flatchoices 
     291            params['choices'] = self.get_flatchoices() 
    292292        else: 
    293293            field_objs = self.get_manipulator_field_objs() 
     
    363363 
    364364    def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH): 
    365         "Returns a list of tuples used as SelectField choices for this field." 
     365        """Returns choices with a default blank choices included, for use 
     366        as SelectField choices for this field.""" 
    366367        first_choice = include_blank and blank_choice or [] 
    367368        if self.choices: 
     
    377378        return self.get_choices() 
    378379 
     380    def get_flatchoices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH): 
     381        "Returns flattened choices with a default blank choice included." 
     382        first_choice = include_blank and blank_choice or [] 
     383        return first_choice + list(self.flatchoices) 
     384 
    379385    def _get_val_from_obj(self, obj): 
    380386        if obj: 
     
    409415 
    410416    def _get_flatchoices(self): 
     417        """Flattened version of choices tuple.""" 
    411418        flat = [] 
    412         for choice, value in self.get_choices_default()
     419        for choice, value in self.choices
    413420            if type(value) in (list, tuple): 
    414421                flat.extend(value) 
     
    417424        return flat 
    418425    flatchoices = property(_get_flatchoices) 
    419      
     426 
    420427    def save_form_data(self, instance, data): 
    421428        setattr(instance, self.name, data) 
     
    450457            raise validators.ValidationError, _("This value must be an integer.") 
    451458 
     459    def get_db_prep_value(self, value): 
     460        if value is None: 
     461            return None 
     462        return int(value) 
     463 
    452464    def get_manipulator_fields(self, opts, manipulator, change, name_prefix='', rel=False, follow=True): 
    453465        if not rel: 
     
    478490    def __init__(self, *args, **kwargs): 
    479491        kwargs['blank'] = True 
     492        if 'default' not in kwargs and not kwargs.get('null'): 
     493            kwargs['default'] = False 
    480494        Field.__init__(self, *args, **kwargs) 
    481495 
     
    488502        if value in ('f', 'False', '0'): return False 
    489503        raise validators.ValidationError, _("This value must be either True or False.") 
     504 
     505    def get_db_prep_value(self, value): 
     506        if value is None: 
     507            return None 
     508        return bool(value) 
    490509 
    491510    def get_manipulator_field_objs(self): 
     
    550569            raise validators.ValidationError, _('Enter a valid date in YYYY-MM-DD format.') 
    551570 
    552     def get_db_prep_lookup(self, lookup_type, value): 
    553         if lookup_type in ('range', 'in'): 
    554             value = [smart_unicode(v) for v in value] 
    555         elif lookup_type in ('exact', 'gt', 'gte', 'lt', 'lte') and hasattr(value, 'strftime'): 
    556             value = datetime_safe.new_date(value).strftime('%Y-%m-%d') 
    557         else: 
    558             value = smart_unicode(value) 
    559         return Field.get_db_prep_lookup(self, lookup_type, value) 
    560  
    561571    def pre_save(self, model_instance, add): 
    562572        if self.auto_now or (self.auto_now_add and add): 
     
    582592            return self.editable or self.auto_now or self.auto_now_add 
    583593 
    584     def get_db_prep_save(self, value): 
    585         # Casts dates into string format for entry into database. 
    586         if value is not None: 
    587             try: 
    588                 value = datetime_safe.new_date(value).strftime('%Y-%m-%d') 
    589             except AttributeError: 
    590                 # If value is already a string it won't have a strftime method, 
    591                 # so we'll just let it pass through. 
    592                 pass 
    593         return Field.get_db_prep_save(self, value) 
     594    def get_db_prep_value(self, value): 
     595        # Casts dates into the format expected by the backend 
     596        return connection.ops.value_to_db_date(self.to_python(value)) 
    594597 
    595598    def get_manipulator_field_objs(self): 
     
    620623        if isinstance(value, datetime.date): 
    621624            return datetime.datetime(value.year, value.month, value.day) 
     625 
     626        # Attempt to parse a datetime: 
     627        value = smart_str(value) 
     628        # split usecs, because they are not recognized by strptime. 
     629        if '.' in value: 
     630            try: 
     631                value, usecs = value.split('.') 
     632                usecs = int(usecs) 
     633            except ValueError: 
     634                raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.') 
     635        else: 
     636            usecs = 0 
     637        kwargs = {'microsecond': usecs} 
    622638        try: # Seconds are optional, so try converting seconds first. 
    623             return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6]) 
     639            return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M:%S')[:6], 
     640                                     **kwargs) 
     641 
    624642        except ValueError: 
    625643            try: # Try without seconds. 
    626                 return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5]) 
     644                return datetime.datetime(*time.strptime(value, '%Y-%m-%d %H:%M')[:5], 
     645                                         **kwargs) 
    627646            except ValueError: # Try without hour/minutes/seconds. 
    628647                try: 
    629                     return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3]) 
     648                    return datetime.datetime(*time.strptime(value, '%Y-%m-%d')[:3], 
     649                                             **kwargs) 
    630650                except ValueError: 
    631                     raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM format.') 
    632  
    633     def get_db_prep_save(self, value): 
    634         # Casts dates into string format for entry into database. 
    635         if value is not None: 
    636             # MySQL will throw a warning if microseconds are given, because it 
    637             # doesn't support microseconds. 
    638             if not connection.features.supports_usecs and hasattr(value, 'microsecond'): 
    639                 value = value.replace(microsecond=0) 
    640             value = smart_unicode(value) 
    641         return Field.get_db_prep_save(self, value) 
    642  
    643     def get_db_prep_lookup(self, lookup_type, value): 
    644         if lookup_type in ('range', 'in'): 
    645             value = [smart_unicode(v) for v in value] 
    646         else: 
    647             value = smart_unicode(value) 
    648         return Field.get_db_prep_lookup(self, lookup_type, value) 
     651                    raise validators.ValidationError, _('Enter a valid date/time in YYYY-MM-DD HH:MM[ss[.uuuuuu]] format.') 
     652 
     653    def get_db_prep_value(self, value): 
     654        # Casts dates into the format expected by the backend 
     655        return connection.ops.value_to_db_datetime(self.to_python(value)) 
    649656 
    650657    def get_manipulator_field_objs(self): 
     
    711718        decimal places. 
    712719        """ 
    713         num_chars = self.max_digits 
    714         # Allow for a decimal point 
    715         if self.decimal_places > 0: 
    716             num_chars += 1 
    717         # Allow for a minus sign 
    718         if value < 0: 
    719             num_chars += 1 
    720  
    721         return u"%.*f" % (self.decimal_places, value) 
    722  
    723     def get_db_prep_save(self, value): 
    724         value = self._format(value) 
    725         return super(DecimalField, self).get_db_prep_save(value) 
    726  
    727     def get_db_prep_lookup(self, lookup_type, value): 
    728         if lookup_type in ('range', 'in'): 
    729             value = [self._format(v) for v in value] 
    730         else: 
    731             value = self._format(value) 
    732         return super(DecimalField, self).get_db_prep_lookup(lookup_type, value) 
     720        # Method moved to django.db.backends.util. 
     721        # 
     722        # It is preserved because it is used by the oracle backend 
     723        # (django.db.backends.oracle.query), and also for 
     724        # backwards-compatibility with any external code which may have used 
     725        # this method. 
     726        from django.db.backends import util 
     727        return util.format_number(value, self.max_digits, self.decimal_places) 
     728 
     729    def get_db_prep_value(self, value): 
     730        return connection.ops.value_to_db_decimal(self.to_python(value), 
     731                self.max_digits, self.decimal_places) 
    733732 
    734733    def get_manipulator_field_objs(self): 
     
    769768        return "FileField" 
    770769 
    771     def get_db_prep_save(self, value): 
     770    def get_db_prep_value(self, value): 
    772771        "Returns field's value prepared for saving into a database." 
    773772        # Need to convert UploadedFile objects provided via a form to unicode for database insertion 
     
    910909    empty_strings_allowed = False 
    911910 
     911    def get_db_prep_value(self, value): 
     912        if value is None: 
     913            return None 
     914        return float(value) 
     915 
    912916    def get_manipulator_field_objs(self): 
    913917        return [oldforms.FloatField] 
     
    957961class IntegerField(Field): 
    958962    empty_strings_allowed = False 
     963    def get_db_prep_value(self, value): 
     964        if value is None: 
     965            return None 
     966        return int(value) 
     967 
    959968    def get_manipulator_field_objs(self): 
    960969        return [oldforms.IntegerField] 
     
    10041013        raise validators.ValidationError, _("This value must be either None, True or False.") 
    10051014 
     1015    def get_db_prep_value(self, value): 
     1016        if value is None: 
     1017            return None 
     1018        return bool(value) 
     1019 
    10061020    def get_manipulator_field_objs(self): 
    10071021        return [oldforms.NullBooleanField] 
     
    10161030        return super(NullBooleanField, self).formfield(**defaults) 
    10171031 
    1018 class PhoneNumberField(IntegerField): 
     1032class PhoneNumberField(Field): 
    10191033    def get_manipulator_field_objs(self): 
    10201034        return [oldforms.PhoneNumberField] 
     
    10981112        return "TimeField" 
    10991113 
    1100     def get_db_prep_lookup(self, lookup_type, value): 
    1101         if connection.features.time_field_needs_date: 
    1102             # Oracle requires a date in order to parse. 
    1103             def prep(value): 
    1104                 if isinstance(value, datetime.time): 
    1105                     value = datetime.datetime.combine(datetime.date(1900, 1, 1), value) 
    1106                 return smart_unicode(value) 
    1107         else: 
    1108             prep = smart_unicode 
    1109         if lookup_type in ('range', 'in'): 
    1110             value = [prep(v) for v in value] 
    1111         else: 
    1112             value = prep(value) 
    1113         return Field.get_db_prep_lookup(self, lookup_type, value) 
     1114    def to_python(self, value): 
     1115        if value is None: 
     1116            return None 
     1117        if isinstance(value, datetime.time): 
     1118            return value 
     1119 
     1120        # Attempt to parse a datetime: 
     1121        value = smart_str(value) 
     1122        # split usecs, because they are not recognized by strptime. 
     1123        if '.' in value: 
     1124            try: 
     1125                value, usecs = value.split('.') 
     1126                usecs = int(usecs) 
     1127            except ValueError: 
     1128                raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.') 
     1129        else: 
     1130            usecs = 0 
     1131        kwargs = {'microsecond': usecs} 
     1132 
     1133        try: # Seconds are optional, so try converting seconds first. 
     1134            return datetime.time(*time.strptime(value, '%H:%M:%S')[3:6], 
     1135                                 **kwargs) 
     1136        except ValueError: 
     1137            try: # Try without seconds. 
     1138                return datetime.time(*time.strptime(value, '%H:%M')[3:5], 
     1139                                         **kwargs) 
     1140            except ValueError: 
     1141                raise validators.ValidationError, _('Enter a valid time in HH:MM[:ss[.uuuuuu]] format.') 
    11141142 
    11151143    def pre_save(self, model_instance, add): 
     
    11211149            return super(TimeField, self).pre_save(model_instance, add) 
    11221150 
    1123     def get_db_prep_save(self, value): 
    1124         # Casts dates into string format for entry into database. 
    1125         if value is not None: 
    1126             # MySQL will throw a warning if microseconds are given, because it 
    1127             # doesn't support microseconds. 
    1128             if not connection.features.supports_usecs and hasattr(value, 'microsecond'): 
    1129                 value = value.replace(microsecond=0) 
    1130             if connection.features.time_field_needs_date: 
    1131                 # cx_Oracle expects a datetime.datetime to persist into TIMESTAMP field. 
    1132                 if isinstance(value, datetime.time): 
    1133                     value = datetime.datetime(1900, 1, 1, value.hour, value.minute, 
    1134                                               value.second, value.microsecond) 
    1135                 elif isinstance(value, basestring): 
    1136                     value = datetime.datetime(*(time.strptime(value, '%H:%M:%S')[:6])) 
    1137             else: 
    1138                 value = smart_unicode(value) 
    1139         return Field.get_db_prep_save(self, value) 
     1151    def get_db_prep_value(self, value): 
     1152        # Casts times into the format expected by the backend 
     1153        return connection.ops.value_to_db_time(self.to_python(value)) 
    11401154 
    11411155    def get_manipulator_field_objs(self): 
  • django/branches/gis/django/db/models/fields/related.py

    r8002 r8215  
    33from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist 
    44from django.db.models.related import RelatedObject 
     5from django.db.models.query import QuerySet 
    56from django.db.models.query_utils import QueryWrapper 
    6 from django.utils.text import capfirst 
    77from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ 
    88from django.utils.functional import curry 
    9 from django.utils.encoding import smart_unicode 
    109from django.core import validators 
    1110from django import oldforms 
     
    2524pending_lookups = {} 
    2625 
    27 def add_lazy_relation(cls, field, relation): 
     26def add_lazy_relation(cls, field, relation, operation): 
    2827    """ 
    2928    Adds a lookup on ``cls`` when a related field is defined using a string, 
     
    4746    lazy relationships -- then the relation won't be set up until the 
    4847    class_prepared signal fires at the end of model initialization. 
     48     
     49    operation is the work that must be performed once the relation can be resolved. 
    4950    """ 
    5051    # Check for recursive relations 
     
    6869    model = get_model(app_label, model_name, False) 
    6970    if model: 
    70         field.rel.to = model 
    71         field.do_related_class(model, cls) 
     71        operation(field, model, cls) 
    7272    else: 
    7373        key = (app_label, model_name) 
    74         value = (cls, field
     74        value = (cls, field, operation
    7575        pending_lookups.setdefault(key, []).append(value) 
    7676 
     
    8080    """ 
    8181    key = (sender._meta.app_label, sender.__name__) 
    82     for cls, field in pending_lookups.pop(key, []): 
    83         field.rel.to = sender 
    84         field.do_related_class(sender, cls) 
     82    for cls, field, operation in pending_lookups.pop(key, []): 
     83        operation(field, sender, cls) 
    8584 
    8685dispatcher.connect(do_pending_lookups, signal=signals.class_prepared) 
     
    110109        other = self.rel.to 
    111110        if isinstance(other, basestring): 
    112             add_lazy_relation(cls, self, other) 
     111            def resolve_related_class(field, model, cls): 
     112                field.rel.to = model 
     113                field.do_related_class(model, cls) 
     114            add_lazy_relation(cls, self, other, resolve_related_class) 
    113115        else: 
    114116            self.do_related_class(other, cls) 
     
    116118    def set_attributes_from_rel(self): 
    117119        self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name) 
    118         self.verbose_name = self.verbose_name or self.rel.to._meta.verbose_name 
     120        if self.verbose_name is None: 
     121            self.verbose_name = self.rel.to._meta.verbose_name 
    119122        self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name 
    120123 
     
    237240            else: 
    238241                params = {'%s__exact' % self.field.rel.field_name: val} 
    239             rel_obj = self.field.rel.to._default_manager.get(**params) 
     242 
     243            # If the related manager indicates that it should be used for 
     244            # related fields, respect that. 
     245            rel_mgr = self.field.rel.to._default_manager 
     246            if getattr(rel_mgr, 'use_for_related_fields', False): 
     247                rel_obj = rel_mgr.get(**params) 
     248            else: 
     249                rel_obj = QuerySet(self.field.rel.to).get(**params) 
    240250            setattr(instance, cache_name, rel_obj) 
    241251            return rel_obj 
     
    341351        manager.add(*value) 
    342352 
    343 def create_many_related_manager(superclass): 
     353def create_many_related_manager(superclass, through=False): 
    344354    """Creates a manager that subclasses 'superclass' (which is a Manager) 
    345355    and adds behavior for many-to-many related objects.""" 
     
    355365            self.source_col_name = source_col_name 
    356366            self.target_col_name = target_col_name 
     367            self.through = through 
    357368            self._pk_val = self.instance._get_pk_val() 
    358369            if self._pk_val is None: 
     
    362373            return superclass.get_query_set(self).filter(**(self.core_filters)) 
    363374 
    364         def add(self, *objs): 
    365             self._add_items(self.source_col_name, self.target_col_name, *objs) 
    366  
    367             # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 
    368             if self.symmetrical: 
    369                 self._add_items(self.target_col_name, self.source_col_name, *objs) 
    370         add.alters_data = True 
    371  
    372         def remove(self, *objs): 
    373             self._remove_items(self.source_col_name, self.target_col_name, *objs) 
    374  
    375             # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 
    376             if self.symmetrical: 
    377                 self._remove_items(self.target_col_name, self.source_col_name, *objs) 
    378         remove.alters_data = True 
     375        # If the ManyToMany relation has an intermediary model,  
     376        # the add and remove methods do not exist. 
     377        if through is None: 
     378            def add(self, *objs): 
     379                self._add_items(self.source_col_name, self.target_col_name, *objs) 
     380 
     381                # If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table 
     382                if self.symmetrical: 
     383                    self._add_items(self.target_col_name, self.source_col_name, *objs) 
     384            add.alters_data = True 
     385 
     386            def remove(self, *objs): 
     387                self._remove_items(self.source_col_name, self.target_col_name, *objs) 
     388 
     389                # If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table 
     390                if self.symmetrical: 
     391                    self._remove_items(self.target_col_name, self.source_col_name, *objs) 
     392            remove.alters_data = True 
    379393 
    380394        def clear(self): 
     
    387401 
    388402        def create(self, **kwargs): 
     403            # This check needs to be done here, since we can't later remove this 
     404            # from the method lookup table, as we do with add and remove. 
     405            if through is not None: 
     406                raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 
    389407            new_obj = self.model(**kwargs) 
    390408            new_obj.save() 
     
    474492        rel_model = self.related.model 
    475493        superclass = rel_model._default_manager.__class__ 
    476         RelatedManager = create_many_related_manager(superclass
     494        RelatedManager = create_many_related_manager(superclass, self.related.field.rel.through
    477495 
    478496        qn = connection.ops.quote_name 
     
    493511            raise AttributeError, "Manager must be accessed via instance" 
    494512 
     513        through = getattr(self.related.field.rel, 'through', None) 
     514        if through is not None: 
     515            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through 
     516 
    495517        manager = self.__get__(instance) 
    496518        manager.clear() 
     
    515537        rel_model=self.field.rel.to 
    516538        superclass = rel_model._default_manager.__class__ 
    517         RelatedManager = create_many_related_manager(superclass
     539        RelatedManager = create_many_related_manager(superclass, self.field.rel.through
    518540 
    519541        qn = connection.ops.quote_name 
     
    533555        if instance is None: 
    534556            raise AttributeError, "Manager must be accessed via instance" 
     557 
     558        through = getattr(self.field.rel, 'through', None) 
     559        if through is not None: 
     560            raise AttributeError, "Cannot set values on a ManyToManyField which specifies an intermediary model.  Use %s's Manager instead." % through 
    535561 
    536562        manager = self.__get__(instance) 
     
    585611class ManyToManyRel(object): 
    586612    def __init__(self, to, num_in_admin=0, related_name=None, 
    587         limit_choices_to=None, symmetrical=True): 
     613        limit_choices_to=None, symmetrical=True, through=None): 
    588614        self.to = to 
    589615        self.num_in_admin = num_in_admin 
     
    595621        self.symmetrical = symmetrical 
    596622        self.multiple = True 
     623        self.through = through 
    597624 
    598625class ForeignKey(RelatedField, Field): 
     
    605632        else: 
    606633            to_field = to_field or to._meta.pk.name 
    607         kwargs['verbose_name'] = kwargs.get('verbose_name', '') 
    608  
    609         if 'edit_inline_type' in kwargs: 
    610             import warnings 
    611             warnings.warn("edit_inline_type is deprecated. Use edit_inline instead.", DeprecationWarning) 
    612             kwargs['edit_inline'] = kwargs.pop('edit_inline_type') 
     634        kwargs['verbose_name'] = kwargs.get('verbose_name', None) 
    613635 
    614636        kwargs['rel'] = rel_class(to, to_field, 
     
    706728    def __init__(self, to, to_field=None, **kwargs): 
    707729        kwargs['unique'] = True 
     730        kwargs['editable'] = False 
    708731        if 'num_in_admin' not in kwargs: 
    709732            kwargs['num_in_admin'] = 0 
     
    723746            related_name=kwargs.pop('related_name', None), 
    724747            limit_choices_to=kwargs.pop('limit_choices_to', None), 
    725             symmetrical=kwargs.pop('symmetrical', True)) 
     748            symmetrical=kwargs.pop('symmetrical', True), 
     749            through=kwargs.pop('through', None)) 
     750             
    726751        self.db_table = kwargs.pop('db_table', None) 
     752        if kwargs['rel'].through is not None: 
     753            self.creates_table = False 
     754            assert self.db_table is None, "Cannot specify a db_table if an intermediary model is used." 
     755        else: 
     756            self.creates_table = True 
     757 
    727758        Field.__init__(self, **kwargs) 
    728759 
     
    739770    def _get_m2m_db_table(self, opts): 
    740771        "Function that can be curried to provide the m2m table name for this relation" 
    741         if self.db_table: 
     772        if self.rel.through is not None: 
     773            return self.rel.through_model._meta.db_table 
     774        elif self.db_table: 
    742775            return self.db_table 
    743776        else: 
     
    746779    def _get_m2m_column_name(self, related): 
    747780        "Function that can be curried to provide the source column name for the m2m table" 
    748         # If this is an m2m relation to self, avoid the inevitable name clash 
    749         if related.model == related.parent_model: 
    750             return 'from_' + related.model._meta.object_name.lower() + '_id' 
    751         else: 
    752             return related.model._meta.object_name.lower() + '_id' 
     781        try: 
     782            return self._m2m_column_name_cache 
     783        except: 
     784            if self.rel.through is not None: 
     785                for f in self.rel.through_model._meta.fields: 
     786                    if hasattr(f,'rel') and f.rel and f.rel.to == related.model: 
     787                        self._m2m_column_name_cache = f.column 
     788                        break 
     789            # If this is an m2m relation to self, avoid the inevitable name clash 
     790            elif related.model == related.parent_model: 
     791                self._m2m_column_name_cache = 'from_' + related.model._meta.object_name.lower() + '_id' 
     792            else: 
     793                self._m2m_column_name_cache = related.model._meta.object_name.lower() + '_id' 
     794                 
     795            # Return the newly cached value 
     796            return self._m2m_column_name_cache 
    753797 
    754798    def _get_m2m_reverse_name(self, related): 
    755799        "Function that can be curried to provide the related column name for the m2m table" 
    756         # If this is an m2m relation to self, avoid the inevitable name clash 
    757         if related.model == related.parent_model: 
    758             return 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
    759         else: 
    760             return related.parent_model._meta.object_name.lower() + '_id' 
     800        try: 
     801            return self._m2m_reverse_name_cache 
     802        except: 
     803            if self.rel.through is not None: 
     804                found = False 
     805                for f in self.rel.through_model._meta.fields: 
     806                    if hasattr(f,'rel') and f.rel and f.rel.to == related.parent_model: 
     807                        if related.model == related.parent_model: 
     808                            # If this is an m2m-intermediate to self,  
     809                            # the first foreign key you find will be  
     810                            # the source column. Keep searching for 
     811                            # the second foreign key. 
     812                            if found: 
     813                                self._m2m_reverse_name_cache = f.column 
     814                                break 
     815                            else: 
     816                                found = True 
     817                        else: 
     818                            self._m2m_reverse_name_cache = f.column 
     819                            break 
     820            # If this is an m2m relation to self, avoid the inevitable name clash 
     821            elif related.model == related.parent_model: 
     822                self._m2m_reverse_name_cache = 'to_' + related.parent_model._meta.object_name.lower() + '_id' 
     823            else: 
     824                self._m2m_reverse_name_cache = related.parent_model._meta.object_name.lower() + '_id' 
     825 
     826            # Return the newly cached value 
     827            return self._m2m_reverse_name_cache 
    761828 
    762829    def isValidIDList(self, field_data, all_data): 
     
    792859 
    793860    def contribute_to_class(self, cls, name): 
    794         super(ManyToManyField, self).contribute_to_class(cls, name) 
     861        super(ManyToManyField, self).contribute_to_class(cls, name)         
    795862        # Add the descriptor for the m2m relation 
    796863        setattr(cls, self.name, ReverseManyRelatedObjectsDescriptor(self)) 
     
    798865        # Set up the accessor for the m2m table name for the relation 
    799866        self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta) 
    800  
     867         
     868        # Populate some necessary rel arguments so that cross-app relations 
     869        # work correctly. 
     870        if isinstance(self.rel.through, basestring): 
     871            def resolve_through_model(field, model, cls): 
     872                field.rel.through_model = model 
     873            add_lazy_relation(cls, self, self.rel.through, resolve_through_model) 
     874        elif self.rel.through: 
     875            self.rel.through_model = self.rel.through 
     876            self.rel.through = self.rel.through._meta.object_name 
     877             
    801878        if isinstance(self.rel.to, basestring): 
    802879            target = self.rel.to 
  • django/branches/gis/django/db/models/fields/subclassing.py

    r7354 r8215  
    66""" 
    77 
    8 from django.utils.maxlength import LegacyMaxlength 
    9  
    10 class SubfieldBase(LegacyMaxlength): 
     8class SubfieldBase(type): 
    119    """ 
    1210    A metaclass for custom Field subclasses. This ensures the model's attribute 
     
    5149 
    5250    return contribute_to_class 
    53  
  • django/branches/gis/django/db/models/__init__.py

    r7979 r8215  
    1111from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED 
    1212from django.db.models import signals 
    13 from django.utils.functional import curry 
    14 from django.utils.text import capfirst 
    1513 
    1614# Admin stages. 
  • django/branches/gis/django/db/models/query.py

    r7979 r8215  
    1 import warnings 
    21try: 
    32    set 
     
    758757 
    759758 
    760 # QOperator, QNot, QAnd and QOr are temporarily retained for backwards 
    761 # compatibility. All the old functionality is now part of the 'Q' class. 
    762 class QOperator(Q): 
    763     def __init__(self, *args, **kwargs): 
    764         warnings.warn('Use Q instead of QOr, QAnd or QOperation.', 
    765                 DeprecationWarning, stacklevel=2) 
    766         super(QOperator, self).__init__(*args, **kwargs) 
    767  
    768 QOr = QAnd = QOperator 
    769  
    770 &n