Meenakshi
Meenakshi

Reputation: 359

Make the prepopulated slug field as readonly in Django Admin

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

Upvotes: 5

Views: 5438

Answers (5)

Dan Bridson
Dan Bridson

Reputation: 1

I have a simple solution using HTML5's readonly input element attribute. Here's an example:

models.py:

from django.db import models


class Example(models.Model):
    name = models.CharField(max_length=100)
    slug = models.SlugField()

    def __str__(self):
        return self.name

forms.py:

from django import forms

from .models import Example


class ExampleModelForm(forms.ModelForm):
    class Meta:
        model = Example
        fields = "__all__"

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["slug"].widget.attrs.update({"readonly": "1"})

admin.py:

from django.contrib import admin

from .forms import ExampleModelForm
from .models import Example


@admin.register(Example)
class ExampleAdmin(admin.ModelAdmin):
    form = ExampleModelForm
    prepopulated_fields = {"slug": ("name",)}

Upvotes: 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

Upvotes: 0

abidibo
abidibo

Reputation: 4287

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 {}

Upvotes: 2

cansadadeserfeliz
cansadadeserfeliz

Reputation: 3133

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

Upvotes: 8

Meenakshi
Meenakshi

Reputation: 359

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.

Upvotes: 0

Related Questions