Django admin search function in GenerifForeignkey field with Content_object relation - django

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

Related

How to allow an inline model with Foreign Key to be used only once?

My Goal:
When I add Inlines in admin, to add that Model Object, only one Inline Item with a particular Foreign Key should be allowed.
My Models:
class RateCard(models.Model):
name = models.CharField(max_length=100)
year = models.IntegerField(choices=YEAR_CHOICES, default=timezone.now().year)
class BillingCategory(models.Model):
title = models.CharField(max_length=200)
def __str__(self):
return self.title
class Meta:
verbose_name_plural = "Billing Categories"
class BillingCatagoryRate(models.Model):
billing_category = models.ForeignKey(BillingCategory, on_delete=models.CASCADE)
billing_rate = models.PositiveIntegerField()
rate_card = models.ForeignKey(RateCard,
on_delete=models.CASCADE, blank=True, null=True)
Refer the admin screenshot.
I can select the Drop Down with same value more than once and save successfully. What I need is, RateCard should have only one BillingCatagory used once . NOT multiple (like shown in the picture)
My Ideas:
During Save, may be we can check if there are any duplicates in Billing Categories.
This is how I finally solved it . I added a check in admin.py for the ModelAdmin class.
We need to override the "save_formset" method to access the inline values . This is how my RateCardAdmin looks.
class RateCardAdmin(admin.ModelAdmin):
model = RateCard
list_display = ('name', 'year')
inlines = [BillingCatagoryRateInline]
def save_formset(self, request, form, formset, change):
ids = []
for data in formset.cleaned_data:
ids.append(data['billing_category'])
common_ids = [item for item , count in collections.Counter(ids).items() if count > 1]
if common_ids:
raise ValidationError('Billing Category Duplicate found')
formset.save()

Generic Foreign Keys in Django Admin

Is it possible to filter by GenericForeignKey object titles in the Django admin?
I want to filter by program name, either NonSupportedProgram.title or SupportedProgram.title (list_filter = (SOME FIELD HERE)), but can't figure out how?
models.py
class FullCitation(models.Model):
# the software to which this citation belongs
# either a supported software program or a non-supported software program
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class NonSupportedProgram(models.Model):
title = models.CharField(max_length=256, blank = True)
full_citation = generic.GenericRelation('FullCitation')
class SupportedProgram(models.Model):
title = models.CharField(max_length=256, blank = True)
full_citation = generic.GenericRelation('FullCitation')
Perhaps you could use a SimpleListFilter and define your own queryset so that you can get your own results.
More info here:
https://docs.djangoproject.com/en/1.8/ref/contrib/admin/#django.contrib.admin.ModelAdmin.list_filter
class YourCustomListFilter(admin.SimpleListFilter):
title = 'By Program Name'
parameter_name = 'program'
def lookups(self, request, model_admin):
return(
('supported','Supported Programs'),
('nonsupported', 'Non-Supported Programs')
)
def queryset(self, request, queryset):
if self.value() == 'supported':
return queryset.filter(supported__isnull=False)
if self.value() == 'nonsupported':
return queryset.filter(nonsupported__isnull=False)
admin.py
list_filter = (YourCustomListFilter,)
You will need to add related_query_name='supported' and related_query_name='nonsupported' on your GenericRelation declaration to allow querying from the related objects.
More info on querying GenericRelations here:
https://docs.djangoproject.com/en/1.8/ref/contrib/contenttypes/#reverse-generic-relations
This should be a little push on the right direction.
One way I see fit is the following:
You could change your models to the following:
class FullCitaton(models.Model):
# Any Model Fields you want...
program = ForeignKey('Program')
class Program(models.Model):
title = models.CharField(max_length=256, blank = True)
is_supported = models.NullBooleanField()
Note that both SupportedProgram and NonSupportedProgram are in the same Progam model right now. The way to differ them is through the is_supported NullBoolean field.
Then, all you have to do is queryth Program model using the title field.
Now, maybe you didn't do it this way because you have another issue I'm unable to see right now. But, anyway, this should work.
Cheers!

Limit number of relationship in ManyToManyField in django

I am using ManyToMany relationship in my code in a scenario where i have showrooms and their categories. Now a showroom can fall into maximum three categories and i have to validate it while saving. Below is my code:
##models.py
class Showrooms(models.Model):
name = models.CharField(max_length = 100)
contact_person = models.CharField(max_length = 100)
offer = models.TextField()
categories = models.ManyToManyField(Categories, null=True, blank=True, related_name='categories')
class Meta:
db_table = 'showrooms'
verbose_name_plural = "showrooms"
class Categories(models.Model):
category = models.CharField(max_length = 100)
image = models.ImageField(upload_to = showroom_upload_path, null=True, blank=True)
slug = models.SlugField(blank=True)
class Meta:
db_table = 'showroom_categories'
verbose_name_plural = "categories"
def __str__(self):
return self.category
everything is working fine except i am not able to put validation on number of categories per showroom. And i am not using it in views but just wanna to do it in admin.
Please help
Thanks
Edits
Ok.. I have solved my issue. I created a form in forms.py
class ShowroomsForm(forms.ModelForm):
class Meta:
model = Showrooms
def clean(self):
categories = self.cleaned_data.get('categories')
if categories and categories.count() > 3:
raise ValidationError('Maximum three categories are allowed.')
return self.cleaned_data
and added it to admin.py like below:
class ShowroomsAdmin(admin.ModelAdmin):
form = ShowroomsForm
admin.site.register(Showrooms, ShowroomsAdmin)
You could define a clean() method on your model an raise a validation error whenever a showroom gets assigned to more than 3 categories.
https://docs.djangoproject.com/en/1.5/ref/models/instances/#django.db.models.Model.clean
I had the same problem and used #Rakesh Kumar's solution. But then I got an error
django.core.exceptions.ImproperlyConfigured: Creating a ModelForm without either the 'fields' attribute or the 'exclude' attribute is prohibited; form JobsForm needs updating.
The problem was that Rashid's form didn't have any form fields.
So I modified his solution just a little.
forms.py - I added fields
class ShowroomsForm(forms.ModelForm):
class Meta:
model = Showrooms
fields = "__all__"
def clean(self):
categories = self.cleaned_data.get('categories')
if categories and categories.count() > 3:
raise ValidationError('Maximum three categories are allowed.')
return self.cleaned_data
admin.py - this remained the same:
class ShowroomsAdmin(admin.ModelAdmin):
form = ShowroomsForm
admin.site.register(Showrooms, ShowroomsAdmin)
After that, it worked perfectly!
All I needed was to create a model form:
class ShowroomsForm(forms.ModelForm):
class Meta:
model = Showrooms
def clean(self):
categories = self.cleaned_data.get('categories')
if categories and categories.count() > 3:
raise ValidationError('Maximum three categories are allowed.')
return self.cleaned_data
and added it to admin.py like below:
class ShowroomsAdmin(admin.ModelAdmin):
form = ShowroomsForm
admin.site.register(Showrooms, ShowroomsAdmin)

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

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