diff --git a/0-setup_web_static.sh b/0-setup_web_static.sh new file mode 100644 index 000000000000..e7040b00bdda --- /dev/null +++ b/0-setup_web_static.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# Shell script for configuring web servers to deploy web_static content + +sudo apt-get update +sudo apt-get -y install nginx +sudo ufw allow 'Nginx HTTP' + +sudo mkdir -p /data/ +sudo mkdir -p /data/web_static/ +sudo mkdir -p /data/web_static/releases/ +sudo mkdir -p /data/web_static/shared/ +sudo mkdir -p /data/web_static/releases/test/ +sudo touch /data/web_static/releases/test/index.html +sudo echo " + + + + Holberton School + +" | sudo tee /data/web_static/releases/test/index.html + +sudo ln -s -f /data/web_static/releases/test/ /data/web_static/current + +sudo chown -R ubuntu:ubuntu /data/ + +sudo sed -i '/listen 80 default_server/a location /hbnb_static { alias /data/web_static/current/;}' /etc/nginx/sites-enabled/default + +sudo service nginx restart diff --git a/1-pack_web_static.py b/1-pack_web_static.py new file mode 100644 index 000000000000..f00f28548be2 --- /dev/null +++ b/1-pack_web_static.py @@ -0,0 +1,19 @@ +#!/usr/bin/python3 +from fabric.api import local +from time import strftime +from datetime import date + + +def do_pack(): + """Script to create an archive of the contents in the web_static folder""" + + filename = strftime("%Y%m%d%H%M%S") + try: + local("mkdir -p versions") + local("tar -czvf versions/web_static_{}.tgz web_static/" + .format(filename)) + + return "versions/web_static_{}.tgz".format(filename) + + except Exception as e: + return None diff --git a/100-clean_web_static.py b/100-clean_web_static.py new file mode 100644 index 000000000000..f1338d49ecd8 --- /dev/null +++ b/100-clean_web_static.py @@ -0,0 +1,62 @@ +#!/usr/bin/python3 +import os.path +from datetime import datetime +from fabric.api import env, local, put, run + +env.hosts = ['100.25.19.204', '54.157.159.85'] + +def do_pack(): + """Create a tar gzipped archive of the web_static directory.""" + dt = datetime.utcnow() + file = "versions/web_static_{}{}{}{}{}{}.tgz".format(dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second) + if not os.path.isdir("versions"): + if local("mkdir -p versions").failed: + return None + if local("tar -cvzf {} web_static".format(file)).failed: + return None + return file + +def do_deploy(archive_path): + """Deploy an archive to the web server. + + Args: + archive_path (str): Path to the archive file to deploy. + Returns: + bool: False if the file doesn't exist or an error occurs, True otherwise. + """ + if not os.path.isfile(archive_path): + return False + file = os.path.basename(archive_path) + name = file.split(".")[0] + + if put(archive_path, "/tmp/{}".format(file)).failed: + return False + if run("rm -rf /data/web_static/releases/{}/".format(name)).failed: + return False + if run("mkdir -p /data/web_static/releases/{}/".format(name)).failed: + return False + if run("tar -xzf /tmp/{} -C /data/web_static/releases/{}/".format(file, name)).failed: + return False + if run("rm /tmp/{}".format(file)).failed: + return False + if run("mv /data/web_static/releases/{}/web_static/* /data/web_static/releases/{}/".format(name, name)).failed: + return False + if run("rm -rf /data/web_static/releases/{}/web_static".format(name)).failed: + return False + if run("rm -rf /data/web_static/current").failed: + return False + if run("ln -s /data/web_static/releases/{}/ /data/web_static/current".format(name)).failed: + return False + return True + +def deploy(): + """Create and deploy an archive to the web server.""" + file = do_pack() + if file is None: + return False + return do_deploy(file) diff --git a/101-setup_web_static.pp b/101-setup_web_static.pp new file mode 100644 index 000000000000..c7a89dc06866 --- /dev/null +++ b/101-setup_web_static.pp @@ -0,0 +1,31 @@ +#!/usr/bin/python3 +import os +from fabric.api import * + +env.hosts = ['100.25.19.204', '54.157.159.85'] + +def do_clean(number=0): + """Remove outdated archives. + + Args: + number (int): The number of archives to retain. + + If number is 0 or 1, keeps only the most recent archive. If + number is 2, keeps the two most recent archives, and so on. + """ + number = 1 if int(number) == 0 else int(number) + + # List and sort archives in the versions directory + archives = sorted(os.listdir("versions")) + [archives.pop() for i in range(number)] + + # Remove older archives locally + with lcd("versions"): + [local("rm ./{}".format(a)) for a in archives] + + # Remove older archives on the remote server + with cd("/data/web_static/releases"): + archives = run("ls -tr").split() + archives = [a for a in archives if "web_static_" in a] + [archives.pop() for i in range(number)] + [run("rm -rf ./{}".format(a)) for a in archives] diff --git a/2-do_deploy_web_static.py b/2-do_deploy_web_static.py new file mode 100644 index 000000000000..2d7e45aa729c --- /dev/null +++ b/2-do_deploy_web_static.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 +"""Script to compress the web static package +""" +from fabric.api import * +from datetime import datetime +from os import path + +# Define the hosts, user, and key for connecting to the servers +env.hosts = ['100.25.19.204', '54.157.159.85'] +env.user = 'ubuntu' +env.key_filename = '~/.ssh/id_rsa' + +def do_deploy(archive_path): + """Deploy web files to the server + """ + try: + if not path.exists(archive_path): + return False + + # Upload the archive to the server + put(archive_path, '/tmp/') + + # Create the target directory + timestamp = archive_path[-18:-4] + run('sudo mkdir -p /data/web_static/releases/web_static_{}/'.format(timestamp)) + + # Uncompress the archive and delete the .tgz file + run('sudo tar -xzf /tmp/web_static_{}.tgz -C /data/web_static/releases/web_static_{}/'.format(timestamp, timestamp)) + + # Remove the archive from the server + run('sudo rm /tmp/web_static_{}.tgz'.format(timestamp)) + + # Move the contents to the web_static directory on the server + run('sudo mv /data/web_static/releases/web_static_{}/web_static/* /data/web_static/releases/web_static_{}/'.format(timestamp, timestamp)) + + # Remove the extra web_static directory + run('sudo rm -rf /data/web_static/releases/web_static_{}/web_static'.format(timestamp)) + + # Delete the existing symbolic link + run('sudo rm -rf /data/web_static/current') + + # Create a new symbolic link + run('sudo ln -s /data/web_static/releases/web_static_{}/ /data/web_static/current'.format(timestamp)) + except: + return False + + # Return True if the deployment is successful + return True diff --git a/3-deploy_web_static.py b/3-deploy_web_static.py new file mode 100644 index 000000000000..f1338d49ecd8 --- /dev/null +++ b/3-deploy_web_static.py @@ -0,0 +1,62 @@ +#!/usr/bin/python3 +import os.path +from datetime import datetime +from fabric.api import env, local, put, run + +env.hosts = ['100.25.19.204', '54.157.159.85'] + +def do_pack(): + """Create a tar gzipped archive of the web_static directory.""" + dt = datetime.utcnow() + file = "versions/web_static_{}{}{}{}{}{}.tgz".format(dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second) + if not os.path.isdir("versions"): + if local("mkdir -p versions").failed: + return None + if local("tar -cvzf {} web_static".format(file)).failed: + return None + return file + +def do_deploy(archive_path): + """Deploy an archive to the web server. + + Args: + archive_path (str): Path to the archive file to deploy. + Returns: + bool: False if the file doesn't exist or an error occurs, True otherwise. + """ + if not os.path.isfile(archive_path): + return False + file = os.path.basename(archive_path) + name = file.split(".")[0] + + if put(archive_path, "/tmp/{}".format(file)).failed: + return False + if run("rm -rf /data/web_static/releases/{}/".format(name)).failed: + return False + if run("mkdir -p /data/web_static/releases/{}/".format(name)).failed: + return False + if run("tar -xzf /tmp/{} -C /data/web_static/releases/{}/".format(file, name)).failed: + return False + if run("rm /tmp/{}".format(file)).failed: + return False + if run("mv /data/web_static/releases/{}/web_static/* /data/web_static/releases/{}/".format(name, name)).failed: + return False + if run("rm -rf /data/web_static/releases/{}/web_static".format(name)).failed: + return False + if run("rm -rf /data/web_static/current").failed: + return False + if run("ln -s /data/web_static/releases/{}/ /data/web_static/current".format(name)).failed: + return False + return True + +def deploy(): + """Create and deploy an archive to the web server.""" + file = do_pack() + if file is None: + return False + return do_deploy(file) diff --git a/AUTHORS b/AUTHORS index 6fb53b5277cf..3d39f2b5bbd8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -2,3 +2,4 @@ Ezra Nobrega Justin Majetich +Derick Mookua diff --git a/console.py b/console.py index 13a8af68e930..6c4b5606f057 100755 --- a/console.py +++ b/console.py @@ -1,324 +1,272 @@ #!/usr/bin/python3 -""" Console Module """ +"""Defines the HBNB console.""" import cmd -import sys +from shlex import split +from models import storage +from datetime import datetime from models.base_model import BaseModel -from models.__init__ import storage from models.user import User -from models.place import Place from models.state import State from models.city import City from models.amenity import Amenity +from models.place import Place from models.review import Review class HBNBCommand(cmd.Cmd): - """ Contains the functionality for the HBNB console""" - - # determines prompt for interactive/non-interactive modes - prompt = '(hbnb) ' if sys.__stdin__.isatty() else '' + """Defines the HolbertonBnB command interpreter.""" + + prompt = "(hbnb) " + __classes = { + "BaseModel", + "User", + "State", + "City", + "Amenity", + "Place", + "Review" + } - classes = { - 'BaseModel': BaseModel, 'User': User, 'Place': Place, - 'State': State, 'City': City, 'Amenity': Amenity, - 'Review': Review - } - dot_cmds = ['all', 'count', 'show', 'destroy', 'update'] - types = { - 'number_rooms': int, 'number_bathrooms': int, - 'max_guest': int, 'price_by_night': int, - 'latitude': float, 'longitude': float - } + def emptyline(self): + """Ignore empty spaces.""" + pass - def preloop(self): - """Prints if isatty is false""" - if not sys.__stdin__.isatty(): - print('(hbnb)') + def do_quit(self, line): + """Quit command to exit the program.""" + return True - def precmd(self, line): - """Reformat command line for advanced command syntax. + def do_EOF(self, line): + """EOF signal to exit the program.""" + print("") + return True - Usage: .([ [<*args> or <**kwargs>]]) - (Brackets denote optional fields in usage example.) + def do_create(self, line): + """Usage: create = = ... + Create a new class instance with given keys/values and print its id. """ - _cmd = _cls = _id = _args = '' # initialize line elements - - # scan for general formating - i.e '.', '(', ')' - if not ('.' in line and '(' in line and ')' in line): - return line - - try: # parse line left to right - pline = line[:] # parsed line - - # isolate - _cls = pline[:pline.find('.')] - - # isolate and validate - _cmd = pline[pline.find('.') + 1:pline.find('(')] - if _cmd not in HBNBCommand.dot_cmds: - raise Exception - - # if parantheses contain arguments, parse them - pline = pline[pline.find('(') + 1:pline.find(')')] - if pline: - # partition args: (, [], [<*args>]) - pline = pline.partition(', ') # pline convert to tuple - - # isolate _id, stripping quotes - _id = pline[0].replace('\"', '') - # possible bug here: - # empty quotes register as empty _id when replaced - - # if arguments exist beyond _id - pline = pline[2].strip() # pline is now str - if pline: - # check for *args or **kwargs - if pline[0] is '{' and pline[-1] is'}'\ - and type(eval(pline)) is dict: - _args = pline - else: - _args = pline.replace(',', '') - # _args = _args.replace('\"', '') - line = ' '.join([_cmd, _cls, _id, _args]) - - except Exception as mess: - pass - finally: - return line - - def postcmd(self, stop, line): - """Prints if isatty is false""" - if not sys.__stdin__.isatty(): - print('(hbnb) ', end='') - return stop - - def do_quit(self, command): - """ Method to exit the HBNB console""" - exit() - - def help_quit(self): - """ Prints the help documentation for quit """ - print("Exits the program with formatting\n") - - def do_EOF(self, arg): - """ Handles EOF to exit program """ - print() - exit() - - def help_EOF(self): - """ Prints the help documentation for EOF """ - print("Exits the program without formatting\n") - - def emptyline(self): - """ Overrides the emptyline method of CMD """ - pass - - def do_create(self, args): - """ Create an object of any class""" - if not args: + try: + if not line: + raise SyntaxError() + my_list = line.split(" ") + + kwargs = {} + for i in range(1, len(my_list)): + key, value = tuple(my_list[i].split("=")) + if value[0] == '"': + value = value.strip('"').replace("_", " ") + else: + try: + value = eval(value) + except (SyntaxError, NameError): + continue + kwargs[key] = value + + if kwargs == {}: + obj = eval(my_list[0])() + else: + obj = eval(my_list[0])(**kwargs) + storage.new(obj) + print(obj.id) + obj.save() + + except SyntaxError: print("** class name missing **") - return - elif args not in HBNBCommand.classes: + except NameError: print("** class doesn't exist **") - return - new_instance = HBNBCommand.classes[args]() - storage.save() - print(new_instance.id) - storage.save() - - def help_create(self): - """ Help information for the create method """ - print("Creates a class of any type") - print("[Usage]: create \n") - def do_show(self, args): - """ Method to show an individual object """ - new = args.partition(" ") - c_name = new[0] - c_id = new[2] - - # guard against trailing args - if c_id and ' ' in c_id: - c_id = c_id.partition(' ')[0] - - if not c_name: + def do_show(self, line): + """Prints the string representation of an instance + Exceptions: + SyntaxError: when there is no args given + NameError: when there is no object taht has the name + IndexError: when there is no id given + KeyError: when there is no valid id given + """ + try: + if not line: + raise SyntaxError() + my_list = line.split(" ") + if my_list[0] not in self.__classes: + raise NameError() + if len(my_list) < 2: + raise IndexError() + objects = storage.all() + key = my_list[0] + '.' + my_list[1] + if key in objects: + print(objects[key]) + else: + raise KeyError() + except SyntaxError: print("** class name missing **") - return - - if c_name not in HBNBCommand.classes: + except NameError: print("** class doesn't exist **") - return - - if not c_id: + except IndexError: print("** instance id missing **") - return - - key = c_name + "." + c_id - try: - print(storage._FileStorage__objects[key]) except KeyError: print("** no instance found **") - def help_show(self): - """ Help information for the show command """ - print("Shows an individual instance of a class") - print("[Usage]: show \n") - - def do_destroy(self, args): - """ Destroys a specified object """ - new = args.partition(" ") - c_name = new[0] - c_id = new[2] - if c_id and ' ' in c_id: - c_id = c_id.partition(' ')[0] - - if not c_name: + def do_destroy(self, line): + """Deletes an instance based on the class name and id + Exceptions: + SyntaxError: when there is no args given + NameError: when there is no object taht has the name + IndexError: when there is no id given + KeyError: when there is no valid id given + """ + try: + if not line: + raise SyntaxError() + my_list = line.split(" ") + if my_list[0] not in self.__classes: + raise NameError() + if len(my_list) < 2: + raise IndexError() + objects = storage.all() + key = my_list[0] + '.' + my_list[1] + if key in objects: + del objects[key] + storage.save() + else: + raise KeyError() + except SyntaxError: print("** class name missing **") - return - - if c_name not in HBNBCommand.classes: + except NameError: print("** class doesn't exist **") - return - - if not c_id: + except IndexError: print("** instance id missing **") - return - - key = c_name + "." + c_id - - try: - del(storage.all()[key]) - storage.save() except KeyError: print("** no instance found **") - def help_destroy(self): - """ Help information for the destroy command """ - print("Destroys an individual instance of a class") - print("[Usage]: destroy \n") - - def do_all(self, args): - """ Shows all objects, or all objects of a class""" - print_list = [] - - if args: - args = args.split(' ')[0] # remove possible trailing args - if args not in HBNBCommand.classes: - print("** class doesn't exist **") - return - for k, v in storage._FileStorage__objects.items(): - if k.split('.')[0] == args: - print_list.append(str(v)) - else: - for k, v in storage._FileStorage__objects.items(): - print_list.append(str(v)) - - print(print_list) - - def help_all(self): - """ Help information for the all command """ - print("Shows all objects, or all of a class") - print("[Usage]: all \n") - - def do_count(self, args): - """Count current number of class instances""" - count = 0 - for k, v in storage._FileStorage__objects.items(): - if args == k.split('.')[0]: - count += 1 - print(count) + def do_all(self, line): + """Usage: all or all or .all() + Display string representations of all instances of a given class. + If no class is specified, displays all instantiated objects.""" + if not line: + o = storage.all() + print([o[k].__str__() for k in o]) + return + try: + args = line.split(" ") + if args[0] not in self.__classes: + raise NameError() - def help_count(self): - """ """ - print("Usage: count ") + o = storage.all(eval(args[0])) + print([o[k].__str__() for k in o]) - def do_update(self, args): - """ Updates a certain object with new info """ - c_name = c_id = att_name = att_val = kwargs = '' + except NameError: + print("** class doesn't exist **") - # isolate cls from id/args, ex: (, delim, ) - args = args.partition(" ") - if args[0]: - c_name = args[0] - else: # class name not present + def do_update(self, line): + """Updates an instanceby adding or updating attribute + Exceptions: + SyntaxError: when there is no args given + NameError: when there is no object taht has the name + IndexError: when there is no id given + KeyError: when there is no valid id given + AttributeError: when there is no attribute given + ValueError: when there is no value given + """ + try: + if not line: + raise SyntaxError() + my_list = split(line, " ") + if my_list[0] not in self.__classes: + raise NameError() + if len(my_list) < 2: + raise IndexError() + objects = storage.all() + key = my_list[0] + '.' + my_list[1] + if key not in objects: + raise KeyError() + if len(my_list) < 3: + raise AttributeError() + if len(my_list) < 4: + raise ValueError() + v = objects[key] + try: + v.__dict__[my_list[2]] = eval(my_list[3]) + except Exception: + v.__dict__[my_list[2]] = my_list[3] + v.save() + except SyntaxError: print("** class name missing **") - return - if c_name not in HBNBCommand.classes: # class name invalid + except NameError: print("** class doesn't exist **") - return - - # isolate id from args - args = args[2].partition(" ") - if args[0]: - c_id = args[0] - else: # id not present + except IndexError: print("** instance id missing **") - return - - # generate key from class and id - key = c_name + "." + c_id - - # determine if key is present - if key not in storage.all(): + except KeyError: print("** no instance found **") - return - - # first determine if kwargs or args - if '{' in args[2] and '}' in args[2] and type(eval(args[2])) is dict: - kwargs = eval(args[2]) - args = [] # reformat kwargs into list, ex: [, , ...] - for k, v in kwargs.items(): - args.append(k) - args.append(v) - else: # isolate args - args = args[2] - if args and args[0] is '\"': # check for quoted arg - second_quote = args.find('\"', 1) - att_name = args[1:second_quote] - args = args[second_quote + 1:] - - args = args.partition(' ') - - # if att_name was not quoted arg - if not att_name and args[0] is not ' ': - att_name = args[0] - # check for quoted val arg - if args[2] and args[2][0] is '\"': - att_val = args[2][1:args[2].find('\"', 1)] - - # if att_val was not quoted arg - if not att_val and args[2]: - att_val = args[2].partition(' ')[0] + except AttributeError: + print("** attribute name missing **") + except ValueError: + print("** value missing **") - args = [att_name, att_val] - - # retrieve dictionary of current objects - new_dict = storage.all()[key] - - # iterate through attr names and values - for i, att_name in enumerate(args): - # block only runs on even iterations - if (i % 2 == 0): - att_val = args[i + 1] # following item is value - if not att_name: # check for att_name - print("** attribute name missing **") - return - if not att_val: # check for att_value - print("** value missing **") - return - # type cast as necessary - if att_name in HBNBCommand.types: - att_val = HBNBCommand.types[att_name](att_val) - - # update dictionary with name, value pair - new_dict.__dict__.update({att_name: att_val}) + def count(self, line): + """count the number of instances of a class + """ + counter = 0 + try: + my_list = split(line, " ") + if my_list[0] not in self.__classes: + raise NameError() + objects = storage.all() + for key in objects: + name = key.split('.') + if name[0] == my_list[0]: + counter += 1 + print(counter) + except NameError: + print("** class doesn't exist **") - new_dict.save() # save updates to file + def strip_clean(self, args): + """strips the argument and return a string of command + Args: + args: input list of args + Return: + returns string of argumetns + """ + new_list = [] + new_list.append(args[0]) + try: + my_dict = eval( + args[1][args[1].find('{'):args[1].find('}')+1]) + except Exception: + my_dict = None + if isinstance(my_dict, dict): + new_str = args[1][args[1].find('(')+1:args[1].find(')')] + new_list.append(((new_str.split(", "))[0]).strip('"')) + new_list.append(my_dict) + return new_list + new_str = args[1][args[1].find('(')+1:args[1].find(')')] + new_list.append(" ".join(new_str.split(", "))) + return " ".join(i for i in new_list) + + def default(self, line): + """retrieve all instances of a class and + retrieve the number of instances + """ + my_list = line.split('.') + if len(my_list) >= 2: + if my_list[1] == "all()": + self.do_all(my_list[0]) + elif my_list[1] == "count()": + self.count(my_list[0]) + elif my_list[1][:4] == "show": + self.do_show(self.strip_clean(my_list)) + elif my_list[1][:7] == "destroy": + self.do_destroy(self.strip_clean(my_list)) + elif my_list[1][:6] == "update": + args = self.strip_clean(my_list) + if isinstance(args, list): + obj = storage.all() + key = args[0] + ' ' + args[1] + for k, v in args[2].items(): + self.do_update(key + ' "{}" "{}"'.format(k, v)) + else: + self.do_update(args) + else: + cmd.Cmd.default(self, line) - def help_update(self): - """ Help information for the update class """ - print("Updates an object with new information") - print("Usage: update \n") -if __name__ == "__main__": +if __name__ == '__main__': HBNBCommand().cmdloop() diff --git a/models/engine/db_storage.py b/models/engine/db_storage.py new file mode 100644 index 000000000000..d616a39c9714 --- /dev/null +++ b/models/engine/db_storage.py @@ -0,0 +1,103 @@ +#!/usr/bin/python3 +"""DB storage engine using SQLAlchemy with a mysql+mysqldb database +connection. +""" + +import os +from models.base_model import Base +from models.amenity import Amenity +from models.city import City +from models.place import Place +from models.state import State +from models.review import Review +from models.user import User +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, scoped_session +name2class = { + 'Amenity': Amenity, + 'City': City, + 'Place': Place, + 'State': State, + 'Review': Review, + 'User': User +} + + +class DBStorage: + """Database Storage""" + __engine = None + __session = None + + def __init__(self): + """Initializes the object""" + user = os.getenv('HBNB_MYSQL_USER') + passwd = os.getenv('HBNB_MYSQL_PWD') + host = os.getenv('HBNB_MYSQL_HOST') + database = os.getenv('HBNB_MYSQL_DB') + self.__engine = create_engine('mysql+mysqldb://{}:{}@{}/{}' + .format(user, passwd, host, database)) + if os.getenv('HBNB_ENV') == 'test': + Base.metadata.drop_all(self.__engine) + + def all(self, cls=None): + """returns a dictionary of all the objects present""" + if not self.__session: + self.reload() + objects = {} + if type(cls) == str: + cls = name2class.get(cls, None) + if cls: + for obj in self.__session.query(cls): + objects[obj.__class__.__name__ + '.' + obj.id] = obj + else: + for cls in name2class.values(): + for obj in self.__session.query(cls): + objects[obj.__class__.__name__ + '.' + obj.id] = obj + return objects + + def reload(self): + """reloads objects from the database""" + session_factory = sessionmaker(bind=self.__engine, + expire_on_commit=False) + Base.metadata.create_all(self.__engine) + self.__session = scoped_session(session_factory) + + def new(self, obj): + """creates a new object""" + self.__session.add(obj) + + def save(self): + """saves the current session""" + self.__session.commit() + + def delete(self, obj=None): + """deletes an object""" + if not self.__session: + self.reload() + if obj: + self.__session.delete(obj) + + def close(self): + """Dispose of current session if active""" + self.__session.remove() + + def get(self, cls, id): + """Retrieve an object""" + if cls is not None and type(cls) is str and id is not None and\ + type(id) is str and cls in name2class: + cls = name2class[cls] + result = self.__session.query(cls).filter(cls.id == id).first() + return result + else: + return None + + def count(self, cls=None): + """Count number of objects in storage""" + total = 0 + if type(cls) == str and cls in name2class: + cls = name2class[cls] + total = self.__session.query(cls).count() + elif cls is None: + for cls in name2class.values(): + total += self.__session.query(cls).count() + return total diff --git a/models/engine/file_storage.py b/models/engine/file_storage.py index 6f5d7f8d4680..2e2c00543b28 100644 --- a/models/engine/file_storage.py +++ b/models/engine/file_storage.py @@ -1,50 +1,87 @@ #!/usr/bin/python3 -"""This module defines a class to manage file storage for hbnb clone""" +""" +Has the FileStorage class +""" + import json +from models.amenity import Amenity +from models.base_model import BaseModel +from models.city import City +from models.place import Place +from models.review import Review +from models.state import State +from models.user import User + +classes = {"Amenity": Amenity, "BaseModel": BaseModel, "City": City, + "Place": Place, "Review": Review, "State": State, "User": User} class FileStorage: - """This class manages storage of hbnb models in JSON format""" - __file_path = 'file.json' + """serializes instances to a JSON file & deserializes back to instances""" + + __file_path = "file.json" __objects = {} - def all(self): - """Returns a dictionary of models currently in storage""" - return FileStorage.__objects + def all(self, cls=None): + """returns the dictionary __objects""" + if not cls: + return self.__objects + elif type(cls) == str: + return {k: v for k, v in self.__objects.items() + if v.__class__.__name__ == cls} + else: + return {k: v for k, v in self.__objects.items() + if v.__class__ == cls} def new(self, obj): - """Adds new object to storage dictionary""" - self.all().update({obj.to_dict()['__class__'] + '.' + obj.id: obj}) + """sets in __objects the obj with key .id""" + if obj is not None: + key = obj.__class__.__name__ + "." + obj.id + self.__objects[key] = obj def save(self): - """Saves storage dictionary to file""" - with open(FileStorage.__file_path, 'w') as f: - temp = {} - temp.update(FileStorage.__objects) - for key, val in temp.items(): - temp[key] = val.to_dict() - json.dump(temp, f) + """serializes __objects to the JSON file (path: __file_path)""" + json_objects = {} + for key in self.__objects: + json_objects[key] = self.__objects[key].to_dict(save_to_disk=True) + with open(self.__file_path, 'w') as f: + json.dump(json_objects, f) def reload(self): - """Loads storage dictionary from file""" - from models.base_model import BaseModel - from models.user import User - from models.place import Place - from models.state import State - from models.city import City - from models.amenity import Amenity - from models.review import Review - - classes = { - 'BaseModel': BaseModel, 'User': User, 'Place': Place, - 'State': State, 'City': City, 'Amenity': Amenity, - 'Review': Review - } + """deserializes the JSON file to __objects""" try: - temp = {} - with open(FileStorage.__file_path, 'r') as f: - temp = json.load(f) - for key, val in temp.items(): - self.all()[key] = classes[val['__class__']](**val) - except FileNotFoundError: + with open(self.__file_path, 'r') as f: + jo = json.load(f) + for key in jo: + self.__objects[key] = classes[jo[key]["__class__"]](**jo[key]) + except: pass + + def delete(self, obj=None): + """delete obj from __objects if it’s inside""" + if obj is not None: + del self.__objects[obj.__class__.__name__ + '.' + obj.id] + self.save() + + def close(self): + """Deserialize JSON file to objects""" + self.reload() + + def get(self, cls, id): + """Retrieve an object""" + if cls is not None and type(cls) is str and id is not None and\ + type(id) is str and cls in classes: + key = cls + '.' + id + obj = self.__objects.get(key, None) + return obj + else: + return None + + def count(self, cls=None): + """Count number of objects in storage""" + total = 0 + if type(cls) == str and cls in classes: + total = len(self.all(cls)) + elif cls is None: + total = len(self.__objects) + return total diff --git a/models/state.py b/models/state.py index 583f041f07e4..929b1fc4bae4 100644 --- a/models/state.py +++ b/models/state.py @@ -1,8 +1,35 @@ #!/usr/bin/python3 -""" State Module for HBNB project """ -from models.base_model import BaseModel +"""class State""" +import models +from models.base_model import BaseModel, Base +from os import getenv +import sqlalchemy +from sqlalchemy import Column, String +from sqlalchemy.orm import relationship -class State(BaseModel): - """ State class """ - name = "" +class State(BaseModel, Base): + """Represent a state """ + if getenv('HBNB_TYPE_STORAGE') == 'db': + __tablename__ = 'states' + name = Column(String(128), + nullable=False) + cities = relationship("City", cascade="all, delete", + backref="states") + else: + name = "" + + def __init__(self, *args, **kwargs): + """initializes state""" + super().__init__(*args, **kwargs) + + if getenv('HBNB_TYPE_STORAGE') != 'db': + @property + def cities(self): + """fs getter attribute that returns City instances""" + values_city = models.storage.all("City").values() + list_city = [] + for city in values_city: + if city.state_id == self.id: + list_city.append(city) + return list_city diff --git a/setup_mysql_dev.sql b/setup_mysql_dev.sql new file mode 100644 index 000000000000..9d67f1fdc442 --- /dev/null +++ b/setup_mysql_dev.sql @@ -0,0 +1,21 @@ +-- Creates a MySQL server with: +-- Database: hbnb_dev_db +-- User: hbnb_dev +-- Password: hbnb_dev_pwd +-- Host: localhost +-- Grants all privileges for the user hbnb_dev on the database hbnb_dev_db. +-- Grants SELECT privilege for the user hbnb_dev on the performance_schema database. + +CREATE DATABASE IF NOT EXISTS hbnb_dev_db; +CREATE USER + IF NOT EXISTS 'hbnb_dev'@'localhost' + IDENTIFIED BY 'hbnb_dev_pwd'; +GRANT ALL PRIVILEGES + ON `hbnb_dev_db`.* + TO 'hbnb_dev'@'localhost' + IDENTIFIED BY 'hbnb_dev_pwd'; +GRANT SELECT + ON `performance_schema`.* + TO 'hbnb_dev'@'localhost' + IDENTIFIED BY 'hbnb_dev_pwd'; +FLUSH PRIVILEGES; diff --git a/setup_mysql_test.sql b/setup_mysql_test.sql new file mode 100644 index 000000000000..2a568878eaa5 --- /dev/null +++ b/setup_mysql_test.sql @@ -0,0 +1,19 @@ + + -- Creates a MySQL server with: + -- Database hbnb_test_db. + -- User hbnb_test with password hbnb_test_pwd in localhost. + -- Grants all privileges for hbnb_test on hbnb_test_db. + -- Grants SELECT privilege for hbnb_test on performance_schema. +CREATE DATABASE IF NOT EXISTS hbnb_test_db; +CREATE USER + IF NOT EXISTS 'hbnb_test'@'localhost' + IDENTIFIED BY 'hbnb_test_pwd'; +GRANT ALL PRIVILEGES + ON `hbnb_test_db`.* + TO 'hbnb_test'@'localhost' + IDENTIFIED BY 'hbnb_test_pwd'; +GRANT SELECT + ON `performance_schema`.* + TO 'hbnb_test'@'localhost' + IDENTIFIED BY 'hbnb_test_pwd'; +FLUSH PRIVILEGES; diff --git a/web_flask/0-hello_route.py b/web_flask/0-hello_route.py new file mode 100644 index 000000000000..c14463b77de5 --- /dev/null +++ b/web_flask/0-hello_route.py @@ -0,0 +1,18 @@ +#!/usr/bin/python3 +"""Starts a flask web application +Your web application must be listening on 0.0.0.0, port 5000 +""" + +from flask import Flask + +app = Flask("__name__") + + +@app.route('/', strict_slashes=False) +def hello(): + """Return a given string""" + return ("Hello HBNB!") + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=None) diff --git a/web_flask/1-hbnb_route.py b/web_flask/1-hbnb_route.py new file mode 100644 index 000000000000..991bc8c33c76 --- /dev/null +++ b/web_flask/1-hbnb_route.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 +""" Starts a Flask web application: +Your web appli +cation must be listening on 0.0.0.0, port 5000 +""" + +from flask import Flask + +app = Flask("__name__") + + +@app.route('/', strict_slashes=False) +def hello(): + """Return a given string""" + return ("Hello HBNB!") + + +@app.route("/hbnb", strict_slashes=False) +def hbnb(): + """Returns a given string""" + return ("HBNB") + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=None) diff --git a/web_flask/10-hbnb_filters.py b/web_flask/10-hbnb_filters.py new file mode 100644 index 000000000000..a22fbf6e0206 --- /dev/null +++ b/web_flask/10-hbnb_filters.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +"""a script that starts a Flask web application""" +from models import storage +from flask import Flask +from flask import render_template + +app = Flask(__name__) + + +@app.route("/hbnb_filters", strict_slashes=False) +def hbnb_filters(): + """Displays the main HBnB filters HTML page.""" + states = storage.all("State") + amenities = storage.all("Amenity") + return render_template("10-hbnb_filters.html", + states=states, amenities=amenities) + + +@app.teardown_appcontext +def teardown(exc): + """Remove the current SQLAlchemy session.""" + storage.close() + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) diff --git a/web_flask/2-c_route.py b/web_flask/2-c_route.py new file mode 100644 index 000000000000..da8db1074a5b --- /dev/null +++ b/web_flask/2-c_route.py @@ -0,0 +1,30 @@ +#!/usr/bin/python3 +""" Starts a Flask web application: +Your web application must be listening on 0.0.0.0, port 5000 +""" + +from flask import Flask + +app = Flask("__name__") + + +@app.route('/', strict_slashes=False) +def hello(): + """Returns a given string""" + return ("Hello HBNB!") + + +@app.route("/hbnb", strict_slashes=False) +def hbnb(): + """Return a given string""" + return ("HBNB") + + +@app.route("/c/", strict_slashes=False) +def cText(text): + """Displays C followed by the value of the text variable""" + return "C {}".format(text.replace("_", " ")) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=None) diff --git a/web_flask/3-python_route.py b/web_flask/3-python_route.py new file mode 100644 index 000000000000..564e006e48ca --- /dev/null +++ b/web_flask/3-python_route.py @@ -0,0 +1,37 @@ +#!/usr/bin/python3 +""" Starts a Flask web application: +Your web application must be listening on 0.0.0.0, port 5000 +""" + +from flask import Flask + +app = Flask("__name__") + + +@app.route('/', strict_slashes=False) +def hello(): + """Returns a given string""" + return ("Hello HBNB!") + + +@app.route("/hbnb", strict_slashes=False) +def hbnb(): + """Return a given string""" + return ("HBNB") + + +@app.route("/c/", strict_slashes=False) +def cText(text): + """Display C followed by the value of the text variable""" + return "C {}".format(text.replace("_", " ")) + + +@app.route('/python', strict_slashes=False) +@app.route("/python/", strict_slashes=False) +def pythonText(text="is cool"): + """display Python followed by the value of the text variable""" + return "Python {}".format(text.replace("_", " ")) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=None) diff --git a/web_flask/4-number_route.p b/web_flask/4-number_route.p new file mode 100644 index 000000000000..d1cbcf58f410 --- /dev/null +++ b/web_flask/4-number_route.p @@ -0,0 +1,44 @@ +#!/usr/bin/python3 +""" A script that starts a Flask web application: + must be listening on 0.0.0.0, port 5000 +""" + +from flask import Flask + +app = Flask("__name__") + + +@app.route('/', strict_slashes=False) +def hello(): + """Return a given string""" + return ("Hello HBNB!") + + +@app.route("/hbnb", strict_slashes=False) +def hbnb(): + """Returns a given string""" + return ("HBNB") + + +@app.route("/c/", strict_slashes=False) +def cText(text): + """display C followed by the value of the text variable""" + return "C {}".format(text.replace("_", " ")) + + +@app.route('/python', strict_slashes=False) +@app.route("/python/", strict_slashes=False) +def pythonText(text="is cool"): + """display Python followed by the value of the text variable""" + return "Python {}".format(text.replace("_", " ")) + + +@app.route("/number/", strict_slashes=False) +def isNumber(n): + """display “n is a number” only if n is an integer""" + if isinstance(n, int): + return "{} is a number".format(n) + + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000, debug=None) diff --git a/web_flask/5-number_template.py b/web_flask/5-number_template.py new file mode 100644 index 000000000000..e139187b60a9 --- /dev/null +++ b/web_flask/5-number_template.py @@ -0,0 +1,47 @@ +#!/usr/bin/python3 +"""Flask framework""" +from flask import Flask, render_template +app = Flask(__name__) + + +@app.route('/', strict_slashes=False) +def hello(): + """Return a given string""" + return "Hello HBNB!" + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + """Returns a given string""" + return "HBNB" + + +@app.route('/c/', strict_slashes=False) +def cText(text): + """Display C followed by the value of the text variable""" + return "C {}".format(text.replace("_", " ")) + + +@app.route('/python', strict_slashes=False) +@app.route('/python/', strict_slashes=False) +def pythonText(text="is cool"): + """display Python followed by the value of the text variable""" + return "Python {}".format(text.replace("_", " ")) + + +@app.route('/number/', strict_slashes=False) +def isNumber(n): + """display “n is a number” only an integer""" + if isinstance(n, int): + return "{} is a number".format(n) + + +@app.route('/number_template/', strict_slashes=False) +def number_template(n=None): + """display HTML page only if n is an integer""" + if isinstance(n, int): + return render_template("5-number.html", n=n) + + +if __name__ == "__main__": + app.run() diff --git a/web_flask/6-number_odd_or_even.py b/web_flask/6-number_odd_or_even.py new file mode 100644 index 000000000000..5398ff4339a0 --- /dev/null +++ b/web_flask/6-number_odd_or_even.py @@ -0,0 +1,60 @@ +#!/usr/bin/python3 +"""more Flask""" +from flask import Flask, render_template +app = Flask(__name__) + + +@app.route('/', strict_slashes=False) +def hello(): + """Return a given string""" + return "Hello HBNB!" + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + """Returns a given string""" + return "HBNB" + + +@app.route('/c/', strict_slashes=False) +def cText(text): + """display C followed by the value of the text variable""" + return "C {}".format(text.replace("_", " ")) + + +@app.route('/python', strict_slashes=False) +@app.route('/python/', strict_slashes=False) +def pythonText(text="is cool"): + """Shows Python followed by the value of the text variable""" + return "Python {}".format(text.replace("_", " ")) + + +@app.route('/number/', strict_slashes=False) +def isNumber(n): + """displays“n is a number” only if n is an integer""" + if isinstance(n, int): + return "{} is a number".format(n) + + +@app.route('/number_template/', strict_slashes=False) +def number_template(n=None): + """displays a HTML page only if n is an integer""" + if isinstance(n, int): + return render_template("5-number.html", n=n) + + +@app.route('/number_odd_or_even/', strict_slashes=False) +def number_odd_or_even(n=None): + """display a HTML page only if n is an integer: + H1 tag: “Number: n is even|odd” inside the tag BODY + """ + if isinstance(n, int): + if n % 2: + eo = "odd" + else: + eo = "even" + return render_template("6-number_odd_or_even.html", n=n, eo=eo) + + +if __name__ == "__main__": + app.run() diff --git a/web_flask/7-states_list.py b/web_flask/7-states_list.py new file mode 100644 index 000000000000..030dc5d28bc3 --- /dev/null +++ b/web_flask/7-states_list.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 +"""Import Flask to run a web app""" +from flask import Flask, render_template +from models import storage +from models.state import State + + +app = Flask(__name__) + + +@app.route("/states_list", strict_slashes=False) +def display_states(): + """Rendering state_list html page to display States created""" + states = storage.all() + return render_template('7-states_list.html', states=states) + + +@app.teardown_appcontext +def teardown(self): + """Method to remove current SQLAlchemy Session""" + storage.close() + + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=5000) diff --git a/web_flask/8-cities_by_states.py b/web_flask/8-cities_by_states.py new file mode 100644 index 000000000000..d5944f2d0e5e --- /dev/null +++ b/web_flask/8-cities_by_states.py @@ -0,0 +1,25 @@ +#!/usr/bin/python3 +"""Import Flask to run the web app""" +from flask import Flask, render_template +from models import storage +from models.state import State + + +app = Flask(__name__) + + +@app.teardown_appcontext +def close(self): + """close the session """ + storage.close() + + +@app.route('/cities_by_states', strict_slashes=False) +def cities_by_states(): + """Display a html page with states and cities""" + states = storage.all(State) + return render_template('8-cities_by_states.html', states=states) + + +if __name__ == '__main__': + app.run(host="0.0.0.0", port="5000") diff --git a/web_flask/9-states.py b/web_flask/9-states.py new file mode 100644 index 000000000000..8ee8b8b99bd4 --- /dev/null +++ b/web_flask/9-states.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +"""Import Flask to run the web app""" +from flask import Flask, render_template +from models import storage +from models.state import State + + +app = Flask(__name__) + + +@app.teardown_appcontext +def close(self): + """ Close the session """ + storage.close() + + +@app.route('/states', strict_slashes=False) +def state(): + """Display html page with states""" + states = storage.all(State) + return render_template('9-states.html', states=states, mode='all') + + +@app.route('/states/', strict_slashes=False) +def state_by_id(id): + """Displays a html page with citys of that state""" + for state in storage.all(State).values(): + if state.id == id: + return render_template('9-states.html', states=state, mode='id') + return render_template('9-states.html', states=state, mode='none') + + +if __name__ == '__main__': + app.run(host="0.0.0.0", port="5000") diff --git a/web_flask/README.md b/web_flask/README.md new file mode 100644 index 000000000000..86cb33ac36ea --- /dev/null +++ b/web_flask/README.md @@ -0,0 +1,2 @@ + +0x04. AirBnB clone - Web framework diff --git a/web_flask/__init__.py b/web_flask/__init__.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/web_flask/__init__.py @@ -0,0 +1 @@ + diff --git a/web_flask/__pycache__/7-states_list.cpython-310.py b/web_flask/__pycache__/7-states_list.cpython-310.py new file mode 100644 index 000000000000..8b137891791f --- /dev/null +++ b/web_flask/__pycache__/7-states_list.cpython-310.py @@ -0,0 +1 @@ + diff --git a/web_flask/templates/10-hbnb_filters.html b/web_flask/templates/10-hbnb_filters.html new file mode 100644 index 000000000000..e9cb7c704a67 --- /dev/null +++ b/web_flask/templates/10-hbnb_filters.html @@ -0,0 +1,53 @@ + + + + + AirBnB clone + + + + + + + + + +
+ +
+
+
+

