5

I want to make the slug field as read_only depending on the other field value like "lock_slug".

Means There will be Two conditions.

1) When value of "lock_slug" is false then the slug field directly prepopulated from the field "title".

prepopulated_fields = {"slug": ("title",),}

2) When value of "lock_slug" is true then the slug field make as readonly.

def get_readonly_fields(self, request, obj = None):
    if obj and obj.lock_slug == True:
        return ('slug',) + self.readonly_fields        
    return self.readonly_fields

These two works fine independently, but problematic when used both.

Means when I try to add get_readonly_fields() on edit then it gives error because of prepopulated_fields.These two crashes with each other.

There will be any solution for working both on admin side.

I also refer below links

Making a field readonly in Django Admin, based on another field's value

django admin make a field read-only when modifying obj but required when adding new obj

But not working these two at the same time.

Thanks,

Meenakshi

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129
Meenakshi
  • 259
  • 5
  • 19

4 Answers4

8

Here is another way:

class PostAdmin(admin.ModelAdmin):
    list_display = (
        'title',
        'slug',
    )
    prepopulated_fields = {'slug': ('title',)}

    def get_readonly_fields(self, request, obj=None):
        if obj:
            self.prepopulated_fields = {}
            return self.readonly_fields + ('slug',)
        return self.readonly_fields
cansadadeserfeliz
  • 3,033
  • 5
  • 34
  • 50
  • 8
    In 2016, this doesn't work. If field is readonly, that means it wouldn't show, and prepopulated field will have nothing to prepopulate to. I get a `u"Key 'slug' not found in 'theModelForm'.` – KhoPhi Sep 03 '16 at 05:49
  • In 2021, this worked and solved my issue :) – raphodn Jun 08 '21 at 13:49
2

As @Rexford pointed out, the most voted answer doesn't work for recent django versions, since you can't make readonly a prepopulated field.

By the way, you can still get what you want, just override also the get_prepopulated_fields method using the same logic, i.e:

class PageAdmin(admin.ModelAdmin):
    prepopulated_fields = {
        'slug': ('title', ),
    }


    def get_readonly_fields(self, request, obj=None):
        readonly_fields = super().get_readonly_fields(request, obj)
        if request.user.is_superuser:
            return readonly_fields
        return list(readonly_fields) + ['slug', ]

    def get_prepopulated_fields(self, request, obj=None):
        prepopulated_fields = super().get_prepopulated_fields(request, obj)
        if request.user.is_superuser:
            return prepopulated_fields
        else:
            return {}
raratiru
  • 8,748
  • 4
  • 73
  • 113
abidibo
  • 4,175
  • 2
  • 25
  • 33
  • Thank you, this is a brilliant solution! I added `super()` in play, so changes and updates to the parent method are taken into account. – raratiru Mar 29 '19 at 14:48
0

We can not make the prepoluted field as read_only. So I create the new field which is not prepopulated and perform the action on that field and my problem get solved.

Meenakshi
  • 259
  • 5
  • 19
0

Mar, 2022 Update:

You cannot use "prepopulated_fields" with "readonly_fields" or "exclude" setting the same field "slug" as shown below because there is a confliction between them, then you will get error.

"prepopulated_fields" with "readonly_fields" setting the same field "slug":

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ['title']} # "slug" field is set
    readonly_fields = ['slug'] # "slug" field is set

"prepopulated_fields" with "exclude" setting the same field "slug":

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ['title']} # "slug" field is set
    exclude = ['slug'] # "slug" field is set

Moreover, with "prepopulated_fields" set "slug" field, you cannot use "fields" not set "slug" field as shown below because there is also a confliction between them, then you will get error:

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ['title']} # "slug" field is set
    fields = ['title'] # "slug" field is not set

Moreover again, you cannot use **"prepopulated_fields" set "slug" field in "admin.py" in this case you set "editable=False" to "slug" field in "models.py" because again, there is a confliction between them:

