Skip to content

Commit 44758ce

Browse files
Matthew FreireMatthew Freire
authored andcommitted
Shopping cart count and add to cart
1 parent 8bcc57b commit 44758ce

File tree

13 files changed

+268
-15
lines changed

13 files changed

+268
-15
lines changed

core/api/serializers.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
from rest_framework import serializers
2-
from core.models import Item
2+
from core.models import Item, Order, OrderItem
3+
4+
5+
class StringSerializer(serializers.StringRelatedField):
6+
def to_internal_value(self, value):
7+
return value
38

49

510
class ItemSerializer(serializers.ModelSerializer):
@@ -25,3 +30,29 @@ def get_category(self, obj):
2530

2631
def get_label(self, obj):
2732
return obj.get_label_display()
33+
34+
35+
class OrderItemSerializer(serializers.ModelSerializer):
36+
item = StringSerializer()
37+
38+
class Meta:
39+
model = OrderItem
40+
fields = (
41+
'id',
42+
'item',
43+
'quantity'
44+
)
45+
46+
47+
class OrderSerializer(serializers.ModelSerializer):
48+
order_items = serializers.SerializerMethodField()
49+
50+
class Meta:
51+
model = Order
52+
fields = (
53+
'id',
54+
'order_items'
55+
)
56+
57+
def get_order_items(self, obj):
58+
return OrderItemSerializer(obj.items.all(), many=True).data

core/api/urls.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
11
from django.urls import path
2-
from .views import ItemListView
2+
from .views import (
3+
ItemListView,
4+
AddToCartView,
5+
OrderDetailView
6+
)
37

48
urlpatterns = [
5-
path('product-list/', ItemListView.as_view(), name='product-list')
9+
path('product-list/', ItemListView.as_view(), name='product-list'),
10+
path('add-to-cart/', AddToCartView.as_view(), name='add-to-cart'),
11+
path('order-summary/', OrderDetailView.as_view(), name='order-summary')
612
]

core/api/views.py

Lines changed: 54 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,60 @@
1-
from rest_framework.generics import ListAPIView
2-
from rest_framework.permissions import AllowAny
3-
from core.models import Item
4-
from .serializers import ItemSerializer
1+
from django.conf import settings
2+
from django.core.exceptions import ObjectDoesNotExist
3+
from django.shortcuts import render, get_object_or_404
4+
from django.utils import timezone
5+
from rest_framework.generics import ListAPIView, RetrieveAPIView
6+
from rest_framework.permissions import AllowAny, IsAuthenticated
7+
from rest_framework.views import APIView
8+
from rest_framework.response import Response
9+
from rest_framework.status import HTTP_200_OK, HTTP_400_BAD_REQUEST
10+
from core.models import Item, OrderItem, Order
11+
from .serializers import ItemSerializer, OrderSerializer
512

613

714
class ItemListView(ListAPIView):
815
permission_classes = (AllowAny,)
916
serializer_class = ItemSerializer
1017
queryset = Item.objects.all()
18+
19+
20+
class AddToCartView(APIView):
21+
def post(self, request, *args, **kwargs):
22+
slug = request.data.get('slug', None)
23+
if slug is None:
24+
return Response({"message": "Invalid request"}, status=HTTP_400_BAD_REQUESTß)
25+
item = get_object_or_404(Item, slug=slug)
26+
order_item, created = OrderItem.objects.get_or_create(
27+
item=item,
28+
user=request.user,
29+
ordered=False
30+
)
31+
order_qs = Order.objects.filter(user=request.user, ordered=False)
32+
if order_qs.exists():
33+
order = order_qs[0]
34+
# check if the order item is in the order
35+
if order.items.filter(item__slug=item.slug).exists():
36+
order_item.quantity += 1
37+
order_item.save()
38+
return Response(status=HTTP_200_OK)
39+
else:
40+
order.items.add(order_item)
41+
return Response(status=HTTP_200_OK)
42+
43+
else:
44+
ordered_date = timezone.now()
45+
order = Order.objects.create(
46+
user=request.user, ordered_date=ordered_date)
47+
order.items.add(order_item)
48+
return Response(status=HTTP_200_OK)
49+
50+
51+
class OrderDetailView(RetrieveAPIView):
52+
serializer_class = OrderSerializer
53+
permission_classes = (IsAuthenticated,)
54+
55+
def get_object(self):
56+
try:
57+
order = Order.objects.get(user=self.request.user, ordered=False)
58+
return order
59+
except ObjectDoesNotExist:
60+
return Response({"message": "You do not have an active order"}, status=HTTP_400_BAD_REQUEST)

