initial commit
commit
7773011c11
@ -0,0 +1,68 @@
|
||||
# ---> Python
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
env/
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*,cover
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
target/
|
||||
|
||||
# Pycharm
|
||||
.idea
|
||||
|
||||
# Mongo
|
||||
/data
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
@ -0,0 +1,25 @@
|
||||
# MongoDB Quickstart with Python course
|
||||
|
||||
This project follow's Michael C. Kennedy's [MongoDB Quickstart.](https://github.com/mikeckennedy/mongodb-quickstart-course)
|
||||
|
||||
## Docker instructions
|
||||
|
||||
The docker container and documentation can be found [here](https://hub.docker.com/_/mongo).
|
||||
|
||||
We will use the following command to start up a container to host our data and
|
||||
|
||||
```
|
||||
docker run --name quick-mongo -p 27017:27017 -v /Users/Drewbednar/PycharmProjects/try_mongo/data:/data/db -d mongo:3.6.12-xenial
|
||||
```
|
||||
|
||||
When looking to restart your container simply run:
|
||||
|
||||
```
|
||||
docker start quick-mongo
|
||||
```
|
||||
|
||||
To check the configuration for the container run:
|
||||
|
||||
```
|
||||
docker inspect quick-mongo
|
||||
```
|
Binary file not shown.
After Width: | Height: | Size: 55 KiB |
@ -0,0 +1,9 @@
|
||||
# Restore this database
|
||||
|
||||
If you want to use the exact, final data from the videos, you can restore this database into MongoDB.
|
||||
|
||||
1. Make sure MongoDB is running (steps here: [Windows](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-windows/), [macOS](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-os-x/), [Linux](https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/))
|
||||
2. Run this command: `mongorestore --drop --db snake_bnb ./snake_bnb/` in the same folder
|
||||
as you unzipped the data files (containing the snake_bnb folder).
|
||||
|
||||
You'll need mongorestore in your path for this to work. Otherwise use the full path.
|
Binary file not shown.
@ -0,0 +1,5 @@
|
||||
pymongo
|
||||
mongoengine
|
||||
tqdm
|
||||
colorama
|
||||
python-dateutil
|
@ -0,0 +1,18 @@
|
||||
import mongoengine
|
||||
|
||||
|
||||
class Booking(mongoengine.EmbeddedDocument):
|
||||
guest_owner_id = mongoengine.ObjectIdField()
|
||||
guest_snake_id = mongoengine.ObjectIdField()
|
||||
|
||||
booked_date = mongoengine.DateTimeField()
|
||||
check_in_date = mongoengine.DateTimeField(required=True)
|
||||
check_out_date = mongoengine.DateTimeField(required=True)
|
||||
|
||||
review = mongoengine.StringField()
|
||||
rating = mongoengine.IntField(default=0)
|
||||
|
||||
@property
|
||||
def duration_in_days(self):
|
||||
dt = self.check_out_date - self.check_in_date
|
||||
return dt.days
|
@ -0,0 +1,22 @@
|
||||
import datetime
|
||||
import mongoengine
|
||||
|
||||
from data.bookings import Booking
|
||||
|
||||
|
||||
class Cage(mongoengine.Document):
|
||||
registered_date = mongoengine.DateTimeField(default=datetime.datetime.now)
|
||||
|
||||
name = mongoengine.StringField(required=True)
|
||||
price = mongoengine.FloatField(required=True)
|
||||
square_meters = mongoengine.FloatField(required=True)
|
||||
is_carpeted = mongoengine.BooleanField(required=True)
|
||||
has_toys = mongoengine.BooleanField(required=True)
|
||||
allow_dangerous_snakes = mongoengine.BooleanField(default=False)
|
||||
|
||||
bookings = mongoengine.EmbeddedDocumentListField(Booking)
|
||||
|
||||
meta = {
|
||||
'db_alias': 'core',
|
||||
'collection': 'cages'
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
import mongoengine
|
||||
|
||||
|
||||
def global_init():
|
||||
mongoengine.register_connection(alias='core', name='snake_bnb')
|
@ -0,0 +1,16 @@
|
||||
import datetime
|
||||
import mongoengine
|
||||
|
||||
|
||||
class Owner(mongoengine.Document):
|
||||
registered_date = mongoengine.DateTimeField(default=datetime.datetime.now)
|
||||
name = mongoengine.StringField(required=True)
|
||||
email = mongoengine.StringField(required=True)
|
||||
|
||||
snake_ids = mongoengine.ListField()
|
||||
cage_ids = mongoengine.ListField()
|
||||
|
||||
meta = {
|
||||
'db_alias': 'core',
|
||||
'collection': 'owners'
|
||||
}
|
@ -0,0 +1 @@
|
||||
We'll put some data things in this folder when we start the course.
|
@ -0,0 +1,16 @@
|
||||
import datetime
|
||||
import mongoengine
|
||||
|
||||
|
||||
class Snake(mongoengine.Document):
|
||||
registered_date = mongoengine.DateTimeField(default=datetime.datetime.now)
|
||||
species = mongoengine.StringField(required=True)
|
||||
|
||||
length = mongoengine.FloatField(required=True)
|
||||
name = mongoengine.StringField(required=True)
|
||||
is_venomous = mongoengine.BooleanField(required=True)
|
||||
|
||||
meta = {
|
||||
'db_alias': 'core',
|
||||
'collection': 'snakes'
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
from data.owners import Owner
|
||||
import services.data_service as svc
|
||||
|
||||
active_account: Owner = None
|
||||
|
||||
|
||||
def reload_account():
|
||||
global active_account
|
||||
if not active_account:
|
||||
return
|
||||
|
||||
active_account = svc.find_account_by_email(active_account.email)
|
@ -0,0 +1,108 @@
|
||||
import uuid
|
||||
from typing import Callable, Any
|
||||
|
||||
|
||||
class switch:
|
||||
"""
|
||||
python-switch is a module-level implementation of the switch statement for Python.
|
||||
See https://github.com/mikeckennedy/python-switch for full details.
|
||||
Copyright Michael Kennedy (https://twitter.com/mkennedy)
|
||||
"""
|
||||
__no_result = uuid.uuid4()
|
||||
__default = uuid.uuid4()
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.cases = set()
|
||||
self._found = False
|
||||
self.__result = switch.__no_result
|
||||
self._falling_through = False
|
||||
self._func_stack = []
|
||||
|
||||
def default(self, func: Callable[[], Any]):
|
||||
"""
|
||||
Use as option final statement in switch block.
|
||||
|
||||
with switch(val) as s:
|
||||
s.case(...)
|
||||
s.case(...)
|
||||
s.default(function)
|
||||
|
||||
:param func: Any callable taking no parameters to be executed if this (default) case matches.
|
||||
:return: None
|
||||
"""
|
||||
self.case(switch.__default, func)
|
||||
|
||||
def case(self, key, func: Callable[[], Any], fallthrough=False):
|
||||
"""
|
||||
Specify a case for the switch block:
|
||||
|
||||
with switch(val) as s:
|
||||
s.case('a', function)
|
||||
s.case('b', function, fallthrough=True)
|
||||
s.default(function)
|
||||
|
||||
:param key: Key for the case test (if this is a list or range, the items will each be added as a case)
|
||||
:param func: Any callable taking no parameters to be executed if this case matches.
|
||||
:param fallthrough: Optionally fall through to the subsequent case (defaults to False)
|
||||
:return:
|
||||
"""
|
||||
if fallthrough is not None:
|
||||
if self._falling_through:
|
||||
self._func_stack.append(func)
|
||||
if not fallthrough:
|
||||
self._falling_through = False
|
||||
|
||||
if isinstance(key, list) or isinstance(key, range):
|
||||
found = False
|
||||
for i in key:
|
||||
if self.case(i, func, fallthrough=None):
|
||||
found = True
|
||||
if fallthrough is not None:
|
||||
self._falling_through = fallthrough
|
||||
return found
|
||||
|
||||
if key in self.cases:
|
||||
raise ValueError("Duplicate case: {}".format(key))
|
||||
if not func:
|
||||
raise ValueError("Action for case cannot be None.")
|
||||
if not callable(func):
|
||||
raise ValueError("Func must be callable.")
|
||||
|
||||
self.cases.add(key)
|
||||
if key == self.value or not self._found and key == self.__default:
|
||||
self._func_stack.append(func)
|
||||
self._found = True
|
||||
if fallthrough is not None:
|
||||
self._falling_through = fallthrough
|
||||
return True
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if exc_val:
|
||||
raise exc_val
|
||||
|
||||
if not self._func_stack:
|
||||
raise Exception("Value does not match any case and there "
|
||||
"is no default case: value {}".format(self.value))
|
||||
|
||||
for func in self._func_stack:
|
||||
# noinspection PyCallingNonCallable
|
||||
self.__result = func()
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
if self.__result == switch.__no_result:
|
||||
raise Exception("No result has been computed (did you access "
|
||||
"switch.result inside the with block?)")
|
||||
|
||||
return self.__result
|
||||
|
||||
|
||||
def closed_range(start: int, stop: int, step=1) -> range:
|
||||
if start >= stop:
|
||||
raise ValueError("Start must be less than stop.")
|
||||
|
||||
return range(start, stop + step, step)
|
@ -0,0 +1,65 @@
|
||||
from colorama import Fore
|
||||
import program_guests
|
||||
import program_hosts
|
||||
import data.mongo_setup as mongo_setup
|
||||
|
||||
|
||||
def main():
|
||||
mongo_setup.global_init()
|
||||
|
||||
print_header()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if find_user_intent() == 'book':
|
||||
program_guests.run()
|
||||
else:
|
||||
program_hosts.run()
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
|
||||
|
||||
def print_header():
|
||||
snake = \
|
||||
"""
|
||||
~8I?? OM
|
||||
M..I?Z 7O?M
|
||||
? ?8 ?I8
|
||||
MOM???I?ZO??IZ
|
||||
M:??O??????MII
|
||||
OIIII$NI7??I$
|
||||
IIID?IIZ
|
||||
+$ ,IM ,~7??I7$
|
||||
I? MM ?:::?7$
|
||||
?? 7,::?778+=~+??8
|
||||
??Z ?,:,:I7$I??????+~~+
|
||||
??D N==7,::,I77??????????=~$
|
||||
~??? I~~I?,::,77$Z?????????????
|
||||
???+~M $+~+???? :::II7$II777II??????N
|
||||
OI??????????I$$M=,:+7??I$7I???????????
|
||||
N$$$ZDI =++:$???????????II78
|
||||
=~~:~~7II777$$Z
|
||||
~ZMM~ """
|
||||
|
||||
print(Fore.WHITE + '**************** SNAKE BnB ****************')
|
||||
print(Fore.GREEN + snake)
|
||||
print(Fore.WHITE + '*********************************************')
|
||||
print()
|
||||
print("Welcome to Snake BnB!")
|
||||
print("Why are you here?")
|
||||
print()
|
||||
|
||||
|
||||
def find_user_intent():
|
||||
print("[g] Book a cage for your snake")
|
||||
print("[h] Offer extra cage space")
|
||||
print()
|
||||
choice = input("Are you a [g]uest or [h]ost? ")
|
||||
if choice == 'h':
|
||||
return 'offer'
|
||||
|
||||
return 'book'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,171 @@
|
||||
import datetime
|
||||
from dateutil import parser
|
||||
|
||||
from infrastructure.switchlang import switch
|
||||
import program_hosts as hosts
|
||||
import services.data_service as svc
|
||||
from program_hosts import success_msg, error_msg
|
||||
import infrastructure.state as state
|
||||
|
||||
|
||||
def run():
|
||||
print(' ****************** Welcome guest **************** ')
|
||||
print()
|
||||
|
||||
show_commands()
|
||||
|
||||
while True:
|
||||
action = hosts.get_action()
|
||||
|
||||
with switch(action) as s:
|
||||
s.case('c', hosts.create_account)
|
||||
s.case('l', hosts.log_into_account)
|
||||
|
||||
s.case('a', add_a_snake)
|
||||
s.case('y', view_your_snakes)
|
||||
s.case('b', book_a_cage)
|
||||
s.case('v', view_bookings)
|
||||
s.case('m', lambda: 'change_mode')
|
||||
|
||||
s.case('?', show_commands)
|
||||
s.case('', lambda: None)
|
||||
s.case(['x', 'bye', 'exit', 'exit()'], hosts.exit_app)
|
||||
|
||||
s.default(hosts.unknown_command)
|
||||
|
||||
state.reload_account()
|
||||
|
||||
if action:
|
||||
print()
|
||||
|
||||
if s.result == 'change_mode':
|
||||
return
|
||||
|
||||
|
||||
def show_commands():
|
||||
print('What action would you like to take:')
|
||||
print('[C]reate an account')
|
||||
print('[L]ogin to your account')
|
||||
print('[B]ook a cage')
|
||||
print('[A]dd a snake')
|
||||
print('View [y]our snakes')
|
||||
print('[V]iew your bookings')
|
||||
print('[M]ain menu')
|
||||
print('e[X]it app')
|
||||
print('[?] Help (this info)')
|
||||
print()
|
||||
|
||||
|
||||
def add_a_snake():
|
||||
print(' ****************** Add a snake **************** ')
|
||||
if not state.active_account:
|
||||
error_msg("You must log in first to add a snake")
|
||||
return
|
||||
|
||||
name = input("What is your snake's name? ")
|
||||
if not name:
|
||||
error_msg('cancelled')
|
||||
return
|
||||
|
||||
length = float(input('How long is your snake (in meters)? '))
|
||||
species = input("Species? ")
|
||||
is_venomous = input("Is your snake venomous [y]es, [n]o? ").lower().startswith('y')
|
||||
|
||||
snake = svc.add_snake(state.active_account, name, length, species, is_venomous)
|
||||
state.reload_account()
|
||||
success_msg('Created {} with id {}'.format(snake.name, snake.id))
|
||||
|
||||
|
||||
def view_your_snakes():
|
||||
print(' ****************** Your snakes **************** ')
|
||||
if not state.active_account:
|
||||
error_msg("You must log in first to view your snakes")
|
||||
return
|
||||
|
||||
snakes = svc.get_snakes_for_user(state.active_account.id)
|
||||
print("You have {} snakes.".format(len(snakes)))
|
||||
for s in snakes:
|
||||
print(" * {} is a {} that is {}m long and is {}venomous.".format(
|
||||
s.name,
|
||||
s.species,
|
||||
s.length,
|
||||
'' if s.is_venomous else 'not '
|
||||
))
|
||||
|
||||
|
||||
def book_a_cage():
|
||||
print(' ****************** Book a cage **************** ')
|
||||
if not state.active_account:
|
||||
error_msg("You must log in first to book a cage")
|
||||
return
|
||||
|
||||
snakes = svc.get_snakes_for_user(state.active_account.id)
|
||||
if not snakes:
|
||||
error_msg('You must first [a]dd a snake before you can book a cage.')
|
||||
return
|
||||
|
||||
print("Let's start by finding available cages.")
|
||||
start_text = input("Check-in date [yyyy-mm-dd]: ")
|
||||
if not start_text:
|
||||
error_msg('cancelled')
|
||||
return
|
||||
|
||||
checkin = parser.parse(
|
||||
start_text
|
||||
)
|
||||
checkout = parser.parse(
|
||||
input("Check-out date [yyyy-mm-dd]: ")
|
||||
)
|
||||
if checkin >= checkout:
|
||||
error_msg('Check in must be before check out')
|
||||
return
|
||||
|
||||
print()
|
||||
for idx, s in enumerate(snakes):
|
||||
print('{}. {} (length: {}, venomous: {})'.format(
|
||||
idx + 1,
|
||||
s.name,
|
||||
s.length,
|
||||
'yes' if s.is_venomous else 'no'
|
||||
))
|
||||
|
||||
snake = snakes[int(input('Which snake do you want to book (number)')) - 1]
|
||||
|
||||
cages = svc.get_available_cages(checkin, checkout, snake)
|
||||
|
||||
print("There are {} cages available in that time.".format(len(cages)))
|
||||
for idx, c in enumerate(cages):
|
||||
print(" {}. {} with {}m carpeted: {}, has toys: {}.".format(
|
||||
idx + 1,
|
||||
c.name,
|
||||
c.square_meters,
|
||||
'yes' if c.is_carpeted else 'no',
|
||||
'yes' if c.has_toys else 'no'))
|
||||
|
||||
if not cages:
|
||||
error_msg("Sorry, no cages are available for that date.")
|
||||
return
|
||||
|
||||
cage = cages[int(input('Which cage do you want to book (number)')) - 1]
|
||||
svc.book_cage(state.active_account, snake, cage, checkin, checkout)
|
||||
|
||||
success_msg('Successfully booked {} for {} at ${}/night.'.format(cage.name, snake.name, cage.price))
|
||||
|
||||
|
||||
def view_bookings():
|
||||
print(' ****************** Your bookings **************** ')
|
||||
if not state.active_account:
|
||||
error_msg("You must log in first to register a cage")
|
||||
return
|
||||
|
||||
snakes = {s.id: s for s in svc.get_snakes_for_user(state.active_account.id)}
|
||||
bookings = svc.get_bookings_for_user(state.active_account.email)
|
||||
|
||||
print("You have {} bookings.".format(len(bookings)))
|
||||
for b in bookings:
|
||||
print(' * Snake: {} is booked at {} from {} for {} days.'.format(
|
||||
snakes.get(b.guest_snake_id).name,
|
||||
b.cage.name,
|
||||
datetime.date(b.check_in_date.year, b.check_in_date.month, b.check_in_date.day),
|
||||
(b.check_out_date - b.check_in_date).days
|
||||
))
|
@ -0,0 +1,217 @@
|
||||
import datetime
|
||||
from colorama import Fore
|
||||
from dateutil import parser
|
||||
|
||||
from infrastructure.switchlang import switch
|
||||
import infrastructure.state as state
|
||||
import services.data_service as svc
|
||||
|
||||
|
||||
def run():
|
||||
print(' ****************** Welcome host **************** ')
|
||||
print()
|
||||
|
||||
show_commands()
|
||||
|
||||
while True:
|
||||
action = get_action()
|
||||
|
||||
with switch(action) as s:
|
||||
s.case('c', create_account)
|
||||
s.case('a', create_account)
|
||||
s.case('l', log_into_account)
|
||||
s.case('y', list_cages)
|
||||
s.case('r', register_cage)
|
||||
s.case('u', update_availability)
|
||||
s.case('v', view_bookings)
|
||||
s.case('m', lambda: 'change_mode')
|
||||
s.case(['x', 'bye', 'exit', 'exit()'], exit_app)
|
||||
s.case('?', show_commands)
|
||||
s.case('', lambda: None)
|
||||
s.default(unknown_command)
|
||||
|
||||
if action:
|
||||
print()
|
||||
|
||||
if s.result == 'change_mode':
|
||||
return
|
||||
|
||||
|
||||
def show_commands():
|
||||
print('What action would you like to take:')
|
||||
print('[C]reate an [a]ccount')
|
||||
print('[L]ogin to your account')
|
||||
print('List [y]our cages')
|
||||
print('[R]egister a cage')
|
||||
print('[U]pdate cage availability')
|
||||
print('[V]iew your bookings')
|
||||
print('Change [M]ode (guest or host)')
|
||||
print('e[X]it app')
|
||||
print('[?] Help (this info)')
|
||||
print()
|
||||
|
||||
|
||||
def create_account():
|
||||
print(' ****************** REGISTER **************** ')
|
||||
|
||||
name = input('What is your name? ')
|
||||
email = input('What is your email? ').strip().lower()
|
||||
|
||||
old_account = svc.find_account_by_email(email)
|
||||
if old_account:
|
||||
error_msg(f"ERROR: Account with email {email} already exists.")
|
||||
return
|
||||
|
||||
state.active_account = svc.create_account(name, email)
|
||||
success_msg(f"Created new account with id {state.active_account.id}.")
|
||||
|
||||
|
||||
def log_into_account():
|
||||
print(' ****************** LOGIN **************** ')
|
||||
|
||||
email = input('What is your email? ').strip().lower()
|
||||
account = svc.find_account_by_email(email)
|
||||
|
||||
if not account:
|
||||
error_msg(f'Could not find account with email {email}.')
|
||||
return
|
||||
|
||||
state.active_account = account
|
||||
success_msg('Logged in successfully.')
|
||||
|
||||
|
||||
def register_cage():
|
||||
print(' ****************** REGISTER CAGE **************** ')
|
||||
|
||||
if not state.active_account:
|
||||
error_msg('You must login first to register a cage.')
|
||||
return
|
||||
|
||||
meters = input('How many square meters is the cage? ')
|
||||
if not meters:
|
||||
error_msg('Cancelled')
|
||||
return
|
||||
|
||||
meters = float(meters)
|
||||
carpeted = input("Is it carpeted [y, n]? ").lower().startswith('y')
|
||||
has_toys = input("Have snake toys [y, n]? ").lower().startswith('y')
|
||||
allow_dangerous = input("Can you host venomous snakes [y, n]? ").lower().startswith('y')
|
||||
name = input("Give your cage a name: ")
|
||||
price = float(input("How much are you charging? "))
|
||||
|
||||
cage = svc.register_cage(
|
||||
state.active_account, name,
|
||||
allow_dangerous, has_toys, carpeted, meters, price
|
||||
)
|
||||
|
||||
state.reload_account()
|
||||
success_msg(f'Register new cage with id {cage.id}.')
|
||||
|
||||
|
||||
def list_cages(suppress_header=False):
|
||||
if not suppress_header:
|
||||
print(' ****************** Your cages **************** ')
|
||||
|
||||
if not state.active_account:
|
||||
error_msg('You must login first to register a cage.')
|
||||
return
|
||||
|
||||
cages = svc.find_cages_for_user(state.active_account)
|
||||
print(f"You have {len(cages)} cages.")
|
||||
for idx, c in enumerate(cages):
|
||||
print(f' {idx+1}. {c.name} is {c.square_meters} meters.')
|
||||
for b in c.bookings:
|
||||
print(' * Booking: {}, {} days, booked? {}'.format(
|
||||
b.check_in_date,
|
||||
(b.check_out_date - b.check_in_date).days,
|
||||
'YES' if b.booked_date is not None else 'no'
|
||||
))
|
||||
|
||||
|
||||
def update_availability():
|
||||
print(' ****************** Add available date **************** ')
|
||||
|
||||
if not state.active_account:
|
||||
error_msg("You must log in first to register a cage")
|
||||
return
|
||||
|
||||
list_cages(suppress_header=True)
|
||||
|
||||
cage_number = input("Enter cage number: ")
|
||||
if not cage_number.strip():
|
||||
error_msg('Cancelled')
|
||||
print()
|
||||
return
|
||||
|
||||
cage_number = int(cage_number)
|
||||
|
||||
cages = svc.find_cages_for_user(state.active_account)
|
||||
selected_cage = cages[cage_number - 1]
|
||||
|
||||
success_msg("Selected cage {}".format(selected_cage.name))
|
||||
|
||||
start_date = parser.parse(
|
||||
input("Enter available date [yyyy-mm-dd]: ")
|
||||
)
|
||||
days = int(input("How many days is this block of time? "))
|
||||
|
||||
svc.add_available_date(
|
||||
selected_cage,
|
||||
start_date,
|
||||
days
|
||||
)
|
||||
|
||||
success_msg(f'Date added to cage {selected_cage.name}.')
|
||||
|
||||
|
||||
def view_bookings():
|
||||
print(' ****************** Your bookings **************** ')
|
||||
|
||||
if not state.active_account:
|
||||
error_msg("You must log in first to register a cage")
|
||||
return
|
||||
|
||||
cages = svc.find_cages_for_user(state.active_account)
|
||||
|
||||
bookings = [
|
||||
(c, b)
|
||||
for c in cages
|
||||
for b in c.bookings
|
||||
if b.booked_date is not None
|
||||
]
|
||||
|
||||
print("You have {} bookings.".format(len(bookings)))
|
||||
for c, b in bookings:
|
||||
print(' * Cage: {}, booked date: {}, from {} for {} days.'.format(
|
||||
c.name,
|
||||
datetime.date(b.booked_date.year, b.booked_date.month, b.booked_date.day),
|
||||
datetime.date(b.check_in_date.year, b.check_in_date.month, b.check_in_date.day),
|
||||
b.duration_in_days
|
||||
))
|
||||
|
||||
|
||||
def exit_app():
|
||||
print()
|
||||
print('bye')
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
|
||||
def get_action():
|
||||
text = '> '
|
||||
if state.active_account:
|
||||
text = f'{state.active_account.name}> '
|
||||
|
||||
action = input(Fore.YELLOW + text + Fore.WHITE)
|
||||
return action.strip().lower()
|
||||
|
||||
|
||||
def unknown_command():
|
||||
print("Sorry we didn't understand that command.")
|
||||
|
||||
|
||||
def success_msg(text):
|
||||
print(Fore.LIGHTGREEN_EX + text + Fore.WHITE)
|
||||
|
||||
|
||||
def error_msg(text):
|
||||
print(Fore.LIGHTRED_EX + text + Fore.WHITE)
|
@ -0,0 +1,147 @@
|
||||
from typing import List
|
||||
|
||||
import datetime
|
||||
|
||||
import bson
|
||||
|
||||
from data.bookings import Booking
|
||||
from data.cages import Cage
|
||||
from data.owners import Owner
|
||||
from data.snakes import Snake
|
||||
|
||||
|
||||
def create_account(name: str, email: str) -> Owner:
|
||||
owner = Owner()
|
||||
owner.name = name
|
||||
owner.email = email
|
||||
|
||||
owner.save()
|
||||
|
||||
return owner
|
||||
|
||||
|
||||
def find_account_by_email(email: str) -> Owner:
|
||||
owner = Owner.objects(email=email).first()
|
||||
return owner
|
||||
|
||||
|
||||
def register_cage(active_account: Owner,
|
||||
name, allow_dangerous, has_toys,
|
||||
carpeted, meters, price) -> Cage:
|
||||
cage = Cage()
|
||||
|
||||
cage.name = name
|
||||
cage.square_meters = meters
|
||||
cage.is_carpeted = carpeted
|
||||
cage.has_toys = has_toys
|
||||
cage.allow_dangerous_snakes = allow_dangerous
|
||||
cage.price = price
|
||||
|
||||
cage.save()
|
||||
|
||||
account = find_account_by_email(active_account.email)
|
||||
account.cage_ids.append(cage.id)
|
||||
account.save()
|
||||
|
||||
return cage
|
||||
|
||||
|
||||
def find_cages_for_user(account: Owner) -> List[Cage]:
|
||||
query = Cage.objects(id__in=account.cage_ids)
|
||||
cages = list(query)
|
||||
|
||||
return cages
|
||||
|
||||
|
||||
def add_available_date(cage: Cage,
|
||||
start_date: datetime.datetime, days: int) -> Cage:
|
||||
booking = Booking()
|
||||
booking.check_in_date = start_date
|
||||
booking.check_out_date = start_date + datetime.timedelta(days=days)
|
||||
|
||||
cage = Cage.objects(id=cage.id).first()
|
||||
cage.bookings.append(booking)
|
||||
cage.save()
|
||||
|
||||
return cage
|
||||
|
||||
|
||||
def add_snake(account, name, length, species, is_venomous) -> Snake:
|
||||
snake = Snake()
|
||||
snake.name = name
|
||||
snake.length = length
|
||||
snake.species = species
|
||||
snake.is_venomous = is_venomous
|
||||
snake.save()
|
||||
|
||||
owner = find_account_by_email(account.email)
|
||||
owner.snake_ids.append(snake.id)
|
||||
owner.save()
|
||||
|
||||
return snake
|
||||
|
||||
|
||||
def get_snakes_for_user(user_id: bson.ObjectId) -> List[Snake]:
|
||||
owner = Owner.objects(id=user_id).first()
|
||||
snakes = Snake.objects(id__in=owner.snake_ids).all()
|
||||
|
||||
return list(snakes)
|
||||
|
||||
|
||||
def get_available_cages(checkin: datetime.datetime,
|
||||
checkout: datetime.datetime, snake: Snake) -> List[Cage]:
|
||||
min_size = snake.length / 4
|
||||
|
||||
query = Cage.objects() \
|
||||
.filter(square_meters__gte=min_size) \
|
||||
.filter(bookings__check_in_date__lte=checkin) \
|
||||
.filter(bookings__check_out_date__gte=checkout)
|
||||
|
||||
if snake.is_venomous:
|
||||
query = query.filter(allow_dangerous_snakes=True)
|
||||
|
||||
cages = query.order_by('price', '-square_meters')
|
||||
|
||||
final_cages = []
|
||||
for c in cages:
|
||||
for b in c.bookings:
|
||||
if b.check_in_date <= checkin and b.check_out_date >= checkout and b.guest_snake_id is None:
|
||||
final_cages.append(c)
|
||||
|
||||
return final_cages
|
||||
|
||||
|
||||
def book_cage(account, snake, cage, checkin, checkout):
|
||||
booking: Booking = None
|
||||
|
||||
for b in cage.bookings:
|
||||
if b.check_in_date <= checkin and b.check_out_date >= checkout and b.guest_snake_id is None:
|
||||
booking = b
|
||||
break
|
||||
|
||||
booking.guest_owner_id = account.id
|
||||
booking.guest_snake_id = snake.id
|
||||
booking.booked_date = datetime.datetime.now()
|
||||
|
||||
cage.save()
|
||||
|
||||
|
||||
def get_bookings_for_user(email: str) -> List[Booking]:
|
||||
account = find_account_by_email(email)
|
||||
|
||||
booked_cages = Cage.objects() \
|
||||
.filter(bookings__guest_owner_id=account.id) \
|
||||
.only('bookings', 'name')
|
||||
|
||||
def map_cage_to_booking(cage, booking):
|
||||
booking.cage = cage
|
||||
return booking
|
||||
|
||||
bookings = [
|
||||
map_cage_to_booking(cage, booking)
|
||||
for cage in booked_cages
|
||||
for booking in cage.bookings
|
||||
if booking.guest_owner_id == account.id
|
||||
]
|
||||
|
||||
return bookings
|
@ -0,0 +1 @@
|
||||
We'll put our data access code and logic here.
|
@ -0,0 +1,5 @@
|
||||
pymongo
|
||||
mongoengine
|
||||
tqdm
|
||||
colorama
|
||||
python-dateutil
|
@ -0,0 +1 @@
|
||||
We'll put some data things in this folder when we start the course.
|
@ -0,0 +1,10 @@
|
||||
active_account = None
|
||||
|
||||
|
||||
def reload_account():
|
||||
global active_account
|
||||
if not active_account:
|
||||
return
|
||||
|
||||
# TODO: pull owner account from the database.
|
||||
pass
|
@ -0,0 +1,108 @@
|
||||
import uuid
|
||||
from typing import Callable, Any
|
||||
|
||||
|
||||
class switch:
|
||||
"""
|
||||
python-switch is a module-level implementation of the switch statement for Python.
|
||||
See https://github.com/mikeckennedy/python-switch for full details.
|
||||
Copyright Michael Kennedy (https://twitter.com/mkennedy)
|
||||
"""
|
||||
__no_result = uuid.uuid4()
|
||||
__default = uuid.uuid4()
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
self.cases = set()
|
||||
self._found = False
|
||||
self.__result = switch.__no_result
|
||||
self._falling_through = False
|
||||
self._func_stack = []
|
||||
|
||||
def default(self, func: Callable[[], Any]):
|
||||
"""
|
||||
Use as option final statement in switch block.
|
||||
|
||||
with switch(val) as s:
|
||||
s.case(...)
|
||||
s.case(...)
|
||||
s.default(function)
|
||||
|
||||
:param func: Any callable taking no parameters to be executed if this (default) case matches.
|
||||
:return: None
|
||||
"""
|
||||
self.case(switch.__default, func)
|
||||
|
||||
def case(self, key, func: Callable[[], Any], fallthrough=False):
|
||||
"""
|
||||
Specify a case for the switch block:
|
||||
|
||||
with switch(val) as s:
|
||||
s.case('a', function)
|
||||
s.case('b', function, fallthrough=True)
|
||||
s.default(function)
|
||||
|
||||
:param key: Key for the case test (if this is a list or range, the items will each be added as a case)
|
||||
:param func: Any callable taking no parameters to be executed if this case matches.
|
||||
:param fallthrough: Optionally fall through to the subsequent case (defaults to False)
|
||||
:return:
|
||||
"""
|
||||
if fallthrough is not None:
|
||||
if self._falling_through:
|
||||
self._func_stack.append(func)
|
||||
if not fallthrough:
|
||||
self._falling_through = False
|
||||
|
||||
if isinstance(key, list) or isinstance(key, range):
|
||||
found = False
|
||||
for i in key:
|
||||
if self.case(i, func, fallthrough=None):
|
||||
found = True
|
||||
if fallthrough is not None:
|
||||
self._falling_through = fallthrough
|
||||
return found
|
||||
|
||||
if key in self.cases:
|
||||
raise ValueError("Duplicate case: {}".format(key))
|
||||
if not func:
|
||||
raise ValueError("Action for case cannot be None.")
|
||||
if not callable(func):
|
||||
raise ValueError("Func must be callable.")
|
||||
|
||||
self.cases.add(key)
|
||||
if key == self.value or not self._found and key == self.__default:
|
||||
self._func_stack.append(func)
|
||||
self._found = True
|
||||
if fallthrough is not None:
|
||||
self._falling_through = fallthrough
|
||||
return True
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
if exc_val:
|
||||
raise exc_val
|
||||
|
||||
if not self._func_stack:
|
||||
raise Exception("Value does not match any case and there "
|
||||
"is no default case: value {}".format(self.value))
|
||||
|
||||
for func in self._func_stack:
|
||||
# noinspection PyCallingNonCallable
|
||||
self.__result = func()
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
if self.__result == switch.__no_result:
|
||||
raise Exception("No result has been computed (did you access "
|
||||
"switch.result inside the with block?)")
|
||||
|
||||
return self.__result
|
||||
|
||||
|
||||
def closed_range(start: int, stop: int, step=1) -> range:
|
||||
if start >= stop:
|
||||
raise ValueError("Start must be less than stop.")
|
||||
|
||||
return range(start, stop + step, step)
|
@ -0,0 +1,64 @@
|
||||
from colorama import Fore
|
||||
import program_guests
|
||||
import program_hosts
|
||||
|
||||
|
||||
def main():
|
||||
# TODO: Setup mongoengine global values
|
||||
|
||||
print_header()
|
||||
|
||||
try:
|
||||
while True:
|
||||
if find_user_intent() == 'book':
|
||||
program_guests.run()
|
||||
else:
|
||||
program_hosts.run()
|
||||
except KeyboardInterrupt:
|
||||
return
|
||||
|
||||
|
||||
def print_header():
|
||||
snake = \
|
||||
"""
|
||||
~8I?? OM
|
||||
M..I?Z 7O?M
|
||||
? ?8 ?I8
|
||||
MOM???I?ZO??IZ
|
||||
M:??O??????MII
|
||||
OIIII$NI7??I$
|
||||
IIID?IIZ
|
||||
+$ ,IM ,~7??I7$
|
||||
I? MM ?:::?7$
|
||||
?? 7,::?778+=~+??8
|
||||
??Z ?,:,:I7$I??????+~~+
|
||||
??D N==7,::,I77??????????=~$
|
||||
~??? I~~I?,::,77$Z?????????????
|
||||
???+~M $+~+???? :::II7$II777II??????N
|
||||
OI??????????I$$M=,:+7??I$7I???????????
|
||||
N$$$ZDI =++:$???????????II78
|
||||
=~~:~~7II777$$Z
|
||||
~ZMM~ """
|
||||
|
||||
print(Fore.WHITE + '**************** SNAKE BnB ****************')
|
||||
print(Fore.GREEN + snake)
|
||||
print(Fore.WHITE + '*********************************************')
|
||||
print()
|
||||
print("Welcome to Snake BnB!")
|
||||
print("Why are you here?")
|
||||
print()
|
||||
|
||||
|
||||
def find_user_intent():
|
||||
print("[g] Book a cage for your snake")
|
||||
print("[h] Offer extra cage space")
|
||||
print()
|
||||
choice = input("Are you a [g]uest or [h]ost? ")
|
||||
if choice == 'h':
|
||||
return 'offer'
|
||||
|
||||
return 'book'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,88 @@
|
||||
from infrastructure.switchlang import switch
|
||||
import program_hosts as hosts
|
||||
import infrastructure.state as state
|
||||
|
||||
|
||||
def run():
|
||||
print(' ****************** Welcome guest **************** ')
|
||||
print()
|
||||
|
||||
show_commands()
|
||||
|
||||
while True:
|
||||
action = hosts.get_action()
|
||||
|
||||
with switch(action) as s:
|
||||
s.case('c', hosts.create_account)
|
||||
s.case('l', hosts.log_into_account)
|
||||
|
||||
s.case('a', add_a_snake)
|
||||
s.case('y', view_your_snakes)
|
||||
s.case('b', book_a_cage)
|
||||
s.case('v', view_bookings)
|
||||
s.case('m', lambda: 'change_mode')
|
||||
|
||||
s.case('?', show_commands)
|
||||
s.case('', lambda: None)
|
||||
s.case(['x', 'bye', 'exit', 'exit()'], hosts.exit_app)
|
||||
|
||||
s.default(hosts.unknown_command)
|
||||
|
||||
state.reload_account()
|
||||
|
||||
if action:
|
||||
print()
|
||||
|
||||
if s.result == 'change_mode':
|
||||
return
|
||||
|
||||
|
||||
def show_commands():
|
||||
print('What action would you like to take:')
|
||||
print('[C]reate an account')
|
||||
print('[L]ogin to your account')
|
||||
print('[B]ook a cage')
|
||||
print('[A]dd a snake')
|
||||
print('View [y]our snakes')
|
||||
print('[V]iew your bookings')
|
||||
print('[M]ain menu')
|
||||
print('e[X]it app')
|
||||
print('[?] Help (this info)')
|
||||
print()
|
||||
|
||||
|
||||
def add_a_snake():
|
||||
print(' ****************** Add a snake **************** ')
|
||||
# TODO: Require an account
|
||||
# TODO: Get snake info from user
|
||||
# TODO: Create the snake in the DB.
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def view_your_snakes():
|
||||
print(' ****************** Your snakes **************** ')
|
||||
|
||||
# TODO: Require an account
|
||||
# TODO: Get snakes from DB, show details list
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def book_a_cage():
|
||||
print(' ****************** Book a cage **************** ')
|
||||
# TODO: Require an account
|
||||
# TODO: Verify they have a snake
|
||||
# TODO: Get dates and select snake
|
||||
# TODO: Find cages available across date range
|
||||
# TODO: Let user select cage to book.
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def view_bookings():
|
||||
print(' ****************** Your bookings **************** ')
|
||||
# TODO: Require an account
|
||||
# TODO: List booking info along with snake info
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
@ -0,0 +1,131 @@
|
||||
from colorama import Fore
|
||||
from infrastructure.switchlang import switch
|
||||
import infrastructure.state as state
|
||||
|
||||
|
||||
def run():
|
||||
print(' ****************** Welcome host **************** ')
|
||||
print()
|
||||
|
||||
show_commands()
|
||||
|
||||
while True:
|
||||
action = get_action()
|
||||
|
||||
with switch(action) as s:
|
||||
s.case('c', create_account)
|
||||
s.case('a', log_into_account)
|
||||
s.case('l', list_cages)
|
||||
s.case('r', register_cage)
|
||||
s.case('u', update_availability)
|
||||
s.case('v', view_bookings)
|
||||
s.case('m', lambda: 'change_mode')
|
||||
s.case(['x', 'bye', 'exit', 'exit()'], exit_app)
|
||||
s.case('?', show_commands)
|
||||
s.case('', lambda: None)
|
||||
s.default(unknown_command)
|
||||
|
||||
if action:
|
||||
print()
|
||||
|
||||
if s.result == 'change_mode':
|
||||
return
|
||||
|
||||
|
||||
def show_commands():
|
||||
print('What action would you like to take:')
|
||||
print('[C]reate an account')
|
||||
print('Login to your [a]ccount')
|
||||
print('[L]ist your cages')
|
||||
print('[R]egister a cage')
|
||||
print('[U]pdate cage availability')
|
||||
print('[V]iew your bookings')
|
||||
print('Change [M]ode (guest or host)')
|
||||
print('e[X]it app')
|
||||
print('[?] Help (this info)')
|
||||
print()
|
||||
|
||||
|
||||
def create_account():
|
||||
print(' ****************** REGISTER **************** ')
|
||||
# TODO: Get name & email
|
||||
# TODO: Create account, set as logged in.
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def log_into_account():
|
||||
print(' ****************** LOGIN **************** ')
|
||||
|
||||
# TODO: Get email
|
||||
# TODO: Find account in DB, set as logged in.
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def register_cage():
|
||||
print(' ****************** REGISTER CAGE **************** ')
|
||||
|
||||
# TODO: Require an account
|
||||
# TODO: Get info about cage
|
||||
# TODO: Save cage to DB.
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def list_cages(supress_header=False):
|
||||
if not supress_header:
|
||||
print(' ****************** Your cages **************** ')
|
||||
|
||||
# TODO: Require an account
|
||||
# TODO: Get cages, list details
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def update_availability():
|
||||
print(' ****************** Add available date **************** ')
|
||||
|
||||
# TODO: Require an account
|
||||
# TODO: list cages
|
||||
# TODO: Choose cage
|
||||
# TODO: Set dates, save to DB.
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def view_bookings():
|
||||
print(' ****************** Your bookings **************** ')
|
||||
|
||||
# TODO: Require an account
|
||||
# TODO: Get cages, and nested bookings as flat list
|
||||
# TODO: Print details for each
|
||||
|
||||
print(" -------- NOT IMPLEMENTED -------- ")
|
||||
|
||||
|
||||
def exit_app():
|
||||
print()
|
||||
print('bye')
|
||||
raise KeyboardInterrupt()
|
||||
|
||||
|
||||
def get_action():
|
||||
text = '> '
|
||||
if state.active_account:
|
||||
text = f'{state.active_account.name}> '
|
||||
|
||||
action = input(Fore.YELLOW + text + Fore.WHITE)
|
||||
return action.strip().lower()
|
||||
|
||||
|
||||
def unknown_command():
|
||||
print("Sorry we didn't understand that command.")
|
||||
|
||||
|
||||
def success_msg(text):
|
||||
print(Fore.LIGHTGREEN_EX + text + Fore.WHITE)
|
||||
|
||||
|
||||
def error_msg(text):
|
||||
print(Fore.LIGHTRED_EX + text + Fore.WHITE)
|
@ -0,0 +1 @@
|
||||
We'll put our data access code and logic here.
|
@ -0,0 +1,118 @@
|
||||
00:01 Hello and welcome to MongoDB Quick Start with Python.
|
||||
00:05 MongoDB is one of the most popular
|
||||
00:07 and exciting database technologies around.
|
||||
00:09 Python is one of the most popular
|
||||
00:11 and fastest growing language there is
|
||||
00:12 and these two technologies work great together
|
||||
00:15 and that's exactly what this course is about.
|
||||
00:17 We're going to quickly get started with MongoDB,
|
||||
00:20 write some code against it,
|
||||
00:22 and build some realistic applications.
|
||||
00:24 The goal with this class is to teach you MongoDB.
|
||||
00:27 We assume you know a little bit of Python
|
||||
00:29 but you'll of course pick some things up along the way
|
||||
00:31 if you don't know it already, and really,
|
||||
00:33 we want to get you going quick, hence the name, Quick Start.
|
||||
00:36 Let's get started by talking about
|
||||
00:38 what we're going to cover in this course.
|
||||
00:39 We're going to focus on three main things.
|
||||
00:41 We're going to start with why do you care about
|
||||
00:44 NoSQL and Document Databases.
|
||||
00:46 How do document databases make working with schemas easier,
|
||||
00:50 modeling data easier, as well as add performance
|
||||
00:54 and flexibility to our applications.
|
||||
00:55 We're going to talk about modeling specifically,
|
||||
00:58 'cause it is one of the things that is
|
||||
00:59 pretty challenging about document databases.
|
||||
01:02 If you come from a relational database world,
|
||||
01:04 you probably know about third normal form.
|
||||
01:06 This is a way to carefully and
|
||||
01:08 more structured way to plan out how you model your data.
|
||||
01:11 In document databases, you don't really have that.
|
||||
01:13 There's a lot more sort of flexibility
|
||||
01:16 in how you design things.
|
||||
01:18 This is great for you once you understand it
|
||||
01:20 and get really good at it, but it's challenging to know
|
||||
01:22 how to model things when you get started
|
||||
01:24 because there's so much flexibility
|
||||
01:26 and it's kind of open-ended.
|
||||
01:27 It feels a little more like art than science, in some sense.
|
||||
01:30 So we're going to focus specifically on
|
||||
01:32 techniques and trade offs, and some guidelines
|
||||
01:35 I have to come up with to help you be successful
|
||||
01:38 modeling your data with document databases such as MongoDB.
|
||||
01:42 Then we're going to start writing code for real.
|
||||
01:44 We're going to use a Mongo ODM, Object Document Mapper.
|
||||
01:49 Think of SQL Alchemy but for document databases.
|
||||
01:51 They're called MongoEngine, and we're going to use that
|
||||
01:54 to create some classes to model our data,
|
||||
01:57 and map those classes to and from MongoDB
|
||||
01:59 and use that as the foundation of our application.
|
||||
02:02 Speaking of applications,
|
||||
02:03 what are we going to build in this class?
|
||||
02:05 Well, we're going to build an AirBnB clone but for snakes.
|
||||
02:09 Okay, so we're going to build Snake BnB
|
||||
02:11 and this allows you, when you're traveling with your snake,
|
||||
02:14 your pet snake, you don't want it to have to
|
||||
02:17 live out in the car or something like that.
|
||||
02:19 You want to get it a cage that it can live in,
|
||||
02:21 a proper snake cage where it'll be happy.
|
||||
02:23 There'll be owners of cages
|
||||
02:25 who can put their cages up for rent,
|
||||
02:27 and snake owners, pet owners
|
||||
02:29 who want to travel with their pets and
|
||||
02:31 have their pet have a place to stay.
|
||||
02:33 We're going to create this sort of silly
|
||||
02:35 AirBnB knockoff clone, but we're going to model
|
||||
02:37 many of the operations you would see
|
||||
02:39 in real AirBnB on our application here
|
||||
02:42 so it's going to be pretty rich in terms of data.
|
||||
02:44 As far as tools go, well of course,
|
||||
02:45 we're going to talk about MongoDB, right.
|
||||
02:47 That's the database we're using,
|
||||
02:49 but you're also going to learn some other things
|
||||
02:50 that are pretty awesome in the course.
|
||||
02:52 We're going to talk about MongoEngine.
|
||||
02:54 This is the primary library
|
||||
02:55 we're going to use to talk to MongoDB.
|
||||
02:57 It's built upon another one that's very popular
|
||||
03:00 that has sort of the lowest level
|
||||
03:01 official way to talk to MongoDB called PyMongo,
|
||||
03:04 so you might see a little bit of
|
||||
03:05 both of those actually in the course.
|
||||
03:07 We're going to use what I think is
|
||||
03:08 the best tool for accessing MongoDB,
|
||||
03:11 something that used to be called Robomongo
|
||||
03:14 but now it's called Robo 3T,
|
||||
03:16 'cause it was acquired by a company called 3T.
|
||||
03:18 This is part command line,
|
||||
03:20 part GUI way to interact with MongoDB,
|
||||
03:23 and it's beautiful, and free open source, it's great.
|
||||
03:26 Also, we're going to use PyCharm for our Python code.
|
||||
03:29 You don't have to use PyCharm for this course,
|
||||
03:31 but you'll see me using it, and I think you'll see
|
||||
03:33 a lot of benefits as we go.
|
||||
03:36 Speaking of me, who am I anyway?
|
||||
03:37 Who is this voice that you're listening to?
|
||||
03:39 Hi, my name is Michael Kennedy.
|
||||
03:40 You can find me on Twitter at @mkennedy.
|
||||
03:43 What makes me qualified to teach this course?
|
||||
03:45 Well, first of all, I host the most popular Python podcast
|
||||
03:48 called Talk Python to Me, and I've interviewed
|
||||
03:51 many, many people, including some of the folks from MongoDB,
|
||||
03:54 the company as well as authors who've written about
|
||||
03:57 MongoDB design patterns and things like that,
|
||||
03:59 so I've had a lot of experience
|
||||
04:00 working with people from Python and MongoDB.
|
||||
04:03 I've created the Talk Python Training Company
|
||||
04:06 and written many Python courses
|
||||
04:08 and MongoDB courses there as well.
|
||||
04:10 And also, I am part of the MongoDB Masters Program.
|
||||
04:14 This is a group of about 35 external community members
|
||||
04:18 who give advice back to MongoDB,
|
||||
04:21 and I've been part of this for many years,
|
||||
04:23 worked closely with the folks inside MongoDB over the years.
|
||||
04:26 So this is what you have in store for you,
|
||||
04:28 lots of awesome MongoDB and Python.
|
||||
04:30 I hope you're excited.
|
||||
04:31 Let's get right to it.
|
@ -0,0 +1,87 @@
|
||||
00:01 Let's begin by looking at why you might choose MongoDB
|
||||
00:05 over other NoSQL databases, other document databases,
|
||||
00:09 or even other relational databases.
|
||||
00:12 I'm sure you've heard of MongoDB.
|
||||
00:14 That's why you're taking this course.
|
||||
00:15 But how popular is it relative to other databases?
|
||||
00:19 Is it really the right choice?
|
||||
00:20 Well, let's look at some data from 2017.
|
||||
00:23 If you compare MongoDB against other NoSQL databases,
|
||||
00:27 Cassandra, CouchDB, RavenDB, things like that.
|
||||
00:31 You will find one of these databases is unlike the others.
|
||||
00:34 Look at that, that's incredible,
|
||||
00:36 how much more popular MongoDB is over these.
|
||||
00:39 It's not just the popularity that it's five,
|
||||
00:42 10 times, maybe 50 times more popular than RavenDB.
|
||||
00:46 That's great.
|
||||
00:47 That's very important.
|
||||
00:47 But also, the trend.
|
||||
00:50 These others are either flat or turning downwards.
|
||||
00:51 And this is, besides the little blip here,
|
||||
00:54 based on probably the end of the year numbers
|
||||
00:57 or something like that.
|
||||
00:58 At the end of the data, this is a really big deal.
|
||||
01:01 This is incredible how much growth is here,
|
||||
01:04 and it's still going up.
|
||||
01:05 MongoDB is really, really popular
|
||||
01:07 in terms of a database that people are using.
|
||||
01:11 And that's great because that means it's well-tested.
|
||||
01:14 When we get the section a little bit later,
|
||||
01:16 we'll look at some of the users of MongoDB.
|
||||
01:18 But it's really important
|
||||
01:19 that there are some heavy workloads put onto these databases
|
||||
01:23 that you're going to depend upon.
|
||||
01:24 And if it can take what those people are doing,
|
||||
01:26 surely, it can take what you have to throw at it as well.
|
||||
01:29 Now, MongoDB is also loved.
|
||||
01:31 If you look at Stack Overflow,
|
||||
01:33 at their 2017 Developer's Survey,
|
||||
01:36 and you look at the most loved databases:
|
||||
01:38 these are databases that the developers are using currently
|
||||
01:43 and how they feel about it.
|
||||
01:44 We see MongoDB ranks right near the top.
|
||||
01:46 So, definitely 55% of the people
|
||||
01:49 who are using MongoDB love it compared to
|
||||
01:51 say SQLite or Cassandra.
|
||||
01:54 So, this is pretty good.
|
||||
01:55 But what's even more interesting
|
||||
01:56 is that it's the most wanted database.
|
||||
01:59 These are technologies that you're not currently
|
||||
02:01 able to work with but you would like to.
|
||||
02:03 So, there are tons of people that want to work with MongoDB
|
||||
02:05 but for whatever reason, they've got some Legacy system
|
||||
02:07 built on MySQL or whatever, they don't get the chance to.
|
||||
02:11 All these pieces of data tell you
|
||||
02:13 MongoDB is a really good technology to have
|
||||
02:15 in your tool belt.
|
||||
02:16 And it's easy to get ahold of.
|
||||
02:18 MongoDB is open source and it's free.
|
||||
02:20 You can just go to github.com/mongodb/mongo
|
||||
02:23 and clone it and it's right there.
|
||||
02:25 You can see it has 11,000 stars, 3,000 forks.
|
||||
02:29 And this screenshot I took here was updated two hours ago.
|
||||
02:32 It's under very, very active development.
|
||||
02:34 It's a live and vibrant project.
|
||||
02:36 Finally, if you actually want to get MongoDB,
|
||||
02:38 don't go to GitHub.
|
||||
02:40 GitHub is cool, it's great.
|
||||
02:41 You have the source but don't go there.
|
||||
02:42 What you really want to do is you want
|
||||
02:43 to go to mongodb.com/download-center.
|
||||
02:47 Or just go to mongodb.com and click Download.
|
||||
02:49 It'll take you here.
|
||||
02:50 And you download here, you get it as binary.
|
||||
02:53 So, if you're on Mac,
|
||||
02:54 I recommend you use Homebrew to install it.
|
||||
02:56 But you can also download a tarball.
|
||||
02:58 If you're on Windows, get the MSI.
|
||||
03:00 And on Linux, you can actually install it
|
||||
03:02 with aptitude or some package manager like that.
|
||||
03:05 In this course, we're not going to go into the details
|
||||
03:07 of setting up MongoDB.
|
||||
03:08 Just follow the instructions.
|
||||
03:09 In the full MongoDB course you have,
|
||||
03:11 we walk through all the steps,
|
||||
03:12 but we just want to get started.
|
||||
03:13 This the quick start.
|
||||
03:14 So, let's keep moving.
|
@ -0,0 +1,72 @@
|
||||
00:00 Let's look at one of these records stored in MongoDB
|
||||
00:03 to see how document databases work.
|
||||
00:05 Here we have a JSON record.
|
||||
00:08 This is actually from one of my courses,
|
||||
00:10 the Python Jumpstart by Building 10 Apps,
|
||||
00:12 and this is how I represent it in the database.
|
||||
00:14 We've got standard columnar type things,
|
||||
00:17 so we have an ID, we have a title, a course ID,
|
||||
00:20 duration in seconds, these types of things.
|
||||
00:22 Now, first of all, you might be wondering,
|
||||
00:24 wait a minute, JSON database?
|
||||
00:26 JSON database, is that really efficient?
|
||||
00:28 Well, probably not.
|
||||
00:29 What MongoDB actually stores is a binary representation.
|
||||
00:33 So like a binary tokenized version of this record,
|
||||
00:37 but they call it BSON,
|
||||
00:38 because it's like binary JSON.
|
||||
00:40 But we're humans, we don't read binary, we read text.
|
||||
00:43 So we're looking at the textual representation.
|
||||
00:45 So this is not exactly how it gets stored,
|
||||
00:46 but this is pretty close.
|
||||
00:47 So we have these regular column type pieces of information,
|
||||
00:51 ID, title, and so on,
|
||||
00:53 but we also have this other thing, these lectures.
|
||||
00:55 Now these lectures are in this chapter represented
|
||||
00:58 by this record from this course,
|
||||
00:59 and notice the bracket in JavaScript,
|
||||
01:02 which basically this
|
||||
01:03 means that it is an array.
|
||||
01:06 And the array contains a bunch of sub-objects.
|
||||
01:08 So this is really interesting.
|
||||
01:09 Instead of just having a chapter table
|
||||
01:12 and a lecture table,
|
||||
01:13 and doing a join or something like that,
|
||||
01:15 a form key relationship,
|
||||
01:16 we're actually putting the lectures inside the same record.
|
||||
01:19 This is pretty interesting.
|
||||
01:21 When you look at it like this,
|
||||
01:22 you can imagine that this is like a precomputed join.
|
||||
01:25 If I do a query for the lecture,
|
||||
01:28 say, given ID1001,
|
||||
01:30 and I get this record back, it already has the lectures.
|
||||
01:33 I don't have to make another round trip
|
||||
01:35 to the database to get them.
|
||||
01:36 I don't have to do a join against several tables
|
||||
01:39 in that original query.
|
||||
01:40 It's literally a primary key query
|
||||
01:42 against an indexed primary key and is insanely fast,
|
||||
01:45 yet it already comes back
|
||||
01:46 with all this extra information.
|
||||
01:48 So this is really cool.
|
||||
01:49 If I get the chapter, I have the lectures, great.
|
||||
01:51 But, you might be wondering well, really,
|
||||
01:54 what if I need to ask the question in reverse?
|
||||
01:57 Like, fundamentally, if I need to get at lecture 10106,
|
||||
02:02 will I be able to query MongoDB quickly and efficiently
|
||||
02:06 to get that lecture?
|
||||
02:08 And it turns out, the answer is yes.
|
||||
02:10 And that's why document databases are awesome.
|
||||
02:12 It's not just some nested blob stored in
|
||||
02:15 the original record,
|
||||
02:16 you can, as part of the query language
|
||||
02:18 and part of indexes, traverse these hierarchies
|
||||
02:21 in very, very rich and powerful ways.
|
||||
02:23 We don't lose very much query capability
|
||||
02:26 just by putting lectures in this one record.
|
||||
02:29 So this is really neat,
|
||||
02:30 and this is sort of the foundational,
|
||||
02:32 most important take away from document databases.
|
||||
02:35 We store them in these flexible JSON type of objects,
|
||||
02:38 and we can nest additional things like lists of numbers,
|
||||
02:43 or even subdocuments, as we have in this case.
|
@ -0,0 +1,66 @@
|
||||
00:02 Before we move on,
|
||||
00:03 let's take a moment and look at who uses MongoDB.
|
||||
00:06 Programming languages and databases
|
||||
00:09 and technologies aren't necessarily popularity contests.
|
||||
00:13 Just because something's popular
|
||||
00:14 doesn't necessarily mean that it's great:
|
||||
00:17 evidence, PHP for example,
|
||||
00:19 or VB, or something like this.
|
||||
00:20 Technologies are popular and some of them are great,
|
||||
00:23 and sometimes great technologies are popular.
|
||||
00:25 There are some important things that come along
|
||||
00:28 with being popular and used by big important companies.
|
||||
00:32 That means these things are durable,
|
||||
00:34 tested, and have really been through the ringer.
|
||||
00:36 MongoDB has been used by some really big customers
|
||||
00:40 and some really interesting use cases.
|
||||
00:42 I know some of the biggest ones
|
||||
00:44 are not on this page even.
|
||||
00:46 They're not listed here.
|
||||
00:46 So we're going to take a quick tour of a couple of customers
|
||||
00:49 who are using MongoDB
|
||||
00:50 and we'll look at how they're using it.
|
||||
00:52 Okay, so the first one that I want to look at,
|
||||
00:54 scroll down here, you can see names that you might know.
|
||||
00:56 Some cool stuff here.
|
||||
00:57 Let's check out Royal Bank of Scotland.
|
||||
00:58 Banks are supposed to be conservative.
|
||||
01:01 Things like this, they probably wouldn't use weird
|
||||
01:03 NoSQL document databases,
|
||||
01:04 they're going to stick to their traditional Oracle
|
||||
01:06 or single server, or whatever.
|
||||
01:07 Well, if we look at Royal Bank of Scotland,
|
||||
01:09 they're using MongoDB
|
||||
01:11 to support a global enterprise data service
|
||||
01:14 underpinning several core trading systems.
|
||||
01:16 If you're a bank, the core trading systems are
|
||||
01:18 pretty much the center of the universe, right?
|
||||
01:22 So the fact that they're driving that with Mongo,
|
||||
01:23 and that's high performance,
|
||||
01:25 and it's doing that for them,
|
||||
01:26 that's really awesome.
|
||||
01:26 Let's check out Expedia.
|
||||
01:27 Expedia, they have their app built on MongoDB,
|
||||
01:30 and they are supporting millions of customers
|
||||
01:32 shopping for flights, hotels, cars, things like that.
|
||||
01:37 That's pretty awesome.
|
||||
01:38 Let's check out another one down here.
|
||||
01:39 EA, so video games.
|
||||
01:41 This is the world's best-selling sports game franchise,
|
||||
01:43 and they rely on MongoDB to scale the millions of players.
|
||||
01:47 That is really awesome.
|
||||
01:48 One more, before we move on.
|
||||
01:49 These guys down here a little farther,
|
||||
01:52 that's SailThru.
|
||||
01:54 SailThru is a company that does outbound mail
|
||||
01:57 and analytics and things like that.
|
||||
01:58 They are a marketing company
|
||||
02:01 very much doing tons of email type of stuff, like I said.
|
||||
02:04 And they have over 40 TB of data
|
||||
02:06 and a 120 physical, mostly physical, nodes.
|
||||
02:10 So 120 servers all working, running MongoDB,
|
||||
02:16 in some giant cluster, which is a pretty awesome use case.
|
||||
02:20 And, of course, you have things like Shutterfly
|
||||
02:21 running on MongoDB, Squarespace, on and on.
|
||||
02:24 There's a ton of cool use cases down here,
|
||||
02:26 but I think you've got the idea.
|
@ -0,0 +1,94 @@
|
||||
00:00 Are you ready to model some real applications
|
||||
00:03 in MongoDB using documents?
|
||||
00:05 It's time to get our hands dirty
|
||||
00:06 and to really start building something.
|
||||
00:07 In this chapter, we're going to go through
|
||||
00:10 our SnakeBNB application, come up with the entities,
|
||||
00:13 the classes and tables, or collections
|
||||
00:16 as they're called in MongoDB, and model them out.
|
||||
00:18 So we're going to first look at how this modeling
|
||||
00:22 and document databases compares to traditional
|
||||
00:24 third normal form modeling and relational databases.
|
||||
00:27 We're going to use a pretty simple example
|
||||
00:28 but I think you'll get the idea,
|
||||
00:30 and you'll see it much more in action
|
||||
00:33 when we build the real app.
|
||||
00:34 Let's take this simple bookstore model here.
|
||||
00:37 We have books, they have a bunch of properties.
|
||||
00:39 We have people who publish those books named publishers,
|
||||
00:41 they have a name, when they were founded,
|
||||
00:43 and you can navigate this foreign key relationship
|
||||
00:46 from publisher over to the publisher ID on the book.
|
||||
00:49 Now, we also have a user, and a user might rate a book.
|
||||
00:53 So we have users and we have ratings
|
||||
00:55 and we have a foreign key relationship between them.
|
||||
00:57 And then out from rating over to book
|
||||
00:59 we have a one-to-many relationship there, right?
|
||||
01:01 A book can have many ratings.
|
||||
01:03 We have a couple of foreign key relationships
|
||||
01:05 going on in this place.
|
||||
01:07 Now let me tell you, in a real application,
|
||||
01:08 there'd be many more little tables
|
||||
01:11 with extra information like this.
|
||||
01:12 Like ratings about books and so on, and
|
||||
01:15 let's say reviews for example; things like that.
|
||||
01:17 Maybe even related items that we've pre-computed
|
||||
01:20 from some sort of machine learning,
|
||||
01:22 but we want to store that in the database.
|
||||
01:24 So imagine this model here having
|
||||
01:26 15 tables with relationships
|
||||
01:29 across all the various pieces back and forth.
|
||||
01:31 I just want to keep it simple.
|
||||
01:32 Fits on a screen, you're not going to go crazy with it.
|
||||
01:34 So, how would we model this in MongoDB?
|
||||
01:37 How would we model this using documents?
|
||||
01:39 Well, you would see that it's somewhat simpler.
|
||||
01:41 The more craziness that we had in a relational model,
|
||||
01:44 the sort of more contrast you will see here.
|
||||
01:46 So we still have our publisher and they have their ID
|
||||
01:48 and when they were founded.
|
||||
01:49 We have our user in the same columns,
|
||||
01:51 or pieces of information there as well.
|
||||
01:53 Same as book.
|
||||
01:54 But now our ratings, we've decided,
|
||||
01:56 when we get a book, most of the time
|
||||
01:58 we actually want to know the rating.
|
||||
01:59 We want to say this is a 3.4 star book, or it has 72 ratings.
|
||||
02:04 Even in a list, we want to show that stuff.
|
||||
02:06 So we're pretty sure we want to have
|
||||
02:08 these ratings always with the books.
|
||||
02:10 Why put 'em in a separate table?
|
||||
02:12 Let's embed them.
|
||||
02:12 Now we still have some relationships
|
||||
02:14 like we had in the relational model.
|
||||
02:16 For example, we have our publisher ID on books
|
||||
02:18 and that links over to publisher.
|
||||
02:20 Now, this is what, in MongoDB, I refer to as
|
||||
02:22 a soft foreign key constraint.
|
||||
02:24 If it was a relationship between books and publisher,
|
||||
02:27 it's the publisher ID set to the ID of the publisher.
|
||||
02:29 But the database itself doesn't enforce this, alright?
|
||||
02:32 You need to be a little more careful in your app
|
||||
02:34 and we'll see how we do that when we get to the code.
|
||||
02:36 But, as I was saying about ratings,
|
||||
02:37 these we're not going to put in a separate collection.
|
||||
02:40 In fact, we're going to store those inside our books,
|
||||
02:43 so we can embed these objects and arrays
|
||||
02:46 of either straight values like numbers or strings,
|
||||
02:49 or actual sub-documents like our ratings here.
|
||||
02:52 So we might have rating one, two, three, and so on.
|
||||
02:54 It's actually part of the record of the book.
|
||||
02:56 So when we get the book record back,
|
||||
02:57 we already have the ratings, things like that.
|
||||
03:00 So again, we can think of these ratings
|
||||
03:02 being embedded within books as a pre-computed join.
|
||||
03:06 There's a slight bit of overhead
|
||||
03:08 if you actually wanted the book without the ratings.
|
||||
03:10 I mean, you're just going to get them back and ignore them
|
||||
03:12 most of the time anyway, if that was the case.
|
||||
03:14 Most of the time you do want the ratings.
|
||||
03:16 this is a huge speed up.
|
||||
03:18 And like I said, imagine there were 15 tables before
|
||||
03:20 and five of them could be collapsed into the books.
|
||||
03:23 Now you have a five-way join going down
|
||||
03:24 to a single primary key query; that'd be amazing.
|
@ -0,0 +1,152 @@
|
||||
00:00 As we discussed, modeling with documents and
|
||||
00:03 document databases is little bit more art.
|
||||
00:05 There's a little bit more flexibility and
|
||||
00:07 kind of just gut-feel of how you should do it,
|
||||
00:09 but let me give you some guidelines that will
|
||||
00:12 give you a clear set of considerations
|
||||
00:15 as you work on your data models.
|
||||
00:17 You'll see that the primary question for working
|
||||
00:20 with document databases is to embed or not to embed.
|
||||
00:24 That is, when there's a relationship between
|
||||
00:26 two things in your application.
|
||||
00:28 Should one of those be a sub-document?
|
||||
00:31 Should it be contained within the same record
|
||||
00:33 as the thing it is related to?
|
||||
00:34 Or should they be two separate collections,
|
||||
00:36 (what MongoDB calls tables: collections),
|
||||
00:39 because they're not tabular, right?
|
||||
00:40 Should those be two separate collections,
|
||||
00:42 but just relate to each other.
|
||||
00:44 We're going to try to answer this question.
|
||||
00:46 I'll try to provide you some guidelines
|
||||
00:48 for answering this question.
|
||||
00:49 The first one is: is the embedded data
|
||||
00:52 wanted 80% of the time when you have the outer,
|
||||
00:56 or other related object?
|
||||
00:58 Let's go back to our example we just worked with.
|
||||
01:00 We had a book and the book had ratings.
|
||||
01:02 To make this concrete, the question here is
|
||||
01:05 do we care about having information about the ratings
|
||||
01:08 most of the time when we're working with books?
|
||||
01:10 So if our website lists books,
|
||||
01:13 and that listing has a number of ratings
|
||||
01:16 and the average rating, things like that,
|
||||
01:17 listed as part of the listing,
|
||||
01:19 you pull up a book, maybe the ratings,
|
||||
01:21 and the reviews are shown right there--
|
||||
01:22 most of the time, when we have the book,
|
||||
01:24 we have the ratings involved somehow,
|
||||
01:26 then we would want to embed the ratings within the book.
|
||||
01:29 That's what we did in our data model.
|
||||
01:30 We said, yes,
|
||||
01:31 we do want the ratings most of the time.
|
||||
01:34 Now, let's look at this in reverse.
|
||||
01:36 How often do you want the embedded data
|
||||
01:39 without the containing document?
|
||||
01:41 So in the same example, how often is it the case
|
||||
01:43 that you would like the ratings without the book?
|
||||
01:49 Where would that show up in our apps?
|
||||
01:50 So maybe you as a user,
|
||||
01:52 go to your profile page in the bookstore,
|
||||
01:55 and there you can see all the books you've rated, right?
|
||||
01:57 And the details about the ratings.
|
||||
01:58 You don't actually care about necessarily the books,
|
||||
02:00 you just want, these are the ratings
|
||||
02:02 that I've given to things, and here's my comment and so on.
|
||||
02:05 You don't actually want most of the details
|
||||
02:07 or maybe any of the details about the book itself.
|
||||
02:09 So if you're in that sort of situation,
|
||||
02:12 a lot of the time, you might want to put that
|
||||
02:14 into a separate collection and not embed it.
|
||||
02:16 You can still do it.
|
||||
02:18 You can still do this query and it'll come back very quickly,
|
||||
02:20 there's ways to work with it.
|
||||
02:21 But you'll see that you have to do a query in the database
|
||||
02:24 and then a little bit of filtering on the application side.
|
||||
02:26 So it's not prohibitive,
|
||||
02:31 it's not that you can't get the contained document
|
||||
02:33 without its container,
|
||||
02:34 but it's a little bit more clumsy.
|
||||
02:36 So if this is something you frequently want to do,
|
||||
02:39 then maybe consider not embedding it.
|
||||
02:41 Now, is the embedded document a bounded set?
|
||||
02:43 Let's look at ratings.
|
||||
02:45 How many ratings might a book have?
|
||||
02:47 10 ratings, 100 ratings, 1000 ratings.
|
||||
02:49 Is that number going to just grow
|
||||
02:51 The number of ratings that we have?
|
||||
02:53 If this was page views and details about the browser,
|
||||
02:57 IP address, and date and time of a page we viewed,
|
||||
03:00 that would not make a good candidate for embedding that,
|
||||
03:02 like say, for views of a book
|
||||
03:04 because that could just grow and grow
|
||||
03:05 as the popularity of your site grows.
|
||||
03:07 And it could make the document so large
|
||||
03:09 that when you retrieve it from the database,
|
||||
03:10 actually the network traffic and the disk traffic
|
||||
03:12 would be a problem.
|
||||
03:13 I don't really see that happening with ratings.
|
||||
03:15 I mean, even on Amazon, super popular books have
|
||||
03:18 hundreds not millions of ratings.
|
||||
03:20 So this is probably okay, but if it's an unbounded set,
|
||||
03:23 you do not want to embed it.
|
||||
03:25 And these have bounds small, right?
|
||||
03:27 Maybe millions of views still are being recorded
|
||||
03:31 within sign of a book would be a really bad idea.
|
||||
03:33 And the reason is these documents are limited
|
||||
03:35 to 16 megabytes.
|
||||
03:37 So no single record of MongoDB can be larger
|
||||
03:40 than 16 megabytes.
|
||||
03:41 And this is not a limitation of MongoDB.
|
||||
03:42 This is them trying to protect you from yourself.
|
||||
03:45 You do not want to go and just say, query buy, say, ISBN,
|
||||
03:49 and try to pull back a book
|
||||
03:50 and actually read 100 megabytes off a disk in
|
||||
03:53 over the network.
|
||||
03:53 That would destroy the performance of your database.
|
||||
03:56 So having these very, very large records is a problem.
|
||||
03:59 So they actually set an upper bound on how large
|
||||
04:02 that can be.
|
||||
04:03 And that limit is right now, currently,
|
||||
04:05 at the time of recording, 16 megabytes.
|
||||
04:07 But you shouldn't think of 16 megabytes as like,
|
||||
04:09 well, if it's 10 megabytes, everything's fine.
|
||||
04:11 We still got a long ways to go.
|
||||
04:13 No, you should try to keep these,
|
||||
04:15 in the kilobytes: tens, twenties, hundreds of kilobytes,
|
||||
04:18 not megabytes because that's going to really hurt your
|
||||
04:21 database performance unless in some situation,
|
||||
04:23 it just makes avton of sense to have these
|
||||
04:25 very large documents.
|
||||
04:25 So having small bounded sets means that your documents
|
||||
04:29 won't grow into huge, huge monolithic things
|
||||
04:32 that are hard to work with.
|
||||
04:33 Also, how varied are your queries?
|
||||
04:35 So one of the things that you do with document database is
|
||||
04:38 is you try to structure the document to answer
|
||||
04:41 the most common questions in the most well-structured way.
|
||||
04:46 So if you're always going to say, I would like to, on my pages,
|
||||
04:49 show a book in this related ratings,
|
||||
04:51 you would absolutely put the ratings inside the book
|
||||
04:53 because that means you just do a query against a book
|
||||
04:55 and you already have that pre-joined data.
|
||||
04:58 But if it's sort of a data warehouse
|
||||
05:00 and you're asking all kinds of questions
|
||||
05:02 from all different sorts of applications,
|
||||
05:04 then trying to understand, well, what is the right way
|
||||
05:06 to build my document so it matches the queries
|
||||
05:08 that I typically do or the ones that I need to be
|
||||
05:10 really quick and fast?
|
||||
05:12 That becomes hard because there's all
|
||||
05:13 these different queries and
|
||||
05:15 the way you model for one is actually the opposite
|
||||
05:18 of the way you model for the other.
|
||||
05:20 So depending on how focused your application is,
|
||||
05:22 or how many applications are using the database,
|
||||
05:24 you'll have a different answer to this question.
|
||||
05:26 So the more specific your queries are,
|
||||
05:28 the more likely you are to embed things and structure them
|
||||
05:31 exactly to match those queries.
|
||||
05:33 Related to that, is are you working with an
|
||||
05:36 integration database or an application database?
|
||||
05:39 We'll get to that next.
|
@ -0,0 +1,62 @@
|
||||
00:00 So wait, what is an integration database?
|
||||
00:02 If you were just working on your own personal website
|
||||
00:05 or some small project,
|
||||
00:06 you don't have an integration database.
|
||||
00:08 But if you work in a big enterprise,
|
||||
00:10 a big corporation where there's many internal systems,
|
||||
00:13 you may be working with an integration database.
|
||||
00:15 Honestly, that's not a great fit
|
||||
00:17 for NoSQL databases in general.
|
||||
00:19 It also makes designing documents
|
||||
00:21 for them more difficult.
|
||||
00:22 In large corporations where you have many applications
|
||||
00:26 that share the same data,
|
||||
00:27 one way that we have built applications to share data
|
||||
00:31 is to just share the same database.
|
||||
00:33 We might have a bunch of different applications
|
||||
00:36 and they're all going to talk to the same database
|
||||
00:38 so they all have the same concept of a user,
|
||||
00:40 they all have the same concept of an order,
|
||||
00:42 things like that.
|
||||
00:43 And this means the concept of the user
|
||||
00:45 is as complicated as it can get.
|
||||
00:47 Maybe the application on the top left
|
||||
00:48 could have a real simple user,
|
||||
00:49 the one on the top right actually needs something else,
|
||||
00:52 the bottom one is something else still
|
||||
00:54 until you've got a model across all these applications,
|
||||
00:56 and that makes it super tricky.
|
||||
00:58 Also in NoSQL databases and document databases,
|
||||
01:01 the relationships are enforced in the application,
|
||||
01:03 so that means all of these have to agree on
|
||||
01:05 what the constraints are, what the relationships are,
|
||||
01:08 and that can actually cause data integrity issues.
|
||||
01:10 There's a lot of reasons
|
||||
01:11 that an integration database isn't a great idea
|
||||
01:13 for relational databases.
|
||||
01:14 In fact, it's not a great idea at all, but it has been used
|
||||
01:17 and because there's different applications
|
||||
01:19 with different query patterns,
|
||||
01:20 it makes designing your documents more difficult.
|
||||
01:23 So instead what do we do?
|
||||
01:24 We build application databases.
|
||||
01:26 Maybe we have a bunch of different applications
|
||||
01:28 just like before, but they all have their own data store
|
||||
01:30 and they all talk to their own databases.
|
||||
01:33 Of course they need to exchange data before
|
||||
01:35 so maybe we do some sort of microservice thing
|
||||
01:37 where they talk to a service bus
|
||||
01:38 or they just talk to each other, things like that.
|
||||
01:40 That means each individual database
|
||||
01:43 and interaction with its own application
|
||||
01:45 is super, super focused and limited.
|
||||
01:48 Here in these cases,
|
||||
01:49 MongoDB document databases make a lot more sense,
|
||||
01:52 and it's easier to design the documents
|
||||
01:54 because the range of queries is extremely focused
|
||||
01:58 so you can target those particular questions
|
||||
02:00 against a small set of queries,
|
||||
02:03 the guidelines we just talked about.
|
||||
02:04 So this is the kind of model you want to have
|
||||
02:06 if you're doing data exchange within your organization,
|
||||
02:10 and you're working with a document database.
|
@ -0,0 +1,157 @@
|
||||
00:00 I don't know about you,
|
||||
00:01 but I feel like we've talked about coding
|
||||
00:03 and talked about MongoDB in theory enough.
|
||||
00:05 And it's time to write some code, and use MongoDB.
|
||||
00:08 So that brings us to getting started
|
||||
00:09 with our demo application.
|
||||
00:11 Throughout the rest of the course,
|
||||
00:12 we're going to spend a significant amount
|
||||
00:13 of time focusing on this.
|
||||
00:16 And remember, we're building Snakebnb.
|
||||
00:18 This wonderful experience where snake owners
|
||||
00:21 and their pets can share other snake cages
|
||||
00:24 when they're traveling.
|
||||
00:25 So they feel totally comfortable
|
||||
00:26 on every vacation you need to take your snake on.
|
||||
00:28 Of course, it's just a knockoff of Airbnb type thing.
|
||||
00:30 And in this video,
|
||||
00:32 we're going to see how to get it from GitHub
|
||||
00:34 and how to get it up and running in Python and PyCharm.
|
||||
00:36 So we'll start out over here on
|
||||
00:38 github.com/mikeckennedy/mongodb-quickstart-course.
|
||||
00:42 And you can see that we've got a couple things here.
|
||||
00:45 We've got some data.
|
||||
00:46 This is empty right now, but I'm going to fill it up
|
||||
00:47 with stuff as we go through the class.
|
||||
00:49 So you'll be able to recreate the database.
|
||||
00:51 There'll be instructions in there on how to restore that.
|
||||
00:53 Then if you go over to source,
|
||||
00:54 this is the most interesting part.
|
||||
00:56 We're going to be working in this area here,
|
||||
00:59 but I've made a snapshot of starter code Snakebnb.
|
||||
01:02 And this is exactly a snapshot of what we're starting from.
|
||||
01:05 Okay, but I'm going to be working in here
|
||||
01:07 because I want to have it build up, right?
|
||||
01:09 Also, try to do, make some branches or other save points,
|
||||
01:12 really obvious when we get to the various videos.
|
||||
01:15 Right now there's no other branches, but we'll get to those.
|
||||
01:17 Okay, let's go and check this out.
|
||||
01:19 So we'll go copy what we need.
|
||||
01:21 And we'll say "git clone" this.
|
||||
01:24 Nice and quick, and let's go work with it.
|
||||
01:26 Over here,
|
||||
01:29 we have our source code, and we have our Snakebnb,
|
||||
01:32 and we have our starter code Snakebnb.
|
||||
01:34 So these are the two projects here.
|
||||
01:37 And what I want to do is I'm going to put this into PyCharm.
|
||||
01:40 On macOS you can just drag and drop this onto PyCharm,
|
||||
01:43 and it'll load the project from that folder.
|
||||
01:45 However, if you do this on Windows or on Linux,
|
||||
01:49 I think you have to go to PyCharm and say,
|
||||
01:50 File, Open Directory.
|
||||
01:52 However, before I do, let's go into this folder really quick
|
||||
01:56 and create a virtual environment.
|
||||
01:57 So you may be familiar with Python,
|
||||
01:58 and virtual environments, and so on,
|
||||
02:00 but if you're not, let me give you the quick overview
|
||||
02:02 of what's going on here.
|
||||
02:04 If we look here we're going to have,
|
||||
02:06 apparently, a misspelled requirements file,
|
||||
02:09 which we're going to take care of in a second.
|
||||
02:10 But notice in this requirements file,
|
||||
02:12 these are the external libraries.
|
||||
02:13 PyMongo and MongoEngine for MongoDB
|
||||
02:16 and some other random stuff for working with color
|
||||
02:20 output on the console,
|
||||
02:21 as well as parsing date times entered from the user.
|
||||
02:25 So we need these libraries,
|
||||
02:26 and we don't want to install them and manage them,
|
||||
02:28 basically, as a machine-wide thing.
|
||||
02:30 We want to install them into our virtual environment.
|
||||
02:33 So, let's go over here first.
|
||||
02:35 Your name requirements.
|
||||
02:37 And we're going to go and actually
|
||||
02:39 create the virtual environment, and then we'll install.
|
||||
02:42 We can install stuff into it.
|
||||
02:43 So here we are again in this source folder.
|
||||
02:45 So we'll say Python3-M VENV.
|
||||
02:49 So run the virtual environment module into .env.
|
||||
02:52 This naming convention, .env,
|
||||
02:54 is something that PyCharm understands,
|
||||
02:56 will automatically detect, and start using.
|
||||
02:58 We're going to pass a test copies flag.
|
||||
03:00 That's only required on macOS, I believe.
|
||||
03:02 But, anyway, we'll go with that.
|
||||
03:03 Now, if we do an LSAH, you can see this hidden .env.
|
||||
03:07 But we don't need to do anything else with it,
|
||||
03:09 PyCharm should take it from here.
|
||||
03:10 So we can go and grab this folder.
|
||||
03:12 On macOS, remember, File, Open Directory on the other OSs,
|
||||
03:15 and drop it here.
|
||||
03:17 Let's go ahead and tell PyCharm about git.
|
||||
03:20 For the very first time PyCharm will index
|
||||
03:22 the Python environment we gave it.
|
||||
03:25 Then it should be up and running.
|
||||
03:26 Okay, so let's look dow here, the terminal.
|
||||
03:28 You should see the .env
|
||||
03:30 You can ask questions like, which Python?
|
||||
03:32 And it shows you it's the one that we created.
|
||||
03:35 In Window's it's "where Python," not "which Python."
|
||||
03:38 If we go over here we have our requirements and so on.
|
||||
03:40 Now, the other thing we need to do
|
||||
03:42 is we need to right click and say,
|
||||
03:43 set this as the relative path.
|
||||
03:46 In this file, when I import some other file,
|
||||
03:50 it looks relative to that.
|
||||
03:51 You can right click here and say,
|
||||
03:53 mark directory as sources route,
|
||||
03:55 or just be in this folder when you run it in Python.
|
||||
03:58 And basically, your working directory.
|
||||
03:59 Okay, so we're almost ready to run things.
|
||||
04:01 The last thing we need to do is install these requirements.
|
||||
04:03 So we can say, "pip install -r" the requirements file.
|
||||
04:08 And that will install those libraries for us,
|
||||
04:10 so when we run the application it has everything it needs.
|
||||
04:13 So if we try to run it now, it'll crash and say,
|
||||
04:14 it can't find Colorama or something like that.
|
||||
04:17 Now, this application is empty.
|
||||
04:18 It doesn't do anything
|
||||
04:19 other than ask for a couple of prompts.
|
||||
04:21 There's no data access for MongoDB, anything in here.
|
||||
04:24 But let's go ahead and just get it to run.
|
||||
04:25 So we can right click on "Program,"
|
||||
04:27 and right click and say "Run Program."
|
||||
04:30 It runs, and you can see, if I make it bigger,
|
||||
04:33 here we have our Snakebnb,
|
||||
04:34 and I put a little snake there for your guys.
|
||||
04:36 And it asks you a question: "Are you a guest or a host?"
|
||||
04:38 Are you looking for a cage,
|
||||
04:39 or do you want to offer up your cage?
|
||||
04:41 So let's go with guest.
|
||||
04:43 And it lets you do things like create an account,
|
||||
04:46 add your snake, and so on.
|
||||
04:47 So I could say, I'd like to log in.
|
||||
04:49 It says, you know, that's not implemented yet.
|
||||
04:51 In fact, that's what we're going to be doing next,
|
||||
04:53 implementing all of these features in the database,
|
||||
04:56 creating an account, logging in, booking a cage,
|
||||
04:59 viewing cages, things like that.
|
||||
05:01 All the actions you might do
|
||||
05:02 in a typical Airbnb situation.
|
||||
05:04 So that's it for now.
|
||||
05:06 We have this up and running.
|
||||
05:07 Let's do one more thing.
|
||||
05:09 Because of the output, I find this looks a little better
|
||||
05:11 if we just run it separate outside of PyCharm.
|
||||
05:14 So we can say copy the path here.
|
||||
05:17 And we're still in this folder with the .environment,
|
||||
05:20 so we need to activate it if we're going to run it over here.
|
||||
05:22 So we would say ". .env/bin/activate"
|
||||
05:28 On Windows, you don't need the first dot,
|
||||
05:30 and it's not "bin," it's "script."
|
||||
05:31 Script or scripts, I can't remember.
|
||||
05:33 I think it's "scripts."
|
||||
05:34 Either way, a prompt should change.
|
||||
05:36 And now we can run this.
|
||||
05:39 Here's our snake again.
|
||||
05:40 Okay, so we're all set up and ready to run our code.
|
@ -0,0 +1,69 @@
|
||||
00:01 It's time to write some code against MongoDB
|
||||
00:04 and connect to MongoDB and we're going to do that
|
||||
00:05 with an ODM: an Object Document Mapper.
|
||||
00:08 If this term's new to you,
|
||||
00:09 think of Object-Relational Mapper, like SQLAlchemy,
|
||||
00:12 but for document databases instead.
|
||||
00:14 So, let's compare first this ODM-style of programming
|
||||
00:18 against the most basic, lowest level way to program,
|
||||
00:22 or interact with, MongoDB from Python called PyMongo.
|
||||
00:25 Every programming language
|
||||
00:26 that you can talk to MongoDB from, see there is many of them
|
||||
00:29 20, 30, something like that.
|
||||
00:31 Many, many languages can talk to MongoDB
|
||||
00:34 and they each have what's called a driver,
|
||||
00:35 and this is typically provided by MongoDB,
|
||||
00:38 the company itself.
|
||||
00:39 PyMongo is no different.
|
||||
00:41 It's this low level foundational way to talk to MongoDB
|
||||
00:46 and you do this in the native query syntax
|
||||
00:48 of MongoDB, this Java Script JSON-style
|
||||
00:52 of interacting with the database.
|
||||
00:53 Now, it's important to know that
|
||||
00:55 if you're working with MongoDB
|
||||
00:56 in terms of running and managing it,
|
||||
00:58 but from writing code, we're going to focus on something
|
||||
01:01 higher level: an ODM, so we can take structured classes
|
||||
01:05 and map those to and from the database.
|
||||
01:07 So let's see how it would work if we just used PyMongo.
|
||||
01:09 We've got our app here and we have the PyMongo package
|
||||
01:13 we're going to work with, and we have MongoDB, the database.
|
||||
01:15 We write direct queries in this raw MongoDB API.
|
||||
01:20 You have to know the API really carefully.
|
||||
01:22 You have to map those back to your classes.
|
||||
01:24 Basically what you do is you pass dictionaries to PyMongo.
|
||||
01:28 It uses those as part of the query
|
||||
01:29 and then you get the dictionaries back.
|
||||
01:31 It's pretty unstructured but it's very low-level and fast.
|
||||
01:33 With an ODM, similarly, we've got our app
|
||||
01:36 and we've got PyMongo and MongoDB,
|
||||
01:38 but we also have another layer,
|
||||
01:40 the layer that we directly interact with,
|
||||
01:41 called the ODM, Object Document Mapper.
|
||||
01:44 And there's a bunch of different kinds.
|
||||
01:45 There's MongoEngine, there's Ming, there's MongoKit,
|
||||
01:48 there's MongoAlchemy, MiniMongo,
|
||||
01:51 and there's more than that actually; there's a ton of them.
|
||||
01:54 Just so happens we're going to use MongoEngine,
|
||||
01:55 one of the more popular and well-polished ones.
|
||||
01:58 So, in this model, we don't query in terms
|
||||
02:01 of raw dictionaries against PyMongo,
|
||||
02:02 we just talk to the classes to find by the ODM.
|
||||
02:05 And our queries are based on those types, on those classes.
|
||||
02:09 That itself will translate to the MongoDB API
|
||||
02:13 sometimes in real basic ways,
|
||||
02:14 sometimes in really advanced ways,
|
||||
02:15 and it'll actually leverage some of the advanced operators.
|
||||
02:18 The dollar operators, if you're familiar with them,
|
||||
02:21 for MongoDB like dollar set, dollar add to set,
|
||||
02:23 things like this.
|
||||
02:24 So really, really cool that it leverages
|
||||
02:27 the advanced operators, not just save this document,
|
||||
02:30 read this document-type programming.
|
||||
02:32 I think the ODM model is a much
|
||||
02:34 better way to write your application.
|
||||
02:36 You'll see there's not much structure
|
||||
02:37 in a schema-less database, so having a little bit
|
||||
02:41 of extra structure to find by these classes
|
||||
02:43 that are a part of this ODM model really adds
|
||||
02:46 a lot of safety to your application, in my opinion.
|
@ -0,0 +1,32 @@
|
||||
00:00 The ODM we're going to use for this course
|
||||
00:01 is MongoEngine, and you can find it's homepage
|
||||
00:04 and details about it, documentation and so on,
|
||||
00:07 at MongoEngine.org.
|
||||
00:08 So MongoEngine is open source like many things
|
||||
00:11 you'll find in Python, as we said,
|
||||
00:12 it depends upon PyMongo.
|
||||
00:14 You saw us install it earlier,
|
||||
00:16 we just did Pip install MongoEngine
|
||||
00:18 and that installed PyMongo with it.
|
||||
00:20 We did that through the requirements file
|
||||
00:21 but you can do that directly if you prefer.
|
||||
00:23 You can find MongoEngine on GitHub.
|
||||
00:26 You can see that it's quite popular,
|
||||
00:27 almost 2,000 stars.
|
||||
00:29 This is much more popular that the other MongoDB ODMs,
|
||||
00:33 as far as I can tell, looking at the other ones,
|
||||
00:35 this is the definitely most popular,
|
||||
00:37 at least, among the most popular of them,
|
||||
00:39 and it's very actively under development.
|
||||
00:41 I just took this screenshot right now
|
||||
00:43 before I started recording here
|
||||
00:45 to give you the latest version.
|
||||
00:46 You can see that it's been updated in the last 24 hours,
|
||||
00:50 and some other stuff under the actual code
|
||||
00:53 has been updated in the last 21 hours,
|
||||
00:56 so very active, this is important
|
||||
00:57 for an open source project you're going to depend upon,
|
||||
01:00 so I think you know, judging by that,
|
||||
01:02 MongoEngine is the best choice,
|
||||
01:03 and the API is excellent,
|
||||
01:04 we're going to start working with it next.
|
@ -0,0 +1,208 @@
|
||||
00:00 Next thing we need to do as the host
|
||||
00:03 is to be able to register our cage so that
|
||||
00:05 people can view it and maybe book it.
|
||||
00:07 That'd be great, right?
|
||||
00:08 So, let's go over here and work with that.
|
||||
00:11 Now, the cage has to be associated with an owner
|
||||
00:14 through this software key relationship in MongoDB.
|
||||
00:18 We're going to require an account,
|
||||
00:19 and that's going to look like just an error message.
|
||||
00:25 We'll just say if there's no account,
|
||||
00:27 you must log in first to register a cage.
|
||||
00:29 Alright, so now we have the account
|
||||
00:31 and we're going to use that as part of it.
|
||||
00:33 Let's go ahead and figure out how many square meters
|
||||
00:36 this is going to be.
|
||||
00:38 We'll say "meters = " something like this.
|
||||
00:47 You might want to just directly convert that into a float.
|
||||
00:50 But I found as I was interacting with this application
|
||||
00:52 I'd accidentally go to register a cage
|
||||
00:55 and I meant to list them.
|
||||
00:56 You want some kind of way to cancel out,
|
||||
00:57 so let's just suppose if they don't enter anything,
|
||||
00:59 they just hit enter, it kind of short circuits everything.
|
||||
01:02 We'll say "if not" something like this,
|
||||
01:07 so if they don't enter anything, we'll just bail out.
|
||||
01:09 Otherwise we'll just say, float of meters,
|
||||
01:13 so convert it from a string to a float.
|
||||
01:15 We're not doing the error handling on this.
|
||||
01:17 You guys should probably add that, but we can just do this.
|
||||
01:20 And we have to ask a bunch of other questions.
|
||||
01:22 Is it carpeted, does it have toys?
|
||||
01:24 Thing like that.
|
||||
01:25 Let me just copy that over .
|
||||
01:29 This is just user input stuff, right?
|
||||
01:32 And then, we want to go down
|
||||
01:34 and we want to actually register the cage.
|
||||
01:37 Again, we want to do this at our data layer,
|
||||
01:39 not here in our application code,
|
||||
01:41 so we'll say "svc.register_cage()" and again,
|
||||
01:44 that doesn't exist, but it's going to in a second.
|
||||
01:47 We are going to pass the account.
|
||||
01:52 We'll pass the active account here,
|
||||
01:54 we'll pass the name of the cage,
|
||||
01:57 we'll pass whether or not it allows dangerous,
|
||||
02:00 just all of these items.
|
||||
02:02 Whether or not it has toys,
|
||||
02:04 whether it's carpeted, and the meters.
|
||||
02:10 Okay, so we're going to go and call this function which,
|
||||
02:13 obviously, doesn't exist yet,
|
||||
02:15 but PyCharm will write it for us.
|
||||
02:17 Thank you, PyCharm.
|
||||
02:19 Here we can say this is an owner,
|
||||
02:21 here's the name of the string, and so on.
|
||||
02:24 There we go.
|
||||
02:25 Let's say this is going to return a cage
|
||||
02:28 which we have to import at the top.
|
||||
02:29 Again, thank you PyCharm.
|
||||
02:31 This is very, very similar to the create account.
|
||||
02:33 We're just going to create a cage and save it.
|
||||
02:39 We've set all the properties and we'll call "cage.save()"
|
||||
02:42 and that's going to store it in the database.
|
||||
02:45 Now, we want to, remember if we look over at our owners,
|
||||
02:49 it has cage IDs to manage the relationships.
|
||||
02:53 The order in which we do this is super important.
|
||||
02:57 Now, we need to be a little bit careful
|
||||
03:00 with this account here.
|
||||
03:02 We want to make sure that we're getting the latest
|
||||
03:05 account from the database, so we'll do something like this.
|
||||
03:08 We'll say "account = find_account_by_email"
|
||||
03:14 this "active_account.email"
|
||||
03:17 That makes sure we don't have any stale data.
|
||||
03:19 Then we're going to go "account.cage_ids"
|
||||
03:24 This is a list, so we can append to the cage.ID.
|
||||
03:28 It's super important, the order here.
|
||||
03:31 We must call save so that this is an actual generated value.
|
||||
03:36 It's just none beforehand, so we want to make sure
|
||||
03:38 that's generated in the database
|
||||
03:40 and then we can return the cage.
|
||||
03:42 So, that warning up there goes away
|
||||
03:45 because we are returning the cage.
|
||||
03:49 The other thing that we need to do is,
|
||||
03:52 this has changed the account,
|
||||
03:54 but we haven't pushed those changes back to the database.
|
||||
04:00 Alright, so our registered cage seems like it's working.
|
||||
04:03 Let's go over here, and let's do one more thing.
|
||||
04:06 This active account needs to have that data adjusted as well
|
||||
04:11 so let's go over here to state, and has a reload account.
|
||||
04:15 That's not written yet, let's do that.
|
||||
04:18 This is super easy to do.
|
||||
04:19 All we have to do is go to the database and pull it back
|
||||
04:23 so we have this active account global variable
|
||||
04:25 and we can come over here and say import the service
|
||||
04:30 and here we're going to say "find_account_by_email"
|
||||
04:32 "(active_account.email)", alright?
|
||||
04:35 That's all we've got to do to reload it.
|
||||
04:37 So, this'll make sure that it can work
|
||||
04:40 with our "state.active_account"
|
||||
04:41 It's the fresh one that just got its data changed down here.
|
||||
04:46 Let's do one more thing before we carry on.
|
||||
04:48 Let's go ahead and implement register cage here,
|
||||
04:52 and let's spell suppress correctly.
|
||||
04:54 Let's add the ability to list our cages
|
||||
04:57 so that we can test that this actually worked.
|
||||
04:59 Let's also do a success message,
|
||||
05:02 "Registered new cage with id"
|
||||
05:09 Make that an F string, actually.
|
||||
05:12 "cage.ID" so we've got to store that up here.
|
||||
05:19 Excellent, so now we've got our cage
|
||||
05:20 and we'll see that come out, but let's go ahead
|
||||
05:22 and we're going to require an account again,
|
||||
05:24 which is the same info up here.
|
||||
05:31 And all we've got to do is get the cages.
|
||||
05:33 So, let's write like this.
|
||||
05:36 Let's say, "cages = svc.find_cages_for_user"
|
||||
05:44 Right, got to write that function.
|
||||
05:46 There we go, creates an account.
|
||||
05:49 Let's not call it active account, let's just say account
|
||||
05:52 and that is an owner, and it returns a cage.
|
||||
05:58 Actually, it's not a cage, what does it return?
|
||||
05:59 It returns a list of cages, so we've got to go
|
||||
06:01 to the typing module here, typing, and new cage.
|
||||
06:06 Okay, perfect.
|
||||
06:07 What we're going to do is, we already have the owner,
|
||||
06:10 and because of our refresh account stuff, it should be fresh
|
||||
06:14 so we'll have cage IDs, and we should have this
|
||||
06:17 "account.cage_ids" right there that we can use.
|
||||
06:21 Now, how do we query for this particular thing?
|
||||
06:25 We're going to do something you haven't seen yet.
|
||||
06:27 We're going to come to the cage, and again,
|
||||
06:29 we're going to go to this objects and I'm going to do the query,
|
||||
06:31 but instead of saying, remember before we had "email = "
|
||||
06:34 and that did the query against MongoDB for testing quality?
|
||||
06:38 We have something different we need to do.
|
||||
06:40 We want to go to the ID of the cage,
|
||||
06:42 and we don't want to just say, well, it's equal to,
|
||||
06:45 it's not going to be equal to the list.
|
||||
06:47 We can't say not cage IDs, because one is an object ID
|
||||
06:51 and one is a list of object IDs,
|
||||
06:54 so we have to use a special operator,
|
||||
06:56 and MongoDB has all these simple dollar operators.
|
||||
07:00 $set, $in, $not, $or,
|
||||
07:04 these types of things.
|
||||
07:07 And the way that we work with those in MongoEngine
|
||||
07:10 is we use a double underscore to say
|
||||
07:12 we're applying this to the ID but then there's this other
|
||||
07:15 thing that we're doing and we're going to say "in"
|
||||
07:18 This query right here, do a little cleanup,
|
||||
07:21 this query says go to the cage and find all the cages
|
||||
07:24 whose ID is in this list of IDs.
|
||||
07:26 Now, we'll have "cages = " this is a query
|
||||
07:32 but we want to execute the query and sort of snapshot it
|
||||
07:35 for our app so it'll return cages.
|
||||
07:41 And that should make our app totally happy.
|
||||
07:44 That little warning, up and away.
|
||||
07:46 Okay, great, so we've written this function
|
||||
07:48 and we've used the "in" operator,
|
||||
07:50 the double underscore, to access it.
|
||||
07:52 Here's our cages, we just need to print them out.
|
||||
07:54 Or see in cages...
|
||||
07:58 Let's make these F strings, and we'll put them here
|
||||
08:00 and we'll say "c." whatever we want here.
|
||||
08:02 We want name...
|
||||
08:06 Let's also print out something like this.
|
||||
08:10 You have however many lengths of cages you have
|
||||
08:13 and then we'll print those off, okay?
|
||||
08:15 Let's try to test these two things that we've written.
|
||||
08:19 For host, we first want to register a cage.
|
||||
08:22 Actually, let's try to list our cages.
|
||||
08:30 Login "michael@talkpython.fm." Let's list our cages
|
||||
08:32 You have zero cages, great, so let's register a cage.
|
||||
08:37 Now we're logged in it'll let us.
|
||||
08:39 It's 2.2 square meters.
|
||||
08:41 Yes, it's carpeted.
|
||||
08:42 Yes, it has toys.
|
||||
08:43 No, it has no venomous.
|
||||
08:45 This'll be Bully's Cage.
|
||||
08:49 It looks like we haven't set the price, hmm.
|
||||
08:52 We've forgotten something, haven't we?
|
||||
08:53 Okay, good thing we tested that here.
|
||||
08:59 The price, and this will be, how much are you charging?
|
||||
09:03 Alright, let's go ahead.
|
||||
09:04 Price here, price, okay.
|
||||
09:09 That was kind of annoying that that crashed,
|
||||
09:12 but it's also cool.
|
||||
09:13 Why is that cool?
|
||||
09:14 Because if we said the cage must have a price and it didn't,
|
||||
09:17 if that was regular MongoDB, that would've just let
|
||||
09:19 that happen, but because it was MongoEngine, it did not.
|
||||
09:22 Alright, let's do this again.
|
||||
09:31 List my cages.
|
||||
09:33 Whoops, list my cages.
|
||||
09:34 We have no cages, let's register a cage and do this again.
|
||||
09:42 How much are we charging?
|
||||
09:44 We're charging $29 a night.
|
||||
09:46 This is one fancy cage, folks.
|
||||
09:48 Boom, we've registered a new cage.
|
||||
09:50 Now, let's list your cages.
|
||||
09:52 Ooh, we have "1 cages."
|
||||
09:54 Maybe a plurality thing there, but Bully's cage is out.
|
||||
09:57 Let's register one more cage.
|
||||
10:00 This is a huge cage.
|
||||
10:02 It's carpeted, has all the toys,
|
||||
10:03 and this one's even for venomous snakes.
|
||||
10:05 This would be the "Large boa cage," who knows.
|
||||
10:11 And this is $39.
|
||||
10:12 Now if we look at our cages, we have two cages.
|
||||
10:16 Beautiful, so it looks like our registering
|
||||
10:18 and our listing cages is working great.
|
@ -0,0 +1,121 @@
|
||||
00:01 Let's add a bookable time to one of our existing cages.
|
||||
00:05 We were able to register cages and list them.
|
||||
00:07 Now let's make them available for snakes to stay in.
|
||||
00:12 We're going to work on this update availability
|
||||
00:14 that we did here.
|
||||
00:15 We've got our requirement that you have to have an account
|
||||
00:18 because they're your cages,
|
||||
00:20 whose cages you're going to manage, things like that.
|
||||
00:23 And we're going to just print out the list cages.
|
||||
00:25 I've changed that slightly so it has a number.
|
||||
00:27 I'll show you really quick.
|
||||
00:29 I'm using enumerate in an index.
|
||||
00:32 It says one, two, three instead of star, star, star
|
||||
00:34 because we want to ask like,
|
||||
00:35 "hey, what cage do you want to work with?"
|
||||
00:38 We're going to add that here
|
||||
00:40 and just for the sake of time I'm just going to paste that.
|
||||
00:42 So it's going to say, "What cage do you want?"
|
||||
00:45 Either you can cancel or it's going to parse that
|
||||
00:47 into an integer
|
||||
00:48 and then we're going to work with a particular cage.
|
||||
00:51 Let's go down here and say "selected_cage"
|
||||
00:55 Oh, first we need the cages.
|
||||
00:56 Let's say "cages = " we'll just use our service again.
|
||||
01:00 Get the cages for "state.active_account"
|
||||
01:05 We'll go down here and say "cages[cage_number-1]
|
||||
01:10 Because we're showing it to them one, two, three.
|
||||
01:11 We got to convert that back to zero base.
|
||||
01:15 Once we've gotten our cage set, we need to get the dates.
|
||||
01:18 We've selected this cage.
|
||||
01:19 We'll say the name that you're going to work with.
|
||||
01:20 We're going to say enter date, year, month, day
|
||||
01:24 that you'd like to start this available time slot on
|
||||
01:27 and for how many days.
|
||||
01:28 I want to start a particular date
|
||||
01:30 and make that available for five days.
|
||||
01:34 We're using this thing called parser.
|
||||
01:37 That comes from "dateutil"
|
||||
01:39 so python-dateutil is the module name.
|
||||
01:41 It's in the requirements already.
|
||||
01:43 Parser is a really sweet way to parse date times.
|
||||
01:47 It has many, many different formats it understands.
|
||||
01:50 We're going to use that
|
||||
01:51 instead of the built-in date-time parsing.
|
||||
01:53 Once we have this, we just need to go and use our service
|
||||
01:56 and write another data access method.
|
||||
01:58 We'll say "svc.add_available_date"
|
||||
02:02 What are we going to pass?
|
||||
02:02 We have to have the account.
|
||||
02:03 Let's say active account there.
|
||||
02:07 Select the cage that we're going to add it to.
|
||||
02:09 We have to have the start date and the days.
|
||||
02:13 Let's go ahead and say "state.reload_account"
|
||||
02:16 This might change the account.
|
||||
02:19 Then we want to have a little success message like
|
||||
02:22 hey, good job.
|
||||
02:26 And F String this.
|
||||
02:32 Now we're down to just pure data access and MOGO,
|
||||
02:35 the interesting part, right?
|
||||
02:37 Let's go have PyCharm add that function.
|
||||
02:41 We'll have a few things here.
|
||||
02:43 This is going to be an owner.
|
||||
02:48 Cage, date, time.
|
||||
02:58 And an "int" and it's going to return nothing.
|
||||
03:01 This looks great.
|
||||
03:02 Remember what we're adding here is actually a booking.
|
||||
03:06 Bookings are not top-level items.
|
||||
03:08 But we'll go and create one to get started.
|
||||
03:10 I'm going to say booking.
|
||||
03:11 It's a booking like so.
|
||||
03:14 We just got to set the properties.
|
||||
03:17 This one we got to do a tiny bit of math here.
|
||||
03:20 We'll say "start_date + timedelta"
|
||||
03:30 Now we want to change the cage.
|
||||
03:32 The way it's working probably is fine.
|
||||
03:34 Just change the cage and call save.
|
||||
03:36 But I want to make sure that we absolutely are working
|
||||
03:39 with what's in the database.
|
||||
03:40 So I'll say "cage = "
|
||||
03:44 Actually we can just look it up here, I think.
|
||||
03:46 I'm not sure if we need it again.
|
||||
03:48 We'll just say "cage.objects(id=cage.id).first()"
|
||||
03:55 Then we're going to go over here, select the cage that was.
|
||||
03:59 Let's check that here.
|
||||
04:03 Again remember the bookings are not top-level items.
|
||||
04:05 They live inside of the cages.
|
||||
04:07 Here we're going to append the booking here
|
||||
04:09 and we call save on the cage not on the booking object.
|
||||
04:15 It doesn't live on its own, it lives inside the cages.
|
||||
04:20 Here we can return a cage, I suppose, if we want.
|
||||
04:23 Here's kind of the updated cage.
|
||||
04:25 We could even tell consumers that that happens.
|
||||
04:30 I guess we don't need our active account here, do we?
|
||||
04:32 So we can go ahead and drop that.
|
||||
04:34 Let's just do a quick clean-up here, get rid of this.
|
||||
04:37 We're not doing that, nothing changes there.
|
||||
04:39 Okay, great.
|
||||
04:40 It looks like we can probably add some time.
|
||||
04:43 Let's go and try to test this out here.
|
||||
04:46 Going to be the host, we need a log in.
|
||||
04:49 Let's see our cages.
|
||||
04:51 We've got these two, let's update the cage availability.
|
||||
04:54 The large boa constrictor one is available
|
||||
04:57 so that's going to be number two.
|
||||
04:58 Great, we've selected it.
|
||||
05:00 This is going to be 2018/01/01.
|
||||
05:06 Brand new year, cages available.
|
||||
05:09 Let's say that's for five days.
|
||||
05:11 Great, a date was added.
|
||||
05:12 Let's add one more.
|
||||
05:16 Cool, now if we list our cages,
|
||||
05:18 you can see our low large boa constrictor cage
|
||||
05:21 now has two available bookings.
|
||||
05:23 It has this time for five days and that time for ten days,
|
||||
05:26 And neither of them are booked
|
||||
05:28 because no guests have come along and actually booked it.
|
||||
05:30 But it's available and now they can go and ask,
|
||||
05:34 hey what cages are available for my snake?
|
||||
05:37 When they ask, this large boa cage should come up.
|
||||
05:40 Maybe we'll make a little side money
|
||||
05:42 while our boa constrictor is not using it.
|
@ -0,0 +1,86 @@
|
||||
00:01 So before we finish the host side,
|
||||
00:03 where you actually can see your bookings
|
||||
00:05 and things like that,
|
||||
00:06 that turns out to be one of the most complex
|
||||
00:08 types of queries we're doing in this entire application.
|
||||
00:12 I want to make sure that you can book some stuff,
|
||||
00:14 you can see it so the data comes out when we write it.
|
||||
00:16 That'll make a lot more sense.
|
||||
00:17 So let's take a moment and focus
|
||||
00:18 on the guest side of the things.
|
||||
00:21 Let people come in, log in, create an account,
|
||||
00:24 register their snakes and so on.
|
||||
00:26 So if we go up to the top
|
||||
00:27 to our little switch action thing here,
|
||||
00:30 notice that I'm using the create and login from host,
|
||||
00:33 there's no reason to write that code twice,
|
||||
00:35 we'll just use that one.
|
||||
00:36 Now the thing I want to focus on for a moment is
|
||||
00:38 adding a snake and viewing your snake.
|
||||
00:40 Now this is super, super similar
|
||||
00:42 to what we already did so let's go ahead
|
||||
00:44 and just talk through this real quick
|
||||
00:46 instead of write it from scratch.
|
||||
00:47 So just like before, we have to have an account,
|
||||
00:50 ask a few basic questions like what is your snake's name,
|
||||
00:53 let them cancel by hitting nothing in the first one,
|
||||
00:55 get the information about the snake
|
||||
00:57 and then we call the function "add snake"
|
||||
00:59 and you know, you could see this is very, very similar
|
||||
01:02 so we're going to get the owner,
|
||||
01:03 get the owner back and maybe it makes more sense
|
||||
01:06 because we've been writing others like this,
|
||||
01:07 to code like this.
|
||||
01:09 We're going to create the snake and save it,
|
||||
01:10 but remember the relationship between snake and owners
|
||||
01:13 is managed by the snake IDs inside of the owners,
|
||||
01:17 so we're going to go get a fresh copy of the owner
|
||||
01:18 from the database,
|
||||
01:20 update that, and save it.
|
||||
01:22 Then we'll go back to the snake that we just created.
|
||||
01:25 Also, we'll call a quick reload account
|
||||
01:27 to make sure the snake ID,
|
||||
01:28 for some reason if they get reused real quick,
|
||||
01:29 are in the active in memory account.
|
||||
01:32 So that's all it means to create a snake,
|
||||
01:35 just like creating a cage was.
|
||||
01:38 So everything's exactly the same there.
|
||||
01:40 And then to view your snakes,
|
||||
01:42 we just write the function,
|
||||
01:43 get me the snakes for the user,
|
||||
01:45 and we loop over them.
|
||||
01:47 That again, is very much like the cages,
|
||||
01:50 we get the owner, in this case just to be sure
|
||||
01:53 that we have the fresh set of IDs there,
|
||||
01:56 and then we do the ID in "owner.snake_ids"
|
||||
01:59 and we can call to all function,
|
||||
02:00 and then convert it to a list.
|
||||
02:01 I suppose we could probably skip this,
|
||||
02:03 but either way, this is all good.
|
||||
02:05 So this gives us our snakes,
|
||||
02:07 and we're going to list it out.
|
||||
02:08 Let's go ahead and just run that to make sure
|
||||
02:10 this is all working here.
|
||||
02:11 This time, we're going to be a guest.
|
||||
02:13 And let's go ahead and log in
|
||||
02:14 and this time I want to log in as Sarah.
|
||||
02:16 So Sarah's going to be my guest,
|
||||
02:18 and Michael is going to be the person with the cages.
|
||||
02:21 Login, so we're logging in as Sarah.
|
||||
02:24 And let's say I'd like to view my snakes.
|
||||
02:27 Hmm, you have zero snakes.
|
||||
02:29 Okay, let's add a snake.
|
||||
02:31 They'll have Slither,
|
||||
02:33 and Slither is 1.2 meters long,
|
||||
02:36 this is a large gardener,
|
||||
02:39 and no those are not venomous,
|
||||
02:40 so we've created Slither.
|
||||
02:42 And let's add one more snake.
|
||||
02:43 This is going to be Bully,
|
||||
02:46 and Bully is .5, .4 meters let's say,
|
||||
02:49 this is a bull snake.
|
||||
02:52 Now if we say view your snakes,
|
||||
02:55 there's your two snakes.
|
||||
02:56 Okay, so our guest side of registering a snake
|
||||
02:59 so that we can book it into a cage,
|
||||
03:01 and viewing it, that kind of stuff, is all finished.
|
@ -0,0 +1,338 @@
|
||||
00:00 So, we have cages.
|
||||
00:02 We have available dates in cages, and we have snakes.
|
||||
00:05 Time to book a snake into a cage
|
||||
00:07 on one of those available dates.
|
||||
00:08 This turns out to be one of the more complicated things
|
||||
00:10 we're going to do in this application.
|
||||
00:12 And there's a lot of input, and juggling, and stuff.
|
||||
00:15 So I'm going to paste a few things here
|
||||
00:17 just about asking questions about the dates and stuff.
|
||||
00:21 And then we're going to go and write
|
||||
00:22 the database queries from scratch.
|
||||
00:25 So let's start here.
|
||||
00:26 We're going to start by making sure you have an account.
|
||||
00:29 And then we're going to get all the snakes
|
||||
00:31 and make sure you have a snake,
|
||||
00:32 because having an account is not enough.
|
||||
00:34 You also have to have a snake you can put into there.
|
||||
00:36 We're going to ask some questions
|
||||
00:38 about when do you want to do this.
|
||||
00:39 We're going to use Python "dateutil" to parse that.
|
||||
00:43 So just like before, we're going to do a little error handling
|
||||
00:45 to make sure you're not trying to book some sort of
|
||||
00:48 reverse thing like I check out before I check in,
|
||||
00:53 something like this, right?
|
||||
00:54 Okay, so then the next thing we need to do
|
||||
00:56 is find out the available cages.
|
||||
00:58 And this is where it gets interesting.
|
||||
01:00 So we're going to write a function called get available cages.
|
||||
01:02 We're going to take the check in, the check out, and the snake.
|
||||
01:06 We also need to figure out which snake you want.
|
||||
01:09 So first of all, let's do an enumerate,
|
||||
01:11 lift out your snakes,
|
||||
01:13 and we'll say snake one this, snake two is not,
|
||||
01:16 and you'll pick the snake, okay.
|
||||
01:18 So, take our snakes, pick that, got our time
|
||||
01:22 and then we're going to go to the database
|
||||
01:23 and find particular cage that we can work with.
|
||||
01:28 Now, that's not all there is to it.
|
||||
01:29 So this is just going to get us the cages
|
||||
01:31 that could be booked, and then we have to ask,
|
||||
01:34 that's this little section right here,
|
||||
01:35 and we have to let the user pick a cage,
|
||||
01:37 and we'll find the underlying booking behind it.
|
||||
01:40 So let's write this function.
|
||||
01:41 So this can be a date-time and a snake.
|
||||
01:51 You, of course, don't have to put the tie pins.
|
||||
01:53 But I find at the data access layer,
|
||||
01:56 it's really helpful, maybe through,
|
||||
01:58 you can see it through the rest of the application,
|
||||
01:59 we're not doing this.
|
||||
02:00 But at the data access layer,
|
||||
02:02 I find it really helpful to say
|
||||
02:03 these are the things that go in,
|
||||
02:04 these are the things that go out.
|
||||
02:05 This is how we're working with the database.
|
||||
02:08 Okay, so here's where we get down to business.
|
||||
02:11 We're going to come in here. Let me move this up for you.
|
||||
02:13 So we need to do a couple of things.
|
||||
02:15 We need to find all the cages that have bookings,
|
||||
02:19 that are not booked between this time and that time.
|
||||
02:23 All right?
|
||||
02:24 And we need the snake information,
|
||||
02:25 because not all cages allow poisonous snakes,
|
||||
02:29 and they don't all necessarily fit.
|
||||
02:31 If I have a 20 foot snake, I can't put into a two foot cage.
|
||||
02:35 So let's just do a little quick rule of thumb to say,
|
||||
02:40 if your snake is four times longer or more than the cage,
|
||||
02:45 then the snake can't go into it, right?
|
||||
02:46 Snakes can curl up, but they can only curl up so much.
|
||||
02:48 So we'll say something like, the minimum size of the cage
|
||||
02:51 that we're going to get is "snake.length / 4"
|
||||
02:55 This is going to be part of our query.
|
||||
02:58 The date's going to be part of the query,
|
||||
02:59 and whether it's venomous or not.
|
||||
03:02 We're going to do a few interesting things here.
|
||||
03:03 This is definitely part of the more complicated queries.
|
||||
03:06 So I'm going to go to the cage, and we'll say objects.
|
||||
03:08 And when you have these compact queries,
|
||||
03:11 I find it's nice to spread this across multiple lines.
|
||||
03:13 Well, I'll say ".filter"
|
||||
03:15 And on multiple filters the are effectively and
|
||||
03:18 I'll say square meters.
|
||||
03:21 Now, I'd like to say, let's say
|
||||
03:23 "=min_size" or greater, right?
|
||||
03:25 Just like we saw with the operators about in,
|
||||
03:28 there's one for greater than or equal.
|
||||
03:30 And we can say, the square meters
|
||||
03:31 are greater than or equal to this minimum size.
|
||||
03:34 But that's not the only thing that we need.
|
||||
03:36 We also need to go and do another pretty wild thing.
|
||||
03:39 We want to go to the bookings.
|
||||
03:41 Now, remember, refresh, over here we have a cage.
|
||||
03:44 The cage has a bookings field.
|
||||
03:46 We go to the definition for bookings.
|
||||
03:48 Bookings have a check-in date and a check-out date.
|
||||
03:51 We want to work with that.
|
||||
03:53 How do we do that in MongoEngine?
|
||||
03:56 We come over here and we can traverse
|
||||
03:57 that hierarchy with underscores as well.
|
||||
03:59 So we can say bookings.check_in_date,
|
||||
04:02 and we want to have the check in date
|
||||
04:06 before or equal to the check in that those passed, right?
|
||||
04:10 So the time you can check in has to precede
|
||||
04:13 the time this person is checking in.
|
||||
04:16 And then we'll do something similar for check-out.
|
||||
04:22 Okay, so this is part of the query.
|
||||
04:24 Now, if the snake is poisonous,
|
||||
04:27 we also want to say that they allow poisonous snakes.
|
||||
04:30 So we'll say this, we'll say if "snake.is_venomous"
|
||||
04:33 we need to augment this query.
|
||||
04:35 So we can do that because it hasn't executed yet.
|
||||
04:38 It's like the potential to be executed.
|
||||
04:40 So we can say "query = query.filter"
|
||||
04:43 and is thing "allow_dangerous_snakes"
|
||||
04:47 That's what we want to work with.
|
||||
04:51 "=True"
|
||||
04:52 Because non-dangerous ones can stay
|
||||
04:54 in cages that will either allow
|
||||
04:56 or not allow dangerous snakes.
|
||||
04:57 But if it's venomous, we have to
|
||||
04:59 have this additional criteria.
|
||||
05:02 And maybe we want to have some kind of order by,
|
||||
05:04 like we'd like to show them the cheaper ones.
|
||||
05:06 So let's go like this.
|
||||
05:07 We'll say "cages = "
|
||||
05:09 and we'll finalize the query like this.
|
||||
05:11 We'll say "query.order_by"
|
||||
05:16 Now, you don't do this sort of default
|
||||
05:19 of this named parameter type thing for this.
|
||||
05:22 We want to order by price.
|
||||
05:24 And default is ascending, so cheapest ones first,
|
||||
05:27 and maybe you want to see the biggest ones first as well.
|
||||
05:29 So we'll say square meters, like this.
|
||||
05:33 So we're going to say, first order by price, lowest to highest,
|
||||
05:37 and then show us, if the price is the same,
|
||||
05:40 show us the largest ones at that price level
|
||||
05:44 down to the smallest ones.
|
||||
05:46 Excellent.
|
||||
05:47 So this is pretty much working.
|
||||
05:50 It turns out it looks like it's going to completely work,
|
||||
05:53 but it turns out that there's a challenge
|
||||
05:55 we're going to run into.
|
||||
05:56 And in PyMongo this is straightforward to solve,
|
||||
05:59 although, you have to use a lot of operators,
|
||||
06:00 these dollar operators to make it work.
|
||||
06:02 But I haven't found a good way in MongoEngine.
|
||||
06:04 And so I still find the on balance
|
||||
06:06 that work with MongoEngine, even for this query, is better.
|
||||
06:09 But here's the problem.
|
||||
06:10 What this query is asking.
|
||||
06:11 You're probably thinking it looks right.
|
||||
06:14 It takes a moment to realize
|
||||
06:16 the challenge we're hitting here.
|
||||
06:18 What this query says is, go to the cage,
|
||||
06:21 and find me the cage where the square meter is
|
||||
06:22 at least minimum size.
|
||||
06:24 That's totally fine, that works perfectly.
|
||||
06:26 And it says, show me where there's a booking
|
||||
06:29 query within check out.
|
||||
06:30 And there's a booking, oops, this should be greater than.
|
||||
06:36 That was almost an error.
|
||||
06:37 So where there's a check-out date passed,
|
||||
06:41 equal to or passed where I'm willing
|
||||
06:42 to check-out for my snake.
|
||||
06:45 The problem is if I have, let's say,
|
||||
06:47 20 bookings in this cage, right,
|
||||
06:51 I probably want to check one more thing,
|
||||
06:54 but I can just check it.
|
||||
06:55 Damn, we're going to have to do one more bit at the bottom.
|
||||
06:57 But the problem is, what if, there's two bookings?
|
||||
07:03 One that starts way in the past,
|
||||
07:05 but the check-out is one day later.
|
||||
07:08 And then there's another one where
|
||||
07:10 the check-out date is way in the future,
|
||||
07:12 but you can only check-in one day before.
|
||||
07:14 And these are not the same bookings, right?
|
||||
07:16 There's a booking where the check-in date
|
||||
07:17 is before the check-in,
|
||||
07:18 and there's a booking where the check-out date
|
||||
07:20 is after the check-out,
|
||||
07:21 but those are not the same.
|
||||
07:23 You need to say, there's an individual booking,
|
||||
07:26 not like some set of bookings where one matches one clause,
|
||||
07:31 and the other matches the other.
|
||||
07:32 So the way you do that in Mongo,
|
||||
07:35 is you say, dollar element match.
|
||||
07:37 I think it's "$elemmatch"
|
||||
07:39 So element match is the description of the thing.
|
||||
07:43 So you can say, it must have both of these.
|
||||
07:46 But I don't see how to do that in MongoEngine.
|
||||
07:48 It seems like it should be possible,
|
||||
07:49 it certainly is possible for equality.
|
||||
07:53 But for these operators plus element match,
|
||||
07:56 didn't seem to work for me.
|
||||
07:57 Anyway, you figure it out, feel free to use element match.
|
||||
08:00 I didn't, so I've got to add one more line here.
|
||||
08:04 And I'm just going to copy that over real quick,
|
||||
08:06 and we'll talk about it.
|
||||
08:07 So what we're going to do is we're going to say,
|
||||
08:09 let's go and actually, these are the cages we care about.
|
||||
08:13 I'm going to iterate over the query, which executes it here.
|
||||
08:16 And remember, the cage,
|
||||
08:18 each cage contains a number of bookings.
|
||||
08:19 For each booking, I want to check that both
|
||||
08:23 the check-in is before and the check-in is after,
|
||||
08:26 and that the snake ID is none.
|
||||
08:29 So it's not already booked during that time.
|
||||
08:32 Though if it's both available,
|
||||
08:34 and the check-in check-out date matches,
|
||||
08:38 then we can make that part of our final cage there,
|
||||
08:41 the final cage list.
|
||||
08:42 Okay, so, and it says it returns this,
|
||||
08:45 but actually, what it returns is a list of cages.
|
||||
08:49 There we go.
|
||||
08:50 Okay, so that's what we got to do.
|
||||
08:52 If I could get element match to work with
|
||||
08:54 greater than less than in MongoEngine,
|
||||
08:56 this would not be necessary.
|
||||
08:58 You could just straight up run that query.
|
||||
09:01 But anyway, it's not a huge deal.
|
||||
09:03 Remember, this set is already filtered down to where,
|
||||
09:06 significantly, right, where the check-in and check-outs
|
||||
09:09 do match, it just happens to be
|
||||
09:11 maybe one more thing is missing there.
|
||||
09:13 Okay, so we're getting available dates.
|
||||
09:15 So let's come back to our guest here.
|
||||
09:17 We've got our available cages.
|
||||
09:19 Now, we just have to like show them to the user,
|
||||
09:22 and let them pick it.
|
||||
09:25 All right, so this just takes some
|
||||
09:26 pre-written code for this.
|
||||
09:27 There are a certain number of cages available,
|
||||
09:29 and we're going to enumerate over them.
|
||||
09:31 And don't need the average radian right now.
|
||||
09:42 Do it like this.
|
||||
09:43 We're just going to print out the name, the square meters,
|
||||
09:44 whether it's carpeted, and whether or not it has toys.
|
||||
09:47 We want that to be true-false.
|
||||
09:48 So let's put that "yes and no," more friendly, right?
|
||||
09:51 And if there's no cages, "sorry, there's no cages."
|
||||
09:54 But if there are, we'll ask you which one,
|
||||
09:56 and we'll pick that out in a zero based, of course.
|
||||
10:00 And finally, the final thing to do is going to be book a cage.
|
||||
10:03 And then actually, we'll just give out
|
||||
10:05 this nice little success message saying,
|
||||
10:08 "Hey, you booked it for this time."
|
||||
10:10 So last thing to do with this booking a cage
|
||||
10:12 is to actually book it.
|
||||
10:17 So let's go over here.
|
||||
10:18 Put my term, write it one more time here.
|
||||
10:21 And now, what we're going to do is
|
||||
10:23 we're going to loop over that cages booking.
|
||||
10:27 So the way it works is, they've selected a cage.
|
||||
10:31 They haven't selected the individual booking.
|
||||
10:33 So we just have to go one more time over the bookings,
|
||||
10:35 and go, let's find a booking within this cage,
|
||||
10:38 which we know exists because it's in the list,
|
||||
10:40 and let's assign it to the snake.
|
||||
10:43 So we'll do something like this.
|
||||
10:44 We'll come down here, and we'll start up by
|
||||
10:46 sending this little booking to nothing
|
||||
10:49 just in case for some odd reason we don't find one.
|
||||
10:52 I'm going to go through and, again,
|
||||
10:53 do a similar test as we did right there, right?
|
||||
10:57 We got to find the available booking within the cage.
|
||||
10:59 We know it exists, but we got to find it.
|
||||
11:01 Then down here we're going to just set a few things.
|
||||
11:04 Say the "booking.guest"
|
||||
11:08 and get a little telesense auto-completion if you want.
|
||||
11:14 Set the guest owner ID.
|
||||
11:18 I guess we probably got to pass the account as well.
|
||||
11:23 We'll say "account.ID"
|
||||
11:27 Say booking dot
|
||||
11:34 Set the booked date.
|
||||
11:36 It was booked right now, regardless of when the booking was.
|
||||
11:40 Then we also need to set the snake.
|
||||
11:47 There we go.
|
||||
11:48 And then we got to go back and save it,
|
||||
11:51 but remember, we don't call save there.
|
||||
11:53 We call a "cage.save()"
|
||||
11:55 Okay, excellent.
|
||||
11:56 Now, I think it just believes that's a misspelling,
|
||||
11:59 but I'm going to say it's not.
|
||||
12:01 All right, so that should let us book a cage.
|
||||
12:04 That was a tough one, right?
|
||||
12:05 So pretty interesting query.
|
||||
12:08 We're using the operators, greater than less than.
|
||||
12:11 We're traversing the hierarchy.
|
||||
12:13 And like I said, that we're sort of effectively
|
||||
12:16 in memory applying this element match.
|
||||
12:18 Element match works in MongoEngine,
|
||||
12:20 but I couldn't get it to work with both
|
||||
12:22 element match and the operators.
|
||||
12:24 So anyway, this will be fine.
|
||||
12:27 Come down here and given a cage, we'll pull out the booking.
|
||||
12:31 We probably could structure it slightly differently,
|
||||
12:33 so we could skip this step and somehow
|
||||
12:35 capture the booking directly, but this is fine.
|
||||
12:37 It works plenty fast forwarded.
|
||||
12:39 Set the, hey you booked it, values of the booking,
|
||||
12:43 and call save.
|
||||
12:44 All right, I think it's time for us to test our book a cage.
|
||||
12:49 And I notice I almost forgot to add this here,
|
||||
12:52 "state.active_account" I added it below.
|
||||
12:55 So let's go ahead and run this.
|
||||
12:57 And we'll come in here, and we'll be, oops, be a guest.
|
||||
13:01 And let's go ahead and log in.
|
||||
13:06 And let's see our snakes.
|
||||
13:08 So we have these two snakes, neither of them are venomous.
|
||||
13:10 Let's book a cage.
|
||||
13:12 I'm going to start by booking this.
|
||||
13:13 Now, how do I know that date?
|
||||
13:15 Cuz over here, we have two available bookings
|
||||
13:20 for the large boa cage,
|
||||
13:21 and these times one to six in January.
|
||||
13:25 So we'll go two, let's say two to four.
|
||||
13:27 It should be fine.
|
||||
13:29 This four.
|
||||
13:32 And it says which snake, remember,
|
||||
13:34 it matters the size of the cage and snake,
|
||||
13:37 as well as whether it's venomous.
|
||||
13:38 So we'll pick slither.
|
||||
13:40 And hey, look, the one cage is here.
|
||||
13:41 So let's say, all right, let's book it.
|
||||
13:44 We've successfully booked the large boa.
|
||||
13:48 All right, now we haven't written view your bookings,
|
||||
13:52 but we do have that, I believe,
|
||||
13:55 we might have that for the other one.
|
||||
13:57 Go over here as a host, and we log in as Michael.
|
||||
14:01 I think we might not have implemented this as well.
|
||||
14:04 But we can list our cages.
|
||||
14:06 Yes, there we go.
|
||||
14:07 We can see that we have two cages,
|
||||
14:10 Bully's cage and large boa.
|
||||
14:12 And look at this, somebody has booked this one,
|
||||
14:16 this slot, for the large boa cage.
|
||||
14:18 Yes, so it looks like that worked successfully
|
||||
14:21 just like we expected.
|
@ -0,0 +1,137 @@
|
||||
00:00 Alright we're down to the very last thing we're
|
||||
00:02 going to do as a guest which is to view our bookings.
|
||||
00:05 We're able to book a gauge but as a guest we couldn't see
|
||||
00:09 what are your upcoming stays for your snakes
|
||||
00:12 and things like that. So, again, just for the sake of time
|
||||
00:15 let me go over here and put some pre-written code
|
||||
00:17 and we'll go write the data access later.
|
||||
00:21 So, require an account and we're going to call
|
||||
00:24 "get_bookings_for_user" and this is going to return a set of
|
||||
00:27 bookings and just to remind you what that looks like
|
||||
00:30 the bookings are going to have the days, possible reviews and
|
||||
00:33 the snake ID is going to be really important.
|
||||
00:37 So what we want to do is we want to say give us the snake
|
||||
00:41 given a snake ID and a super simple way for us to do that
|
||||
00:44 is to actually generate a dictionary using a
|
||||
00:47 dictionary comprehension. So this little expression on here
|
||||
00:51 is going to create a dictionary where they key is the ID
|
||||
00:54 and the value is the snake for all the snakes
|
||||
00:58 belonging to us, whoever the logged in user is.
|
||||
01:00 Then we're going to get the bookings and this part we're
|
||||
01:03 going to write and then we're going to loop over here
|
||||
01:06 and we're going to print out the snake
|
||||
01:10 and we'll use the dictionary to look up
|
||||
01:11 to get the name here. We're going to print the cage name
|
||||
01:15 and you're going to import date/time there.
|
||||
01:19 We're going to create a date/time and do a little bit
|
||||
01:22 of math here on the check out. So we're going to turn this
|
||||
01:25 back into day. If you're checking in on this day
|
||||
01:27 for five days or something like that.
|
||||
01:29 Okay so that's what our view bookings UI
|
||||
01:33 pretty much stretch. Right, with our app
|
||||
01:36 this is kind of the UI code, if you will.
|
||||
01:39 But we've got to write this bookings for user
|
||||
01:43 this is going to be a "bson.ObjectID"
|
||||
01:48 We'll bring it back as a list of booking, I believe.
|
||||
01:52 Now, notice one other thing before we write this code
|
||||
01:56 Over here we're saying "b.cage.name"
|
||||
02:00 Now cages have bookings, but bookings don't have cages.
|
||||
02:04 There's not a super nice way to create that
|
||||
02:07 reverse association in MongoEngine,
|
||||
02:11 so what we're going to do is part of what we're going to do
|
||||
02:14 in this function is we're going to set up that relationship
|
||||
02:17 and let's call this "user_id" or account ID
|
||||
02:20 or something like that. Okay, so
|
||||
02:22 the first thing we need to do is find the owner
|
||||
02:25 so close account equals let's pass in an email instead
|
||||
02:31 something like that and before I forget
|
||||
02:33 pass in the email there. Okay great.
|
||||
02:35 We'll have our account and we've already verified
|
||||
02:38 that they're logged in. So we can just assume that
|
||||
02:40 that happens. So we can say booked cages and so we can
|
||||
02:43 find all the cages that have been booked
|
||||
02:46 by this person. So we can say "Cage.objects"
|
||||
02:51 and now we'll do a few other interesting things
|
||||
02:52 we haven't seen yet. Let's say filter
|
||||
02:54 and here we'll say "bookings__"
|
||||
02:58 and then we're looking for
|
||||
03:00 the guest owner ID. Let's go to our booking
|
||||
03:06 that matches the owner. That equals to "account.id"
|
||||
03:14 We don't actually care about all the details about the cage
|
||||
03:16 and you can skip this little part right here,
|
||||
03:18 but just as a means of efficiency we go over here
|
||||
03:22 and say only. We haven't talked about this yet.
|
||||
03:24 What we can do is say we only want to get back
|
||||
03:27 two pieces of information not potentially tons of
|
||||
03:30 information in this document. We want the bookings,
|
||||
03:34 and we want the name. So when we say "cage.nameabove"
|
||||
03:37 that means something, right?
|
||||
03:40 So let's create the bookings and we'll do this with
|
||||
03:42 a list comprehension. So we'll say bookings is this
|
||||
03:46 and I'm going to write it one way and then I'm going to have
|
||||
03:48 to make a change to do this reverse association with
|
||||
03:50 the cage. So I can say booking
|
||||
03:55 or booking in the book cages
|
||||
03:59 but remember there are other bookings that are unrelated
|
||||
04:02 to us. Here right could be two different snakes staying
|
||||
04:04 in the same cage, different days. We need a little test
|
||||
04:12 alright so this is going to be the bookings that are
|
||||
04:17 assigned to us within the cages for which we have booked
|
||||
04:21 right, so show me all the cages where we've booked
|
||||
04:23 at least one of the bookings and we're going to strip out
|
||||
04:26 the unrelated ones. So you might think that we're kind of done
|
||||
04:29 and that we're very, very close to done, but we're not done.
|
||||
04:33 So we've run this, you should see this is going to crash.
|
||||
04:37 It's that line right there. I almost messed up.
|
||||
04:40 So if this is actually for cage in the booked cages
|
||||
04:44 and each cage contains a booking. So we've got to do a
|
||||
04:47 double loop here. Booking in "cage.bookings"
|
||||
04:52 What we're going to do is going to take the tyrannical list of
|
||||
04:54 cages which nested inside them contain a bunch of bookings
|
||||
04:59 and we're going to flatten that list with a double loop
|
||||
05:02 go through each cage, go through each booking and just
|
||||
05:04 turn that into a list and across all those bookings
|
||||
05:08 across all the cages only show the ones which we some point
|
||||
05:11 have booked. Okay so this is close but if we try to run it
|
||||
05:16 this reverse lookup of the cage here it's not going to work.
|
||||
05:21 So, let's see we're going to try and run that real quick
|
||||
05:24 save, go to guest login as Sarah
|
||||
05:30 now if we try to view your bookings
|
||||
05:32 we see no cage. So let's view one more little trick.
|
||||
05:36 We can do a transformation at this level, right
|
||||
05:39 this is like the select part of the list comprehension
|
||||
05:42 now this has to be an expression
|
||||
05:44 I don't think we can do this with a lambda expression
|
||||
05:49 cause it doesn't allow us to make modifications
|
||||
05:51 so we got to define this little local function
|
||||
05:53 so we'll say "map_cage_to_booking"
|
||||
05:59 given a cage and a booking this is going to
|
||||
06:03 be the silliest thing you've seen. "booking.cage = cage"
|
||||
06:08 well, and then we're going to return cage--sorry--booking
|
||||
06:13 but why do we need that? We need that so we come down here
|
||||
06:16 and we add this function. It's going to take a booking and it's
|
||||
06:20 going to put that same booking right back into the list
|
||||
06:24 but the booking will be changed in that it's
|
||||
06:25 going to have a cage associated with it.
|
||||
06:27 Okay, I know that's not super obvious but that's what
|
||||
06:30 we need to make that one line work.
|
||||
06:33 I made a quick error here, I had booking cage here
|
||||
06:38 and cage booking there so cage booking, cage booking
|
||||
06:42 okay looks like its ready. We'll try again.
|
||||
06:55 Alright, so fewer bookings. Woo hoo it works!
|
||||
06:57 We have one booking. Our snake Slither is booked in the
|
||||
07:01 large boa on the stay for five days.
|
||||
07:04 Let's add one more booking just to make sure this is working.
|
||||
07:07 We'll book a cage, alright so let's try to book that other
|
||||
07:12 available booking. This time we're going to put Bully in there
|
||||
07:16 and I guess we're going to book that one. Great
|
||||
07:18 we view our bookings again. We now have our snakes booked
|
||||
07:20 into the different sections at the different times.
|
||||
07:24 Let's just try one more time now that those bookings
|
||||
07:26 should have been used up, to see what happens.
|
||||
07:30 So let's try to book one more cage. Say we'll start
|
||||
07:32 on that date and we'll check out on that date
|
||||
07:35 and we'll use this one. "Sorry, no cages, both
|
||||
07:38 available spots have already been booked." Just so happens
|
||||
07:41 to be to our snakes. Awesome.
|
||||
07:43 It looks like the guest side of things is 100% working
|
@ -0,0 +1,120 @@
|
||||
00:01 We just have one function left to write
|
||||
00:04 for viewing the bookings as a host.
|
||||
00:07 What bookings, available booking dates in your cages,
|
||||
00:10 have been booked?
|
||||
00:11 And then we'll be done with this application.
|
||||
00:13 So this require account stuff, paste that,
|
||||
00:16 that's the same, nothing special there.
|
||||
00:19 Now we want to get the cages for the current user.
|
||||
00:22 Well guess what?
|
||||
00:23 We've already written that.
|
||||
00:25 "find_cages_for_user(state.active_account)"
|
||||
00:28 That's cool, so that was already done
|
||||
00:30 at some point along the way.
|
||||
00:32 And then what we need to do is we want to get
|
||||
00:35 the bookings and we want actually a flat list of
|
||||
00:38 these are all the available slots that
|
||||
00:40 people have booked across all the cages.
|
||||
00:44 All right, so what we're going to do is
|
||||
00:47 something called bookings, like this,
|
||||
00:48 a list comprehension.
|
||||
00:50 And we're going to use sort of a dual comprehension
|
||||
00:52 to flatten it.
|
||||
00:53 So I'm going to put something here, one or two things,
|
||||
00:57 I'll say what those are in a minute.
|
||||
00:59 So we want to say "for c in cages"
|
||||
01:02 And then for each cage we want to say
|
||||
01:05 "for b in c.bookings"
|
||||
01:08 Now we don't want all of them, right?
|
||||
01:10 There's already a way to see that kind of stuff.
|
||||
01:12 But what we want is the ones that have been booked.
|
||||
01:15 We know they're booked if the booked time
|
||||
01:17 or the booked date was set.
|
||||
01:19 Let's say "if b.booked_date is not None"
|
||||
01:24 What do we want to get back here?
|
||||
01:25 We want to get the cage and the booking.
|
||||
01:27 So we're going to get a flat list of all the bookings
|
||||
01:30 and along with it we're going to carry along
|
||||
01:32 its cage that it came from.
|
||||
01:35 So that's pretty straightforward.
|
||||
01:36 And then, that's this part, getting them as a flat list.
|
||||
01:40 And then the last thing to do is just print them out
|
||||
01:42 and that's a lot of typing for not a lot of value.
|
||||
01:45 So let's paste that over.
|
||||
01:48 We'll import date time,
|
||||
01:51 and we're going to, as we loop over them, we're going to
|
||||
01:54 unpack that
|
||||
01:55 So the cb here gets unpacked into the cb right there
|
||||
01:59 and we'll say for this cage, it was booked on this date
|
||||
02:02 by so and so.
|
||||
02:04 All right, looks like this is going to work.
|
||||
02:05 I have a lot of faith in it.
|
||||
02:06 Let's give it a try.
|
||||
02:08 We'll come over here, we're going to be host,
|
||||
02:10 we're going to log in as me.
|
||||
02:14 And, are you ready?
|
||||
02:15 Moment of truth, view your bookings.
|
||||
02:18 No, there's no duration and days.
|
||||
02:21 Ah, so where did this duration and days go?
|
||||
02:25 So this is the booking.
|
||||
02:26 Let's look at the booking real quick here.
|
||||
02:28 It has a check-in and check-out date.
|
||||
02:30 Let's add this duration in days.
|
||||
02:33 Now if we add it as an actual thing that MongoEngine saves,
|
||||
02:37 that won't be so great because it's going
|
||||
02:39 to have duplicate data.
|
||||
02:41 Check-in, check-out, and days, could get
|
||||
02:43 out of sync.
|
||||
02:43 So what we're going to do is we're going to add a property.
|
||||
02:46 So we'll have a property called "duration_in_days"
|
||||
02:52 So close, days.
|
||||
02:54 That the same, yes.
|
||||
02:55 Duration in days.
|
||||
02:57 So down here we just need to use the time delta
|
||||
03:00 to figure out what that is.
|
||||
03:01 So we'll say "dt=self.check_out_date - self.check_in_date"
|
||||
03:04 and will return
|
||||
03:08 DT dot days.
|
||||
03:09 All right, let's try to run this again.
|
||||
03:13 Oh double ats, come on, too much help here.
|
||||
03:18 Here we go, all right.
|
||||
03:19 Come as a host, log as me, all right, ready?
|
||||
03:22 View your bookings.
|
||||
03:23 Ta-da, beautiful.
|
||||
03:25 And you can see our property is working just right there.
|
||||
03:29 So five days and 10 days.
|
||||
03:31 Now remember when we actually checked,
|
||||
03:34 we said we were going to book it?
|
||||
03:36 We could book it for a sub-set of time,
|
||||
03:38 we just don't store the data for how long
|
||||
03:40 the user said versus how long the time slot was.
|
||||
03:43 So they kind of get that whole slot,
|
||||
03:45 and that slot is worth five days,
|
||||
03:47 and 10 days in duration.
|
||||
03:49 But given the data that we're keeping,
|
||||
03:51 this is working totally well.
|
||||
03:54 So I think we've done everything.
|
||||
03:55 We can create a count, log in,
|
||||
03:57 we can list our cages.
|
||||
04:00 We can register a cage, which we already did.
|
||||
04:03 We can update the availability which is
|
||||
04:05 how we got these slots.
|
||||
04:06 We can view our bookings, which we just wrote.
|
||||
04:08 And we can even get little help
|
||||
04:10 and go back to the main menu.
|
||||
04:12 And we can check out the guests, just one
|
||||
04:14 more time around.
|
||||
04:15 Again, create log in, same thing.
|
||||
04:18 It says book a cage, that's reserve a cage
|
||||
04:21 for your snake.
|
||||
04:22 We saw that we can add snakes.
|
||||
04:24 Oh, I got to log in as Sarah, she's the one with snakes.
|
||||
04:29 She has those snakes there.
|
||||
04:30 She can view her bookings from the perspective
|
||||
04:33 of her snake, not from the available slots,
|
||||
04:36 things like that.
|
||||
04:37 She only sees her bookings, not all the
|
||||
04:39 bookings across everything.
|
||||
04:41 And we can go back to the main menu,
|
||||
04:42 or 'cause we're done, we can say goodbye
|
||||
04:45 to Snakebnb
|
@ -0,0 +1,30 @@
|
||||
00:01 Now we built our app but let's review
|
||||
00:02 some of the core concepts we saw along the way.
|
||||
00:05 If we want to insert an object
|
||||
00:07 we just create a standard Python object style.
|
||||
00:11 We just say owner = owner.
|
||||
00:13 Called the initializer.
|
||||
00:15 We could either pass the values as keyword arguments
|
||||
00:18 or we could say owner.name = name.
|
||||
00:20 Owner.email = email.
|
||||
00:22 And at this moment there is no ID associated
|
||||
00:25 with this object.
|
||||
00:26 But then we just call owner.save()
|
||||
00:27 and now the object ID or whatever the primary key is
|
||||
00:32 we can set functions to be called
|
||||
00:33 when that happens for that generation.
|
||||
00:36 Whatever that's going to be.
|
||||
00:37 We've got it set after you call save,
|
||||
00:40 so now you can start working with it
|
||||
00:41 as if it came from the database.
|
||||
00:43 We also might want to insert a bunch of things.
|
||||
00:46 It turns out, if you have 100,000 items to insert,
|
||||
00:49 and you create one save, create one called save,
|
||||
00:52 create one called save, it's a lot of database
|
||||
00:55 back and forth, and it's very slow.
|
||||
00:56 So what you would rather do is create a list of them.
|
||||
00:59 So here we have a bunch of snakes we want to save,
|
||||
01:01 we create a bunch of them, put them in this list,
|
||||
01:03 and then you call snake.objects().insert(snakes)
|
||||
01:05 and you give it the list, and that's much quicker
|
||||
01:07 if you want to do a bulk insert type of thing.
|
@ -0,0 +1,22 @@
|
||||
00:00 We sound like querying the database
|
||||
00:01 is pretty straightforward.
|
||||
00:02 The way it works is we start
|
||||
00:04 with the collection,
|
||||
00:05 or the type that represents the collection,
|
||||
00:07 we want to work with.
|
||||
00:08 And then we say ".filter"
|
||||
00:10 so here we're saying filter,
|
||||
00:11 and we want to do a match, where the email
|
||||
00:14 equals the value of the email variable.
|
||||
00:16 And you could have more than one thing in here.
|
||||
00:18 You could have more than one filter statement,
|
||||
00:19 and those basically combine as an and.
|
||||
00:23 Now this would return
|
||||
00:25 potentially a bunch of owners,
|
||||
00:26 but we don't want a bunch of owners.
|
||||
00:29 We want the one that we know matches this email,
|
||||
00:31 so we can say ".first"
|
||||
00:32 and it'll give us the one, or at least the first,
|
||||
00:34 item to match.
|
||||
00:36 If there's no match, we get none back.
|
||||
00:38 We don't get a crash or anything like that.
|
@ -0,0 +1,33 @@
|
||||
00:01 When we're creating against just plain, straight fields
|
||||
00:04 in our object, that's pretty straightforward.
|
||||
00:06 We said email equals the value that we're looking for,
|
||||
00:09 but if we're looking deep inside of a hierarchy,
|
||||
00:12 it can get a little more, let's say, not obvious.
|
||||
00:15 So we're combining two really interesting things here.
|
||||
00:17 We're going to the cage and we're going to search within
|
||||
00:20 the bookings embedded document list, right?
|
||||
00:25 Bookings is a list and it contains a bunch
|
||||
00:27 of these booking objects.
|
||||
00:29 So the first thing that we're doing is using double
|
||||
00:30 underscores say bookings, guest, snake ID.
|
||||
00:33 So we're looking at the value guest snake ID of the booking
|
||||
00:36 items within that list and we're also applying
|
||||
00:40 the N operator.
|
||||
00:41 So the double underscore N means the collection
|
||||
00:45 on the right, we're doing an N test for the various booking
|
||||
00:49 and the snake ID.
|
||||
00:51 So we use the double underscore to separate and navigate
|
||||
00:53 the levels in subdocuments as well as to apply
|
||||
00:57 the particular dollar operators and then what I consider
|
||||
01:01 best practice is to fully execute the query before
|
||||
01:05 you leave this function.
|
||||
01:06 If we just returned book cages, it kind of still
|
||||
01:09 is not yet executed.
|
||||
01:10 It hasn't quite talked to the database and so I want
|
||||
01:12 the database to be done and over with by the time
|
||||
01:14 we leave this method.
|
||||
01:15 So wrapping it in a list will execute all that stuff
|
||||
01:19 and pull it back.
|
||||
01:20 For super large amounts of data, there might be reasons
|
||||
01:22 you don't do this, but for most of them, I would do
|
||||
01:26 something like this.
|
@ -0,0 +1,19 @@
|
||||
00:00 Now, sometimes you don't want a quality in your matches.
|
||||
00:03 You want some kind of operator like greater than
|
||||
00:05 or less than or in or things like that.
|
||||
00:09 So here you can see, we're going to the cage
|
||||
00:11 and we're finding all the cages
|
||||
00:13 where the square meters is at least the minimum size.
|
||||
00:16 So just like with subdocuments, we use the double underscore
|
||||
00:19 and the operator name, GTE, here
|
||||
00:22 to actually do this query.
|
||||
00:24 There's a bunch of dollar operators.
|
||||
00:25 You can find them in the MongoDB documentation,
|
||||
00:27 and you apply all of them in this way.
|
||||
00:31 Now, the other thing that we're looking at here
|
||||
00:32 that we didn't do in our app is count.
|
||||
00:35 So if we want to know how many cages there were,
|
||||
00:37 we could say do the query.count,
|
||||
00:39 and it'll just do a count in the database
|
||||
00:41 rather than pull all the objects back
|
||||
00:44 where you do a length of it or something like that.
|
@ -0,0 +1,37 @@
|
||||
00:00 The most straightforward way
|
||||
00:01 to make changes to a document, or to a record
|
||||
00:04 is to go get it from the database,
|
||||
00:06 change the class, and call save.
|
||||
00:08 So in this example,
|
||||
00:09 we're going to go get the owner out of the database.
|
||||
00:11 Make sure that's all good, don't want to have errors.
|
||||
00:14 We're going to create a snake,
|
||||
00:15 we're going to do work with the snake,
|
||||
00:17 and then we want to append the snake ID
|
||||
00:21 onto the owners snake ID collection.
|
||||
00:25 So on line 11 we say owner.snakeIDs.append,
|
||||
00:28 and we give it this new ID that was gotten
|
||||
00:31 from the snake on line nine, when we called save.
|
||||
00:33 And we save the owner, and that's that.
|
||||
00:36 So we get the document, we make a change to it,
|
||||
00:38 in the case of line 11 here, and then we just call save
|
||||
00:40 and that pushes it back.
|
||||
00:42 This works, but that transfers the entire document
|
||||
00:45 out of the database, over to our app,
|
||||
00:47 deserializes it, processes it,
|
||||
00:49 and then reverses that back to the database.
|
||||
00:52 That can be slow,
|
||||
00:53 but that can also have concurrency issues.
|
||||
00:56 If two people run this exact same method
|
||||
00:59 at almost exactly the same time,
|
||||
01:01 with the same email address, there's a chance
|
||||
01:04 that one save is going to overwrite
|
||||
01:06 the snake ID's of the other, right.
|
||||
01:08 Both of them read the owner,
|
||||
01:09 one makes a change, one makes a change,
|
||||
01:11 one saves, the other saves.
|
||||
01:12 You only have one snake, not two.
|
||||
01:14 So there are challenges with this,
|
||||
01:16 but if you're pretty confident that that's not an issue
|
||||
01:18 you're going to run into, this is a really nice
|
||||
01:20 and easy way to do it.
|
@ -0,0 +1,57 @@
|
||||
00:00 While MongoDB and MongoEngine
|
||||
00:02 let us work with these documents, make changes,
|
||||
00:05 and push them back in, there might be a better way.
|
||||
00:08 So, if we know that we want to just change
|
||||
00:11 some small part of the document in a very controlled way,
|
||||
00:14 we might want to use some of the atomic operators.
|
||||
00:16 So here let's suppose that there's a number of stays field,
|
||||
00:21 which is an integer, in the cage.
|
||||
00:24 So we just keep track of how many times
|
||||
00:25 people have stayed at it.
|
||||
00:27 Now there probably are better ways to get this,
|
||||
00:29 but let's suppose that is a number
|
||||
00:31 and we want to increment it.
|
||||
00:32 Instead of pulling the cage back
|
||||
00:34 and doing a plus equals one sort of thing and saving it,
|
||||
00:36 we can literally go to Mongo and say
|
||||
00:39 increment this number of value of number of stays by one.
|
||||
00:43 That could be negative, that could be ten,
|
||||
00:44 but I have one here.
|
||||
00:46 So you write the "query.update_one"
|
||||
00:49 And then pass a little operator.
|
||||
00:50 So that's really great.
|
||||
00:52 Now how about this putting the snake ID on the owner.
|
||||
00:55 We can do that too.
|
||||
00:57 Over here we're adding a snake.
|
||||
00:59 We generate the snake we call save.
|
||||
01:01 That's standard, that's an insert.
|
||||
01:02 But then this line where you have the number of updated,
|
||||
01:06 we have owner, objects, emails, email,
|
||||
01:08 and then instead of saying get it back,
|
||||
01:11 make the change, append it to the snake ID as impulse save,
|
||||
01:15 we're doing something with an operator.
|
||||
01:17 We're saying update_one, and we're using the push operator.
|
||||
01:20 That's $push.
|
||||
01:21 We're going to push it onto the snake IDs collection.
|
||||
01:25 Another thing we might do,
|
||||
01:26 which probably makes even more sense,
|
||||
01:28 would be add to set.
|
||||
01:29 It's another related operator that will say
|
||||
01:32 add this ID to this set or this list
|
||||
01:35 if and only if it doesn't already exist
|
||||
01:36 so you won't get duplicates.
|
||||
01:38 So we're pushing the snake ID on there
|
||||
01:41 that way we never pull the owner back.
|
||||
01:43 These are atomic.
|
||||
01:44 They're perfectly fine in concurrent situations,
|
||||
01:46 things like that.
|
||||
01:47 So this time we have to check that we updated it
|
||||
01:49 in a different way, but the same effect as we saw before.
|
||||
01:54 So MongoEngine supports these in place updates,
|
||||
01:57 array operations, and set operations.
|
||||
02:00 So increment as well as push,
|
||||
02:03 and these are both better for concurrent safety.
|
||||
02:05 Think of them as basically transactional
|
||||
02:07 and they're better in pure performance.
|
||||
02:09 They're not always as simple to work with,
|
||||
02:11 but they are better if you can use them.
|
@ -0,0 +1,83 @@
|
||||
00:00 The first thing that we need to do
|
||||
00:01 to start working with MongoEngine is
|
||||
00:03 tell MongoEngine how to speak to MongoDB.
|
||||
00:06 Let's go back to our Snakebnb app we've been working on.
|
||||
00:09 And we're going to go in here to this data section.
|
||||
00:12 we're going to create a new file,
|
||||
00:14 a new Python file called mongo_setup.
|
||||
00:16 So in here, we're going to write a simple method
|
||||
00:19 that we can call from other places,
|
||||
00:20 and as you'll see in the slides that we look at later,
|
||||
00:24 for real applications that use proper connections,
|
||||
00:28 encryption, accounts, things like that,
|
||||
00:29 this can be a little more complicated,
|
||||
00:31 but it's going to start out pretty simple,
|
||||
00:32 so we'll say, "global_init()" and in here,
|
||||
00:35 we just want to call one function,
|
||||
00:37 so we're going to have to have MongoEngine here,
|
||||
00:40 so we'll import MongoEngine,
|
||||
00:41 and we'll just say, "mongoengine.register_connection()"
|
||||
00:44 and the first thing that we pass is an alias,
|
||||
00:47 so I'll be real explicit and say, "alias='core' "
|
||||
00:50 Now what is this alias thing about?
|
||||
00:51 We can have multiple connections to even multiple databases,
|
||||
00:55 or even multiple database servers registered here.
|
||||
00:58 We could have like a core data and
|
||||
01:01 say analytics for just analytics that goes to a
|
||||
01:03 separate database that maybe has tons more data
|
||||
01:06 because it's page views, and actions, and so on.
|
||||
01:08 But we might keep that separate, so we can back it up
|
||||
01:10 on a separate schedule, something like that.
|
||||
01:12 Then, we need to set the name of the database,
|
||||
01:15 and we'll set that to Snakebnb.
|
||||
01:17 Do a quick format here, and it's all good to go.
|
||||
01:20 Like I said, this gets more interesting in real connections.
|
||||
01:23 We're going to need to call this to talk to MongoDB,
|
||||
01:27 so let's go over to our little program,
|
||||
01:28 and you saw up here at the top, there was this to do,
|
||||
01:31 setup MongoEngine global values,
|
||||
01:32 and that was basically what we were doing there,
|
||||
01:35 so we need to come over here and say,
|
||||
01:36 you need to go to "data.mongo_setup"
|
||||
01:39 We'll just call it "mongo_setup"
|
||||
01:42 And this should be pretty simple,
|
||||
01:43 mongo_setup.global_init()
|
||||
01:45 Now we just need to make sure we call this once
|
||||
01:47 in our application and we need to do this
|
||||
01:49 before we actually interact with anything else,
|
||||
01:52 so let's go and apply these settings
|
||||
01:54 over to our entities as well, so we'll look at snakes first.
|
||||
01:59 Now this model is going to be mapped
|
||||
02:01 into one or more of those databases.
|
||||
02:04 Well, one among many potential databases,
|
||||
02:06 so the way that we can tell it how to work,
|
||||
02:09 MongoEngine will use a property we can add to it.
|
||||
02:12 We can say "meta = " and we give it a dictionary,
|
||||
02:14 and we can say " 'db_alias' "
|
||||
02:17 and we'll say " 'core', " here, okay?
|
||||
02:18 While we're at it let's say, " 'collection': 'snakes' "
|
||||
02:22 So even though we called it capital S snake,
|
||||
02:25 the thing in the database where these records,
|
||||
02:27 these documents are stored will be called
|
||||
02:29 snakes, plural, lowercase, and here we can tell it
|
||||
02:31 if this goes into the core database,
|
||||
02:33 unlike maybe the analytics one, or something like,
|
||||
02:36 all right, we're only going to have one in here,
|
||||
02:37 but for our example, you want to have this here,
|
||||
02:39 in case you want to add more later.
|
||||
02:41 All right, I'll go ahead and add the rest of these
|
||||
02:43 to owners and cages, but not bookings,
|
||||
02:46 because bookings is going to be nested inside here, all right,
|
||||
02:49 so we don't need to tell that how it gets stored,
|
||||
02:51 'cause it's stored alongside cage,
|
||||
02:54 triple set of cages, and no surprise, owners.
|
||||
02:57 All right, so those are our three top-level entities
|
||||
03:00 that map to our three top-level collections in MongoDB.
|
||||
03:02 Now, we've registered the connection
|
||||
03:04 by using our Mongo Setup Global,
|
||||
03:06 which should we just call a regular connection.
|
||||
03:08 Like I said, this shuouldn't get
|
||||
03:09 way more complicated in reality, and then,
|
||||
03:11 we just go and we set this meta to use the core connection
|
||||
03:14 as well as naming the actual table
|
||||
03:16 or collection it's going to go to.
|
@ -0,0 +1,51 @@
|
||||
00:00 Let's take a moment and look
|
||||
00:01 at this concept of registering connections.
|
||||
00:03 We're going to need to work with MongoEngine,
|
||||
00:05 so of course, "import mongoengine"
|
||||
00:07 and we need to set the alias and the name of the database,
|
||||
00:10 so it should say "mongoengine.register_connection"
|
||||
00:12 give it the alias, give it the name.
|
||||
00:14 We need to call this before we start interacting
|
||||
00:16 with our classes and other types, before we try
|
||||
00:19 to do any queries or save any data, this has to be set up.
|
||||
00:22 So, this is what we wrote in our application
|
||||
00:23 and this works fine when you're talking to
|
||||
00:25 the local MongoDB running no encryption, no accounts,
|
||||
00:29 default port running on the local machine.
|
||||
00:31 Same machine that this could.
|
||||
00:32 Now, if you're doing this in production, there's more to it.
|
||||
00:35 You need to set the username and password,
|
||||
00:37 which you have to set up in MongoDB,
|
||||
00:39 there's none by default, so you got to
|
||||
00:41 set that up with the right permissions.
|
||||
00:43 Probably it's a different server on
|
||||
00:44 an alternative port, so set the host and the port.
|
||||
00:47 You would like to create some sort of admin account,
|
||||
00:50 which is associated with that username and password
|
||||
00:52 so you say, "Look you authenticate an admin,
|
||||
00:54 use this mechanism."
|
||||
00:56 Finally, if you're going to do connections across,
|
||||
00:59 somewhere outside your data center you pretty much
|
||||
01:01 should just turn this on, you need to turn on SSL
|
||||
01:03 and configure the server for SSL.
|
||||
01:05 And then you pass that additional data
|
||||
01:07 in addition to the alias and the DB.
|
||||
01:10 Now, there's a lot going on here, and deployment
|
||||
01:12 and running MongoDB and production
|
||||
01:14 is not as simple maybe, as it could be.
|
||||
01:16 Certainly it's something that you need to be very careful
|
||||
01:19 about, like no authentication, no encryption, right?
|
||||
01:22 Don't run your code that way, it's fine to do it
|
||||
01:24 for development, but don't do it for production.
|
||||
01:27 In my full MongoDB course I actually go into,
|
||||
01:30 spend an hour, go and create a Linux server
|
||||
01:32 and set it up in a cluster of the database
|
||||
01:34 and the web servers and those kinds of things
|
||||
01:36 and really make this work perfectly and safely.
|
||||
01:39 But, in this course we're not going to go into it,
|
||||
01:41 I just want to leave you with you need to set this up
|
||||
01:43 you can look at the MongoDB.org site as well,
|
||||
01:46 and MongoDB.com site and go through
|
||||
01:47 the documentation on some of the steps.
|
||||
01:49 Or, just take my other course if you're
|
||||
01:51 really going to go and use this in production.
|
@ -0,0 +1,156 @@
|
||||
00:03 So far we've modeled our data with Python classes,
|
||||
00:05 but there was no real MongoEngine entity stuff,
|
||||
00:08 they wouldn't actually be saved or processed by MongoEngine.
|
||||
00:11 It just happens to be we kind of sketched
|
||||
00:13 them out in Python class style.
|
||||
00:15 So we're going to change that now.
|
||||
00:16 We're going to go make our standard, plain old Python classes.
|
||||
00:19 Proper MongoEngine entities.
|
||||
00:21 The snake is pretty simple.
|
||||
00:22 So let's start there.
|
||||
00:23 So in order to work with MongoEngine over here,
|
||||
00:26 I'm going to have to import it.
|
||||
00:28 Now, you might want to do
|
||||
00:30 from MongoEngine, import some things,
|
||||
00:32 but I like to be real explicit.
|
||||
00:34 These things are coming from MongoEngine,
|
||||
00:36 even in my production code.
|
||||
00:37 So this is how I'm going to do it.
|
||||
00:38 We're going to set the register date
|
||||
00:40 to a particular type of descriptor
|
||||
00:42 that comes from MongoEngine,
|
||||
00:44 and at sort of the type of level this tells MongoEngine
|
||||
00:47 what type of data, and constraints,
|
||||
00:49 and requirements go onto this field.
|
||||
00:52 However, at run time, it's going to act like,
|
||||
00:54 say date time or whatever it is.
|
||||
00:56 In this case the date time
|
||||
00:57 and species would be string and so on.
|
||||
00:58 So we'll come over and say "mongoengine.DateTimeField()"
|
||||
01:02 and we'll just go like this.
|
||||
01:03 This will tell MongoEngine to map
|
||||
01:04 that to the database as a date-time field.
|
||||
01:06 Over here, we'll say "mongoengine.StringField"
|
||||
01:09 and over here, the length,
|
||||
01:11 let's say this is in meters and that's probably decent floats.
|
||||
01:15 alright so this will be a float field.
|
||||
01:17 The name, again is string field.
|
||||
01:19 Whether it's venomous or not, that's true or false.
|
||||
01:22 It could be a number, like level of venomous.
|
||||
01:24 I don't know, but we're going to call this a boolean field
|
||||
01:27 and that's that.
|
||||
01:27 So our snake is all ready to map into MongoDB.
|
||||
01:31 MongoDB doesn't have things like required fields
|
||||
01:34 or default values or anything like that,
|
||||
01:36 but MongoEngine does.
|
||||
01:37 So let's change this to make it a little simpler
|
||||
01:39 to create a snake.
|
||||
01:40 So for example, "registered_date" is almost always just
|
||||
01:43 whenever you inserted it, right?
|
||||
01:45 So what we can do is we can come over here
|
||||
01:46 and set a default function
|
||||
01:47 that will execute any time MongoEngine inserts a new snake.
|
||||
01:50 So we're going to start with date-time.
|
||||
01:52 So the function that we want to call is the now function
|
||||
01:55 which gives us the full year, month, day,
|
||||
01:58 hour, minute, second representation of time.
|
||||
02:00 So we'll come in here, say "datetime.datetime.now"
|
||||
02:03 and be very careful not to put the parenthesis.
|
||||
02:06 You're passing the function, not the value of "now"
|
||||
02:08 That would be when the program started.
|
||||
02:10 So that can be a little tricky.
|
||||
02:11 Over here for species, let's say that you have
|
||||
02:13 to say the species.
|
||||
02:14 So we're going to say this is going to be required is true.
|
||||
02:17 In fact, the length is required, the name is required,
|
||||
02:20 whether it's venomous is required.
|
||||
02:22 We can have things like minimum values have to be like 0.001
|
||||
02:27 or things like this.
|
||||
02:29 So you can't have like a negative length.
|
||||
02:30 There's a lot of cool constraints
|
||||
02:32 that we can do with our types here.
|
||||
02:33 So this snake is now ready to be used in our database.
|
||||
02:38 Let's look at the next one.
|
||||
02:39 Let's work on the cage next.
|
||||
02:41 So again, import MongoEngine and we'll use that
|
||||
02:43 in a few places.
|
||||
02:44 This is exactly the same here.
|
||||
02:46 So set the default and then the name is
|
||||
02:48 just going to be string and so on.
|
||||
02:50 So I'll just sketch these out for you.
|
||||
02:53 So these seem like reasonable types here, and let's go ahead
|
||||
02:55 and set the required properties for things that we require.
|
||||
02:59 Most of these would be required,
|
||||
03:01 and whether or not we allow dangerous snakes.
|
||||
03:03 If you don't set that, let's say no,
|
||||
03:05 by default you're not going to have a dangerous snake.
|
||||
03:08 Okay, so these are just like the snake before.
|
||||
03:10 This however, gets more interesting.
|
||||
03:12 We're going to come down here and we're going to set this
|
||||
03:14 to be a "mongoengine.EmbeddedDocument"
|
||||
03:18 We can have just a single thing, like a booking
|
||||
03:20 or embed the snake in the owner or something like that,
|
||||
03:25 but we want to have a list of embedded documents
|
||||
03:28 and what we need to pass in here is the actual type
|
||||
03:32 that is contained in there.
|
||||
03:32 So we're going to import "data.bookings.Booking"
|
||||
03:35 One other thing that I also realized that I forgot to do
|
||||
03:38 in the previous ones and we'll go back and fix that,
|
||||
03:39 is we need to tell MongoEngine
|
||||
03:41 that this is a top level document.
|
||||
03:43 We need to make this have a base class of type document.
|
||||
03:47 We'll do that for snakes as well.
|
||||
03:54 Now let's go to the booking, we were just working with that.
|
||||
03:56 So this one, recall, is the type
|
||||
04:00 that's embedded within the cage
|
||||
04:03 That's embedded in the cage, that means it's not a document.
|
||||
04:06 Right? That would be a top level thing.
|
||||
04:07 This is an embedded document.
|
||||
04:09 Alright, so this can be contained within other documents,
|
||||
04:11 but itself cannot be top level.
|
||||
04:14 Let's go ahead and set these, as well.
|
||||
04:18 Now, when we're talking about IDs in MongoDB,
|
||||
04:20 the default is something called an object ID.
|
||||
04:23 Like a UID, or GUID or something,
|
||||
04:25 so when we're talking about a reference,
|
||||
04:27 typically it doesn't have to be, but it typically is.
|
||||
04:33 There we go.
|
||||
04:34 We've got our two references as object IDs.
|
||||
04:37 We've got our booking date,
|
||||
04:38 which does not have to be required,
|
||||
04:39 and it doesn't have a default value.
|
||||
04:41 This is when was booked,
|
||||
04:42 which happens after the booking slot was made available,
|
||||
04:47 but at the time of creation of the slot of booking, right,
|
||||
04:50 we've like put a note for booking.
|
||||
04:51 You have to say the check in and check out date,
|
||||
04:53 and again, the reviews.
|
||||
04:55 These are not getting set until after.
|
||||
04:58 I set this to zero so we can say,
|
||||
05:00 like, you know, required to be one to five.
|
||||
05:03 They actually rate the thing and then you
|
||||
05:05 can sort of exclude the ones that are zero.
|
||||
05:08 The final one is owner and it's very, very similar.
|
||||
05:10 I'll just sketch that out for you.
|
||||
05:13 We've got our flat pieces here.
|
||||
05:15 Our register date, name and email
|
||||
05:17 and now we're going to have a list of IDs.
|
||||
05:19 So we'll come in here and say "mongoengine.ListField"
|
||||
05:23 for both of them.
|
||||
05:26 So this'll let us store the object IDs
|
||||
05:28 that refer to the snakes and the object IDs
|
||||
05:30 that refer to the cages.
|
||||
05:32 Last thing to do is make the base class the document here.
|
||||
05:36 Alright, so what have we done?
|
||||
05:38 We've set all of the fields to their respective types
|
||||
05:41 out of MongoEngine as descriptors.
|
||||
05:43 We've set either default or required values
|
||||
05:46 and we've set the metadata,
|
||||
05:48 which talks about which database connection to use
|
||||
05:50 and what to call the collection
|
||||
05:52 when it goes into the database.
|
||||
05:54 And we've done that for our three top level items here.
|
||||
05:58 The one that is different and stands out is the booking,
|
||||
06:00 which is embedded within the cage
|
||||
06:03 and this is an embedded document,
|
||||
06:06 but otherwise everything goes pretty much the same.
|
@ -0,0 +1,91 @@
|
||||
00:02 Let's review the core concepts around
|
||||
00:04 creating MongoEngine entities.
|
||||
00:06 We started out by creating what I call basic classes.
|
||||
00:09 These are classes that could just as easily have been mapped
|
||||
00:12 to a relational database,
|
||||
00:14 because you just have flat fields or columns,
|
||||
00:16 if you want to think of them that way.
|
||||
00:18 And none of the nested or particular capabilities
|
||||
00:22 of document databases.
|
||||
00:24 The one that matched that was the snake.
|
||||
00:26 The snake, we make sure that it derives
|
||||
00:28 from "mongoengine.Document,"
|
||||
00:30 and then we specify the fields by
|
||||
00:33 passing along or creating these MongoEngine descriptors.
|
||||
00:36 So we said there's a registered date,
|
||||
00:38 and that's a mongoengine.DateTimeField.
|
||||
00:40 The length, that was a float.
|
||||
00:41 That was the length of the snake in meters.
|
||||
00:43 The name of the snake is a string,
|
||||
00:45 species has a string as well,
|
||||
00:47 and whether or not it's venomous,
|
||||
00:50 is a Boolean, true or false.
|
||||
00:52 So you can see we can map out the types
|
||||
00:54 for this basic snake class, really easily here.
|
||||
00:57 Of course our snake should have default values,
|
||||
01:00 constraints like required fields, and things like that.
|
||||
01:03 So here we've taken that same snake class,
|
||||
01:06 and we've added a default value for the register date.
|
||||
01:09 We said just call the function "datetime.datetime.now"
|
||||
01:12 anytime you insert a new snake.
|
||||
01:14 So it's going to automatically tag that new entity,
|
||||
01:18 or that new document,
|
||||
01:19 with the date in which it was inserted.
|
||||
01:21 Now remember, be super careful.
|
||||
01:23 Do not call the function now,
|
||||
01:24 pass the function now.
|
||||
01:26 Okay, we also set the length to a float.
|
||||
01:29 We said that's a required float.
|
||||
01:31 You have to specify the length
|
||||
01:32 or MongoEngine will give you an error.
|
||||
01:35 So you can't insert this thing; that field is required.
|
||||
01:37 It's interesting that that's not
|
||||
01:39 a feature of MongoDB, that's a feature of MongoEngine.
|
||||
01:41 So by using MongoEngine instead of, say, PyMongo,
|
||||
01:44 we get these additional features, same for the default.
|
||||
01:47 And name, species, and venomous,
|
||||
01:49 also these are all required, so we can do this here.
|
||||
01:52 Now again, this is still one of these sort of basic
|
||||
01:53 classes with just our constraints and defaults.
|
||||
01:57 Let's look at the cage.
|
||||
01:58 The cage takes better advantage
|
||||
02:01 of the document database.
|
||||
02:03 So we have the name, the price, the square meters,
|
||||
02:07 required standard stuff there.
|
||||
02:08 We also have the bookings.
|
||||
02:10 These are either the times in which a cage can be booked,
|
||||
02:13 or an active booking where a snake has registered
|
||||
02:16 to be there at a certain time.
|
||||
02:18 We model that through the booking class,
|
||||
02:20 and we said this cage is going to embed
|
||||
02:23 the bookings into it.
|
||||
02:24 So to do that,
|
||||
02:25 we use the MongoEngine Embedded Document List Field.
|
||||
02:28 So a list of embedded documents,
|
||||
02:31 and the argument would pass
|
||||
02:32 as the type of embedded document.
|
||||
02:33 So it's a booking that we'd put in with this list.
|
||||
02:38 How does this look if we populate this cage
|
||||
02:42 we add a couple bookings and we call save?
|
||||
02:44 It looks like this.
|
||||
02:46 It has the standard fields, right,
|
||||
02:48 like an autogenerated_ID, the date that was registered,
|
||||
02:52 this is set as a default value in the full class.
|
||||
02:55 We have the name, the price, the square meters, and so on.
|
||||
02:58 So that's all standard stuff, and we've seen that before.
|
||||
03:01 But the bookings part, check that out.
|
||||
03:03 So we have bookings, and it's a list, right?
|
||||
03:06 Square brackets, not technically an array,
|
||||
03:08 in JavaScript, right?
|
||||
03:10 And the items in this list are those bookings.
|
||||
03:14 We have a check-in date, check-out date in a range.
|
||||
03:16 We have added two bookings in here.
|
||||
03:19 Now we didn't fill out the, they're not booked,
|
||||
03:22 we don't have a guest snake, and an owner ID,
|
||||
03:25 and they haven't already taken them,
|
||||
03:27 so they haven't rated it or given a review.
|
||||
03:29 Some of the pieces are not saved into the database
|
||||
03:31 to save space.
|
||||
03:32 Nonetheless here we have our embedded bookings inside
|
||||
03:36 of our document and we did that through
|
||||
03:38 the Embedded Document List Field.
|
@ -0,0 +1,187 @@
|
||||
00:00 We have our models, our classes in place.
|
||||
00:04 We have MongoEngine registered
|
||||
00:06 and told to connect to the default values
|
||||
00:09 for all the local stuff for MongoDB running locally.
|
||||
00:13 I already have MongoDB started up and running.
|
||||
00:15 Check out the documentation on how to get that working
|
||||
00:17 on your operating system.
|
||||
00:18 Like I said at the beginning.
|
||||
00:20 And it's time to create an account, logins,
|
||||
00:24 basically start implementing all these actions.
|
||||
00:26 And now we'll really get to start programming
|
||||
00:29 with the entities we've designed in MongoEngine.
|
||||
00:32 So let's begin by going down here to create account
|
||||
00:35 and program host.
|
||||
00:36 And we're going to be able to use this
|
||||
00:37 for actually both the host and the guest.
|
||||
00:41 Alright, so you can see there's a couple things
|
||||
00:42 we have to do, and then we're going to create the account.
|
||||
00:44 So let's work on this get name and email first.
|
||||
00:46 So we'll say "name= "
|
||||
00:47 and we'll just use simple input stuff.
|
||||
00:50 "What is your name?"
|
||||
00:53 Something like this.
|
||||
00:54 And we'll do email.
|
||||
00:58 We should of course have them, you know,
|
||||
00:59 give us a password, and things like that.
|
||||
01:01 But this is not a real website.
|
||||
01:02 We're not really actually logging in,
|
||||
01:04 it's just sort of a user creation-type thing.
|
||||
01:07 So we're going to create that account.
|
||||
01:08 Now I could go write the MongoEngine code
|
||||
01:10 to talk to Mongo and do the inserts here.
|
||||
01:13 But you'll see that we can do much, much better
|
||||
01:15 if we isolate all of these behaviors
|
||||
01:18 within a central location
|
||||
01:21 that we can use throughout our application.
|
||||
01:23 In these NoSQL databases,
|
||||
01:25 these document databases,
|
||||
01:27 there's not much structure in the database.
|
||||
01:30 We already have some structure added
|
||||
01:32 by having our classes, our MongoEngine types
|
||||
01:35 that we work with.
|
||||
01:36 We can also do a little bit better
|
||||
01:38 by having like a centralized data access piece.
|
||||
01:41 That's what we're going to work with here.
|
||||
01:44 Let's go create something called a "data_service"
|
||||
01:47 And we'll just put a bunch of functions
|
||||
01:49 that we need to work with here.
|
||||
01:51 Let's go back and let's import this,
|
||||
01:53 and I'm going to import in a little short way.
|
||||
01:55 So we'll say "import_services.data_service as svc"
|
||||
02:02 We're just going to use the functions of that module
|
||||
02:04 by calling it that.
|
||||
02:05 We come back down here.
|
||||
02:07 Instead of saying not implemented,
|
||||
02:09 let's say this.
|
||||
02:12 "svc.create_account()"
|
||||
02:15 and we're going to pass the name and the email.
|
||||
02:18 What we're going to get back is an account.
|
||||
02:20 So we want to actually store that
|
||||
02:24 in the statefulness of our application
|
||||
02:26 again, in a web-app, this would be with cookies
|
||||
02:28 and we get it back from the database every time.
|
||||
02:30 But we have this state, which has an active account.
|
||||
02:34 So what we're going to do is we're going to come up over here
|
||||
02:37 and we're going to say "state.active_account = " this.
|
||||
02:43 So we're going to get back an account from here.
|
||||
02:46 Now PyCharm says,
|
||||
02:47 whoa, whoa, whoa, there's something going on here.
|
||||
02:48 There's no account.
|
||||
02:49 There's no method called create account or function,
|
||||
02:52 but if I hit Alt+Enter,
|
||||
02:55 it'll say do you want to create one?
|
||||
02:57 Of course we want to create one.
|
||||
02:58 So name, let's even give this a little bit of typing here.
|
||||
03:03 Say it's going to return an owner.
|
||||
03:07 Okay, so that's all well and good.
|
||||
03:09 Now we need to use this.
|
||||
03:11 So now we get to programming with MongoEngine.
|
||||
03:13 How do we create one of these owners?
|
||||
03:14 Well, how would you do it if it was a regular class?
|
||||
03:18 You would say this.
|
||||
03:20 And you would set some properties like name equals name,
|
||||
03:23 owner email is that,
|
||||
03:28 and now we want to put it in the database.
|
||||
03:30 So we do that by calling using
|
||||
03:32 what's called the Active Record Design Pattern.
|
||||
03:34 We'll just call save right on this.
|
||||
03:36 Now we want to return owner.
|
||||
03:39 Now the important point here,
|
||||
03:40 is when we call save, all the default values are set.
|
||||
03:43 And we call save the primary key the _id
|
||||
03:47 is automatically generated.
|
||||
03:48 Here, it's just ".id" at MongoEngine.
|
||||
03:52 But in the database level, it's _id.
|
||||
03:54 That's automatically set.
|
||||
03:55 So this thing is up and running.
|
||||
03:56 We should have everything working well here.
|
||||
04:00 So let's go ahead and try to run this
|
||||
04:02 and see if everything's hanging together.
|
||||
04:04 Let me run it over, like this.
|
||||
04:09 So here's our Snakebnb.
|
||||
04:10 We're going to go and say we're our host.
|
||||
04:12 Notice at the prompt here,
|
||||
04:13 this little yellow thing,
|
||||
04:15 there's no name.
|
||||
04:17 So we'll go and create an account,
|
||||
04:19 with my name, my name is Michael,
|
||||
04:21 and my email is michael@talkpython.fm.
|
||||
04:25 Boom, logged in.
|
||||
04:26 You can see now the prompt has my logged in name.
|
||||
04:30 The next thing we got to do is just go from top to bottom.
|
||||
04:33 Let's go and log in.
|
||||
04:34 However, there is a problem.
|
||||
04:36 What if I say I want to create an account,
|
||||
04:38 and I say my name is Michael2,
|
||||
04:40 and I say it's michael@talkpython.fm.
|
||||
04:45 If I hit enter, there's just two of those.
|
||||
04:47 That's bad.
|
||||
04:48 So, what we want to do is
|
||||
04:50 we want to do a little check over here.
|
||||
04:55 So this is great, we got this working,
|
||||
04:56 and let's go ahead and annotate the type here as well.
|
||||
04:59 Let's say this is an owner.
|
||||
05:02 That's going to let us, when we interact with it later,
|
||||
05:05 say things like this and get, you know,
|
||||
05:08 all the IntelliSense and what not,
|
||||
05:09 Snake IDs, whatever.
|
||||
05:11 Okay, now before we do this,
|
||||
05:13 we want to verify that the account doesn't exist.
|
||||
05:15 So we'll say old account,
|
||||
05:19 let's say find an account by email.
|
||||
05:20 And again, this doesn't exist,
|
||||
05:22 so we'll create this function over here.
|
||||
05:25 This will let us see how to query, right?
|
||||
05:27 So to insert, we create one of these and we call save.
|
||||
05:31 To do the query, we're going to do this,
|
||||
05:34 we'll say the "owner = " we work with type
|
||||
05:37 and we say objects.
|
||||
05:39 Now there's a couple of things we could do.
|
||||
05:41 We could say filter, we kind of lose autocomplete here,
|
||||
05:44 but that's fine.
|
||||
05:44 We could say filter, and we could say "email=email"
|
||||
05:48 So we would match one of the fields there.
|
||||
05:53 Alright, and we would not put that of course.
|
||||
05:55 And this is going to return a query,
|
||||
05:57 and we want just one of them.
|
||||
05:59 So we'll save first.
|
||||
06:00 Now, it turns out when you have just one filter statement,
|
||||
06:03 you can actually condense it down like this.
|
||||
06:05 So we'll go ahead and write that.
|
||||
06:07 And we'll just say "return Owner"
|
||||
06:08 Okay, so there's our find account by email.
|
||||
06:11 And we'll check if old account,
|
||||
06:15 it'll be none if it's not found.
|
||||
06:17 So if there's old account will print a few functions,
|
||||
06:22 error message, success message,
|
||||
06:23 with some coloration, we'll say,
|
||||
06:26 "ERROR: Account with email already exists."
|
||||
06:31 And let's make this a cool Python 36 F String.
|
||||
06:38 Like so.
|
||||
06:40 Of course, we want to bail,
|
||||
06:41 and we don't want to actually create it.
|
||||
06:43 Here, we could maybe do something like that print.
|
||||
06:46 Let's do the success created new account with id.
|
||||
06:53 And let's say "state.active_account.id" like so.
|
||||
06:59 Great, let's just run this one more time.
|
||||
07:06 We want to come is as a host,
|
||||
07:08 we'll create an account,
|
||||
07:09 let's call this Sarah.
|
||||
07:11 So Sarah wants to come in,
|
||||
07:12 and maybe she's going to be able to like,
|
||||
07:14 she's going to be a guest.
|
||||
07:16 But right, we're going to use this host path
|
||||
07:19 to do it for a second.
|
||||
07:19 And so "sarah@talkpython.fm"
|
||||
07:24 Great, we've created a new account.
|
||||
07:25 Now let's just test this thing again.
|
||||
07:28 So we'll say I want to create an account again.
|
||||
07:30 Sarah, it didn't actually matter,
|
||||
07:32 let's say Susie and it's "sarah@talkpython.fm"
|
||||
07:38 This should no longer work.
|
||||
07:39 It should go and the query database and find this,
|
||||
07:41 and no, no, no error an account email "sarah@talkpython.fm"
|
||||
07:45 already exists.
|
||||
07:46 Perfect.
|
||||
07:47 I think the create account is done.
|
@ -0,0 +1,32 @@
|
||||
00:00 So we saved some data, and it looked like
|
||||
00:02 it went into the database, right?
|
||||
00:03 When we successfully saved it, we saw an idea was generated.
|
||||
00:07 When we tried to log in with the same,
|
||||
00:08 or create an account with the same email,
|
||||
00:10 we got an error node that it already exists,
|
||||
00:11 So, it's definitely working, but let's look at the data.
|
||||
00:14 With my favorite tool for working with MongoDB,
|
||||
00:18 Robo 3T, it use to be called Robomongo.
|
||||
00:21 Robo 3T, so here it is, you can see
|
||||
00:24 just by virtue of calling save
|
||||
00:26 that actually connected to the database,
|
||||
00:28 created this particular ... connected to the server
|
||||
00:31 created this database and the various collections.
|
||||
00:34 We only called save on owners, we only have owners so far.
|
||||
00:38 Nevermind the fact that we created the other types.
|
||||
00:40 We haven't saved anything there so, it doesn't exist.
|
||||
00:43 Wwe can come down here, we can say view documents
|
||||
00:45 and we actually have the two documents here.
|
||||
00:47 Remember, we created two accounts.
|
||||
00:49 We can view them this way, and there you go.
|
||||
00:52 We have Michael and my email, Sarah and her email,
|
||||
00:55 the registered dates and times
|
||||
00:57 and we don't yet have a snake or a cage cause,
|
||||
01:01 because well, we haven't implemented the ability to do that.
|
||||
01:03 But, we're going to have snake IDs and cage IDs
|
||||
01:05 in here as we create these snakes and cages.
|
||||
01:08 This is a really great way to work with the data
|
||||
01:10 if you go back to this mode you can even edit it,
|
||||
01:14 and make changes in here if you really wanted to.
|
||||
01:17 Alright, so definitely recommend installing this.
|
||||
01:19 It works on all the platforms, it's free and it's awesome.
|
@ -0,0 +1,38 @@
|
||||
00:01 We're able to create our account.
|
||||
00:02 So let's now add the ability to log in
|
||||
00:05 once we exit the application.
|
||||
00:07 So, we're over hear in the "log_into_account()"
|
||||
00:09 and the program host, this is super easy.
|
||||
00:11 We'll just have to get the email from the user,
|
||||
00:14 like this, so we'll say something to the fact of...
|
||||
00:18 So, we're going to log in, ask them what their email is
|
||||
00:19 and let's go ahead and do a "strip()" and a
|
||||
00:23 ".lower()" on this and in fact, let's always store that.
|
||||
00:26 So, go back up to our "create_account()" and do that here,
|
||||
00:29 so ".strip()" takes all the white space
|
||||
00:31 in case there's like a space or something on the end,
|
||||
00:33 and ".lower()" of course makes it lowercase.
|
||||
00:35 So, then we just need to see if the account exists.
|
||||
00:39 Well, we actually already wrote that so let's say this:
|
||||
00:43 Say the account is, the service
|
||||
00:45 not find account by, guessed it, email.
|
||||
00:49 And then we'll say we had a little error handling
|
||||
00:51 and say if not account...
|
||||
00:54 It's an error message.
|
||||
00:56 So, nothing there, and if it worked
|
||||
00:58 all we have to do is save it and maybe say,
|
||||
01:01 "You've logged in, yay!"
|
||||
01:03 So, let's say "state.active_account = account"
|
||||
01:09 and then we'll do a success message, "Logged in successfully."
|
||||
01:15 And then our little prompt will change straight away,
|
||||
01:18 so that should be good, let's try this.
|
||||
01:23 Come over here to the host, let's try to log in
|
||||
01:26 and remember, there's no real passwords we're just
|
||||
01:29 sort of playing around with accounts here.
|
||||
01:30 So, michael@talkckpython.fm.
|
||||
01:35 Boom! "Logged in successfully."
|
||||
01:37 Awesome, and you can see the prompt change.
|
||||
01:39 Let's try to log in again and I'll
|
||||
01:40 try to just use jeff@j.com.
|
||||
01:42 Nope, "Could not find an email with jeff@j.com."
|
||||
01:46 Looks like log in is working.
|
@ -0,0 +1,11 @@
|
||||
00:00 There it is, the finish line!
|
||||
00:01 You have made it, congratulations!
|
||||
00:04 I hope you've learned a lot throughout this course.
|
||||
00:06 We really have covered a majority
|
||||
00:08 of what you need to program against MongoDB.
|
||||
00:11 So you've done it, you've crossed the line,
|
||||
00:13 now you know enough to start building applications
|
||||
00:16 based on MongoDB.
|
||||
00:17 So the big question is, what are you going to build now?
|
||||
00:20 I hope you go out and build something amazing.
|
||||
00:22 Please share it with me when you do.
|
@ -0,0 +1,24 @@
|
||||
00:00 I want to take this moment
|
||||
00:01 to remind you to get the source code, come over to:
|
||||
00:04 Github.com/MikeCKennedy/MongoDB-QuickStart-Course
|
||||
00:08 and star and fork this so you're sure to have a copy.
|
||||
00:12 So one thing I do want to take you through
|
||||
00:14 really quick, two things.
|
||||
00:15 One, I covered in the beginning,
|
||||
00:17 is there is the starter code, which is here.
|
||||
00:20 This is exactly what we started from.
|
||||
00:22 And here is what we have finished with.
|
||||
00:25 You can see, right there, 15 minutes ago
|
||||
00:27 I wrote the final code.
|
||||
00:29 But I also wanted to make sure
|
||||
00:30 there were saved points along the way,
|
||||
00:32 so if we go back here and check out the branches,
|
||||
00:35 you'll see there's all these different branches.
|
||||
00:37 So these are all different save points that you can grab.
|
||||
00:40 So if I go here, for example,
|
||||
00:42 you can see final registered and list cages,
|
||||
00:45 guests can now book a snake into a cage, and things like,
|
||||
00:48 so these are different points in the course
|
||||
00:50 that you can go back and forward to.
|
||||
00:51 So be sure to make use of the branches
|
||||
00:54 and things like that if that can help you.
|
@ -0,0 +1,61 @@
|
||||
00:00 You've learned a bunch of stuff
|
||||
00:01 to program MongoDBs in MongoEngine,
|
||||
00:04 but there's actually a lot of other things
|
||||
00:06 that you need to take into consideration
|
||||
00:08 when you're doing MongoDB in production
|
||||
00:10 for real applications.
|
||||
00:11 So I want to encourage you to check out my paid course,
|
||||
00:15 MongoDB for Python Developers,
|
||||
00:17 and just some of the things we're covering,
|
||||
00:19 you've seen a little bit of it,
|
||||
00:20 but there's actually a lot more.
|
||||
00:21 So this is over seven hours of professional-grade
|
||||
00:24 MongoDB and Python programming,
|
||||
00:26 not just MongoEngine, but the core PyMongo,
|
||||
00:29 the JavaScript API and so on
|
||||
00:31 so let's see a little bit what's covered.
|
||||
00:32 So we talk in depth about how to set up your machine,
|
||||
00:36 whether it's Windows, Mac, or Linux,
|
||||
00:38 the tools that we're going to use,
|
||||
00:39 how to get them installed,
|
||||
00:41 the theory behind NoSQL,
|
||||
00:43 why NoSQL, why document databases,
|
||||
00:45 MongoDB's native shell and native query syntax,
|
||||
00:48 assuming we run these operations,
|
||||
00:50 these queries, filter statements and so on in MongoEngine,
|
||||
00:53 how does that map down to the database?
|
||||
00:55 This is important
|
||||
00:56 because when you're running MongoDB in production,
|
||||
00:59 you need to be able to use the tools
|
||||
01:01 and the query language to talk to it and manage it, right?
|
||||
01:04 And that is in this native query API.
|
||||
01:07 How to model data with documents,
|
||||
01:09 we did talk about this.
|
||||
01:10 We go into more depth in this course.
|
||||
01:13 MongoDB from PyMongo, this is the foundation of MongoEngine
|
||||
01:17 and basically Python's equivalent
|
||||
01:19 of the native query syntax.
|
||||
01:21 MongoEngine, we covered a lot of that in this course.
|
||||
01:24 This is pretty similar.
|
||||
01:25 High-performance techniques,
|
||||
01:26 so performance around document design,
|
||||
01:28 performance around indexes
|
||||
01:30 and using profiling to discover where you need those,
|
||||
01:33 so that's covered in this course.
|
||||
01:34 And super, super important is how to properly deploy MongoDB
|
||||
01:38 in production on the internet so it doesn't get hacked
|
||||
01:41 and you don't lose data or anything like that.
|
||||
01:44 So if you want to check out this course,
|
||||
01:45 here's a tremendously long URL
|
||||
01:47 that you very likely don't want to type so type this,
|
||||
01:50 bit.ly/mongocourse and that'll take you right there.
|
||||
01:53 You can check it out.
|
||||
01:53 I encourage you to take this course.
|
||||
01:55 If you like what you saw in this course,
|
||||
01:57 here's seven more hours going even more in depth.
|
||||
02:00 So with that, I want to say thank you.
|
||||
02:02 Thank you so much for taking my course.
|
||||
02:03 I really hope you learned a lot and you enjoyed it.
|
||||
02:05 Please connect with me on Twitter @mkennedy
|
||||
02:08 or various other places you find me on the internet.
|
||||
02:10 Thanks and see you later.
|
Loading…
Reference in New Issue