"prepopulated_fields" in "admin.py":

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ['title']} # "slug" field is set

"editable=False" in "models.py"

class Page(models.Model):
    title = models.CharField(max_length=255) # "editable=False" is set
    slug = models.SlugField(max_length=255, editable=False)

Moreover again and again, "prepopulated_fields" with "get_readonly_fields()" and "get_prepopulated_fields()" doesn't work:

from django.contrib import admin
from .models import (
    Page,
)

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    prepopulated_fields = {'slug': ['title']}

    def get_readonly_fields(self, request, obj=None):
        readonly_fields = super().get_readonly_fields(request, obj)
        if request.user.is_superuser:
            return readonly_fields
        return list(readonly_fields) + ['slug']

    def get_prepopulated_fields(self, request, obj=None):
        prepopulated_fields = super().get_prepopulated_fields(request, obj)
        if request.user.is_superuser:
            return prepopulated_fields
        else:
            return {}

So finally, I found it harmful to use "prepopulated_fields" so I recommend not to use "prepopulated_fields". So for now, my best solution is generating "slug" automatically without using "prepopulated_fields" as shown below.

First, add "editable=False" to "slug" field in "Page" model in "models.py":

                                     # "editable=False" is set
slug = models.SlugField(max_length=255, editable=False)

Then, add this code below to "Page" model in "models.py":

def save(self, *args, **kwargs):
    self.slug = slugify(self.title)
    super(Page, self).save(*args, **kwargs)

This is the full code of "Page" model in "models.py":

from django.db import models
from django.utils.text import slugify

class Page(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField(max_length=255, editable=False) 
    
    def save(self, *args, **kwargs):
        self.slug = slugify(self.title)
        super(Page, self).save(*args, **kwargs)

Then, as you can see below, "slug" field doesn't appear(is hidden) in Add page in the form:

enter image description here

But no worries!! When you fill "Title" field and click on "Save", "slug" is automatically generated:

enter image description here

You can check if "slug" is automatically generated or not by adding this code to "admin.py":

from django.contrib import admin
from .models import Page

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    list_display = ['title', 'slug']

Now, you can see the slug "this-is-my-page-1" is generated automatically:

enter image description here

In addition, if you remove "editable=False" from "slug" field in "Page" model in "models.py":

                        # "editable=False" is removed
slug = models.SlugField(max_length=255) 

"slug" field appears:

enter image description here

Then, when you fill "Title" field and click on "Save", you are asked to fill "slug" field which means "slug" is not automatically generated:

enter image description here

So to automatically generate "slug", you need to hide "slug" field from the form by adding "editable=False" to "slug" field:

                                        # Important to hide "slug" field
slug = models.SlugField(max_length=255, editable=False) 

In addition, you can hide "slug" field from the form by adding "exclude = ['slug']" or "fields = ['title']" to "PageAdmin" class in "admin.py".

"exclude = ['slug']":

from django.contrib import admin
from .models import Page

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    list_display = ['title', 'slug']
    exclude = ['slug'] # Here

"fields = ['title']":

from django.contrib import admin
from .models import Page

@admin.register(Page)
class PageAdmin(admin.ModelAdmin):
    list_display = ['title', 'slug']
    fields = ['title'] # Here

And in this case you add "exclude = ['slug']" or "fields = ['title']" to "PageAdmin" class in "admin.py", you can remove "editable=False" from "slug" field:

                        # "editable=False" is removed
slug = models.SlugField(max_length=255) 

Finally, you can hide "slug" field from the form by adding "exclude = ['slug']" or "fields = ['title']" to "PageAdmin" class in "admin.py" without adding "editable=False" to "slug" field:

enter image description here

Then, you fill "Title" field and click on "Save":

enter image description here

Then, the slug "this-is-my-page-2" is automatically generated:

enter image description here

Super Kai - Kazuya Ito
  • 22,221
  • 10
  • 124
  • 129