Re: Symmetrical, Self-referencing ManyToManyField with Through Table

2020-06-25 Thread Jim Shepherd
Nice solution. Thanks!

On Thursday, June 25, 2020 at 5:49:23 PM UTC-4, Dan Madere wrote:
>
> I don't know of a way to configure the admin do that, but one solution 
> would be to use signals to notice when one-way records are created, then 
> automatically create the record for reverse relationship. We need to notice 
> when records are deleted as well. This approach would allow showing one 
> inline that shows the combined results. I was able to get it working with 
> this: https://gist.github.com/dgmdan/e7888a73c14446dccd7ad9aaf5055b10
> Hope this helps!
>

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/d9cd8467-74c5-4915-a452-15f4fc2d03bfo%40googlegroups.com.


Re: Symmetrical, Self-referencing ManyToManyField with Through Table

2020-06-25 Thread Dan Madere
I don't know of a way to configure the admin do that, but one solution 
would be to use signals to notice when one-way records are created, then 
automatically create the record for reverse relationship. We need to notice 
when records are deleted as well. This approach would allow showing one 
inline that shows the combined results. I was able to get it working with 
this: https://gist.github.com/dgmdan/e7888a73c14446dccd7ad9aaf5055b10
Hope this helps!

On Thursday, June 25, 2020 at 1:53:45 PM UTC-4 jes...@gmail.com wrote:

> After reviewing the tests, I think I now understand what is going on.
>
> Basically, for symmetric ManyToManyField, Django creates entries for both 
> directions automatically if the correct interface is used. From the test 
> models:
>
> class PersonSelfRefM2M(models.Model):
> name = models.CharField(max_length=5)
> sym_friends = models.ManyToManyField('self', 
> through='SymmetricalFriendship', symmetrical=True)
>
>
> class SymmetricalFriendship(models.Model):
> first = models.ForeignKey(PersonSelfRefM2M, models.CASCADE)
> second = models.ForeignKey(PersonSelfRefM2M, models.CASCADE, 
> related_name='+')
> date_friended = models.DateField()
>
>
> And the tests:
>
> def test_self_referential_symmetrical(self):
> tony = PersonSelfRefM2M.objects.create(name='Tony')
> chris = PersonSelfRefM2M.objects.create(name='Chris')
> SymmetricalFriendship.objects.create(
> first=tony, second=chris, date_friended=date.today(),
> )
> self.assertSequenceEqual(tony.sym_friends.all(), [chris])
> # Manually created symmetrical m2m relation doesn't add mirror entry
> # automatically.
> self.assertSequenceEqual(chris.sym_friends.all(), [])
> SymmetricalFriendship.objects.create(
> first=chris, second=tony, date_friended=date.today()
> )
> self.assertSequenceEqual(chris.sym_friends.all(), [tony])
>
> def test_add_on_symmetrical_m2m_with_intermediate_model(self):
> tony = PersonSelfRefM2M.objects.create(name='Tony')
> chris = PersonSelfRefM2M.objects.create(name='Chris')
> date_friended = date(2017, 1, 3)
> tony.sym_friends.add(chris, through_defaults={'date_friended': 
> date_friended})
> self.assertSequenceEqual(tony.sym_friends.all(), [chris])
> self.assertSequenceEqual(chris.sym_friends.all(), [tony])
> friendship = tony.symmetricalfriendship_set.get()
> self.assertEqual(friendship.date_friended, date_friended)
>
>
> So the tests show that the add() method needs to be used to create both 
> sides of the relationship. I assume that the Admin page does not use the 
> add method, but creates the intermediate entries which would require both 
> the be added for each entry.
>
> Is it possible to configure the Admin models to use add() or to create 
> both directions automatically?
>
> Thanks!
>
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/b8e0927d-6079-4a0a-91db-044d6f393998n%40googlegroups.com.


Re: Symmetrical, Self-referencing ManyToManyField with Through Table

2020-06-25 Thread Jim Shepherd
After reviewing the tests, I think I now understand what is going on.

Basically, for symmetric ManyToManyField, Django creates entries for both 
directions automatically if the correct interface is used. From the test 
models:

class PersonSelfRefM2M(models.Model):
name = models.CharField(max_length=5)
sym_friends = models.ManyToManyField('self', 
through='SymmetricalFriendship', symmetrical=True)


class SymmetricalFriendship(models.Model):
first = models.ForeignKey(PersonSelfRefM2M, models.CASCADE)
second = models.ForeignKey(PersonSelfRefM2M, models.CASCADE, 
related_name='+')
date_friended = models.DateField()


And the tests:

