I want to know how to show a one2many computed field in a tree view, I tried the following code but without results:
#api.one
def _compute_o2m_field(self):
related_recordset = self.env["product.product"].search([])
self.productos = related_recordset
products = fields.One2many(string="products", compute='_compute_o2m_field')
any idea?,
thanks
#api.one
def _compute_o2m_field(self):
related_recordset = self.env["product.product"].search([])
self.products = related_recordset.ids
products = fields.One2many('product.product','many2onefieldonproduct',string="products", compute='_compute_o2m_field')
Computed Fields
There is no more direct creation of fields.function.
Instead you add a compute kwarg. The value is the name of the function as a string or a function. This allows to
have fields definition atop of class
Your compute function should be like this,
#api.multi
def _compute_o2m_field(self):
related_recordset = self.env["product.product"].search([])
for obj in self:
obj.products = related_recordset
One2many
Store a relation against many rows of co-model.
Specific options:
• comodel_name: name of the opposite model
• comodel_name: relational column of the opposite model
So, your field definition should be like this,
products = fields.One2many(comodel_name,comodel_name,string="products", compute='_compute_o2m_field')
Related
I'm trying to develop simple library app with Odoo but I get an error with manytomany relationship.
Here are classes:
class Book(models.Model):
_name = "library.book"
publisher_id = fields.Many2one(comodel_name="library.book.partner", string="Publisher")
author_ids = fields.Many2many(comodel_name="library.book.partner", relation='library_book_profile_partner_authors', column1='book_id', column2='partner_id', string="Authors")
class Partner(models.Model):
_name = "library.book.partner"
_inherit = "res.partner"
published_book_ids = fields.One2many("library.book", "publisher_id", string="Published Books")
book_ids = fields.Many2many("library.book", 'partner_books', 'book_id', 'partner_id', string="Authored Books")
This is the error I always get when installing the app
TypeError: Many2many fields library.book.partner.channel_ids and res.partner.channel_ids use the same table and columns
Someone can help to solve this please ?
It's the _inherit = "res.partner". Odoo is inheriting the model with a new model name which means it's creating a new table and copying all fields. For channel_ids it's trying to create the many2many "join" table, but with the same name as in res.partner. That happens because the table name is directly defined in the field (mail_channel_partner).
channel_ids = fields.Many2many('mail.channel', 'mail_channel_partner',
'partner_id', 'channel_id', string='Channels', copy=False)
So to solve the problem you have to "redefine" channel_ids on your new model again and change the table name, for example like:
channel_ids = fields.Many2many(relation='mail_channel_library_book_partner')
I have two models reservation(inherit from sale.order) and places . I want to make a one2many field in the places model so when a reservation(sale.order) is confirmed, the new customer who reserved the place is added to this field
here is my code
model reservation
from odoo import fields, models,api,_
from odoo.exceptions import Validation-error
class Customers2(models.Model):
_inherit='sale.order'
client=fields.Many2one('res.partner',string='client')
secure_place=fields.Many2one(comodel_name='product.template',string='Secure place')
guests=fields.Integer(string='guests')
hotel_reser=fields.Many2one('product.template')
start_date=fields.Datetime(string='start date')
end_date=fields.Datetime(string='end date')
reserv_price=fields.Monetary(string='Price')
currency_id = fields.Many2one(comodel_name='res.currency',string='Currency')
reserv_status = fields.Selection(
[('C', 'Confirmed'), ('D', 'Draft')],
string='Reservation type')
model places
from odoo import fields , models,api
class Place(models.Model):
_inherit='product.template'
hotel=fields.Selection([('H', 'Habit'),('Y','Yasmine'),('M','movenpick')],string='Hotel')
type_of_room=fields.Selection([('S', 'spa'),('M','meeting room'),('N','Nightclub')],string='Room')
reserv_persons=fields.One2many('sale.order','hotel_reser',string='clients reserved',compute='_compute_reservations')
To add the new customer who reserved the place in the reserv_persons field, the related model should be res.partner and its type many2many.
You want to use a computed field (the value is computed when needed), so you can search for sale orders in the sale state to show all customers who reserved the place
Example:
from odoo import api, fields, models, _, Command
class Place(models.Model):
_inherit = 'product.template'
reserv_persons = fields.Many2many('res.partner', string='clients reserved', compute='_compute_reservations')
def _compute_reservations(self):
sale_order = self.env['sale.order']
for tmpl in self:
tmpl.reserv_persons = [Command.link(so.client.id) for so in sale_order.search(
[('secure_place', '=', tmpl.id), ('state', '=', 'sale')])]
The Command used above is a special command to manipulate the relation of One2many and Many2many
i have two fields that should be related to res.partner
in partner_ids i want to choose partner and in recipients_ids i want to choose another partner that will get a copy of the document. the problem that in form view if i change partner_ids or recipient_ids both fields became the same. how can i do that i can choose different partners in those fields?
partners_ids = fields.Many2many('res.partner', string='Companys Names')
recipients_ids = fields.Many2many('res.partner', string='Copys for')
You are getting the error because the two field work on the same table in postgres
because odoo create a table for that name like this:
current_model_name_co_model_name_rel
in your case
your_model_res_partner_rel
so you need to tell odoo that every field has it's own relation
partners_ids = fields.Many2many('res.partner', # co_model
'your_model_partners_rel', # relation name change your_model to much your model name
string='Companys Names')
recipients_ids = fields.Many2many('res.partner',
'your_model_recipients_rel',
string='Copys for')
when you create m2m field it's better to specify this value by keyarguement
_name = 'my.model'
# exmple
user_ids = fields.Many2many(comodel_name='res.users', # name of the model
relation='my_model_users_rel', # name of relation in postgres
column1='session_id', # id reference to current mode
column2='user_id', # id reference to co_model
string='Allowed users')
I have 4 model like this
class Site(models.Model):
name = models.CharField(max_length=200)
def get_lowest_price(self, mm_date):
'''This method returns lowest product price on a site at a particular date'''
class Category(models.Model):
name = models.CharField(max_length=200)
site = models.ForeignKey(Site)
class Product(models.Model):
name = models.CharField(max_length=200)
category = models.ForeignKey(Category)
class Price(models.Model):
date = models.DateField()
price = models.IntegerField()
product = models.ForeignKey(Product)
Here every have many category, every category have many product. Now product price can change every day so price model will hold the product price and date.
My problem is I want list of site filter by price range. This price range will depends on the get_lowest_price method and can be sort Min to Max and Max to Min. Already I've used lambda expression to do that but I think it's not appropriate
sorted(Site.objects.all(), key=lambda x: x.get_lowest_price(the_date))
Also I can get all site within a price range by running a loop but this is also not a good idea. Please help my someone to do the query in right manner.
If you still need more clear view of the question please see the first comment from "Ishtiaque Khan", his assumption is 100% right.
*In these models writing frequency is low and reading frequency is high.
1. Using query
If you just wanna query using a specific date. Here is how:
q = Site.objects.filter(category__product__price__date=mm_date) \
.annotate(min_price=Min('category__product__price__price')) \
.filter(min_price__gte=min_price, min_price__lte=max_price)
It will return a list of Site with lowest price on mm_date fall within range of min_price - max_price. You can also query for multiple date using query like so:
q = Site.objects.values('name', 'category__product__price__date') \
.annotate(min_price=Min('category__product__price__price')) \
.filter(min_price__gte=min_price, min_price__lte=max_price)
2. Eager/pre-calculation, you can use post_save signal. Since the write frequency is low this will not be expensive
Create another Table to hold lowest prices per date. Like this:
class LowestPrice(models.Model):
date = models.DateField()
site = models.ForeignKey(Site)
lowest_price = models.IntegerField(default=0)
Use post_save signal to calculate and update this every time there. Sample code (not tested)
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=Price)
def update_price(sender, instance, **kwargs):
cur_price = LowestPrice.objects.filter(site=instance.product.category.site, date=instance.date).first()
if not cur_price:
new_price = LowestPrice()
new_price.site = instance.product.category.site
new_price.date = instance.date
else:
new_price = cur_price
# update price only if needed
if instance.price<new_price.lowest_price:
new_price.lowest_price = instance.price
new_price.save()
Then just query directly from this table when needed:
LowestPrice.objects.filter(date=mm_date, lowest_price__gte=min_price, lowest_price__lte=max_price)
Solution:
from django.db.models import Min
Site.objects.annotate(
price_min=Min('categories__products__prices__price')
).filter(
categories__products__prices__date=the_date,
).distinct().order_by('price_min') # prefix '-' for descending order
For this to work, you need to modify the models by adding a related_name attribute to the ForeignKey fields.
Like this -
class Category(models.Model):
# rest of the fields
site = models.ForeignKey(Site, related_name='categories')
Similary, for Product and Price models, add related_name as products and prices in the ForeignKey fields.
Explanation:
Starting with related_name, it describes the reverse relation from one model to another.
After the reverse relationship is setup, you can use them to inner join the tables.
You can use the reverse relationships to get the price of a product of a category on a site and annotate the min price, filtered by the_date. I have used the annotated value to order by min price of the product, in ascending order. You can use '-' as a prefix character to do in descending order.
Do it with django queryset operations
Price.objects.all().order_by('price') #add [0] for only the first object
or
Price.objects.all().order_by('-price') #add [0] for only the first object
or
Price.objects.filter(date= ... ).order_by('price') #add [0] for only the first object
or
Price.objects.filter(date= ... ).order_by('-price') #add [0] for only the first object
or
Price.objects.filter(date= ... , price__gte=lower_limit, price__lte=upper_limit ).order_by('price') #add [0] for only the first object
or
Price.objects.filter(date= ... , price__gte=lower_limit, price__lte=upper_limit ).order_by('-price') #add [0] for only the first object
I think this ORM query could do the job ...
from django.db.models import Min
sites = Site.objects.annotate(price_min= Min('category__product__price'))
.filter(category__product__price=mm_date).unique().order_by('price_min')
or /and for reversing the order :
sites = Site.objects.annotate(price_min= Min('category__product__price'))
.filter(category__product__price=mm_date).unique().order_by('-price_min')
In 'products.template' table I created three fields width,length and gsm. Now I want to retrieve it in 'mrp' table.First I will get the ids from mrp bill of materials and assign it to a variable called prod. 'mrp.bom.line' table contains product id.So through iterator I want to pass id of product stored in mrp bill of materials table to retrieve the value of width,length and gsm stored in product.template table.I am getting error as programming error can't adapt type 'product.product'.
#api.multi
def _compute_rim_weight(self):
bill_of_materials_id=[1,2,3]
prod = self.env['mrp.bom.line'].browse(bill_of_materials_id)
for i in prod:
j = self.env['product.template'].browse(i.product_id)
self.rim_weight = (j.width * j.length * j.gsm)/20000
return self.rim_weight
In ODOO browse take id not object
so just replace browse(i.product_id) with browse(i.product_id.id) as below:
j = self.env['product.template'].browse(i.product_id.id)
One more thing if in case product.template have** many2one relation** with model :mrp.bom.line from my understanding you even don't need the call browse .
Directly call line.product_id.width ,line.product_id.length,line.product_id.gsm as below :
#api.multi
def _compute_rim_weight(self):
bill_of_materials_ids=[1,2,3]
bom_lines = self.env['mrp.bom.line'].browse(bill_of_materials_ids)
for line in bom_lines:
self.rim_weight = (line.product_id.width * line.product_id.length * line.product_id.gsm)/20000
return self.rim_weight
#api.one
def _compute_rim_weight(self):
rim_weight =0
for line in self.bom_id.bom_line_ids:
rim_weight+ = (line.product_id.width * line.product_id.length * line.product_id.gsm)/20000
self.rim_weight =rim_weight