├── .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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------