Introduktion
Many2One er en af de mest centrale byggesten i Odos datamodel. Når du i dag binder en salgordre til en kunde, placerer et produkt i en kategori eller knytter en opgave til et projekt, benytter du en Many2One-relation. Forståelsen af denne felt-type er afgørende for både udviklere og superbrugere, der arbejder seriøst med Odoo-opsætning eller videreudvikling.
Set fra virksomhedens synspunkt er Many2One-felter det, der gør Odoo til et sammenhængende system. Uden relationerne ville hver app være et isoleret værktøj; med dem flyder data mellem dokumenter, og brugere kan hoppe fra én post til en anden uden at tænke på underliggende databasekonstruktioner.
Denne vejledning forklarer, hvad Many2One-feltet gemmer, hvordan det opfører sig i ORM og i brugerfladen, hvordan du opretter og konfigurerer det via Odoo Studio eller Python, samt konkrete eksempler fra CRM, Salg, Lager og Regnskab.
Hvad er Many2One-feltet i Odoo
I Odoo ORM skaber et Many2One-felt en peger fra en post til præcis én post i et andet model. Betegnelsen perspektiverer relationen fra det aktuelle models side: mange poster her kan pege på én post dér. Eksempelvis kan mange salgsordrer pege på den samme kunde, og mange produkter kan høre til én produktkategori.
På databaseniveau er Many2One blot en fremmednøgle i den nuværende tabel. Hvis en salgsordre peger på kunde med ID 42, ligger tallet 42 i kolonnen partner_id på sale_order-tabellen. Odoo står for at lave join’et og hente det visningsnavn, der skal vises i brugerfladen.
I brugerfladen oplever brugeren et Many2One-felt som en rullemenu eller en søgefelt-typeahead. Begynd at skrive, og systemet filtrerer matchende poster løbende. Feltet viser navnet på den valgte post, ikke dens interne ID — det gør det intuitivt at bruge for de fleste slutbrugere.
Her er et eksempel på, hvordan et Many2One defineres i en Python-model:
from odoo import fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
x_account_manager_id = fields.Many2One(
comodel_name='res.users',
string='Account Manager',
ondelete='set null',
)
Parameteren comodel_name angiver det tekniske navn på målmodellen, mens ondelete styrer, hvad der sker, hvis den linkede post slettes. Valgmulighederne er cascade (slet afhængige poster), set null (ryd linket) og restrict (forhindre sletning, hvis nogen peger på posten).
I Odoo Studio finder du Many2One under navnet Many2One i feltræet. Når du trækker et felt ind, vælger du målmodellen i en liste, og Studio opretter feltet for dig — en hurtig måde at få relationer ind i formularer uden at skrive kode.
Hvordan feltet fungerer
Når du læser et Many2One-felt gennem ORM’en, får du et recordset med den linkede post. Er feltet tomt, er recordsettet tomt. I Python kan du derfor tilgå linkede felter direkte ved hjælp af punktnotation:
order = self.env['sale.order'].browse(1) customer_name = order.partner_id.name customer_city = order.partner_id.city
Denne kæde-navigation er en af ORM’ens store fordele: du behøver ikke selv skrive SQL-joins eller ekstra forespørgsler — frameworket tager sig af opslagene i baggrunden.
Når du henter data gennem XML-RPC API’en, returnerer et Many2One typisk en liste med to elementer: det numeriske ID og det visningsnavn, fx [42, "Acme Corp"]. Er feltet tomt returneres False. Dette er vigtigt at håndtere korrekt i automatiseringsscripts og integrationer.
Vigtige feltattributter
Her er de vigtigste indstillinger, du kan konfigurere på et Many2One-felt i Odoo-rammen:
- comodel_name: Det tekniske navn på den model, feltet peger på — det eneste påkrævede parameter.
- ondelete: Styrer, hvad der sker med den aktuelle post, når den linkede post slettes. Muligheder: 'cascade', 'set null' og 'restrict'. Standard er 'set null'.
- domain: Et filter, der begrænser hvilke poster brugeren kan vælge i dropdownen. Fx domain=[('customer_rank', '>', 0)] for kun at vise kunder.
- context: Ekstra kontekstværdier, der sendes, når dropdown’en åbnes eller en ny relateret post oprettes — praktisk til at udfylde felter automatisk.
- required: Gør feltet obligatorisk — posten kan ikke gemmes uden en værdi.
- readonly: Forhindrer brugere i at ændre linket, nyttigt når relationen sættes automatisk.
- delegate: Når True, får felterne fra den relaterede model direkte adgang på den aktuelle model. Bruges ved modelarv, ikke til almindelige relationer.
Visning i forskellige views
På formularsider viser Many2One sig som en søgbar dropdown med mulighed for at åbne den linkede post direkte via en pil — det gør det nemt at hoppe mellem dokumenter uden at navigere via hovedmenuen.
I listevisninger viser feltet navnet på den linkede post og kan bruges til gruppevisning: fx gruppeordrer efter kunde eller opgaver efter projekt. Denne gruppering er flittigt brugt i rapporter og oversigter.
I søgefiltre kan Many2One felter bruges som betingelser og som group-by kriterier — når du filtrerer CRM-pipeline på kunde, arbejder du med et Many2One-felt.
Det omvendte One2Many
Hvert Many2One har et naturligt spejl: One2Many. Hvis salgsordrer peger på en kunde via Many2One, kan kunden vise alle sine ordrer gennem et One2Many-felt. Det ændrer ikke databasen med ekstra kolonner; det er en beregnet visning baseret på Many2One’s fremmednøgle. At implementere begge sider giver bedre navigation for brugerne.
Forretningsscenarier
Praktiske eksempler fra hverdagen
CRM: Leads og sælgere
I CRM hører hvert lead ofte en bruger (user_id), en Many2One til res.users — den ansvarlige sælger. Det gør det muligt at filtrere pipeline, måle konverteringsrater per rep og tildele leads i bulk uden ekstra udvikling. Vil du registrere en sekundær ansvarlig, opretter du bare et ekstra Many2One-felt til samme model.
Salg: Ordrer, kunder og prisbøger
En salgsordre har typisk partner_id (kunde) og pricelist_id (prisliste) som Many2One-felter. Når kunden vælges, kan Odoo udfylde prisliste, betalingsbetingelser og leveringsadresse via onchange-metoder, som læser Many2One-værdien og opdaterer andre felter — en af de mest synlige fordele ved en veldesignet datamodel.
Lager: Produkter og kategorier
Hvert produkt har en kategori via et Many2One-felt (categ_id). Kategorien styrer fx regnskabskonti, værdiansættelsesmetoder og lagerfjernelsesmåder. Rigtigt kategorivalg pr. produkt er afgørende for korrekt bogføring; Many2One gør det overskueligt: én dropdown per produkt, én kategori delt for mange produkter.
Regnskab: Posteringer og journaler
Hver regnskabspost (account.move) peger på en journal gennem journal_id Many2One. Journalen bestemmer nummerserie, posttype (salg, indkøb, bank osv.) og ofte standardkonti. Forkert journalvalg skaber fejlkategorisering i hovedbogen, så Many2One her er både funktionelt og kontrollerende for regnskabsnøjagtighed.
Projekt: Opgaver og projekter
I projektmodulet bestemmer project_id på project.task, hvilket stadie opgaven følger, hvilke teammedlemmer der har adgang, og hvordan timesedler tilknyttes. For konsulentfirmaer er relationen mellem timesedler, opgaver og projekt central for fakturering og indtjening — Many2One er rygraden i denne kæde.
Oprettelse og tilpasning af Many2One-feltet
Tre måder at tilføje Many2One på
Der er tre hovedtilgange afhængig af dine værktøjer: via Studio (no-code), som Python-felt i et modul (for udviklere) eller via API-skripter til automatiserede opsætninger.
Brug Odoo Studio (ingen kode)
- Åbn Odoo Studio fra hovedmenuen.
- Gå til den formular, hvor feltet skal tilføjes.
- Træk et Many2One-felt fra feltpanelet ind i formularen.
- Vælg målmodellen i feltets egenskaber.
- Giv feltet en label og evt. en domain for at afgrænse valgmulighederne.
- Gem og luk Studio.
Studio opretter feltet med x_studio_-præfiks og tilføjer det til visningen. Feltet er straks aktivt, så brugerne kan søge og vælge poster. Det er en hurtig måde at få relationer på uden teknisk baggrund.
Brug Python i et custom modul
For udviklere defineres Many2One i Python — anbefalet, når ændringer skal versionsstyres og udrulles i flere miljøer:
from odoo import fields, models
class ProjectTask(models.Model):
_inherit = 'project.task'
x_client_contact_id = fields.Many2One(
comodel_name='res.partner',
string='Client Contact',
domain=[('type', '=', 'contact')],
ondelete='set null',
help='The main contact at the client for this task.',
)
Efter feltdefinition skal det inkluderes i visnings-XML og modulet opdateres for at ændringerne slår igennem i databasen. Denne metode giver fuld kontrol over domain, ondelete, compute-metoder og constraints — standarden i produktionsudvikling.
Det er god praksis også at tilføje den reciprokke One2Many på den relaterede model, så navigation kan ske begge veje:
class ResPartner(models.Model):
_inherit = 'res.partner'
x_task_ids = fields.One2Many(
comodel_name='project.task',
inverse_name='x_client_contact_id',
string='Related Tasks',
)
Brug af XML-RPC API
Hvis du automatiserer opsætning via scripts eller fjernkonfiguration, kan du oprette Many2One-felter gennem XML-RPC:
field_id = models.execute_kw(
ODOO_DB, uid, ODOO_API_KEY,
'ir.model.fields', 'create',
[{
'name': 'x_client_segment_id',
'field_description': 'Client Segment',
'model_id': model_id,
'ttype': 'many2one',
'relation': 'res.partner.category',
'on_delete': 'set null',
'state': 'manual',
}]
)
Nøglen relation angiver målmodellen, og on_delete styrer sletteadfærden. Opret altid også den inverse One2Many, så navigation fungerer fra begge sider — en fast regel ved automatiserede konfigurationer.
Gode fremgangsmåder
1. Opret altid den reciprokke One2Many
Når du tilføjer et Many2One, så sørg for at lave den tilsvarende One2Many på den relaterede model. Det koster ikke ekstra kolonner, men det forbedrer brugeroplevelsen markant: fra kundekortet kan man hurtigt se alle tilknyttede ordrer, opgaver eller specialregistre.
2. Brug domain-filtre til at begrænse valg
Et Many2One til res.partner viser som standard alle partner-typer. Hvis feltet kun giver mening for kunder, så tilføj et domain som [('customer_rank','>',0)]. Det gør søgeresultaterne mere relevante og reducerer risikoen for forkerte valg.
3. Vælg ondelete med omtanke
ondelete har reel betydning: cascade sletter afhængige poster og kan føre til utilsigtet datatab. I langt de fleste business-cases er set null den sikreste mulighed. Brug restrict hvor en post ikke må slettes, mens noget peger på den, fx en aktiv produktkategori.
4. Undgå at duplikere data i fritekstfelter
En typisk fejlagtig løsning er at gemme et firmanavn i et char-felt i stedet for at pege på res.partner. Det skaber duplikation, gør gruppering og søgning upålideligt og fører til inkonsistens når navne ændres. Har informationen et naturligt hjem i en anden model, så brug Many2One.
5. Brug context til at forudfylde nye relaterede poster
Context kan sende standardværdier, når brugeren opretter en ny relateret post fra dropdown’en. Fx kan et Many2One pege på en kontakt inden for et projekt, og context kan automatisk fylde projektfeltet på den nye kontakt — det sparer tid og sikrer konsistente data.
Almindelige faldgruber
Oversetheden: glem den omvendte One2Many
Det hyppigste fejltrin er at oprette Many2One uden inverse One2Many. Konsekvensen er en ensrettet relation: man kan se linket fra den ene side, men kan ikke få en samlet liste fra den anden. Brugerklager om manglende synlighed ender ofte med ad hoc-rapporter eller ekstra søgninger for at kompensere.
Farligt: cascade-sletning uden omtanke
at sætte ondelete='cascade' på relationer mellem operationelle poster og masterdata kan føre til alvorligt datatab. Hvis en produktkategori slettes og alle produkter kaskades væk, forsvinder store mængder data. I de fleste tilfælde er set null eller restrict mere fornuftigt.
Manglende tjek for False i Python-kode
Når et Many2One er tomt, får du et tomt recordset (falsy) i Python. Hvis koden antager tilstedeværelse og laver kæde-opslag som order.partner_id.country_id.name uden sikkerhed, kan det føre til tomme strenge eller skjulte fejl i rapporter og emails. Tjek for tomme relationer når feltet ikke er obligatorisk.
Pegning mod forkert model
res.partner rummer kunder, leverandører, kontakter og firmaer. Et Many2One mod res.partner uden domain kan derfor vise interne brugere, afleveringsadresser og leverandører i dropdown’en, hvilket forvirrer salgsfolk. Definér domains til den forretningsmæssige hensigt.
Overforbrug af Many2One hvor en Selection ville være nok
Hvis sættet af mulige værdier er lille og statisk, er en Selection mere enkel og hurtigere. Many2One kræver en separat model og indfører join-omkostninger i forespørgsler. Til fx en status med tre-fire værdier er Selection ofte det rigtige valg; brug Many2One når værdierne er store, vedligeholdes af brugere eller deles på tværs af modeller.
Konklusion
Many2One-feltet binder Odoo’s moduler sammen. At kende det er ikke kun for udviklere — også for forretningsanalytikere, konsulenter og superbrugere, der bygger ud via Studio, er viden om hvornår og hvordan man bruger Many2One vigtig for et holdbart system.
Husk især: opret altid den omvendte One2Many, brug domain-filtre for at holde dropdowns relevante, vælg ondelete-adfærd med omtanke, og undgå Many2One når en simpel Selection dækker behovet.
Uanset om du arbejder i Studio, skriver et Python-modul eller konfigurerer via API, betaler korrekt håndtering af relationer sig af i form af færre fejl, enklere vedligehold og mere stabile implementeringer over tid.
Hos Dasolo hjælper vi virksomheder med at implementere, tilpasse og optimere Odoo i hele organisationen. Uanset om du har brug for hjælp til at designe en ren datamodel, tilføje relationer til dine formularer eller bygge et komplet modul, står vores team klar til at assistere. Kontakt os og lad os tage en snak om dit Odoo-projekt.