5

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?

sybaritic
  • 392
  • 6
  • 15
  • Or does anyone know if I could use an `if` statement in the `get_query_set` so the save method always goes through regardless? I just want to use the filter on all "read" queries. – sybaritic Nov 23 '14 at 07:22

2 Answers2

5

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.

sybaritic
  • 392
  • 6
  • 15
1

To make admin page work with a different manager, you can implement get_queryset method on your ModelAdmin class.

class StoryAdmin(ModelAdmin):

    def get_queryset(self, request):
        return self.model.full_set.get_queryset()
inancsevinc
  • 2,632
  • 2
  • 17
  • 9
  • I've done this, and the admin page can see/read inactive rows. However, any changes/updates hit the same error as in shell. The save() method cannot be called. I'm still thinking save() somehow requires the default model manager to access the element, but not sure why. – sybaritic Nov 23 '14 at 06:31
  • You're right, save is [using](https://github.com/django/django/blob/e7b9a58b081299b30f807d5c66f7a5d1940efe4c/django/db/models/base.py#L713) the `_base_manager` which is objects by default. – inancsevinc Nov 23 '14 at 06:46
  • Any other thoughts on this? I feel like `use_for_related_fields` was made for just this use case, but it seems odd that once you declare it there is immediately no way to save the ineligible objects. – sybaritic Nov 23 '14 at 07:20