1

I'd like to build a new related field type. Here's a simple example:

class CustomQuerySet(QuerySet):
    def current(self):
        return self.filter(invalid_date__isnull=True)

class CustomManager(Manager):
    def get_query_set(self):
        return CustomQuerySet(self.model, using=self._db)
    def current(self):
        return self.get_query_set().current()

class Item(models.Model):
    objects = CustomManager()

    row_id = models.IntegerField(primary_key=True)
    id = models.IntegerField()
    name = models.CharField(max_length=100)
    change_date = models.DateTimeField(auto_now_add=True)
    invalid_date = models.DateTimeField(null=True, blank=True)


class Collection(models.Model):
    item = MultipleRelatedField(Item, related_field='id', related_name='collections')
    name = models.CharField(max_length=100)

Given c = Collection():

  • c.item should return a queryset equivalent to Item.objects.filter(id=c.item_id).
  • That queryset does need to be an instance of CustomQuerySet
  • Item.objects.filter(collections__name='SomeName') should work as expected.
  • The operation Collection.objects.filter(item__name='OtherName', item__invalid_date__isnull=True) should work as expected.

I realize this could be implemented with a ManyToManyField but I don't want to manually add/remove item objects to c.item. I don't need the join table as c.item always uses a single id value, it's just not a primary key on Collection. And c.item may /not/ contain item objects with different id values.

I'm aware that this is likely going require subclassing django.db.models.fields.related.ForeignRelatedObjectsDescriptor and/or django.db.models.fields.related.ForeignObject (or possibly the ManyToMany equivalents).

Aaron McMillin
  • 2,532
  • 27
  • 42
  • Will each item belong to a single collection? If so, you probably just need a `ForeignKey` on your `Item` object pointing to the `Collection`. You can use a `RelatedManager` to customize the queryset the reverse relation gives you. – Michael C. O'Connor Feb 14 '14 at 19:31
  • Good question. The names are contrived, there could be multiple classes like `Collection`, and it's not acceptable to modify `Item` to add `ForeignKey`s for each of them. `MultipleRelatedField` functions as a `ForeignKey` that returns multiple values. So more than one `Collection` could reference the same `id` value. – Aaron McMillin Feb 14 '14 at 19:58

1 Answers1

1

It looks to me like what you really want is an automatic way to generate a queryset from a model A against another model B based on the value of a field in A.

I suspect that ultimately, returning that queryset as the value of the field on A isn't really the best solution--that really goes against the grain of the ORM and would make it difficult, for example, to use that model in the admin or other common places.

Instead, it would be better to take an idea from how Django treats fields with choices set: automatically generate a method in the model class that return the derived information, such that if your model has a field:

item = MultipleRelatedField(Item, related_field='id', related_name='collections')

you would automatically get a method like get_item_queryset that would return the queryset you've specified.

To build that, you'd likely want to subclass an existing field (IntegerField would probably be a good starting point) and then override the __init__ method (to accept additional arguments) and the contribute_to_class method (where the new method would be generated). You can see the place where this happens for the choices-derived get_FIELD_display method here for inspiration.

Michael C. O'Connor
  • 9,742
  • 3
  • 37
  • 49
  • Not a terrible solution, but doesn't address the issue of joins. – Aaron McMillin Feb 17 '14 at 15:10
  • @AaronMcMillin, I'm not sure what you mean by "the issue of joins" – Michael C. O'Connor Feb 17 '14 at 17:04
  • This: `Item.objects.filter(collections__name='SomeName')` will not work with your solution as it doesn't create a relation as far as Django is concerned. – Aaron McMillin Feb 17 '14 at 18:04
  • @AaronMcMillin It would be possible to add a `RelatedManager` in the `contribute_to_class` method, as in a `ForeignKey`, but that's really pressing the ORM to do something it's not meant to--the relationship you're describing does not map well to a relational theory. If you want to pursue that route, You should read the source for `ForeignKey`. The meat of the issue will probably be adapting `ForeignRelatedObjectsDescriptor` and applying it to the foreign model like in https://github.com/django/django/blob/master/django/db/models/fields/related.py#L1552 – Michael C. O'Connor Feb 18 '14 at 22:47
  • I think I mentioned that in the question :) How does select * from A where a.id == b.a_id; not map to relational theory? This is essentially a ManyToMany relationship without the join table. – Aaron McMillin Feb 19 '14 at 15:40