Jak se zorientovat v ManyToMany

57 views
Skip to first unread message

MirekZv

unread,
Dec 13, 2017, 5:31:23 AM12/13/17
to django-cs
Nemohl byste mě někdo navést?
Na všechno o Djangu jsou milióny textů, ale zrovna o tomhle toho je minimum a nemůžu najít nic dobrého.

Napřed jsem ani nevěděl, že je nějaká extra podpora (kromě Inlinů v Adminu) a myslel jsem, že prostě do modelu přidám vazební tabulku (minimalisticky se 2 cizími klíči).

Pak jsem zjistil, že existuje v modelu ManyToManyField a sice ve 2 vzájemně nekompatibilních verzích:
bez through=...
s through=... (ten mi v Adminu negeneruje widget; dělám něco blbě nebo to tak má být?)

Obě pracují s vazební tabulkou, jen ve druhém případě k ní dělám model vazební tabulky ručně.
A jak se tedy varianta s through= liší od toho, když udělám jen tu vazební tabulku a pole ManyToManyField nepoužiju?

Mate mě taky to, že m:m relace není nahlížena symetricky, ale že si mám vybrat jen jednu z těch dvou tabulek a do ní ManyToManyField přidat.
API pro výběr je (prý) sice stejné, ale v té druhé tabulce nebudu mít příslušný widget.

Otázka tedy je:
Kterou variantu si mám vybrat a proč?

Nejde mi o jednoduchý příklad HlavniTabulka>=<Tagy - to chápu, že tam se hodí ta easy varianta bez through,
ale o složitější datová schémata s možností dlouhodobé udržitelnosti a rozvoje (zesložitění schématu).

Díky za případné nasměrování ....

Beda Kosata

unread,
Dec 13, 2017, 12:29:43 PM12/13/17
to djan...@googlegroups.com
Ahoj,

2017-12-13 11:31 GMT+01:00 MirekZv <mirek....@gmail.com>:
Nemohl byste mě někdo navést?
Na všechno o Djangu jsou milióny textů, ale zrovna o tomhle toho je minimum a nemůžu najít nic dobrého.

Napřed jsem ani nevěděl, že je nějaká extra podpora (kromě Inlinů v Adminu) a myslel jsem, že prostě do modelu přidám vazební tabulku (minimalisticky se 2 cizími klíči).

Pak jsem zjistil, že existuje v modelu ManyToManyField a sice ve 2 vzájemně nekompatibilních verzích:
bez through=...
s through=... (ten mi v Adminu negeneruje widget; dělám něco blbě nebo to tak má být?)

Obě pracují s vazební tabulkou, jen ve druhém případě k ní dělám model vazební tabulky ručně.
A jak se tedy varianta s through= liší od toho, když udělám jen tu vazební tabulku a pole ManyToManyField nepoužiju?


Obecně bych doporučil použít variantu bez through=, když to tak stačí a o mezi tabulku se tím pádem vůbec nestarat. Pokud ji potřebuješ (typicky pro ukládání pořadí, ale i dalších věcí), tak pak je to ManyToManyField vlastně jen syntactic sugar, který Ti umožní se na připojené objekty snadněji dotazovat v případě, že tu data z mezitabulky zrovna v dané query nepotřebuješ.

Další výhoda toho through (a já ji kvůli tomu mám někdy i v případech, kdy na mezitabulce nejsou žádná další data) je když potřebuješ udělat bulk select nad oběma propojenými tabulkami a v jedné query vybrat více propojených objektů (např. všechny autory, kteří začínají na "A" a k nim připojené články). To se pak dá dělat právě přes ten meziobjekt a pomocí select_related k tomu připojit objekty na obou stranách toho spojení. Select_related totiž jinak pro many-to-many použít nejde.
 
Mate mě taky to, že m:m relace není nahlížena symetricky, ale že si mám vybrat jen jednu z těch dvou tabulek a do ní ManyToManyField přidat.
API pro výběr je (prý) sice stejné, ale v té druhé tabulce nebudu mít příslušný widget.

Je to opravdu celkem symetrické (i když nevím, jak se to chová v adminu). Na druhé straně (tam, kde to nedefinuješ) si můžeš zvolit jméno atributu, přes který k tomu budeš přistupovat pomocí "related_name" v té definici (by default vznikne jméno xxxx_set). Dotazovat se pak můžeš buď z jedné strany, z druhé strany a nebo i z prostředka přes tu propojovací tabulku, jak jsem psal výše.
 

Otázka tedy je:
Kterou variantu si mám vybrat a proč?

Jak už jsem zmínil - na jednoduché věci bez through na složitější s tím. Dá se to kdyžtak i časem změnit pomocí migrací, takže pokud to zatím nepotřebuješ a víš že brzy nebudeš, tak nemusíš ručně mezitabulku vytvářet jen spekulativně (i když i to jsem občas viděl).

Jasný důvod pro mezitabulku jsou další navázané věci k té vazbě a případně zmíněná možnost efektivnějších bulk queries.
 

