summaryrefslogtreecommitdiff
path: root/django/db
diff options
context:
space:
mode:
Diffstat (limited to 'django/db')
-rw-r--r--django/db/backends/ado_mssql/base.py3
-rw-r--r--django/db/backends/mysql/base.py3
-rw-r--r--django/db/backends/oracle/base.py3
-rw-r--r--django/db/backends/postgresql/base.py3
-rw-r--r--django/db/backends/postgresql_psycopg2/base.py3
-rw-r--r--django/db/backends/sqlite3/base.py3
-rw-r--r--django/db/backends/util.py2
-rw-r--r--django/db/models/__init__.py3
-rw-r--r--django/db/models/base.py21
-rw-r--r--django/db/models/fields/__init__.py2
-rw-r--r--django/db/models/fields/generic.py259
-rw-r--r--django/db/models/fields/related.py4
-rw-r--r--django/db/models/manager.py11
-rw-r--r--django/db/models/options.py4
-rw-r--r--django/db/models/query.py19
-rw-r--r--django/db/transaction.py2
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.
"""