States

+

 

+
+
    + {% for state in states.values()|sort(attribute="name") %} +
  • {{ state.name }} +
      + {% for city in state.cities|sort(attribute="name") %} +
    • {{ city.name }}
    • + {% endfor %} +
    +
  • + {% endfor %} +
+
+ +
+

Amenities

+

 

+
    + {% for amenity in amenities.values()|sort(attribute="name") %} +
  • {{ amenity.name }}
  • + {% endfor %} +
+
+ +
+
+ +
+ Holberton School +
+ + diff --git a/web_flask/templates/100-hbnb.html b/web_flask/templates/100-hbnb.html new file mode 100644 index 000000000000..263c08502d9c --- /dev/null +++ b/web_flask/templates/100-hbnb.html @@ -0,0 +1,113 @@ + + + + + + HBnB + + + + + + + + + + + +
+ +
+
+ + +
+

Places

+ {% for place in places.values()|sort(attribute="name") %} +
+
+

{{ place.name }}

+
${{ place.price_by_night }}
+
+ +
+
+ +
{{ place.max_guest }} Guests +
+
+ +
{{ place.number_rooms }} Rooms +
+
+ +
{{ place.number_bathrooms }} Bathrooms +
+
+ +
+ Owner: {{ place.user.first_name }} {{ place.user.last_name }} +
+ +
{{ place.description|safe }}
+ +
+