def test_self_referential_symmetrical(self):
tony = PersonSelfRefM2M.objects.create(name='Tony')
chris = PersonSelfRefM2M.objects.create(name='Chris')
SymmetricalFriendship.objects.create(
first=tony, second=chris, date_friended=date.today(),
)
self.assertSequenceEqual(tony.sym_friends.all(), [chris])
# Manually created symmetrical m2m relation doesn't add mirror entry
# automatically.
self.assertSequenceEqual(chris.sym_friends.all(), [])
SymmetricalFriendship.objects.create(
first=chris, second=tony, date_friended=date.today()
)
self.assertSequenceEqual(chris.sym_friends.all(), [tony])

def test_add_on_symmetrical_m2m_with_intermediate_model(self):
tony = PersonSelfRefM2M.objects.create(name='Tony')
chris = PersonSelfRefM2M.objects.create(name='Chris')
date_friended = date(2017, 1, 3)
tony.sym_friends.add(chris, through_defaults={'date_friended': 
date_friended})
self.assertSequenceEqual(tony.sym_friends.all(), [chris])
self.assertSequenceEqual(chris.sym_friends.all(), [tony])
friendship = tony.symmetricalfriendship_set.get()
self.assertEqual(friendship.date_friended, date_friended)


So the tests show that the add() method needs to be used to create both 
sides of the relationship. I assume that the Admin page does not use the 
add method, but creates the intermediate entries which would require both 
the be added for each entry.

Is it possible to configure the Admin models to use add() or to create both 
directions automatically?

Thanks!


-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/3ad9f7f2-539f-4b66-9717-863d5f412ef4o%40googlegroups.com.


Re: Symmetrical, Self-referencing ManyToManyField with Through Table

2020-06-25 Thread Jim Shepherd
On Thursday, June 25, 2020 at 10:26:25 AM UTC-4, Dan Madere wrote:
>
> I tried out the example code, and can replicate this. What's interesting 
> is that if I try removing the ContactConnection model, and the "through" 
> attribute, this allows Django to create the intermediate table on its own, 
> and then your get_connections() method works as expected! It seems like 
> using a custom through table is causing this somehow. I haven't found a fix 
> but will keep looking.


 Dan, thanks for looking into this issue.

I found the unit tests for this capability in 
https://github.com/django/django/tree/master/tests/m2m_through which seems 
to indicate that it does work.

I updated my version of Django to 3.1.b1 and copied the models and tests 
into my code. Neither the admin nor queries on the ManyToManyField return 
the full "symmetric" results.

Any other ideas?

Thanks!

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/05819cd1-7c67-4c64-bfce-69934315782eo%40googlegroups.com.


Re: Symmetrical, Self-referencing ManyToManyField with Through Table

2020-06-25 Thread Dan Madere
I tried out the example code, and can replicate this. What's interesting is 
that if I try removing the ContactConnection model, and the "through" 
attribute, this allows Django to create the intermediate table on its own, 
and then your get_connections() method works as expected! It seems like 
using a custom through table is causing this somehow. I haven't found a fix 
but will keep looking.

On Thursday, June 25, 2020 at 8:52:47 AM UTC-4 jes...@gmail.com wrote:

> Mike, Thanks for your suggestions. 
>
> Just a stab in the dark - have you tried giving from_contact a 
>> related_name? 
>>
>
> Yes, I have tried a few different combinations of providing a single 
> related_name and various naming conventions when providing related_name on 
> both ForeignKey fields without success.
>  
>
>> Another stab ... maybe you could just display the 
>> ContactConnectionAdmin(admin.ModelAdmin) with model=ContactConnection? 
>>
>>
> Yes, adding a ModelAdmin for the through table will show all of the 
> relationships, but it still requires searching both ForeignKey fields to 
> capture all of the relationships, regardless of which Contact hey were 
> created on.
>
> Workarounds are to add two Inlines, one for each through table foreign 
> key, or perform a compound query to combine the two results. I just figured 
> that since the capability was added in Django 3.0, that the symmetric 
> queries were included as well. My guess is that it is possible with the 
> correct configuration / naming conventions. I'll dive into the code to see 
> if anything pops up.
>
> Thanks!
>

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/7ee482c1-60ea-482f-8ffb-5f71ff92a1edn%40googlegroups.com.


Re: Symmetrical, Self-referencing ManyToManyField with Through Table

2020-06-25 Thread Jim Shepherd
Mike, Thanks for your suggestions. 

Just a stab in the dark - have you tried giving from_contact a 
> related_name? 
>

Yes, I have tried a few different combinations of providing a single 
related_name and various naming conventions when providing related_name on 
both ForeignKey fields without success.
 

> Another stab ... maybe you could just display the 
> ContactConnectionAdmin(admin.ModelAdmin) with model=ContactConnection? 
>
>
Yes, adding a ModelAdmin for the through table will show all of the 
relationships, but it still requires searching both ForeignKey fields to 
capture all of the relationships, regardless of which Contact hey were 
created on.

