Skip to content

Commit d55eead

Browse files
Built base things!
1 parent bd480d8 commit d55eead

File tree

13 files changed

+390
-19
lines changed

13 files changed

+390
-19
lines changed

.env

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
DB_NAME=eCommerce
2+
DB_USER=eCommerce
3+
DB_PASSWORD=0592876798
4+
DB_HOST=localhost
5+
REDIS_HOST=localhost
6+
REDIS_PORT=6379

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
.venv
2-
.idea
2+
.idea
3+
Notes

README.md

218 Bytes

api/Pagination.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
from rest_framework.pagination import PageNumberPagination
2+
3+
4+
class CustomPageNumberPagination(PageNumberPagination):
5+
page_size = 10
6+
page_size_query_param = 'page_size'
7+
max_page_size = 100
8+
page_query_param = 'page'

api/admin.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,34 @@
11
from django.contrib import admin
22

3-
# Register your models here.
3+
from .models import User, Profile, Category, Product
4+
5+
6+
@admin.register(User)
7+
class UserAdmin(admin.ModelAdmin):
8+
list_display = ('username', 'email', 'first_name', 'last_name', 'is_staff', 'is_active')
9+
list_filter = ('is_staff', 'is_active', 'date_joined')
10+
search_fields = ('username', 'email', 'first_name', 'last_name')
11+
ordering = ('date_joined',)
12+
13+
14+
@admin.register(Profile)
15+
class ProfileAdmin(admin.ModelAdmin):
16+
list_display = ('user', 'location', 'birth_date')
17+
search_fields = ('user__username', 'location')
18+
list_filter = ('birth_date',)
19+
20+
21+
@admin.register(Category)
22+
class CategoryAdmin(admin.ModelAdmin):
23+
list_display = ('name', 'slug')
24+
search_fields = ('name',)
25+
prepopulated_fields = {'slug': ('name',)}
26+
27+
28+
@admin.register(Product)
29+
class ProductAdmin(admin.ModelAdmin):
30+
list_display = ('name', 'price', 'stock', 'category', 'slug')
31+
list_filter = ('category', 'tags', 'price', 'stock')
32+
search_fields = ('name', 'description', 'category__name', 'tags__name')
33+
prepopulated_fields = {'slug': ('name',)}
34+
autocomplete_fields = ('category',)

api/filters.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from django.contrib.postgres.search import TrigramSimilarity
2+
from django_filters import FilterSet
3+
from rest_framework.filters import BaseFilterBackend
4+
5+
from api.models import Product
6+
7+
8+
class ProductFilter(FilterSet):
9+
"""
10+
FilterSet for Product model.
11+
This filter allows filtering products based on various fields such as
12+
name, description, price, stock, category, and tags.
13+
"""
14+
15+
class Meta:
16+
model = Product
17+
fields = {
18+
'name': ['exact', 'icontains'],
19+
'description': ['icontains'],
20+
'price': ['exact', 'lt', 'gt', 'range'],
21+
'stock': ['exact', 'lt', 'gt'],
22+
'category__name': ['exact', 'icontains'],
23+
'tags__name': ['exact', 'icontains'],
24+
}
25+
26+
27+
class InStockFilterBackend(BaseFilterBackend):
28+
"""
29+
Custom filter to only show products that are in stock.
30+
"""
31+
32+
def filter_queryset(self, request, queryset, view):
33+
return queryset.filter(stock__gt=0)
34+
35+
36+
class ProductSearchFilterBackend(BaseFilterBackend):
37+
"""
38+
Custom filter to search products by name and description.
39+
"""
40+
41+
def filter_queryset(self, request, queryset, view):
42+
search = request.query_params.get('search', None)
43+
if search:
44+
queryset.annotate(
45+
similarity=TrigramSimilarity('name', search),
46+
).filter(similarity__gt=0.1).order_by('-similarity')
47+
return queryset

api/models.py

Lines changed: 94 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ class Profile(models.Model):
1919
birth_date = models.DateField(null=True, blank=True)
2020
picture = models.ImageField(upload_to=profile_upload_to_unique, null=True, blank=True)
2121

22+
class Meta:
23+
verbose_name = "Profile"
24+
verbose_name_plural = "Profiles"
25+
indexes = [
26+
models.Index(fields=['user']),
27+
]
28+
ordering = ['user']
29+
2230
def __str__(self):
2331
return f'{self.user.username} Profile'
2432

@@ -27,8 +35,16 @@ class Category(models.Model):
2735
name = models.CharField(max_length=255)
2836
slug = models.SlugField(max_length=255, unique=True)
2937

38+
class Meta:
39+
verbose_name = "Category"
40+
verbose_name_plural = "Categories"
41+
indexes = [
42+
models.Index(fields=['name']),
43+
]
44+
ordering = ["name"]
45+
3046
def __str__(self):
31-
return self.name
47+
return f"Category: {self.name}"
3248

3349