Amenities

+ {% for amenity in place.amenities|sort(attribute="name") %} +
    +
  • {{ amenity.name }}

  • +
+ {% endfor %} +
+ +
+

{{ place.reviews.__len__() }} Reviews

+ {% for review in place.reviews %} +

From {{ review.user.first_name }} the {{ review.created_at.date().__str__() }}

+
    +
  • {{ review.text|safe }}

  • +
+ {% endfor %} +
+
+ {% endfor %} +
+
+
+ +
+ Holberton School +
+ + diff --git a/web_flask/templates/5-number.html b/web_flask/templates/5-number.html new file mode 100644 index 000000000000..8a6ab977ff1e --- /dev/null +++ b/web_flask/templates/5-number.html @@ -0,0 +1,10 @@ + + + + + HBNB + + +

Number: {{ n }}

+ + diff --git a/web_flask/templates/6-number_odd_or_even.html b/web_flask/templates/6-number_odd_or_even.html new file mode 100644 index 000000000000..e74dc8929351 --- /dev/null +++ b/web_flask/templates/6-number_odd_or_even.html @@ -0,0 +1,13 @@ + + + + HBNB + + + {% if n is even %} +

Number: {{ n }} is even

+ {% else %} +

Number: {{ n }} is odd

+ {% endif %} + + diff --git a/web_flask/templates/7-states_list.html b/web_flask/templates/7-states_list.html new file mode 100644 index 000000000000..afd4fdf00747 --- /dev/null +++ b/web_flask/templates/7-states_list.html @@ -0,0 +1,14 @@ + + + + HBNB + + +

