[Django]-Django admin: separate read-only view and change view

20đź‘Ť

âś…

Here’s an answer that literally does what I asked with only a few lines of code and just a couple of template changes:

class MyModelAdmin(admin.ModelAdmin):
    fieldsets = [...]

    def get_readonly_fields(self, request, obj=None):
        if 'edit' not in request.GET:
            return <list all fields here>
        else:
            return self.readonly_fields

Now the usual URL for the change_form will produce a read only change_form, but if you append “?edit=1” to the URL, you will be able to edit.

The change_form template can also be customized depending on whether “?edit=1” is in the URL. To do this, put 'django.core.context_processors.request' in TEMPLATE_CONTEXT_PROCESSORS in settings.py, and then use request.GET.edit in the template.

For example, to add an “Edit” button when not in edit mode, insert

  {% if not request.GET.edit %}                                                 
  <li><a href="?edit=1">Edit</a></li>                                           
  {% endif %} 

just after <ul class="object-tools"> in change_form.html.

As another example, changing change_form.html to contain

{% if save_on_top and request.GET.edit %}{% submit_row %}{% endif %}            

will mean that the submit row will only be shown in edit mode. One can also hide the Delete buttons on inlines, etc, using this method.

For reference, here is what I put in settings.py:

TEMPLATE_CONTEXT_PROCESSORS = (                                                 
    'django.contrib.auth.context_processors.auth',                              
    'django.core.context_processors.debug',                                     
    'django.core.context_processors.i18n',                                      
    'django.core.context_processors.media',                                     
    'django.contrib.messages.context_processors.messages',                      
    # Above here are the defaults.                                              
    'django.core.context_processors.request',                                   
)

1đź‘Ť

I’d suggest to reconsider using custom views. With the help of generic DetailView, you’ll need to write literally two lines of code. The template won’t require much work either. You just extend standard change_form.html template, overriding field_sets block.

I know how to use the readonly attributes to produces a read-only view, but I don’t know how to produce two views, one read-only and one that allows changes.

You actually can register one model in the admin twice[1], using proxy models. (There’re some inconsistencies with permissions for proxy models, but it may not be a problem in your case.)

It seems to be possible to register multiple admin sites[2], too.

I’d like to reuse as much of the admin interface for this as possible, rather than writing a view from scratch.

Interface reuse as such has little to do with views, being mostly template- and style-related thing. View, however, should provide the template context necessary for interface reuse, as you correctly pointed out.

If you decide to go with multiple views per one ModelAdmin, then it might be useful for you to check how django-reversion project implements its admin integration: reversion/admin.py.

References

0đź‘Ť

You will need to change template django admin uses for model form. Make it readonly and add a button to original template linked to another url.

Note:

I highly discourage this approach, you will definitely not prevent simultaneous changes. This should be solved with locking.

Also, I recommend using django-reversion for keeping history of objects and eliminating “accidental changes” risk.

👤iElectric

0đź‘Ť

You could create a custom view and display your object there.

To create a custom view in an admin module, override the get_urls() method :

class MyAdmin(admin.ModelAdmin):
    …
    def get_urls(self):
        urls = super(MyAdmin, self).get_urls()
        my_urls = patterns('',
            url(r'^custom_view/(?P<my_id>\d+)/$', self.admin_site.admin_view(self.custom_viem), name='custom_view')
        )
        return my_urls + urls

    def custom_view(self, request, my_id):
        """Define your view function as usual in views.py

        Link to this view using reverse('admin:custom_view')

        """
        from myapp import views
        return views.custom_view(request, my_id, self)

In views.py :

def custom_view(request, object_id, model_admin):
    admin_site = model_admin.admin_site
    opts = model_admin.model._meta
    my_object = get_object_or_404(MyObject, pk=object_id)

    # do stuff

    context = {
        'admin_site': admin_site.name,
        'opts': opts,
        'title': _('My custom view'),
        'root_path': '%s' % admin_site.root_path,
        'app_label': opts.app_label,
        'my_object': my_object,
    }

    return render_to_response('my_template.html', context,
            context_instance=RequestContext(request))

In your template, use {% extends "admin/base_site.html" %} to keep the admin look and feel.

👤Thibault J

0đź‘Ť

The below code is implementation of read-only admin using proxy models.

Models.py

//real model

class CompetitionEntry(models.Model):
    pass

//Proxy model

class ReviewEntry(CompetitionEntry):
    class Meta:
        proxy = True
    def save(self, *args, **kwargs):
        pass

admin.py

//editable admin

class CompetitionEntryAdmin(admin.ModelAdmin):
     pass
admin.site.register(CompetitionEntry, CompetitionEntryAdmin)

// read-only admin (assign only “change” permission for this)

class ReviewEntryAdmin(admin.ModelAdmin):
    pass
admin.site.register(ReviewEntry, ReviewEntryAdmin)
👤Ryu_hayabusa

Leave a comment