W1ldPo1nter / django-queryable-properties

Write Django model properties that can be used in database queries.
BSD 3-Clause "New" or "Revised" License
72 stars 1 forks source link

question: is it possible to return a model instance (or queryset)? #11

Closed ShaheedHaque closed 11 months ago

ShaheedHaque commented 11 months ago

I have one model which looks like this (simplified):

class Result(db_models.Model):
    company = db_models.ForeignKey(Company, on_delete=db_models.CASCADE)
    report_name = db_models.CharField(max_length=...)

and a second model like this:

class Report(db_models.Model):
     company = db_models.ForeignKey(Company, on_delete=db_models.CASCADE)
     name = db_models.CharField(max_length=...)

Note that the two models are are NOT related to each other. Can I use a QueryableProperty to fake a ForeignKey? I tried something like this:

class Result(db_models.Model):
   ... as above ...

    @queryable_property
    def definition(self) -> Optional[str]:
        return self.company.report_set.filter(name=self.report_name).first()

    @definition.annotater
    @classmethod
    def definition(cls):
        subquery = Report.objects.filter(company=OuterRef('company_id'), name=OuterRef('report_name'))[:1]
        definition = Value(Subquery(subquery))
        return definition

This particular variant says "Cannot resolve expression type, unknown output_field", bu t of course I have tried a variety of things. I guess part of my problem is that I am trying to return a complete instance, not some more primitive field value. Is there a way to make this work? Possibly involving a Lookup?

W1ldPo1nter commented 11 months ago

Not really, no. This isn't really because of a limitation of this library, but because of how Django works in general. Each field in a queryset (regardless of it being an actual model field or an annotation) always maps to an SQL column. You want an entire instance comprised of multiple fields, which is something that Django's subqueries themselves don't even offer. django-queryable-properties is pretty much just an intelligent wrapper around regular annotations, so it doesn't offer anything more here.

Depending on what you want to do with the subquery row in queries, you could just use multiple queryable properties - one for each Report field that you need access to. That way, you could use filters, ordering, etc. on those fields. To simplify this a bit, you could use the common subquery pattern that requires less boilerplate around subquery-based properties.

I guess it would theoretically be possible to build something more akin to what you need based on queryable properties by having one property for each field to query and then another property that combines those fields back into an instance after the query was executed. That's not really trivial as that last property would need to be somewhat intelligent to handle queried values via .select_properties() and regular getter accesses correctly, without performing unnecessary database queries. I could maybe even consider implementing something like this as part of django-queryable-properties, but it would be more of a long-term thing because it would be a somewhat large new feature.

ShaheedHaque commented 11 months ago

Thanks for the clear explanation (and quick response!).