Search code examples
djangodjango-modelsdjango-admindjango-managers

Django, Custom Managers affect save method?


I'm using Django 1.7. I've got a default custom manager that filters on an "active" boolean field. According to the docs, it needs to be the default manager to work with related fields (ie. accessing User.story_set only shows active Story objects). I'm keeping the standard manager for admin and shell access, but I am unable to save changes to objects, I'm speculating because save() methods pass through the default manager at some point.

class Story(models.Model):
    active = models.BooleanField(default=True)
    ....

    objects = ActiveStoryManager()
    full_set = models.Manager()

class ActiveStoryManager(models.Manager):
    def get_query_set(self):
        return super(ActiveStoryManager, self).get_query_set().filter(active=True)
    use_for_related_fields = True

This works well for all public-facing use. However, in admin and shell I am unable to affect inactive objects, including turning them back active.

story = Story.full_set.get(id=#) will fetch a story with active=False, but after setting active=True I am unable to save, getting a

django.db.utils.IntegrityError: duplicate key value violates unique constraint "stories_story_pkey" DETAIL: Key (id)=(#) already exists.

Calling save.(force_update=True) returns django.db.utils.DatabaseError: Forced update did not affect any rows.

So while save() is a model method, it seems to depend on the default manager at some point in the saving process.

A workaround is using the Queryset API, e.g. Story.full_set.filter(id=#).update(active=True), but that's only usable in the shell, and requires manually typing each change, still can't save inactive instances in the admin.

Any help on this?


Solution

  • It cannot be done! As inancsevinc pointed out, save() calls on the default manager. The Django docs mention that get_query_set should not be modified on default managers, and I have sadly found out why. Hopefully in the future relatedManagers can be specified/controlled, but for now this method will not work for me. Confirmed in Django IRC chat.

    Instead, I'm throwing together a ordinary Manager method, as well as model methods for some models, to get equivalent functionality. Also requires changing all the related_set calls in the template to include the new methods, so it's a pain, but no other way.