Proposal: Allow custom redirects in the admin system

26 views
Skip to first unread message

Joseph Kocherhans

unread,
Jan 11, 2006, 11:16:35 AM1/11/06
to django-d...@googlegroups.com
I hate to request more changes to magic-removal... but that's where
this should happen. Maybe it should wait until after it's been merged
though.

Currently the redirects after add_stage and change_stage in the admin
system are hardcoded. One of the most frequent requests I get in my
projects is to allow people to go to some other related object after
they save, not back to the list of objects. The redirect should be
customizable. Here's how I propose it should be done.

Add a template block {% block submitrow %} around the submit_row in
the admin/change_form.html. This would allow people to override the
save buttons. It would be cool to do this with a combination of
template tags, and attributes in the inner Admin class, but a block
would still be useful to have. If anyone has suggestions on how
attributes+template tags might work, I'd like to hear them. I haven't
thought about it too much yet.

By default, the inner Admin class should have a couple of new methods
(or attributes that are assigned to a callable) One callable returns
an HttpResponseRedirect for adding, and the other, after changing.
These callables would take the request and the new (or updated) object
as arguments. So the default for change_stage would look like:

def after_change_action(request, new_object):
pk_value = getattr(new_object, opts.pk.attname)
if request.POST.has_key("_continue"):
request.user.add_message(msg + ' ' + _("You may edit it again below."))
if request.REQUEST.has_key('_popup'):
return HttpResponseRedirect(request.path + "?_popup=1")
else:
return HttpResponseRedirect(request.path)
elif request.POST.has_key("_saveasnew"):
request.user.add_message(_('The %(name)s "%(obj)s" was added
successfully. You may edit it again below.') % {'name':
opts.verbose_name, 'obj': new_object})
return HttpResponseRedirect("../../%s/" % pk_value)
elif request.POST.has_key("_addanother"):
request.user.add_message(msg + ' ' + (_("You may add another
%s below.") % opts.verbose_name))
return HttpResponseRedirect("../../add/")
else:
request.user.add_message(msg)
return HttpResponseRedirect("../../")

Another option would be to have the callable return a (url, message)
tuple, and let the view handle HttpResponseRedirect and
request.user.add_message. I think it depends on whether people think
it would be useful to do anything but a redirect on successful
adding/editing of an object using the admin system.

It would be nice to be able to override log_change_message in a
similar way as well, but I think rjwittams has something event based
in mind for that. I could be wrong.

At any rate, I think the new inner Admin class is a clean place to put
this kind of stuff.

Joseph

Jacob Kaplan-Moss

unread,
Jan 11, 2006, 11:29:05 AM1/11/06
to django-d...@googlegroups.com
I think this is a very good idea, and I like the precedent of adding
methods to the inner Admin class to influence the admin behavior. I
do agree, however, that this should wait until after we finish the
magic-removal branch. There's nothing in this proposed change that
would break any existing code, so let's keep the ball moving and take
care of this slightly later.

Jacob

oggie rob

unread,
Jan 11, 2006, 3:41:49 PM1/11/06
to Django developers
> Another option would be to have the callable return a (url, message)
tuple, and let the view handle HttpResponseRedirect and
request.user.add_message.

I think you should use a dictionary. For example, you could pass in the
following dictionary:
{None:'../report1/%s'}
and then in the default method:
else:
request.user.add_message(msg)
redir = redir_dict.get(None, '../../')
return HttpResponseRedirect(redir)

etc. for all types of POST in the method.

Anyway, you could pass this into the change_stage method via the admin
class, or via urls.py (which is how I'm using this technique).
('url1/', 'change_list', {'app_label':'myapp', 'module_name':'mymod'}),
('url1/\d+/', 'change_stage', {'app_label':'myapp', 'module_name':
'mymod', 'redir_dict':{None:'../report1/%s'}}),
('url1/add/', 'add_stage',
{'app_label':'myapp','module_name':'mymod','redir_dict':{None:'../report1/%s'}}),

or you could write a tiny custom view that does the same and looks
cleaner!

Of course you could throw the same dict into the admin class to achieve
the same.

The reason I think it is nice to do this without a custom
"after_change_action" method is that much of the after_change_action
code would be duplicated on different modules, but the actual url is
the part that changes. Also much of the time you will just want to
redir the "Save" action and none of the others.

-rob

Matthew Flanagan

unread,
Jan 11, 2006, 7:03:04 PM1/11/06
to django-d...@googlegroups.com

I've been looking at the same thing in the last day and I found the
'post_url' keyword arg to the add_stage() view that allows you to
redirect somewhere after adding a new object but this arg doesn't
exist for the change_stage() view. Would adding a similar argument to
change_stage() solve this?


matthew

Joseph Kocherhans

unread,
Jan 11, 2006, 7:08:12 PM1/11/06
to django-d...@googlegroups.com
On 1/11/06, Matthew Flanagan <mattim...@gmail.com> wrote:
>
> I've been looking at the same thing in the last day and I found the
> 'post_url' keyword arg to the add_stage() view that allows you to
> redirect somewhere after adding a new object but this arg doesn't
> exist for the change_stage() view. Would adding a similar argument to
> change_stage() solve this?

Kind of, but that wouldn't allow you to specify new save button action
types, ("save and continue editing", "save and add X", etc.) and you'd
have to override urls to get the custom redirect. I'd rather avoid
that if possible.

Also, I don't believe post_url would allow you to use something like
the new object id in your url.

Joseph

Matthew Flanagan

unread,
Jan 11, 2006, 7:31:40 PM1/11/06
to django-d...@googlegroups.com
On 1/12/06, Joseph Kocherhans <jkoch...@gmail.com> wrote:
>
> On 1/11/06, Matthew Flanagan <mattim...@gmail.com> wrote:
> >
> > I've been looking at the same thing in the last day and I found the
> > 'post_url' keyword arg to the add_stage() view that allows you to
> > redirect somewhere after adding a new object but this arg doesn't
> > exist for the change_stage() view. Would adding a similar argument to
> > change_stage() solve this?
>
> Kind of, but that wouldn't allow you to specify new save button action
> types, ("save and continue editing", "save and add X", etc.) and you'd
> have to override urls to get the custom redirect. I'd rather avoid
> that if possible.
>

Similar to a "wizard" style interface? This would be handy for complex models.

> Also, I don't believe post_url would allow you to use something like
> the new object id in your url.

Now that would be useful!

>
> Joseph
>

oggie rob

unread,
Jan 11, 2006, 9:54:18 PM1/11/06
to Django developers
> Also, I don't believe post_url would allow you to use something like
the new object id in your url.

I needed this capability (I have wizard-like interfaces derived from
the admin pages), and ended up modifying change_stage so it read
something like this:
redir = redir_dict.get(None, '../../') # actually I just used a
post_url arg like in add_stage
if redir.find('%s') >= 0:
redir = redir % pk_value
return httpwrappers.HttpResponseRedirect(redir)

and I pass in post_url strings like '/report1/%s/'

Still this only works in simple situations (e.g. you cannot derive
other info from the POST like perhaps a date field as well, to format a
url like '/report1/123/2006/01/') so I think there is still value in
custom after_change_action methods.

-rob

Joseph Kocherhans

unread,
Feb 1, 2006, 7:08:11 PM2/1/06
to django-d...@googlegroups.com
On 1/11/06, Joseph Kocherhans <jkoch...@gmail.com> wrote:
> I hate to request more changes to magic-removal... but that's where
> this should happen. Maybe it should wait until after it's been merged
> though.
>
> Currently the redirects after add_stage and change_stage in the admin
> system are hardcoded. One of the most frequent requests I get in my
> projects is to allow people to go to some other related object after
> they save, not back to the list of objects. The redirect should be
> customizable. Here's how I propose it should be done.
>
> Add a template block {% block submitrow %} around the submit_row in
> the admin/change_form.html. This would allow people to override the
> save buttons.

This just occured to me today. I think it's flexible enough without
being too complicated.

def continue(request, new_object, msg):
"The save and continue action for the admin system."
pk_value = new_object._get_pk_val()


request.user.add_message(msg + ' ' + _("You may edit it again below."))

if request.POST.has_key("_popup"):
post_url_continue += "?_popup=1"
return HttpResponseRedirect(post_url_continue % pk_value)

default_add_options = (
('Save and Continue Editing', '_continue', continue),
('Save and Add Another', '_addanother', add_another),
('Save', '_save', save),
)

class Article(models.Model):
title = models.CharField(maxlength=100)

Admin:
# use the defaults, but nix the first option
# the default options should be easy to import so people can modify them
add_options = default_add_options[1:]

# use a totally custom set of options
change_options = (
('Save and Continue Editing', '_continue', continue),
('Save', '_save', save),
('Save and Add Another', '_addanother', add_another),
)

add_options and change_options are tuples of tuples. Each
(VERBOSE_NAME, POST_VAR_NAME, CALLABLE) tuple corresponds to a
button/action. Now, when there are no validation errors, the admin
does something like this:

new_object = manipulator.save(new_data)
# admin log stuff
msg = _('The %(name)s "%(obj)s" was added successfully.') % {'name':
opts.verbose_name, 'obj': new_object}
for save_option in opts.change_options:
if request.POST.has_key(save_option[1]):
return save_option[2](request, new_object, msg)

Obviously, the first matching one gets called even if more are present
in the post data. This might trip users up, but I think checking for
more than one in the post data is overkill. Maybe the model validator
should check though *shrug*

There are a few different options for deciding which is the default.
The options should be displayed in order, so.

1. The last option could implicitly be the default.
2. Add one more item, 'default', at the the end of the default options. (yuck!)
3. Another couple of Admin attributes set to the HTTP var name:
default_add_option = '_continue'
default_change_option = '_addanother'

I like a combination of 1 and 3 the best. Implicitly use the last
tuple as the default (so there's ALWAYS a default) and override that
default with default_add/change_option.

Thoughts?

Also, the post_url='../', post_url_continue='../%s/' args should be
removed from add_stage, or added to change_stage. My vote is for
removing them, or maybe we could pass in a tuple of options exactly
like the new Admin attributes instead. If custom options get passed
in, use them instead of the ones from the model, or maybe try to merge
them with the passed ones taking precedence.

As far as I can see, the form_url='' argument for add_stage and
render_change_form aren't used correctly either. (the add_stage's
form_url arg never gets passed in to render_change_form) Are these
still needed?

Joseph

Reply all
Reply to author
Forward
0 new messages