samedi 29 novembre 2014

Relation inverse de Django requête sans clé étrangère - Stack Overflow


I'm currently working on a Django library to manage images of multiple resolutions (django-multires) where I am stuck optimizing a reserve relationship query. Before I explain the issue, let me try to explain what I am trying to achieve.


Background


The idea is to store multiple resolutions of an image however keep references to the original image in terms of image paths instead of foreign keys. I think an example will make more sense:


# the goal is to store multiple resolutions for 'image' field
class FooModel(models.Model):
image = MultiresImageField(...)

# MultiresImage.source will be the identical to FooModel.image
# so MultiresImage.source will act sort of like a foreign key
class MultiresImage(models.Model):
source = models.ImageField(...)
...

Using this approach instead of using a foreign key (or generic foreign key) to link to the source image allows to add multiple MultiresImageFields to the source model:


class FooModel(models.Model):
image = MultiresImageField(...)
image2 = MultiresImageField(...)

Now lets say you need to get all different resolutions for an image field within the source model:


foo = FooModel(...)
foo.image.get_all_multires_images()
# which behind the scenes will do something similar to
return MultiresImage.objects.filter(source=foo.image.name)

That works well until you need to query multiple FooModel instances in which case for each model instance I am forced to do a db lookup to get all resolutions for that model:


sources = FooModel.objects.filter(...)
for source in sources:
# this incurs a db query
foo.image.get_all_multires_images()

Usually I would use prefetch_related to do the performance optimization however in here I can't because my multires model does not have a foreign key to the source model hence reverse relationship does not exist on the source model.


Question


So my question is how can the above query be optimized?


Some current thoughts



  • Since I am making a custom model field, I could use contribute_to_class to manually add a reverse relationship to source model however I can't figure out how?

  • Another though is to use Prefetch API in Django>=1.7 but I can't figure out how to make that work as well.




The easiest, but probably not most elegant solution would be to write a kind of helper class:


def prefetch_related_images(image_queryset):
multires_images = MultiresImage.objects.filter(source__in=[image.name for image in image_queryset ])
for image in image_queryset:
image.multires_images = []
for multires_image in multires_images:
if multires_image.source == image.name:
image.multires_images.append(multires_image)

And well, a more elegant solution could be along the lines on your thought on contribute_to_class. Why don't you try something like a Generic Relationship:


class MultiresImage(models.Model):
source = models.ImageField(...)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
image_target= GenericForeignKey('content_type', 'object_id')

and then amend contribute_to_class like that:


def contribute_to_class(self, cls, name):
"""
Attached necessary garbage collection signals.
"""
super(MultiresImageField, self).contribute_to_class(cls, name)

setattr(cls,'%_source', GenericRelation(MultiresImage)

And then handle the details of managing the relationship via the signals (as you already do).



I'm currently working on a Django library to manage images of multiple resolutions (django-multires) where I am stuck optimizing a reserve relationship query. Before I explain the issue, let me try to explain what I am trying to achieve.


Background


The idea is to store multiple resolutions of an image however keep references to the original image in terms of image paths instead of foreign keys. I think an example will make more sense:


# the goal is to store multiple resolutions for 'image' field
class FooModel(models.Model):
image = MultiresImageField(...)

# MultiresImage.source will be the identical to FooModel.image
# so MultiresImage.source will act sort of like a foreign key
class MultiresImage(models.Model):
source = models.ImageField(...)
...

Using this approach instead of using a foreign key (or generic foreign key) to link to the source image allows to add multiple MultiresImageFields to the source model:


class FooModel(models.Model):
image = MultiresImageField(...)
image2 = MultiresImageField(...)

Now lets say you need to get all different resolutions for an image field within the source model:


foo = FooModel(...)
foo.image.get_all_multires_images()
# which behind the scenes will do something similar to
return MultiresImage.objects.filter(source=foo.image.name)

That works well until you need to query multiple FooModel instances in which case for each model instance I am forced to do a db lookup to get all resolutions for that model:


sources = FooModel.objects.filter(...)
for source in sources:
# this incurs a db query
foo.image.get_all_multires_images()

Usually I would use prefetch_related to do the performance optimization however in here I can't because my multires model does not have a foreign key to the source model hence reverse relationship does not exist on the source model.


Question


So my question is how can the above query be optimized?


Some current thoughts



  • Since I am making a custom model field, I could use contribute_to_class to manually add a reverse relationship to source model however I can't figure out how?

  • Another though is to use Prefetch API in Django>=1.7 but I can't figure out how to make that work as well.



The easiest, but probably not most elegant solution would be to write a kind of helper class:


def prefetch_related_images(image_queryset):
multires_images = MultiresImage.objects.filter(source__in=[image.name for image in image_queryset ])
for image in image_queryset:
image.multires_images = []
for multires_image in multires_images:
if multires_image.source == image.name:
image.multires_images.append(multires_image)

And well, a more elegant solution could be along the lines on your thought on contribute_to_class. Why don't you try something like a Generic Relationship:


class MultiresImage(models.Model):
source = models.ImageField(...)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
image_target= GenericForeignKey('content_type', 'object_id')

and then amend contribute_to_class like that:


def contribute_to_class(self, cls, name):
"""
Attached necessary garbage collection signals.
"""
super(MultiresImageField, self).contribute_to_class(cls, name)

setattr(cls,'%_source', GenericRelation(MultiresImage)

And then handle the details of managing the relationship via the signals (as you already do).


0 commentaires:

Enregistrer un commentaire