Is there a way to have scrapy fields to be serialized under a different name? For example field 'product_name' would become 'product name'.
class PropertyItem(Item):
product_name = Field()
then
l.add_xpath('product_name','//[#id="some_id"]/text()')
will be serialized as 'product_name': "some value", while i want it to be 'some other name': "some value"
Thanks
I'm not sure if I completely understand you but you can always use item pipelines to edit and change the items your spider returns.
For example, you can do something like this:
class FooPipeline(object):
def process_item(self, item, spider):
new_value = item['product_name'] + ' new name'
del item['product_name']
item['some other name'] = new_value
return item
By default scrapy.Item fields are static and only defined fields can be set. You can avoid this by overriding __setitem__() magic method:
class TestItem(scrapy.Item):
name = scrapy.Field()
def __setitem__(self, key, value):
self._values[key] = value
And result:
t = TestItem()
t['name2'] = 'one'
print(t)
>>> {'name2': 'one'}
# even though name2 is not defined
If you define additional fields in the __init__ method or your Item class, you are less restricted in choosing their names:
class TestItem(scrapy.Item):
name = scrapy.Field()
def __init__(self):
super().__init__()
self.fields["product name"] = scrapy.Field()
Related
In this image, there is a product search record that will search for name and default_code. I need to make it so that it will also look at my custom Many2Many field.
This is the field in the inherited model.
product_list = fields.Many2many("product.list", string="Product List")
The custom model only has _name, _description, and name variables.
The question is how to make the search to also look at all of the possible Many2Many data of this field.
I have tried this in the inherited model:
#api.model
def name_search(self, name='', args=None, operator='ilike', limit=100):
res = super(product_template_inherit, self).name_search(name='', args=None, operator='ilike', limit=100)
ids = self.search(args + [(name, 'in', 'product_list.name')], limit=limit)
if ids:
return ids.name_get()
return res
Nothing happens to the search. It still searches using the same behavior regardless of the code above.
Summary: I need to be able to search product by product list (custom Many2Many field inherited in the product.template model)
=============================================
UPDATE
Current code from what I have been trying is now this.
#api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
args = args or []
if operator == 'ilike' and not (name or '').strip():
domain = []
else:
domain = ['|', ('name', 'ilike', name), ('product_list.name', 'ilike', name)]
product_ids = self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
return self.browse(product_ids).name_get()
However, it looks like it still searches using the same old fields. It does not change to behave as my function is written.
You can compute the search domain then return the result of the _search method.
The fleet module already uses the same logic to search vehicles using the driver name, you have just to replace the driver_id with product_list:
class ProductProduct(models.Model):
_inherit = 'product.product'
#api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
args = args or []
if operator == 'ilike' and not (name or '').strip():
domain = []
else:
domain = ['|', ('name', operator, name), ('product_list.name', operator, name)]
return self._search(expression.AND([domain, args]), limit=limit, access_rights_uid=name_get_uid)
I would like to have my selections depend on the value of a Char field, for instance, a Char field defined as such:
my_char = fields.Char("Enter Something", readonly = False)
so I suppose the selection field should call a function, something like "_get_value"
my_selection = fields.Selection(selection = ' _get_value')
#api.model
def _get_value(self):
my_list = [('key1','value1')]
#no idea how to assign the value of my_char to value1
return my_list
Eventually, I would like to have the selections in the drop down list vary as the user input different strings in my_char.
Is this achievable in Odoo? Because if it's not, I should better start reorganizing my structure. Thanks a lot.
As far is i know, it isn't possible with field type Selection. But you can use a Many2one field for such a behaviour.
class MySelectionModel(model.Models):
_name = "my.selection.model"
name = fields.Char()
class MyModel(models.Model):
_name = "my.model"
my_char = fields.Char()
my_selection_id = fields.Many2one(
comodel_name="my.selection.model", string="My Selection")
#api.onchange("my_char")
def onchange_my_char(self):
return {'domain': {'my_selection_id': [('name', 'ilike', self.my_char)]}}
Or without a onchange method:
my_selection_id = fields.Many2one(
comodel_name="my.selection.model", string="My Selection",
domain="[('name', 'ilike', my_char)]")
To let the Many2one field look like a selection, add the widget="selection" on that field in the form view.
How the domain should look like, should be decided by you. Here it is just an example.
No need to write method here. Just declare the dictionary to a variable and call it in selection field.
VAR_LIST = [('a','ABC'),
('p','PQR'),
('x','XYZ')]
my_selection = fields.Selection(string="Field Name",VAR_LIST)
I've the following model, and the extend to the product_template
class Version(models.Model):
_name='product_cars_application.version'
name = fields.Char()
model_id = fields.Many2one('product_cars_application.model',string="Model")
brand_id = fields.Char(related='model_id.brand_id.name',store=True,readonly=1)
year_id = fields.Char(related='model_id.year_id.name',store=True,readonly=1)
from openerp.osv import osv,fields as Fields
class product_template(osv.osv):
_name = 'product.template'
_inherit = _name
_columns = {
'versions_ids':Fields.many2many('product_cars_application.version',string='Versions')
}
And the following controller which I need to filter products by version_id
#http.route('/pa/get_products/<version_id>', auth='none', type='json',website=True)
def get_products(self,version_id,**kwargs):
#TODO APPEND SECURITY
version_id = int(version_id)
products = http.request.env['product.template'].sudo().search([(version_id,'in','versions_ids')])
I get none products in return while the version_id is in versions_ids.
Do anyone knows what I'm doing wrong?
I need to make the value of comparison of the field a list, maybe becouse the field versions_ids is a many2many
I have solved like this:
#http.route('/pa/get_products/<version_id>', auth='none', type='json',website=True)
def get_products(self,version_id,**kwargs):
#TODO APPEND SECURITY
products = http.request.env['product.template'].sudo().search([('versions_ids','in',[version_id])])
list = []
for p in products:
list.append([p.id, p.name])
return {
'products':list,
}
"return products.ids" is missing inside get_products like:
#http.route('/pa/get_products/<version_id>', auth='none', type='json',website=True)
def get_products(self,version_id,**kwargs):
#TODO APPEND SECURITY
version_id = int(version_id)
products = http.request.env['product.template'].sudo().search([(version_id,'in','versions_ids')])
return products.ids
What is the correct way to nest Item data?
For example, I want the output of a product:
{
'price': price,
'title': title,
'meta': {
'url': url,
'added_on': added_on
}
I have scrapy.Item of:
class ProductItem(scrapy.Item):
url = scrapy.Field(output_processor=TakeFirst())
price = scrapy.Field(output_processor=TakeFirst())
title = scrapy.Field(output_processor=TakeFirst())
url = scrapy.Field(output_processor=TakeFirst())
added_on = scrapy.Field(output_processor=TakeFirst())
Now, the way I do it is just to reformat the whole item in the pipeline according to new item template:
class FormatedItem(scrapy.Item):
title = scrapy.Field()
price = scrapy.Field()
meta = scrapy.Field()
and in pipeline:
def process_item(self, item, spider):
formated_item = FormatedItem()
formated_item['title'] = item['title']
formated_item['price'] = item['price']
formated_item['meta'] = {
'url': item['url'],
'added_on': item['added_on']
}
return formated_item
Is this correct way to approach this or is there a more straight-forward way to approach this without breaking the philosophy of the framework?
UPDATE from comments: Looks like nested loaders is the updated approach. Another comment suggests this approach will cause errors during serialization.
Best way to approach this is by creating a main and a meta item class/loader.
from scrapy.item import Item, Field
from scrapy.contrib.loader import ItemLoader
from scrapy.contrib.loader.processor import TakeFirst
class MetaItem(Item):
url = Field()
added_on = Field()
class MainItem(Item):
price = Field()
title = Field()
meta = Field(serializer=MetaItem)
class MainItemLoader(ItemLoader):
default_item_class = MainItem
default_output_processor = TakeFirst()
class MetaItemLoader(ItemLoader):
default_item_class = MetaItem
default_output_processor = TakeFirst()
Sample usage:
from scrapy.spider import Spider
from qwerty.items import MainItemLoader, MetaItemLoader
from scrapy.selector import Selector
class DmozSpider(Spider):
name = "dmoz"
allowed_domains = ["example.com"]
start_urls = ["http://example.com"]
def parse(self, response):
mainloader = MainItemLoader(selector=Selector(response))
mainloader.add_value('title', 'test')
mainloader.add_value('price', 'price')
mainloader.add_value('meta', self.get_meta(response))
return mainloader.load_item()
def get_meta(self, response):
metaloader = MetaItemLoader(selector=Selector(response))
metaloader.add_value('url', response.url)
metaloader.add_value('added_on', 'now')
return metaloader.load_item()
After that, you can easily expand your items in the future by creating more "sub-items."
I think it would be more straightforward to construct the dictionary in the spider. Here are two different ways of doing it, both achieving the same result. The only possible dealbreaker here is that the processors apply on the item['meta'] field, not on the item['meta']['added_on'] and item['meta']['url'] fields.
def parse(self, response):
item = MyItem()
item['meta'] = {'added_on': response.css("a::text").extract()[0]}
item['meta']['url'] = response.xpath("//a/#href").extract()[0]
return item
Is there a specific reason for which you want to construct it that way instead of unpacking the meta field ?
This is my code in .py file.I want to fetch value of field list_price in product.product and use it in my custom module which inherits sale.order.
Can i store the value of list_price field in my custom field i.e qty_available?
When i print value of wg_qty_avail it shows None even list_price is having value 2000
class practice(osv.osv):
_inherit = 'sale.order'
_columns = {
'qty_available': fields.float('Quantity'),
}
def get_val(self, cr, uid, id, product, context=None):
result={}
wg_qty_avail = self.pool.get('product.product').browse(cr, uid,product,context=context).list_price
print "---------------------------", wg_qty_avail
result['qty_available'] = wg_qty_avail
practice()
xml file is ok..it calls the method get_val by a button click.
Please help.Where am i wrong..
You are not assigning the value to 'qty_available' field correctly
Remove result['qty_available'] = wg_qty_avail
return {'value': {'qty_available':wg_qty_avail}}
Hope this helps...