Nejde mi o jednoduchý příklad HlavniTabulka>=<Tagy - to chápu, že tam se hodí ta easy varianta bez through,
ale o složitější datová schémata s možností dlouhodobé udržitelnosti a rozvoje (zesložitění schématu).

Díky za případné nasměrování ....


Snad jsem aspoň trochu pomohl.

Beda
 

--
--
E-mailová skupina djan...@googlegroups.com
Správa: http://groups.google.cz/group/django-cs
---
Tuto zprávu jste obdrželi, protože jste přihlášeni k odběru skupiny „django-cs“ ve Skupinách Google.
Chcete-li zrušit odběr skupiny a přestat dostávat e-maily ze skupiny, zašlete e-mail na adresu django-cs+unsubscribe@googlegroups.com.
Chcete-li tuto diskusi zobrazit na webu, navštivte https://groups.google.com/d/msgid/django-cs/a9155410-4dee-4a21-8313-fa5417fa7a12%40googlegroups.com.
Další možnosti najdete na https://groups.google.com/d/optout.

hynek

unread,
Dec 17, 2017, 2:32:19 PM12/17/17
to django-cs
Obojí možnosti ManyToManyField, jak s through=... tak i bez něho, jsou zcela kompatibilní. Pokud bude k nějakému účelu užitečné se odvolávat na vazební tabulku, lze se k umělému modelu dostat pomocí Model._meta API.

Příklad: Někdo má modely Group a Permission. V modelu Permission je pole "groups". Pokud se potřebuji odvolat na vnitřní model vazební tabullky, mohu si uložit na něj referenci, abych nepsal dlouhé výrazy a zároveň si jej srozumitelně pojmenovat např. "GroupPermissions":

    GroupPermissions = Permission._meta.get_field('group').through

Nemusí se sahat na model, toto lze narychlo dodělat třeba uvnitř models.py nebo jednoho view nebo vyzkoušet na příkazové řádce manage.py shell. Chová se to, jako by explicitní "through=..." existovalo už od nepaměti. Změna nezpůsobí žádnou migraci ani zásah do jiného kódu.

Není mi znám žádný případ, kde by v posledních verzích Djanga bylo potřeba se v dotazu odvolávat na explicitní triviální vazební tabulku, ale uznávám, že je to s ní více intuitivní zápis. Myslí si snad někdo, že zná protipříklad, abych to na něm měl ukázat? V minulých letech byla vazební tabulka potřeba pro některé dotazy a ze setrvačnosti jsem pokračoval, ale letos jsem se to naučil i bez ní.

Je ohromně užitečné, že obě relace se zapisují asymetricky jen na jedné straně. Například model User nebo Person bývá použitý v mnoha projektech. K němu se může vztahovat několik aplikací a vždycky se použijí jen vybrané potřebné. Bylo by nežádoucí, kdyby všeobecný model musel zaviset na mnoha jednoúčelových modelech.

Pokud se k vazební tabulce časem přidá pole, dá se to jednoduše udělat tak, aby se tím v rámci migrace přidalo jen to pole, ačkoli byl dopsán nový explicitní vazební model. Všiml sis, že jsem si troufl pojmenovat odkaz na model anglicky srozumitelněji "GroupPermissions"? Raději pak použiji parametr `db_table='permission_group'`, než abych lámal slovosled na neanglické "PermissionGroup". Zkrátka lze bez obav plynule navázat ve dvou krocích, podle toho, jak košatý model je momentálně potřeba.

MirekZv

unread,
Dec 20, 2017, 12:28:31 PM12/20/17
to django-cs
Díky moc za informace.
No, nepůjde mi to tak rychle, jak jsem si myslel.
V ORM API (nebo jak mám ten jazyk nazývat?) je to asi fakt symetrické, a slouží to (jestli jsem vás dobře pochopil) jako zkratka, aby nebylo potřeba zařazovat (v reálu ovšem vždy existující) vazební tabulku.
Bezva.

Ale v adminu mi to moc symetrické nepřijde a ani podobné jedno druhému.

Bez through=.. se zobrazí Select prvek, ale jen na jedné straně vazby, na té, kde je definováno ManyToManyField (našel jsem nějaký 5 let starý článek, jak ho přidat i na druhou stranu, a po nějakém úsilí ho doplnil pro py36/dj20 a zobecnil).

S through=.. se mi Select nezobrazí vůbec, a při pokusu ho přidat pomocí explicitního seznamu fields=[.., ..] řve System Check, že to nelze.
Tak nevím, jestli je to vůbec přidatelné, nebo to vůbec nelze udělat. Pokud to přijatelné je, tak asi přidáním extra datových managerů k modelům na první i druhé straně (?).

