+attribute_value_limiter = ContentTypeSubclassLimiter(AttributeValue)
+
+
+class JSONValue(AttributeValue):
+ value = JSONField(verbose_name='Value (JSON)', help_text='This value must be valid JSON.', default='null', db_index=True)
+
+ def __unicode__(self):
+ return force_unicode(self.value)
+
+ def value_formfields(self):
+ kwargs = {'initial': self.value_json}
+ field = self._meta.get_field('value')
+ return {field.name: field.formfield(**kwargs)}
+
+ def construct_instance(self, **kwargs):
+ field_name = self._meta.get_field('value').name
+ self.set_value(kwargs.pop(field_name, None))
+
+ def set_value(self, value):
+ self.value = value
+
+ class Meta:
+ app_label = 'philo'
+
+
+class ForeignKeyValue(AttributeValue):
+ content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
+ object_id = models.PositiveIntegerField(verbose_name='Value ID', null=True, blank=True, db_index=True)
+ value = generic.GenericForeignKey()
+
+ def value_formfields(self):
+ field = self._meta.get_field('content_type')
+ fields = {field.name: field.formfield(initial=getattr(self.content_type, 'pk', None))}
+
+ if self.content_type:
+ kwargs = {
+ 'initial': self.object_id,
+ 'required': False,
+ 'queryset': self.content_type.model_class()._default_manager.all()
+ }
+ fields['value'] = forms.ModelChoiceField(**kwargs)
+ return fields
+
+ def construct_instance(self, **kwargs):
+ field_name = self._meta.get_field('content_type').name
+ ct = kwargs.pop(field_name, None)
+ if ct is None or ct != self.content_type:
+ self.object_id = None
+ self.content_type = ct
+ else:
+ value = kwargs.pop('value', None)
+ self.set_value(value)
+ if value is None:
+ self.content_type = ct
+
+ def set_value(self, value):
+ self.value = value
+
+ class Meta:
+ app_label = 'philo'
+
+
+class ManyToManyValue(AttributeValue):
+ content_type = models.ForeignKey(ContentType, limit_choices_to=value_content_type_limiter, verbose_name='Value type', null=True, blank=True)
+ values = models.ManyToManyField(ForeignKeyValue, blank=True, null=True)
+
+ def get_object_ids(self):
+ return self.values.values_list('object_id', flat=True)
+ object_ids = property(get_object_ids)
+
+ def set_value(self, value):
+ # Value must be a queryset. Watch out for ModelMultipleChoiceField;
+ # it returns its value as a list if empty.
+
+ self.content_type = ContentType.objects.get_for_model(value.model)
+
+ # Before we can fiddle with the many-to-many to foreignkeyvalues, we need
+ # a pk.
+ if self.pk is None:
+ self.save()
+
+ object_ids = value.values_list('id', flat=True)
+
+ # These lines shouldn't be necessary; however, if object_ids is an EmptyQuerySet,
+ # the code (specifically the object_id__in query) won't work without them. Unclear why...
+ # TODO: is this still the case?
+ if not object_ids:
+ self.values.all().delete()
+ else:
+ self.values.exclude(object_id__in=object_ids, content_type=self.content_type).delete()
+
+ current_ids = self.object_ids
+
+ for object_id in object_ids:
+ if object_id in current_ids:
+ continue
+ self.values.create(content_type=self.content_type, object_id=object_id)
+
+ def get_value(self):
+ if self.content_type is None:
+ return None
+
+ # HACK to be safely explicit until http://code.djangoproject.com/ticket/15145 is resolved
+ object_ids = self.object_ids
+ manager = self.content_type.model_class()._default_manager
+ if not object_ids:
+ return manager.none()
+ return manager.filter(id__in=self.object_ids)
+
+ value = property(get_value, set_value)
+
+ def value_formfields(self):
+ field = self._meta.get_field('content_type')
+ fields = {field.name: field.formfield(initial=getattr(self.content_type, 'pk', None))}
+
+ if self.content_type:
+ kwargs = {
+ 'initial': self.object_ids,
+ 'required': False,
+ 'queryset': self.content_type.model_class()._default_manager.all()
+ }
+ fields['value'] = forms.ModelMultipleChoiceField(**kwargs)
+ return fields
+
+ def construct_instance(self, **kwargs):
+ field_name = self._meta.get_field('content_type').name
+ ct = kwargs.pop(field_name, None)
+ if ct is None or ct != self.content_type:
+ self.values.clear()
+ self.content_type = ct
+ else:
+ value = kwargs.get('value', None)
+ if not value:
+ value = self.content_type.model_class()._default_manager.none()
+ self.set_value(value)
+ construct_instance.alters_data = True
+
+ class Meta:
+ app_label = 'philo'
+
+
+class Attribute(models.Model):
+ entity_content_type = models.ForeignKey(ContentType, related_name='attribute_entity_set', verbose_name='Entity type')
+ entity_object_id = models.PositiveIntegerField(verbose_name='Entity ID', db_index=True)