André
André

Reputation: 25554

Django - How to do a transaction when saving a form?

I'm saving a form, but there is one save() method that is outside the transaction. The save() method outside a transaction is the save() on the "BicycleAdCategoryForm".

Here is the code:

models.py

class Main(models.Model):
    section             = models.ForeignKey(Section)
    user                = models.ForeignKey(User)
    title               = models.CharField(max_length=250)
    date_inserted       = models.DateTimeField(auto_now_add=True)
    date_last_update    = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return self.title

    # To order in the admin by name of the section
    class Meta:
        ordering = ['date_inserted']

class BicycleAd(models.Model):
    main                = models.ForeignKey(Main)
    bicycleadtype       = models.ForeignKey(BicycleAdType)
    bicycleaditemkind   = models.ForeignKey(BicycleAdItemKind) # MPTT Model
    bicycleadcondition  = models.ForeignKey(BicycleAdCondition)
    country             = models.ForeignKey(GeonamesCountry)       
    city                = models.ForeignKey(GeonamesLocal) 
    date_inserted       = models.DateTimeField(auto_now_add=True)
    date_last_update    = models.DateTimeField(auto_now=True)

    # To order in the admin by name of the section
    class Meta:
        ordering = ['date_inserted']   


class BicycleAdCategoryType(models.Model):
    n_bicycle_ad_category_type = models.CharField(max_length=100) # COMPRA, VENDA, TROCA
    date_inserted              = models.DateTimeField(auto_now_add=True)
    date_last_update           = models.DateTimeField(auto_now=True)

    def __unicode__(self):
        return self.n_bicycle_ad_category_type

    # To order in the admin by name of the section
    class Meta:
        ordering = ['n_bicycle_ad_category_type'] 

forms.py

class MainForm(forms.ModelForm):
    class Meta:
        model = Main
        exclude = ('user', 'section')

class BicycleAdForm(forms.ModelForm):
    class Meta:
        model = BicycleAd
        exclude = ('main', 'bicycleadtype', 'bicycleaditemkind', 'bicycleadcondition', 'city') # DPS RETIRAR DAQUI A "CITY"

class BicycleAdCategoryForm(forms.ModelForm):
    bicycleadcategorytype = forms.ModelMultipleChoiceField(queryset=BicycleAdCategoryType.objects.all(), required=False, widget=forms.CheckboxSelectMultiple) # Se retirar o widget fico uma SELECT box em q posso selecionar varias opcoes

    class Meta:
        model = BicycleAdCategory
        exclude = ('bicyclead',)

    def save(self, commit, rel_obj):
        data = self.data.getlist('bicycleadcategorytype')
        for item in data:
            obj_bicycleadcategory = BicycleAdCategory()
            obj_bicycleadcategory.bicyclead = rel_obj
            obj_bicycleadcategory.bicycleadcategorytype = BicycleAdCategoryType.objects.get(pk=item)
            obj_bicycleadcategory.save()

    def clean_bicycleadcategorytype(self):
        data = self.cleaned_data['bicycleadcategorytype']
        try:
            for item in data:
                bicycleadcategorytype = BicycleAdCategoryType.objects.get(pk=item.pk)
            return bicycleadcategorytype
        except (KeyError, BicycleAdCategoryType.DoesNotExist):
            raise forms.ValidationError('Invalid Bicycle Ad Category Type. Please try again.')    

views.py

def submit_ad_view(request):
    if request.method == 'POST':      
        model_main = Main()
        model_main.section = Section.objects.get(pk=request.POST['section'])
        model_main.user = request.user

        model_bicyclead = BicycleAd()
        model_bicyclead.bicycleadtype = BicycleAdType.objects.get(pk=2)
        model_bicyclead.bicycleaditemkind = BicycleAdItemKind.objects.get(pk=4)
        model_bicyclead.bicycleadcondition = BicycleAdCondition.objects.get(pk=2)
        model_bicyclead.city = GeonamesLocal.objects.get(pk=4803854)


        form_main = MainForm(request.POST, instance = model_main)
        form_bicyclead = BicycleAdForm(request.POST, instance = model_bicyclead)
        form_bicycleadcategory = BicycleAdCategoryForm(request.POST)

        if form_main.is_valid() and form_bicyclead.is_valid() and form_bicycleadcategory.is_valid():
            main_f = form_main.save()

            bicyclead_f = form_bicyclead.save(commit=False)
            bicyclead_f.main = main_f
            bicyclead_f.save()

            bicycleadcategory_f = form_bicycleadcategory.save(commit=False, rel_obj=model_bicyclead)


            resultado = 'valid'
        else:
            resultado = 'n_valid'


    return render_to_response('app/submit_ad.html', {'resultado': resultado}, context_instance=RequestContext(request))

I think main_f and bicyclead_f are inside a transaction but bicycleadcategory_f is outside a transaction. When bicycleadcategory_f fails, main_f and bicyclead_f are stored in the database.

Any clue on what I'm doing wrong?

Best Regards,

Upvotes: 1

Views: 1909

Answers (1)

Krzysztof Rosiński
Krzysztof Rosiński

Reputation: 1478

Django executes views using the commit_on_success decorator (or at least it behaves that way). If you're view crashes (uncaught exceptions), a rollback should take place. If some data is stored, and some is not there is a possibility that your DB engine does not support transactional processing.

Check out the django doc for more info https://docs.djangoproject.com/en/dev/ref/databases/

For example, if you're using MySQL with MyISAM you may encounter some problems

edit: Krzysiek Szularz: I guess everybody is using django TransactionMiddleware or simmilar things, so I skipped it - and mentioned only the logic layer.

Upvotes: 1

Related Questions