No, upřímně, čekal jsem, že to nebude taková dřina, a že v roce 2018 je to už trochu dále.
Když třeba budu mít knihu, je schéma (více autorů : více knih) úplně jasná věc, a zároveň je autor (resp. autoři) hlavním údajem knihy. Takže pod autorem mohu uživatelovi promítnout knihy v Inlinu, ale pod knihou promítat autory v Inlinu (a ne v Selectu na hlavní záložce) je prostě šílené.
Tak až zas zbyde nějaký čas, budu se dál snažit...




Dne středa 13. prosince 2017 11:31:23 UTC+1 MirekZv napsal(a):

Jan Bednařík

unread,
Dec 21, 2017, 5:09:51 AM12/21/17
to djan...@googlegroups.com
Ahoj,

a jak by měl ten Select s trough fungovat? V tom trough modelu může být libovolné množství fieldů, které v selectu nemáš jak vyplnit. Pokud potřebuješ trough model s fieldy navíc, tak to musíš hold plnit v adminu toho trough modelu.

Honza



--
--
E-mailová skupina djan...@googlegroups.com
Správa: http://groups.google.cz/group/django-cs
---
Tuto zprávu jste obdrželi, protože jste přihlášeni k odběru skupiny „django-cs“ ve Skupinách Google.
Chcete-li zrušit odběr skupiny a přestat dostávat e-maily ze skupiny, zašlete e-mail na adresu django-cs+unsubscribe@googlegroups.com.

Martin Tiršel

unread,
Dec 21, 2017, 5:41:57 AM12/21/17
to djan...@googlegroups.com
Ahoj,

pokud je m2m a through, tak bud musis v adminu pridat ten propojovaci model a editovat jeho zaznamy (coz nemusi byt pohodlne) anebo bude nutne pouzit InlineModelAdmin, protoze ty dodatecne fieldy musi byt mozne nejak editovat. Tohle myslim bude to, co hledas - https://docs.djangoproject.com/en/1.11/ref/contrib/admin/#working-with-many-to-many-models 

S pozdravom,
Martin Tiršel,
tel.:
+420 776 790 511 (Vodafone CR)

MirekZv

unread,
Dec 22, 2017, 6:23:30 AM12/22/17
to django-cs
>> a jak by měl ten Select s trough fungovat?

Ahoj Honzo,
Možností, jak vymyslet design/ovládání je jistě mraky, ale když už se takhle ptáš, tak jedna odpověď se přímo nabízí:

Měl by fungovat úplně přesně stejně jako TabularInline, akorát by měl být jako fieldset zamíchatelný kamkoli mezi fieldy/fieldsety té základní tabulky.





Dne čtvrtek 21. prosince 2017 11:09:51 UTC+1 Jan Bednařík napsal(a):
Chcete-li zrušit odběr skupiny a přestat dostávat e-maily ze skupiny, zašlete e-mail na adresu django-cs+...@googlegroups.com.

MirekZv

unread,
Dec 22, 2017, 6:41:16 AM12/22/17
to django-cs
Trochu shrnu, co jsem se dověděl.

Asi by pro začátečníka bylo dobré, kdyby trochu znal historii Djanga, která předpokládám byla asi takováto (?):

1. Nejdřív se zavedly ForeignKey a nic víc - pro m2m z toho vyplývalo obvyklé řešení s vazební tabulkou.

2. Pak někoho napadlo, že když ve vazební tabulce není nic, než 2 cizí klíče, tak ManyToManyField by byla hezká zkratka,
včetně toho řešení s definováním na jedné straně (což je zajímavá a asi bezva věc - jak píše Hynek).
Udělala se pro to i podpora do základního admin formuláře, ale jen na té jedné straně, kde se to definuje.

3. Pak někoho napadlo, že často v té vazební tabulce bude sloupců víc (než jen 2 cizí klíče) a udělalo se (s předchozím nepříliš kompatibilní) řešení s through=..
Od podpory v základním admin formuláři se upustilo.

Někde časově mezitím vznikly Inliny, což je jistě bezva věc, pro tento účel.

Teď by se asi dalo filozofovat, co dál, nabízejí se dvě věci:
- buď to nechat jak to je, a nechat lidi, ať si nad to dopíšou, co chtějí;
- nebo přikročit k:
4. Prohlásit řešení bez through= za obsolete, zlikvidovat ho v budoucích verzích Djanga.
Bylo by to asi trochu bolestné, nicméně veškerá další řešení, která by se nad tím dělala (v jednotlivých aplikacích i v nadstavbových balících) by byla jednotně koncipovaná.

.... zatím všem vřelé díky ....

MirekZv

unread,
Dec 22, 2017, 6:53:35 AM12/22/17
to django-cs
Ještě mi vlastně uniká toto:
a) [Martin, v odkazu v dokumentaci]: VazebniModel = Group.members.through
b) [Hynek]: VazebniModel = Group._meta.get_field('members').through

To je obojí totéž? - v tom případě bych asi používal to první, jako jednodušší.
Nebo jsi to (Hynku) myslel pro nějaký extra případ, kdy to tím prvním způsobem nelze?

Reply all
Reply to author
Forward
0 new messages