AP257
AP257

Reputation: 93813

Django admin: hide read-only fields on new records?

I'm using the Django admin site with some readonly fields on records:

class BookAdmin(admin.ModelAdmin):
    fieldsets = [
    (None, {'fields': ['title', 'library_id', 'is_missing', \
                       'transactions_all_time']}),
    ]
    readonly_fields = ['transactions_all_time',]
    list_display = ('library_id', 'author', 'title')

This works great when editing records - the transactions_all_time field is read-only, just as I want.

However, when adding new records it behaves a bit oddly. I get a read-only section at the bottom of the page, which I can't edit and which is irrelevant at this point.

It would be much better if this field was not present at all when adding new records.

Is there any Django option for not displaying read-only fields while adding a new record? I know I can hack the CSS on add_form.html to hide it, but is there a better way?

Thanks.

Upvotes: 6

Views: 4759

Answers (5)

sparrowt
sparrowt

Reputation: 2968

This is how I ended up hiding the readonly_fields from the 'Add' page:

class MyAdmin(admin.ModelAdmin):

    readonly_fields = ['foo', 'bar']

    def get_exclude(self, request, obj=None):
        exclude = super().get_exclude(request, obj)
        if obj is None:
            # Adding a new one
            exclude = (exclude or []) + self.readonly_fields
        return exclude

Without this 'foo' and 'bar' were shown when creating a new object and - despite being read-only - the page showed values generated by their respective field default functions but not the actual values that get saved which is confusing.

For example foo = models.DateTimeField(default=django.utils.timezone.now) was showing up with the time the 'Add' page was loaded, but the resultant created object had the time the form was submitted. Or a field with default=uuid.uuid4 shows a completely random value shown on the 'Add' page, which is then thrown away and replaced with another on save.

The documentation for readonly_fields says:

they are also excluded from the ModelForm used for creating and editing

yet this does not appear to be the case, despite the Django code appearing to suggest that they are - so if anyone can explain why I need to add the above get_exclude override to get this behaviour I would be interested to hear! Maybe 'excluded from the ModelForm' doesn't necessarily mean they aren't on the page?

Upvotes: 0

Jack Cushman
Jack Cushman

Reputation: 2170

Here's a DRY version of Kushal's solution:

def get_fieldsets(self, request, obj=None):
    def if_editing(*args):
        return args if obj else ()
    return (
        (None, {
            'classes': ('wide',),
            'fields': if_editing('admin_thumb', 'admin_image',) +
                      ('original_image', 'user', 'supplier', 'customer', 'priority',)}
        ),
    )  

Note that this only works on the main form -- on an inline form, you're passed the main obj, not the inline obj.

Upvotes: 4

Kushal
Kushal

Reputation: 1189

I had a similar problem with a slightly different solution. I wanted to hide image previews (read only fields) from the 'new' form (the 'add' view) but display them when fetching a new object:

class PhotoAdmin(admin.ModelAdmin):
readonly_fields = ('admin_image', 'admin_thumb', )
search_fields = ('filename', 'user', 'supplier', 'customer')
list_display= ('admin_thumb','filename', 'user', 'supplier', 'customer')
#fields = ('admin_thumb', 'admin_image', 'original_image', 'user', 'supplier', 'customer')


def get_fieldsets(self, request, obj=None):
    fieldset_existing = (
        (None, {
            'classes': ('wide',),
            'fields': ('admin_thumb', 'admin_image',
                'original_image', 'user', 'supplier', 'customer', 'priority',)}
        ),
    )
    fieldset_new = (
        (None, {
            'classes': ('wide',),
            'fields': ('original_image', 'user', 'supplier', 'customer', 'priority',)}
        ),
    )
    if obj: # Editing
        return fieldset_existing
    return fieldset_new

The #fields line shows the original fields. I admit this solution is not very 'DRY' but it's clear and simple.

Upvotes: 2

zaan
zaan

Reputation: 897

I had similar problem. Resolved it like this

class MyModelAdmin(admin.ModelAdmin):
    readonly_fields = ('field_one',)
    def get_readonly_fields(self, request, obj=None):
        if obj: # Editing
            return self.readonly_fields
        return ()

Upvotes: 15

Black Hand
Black Hand

Reputation: 290

another alternative is that you set the editable arg in the original Model.

class Book(models.Model):
    transactions_all_time = models.BooleanField(editable=False)

your ModelAdmin will not show the field in edition, the field will get excluded.

Upvotes: 0

Related Questions