Edwinner
Edwinner

Reputation: 2657

save() prohibited to prevent data loss due to unsaved related object

I need to pass a primary key from a newly created ModelForm to another form field in the same view but I get an error. Any suggestions to make this work? It looks like in the past, this would be the answer:

def contact_create(request):
    if request.method == 'POST':
        form = ContactForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(reverse(contact_details, args=(form.pk,)))
    else:
        form = ContactForm()

From the documentation, this is what is happening in the newer Django version > 1.8.3

p3 = Place(name='Demon Dogs', address='944 W. Fullerton') Restaurant.objects.create(place=p3, serves_hot_dogs=True, serves_pizza=False)
Traceback (most recent call last):
...
ValueError: save() prohibited to prevent data loss due to unsaved related object 'place'.

This is how I am getting my pk from the view:

my_id = ""
if form.is_valid():
    # deal with form first to get id
    model_instance = form.save(commit=False)
    model_instance.pub_date= timezone.now()
    model_instance.user= current_user.id
    model_instance.save()
    my_id = model_instance.pk

if hourformset.is_valid():
    hourformset.save(commit=False)
    for product in hourformset:
        if product.is_valid():
            product.save(commit=False)
            product.company =  my_id
            product.save()
else:
    print(" modelform not saved")
return HttpResponseRedirect('/bizprofile/success')

Upvotes: 72

Views: 69799

Answers (5)

Bal Krishna Jha
Bal Krishna Jha

Reputation: 7226

save will work in many cases, but there are cases when you'd not like to save one object if later on due to some error another object saving fails. Basically you want to rollback the changes. If you don't handle this case properly you might end up having corrupt data.

Django already provides atomic transaction api to handle such cases. Encapsulate all of the model changes in one function and wrap it with the atomic transaction.

Upvotes: 0

AnonymousUser
AnonymousUser

Reputation: 786

I just removed my model.save() and the error went away.


This only works if you are saving it when there are no changes. Otherwise you should save it one time, if you changed something in the queryset.

an example:

views.py

queryset = myModel.objects.get(name="someName", value=4)

queryset.value = 5
# here you need to save it, if you want to keep the changes.
queryset.save()
...

# If you save it again, without any changes, for me I got the error save() prohibited to prevent data loss due to unsaved related object

# don't save it again, unless you have changes.
# queryset.save()

Upvotes: 0

Edwinner
Edwinner

Reputation: 2657

Answered - The problem arose from django not saving empty or unchanged forms. This led to null fields on those unsaved forms. Problem was fixed by allowing null fields on foreign keys, as a matter of fact -- all fields. That way, empty or unchanged forms did not return any errors on save.

FYI: Refer to @wolendranh answer.

Upvotes: 4

wolendranh
wolendranh

Reputation: 4292

This was introduced in Django 1.8. Previously you could assign not saved instance to One-To-One relation and in case of fail it was silently skipped. Starting from Django 1.8 you will get error message in this case. Check a documentation of Django 1.7 -> 1.8 upgrade.

It says:

Assigning unsaved objects to a ForeignKey, GenericForeignKey, and OneToOneField now raises a ValueError.

If you are interested in more details, you can check save method in django.db.models.base: Some part of it:

for field in self._meta.concrete_fields:
    if field.is_relation:
        # If the related field isn't cached, then an instance hasn't
        # been assigned and there's no need to worry about this check.
        try:
            getattr(self, field.get_cache_name())
        except AttributeError:
            continue
        obj = getattr(self, field.name, None)
        # A pk may have been assigned manually to a model instance not
        # saved to the database (or auto-generated in a case like
        # UUIDField), but we allow the save to proceed and rely on the
        # database to raise an IntegrityError if applicable. If
        # constraints aren't supported by the database, there's the
        # unavoidable risk of data corruption.
        if obj and obj.pk is None:
            raise ValueError(
                "save() prohibited to prevent data loss due to "
                "unsaved related object '%s'." % field.name
            )

Last 5 rows are where this error is raised. basically your related obj which is not saved will have obj.pk == None and ValueError will be raised.

Upvotes: 36

doniyor
doniyor

Reputation: 37904

it is simple:

p3 = Place(name='Demon Dogs', address='944 W. Fullerton')   
p3.save() # <--- you need to save the instance first, and then assign
Restaurant.objects.create(
    place=p3, serves_hot_dogs=True, serves_pizza=False
) 

Upvotes: 45

Related Questions