home/urls.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from django.conf import settings
2+
from django.conf.urls.static import static
13
from django.contrib import admin
24
from django.urls import path, include, re_path
35
from django.views.generic import TemplateView
@@ -8,5 +10,13 @@
810
path('rest-auth/registration/', include('rest_auth.registration.urls')),
911
path('admin/', admin.site.urls),
1012
path('api/', include('core.api.urls')),
11-
re_path(r'^.*', TemplateView.as_view(template_name='index.html')),
1213
]
14+
15+
if settings.DEBUG:
16+
urlpatterns += static(settings.MEDIA_URL,
17+
document_root=settings.MEDIA_ROOT)
18+
19+
20+
if not settings.DEBUG:
21+
urlpatterns += [re_path(r'^.*',
22+
TemplateView.as_view(template_name='index.html'))]

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ lazy-object-proxy==1.4.1
1616
mccabe==0.6.1
1717
oauthlib==3.0.1
1818
pep8==1.7.1
19+
Pillow==6.1.0
1920
pycodestyle==2.5.0
2021
pylint==2.3.1
2122
python3-openid==3.1.0

src/constants.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ const localhost = "http://127.0.0.1:8000";
22

33
const apiURL = "/api";
44

5-
const endpoint = `${localhost}${apiURL}`;
5+
export const endpoint = `${localhost}${apiURL}`;
66

77
export const productListURL = `${endpoint}/product-list/`;
8+
export const addToCartURL = `${endpoint}/add-to-cart/`;
9+
export const orderSummaryURL = `${endpoint}/order-summary/`;