Workarounds are to add two Inlines, one for each through table foreign key, 
or perform a compound query to combine the two results. I just figured that 
since the capability was added in Django 3.0, that the symmetric queries 
were included as well. My guess is that it is possible with the correct 
configuration / naming conventions. I'll dive into the code to see if 
anything pops up.

Thanks!

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/1f730af3-11cb-49c6-a62f-e147107233a0o%40googlegroups.com.


Re: Symmetrical, Self-referencing ManyToManyField with Through Table

2020-06-24 Thread Mike Dewhirst

On 25/06/2020 8:29 am, Jim Shepherd wrote:
I am unable to get a symmetrical, self-referencing ManyToManyField 
with a through table to actually be symmetrical, at least through the 
Admin interface. I am using Django 3.0 so it looks like the capability 
is supported.


Models:
class Contact(models.Model):
 last_name = models.TextField(default='', blank=True)
 connections = models.ManyToManyField('self', through='ContactConnection', 
symmetrical=True, blank=True)

 def get_connections(self):
 return ', '.join([str(p.last_name)for pin self.connections.all()])


class ContactConnection(models.Model):
 to_contact = models.ForeignKey(Contact, related_name='contacts', 
on_delete=models.CASCADE, null=False)
 from_contact = models.ForeignKey(Contact, on_delete=models.CASCADE, 
null=False)
 comments = models.TextField(default='', blank=True)



Just a stab in the dark - have you tried giving from_contact a related_name?




Admin:
class ContactConnectionInline(admin.TabularInline):
 model = Contact.connections.through
 fk_name ='from_contact' extra =0
@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
 ordering = ('last_name',)
 list_display = ['last_name', 'get_connections']
 inlines = (ContactConnectionInline)
From the Admin page, when viewing a Contact, only one-direction of the 
ContactConnection relationship is shown which makes sense due to the 
fk_name='from_contact attribute fixing the direction.
But it seems that at least the get_connections() method would get both 
sides.


Is there a way either through a query or preferably an Inline to show 
the Contacts on both sides of the ContactConnection?


Another stab ... maybe you could just display the 
ContactConnectionAdmin(admin.ModelAdmin) with model=ContactConnection?


I haven't advanced to 3.x so I don't know what it is capable of.




Thanks,
Jim
--
You received this message because you are subscribed to the Google 
Groups "Django users" group.
To unsubscribe from this group and stop receiving emails from it, send 
an email to django-users+unsubscr...@googlegroups.com 
<mailto:django-users+unsubscr...@googlegroups.com>.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/91dad4bc-7eba-461d-89f1-aaf173e80831o%40googlegroups.com 
<https://groups.google.com/d/msgid/django-users/91dad4bc-7eba-461d-89f1-aaf173e80831o%40googlegroups.com?utm_medium=email_source=footer>.


--
You received this message because you are subscribed to the Google Groups "Django 
users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/174d1c37-cc87-4015-89c0-83f325f8d8c1%40dewhirst.com.au.


Symmetrical, Self-referencing ManyToManyField with Through Table

2020-06-24 Thread Jim Shepherd
I am unable to get a symmetrical, self-referencing ManyToManyField with a 
through table to actually be symmetrical, at least through the Admin 
interface. I am using Django 3.0 so it looks like the capability is 
supported.

Models:


class Contact(models.Model):
last_name = models.TextField(default='', blank=True)
connections = models.ManyToManyField('self',
 through='ContactConnection',
 symmetrical=True,
 blank=True)

def get_connections(self):
return ', '.join([str(p.last_name) for p in self.connections.all()])


class ContactConnection(models.Model):
to_contact = models.ForeignKey(Contact,
   related_name='contacts',
   on_delete=models.CASCADE,
   null=False)
from_contact = models.ForeignKey(Contact,
 on_delete=models.CASCADE,
 null=False)
comments = models.TextField(default='', blank=True)




Admin:

class ContactConnectionInline(admin.TabularInline):
model = Contact.connections.through
fk_name = 'from_contact'
extra = 0

@admin.register(Contact)
class ContactAdmin(admin.ModelAdmin):
ordering = ('last_name',)
list_display = ['last_name', 'get_connections']
inlines = (ContactConnectionInline)


 
>From the Admin page, when viewing a Contact, only one-direction of the 
ContactConnection relationship is shown which makes sense due to the 
fk_name='from_contact attribute fixing the direction.
But it seems that at least the get_connections() method would get both 
sides.

Is there a way either through a query or preferably an Inline to show the 
Contacts on both sides of the ContactConnection?

Thanks,
Jim

-- 
You received this message because you are subscribed to the Google Groups 
"Django users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to django-users+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/django-users/91dad4bc-7eba-461d-89f1-aaf173e80831o%40googlegroups.com.