3450
class Product(models.Model):
@@ -43,20 +59,90 @@ class Product(models.Model):
4359
blank=True,
4460
upload_to=product_upload_to_unique
4561
)
46-
4762
category = models.ForeignKey(
4863
Category,
4964
related_name="products",
5065
on_delete=models.CASCADE,
51-
5266
)
53-
54-
objects = models.Manager()
55-
tags = TaggableManager
67+
tags = TaggableManager()
68+
69+
class Meta:
70+
verbose_name = "Product"
71+
verbose_name_plural = "Products"
72+
indexes = [
73+
models.Index(fields=['slug']),
74+
models.Index(fields=['name']),
75+
models.Index(fields=['category']),
76+
]
77+
ordering = ["name"]
5678

5779
def save(self, *args, **kwargs):
58-
self.slug = slugify(self.name)
80+
# Auto-generate slug if not set or name changes
81+
if not self.slug or self.name != self.__class__.objects.get(pk=self.pk).name:
82+
self.slug = slugify(self.name)
5983
super().save(*args, **kwargs)
6084

6185
def __str__(self):
62-
return self.name
86+
return f"Product: {self.name} (ID: {self.product_id})"
87+
88+
89+
class Order(models.Model):
90+
class Status(models.TextChoices):
91+
PENDING = 'PE', 'Pending'
92+
COMPLETED = 'CO', 'Completed'
93+
CANCELLED = 'CA', 'Cancelled'
94+
95+
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
96+
quantity = models.IntegerField()
97+
order_date = models.DateTimeField(auto_now_add=True)
98+
status = models.CharField(max_length=10, choices=Status.choices, default=Status.PENDING)
99+
100+
class Meta:
101+
verbose_name = "Order"
102+
verbose_name_plural = "Orders"
103+
indexes = [
104+
models.Index(fields=['user']),
105+
models.Index(fields=['order_date']),
106+
]
107+
ordering = ["-order_date"]
108+
109+
@property
110+
def total_price(self):
111+
"""
112+
Calculate the total price of the order.
113+
114+
Returns:
115+
Decimal: The total price, which is the sum of all order items' prices.
116+
"""
117+
return sum(item.price for item in self.items.all())
118+
119+
def __str__(self):
120+
return f"Order: {self.id} by {self.user.username if self.user else 'Deleted User'}"
121+
122+
123+
class OrderItem(models.Model):
124+
order = models.ForeignKey(Order, related_name='items', on_delete=models.CASCADE)
125+
product = models.ForeignKey(Product, on_delete=models.CASCADE)
126+
quantity = models.PositiveSmallIntegerField()
127+
128+
class Meta:
129+
verbose_name = "Order Item"
130+
verbose_name_plural = "Order Items"
131+
indexes = [
132+
models.Index(fields=['order']),
133+
models.Index(fields=['product']),
134+
]
135+
ordering = ["order"]
136+
137+
@property
138+
def price(self):
139+
"""
140+
Calculate the total price for the order item.
141+
142+
Returns:
143+
Decimal: The total price, which is the product of the item's price and quantity.
144+
"""
145+
return self.product.price * self.quantity
146+
147+
def __str__(self):
148+
return f"Order Item: {self.product.name} (Order ID: {self.order.id})"

api/serializers.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from rest_framework import serializers
22

3-
from .models import Category, Product, User, Profile
3+
from .models import Category, Product, User, Profile, Order, OrderItem
44

55

66
class UserSerializer(serializers.ModelSerializer):
@@ -51,8 +51,7 @@ def update(self, instance, validated_data):
5151
# Custom update method to handle tags
5252
tags = validated_data.pop('tags', None) # Extract tags from validated data
5353
if tags:
54-
instance.tags.clear() # Clear existing tags
55-
instance.tags.set(*tags) # Assign new tags
54+
instance.tags.set(*tags) # Replace existing tags with new ones
5655
else:
5756
instance.tags.clear() # Clear tags if none are provided
5857

@@ -71,3 +70,19 @@ class Meta:
7170
'category',
7271
'tags'
7372
]
73+
74+
75+
class OrderSerializer(serializers.ModelSerializer):
76+
class OrderItemSerializer(serializers.ModelSerializer):
77+
product = ProductSerializer(many=False, read_only=True)
78+
79+
class Meta:
80+
model = OrderItem
81+
fields = ['product', 'quantity', 'price']
82+
83+
order_items = OrderItemSerializer(many=True, read_only=True)
84+
85+
class Meta:
86+
model = Order
87+
fields = ['user', 'quantity', 'order_date', 'order_items', 'total_price']
88+
read_only_fields = ['user', 'order_date']

api/signals.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
from django.db.models.signals import post_save, post_delete
2+
from django.dispatch import receiver
3+
4+
from api.models import Category
5+
6+
7+
@receiver([post_save, post_delete], sender=Category)
8+
def invalidate_category_cache(sender, instance, **kwargs):
9+
"""
10+
Invalidate the cache for the category list when a category is created or deleted.
11+
"""
12+
from django.core.cache import cache
13+
14+
# Clear the cache for the category list
15+
cache.delete_pattern('*category_list*')

0 commit comments

Comments
 (0)