src/containers/Layout.js

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,16 @@ import {
1313
import { Link, withRouter } from "react-router-dom";
1414
import { connect } from "react-redux";
1515
import { logout } from "../store/actions/auth";
16+
import { fetchCart } from "../store/actions/cart";
1617

1718
class CustomLayout extends React.Component {
19+
componentDidMount() {
20+
this.props.fetchCart();
21+
}
22+
1823
render() {
19-
const { authenticated } = this.props;
24+
const { authenticated, cart, loading } = this.props;
25+
console.log(cart);
2026
return (
2127
<div>
2228
<Menu inverted>
@@ -41,6 +47,31 @@ class CustomLayout extends React.Component {
4147
<Link to="/products">
4248
<Menu.Item header>Products</Menu.Item>
4349
</Link>
50+
<Menu.Menu inverted position="right">
51+
<Dropdown
52+
icon="cart"
53+
loading={loading}
54+
text={`${cart !== null ? cart.order_items.length : 0}`}
55+
pointing
56+
className="link item"
57+
>
58+
<Dropdown.Menu>
59+
{cart &&
60+
cart.order_items.map(order_item => {
61+
return (
62+
<Dropdown.Item key={order_item.id}>
63+
{order_item.quantity} x {order_item.item}
64+
</Dropdown.Item>
65+
);
66+
})}
67+
{cart && cart.order_items.length < 1 ? (
68+
<Dropdown.Item>No items in your cart</Dropdown.Item>
69+
) : null}
70+
<Dropdown.Divider />
71+
<Dropdown.Item icon="arrow right" text="Checkout" />
72+
</Dropdown.Menu>
73+
</Dropdown>
74+
</Menu.Menu>
4475
</Container>
4576
</Menu>
4677

@@ -114,13 +145,16 @@ class CustomLayout extends React.Component {
114145

115146
const mapStateToProps = state => {
116147
return {
117-
authenticated: state.auth.token !== null
148+
authenticated: state.auth.token !== null,
149+
cart: state.cart.shoppingCart,
150+
loading: state.cart.loading
118151
};
119152
};
120153

121154
const mapDispatchToProps = dispatch => {
122155
return {
123-
logout: () => dispatch(logout())
156+
logout: () => dispatch(logout()),
157+
fetchCart: () => dispatch(fetchCart())
124158
};
125159
};
126160

src/containers/ProductList.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ import {
1212
Message,
1313
Segment
1414
} from "semantic-ui-react";
15-
import { productListURL } from "../constants";
15+
import { productListURL, addToCartURL } from "../constants";
16+
import { authAxios } from "../utils";
1617

1718
class ProductList extends React.Component {
1819
state = {
@@ -34,6 +35,20 @@ class ProductList extends React.Component {
3435
});
3536
}
3637

38+
handleAddToCart = slug => {
39+
this.setState({ loading: true });
40+
authAxios
41+
.post(addToCartURL, { slug })
42+
.then(res => {
43+
console.log(res.data);
44+
// update the cart count
45+
this.setState({ loading: false });
46+
})
47+
.catch(err => {
48+
this.setState({ error: err, loading: false });
49+
});
50+
};
51+
3752
render() {
3853
const { data, error, loading } = this.state;
3954
return (
@@ -66,7 +81,13 @@ class ProductList extends React.Component {
6681
</Item.Meta>
6782
<Item.Description>{item.description}</Item.Description>
6883
<Item.Extra>
69-
<Button primary floated="right" icon labelPosition="right">
84+
<Button
85+
primary
86+
floated="right"
87+
icon
88+
labelPosition="right"
89+
onClick={() => this.handleAddToCart(item.slug)}
90+
>
7091
Add to cart
7192
<Icon name="cart plus" />
7293
</Button>

src/index.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import { Provider } from "react-redux";
77
import thunk from "redux-thunk";
88

99
import authReducer from "./store/reducers/auth";
10+
import cartReducer from "./store/reducers/cart";
1011

1112
const composeEnhances = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
1213

1314
const rootReducer = combineReducers({
14-
auth: authReducer
15+
auth: authReducer,
16+
cart: cartReducer
1517
});
1618

1719
const store = createStore(rootReducer, composeEnhances(applyMiddleware(thunk)));

src/store/actions/actionTypes.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,7 @@ export const AUTH_START = "AUTH_START";
22
export const AUTH_SUCCESS = "AUTH_SUCCESS";
33
export const AUTH_FAIL = "AUTH_FAIL";
44
export const AUTH_LOGOUT = "AUTH_LOGOUT";
5+
6+
export const CART_START = "CART_START";
7+
export const CART_SUCCESS = "CART_SUCCESS";
8+
export const CART_FAIL = "CART_FAIL";

src/store/actions/cart.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import axios from "axios";
2+
import { CART_START, CART_SUCCESS, CART_FAIL } from "./actionTypes";
3+
import { authAxios } from "../../utils";
4+
import { orderSummaryURL } from "../../constants";
5+
6+
export const cartStart = () => {
7+
return {
8+
type: CART_START
9+
};
10+
};
11+
12+
export const cartSuccess = data => {
13+
return {
14+
type: CART_SUCCESS,
15+
data
16+
};
17+
};
18+
19+
export const cartFail = error => {
20+
return {
21+
type: CART_FAIL,
22+
error: error
23+
};
24+
};
25+
26+
export const fetchCart = () => {
27+
return dispatch => {
28+
dispatch(cartStart());
29+
authAxios
30+
.get(orderSummaryURL)
31+
.then(res => {
32+
dispatch(cartSuccess(res.data));
33+
})
34+
.catch(err => {
35+
dispatch(cartFail(err));
36+
});
37+
};
38+
};

src/store/reducers/cart.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { CART_START, CART_SUCCESS, CART_FAIL } from "../actions/actionTypes";
2+
import { updateObject } from "../utility";
3+
4+
const initialState = {
5+
shoppingCart: null,
6+
error: null,
7+
loading: false
8+
};
9+
10+
const cartStart = (state, action) => {
11+
return updateObject(state, {
12+
error: null,
13+
loading: true
14+
});
15+
};
16+
17+
const cartSuccess = (state, action) => {
18+
return updateObject(state, {
19+
shoppingCart: action.data,
20+
error: null,
21+
loading: false
22+
});
23+
};
24+
25+
const cartFail = (state, action) => {
26+
return updateObject(state, {
27+
error: action.error,
28+
loading: false
29+
});
30+
};
31+
32+
const reducer = (state = initialState, action) => {
33+
switch (action.type) {
34+
case CART_START:
35+
return cartStart(state, action);
36+
case CART_SUCCESS:
37+
return cartSuccess(state, action);
38+
case CART_FAIL:
39+
return cartFail(state, action);
40+
default:
41+
return state;
42+
}
43+
};
44+
45+
export default reducer;

src/utils.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import axios from "axios";
2+
import { endpoint } from "./constants";
3+
4+
export const authAxios = axios.create({
5+
baseURL: endpoint,
6+
headers: {
7+
Authorization: `Token ${localStorage.getItem("token")}`
8+
}
9+
});

0 commit comments

Comments
 (0)