Skip to content

Commit 9147275

Browse files
Built base things!
1 parent d55eead commit 9147275

File tree

4 files changed

+217
-0
lines changed

4 files changed

+217
-0
lines changed

api/migrations/0004_trigram_ext.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Generated by Django 5.2 on 2025-04-13 10:17
2+
from django.contrib.postgres.operations import TrigramExtension
3+
from django.db import migrations
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0003_rename_picture_url_profile_picture'),
10+
]
11+
12+
operations = [
13+
TrigramExtension(),
14+
]
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
# Generated by Django 5.2 on 2025-04-13 13:39
2+
3+
import django.db.models.deletion
4+
import taggit.managers
5+
from django.conf import settings
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('api', '0004_trigram_ext'),
13+
('taggit', '0006_rename_taggeditem_content_type_object_id_taggit_tagg_content_8fc721_idx'),
14+
]
15+
16+
operations = [
17+
migrations.CreateModel(
18+
name='Order',
19+
fields=[
20+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21+
('quantity', models.IntegerField()),
22+
('order_date', models.DateTimeField(auto_now_add=True)),
23+
],
24+
options={
25+
'verbose_name': 'Order',
26+
'verbose_name_plural': 'Orders',
27+
'ordering': ['-order_date'],
28+
},
29+
),
30+
migrations.CreateModel(
31+
name='OrderItem',
32+
fields=[
33+
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
34+
('quantity', models.PositiveSmallIntegerField()),
35+
],
36+
options={
37+
'verbose_name': 'Order Item',
38+
'verbose_name_plural': 'Order Items',
39+
'ordering': ['order'],
40+
},
41+
),
42+
migrations.AlterModelOptions(
43+
name='category',
44+
options={'ordering': ['name'], 'verbose_name': 'Category', 'verbose_name_plural': 'Categories'},
45+
),
46+
migrations.AlterModelOptions(
47+
name='product',
48+
options={'ordering': ['name'], 'verbose_name': 'Product', 'verbose_name_plural': 'Products'},
49+
),
50+
migrations.AlterModelOptions(
51+
name='profile',
52+
options={'ordering': ['user'], 'verbose_name': 'Profile', 'verbose_name_plural': 'Profiles'},
53+
),
54+
migrations.AddField(
55+
model_name='product',
56+
name='tags',
57+
field=taggit.managers.TaggableManager(help_text='A comma-separated list of tags.', through='taggit.TaggedItem', to='taggit.Tag', verbose_name='Tags'),
58+
),
59+
migrations.AddIndex(
60+
model_name='category',
61+
index=models.Index(fields=['name'], name='api_categor_name_53a3ad_idx'),
62+
),
63+
migrations.AddIndex(
64+
model_name='product',
65+
index=models.Index(fields=['slug'], name='api_product_slug_e70af7_idx'),
66+
),
67+
migrations.AddIndex(
68+
model_name='product',
69+
index=models.Index(fields=['name'], name='api_product_name_73c704_idx'),
70+
),
71+
migrations.AddIndex(
72+
model_name='product',
73+
index=models.Index(fields=['category'], name='api_product_categor_5c53c5_idx'),
74+
),
75+
migrations.AddIndex(
76+
model_name='profile',
77+
index=models.Index(fields=['user'], name='api_profile_user_id_bc09a5_idx'),
78+
),
79+
migrations.AddField(
80+
model_name='order',
81+
name='user',
82+
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
83+
),
84+
migrations.AddField(
85+
model_name='orderitem',
86+
name='order',
87+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='api.order'),
88+
),
89+
migrations.AddField(
90+
model_name='orderitem',
91+
name='product',
92+
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.product'),
93+
),
94+
migrations.AddIndex(
95+
model_name='order',
96+
index=models.Index(fields=['user'], name='api_order_user_id_764746_idx'),
97+
),
98+
migrations.AddIndex(
99+
model_name='order',
100+
index=models.Index(fields=['order_date'], name='api_order_order_d_f254aa_idx'),
101+
),
102+
migrations.AddIndex(
103+
model_name='orderitem',
104+
index=models.Index(fields=['order'], name='api_orderit_order_i_3f9c70_idx'),
105+
),
106+
migrations.AddIndex(
107+
model_name='orderitem',
108+
index=models.Index(fields=['product'], name='api_orderit_product_1a13b6_idx'),
109+
),
110+
]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Generated by Django 5.2 on 2025-04-13 13:46
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('api', '0005_order_orderitem_alter_category_options_and_more'),
10+
]
11+
12+
operations = [
13+
migrations.AddField(
14+
model_name='order',
15+
name='status',
16+
field=models.CharField(choices=[('PE', 'Pending'), ('CO', 'Completed'), ('CA', 'Cancelled')], default='PE', max_length=10),
17+
),
18+
]

api/recommender.py

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import redis
2+
from django.conf import settings
3+
4+
from api.models import Product
5+
6+
# connect to redis
7+
r = redis.Redis(
8+
host=settings.REDIS_HOST,
9+
port=settings.REDIS_PORT,
10+
db=settings.REDIS_DB
11+
)
12+
13+
14+
class Recommender:
15+
def get_product_key(self, id):
16+
return f'product:{id}:purchased_with'
17+
18+
def products_bought(self, products):
19+
product_ids = [p.id for p in products]
20+
for product_id in product_ids:
21+
for with_id in product_ids:
22+
# get the other products bought with each product
23+
if product_id != with_id:
24+
# increment score for product purchased together
25+
r.zincrby(
26+
self.get_product_key(product_id), 1, with_id
27+
)
28+
29+
def suggest_products_for(self, products, max_results=6):
30+
product_ids = [p.id for p in products]
31+
if len(products) == 1:
32+
# only 1 product
33+
suggestions = r.zrange(
34+
self.get_product_key(product_ids[0]), 0, -1, desc=True
35+
)[:max_results]
36+
else:
37+
# generate a temporary key
38+
flat_ids = ''.join([str(id) for id in product_ids])
39+
tmp_key = f'tmp_{flat_ids}'
40+
# multiple products, combine scores of all products
41+
# store the resulting sorted set in a temporary key
42+
keys = [self.get_product_key(id) for id in product_ids]
43+
r.zunionstore(tmp_key, keys)
44+
# remove ids for the products the recommendation is for
45+
r.zrem(tmp_key, *product_ids)
46+
# get the product ids by their score, descendant sort
47+
suggestions = r.zrange(
48+
tmp_key, 0, -1, desc=True
49+
)[:max_results]
50+
# remove the temporary key
51+
r.delete(tmp_key)
52+
suggested_products_ids = [int(id) for id in suggestions]
53+
# get suggested products and sort by order of appearance
54+
suggested_products = list(
55+
Product.objects.filter(id__in=suggested_products_ids)
56+
)
57+
suggested_products.sort(
58+
key=lambda x: suggested_products_ids.index(x.id)
59+
)
60+
return suggested_products
61+
#
62+
# def suggest_for_user(self, user, max_results=100):
63+
# """
64+
# Suggest products for the logged-in user based on their previous purchases.
65+
# """
66+
# # Get the products purchased by the user
67+
# purchased_products = Product.objects.filter(
68+
# order__user=user
69+
# ).distinct() # Assuming an `Order` model exists with a `user` field
70+
#
71+
# if not purchased_products.exists():
72+
# return [] # Return an empty list if no purchases are found
73+
#
74+
# # Use the existing recommendation logic
75+
# return self.suggest_products_for(purchased_products, max_results=max_results)

0 commit comments

Comments
 (0)