PickledManyToManyField

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import pickle
from django.db.models import Field, BLANK_CHOICE_DASH
from django.utils.translation import ugettext_lazy, string_concat
from django.utils.encoding import smart_unicode
from django.utils.functional import curry
class PickledManyToManyField(Field):
def __init__(self, to, to_field=None, **kwargs):
to_field = to_field or to._meta.pk.name
kwargs['verbose_name'] = kwargs.get('verbose_name', None)
self.my_rel = PickledManyToManyRel(to, to_field,
num_in_admin=kwargs.pop('num_in_admin', 0),
filter_interface=kwargs.pop('filter_interface', None),
limit_choices_to=kwargs.pop('limit_choices_to', None),
raw_id_admin=kwargs.pop('raw_id_admin', False))
if self.my_rel.raw_id_admin:
kwargs.setdefault('validator_list', []).append(self.isValidIDList)
super(PickledManyToManyField, self).__init__(**kwargs)
if self.my_rel.raw_id_admin:
msg = ugettext_lazy('Separate multiple IDs with commas.')
else:
msg = ugettext_lazy('Hold down "Control", or "Command" on a Mac, to select more than one.')
self.help_text = string_concat(self.help_text, ' ', msg)
def get_attname(self):
return '%s_%ss' % (self.name, self.my_rel.to._meta.pk.name)
def set_attributes_from_rel(self):
self.name = self.name or self.get_attname()
self.verbose_name = self.verbose_name or self.my_rel.to._meta.verbose_name
self.my_rel.field_name = self.my_rel.field_name or self.my_rel.to._meta.pk.name
def contribute_to_class(self, cls, name):
super(PickledManyToManyField, self).contribute_to_class(cls, name)
self.set_attributes_from_rel()
self.related_query_name = lambda self: opts.object_name.lower()
setattr(cls, self.name, PickledManyRelatedObjectDescriptor(self))
standard_init = cls.__init__
def unpickling_init(slf, *args, **kwargs):
standard_init(slf, *args, **kwargs)
value = getattr(slf, self.attname)
if not isinstance(value, set):
value = pickle.loads(value)
setattr(slf, self.attname, value)
cls.__init__ = unpickling_init
def db_type(self):
return 'text'
def to_python(self, value):
return pickle.loads(value)
def get_db_prep_save(self, value):
return pickle.dumps(value)
###############################################
# this is just copy-pasted from ManyToManyField
def get_manipulator_field_objs(self):
from django import oldforms
if self.my_rel.raw_id_admin:
return [oldforms.RawIdAdminField]
else:
choices = self.get_choices_default()
return [curry(oldforms.SelectMultipleField,
size=min(max(len(choices), 5), 15),
choices=choices)]
def get_choices_default(self):
return self.get_choices(include_blank=False)
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH):
"Returns a list of tuples used as SelectField choices for this field."
first_choice = include_blank and blank_choice or []
if self.choices:
return first_choice + list(self.choices)
rel_model = self.my_rel.to
if hasattr(self.my_rel, 'get_related_field'):
lst = [(getattr(x, self.my_rel.get_related_field().attname), smart_unicode(x))
for x in rel_model._default_manager.complex_filter(self.my_rel.limit_choices_to)]
else:
lst = [(x._get_pk_val(), smart_unicode(x))
for x in rel_model._default_manager.complex_filter(self.my_rel.limit_choices_to)]
return first_choice + lst
def isValidIDList(self, field_data, all_data):
"Validates that the value is a valid list of foreign keys"
mod = self.my_rel.to
try:
pks = map(int, field_data.split(','))
except ValueError:
# the CommaSeparatedIntegerField validator will catch this error
return
objects = mod._default_manager.in_bulk(pks)
if len(objects) != len(pks):
badkeys = [k for k in pks if k not in objects]
raise validators.ValidationError, ungettext("Please enter valid %(self)s IDs. The value %(value)r is invalid.",
"Please enter valid %(self)s IDs. The values %(value)r are invalid.", len(badkeys)) % {
'self': self.verbose_name,
'value': len(badkeys) == 1 and badkeys[0] or tuple(badkeys),
}
# end of copy-past
##################
class PickledManyRelatedObjectDescriptor(object):
def __init__(self, field_with_rel):
self.field = field_with_rel
def __get__(self, instance, instance_type=None):
if instance is None:
raise AttributeError, "%s must be accessed via instance" % self.field.name
self.field.renew_attfield(instance)
val = getattr(instance, self.field.attname)
rel_model = self.field.my_rel.to
superclass = rel_model._default_manager.__class__
RelatedManager = create_pickled_many_related_manager(superclass)
manager = RelatedManager(
model=rel_model,
core_filters={'%s__in' % self.field.my_rel.field_name: val},
instance=instance,
attname=self.field.attname,
)
return manager
def __set__(self, instance, value):
if instance is None:
raise AttributeError, "%s must be accessed via instance" % self._field.name
# Set the value of the related field
val = set()
for obj in value:
try:
val.add(getattr(obj, self.field.my_rel.get_related_field().attname))
except AttributeError:
pass
setattr(instance, self.field.attname, val)
# Clear the cache, if it exists
try:
delattr(instance, self.field.get_cache_name())
except AttributeError:
pass
class PickledManyToManyRel(object):
def __init__(self, to, field_name, num_in_admin=0, filter_interface=None, limit_choices_to=None, raw_id_admin=False):
self.to = to
self.field_name = field_name
self.num_in_admin = num_in_admin
self.filter_interface = filter_interface
if limit_choices_to is None:
limit_choices_to = {}
self.limit_choices_to = limit_choices_to
self.edit_inline = False
self.raw_id_admin = raw_id_admin
self.multiple = True
self.related_name = '%s_%s_set' % (to._meta.pk.name, field_name)
assert not (self.raw_id_admin and self.filter_interface), "ManyToManyRels may not use both raw_id_admin and filter_interface"
# get_related_field = ManyToOneRel.get_related_field
def get_related_field(self):
"Returns the Field in the 'to' object to which this relationship is tied."
return self.to._meta.get_field(self.field_name)
def create_pickled_many_related_manager(superclass):
class RelatedManager(superclass):
def __init__(self, model=None, core_filters=None, instance=None, attname=None):
super(RelatedManager, self).__init__()
self.model = model
self.core_filters = core_filters
self.instance = instance
self.attname = attname
def get_query_set(self):
return superclass.get_query_set(self).filter(**(self.core_filters))
def add(self, *objs):
obj_pks = (obj.pk for obj in objs)
setattr(self.instance, self.attname, getattr(self.instance, self.attname).union(obj_pks))
add.alters_data = True
def remove(self, *objs):
obj_pks = (obj.pk for obj in objs)
setattr(self.instance, self.attname, getattr(self.instance, self.attname).difference(obj_pks))
remove.alters_data = True
def clear(self):
setattr(self.instance, self.attname, set())
clear.alters_data = True
def create(self, **kwargs):
new_obj = self.model(**kwargs)
new_obj.save()
self.add(new_obj)
return new_obj
create.alters_data = True
return RelatedManager