├── .gitignore ├── .gitignore.save ├── basicAPI ├── main.py └── models.py ├── requirements.txt └── shoppingAPI ├── authentication.py ├── database.sqlite3 ├── email_template.html ├── emails.py ├── main.py ├── models.py ├── static └── images │ ├── 011766faf1be9e199294.png │ ├── 7c10bd829485524574d0.jpg │ └── defce59678b9cc64e57c.png └── templates └── verification.html /.gitignore: -------------------------------------------------------------------------------- 1 | #Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | .env 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | cover/ 54 | 55 | # Translations 56 | *.mo 57 | *.pot 58 | 59 | # Django stuff: 60 | *.log 61 | local_settings.py 62 | db.sqlite3 63 | db.sqlite3-journal 64 | 65 | # Flask stuff: 66 | instance/ 67 | .webassets-cache 68 | 69 | # Scrapy stuff: 70 | .scrapy 71 | 72 | # Sphinx documentation 73 | docs/_build/ 74 | 75 | # PyBuilder 76 | .pybuilder/ 77 | target/ 78 | 79 | # Jupyter Notebook 80 | .ipynb_checkpoints 81 | 82 | # IPython 83 | profile_default/ 84 | ipython_config.py 85 | 86 | # pyenv 87 | # For a library or package, you might want to ignore these files since the code is 88 | # intended to run in multiple environments; otherwise, check them in: 89 | # .python-version 90 | 91 | # pipenv 92 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 93 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 94 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 95 | # install all needed dependencies. 96 | #Pipfile.lock 97 | 98 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 99 | __pypackages__/ 100 | 101 | # Celery stuff 102 | celerybeat-schedule 103 | celerybeat.pid 104 | 105 | # SageMath parsed files 106 | *.sage.py 107 | 108 | # Environments 109 | .env 110 | .venv 111 | env/ 112 | venv/ 113 | ENV/ 114 | env.bak/ 115 | venv.bak/ 116 | 117 | # Spyder project settings 118 | .spyderproject 119 | .spyproject 120 | 121 | # Rope project settings 122 | .ropeproject 123 | 124 | # mkdocs documentation 125 | /site 126 | 127 | # mypy 128 | .mypy_cache/ 129 | .dmypy.json 130 | dmypy.json 131 | 132 | # Pyre type checker 133 | .pyre/ 134 | 135 | # pytype static type analyzer 136 | .pytype/ 137 | 138 | # Cython debug symbols 139 | cython_debug/ -------------------------------------------------------------------------------- /.gitignore.save: -------------------------------------------------------------------------------- 1 | https://github.com/Princekrampah/learningFastAPI 2 | -------------------------------------------------------------------------------- /basicAPI/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from pydantic import BaseModel 3 | from tortoise.contrib.pydantic import pydantic_model_creator 4 | from tortoise.contrib.fastapi import register_tortoise 5 | from models import Todo 6 | 7 | app = FastAPI() 8 | 9 | # before you can create a database, just like in flutter, 10 | # you need a model from whele to map data to this is created 11 | # using pydantic BaseModel class. You can then specify the data 12 | # types uisng typehints 13 | class CreateTodo(BaseModel): 14 | name: str 15 | description: str 16 | 17 | # we can also create our own pydantic model using 18 | # tortoise, using this, we dont need our custom BaseModel any more 19 | todo_pydantic = pydantic_model_creator(Todo, name = "Todo") 20 | todo_pydanticIn = pydantic_model_creator(Todo, name = "TodoIn", exclude_readonly = True) 21 | 22 | todos = [] 23 | 24 | @app.get('/todo') 25 | async def get_todos(): 26 | todos = await todo_pydantic.from_queryset(Todo.all()) 27 | return {'status': 'ok', 'data' : todos} 28 | 29 | @app.get('/todo/{todo_id}') 30 | async def get_todo(todo_id:int): 31 | todo = await todo_pydantic.from_queryset_single(Todo.get(id = todo_id)) 32 | return {'status': 'ok', 'data' : todo} 33 | 34 | @app.delete('/todo/{todo_id}') 35 | async def delete_todo(todo_id: int): 36 | await Todo.filter(id = todo_id).delete() 37 | return {} 38 | 39 | @app.post('/todo') 40 | async def create_todo(todo: todo_pydanticIn): 41 | print(todo.json()) 42 | # exclude_unset will deal with null collumns if the user does not pass it in 43 | todo_obj = await Todo.create(**todo.dict(exclude_unset = True)) 44 | response = await todo_pydantic.from_tortoise_orm(todo_obj) 45 | return {'status' : 'ok', 'data' : response} 46 | 47 | 48 | register_tortoise( 49 | app, 50 | db_url='sqlite://db.sqlite3', 51 | modules={'models': ['models']}, 52 | generate_schemas = True, 53 | add_exception_handlers = True 54 | ) 55 | -------------------------------------------------------------------------------- /basicAPI/models.py: -------------------------------------------------------------------------------- 1 | from tortoise.models import Model 2 | from tortoise import fields 3 | 4 | class Todo(Model): 5 | id = fields.IntField(pk = True) 6 | name = fields.CharField(100, unique = True) 7 | description = fields.CharField(400, unique = True) 8 | # https://tortoise-orm.readthedocs.io/en/latest/fields.html#module-tortoise.fields.data 9 | date_created = fields.DatetimeField(auto_now_add = True) 10 | last_update = fields.DatetimeField(auto_now = True) 11 | 12 | class Meta: 13 | ordering = ['date_created'] 14 | 15 | def __str__(self): 16 | return self.name -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiofiles==0.7.0 2 | aioredis==1.3.1 3 | aiosmtplib==1.1.4 4 | aiosqlite==0.16.1 5 | async-timeout==3.0.1 6 | bcrypt==3.2.0 7 | blinker==1.4 8 | certifi==2020.12.5 9 | cffi==1.14.5 10 | click==7.1.2 11 | dnspython==2.1.0 12 | email-validator==1.1.2 13 | fakeredis==1.5.0 14 | fastapi==0.63.0 15 | fastapi-mail==0.3.5.0 16 | h11==0.12.0 17 | hiredis==2.0.0 18 | httpcore==0.12.3 19 | httpx==0.17.1 20 | idna==3.1 21 | iso8601==0.1.14 22 | Jinja2==2.11.3 23 | MarkupSafe==1.1.1 24 | passlib==1.7.4 25 | Pillow==8.2.0 26 | pkg-resources==0.0.0 27 | pycparser==2.20 28 | pydantic==1.8.1 29 | PyJWT==2.0.1 30 | pypika-tortoise==0.1.0 31 | python-dotenv==0.17.0 32 | python-multipart==0.0.5 33 | pytz==2020.5 34 | redis==3.5.3 35 | rfc3986==1.4.0 36 | six==1.15.0 37 | sniffio==1.2.0 38 | sortedcontainers==2.3.0 39 | starlette==0.13.6 40 | tortoise-orm==0.17.2 41 | typing-extensions==3.7.4.3 42 | uvicorn==0.13.4 43 | -------------------------------------------------------------------------------- /shoppingAPI/authentication.py: -------------------------------------------------------------------------------- 1 | from dotenv import dotenv_values 2 | import jwt 3 | from fastapi import (FastAPI, Depends, HTTPException, status) 4 | 5 | from models import (User, Business, Product, user_pydantic, user_pydanticIn, 6 | product_pydantic,product_pydanticIn, business_pydantic, 7 | business_pydanticIn) 8 | from passlib.context import CryptContext 9 | 10 | 11 | config_credentials = dict(dotenv_values(".env")) 12 | 13 | pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto") 14 | 15 | def get_password_hash(password): 16 | return pwd_context.hash(password) 17 | 18 | def verify_password(plain_password, hashed_password): 19 | return pwd_context.verify(plain_password, hashed_password) 20 | 21 | 22 | 23 | async def authenticate_user(username: str, password: str): 24 | user = await User.get(username = username) 25 | 26 | if user and verify_password(password, user.password): 27 | return user 28 | 29 | return False 30 | 31 | async def token_generator(username: str, password: str): 32 | user = await authenticate_user(username, password) 33 | 34 | if not user: 35 | raise HTTPException( 36 | status_code = status.HTTP_401_UNAUTHORIZED, 37 | detail = "Invalid username or password", 38 | headers={"WWW-Authenticate": "Bearer"}, 39 | ) 40 | 41 | token_data = { 42 | "id" : user.id, 43 | "username" : user.username 44 | } 45 | 46 | token = jwt.encode(token_data, config_credentials["SECRET"]) 47 | return token 48 | 49 | 50 | 51 | async def verify_token(token: str): 52 | try: 53 | payload = jwt.decode(token, config_credentials['SECRET'], algorithms = ['HS256']) 54 | user = await User.get(id = payload.get('id')) 55 | except: 56 | raise HTTPException( 57 | status_code = status.HTTP_401_UNAUTHORIZED, 58 | detail = "Invalid username or password", 59 | headers={"WWW-Authenticate": "Bearer"}, 60 | ) 61 | 62 | return user 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /shoppingAPI/database.sqlite3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Princekrampah/learningFastAPI/40d25792e3e66aeb2fad0e67574d286e1d489fde/shoppingAPI/database.sqlite3 -------------------------------------------------------------------------------- /shoppingAPI/email_template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |

Account Verification

8 |
9 |

Thanks for choosing EasyShopas, please 10 | click on the link below to verify your account

11 | 12 | 13 | Verify your email 14 | 15 | 16 |
If you did not register for EasyShopas, 17 | please kindly ignore this email and nothing will happen. Thanks

18 |

19 | 20 | -------------------------------------------------------------------------------- /shoppingAPI/emails.py: -------------------------------------------------------------------------------- 1 | from fastapi import (BackgroundTasks, UploadFile, 2 | File, Form, Depends, HTTPException, status) 3 | 4 | from dotenv import dotenv_values 5 | from pydantic import BaseModel, EmailStr 6 | from typing import List 7 | from fastapi_mail import FastMail, MessageSchema,ConnectionConfig 8 | import jwt 9 | from models import User 10 | 11 | config_credentials = dict(dotenv_values(".env")) 12 | conf = ConnectionConfig( 13 | MAIL_USERNAME = config_credentials["EMAIL"], 14 | MAIL_PASSWORD = config_credentials["PASS"], 15 | MAIL_FROM = config_credentials["EMAIL"], 16 | MAIL_PORT = 587, 17 | MAIL_SERVER = "smtp.gmail.com", 18 | MAIL_TLS = True, 19 | MAIL_SSL = False, 20 | USE_CREDENTIALS = True 21 | ) 22 | 23 | class EmailSchema(BaseModel): 24 | email: List[EmailStr] 25 | 26 | async def send_email(email : list, instance: User): 27 | 28 | token_data = { 29 | "id" : instance.id, 30 | "username" : instance.username 31 | } 32 | 33 | token = jwt.encode(token_data, config_credentials["SECRET"]) 34 | 35 | template = f""" 36 | 37 | 38 | 39 | 40 | 41 |
42 |

Account Verification

43 |
44 |

Thanks for choosing EasyShopas, please 45 | click on the link below to verify your account

46 | 47 |
49 | Verify your email 50 | 51 | 52 |

If you did not register for EasyShopas, 53 | please kindly ignore this email and nothing will happen. Thanks

54 |

55 | 56 | 57 | """ 58 | 59 | message = MessageSchema( 60 | subject="EasyShopas Account Verification Mail", 61 | recipients=email, # List of recipients, as many as you can pass 62 | body=template, 63 | subtype="html" 64 | ) 65 | 66 | fm = FastMail(conf) 67 | await fm.send_message(message) -------------------------------------------------------------------------------- /shoppingAPI/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import (FastAPI, BackgroundTasks, UploadFile, 2 | File, Form, Depends, HTTPException, status, Request) 3 | from tortoise.contrib.fastapi import register_tortoise 4 | from models import (User, Business, Product, user_pydantic, user_pydanticIn, 5 | product_pydantic,product_pydanticIn, business_pydantic, 6 | business_pydanticIn, user_pydanticOut) 7 | 8 | # signals 9 | from tortoise.signals import post_save 10 | from typing import List, Optional, Type 11 | from tortoise import BaseDBAsyncClient 12 | 13 | from starlette.responses import JSONResponse 14 | from starlette.requests import Request 15 | 16 | #authentication and authorization 17 | import jwt 18 | from dotenv import dotenv_values 19 | from fastapi.security import ( 20 | OAuth2PasswordBearer, 21 | OAuth2PasswordRequestForm 22 | ) 23 | # self packages 24 | from emails import * 25 | from authentication import * 26 | from dotenv import dotenv_values 27 | import math 28 | 29 | # user image uploads 30 | # pip install python-multipart 31 | from fastapi import File, UploadFile 32 | import secrets 33 | # static files 34 | from fastapi.staticfiles import StaticFiles 35 | 36 | # pillow 37 | from PIL import Image 38 | 39 | # templates 40 | from fastapi.templating import Jinja2Templates 41 | 42 | # HTMLResponse 43 | from fastapi.responses import HTMLResponse 44 | 45 | config_credentials = dict(dotenv_values(".env")) 46 | 47 | 48 | app = FastAPI() 49 | 50 | # static files 51 | # pip install aiofiles 52 | app.mount("/static", StaticFiles(directory="static"), name="static") 53 | 54 | # authorization configs 55 | oath2_scheme = OAuth2PasswordBearer(tokenUrl = 'token') 56 | 57 | # password helper functions 58 | @app.post('/token') 59 | async def generate_token(request_form: OAuth2PasswordRequestForm = Depends()): 60 | token = await token_generator(request_form.username, request_form.password) 61 | return {'access_token' : token, 'token_type' : 'bearer'} 62 | 63 | 64 | 65 | # process signals here 66 | @post_save(User) 67 | async def create_business( 68 | sender: "Type[User]", 69 | instance: User, 70 | created: bool, 71 | using_db: "Optional[BaseDBAsyncClient]", 72 | update_fields: List[str]) -> None: 73 | 74 | if created: 75 | business_obj = await Business.create( 76 | business_name = instance.username, owner = instance) 77 | await business_pydantic.from_tortoise_orm(business_obj) 78 | # send email functionality 79 | await send_email([instance.email], instance) 80 | 81 | 82 | @app.post('/registration') 83 | async def user_registration(user: user_pydanticIn): 84 | user_info = user.dict(exclude_unset = True) 85 | user_info['password'] = get_password_hash(user_info['password']) 86 | user_obj = await User.create(**user_info) 87 | new_user = await user_pydantic.from_tortoise_orm(user_obj) 88 | 89 | return {"status" : "ok", 90 | "data" : 91 | f"Hello {new_user.username} thanks for choosing our services. Please check your email inbox and click on the link to confirm your registration."} 92 | 93 | 94 | 95 | # template for email verification 96 | templates = Jinja2Templates(directory="templates") 97 | 98 | @app.get('/verification', response_class=HTMLResponse) 99 | # make sure to import request from fastapi and HTMLResponse 100 | async def email_verification(request: Request, token: str): 101 | user = await verify_token(token) 102 | if user and not user.is_verified: 103 | user.is_verified = True 104 | await user.save() 105 | return templates.TemplateResponse("verification.html", 106 | {"request": request, "username": user.username} 107 | ) 108 | raise HTTPException( 109 | status_code = status.HTTP_401_UNAUTHORIZED, 110 | detail = "Invalid or expired token", 111 | headers={"WWW-Authenticate": "Bearer"}, 112 | ) 113 | 114 | 115 | async def get_current_user(token: str = Depends(oath2_scheme)): 116 | try: 117 | payload = jwt.decode(token, config_credentials['SECRET'], algorithms = ['HS256']) 118 | user = await User.get(id = payload.get("id")) 119 | except: 120 | raise HTTPException( 121 | status_code = status.HTTP_401_UNAUTHORIZED, 122 | detail = "Invalid username or password", 123 | headers={"WWW-Authenticate": "Bearer"}, 124 | ) 125 | 126 | return await user 127 | 128 | @app.post('/user/me') 129 | async def user_login(user: user_pydantic = Depends(get_current_user)): 130 | 131 | business = await Business.get(owner = user) 132 | logo = business.logo 133 | logo = "localhost:8000/static/images/"+logo 134 | 135 | return {"status" : "ok", 136 | "data" : 137 | { 138 | "username" : user.username, 139 | "email" : user.email, 140 | "verified" : user.is_verified, 141 | "join_date" : user.join_date.strftime("%b %d %Y"), 142 | "logo" : logo 143 | } 144 | } 145 | 146 | 147 | @app.post("/products") 148 | async def add_new_product(product: product_pydanticIn, 149 | user: user_pydantic = Depends(get_current_user)): 150 | product = product.dict(exclude_unset = True) 151 | # to avoid division by zero error 152 | if product['original_price'] > 0: 153 | product["percentage_discount"] = ((product["original_price"] - product['new_price'] )/ product['original_price']) * 100 154 | 155 | product_obj = await Product.create(**product, business = user) 156 | product_obj = await product_pydantic.from_tortoise_orm(product_obj) 157 | return {"status" : "ok", "data" : product_obj} 158 | 159 | 160 | @app.get("/products") 161 | async def get_products(): 162 | response = await product_pydantic.from_tortoise_orm(Product.all()) 163 | return {"status" : "ok", "data" : response} 164 | 165 | 166 | @app.get("/products/{id}") 167 | async def specific_product(id: int): 168 | product = await Product.get(id = id) 169 | business = await product.business 170 | owner = await business.owner 171 | response = await product_pydantic.from_queryset_single(Product.get(id = id)) 172 | print(type(response)) 173 | return {"status" : "ok", 174 | "data" : 175 | { 176 | "product_details" : response, 177 | "business_details" : { 178 | "name" : business.business_name, 179 | "city" : business.city, 180 | "region" : business.region, 181 | "description" : business.business_description, 182 | "logo" : business.logo, 183 | "owner_id" : owner.id, 184 | "email" : owner.email, 185 | "join_date" : owner.join_date.strftime("%b %d %Y") 186 | } 187 | } 188 | } 189 | 190 | 191 | @app.delete("/products/{id}") 192 | async def delete_product(id: int, user: user_pydantic = Depends(get_current_user)): 193 | product = await Product.get(id = id) 194 | business = await product.business 195 | owner = await business.owner 196 | if user == owner: 197 | product.delete() 198 | else: 199 | raise HTTPException( 200 | status_code = status.HTTP_401_UNAUTHORIZED, 201 | detail = "Not authenticated to perform this action", 202 | headers={"WWW-Authenticate": "Bearer"}, 203 | ) 204 | return {"status" : "ok"} 205 | 206 | 207 | # image upload 208 | @app.post("/uploadfile/profile") 209 | async def create_upload_file(file: UploadFile = File(...), 210 | user: user_pydantic = Depends(get_current_user)): 211 | FILEPATH = "./static/images/" 212 | filename = file.filename 213 | extension = filename.split(".")[1] 214 | 215 | if extension not in ["jpg", "png"]: 216 | return {"status" : "error", "detail" : "file extension not allowed"} 217 | 218 | token_name = secrets.token_hex(10)+"."+extension 219 | generated_name = FILEPATH + token_name 220 | file_content = await file.read() 221 | with open(generated_name, "wb") as file: 222 | file.write(file_content) 223 | 224 | 225 | # pillow 226 | img = Image.open(generated_name) 227 | img = img.resize(size = (200,200)) 228 | img.save(generated_name) 229 | 230 | file.close() 231 | 232 | business = await Business.get(owner = user) 233 | owner = await business.owner 234 | 235 | # check if the user making the request is authenticated 236 | print(user.id) 237 | print(owner.id) 238 | if owner == user: 239 | business.logo = token_name 240 | await business.save() 241 | 242 | else: 243 | raise HTTPException( 244 | status_code = status.HTTP_401_UNAUTHORIZED, 245 | detail = "Not authenticated to perform this action", 246 | headers={"WWW-Authenticate": "Bearer"}, 247 | ) 248 | file_url = "localhost:8000" + generated_name[1:] 249 | return {"status": "ok", "filename": file_url} 250 | 251 | 252 | @app.post("/uploadfile/product/{id}") 253 | # check for product owner before making the changes. 254 | async def create_upload_file(id: int, file: UploadFile = File(...), 255 | user: user_pydantic = Depends(get_current_user)): 256 | FILEPATH = "./static/images/" 257 | filename = file.filename 258 | extension = filename.split(".")[1] 259 | 260 | if extension not in ["jpg", "png"]: 261 | return {"status" : "error", "detail" : "file extension not allowed"} 262 | 263 | token_name = secrets.token_hex(10)+"."+extension 264 | generated_name = FILEPATH + token_name 265 | file_content = await file.read() 266 | with open(generated_name, "wb") as file: 267 | file.write(file_content) 268 | 269 | 270 | # pillow 271 | img = Image.open(generated_name) 272 | img = img.resize(size = (200,200)) 273 | img.save(generated_name) 274 | 275 | file.close() 276 | 277 | #get product details 278 | product = await Product.get(id = id) 279 | business = await product.business 280 | owner = await business.owner 281 | 282 | # check if the user making the request is authenticated 283 | if owner == user: 284 | product.product_image = token_name 285 | await product.save() 286 | 287 | else: 288 | raise HTTPException( 289 | status_code = status.HTTP_401_UNAUTHORIZED, 290 | detail = "Not authenticated to perform this action", 291 | headers={"WWW-Authenticate": "Bearer"}, 292 | ) 293 | 294 | 295 | file_url = "localhost:8000" + generated_name[1:] 296 | return {"status": "ok", "filename": file_url} 297 | 298 | 299 | 300 | 301 | register_tortoise( 302 | app, 303 | db_url='sqlite://database.sqlite3', 304 | modules={'models': ['models']}, 305 | generate_schemas = True, 306 | add_exception_handlers = True 307 | ) -------------------------------------------------------------------------------- /shoppingAPI/models.py: -------------------------------------------------------------------------------- 1 | from tortoise import Model 2 | from pydantic import BaseModel 3 | from tortoise import fields 4 | from tortoise.contrib.pydantic import pydantic_model_creator 5 | from datetime import datetime 6 | 7 | # Referrences: 8 | # https://stackoverflow.com/questions/7296846/how-to-implement-one-to-one-one-to-many-and-many-to-many-relationships-while-de 9 | 10 | class User(Model): 11 | # fields are null = False by default but i specified it for clarity 12 | id = fields.IntField(pk = True, index = True) 13 | username = fields.CharField(max_length = 20, null = False, unique = True) 14 | email = fields.CharField(max_length = 200, null = False, unique = True) 15 | password = fields.CharField(max_length = 100, null = False) 16 | is_verified = fields.BooleanField(default = False) 17 | join_date = fields.DatetimeField(default = datetime.utcnow) 18 | 19 | 20 | class Business(Model): 21 | id = fields.IntField(pk = True, index = True) 22 | business_name = fields.CharField(max_length = 20, nullable = False, unique = True) 23 | city = fields.CharField(max_length = 100, null = False, default = "Unspecified") 24 | region = fields.CharField(max_length = 100, null = False, default = "Unspecified") 25 | business_description = fields.TextField(null = True) 26 | logo = fields.CharField(max_length =200, null = False, default = "default.jpg") 27 | owner = fields.ForeignKeyField('models.User', related_name='business') 28 | 29 | 30 | class Product(Model): 31 | # 12 signinficant digits, 2 of the significant digits are decimals. 32 | id = fields.IntField(pk = True, index = True) 33 | name = fields.CharField(max_length = 100, null = False, index = True) 34 | category = fields.CharField(max_length = 20, index = True) 35 | original_price = fields.DecimalField(max_digits = 12, decimal_places = 2) 36 | new_price = fields.DecimalField(max_digits = 12, decimal_places = 2) 37 | percentage_discount = fields.IntField() 38 | offer_expiration_date = fields.DateField(default = datetime.utcnow) 39 | product_image = fields.CharField(max_length =200, null = False, default = "productDefault.jpg") 40 | date_published = fields.DatetimeField(default = datetime.utcnow) 41 | # the value for percentage_discount will be computed and 42 | # added using storing user input in the routes 43 | 44 | business = fields.ForeignKeyField('models.Business', related_name='products') 45 | 46 | 47 | # user_pydanticIn will be used to create users since it allows 48 | # readOnly fields same for all the others 49 | user_pydantic = pydantic_model_creator(User, name ="User", exclude=("is_verified",)) 50 | user_pydanticIn = pydantic_model_creator(User, name = "UserIn", exclude_readonly = True, exclude=("is_verified", 'join_date')) 51 | user_pydanticOut = pydantic_model_creator(User, name = "UserOut", exclude = ("password", )) 52 | 53 | business_pydantic = pydantic_model_creator(Business, name = "Business") 54 | business_pydanticIn = pydantic_model_creator(Business, name = "Business", exclude_readonly = True) 55 | 56 | 57 | product_pydantic = pydantic_model_creator(Product, name = "Product") 58 | product_pydanticIn = pydantic_model_creator(Product, name = "ProductIn", 59 | exclude = ("percentage_discount", "id")) 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /shoppingAPI/static/images/011766faf1be9e199294.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Princekrampah/learningFastAPI/40d25792e3e66aeb2fad0e67574d286e1d489fde/shoppingAPI/static/images/011766faf1be9e199294.png -------------------------------------------------------------------------------- /shoppingAPI/static/images/7c10bd829485524574d0.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Princekrampah/learningFastAPI/40d25792e3e66aeb2fad0e67574d286e1d489fde/shoppingAPI/static/images/7c10bd829485524574d0.jpg -------------------------------------------------------------------------------- /shoppingAPI/static/images/defce59678b9cc64e57c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Princekrampah/learningFastAPI/40d25792e3e66aeb2fad0e67574d286e1d489fde/shoppingAPI/static/images/defce59678b9cc64e57c.png -------------------------------------------------------------------------------- /shoppingAPI/templates/verification.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Document 9 | 10 | 11 | 12 |
13 |
14 |
15 |
16 |
17 | Email Verification 18 |
19 |
20 |
Email Verified Successfully
21 |

Hello {{username}}, you can now login into you account, enjoy the free discounts :)

22 | EasyShopas 23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------