Admin interface not preventing simultaneous editing of the same record

162 views
Skip to first unread message

David

unread,
Aug 11, 2009, 9:24:55 PM8/11/09
to Django developers
After being unable to get any advice or further information via
google, #django or the users' mailing list, I opened
http://code.djangoproject.com/ticket/11652 regarding the admin
interface not preventing simultaneous editing of the same record (or
at least not providing that as an option). I guess this is not a
typical usage scenario, but for my use case I would like to implement
it if at all possible.

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.

Is there a way to save information in the session and get it back on
subsequent calls?

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.

Cheers,
Dave

Russell Keith-Magee

unread,
Aug 11, 2009, 10:29:16 PM8/11/09
to django-d...@googlegroups.com
On Wed, Aug 12, 2009 at 9:24 AM, David<da...@davidfindlay.org> wrote:
>
> After being unable to get any advice or further information via
> google, #django or the users' mailing list, I opened
> http://code.djangoproject.com/ticket/11652 regarding the admin
> interface not preventing simultaneous editing of the same record (or
> at least not providing that as an option). I guess this is not a
> typical usage scenario, but for my use case I would like to implement
> it if at all possible.

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 %-)

David

unread,
Aug 11, 2009, 10:55:12 PM8/11/09
to Django developers


On Aug 11, 10:29 pm, Russell Keith-Magee <freakboy3...@gmail.com>
wrote:

> 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.
It didn't occur to me before, but would the History mechanism be any
use here? It must already be tracking edits.

> A few random thoughts:
Thanks very much for those. I am digesting them now :-) I like the
idea of doing this at the form level rather than adding anything to
the model.

> 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.
It definitely does, and if I can find a way to do it that would be
useful to others that would be wonderful. I'm extremely impressed with
Django in my limited-to-this-point use of it.

Russell Keith-Magee

unread,
Aug 12, 2009, 12:06:43 AM8/12/09
to django-d...@googlegroups.com
On Wed, Aug 12, 2009 at 10:55 AM, David<da...@davidfindlay.org> wrote:
>
>
>
> On Aug 11, 10:29 pm, Russell Keith-Magee <freakboy3...@gmail.com>
> wrote:
>
>> 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.
> It didn't occur to me before, but would the History mechanism be any
> use here? It must already be tracking edits.

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 %-)

David

unread,
Aug 12, 2009, 12:17:49 AM8/12/09
to Django developers


On Aug 12, 12:06 am, Russell Keith-Magee <freakboy3...@gmail.com>
wrote:
> 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.

Agreed. I shall look at that first since it's more generally useful.

Alex Gaynor

unread,
Aug 12, 2009, 12:20:20 AM8/12/09
to django-d...@googlegroups.com
The problem with anything relying on AdminLog is it doesn't work if
something is changed outside the admin. Personally I think including
a JSON dump of the model with the page is the easiest solution, but I
could be convinced otherwise.

Alex

--
"I disapprove of what you say, but I will defend to the death your
right to say it." -- Voltaire
"The people's good is the highest law." -- Cicero
"Code can always be simpler than you think, but never as simple as you
want" -- Me

Doug Blank

unread,
Aug 12, 2009, 9:18:26 AM8/12/09
to Django developers
New Django user here, but also a developer on other Python projects: I
thought that I'd mention a couple of points.

For our use of his feature, we don't need to see what the data was,
but a diff of what we would like to save, and the way the data is now
would be useful. I imagine it might work like this: "The data you are
attempting to save has changed since you began your edit. You have the
option of a) using your version and overwriting the other data, b)
aborting your edit, or c) revising your edit based on the differences
below. Your data is: ... Current data is:..."

It would be nice if this were an option which could easily be added on
a view; however, there would be some fields that you wouldn't want/
need to check for changes. For example, an update that would timestamp
a field would not need to trigger this condition. So the view-level
options should include a list of fields to ignore. Perhaps it could
also take a function name for adding a custom saving conflict manager.

My $.02.

In any event, thank you all for a beautiful project!

-Doug

David

unread,
Aug 12, 2009, 5:31:19 PM8/12/09
to Django developers


On Aug 12, 12:20 am, Alex Gaynor <alex.gay...@gmail.com> wrote:
> Personally I think including
> a JSON dump of the model with the page is the easiest solution, but I
> could be convinced otherwise.

I have made a little bit of progress:

I already had a subclass of forms.ModelForm. I added a form field,
overrode overrode __init__ to set the field's initial value to the
json repr of the instance, and overrode clean() to do the comparison:

http://pastebin.com/f3b2c03cb

Problems:
1. The hidden field with the url-encoded json shows up on the webpage,
which is good, but I get the colon between my empty string label and
the hidden field, so it looks a bit odd.

2. I'm explicitly setting my hidden form field's "initial" attribute
which must be wrong - it seems like I'm breaking encapsulation anyway.

3. The biggie: I haven't figured out how to grab the JSON back from
the POST data, as I'm not clear where to do that - overriding the view
somehow? So as it stands my changes are useless, since separate
instances of my form are instantiated on the GET and the POST, and
hence my comparison never fails - the JSON for my hidden field for the
form instance created for the POST generates the JSON from the at-time-
of-POST instance of the model, instead of grabbing the previously-
generated JSON it from the hidden field in the POST data.

Does it seem like I'm on the right track?

David

unread,
Aug 13, 2009, 3:45:48 PM8/13/09
to Django developers

On Aug 12, 12:20 am, Alex Gaynor <alex.gay...@gmail.com> wrote:
> Personally I think including
> a JSON dump of the model with the page is the easiest solution, but I
> could be convinced otherwise.


I have something that works now:

http://pastebin.com/f1bd4c7a3

There's a big comment in the clean() method that explains it but it's
basically looking at the value and initial-value of the hidden form
field that has the JSON representation of the model in it.

I based the extraction of the two values from the field on BaseForm's
_get_changed_data() method.

I'm sure I'm doing several of the operations in a less than ideal
fashion, but it's working.
I also need to figure out how to stop it from showing the label on the
form the user actually sees - it's a blank label followed by a colon
followed by a hidden field so all you see is this odd colon sitting
there.

Will Hardy

unread,
Aug 13, 2009, 8:23:11 PM8/13/09
to django-d...@googlegroups.com
A quick note on your approach: What if the user isn't supposed to have
access to certain fields in a customised form?

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

Reply all
Reply to author
Forward
0 new messages