소개
Many2One 필드는 Odoo 데이터 모델의 핵심 연결고리입니다. 판매주문과 고객을 연결하거나 상품을 카테고리에 묶고, 업무를 프로젝트에 속하게 할 때마다 이 관계를 사용합니다. 즉, 시스템 전반에서 가장 빈번하게 쓰이는 관계형 필드로서, 진지하게 Odoo를 확장하거나 개발하려면 이 개념을 확실히 이해해야 합니다.
비즈니스 관점에서 Many2One은 모듈들 간에 정보가 자연스럽게 흐르도록 만드는 접착제 역할을 합니다. 이 필드가 없었다면 각 모듈은 독립된 섬처럼 고립되었을 것입니다. 반대로 Many2One 덕분에 관련 문서들을 사용자가 의식하지 않고도 손쉽게 오가며 업무 흐름이 매끄럽게 이어집니다.
이 가이드는 Many2One이 실제로 무엇을 저장하는지, Odoo ORM과 UI에서 어떻게 동작하는지, Odoo Studio나 Python으로 필드를 생성·설정하는 방법, 그리고 CRM·영업·재고·회계 등에서의 실무 적용 사례를 종합적으로 다룹니다.
Odoo의 Many2One 필드란 무엇인가
ORM 관점에서는 Many2One 필드가 한 모델의 레코드에서 다른 모델의 단일 레코드로 향하는 외래 키 링크를 만듭니다. 현재 모델에서 보면 여러 레코드가 상대 모델의 한 레코드를 가리킬 수 있다는 의미입니다. 예컨대 여러 판매주문이 하나의 고객을 가리키고, 여러 상품이 하나의 상품 카테고리에 속할 수 있습니다.
데이터베이스 수준에서는 Many2One이 현재 테이블에 외래 키 정수 값을 저장합니다. 예를 들어 판매주문의 파트너가 ID 42라면 sale_order 테이블의 partner_id 컬럼에 정수 42가 들어갑니다. Odoo는 이 값을 바탕으로 자동으로 조인을 처리하고 연결된 레코드의 이름을 가져옵니다.
사용자 인터페이스에서는 Many2One이 드롭다운 또는 자동완성 입력창으로 나타납니다. 사용자가 이름 일부를 입력하면 실시간으로 일치하는 레코드를 필터링해 보여주고, 선택하면 연결이 설정됩니다. 화면에는 내부 ID가 아니라 연결된 레코드의 표시명이 보이므로 UX 측면에서 매우 자연스럽습니다.
Python 모델 정의에서는 다음과 같은 방식으로 선언합니다:
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',
)
여기서 comodel_name은 연결할 대상 모델의 기술명이고, ondelete는 대상 레코드가 삭제될 때 현재 레코드에 어떤 영향을 줄지 정합니다. 선택지는 cascade(연쇄 삭제), set null(연결만 해제), restrict(연결이 존재하면 삭제 차단)입니다.
Odoo Studio에서는 필드 선택기에서 Many2One 항목을 골라 폼에 끌어다 놓기만 하면 됩니다. 대상 모델을 목록에서 선택하면 Studio가 필드 생성 작업을 대신 처리해 주므로 코드 없이도 관계형 필드를 쉽게 추가할 수 있습니다.
필드의 동작 원리
ORM을 통해 Many2One을 읽으면 연결된 레코드를 담은 레코드셋이 반환됩니다. 필드가 비어 있으면 빈 레코드셋이 돌아옵니다. 파이썬에서는 점 표기법으로 바로 관련 필드에 접근할 수 있습니다.
order = self.env['sale.order'].browse(1)
customer_name = order.partner_id.name
customer_city = order.partner_id.city
이처럼 점 체이닝으로 다른 테이블에 대해 별도의 조인 쿼리를 직접 작성할 필요 없이 ORM이 내부적으로 처리해 주는 점이 Odoo의 큰 장점입니다.
XML-RPC 같은 외부 API로 읽을 때 Many2One은 [ID, 표시명] 형태의 리스트로 반환됩니다. 예: [42, "Acme Corp"]. 값이 없으면 False가 반환되므로 API 응답을 처리할 때 이 점을 염두에 두어야 합니다.
주요 필드 속성
Many2One 필드에서 자주 설정하는 핵심 속성들은 다음과 같습니다:
- comodel_name: 대상 모델의 기술명. 이 파라미터는 필수입니다.
- ondelete: 연결된 레코드가 삭제될 때 현재 레코드에 적용할 동작. 기본값은
'set null'이며, 옵션은'cascade','set null','restrict'입니다. - domain: 드롭다운에서 선택 가능한 레코드를 제한하는 필터입니다. 예:
domain=[('customer_rank','>',0)]는 고객만 선택되도록 합니다. - context: 연결된 레코드를 열거나 새 레코드를 만들 때 전달되는 추가 컨텍스트 값으로, 새 레코드의 초기값을 채우는 데 유용합니다.
- required: 필드를 필수로 만들어 값이 없으면 레코드를 저장할 수 없게 합니다.
- readonly: 사용자가 값을 변경하지 못하게 잠급니다. 보통 프로그램적으로 설정된 링크를 사용자가 수정하지 못하게 할 때 씁니다.
- delegate: True로 설정하면 대상 모델의 필드들을 현재 모델에서 직접 접근할 수 있게 합니다. 이는 Odoo의 상속 패턴에 쓰이지만 일반적인 관계형 필드 용도로는 사용되지 않습니다.
뷰에서의 표현
폼 뷰에서는 Many2One이 검색 입력이 포함된 드롭다운으로 보입니다. 사용자는 부분 문자열로 검색해 결과를 좁힐 수 있고, 필드 옆의 화살표를 눌러 연결된 레코드를 바로 열어볼 수 있습니다. 이 기능은 메뉴를 거치지 않고 관련 문서를 빠르게 확인할 때 매우 편리합니다.
리스트(트리) 뷰에서는 연결된 레코드의 표시명이 보이며, 그룹핑으로 활용할 수 있습니다. 예를 들어 판매주문을 고객별로 묶거나 업무를 프로젝트별로 그룹화하는 보고서 기능에 자주 사용됩니다.
검색 뷰에서는 필터나 그룹 기준으로 Many2One을 활용할 수 있습니다. CRM 파이프라인에서 고객으로 필터링하는 동작 자체가 Many2One 필드를 이용한 검색입니다.
역방향 One2Many
모든 Many2One에는 자연스러운 역방향이 존재하는데, 그것이 One2Many입니다. 예컨대 여러 판매주문이 고객을 가리키면 고객 모델은 One2Many를 통해 모든 연결된 주문 목록을 보여줄 수 있습니다. 실무에서는 양쪽을 모두 정의해 두는 것이 좋습니다. One2Many는 별도의 DB 컬럼을 추가하지 않으며, 반대편 Many2One의 외래 키를 읽어 동적으로 계산됩니다.
비즈니스 활용 사례
표준 Odoo 도입 환경에서 Many2One은 매우 흔하게 쓰입니다. 다음은 실제 업무에서 자주 등장하는 다섯 가지 예시입니다.
CRM: 리드와 담당자 연결
CRM에서는 각 리드/기회에 user_id 같은 Many2One 필드가 있어 담당 영업사원을 지정합니다. 관리자는 담당자별 파이프라인을 필터링하거나 전환율을 확인하고, 리드를 일괄 할당할 수 있습니다. 추가로 계정 관리자 같은 보조 담당자를 추가하려면 동일 모델을 향하는 또 다른 Many2One을 추가하면 됩니다.
영업: 주문과 고객·가격표 연결
판매주문에는 보통 고객(partner_id)과 가격표(pricelist_id) 같은 필수 Many2One이 있습니다. 고객을 선택하면 Odoo가 가격표, 결제 조건, 배송지 등을 자동으로 채워줄 수 있는데, 이는 onchange 메서드가 Many2One 값을 읽어 관련 필드를 채우기 때문에 가능한 동작입니다.
재고: 상품과 카테고리
각 상품은 상품 카테고리(product.template의 categ_id)를 Many2One으로 참조합니다. 카테고리는 원가·수익 계정, 평가 방식, 출고 전략 등 재고·회계 규칙을 결정합니다. 수백 개 상품에 동일한 카테고리 레코드를 공유하는 방식은 재무 보고의 정확성을 확보하는 데 매우 중요합니다.
회계: 분개와 전표 유형
회계 분개는 회계저널(journal_id)을 Many2One으로 참조합니다. 저널은 문서 번호 시퀀스, 분개 유형(매출·매입·은행 등), 기본 차변·대변 계정 등을 결정합니다. 잘못된 저널 선택은 장부 구조를 망가뜨리므로 이 Many2One은 단순 편의 기능을 넘어 회계 통제의 핵심 지점입니다.
프로젝트 관리: 업무와 프로젝트
프로젝트 앱에서는 각 과제가 프로젝트(project_id)에 속하며, 이 링크 하나가 과제의 단계, 접근 권한, 타임시트 배분 방식 등을 결정합니다. 프로젝트 기반으로 과금을 하는 회사에서는 타임시트→과제→프로젝트로 이어지는 Many2One 체인이 매출 인식과 청구 흐름의 기반이 됩니다.
Many2One 필드 생성 및 커스터마이징 방법
Many2One 필드를 모델에 추가하는 방법은 기술적 상황에 따라 크게 세 가지가 있습니다.
Odoo Studio 사용(코드 없음)
Odoo Studio는 내장형 로우코드 도구입니다. 코드 작성 없이 Many2One을 추가하려면 다음 절차를 따릅니다:
- 메인 메뉴에서 Odoo Studio를 엽니다.
- 필드를 추가할 폼으로 이동합니다.
- 필드 선택기에서 Many2One을 끌어다 폼에 놓습니다.
- 속성 패널에서 대상 모델을 목록에서 선택합니다.
- 레이블과 선택 제한(도메인)을 설정해 사용자가 선택할 수 있는 레코드를 제어합니다.
- 저장하고 Studio를 닫습니다.
Studio는 자동으로 x_studio_ 접두사를 붙여 필드를 생성하고 폼에 추가합니다. 바로 사용 가능해서 기술 지식이 없는 사용자도 관계형 링크를 빠르게 확장할 수 있습니다.
커스텀 모듈에서 Python 사용
개발자가 모듈을 만들 때는 Python으로 Many2One을 정의합니다. 버전 관리와 여러 환경 배포가 필요한 경우 이 방식이 권장됩니다:
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.',
)
필드를 모델에 정의한 뒤에는 해당 뷰 XML에 포함시키고 모듈 업그레이드를 수행해 DB 변경을 적용합니다. 이 방법은 도메인, ondelete, compute 메서드나 제약 조건과의 통합을 세밀하게 제어할 수 있어 운영 환경의 표준 개발 방식입니다.
Many2One을 추가할 때는 관련 모델에 역방향 One2Many를 함께 만들어 두는 것이 실무적 관례입니다.
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',
)
XML-RPC API로 생성하기
원격 스크립트나 자동화 도구로 Odoo를 관리하는 경우 XML-RPC API를 통해 Many2One을 생성할 수도 있습니다:
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',
}]
)
여기서 relation은 대상 모델을 지정하고, on_delete는 삭제 동작을 설정합니다. API로 생성할 때에도 역방향 One2Many를 함께 만들어 두어 양방향 네비게이션이 가능하도록 하는 것이 필수 관행입니다.
모범 사례
1. 항상 역방향 One2Many도 함께 추가하세요
Many2One을 추가하면 관련 모델에 One2Many를 만들어 두는 것이 중요합니다. 추가 비용(컬럼 증가)은 없지만 사용자 관점에서 네비게이션이 훨씬 자연스러워집니다. 역방향 필드가 없으면 고객 화면에서 해당 고객과 연결된 주문이나 과제를 바로 확인할 수 없어 불편이 생깁니다.
2. 도메인 필터로 선택 범위를 제한하세요
res.partner를 가리키는 Many2One은 기본적으로 모든 파트너(벤더·고객·내부 사용자·배송지 등)를 보여줍니다. 비즈니스 로직이 고객에만 해당한다면 domain=[('customer_rank','>',0)]처럼 도메인을 설정해 드롭다운을 정리하면 사용자가 잘못 선택해 downstream 오류를 일으키는 일을 방지할 수 있습니다.
3. ondelete는 신중히 선택하세요
ondelete='cascade'는 연결된 대상 삭제 시 현재 레코드도 함께 삭제하기 때문에 의도치 않은 대량 삭제를 초래할 수 있습니다. 대부분의 경우는 'set null'가 안전한 선택이며, 특정 마스터 레코드가 절대 삭제되어선 안 되는 상황이라면 'restrict'를 사용하세요.
4. Many2One으로 해결할 수 있는 데이터를 중복 저장하지 마세요
초기 커스터마이징에서 회사명이나 카테고리 라벨을 단순 Char 필드로 넣어버리는 실수가 흔합니다. 이미 다른 모델에 레코드로 존재하는 값이라면 Many2One으로 연결하는 것이 맞습니다. 문자열 중복은 필터링·그룹화·이름 변경 시 일관성 문제를 일으킵니다.
5. context로 새 연결 레코드의 기본값을 전달하세요
Many2One의 context를 이용하면 드롭다운에서 새 레코드를 만들 때 자동으로 특정 필드를 채워 넣을 수 있습니다. 예를 들어 프로젝트 내 연락처를 만들 때 프로젝트 필드를 미리 채워 넣어 수동 입력을 줄이고 데이터 일관성을 높일 수 있습니다.
자주 발생하는 문제점
역방향 One2Many를 빼먹는 것
Many2One만 추가하고 역방향 One2Many를 만들지 않으면 관계가 단방향으로 남습니다. 현재 모델에서는 연결을 볼 수 있지만, 상대 모델에서는 어느 레코드들이 자신을 가리키는지 확인할 방법이 없습니다. 결국 사용자 불만이 생기고 누군가는 이를 보완하기 위해 별도 검색이나 리포트를 만들게 됩니다.
무분별한 cascade 삭제 사용
운영 레코드를 마스터 레코드에 연결하면서 ondelete='cascade'를 설정하면 큰 데이터 손실을 초래할 수 있습니다. 예컨대 카테고리를 삭제할 때 카테고리에 속한 모든 상품이 함께 삭제되는 상황을 피하려면 대부분 set null이나 restrict를 선택하는 것이 안전합니다.
Python에서 False 체크를 하지 않는 것
Many2One이 비어 있으면 Python에서는 빈 레코드셋(거짓값)이 반환됩니다. order.partner_id.name처럼 바로 접근하면 빈 문자열을 받는 경우가 많지만, order.partner_id.country_id.name처럼 연쇄적으로 접근할 경우 중간에 비어 있는 링크가 있으면 의도치 않은 빈 값이 최종 결과로 나오게 됩니다. 필드가 필수인지 확실하지 않다면 항상 존재 여부를 확인하세요.
잘못된 모델을 가리키는 것
res.partner는 고객·벤더·연락처·회사 정보를 모두 담고 있습니다. Many2One이 res.partner를 가리키되 도메인을 설정하지 않으면 내부 사용자나 배송지 등 불필요한 항목까지 보일 수 있습니다. 필드의 실제 목적에 맞는 도메인을 꼭 정의하세요.
Selection이 더 적절한 경우에 Many2One을 남발하는 것
값의 집합이 작고 고정적이라면 Selection 필드가 더 간단하고 성능 측면에서도 유리합니다. Many2One은 별도 모델과 레코드를 요구하고 쿼리 시 조인이 발생합니다. 상태값처럼 세네 가지 옵션만 있다면 Selection을 고려하세요. Many2One은 값의 수가 많거나 사용자가 관리해야 하거나 여러 모델에서 공유되는 경우에 적합합니다.
결론
Many2One은 Odoo 모듈들 간 데이터를 엮는 중심축입니다. 이 필드를 잘 이해하는 것은 개발자만의 숙제가 아니라 비즈니스 애널리스트, 기능 컨설턴트, Odoo Studio로 확장하려는 파워유저 모두에게 중요한 능력입니다.
기억할 핵심 사항: 항상 역방향 One2Many를 만들어 양방향 네비게이션을 확보하고, 도메인으로 드롭다운을 정리하며, ondelete 동작을 신중히 결정하고, 단순한 고정 목록에는 Selection 필드를 고려하세요.
Odoo Studio로 필드를 추가하든 Python 모듈을 작성하든 XML-RPC로 모델을 관리하든 관계형 필드를 처음부터 바르게 설계하면 구현은 더 신뢰할 수 있고 유지보수도 훨씬 쉬워집니다.
Dasolo에서는 기업들이 전사적으로 Odoo를 도입하고 최적화하도록 지원합니다. 데이터 모델 설계, 폼에 관계형 필드 추가, 혹은 모듈 전체를 처음부터 제작하는 작업까지 필요하시면 함께 도와드릴 수 있습니다. 문의하기 귀사의 Odoo 프로젝트에 대해 이야기해 보겠습니다.