66

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')
Nrzonline
  • 1,600
  • 2
  • 18
  • 37
Edwinner
  • 2,458
  • 1
  • 22
  • 33
  • 2
    A couple of things: 1) why are you setting model_instance.user to current_user.id instead of just current_user, 2) same with product.company = my_id instead of product.company = model_instance, and 3) what is the error you're getting (is it the ValueError you quoted?) and at which line in the code? – Scott A Nov 21 '15 at 01:13
  • 1
    the error is always at product.save(). – Edwinner Nov 21 '15 at 01:22
  • 1
    save() prohibited to prevent data loss due to unsaved related object 'company'. I applied your suggestions already. – Edwinner Nov 21 '15 at 01:22
  • 1
    I suggest using pdb or print statements to determine what my_id (or model_instance) are when saving product. It looks possible in that code to attempt saving products even if form.is_valid() is False. – Scott A Nov 21 '15 at 01:37
  • 1
    At what point exactly is the model saved? I am actually connected to the psql server and i can confirm that the first form data is there with the primary key. – Edwinner Nov 21 '15 at 01:43
  • What happens if you get rid of this line: product.save(commit=False)? – Scott A Nov 21 '15 at 01:47
  • Same error. Data from first form in database. – Edwinner Nov 21 '15 at 01:54
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/95755/discussion-between-scott-a-and-godfrey). – Scott A Nov 21 '15 at 01:55

4 Answers4

42

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
) 
doniyor
  • 36,596
  • 57
  • 175
  • 260
  • 4
    I am getting the same value error even though i am saving the modelform instance. Look at my code and tell me why i am getting this: save() prohibited to prevent data loss due to unsaved related object 'company'. – Edwinner Nov 21 '15 at 14:01
  • 1
    It's not that simple if you want to create a new object from model B in model A only on object creation of model A, if you override the `save()` method on model A. For example, `if self.pk is None:` this can't be used. – DjangoDev1 Mar 29 '21 at 23:32
34

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.

wolendranh
  • 4,202
  • 1
  • 28
  • 37
  • 5
    what if I know that object is in the database? do I have to make an extra trip just to convince Django? – mehmet Apr 06 '18 at 01:35
  • 2
    What If I want to delete this object? What if I need it to be None? – Богуслав Павлишинець Aug 14 '20 at 07:36
  • 1
    I ran into this while trying to modify fields and delete a related 1-1 object in a django-fsm (finite state machine) transition. I resolved it using [transaction management](https://django.readthedocs.io/en/latest/topics/db/transactions.html) to delete the related object after the FSM transition was committed: `transaction.on_commit(delete_related_object)` – Mike Gostomski Dec 09 '20 at 23:21
4

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.

Edwinner
  • 2,458
  • 1
  • 22
  • 33
  • 1
    Just to let know anyone reading this. Having null value is a bad idea, especially for ForeignKeys. Use only null values make it makes sense (usually never) – llazzaro Nov 24 '16 at 20:42
  • 3
    @llazzaro, why is this the case, can you provide some explanations or references to the claim. – unlockme Jan 27 '17 at 21:41
  • 1
    @llazzaro, i forgot the ins and outs of that project but i was auto-generating seven forms related to each day of the week for users. The users had to then fill out open and closing hours for each day. Some days naturally were left empty or unchanged. I think the answer below mine explains this in detail. The question was how to deal with those empty forms. – Edwinner Jan 28 '17 at 02:22
0

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()
AnonymousUser
  • 690
  • 7
  • 26