Automatic related products on product page based on its categories - opencart

I want to display products related to the one being display on product/product page based on this products categories, instead of relying only on the manually linked related products. This is a great time saver specially on stores with a lot of different products in catalog. I found some paid extensions, some with really great features, but I need something simple and don´t want to mess too much with my core code as my store is already highly modified.

On catalog/controller/product/product.php, after line ~394:
$results = $this->model_catalog_product->getProductRelated($this->request->get['product_id']);
Insert this chunk of code:
########## INSERT RELATED PRODUCTS BASED ON PRODUCT CATEGORIES ===== BEGIN ##########
//create an array to insert all new related products found on queries
$all_related_products_from_categories = [];
//set total quantity of related products - note that the manual related products
//set on product admin page will be displayed first, so the total amount of products can be higher than this
$total_products_to_show = 20;
//find this products categories
$product_categories = $this->model_catalog_product->getCategories($data['product_id']);
//find each category´s products
foreach($product_categories as $pc){
//get category´s products
$related_products_from_category = $this->model_catalog_product->getProducts(['filter_category_id' => $pc['category_id']]);
//insrt into array
foreach($related_products_from_category as $rpc){
$all_related_products_from_categories[] = $rpc;
}
}
//if the quantity of products found before is lower than the desired amount of related products, get random products to add
if(count($all_related_products_from_categories) < $total_products_to_show){
//find random products (needs improvement - right now it is only getting the first products - need to find a way to be really random)
$random_products = $this->model_catalog_product->getProducts(['start' => 1, 'limit' => $total_products_to_show-count($all_related_products_from_categories)]);
//merge with products from categories
$auto_related_products = array_merge($all_related_products_from_categories, $random_products);
} else {
//if the desired amount is already met, just keep going
$auto_related_products = $all_related_products_from_categories;
}
$count = 0;
//foreach product found on previous queries
foreach($auto_related_products as $arp){
//break loop if reaches the desired amount
if($count > $total_products_to_show){ break; }
//check if product has stock and it is not the same as product on page
if($arp['product_id'] == $this->request->get['product_id'] || $arp['quantity'] < 1){ continue; }
//insert in $results array, which will be used at the related
//products section of the page alongside the manually defined related products of this product
$results[$arp['product_id']] = $arp;
$count++;
}
########## INSERT RELATED PRODUCTS BASED ON PRODUCT CATEGORIES ===== END ##########
Tested on OC 3.0.3.3 and 3.0.3.7.
I know it needs a lot of improvements, please give any suggestions you have.

Related

I want to query 3 Random Products from the Database everytime only 3

y = (Product.objects.all()).count()
x = random.randint(3,y)
prouct1 = Product.objects.get(id = x+1)
prouct2 = Product.objects.get(id = x-1)
prouct3 = Product.objects.get(id = x-2)
I want to obtain 3 random products everytime i run a function initially i use the above method But the problem with this is If i delete a object Then i get the error i dont want that thing to happen obviously i can run more checks but i feel there is some simple way to do that The objective is to Get the random object from the database every time the page is renderd
Work with OFFSET … LIMIT … instead:
qs = Product.objects.all()
n = qs.count()
x0, x1, x2 = random.sample(range(n), 3)
product0 = qs[x0]
product1 = qs[x1]
product2 = qs[x2]
this requires that there are at least three items in the Product table, and will make four queries: one for the count and three for the individual products. It will also guarantee that the three products are distinct.
If the table is not that large, you can sample from the list of primary keys and then fetch three items:
pks = Product.objects.values_list('pk', flat=True)
product0, product1, product2 = Product.objects.filter(pk__in=random.sample(pks, 3))
This will make two queries: one to load all primary keys, and another to fetch the three products in bulk. This is more extensible if the number of products you want to fetch increases since fetching ten products for example, will still require two queries.
Simply you can fetch random product by using .order_by('?'):
ran_prod = Product.objects.all().order_by('?')[:3]
Note :
order_by('?') queries may be expensive and slow, depending on
the database backend you’re using.

Django - creating and saving multiple object in a loop, with ForeignKeys

I am having trouble creating and saving objects in Django. I am very new to Django so I'm sure I'm missing something very obvious!
I am building a price comparison app, and I have a Search model:
Search - all searches carried out, recording best price, worst price, product searched for, time of search etc. I have successfully managed to save these searches to a DB and am happy with this model.
The two new models I am working with are:
Result - this is intended to record all search results returned, for each search carried out. I.e. Seller 1 £100, Seller 2 £200, Seller 3, £300. (One search has many search results).
'Agent' - a simple table of Agents that I compare prices at. (One Agent can have many search Results).
class Agent(models.Model):
agent_id = models.AutoField(primary_key=True)
agent_name = models.CharField(max_length=30)
class Result(models.Model):
search_id = models.ForeignKey(Search, on_delete=models.CASCADE) # Foreign Key of Search table
agent_id = models.ForeignKey(Agent, on_delete=models.CASCADE) # Foreign Key of Agent table
price = models.FloatField()
search_position = models.IntegerField().
My code that is creating and saving the objects is here:
def update_search_table(listed, product):
if len(listed) > 0:
search = Search(product=product,
no_of_agents=len(listed),
valid_search=1,
best_price=listed[0]['cost'],
worst_price=listed[-1]['cost'])
search.save()
for i in range(len(listed)):
agent = Agent.objects.get(agent_name = listed[i]['company'])
# print(agent.agent_id) # Prints expected value
# print(search.search_id) # Prints expected value
# print(listed[i]['cost']) # Prints expected value
# print(i + 1) # Prints expected value
result = Result(search_id = search,
agent_id = agent,
price = listed[i]['cost'],
position = i + 1)
search.result_set.add(result)
agent.result_set.add(result)
result.save()
Up to search.save() is working as expected.
The first line of the for loop is also correctly retrieving the relevant Agent.
The rest of it is going wrong (i.e. not saving any Result objects to the Result table). What I want to achieve is, if there are 10 different agent results returned, create 10 Result objects and save each one. Link each of those 10 objects to the Search that triggered the results, and link each of those 10 objects to the relevant Agent.
Have tried quite a few iterations but not sure where I'm going wrong.
Thanks

