Tuesday, November 26, 2019

On the tickets.Ticket class we now have the following:

comments = GenericRelation('comments.Comment',
    content_type_field='owner_type', object_id_field='owner_id',

I thought it is a pity that we had no ticket with more than one comment in our demo data. So I changed that in the demo2 fixture of lino.modlib.comments.

>>> from lino import startup
>>> startup('lino_book.projects.team.settings.demo')
>>> from lino.api.doctest import *

On every ticket we now have an attribute comments which is an object manager that returns all the comments pointing to this ticket, i.e. that have lino.modlib.comments.Comment.owner set to this ticket. Similar to the related_name of a ForeignKey, but for a GenericForeignKey.

>>> obj = tickets.Ticket.objects.get(pk=1)
>>> list(obj.comments.all())
[Comment #85 ('Comment #85'), Comment #86 ('Comment #86'), Comment #87 ('Comment #87'), Comment #88 ('Comment #88'), Comment #89 ('Comment #89'), Comment #90 ('Comment #90'), Comment #91 ('Comment #91'), Comment #92 ('Comment #92')]

And because we specified the related_query_name "ticket", we also have a virtual field named ticket on each comment which contains almost the same as the GFK owner, but only if that owner is a ticket.

>>> obj = comments.Comment.objects.get(pk=85)
>>> obj.owner 
Ticket #1 ('#1 (⚹ Föö fails to bar when baz)')
>>> obj.ticket.first()
Ticket #1 ('#1 (⚹ Föö fails to bar when baz)')

The ticket field is implemented as an object manager, that’s why we must call first() to get the actual ticket. Don’t ask me why.

>>> obj.ticket 
<django.db.models.fields.related_descriptors.create_reverse_many_to_one_manager.<locals>.RelatedManager object at ...>

We can ask them all, but there will always be either one or no ticket.

>>> list(obj.ticket.all())
[Ticket #1 ('#1 (⚹ Föö fails to bar when baz)')]

That worked fine already yesterday. The problem I stumbled into today was that Lino did not yet support remote fields on the reverse generic relation. For example to show, in a table of comments, the site of the ticket of a comment.

>>> rt.login("robin").show(comments.Comments,
... column_names="id owner owner_type ticket__site", offset=82, limit=6)
==== =============================================== ====================== ========
 ID   Controlled by                                   Controlled by (type)   Site
---- ----------------------------------------------- ---------------------- --------
 83   `Developers <Detail>`__                         Team
 84   `Managers <Detail>`__                           Team
 85   `#1 (⚹ Föö fails to bar when baz) <Detail>`__   Ticket                 welket
 86   `#1 (⚹ Föö fails to bar when baz) <Detail>`__   Ticket                 welket
 87   `#1 (⚹ Föö fails to bar when baz) <Detail>`__   Ticket                 welket
 88   `#1 (⚹ Föö fails to bar when baz) <Detail>`__   Ticket                 welket
==== =============================================== ====================== ========
>>> from django.db.models import Q
>>> flt1 = Q(private=True)
>>> flt2 = Q(private=False)
>>> comments.Comment.objects.filter(flt1|flt2).count()