commit 7773011c11a530d4fecb3a554b9da543db4658de Author: androiddrew Date: Sun May 19 19:32:26 2019 -0400 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..46c7c11 --- /dev/null +++ b/.gitignore @@ -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 \ No newline at end of file diff --git a/README.md b/README.md new file mode 100755 index 0000000..7a0efbf --- /dev/null +++ b/README.md @@ -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 +``` \ No newline at end of file diff --git a/readme_resources/mongo-quickstart-logo-scaled.jpg b/readme_resources/mongo-quickstart-logo-scaled.jpg new file mode 100755 index 0000000..5733e7b Binary files /dev/null and b/readme_resources/mongo-quickstart-logo-scaled.jpg differ diff --git a/sample_data/readme.md b/sample_data/readme.md new file mode 100755 index 0000000..0376c8a --- /dev/null +++ b/sample_data/readme.md @@ -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. diff --git a/sample_data/snake_bnb.zip b/sample_data/snake_bnb.zip new file mode 100755 index 0000000..df9c419 Binary files /dev/null and b/sample_data/snake_bnb.zip differ diff --git a/src/snake_bnb/requirements.txt b/src/snake_bnb/requirements.txt new file mode 100755 index 0000000..11d3b39 --- /dev/null +++ b/src/snake_bnb/requirements.txt @@ -0,0 +1,5 @@ +pymongo +mongoengine +tqdm +colorama +python-dateutil diff --git a/src/snake_bnb/src/data/bookings.py b/src/snake_bnb/src/data/bookings.py new file mode 100755 index 0000000..d3c3fd9 --- /dev/null +++ b/src/snake_bnb/src/data/bookings.py @@ -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 diff --git a/src/snake_bnb/src/data/cages.py b/src/snake_bnb/src/data/cages.py new file mode 100755 index 0000000..ee5006e --- /dev/null +++ b/src/snake_bnb/src/data/cages.py @@ -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' + } diff --git a/src/snake_bnb/src/data/mongo_setup.py b/src/snake_bnb/src/data/mongo_setup.py new file mode 100755 index 0000000..f9fe85c --- /dev/null +++ b/src/snake_bnb/src/data/mongo_setup.py @@ -0,0 +1,5 @@ +import mongoengine + + +def global_init(): + mongoengine.register_connection(alias='core', name='snake_bnb') diff --git a/src/snake_bnb/src/data/owners.py b/src/snake_bnb/src/data/owners.py new file mode 100755 index 0000000..90978a4 --- /dev/null +++ b/src/snake_bnb/src/data/owners.py @@ -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' + } diff --git a/src/snake_bnb/src/data/readme.md b/src/snake_bnb/src/data/readme.md new file mode 100755 index 0000000..9a566d3 --- /dev/null +++ b/src/snake_bnb/src/data/readme.md @@ -0,0 +1 @@ +We'll put some data things in this folder when we start the course. \ No newline at end of file diff --git a/src/snake_bnb/src/data/snakes.py b/src/snake_bnb/src/data/snakes.py new file mode 100755 index 0000000..9106fcf --- /dev/null +++ b/src/snake_bnb/src/data/snakes.py @@ -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' + } diff --git a/src/snake_bnb/src/infrastructure/state.py b/src/snake_bnb/src/infrastructure/state.py new file mode 100755 index 0000000..5c695de --- /dev/null +++ b/src/snake_bnb/src/infrastructure/state.py @@ -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) diff --git a/src/snake_bnb/src/infrastructure/switchlang.py b/src/snake_bnb/src/infrastructure/switchlang.py new file mode 100755 index 0000000..bda6ea9 --- /dev/null +++ b/src/snake_bnb/src/infrastructure/switchlang.py @@ -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) \ No newline at end of file diff --git a/src/snake_bnb/src/program.py b/src/snake_bnb/src/program.py new file mode 100755 index 0000000..2ca37cf --- /dev/null +++ b/src/snake_bnb/src/program.py @@ -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() diff --git a/src/snake_bnb/src/program_guests.py b/src/snake_bnb/src/program_guests.py new file mode 100755 index 0000000..7548c86 --- /dev/null +++ b/src/snake_bnb/src/program_guests.py @@ -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 + )) diff --git a/src/snake_bnb/src/program_hosts.py b/src/snake_bnb/src/program_hosts.py new file mode 100755 index 0000000..cabf764 --- /dev/null +++ b/src/snake_bnb/src/program_hosts.py @@ -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) diff --git a/src/snake_bnb/src/services/data_service.py b/src/snake_bnb/src/services/data_service.py new file mode 100755 index 0000000..e745fd1 --- /dev/null +++ b/src/snake_bnb/src/services/data_service.py @@ -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 diff --git a/src/snake_bnb/src/services/readme.md b/src/snake_bnb/src/services/readme.md new file mode 100755 index 0000000..a7f1060 --- /dev/null +++ b/src/snake_bnb/src/services/readme.md @@ -0,0 +1 @@ +We'll put our data access code and logic here. \ No newline at end of file diff --git a/src/starter_code_snake_bnb/requirements.txt b/src/starter_code_snake_bnb/requirements.txt new file mode 100755 index 0000000..11d3b39 --- /dev/null +++ b/src/starter_code_snake_bnb/requirements.txt @@ -0,0 +1,5 @@ +pymongo +mongoengine +tqdm +colorama +python-dateutil diff --git a/src/starter_code_snake_bnb/src/data/readme.md b/src/starter_code_snake_bnb/src/data/readme.md new file mode 100755 index 0000000..9a566d3 --- /dev/null +++ b/src/starter_code_snake_bnb/src/data/readme.md @@ -0,0 +1 @@ +We'll put some data things in this folder when we start the course. \ No newline at end of file diff --git a/src/starter_code_snake_bnb/src/infrastructure/state.py b/src/starter_code_snake_bnb/src/infrastructure/state.py new file mode 100755 index 0000000..26a4b30 --- /dev/null +++ b/src/starter_code_snake_bnb/src/infrastructure/state.py @@ -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 diff --git a/src/starter_code_snake_bnb/src/infrastructure/switchlang.py b/src/starter_code_snake_bnb/src/infrastructure/switchlang.py new file mode 100755 index 0000000..bda6ea9 --- /dev/null +++ b/src/starter_code_snake_bnb/src/infrastructure/switchlang.py @@ -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) \ No newline at end of file diff --git a/src/starter_code_snake_bnb/src/program.py b/src/starter_code_snake_bnb/src/program.py new file mode 100755 index 0000000..4eeb2a0 --- /dev/null +++ b/src/starter_code_snake_bnb/src/program.py @@ -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() diff --git a/src/starter_code_snake_bnb/src/program_guests.py b/src/starter_code_snake_bnb/src/program_guests.py new file mode 100755 index 0000000..ee8f953 --- /dev/null +++ b/src/starter_code_snake_bnb/src/program_guests.py @@ -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 -------- ") diff --git a/src/starter_code_snake_bnb/src/program_hosts.py b/src/starter_code_snake_bnb/src/program_hosts.py new file mode 100755 index 0000000..bd7e34a --- /dev/null +++ b/src/starter_code_snake_bnb/src/program_hosts.py @@ -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) diff --git a/src/starter_code_snake_bnb/src/services/readme.md b/src/starter_code_snake_bnb/src/services/readme.md new file mode 100755 index 0000000..a7f1060 --- /dev/null +++ b/src/starter_code_snake_bnb/src/services/readme.md @@ -0,0 +1 @@ +We'll put our data access code and logic here. \ No newline at end of file diff --git a/video_transcripts/1-welcome/1-welcome_transcript_final.txt b/video_transcripts/1-welcome/1-welcome_transcript_final.txt new file mode 100755 index 0000000..260657e --- /dev/null +++ b/video_transcripts/1-welcome/1-welcome_transcript_final.txt @@ -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. diff --git a/video_transcripts/2-why-nosql-and-mongodb/1-intro-to-mongodb_transcript_final.txt b/video_transcripts/2-why-nosql-and-mongodb/1-intro-to-mongodb_transcript_final.txt new file mode 100755 index 0000000..269246d --- /dev/null +++ b/video_transcripts/2-why-nosql-and-mongodb/1-intro-to-mongodb_transcript_final.txt @@ -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. diff --git a/video_transcripts/2-why-nosql-and-mongodb/2-how-doc-dbs-work_transcript_final.txt b/video_transcripts/2-why-nosql-and-mongodb/2-how-doc-dbs-work_transcript_final.txt new file mode 100755 index 0000000..8ea79b3 --- /dev/null +++ b/video_transcripts/2-why-nosql-and-mongodb/2-how-doc-dbs-work_transcript_final.txt @@ -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. diff --git a/video_transcripts/2-why-nosql-and-mongodb/3-who-uses-mongodb_transcript_final.txt b/video_transcripts/2-why-nosql-and-mongodb/3-who-uses-mongodb_transcript_final.txt new file mode 100755 index 0000000..0201939 --- /dev/null +++ b/video_transcripts/2-why-nosql-and-mongodb/3-who-uses-mongodb_transcript_final.txt @@ -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. diff --git a/video_transcripts/3-modeling-documents/1-relational-modeling-vs-doc-modeling_transcript_final.txt b/video_transcripts/3-modeling-documents/1-relational-modeling-vs-doc-modeling_transcript_final.txt new file mode 100755 index 0000000..4ca275b --- /dev/null +++ b/video_transcripts/3-modeling-documents/1-relational-modeling-vs-doc-modeling_transcript_final.txt @@ -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. diff --git a/video_transcripts/3-modeling-documents/2-modeling-guidelines_transcript_final.txt b/video_transcripts/3-modeling-documents/2-modeling-guidelines_transcript_final.txt new file mode 100755 index 0000000..e3afba9 --- /dev/null +++ b/video_transcripts/3-modeling-documents/2-modeling-guidelines_transcript_final.txt @@ -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. diff --git a/video_transcripts/3-modeling-documents/3-integration-vs-app-dbs_transcript_final.txt b/video_transcripts/3-modeling-documents/3-integration-vs-app-dbs_transcript_final.txt new file mode 100755 index 0000000..6b079c8 --- /dev/null +++ b/video_transcripts/3-modeling-documents/3-integration-vs-app-dbs_transcript_final.txt @@ -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. diff --git a/video_transcripts/3-modeling-documents/4-getting-demo-starter-code_transcript_final.txt b/video_transcripts/3-modeling-documents/4-getting-demo-starter-code_transcript_final.txt new file mode 100755 index 0000000..b054736 --- /dev/null +++ b/video_transcripts/3-modeling-documents/4-getting-demo-starter-code_transcript_final.txt @@ -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. diff --git a/video_transcripts/4-mongoengine/1-how-odms-work_transcript_final.txt b/video_transcripts/4-mongoengine/1-how-odms-work_transcript_final.txt new file mode 100755 index 0000000..e73e36d --- /dev/null +++ b/video_transcripts/4-mongoengine/1-how-odms-work_transcript_final.txt @@ -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. \ No newline at end of file diff --git a/video_transcripts/4-mongoengine/2-intro-to-mongoengine_transcript_final.txt b/video_transcripts/4-mongoengine/2-intro-to-mongoengine_transcript_final.txt new file mode 100755 index 0000000..7fcb4a5 --- /dev/null +++ b/video_transcripts/4-mongoengine/2-intro-to-mongoengine_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/10-demo-register-cage_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/10-demo-register-cage_transcript_final.txt new file mode 100755 index 0000000..0e9bda3 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/10-demo-register-cage_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/11-demo-add-a-bookable-time_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/11-demo-add-a-bookable-time_transcript_final.txt new file mode 100755 index 0000000..f2b50c9 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/11-demo-add-a-bookable-time_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/12-demo-managing-snakes_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/12-demo-managing-snakes_transcript_final.txt new file mode 100755 index 0000000..00fc352 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/12-demo-managing-snakes_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/13-demo-book-a-cage_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/13-demo-book-a-cage_transcript_final.txt new file mode 100755 index 0000000..8659090 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/13-demo-book-a-cage_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/14-demo-view-bookings-as-guest_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/14-demo-view-bookings-as-guest_transcript_final.txt new file mode 100755 index 0000000..9ab0ca8 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/14-demo-view-bookings-as-guest_transcript_final.txt @@ -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 diff --git a/video_transcripts/5-building-with-mongoengine/15-demo-view-bookings-as-host_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/15-demo-view-bookings-as-host_transcript_final.txt new file mode 100755 index 0000000..50d223c --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/15-demo-view-bookings-as-host_transcript_final.txt @@ -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 diff --git a/video_transcripts/5-building-with-mongoengine/16-concept-inserting_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/16-concept-inserting_transcript_final.txt new file mode 100755 index 0000000..7ac4cda --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/16-concept-inserting_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/17-concept-queries_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/17-concept-queries_transcript_final.txt new file mode 100755 index 0000000..ddbb5c5 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/17-concept-queries_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/18-concept-querying-subdocuments_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/18-concept-querying-subdocuments_transcript_final.txt new file mode 100755 index 0000000..50c12f0 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/18-concept-querying-subdocuments_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/19-concept-querying-operators_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/19-concept-querying-operators_transcript_final.txt new file mode 100755 index 0000000..c8cb6a7 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/19-concept-querying-operators_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/20-concept-updating-via-docs_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/20-concept-updating-via-docs_transcript_final.txt new file mode 100755 index 0000000..03af5df --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/20-concept-updating-via-docs_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/21-concept-updating-via-operators_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/21-concept-updating-via-operators_transcript_final.txt new file mode 100755 index 0000000..399ace6 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/21-concept-updating-via-operators_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/3-register-connections_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/3-register-connections_transcript_final.txt new file mode 100755 index 0000000..35797b8 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/3-register-connections_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/4-concept-register-connections_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/4-concept-register-connections_transcript_final.txt new file mode 100755 index 0000000..c6c3a0d --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/4-concept-register-connections_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/5-mongoengine-entities_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/5-mongoengine-entities_transcript_final.txt new file mode 100755 index 0000000..d7376cc --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/5-mongoengine-entities_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/6-concept-mongoengine-entities_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/6-concept-mongoengine-entities_transcript_final.txt new file mode 100755 index 0000000..9862293 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/6-concept-mongoengine-entities_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/7-demo-create-account_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/7-demo-create-account_transcript_final.txt new file mode 100755 index 0000000..6ca02f8 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/7-demo-create-account_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/8-demo-robo-3t_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/8-demo-robo-3t_transcript_final.txt new file mode 100755 index 0000000..501b492 --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/8-demo-robo-3t_transcript_final.txt @@ -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. diff --git a/video_transcripts/5-building-with-mongoengine/9-demo-login_transcript_final.txt b/video_transcripts/5-building-with-mongoengine/9-demo-login_transcript_final.txt new file mode 100755 index 0000000..8ae764b --- /dev/null +++ b/video_transcripts/5-building-with-mongoengine/9-demo-login_transcript_final.txt @@ -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. diff --git a/video_transcripts/6-conclusion/1-youve-done-it_transcript_final.txt b/video_transcripts/6-conclusion/1-youve-done-it_transcript_final.txt new file mode 100755 index 0000000..9ad2ec5 --- /dev/null +++ b/video_transcripts/6-conclusion/1-youve-done-it_transcript_final.txt @@ -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. \ No newline at end of file diff --git a/video_transcripts/6-conclusion/2-get-the-code_transcript_final.txt b/video_transcripts/6-conclusion/2-get-the-code_transcript_final.txt new file mode 100755 index 0000000..1bc64d1 --- /dev/null +++ b/video_transcripts/6-conclusion/2-get-the-code_transcript_final.txt @@ -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. diff --git a/video_transcripts/6-conclusion/3-full-course_transcript_final.txt b/video_transcripts/6-conclusion/3-full-course_transcript_final.txt new file mode 100755 index 0000000..58e8e34 --- /dev/null +++ b/video_transcripts/6-conclusion/3-full-course_transcript_final.txt @@ -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.