diff --git a/.idea/03-commerce.iml b/.idea/03-commerce.iml
index f602895..c6d616b 100644
--- a/.idea/03-commerce.iml
+++ b/.idea/03-commerce.iml
@@ -16,7 +16,7 @@
-
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 0c95c56..5876997 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,4 @@
-
+
\ No newline at end of file
diff --git a/account/authorization.py b/account/authorization.py
index 661ada1..83841aa 100644
--- a/account/authorization.py
+++ b/account/authorization.py
@@ -9,6 +9,7 @@
TIME_DELTA = timedelta(days=120)
+
class GlobalAuth(HttpBearer):
def authenticate(self, request, token):
try:
@@ -24,4 +25,3 @@ def get_tokens_for_user(user):
return {
'access': str(token),
}
-
diff --git a/account/migrations/0002_alter_user_options.py b/account/migrations/0002_alter_user_options.py
new file mode 100644
index 0000000..9163097
--- /dev/null
+++ b/account/migrations/0002_alter_user_options.py
@@ -0,0 +1,17 @@
+# Generated by Django 3.2.8 on 2021-11-06 20:02
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='user',
+ options={'verbose_name': 'user', 'verbose_name_plural': 'users'},
+ ),
+ ]
diff --git a/account/schemas.py b/account/schemas.py
index 681457b..609550f 100644
--- a/account/schemas.py
+++ b/account/schemas.py
@@ -22,13 +22,16 @@ class AccountOut(Schema):
company_name: str = None
company_website: str = None
+
class TokenOut(Schema):
access: str
+
class AuthOut(Schema):
token: TokenOut
account: AccountOut
+
class SigninSchema(Schema):
email: EmailStr
password: str
@@ -48,3 +51,5 @@ class ChangePasswordSchema(Schema):
old_password: str
new_password1: str
new_password2: str
+
+
diff --git a/commerce/controllers.py b/commerce/controllers.py
index a8a551a..3bde334 100644
--- a/commerce/controllers.py
+++ b/commerce/controllers.py
@@ -2,15 +2,16 @@
import string
from typing import List
-from django.contrib.auth import get_user_model
from django.db.models import Q
from django.shortcuts import get_object_or_404
from ninja import Router
from pydantic import UUID4
+from django.contrib.auth import get_user_model
from account.authorization import GlobalAuth
-from commerce.models import Product, Category, City, Vendor, Item, Order, OrderStatus
-from commerce.schemas import ProductOut, CitiesOut, CitySchema, VendorOut, ItemOut, ItemSchema, ItemCreate
+from commerce.models import Product, Category, City, Vendor, Item, Order, OrderStatus, Address
+from commerce.schemas import ProductOut, CitiesOut, CitySchema, VendorOut, ItemOut, ItemSchema, ItemCreate, \
+ AddressSchema, AddressCreate, AddressOut, CheckoutSchema
from config.utils.schemas import MessageOut
products_controller = Router(tags=['products'])
@@ -20,6 +21,7 @@
User = get_user_model()
+
@vendor_controller.get('', response=List[VendorOut])
def list_vendors(request):
return Vendor.objects.all()
@@ -87,7 +89,7 @@ def list_products(
for p in products:
print(p)
-
+
for every product, we retrieve (Merchant, Label, Category, Vendor) records
Merchant.objects.get(id=p.merchant_id) (select * from merchant where id = 'p.merchant_id')
@@ -114,16 +116,113 @@ def list_products(
"""
-@address_controller.get('')
+# address CRUD operations endpoints are here!
+# All addresses require authentication because they're sensitive data
+# ----
+@address_controller.get('', auth=GlobalAuth(), response={
+ 200: List[AddressSchema],
+ 404: MessageOut,
+ 401: MessageOut
+})
def list_addresses(request):
- pass
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+ # gets all the addresses for the given user
+ user_addresses = Address.objects.filter(user=user)
+ return user_addresses
+
+
+@address_controller.get('/{id}', auth=GlobalAuth(), response={
+ 200: AddressOut,
+ 404: MessageOut,
+ 401: MessageOut
+})
+def retrieve_address(request, id: UUID4):
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ user = User.objects.filter(id=request.auth['pk'])[0]
+ address = Address.objects.filter(id=id, user=user)
+
+ # checks if the address belongs to the user or not
+ if not address:
+ return 401, {'detail': 'unauthorized'}
+
+ # only returns address if it's the user's AND if it's a valid token
+ return 200, address[0]
+
+
+@address_controller.post('', auth=GlobalAuth(), response={
+ 201: AddressOut,
+ 400: MessageOut,
+ 401: MessageOut
+})
+def create_address(request, address_in: AddressCreate):
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+ address = Address(**address_in.dict(), user=user)
+ address.save()
+ return 201, address
+
+
+@address_controller.put('/{id}', auth=GlobalAuth(), response={
+ 200: MessageOut,
+ 400: MessageOut,
+ 401: MessageOut
+})
+def update_address(request, id: UUID4, address_in: AddressCreate):
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+
+ address = Address.objects.filter(id=id, user=user)
+ # if the user doesn't own this address, return 401
+ if not address:
+ return 401, {'detail': 'unauthorized'}
+
+ updated = address.update(**address_in.dict())
+ if updated:
+ return 200, {"detail": "updated successfully"}
+ return 400, {"detail": "No address found"}
+
+
+@address_controller.delete("/{id}", auth=GlobalAuth(), response={
+ 204: MessageOut,
+ 404: MessageOut,
+ 401: MessageOut
+})
+def delete_address(request, id: UUID4):
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+
+ address = Address.objects.filter(id=id, user=user)
+ # if the user doesn't own this address, return 401
+ if not address:
+ return 401, {'detail': 'unauthorized'}
+
+ address.delete()
+ return 204, {"detail": ""}
+
+
+# ----
# @products_controller.get('categories', response=List[CategoryOut])
# def list_categories(request):
# return Category.objects.all()
-
+# cities endpoints are here (Everyone can add, remove, and update cities.)
@address_controller.get('cities', response={
200: List[CitiesOut],
404: MessageOut
@@ -175,12 +274,23 @@ def delete_city(request, id: UUID4):
return 204, {'detail': ''}
-@order_controller.get('cart', response={
+# ---------------------
+# cart, order and checkout endpoints are here.
+
+@order_controller.get('cart', auth=GlobalAuth(), response={
200: List[ItemOut],
- 404: MessageOut
+ 404: MessageOut,
+ 401: MessageOut
})
def view_cart(request):
- cart_items = Item.objects.filter(user=User.objects.first(), ordered=False)
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+
+ # gets the cart for the given user
+ cart_items = Item.objects.filter(user=user, ordered=False)
if cart_items:
return cart_items
@@ -188,26 +298,43 @@ def view_cart(request):
return 404, {'detail': 'Your cart is empty, go shop like crazy!'}
-@order_controller.post('add-to-cart', response={
+@order_controller.post('add-to-cart', auth=GlobalAuth(), response={
200: MessageOut,
+ 401: MessageOut
# 400: MessageOut
})
def add_update_cart(request, item_in: ItemCreate):
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+
try:
- item = Item.objects.get(product_id=item_in.product_id, user=User.objects.first())
- item.item_qty += 1
+ # if the item already exists then add the qty amount to the current amount
+ item = Item.objects.get(product_id=item_in.product_id, user=user)
+ item.item_qty += item_in.item_qty
item.save()
except Item.DoesNotExist:
- Item.objects.create(**item_in.dict(), user=User.objects.first())
+ # if it doesn't exist just create it with the desired amount
+ Item.objects.create(**item_in.dict(), user=user)
return 200, {'detail': 'Added to cart successfully'}
-@order_controller.post('item/{id}/reduce-quantity', response={
+@order_controller.post('item/{id}/reduce-quantity', auth=GlobalAuth(), response={
200: MessageOut,
+ 401: MessageOut
})
def reduce_item_quantity(request, id: UUID4):
- item = get_object_or_404(Item, id=id, user=User.objects.first())
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+
+ # if its the users item it will reduce the quantity, else, it will return 404
+ item = get_object_or_404(Item, id=id, user=user)
if item.item_qty <= 1:
item.delete()
return 200, {'detail': 'Item deleted!'}
@@ -217,21 +344,50 @@ def reduce_item_quantity(request, id: UUID4):
return 200, {'detail': 'Item quantity reduced successfully!'}
-@order_controller.delete('item/{id}', response={
- 204: MessageOut
+@order_controller.delete('item/{id}', auth=GlobalAuth(), response={
+ 204: MessageOut,
+ 401: MessageOut
})
def delete_item(request, id: UUID4):
- item = get_object_or_404(Item, id=id, user=User.objects.first())
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+
+ # it will delete the item only if the item id and the product id are valid with the same item, else, 404
+ item = get_object_or_404(Item, id=id, user=user)
item.delete()
return 204, {'detail': 'Item deleted!'}
+@order_controller.put("item/{id}/increase-quantity", auth=GlobalAuth(), response={
+ 200: MessageOut,
+ 404: MessageOut
+})
+def increase_quantity(request, id: UUID4):
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+
+ item = get_object_or_404(Item, id=id, user=user)
+ item.item_qty += 1
+ item.save()
+ return 200, {"detail": f"Item increased from {item.item_qty - 1} to {item.item_qty} successfully."}
+
+
def generate_ref_code():
return ''.join(random.sample(string.ascii_letters + string.digits, 6))
-@order_controller.post('create-order', auth=GlobalAuth(), response=MessageOut)
+@order_controller.post('create-order', auth=GlobalAuth(), response={
+ 201: MessageOut,
+ 400: MessageOut,
+ 403: MessageOut,
+ 401: MessageOut
+})
def create_order(request):
'''
* add items and mark (ordered) field as True
@@ -239,19 +395,79 @@ def create_order(request):
* add NEW status
* calculate the total
'''
+ # checks if the token is valid or exists
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
- order_qs = Order.objects.create(
- user=User.objects.first(),
- status=OrderStatus.objects.get(is_default=True),
- ref_code=generate_ref_code(),
- ordered=False,
- )
-
- user_items = Item.objects.filter(user=User.objects.first()).filter(ordered=False)
+ # First, checks whether there are items in the cart (items that are not ordered)
+ user_items = Item.objects.filter(user=user, ordered=False)
+ if not user_items:
+ return 400, {'detail': 'Can not create an order of an empty cart.'}
- order_qs.items.add(*user_items)
- order_qs.total = order_qs.order_total
+ '''
+ Secondly, checks if there are no already active order.
+ If True, make a new active order
+ Else, add the items then merge the duplicates
+ '''
+ order = Order.objects.filter(user=user, ordered=False)
+ if not order:
+ # New order
+ # Create an order query set and we'll initially use 4 out of 8 attributes in an order
+ order_qs = Order.objects.create(
+ user=user,
+ status=OrderStatus.objects.get(is_default=True), # Which is 'NEW' (must be added in the database)
+ ref_code=generate_ref_code(),
+ ordered=False,
+ )
+ # add them to the order
+ order_qs.items.add(*user_items)
+ # calculate the total
+ order_qs.total = order_qs.order_total
+ # mark items as ordered (added to a user order)
+ user_items.update(ordered=True)
+ order_qs.save()
+
+ return 201, {'detail': 'order created successfully'}
+
+ # get the order from query set to start working on it
+ order = order[0]
+
+ ordered_products_id = list(Item.objects.filter(ordered=True).values('product_id'))
+ ordered_products_id = list(map(lambda x: x['product_id'], ordered_products_id))
+ for item in user_items:
+ if item.product_id in ordered_products_id:
+ ordered_item = Item.objects.filter(product_id=item.product_id)[0]
+ ordered_item.item_qty += item.item_qty
+ ordered_item.save()
+ # after merge delete item
+ item.delete()
+ else:
+ order.items.add(item)
+ # calculate the total
+ order.total = order.order_total
+ # mark items as ordered (added to a user order)
user_items.update(ordered=True)
- order_qs.save()
+ order.save()
+
+ return 201, {"detail": "There was already an order and your cart items were merged."}
+
- return {'detail': 'order created successfully'}
+@order_controller.post("checkout", auth=GlobalAuth(), response={
+ 200: MessageOut,
+ 404: MessageOut,
+ 401: MessageOut
+})
+def checkout(request, checkout_data_in: CheckoutSchema):
+ if 'pk' not in request.auth:
+ return 401, {'detail': 'unauthorized'}
+ # gets the user from pk
+ user = User.objects.filter(id=request.auth['pk'])[0]
+
+ current_order = Order.objects.filter(user=user, ordered=False)
+ if current_order:
+ processing = OrderStatus.objects.get(title='PROCESSING') # proccessing must be added
+ current_order.update(**checkout_data_in.dict(), status=processing.id, ordered=True)
+ return 200, {'detail': 'Checkout was successful'}
+ return 404, {"detail": "No current order found"}
diff --git a/commerce/schemas.py b/commerce/schemas.py
index 5d68396..084e6b8 100644
--- a/commerce/schemas.py
+++ b/commerce/schemas.py
@@ -7,9 +7,6 @@
from commerce.models import Product, Merchant
-
-
-
class UUIDSchema(Schema):
id: UUID4
@@ -91,3 +88,21 @@ class ItemOut(UUIDSchema, ItemSchema):
pass
+class AddressSchema(Schema):
+ work_address: bool = False
+ address1: str
+ address2: str = None
+ phone: str
+
+
+class AddressOut(AddressSchema, UUIDSchema):
+ city: CitySchema
+
+
+class AddressCreate(AddressSchema):
+ city_id: UUID4
+
+
+class CheckoutSchema(Schema):
+ address: UUID4
+ note: str = None