2

I have a form called Vehicles and i'm trying to assign a unique id to each one, each time a user completes one.

class Vehicles(models.Model):
     id = models.DecimalField(primary_key=True, unique=True)

Trying to avoid race conditions(when two forms are being submitted in the same time) after the initial value that I assign to the id field according to the last vehicle-db-record, before saving the form I query again the db for the id of the last record. More or less I do it this way:

def vehicle(request):
    vehicles= Vehicles.objects.all().order_by("-id")[0]
    id = vehicles.id+1

    if request.method == 'POST':
        form = VehiclesForm(data=request.POST)

        if form.is_valid():
            vehicles= Vehicles.objects.all().order_by("-id")[0]
            id = vehicles.id+1
            temp = form.save(new_veh_id=id)
            return render_to_response('success.html', locals(), context_instance= RequestContext(request))
    else:
        form = VehiclesForm(initial={'id': id})
    return render_to_response('insertVehicle.html', locals(), context_instance= RequestContext(request))

and in forms I override the save method:

    def save(self, *args, **kwargs):
        commit = kwargs.pop('commit', True)
        new_veh_id = kwargs.pop('new_veh_id', None)
        instance = super(VehiclesForm, self).save(*args, commit = False, **kwargs)

        if id is not None: instance.id = new_veh_id
        if commit:
            instance.save()
        return instance

but is_valid returns false with form error:vehicles with this id already exists. I use exactly this practice with another model and form (identical fields to these) and it works like a charm, forms pass validation despite the same id and it changes it in the last submitted form. Can anyone help me on this or suggest a better solution to achieve this functionality? What I do is maybe somehow 'custom'.

EDIT: I also tried this, but is_valid fails again

    def clean(self):
        cleaned_data = super(VehiclesForm, self).clean()
        id = unicode(self.cleaned_data.get('id'))

        vehicles= Vehicles.objects.all().order_by("-id")[0]
        id = vehicles.id+1
        cleaned_data[id] = id

        return cleaned_data
marlen
  • 473
  • 6
  • 20
  • 3
    why do you want to handle the unique values yourself? Consider using autofield https://docs.djangoproject.com/en/dev/topics/db/models/#automatic-primary-key-fields – karthikr Sep 12 '12 at 18:24
  • I'm working with a legacy db so I'm afraid that's not possible, or? – marlen Sep 12 '12 at 18:31
  • Then, in the view, vehicles= Vehicles.objects.all().order_by("-id")[0] id = vehicles.id+1 would not help. You would have to modify in save() method. to avoid concurrency, some examples are given here: http://stackoverflow.com/questions/320096/django-how-can-i-protect-against-concurrent-modification-of-database-entries – karthikr Sep 12 '12 at 18:41
  • yes, but in order to reach save(), form should pass validation. But when two forms are being submitted in the same time from different users the ids are the same and the last one fails validation. this is my problem. – marlen Sep 12 '12 at 18:50
  • you can use the clean method to populate just before saving. – karthikr Sep 12 '12 at 18:55
  • yes,I know that but again: form should pass is_valid first in order to call clean() or save() method and it doesn't in the case that I described. Correct me if I'm wrong. – marlen Sep 12 '12 at 18:59
  • is_valid() calls clean() on the form automatically – karthikr Sep 12 '12 at 19:00
  • 1
    @marlen have you considered [locking your table](http://djangosnippets.org/snippets/833/)? – César Sep 12 '12 at 19:12
  • @karthikr u're right. Although check the edit. – marlen Sep 12 '12 at 19:16
  • cleaned_data['id'] as opposed to clean_data[id] – karthikr Sep 12 '12 at 19:21
  • typo. It passed validation! Do u think that is sufficient or I should do it again in save()? Thank you for your help! – marlen Sep 12 '12 at 19:41
  • No you would not have to do it again. I would just do def clean_id(self): and return the cleaned id, instead of def clean() – karthikr Sep 12 '12 at 20:59
  • Do the ids have to be decimals? Perhaps using a GUID type identifier would work well in this situation. – Stephen Paulger Sep 14 '12 at 13:56

1 Answers1

1

I think you need select_for_update which locks the rows until the end of transaction. Mind you that although it is designated to work with many rows, you can still lock just one row, by making sure your filter query will return only one object.

rantanplan
  • 7,283
  • 1
  • 24
  • 45