Digamos que tengo un modelo de publicación como este:
class Post(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) text_content = models.CharField() @property def comment_count(self): return self.comments.count() Digamos que necesito obtener todos los datos para la publicación con ID 3. Sé que puedo recuperar al usuario junto con la publicación en una consulta, haciendo Post.objects.select_related('user').get(pk=3) , permitiéndome guardar una consulta. Sin embargo, ¿hay alguna manera de obtener automáticamente comment_count también?
Sé que podría usar Post.objects.annotate(comment_count=Count('comments')) (que requerirá que elimine la propiedad o cambie el nombre de la anotación, ya que habría un AttributeError ), sin embargo, ¿es posible hacer que el ORM haga esa parte automáticamente, ya que la propiedad está declarada en el modelo?
Aunque podría simplemente agregar el .annotate , esto puede volverse muy tedioso cuando hay varias propiedades y claves externas que deben buscarse en varios lugares.
Tal vez esta no sea una solución perfecta para usted, pero puede encontrar algo similar que funcione. Puede agregar un administrador de objetos personalizado, consulte los documentos
Entonces podría agregar un método como with_comment_count() . O si siempre desea anotar, puede modificar el conjunto de consultas inicial .
class PostManager(models.Manager): def with_comment_count(self): return self.annotate( comment_count=Count('comments') ) # Or this to always annotate def get_queryset(self): return super().get_queryset().annotate( comment_count=Count('comments') ) class Post(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) text_content = models.CharField() objects = PostManager()Entonces podrías consultar así.
post = Post.objects.get(pk=1).with_comment_count() post.comment_count¡Gracias a Felix Eklöf logré hacerlo funcionar! Como se especifica en la pregunta, esto fue en el caso en el que había varias propiedades/claves externas que necesitaban captación previa y, como tal, se necesitaba la capacidad de "encadenar" la captación previa. Esto no es posible encadenando métodos de PostManager , ya que no devuelven un PostManager sino un QuerySet , sin embargo, se me ocurrió una función que almacena en caché todo lo que se le pide que almacene en caché de una vez, evitando la necesidad de encadenar métodos:
class PostManager(models.Manager): def caching(self, *args): query = self.all() if 'views' in args: query = query.annotate(view_count=Count('views')) if 'comments' in args: query = query.annotate(comment_count=Count('comments')) if 'user' in args: query = query.select_related('user') if 'archives' in args: query = query.prefetch_related('archives') return query class Post(models.Model): objects = PostManager() ... # caching can then be added by doing, this, for instance: Post.objects.caching('views', 'user', 'archives').get(id=3) Tenga en cuenta que para que esto funcione, también tuve que cambiar @property a @cached_property , lo que permitió que el ORM reemplazara el valor y permitiera el almacenamiento en caché adecuado con el mismo nombre.