initial commit

master
androiddrew 6 years ago
commit 7773011c11

68
.gitignore vendored

@ -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…
Cancel
Save