You are correct that django.contrib.admin doesn't currently provide
protection against simultaneous edits.
I can't say this is a problem I've experienced myself. The sites I've
dealt with have fairly low traffic admin sites, or only a small number
of editors. However, I agree that it would be nice if this sort of
protection were to be 'baked in' - preferably at the ModelForm level,
if that's not possible, then as a specific extension to admin.
> My first thought was to try grab a JSON representation of the original
> record in an override of ModelAdmin.change_view; I thought I might be
> able to save this via extra_context and somehow get it back when
> change_view is called the second time for the HTTP POST of the
> modified record. I was hoping to then compare that saved JSON
> representation of the original record with the JSON representation of
> the record as it exists in the database on that POST call; if they
> differed then someone must have changed the record in the meantime and
> so I could generate an error message.
>
> I realized if I added an AutoField data to the record and saved that
> instead, then that would be a lot cheaper to save and compare, but I
> still don't know how to save that information.
There are any number of ways to solve this problem by adding a field
to the model (autofield with an 'edit number', timestamp tracking last
edit time etc). However, these aren't really candidates for a general
solution.
> Is there a way to save information in the session and get it back on
> subsequent calls?
Well... yes... that's what a session is :-)
That said, I'm not sure the session is the right place for this. It
really needs to be stored per form - consider the case of a single
user with multiple tabs open in the admin interface.
> Using an AutoField date it would probably be sufficient to just know
> when the original GET of the record happened for the user to start
> changing it. Is there a way to find that out?
>
> If anyone has any pointers or advice I'd be most grateful.
A few random thoughts:
Django's forms already have some functionality that is tangentially
related. If you have fields with callable default values, Django will
insert a hidden field into the form to contain the initial value. This
is required because the value of the callable at time of POST
processing may not be the same as the value of the callable when the
form is originally displayed. This isn't exactly the same scenario,
but it may give you some ideas to see how this could be implemented
for the larger problem.
Another piece of related work - In the run up to v1.1, a few changes
were made to avoid modification issues in FormSets. #10922 described a
problem - again, it's not quite the same problem, but it's vaguely
related, and may provide some illumination on ways to tackle the
problem.
A related thought - prior to v1.0, Django's login form would store
POST data in a pickle in a hidden field if the login session data
expired. This was removed from the login form because an attack vector
was demonstrated using this POST data, but as a general approach for
checking for modifications, I suspect it could be used here. A hidden
field on a form that contains a pickle of the original object data
could be added to the field on the original form render; on POST, the
pickle could be decompiled to give the original field values for
comparison.
A somewhat simpler approach would be to use a checksum - calculate an
md5/sha hash of the original object data and include it in a hidden
field. On submit, recompute the hash based on current values. If the
has changed, there have been modifications. I suspect that this would
be easier to implement, but would give you slightly less feedback - it
would be able to tell you that the object has been modified, but
wouldn't be able to tell you the nature of the modifications.
Yet another approach would be to only make this 'feature' available to
those objects that support it. That is, define a
'ConcurrentModificationField' as an extension of AutoField that you
can put on a model; this field would include a default form
implementation that does the appropriate validation. This isn't a
complete solution, but it could be implemented without any
modifications to Django itself.
If this is a problem that interests you, then I encourage you to dig
around. Developing the code to implement this fix will certainly give
you a very good grounding in the internals of Django's form
infrastructure. If you can find a clean way to implement this
functionality, then we are certainly interested in integrating this
into the Django core.
Yours,
Russ Magee %-)
An interesting thought. I can't think of any obvious technical reason
that the admin Log combined with a timestamp on form submission
couldn't be used. You would need to dig into specifics to see if there
are any complications, but this is certainly worth looking into.
This would fit into the same camp as the checksum solution - it would
tell you that a concurrent edit has occurred, but wouldn't tell you
how to resolve the concurrent edit.
Another cause for hesitation is that it would be an admin-specific
solution to the problem. A generic solution that would work for all
ModelForms would be nice if it is possible. However, a working
solution in admin would be better than a theoretical but non-existent
solution in forms.
Yours,
Russ Magee %-)
You probably want to take a checksum as Russell suggested earlier and
put that in the hidden field. Of course, dealing with conflicts will
be harder if you can't show the user which fields have changed, but
I'm sure there's a usable way around this.
Cheers,
Will