Accessing referring object with GenericForeignKey in django - django

I have the following models:
Class A(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
thumbnail = models.ImageField(...)
class B(models.Model)
title = models.CharField()
def save(*args, **kwargs):
# Based on the title field I want to fetch some picture and then save the thumbnail in A
I have more classes like B which should be referenced from A (this is why I use GenericForeignKey). The problem I am trying to figure out is how to save the thumbnail field (on A) when I am in the save() method in B. Inserting many if statement in A to check the type of the referenced class and save the thumbnail accordingly is pretty cumbersome.

Looking at the docs, you can add a reverse generic relation from B to A:
If you know which models you’ll be using most often, you can also add a “reverse” generic relationship to enable an additional API
class A_Model(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
thumbnail = models.ImageField(...)
class B_Models(models.Model)
title = models.CharField()
a_models = generic.GenericRelation(A_Model)
and now you can do:
b = B_Model()
a = A_Model(content_object=b, thumbnail=...)
a.save()
b.a_models.all()

Related

How can I have two models relate to one generic model?

Model "A" is a generic model, meaning any model can relate to it. Model "B" and "C" are models that want to establish a foreign key relation with Model "A". How can this be done?
class A(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
list = # What goes here?
class B(models.Model):
parent = # What goes here to refer to model A?
name = models.CharField(max_length=255)
age = models.IntegerField()
class C(models.Model):
parent = # What goes here to refer to model A?
title = models.CharField(max_length=255)
body = models.TextField()
An example lookup would be:
A.list.all()
Giving me a list of either B objects or C objects.
This is completely the wrong design for what you are asking for. Your structure only allows one single item to be related to each A, whether it is a B or a C.
Instead you need an intermediate model, which contains the GenericForeignKey and which also has a (normal) ForeignKey to A. That is how tagging applications work: see for example django-tagging's TaggedItem model.
I've decided to use different related names which works in my case:
class A(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class B(models.Model):
parent = models.ForeignKey('A', related_name='B')
name = models.CharField(max_length=255)
age = models.IntegerField()
class C(models.Model):
parent = models.ForeignKey('A', related_name='C')
title = models.CharField(max_length=255)
body = models.TextField()

Generic relations vs. iterating on objects in Django

I have three models called Post, Category and Flag.
I let users to make a Flag on each Post which is tagged with a Category. Now I want to list all of Flags, related to Posts on a certain Category.
I've found two ways of doing it using ListView generic view:
First approach:
Define a generic relation between Flag and Category so I can call something like: Flag.objects.filter(object__categor=self.category)
Second approach:
def get_queryset(self):
pk = self.kwargs['pk']
self.category = get_object_or_404(Category, pk=pk)
flags = Flag.objects.all()
qs=[]
for f in flags:
if f.object.category == self.category:
qs.append(f)
return qs
My question is which approach is more efficient? I'm not familiar with generic relations, but it seems a little bit heavy to do. On the other hand, querying on all Flag objects and iterating through them isn't cheap one.
Models.py
class Flag(models.Model):
flagger = models.ForeignKey(User, related_name='flaggers')
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = GenericForeignKey('content_type', 'object_id')
submit_date = models.DateTimeField(auto_now_add=True)
reason = models.IntegerField(choices=REASON_CHOICES, default=BROKEN_LINK)
class Category(models.Model):
name = models.CharField(max_length=100)
slug = models.SlugField(unique=True)
...
class Base(models.Model):
title = models.CharField(max_length=200)
slug = models.CharField(max_length=200, db_index=True)
category = models.ForeignKey(Category, default=1)
...
class Text(Base):
description = models.TextField(max_length=500)
...
class Image(Base):
image = models.ImageField()
class Post(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
created_at = models.DateTimeField(null=True)
After saving each of Text or Image instances, I create a Post model. So I can get either Posts flagged or text flagged and image flagged separately.
[also any tip about how can I define generic relation in case I should use it, would be appreciated]

Django tastypie save reverse GenericForeignKeyField

Django Tastypie can save related objects even with reverse relationship.
But is it able for Django Tastypie to save reverse relationship of GenericForeignKeyField?
My resources (not full, but the important only),
class AreaResource(ModelResource):
tripl3user = fields.ManyToManyField(
'tripl3sales.api.resources.area.Tripl3UserResource',
'tripl3user',
related_name='area',
full=True
)
class Tripl3UserResource(ModelResource):
content_type = fields.ForeignKey(
'tripl3sales.api.resources.contenttype.ContentTypeResource',
'content_type'
)
content_object = GenericForeignKeyField({
Area : AreaResource
}, 'content_object')
My models.py,
class Area(models.Model):
name = models.CharField(max_length=50, unique=True)
tripl3user = generic.GenericRelation('Tripl3User')
class Tripl3User(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
Is it possible to save reverse relationship of generic foreign key? If so, then how to do it? What does the data look like?
Finally I got the answer.
In a resource that has content_type and object_id, there is no need to declare content_type because content_object is enough. And for related_name, instead of using area, we use content_object.
So, my resources.py should be,
class AreaResource(ModelResource):
tripl3user = fields.ManyToManyField(
'tripl3sales.api.resources.area.Tripl3UserResource',
'tripl3user',
related_name='content_object',
full=True
)
class Tripl3UserResource(ModelResource):
content_object = GenericForeignKeyField({
Area : AreaResource
}, 'content_object')
Hope this will help others.

Generic Relation Constraints in Django

I want the a counterpart of Tag (BlogPost) to have at least 1 instance of Tag or it shouldn't be created. (same effect like null=False). I tried a lot but couldn't figure out to apply these contrains. Any ideas?
class Tag(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
text = models.CharField("text", max_length=255)
class Meta:
unique_together = ('content_type', 'object_id', 'text',)
class BlogPost(models.Model):
title = models.CharField("title", max_length=255)
tags = generic.GenericRelation(Tag, verbose_name="tags")
class TagInline(generic.GenericTabularInline):
model = Tag
extra = 1
class BlogPostAdmin(admin.ModelAdmin):
inlines = (TagInline,)
If you want this in the form of a Database constraint, then I'm not sure that such a thing exists.
Otherwise I would go with overriding the clean( self ) function on your model.
This can be used for custom validation.
def clean( self ):
# validate that this model has one or more tag

generic relation setting content_object with unsaved model

Say I have the following model:
class Foo(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Bar(models.Model):
baz = models.BooleanField()
then run the following code:
f = Foo(content_object=Bar(baz=False))
print f.content_object
what I would expect to see is something like:
<Bar: Bar object>
but instead it seems as if it's empty... why is this?
Follow the following:
b=Bar(baz=False)
b.save()
f = Foo(content_object=b)
f.content_object
This gives the desired result for you.
Content_object has to be split into content_type and object_id. And until you save the object into the database there is no object_id available. Therefore you have to save it first - like Sandip suggested. You can do it in a shorter form as well: Baz.objects.create(baz=False)