| 1 |
from django.db import connection, transaction |
|---|
| 2 |
from django.db.models import signals, get_model |
|---|
| 3 |
from django.db.models.fields import AutoField, Field, IntegerField, PositiveIntegerField, PositiveSmallIntegerField, FieldDoesNotExist |
|---|
| 4 |
from django.db.models.related import RelatedObject |
|---|
| 5 |
from django.db.models.query import QuerySet |
|---|
| 6 |
from django.db.models.query_utils import QueryWrapper |
|---|
| 7 |
from django.utils.encoding import smart_unicode |
|---|
| 8 |
from django.utils.translation import ugettext_lazy, string_concat, ungettext, ugettext as _ |
|---|
| 9 |
from django.utils.functional import curry |
|---|
| 10 |
from django.core import exceptions |
|---|
| 11 |
from django import forms |
|---|
| 12 |
|
|---|
| 13 |
try: |
|---|
| 14 |
set |
|---|
| 15 |
except NameError: |
|---|
| 16 |
from sets import Set as set # Python 2.3 fallback |
|---|
| 17 |
|
|---|
| 18 |
RECURSIVE_RELATIONSHIP_CONSTANT = 'self' |
|---|
| 19 |
|
|---|
| 20 |
pending_lookups = {} |
|---|
| 21 |
|
|---|
| 22 |
def add_lazy_relation(cls, field, relation, operation): |
|---|
| 23 |
""" |
|---|
| 24 |
Adds a lookup on ``cls`` when a related field is defined using a string, |
|---|
| 25 |
i.e.:: |
|---|
| 26 |
|
|---|
| 27 |
class MyModel(Model): |
|---|
| 28 |
fk = ForeignKey("AnotherModel") |
|---|
| 29 |
|
|---|
| 30 |
This string can be: |
|---|
| 31 |
|
|---|
| 32 |
* RECURSIVE_RELATIONSHIP_CONSTANT (i.e. "self") to indicate a recursive |
|---|
| 33 |
relation. |
|---|
| 34 |
|
|---|
| 35 |
* The name of a model (i.e "AnotherModel") to indicate another model in |
|---|
| 36 |
the same app. |
|---|
| 37 |
|
|---|
| 38 |
* An app-label and model name (i.e. "someapp.AnotherModel") to indicate |
|---|
| 39 |
another model in a different app. |
|---|
| 40 |
|
|---|
| 41 |
If the other model hasn't yet been loaded -- almost a given if you're using |
|---|
| 42 |
lazy relationships -- then the relation won't be set up until the |
|---|
| 43 |
class_prepared signal fires at the end of model initialization. |
|---|
| 44 |
|
|---|
| 45 |
operation is the work that must be performed once the relation can be resolved. |
|---|
| 46 |
""" |
|---|
| 47 |
# Check for recursive relations |
|---|
| 48 |
if relation == RECURSIVE_RELATIONSHIP_CONSTANT: |
|---|
| 49 |
app_label = cls._meta.app_label |
|---|
| 50 |
model_name = cls.__name__ |
|---|
| 51 |
|
|---|
| 52 |
else: |
|---|
| 53 |
# Look for an "app.Model" relation |
|---|
| 54 |
try: |
|---|
| 55 |
app_label, model_name = relation.split(".") |
|---|
| 56 |
except ValueError: |
|---|
| 57 |
# If we can't split, assume a model in current app |
|---|
| 58 |
app_label = cls._meta.app_label |
|---|
| 59 |
model_name = relation |
|---|
| 60 |
|
|---|
| 61 |
# Try to look up the related model, and if it's already loaded resolve the |
|---|
| 62 |
# string right away. If get_model returns None, it means that the related |
|---|
| 63 |
# model isn't loaded yet, so we need to pend the relation until the class |
|---|
| 64 |
# is prepared. |
|---|
| 65 |
model = get_model(app_label, model_name, False) |
|---|
| 66 |
if model: |
|---|
| 67 |
operation(field, model, cls) |
|---|
| 68 |
else: |
|---|
| 69 |
key = (app_label, model_name) |
|---|
| 70 |
value = (cls, field, operation) |
|---|
| 71 |
pending_lookups.setdefault(key, []).append(value) |
|---|
| 72 |
|
|---|
| 73 |
def do_pending_lookups(sender, **kwargs): |
|---|
| 74 |
""" |
|---|
| 75 |
Handle any pending relations to the sending model. Sent from class_prepared. |
|---|
| 76 |
""" |
|---|
| 77 |
key = (sender._meta.app_label, sender.__name__) |
|---|
| 78 |
for cls, field, operation in pending_lookups.pop(key, []): |
|---|
| 79 |
operation(field, sender, cls) |
|---|
| 80 |
|
|---|
| 81 |
signals.class_prepared.connect(do_pending_lookups) |
|---|
| 82 |
|
|---|
| 83 |
#HACK |
|---|
| 84 |
class RelatedField(object): |
|---|
| 85 |
def contribute_to_class(self, cls, name): |
|---|
| 86 |
sup = super(RelatedField, self) |
|---|
| 87 |
|
|---|
| 88 |
# Add an accessor to allow easy determination of the related query path for this field |
|---|
| 89 |
self.related_query_name = curry(self._get_related_query_name, cls._meta) |
|---|
| 90 |
|
|---|
| 91 |
if hasattr(sup, 'contribute_to_class'): |
|---|
| 92 |
sup.contribute_to_class(cls, name) |
|---|
| 93 |
|
|---|
| 94 |
if not cls._meta.abstract and self.rel.related_name: |
|---|
| 95 |
self.rel.related_name = self.rel.related_name % {'class': cls.__name__.lower()} |
|---|
| 96 |
|
|---|
| 97 |
other = self.rel.to |
|---|
| 98 |
if isinstance(other, basestring): |
|---|
| 99 |
def resolve_related_class(field, model, cls): |
|---|
| 100 |
field.rel.to = model |
|---|
| 101 |
field.do_related_class(model, cls) |
|---|
| 102 |
add_lazy_relation(cls, self, other, resolve_related_class) |
|---|
| 103 |
else: |
|---|
| 104 |
self.do_related_class(other, cls) |
|---|
| 105 |
|
|---|
| 106 |
def set_attributes_from_rel(self): |
|---|
| 107 |
self.name = self.name or (self.rel.to._meta.object_name.lower() + '_' + self.rel.to._meta.pk.name) |
|---|
| 108 |
if self.verbose_name is None: |
|---|
| 109 |
self.verbose_name = self.rel.to._meta.verbose_name |
|---|
| 110 |
self.rel.field_name = self.rel.field_name or self.rel.to._meta.pk.name |
|---|
| 111 |
|
|---|
| 112 |
def do_related_class(self, other, cls): |
|---|
| 113 |
self.set_attributes_from_rel() |
|---|
| 114 |
related = RelatedObject(other, cls, self) |
|---|
| 115 |
if not cls._meta.abstract: |
|---|
| 116 |
self.contribute_to_related_class(other, related) |
|---|
| 117 |
|
|---|
| 118 |
def get_db_prep_lookup(self, lookup_type, value): |
|---|
| 119 |
# If we are doing a lookup on a Related Field, we must be |
|---|
| 120 |
# comparing object instances. The value should be the PK of value, |
|---|
| 121 |
# not value itself. |
|---|
| 122 |
def pk_trace(value): |
|---|
| 123 |
# Value may be a primary key, or an object held in a relation. |
|---|
| 124 |
# If it is an object, then we need to get the primary key value for |
|---|
| 125 |
# that object. In certain conditions (especially one-to-one relations), |
|---|
| 126 |
# the primary key may itself be an object - so we need to keep drilling |
|---|
| 127 |
# down until we hit a value that can be used for a comparison. |
|---|
| 128 |
v, field = value, None |
|---|
| 129 |
try: |
|---|
| 130 |
while True: |
|---|
| 131 |
v, field = getattr(v, v._meta.pk.name), v._meta.pk |
|---|
| 132 |
except AttributeError: |
|---|
| 133 |
pass |
|---|
| 134 |
if field: |
|---|
| 135 |
if lookup_type in ('range', 'in'): |
|---|
| 136 |
v = [v] |
|---|
| 137 |
v = field.get_db_prep_lookup(lookup_type, v) |
|---|
| 138 |
if isinstance(v, list): |
|---|
| 139 |
v = v[0] |
|---|
| 140 |
return v |
|---|
| 141 |
|
|---|
| 142 |
if hasattr(value, 'as_sql'): |
|---|
| 143 |
sql, params = value.as_sql() |
|---|
| 144 |
return QueryWrapper(('(%s)' % sql), params) |
|---|
| 145 |
|
|---|
| 146 |
# FIXME: lt and gt are explicitally allowed to make |
|---|
| 147 |
# get_(next/prev)_by_date work; other lookups are not allowed since that |
|---|
| 148 |
# gets messy pretty quick. This is a good candidate for some refactoring |
|---|
| 149 |
# in the future. |
|---|
| 150 |
if lookup_type in ['exact', 'gt', 'lt']: |
|---|
| 151 |
return [pk_trace(value)] |
|---|
| 152 |
if lookup_type in ('range', 'in'): |
|---|
| 153 |
return [pk_trace(v) for v in value] |
|---|
| 154 |
elif lookup_type == 'isnull': |
|---|
| 155 |
return [] |
|---|
| 156 |
raise TypeError, "Related Field has invalid lookup: %s" % lookup_type |
|---|
| 157 |
|
|---|
| 158 |
def _get_related_query_name(self, opts): |
|---|
| 159 |
# This method defines the name that can be used to identify this |
|---|
| 160 |
# related object in a table-spanning query. It uses the lower-cased |
|---|
| 161 |
# object_name by default, but this can be overridden with the |
|---|
| 162 |
# "related_name" option. |
|---|
| 163 |
return self.rel.related_name or opts.object_name.lower() |
|---|
| 164 |
|
|---|
| 165 |
class SingleRelatedObjectDescriptor(object): |
|---|
| 166 |
# This class provides the functionality that makes the related-object |
|---|
| 167 |
# managers available as attributes on a model class, for fields that have |
|---|
| 168 |
# a single "remote" value, on the class pointed to by a related field. |
|---|
| 169 |
# In the example "place.restaurant", the restaurant attribute is a |
|---|
| 170 |
# SingleRelatedObjectDescriptor instance. |
|---|
| 171 |
def __init__(self, related): |
|---|
| 172 |
self.related = related |
|---|
| 173 |
self.cache_name = '_%s_cache' % related.get_accessor_name() |
|---|
| 174 |
|
|---|
| 175 |
def __get__(self, instance, instance_type=None): |
|---|
| 176 |
if instance is None: |
|---|
| 177 |
raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name |
|---|
| 178 |
|
|---|
| 179 |
try: |
|---|
| 180 |
return getattr(instance, self.cache_name) |
|---|
| 181 |
except AttributeError: |
|---|
| 182 |
params = {'%s__pk' % self.related.field.name: instance._get_pk_val()} |
|---|
| 183 |
rel_obj = self.related.model._default_manager.get(**params) |
|---|
| 184 |
setattr(instance, self.cache_name, rel_obj) |
|---|
| 185 |
return rel_obj |
|---|
| 186 |
|
|---|
| 187 |
def __set__(self, instance, value): |
|---|
| 188 |
if instance is None: |
|---|
| 189 |
raise AttributeError, "%s must be accessed via instance" % self.related.opts.object_name |
|---|
| 190 |
|
|---|
| 191 |
# The similarity of the code below to the code in |
|---|
| 192 |
# ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch |
|---|
| 193 |
# of small differences that would make a common base class convoluted. |
|---|
| 194 |
|
|---|
| 195 |
# If null=True, we can assign null here, but otherwise the value needs |
|---|
| 196 |
# to be an instance of the related class. |
|---|
| 197 |
if value is None and self.related.field.null == False: |
|---|
| 198 |
raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' % |
|---|
| 199 |
(instance._meta.object_name, self.related.get_accessor_name())) |
|---|
| 200 |
elif value is not None and not isinstance(value, self.related.model): |
|---|
| 201 |
raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' % |
|---|
| 202 |
(value, instance._meta.object_name, |
|---|
| 203 |
self.related.get_accessor_name(), self.related.opts.object_name)) |
|---|
| 204 |
|
|---|
| 205 |
# Set the value of the related field |
|---|
| 206 |
setattr(value, self.related.field.rel.get_related_field().attname, instance) |
|---|
| 207 |
|
|---|
| 208 |
# Since we already know what the related object is, seed the related |
|---|
| 209 |
# object caches now, too. This avoids another db hit if you get the |
|---|
| 210 |
# object you just set. |
|---|
| 211 |
setattr(instance, self.cache_name, value) |
|---|
| 212 |
setattr(value, self.related.field.get_cache_name(), instance) |
|---|
| 213 |
|
|---|
| 214 |
class ReverseSingleRelatedObjectDescriptor(object): |
|---|
| 215 |
# This class provides the functionality that makes the related-object |
|---|
| 216 |
# managers available as attributes on a model class, for fields that have |
|---|
| 217 |
# a single "remote" value, on the class that defines the related field. |
|---|
| 218 |
# In the example "choice.poll", the poll attribute is a |
|---|
| 219 |
# ReverseSingleRelatedObjectDescriptor instance. |
|---|
| 220 |
def __init__(self, field_with_rel): |
|---|
| 221 |
self.field = field_with_rel |
|---|
| 222 |
|
|---|
| 223 |
def __get__(self, instance, instance_type=None): |
|---|
| 224 |
if instance is None: |
|---|
| 225 |
raise AttributeError, "%s must be accessed via instance" % self.field.name |
|---|
| 226 |
cache_name = self.field.get_cache_name() |
|---|
| 227 |
try: |
|---|
| 228 |
return getattr(instance, cache_name) |
|---|
| 229 |
except AttributeError: |
|---|
| 230 |
val = getattr(instance, self.field.attname) |
|---|
| 231 |
if val is None: |
|---|
| 232 |
# If NULL is an allowed value, return it. |
|---|
| 233 |
if self.field.null: |
|---|
| 234 |
return None |
|---|
| 235 |
raise self.field.rel.to.DoesNotExist |
|---|
| 236 |
other_field = self.field.rel.get_related_field() |
|---|
| 237 |
if other_field.rel: |
|---|
| 238 |
params = {'%s__pk' % self.field.rel.field_name: val} |
|---|
| 239 |
else: |
|---|
| 240 |
params = {'%s__exact' % self.field.rel.field_name: val} |
|---|
| 241 |
|
|---|
| 242 |
# If the related manager indicates that it should be used for |
|---|
| 243 |
# related fields, respect that. |
|---|
| 244 |
rel_mgr = self.field.rel.to._default_manager |
|---|
| 245 |
if getattr(rel_mgr, 'use_for_related_fields', False): |
|---|
| 246 |
rel_obj = rel_mgr.get(**params) |
|---|
| 247 |
else: |
|---|
| 248 |
rel_obj = QuerySet(self.field.rel.to).get(**params) |
|---|
| 249 |
setattr(instance, cache_name, rel_obj) |
|---|
| 250 |
return rel_obj |
|---|
| 251 |
|
|---|
| 252 |
def __set__(self, instance, value): |
|---|
| 253 |
if instance is None: |
|---|
| 254 |
raise AttributeError, "%s must be accessed via instance" % self._field.name |
|---|
| 255 |
|
|---|
| 256 |
# If null=True, we can assign null here, but otherwise the value needs |
|---|
| 257 |
# to be an instance of the related class. |
|---|
| 258 |
if value is None and self.field.null == False: |
|---|
| 259 |
raise ValueError('Cannot assign None: "%s.%s" does not allow null values.' % |
|---|
| 260 |
(instance._meta.object_name, self.field.name)) |
|---|
| 261 |
elif value is not None and not isinstance(value, self.field.rel.to): |
|---|
| 262 |
raise ValueError('Cannot assign "%r": "%s.%s" must be a "%s" instance.' % |
|---|
| 263 |
(value, instance._meta.object_name, |
|---|
| 264 |
self.field.name, self.field.rel.to._meta.object_name)) |
|---|
| 265 |
|
|---|
| 266 |
# Set the value of the related field |
|---|
| 267 |
try: |
|---|
| 268 |
val = getattr(value, self.field.rel.get_related_field().attname) |
|---|
| 269 |
except AttributeError: |
|---|
| 270 |
val = None |
|---|
| 271 |
setattr(instance, self.field.attname, val) |
|---|
| 272 |
|
|---|
| 273 |
# Since we already know what the related object is, seed the related |
|---|
| 274 |
# object cache now, too. This avoids another db hit if you get the |
|---|
| 275 |
# object you just set. |
|---|
| 276 |
setattr(instance, self.field.get_cache_name(), value) |
|---|
| 277 |
|
|---|
| 278 |
class ForeignRelatedObjectsDescriptor(object): |
|---|
| 279 |
# This class provides the functionality that makes the related-object |
|---|
| 280 |
# managers available as attributes on a model class, for fields that have |
|---|
| 281 |
# multiple "remote" values and have a ForeignKey pointed at them by |
|---|
| 282 |
# some other model. In the example "poll.choice_set", the choice_set |
|---|
| 283 |
# attribute is a ForeignRelatedObjectsDescriptor instance. |
|---|
| 284 |
def __init__(self, related): |
|---|
| 285 |
self.related = related # RelatedObject instance |
|---|
| 286 |
|
|---|
| 287 |
def __get__(self, instance, instance_type=None): |
|---|
| 288 |
if instance is None: |
|---|
| 289 |
raise AttributeError, "Manager must be accessed via instance" |
|---|
| 290 |
|
|---|
| 291 |
rel_field = self.related.field |
|---|
| 292 |
rel_model = self.related.model |
|---|
| 293 |
|
|---|
| 294 |
# Dynamically create a class that subclasses the related |
|---|
| 295 |
# model's default manager. |
|---|
| 296 |
superclass = self.related.model._default_manager.__class__ |
|---|
| 297 |
|
|---|
| 298 |
class RelatedManager(superclass): |
|---|
| 299 |
def get_query_set(self): |
|---|
| 300 |
return superclass.get_query_set(self).filter(**(self.core_filters)) |
|---|
| 301 |
|
|---|
| 302 |
def add(self, *objs): |
|---|
| 303 |
for obj in objs: |
|---|
| 304 |
setattr(obj, rel_field.name, instance) |
|---|
| 305 |
obj.save() |
|---|
| 306 |
add.alters_data = True |
|---|
| 307 |
|
|---|
| 308 |
def create(self, **kwargs): |
|---|
| 309 |
kwargs.update({rel_field.name: instance}) |
|---|
| 310 |
return super(RelatedManager, self).create(**kwargs) |
|---|
| 311 |
create.alters_data = True |
|---|
| 312 |
|
|---|
| 313 |
def get_or_create(self, **kwargs): |
|---|
| 314 |
# Update kwargs with the related object that this |
|---|
| 315 |
# ForeignRelatedObjectsDescriptor knows about. |
|---|
| 316 |
kwargs.update({rel_field.name: instance}) |
|---|
| 317 |
return super(RelatedManager, self).get_or_create(**kwargs) |
|---|
| 318 |
get_or_create.alters_data = True |
|---|
| 319 |
|
|---|
| 320 |
# remove() and clear() are only provided if the ForeignKey can have a value of null. |
|---|
| 321 |
if rel_field.null: |
|---|
| 322 |
def remove(self, *objs): |
|---|
| 323 |
val = getattr(instance, rel_field.rel.get_related_field().attname) |
|---|
| 324 |
for obj in objs: |
|---|
| 325 |
# Is obj actually part of this descriptor set? |
|---|
| 326 |
if getattr(obj, rel_field.attname) == val: |
|---|
| 327 |
setattr(obj, rel_field.name, None) |
|---|
| 328 |
obj.save() |
|---|
| 329 |
else: |
|---|
| 330 |
raise rel_field.rel.to.DoesNotExist, "%r is not related to %r." % (obj, instance) |
|---|
| 331 |
remove.alters_data = True |
|---|
| 332 |
|
|---|
| 333 |
def clear(self): |
|---|
| 334 |
for obj in self.all(): |
|---|
| 335 |
setattr(obj, rel_field.name, None) |
|---|
| 336 |
obj.save() |
|---|
| 337 |
clear.alters_data = True |
|---|
| 338 |
|
|---|
| 339 |
manager = RelatedManager() |
|---|
| 340 |
attname = rel_field.rel.get_related_field().name |
|---|
| 341 |
manager.core_filters = {'%s__%s' % (rel_field.name, attname): |
|---|
| 342 |
getattr(instance, attname)} |
|---|
| 343 |
manager.model = self.related.model |
|---|
| 344 |
|
|---|
| 345 |
return manager |
|---|
| 346 |
|
|---|
| 347 |
def __set__(self, instance, value): |
|---|
| 348 |
if instance is None: |
|---|
| 349 |
raise AttributeError, "Manager must be accessed via instance" |
|---|
| 350 |
|
|---|
| 351 |
manager = self.__get__(instance) |
|---|
| 352 |
# If the foreign key can support nulls, then completely clear the related set. |
|---|
| 353 |
# Otherwise, just move the named objects into the set. |
|---|
| 354 |
if self.related.field.null: |
|---|
| 355 |
manager.clear() |
|---|
| 356 |
manager.add(*value) |
|---|
| 357 |
|
|---|
| 358 |
def create_many_related_manager(superclass, through=False): |
|---|
| 359 |
"""Creates a manager that subclasses 'superclass' (which is a Manager) |
|---|
| 360 |
and adds behavior for many-to-many related objects.""" |
|---|
| 361 |
class ManyRelatedManager(superclass): |
|---|
| 362 |
def __init__(self, model=None, core_filters=None, instance=None, symmetrical=None, |
|---|
| 363 |
join_table=None, source_col_name=None, target_col_name=None): |
|---|
| 364 |
super(ManyRelatedManager, self).__init__() |
|---|
| 365 |
self.core_filters = core_filters |
|---|
| 366 |
self.model = model |
|---|
| 367 |
self.symmetrical = symmetrical |
|---|
| 368 |
self.instance = instance |
|---|
| 369 |
self.join_table = join_table |
|---|
| 370 |
self.source_col_name = source_col_name |
|---|
| 371 |
self.target_col_name = target_col_name |
|---|
| 372 |
self.through = through |
|---|
| 373 |
self._pk_val = self.instance._get_pk_val() |
|---|
| 374 |
if self._pk_val is None: |
|---|
| 375 |
raise ValueError("%r instance needs to have a primary key value before a many-to-many relationship can be used." % instance.__class__.__name__) |
|---|
| 376 |
|
|---|
| 377 |
def get_query_set(self): |
|---|
| 378 |
return superclass.get_query_set(self)._next_is_sticky().filter(**(self.core_filters)) |
|---|
| 379 |
|
|---|
| 380 |
# If the ManyToMany relation has an intermediary model, |
|---|
| 381 |
# the add and remove methods do not exist. |
|---|
| 382 |
if through is None: |
|---|
| 383 |
def add(self, *objs): |
|---|
| 384 |
self._add_items(self.source_col_name, self.target_col_name, *objs) |
|---|
| 385 |
|
|---|
| 386 |
# If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table |
|---|
| 387 |
if self.symmetrical: |
|---|
| 388 |
self._add_items(self.target_col_name, self.source_col_name, *objs) |
|---|
| 389 |
add.alters_data = True |
|---|
| 390 |
|
|---|
| 391 |
def remove(self, *objs): |
|---|
| 392 |
self._remove_items(self.source_col_name, self.target_col_name, *objs) |
|---|
| 393 |
|
|---|
| 394 |
# If this is a symmetrical m2m relation to self, remove the mirror entry in the m2m table |
|---|
| 395 |
if self.symmetrical: |
|---|
| 396 |
self._remove_items(self.target_col_name, self.source_col_name, *objs) |
|---|
| 397 |
remove.alters_data = True |
|---|
| 398 |
|
|---|
| 399 |
def clear(self): |
|---|
| 400 |
self._clear_items(self.source_col_name) |
|---|
| 401 |
|
|---|
| 402 |
# If this is a symmetrical m2m relation to self, clear the mirror entry in the m2m table |
|---|
| 403 |
if self.symmetrical: |
|---|
| 404 |
self._clear_items(self.target_col_name) |
|---|
| 405 |
clear.alters_data = True |
|---|
| 406 |
|
|---|
| 407 |
def create(self, **kwargs): |
|---|
| 408 |
# This check needs to be done here, since we can't later remove this |
|---|
| 409 |
# from the method lookup table, as we do with add and remove. |
|---|
| 410 |
if through is not None: |
|---|
| 411 |
raise AttributeError, "Cannot use create() on a ManyToManyField which specifies an intermediary model. Use %s's Manager instead." % through |
|---|
| 412 |
new_obj = super(ManyRelatedManager, self).create(**kwargs) |
|---|
| 413 |
self.add(new_obj) |
|---|
| 414 |
return new_obj |
|---|
| 415 |
create.alters_data = True |
|---|
| 416 |
|
|---|
| 417 |
def get_or_create(self, **kwargs): |
|---|
| 418 |
obj, created = \ |
|---|
| 419 |
super(ManyRelatedManager, self).get_or_create(**kwargs) |
|---|
| 420 |
# We only need to add() if created because if we got an object back |
|---|
| 421 |
# from get() then the relationship already exists. |
|---|
| 422 |
if created: |
|---|
| 423 |
self.add(obj) |
|---|
| 424 |
return obj, created |
|---|
| 425 |
get_or_create.alters_data = True |
|---|
| 426 |
|
|---|
| 427 |
def _add_items(self, source_col_name, target_col_name, *objs): |
|---|
| 428 |
# join_table: name of the m2m link table |
|---|
| 429 |
# source_col_name: the PK colname in join_table for the source object |
|---|
| 430 |
# target_col_name: the PK colname in join_table for the target object |
|---|
| 431 |
# *objs - objects to add. Either object instances, or primary keys of object instances. |
|---|
| 432 |
|
|---|
| 433 |
# If there aren't any objects, there is nothing to do. |
|---|
| 434 |
if objs: |
|---|
| 435 |
# Check that all the objects are of the right type |
|---|
| 436 |
new_ids = set() |
|---|
| 437 |
for obj in objs: |
|---|
| 438 |
if isinstance(obj, self.model): |
|---|
| 439 |
new_ids.add(obj._get_pk_val()) |
|---|
| 440 |
else: |
|---|
| 441 |
new_ids.add(obj) |
|---|
| 442 |
# Add the newly created or already existing objects to the join table. |
|---|
| 443 |
# First find out which items are already added, to avoid adding them twice |
|---|
| 444 |
cursor = connection.cursor() |
|---|
| 445 |
cursor.execute("SELECT %s FROM %s WHERE %s = %%s AND %s IN (%s)" % \ |
|---|
| 446 |
(target_col_name, self.join_table, source_col_name, |
|---|
| 447 |
target_col_name, ",".join(['%s'] * len(new_ids))), |
|---|
| 448 |
[self._pk_val] + list(new_ids)) |
|---|
| 449 |
existing_ids = set([row[0] for row in cursor.fetchall()]) |
|---|
| 450 |
|
|---|
| 451 |
# Add the ones that aren't there already |
|---|
| 452 |
for obj_id in (new_ids - existing_ids): |
|---|
| 453 |
cursor.execute("INSERT INTO %s (%s, %s) VALUES (%%s, %%s)" % \ |
|---|
| 454 |
(self.join_table, source_col_name, target_col_name), |
|---|
| 455 |
[self._pk_val, obj_id]) |
|---|
| 456 |
transaction.commit_unless_managed() |
|---|
| 457 |
|
|---|
| 458 |
def _remove_items(self, source_col_name, target_col_name, *objs): |
|---|
| 459 |
# source_col_name: the PK colname in join_table for the source object |
|---|
| 460 |
# target_col_name: the PK colname in join_table for the target object |
|---|
| 461 |
# *objs - objects to remove |
|---|
| 462 |
|
|---|
| 463 |
# If there aren't any objects, there is nothing to do. |
|---|
| 464 |
if objs: |
|---|
| 465 |
# Check that all the objects are of the right type |
|---|
| 466 |
old_ids = set() |
|---|
| 467 |
for obj in objs: |
|---|
| 468 |
if isinstance(obj, self.model): |
|---|
| 469 |
old_ids.add(obj._get_pk_val()) |
|---|
| 470 |
else: |
|---|
| 471 |
old_ids.add(obj) |
|---|
| 472 |
# Remove the specified objects from the join table |
|---|
| 473 |
cursor = connection.cursor() |
|---|
| 474 |
cursor.execute("DELETE FROM %s WHERE %s = %%s AND %s IN (%s)" % \ |
|---|
| 475 |
(self.join_table, source_col_name, |
|---|
| 476 |
target_col_name, ",".join(['%s'] * len(old_ids))), |
|---|
| 477 |
[self |
|---|