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')
Related
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 am uncertain regarding what child_of does after reading the docs and the use of child_of operator over using in operator for filtering
Odoo v10 domain docs can be found here which states the following for child_of
is a child (descendant) of a value record.
Takes the semantics of the model into account (i.e following the relationship field named by _parent_name)
Consider the model product.product having a many2one field called pos_categ_id that points to model pos.category
To only get products with PoS category id of x, domain value of ['pos_categ_id', 'child_of', x] can be used, however ['pos_categ_id', 'in', [x]] seems to also do the same thing.
In addition to that, in operator can be used for many2many and one2many fields with same format as above example while use of child_of operator for those two field types cause errors.
Example the domain ['session_ids', 'child_of', [572]] where session_ids is a many2many field causes the following error Invalid field 'parent_id' in leaf \"<osv.ExtendedLeaf: ('parent_id', 'in', [572]) on pos_session (ctx: )>\"
So, what is the example case where child_of is preferred over in operator? Both seems to function the same except in can work on all types of relational fields. Second, what does the second line of docs for child_of mean by the phrase following the relationship field named by _parent_name?
No they are not the same, yes they are the same in the simple example that you used:
To only get products with PoS category id of x, domain value of
['pos_categ_id', 'child_of', x] can be used, however ['pos_categ_id',
'in', [x]] seems to also do the same thing.
But consider this case:
# x
# / \
# f b
# / \
# c d
when we do this ['pos_categ_id', 'child_of', x] this will return product that their category is one of this list [x, f, b, c, d] it's equivalent to ['pos_categ_id', 'in', [x, f, b, c, d]].
because they are all child of x (child_of includes x itself).
but this: ['pos_categ_id', 'in', [x]] will return only product that their category is X but will not return product with category b or c ..etc.
To activate this feature in your custom model you should set this attribute
class YourModel(models.Model):
_name = 'some.model'
_parent_name = 'parent_id' # by default its name is parent_id you can change it
_parent_store = True # tell odoo that this model support parent & child relation ship
# same name of _parent_name
parent_id = fields.Many2one('some.model', 'Parent')
Basically child_of functionality is provided by odoo, not python.
But to understand it you must need to knowledge of parent, 'child` relationship in database concept.
If any of your tables are using a self join like below example.
_name = 'my.table'
parent_id = fields.Many2one('my.table', string='Parent Id')
In the above case, your model has connected itself. In this type of cases in Odoo, you can use child_of operator in the domain.
So, it will search in database like parent_id = <your_value>.
For in domain,
You can pass the list of data which you need to search in id field. It similar to any database in operator.
class Partner(models.Model):
_name = 'res.partner'
_parent_name = 'parent_partner_id' # the many2one field used as parent
_parent_store = True
parent_partner_id = fields.Many2one('res.partner', store=True)
parent_path = fields.Char(index=True)
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')
I'm making a module for reservations in Odoo 9 and one field of my model is populated based if it's reserved or no. Basically my model is:
class Reservation(models.Model):
....
room_id = fields.Many2one('reservation.room', string="Room")
I've defined an onchange function that return a domain to filter the room_ids that aren't reserved:
#api.onchange('date')
def _set_available_room(self):
.....
return {'domain': {'room_id': [('id', 'in', res)]}}
This works fine and when I set the date, the rooms are filtered ok. My problem is when I save a reservation and enter again to edit it. The room_id field show all values and only when I change the date the room_id is filtered.
I've tried using the domain attribute in the field definition like this, but it doesn't works:
room_id = fields.Many2one('reservation.room', string="Room", domain=lambda self: self._get_available_slots())
How can I filter this field on the load view using my function than search for available rooms?
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')