Retrieving random records from database depending on an attribute value until a limit, and complete with other if the limit is not reached

I'm using Django with Postgres.
On a page I can show a list of featured items, let's say 10.
If in the database I have more featured items than 10, I want to get them random/(better rotate).
If the number of featured item is lower than 10, get all featured item and add to the list until 10 non-featured items.
Because the random takes more time on database, I do the sampling in python:
count = Item.objects.filter(is_featured=True).count()
if count >= 10:
item = random.sample(list(Item.objects.filter(is_featured=True))[:10])
else:
item = list(Item.objects.all()[:10])
The code above miss the case where there less than 10 featured(for example 8, to add 2 non-featured).
I can try to add a new query, but I don't know if this is an efficient retrive, using 4-5 queries for this.
The best solution I could find is this:
from itertools import chain
items = list(chain(Item.objects.filter(is_featured=True).order_by('?'), Item.objects.filter(is_featured=False).order_by('?')))[:10]
In this way, the order of the querysets are retained, but downside is that items becomes a list not a Queryset. You can see more details in this SO Answer. FYI: there are some fantastic solutions like using Q or pipe but they don't retain order of queryset.
SQL method: You can achieve that with an SQL statement like this:
SELECT uuid_generate_v4(), *
FROM table_name
ORDER BY NOT is_featured, uuid_generate_v4()
LIMIT 10;
Explain: The generated UUID should simulate randomness (for the purpose of e-commerce, this should suffice). While sorting the rows by NOT is_featured will put the is_featured rows on top; and automatically flow the rows down to 10 limits if it run out of featured items.

how to display different price for same product in opencart?

Want to display different prices for a same product. Multiple sellers will be selling the same product with their respective prices is what I want to display if someone views a product....
If I understand you, It's not a coding case.
1 - go to Admin/Sales/Customers/Customers Group, and create as many groups as you want.
2 - go to Admin/Catalog/Products and edit an exiting product or create a new product. in Special tab you can assign different prices for each group created on step 1.
My first thought is to make them separate products, however you might want to display a product on a single page with a list of sellers to choose from, in which case...
The different sellers are product options!
The way I have set this up is by adding fields for them in the SQL products table such as price_a, price_b, price_c and then adding another field to the customers table called price_category with the relevant prefix (A,B,C). Then I wrote a function under getProduct (catalog/model/catalog/product.php) to cater for this.
The reason I took this route is because my files are uploaded automatically to the table and links to another program which generates invoices and sends the result back to the website automatically.
My Function is as follows:
if ($query->rows) {
foreach ($query1->rows as $row) {
$price_category = strtolower($row['price_category']);
$debtor_class = $row['debtor_class'];
$price_percentage = $row['price_percentage'];
}
} else {
$price = ($query->row['discount'] ? $query->row['discount'] : $query->row['price']);
$special = $query->row['special'];
}
$product_special_query = $this->db->query("SELECT price, to_qty, bonus_qty FROM product_special WHERE debtor_class = '".$debtor_class."' AND product_id = '".(int)$product_id."' AND customer_group_id = '".(int)$customer_group_id. "'");

Computed property that keeps a running total of values in ArrayController's model

I have an array controller holding a list of models which includes a numeric amount (i.e a dollar value).
Initially the model contains 1 row with an amount of 0, as the user adds rows to the model and changes the amounts I want to keep a running total at the bottom of the list.
I'm about 4 hours into working with Ember and I don't quite understand how to iterate over the model in a computed property and return the sum and most importantly, have it recalculate as I add values to the model.
What I'm trying to use is:
totalLoanAmount: function() {
var t = 0;
var amounts = this.get('content.#each.amount').toArray();
for (index = 0; index < amounts.length; index++) {
t += amounts[index];
}
return t;
}.property('content.#each.amount'),
But this doesn't seem to update as I add items to the model.
The dependent-key part looks correct to me. But I don't believe you can use #each to map properties in the way you're attempting on the third line of your code. Edit: OK, it turns out you can do this though I can't find any documentation indicating that this is recommended or expected.
The quick fix, I believe, would be to use var amounts = this.get('content').mapBy('amount').
But you might also look into refactoring this function using Ember's reduce functionality as demonstrated here.