Odoo domain for Many2Many relation - odoo

Here is code:
class Car(models.Model):
_name = 'car'
parking_id = fields.Many2one('cars')
class Parking(models.Model):
_name = 'parking'
cars_ids = fields.One2many('cars', 'parking_id')
class Group(models.Model):
_name = 'group'
parking_id = fields.Many2one('parking')
cars_ids = fields.Many2many('cars', lambda self: [('parking_id', '=', self.parking_id)])
What i want is to limit records of cars when i picking it in group form by parking on that cars are.
But my code does not work. What the mistake ?

If you define the domain in python code it will not be changed after the user
select the parking_id you need to update the domain every time the user
changes the parking_id:
class Group(models.Model):
_name = 'group'
parking_id = fields.Many2one('parking')
# it's always better to define m2m field with full properties
# cars_ids = fields.Many2many('cars','group_cars_rel', 'group_id', 'car_id', 'List of cars' )
cars_ids = fields.Many2many('cars')
#api.onchange('parking_id')
def onchange_parking(self):
"""change the domain when user change parking_id"""
# you may need to empty the many2many field if the user change the parking
# if not just remove this line
self.cars_ids = [(5, 0, 0)] # remove all record from many2many
if self.parking_id:
return {'domain': {'cars_ids': [('parking_id', '=', self.parking_id.id)]}}
else:
# remove domain
return {'domain': {'cars_ids': []}}

It seems your syntax is wrong for domain, change as below:
class Group(models.Model):
_name = 'group'
parking_id = fields.Many2one('parking')
cars_ids = fields.Many2many('cars', domain=[('parking_id', '=', parking_id)])

Related

How write a Django REST APIView POST request with a condition?

I am trying to create a post request for a game api. The game implies that a user can label a picture. A label entered once is a tagging, a label entered twice for the same resource is a tag.
This is how I am trying to create a Tagging so far:
saved_tagging = Tagging.objects.create(user_id=current_user_id,
gameround=gameround,
resource=random_resource,
tag='tag newwww',
created=datetime.now(),
score=score,
origin=origin
)
tagging_serializer = TaggingSerializer(saved_tagging)
At the moment I am getting the ValueError: Cannot assign "'tag newwww'": "Tagging.tag" must be a "Tag" instance.
Is there any way that I can avoid this?
Here are also my models and the relevant serializer.
models.py
class Tag(models.Model):
name = models.CharField(max_length=256)
language = models.CharField(max_length=256)
objects = models.Manager()
def create(self, validated_data):
tag_data = validated_data.pop('tag')
Tag.objects.create(**tag_data)
return tag_data
def __str__(self):
return self.name or ''
class Tagging(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.SET_NULL, null=True)
gameround = models.ForeignKey(Gameround, on_delete=models.CASCADE, related_name='taggings')
resource = models.ForeignKey(Resource, on_delete=models.CASCADE, related_name='taggings')
tag = models.ForeignKey(Tag, on_delete=models.CASCADE, related_name='tagging')
created = models.DateTimeField(editable=False)
score = models.PositiveIntegerField(default=0)
origin = models.URLField(max_length=256, blank=True, default='')
objects = models.Manager()
def create(self, validated_data):
tag_data = validated_data.pop('tag')
tagging = Tagging.objects.create(**validated_data)
Tag.objects.create(name=tagging, **tag_data)
return tagging
def __str__(self):
return str(self.tag) or ''
serializers.py
class TaggingSerializer(serializers.ModelSerializer):
tag = StringRelatedField()
resource = ResourceSerializer(read_only=True)
gameround = GameroundSerializer(read_only=True)
class Meta:
model = Tagging
fields = ('id', 'tag', 'gameround', 'created', 'score', 'resource', 'origin')
def create(self, validated_data):
return Tagging.objects.create(**validated_data)
def to_representation(self, data):
data = super().to_representation(data)
return data
tag must be a Tag Instance !
So... you can do this in two ways (in my opinion).
First you can create a Tag object in your view and than pass this object to tag value in your Tagging create method.
Or create a service layer on your app, and create a custom create method for your model. This in my opinion is superior because your a centralizing your rules in one method instead of in one view.
Ex.:
services/tag_service.py
def create(user_id,gameround,resource,tag,...origin):
if not isinstance(tag, Tag):
#create your tag model based on the String passed
your_new_tag_object = Tag.objects.create(tag=tag, ...)
# Here your create others rules too for model creation
# Return your model .create method
return Tagging.objects.create(user_id=user_id,...tag=your_new_tag_object,...)
And Than use this new create method inside your POST serializer.
from services import tag_service
class TaggingSerializer(serializers.ModelSerializer):
# your normal serializer here
def create(self, validated_data):
return tag_service.create(**validated_data)

Domain on Many2one field

