I'm working with Django admin, and I'd like to be able to use an existing instance of a model as a template for making a new object, so people using django admin don't need to re-key all the same properties on a object, when making new objects.
I'm picturing it a bit like this at the bottom of the Django admin form for updating a single object:
The django docs explain how to add bulk actions, by adding to the actions on a model, like so:
class ArticleAdmin(admin.ModelAdmin):
actions = ['make_published']
def make_published(self, request, queryset):
queryset.update(status='p')
make_published.short_description = "Mark selected stories as published"
However, it wasn't so clear to me how to do this for a single change model
form on an object, for actions I only want to apply to model at a time.
How would I do this?
I'm guessing I probably need to hack around with the change_model form, but beyond that, I'm not so sure.
Is there a fast way to do this without overriding loads of templates ?
Django Admin does not provide a way to add custom actions for change forms.
However, you can get what you want with a few hacking.
First you will have to override the submit row.
your_app/templates/admin/submit_line.html
{% load i18n admin_urls %}
<div class="submit-row">
{% if show_save %}<input type="submit" value="{% trans 'Save' %}" class="default" name="_save" />{% endif %}
{% if show_delete_link %}
{% url opts|admin_urlname:'delete' original.pk|admin_urlquote as delete_url %}
<p class="deletelink-box"><a href="{% add_preserved_filters delete_url %}" class="deletelink">{% trans "Delete" %}</a></p>
{% endif %}
{% if show_save_and_copy %}<input type="submit" value="{% trans 'Create a new item based on this one' %}" name="_save_and_copy" />{% endif %}
{% if show_save_as_new %}<input type="submit" value="{% trans 'Save as new' %}" name="_saveasnew" />{% endif %}
{% if show_save_and_add_another %}<input type="submit" value="{% trans 'Save and add another' %}" name="_addanother" />{% endif %}
{% if show_save_and_continue %}<input type="submit" value="{% trans 'Save and continue editing' %}" name="_continue" />{% endif %}
</div>
In the above template, I just added the line {% if show_save_and_copy %}<input type="submit" value="{% trans 'Create a new item based on this one' %}" name="_save_and_copy" />{% endif %}
. All other line are from default django implementation.
Then you will have to handle your button '_save_and_copy'
your_app/admin.py
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
class ArticleAdmin(admin.ModelAdmin):
def render_change_form(self, request, context, *args, **kwargs):
"""We need to update the context to show the button."""
context.update({'show_save_and_copy': True})
return super().render_change_form(request, context, *args, **kwargs)
def response_post_save_change(self, request, obj):
"""This method is called by `self.changeform_view()` when the form
was submitted successfully and should return an HttpResponse.
"""
# Check that you clicked the button `_save_and_copy`
if '_save_and_copy' in request.POST:
# Create a copy of your object
# Assuming you have a method `create_from_existing()` in your manager
new_obj = self.model.objects.create_from_existing(obj)
# Get its admin url
opts = self.model._meta
info = self.admin_site, opts.app_label, opts.model_name
route = '{}:{}_{}_change'.format(*info)
post_url = reverse(route, args=(new_obj.pk,))
# And redirect
return HttpResponseRedirect(post_url)
else:
# Otherwise, use default behavior
return super().response_post_save_change(request, obj)
This example is for your specific case, it's up to you to make it more generic if you need to.
That being said, for your specific case you can also just click "Save and continue" to save your work, and then click "Save as new" to make a copy of it. Don't you ?
As pointed out, there is not a way and needs to be hacked. Here's I think an elegant hack for adding custom actions to both the list and change form views. It doesn't actually save the form just execute whatever custom action you want against the current object and return you back to the same change form page.
from django.db.models import Model
from django.contrib import admin, messages
from django.contrib.admin.options import (
unquote,
csrf_protect_m,
HttpResponseRedirect,
)
class ArticleAdmin(admin.ModelAdmin):
change_form_template = 'book/admin_change_form_book.html'
actions = ['make_published']
def make_published(self, request, queryset):
if isinstance(queryset, Model):
obj = queryset
obj.status = 'p'
obj.save()
updated_count = 1
else:
updated_count = queryset.update(status='p')
msg = "Marked {} new objects from existing".format(updated_count)
self.message_user(request, msg, messages.SUCCESS)
make_published.short_description = "Mark selected stories as published"
@csrf_protect_m
def changeform_view(self, request, object_id=None, form_url='', extra_context=None):
if request.method == 'POST' and '_make_published' in request.POST:
obj = self.get_object(request, unquote(object_id))
self.make_published(request, obj)
return HttpResponseRedirect(request.get_full_path())
return admin.ModelAdmin.changeform_view(
self, request,
object_id=object_id,
form_url=form_url,
extra_context=extra_context,
)
Now you can add an <input>
for the action to the custom template view (this example uses book/admin_change_form_book.html
in change_form_template)
{% extends 'admin/change_form.html' %}
{% block form_top %}
<input
type="submit"
name="_make_published"
value="Mark Published"
class="grp-button grp-default"
>
{% endblock %}
If you look at the admin/change_form.html
(i.e. "django/contrib/admin/templates/admin/change_form.html") you can insert this custom <input>
action anywhere between the <form...> </form>
tags on the page. Including these blocks:
{% block form_top %}
{% block after_related_objects %}
{% block submit_buttons_bottom %}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With