Given the following models adapted from http://www.djangoproject.com/documentation/models/generic_relations/
class TaggedItem(models.Model):
"""A tag on an item."""
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
class Vegetable(models.Model):
name = models.CharField(max_length=150)
is_yucky = models.BooleanField(default=True)
edible = models.BooleanField(default=True)
class Mineral(models.Model):
name = models.CharField(max_length=150)
hardness = models.PositiveSmallIntegerField()
edible = models.BooleanField(default=True)
How would I filter TaggedItems so that I get only those with content_objects that are edible?
ideally, something like:
TaggedItem.objects.filter(content_object.edible=True)
What if Vegetable and Mineral had is_edible methods?
You can't really do this with generic relations, because there's nothing to guarantee that the target model will even have an edible field.
An alternative is to change the structure to use model inheritance (multi-table). Vegetable and Mineral would both inherit from a Taggable model, which contained the edible field (or anything else you need to filter on). Then TaggedItem would have a standard ForeignKey to Taggable, so you would use the standard double-underscore filter syntax.
Related
I need to express multiple one-to-many relations in Django. That is, given several different models, I need each of these to have a one-to-many relation with a single table. Logically, the relation belongs to the model "owning" the one-to-many relation, but Django forces me to use a many-to-one relation on the target table, instead of a one-to-many relation on the source table. Here's what I wish I could do:
class Sink(models.Model):
name = models.CharField('name', max_length=24)
class A(models.Model):
name = models.CharField('name', max_length=24)
sink = models.ManyToOneField(Sink)
class B(models.Model):
name = models.CharField('name', max_length=24)
sink = models.ManyToOneField(Sink)
but ManyToOneField doesn't exist. Instead, I'm supposed to use ForeignKey for each one-to-many field, like:
class Sink(models.Model):
name = models.CharField('name', max_length=24)
a = models.ForeignKey(A)
b = models.ForeignKey(B)
class A(models.Model):
name = models.CharField('name', max_length=24)
class B(models.Model):
name = models.CharField('name', max_length=24)
which is logically just wrong, since there is never a case where I want both Sink.a and Sink.b to be non-null. If ManyToManyField allowed me to specify that it's not really many-to-many, I could do that, but it doesn't seem to allow that. What's the right way to do something like this?
You could use generic relations to link your Sink model to a single A or B model with a GenericForeignKey:
class Sink(models.Model):
name = models.CharField('name', max_length=24)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
class A(models.Model):
name = models.CharField('name', max_length=24)
class B(models.Model):
name = models.CharField('name', max_length=24)
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()
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]
I have a simple product database with the following:
class Product(models.Model):
sku = models.CharField(max_length=200)
name = models.CharField(max_length=200)
price = models.CharField(max_length=200)
class meta:
abstract = True
class Shoe(Product): #inherits from product
size = models.CharField(max_length=200)
class Computer(Product): #inherits from product
cpu = models.CharField(max_length=200)
This leaves 2 tables in the DB...Shoe and Computer. Now what if I want to organize them by category? As in have a Category model with a nice human-readable name, an icon for displaying on the product drop-down menu, and suchlike. I'm lost! Shoe and Computer are different models...so how can one model (Category) organize them?
HALP?
Just put this into Product:
category = models.ForeignKey(Category, related_name='%(class)s_set')
You can learn more about foreign keys here.
Perhaps you could use Generic Foreign Keys
class Category(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
name = models.charField(max_length=50)
class Product(models.Model):
categories = generic.GenericRelation(Category)
I"m not quite sure what you had in mind for categories, but you can create new categories referencing any object, Computer or Shoe
shoe = Shoe.object.get(pk=1)
category = Category(content_object=shoe, name='tennis')
category.save()
category = Category(content_object=shoe, name='waterproof')
category.save()
shoe.catetories.all()
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