4

My model won't update after save() is called in my view. Looking into several places (here, here) didn't yield any answers for my case. My model does contains a ManyToManyField:

Model:

class Event(models.Model):
    name = models.CharField(max_length=120)
    date = models.DateTimeField(auto_now=False, auto_now_add=False)
    attendees = models.ManyToManyField(Employee)
    approval = models.ForeignKey(EventStatus, default=2)

    def __unicode__(self):
        return self.name

    def approve_url(self):
        return reverse("RGAU:EventApprove", kwargs={"id": self.id})

View:

def EventApprove(request, id=None, **kwargs):

    instance = get_object_or_404(Event, id=id)

    instance.approval.id = 3
    instance.save()

    messages.success(request, "Event Approved")
    return redirect("RGAU:UserDashboard")

The goal is to have the Event instance updated to "Approved" when a button is pushed on the webpage (hence the view EventApprove).

instance.approval.id is suppose to assign the correct approval status id but the save is not committed.

What am I doing wrong?

Community
  • 1
  • 1
NickBraunagel
  • 1,559
  • 1
  • 16
  • 30
  • Your Approval is a ForeignKey. Changing `instance.approval.id = 3` to `instance.approval = Approval.objects.get(id=3)` works? – Rafael May 03 '17 at 00:25

1 Answers1

7

You can do this two different ways. If you have only the pk (id) value of the related object you want to associate, use the fieldname_id syntax, like this:

instance.approval_id = 3
instance.save()

If you have an instance of the relation handy, you can just assign it:

approved = EventStatus.objects.get(id=3)
instance.approval = approved
instance.save()

Either way, your instance.approval will now be associated with the EventStatus having the id=3.

To explain further: instance.approval is a property of instance which contains the associated instance of EventStatus. If you then further dig into that by using instance.approval.id you are now touching the EventStatus instance and changing its id, which you really don't want to do.

instance.approval_id is a little magic where Django allows you to directly set the database column value that associates the two models.. bypassing the need to actually fetch the instance of your EventStatus from the database just to be able to associate it with Event. It is handy in situations like this when you are using well known reference id values that never change. But even so it's good practice to define them somewhere, eg:

class EventStatus(models.Model):
    APPROVED = 3

    ... etc etc...

Then instead of using instance.approval_id = 3 you can use instance.approval_id = EventStatus.APPROVED which is a lot easier to understand later.

little_birdie
  • 5,600
  • 3
  • 23
  • 28
  • Great explanation, thank you. Originally, I was incorrectly using `.filter()` instead of `.get()` to retrieve the appropriate `EventStatus` instance, which returned the error `Cannot assign "[]": "Event.approval" must be a "EventStatus" instance.` But I should have looked closer because this is a `list` of `objects`, not a single `object`! – NickBraunagel May 03 '17 at 12:18
  • Right yea, filter returns a Queryset which isn't a list exactly, it is iterable which, when you iterate over it, goes out to the database and does the query. It could be no results, one, or many... Whereas with get() it goes out to the database immedately, and expects to exactly one result row, which it then returns. If it gets none, or if it gets more than one.. then it throws an exception. So it's useful to use get() all the time for queries for single objects that should never fail unless there's a bug. – little_birdie May 03 '17 at 23:45