diff --git a/services/community/api/auth/token.go b/services/community/api/auth/token.go index ffa453c6..c20f2e77 100644 --- a/services/community/api/auth/token.go +++ b/services/community/api/auth/token.go @@ -75,11 +75,11 @@ func ExtractTokenID(r *http.Request, db *gorm.DB) (uint32, error) { tokenValid := resp.StatusCode == 200 token, _, err := new(jwt.Parser).ParseUnverified(tokenString, jwt.MapClaims{}) - claims, ok := token.Claims.(jwt.MapClaims) if err != nil { log.Println(err) return 0, err } + claims, ok := token.Claims.(jwt.MapClaims) if ok && tokenValid { name := claims["sub"] diff --git a/services/community/api/controllers/post_controller.go b/services/community/api/controllers/post_controller.go index 3538ac26..6077fcb0 100644 --- a/services/community/api/controllers/post_controller.go +++ b/services/community/api/controllers/post_controller.go @@ -71,15 +71,17 @@ func (s *Server) GetPostByID(w http.ResponseWriter, r *http.Request) { } + + //GetPost Vulnerabilities func (s *Server) GetPost(w http.ResponseWriter, r *http.Request) { //post := models.Post{} limit_param := r.URL.Query().Get("limit") - limit := 30 + var limit int64 = 30 err := error(nil) if limit_param != "" { // Parse limit_param and set to limit - limit, err = strconv.Atoi(limit_param) + limit, err = strconv.ParseInt(limit_param, 10, 64) if err != nil { limit = 30 } @@ -88,10 +90,10 @@ func (s *Server) GetPost(w http.ResponseWriter, r *http.Request) { limit = 50 } - offset := 0 + var offset int64 = 0 offset_param := r.URL.Query().Get("offset") if offset_param != "" { - offset, err = strconv.Atoi(offset_param) + offset, err = strconv.ParseInt(offset_param, 10, 64) if err != nil { offset = 0 } diff --git a/services/community/api/models/coupon.go b/services/community/api/models/coupon.go index 171772b7..7cb0b58c 100644 --- a/services/community/api/models/coupon.go +++ b/services/community/api/models/coupon.go @@ -46,20 +46,20 @@ func (c *Coupon) Prepare() { func (c *Coupon) Validate() error { if c.CouponCode == "" { - return errors.New("Required Coupon Code") + return errors.New("required coupon code") } if c.Amount == "" { - return errors.New("Required Coupon Amount") + return errors.New("required coupon amount") } return nil } -//SaveCoupon save coupon database. +//SaveCoupon save coupon in database. func SaveCoupon(client *mongo.Client, coupon Coupon) (Coupon, error) { // Get a handle for your collection - collection := client.Database("crapi").Collection("coupon") + collection := client.Database("crapi").Collection("coupons") // Insert a single document insertResult, err := collection.InsertOne(context.TODO(), coupon) @@ -67,7 +67,6 @@ func SaveCoupon(client *mongo.Client, coupon Coupon) (Coupon, error) { log.Println(err) } log.Println("Inserted a single document: ", insertResult.InsertedID) - return coupon, err } diff --git a/services/community/api/models/post.go b/services/community/api/models/post.go index 71f9fabd..6a5f80ae 100644 --- a/services/community/api/models/post.go +++ b/services/community/api/models/post.go @@ -19,7 +19,6 @@ import ( "errors" "html" "log" - "reflect" "strings" "time" @@ -51,17 +50,24 @@ func (post *Post) Prepare() { post.CreatedAt = time.Now() } +type PostsResponse struct { + Posts []Post `json:"posts"` + NextOffset *int64 `json:"next_offset"` + PrevOffset *int64 `json:"previous_offset"` + Total int `json:"total"` +} + //Validate data of post func (post *Post) Validate() error { if post.Title == "" { - return errors.New("Required Title") + return errors.New("required title") } if post.Content == "" { - return errors.New("Required Content") + return errors.New("tequired content") } if post.AuthorID < 1 { - return errors.New("Required Author") + return errors.New("required author") } return nil } @@ -97,42 +103,57 @@ func GetPostByID(client *mongo.Client, ID string) (Post, error) { collection := client.Database("crapi").Collection("post") filter := bson.D{{Key: "id", Value: ID}} err := collection.FindOne(context.TODO(), filter).Decode(&post) + if err != nil { + log.Println(err) + } return post, err } //FindAllPost return all recent post -func FindAllPost(client *mongo.Client, offset int, limit int) ([]interface{}, error) { - post := []Post{} - +func FindAllPost(client *mongo.Client, offset int64, limit int64) (PostsResponse, error) { + postList := []Post{} + var postsResponse PostsResponse = PostsResponse{} options := options.Find() options.SetSort(bson.D{{Key: "_id", Value: -1}}) - options.SetLimit(int64(limit)) - options.SetSkip(int64(offset * limit)) + options.SetLimit(limit) + options.SetSkip(offset) + ctx := context.Background() collection := client.Database("crapi").Collection("post") - cur, err := collection.Find(context.Background(), bson.D{}, options) + cur, err := collection.Find(ctx, bson.D{}, options) if err != nil { - log.Println(err) + log.Println("Error in finding posts: ", err) + return postsResponse, err } - log.Println(cur) - objectType := reflect.TypeOf(post).Elem() - var list = make([]interface{}, 0) - defer cur.Close(context.Background()) - for cur.Next(context.Background()) { - result := reflect.New(objectType).Interface() - err := cur.Decode(result) - + for cur.Next(ctx) { + var elem Post + err := cur.Decode(&elem) if err != nil { - log.Println(err) - return nil, err + log.Println("Error in decoding posts: ", err) + return postsResponse, err } + postList = append(postList, elem) + } - list = append(list, result) + postsResponse.Posts = postList + // get posts count for pagination + count, err1 := collection.CountDocuments(context.Background(), bson.D{}) + if err1 != nil { + log.Println("Error in counting posts: ", err1) + return postsResponse, err1 } - if err := cur.Err(); err != nil { - return nil, err + if offset - limit >= 0 { + tempOffset := offset - limit + postsResponse.PrevOffset = &tempOffset } - - return list, err + if offset + limit < count { + tempOffset := offset + limit + postsResponse.NextOffset = &tempOffset + } + postsResponse.Total = len(postList) + if err = cur.Err(); err != nil { + log.Println("Error in cursor: ", err) + } + return postsResponse, err } diff --git a/services/community/api/models/user.go b/services/community/api/models/user.go index 1c6e44f8..f95d54d0 100644 --- a/services/community/api/models/user.go +++ b/services/community/api/models/user.go @@ -62,36 +62,36 @@ func (u *Author) Validate(action string) error { switch strings.ToLower(action) { case "update": if u.Nickname == "" { - return errors.New("Required Nickname") + return errors.New("required nickname") } if u.Email == "" { - return errors.New("Required Email") + return errors.New("required email") } if err := checkmail.ValidateFormat(u.Email); err != nil { - return errors.New("Invalid Email") + return errors.New("invalid email") } return nil case "login": if u.Nickname == "" { - return errors.New("Required Nickname") + return errors.New("required nickname") } if u.Email == "" { - return errors.New("Required Email") + return errors.New("required email") } if err := checkmail.ValidateFormat(u.Email); err != nil { - return errors.New("Invalid Email") + return errors.New("invalid email") } return nil default: if u.Nickname == "" { - return errors.New("Required Nickname") + return errors.New("required nickname") } if u.Email == "" { - return errors.New("Required Email") + return errors.New("required email") } if err := checkmail.ValidateFormat(u.Email); err != nil { - return errors.New("Invalid Email") + return errors.New("invalid email") } return nil } diff --git a/services/web/package.json b/services/web/package.json index 1f3e7fee..6c9ca87a 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -2,6 +2,7 @@ "name": "crapi-web", "version": "0.1.0", "private": true, + "proxy": "http://localhost:8889", "dependencies": { "@ant-design/icons": "^4.8.1", "@testing-library/jest-dom": "^4.2.4", @@ -28,7 +29,8 @@ "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", - "lint": "prettier --check src/**/*.{js,jsx}" + "lint": "prettier --check src/**/*.{js,jsx}", + "lint:fix": "prettier --write src/**/*.{js,jsx}" }, "eslintConfig": { "extends": "react-app" diff --git a/services/web/src/actions/communityActions.js b/services/web/src/actions/communityActions.js index 117f9f21..e788831f 100644 --- a/services/web/src/actions/communityActions.js +++ b/services/web/src/actions/communityActions.js @@ -15,11 +15,12 @@ import actionTypes from "../constants/actionTypes"; -export const getPostsAction = ({ accessToken, callback }) => { +export const getPostsAction = ({ accessToken, callback, ...data }) => { return { type: actionTypes.GET_POSTS, accessToken, callback, + ...data, }; }; diff --git a/services/web/src/actions/shopActions.js b/services/web/src/actions/shopActions.js index 328a49b2..15d5f958 100644 --- a/services/web/src/actions/shopActions.js +++ b/services/web/src/actions/shopActions.js @@ -15,11 +15,12 @@ import actionTypes from "../constants/actionTypes"; -export const getProductsAction = ({ callback, accessToken }) => { +export const getProductsAction = ({ callback, accessToken, ...data }) => { return { type: actionTypes.GET_PRODUCTS, accessToken, callback, + ...data, }; }; @@ -32,11 +33,12 @@ export const buyProductAction = ({ callback, accessToken, ...data }) => { }; }; -export const getOrdersAction = ({ callback, accessToken }) => { +export const getOrdersAction = ({ callback, accessToken, ...data }) => { return { type: actionTypes.GET_ORDERS, accessToken, callback, + ...data, }; }; diff --git a/services/web/src/actions/userActions.js b/services/web/src/actions/userActions.js index 7af5b239..53b72535 100644 --- a/services/web/src/actions/userActions.js +++ b/services/web/src/actions/userActions.js @@ -108,11 +108,12 @@ export const resetPasswordAction = ({ }; }; -export const getServicesAction = ({ callback, accessToken }) => { +export const getServicesAction = ({ callback, accessToken, ...data }) => { return { type: actionTypes.GET_SERVICES, accessToken, callback, + ...data, }; }; diff --git a/services/web/src/actions/vehicleActions.js b/services/web/src/actions/vehicleActions.js index e5e13508..dd532408 100644 --- a/services/web/src/actions/vehicleActions.js +++ b/services/web/src/actions/vehicleActions.js @@ -24,20 +24,27 @@ export const verifyVehicleAction = ({ callback, accessToken, ...data }) => { }; }; -export const getMechanicsAction = ({ callback, accessToken }) => { +export const getMechanicsAction = ({ callback, accessToken, ...data }) => { return { type: actionTypes.GET_MECHANICS, accessToken, callback, + ...data, }; }; -export const getVehiclesAction = ({ callback, accessToken, email }) => { +export const getVehiclesAction = ({ + callback, + accessToken, + email, + ...data +}) => { return { type: actionTypes.GET_VEHICLES, accessToken, callback, email, + ...data, }; }; diff --git a/services/web/src/components/forum/forum.js b/services/web/src/components/forum/forum.js index 3bc0a931..da2d2775 100644 --- a/services/web/src/components/forum/forum.js +++ b/services/web/src/components/forum/forum.js @@ -37,12 +37,13 @@ const { Meta } = Card; const { Paragraph } = Typography; const Forum = (props) => { - const { posts } = props; + const { posts, prevOffset, nextOffset } = props; const renderAvatar = (url) => ( ); - + console.log("Prev offset", prevOffset); + console.log("Next offset", nextOffset); return ( { ))} + + + + ); @@ -98,10 +119,15 @@ const Forum = (props) => { Forum.propTypes = { history: PropTypes.object, posts: PropTypes.array, + prevOffset: PropTypes.number, + nextOffset: PropTypes.number, + handleOffsetChange: PropTypes.func, }; -const mapStateToProps = ({ communityReducer: { posts } }) => { - return { posts }; +const mapStateToProps = ({ + communityReducer: { posts, prevOffset, nextOffset }, +}) => { + return { posts, prevOffset, nextOffset }; }; export default connect(mapStateToProps)(Forum); diff --git a/services/web/src/components/pastOrders/pastOrders.js b/services/web/src/components/pastOrders/pastOrders.js index 4237b7a5..60ab3666 100644 --- a/services/web/src/components/pastOrders/pastOrders.js +++ b/services/web/src/components/pastOrders/pastOrders.js @@ -85,6 +85,28 @@ const PastOrders = (props) => { ))} + + + + ); @@ -94,10 +116,15 @@ PastOrders.propTypes = { history: PropTypes.object, pastOrders: PropTypes.array, returnOrder: PropTypes.func, + prevOffset: PropTypes.number, + nextOffset: PropTypes.number, + handleOffsetChange: PropTypes.func, }; -const mapStateToProps = ({ shopReducer: { pastOrders } }) => { - return { pastOrders }; +const mapStateToProps = ({ + shopReducer: { pastOrders, prevOffset, nextOffset }, +}) => { + return { pastOrders, prevOffset, nextOffset }; }; export default connect(mapStateToProps)(PastOrders); diff --git a/services/web/src/components/shop/shop.js b/services/web/src/components/shop/shop.js index 5d613802..88be7bc5 100644 --- a/services/web/src/components/shop/shop.js +++ b/services/web/src/components/shop/shop.js @@ -68,6 +68,7 @@ const ProductDescription = (product, onBuyProduct) => ( const Shop = (props) => { const { + accessToken, products, availableCredit, history, @@ -76,6 +77,9 @@ const Shop = (props) => { hasErrored, errorMessage, onFinish, + prevOffset, + nextOffset, + onOffsetChange, } = props; return ( @@ -123,10 +127,32 @@ const Shop = (props) => { ))} + + + + setIsCouponFormOpen(false)} > @@ -156,6 +182,7 @@ const Shop = (props) => { }; Shop.propTypes = { + accessToken: PropTypes.string, history: PropTypes.object, products: PropTypes.array, availableCredit: PropTypes.number, @@ -165,10 +192,21 @@ Shop.propTypes = { hasErrored: PropTypes.bool, errorMessage: PropTypes.string, onFinish: PropTypes.func, + prevOffset: PropTypes.number, + nextOffset: PropTypes.number, + onOffsetChange: PropTypes.func, }; -const mapStateToProps = ({ shopReducer: { availableCredit, products } }) => { - return { availableCredit, products }; +const mapStateToProps = ({ + shopReducer: { + accessToken, + availableCredit, + products, + prevOffset, + nextOffset, + }, +}) => { + return { accessToken, availableCredit, products, prevOffset, nextOffset }; }; export default connect(mapStateToProps)(Shop); diff --git a/services/web/src/containers/forum/forum.js b/services/web/src/containers/forum/forum.js index 63a49404..17c7e599 100644 --- a/services/web/src/containers/forum/forum.js +++ b/services/web/src/containers/forum/forum.js @@ -37,7 +37,19 @@ const ForumContainer = (props) => { getPosts({ callback, accessToken }); }, [accessToken, getPosts]); - return ; + const onOffsetChange = (offset) => { + const callback = (res, data) => { + if (res !== responseTypes.SUCCESS) { + Modal.error({ + title: FAILURE_MESSAGE, + content: data, + }); + } + }; + getPosts({ callback, accessToken, offset }); + }; + + return ; }; const mapStateToProps = ({ userReducer: { accessToken } }) => { @@ -52,6 +64,9 @@ ForumContainer.propTypes = { accessToken: PropTypes.string, getPosts: PropTypes.func, history: PropTypes.object, + prevOffset: PropTypes.string, + nextOffset: PropTypes.string, + handleOffsetChange: PropTypes.func, }; export default connect(mapStateToProps, mapDispatchToProps)(ForumContainer); diff --git a/services/web/src/containers/pastOrders/pastOrders.js b/services/web/src/containers/pastOrders/pastOrders.js index 25fafef8..3ee22c62 100644 --- a/services/web/src/containers/pastOrders/pastOrders.js +++ b/services/web/src/containers/pastOrders/pastOrders.js @@ -37,6 +37,18 @@ const PastOrdersContainer = (props) => { getOrders({ callback, accessToken }); }, [accessToken, getOrders]); + const handleOffsetChange = (offset) => { + const callback = (res, data) => { + if (res !== responseTypes.SUCCESS) { + Modal.error({ + title: FAILURE_MESSAGE, + content: data, + }); + } + }; + getOrders({ callback, accessToken, offset }); + }; + const handleReturnOrder = (orderId) => { const callback = (res, data) => { if (res === responseTypes.SUCCESS) { @@ -62,7 +74,13 @@ const PastOrdersContainer = (props) => { returnOrder({ callback, accessToken, orderId }); }; - return ; + return ( + + ); }; const mapStateToProps = ({ userReducer: { accessToken } }) => { @@ -79,6 +97,9 @@ PastOrdersContainer.propTypes = { getOrders: PropTypes.func, returnOrder: PropTypes.func, history: PropTypes.object, + prevOffset: PropTypes.number, + nextOffset: PropTypes.number, + handleOffsetChange: PropTypes.func, }; export default connect( diff --git a/services/web/src/containers/shop/shop.js b/services/web/src/containers/shop/shop.js index 124478f5..9ff576ef 100644 --- a/services/web/src/containers/shop/shop.js +++ b/services/web/src/containers/shop/shop.js @@ -64,6 +64,18 @@ const ShopContainer = (props) => { buyProduct({ callback, accessToken, productId: product.id }); }; + const handleOffsetChange = (offset) => { + const callback = (res, data) => { + if (res !== responseTypes.SUCCESS) { + Modal.error({ + title: FAILURE_MESSAGE, + content: data, + }); + } + }; + getProducts({ callback, accessToken, offset }); + }; + const handleFormFinish = (values) => { const callback = (res, data) => { if (res === responseTypes.SUCCESS) { @@ -93,12 +105,16 @@ const ShopContainer = (props) => { hasErrored={hasErrored} errorMessage={errorMessage} onFinish={handleFormFinish} + onOffsetChange={handleOffsetChange} + {...props} /> ); }; -const mapStateToProps = ({ userReducer: { accessToken } }) => { - return { accessToken }; +const mapStateToProps = ({ + userReducer: { accessToken, prevOffset, nextOffset }, +}) => { + return { accessToken, prevOffset, nextOffset }; }; const mapDispatchToProps = { @@ -113,6 +129,9 @@ ShopContainer.propTypes = { buyProduct: PropTypes.func, applyCoupon: PropTypes.func, history: PropTypes.object, + nextOffset: PropTypes.number, + prevOffset: PropTypes.number, + onOffsetChange: PropTypes.func, }; export default connect(mapStateToProps, mapDispatchToProps)(ShopContainer); diff --git a/services/web/src/reducers/communityReducer.js b/services/web/src/reducers/communityReducer.js index 427fceb1..0b3b478a 100644 --- a/services/web/src/reducers/communityReducer.js +++ b/services/web/src/reducers/communityReducer.js @@ -17,6 +17,8 @@ import actionTypes from "../constants/actionTypes"; const initialData = { posts: [], + prevOffset: null, + nextOffset: null, }; const communityReducer = (state = initialData, action) => { @@ -24,7 +26,9 @@ const communityReducer = (state = initialData, action) => { case actionTypes.FETCHED_POSTS: return { ...state, - posts: action.payload, + posts: action.payload.posts, + prevOffset: action.payload.previous_offset, + nextOffset: action.payload.next_offset, }; case actionTypes.FETCHED_POST: return { diff --git a/services/web/src/reducers/shopReducer.js b/services/web/src/reducers/shopReducer.js index b0eb745e..4c53d0fc 100644 --- a/services/web/src/reducers/shopReducer.js +++ b/services/web/src/reducers/shopReducer.js @@ -19,6 +19,8 @@ const initialData = { availableCredit: 0, products: [], pastOrders: [], + prevOffset: null, + nextOffset: null, }; const profileReducer = (state = initialData, action) => { @@ -32,11 +34,15 @@ const profileReducer = (state = initialData, action) => { return { ...state, products: action.payload.products, + prevOffset: action.payload.prevOffset, + nextOffset: action.payload.nextOffset, }; case actionTypes.FETCHED_ORDERS: return { ...state, pastOrders: action.payload.orders, + prevOffset: action.payload.prevOffset, + nextOffset: action.payload.nextOffset, }; case actionTypes.FETCHED_ORDER: return { diff --git a/services/web/src/reducers/vehicleReducer.js b/services/web/src/reducers/vehicleReducer.js index 025f06fb..3c7f9936 100644 --- a/services/web/src/reducers/vehicleReducer.js +++ b/services/web/src/reducers/vehicleReducer.js @@ -31,6 +31,8 @@ const vehicleReducer = (state = initialData, action) => { return { ...state, mechanics: action.payload, + prevOffset: action.payload.prevOffset, + nextOffset: action.payload.nextOffset, }; case actionTypes.REFRESHED_LOCATION: return { diff --git a/services/web/src/sagas/communitySaga.js b/services/web/src/sagas/communitySaga.js index c836c9e9..9436eb07 100644 --- a/services/web/src/sagas/communitySaga.js +++ b/services/web/src/sagas/communitySaga.js @@ -37,8 +37,15 @@ export function* getPosts(param) { let recievedResponse = {}; try { yield put({ type: actionTypes.FETCHING_DATA }); - - const getUrl = APIService.COMMUNITY_SERVICE + requestURLS.GET_POSTS; + let offset = 0; + if (param.offset) { + offset = param.offset; + } + const getUrl = + APIService.COMMUNITY_SERVICE + + requestURLS.GET_POSTS + + "?limit=30&offset=" + + offset; const headers = { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, diff --git a/services/web/src/sagas/shopSaga.js b/services/web/src/sagas/shopSaga.js index d9370548..1f6e8b1b 100644 --- a/services/web/src/sagas/shopSaga.js +++ b/services/web/src/sagas/shopSaga.js @@ -35,11 +35,15 @@ import { * callback : callback method */ export function* getProducts(param) { - const { accessToken, callback } = param; + const { callback, accessToken } = param; let recievedResponse = {}; + let offset = param.offset ? param.offset : 0; try { yield put({ type: actionTypes.FETCHING_DATA }); - const getUrl = APIService.WORKSHOP_SERVICE + requestURLS.GET_PRODUCTS; + const getUrl = + APIService.WORKSHOP_SERVICE + + requestURLS.GET_PRODUCTS + + `?limit=30&offset=${offset}`; const headers = { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, @@ -60,13 +64,18 @@ export function* getProducts(param) { }); yield put({ type: actionTypes.FETCHED_PRODUCTS, - payload: { products: ResponseJson.products }, + payload: { + products: ResponseJson.products, + prevOffset: ResponseJson.previous_offset, + nextOffset: ResponseJson.next_offset, + }, }); callback(responseTypes.SUCCESS, ResponseJson); } else { callback(responseTypes.FAILURE, ResponseJson.message); } } catch (e) { + console.log(e); yield put({ type: actionTypes.FETCHED_DATA, payload: recievedResponse }); callback(responseTypes.FAILURE, NO_PRODUCTS); } @@ -74,7 +83,7 @@ export function* getProducts(param) { /** * buy a product - * @param { accessToken, callback, product_id} param + * @param { callback, accessToken, product_id} param * accessToken: access token of the user * callback : callback method * product_id: id of the product which is to be bought @@ -125,7 +134,11 @@ export function* getOrders(param) { let recievedResponse = {}; try { yield put({ type: actionTypes.FETCHING_DATA }); - const getUrl = APIService.WORKSHOP_SERVICE + requestURLS.GET_ORDERS; + let offset = param.offset ? param.offset : 0; + const getUrl = + APIService.WORKSHOP_SERVICE + + requestURLS.GET_ORDERS + + `?limit=30&offset=${offset}`; const headers = { "Content-Type": "application/json", Authorization: `Bearer ${accessToken}`, @@ -142,7 +155,11 @@ export function* getOrders(param) { if (recievedResponse.ok) { yield put({ type: actionTypes.FETCHED_ORDERS, - payload: { orders: ResponseJson.orders }, + payload: { + orders: ResponseJson.orders, + prevOffset: ResponseJson.previous_offset, + nextOffset: ResponseJson.next_offset, + }, }); callback(responseTypes.SUCCESS, ResponseJson); } else { diff --git a/services/workshop/__init__.py b/services/workshop/__init__.py index 713a92b0..e9b106a0 100644 --- a/services/workshop/__init__.py +++ b/services/workshop/__init__.py @@ -10,4 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/services/workshop/core/management/commands/seed_database.py b/services/workshop/core/management/commands/seed_database.py index 29517ea5..213c93e3 100644 --- a/services/workshop/core/management/commands/seed_database.py +++ b/services/workshop/core/management/commands/seed_database.py @@ -32,51 +32,46 @@ def create_products(): from crapi.shop.models import Product + product_details_all = [ - { - 'name': 'Seat', - 'price': 10, - 'image_url': 'images/seat.svg' - }, - { - 'name': 'Wheel', - 'price': 10, - 'image_url': 'images/wheel.svg' - } + {"name": "Seat", "price": 10, "image_url": "images/seat.svg"}, + {"name": "Wheel", "price": 10, "image_url": "images/wheel.svg"}, ] for product_details in product_details_all: - if Product.objects.filter(name=product_details['name']).exists(): - logger.info("Product already exists. Skipping: "+ product_details['name']) + if Product.objects.filter(name=product_details["name"]).exists(): + logger.info("Product already exists. Skipping: " + product_details["name"]) continue product = Product.objects.create( - name=product_details['name'], - price=float(product_details['price']), - image_url=product_details['image_url'] + name=product_details["name"], + price=float(product_details["price"]), + image_url=product_details["image_url"], ) product.save() - logger.info("Created Product: "+str(product.__dict__)) + logger.info("Created Product: " + str(product.__dict__)) + def create_mechanics(): from crapi.user.models import User, UserDetails from crapi.mechanic.models import Mechanic + mechanic_details_all = [ { - 'name': 'Jhon', - 'email': 'jhon@example.com', - 'number': '', - 'password': 'Admin1@#', - 'mechanic_code': 'TRAC_JHN' + "name": "Jhon", + "email": "jhon@example.com", + "number": "", + "password": "Admin1@#", + "mechanic_code": "TRAC_JHN", }, { - 'name': 'James', - 'email': 'james@example.com', - 'number': '', - 'password': 'Admin1@#', - 'mechanic_code': 'TRAC_JME' + "name": "James", + "email": "james@example.com", + "number": "", + "password": "Admin1@#", + "mechanic_code": "TRAC_JME", }, ] for mechanic_details in mechanic_details_all: - uset = User.objects.filter(email=mechanic_details['email']) + uset = User.objects.filter(email=mechanic_details["email"]) if not uset.exists(): try: cursor = connection.cursor() @@ -84,34 +79,36 @@ def create_mechanics(): result = cursor.fetchone() user_id = result[0] except Exception as e: - logger.error("Failed to fetch user_login_id_seq"+str(e)) + logger.error("Failed to fetch user_login_id_seq" + str(e)) user_id = 1 user = User.objects.create( id=user_id, - email=mechanic_details['email'], - number=mechanic_details['number'], + email=mechanic_details["email"], + number=mechanic_details["number"], password=bcrypt.hashpw( - mechanic_details['password'].encode('utf-8'), - bcrypt.gensalt() + mechanic_details["password"].encode("utf-8"), bcrypt.gensalt() ).decode(), role=User.ROLE_CHOICES.MECH, - created_on=timezone.now() + created_on=timezone.now(), ) user.save() - logger.info("Created User: "+str(user.__dict__)) + logger.info("Created User: " + str(user.__dict__)) else: user = uset.first() - if Mechanic.objects.filter(mechanic_code=mechanic_details['mechanic_code']): - logger.info("Mechanic already exists. Skipping: " - + mechanic_details['mechanic_code'] - + " " + mechanic_details['name'] + " " - + mechanic_details['email']) + if Mechanic.objects.filter(mechanic_code=mechanic_details["mechanic_code"]): + logger.info( + "Mechanic already exists. Skipping: " + + mechanic_details["mechanic_code"] + + " " + + mechanic_details["name"] + + " " + + mechanic_details["email"] + ) continue mechanic = Mechanic.objects.create( - mechanic_code=mechanic_details['mechanic_code'], - user=user + mechanic_code=mechanic_details["mechanic_code"], user=user ) mechanic.save() try: @@ -120,17 +117,18 @@ def create_mechanics(): result = cursor.fetchone() user_details_id = result[0] except Exception as e: - logger.error("Failed to fetch user_details_id_seq"+str(e)) + logger.error("Failed to fetch user_details_id_seq" + str(e)) user_details_id = 1 userdetails = UserDetails.objects.create( id=user_details_id, available_credit=0, - name=mechanic_details['name'], - status='ACTIVE', - user=user + name=mechanic_details["name"], + status="ACTIVE", + user=user, ) userdetails.save() + def create_reports(): import random import sys @@ -138,8 +136,9 @@ def create_reports(): from crapi.user.models import User, UserDetails, Vehicle from crapi.mechanic.models import Mechanic, ServiceRequest from django.utils import timezone + count = ServiceRequest.objects.all().count() - if (count >= 5): + if count >= 5: return logger.info("Creating Reports") mechanics = Mechanic.objects.all() @@ -156,7 +155,8 @@ def create_reports(): service_request = ServiceRequest.objects.create( vehicle=vehicle, mechanic=mechanic, - problem_details=textwrap.dedent("""\ + problem_details=textwrap.dedent( + """\ My car {} - {} is having issues. Can you give me a call on my mobile {}, Or send me an email at {} @@ -167,28 +167,35 @@ def create_reports(): vehicle_model.model, user.number, user.email, - user_detail.name) + user_detail.name, + ) ), status=status, - created_on=timezone.now() + created_on=timezone.now(), ) service_request.save() - logger.info("Created Service Request for User %s: %s", user.email, service_request.__dict__) + logger.info( + "Created Service Request for User %s: %s", + user.email, + service_request.__dict__, + ) except Exception as e: print(sys.exc_info()[0]) - logger.error("Failed to create report: "+str(e)) + logger.error("Failed to create report: " + str(e)) + def create_orders(): import uuid from crapi.user.models import User, UserDetails from crapi.shop.models import Product from crapi.shop.models import Order + if Order.objects.all().count() >= 1: return - users = User.objects.all().order_by('id') + users = User.objects.all().order_by("id") users_seed = users[:5] for user in users_seed: - product = Product.objects.filter(name='Seat').first() + product = Product.objects.filter(name="Seat").first() order = Order.objects.create( user=user, product=product, @@ -201,7 +208,7 @@ def create_orders(): class Command(BaseCommand): - help = 'Seed the database with initial data.' + help = "Seed the database with initial data." def handle(self, *args, **kwargs): """ @@ -212,16 +219,16 @@ def handle(self, *args, **kwargs): try: create_products() except Exception as e: - logger.error("Cannot Pre Populate Products: "+str(e)) + logger.error("Cannot Pre Populate Products: " + str(e)) try: create_mechanics() except Exception as e: - logger.error("Cannot Pre Populate Mechanics: "+str(e)) + logger.error("Cannot Pre Populate Mechanics: " + str(e)) try: create_reports() except Exception as e: - logger.error("Cannot Pre Populate Reports: "+str(e)) + logger.error("Cannot Pre Populate Reports: " + str(e)) try: create_orders() except Exception as e: - logger.error("Cannot Pre Populate Orders: "+str(e)) + logger.error("Cannot Pre Populate Orders: " + str(e)) diff --git a/services/workshop/crapi/__init__.py b/services/workshop/crapi/__init__.py index 713a92b0..e9b106a0 100644 --- a/services/workshop/crapi/__init__.py +++ b/services/workshop/crapi/__init__.py @@ -10,4 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/services/workshop/crapi/apps.py b/services/workshop/crapi/apps.py index 59b3913f..d84efc85 100644 --- a/services/workshop/crapi/apps.py +++ b/services/workshop/crapi/apps.py @@ -30,51 +30,46 @@ def create_products(): from crapi.shop.models import Product + product_details_all = [ - { - 'name': 'Seat', - 'price': 10, - 'image_url': 'images/seat.svg' - }, - { - 'name': 'Wheel', - 'price': 10, - 'image_url': 'images/wheel.svg' - } + {"name": "Seat", "price": 10, "image_url": "images/seat.svg"}, + {"name": "Wheel", "price": 10, "image_url": "images/wheel.svg"}, ] for product_details in product_details_all: - if Product.objects.filter(name=product_details['name']).exists(): - logger.info("Product already exists. Skipping: "+ product_details['name']) + if Product.objects.filter(name=product_details["name"]).exists(): + logger.info("Product already exists. Skipping: " + product_details["name"]) continue product = Product.objects.create( - name=product_details['name'], - price=float(product_details['price']), - image_url=product_details['image_url'] + name=product_details["name"], + price=float(product_details["price"]), + image_url=product_details["image_url"], ) product.save() - logger.info("Created Product: "+str(product.__dict__)) + logger.info("Created Product: " + str(product.__dict__)) + def create_mechanics(): from crapi.user.models import User, UserDetails from crapi.mechanic.models import Mechanic + mechanic_details_all = [ { - 'name': 'Jhon', - 'email': 'jhon@example.com', - 'number': '', - 'password': 'Admin1@#', - 'mechanic_code': 'TRAC_JHN' + "name": "Jhon", + "email": "jhon@example.com", + "number": "", + "password": "Admin1@#", + "mechanic_code": "TRAC_JHN", }, { - 'name': 'James', - 'email': 'james@example.com', - 'number': '', - 'password': 'Admin1@#', - 'mechanic_code': 'TRAC_JME' + "name": "James", + "email": "james@example.com", + "number": "", + "password": "Admin1@#", + "mechanic_code": "TRAC_JME", }, ] for mechanic_details in mechanic_details_all: - uset = User.objects.filter(email=mechanic_details['email']) + uset = User.objects.filter(email=mechanic_details["email"]) if not uset.exists(): try: cursor = connection.cursor() @@ -82,34 +77,36 @@ def create_mechanics(): result = cursor.fetchone() user_id = result[0] except Exception as e: - logger.error("Failed to fetch user_login_id_seq"+str(e)) + logger.error("Failed to fetch user_login_id_seq" + str(e)) user_id = 1 user = User.objects.create( id=user_id, - email=mechanic_details['email'], - number=mechanic_details['number'], + email=mechanic_details["email"], + number=mechanic_details["number"], password=bcrypt.hashpw( - mechanic_details['password'].encode('utf-8'), - bcrypt.gensalt() + mechanic_details["password"].encode("utf-8"), bcrypt.gensalt() ).decode(), role=User.ROLE_CHOICES.MECH, - created_on=timezone.now() + created_on=timezone.now(), ) user.save() - logger.info("Created User: "+str(user.__dict__)) + logger.info("Created User: " + str(user.__dict__)) else: user = uset.first() - if Mechanic.objects.filter(mechanic_code=mechanic_details['mechanic_code']): - logger.info("Mechanic already exists. Skipping: " - + mechanic_details['mechanic_code'] - + " " + mechanic_details['name'] + " " - + mechanic_details['email']) + if Mechanic.objects.filter(mechanic_code=mechanic_details["mechanic_code"]): + logger.info( + "Mechanic already exists. Skipping: " + + mechanic_details["mechanic_code"] + + " " + + mechanic_details["name"] + + " " + + mechanic_details["email"] + ) continue mechanic = Mechanic.objects.create( - mechanic_code=mechanic_details['mechanic_code'], - user=user + mechanic_code=mechanic_details["mechanic_code"], user=user ) mechanic.save() try: @@ -118,17 +115,18 @@ def create_mechanics(): result = cursor.fetchone() user_details_id = result[0] except Exception as e: - logger.error("Failed to fetch user_details_id_seq"+str(e)) + logger.error("Failed to fetch user_details_id_seq" + str(e)) user_details_id = 1 userdetails = UserDetails.objects.create( id=user_details_id, available_credit=0, - name=mechanic_details['name'], - status='ACTIVE', - user=user + name=mechanic_details["name"], + status="ACTIVE", + user=user, ) userdetails.save() + def create_reports(): import random import sys @@ -136,8 +134,9 @@ def create_reports(): from crapi.user.models import User, UserDetails, Vehicle from crapi.mechanic.models import Mechanic, ServiceRequest from django.utils import timezone + count = ServiceRequest.objects.all().count() - if (count >= 5): + if count >= 5: return logger.info("Creating Reports") mechanics = Mechanic.objects.all() @@ -154,7 +153,8 @@ def create_reports(): service_request = ServiceRequest.objects.create( vehicle=vehicle, mechanic=mechanic, - problem_details=textwrap.dedent("""\ + problem_details=textwrap.dedent( + """\ My car {} - {} is having issues. Can you give me a call on my mobile {}, Or send me an email at {} @@ -165,27 +165,34 @@ def create_reports(): vehicle_model.model, user.number, user.email, - user_detail.name) + user_detail.name, + ) ), status=status, - created_on=timezone.now() + created_on=timezone.now(), ) service_request.save() - logger.info("Created Service Request for User %s: %s", user.email, service_request.__dict__) + logger.info( + "Created Service Request for User %s: %s", + user.email, + service_request.__dict__, + ) except Exception as e: print(sys.exc_info()[0]) - logger.error("Failed to create report: "+str(e)) + logger.error("Failed to create report: " + str(e)) + def create_orders(): import uuid from crapi.user.models import User, UserDetails from crapi.shop.models import Product from crapi.shop.models import Order + if Order.objects.all().count() >= 1: return - users = User.objects.filter(role=User.ROLE_CHOICES.PREDEFINED).order_by('id') + users = User.objects.filter(role=User.ROLE_CHOICES.PREDEFINED).order_by("id") for user in users: - product = Product.objects.filter(name='Seat').first() + product = Product.objects.filter(name="Seat").first() order = Order.objects.create( user=user, product=product, @@ -197,14 +204,12 @@ def create_orders(): logger.info("Created Order for User %s: %s", user.email, order.__dict__) - - - class CRAPIConfig(AppConfig): """ Stores all meta data of crapi application """ - name = 'crapi' + + name = "crapi" def ready(self): """ @@ -219,16 +224,16 @@ def ready(self): try: create_products() except Exception as e: - logger.error("Cannot Pre Populate Products: "+str(e)) + logger.error("Cannot Pre Populate Products: " + str(e)) try: create_mechanics() except Exception as e: - logger.error("Cannot Pre Populate Mechanics: "+str(e)) + logger.error("Cannot Pre Populate Mechanics: " + str(e)) try: create_reports() except Exception as e: - logger.error("Cannot Pre Populate Reports: "+str(e)) + logger.error("Cannot Pre Populate Reports: " + str(e)) try: create_orders() except Exception as e: - logger.error("Cannot Pre Populate Orders: "+str(e)) + logger.error("Cannot Pre Populate Orders: " + str(e)) diff --git a/services/workshop/crapi/mechanic/__init__.py b/services/workshop/crapi/mechanic/__init__.py index bfd4bb9d..e9b106a0 100644 --- a/services/workshop/crapi/mechanic/__init__.py +++ b/services/workshop/crapi/mechanic/__init__.py @@ -10,5 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - diff --git a/services/workshop/crapi/mechanic/models.py b/services/workshop/crapi/mechanic/models.py index 09b7bf26..79cd9323 100644 --- a/services/workshop/crapi/mechanic/models.py +++ b/services/workshop/crapi/mechanic/models.py @@ -24,17 +24,19 @@ from django_db_cascade.fields import ForeignKey, OneToOneField from django_db_cascade.deletions import DB_CASCADE + class Mechanic(models.Model): """ Mechanic Model represents a mechanic for the application """ + id = models.AutoField(primary_key=True) mechanic_code = models.CharField(max_length=100, null=False, unique=True) user = ForeignKey(User, DB_CASCADE) class Meta: - db_table = 'mechanic' + db_table = "mechanic" def __str__(self): return f"" @@ -45,6 +47,7 @@ class ServiceRequest(models.Model): Service Request Model represents a service request in the application """ + id = models.AutoField(primary_key=True) mechanic = ForeignKey(Mechanic, DB_CASCADE) vehicle = ForeignKey(Vehicle, DB_CASCADE) @@ -53,13 +56,14 @@ class ServiceRequest(models.Model): updated_on = models.DateTimeField(null=True) STATUS_CHOICES = Choices( - ('PEN', "pending", "Pending"), - ('FIN', "finished", "Finished") + ("PEN", "pending", "Pending"), ("FIN", "finished", "Finished") + ) + status = models.CharField( + max_length=10, choices=STATUS_CHOICES, default=STATUS_CHOICES.PEN ) - status = models.CharField(max_length=10, choices=STATUS_CHOICES, default=STATUS_CHOICES.PEN) class Meta: - db_table = 'service_request' + db_table = "service_request" def __str__(self): - return f'' + return f"" diff --git a/services/workshop/crapi/mechanic/serializers.py b/services/workshop/crapi/mechanic/serializers.py index 7c666914..af845c03 100644 --- a/services/workshop/crapi/mechanic/serializers.py +++ b/services/workshop/crapi/mechanic/serializers.py @@ -25,20 +25,23 @@ class MechanicSerializer(serializers.ModelSerializer): """ Serializer for Mechanic model """ + user = UserSerializer() class Meta: """ Meta class for MechanicSerializer """ + model = Mechanic - fields = ('id', 'mechanic_code', 'user') + fields = ("id", "mechanic_code", "user") class ServiceRequestSerializer(serializers.ModelSerializer): """ Serializer for Mechanic model """ + mechanic = MechanicSerializer() vehicle = VehicleSerializer() created_on = serializers.DateTimeField(format="%d %B, %Y, %H:%M:%S") @@ -47,16 +50,25 @@ class Meta: """ Meta class for ServiceRequestSerializer """ + model = ServiceRequest - fields = ('id', 'mechanic', 'vehicle', 'problem_details', 'status', 'created_on') + fields = ( + "id", + "mechanic", + "vehicle", + "problem_details", + "status", + "created_on", + ) class ReceiveReportSerializer(serializers.Serializer): """ Serializer for Receive Report API """ + mechanic_code = serializers.CharField() - problem_details= serializers.CharField() + problem_details = serializers.CharField() vin = serializers.CharField() owner_id = serializers.CharField(required=False) @@ -65,6 +77,7 @@ class SignUpSerializer(serializers.Serializer): """ Serializer for Sign up """ + name = serializers.CharField() email = serializers.EmailField() number = serializers.CharField() diff --git a/services/workshop/crapi/mechanic/tests.py b/services/workshop/crapi/mechanic/tests.py index 1c22a92b..7e33142c 100644 --- a/services/workshop/crapi/mechanic/tests.py +++ b/services/workshop/crapi/mechanic/tests.py @@ -16,7 +16,7 @@ from unittest.mock import patch from utils.mock_methods import get_sample_mechanic_data, mock_jwt_auth_required -patch('utils.jwt.jwt_auth_required', mock_jwt_auth_required).start() +patch("utils.jwt.jwt_auth_required", mock_jwt_auth_required).start() from django.test import TestCase, Client from utils import messages @@ -46,15 +46,19 @@ def test_duplicate_email_signup(self): should get an error response saying email already registered :return: None """ - res = self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + res = self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) self.assertEqual(res.status_code, 200) - res = self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + res = self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) self.assertNotEqual(res.status_code, 200) - self.assertEqual(res.json()['message'], messages.EMAIL_ALREADY_EXISTS) + self.assertEqual(res.json()["message"], messages.EMAIL_ALREADY_EXISTS) def test_duplicate_mechanic_code(self): """ @@ -64,18 +68,21 @@ def test_duplicate_mechanic_code(self): should get an error response saying mechanic_code already exists :return: None """ - res = self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + res = self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) self.assertEqual(res.status_code, 200) - self.mechanic['email'] = 'abcd@example.com' - res = self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + self.mechanic["email"] = "abcd@example.com" + res = self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) self.assertNotEqual(res.status_code, 200) - self.assertEqual(res.json()['message'], - messages.MEC_CODE_ALREADY_EXISTS) + self.assertEqual(res.json()["message"], messages.MEC_CODE_ALREADY_EXISTS) def test_no_duplicate(self): """ @@ -85,20 +92,22 @@ def test_no_duplicate(self): should get a valid response(200) on second signup also :return: """ - res = self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + res = self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) self.assertEqual(res.status_code, 200) - self.mechanic['email'] = 'abcd@example.com' - self.mechanic['mechanic_code'] = 'TRAC_MEC_4' - res = self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + self.mechanic["email"] = "abcd@example.com" + self.mechanic["mechanic_code"] = "TRAC_MEC_4" + res = self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) self.assertEqual(res.status_code, 200) - self.assertIn( - messages.MEC_CREATED.split(':')[0], - res.json()['message']) + self.assertIn(messages.MEC_CREATED.split(":")[0], res.json()["message"]) def test_jwt_token(self): """ @@ -110,18 +119,18 @@ def test_jwt_token(self): should get a valid response(200) of the api :return: None """ - self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) - res = self.client.get('/workshop/api/mechanic/') + res = self.client.get("/workshop/api/mechanic/") self.assertNotEqual(res.status_code, 200) - self.assertEqual(res.json()['message'], messages.JWT_REQUIRED) + self.assertEqual(res.json()["message"], messages.JWT_REQUIRED) - auth_headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + self.mechanic['email'] - } - res = self.client.get('/workshop/api/mechanic/', **auth_headers) + auth_headers = {"HTTP_AUTHORIZATION": "Bearer " + self.mechanic["email"]} + res = self.client.get("/workshop/api/mechanic/", **auth_headers) self.assertEqual(res.status_code, 200) def test_invalid_jwt_token(self): @@ -132,13 +141,13 @@ def test_invalid_jwt_token(self): should get an error response saying token invalid :return: None """ - res = self.client.get('/workshop/api/mechanic/') + res = self.client.get("/workshop/api/mechanic/") self.assertNotEqual(res.status_code, 200) - auth_headers = {'HTTP_AUTHORIZATION': 'Bearer invalid.token'} - res = self.client.get('/workshop/api/mechanic/', **auth_headers) + auth_headers = {"HTTP_AUTHORIZATION": "Bearer invalid.token"} + res = self.client.get("/workshop/api/mechanic/", **auth_headers) self.assertNotEqual(res.status_code, 200) - self.assertEqual(res.json()['message'], messages.INVALID_TOKEN) + self.assertEqual(res.json()["message"], messages.INVALID_TOKEN) def test_bad_request(self): """ @@ -146,8 +155,10 @@ def test_bad_request(self): should get a bad request response :return: None """ - del [self.mechanic['password']] - res = self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + del [self.mechanic["password"]] + res = self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) self.assertEqual(res.status_code, 400) diff --git a/services/workshop/crapi/mechanic/urls.py b/services/workshop/crapi/mechanic/urls.py index f9071f66..361cb49a 100644 --- a/services/workshop/crapi/mechanic/urls.py +++ b/services/workshop/crapi/mechanic/urls.py @@ -21,9 +21,13 @@ import crapi.mechanic.views as mechanic_views urlpatterns = [ - re_path(r'signup$', mechanic_views.SignUpView.as_view()), - re_path(r'receive_report$', mechanic_views.ReceiveReportView.as_view()), - re_path(r'mechanic_report$', mechanic_views.GetReportView.as_view(), name="get-mechanic-report"), - re_path(r'service_requests$', mechanic_views.ServiceRequestsView.as_view()), - re_path(r'$', mechanic_views.MechanicView.as_view()), + re_path(r"signup$", mechanic_views.SignUpView.as_view()), + re_path(r"receive_report$", mechanic_views.ReceiveReportView.as_view()), + re_path( + r"mechanic_report$", + mechanic_views.GetReportView.as_view(), + name="get-mechanic-report", + ), + re_path(r"service_requests$", mechanic_views.ServiceRequestsView.as_view()), + re_path(r"$", mechanic_views.MechanicView.as_view()), ] diff --git a/services/workshop/crapi/mechanic/views.py b/services/workshop/crapi/mechanic/views.py index 782df179..4e0d7bf4 100644 --- a/services/workshop/crapi/mechanic/views.py +++ b/services/workshop/crapi/mechanic/views.py @@ -29,12 +29,20 @@ from crapi.user.models import User, Vehicle, UserDetails from utils.logging import log_error from .models import Mechanic, ServiceRequest -from .serializers import MechanicSerializer, ServiceRequestSerializer, ReceiveReportSerializer, SignUpSerializer +from .serializers import ( + MechanicSerializer, + ServiceRequestSerializer, + ReceiveReportSerializer, + SignUpSerializer, +) +from rest_framework.pagination import LimitOffsetPagination + class SignUpView(APIView): """ Used to add a new mechanic """ + @csrf_exempt def post(self, request): """ @@ -48,52 +56,69 @@ def post(self, request): """ serializer = SignUpSerializer(data=request.data) if not serializer.is_valid(): - log_error(request.path, request.data, status.HTTP_400_BAD_REQUEST, serializer.errors) + log_error( + request.path, + request.data, + status.HTTP_400_BAD_REQUEST, + serializer.errors, + ) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) mechanic_details = serializer.data - if User.objects.filter(email=mechanic_details['email']).exists(): - return Response({'message': messages.EMAIL_ALREADY_EXISTS}, status=status.HTTP_400_BAD_REQUEST) - if Mechanic.objects.filter(mechanic_code=mechanic_details['mechanic_code']).exists(): - return Response({'message': messages.MEC_CODE_ALREADY_EXISTS}, status=status.HTTP_400_BAD_REQUEST) + if User.objects.filter(email=mechanic_details["email"]).exists(): + return Response( + {"message": messages.EMAIL_ALREADY_EXISTS}, + status=status.HTTP_400_BAD_REQUEST, + ) + if Mechanic.objects.filter( + mechanic_code=mechanic_details["mechanic_code"] + ).exists(): + return Response( + {"message": messages.MEC_CODE_ALREADY_EXISTS}, + status=status.HTTP_400_BAD_REQUEST, + ) try: - user_id = User.objects.aggregate(models.Max('id'))['id__max'] + 1 + user_id = User.objects.aggregate(models.Max("id"))["id__max"] + 1 except TypeError: user_id = 1 user = User.objects.create( id=user_id, - email=mechanic_details['email'], - number=mechanic_details['number'], + email=mechanic_details["email"], + number=mechanic_details["number"], password=bcrypt.hashpw( - mechanic_details['password'].encode('utf-8'), - bcrypt.gensalt() + mechanic_details["password"].encode("utf-8"), bcrypt.gensalt() ).decode(), role=User.ROLE_CHOICES.MECH, - created_on=timezone.now() + created_on=timezone.now(), ) Mechanic.objects.create( - mechanic_code=mechanic_details['mechanic_code'], - user=user + mechanic_code=mechanic_details["mechanic_code"], user=user ) try: - user_details_id = UserDetails.objects.aggregate(models.Max('id'))['id__max'] + 1 + user_details_id = ( + UserDetails.objects.aggregate(models.Max("id"))["id__max"] + 1 + ) except TypeError: user_details_id = 1 UserDetails.objects.create( id=user_details_id, available_credit=0, - name=mechanic_details['name'], - status='ACTIVE', - user=user + name=mechanic_details["name"], + status="ACTIVE", + user=user, + ) + return Response( + {"message": messages.MEC_CREATED.format(user.email)}, + status=status.HTTP_200_OK, ) - return Response({'message': messages.MEC_CREATED.format(user.email)}, status=status.HTTP_200_OK) -class MechanicView(APIView): +class MechanicView(APIView, LimitOffsetPagination): """ Mechanic view to fetch all the mechanics """ + @jwt_auth_required def get(self, request, user=None): """ @@ -106,10 +131,24 @@ def get(self, request, user=None): mechanics list and 200 status if no error message and corresponding status if error """ - mechanics = Mechanic.objects.all() - serializer = MechanicSerializer(mechanics, many=True) + mechanics = Mechanic.objects.all().order_by("id") + paginated = self.paginate_queryset(mechanics, request) + if paginated is None: + return Response( + {"message": messages.NO_OBJECT_FOUND}, + status=status.HTTP_400_BAD_REQUEST, + ) + serializer = MechanicSerializer(paginated, many=True) response_data = dict( - mechanics=serializer.data + mechanics=serializer.data, + previous_offset=( + self.offset - self.limit if self.offset - self.limit >= 0 else None + ), + next_offset=( + self.offset + self.limit + if self.offset + self.limit < self.count + else None + ), ) return Response(response_data, status=status.HTTP_200_OK) @@ -118,6 +157,7 @@ class ReceiveReportView(APIView): """ View to receive report from contact mechanic feature """ + def get(self, request): """ receive_report endpoint for mechanic @@ -130,31 +170,38 @@ def get(self, request): """ serializer = ReceiveReportSerializer(data=request.GET) if not serializer.is_valid(): - log_error(request.path, request.data, status.HTTP_400_BAD_REQUEST, serializer.errors) + log_error( + request.path, + request.data, + status.HTTP_400_BAD_REQUEST, + serializer.errors, + ) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) report_details = serializer.data - mechanic = Mechanic.objects.get(mechanic_code=report_details['mechanic_code']) - vehicle = Vehicle.objects.get(vin=report_details['vin']) + mechanic = Mechanic.objects.get(mechanic_code=report_details["mechanic_code"]) + vehicle = Vehicle.objects.get(vin=report_details["vin"]) service_request = ServiceRequest.objects.create( vehicle=vehicle, mechanic=mechanic, - problem_details=report_details['problem_details'], - created_on=timezone.now() + problem_details=report_details["problem_details"], + created_on=timezone.now(), ) service_request.save() - report_link = "{}?report_id={}".format(reverse("get-mechanic-report"), service_request.id) + report_link = "{}?report_id={}".format( + reverse("get-mechanic-report"), service_request.id + ) report_link = request.build_absolute_uri(report_link) - return Response({ - 'id': service_request.id, - 'sent': True, - 'report_link': report_link - }, status=status.HTTP_200_OK) + return Response( + {"id": service_request.id, "sent": True, "report_link": report_link}, + status=status.HTTP_200_OK, + ) class GetReportView(APIView): """ View to get only particular service request """ + @jwt_auth_required def get(self, request, user=None): """ @@ -165,33 +212,38 @@ def get(self, request, user=None): service request object and 200 status if no error message and corresponding status if error """ - report_id = request.GET['report_id'] + report_id = request.GET["report_id"] if not report_id: return Response( - {'message': messages.REPORT_ID_MISSING}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.REPORT_ID_MISSING}, + status=status.HTTP_400_BAD_REQUEST, ) if not report_id.isnumeric(): return Response( - {'message': messages.INVALID_REPORT_ID}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.INVALID_REPORT_ID}, + status=status.HTTP_400_BAD_REQUEST, ) service_request = ServiceRequest.objects.filter(id=report_id).first() if not service_request: return Response( - {'message': messages.REPORT_DOES_NOT_EXIST}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.REPORT_DOES_NOT_EXIST}, + status=status.HTTP_400_BAD_REQUEST, ) serializer = ServiceRequestSerializer(service_request) response_data = dict(serializer.data) return Response(response_data, status=status.HTTP_200_OK) -class ServiceRequestsView(APIView): +class ServiceRequestsView(APIView, LimitOffsetPagination): """ View to return all the service requests """ + + def __init__(self): + super(ServiceRequestsView, self).__init__() + self.default_limit = settings.DEFAULT_LIMIT + @jwt_auth_required def get(self, request, user=None): """ @@ -204,24 +256,27 @@ def get(self, request, user=None): list of service request object and 200 status if no error message and corresponding status if error """ - limit = request.GET.get('limit', str(settings.DEFAULT_LIMIT)) - offset = request.GET.get('offset', str(settings.DEFAULT_OFFSET)) - if not limit.isdigit() or not offset.isdigit(): + + service_requests = ServiceRequest.objects.filter(mechanic__user=user).order_by( + "id" + ) + paginated = self.paginate_queryset(service_requests, request) + if paginated is None: return Response( - {'message': messages.INVALID_LIMIT_OR_OFFSET}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.NO_OBJECT_FOUND}, + status=status.HTTP_400_BAD_REQUEST, ) - limit = int(limit) - offset = int(offset) - if limit > settings.MAX_LIMIT: - limit = 100 - if limit < 0: - limit = settings.DEFAULT_LIMIT - if offset < 0: - offset = settings.DEFAULT_OFFSET - service_requests = ServiceRequest.objects.filter(mechanic__user=user).order_by('id')[offset:offset+limit] serializer = ServiceRequestSerializer(service_requests, many=True) response_data = dict( - service_requests=serializer.data + service_requests=serializer.data, + next_offset=( + self.offset + self.limit + if self.offset + self.limit < self.count + else None + ), + previous_offset=( + self.offset - self.limit if self.offset - self.limit >= 0 else None + ), + count=self.get_count(paginated), ) return Response(response_data, status=status.HTTP_200_OK) diff --git a/services/workshop/crapi/merchant/__init__.py b/services/workshop/crapi/merchant/__init__.py index bfd4bb9d..e9b106a0 100644 --- a/services/workshop/crapi/merchant/__init__.py +++ b/services/workshop/crapi/merchant/__init__.py @@ -10,5 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - diff --git a/services/workshop/crapi/merchant/serializers.py b/services/workshop/crapi/merchant/serializers.py index 09074715..9bcbea1a 100644 --- a/services/workshop/crapi/merchant/serializers.py +++ b/services/workshop/crapi/merchant/serializers.py @@ -22,6 +22,7 @@ class ContactMechanicSerializer(serializers.Serializer): """ Serializer for Contact Mechanic model. """ + mechanic_api = serializers.CharField() repeat_request_if_failed = serializers.BooleanField(required=False) number_of_repeats = serializers.IntegerField(required=False) diff --git a/services/workshop/crapi/merchant/tests.py b/services/workshop/crapi/merchant/tests.py index 7ba8f65e..0b73ca4f 100644 --- a/services/workshop/crapi/merchant/tests.py +++ b/services/workshop/crapi/merchant/tests.py @@ -14,9 +14,13 @@ contains all the test cases related to merchant """ from unittest.mock import patch -from utils.mock_methods import get_sample_mechanic_data, mock_jwt_auth_required, get_sample_user_data +from utils.mock_methods import ( + get_sample_mechanic_data, + mock_jwt_auth_required, + get_sample_user_data, +) -patch('utils.jwt.jwt_auth_required', mock_jwt_auth_required).start() +patch("utils.jwt.jwt_auth_required", mock_jwt_auth_required).start() import bcrypt from django.test import TestCase, Client @@ -47,49 +51,53 @@ def setUp(self): """ self.client = Client() self.mechanic = get_sample_mechanic_data() - self.client.post('/workshop/api/mechanic/signup', - self.mechanic, - content_type="application/json") + self.client.post( + "/workshop/api/mechanic/signup", + self.mechanic, + content_type="application/json", + ) user_data = get_sample_user_data() self.user = User.objects.create( id=2, - email=user_data['email'], - number=user_data['number'], - password=bcrypt.hashpw(user_data['password'].encode('utf-8'), - bcrypt.gensalt()).decode(), + email=user_data["email"], + number=user_data["number"], + password=bcrypt.hashpw( + user_data["password"].encode("utf-8"), bcrypt.gensalt() + ).decode(), role=User.ROLE_CHOICES.USER, - created_on=timezone.now()) - self.user_auth_headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + user_data['email'] - } + created_on=timezone.now(), + ) + self.user_auth_headers = {"HTTP_AUTHORIZATION": "Bearer " + user_data["email"]} self.mechanic_auth_headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + self.mechanic['email'] + "HTTP_AUTHORIZATION": "Bearer " + self.mechanic["email"] } - self.vehicle_company = VehicleCompany.objects.create( - name='RandomCompany') + self.vehicle_company = VehicleCompany.objects.create(name="RandomCompany") self.vehicle_model = VehicleModel.objects.create( - fuel_type='1', - model='NewModel', - vehicle_img='Image', - vehiclecompany=self.vehicle_company) - - self.vehicle = Vehicle.objects.create(pincode='1234', - vin='9NFXO86WBWA082766', - year='2020', - status='ACTIVE', - owner=self.user, - vehicle_model=self.vehicle_model) + fuel_type="1", + model="NewModel", + vehicle_img="Image", + vehiclecompany=self.vehicle_company, + ) + + self.vehicle = Vehicle.objects.create( + pincode="1234", + vin="9NFXO86WBWA082766", + year="2020", + status="ACTIVE", + owner=self.user, + vehicle_model=self.vehicle_model, + ) self.contact_mechanic_request_body = { - 'mechanic_api': 'https://www.google.com', - 'repeat_request_if_failed': True, - 'number_of_repeats': 5, - 'mechanic_code': self.mechanic['mechanic_code'], - 'vin': self.vehicle.vin, - 'problem_details': 'My Car is not working', + "mechanic_api": "https://www.google.com", + "repeat_request_if_failed": True, + "number_of_repeats": 5, + "mechanic_code": self.mechanic["mechanic_code"], + "vin": self.vehicle.vin, + "problem_details": "My Car is not working", } def test_max_retries_exceeded(self): @@ -98,14 +106,15 @@ def test_max_retries_exceeded(self): should get an error message :return: None """ - self.contact_mechanic_request_body['number_of_repeats'] = 110 - res = self.client.post('/workshop/api/merchant/contact_mechanic', - self.contact_mechanic_request_body, - **self.user_auth_headers, - content_type="application/json") + self.contact_mechanic_request_body["number_of_repeats"] = 110 + res = self.client.post( + "/workshop/api/merchant/contact_mechanic", + self.contact_mechanic_request_body, + **self.user_auth_headers, + content_type="application/json" + ) self.assertNotEqual(res.status_code, 200) - self.assertEqual(res.json()['message'], - messages.NO_OF_REPEATS_EXCEEDED) + self.assertEqual(res.json()["message"], messages.NO_OF_REPEATS_EXCEEDED) def test_wrong_mechanic_api(self): """ @@ -113,12 +122,15 @@ def test_wrong_mechanic_api(self): should get an error message :return: None """ - self.contact_mechanic_request_body['mechanic_api'] = \ - 'https://jsonplaceholder.typicode.com/post' - res = self.client.post('/workshop/api/merchant/contact_mechanic', - self.contact_mechanic_request_body, - **self.user_auth_headers, - content_type="application/json") + self.contact_mechanic_request_body["mechanic_api"] = ( + "https://jsonplaceholder.typicode.com/post" + ) + res = self.client.post( + "/workshop/api/merchant/contact_mechanic", + self.contact_mechanic_request_body, + **self.user_auth_headers, + content_type="application/json" + ) self.assertNotEqual(res.status_code, 200) def test_contact_mechanic(self): @@ -127,13 +139,14 @@ def test_contact_mechanic(self): should get a valid response from mechanic_api :return: None """ - res = self.client.post('/workshop/api/merchant/contact_mechanic', - self.contact_mechanic_request_body, - **self.user_auth_headers, - content_type="application/json") + res = self.client.post( + "/workshop/api/merchant/contact_mechanic", + self.contact_mechanic_request_body, + **self.user_auth_headers, + content_type="application/json" + ) self.assertEqual(res.status_code, 200) - self.assertIn('Google', - res.json()['response_from_mechanic_api']) + self.assertIn("Google", res.json()["response_from_mechanic_api"]) def test_repeat_missing_request(self): """ @@ -141,11 +154,13 @@ def test_repeat_missing_request(self): should get a bad request response :return: None """ - del self.contact_mechanic_request_body['repeat_request_if_failed'] - res = self.client.post('/workshop/api/merchant/contact_mechanic', - self.contact_mechanic_request_body, - **self.user_auth_headers, - content_type="application/json") + del self.contact_mechanic_request_body["repeat_request_if_failed"] + res = self.client.post( + "/workshop/api/merchant/contact_mechanic", + self.contact_mechanic_request_body, + **self.user_auth_headers, + content_type="application/json" + ) self.assertEqual(res.status_code, 200) def test_receive_report_and_get_report(self): @@ -161,28 +176,40 @@ def test_receive_report_and_get_report(self): should get the same report :return: None """ - res = self.client.get('/workshop/api/mechanic/receive_report', - self.contact_mechanic_request_body, - **self.user_auth_headers, - content_type="application/json") + res = self.client.get( + "/workshop/api/mechanic/receive_report", + self.contact_mechanic_request_body, + **self.user_auth_headers, + content_type="application/json" + ) self.assertEqual(res.status_code, 200) - self.assertTrue(res.json()['sent']) - self.assertIn('report_link', res.json()) - - report_res = self.client.get(res.json()['report_link'], - **self.user_auth_headers, - content_type="application/json") + self.assertTrue(res.json()["sent"]) + self.assertIn("report_link", res.json()) + + report_res = self.client.get( + res.json()["report_link"], + **self.user_auth_headers, + content_type="application/json" + ) self.assertEqual(report_res.status_code, 200) - self.assertEqual(report_res.json()['problem_details'], - self.contact_mechanic_request_body['problem_details']) - self.assertEqual(report_res.json()['mechanic']['mechanic_code'], - self.contact_mechanic_request_body['mechanic_code']) - self.assertEqual(report_res.json()['vehicle']['vin'], - self.contact_mechanic_request_body['vin']) + self.assertEqual( + report_res.json()["problem_details"], + self.contact_mechanic_request_body["problem_details"], + ) + self.assertEqual( + report_res.json()["mechanic"]["mechanic_code"], + self.contact_mechanic_request_body["mechanic_code"], + ) + self.assertEqual( + report_res.json()["vehicle"]["vin"], + self.contact_mechanic_request_body["vin"], + ) service_requests = self.client.get( - '/workshop/api/mechanic/service_requests', + "/workshop/api/mechanic/service_requests", **self.mechanic_auth_headers, - content_type="application/json") - self.assertEqual(service_requests.json()['service_requests'][0], - report_res.json()) + content_type="application/json" + ) + self.assertEqual( + service_requests.json()["service_requests"][0], report_res.json() + ) diff --git a/services/workshop/crapi/merchant/urls.py b/services/workshop/crapi/merchant/urls.py index e69d37d3..963a37f2 100644 --- a/services/workshop/crapi/merchant/urls.py +++ b/services/workshop/crapi/merchant/urls.py @@ -21,5 +21,5 @@ import crapi.merchant.views as merchant_views urlpatterns = [ - re_path(r'contact_mechanic$', merchant_views.ContactMechanicView.as_view()), + re_path(r"contact_mechanic$", merchant_views.ContactMechanicView.as_view()), ] diff --git a/services/workshop/crapi/merchant/views.py b/services/workshop/crapi/merchant/views.py index 06f516c5..d2dcf51b 100644 --- a/services/workshop/crapi/merchant/views.py +++ b/services/workshop/crapi/merchant/views.py @@ -33,6 +33,7 @@ class ContactMechanicView(APIView): """ View for contact mechanic feature """ + @jwt_auth_required def post(self, request, user=None): """ @@ -49,32 +50,37 @@ def post(self, request, user=None): request_data = request.data serializer = ContactMechanicSerializer(data=request_data) if not serializer.is_valid(): - log_error(request.path, request.data, status.HTTP_400_BAD_REQUEST, serializer.errors) + log_error( + request.path, + request.data, + status.HTTP_400_BAD_REQUEST, + serializer.errors, + ) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - repeat_request_if_failed = request_data.get('repeat_request_if_failed', False) - number_of_repeats = request_data.get('number_of_repeats', 1) + repeat_request_if_failed = request_data.get("repeat_request_if_failed", False) + number_of_repeats = request_data.get("number_of_repeats", 1) if repeat_request_if_failed and number_of_repeats < 1: return Response( - {'message': messages.MIN_NO_OF_REPEATS_FAILED}, - status=status.HTTP_503_SERVICE_UNAVAILABLE + {"message": messages.MIN_NO_OF_REPEATS_FAILED}, + status=status.HTTP_503_SERVICE_UNAVAILABLE, ) elif repeat_request_if_failed and number_of_repeats > 100: return Response( - {'message': messages.NO_OF_REPEATS_EXCEEDED}, - status=status.HTTP_503_SERVICE_UNAVAILABLE + {"message": messages.NO_OF_REPEATS_EXCEEDED}, + status=status.HTTP_503_SERVICE_UNAVAILABLE, ) repeat_count = 0 while True: - request_url=request_data['mechanic_api'] + request_url = request_data["mechanic_api"] logger.info(f"Repeat count: {repeat_count}, mechanic_api: {request_url}") try: mechanic_response = requests.get( request_url, params=request_data, - headers={'Authorization': request.META.get('HTTP_AUTHORIZATION')}, - verify=False + headers={"Authorization": request.META.get("HTTP_AUTHORIZATION")}, + verify=False, ) if mechanic_response.status_code == status.HTTP_200_OK: logger.info(f"Got a valid response at repeat count: {repeat_count}") @@ -86,17 +92,17 @@ def post(self, request, user=None): repeat_count += 1 except (MissingSchema, InvalidURL) as e: log_error(request.path, request.data, status.HTTP_400_BAD_REQUEST, e) - return Response({'message': str(e)}, status=status.HTTP_400_BAD_REQUEST) + return Response({"message": str(e)}, status=status.HTTP_400_BAD_REQUEST) except requests.exceptions.ConnectionError as e: if not repeat_request_if_failed: return Response( - {'message': messages.COULD_NOT_CONNECT}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.COULD_NOT_CONNECT}, + status=status.HTTP_400_BAD_REQUEST, ) if repeat_count == number_of_repeats: return Response( - {'message': messages.COULD_NOT_CONNECT}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.COULD_NOT_CONNECT}, + status=status.HTTP_400_BAD_REQUEST, ) repeat_count += 1 continue @@ -105,7 +111,10 @@ def post(self, request, user=None): mechanic_response = mechanic_response.json() except ValueError: mechanic_response = mechanic_response.text - return Response({ - 'response_from_mechanic_api': mechanic_response, - 'status': mechanic_response_status - }, status=mechanic_response_status) + return Response( + { + "response_from_mechanic_api": mechanic_response, + "status": mechanic_response_status, + }, + status=mechanic_response_status, + ) diff --git a/services/workshop/crapi/migrations/0001_initial.py b/services/workshop/crapi/migrations/0001_initial.py index b0cbd678..a9882af9 100644 --- a/services/workshop/crapi/migrations/0001_initial.py +++ b/services/workshop/crapi/migrations/0001_initial.py @@ -19,158 +19,264 @@ import django.db.models.deletion import django_db_cascade + class Migration(migrations.Migration): initial = True - dependencies = [ - ] + dependencies = [] operations = [ migrations.CreateModel( - name='User', + name="User", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('created_on', models.DateTimeField()), - ('email', models.CharField(max_length=255, unique=True)), - ('jwt_token', models.CharField(max_length=500, null=True, unique=True)), - ('number', models.CharField(max_length=255, null=True)), - ('password', models.CharField(max_length=255)), - ('role', models.IntegerField(choices=[(1, 'User'), (2, 'Mechanic'), (3, 'Admin')], default=1)), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("created_on", models.DateTimeField()), + ("email", models.CharField(max_length=255, unique=True)), + ("jwt_token", models.CharField(max_length=500, null=True, unique=True)), + ("number", models.CharField(max_length=255, null=True)), + ("password", models.CharField(max_length=255)), + ( + "role", + models.IntegerField( + choices=[(1, "User"), (2, "Mechanic"), (3, "Admin")], default=1 + ), + ), ], - options={ - 'db_table': 'user_login', - 'managed': settings.IS_TESTING - }, + options={"db_table": "user_login", "managed": settings.IS_TESTING}, ), migrations.CreateModel( - name='VehicleCompany', + name="VehicleCompany", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('name', models.CharField(max_length=255)), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("name", models.CharField(max_length=255)), ], - options={ - 'db_table': 'vehicle_company', - 'managed': settings.IS_TESTING - }, + options={"db_table": "vehicle_company", "managed": settings.IS_TESTING}, ), migrations.CreateModel( - name='VehicleModel', + name="VehicleModel", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('fuel_type', models.BigIntegerField()), - ('model', models.CharField(max_length=255)), - ('vehicle_img', models.CharField(max_length=255, null=True)), - ('vehiclecompany', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crapi.VehicleCompany')) + ("id", models.AutoField(primary_key=True, serialize=False)), + ("fuel_type", models.BigIntegerField()), + ("model", models.CharField(max_length=255)), + ("vehicle_img", models.CharField(max_length=255, null=True)), + ( + "vehiclecompany", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="crapi.VehicleCompany", + ), + ), ], - options={ - 'db_table': 'vehicle_model', - 'managed': settings.IS_TESTING - }, + options={"db_table": "vehicle_model", "managed": settings.IS_TESTING}, ), migrations.CreateModel( - name='Vehicle', + name="Vehicle", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('pincode', models.CharField(max_length=255, null=True)), - ('vin', models.CharField(max_length=255)), - ('year', models.BigIntegerField(null=True)), - ('vehicle_model', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crapi.VehicleModel')), - ('status', models.CharField(max_length=255)), - ('location_id', models.BigIntegerField(null=True)), - ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crapi.User')), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("pincode", models.CharField(max_length=255, null=True)), + ("vin", models.CharField(max_length=255)), + ("year", models.BigIntegerField(null=True)), + ( + "vehicle_model", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="crapi.VehicleModel", + ), + ), + ("status", models.CharField(max_length=255)), + ("location_id", models.BigIntegerField(null=True)), + ( + "owner", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="crapi.User" + ), + ), ], - options={ - 'db_table': 'vehicle_details', - 'managed': settings.IS_TESTING - }, + options={"db_table": "vehicle_details", "managed": settings.IS_TESTING}, ), migrations.CreateModel( - name='UserDetails', + name="UserDetails", fields=[ - ('id', models.AutoField(primary_key=True, serialize=False)), - ('available_credit', models.FloatField()), - ('name', models.CharField(max_length=255, null=True)), - ('status', models.CharField(max_length=255, null=True)), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='crapi.User')), + ("id", models.AutoField(primary_key=True, serialize=False)), + ("available_credit", models.FloatField()), + ("name", models.CharField(max_length=255, null=True)), + ("status", models.CharField(max_length=255, null=True)), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="crapi.User" + ), + ), ], - options={ - 'db_table': 'user_details', - 'managed': settings.IS_TESTING - }, + options={"db_table": "user_details", "managed": settings.IS_TESTING}, ), migrations.CreateModel( - name='Mechanic', + name="Mechanic", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('mechanic_code', models.CharField(max_length=100, unique=True)), - ('user', django_db_cascade.fields.ForeignKey(on_delete=django_db_cascade.deletions.DB_CASCADE, to='crapi.User')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("mechanic_code", models.CharField(max_length=100, unique=True)), + ( + "user", + django_db_cascade.fields.ForeignKey( + on_delete=django_db_cascade.deletions.DB_CASCADE, + to="crapi.User", + ), + ), ], options={ - 'db_table': 'mechanic', + "db_table": "mechanic", }, ), migrations.CreateModel( - name='Product', + name="Product", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('price', models.DecimalField(decimal_places=2, max_digits=20)), - ('image_url', models.CharField(max_length=255)), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ("price", models.DecimalField(decimal_places=2, max_digits=20)), + ("image_url", models.CharField(max_length=255)), ], options={ - 'db_table': 'product', + "db_table": "product", }, ), migrations.CreateModel( - name='ServiceRequest', + name="ServiceRequest", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('problem_details', models.CharField(blank=True, max_length=500)), - ('created_on', models.DateTimeField()), - ('updated_on', models.DateTimeField(null=True)), - ('status', models.CharField(choices=[('Pending', 'Pending'), ('Finished', 'Finished')], default='Pending', max_length=10)), - ('mechanic', django_db_cascade.fields.ForeignKey(on_delete=django_db_cascade.deletions.DB_CASCADE, to='crapi.Mechanic')), - ('vehicle', django_db_cascade.fields.ForeignKey(on_delete=django_db_cascade.deletions.DB_CASCADE, to='crapi.Vehicle')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("problem_details", models.CharField(blank=True, max_length=500)), + ("created_on", models.DateTimeField()), + ("updated_on", models.DateTimeField(null=True)), + ( + "status", + models.CharField( + choices=[("pending", "Pending"), ("finished", "Finished")], + default="pending", + max_length=10, + ), + ), + ( + "mechanic", + django_db_cascade.fields.ForeignKey( + on_delete=django_db_cascade.deletions.DB_CASCADE, + to="crapi.Mechanic", + ), + ), + ( + "vehicle", + django_db_cascade.fields.ForeignKey( + on_delete=django_db_cascade.deletions.DB_CASCADE, + to="crapi.Vehicle", + ), + ), ], options={ - 'db_table': 'service_request', + "db_table": "service_request", }, ), migrations.CreateModel( - name='Order', + name="Order", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('quantity', models.IntegerField(default=1)), - ('created_on', models.DateTimeField()), - ('status', models.CharField(choices=[('delivered', 'delivered'), ('return pending', 'return pending'), ('returned', 'returned')], default='delivered', max_length=20)), - ('product', django_db_cascade.fields.ForeignKey(on_delete=django_db_cascade.deletions.DB_CASCADE, to='crapi.Product')), - ('user', django_db_cascade.fields.ForeignKey(on_delete=django_db_cascade.deletions.DB_CASCADE, to='crapi.User')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("quantity", models.IntegerField(default=1)), + ("created_on", models.DateTimeField()), + ( + "status", + models.CharField( + choices=[ + ("delivered", "delivered"), + ("return pending", "return pending"), + ("returned", "returned"), + ], + default="delivered", + max_length=20, + ), + ), + ( + "product", + django_db_cascade.fields.ForeignKey( + on_delete=django_db_cascade.deletions.DB_CASCADE, + to="crapi.Product", + ), + ), + ( + "user", + django_db_cascade.fields.ForeignKey( + on_delete=django_db_cascade.deletions.DB_CASCADE, + to="crapi.User", + ), + ), ], options={ - 'db_table': 'order', + "db_table": "order", }, ), migrations.CreateModel( - name='AppliedCoupon', + name="AppliedCoupon", fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('coupon_code', models.CharField(max_length=255)), - ('user', django_db_cascade.fields.ForeignKey(on_delete=django_db_cascade.deletions.DB_CASCADE, to='crapi.User')), + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("coupon_code", models.CharField(max_length=255)), + ( + "user", + django_db_cascade.fields.ForeignKey( + on_delete=django_db_cascade.deletions.DB_CASCADE, + to="crapi.User", + ), + ), ], options={ - 'db_table': 'applied_coupon', + "db_table": "applied_coupon", }, ), migrations.CreateModel( - name='Coupon', + name="Coupon", fields=[ - ('coupon_code', models.CharField(max_length=255, primary_key=True, serialize=False)), - ('amount', models.CharField(max_length=255)), + ( + "coupon_code", + models.CharField(max_length=255, primary_key=True, serialize=False), + ), + ("amount", models.CharField(max_length=255)), ], - options={ - 'db_table': 'coupons', - 'managed': settings.IS_TESTING - }, + options={"db_table": "coupons", "managed": settings.IS_TESTING}, ), ] diff --git a/services/workshop/crapi/migrations/0002_order_transaction_id.py b/services/workshop/crapi/migrations/0002_order_transaction_id.py index 5d2394bc..ae727107 100644 --- a/services/workshop/crapi/migrations/0002_order_transaction_id.py +++ b/services/workshop/crapi/migrations/0002_order_transaction_id.py @@ -7,13 +7,13 @@ class Migration(migrations.Migration): dependencies = [ - ('crapi', '0001_initial'), + ("crapi", "0001_initial"), ] operations = [ migrations.AddField( - model_name='order', - name='transaction_id', + model_name="order", + name="transaction_id", field=models.CharField(default=uuid.uuid4, max_length=255), ), ] diff --git a/services/workshop/crapi/migrations/0003_alter_appliedcoupon_id_alter_mechanic_id_and_more.py b/services/workshop/crapi/migrations/0003_alter_appliedcoupon_id_alter_mechanic_id_and_more.py index 2062d1b1..baec155e 100644 --- a/services/workshop/crapi/migrations/0003_alter_appliedcoupon_id_alter_mechanic_id_and_more.py +++ b/services/workshop/crapi/migrations/0003_alter_appliedcoupon_id_alter_mechanic_id_and_more.py @@ -6,33 +6,33 @@ class Migration(migrations.Migration): dependencies = [ - ('crapi', '0002_order_transaction_id'), + ("crapi", "0002_order_transaction_id"), ] operations = [ migrations.AlterField( - model_name='appliedcoupon', - name='id', + model_name="appliedcoupon", + name="id", field=models.AutoField(primary_key=True, serialize=False), ), migrations.AlterField( - model_name='mechanic', - name='id', + model_name="mechanic", + name="id", field=models.AutoField(primary_key=True, serialize=False), ), migrations.AlterField( - model_name='order', - name='id', + model_name="order", + name="id", field=models.AutoField(primary_key=True, serialize=False), ), migrations.AlterField( - model_name='product', - name='id', + model_name="product", + name="id", field=models.AutoField(primary_key=True, serialize=False), ), migrations.AlterField( - model_name='servicerequest', - name='id', + model_name="servicerequest", + name="id", field=models.AutoField(primary_key=True, serialize=False), ), ] diff --git a/services/workshop/crapi/migrations/__init__.py b/services/workshop/crapi/migrations/__init__.py index bfd4bb9d..e9b106a0 100644 --- a/services/workshop/crapi/migrations/__init__.py +++ b/services/workshop/crapi/migrations/__init__.py @@ -10,5 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - diff --git a/services/workshop/crapi/models.py b/services/workshop/crapi/models.py index bfd4bb9d..e9b106a0 100644 --- a/services/workshop/crapi/models.py +++ b/services/workshop/crapi/models.py @@ -10,5 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - diff --git a/services/workshop/crapi/shop/__init__.py b/services/workshop/crapi/shop/__init__.py index bfd4bb9d..e9b106a0 100644 --- a/services/workshop/crapi/shop/__init__.py +++ b/services/workshop/crapi/shop/__init__.py @@ -10,5 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - diff --git a/services/workshop/crapi/shop/models.py b/services/workshop/crapi/shop/models.py index 043ea4d4..9003fdb5 100644 --- a/services/workshop/crapi/shop/models.py +++ b/services/workshop/crapi/shop/models.py @@ -26,18 +26,20 @@ from django_db_cascade.deletions import DB_CASCADE from django.db.models import DO_NOTHING, SET_NULL + class Product(models.Model): """ Product Model represents a product in the application """ + id = models.AutoField(primary_key=True) name = models.CharField(max_length=255) price = models.DecimalField(max_digits=20, decimal_places=2) image_url = models.CharField(max_length=255) class Meta: - db_table = 'product' + db_table = "product" def __str__(self): return f"{self.name} - {self.price}" @@ -48,6 +50,7 @@ class Order(models.Model): Order Model represents an order in the application """ + id = models.AutoField(primary_key=True) user = ForeignKey(User, DB_CASCADE) product = ForeignKey(Product, DB_CASCADE) @@ -58,42 +61,48 @@ class Order(models.Model): STATUS_CHOICES = Choices( ("DELIVERED", "delivered", "delivered"), ("RETURN_PENDING", "return pending", "return pending"), - ("RETURNED", "returned", "returned") + ("RETURNED", "returned", "returned"), + ) + status = models.CharField( + max_length=20, choices=STATUS_CHOICES, default=STATUS_CHOICES.DELIVERED ) - status = models.CharField(max_length=20, choices=STATUS_CHOICES, default=STATUS_CHOICES.DELIVERED) class Meta: - db_table = 'order' + db_table = "order" def __str__(self): return f"{self.user.email} - {self.product.name} " + class Coupon(models.Model): """ AppliedCoupon Model represents a mapping between coupon_code and user """ + coupon_code = models.CharField(max_length=255, primary_key=True) amount = models.CharField(max_length=255) class Meta: - db_table = 'coupons' + db_table = "coupons" managed = settings.IS_TESTING def __str__(self): return f"{self.coupon_code} - {self.amount}" + class AppliedCoupon(models.Model): """ AppliedCoupon Model represents a mapping between coupon_code and user """ + id = models.AutoField(primary_key=True) user = ForeignKey(User, DB_CASCADE) coupon_code = models.CharField(max_length=255) class Meta: - db_table = 'applied_coupon' + db_table = "applied_coupon" def __str__(self): return f"{self.user.email} - {self.coupon_code} " diff --git a/services/workshop/crapi/shop/serializers.py b/services/workshop/crapi/shop/serializers.py index 74ef6f35..b5422f82 100644 --- a/services/workshop/crapi/shop/serializers.py +++ b/services/workshop/crapi/shop/serializers.py @@ -30,14 +30,16 @@ class Meta: """ Meta class for ProductSerializer """ + model = Product - fields = ('id', 'name', 'price', 'image_url') + fields = ("id", "name", "price", "image_url") class OrderSerializer(serializers.ModelSerializer): """ Serializer for Order model """ + user = UserSerializer() product = ProductSerializer() @@ -45,20 +47,32 @@ class Meta: """ Meta class for OrderSerializer """ + model = Order - fields = ('id', 'user', 'product', 'quantity', 'status', 'transaction_id', 'created_on') + fields = ( + "id", + "user", + "product", + "quantity", + "status", + "transaction_id", + "created_on", + ) class CouponSerializer(serializers.Serializer): """ Serializer for Coupon model """ + coupon_code = serializers.CharField() amount = serializers.IntegerField() + class Meta: """ Meta class for CouponSerializer """ + model = Coupon @@ -66,5 +80,6 @@ class ProductQuantitySerializer(serializers.Serializer): """ Serializer for Product order API """ + product_id = serializers.IntegerField() quantity = serializers.IntegerField() diff --git a/services/workshop/crapi/shop/tests.py b/services/workshop/crapi/shop/tests.py index 312a8883..e3e912b5 100644 --- a/services/workshop/crapi/shop/tests.py +++ b/services/workshop/crapi/shop/tests.py @@ -16,7 +16,7 @@ from unittest.mock import patch from utils.mock_methods import get_sample_user_data, mock_jwt_auth_required -patch('utils.jwt.jwt_auth_required', mock_jwt_auth_required).start() +patch("utils.jwt.jwt_auth_required", mock_jwt_auth_required).start() import logging import bcrypt @@ -27,7 +27,7 @@ from crapi.user.models import User, UserDetails from crapi.shop.models import Coupon -logger = logging.getLogger('ProductTest') +logger = logging.getLogger("ProductTest") class ProductTestCase(TestCase): @@ -41,7 +41,7 @@ class ProductTestCase(TestCase): order_id: Order Identifier of the prder created """ - databases = '__all__' + databases = "__all__" def setUp(self): """ @@ -51,29 +51,33 @@ def setUp(self): """ self.client = Client() - self.coupon = Coupon.objects.using('mongodb').create( - coupon_code='TRAC075', amount=75) + self.coupon = Coupon.objects.using("mongodb").create( + coupon_code="TRAC075", amount=75 + ) - self.coupon = Coupon.objects.using('mongodb').create( - coupon_code='TRAC100', amount=100) + self.coupon = Coupon.objects.using("mongodb").create( + coupon_code="TRAC100", amount=100 + ) user_data = get_sample_user_data() self.user = User.objects.create( - email=user_data['email'], - number=user_data['number'], - password=bcrypt.hashpw(user_data['password'].encode('utf-8'), - bcrypt.gensalt()).decode(), + email=user_data["email"], + number=user_data["number"], + password=bcrypt.hashpw( + user_data["password"].encode("utf-8"), bcrypt.gensalt() + ).decode(), role=User.ROLE_CHOICES.USER, - created_on=timezone.now()) + created_on=timezone.now(), + ) - self.user_details = UserDetails.objects.create(available_credit=100, - name=user_data['name'], - status='ACTIVE', - user=self.user) + self.user_details = UserDetails.objects.create( + available_credit=100, + name=user_data["name"], + status="ACTIVE", + user=self.user, + ) - self.auth_headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + user_data['email'] - } + self.auth_headers = {"HTTP_AUTHORIZATION": "Bearer " + user_data["email"]} def add_product(self): """ @@ -82,16 +86,14 @@ def add_product(self): :return: None """ product_details = { - 'name': - 'test_Seat', - 'price': - 10, - 'image_url': - 'https://4.imimg.com/data4/NI/WE/MY-19393581/ciaz-car-seat-cover-500x500.jpg', + "name": "test_Seat", + "price": 10, + "image_url": "https://4.imimg.com/data4/NI/WE/MY-19393581/ciaz-car-seat-cover-500x500.jpg", } - res = self.client.post('/workshop/api/shop/products', product_details, - **self.auth_headers) - self.product_id = json.loads(res.content)['id'] + res = self.client.post( + "/workshop/api/shop/products", product_details, **self.auth_headers + ) + self.product_id = json.loads(res.content)["id"] self.assertEqual(res.status_code, 200) def apply_coupon(self): @@ -100,14 +102,16 @@ def apply_coupon(self): should get a valid response saying coupon applied :return: None """ - coupon_details = {'coupon_code': 'TRAC075', 'amount': 75} - res = self.client.post('/workshop/api/shop/apply_coupon', - data=coupon_details, - content_type='application/json', - **self.auth_headers) + coupon_details = {"coupon_code": "TRAC075", "amount": 75} + res = self.client.post( + "/workshop/api/shop/apply_coupon", + data=coupon_details, + content_type="application/json", + **self.auth_headers + ) logger.info(res.json()) self.assertEqual(res.status_code, 200) - self.assertEqual(res.json()['message'], messages.COUPON_APPLIED) + self.assertEqual(res.json()["message"], messages.COUPON_APPLIED) def test_apply_coupon_twice(self): """ @@ -115,20 +119,25 @@ def test_apply_coupon_twice(self): should get a error response saying coupon already applied :return: None """ - coupon_details = {'coupon_code': 'TRAC100', 'amount': 100} - res = self.client.post('/workshop/api/shop/apply_coupon', - data=coupon_details, - content_type='application/json', - **self.auth_headers) - res = self.client.post('/workshop/api/shop/apply_coupon', - data=coupon_details, - content_type='application/json', - **self.auth_headers) + coupon_details = {"coupon_code": "TRAC100", "amount": 100} + res = self.client.post( + "/workshop/api/shop/apply_coupon", + data=coupon_details, + content_type="application/json", + **self.auth_headers + ) + res = self.client.post( + "/workshop/api/shop/apply_coupon", + data=coupon_details, + content_type="application/json", + **self.auth_headers + ) logger.info(res.json()) self.assertEqual(res.status_code, 400) self.assertEqual( - res.json()['message'], coupon_details['coupon_code'] + ' ' + - messages.COUPON_ALREADY_APPLIED) + res.json()["message"], + coupon_details["coupon_code"] + " " + messages.COUPON_ALREADY_APPLIED, + ) def test_invalid_coupon(self): """ @@ -136,14 +145,16 @@ def test_invalid_coupon(self): should get a valid response saying coupon is invalid :return: None """ - coupon_details = {'coupon_code': 'TRAC105', 'amount': 75} - res = self.client.post('/workshop/api/shop/apply_coupon', - data=coupon_details, - content_type='application/json', - **self.auth_headers) + coupon_details = {"coupon_code": "TRAC105", "amount": 75} + res = self.client.post( + "/workshop/api/shop/apply_coupon", + data=coupon_details, + content_type="application/json", + **self.auth_headers + ) logger.info(res.json()) self.assertEqual(res.status_code, 400) - self.assertEqual(res.json()['message'], messages.COUPON_NOT_FOUND) + self.assertEqual(res.json()["message"], messages.COUPON_NOT_FOUND) def test_sql_injection(self): """ @@ -152,20 +163,22 @@ def test_sql_injection(self): :return: None """ coupon_details = { - 'coupon_code': - "'; SELECT number FROM user_login WHERE email='" + self.user.email, - 'amount': - 75 + "coupon_code": "'; SELECT number FROM user_login WHERE email='" + + self.user.email, + "amount": 75, } - res = self.client.post('/workshop/api/shop/apply_coupon', - data=coupon_details, - content_type='application/json', - **self.auth_headers) + res = self.client.post( + "/workshop/api/shop/apply_coupon", + data=coupon_details, + content_type="application/json", + **self.auth_headers + ) logger.info(res.json()) self.assertEqual(res.status_code, 400) self.assertEqual( - res.json()['message'], - self.user.number + ' ' + messages.COUPON_ALREADY_APPLIED) + res.json()["message"], + self.user.number + " " + messages.COUPON_ALREADY_APPLIED, + ) def create_order(self): """ @@ -174,11 +187,13 @@ def create_order(self): :return: order details """ order_details = {"product_id": str(self.product_id), "quantity": 1} - res = self.client.post('/workshop/api/shop/orders', - order_details, - content_type='application/json', - **self.auth_headers) - self.order_id = json.loads(res.content)['id'] + res = self.client.post( + "/workshop/api/shop/orders", + order_details, + content_type="application/json", + **self.auth_headers + ) + self.order_id = json.loads(res.content)["id"] self.assertEqual(res.status_code, 200) def test_unauthenticated_get_order(self): @@ -190,6 +205,5 @@ def test_unauthenticated_get_order(self): self.add_product() self.apply_coupon() self.create_order() - res = self.client.get('/workshop/api/shop/orders/' + - str(self.order_id)) + res = self.client.get("/workshop/api/shop/orders/" + str(self.order_id)) self.assertEqual(res.status_code, 200) diff --git a/services/workshop/crapi/shop/urls.py b/services/workshop/crapi/shop/urls.py index e84cb327..dc74121c 100644 --- a/services/workshop/crapi/shop/urls.py +++ b/services/workshop/crapi/shop/urls.py @@ -22,11 +22,15 @@ urlpatterns = [ # Do not change the order of URLs - re_path(r'products$', shop_views.ProductView.as_view()), - re_path(r'orders/all$', shop_views.OrderDetailsView.as_view()), - re_path(r'orders/return_order$', shop_views.ReturnOrder.as_view()), - re_path(r'orders/(?P\d+)$', shop_views.OrderControlView.as_view()), - re_path(r'orders$', shop_views.OrderControlView.as_view()), - re_path(r'apply_coupon$', shop_views.ApplyCouponView.as_view()), - re_path(r'return_qr_code$', shop_views.ReturnQRCodeView.as_view(), name="shop-return-qr-code"), + re_path(r"products$", shop_views.ProductView.as_view()), + re_path(r"orders/all$", shop_views.OrderDetailsView.as_view()), + re_path(r"orders/return_order$", shop_views.ReturnOrder.as_view()), + re_path(r"orders/(?P\d+)$", shop_views.OrderControlView.as_view()), + re_path(r"orders$", shop_views.OrderControlView.as_view()), + re_path(r"apply_coupon$", shop_views.ApplyCouponView.as_view()), + re_path( + r"return_qr_code$", + shop_views.ReturnQRCodeView.as_view(), + name="shop-return-qr-code", + ), ] diff --git a/services/workshop/crapi/shop/views.py b/services/workshop/crapi/shop/views.py index ec79bee3..a7965cea 100644 --- a/services/workshop/crapi/shop/views.py +++ b/services/workshop/crapi/shop/views.py @@ -27,8 +27,12 @@ from rest_framework.response import Response from rest_framework.views import APIView from utils.helper import basic_auth - -from crapi.shop.serializers import OrderSerializer, ProductSerializer, CouponSerializer, ProductQuantitySerializer +from crapi.shop.serializers import ( + OrderSerializer, + ProductSerializer, + CouponSerializer, + ProductQuantitySerializer, +) from crapi.user.serializers import UserSerializer from utils.jwt import jwt_auth_required from utils import messages @@ -36,11 +40,14 @@ from crapi.user.models import UserDetails from utils.logging import log_error from django.core.exceptions import ObjectDoesNotExist +from rest_framework.pagination import LimitOffsetPagination + -class ProductView(APIView): +class ProductView(APIView, LimitOffsetPagination): """ Product Controller View """ + @jwt_auth_required def get(self, request, user): """ @@ -54,11 +61,21 @@ def get(self, request, user): message and corresponding status if error """ user_details = UserDetails.objects.get(user=user) - products = Product.objects.all() - serializer = ProductSerializer(products, many=True) + products = Product.objects.all().order_by("-id") + paginated = self.paginate_queryset(products, request, view=self) + serializer = ProductSerializer(paginated, many=True) response_data = dict( products=serializer.data, credit=user_details.available_credit, + next_offset=( + self.offset + self.limit + if self.offset + self.limit < self.count + else None + ), + previous_offset=( + self.offset - self.limit if self.offset - self.limit >= 0 else None + ), + count=self.get_count(paginated), ) return Response(response_data, status=status.HTTP_200_OK) @@ -88,6 +105,7 @@ class OrderControlView(APIView): """ Order Controller View """ + def get(self, request, order_id=None, user=None): """ order view for fetching a particular order @@ -109,35 +127,37 @@ def get(self, request, order_id=None, user=None): try: user_dict = UserSerializer(user).data user_details = UserDetails.objects.get(user=user) - user_dict['name'] = user_details.name + user_dict["name"] = user_details.name gateway_endpoint = settings.API_GATEWAY_URL + "/v1/payment" - gateway_credential = basic_auth(settings.API_GATEWAY_USERNAME, settings.API_GATEWAY_PASSWORD) + gateway_credential = basic_auth( + settings.API_GATEWAY_USERNAME, settings.API_GATEWAY_PASSWORD + ) logging.debug(gateway_endpoint) - data = { - } - data['user'] = user_dict - data['order'] = order_serializer.data - data['amount'] = float(order.product.price) * int(order.quantity) + data = {} + data["user"] = user_dict + data["order"] = order_serializer.data + data["amount"] = float(order.product.price) * int(order.quantity) payment_response = requests.post( gateway_endpoint, headers={ "Authorization": gateway_credential, - "Content-Type": "application/json" - }, + "Content-Type": "application/json", + }, json=data, - verify=False + verify=False, ) if payment_response.status_code == 200: payment = payment_response.json() else: - logging.error("Payment response error, {}: {}".format(payment_response.status_code, payment_response.content)) + logging.error( + "Payment response error, {}: {}".format( + payment_response.status_code, payment_response.content + ) + ) logging.debug("payment response: {}".format(payment)) except Exception as e: logging.error(e, exc_info=True) - response_data = dict( - order=order_serializer.data, - payment=payment - ) + response_data = dict(order=order_serializer.data, payment=payment) return Response(response_data, status=status.HTTP_200_OK) @jwt_auth_required @@ -159,29 +179,37 @@ def post(self, request, order_id=None, user=None): request_data = request.data serializer = ProductQuantitySerializer(data=request_data) if not serializer.is_valid(): - log_error(request.path, request.data, status.HTTP_400_BAD_REQUEST, serializer.errors) + log_error( + request.path, + request.data, + status.HTTP_400_BAD_REQUEST, + serializer.errors, + ) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) - product = Product.objects.get(id=request_data['product_id']) + product = Product.objects.get(id=request_data["product_id"]) user_details = UserDetails.objects.get(user=user) if user_details.available_credit < product.price: return Response( - {'message': messages.INSUFFICIENT_BALANCE}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.INSUFFICIENT_BALANCE}, + status=status.HTTP_400_BAD_REQUEST, ) - user_details.available_credit -= float(product.price * request_data['quantity']) + user_details.available_credit -= float(product.price * request_data["quantity"]) order = Order.objects.create( user=user, product=product, - quantity=request_data['quantity'], + quantity=request_data["quantity"], created_on=timezone.now(), transaction_id=uuid.uuid4(), ) user_details.save() - return Response({ - 'id': order.id, - 'message': messages.ORDER_CREATED, - 'credit': user_details.available_credit, - }, status=status.HTTP_200_OK) + return Response( + { + "id": order.id, + "message": messages.ORDER_CREATED, + "credit": user_details.available_credit, + }, + status=status.HTTP_200_OK, + ) @jwt_auth_required def put(self, request, order_id=None, user=None): @@ -202,29 +230,32 @@ def put(self, request, order_id=None, user=None): request_data = request.data order = Order.objects.get(id=order_id) if user != order.user: - return Response({'message': messages.RESTRICTED}, status=status.HTTP_403_FORBIDDEN) - if 'quantity' in request_data: - order.quantity = request_data['quantity'] - if 'status' in request_data and not Order.STATUS_CHOICES.has_value(request_data['status']): return Response( - {'message': messages.INVALID_STATUS}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.RESTRICTED}, status=status.HTTP_403_FORBIDDEN + ) + if "quantity" in request_data: + order.quantity = request_data["quantity"] + if "status" in request_data and not Order.STATUS_CHOICES.has_value( + request_data["status"] + ): + return Response( + {"message": messages.INVALID_STATUS}, status=status.HTTP_400_BAD_REQUEST ) user_details = UserDetails.objects.get(user=order.user) - if 'status' in request_data and request_data['status'] != order.status: - order.status = request_data['status'] - if request_data['status'] == Order.STATUS_CHOICES.RETURNED.value: - user_details.available_credit += float(order.quantity * order.product.price) + if "status" in request_data and request_data["status"] != order.status: + order.status = request_data["status"] + if request_data["status"] == Order.STATUS_CHOICES.RETURNED.value: + user_details.available_credit += float( + order.quantity * order.product.price + ) user_details.save() order.save() serializer = OrderSerializer(order) - response_data = dict( - orders=serializer.data - ) + response_data = dict(orders=serializer.data) return Response(response_data, status=status.HTTP_200_OK) -class OrderDetailsView(APIView): +class OrderDetailsView(APIView, LimitOffsetPagination): """ Get the details of the orders. """ @@ -241,25 +272,20 @@ def get(self, request, user=None): list of order object and 200 status if no error message and corresponding status if error """ - limit = request.GET.get('limit', str(settings.DEFAULT_LIMIT)) - offset = request.GET.get('offset', str(settings.DEFAULT_OFFSET)) - if not limit.isdigit() or not offset.isdigit(): - return Response( - {'message': messages.INVALID_LIMIT_OR_OFFSET}, - status=status.HTTP_400_BAD_REQUEST - ) - limit = int(limit) - offset = int(offset) - if limit > settings.MAX_LIMIT: - limit = 100 - if limit < 0: - limit = settings.DEFAULT_LIMIT - if offset < 0: - offset = settings.DEFAULT_OFFSET - orders = Order.objects.filter(user=user).order_by('-id')[offset:offset+limit] - serializer = OrderSerializer(orders, many=True) + orders = Order.objects.filter(user=user).order_by("-id") + paginated = self.paginate_queryset(orders, request, view=self) + serializer = OrderSerializer(paginated, many=True) response_data = dict( - orders=serializer.data + orders=serializer.data, + next_offset=( + self.offset + self.limit + if self.offset + self.limit < self.count + else None + ), + previous_offset=( + self.offset - self.limit if self.offset - self.limit >= 0 else None + ), + count=self.get_count(paginated), ) return Response(response_data, status=status.HTTP_200_OK) @@ -268,6 +294,7 @@ class ReturnOrder(APIView): """ Return Order View """ + @jwt_auth_required def post(self, request, user=None): """ @@ -280,35 +307,41 @@ def post(self, request, user=None): message and 200 status if no error message and corresponding status if error """ - order = Order.objects.get(id=request.GET['order_id']) + order = Order.objects.get(id=request.GET["order_id"]) if user != order.user: - return Response({'message': messages.RESTRICTED}, status=status.HTTP_403_FORBIDDEN) + return Response( + {"message": messages.RESTRICTED}, status=status.HTTP_403_FORBIDDEN + ) if order.status == Order.STATUS_CHOICES.RETURNED.value: return Response( - {'message': messages.ORDER_ALREADY_RETURNED}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.ORDER_ALREADY_RETURNED}, + status=status.HTTP_400_BAD_REQUEST, ) elif order.status == Order.STATUS_CHOICES.RETURN_PENDING.value: return Response( - {'message': messages.ORDER_RETURNED_PENDING}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.ORDER_RETURNED_PENDING}, + status=status.HTTP_400_BAD_REQUEST, ) qr_code_url = request.build_absolute_uri(reverse("shop-return-qr-code")) order.status = Order.STATUS_CHOICES.RETURN_PENDING.value order.save() serializer = OrderSerializer(order) - return Response({ - 'message': messages.ORDER_RETURNING, - 'qr_code_url': qr_code_url, - 'order': serializer.data - }, status=status.HTTP_200_OK) + return Response( + { + "message": messages.ORDER_RETURNING, + "qr_code_url": qr_code_url, + "order": serializer.data, + }, + status=status.HTTP_200_OK, + ) class ReturnQRCodeView(APIView): """ QR code image view """ + def get(self, request): """ returns a qr code image @@ -316,7 +349,7 @@ def get(self, request): method allowed: GET :return: FileResponse """ - img = open('utils/return-qr-code.png', 'rb') + img = open("utils/return-qr-code.png", "rb") return FileResponse(img) @@ -324,6 +357,7 @@ class ApplyCouponView(APIView): """ Apply Coupon View to increase the available credit """ + @jwt_auth_required def post(self, request, user=None): """ @@ -347,44 +381,49 @@ def post(self, request, user=None): row = None with connection.cursor() as cursor: try: - cursor.execute("SELECT coupon_code from applied_coupon WHERE user_id = "\ - + str(user.id)\ - + " AND coupon_code = '"\ - + coupon_request_body['coupon_code']\ - + "'") + cursor.execute( + "SELECT coupon_code from applied_coupon WHERE user_id = " + + str(user.id) + + " AND coupon_code = '" + + coupon_request_body["coupon_code"] + + "'" + ) row = cursor.fetchall() except Exception as e: log_error(request.path, request.data, 500, e) return Response( - {'message': e}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR + {"message": e}, status=status.HTTP_500_INTERNAL_SERVER_ERROR ) if row and row != None: return Response( { - 'message': row[0][0] + " " + messages.COUPON_ALREADY_APPLIED, + "message": row[0][0] + " " + messages.COUPON_ALREADY_APPLIED, }, - status=status.HTTP_400_BAD_REQUEST + status=status.HTTP_400_BAD_REQUEST, ) try: - coupon = Coupon.objects.using('mongodb').get(coupon_code=coupon_request_body['coupon_code']) + coupon = Coupon.objects.using("mongodb").get( + coupon_code=coupon_request_body["coupon_code"] + ) except ObjectDoesNotExist as e: log_error(request.path, request.data, 400, e) return Response( - {'message': messages.COUPON_NOT_FOUND}, - status=status.HTTP_400_BAD_REQUEST + {"message": messages.COUPON_NOT_FOUND}, + status=status.HTTP_400_BAD_REQUEST, ) AppliedCoupon.objects.create( - user=user, - coupon_code=coupon_request_body['coupon_code'] + user=user, coupon_code=coupon_request_body["coupon_code"] ) user_details = UserDetails.objects.get(user=user) - user_details.available_credit += coupon_request_body['amount'] + user_details.available_credit += coupon_request_body["amount"] user_details.save() - return Response({ - 'credit': user_details.available_credit, - 'message': messages.COUPON_APPLIED - }, status=status.HTTP_200_OK) + return Response( + { + "credit": user_details.available_credit, + "message": messages.COUPON_APPLIED, + }, + status=status.HTTP_200_OK, + ) diff --git a/services/workshop/crapi/urls.py b/services/workshop/crapi/urls.py index 5ffa7cf6..8793324c 100644 --- a/services/workshop/crapi/urls.py +++ b/services/workshop/crapi/urls.py @@ -19,8 +19,8 @@ from django.urls import path, include urlpatterns = [ - path('api/mechanic/', include('crapi.mechanic.urls')), - path('api/merchant/', include('crapi.merchant.urls')), - path('api/shop/', include('crapi.shop.urls')), - path('api/management/', include('crapi.user.urls')), + path("api/mechanic/", include("crapi.mechanic.urls")), + path("api/merchant/", include("crapi.merchant.urls")), + path("api/shop/", include("crapi.shop.urls")), + path("api/management/", include("crapi.user.urls")), ] diff --git a/services/workshop/crapi/user/__init__.py b/services/workshop/crapi/user/__init__.py index 713a92b0..e9b106a0 100644 --- a/services/workshop/crapi/user/__init__.py +++ b/services/workshop/crapi/user/__init__.py @@ -10,4 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - diff --git a/services/workshop/crapi/user/models.py b/services/workshop/crapi/user/models.py index c248f4c2..5417e0f2 100644 --- a/services/workshop/crapi/user/models.py +++ b/services/workshop/crapi/user/models.py @@ -22,6 +22,7 @@ from collections import OrderedDict from extended_choices import Choices + class User(models.Model): """ User Model @@ -36,16 +37,16 @@ class User(models.Model): password = models.CharField(max_length=255) ROLE_CHOICES = Choices( - ('PREDEFINED', 0, 'Predefined'), - ('USER', 1, 'User'), - ('MECH', 2, 'Mechanic'), - ('ADMIN', 3, 'Admin'), - dict_class = OrderedDict + ("PREDEFINED", 0, "Predefined"), + ("USER", 1, "User"), + ("MECH", 2, "Mechanic"), + ("ADMIN", 3, "Admin"), + dict_class=OrderedDict, ) role = models.IntegerField(choices=ROLE_CHOICES, default=ROLE_CHOICES.USER) class Meta: - db_table = 'user_login' + db_table = "user_login" managed = settings.IS_TESTING def __str__(self): @@ -57,6 +58,7 @@ class UserDetails(models.Model): UserDetails Model stores additional details for the user """ + id = models.AutoField(primary_key=True) available_credit = models.FloatField() name = models.CharField(max_length=255, null=True) @@ -64,7 +66,7 @@ class UserDetails(models.Model): user = models.ForeignKey(User, on_delete=models.CASCADE) class Meta: - db_table = 'user_details' + db_table = "user_details" managed = settings.IS_TESTING def __str__(self): @@ -76,11 +78,12 @@ class VehicleCompany(models.Model): UserDetails Model stores additional details for the user """ + id = models.AutoField(primary_key=True) name = models.CharField(max_length=255) class Meta: - db_table = 'vehicle_company' + db_table = "vehicle_company" managed = settings.IS_TESTING def __str__(self): @@ -92,6 +95,7 @@ class VehicleModel(models.Model): UserDetails Model stores additional details for the user """ + id = models.AutoField(primary_key=True) fuel_type = models.BigIntegerField() model = models.CharField(max_length=255) @@ -99,7 +103,7 @@ class VehicleModel(models.Model): vehiclecompany = models.ForeignKey(VehicleCompany, on_delete=models.CASCADE) class Meta: - db_table = 'vehicle_model' + db_table = "vehicle_model" managed = settings.IS_TESTING def __str__(self): @@ -111,6 +115,7 @@ class Vehicle(models.Model): Vehicle Model represents a vehicle in the application """ + id = models.AutoField(primary_key=True) pincode = models.CharField(max_length=255, null=True) vin = models.CharField(max_length=255) @@ -121,7 +126,7 @@ class Vehicle(models.Model): location_id = models.BigIntegerField(null=True) class Meta: - db_table = 'vehicle_details' + db_table = "vehicle_details" managed = settings.IS_TESTING def __str__(self): diff --git a/services/workshop/crapi/user/sapps.py b/services/workshop/crapi/user/sapps.py index 5387d8e0..a700398c 100644 --- a/services/workshop/crapi/user/sapps.py +++ b/services/workshop/crapi/user/sapps.py @@ -22,4 +22,5 @@ class UserConfig(AppConfig): """ Stores all meta data of user application """ - name = 'user' + + name = "user" diff --git a/services/workshop/crapi/user/serializers.py b/services/workshop/crapi/user/serializers.py index cc9a309d..f018a8ff 100644 --- a/services/workshop/crapi/user/serializers.py +++ b/services/workshop/crapi/user/serializers.py @@ -29,33 +29,38 @@ class Meta: """ Meta class for UserSerializer """ + model = User - fields = ('email', 'number') + fields = ("email", "number") class UserDetailsSerializer(serializers.ModelSerializer): """ Serializer for User Details model """ + user = UserSerializer() class Meta: """ Meta class for UserSerializer """ + model = UserDetails - fields = ('user', 'available_credit') + fields = ("user", "available_credit") class VehicleSerializer(serializers.ModelSerializer): """ Serializer for Vehicle model """ + owner = UserSerializer() class Meta: """ Meta class for MechanicSerializer """ + model = Vehicle - fields = ('id', 'vin', 'owner') + fields = ("id", "vin", "owner") diff --git a/services/workshop/crapi/user/tests.py b/services/workshop/crapi/user/tests.py index 657f4fbd..fa25ab30 100644 --- a/services/workshop/crapi/user/tests.py +++ b/services/workshop/crapi/user/tests.py @@ -16,9 +16,14 @@ from unittest.mock import patch from django.db import connection -from utils.mock_methods import get_sample_admin_user, get_sample_user_data, get_sample_users, mock_jwt_auth_required +from utils.mock_methods import ( + get_sample_admin_user, + get_sample_user_data, + get_sample_users, + mock_jwt_auth_required, +) -patch('utils.jwt.jwt_auth_required', mock_jwt_auth_required).start() +patch("utils.jwt.jwt_auth_required", mock_jwt_auth_required).start() import logging import bcrypt @@ -29,9 +34,10 @@ from crapi_site import settings from crapi.user.models import User, UserDetails -logger = logging.getLogger('UserTest') +logger = logging.getLogger("UserTest") MAX_USER_COUNT = 40 + class UserDetailsTestCase(TestCase): """ contains all the test cases related to UserDetails @@ -41,35 +47,34 @@ class UserDetailsTestCase(TestCase): auth_headers: Auth headers for dummy user """ - databases = '__all__' + databases = "__all__" setup_done = False def setUp(self): self.client = Client() user_data = get_sample_admin_user() - uset = User.objects.filter(email=user_data['email']) + uset = User.objects.filter(email=user_data["email"]) if not uset.exists(): user = User.objects.create( - email=user_data['email'], - number=user_data['number'], - password=bcrypt.hashpw(user_data['password'].encode('utf-8'), - bcrypt.gensalt()).decode(), - role=user_data['role'], - created_on=timezone.now()) - user_detail = UserDetails.objects.create(available_credit=100, - name=user_data['name'], - status='ACTIVE', - user=user) + email=user_data["email"], + number=user_data["number"], + password=bcrypt.hashpw( + user_data["password"].encode("utf-8"), bcrypt.gensalt() + ).decode(), + role=user_data["role"], + created_on=timezone.now(), + ) + user_detail = UserDetails.objects.create( + available_credit=100, name=user_data["name"], status="ACTIVE", user=user + ) user.save() user_detail.save() - self.auth_headers = { - 'HTTP_AUTHORIZATION': 'Bearer ' + user_data['email'] - } + self.auth_headers = {"HTTP_AUTHORIZATION": "Bearer " + user_data["email"]} def setup_database(self): self.users_data = get_sample_users(MAX_USER_COUNT) for user_data in self.users_data: - uset = User.objects.filter(email=user_data['email']) + uset = User.objects.filter(email=user_data["email"]) if not uset.exists(): try: cursor = connection.cursor() @@ -77,24 +82,27 @@ def setup_database(self): result = cursor.fetchone() user_id = result[0] except Exception as e: - logger.error("Failed to fetch user_login_id_seq"+str(e)) + logger.error("Failed to fetch user_login_id_seq" + str(e)) user_id = 1 user_i = User.objects.create( - id = user_id, - email=user_data['email'], - number=user_data['number'], - password=bcrypt.hashpw(user_data['password'].encode('utf-8'), - bcrypt.gensalt()).decode(), - role=user_data['role'], - created_on=timezone.now()) - user_details_i = UserDetails.objects.create(available_credit=100, - name=user_data['name'], - status='ACTIVE', - user=user_i) + id=user_id, + email=user_data["email"], + number=user_data["number"], + password=bcrypt.hashpw( + user_data["password"].encode("utf-8"), bcrypt.gensalt() + ).decode(), + role=user_data["role"], + created_on=timezone.now(), + ) + user_details_i = UserDetails.objects.create( + available_credit=100, + name=user_data["name"], + status="ACTIVE", + user=user_i, + ) user_i.save() user_details_i.save() - logger.info("Created user with id: "+str(user_id)) - + logger.info("Created user with id: " + str(user_id)) def test_get_api_management_users_all(self): """ @@ -102,27 +110,38 @@ def test_get_api_management_users_all(self): :return: None """ self.setup_database() - response = self.client.get('/workshop/api/management/users/all', **self.auth_headers) + response = self.client.get( + "/workshop/api/management/users/all", **self.auth_headers + ) self.assertEqual(response.status_code, 200) response_data = json.loads(response.content) - self.assertEqual(len(response_data['users']), settings.DEFAULT_LIMIT) - response = self.client.get('/workshop/api/management/users/all?limit=10&offset=0', **self.auth_headers) + all_users_length = len(response_data["users"]) + self.assertEqual( + len(response_data["users"]), min(all_users_length, settings.MAX_LIMIT) + ) + response = self.client.get( + "/workshop/api/management/users/all?limit=10&offset=0", **self.auth_headers + ) self.assertEqual(response.status_code, 200) response_data = json.loads(response.content) - self.assertEqual(len(response_data['users']), 10) - response2 = self.client.get('/workshop/api/management/users/all?limit=10&offset=10', **self.auth_headers) + self.assertEqual(len(response_data["users"]), 10) + response2 = self.client.get( + "/workshop/api/management/users/all?limit=10&offset=10", **self.auth_headers + ) self.assertEqual(response2.status_code, 200) response_data2 = json.loads(response2.content) - self.assertNotEquals(response_data['users'], response_data2['users']) - + self.assertNotEquals(response_data["users"], response_data2["users"]) + response = self.client.get( + "/workshop/api/management/users/all?limit=a&offset=-1", **self.auth_headers + ) + self.assertEqual(response.status_code, 200) + response_data = json.loads(response.content) + self.assertEqual(len(response_data["users"]), all_users_length) def test_bad_get_api_management_users_all(self): """ tests the get user details api :return: None """ - response = self.client.get('/workshop/api/management/users/all') + response = self.client.get("/workshop/api/management/users/all") self.assertEqual(response.status_code, 401) - response = self.client.get('/workshop/api/management/users/all?limit=a&offset=-1', **self.auth_headers) - self.assertEqual(response.status_code, 400) - diff --git a/services/workshop/crapi/user/urls.py b/services/workshop/crapi/user/urls.py index 5ba07973..82db32b0 100644 --- a/services/workshop/crapi/user/urls.py +++ b/services/workshop/crapi/user/urls.py @@ -22,5 +22,5 @@ urlpatterns = [ # Do not change the order of URLs - re_path(r'users/all$', user_views.AdminUserView.as_view()), -] \ No newline at end of file + re_path(r"users/all$", user_views.AdminUserView.as_view()), +] diff --git a/services/workshop/crapi/user/views.py b/services/workshop/crapi/user/views.py index a3c99c52..04c479ca 100644 --- a/services/workshop/crapi/user/views.py +++ b/services/workshop/crapi/user/views.py @@ -27,10 +27,12 @@ from utils.jwt import jwt_auth_required from utils import messages from utils.logging import log_error +from rest_framework.pagination import LimitOffsetPagination logger = logging.getLogger() -class AdminUserView(APIView): + +class AdminUserView(APIView, LimitOffsetPagination): """ View for admin user to fetch user details """ @@ -47,31 +49,24 @@ def get(self, request, user=None): user details and 200 status if no error message and corresponding status if error """ - limit = request.GET.get('limit', str(settings.DEFAULT_LIMIT)) - offset = request.GET.get('offset', str(settings.DEFAULT_OFFSET)) - if not limit.isdigit() or not offset.isdigit(): - return Response( - {'message': messages.INVALID_LIMIT_OR_OFFSET}, - status=status.HTTP_400_BAD_REQUEST - ) - limit = int(limit) - offset = int(offset) - if limit > settings.MAX_LIMIT: - limit = 100 - if limit < 0: - limit = settings.DEFAULT_LIMIT - if offset < 0: - offset = settings.DEFAULT_OFFSET # Sort by id - userdetails = UserDetails.objects.all().order_by('id')[offset:offset+limit] + userdetails = UserDetails.objects.all().order_by("id") if not userdetails: return Response( - {'message': messages.NO_USER_DETAILS}, - status=status.HTTP_404_NOT_FOUND + {"message": messages.NO_USER_DETAILS}, status=status.HTTP_404_NOT_FOUND ) - serializer = UserDetailsSerializer(userdetails, many=True) - response_data = { - "users": serializer.data - } + paginated = self.paginate_queryset(userdetails, request) + serializer = UserDetailsSerializer(paginated, many=True) + response_data = dict( + users=serializer.data, + next_offset=( + self.offset + self.limit + if self.offset + self.limit < self.count + else None + ), + previous_offset=( + self.offset - self.limit if self.offset - self.limit >= 0 else None + ), + count=self.get_count(paginated), + ) return Response(response_data, status=status.HTTP_200_OK) - diff --git a/services/workshop/crapi_site/__init__.py b/services/workshop/crapi_site/__init__.py index bfd4bb9d..e9b106a0 100644 --- a/services/workshop/crapi_site/__init__.py +++ b/services/workshop/crapi_site/__init__.py @@ -10,5 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - diff --git a/services/workshop/crapi_site/settings.py b/services/workshop/crapi_site/settings.py index d81d19fe..3693971f 100644 --- a/services/workshop/crapi_site/settings.py +++ b/services/workshop/crapi_site/settings.py @@ -32,11 +32,12 @@ DEFAULT_OFFSET = 0 MAX_LIMIT = 100 + def get_env_value(env_variable): try: return os.environ[env_variable] except KeyError: - error_msg = f'Set the {env_variable} environment variable' + error_msg = f"Set the {env_variable} environment variable" raise ImproperlyConfigured(error_msg) @@ -47,123 +48,119 @@ def get_env_value(env_variable): # See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = get_env_value('SECRET_KEY') +SECRET_KEY = get_env_value("SECRET_KEY") # Enable it if Testing -IS_TESTING = os.environ.get('IS_TESTING', False) +IS_TESTING = os.environ.get("IS_TESTING", False) # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.environ.get('DEBUG', False) +DEBUG = os.environ.get("DEBUG", False) -ALLOWED_HOSTS = ['*'] +ALLOWED_HOSTS = ["*"] -API_GATEWAY_URL = get_env_value('API_GATEWAY_URL') +API_GATEWAY_URL = get_env_value("API_GATEWAY_URL") API_GATEWAY_USERNAME = "vendorcrapi" API_GATEWAY_PASSWORD = "Pa$$4Vendor_1" # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'corsheaders', - 'health_check', - 'health_check.db', - 'core', - 'crapi', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "corsheaders", + "health_check", + "health_check.db", + "core", + "crapi", # 'crapi.apps.CRAPIConfig', # 'user.apps.UserConfig', "django_extensions", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', - 'corsheaders.middleware.CorsMiddleware', - 'django.middleware.common.BrokenLinkEmailsMiddleware', - 'django.middleware.common.CommonMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", + "corsheaders.middleware.CorsMiddleware", + "django.middleware.common.BrokenLinkEmailsMiddleware", + "django.middleware.common.CommonMiddleware", ] CORS_ORIGIN_ALLOW_ALL = True -ROOT_URLCONF = 'crapi_site.urls' +ROOT_URLCONF = "crapi_site.urls" -TEST_RUNNER = 'xmlrunner.extra.djangotestrunner.XMLTestRunner' +TEST_RUNNER = "xmlrunner.extra.djangotestrunner.XMLTestRunner" -TEST_OUTPUT_DIR = os.path.join(BASE_DIR, 'test-reports') +TEST_OUTPUT_DIR = os.path.join(BASE_DIR, "test-reports") TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'crapi_site.wsgi.application' +WSGI_APPLICATION = "crapi_site.wsgi.application" REST_FRAMEWORK = { - 'DEFAULT_RENDERER_CLASSES': [ - 'rest_framework.renderers.JSONRenderer', + "DEFAULT_RENDERER_CLASSES": [ + "rest_framework.renderers.JSONRenderer", ], - 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', - 'PAGE_SIZE': 100, - 'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',), - 'UNAUTHENTICATED_USER': None, # Needed once you disable django.contrib.auth + "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", + "PAGE_SIZE": MAX_LIMIT, + "DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",), + "UNAUTHENTICATED_USER": None, # Needed once you disable django.contrib.auth } LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'formatters': { - 'standard': { - 'format': '%(asctime)s %(name)-12s [%(levelname)s]: %(message)s' - }, - 'console': { - 'format': '%(name)-12s %(levelname)-8s %(message)s' - }, + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "standard": {"format": "%(asctime)s %(name)-12s [%(levelname)s]: %(message)s"}, + "console": {"format": "%(name)-12s %(levelname)-8s %(message)s"}, }, - 'handlers': { - 'file': { - 'level': 'DEBUG', - 'class': 'logging.FileHandler', - 'filename': BASE_DIR + '/debug.log', - 'formatter': 'standard', + "handlers": { + "file": { + "level": "DEBUG", + "class": "logging.FileHandler", + "filename": BASE_DIR + "/debug.log", + "formatter": "standard", }, - 'console': { - 'level': 'INFO', - 'class': 'logging.StreamHandler', - 'formatter': 'console', + "console": { + "level": "INFO", + "class": "logging.StreamHandler", + "formatter": "console", }, }, - 'root': { - 'handlers': ['file', 'console'], - 'level': 'DEBUG', + "root": { + "handlers": ["file", "console"], + "level": "DEBUG", }, - 'django.request': { - 'handlers': ['file'], - 'level': 'DEBUG', + "django.request": { + "handlers": ["file"], + "level": "DEBUG", }, - 'djongo': { - 'level': 'DEBUG', - 'handlers': ['console'], - 'propogate': True, + "djongo": { + "level": "DEBUG", + "handlers": ["console"], + "propogate": True, }, } @@ -171,34 +168,31 @@ def get_env_value(env_variable): # https://docs.djangoproject.com/en/2.2/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.postgresql', - 'NAME': get_env_value('DB_NAME'), - 'USER': get_env_value('DB_USER'), - 'PASSWORD': get_env_value('DB_PASSWORD'), - 'HOST': get_env_value('DB_HOST'), - 'PORT': get_env_value('DB_PORT'), - 'TEST': { - 'NAME': 'test_crapi', - 'USER': get_env_value('DB_USER'), + "default": { + "ENGINE": "django.db.backends.postgresql", + "NAME": get_env_value("DB_NAME"), + "USER": get_env_value("DB_USER"), + "PASSWORD": get_env_value("DB_PASSWORD"), + "HOST": get_env_value("DB_HOST"), + "PORT": get_env_value("DB_PORT"), + "TEST": { + "NAME": "test_crapi", + "USER": get_env_value("DB_USER"), }, - 'CONN_MAX_AGE': 0, + "CONN_MAX_AGE": 0, }, - 'mongodb': { - 'ENGINE': 'djongo', - 'NAME': get_env_value('MONGO_DB_NAME'), - 'CLIENT' : { - 'host': get_env_value('MONGO_DB_HOST'), - 'port': int(get_env_value('MONGO_DB_PORT')), - 'username': get_env_value('MONGO_DB_USER'), - 'password': get_env_value('MONGO_DB_PASSWORD'), - 'authSource': 'admin' + "mongodb": { + "ENGINE": "djongo", + "NAME": get_env_value("MONGO_DB_NAME"), + "CLIENT": { + "host": get_env_value("MONGO_DB_HOST"), + "port": int(get_env_value("MONGO_DB_PORT")), + "username": get_env_value("MONGO_DB_USER"), + "password": get_env_value("MONGO_DB_PASSWORD"), + "authSource": "admin", }, - 'TEST': { - 'NAME': 'test_crapi_mongo', - 'USER': get_env_value('MONGO_DB_USER') - } - } + "TEST": {"NAME": "test_crapi_mongo", "USER": get_env_value("MONGO_DB_USER")}, + }, } # Password validation @@ -222,9 +216,9 @@ def get_env_value(env_variable): # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -235,13 +229,19 @@ def get_env_value(env_variable): # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.2/howto/static-files/ -STATIC_URL = '/static/' - -IDENTITY_VERIFY = 'http://{}/identity/api/auth/verify'.format(get_env_value('IDENTITY_SERVICE')) -IDENTITY_LOGIN = 'http://{}/identity/api/auth/login'.format(get_env_value('IDENTITY_SERVICE')) -TLS_ENABLED = os.environ.get('TLS_ENABLED') -if TLS_ENABLED and (TLS_ENABLED.lower() in ['true', '1', 'yes']): - IDENTITY_VERIFY = 'https://{}/identity/api/auth/verify'.format(get_env_value('IDENTITY_SERVICE')) - IDENTITY_LOGIN = 'https://{}/identity/api/auth/login'.format(get_env_value('IDENTITY_SERVICE')) - - +STATIC_URL = "/static/" + +IDENTITY_VERIFY = "http://{}/identity/api/auth/verify".format( + get_env_value("IDENTITY_SERVICE") +) +IDENTITY_LOGIN = "http://{}/identity/api/auth/login".format( + get_env_value("IDENTITY_SERVICE") +) +TLS_ENABLED = os.environ.get("TLS_ENABLED") +if TLS_ENABLED and (TLS_ENABLED.lower() in ["true", "1", "yes"]): + IDENTITY_VERIFY = "https://{}/identity/api/auth/verify".format( + get_env_value("IDENTITY_SERVICE") + ) + IDENTITY_LOGIN = "https://{}/identity/api/auth/login".format( + get_env_value("IDENTITY_SERVICE") + ) diff --git a/services/workshop/crapi_site/urls.py b/services/workshop/crapi_site/urls.py index 86df2c00..7cf13eda 100644 --- a/services/workshop/crapi_site/urls.py +++ b/services/workshop/crapi_site/urls.py @@ -32,7 +32,7 @@ from django.urls import path, include urlpatterns = [ - path('workshop/admin/', admin.site.urls), - path('workshop/health_check/', include('health_check.urls')), - path('workshop/', include('crapi.urls')), + path("workshop/admin/", admin.site.urls), + path("workshop/health_check/", include("health_check.urls")), + path("workshop/", include("crapi.urls")), ] diff --git a/services/workshop/crapi_site/wsgi.py b/services/workshop/crapi_site/wsgi.py index 0433bba8..a4df462d 100644 --- a/services/workshop/crapi_site/wsgi.py +++ b/services/workshop/crapi_site/wsgi.py @@ -25,6 +25,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'crapi_site.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "crapi_site.settings") application = get_wsgi_application() diff --git a/services/workshop/manage.py b/services/workshop/manage.py index 221e0e90..9c8780fb 100755 --- a/services/workshop/manage.py +++ b/services/workshop/manage.py @@ -19,7 +19,7 @@ def main(): - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'crapi_site.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "crapi_site.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -31,5 +31,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/services/workshop/requirements.txt b/services/workshop/requirements.txt index d8b2d67b..6c71f547 100644 --- a/services/workshop/requirements.txt +++ b/services/workshop/requirements.txt @@ -20,4 +20,5 @@ Werkzeug==2.0.3 Faker==22.1.0 gunicorn==21.2.0 coverage==7.4.1 -unittest-xml-reporting==3.2.0 \ No newline at end of file +unittest-xml-reporting==3.2.0 +black==24.4.2 diff --git a/services/workshop/setup.py b/services/workshop/setup.py index 316fcc88..0de6f87f 100644 --- a/services/workshop/setup.py +++ b/services/workshop/setup.py @@ -15,11 +15,12 @@ from setuptools import setup, find_packages -with open('requirements.txt') as f: - requirements = f.readlines() +with open("requirements.txt") as f: + requirements = f.readlines() -setup(name='crapi_site', - version='1.0', - packages=find_packages(), - install_requires=requirements, - ) +setup( + name="crapi_site", + version="1.0", + packages=find_packages(), + install_requires=requirements, +) diff --git a/services/workshop/utils/__init__.py b/services/workshop/utils/__init__.py index bfd4bb9d..e9b106a0 100644 --- a/services/workshop/utils/__init__.py +++ b/services/workshop/utils/__init__.py @@ -10,5 +10,3 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - - diff --git a/services/workshop/utils/helper.py b/services/workshop/utils/helper.py index fff21e7c..0561ea1e 100644 --- a/services/workshop/utils/helper.py +++ b/services/workshop/utils/helper.py @@ -2,5 +2,5 @@ def basic_auth(username, password=""): - token = b64encode(f"{username}:{password}".encode('utf-8')).decode("ascii") - return f'Basic {token}' \ No newline at end of file + token = b64encode(f"{username}:{password}".encode("utf-8")).decode("ascii") + return f"Basic {token}" diff --git a/services/workshop/utils/jwt.py b/services/workshop/utils/jwt.py index bdb4fbda..a0d9f1a3 100644 --- a/services/workshop/utils/jwt.py +++ b/services/workshop/utils/jwt.py @@ -28,6 +28,7 @@ logger = logging.getLogger() + def jwt_auth_required(func): """ decorator for authorizing http requests @@ -41,36 +42,49 @@ def jwt_auth_required(func): def new_func(*args, **kwargs): try: request = args[1] - if 'HTTP_AUTHORIZATION' in request.META \ - and request.META.get('HTTP_AUTHORIZATION')[0:7] == 'Bearer ': - token = request.META.get('HTTP_AUTHORIZATION')[7:] - tokenJson = {'token': token} + if ( + "HTTP_AUTHORIZATION" in request.META + and request.META.get("HTTP_AUTHORIZATION")[0:7] == "Bearer " + ): + token = request.META.get("HTTP_AUTHORIZATION")[7:] + tokenJson = {"token": token} identity_url = settings.IDENTITY_VERIFY logger.debug(f"Identity url: {identity_url}, tokenJson: {tokenJson}") token_verify_response = requests.post( - identity_url, json=tokenJson, verify=False) - logger.debug(f"Identity url: {identity_url}, token_verify_response: {token_verify_response}") + identity_url, json=tokenJson, verify=False + ) + logger.debug( + f"Identity url: {identity_url}, token_verify_response: {token_verify_response}" + ) response_status_code = token_verify_response.status_code if response_status_code == status.HTTP_200_OK: decoded = jwt.decode(token, options={"verify_signature": False}) - username = decoded['sub'] + username = decoded["sub"] user = User.objects.get(email=username) # Add user object to the view function if authorized - kwargs['user'] = user + kwargs["user"] = user return func(*args, **kwargs) logger.debug("JWT token verification failed") - return Response({'message': messages.INVALID_TOKEN}, - status=status.HTTP_401_UNAUTHORIZED, - content_type="application/json") + return Response( + {"message": messages.INVALID_TOKEN}, + status=status.HTTP_401_UNAUTHORIZED, + content_type="application/json", + ) logger.debug("JWT token not found in request header") - return Response({'message': messages.JWT_REQUIRED}, - status=status.HTTP_401_UNAUTHORIZED, - content_type="application/json") + return Response( + {"message": messages.JWT_REQUIRED}, + status=status.HTTP_401_UNAUTHORIZED, + content_type="application/json", + ) except (jwt.exceptions.DecodeError, User.DoesNotExist) as e: - logger.debug(f"JWT token verification failed with exception: {e}", exc_info=True) - return Response({'message': messages.INVALID_TOKEN}, - status=status.HTTP_401_UNAUTHORIZED, - content_type="application/json") + logger.debug( + f"JWT token verification failed with exception: {e}", exc_info=True + ) + return Response( + {"message": messages.INVALID_TOKEN}, + status=status.HTTP_401_UNAUTHORIZED, + content_type="application/json", + ) return new_func diff --git a/services/workshop/utils/messages.py b/services/workshop/utils/messages.py index 6393f961..b899ef2b 100644 --- a/services/workshop/utils/messages.py +++ b/services/workshop/utils/messages.py @@ -31,16 +31,25 @@ ORDER_CREATED = "Order sent successfully." ORDER_RETURNED_PENDING = "This order is already requested for returning!" ORDER_ALREADY_RETURNED = "This order is already returned!" -ORDER_RETURNING = "Please use the following QR code to return your order to a UPS store!" -COUPON_ALREADY_APPLIED = "Coupon code is already claimed by you!! Please try with another coupon code" +ORDER_RETURNING = ( + "Please use the following QR code to return your order to a UPS store!" +) +COUPON_ALREADY_APPLIED = ( + "Coupon code is already claimed by you!! Please try with another coupon code" +) COUPON_APPLIED = "Coupon successfully applied!" COUPON_NOT_FOUND = "Coupon not found" RESTRICTED = "You are not allowed to access this resource!" -INVALID_STATUS = "The value of 'status' has to be 'delivered','return pending' or 'returned'" -INVALID_SERVICE_REQUEST_STATUS = "The value of 'status' has to be 'Pending' or 'Finished'" +INVALID_STATUS = ( + "The value of 'status' has to be 'delivered','return pending' or 'returned'" +) +INVALID_SERVICE_REQUEST_STATUS = ( + "The value of 'status' has to be 'Pending' or 'Finished'" +) REPORT_ID_MISSING = "Please enter the report_id value." INVALID_REPORT_ID = "Please enter a valid report_id value." REPORT_DOES_NOT_EXIST = "The Report does not exist for given report_id." COULD_NOT_CONNECT = "Could not connect to mechanic api." INVALID_LIMIT_OR_OFFSET = "Param limit and offset values should be integers." -NO_USER_DETAILS = "No user details found." \ No newline at end of file +NO_USER_DETAILS = "No user details found." +NO_OBJECT_FOUND = "No object found." diff --git a/services/workshop/utils/mock_methods.py b/services/workshop/utils/mock_methods.py index 185399b7..ee94db09 100644 --- a/services/workshop/utils/mock_methods.py +++ b/services/workshop/utils/mock_methods.py @@ -19,6 +19,7 @@ from utils import messages from crapi.user.models import User from faker import Faker + Faker.seed(4321) @@ -29,6 +30,7 @@ logger = logging.getLogger() + def get_sample_mechanic_data(): """ gives mechanic data which can be used for testing @@ -42,6 +44,7 @@ def get_sample_mechanic_data(): "password": "admin", } + def get_sample_user_data(): """ gives user data which can be used for testing @@ -54,8 +57,10 @@ def get_sample_user_data(): "password": "password", } + def fake_phone_number(fake: Faker) -> str: - return f'{fake.msisdn()[3:]}' + return f"{fake.msisdn()[3:]}" + def get_sample_users(users_count=100): """ @@ -64,15 +69,18 @@ def get_sample_users(users_count=100): fake = Faker() users = [] for i in range(users_count): - users.append({ - "name": fake.name(), - "email": fake.email(), - "number": fake_phone_number(fake), - "password": fake.password(), - "role": fake.random_element(elements=dict(User.ROLE_CHOICES).keys()), - }) + users.append( + { + "name": fake.name(), + "email": fake.email(), + "number": fake_phone_number(fake), + "password": fake.password(), + "role": fake.random_element(elements=dict(User.ROLE_CHOICES).keys()), + } + ) return users + def get_sample_admin_user(): """ gives admin user which can be used for testing @@ -86,7 +94,6 @@ def get_sample_admin_user(): } - def mock_jwt_auth_required(func): """ mock function to validate jwt @@ -95,25 +102,32 @@ def mock_jwt_auth_required(func): calls the actual view function if token is a valid email returns error message if token is a invalid email """ + @wraps(func) def new_func(*args, **kwargs): try: request = args[1] - if 'HTTP_AUTHORIZATION' in request.META \ - and request.META.get('HTTP_AUTHORIZATION')[0:7] == 'Bearer ': - token = request.META.get('HTTP_AUTHORIZATION')[7:] + if ( + "HTTP_AUTHORIZATION" in request.META + and request.META.get("HTTP_AUTHORIZATION")[0:7] == "Bearer " + ): + token = request.META.get("HTTP_AUTHORIZATION")[7:] user = User.objects.get(email=token) # Add user object to the view function if authorized - kwargs['user'] = user + kwargs["user"] = user return func(*args, **kwargs) - return Response({'message': messages.JWT_REQUIRED}, - status=status.HTTP_401_UNAUTHORIZED, - content_type="application/json") + return Response( + {"message": messages.JWT_REQUIRED}, + status=status.HTTP_401_UNAUTHORIZED, + content_type="application/json", + ) except (jwt.exceptions.DecodeError, User.DoesNotExist): - return Response({'message': messages.INVALID_TOKEN}, - status=status.HTTP_401_UNAUTHORIZED, - content_type="application/json") + return Response( + {"message": messages.INVALID_TOKEN}, + status=status.HTTP_401_UNAUTHORIZED, + content_type="application/json", + ) return new_func