Generic Relation Constraints in Django - 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

Related

How can I serialize (with Django REST API) when class relates to the same generic relation multiple times?

I have a class that has multiple references to the same generic class for different information:
class Resort(models.Model):
id = models.PositiveIntegerField(_('HapNr.'), primary_key=True)
symbol = models.CharField(_('Kurzzeichen'), max_length=3, blank=True)
short_description = GenericRelation('MultiLingualText',
verbose_name=_('Beschreibung (ML)'),
related_query_name='resortshortdescriptions')
long_description = GenericRelation('MultiLingualText',
verbose_name=_('Einleitung (ML)'),
related_query_name='resortlongdescription')
class MultiLingualText(models.Model):
language = models.ForeignKey(hmodels.LanguageCode, verbose_name=_('Sprache'))
valid_from = models.DateField(_('Gültig ab'), default=timezone.now)
text = models.TextField(_('Text'))
content_type = models.ForeignKey(ContentType, verbose_name=_('Typ'))
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
atu_id = models.CharField(_('ATU Text Id'), max_length=12, editable=False, blank=True)
atu_context = models.CharField(_('ATU Kontext'), max_length=1, editable=False, blank=True)
When I need to work with this class using the Django Admin I have two Inlines, each with a queryset selecting the correct texts for that relation. This works fine.
I tried doing something similar by using individual serializers and viewsets for each relation, but when I retrieve a resort, it still shows all texts with each relation.
class ResortSerializer(serializers.HyperlinkedModelSerializer):
short_description = MultiLingualTextSerializerRSD(many=True, read_only=True)
long_description = MultiLingualTextSerializerRLD(many=True, read_only=True)
class Meta:
model = Resort
class MultiLingualTextSerializerRSD(serializers.HyperlinkedModelSerializer):
language = serializers.PrimaryKeyRelatedField(read_only=True)
class Meta:
model = MultiLingualText
class MultiLingualTextViewSetRSD(viewsets.ModelViewSet):
serializer_class = MultiLingualTextSerializerRSD
queryset = MultiLingualText.objects.exclude(atu_id='').order_by('resort', 'content_type', 'object_id',
'-valid_from')
class ResortViewSet(viewsets.ModelViewSet):
queryset = Resort.objects.all().order_by('id')
serializer_class = ResortSerializer
filter_backends = (filters.DjangoFilterBackend,)
filter_fields = ('id', 'sihot_nr')
So basically my question is, how can I use different querysets for each set of texts? Or is this at all possible?
Correct implementation (Thanks to #lucasnadalutti)
class ResortSerializer(serializers.HyperlinkedModelSerializer):
short_description = serializers.SerializerMethodField()
long_description = serializers.SerializerMethodField()
def get_short_description(self, obj):
qs = MultiLingualText.objects.exclude(atu_id='').order_by('-valid_from', 'language__code')
return MultiLingualTextSerializer(qs, many=True, read_only=True).data
def get_long_description(self, obj):
qs = MultiLingualText.objects.filter(atu_id='').order_by('-valid_from', 'language__code')
return MultiLingualTextSerializer(qs, many=True, read_only=True).data
MultiLingualTextViewSetRSD does not make much sense in this context, as what you want is to send the resorts and their descriptions in one request only, as it should be. In ordinary ForeignKey model field relations, I'm pretty sure that ResortSerializer would serialize only its related records as you expected, but I'm not sure about how DRF serializers work with generic relations.
That said, one solution would be to replace:
short_description = MultiLingualTextSerializerRSD(many=True, read_only=True)
long_description = MultiLingualTextSerializerRLD(many=True, read_only=True)
with:
short_description = SerializerMethodField()
long_description = SerializerMethodField()
And implement your filtering-and-serialization inside get_short_description and get_long_description methods. Another solution is to remove both attributes and place this logic inside your serializer's to_representation method.

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]

Accessing referring object with GenericForeignKey in 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()

Django validation of generic relation from a GenericInlineModelAdmin

I'm trying to validate a generic relation object saved from a GenericInlineModelAdmin form.
When the object is created object_id and content_type are set to None, and I cannot access it's related object, but when the object is updated they are properly set.
Here is the sample code:
In models.py:
class Article(models.Model):
title = models.CharField(max_length=32)
body = models.TextField()
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
def clean(self, exclude=None):
pass
In admin.py:
class InlineTags(generic.GenericTabularInline):
model = TaggedItem
class ArticleAdmin(admin.ModelAdmin):
inlines = [InlineTags]
admin.site.register(Article, ArticleAdmin)
If you add a tag, in TaggedItem.clean() method self.object_id and self.content_type are set to None. If the tag is being edited they are properly set.
I have tried this on both django 1.4.x and 1.5.x.
It seems this is an unresolved bug in Django (issue #19255).
I have yet to test it, but since you are saving the tags in the admin you may be able to work around this issue by adding a custom ModelForm like so:
class InlineTagsForm(forms.ModelForm):
def clean(self):
""" Validate object_id & content_type fields """
assert self.cleaned_data.get('object_id')
assert self.cleaned_data.get('content_type')
return self.cleaned_data
class InlineTags(generic.GenericTabularInline):
model = TaggedItem
form = InlineTagsForm

Please help me understand Django GenericInlineFormSet

Django ContentTypes provides a GenericInlineFormSet, however the documentation does not explain how to use it, except for this test, which doesn't really explain it in a way I understand.
Please can you help me understand it?
Let's say I have the following classes
class Dog(models.Model):
name = models.CharField(max_length=64)
breed = models.CharField(blank=True, max_length=64)
class Meta:
verbose_name = 'Dog'
class Fish(models.Model):
name = models.CharField(max_length=64)
habitat = models.CharField(blank=True, max_length=64)
class Meta:
verbose_name = 'Fish'
class Pet(models.Model):
content_type = models.ForeignKey(
ContentType,
limit_choices_to={'model__in':('dog', 'fish')},
verbose_name='Species'
)
object_id = models.CharField(max_length=64, verbose_name='Animal')
object = generic.GenericForeignKey('content_type', 'object_id')
owner = models.ForeignKey(Owner)
class Meta:
unique_together = [("content_type", "object_id")]
What does the view look like to display a form for a Pet?
GenericInlineFormSet works just like a standard inline formset, except that it uses generic relations rather than standard foreign keys.