I am trying to retrieve a field from a GenericForeignKey and cannot make it work in the model.py - it works in the admin.py though.
models.py:
class Run(models.Model):
name = models.CharField(max_length=100)
class TaskRunRelation(models.Model):
limit = models.Q(app_label = 'thisapp', model = 'run') | models.Q(app_label = 'thisapp', model = 'runb')
content_type = models.ForeignKey(ContentType, limit_choices_to = limit)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def modeltestname(self):
self.content_object.name
admin.py:
class TaskRelationAdmin(admin.ModelAdmin):
list_display = ['modeltestname','mytestname']
def mytestname(self,obj):
return obj.content_object.name
So mytestname shows the correct value in the Admin whereas modeltestname shows "(None)". Why is this not working in the model.py? Am I missing something how Genericforeignkey works or is there any other mistake in there?
That is because you are not returning anything from modeltestname. If a function or method does not return anything explictly, it would return None by default. Hence the result
So change the class method to
def modeltestname(self):
return self.content_object.name
Related
I am trying to build an admin page that lets admins search through 2 fields of the model "SeasonalitiesCalculated". The fields for my search are called "fruit" and "object_id".
"fruit" is a Foreignkey field and returns the "name" field of the corresponding fruit.
"object_id" is Genericforeignkey field that sometimes points at a UUID in a model called "Countries" (with a "country_name" field: Germany) and sometimes points at a UUID in a model called "AdminZones" (with an "admin_zone_name" field: California)
The problem now is that django seems to not have any standard way of searching through GenericForeignkeys. So I tried defining a search function like this:
class SeasonalitiesCalculatedAdmin(admin.ModelAdmin):
list_per_page = 20
def country_or_admin_zone_name(self, obj):
return obj.country_or_admin_zone_name()
country_or_admin_zone_name.short_description = 'Country or Admin Zone'
def search_field(self, obj):
return obj.search_field()
list_display = ('fruit', 'country_or_admin_zone_name', 'content_type', ...)
search_fields = ('fruit__fruit_name', 'search_fields')
the admin page itself works and it also shows the country or admin zone names and other foreignkey related fields properly since I specified this in the SeasonalitiesCalculated model like this
class SeasonalitiesCalculated(LoggingMixinSeasons, Basemodel):
fruit = models.ForeignKey('IngredientOriginals', on_delete=models.PROTECT, related_name='%(class)s_related_ingredient_original')
content_type = models.ForeignKey(ContentType, on_delete=models.PROTECT)
object_id = models.UUIDField(default=uuid.uuid4, editable=False)
content_object = GenericForeignKey('content_type', 'object_id')
...
class Meta:
managed = True
db_table = 'seasonalities_calculated'
verbose_name_plural = 'Seasonalities Calculated'
constraints = [models.UniqueConstraint(fields=['ingredient_original_id', 'content_type_id', 'object_id',], name='unique_seasonalities_calculated')]
def country_or_admin_zone_name(self):
content_object = self.content_object
if isinstance(content_object, Countries):
return content_object.country_name
elif isinstance(content_object, AdminZones1):
return content_object.admin_zone_1_name
else:
return None
def search_field(self):
content_object = self.content_object
if isinstance(content_object, Countries):
return 'content_object__country_name'
elif isinstance(content_object, AdminZones1):
return 'content_object__admin_zone_1_name'
return None
but i just cant figure out how to make the search_fields work becuase of the content object relation.
i tried overriding the standard search query but without success...
any help is much appreciated
Simple question. I have this model:
class Discount(models.Model):
discount_target = GenericForeignKey('content_type', 'object_id')
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE, blank=True, null=True)
object_id = models.PositiveIntegerField(blank=True, null=True)
How do I get the object the GenericForeignKey is pointing?
You just access it like any other field.
my_discount_obj.discount_target
You can create a special function to get the related object.
class Discount(models.Model):
....
def get_related_object(self):
"""
return the related object of content_type.
eg: <Question: Holisticly grow synergistic best practices>
"""
# This should return an error: MultipleObjectsReturned
# return self.content_type.get_object_for_this_type()
# So, i handle it with this one:
model_class = self.content_type.model_class()
return model_class.objects.get(id=self.object_id)
#property
def _model_name(self):
"""
return lowercase of model name.
eg: `discount`, `transaction`
"""
return self.get_related_object()._meta.model_name
I have the following model:
class TaggedItem(models.Model):
tag = models.SlugField()
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
I am trying to serialize this model in a way that I can assign the content object via an API endpoint
So far I have done this:
class TaggedItemSerializer(serializers.ModelSerializer):
content_object = serializers.RelatedField(read_only=True)
class Meta:
model = TaggedItem
However this is readonly. If I remove the read_only parameter, I must specify the queryset for the field. However, I have many different model types for this generic relationship. It seems like I am duplicating code if I specify all the possible model types both within the serializer and elsewhere in the model.
I could also set the content object through the object_id and content_type fields, but when I do this I get an error.
For example:
{
...
object_id: 1,
content_type: 'auth.User'
}
returns a 400 response with "detail": "JSON parse error - Expected object or value"
How can I make this content_object writable via the DRF api?
Override the .to_internal_value , .validate and .create methods like this:
from django.apps import apps
class TaggedItemSerializer(serializers.ModelSerializer):
class Meta:
model = TaggedItem
read_only_fields = ('content_type', 'object_id', 'content_object')
def to_internal_value(self, data):
object_id = data.pop('object_id')
content_type = data.pop('content_type')
ret = super(ConfigCalcSerializer, self).to_internal_value(data)
ret['object_id'] = object_id
ret['content_type'] = content_type
return ret
def validate(self, data):
object_id = data.pop('object_id')
content_type = data.pop('content_type')
Model = apps.get_model(content_type)
try:
content_object = Model.objects.get(id=object_id)
except Model.DoesNotExist:
raise serializers.ValidationError('Not found')
else:
data['content_object'] = content_object
return data
def create(self, validate_data):
return TaggedItem.objects.create(**validate_data)
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
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