I am using Django 3.2
I have the following model:
class Foo(models.Model):
title = models.CharField()
slug = models.SlugField()
# remaining fields listed below
In Admin manager, I have the model:
class FooAdmin(admin.ModelAdmin):
fieldsets = [
('General', {
'fields': [
'title',
'description',
'information',
'cover_photo',
'gallery',
]
}),
('Details', {
'fields' : [
'is_cancelled',
'start',
'finish',
'video_url',
]
}),
]
prepopulated_fields = {'slug': ('title',), }
admin.site.register(Foo, FooAdmin)
When I attempt to create a new Foo in the admin manager, I get the error:
KeyError: "Key 'slug' not found in 'FooForm'. Choices are: cover_photo, description, finish, gallery, information, is_cancelled, start, title, video_url."
When I add the slug field to the list of fieldsets, it works, but then the slug field is shown on the form (which I don't want).
I know that I can solve this by overriding Foo.save()
, but I want the admin manager to handle this for me.
So, how do I use fieldsets with prepopulated fields that are not supposed to appear on the form?
CodePudding user response:
You can override the form of the admin:
from django.utils.text import slugify
class FooAdmin(admin.ModelAdmin):
fieldsets = [
('General', {
'fields': [
'title',
'description',
'information',
'cover_photo',
'gallery',
]
}),
('Details', {
'fields' : [
'is_cancelled',
'start',
'finish',
'video_url',
]
}),
]
def get_form(self, request, obj=None, change=False, **kwargs):
OriginalForm = super().get_form(request, obj, change, **kwargs)
class Form(OriginalForm):
def save(self, commit=True):
instance = super().save(commit=False)
instance.slug = slugify(instance.title) # Here
if commit:
instance.save()
return instance
return Form
As you said the best (and simplest) solution would be to override the save
method of the model.
CodePudding user response:
You cannot exclude slug from admin fields since you are including it in prepopulated_fields. However, you can override your model's save method like below to generate a unique slug. You can make general BaseModel with this save method and extend your models with slug fields everytime:
from django.utils.text import slugify
def generate_unique_slug(klass, field):
"""
return unique slug if origin slug is exist.
eg: `foo-bar` => `foo-bar-1`
:param `klass` is Class model.
:param `field` is specific field for title.
"""
origin_slug = slugify(field)
unique_slug = origin_slug
numb = 1
while klass.objects.filter(slug=unique_slug).exists():
unique_slug = "%s-%d" % (origin_slug, numb)
numb = 1
return unique_slug
class BaseModel(models.Model):
class Meta:
abstact = True
def save(self, *args, **kwargs):
if hasattr(self, "slug") and hasattr(self, "title"):
if self.slug:
if slugify(self.title) != self.slug:
self.slug = generate_unique_slug(self.__class__, self.title)
else: # create
self.slug = generate_unique_slug(self.__class__, self.title)
super().save(*args, **kwargs)
class Foo(BaseModel):
title = models.CharField()
slug = models.SlugField()
# remaining fields listed below
class AnotherModelWithSlug(BaseModel):
title = models.CharField()
slug = models.SlugField()