diff options
Diffstat (limited to 'django/db')
-rw-r--r-- | django/db/backends/ado_mssql/base.py | 3 | ||||
-rw-r--r-- | django/db/backends/mysql/base.py | 3 | ||||
-rw-r--r-- | django/db/backends/oracle/base.py | 3 | ||||
-rw-r--r-- | django/db/backends/postgresql/base.py | 3 | ||||
-rw-r--r-- | django/db/backends/postgresql_psycopg2/base.py | 3 | ||||
-rw-r--r-- | django/db/backends/sqlite3/base.py | 3 | ||||
-rw-r--r-- | django/db/backends/util.py | 2 | ||||
-rw-r--r-- | django/db/models/__init__.py | 3 | ||||
-rw-r--r-- | django/db/models/base.py | 21 | ||||
-rw-r--r-- | django/db/models/fields/__init__.py | 2 | ||||
-rw-r--r-- | django/db/models/fields/generic.py | 259 | ||||
-rw-r--r-- | django/db/models/fields/related.py | 4 | ||||
-rw-r--r-- | django/db/models/manager.py | 11 | ||||
-rw-r--r-- | django/db/models/options.py | 4 | ||||
-rw-r--r-- | django/db/models/query.py | 19 | ||||
-rw-r--r-- | django/db/transaction.py | 2 |
16 files changed, 330 insertions, 15 deletions
diff --git a/django/db/backends/ado_mssql/base.py b/django/db/backends/ado_mssql/base.py index b645b053bf..afe2d19981 100644 --- a/django/db/backends/ado_mssql/base.py +++ b/django/db/backends/ado_mssql/base.py @@ -131,6 +131,9 @@ def get_fulltext_search_sql(field_name): def get_drop_foreignkey_sql(): return "DROP CONSTRAINT" +def get_pk_default_value(): + return "DEFAULT" + OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/mysql/base.py b/django/db/backends/mysql/base.py index 4a13450c67..a522f24f2f 100644 --- a/django/db/backends/mysql/base.py +++ b/django/db/backends/mysql/base.py @@ -158,6 +158,9 @@ def get_fulltext_search_sql(field_name): def get_drop_foreignkey_sql(): return "DROP FOREIGN KEY" +def get_pk_default_value(): + return "DEFAULT" + OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/oracle/base.py b/django/db/backends/oracle/base.py index e981805108..9943ac9610 100644 --- a/django/db/backends/oracle/base.py +++ b/django/db/backends/oracle/base.py @@ -114,6 +114,9 @@ def get_fulltext_search_sql(field_name): def get_drop_foreignkey_sql(): return "DROP FOREIGN KEY" +def get_pk_default_value(): + return "DEFAULT" + OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'LIKE %s', diff --git a/django/db/backends/postgresql/base.py b/django/db/backends/postgresql/base.py index decb160ee9..5355781e81 100644 --- a/django/db/backends/postgresql/base.py +++ b/django/db/backends/postgresql/base.py @@ -108,6 +108,9 @@ def get_fulltext_search_sql(field_name): def get_drop_foreignkey_sql(): return "DROP CONSTRAINT" +def get_pk_default_value(): + return "DEFAULT" + # Register these custom typecasts, because Django expects dates/times to be # in Python's native (standard-library) datetime/time format, whereas psycopg # use mx.DateTime by default. diff --git a/django/db/backends/postgresql_psycopg2/base.py b/django/db/backends/postgresql_psycopg2/base.py index 697a33bb76..55cba81b70 100644 --- a/django/db/backends/postgresql_psycopg2/base.py +++ b/django/db/backends/postgresql_psycopg2/base.py @@ -114,6 +114,9 @@ def get_fulltext_search_sql(field_name): def get_drop_foreignkey_sql(): return "DROP CONSTRAINT" +def get_pk_default_value(): + return "DEFAULT" + OPERATOR_MAPPING = { 'exact': '= %s', 'iexact': 'ILIKE %s', diff --git a/django/db/backends/sqlite3/base.py b/django/db/backends/sqlite3/base.py index 7b51967416..68452e1363 100644 --- a/django/db/backends/sqlite3/base.py +++ b/django/db/backends/sqlite3/base.py @@ -130,6 +130,9 @@ def get_fulltext_search_sql(field_name): def get_drop_foreignkey_sql(): return "" +def get_pk_default_value(): + return "NULL" + def _sqlite_date_trunc(lookup_type, dt): try: dt = util.typecast_timestamp(dt) diff --git a/django/db/backends/util.py b/django/db/backends/util.py index 3098a53556..74d33f42ca 100644 --- a/django/db/backends/util.py +++ b/django/db/backends/util.py @@ -1,7 +1,7 @@ import datetime from time import time -class CursorDebugWrapper: +class CursorDebugWrapper(object): def __init__(self, cursor, db): self.cursor = cursor self.db = db diff --git a/django/db/models/__init__.py b/django/db/models/__init__.py index d708fa60bc..82b1238723 100644 --- a/django/db/models/__init__.py +++ b/django/db/models/__init__.py @@ -8,6 +8,7 @@ from django.db.models.manager import Manager from django.db.models.base import Model, AdminOptions from django.db.models.fields import * from django.db.models.fields.related import ForeignKey, OneToOneField, ManyToManyField, ManyToOneRel, ManyToManyRel, OneToOneRel, TABULAR, STACKED +from django.db.models.fields.generic import GenericRelation, GenericRel, GenericForeignKey from django.db.models import signals from django.utils.functional import curry from django.utils.text import capfirst @@ -15,7 +16,7 @@ from django.utils.text import capfirst # Admin stages. ADD, CHANGE, BOTH = 1, 2, 3 -class LazyDate: +class LazyDate(object): """ Use in limit_choices_to to compare the field to dates calculated at run time instead of when the model is loaded. For example:: diff --git a/django/db/models/base.py b/django/db/models/base.py index 3538826356..7242e6baa7 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -107,6 +107,12 @@ class Model(object): else: val = kwargs.pop(f.attname, f.get_default()) setattr(self, f.attname, val) + for prop in kwargs.keys(): + try: + if isinstance(getattr(self.__class__, prop), property): + setattr(self, prop, kwargs.pop(prop)) + except AttributeError: + pass if kwargs: raise TypeError, "'%s' is an invalid keyword argument for this function" % kwargs.keys()[0] for i, arg in enumerate(args): @@ -165,7 +171,7 @@ class Model(object): cursor.execute("UPDATE %s SET %s WHERE %s=%%s" % \ (backend.quote_name(self._meta.db_table), ','.join(['%s=%%s' % backend.quote_name(f.column) for f in non_pks]), - backend.quote_name(self._meta.pk.attname)), + backend.quote_name(self._meta.pk.column)), db_values + [pk_val]) else: record_exists = False @@ -183,9 +189,16 @@ class Model(object): placeholders.append('(SELECT COUNT(*) FROM %s WHERE %s = %%s)' % \ (backend.quote_name(self._meta.db_table), backend.quote_name(self._meta.order_with_respect_to.column))) db_values.append(getattr(self, self._meta.order_with_respect_to.attname)) - cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \ - (backend.quote_name(self._meta.db_table), ','.join(field_names), - ','.join(placeholders)), db_values) + if db_values: + cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % \ + (backend.quote_name(self._meta.db_table), ','.join(field_names), + ','.join(placeholders)), db_values) + else: + # Create a new record with defaults for everything. + cursor.execute("INSERT INTO %s (%s) VALUES (%s)" % + (backend.quote_name(self._meta.db_table), + backend.quote_name(self._meta.pk.column), + backend.get_pk_default_value())) if self._meta.has_auto_field and not pk_set: setattr(self, self._meta.pk.attname, backend.get_last_insert_id(cursor, self._meta.db_table, self._meta.pk.column)) transaction.commit_unless_managed() diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py index 2f8a8651a1..720737efcf 100644 --- a/django/db/models/fields/__init__.py +++ b/django/db/models/fields/__init__.py @@ -535,7 +535,7 @@ class FileField(Field): if not self.blank: if rel: # This validator makes sure FileFields work in a related context. - class RequiredFileField: + class RequiredFileField(object): def __init__(self, other_field_names, other_file_field_name): self.other_field_names = other_field_names self.other_file_field_name = other_file_field_name diff --git a/django/db/models/fields/generic.py b/django/db/models/fields/generic.py new file mode 100644 index 0000000000..5f4de40e69 --- /dev/null +++ b/django/db/models/fields/generic.py @@ -0,0 +1,259 @@ +""" +Classes allowing "generic" relations through ContentType and object-id fields. +""" + +from django import forms +from django.core.exceptions import ObjectDoesNotExist +from django.db import backend +from django.db.models import signals +from django.db.models.fields.related import RelatedField, Field, ManyToManyRel +from django.db.models.loading import get_model +from django.dispatch import dispatcher +from django.utils.functional import curry + +class GenericForeignKey(object): + """ + Provides a generic relation to any object through content-type/object-id + fields. + """ + + def __init__(self, ct_field="content_type", fk_field="object_id"): + self.ct_field = ct_field + self.fk_field = fk_field + + def contribute_to_class(self, cls, name): + # Make sure the fields exist (these raise FieldDoesNotExist, + # which is a fine error to raise here) + self.name = name + self.model = cls + self.cache_attr = "_%s_cache" % name + + # For some reason I don't totally understand, using weakrefs here doesn't work. + dispatcher.connect(self.instance_pre_init, signal=signals.pre_init, sender=cls, weak=False) + + # Connect myself as the descriptor for this field + setattr(cls, name, self) + + def instance_pre_init(self, signal, sender, args, kwargs): + # Handle initalizing an object with the generic FK instaed of + # content-type/object-id fields. + if kwargs.has_key(self.name): + value = kwargs.pop(self.name) + kwargs[self.ct_field] = self.get_content_type(value) + kwargs[self.fk_field] = value._get_pk_val() + + def get_content_type(self, obj): + # Convenience function using get_model avoids a circular import when using this model + ContentType = get_model("contenttypes", "contenttype") + return ContentType.objects.get_for_model(obj) + + def __get__(self, instance, instance_type=None): + if instance is None: + raise AttributeError, "%s must be accessed via instance" % self.name + + try: + return getattr(instance, self.cache_attr) + except AttributeError: + rel_obj = None + ct = getattr(instance, self.ct_field) + if ct: + try: + rel_obj = ct.get_object_for_this_type(pk=getattr(instance, self.fk_field)) + except ObjectDoesNotExist: + pass + setattr(instance, self.cache_attr, rel_obj) + return rel_obj + + def __set__(self, instance, value): + if instance is None: + raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name + + ct = None + fk = None + if value is not None: + ct = self.get_content_type(value) + fk = value._get_pk_val() + + setattr(instance, self.ct_field, ct) + setattr(instance, self.fk_field, fk) + setattr(instance, self.cache_attr, value) + +class GenericRelation(RelatedField, Field): + """Provides an accessor to generic related objects (i.e. comments)""" + + def __init__(self, to, **kwargs): + kwargs['verbose_name'] = kwargs.get('verbose_name', None) + kwargs['rel'] = GenericRel(to, + related_name=kwargs.pop('related_name', None), + limit_choices_to=kwargs.pop('limit_choices_to', None), + symmetrical=kwargs.pop('symmetrical', True)) + + # Override content-type/object-id field names on the related class + self.object_id_field_name = kwargs.pop("object_id_field", "object_id") + self.content_type_field_name = kwargs.pop("content_type_field", "content_type") + + kwargs['blank'] = True + kwargs['editable'] = False + Field.__init__(self, **kwargs) + + def get_manipulator_field_objs(self): + choices = self.get_choices_default() + return [curry(forms.SelectMultipleField, size=min(max(len(choices), 5), 15), choices=choices)] + + def get_choices_default(self): + return Field.get_choices(self, include_blank=False) + + def flatten_data(self, follow, obj = None): + new_data = {} + if obj: + instance_ids = [instance._get_pk_val() for instance in getattr(obj, self.name).all()] + new_data[self.name] = instance_ids + return new_data + + def m2m_db_table(self): + return self.rel.to._meta.db_table + + def m2m_column_name(self): + return self.object_id_field_name + + def m2m_reverse_name(self): + return self.model._meta.pk.attname + + def contribute_to_class(self, cls, name): + super(GenericRelation, self).contribute_to_class(cls, name) + + # Save a reference to which model this class is on for future use + self.model = cls + + # Add the descriptor for the m2m relation + setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self)) + + def contribute_to_related_class(self, cls, related): + pass + + def set_attributes_from_rel(self): + pass + + def get_internal_type(self): + return "ManyToManyField" + +class ReverseGenericRelatedObjectsDescriptor(object): + """ + This class provides the functionality that makes the related-object + managers available as attributes on a model class, for fields that have + multiple "remote" values and have a GenericRelation defined in their model + (rather than having another model pointed *at* them). In the example + "article.publications", the publications attribute is a + ReverseGenericRelatedObjectsDescriptor instance. + """ + def __init__(self, field): + self.field = field + + def __get__(self, instance, instance_type=None): + if instance is None: + raise AttributeError, "Manager must be accessed via instance" + + # This import is done here to avoid circular import importing this module + from django.contrib.contenttypes.models import ContentType + + # Dynamically create a class that subclasses the related model's + # default manager. + rel_model = self.field.rel.to + superclass = rel_model._default_manager.__class__ + RelatedManager = create_generic_related_manager(superclass) + + manager = RelatedManager( + model = rel_model, + instance = instance, + symmetrical = (self.field.rel.symmetrical and instance.__class__ == rel_model), + join_table = backend.quote_name(self.field.m2m_db_table()), + source_col_name = backend.quote_name(self.field.m2m_column_name()), + target_col_name = backend.quote_name(self.field.m2m_reverse_name()), + content_type = ContentType.objects.get_for_model(self.field.model), + content_type_field_name = self.field.content_type_field_name, + object_id_field_name = self.field.object_id_field_name + ) + + return manager + + def __set__(self, instance, value): + if instance is None: + raise AttributeError, "Manager must be accessed via instance" + + manager = self.__get__(instance) + manager.clear() + for obj in value: + manager.add(obj) + +def create_generic_related_manager(superclass): + """ + Factory function for a manager that subclasses 'superclass' (which is a + Manager) and adds behavior for generic related objects. + """ + + class GenericRelatedObjectManager(superclass): + def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, + join_table=None, source_col_name=None, target_col_name=None, content_type=None, + content_type_field_name=None, object_id_field_name=None): + + super(GenericRelatedObjectManager, self).__init__() + self.core_filters = core_filters or {} + self.model = model + self.content_type = content_type + self.symmetrical = symmetrical + self.instance = instance + self.join_table = join_table + self.join_table = model._meta.db_table + self.source_col_name = source_col_name + self.target_col_name = target_col_name + self.content_type_field_name = content_type_field_name + self.object_id_field_name = object_id_field_name + self.pk_val = self.instance._get_pk_val() + + def get_query_set(self): + query = { + '%s__pk' % self.content_type_field_name : self.content_type.id, + '%s__exact' % self.object_id_field_name : self.pk_val, + } + return superclass.get_query_set(self).filter(**query) + + def add(self, *objs): + for obj in objs: + setattr(obj, self.content_type_field_name, self.content_type) + setattr(obj, self.object_id_field_name, self.pk_val) + obj.save() + add.alters_data = True + + def remove(self, *objs): + for obj in objs: + obj.delete() + remove.alters_data = True + + def clear(self): + for obj in self.all(): + obj.delete() + clear.alters_data = True + + def create(self, **kwargs): + kwargs[self.content_type_field_name] = self.content_type + kwargs[self.object_id_field_name] = self.pk_val + obj = self.model(**kwargs) + obj.save() + return obj + create.alters_data = True + + return GenericRelatedObjectManager + +class GenericRel(ManyToManyRel): + def __init__(self, to, related_name=None, limit_choices_to=None, symmetrical=True): + self.to = to + self.num_in_admin = 0 + self.related_name = related_name + self.filter_interface = None + self.limit_choices_to = limit_choices_to or {} + self.edit_inline = False + self.raw_id_admin = False + self.symmetrical = symmetrical + self.multiple = True + assert not (self.raw_id_admin and self.filter_interface), \ + "Generic relations may not use both raw_id_admin and filter_interface" diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py index 6e0fb6d2a8..f9750217a2 100644 --- a/django/db/models/fields/related.py +++ b/django/db/models/fields/related.py @@ -667,7 +667,7 @@ class ManyToManyField(RelatedField, Field): def set_attributes_from_rel(self): pass -class ManyToOneRel: +class ManyToOneRel(object): def __init__(self, to, field_name, num_in_admin=3, min_num_in_admin=None, max_num_in_admin=None, num_extra_on_change=1, edit_inline=False, related_name=None, limit_choices_to=None, lookup_overrides=None, raw_id_admin=False): @@ -704,7 +704,7 @@ class OneToOneRel(ManyToOneRel): self.raw_id_admin = raw_id_admin self.multiple = False -class ManyToManyRel: +class ManyToManyRel(object): def __init__(self, to, num_in_admin=0, related_name=None, filter_interface=None, limit_choices_to=None, raw_id_admin=False, symmetrical=True): self.to = to diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 93de4a6adc..f679c5492c 100644 --- a/django/db/models/manager.py +++ b/django/db/models/manager.py @@ -3,6 +3,7 @@ from django.db import backend, connection from django.db.models.query import QuerySet from django.dispatch import dispatcher from django.db.models import signals +from django.db.models.fields import FieldDoesNotExist from django.utils.datastructures import SortedDict # Size of each "chunk" for get_iterator calls. @@ -13,8 +14,11 @@ def ensure_default_manager(sender): cls = sender if not hasattr(cls, '_default_manager'): # Create the default manager, if needed. - if hasattr(cls, 'objects'): - raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % name + try: + cls._meta.get_field('objects') + raise ValueError, "Model %s must specify a custom Manager, because it has a field named 'objects'" % cls.__name__ + except FieldDoesNotExist: + pass cls.add_to_class('objects', Manager()) dispatcher.connect(ensure_default_manager, signal=signals.class_prepared) @@ -65,6 +69,9 @@ class Manager(object): def get(self, *args, **kwargs): return self.get_query_set().get(*args, **kwargs) + def get_or_create(self, *args, **kwargs): + return self.get_query_set().get_or_create(*args, **kwargs) + def filter(self, *args, **kwargs): return self.get_query_set().filter(*args, **kwargs) diff --git a/django/db/models/options.py b/django/db/models/options.py index 1023689a86..f8149bdf5c 100644 --- a/django/db/models/options.py +++ b/django/db/models/options.py @@ -15,7 +15,7 @@ DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 'unique_together', 'permissions', 'get_latest_by', 'order_with_respect_to', 'app_label') -class Options: +class Options(object): def __init__(self, meta): self.fields, self.many_to_many = [], [] self.module_name, self.verbose_name = None, None @@ -195,7 +195,7 @@ class Options: self._field_types[field_type] = False return self._field_types[field_type] -class AdminOptions: +class AdminOptions(object): def __init__(self, fields=None, js=None, list_display=None, list_filter=None, date_hierarchy=None, save_as=False, ordering=None, search_fields=None, save_on_top=False, list_select_related=False, manager=None, list_per_page=100): diff --git a/django/db/models/query.py b/django/db/models/query.py index 4bd9b3b9fe..e826efa779 100644 --- a/django/db/models/query.py +++ b/django/db/models/query.py @@ -205,6 +205,23 @@ class QuerySet(object): assert len(obj_list) == 1, "get() returned more than one %s -- it returned %s! Lookup parameters were %s" % (self.model._meta.object_name, len(obj_list), kwargs) return obj_list[0] + def get_or_create(self, **kwargs): + """ + Looks up an object with the given kwargs, creating one if necessary. + Returns a tuple of (object, created), where created is a boolean + specifying whether an object was created. + """ + assert len(kwargs), 'get_or_create() must be passed at least one keyword argument' + defaults = kwargs.pop('defaults', {}) + try: + return self.get(**kwargs), False + except self.model.DoesNotExist: + params = dict([(k, v) for k, v in kwargs.items() if '__' not in k]) + params.update(defaults) + obj = self.model(**params) + obj.save() + return obj, True + def latest(self, field_name=None): """ Returns the latest object, according to the model's 'get_latest_by' @@ -529,7 +546,7 @@ class DateQuerySet(QuerySet): c._order = self._order return c -class QOperator: +class QOperator(object): "Base class for QAnd and QOr" def __init__(self, *args): self.args = args diff --git a/django/db/transaction.py b/django/db/transaction.py index 60a743c42a..4a0658e1c3 100644 --- a/django/db/transaction.py +++ b/django/db/transaction.py @@ -114,7 +114,7 @@ def is_managed(): def managed(flag=True): """ Puts the transaction manager into a manual state: managed transactions have - to be committed explicitely by the user. If you switch off transaction + to be committed explicitly by the user. If you switch off transaction management and there is a pending commit/rollback, the data will be commited. """ |