I found a module that autocomplete search in a Website e-commerce with high-light match words and image. But I did not really understand what each command do .
Can you please explain to me how this code work, and why they did /shop/get_suggest?
class WebsiteSale(http.Controller):
#http.route(['/shop/get_suggest'], type='http', auth="public", methods=['GET'], website=True)
def get_suggest_json(self, **kw):
query = kw.get('query')
names = query.split(' ')
domain = ['|' for k in range(len(names) - 1)] + [('name', 'ilike', name) for name in names]
products = request.env['product.template'].search(domain, limit=15)
products = sorted(products, key=lambda x: SequenceMatcher(None, query.lower(), x.name.lower()).ratio(),
reverse=True)
results = []
for product in products:
results.append({'value': product.name, 'data': {'id': product.id, 'after_selected': product.name}})
return json.dumps({
'query': 'Unit',
'suggestions': results
})
This controller function will be activated when you load page your_domain/shop/get_suggest.
The function just searches for products with similar name of the query given in the search.
Please go through this documentation to learn basics of building a website
Related
hi there
i need to scrap bestbuy i am currently using scrapy i was able to get most of the data i need but however i had faced some problems trying to get the
specification data section where UPC is. i was able to get features but that part i am not
able to grab the data.
really appreciate your help this is my code
from scrapy import Spider
from bestbuy_spider.items import BestbuyProductItem
from scrapy import Request
import re
import json
class Bestbuy2Spider(Spider):
name = 'bestbuy2'
# allowed_domains = ['https://www.bestbuy.com']
allowed_domains = ['bestbuy.com']
# https://www.bestbuy.com/site/searchpage.jsp?cp=1&searchType=search&browsedCategory=pcmcat209400050001&ks=960&sp=-bestsellingsort%20skuidsaas&sc=Global&list=y&usc=All%20Categories&type=page&id=pcat17071&iht=n&nrp=15&seeAll=&st=categoryid%24pcmcat209400050001&qp=carrier_facet%3DCarrier~Verizon
# start_urls = ['https://www.bestbuy.com/site/laptop-computers/all-laptops/pcmcat138500050001.c?id=pcmcat138500050001']
start_urls = ['https://www.bestbuy.com/site/searchpage.jsp?id=pcat17071&qp=storepickupstores_facet%3DStore%20Availability%20-%20In%20Store%20Pickup~237&st=%2A']
def parse(self, response):
text = response.xpath('//div[#class="left-side"]/span/text()').extract_first()
_, items_page, total = tuple(map(lambda x: int(x), re.findall('\d+',text)))
num_pages = total // items_page
#print('number of pages:', num_pages)
urls = [
'https://www.bestbuy.com/site/searchpage.jsp?cp={}&id=pcat17071&qp=storepickupstores_facet%3DStore%20Availability%20-%20In%20Store%20Pickup~237&st=%2A'.format(
x) for x in range(1, num_pages + 1)]
for url in urls[:1]:
# product list page
yield Request(url=url, callback=self.parse_product_list)
def parse_product_list(self, response):
# product list
rows = response.xpath('//ol[#class="sku-item-list"]/li')
# print(len(rows))
# print('=' * 50)
for row in rows:
url = row.xpath('.//div[#class="sku-title"]/h4/a/#href').extract_first()
print(url)
yield Request(url='https://www.bestbuy.com' + str(url), callback=self.parse_product)
#'//ul[#Class="thumbnail-list"]//#src'
def parse_product(self, response):
price_txt = response.xpath('//div[#class="pricing-price__regular-price"]/text()').extract_first()
#reg_price = price_txt.replace('Was ', '')
item = BestbuyProductItem(
product = response.xpath('//div[#class="sku-title"]/h1/text()').extract_first(),
#color = response.xpath('li[#class="image selected"]/div/a/#title').extract_first(),
#skuId = response.xpath('//div[#class="sku product-data"]/span[2]/text()').extract_first(),
#price = response.xpath('//div[#class="priceView-hero-price priceView-customer-price"]/span[1]/text()').extract_first(),
#model = response.xpath('//div[#class="model product-data"]/span[2]/text()').extract_first(),
#main_image = response.xpath('//img[#class="primary-image"]/#src').extract_first(),
#images = response.xpath('//*[#class="thumbnail-list"]//img/#src').extract(),
#description = response.xpath('//div[#class="long-description-container body-copy "]//div/text()').extract(),
#features = response.xpath('//div[#class="list-row"]/p/text()').extract(),
#regular_price = price_txt,
Location = response.xpath('//div[#class="fulfillment-fulfillment-summary"]//div/p[1]/span/text()').extract()
)
yield item
Looking at one product page code (https://www.bestbuy.com/site/sony-65-class-bravia-xr-x95j-4k-uhd-smart-google-tv/6459306.p?skuId=6459306) i notice there's a json with the gtin13 field (the upc code you're looking for). Should be easy to parse it with json module and get what you need.
{
"#context":"http://schema.org/",
"#type":"Product",
"name":"Sony - 65\" class BRAVIA XR X95J 4K UHD Smart Google TV",
"image":"https://pisces.bbystatic.com/image2/BestBuy_US/images/products/6459/6459306_sd.jpg",
"url":"https://www.bestbuy.com/site/sony-65-class-bravia-xr-x95j-4k-uhd-smart-google-tv/6459306.p?skuId=6459306",
"description":"Shop Sony 65\" class BRAVIA XR X95J 4K UHD Smart Google TV at Best Buy. Find low everyday prices and buy online for delivery or in-store pick-up. Price Match Guarantee.",
"sku":"6459306",
"gtin13":"0027242921818",
"model":"XR65X95J",
"width":{
"#type":"http://schema.org/QuantitativeValue",
"unitCode":"INH",
"value":"56.87"
},
"color":"Black",
"brand":{
"#type":"Brand",
"name":"Sony"
},
"aggregateRating":{
"#type":"AggregateRating",
"ratingValue":"4.7",
"reviewCount":"221"
},
"offers":{
"#type":"AggregateOffer",
"priceCurrency":"USD",
"seller":{
"#type":"Organization",
"name":"Best Buy"
},
"lowPrice":"1184.99",
"highPrice":"1499.99",
"offercount":5,
"offers":[
{
"#type":"Offer",
"priceCurrency":"USD",
"price":"1499.99",
"availability":"http://schema.org/InStock",
"itemCondition":"http://schema.org/NewCondition",
"description":"New"
},
{
"#type":"Offer",
"priceCurrency":"USD",
"price":"1319.99",
"itemCondition":"http://schema.org/UsedCondition",
"description":"Open-Box Excellent - Certified"
},
{
"#type":"Offer",
"priceCurrency":"USD",
"price":"1274.99",
"itemCondition":"http://schema.org/UsedCondition",
"description":"Open-Box Excellent"
},
{
"#type":"Offer",
"priceCurrency":"USD",
"price":"1229.99",
"itemCondition":"http://schema.org/UsedCondition",
"description":"Open-Box Satisfactory"
},
{
"#type":"Offer",
"priceCurrency":"USD",
"price":"1184.99",
"itemCondition":"http://schema.org/UsedCondition",
"description":"Open-Box Fair"
}
]
}
}
I want to get a product's location and display it on a custom report table:
and on the "Warehouse" cell it should be all the product's location, so if that product has multiple it should be displayed there. Just for clarification this is the location I'm talking about:
In order to put that there I tried this code:
class StockInventoryValuationReport(models.TransientModel):
_name = 'report.stock.inventory.valuation.report'
_description = 'Stock Inventory Valuation Report'
location_id = fields.Many2one('stock.location')
# filter domain wizard
#api.multi
def _compute_results(self):
self.ensure_one()
stockquant_obj = self.env['stock.quant'].search([("location_id", "=", self.location_id.id)])
print(stockquant_obj.location_id)
line = {
'name': product.name,
'reference': product.default_code,
'barcode': product.barcode,
'qty_at_date': product.qty_at_date,
'uom_id': product.uom_id,
'currency_id': product.currency_id,
'cost_currency_id': product.cost_currency_id,
'standard_price': standard_price,
'stock_value': product.qty_at_date * standard_price,
'cost_method': product.cost_method,
'taxes_id': product.taxes_id,
'location_id': stockquant_obj.location_id,
}
if product.qty_at_date != 0:
self.results += ReportLine.new(line)
but when I'm printing stockquant_obj.location_id it is an empty recordset basically its not finding any locations. Can someone please hint me on anything?
I actually managed to get the product's locations using this code:
class StockInventoryValuationReport(models.TransientModel):
_name = 'report.stock.inventory.valuation.report'
_description = 'Stock Inventory Valuation Report'
location_id = fields.Many2one('stock.location')
# filter domain wizard
#api.multi
def _compute_results(self):
self.ensure_one()
stockquant_obj = self.env['stock.quant'].search([("location_id", "=", self.location_id.id)])
for xyz in stockquant_obj:
line = {
'name': product.name,
'reference': product.default_code,
'barcode': product.barcode,
'qty_at_date': product.qty_at_date,
'uom_id': product.uom_id,
'currency_id': product.currency_id,
'cost_currency_id': product.cost_currency_id,
'standard_price': standard_price,
'stock_value': product.qty_at_date * standard_price,
'cost_method': product.cost_method,
'taxes_id': product.taxes_id,
'location_id': xyz.location_id,
}
if product.qty_at_date != 0:
self.results += ReportLine.new(line)
I debugged further discovering that now stock.quant() could get some record-set but odoo was expecting a singleton when on my old code was stockquant_obj.location_id so since I have seen from other people that the solution to singleton is a for loop and for that reason I added it.
The problem with this is that now not only the warehouse would be added but the same product would repeat as times as long the recordset is. How can I dodge this? How to tell python that I only need to loop through stockquant_obj and xyz should be inside the line variable?
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)
Partner ledger report name is 'account.report_partnerledger.pdf' by default. I want to change it to customer name (eg: john.pdf if customer name is john). How to do this?
Install report_custom_filename.
Go to Settings > Actions > Reports and search for Partner Ledger.
Fill in the Download filename field. This field is evaluated as jinja2 template with objects being a list of browse records of the records to print, and o the first record. If your model contains a name field, you might write something like ${o.name}_report.pdf as filename.
Possible through complex coding but thanks to odoo community we have one module named
report_custom_filename
which will let you do this by little configuration
Install 'report_custom_filename' module and make the following changes in report_routes method
def report_routes(self, reportname, docids=None, converter=None, **data):
cr, uid, context,registry = request.cr, request.uid, request.context,request.registry
response = super(ReportController, self).report_routes(
reportname, docids=docids, converter=converter, **data)
if docids:
docids = [int(i) for i in docids.split(',')]
report_xml = http.request.session.model('ir.actions.report.xml')
report_ids = report_xml.search(
[('report_name', '=', reportname)])
options_data = simplejson.loads(data['options'])
partner_id = options_data.get('ids')
for report in report_xml.browse(report_ids):
if not report.download_filename:
continue
#objects = http.request.session.model(report.model).browse(docids or [])
objects = request.registry[report.model].browse(cr, uid, partner_id, context=context)
customer_name = str(objects.name)
generated_filename = email_template.mako_template_env\
.from_string(report.download_filename)\
.render({
'objects': objects,
'o': customer_name,
'object': objects[:1],
'ext': report.report_type.replace('qweb-', ''),
})
response.headers['Content-Disposition'] = content_disposition(
generated_filename)
return response
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 ?