States

+
    + {% for state in states.values() | sort(attribute='name') %} +
  • {{ state.id }}: {{ state.name }}
  • + {% endfor %} +
+ + diff --git a/web_flask/templates/8-cities_by_states.html b/web_flask/templates/8-cities_by_states.html new file mode 100644 index 000000000000..5e1b79bf5123 --- /dev/null +++ b/web_flask/templates/8-cities_by_states.html @@ -0,0 +1,20 @@ + + + + HBNB + + +

States

+
    + {% for state in states.values()|sort(attribute='name') %} +
  • {{ state.id }}: {{ state.name }} +
      + {% for city in state.cities|sort(attribute='name') %} +
    • {{ city.id }}: {{ city.name }}
    • + {% endfor %} +
    +
  • + {% endfor %} +
+ + diff --git a/web_flask/templates/9-states.html b/web_flask/templates/9-states.html new file mode 100644 index 000000000000..57296ffb5097 --- /dev/null +++ b/web_flask/templates/9-states.html @@ -0,0 +1,26 @@ + + + + HBNB + + + {% if mode == 'all' %} +

States

+
    + {% for state in states.values()|sort(attribute='name') %} +
  • {{ state.id }}: {{ state.name }}
  • + {% endfor %} +
+ {% elif mode == 'id' %} +

State: {{ states.name }}

+

Cities:

+
    + {% for city in states.cities|sort(attribute="name") %} +
  • {{ city.id }}: {{ city.name }}
  • + {% endfor %} +
+ {% else %} +

Not found!

+ {% endif %} + +