I need applay domain on field product_id in purchase.order.line model, after call onchange method in purchase.order model.
Example:
_inherit = "purchase.order"
custom_id = fields.Many2one('custom.model', string='Custom')
#api.onchange('custom_id')
def change_product(self):
lst = [1,2,3]
return {'domain': {'order_id.product_id': [('id', 'in', lst)]}}
This is original field where I want add my domain
product_id = fields.Many2one('product.product', string='Product',domain=[('purchase_ok', '=', True)], change_default=True, required=True)
I don't get any error but in product_id field show all data from database, not product where id 1,2,3 from above example.
write onchange function for product_id, instead of custom_id and return domain.
#api.onchange('product_id')
def change_product(self):
lst = [1,2,3]
return {'domain': {'product_id': [('id', 'in', lst)]}}
inside the onchange function consider the custom_id.
You have a little problem here: you can't set a domain for child relations IIRC.
What you can do is to create a related field on the child relation (here purchase.order.line and trigger the onchange on that.
class PurchaseOrder(models.Model):
_inherit = "purchase.order"
custom_id = fields.Many2one(
comodel_name='custom.model', string='Custom')
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
custom_id = fields.Many2one(
comodel_name="custom.model", related="order_id.custom_id")
#api.onchange('custom_id')
def onchange_custom_id(self):
lst = [1,2,3]
return {'domain': {'product_id': [('id', 'in', lst)]}}
And some more information. Let me presume you have set custom_id on product variants (a) or on product categories (b). Just change the domain of product_id and use the domain operator '=?'. You won't need an onchange method for this solution, but you have to define custom_id in the view (invisible is possible, this is also a must have for the complete first solution)!
(a)
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
custom_id = fields.Many2one(
comodel_name="custom.model", related="order_id.custom_id")
product_id = fields.Many2one(
domain="[('purchase_ok', '=', True), ('custom_id', '=?', custom_id)]")
(b)
class PurchaseOrderLine(models.Model):
_inherit = "purchase.order.line"
custom_id = fields.Many2one(
comodel_name="custom.model", related="order_id.custom_id")
product_id = fields.Many2one(
domain="[('purchase_ok', '=', True), ('categ_id.custom_id', '=?', custom_id)]")
If no custom_id is set the domain will read: product has to be purchasable AND True.

Odoo, can't have two different siblings objects in the same view

I have a class named detail_base, and two other classes name flight_detail and tour_detail, the last two classes inherits from the first one, like this:
class DetailBase(models.Model):
_name = 'detail_base'
fee = fields.Monetary('Fee')
passenger = fields.Char('Passenger')
class FlightDetail(models.Model):
_name = 'flight_detail'
_inherits = 'detail_base'
passport = fields.Char('Passport')
class TourDetail(models.Model):
_name = 'tour_detail'
_inherits = 'detail_base'
age = fields.Integer('Tourist Age')
The problem is when I call the flight_detail and tour_detail in the same view, the browser can't handle the common attributes of both classes, If I assign 5 to tour_detail.fee, that number will be stored into flight_detail.fee.
It seems the problem is related to the attributes with the same name of different objects being siblings.
I will appreciate any help.
You should either use _inherit
class DetailBase(models.AbstractModel):
_name = 'detail_base'
fee = fields.Monetary('Fee')
passenger = fields.Char('Passenger')
class FlightDetail(models.Model):
_name = 'flight_detail'
_inherit = 'detail_base'
passport = fields.Char('Passport')
class TourDetail(models.Model):
_name = 'tour_detail'
_inherit = 'detail_base'
age = fields.Integer('Tourist Age')
which should either create 3 database tables (detail_base as Model) or 2 database tables (AbstractModel).
Or you use _inherits like:
class DetailBase(models.Model):
_name = 'detail_base'
fee = fields.Monetary('Fee')
passenger = fields.Char('Passenger')
class FlightDetail(models.Model):
_name = 'flight_detail'
_inherits = {'detail_base': 'base_id'}
passport = fields.Char('Passport')
base_id = fields.Many2one('detail_base', required=True, ondelete='cascade')
class TourDetail(models.Model):
_name = 'tour_detail'
_inherit = {'detail_base': 'base_id'}
age = fields.Integer('Tourist Age')
base_id = fields.Many2one('detail_base', required=True, ondelete='cascade')
That will create 3 tables, and fee and passenger will be stored in detail_base table. Odoo will get it from there, because it is a sort of delegation inheritence.
use inherit:
1- inherit without _name :
inherit = 'model.name'
new_field = fields...
this add this field to the model.name
2- inherit with _name:
inherit = 'model.name'
_name = 'new.model'
here will create a new tabel in database with the same structure of model.name.
inherits: The delegation inheritance.
best example is res.users and res.partners user is a partener so when we create a res.users record we must create a res.partener that hold commun field like name, email, address ... and information related to users like passowrd and login are stored in res.users model and with type of inheritence you can access field of res.partener directly without having to create a related field. you can do user_record.name or .email or .address this will not be a problem.
i like to think of it as one2one relation.
_inherits = {model.name : many2one_field_id }
_name = 'new.model'
# m2o field should be required and ondelete = cascade
many2one_field_id = fields.Many2one('model.name', string='Label', required=True, ondelete="cascade")
so when you create a record of new.model all field that are in model.name will be stored in model.name.

Inherited model many2one picking a wrong field value

I am trying to reference form_serial_no field of formdownload model in my inherited model(plot.allocate). Instead of the dropdown to show the computed value of form_serial_no which is company_short_code padded with sequence e.g CCK0000006, it's showing "CCK" which is the value of company_short_code excluding the sequence. In actual fact, it's referencing company_short_code of companyname model instead of the computed value of form_serial_no of formdownload model. And in the database, form_serial_no field is showing the right record which is something like these......ABC0000008, CDG0000003 and so on. This is my question, is there any reason why form_serial_no_id Many2one field of plot.allocate model is not picking the value of form_serial_no Char field of formdownload, instead, it's picking the company_short_code Char field of companyname model?
Secondly, how can i change the status field default value of companyname model record to true when a value is picked in form_serial_no_id Many2one field of plot.allocate model?
Kindly help me look into these.
Below are my code snippet
# -*- coding: utf-8 -*-
from openerp import models, fields, api
class CompanyName(models.Model):
_name = 'companyname'
_rec_name = 'company_short_code'
company_name = fields.Char(string="Company Name", required=True)
company_short_code = fields.Char(string="Company short code", required=True)
class FormDownload(models.Model):
_name = 'formdownload'
_rec_name = 'form_serial_no'
name = fields.Many2one('companyname', string="Company Name", ondelete='cascade',
required=True)
form_serial_no = fields.Char(string="Form Serial No", readonly=True)
status = fields.Boolean(string="Status", default=False)
#api.model
def create(self, vals):
serial_no = self.env['ir.sequence'].get('formdownload.form_serial_no')
code = self.env['companyname'].browse(vals['name']).company_short_code
# capitalize company short code values
code = code.upper()
# merge code and serial number
vals['form_serial_no'] = code + serial_no
return super(FormDownload, self).create(vals)
class PlotAllocation(models.Model):
_inherit = 'plot.allocate'
form_serial_no_id = fields.Many2one('formdownload',
domain=[('status', '=', False)],
ondelete='cascade', string="Form Serial No")
#api.model
def create(self, vals):
record = self.env['formdownload'].search([('name', 'like', vals.get('name', False))]).form_serial_no
status = self.env['companyname'].search([('name', 'like', vals.get('name', False))]).status
if vals.get('form_serial_no_id') == record.id:
self.env['companyname'].write({status : True})
return super(PlotAllocation, self).create(vals)
in the view the many2one is always showing the value of the field specified in the Model class by _rec_name :
by default _rec_name = 'name'
so in reality your model is like this :
class FormDownload(models.Model):
_name = 'formdownload'
_rec_name = 'name'
so he will retreive the value of field name name is also a many2one so the value retrieved is the _rec_name of model companyname with is company_short_code:
so i you want to change the value of many2one you have to choice:
if one field is enough than specify the _rec_name = 'form_serial_no'
if you want to combined value like first && last name then override name_get method
#api.multi
def name_get(self):
"""
change the displayed value on m2o
"""
result = []
for record in self:
result.append((record.id, _(" %s %s") % (record.first_name, record.last_name)))
return result
EDITS
in create method is easy just remove all the code in create method and put this two line
rec_id = super(PlotAllocation, self).create(vals)
# create return a record you can acces it's value
rec_id.form_serial_no_id.name.status = True
return rec_id # you need to return the record

How to modify many to many field in wizard?

I have model contains many to many assign_to_id, I want to modify that field in wizard form through escalte_to method when user trigger escalate button
Model:
class Incident(models.Model):
_name = 'itmangement.incident'
assigned_to_id = fields.Many2one('hr.employee', string="Assigned To",domain="[('department_id', '=', dep_id)]",required=True)
Wizard model
class Escalate(models.TransientModel):
escalated_to = fields.Many2one('hr.employee', string="Escalated To", required=True)
#api.one
def escalate(self):
incident_obj = self.env['itmangement.incident']
record = incident_obj.browse(self._context.get('active_ids'))
record.write({'state': 'escalated'})
class Escalate(models.TransientModel):
escalated_to = fields.Many2one('hr.employee', string="Escalated To", required=True)
#api.one
def escalate(self):
object_id = self.env.context.get('active_id')
for object in self.env['itmangement.incident'].browse(object_id) and self.escalated_to:
object.assigned_to_id = self.escalated_to.id