login as nick, has 2 items in cart
click cart, display detail
postgres shoppingCart table, 2 item belong to nick, 1 belongs to andy.
add 1 stage album
cart has 3 items
add 2 great expanse
the qty of great expanse in cart is 3
change qty of stages to 7, qty of the balance to 5, click update
#django/apiview
class ShoppingItemDetail(APIView):
def get_object(self, pk):
try:
return ShoppingItem.objects.get(pk=pk)
except ShoppingItem.DoesNotExist:
raise Http404
def get(self, request, pk, format=None):
shoppingItem = self.get_object(pk)
serializer = ShoppingItemsSerializer(shoppingItem)
return Response(serializer.data)
def put(self, request, pk, format=None):
shoppintItem = self.get_object(pk)
#only owner can edit
if shoppintItem.shopper != request.user:
return Response({"detail": "You do not have permission to perform this action."},
status= status.HTTP_403_FORBIDDEN)
serializer = ShoppingItemsSerializer(shoppintItem, data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
def delete(self, request, pk, format=None):
shoppingItem = self.get_object(pk)
# only owner can delete
if shoppingItem.shopper != request.user:
return Response({"detail": "You do not have permission to perform this action."},
status=status.HTTP_403_FORBIDDEN)
shoppingItem.delete()
return Response(status=status.HTTP_204_NO_CONTENT)
--------------------------------------
#django/urls
path('shoppingItem/<int:pk>/', apiview.ShoppingItemDetail.as_view(), name='ShoppingItemDetail'),
------------------------------------
//pages/shoppingCart
import React, { Component } from 'react';
import '../App.css';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { getShoppingItems } from '../redux/actions/getShoppingIItems';
import { getAlbums } from '../redux/actions/getAlbums';
import { getUsers } from '../redux/actions/getUser';
import { putShoppingItem } from '../redux/actions/putShoppingItem';
import { Input, message } from 'antd';
import { Button } from 'reactstrap';
import { MdExposureNeg1, MdExposurePlus1 } from "react-icons/md";
class ShoppingCart extends Component {
constructor(props) {
super(props);
this.state = {
qty: [],
qty_input: [],
qty_loaded: false,
};
}
componentDidMount() {
//start fetching database once logged in
if (this.props.loggedin) {
if (!this.props.gotAlbums) {
this.props.dispatch(getAlbums(this.props.token));
}
if (!this.props.gotUsers) {
this.props.dispatch(getUsers(this.props.token));
}
if (!this.props.gotShoppingItems) {
this.props.dispatch(getShoppingItems(this.props.token));
}
//wait for 5sec, check every 0.1s to see if shoppingItems are fetched
let i = 0;
const waitShoppingItem = setInterval(async () => {
if (this.props.gotShoppingItems) {
await this.setState({ qty: [], qty_input: [], qty_loaded: false })
//load qty from database, json format {album_id: xx, qty: xx}
this.props.shoppingItems.map(async (item, index) => {
await this.setState(prevState => {
return {
qty: prevState.qty.concat({ album_id: item.album, qty: item.quantity }),
qty_input: prevState.qty_input.concat({ album_id: item.album, qty: null })
}
})
if (this.state.qty.length === this.props.shoppingItems.length) { this.setState({ qty_loaded: true }) }
})
clearInterval(waitShoppingItem);
}
if (i == 50) {
message.error('fetching shopping items timed out.')
clearInterval(waitShoppingItem);
}
i++;
}, 100)
}
}
add = async (album_id) => {
await this.setState(prevState => { return { qty_input: prevState.qty_input.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: null }) } });
this.setState(prevState => { return { qty: prevState.qty.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: prevState.qty.find(item => item.album_id === album_id).qty + 1 }) } })
}
subtract = async (album_id) => {
await this.setState(prevState => { return { qty_input: prevState.qty_input.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: null }) } });
if (this.state.qty.find(item => item.album_id === album_id).qty > 1) {
this.setState(prevState => { return { qty: prevState.qty.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: prevState.qty.find(item => item.album_id === album_id).qty - 1 }) } })
}
}
inputChange = (e, album_id) => {
if (e.target.value > 0) {
const newQty = parseInt(e.target.value)
this.setState(prevState => { return { qty: prevState.qty.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: newQty }) } })
this.setState(prevState => { return { qty_input: prevState.qty_input.filter(item => item.album_id !== album_id).concat({ album_id: album_id, qty: newQty }) } })
} else {
message.error('entered invalid value')
}
}
updateCart = () => {
this.state.qty.map((item, index) => {
//only update items with qty changed
const oldItem = this.props.shoppingItems.find(i => i.album === item.album_id)
if (item.qty !== oldItem.quantity) {
const formData = new FormData();
formData.set('shopper', this.props.users.find(user => user.username === this.props.username).id);
formData.set('album', item.album_id);
formData.set('quantity', item.qty);
this.props.dispatch(putShoppingItem(this.props.token, oldItem.id, formData));
//wait for 5sec, check every sec to see if updateShoppingItem successful
let i = 0;
const waitAdd = setInterval(() => {
const newItem = this.props.shoppingItems.find(i => i.album === item.album_id)
if (newItem.quantity === item.qty) {
message.success('Updated quantity of ' + this.props.albums.filter(album => album.id === item.album_id)[0].album_title + ' to ' + item.qty)
clearInterval(waitAdd);
}
if (i == 50) {
message.error('connection timed out.')
clearInterval(waitAdd);
}
i++;
}, 100)
}
})
}
render() {
if (!this.props.loggedin) {
return <Redirect to='/login' />
}
return (
<div style={{ padding: '10px', marginTop: '10px' }}>
<legend>Shopping Cart</legend>
<hr />
<div style={{ color: 'red' }}>{this.props.errorShoppingItem} {this.props.errorAlbum}</div>
{
this.state.qty_loaded ?
this.props.shoppingItems
.sort((a, b) => { return this.props.albums.find(album => album.id === a.album).album_title.toUpperCase().localeCompare(this.props.albums.find(album => album.id === b.album).album_title.toUpperCase()) })
.map((item, index) => {
return <div key={index}>
<div style={{ width: '80%', display: 'flex', justifyContent: 'space-between' }}>
<span style={{ fontStyle: 'italic' }}>{this.props.albums.find(album => album.id === item.album).album_title}</span>
<Input
type='number'
onChange={(e) => { this.inputChange(e, item.album) }}
style={{ width: '150px' }}
addonBefore={<MdExposureNeg1 onClick={() => this.subtract(item.album)} style={{ cursor: 'pointer', fontSize: '20px' }} />}
addonAfter={<MdExposurePlus1 onClick={() => this.add(item.album)} style={{ cursor: 'pointer', fontSize: '20px' }} />}
value={this.state.qty_input.find(i => i.album_id === item.album).qty || this.state.qty.find(i => i.album_id === item.album).qty}
/>
</div>
<hr />
</div>
})
: null
}
<Button color='success' size='sm' onClick={() => this.updateCart()}>Update</Button>
</div>
);
}
}
export default connect(
(store) => {
return {
loggedin: store.login.fetched,
gotShoppingItems: store.shoppingItems.fetched,
shoppingItems: store.shoppingItems.shoppingItems,
errorShoppingItem: store.shoppingItems.error,
shoppingItemUpdated: store.shoppingItems.updated,
albums: store.albums.albums,
gotAlbums: store.albums.fetched,
errorAlbum: store.albums.error,
users: store.users.users,
gotUsers: store.users.fetched,
errorUser: store.users.error,
username: store.login.username,
token: store.login.token,
loggedin: store.login.fetched,
};
}
)(ShoppingCart);
---------------------------------------------------
//redux/actions/putShoppingItem
import axios from 'axios';
export function putShoppingItem(token, id, data) {
return {
type: "update_shoppingItem",
payload: axios({
method: 'put',
url: 'http://127.0.0.1:8000/api/shoppingItem/' + id.toString() + '/',
headers: {
Authorization: 'Token ' + token,
},
data: data,
})
}
}
--------------------------------------------
//redux/reducers/shoppingItemReducer
export default function reducer(
state = {
shoppingItems: [],
fetching: false,
fetched: false,
deleting: false,
deleted: false,
updating: false,
updated: false,
adding: false,
added: false,
error: ''
},
action
) {
switch (action.type) {
case "fetch_shoppingItems_PENDING": {
return { ...state, fetching: true, fetched: false, error: '' }
}
case "fetch_shoppingItems_FULFILLED": {
return {
...state,
fetching: false,
fetched: true,
shoppingItems: action.payload.data,
error: ''
}
}
case "fetch_shoppingItems_REJECTED": {
return {
...state,
fetching: false,
error: JSON.stringify(action.payload.response.data),
}
}
case "delete_shoppingItem_PENDING": {
return { ...state, deleting: true, deleted: false, error: '' }
}
case "delete_shoppingItem_FULFILLED": {
const deleteId = action.payload.config.data;
return {
...state,
deleting: false,
deleted: true,
shoppingItems: [...state.shoppingItems].filter(item => item.id != deleteId),
error: ''
}
}
case "delete_shoppingItem_REJECTED": {
return {
...state,
deleting: false,
error: JSON.stringify(action.payload.response.data),
}
}
case "add_shoppingItem_PENDING": {
return { ...state, adding: true, added: false, error: '' }
}
case "add_shoppingItem_FULFILLED": {
const data = action.payload.data;
//check if shopper has item in cart
const itemInCart = [...state.shoppingItems].filter(item => item.shopper == data.shopper && item.album == data.album)
//not in cart, create new
if (itemInCart.length === 0) {
return {
...state,
adding: false,
added: true,
shoppingItems: [...state.shoppingItems].concat(data),
error: ''
}
}
//in cart, update
else {
const repurchasedItem = {
shopper: data.shopper,
album: data.album,
quantity: data.quantity,
}
return {
...state,
adding: false,
added: true,
shoppingItems: [...state.shoppingItems].filter(item => item.album != data.album || item.shopper != data.shopper).concat(repurchasedItem),
error: ''
}
}
}
case "add_shoppingItem_REJECTED": {
return {
...state,
adding: false,
error: JSON.stringify(action.payload.response.data),
}
}
case "update_shoppingItem_PENDING": {
return { ...state, updating: true, updated: false, error: '' }
}
case "update_shoppingItem_FULFILLED": {
const data = action.payload.data;
return {
...state,
updating: false,
updated: true,
shoppingItems: [...state.shoppingItems].filter(item => item.album != data.album || item.shopper != data.shopper).concat(data),
error: ''
}
}
case "update_shoppingItem_REJECTED": {
return {
...state,
updating: false,
error: JSON.stringify(action.payload.response.data),
}
}
case "reset": {
return {
...state,
shoppingItems: [],
fetching: false,
fetched: false,
deleting: false,
deleted: false,
updating: false,
updated: false,
error: ''
}
}
default:
break;
}
return state;
}
reference:
http://chuanshuoge2.blogspot.com/2019/07/django-59-create-shopping-cart.html
No comments:
Post a Comment