diff --git a/0-setup_web_static.sh b/0-setup_web_static.sh new file mode 100755 index 000000000000..66f3f6b65ad2 --- /dev/null +++ b/0-setup_web_static.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Bash script that sets up your web servers for the deployment of web_static + +apt-get update +apt-get install -y nginx + +mkdir -p /data/web_static/releases/test/ +mkdir -p /data/web_static/shared/ +echo "Holberton School" > /data/web_static/releases/test/index.html +ln -sf /data/web_static/releases/test/ /data/web_static/current + +chown -R ubuntu /data/ +chgrp -R ubuntu /data/ + +printf %s "server { + listen 80 default_server; + listen [::]:80 default_server; + add_header X-Served-By $HOSTNAME; + root /var/www/html; + index index.html index.htm; + + location /hbnb_static { + alias /data/web_static/current; + index index.html index.htm; + } + + location /redirect_me { + return 301 http://cuberule.com/; + } + + error_page 404 /404.html; + location /404 { + root /var/www/html; + internal; + } +}" > /etc/nginx/sites-available/default + +service nginx restart diff --git a/1-pack_web_static.py b/1-pack_web_static.py new file mode 100755 index 000000000000..0a249a7d307c --- /dev/null +++ b/1-pack_web_static.py @@ -0,0 +1,22 @@ +#!/usr/bin/python3 +# Fabric script that generates a .tgz archive from the contents of the web_static folder of AirBnB Clone. +import os.path +from datetime import datetime +from fabric.api import local + + +def do_pack(): + """Create a tar gzipped archive of the directory web_static.""" + dt = datetime.utcnow() + file = "versions/web_static_{}{}{}{}{}{}.tgz".format(dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second) + if os.path.isdir("versions") is False: + if local("mkdir -p versions").failed is True: + return None + if local("tar -cvzf {} web_static".format(file)).failed is True: + return None + return file diff --git a/100-clean_web_static.py b/100-clean_web_static.py new file mode 100755 index 000000000000..b83279b7e551 --- /dev/null +++ b/100-clean_web_static.py @@ -0,0 +1,29 @@ +#!/usr/bin/python3 +# Fabric script that deletes out-of-date archives, using the function do_clean. + +import os +from fabric.api import * + +env.hosts = ["54.165.55.51", "52.91.147.19"] + + +def do_clean(number=0): + """Delete out-of-date archives. + Args: + number (int): The number of archives to keep. + If number is 0 or 1, keeps only the most recent archive. If + number is 2, keeps the most and second-most recent archives, + etc. + """ + number = 1 if int(number) == 0 else int(number) + + archives = sorted(os.listdir("versions")) + [archives.pop() for i in range(number)] + with lcd("versions"): + [local("rm ./{}".format(a)) for a in archives] + + 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/101-setup_web_static.pp b/101-setup_web_static.pp new file mode 100755 index 000000000000..366f5f00caec --- /dev/null +++ b/101-setup_web_static.pp @@ -0,0 +1,88 @@ +# Script that sets up my web servers for the deployment of web_static using puppet + +# Nginx configuration file +$nginx_conf = "server { + listen 80 default_server; + listen [::]:80 default_server; + add_header X-Served-By ${hostname}; + root /var/www/html; + index index.html index.htm; + location /hbnb_static { + alias /data/web_static/current; + index index.html index.htm; + } + location /redirect_me { + return 301 http://cuberule.com/; + } + error_page 404 /404.html; + location /404 { + root /var/www/html; + internal; + } +}" + +package { 'nginx': + ensure => 'present', + provider => 'apt' +} -> + +file { '/data': + ensure => 'directory' +} -> + +file { '/data/web_static': + ensure => 'directory' +} -> + +file { '/data/web_static/releases': + ensure => 'directory' +} -> + +file { '/data/web_static/releases/test': + ensure => 'directory' +} -> + +file { '/data/web_static/shared': +ensure => 'directory' + } -> + +file { '/data/web_static/releases/test/index.html': + ensure => 'present', + content => "Holberton School Puppet\n" +} -> + +file { '/data/web_static/current': + ensure => 'link', + target => '/data/web_static/releases/test' +} -> + +exec { 'chown -R ubuntu:ubuntu /data/': + path => '/usr/bin/:/usr/local/bin/:/bin/' +} + +file { '/var/www': + ensure => 'directory' +} -> + +file { '/var/www/html': + ensure => 'directory' +} -> + +file { '/var/www/html/index.html': + ensure => 'present', + content => "Holberton School Nginx\n" +} -> + +file { '/var/www/html/404.html': + ensure => 'present', + content => "Ceci n'est pas une page\n" +} -> + +file { '/etc/nginx/sites-available/default': + ensure => 'present', + content => $nginx_conf +} -> + +exec { 'nginx restart': + path => '/etc/init.d/' +} diff --git a/2-do_deploy_web_static.py b/2-do_deploy_web_static.py new file mode 100755 index 000000000000..0a1cb67f2b49 --- /dev/null +++ b/2-do_deploy_web_static.py @@ -0,0 +1,48 @@ +#!/usr/bin/python3 +# Fabric script that distributes archive to my webservers. +import os.path +from fabric.api import env +from fabric.api import put +from fabric.api import run + +env.hosts = ["nickom.tech", "52.91.147.19"] + + +def do_deploy(archive_path): + """Distributes an archive to a web server. + Args: + archive_path (str): The path of the archive to distribute. + Returns: + If the file doesn't exist at archive_path or an error occurs - False. + Otherwise - True. + """ + if os.path.isfile(archive_path) is False: + return False + file = archive_path.split("/")[-1] + name = file.split(".")[0] + + if put(archive_path, "/tmp/{}".format(file)).failed is True: + return False + if run("rm -rf /data/web_static/releases/{}/". + format(name)).failed is True: + return False + if run("mkdir -p /data/web_static/releases/{}/". + format(name)).failed is True: + return False + if run("tar -xzf /tmp/{} -C /data/web_static/releases/{}/". + format(file, name)).failed is True: + return False + if run("rm /tmp/{}".format(file)).failed is True: + return False + if run("mv /data/web_static/releases/{}/web_static/* " + "/data/web_static/releases/{}/".format(name, name)).failed is True: + return False + if run("rm -rf /data/web_static/releases/{}/web_static". + format(name)).failed is True: + return False + if run("rm -rf /data/web_static/current").failed is True: + return False + if run("ln -s /data/web_static/releases/{}/ /data/web_static/current". + format(name)).failed is True: + return False + return True diff --git a/3-deploy_web_static.py b/3-deploy_web_static.py new file mode 100755 index 000000000000..30121b531e56 --- /dev/null +++ b/3-deploy_web_static.py @@ -0,0 +1,75 @@ +#!/usr/bin/python3 +# Fabric script that creates and distributes archive to my web serves +import os.path +from datetime import datetime +from fabric.api import env +from fabric.api import local +from fabric.api import put +from fabric.api import run + +env.hosts = ["54.165.55.51", "52.91.147.19"] + + +def do_pack(): + """Create a tar gzipped archive of the directory web_static.""" + dt = datetime.utcnow() + file = "versions/web_static_{}{}{}{}{}{}.tgz".format(dt.year, + dt.month, + dt.day, + dt.hour, + dt.minute, + dt.second) + if os.path.isdir("versions") is False: + if local("mkdir -p versions").failed is True: + return None + if local("tar -cvzf {} web_static".format(file)).failed is True: + return None + return file + + +def do_deploy(archive_path): + """Distributes an archive to a web server. + Args: + archive_path (str): The path of the archive to distribute. + Returns: + If the file doesn't exist at archive_path or an error occurs - False. + Otherwise - True. + """ + if os.path.isfile(archive_path) is False: + return False + file = archive_path.split("/")[-1] + name = file.split(".")[0] + + if put(archive_path, "/tmp/{}".format(file)).failed is True: + return False + if run("rm -rf /data/web_static/releases/{}/". + format(name)).failed is True: + return False + if run("mkdir -p /data/web_static/releases/{}/". + format(name)).failed is True: + return False + if run("tar -xzf /tmp/{} -C /data/web_static/releases/{}/". + format(file, name)).failed is True: + return False + if run("rm /tmp/{}".format(file)).failed is True: + return False + if run("mv /data/web_static/releases/{}/web_static/* " + "/data/web_static/releases/{}/".format(name, name)).failed is True: + return False + if run("rm -rf /data/web_static/releases/{}/web_static". + format(name)).failed is True: + return False + if run("rm -rf /data/web_static/current").failed is True: + return False + if run("ln -s /data/web_static/releases/{}/ /data/web_static/current". + format(name)).failed is True: + return False + return True + + +def deploy(): + """Create and distribute an archive to a web server.""" + file = do_pack() + if file is None: + return False + return do_deploy(file) diff --git a/README.md b/README.md old mode 100644 new mode 100755 index 3ce462902d67..676c56c1ae07 --- a/README.md +++ b/README.md @@ -139,4 +139,4 @@ Usage: .update(<_id>, ) (hbnb) User.all() (hbnb) ["[User] (98bea5de-9cb0-4d78-8a9d-c4de03521c30) {'updated_at': datetime.datetime(2020, 2, 19, 21, 47, 29, 134362), 'name': 'Fred the Frog', 'age': 9, 'id': '98bea5de-9cb0-4d78-8a9d-c4de03521c30', 'created_at': datetime.datetime(2020, 2, 19, 21, 47, 29, 134343)}"] ``` -
\ No newline at end of file +
diff --git a/console.py b/console.py index 13a8af68e930..0f5e5dc79aa2 100755 --- a/console.py +++ b/console.py @@ -3,7 +3,6 @@ import cmd import sys 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 @@ -13,27 +12,51 @@ class HBNBCommand(cmd.Cmd): - """ Contains the functionality for the HBNB console""" + """Contains the functionality for the HBNB console""" # determines prompt for interactive/non-interactive modes - prompt = '(hbnb) ' if sys.__stdin__.isatty() else '' + prompt = "(hbnb) " if sys.__stdin__.isatty() else "" classes = { - 'BaseModel': BaseModel, 'User': User, 'Place': Place, - 'State': State, 'City': City, 'Amenity': Amenity, - 'Review': Review - } - dot_cmds = ['all', 'count', 'show', 'destroy', 'update'] + "BaseModel": BaseModel, "User": User, "Place": Place, + "State": State, "City": City, "Amenity": Amenity, "Review": Review, + } + + valid_keys = { + """ This are valid keys """ + "BaseModel": ["id", "created_at", "updated_at"], + "User": [ + "id", + "created_at", "updated_at", "email", "password", + "first_name", "last_name", + ], + "City": ["id", "created_at", "updated_at", "state_id", "name"], + "State": ["id", "created_at", "updated_at", "name"], + "Place": [ + "id", "created_at", "updated_at", "city_id", + "user_id", "name", "description", "number_rooms", + "number_bathrooms", "max_guest", "price_by_night", "latitude", + "longitude", "amenity_ids" + ], + "Amenity": ["id", "created_at", "updated_at", "name"], + "Review": ["id", "created_at", "updated_at", + "place_id", "user_id", "text"], + } + + 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 - } + "number_rooms": int, + "number_bathrooms": int, + "max_guest": int, + "price_by_night": int, + "latitude": float, + "longitude": float, + } def preloop(self): """Prints if isatty is false""" if not sys.__stdin__.isatty(): - print('(hbnb)') + print("(hbnb)") def precmd(self, line): """Reformat command line for advanced command syntax. @@ -41,31 +64,31 @@ def precmd(self, line): Usage: .([ [<*args> or <**kwargs>]]) (Brackets denote optional fields in usage example.) """ - _cmd = _cls = _id = _args = '' # initialize line elements + _cmd = _cls = _id = _args = "" # initialize line elements # scan for general formating - i.e '.', '(', ')' - if not ('.' in line and '(' in line and ')' in line): + 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('.')] + _cls = pline[: pline.find(".")] # isolate and validate - _cmd = pline[pline.find('.') + 1:pline.find('(')] + _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(')')] + pline = pline[pline.find("(") + 1: pline.find(")")] if pline: # partition args: (, [], [<*args>]) - pline = pline.partition(', ') # pline convert to tuple + pline = pline.partition(", ") # pline convert to tuple # isolate _id, stripping quotes - _id = pline[0].replace('\"', '') + _id = pline[0].replace('"', "") # possible bug here: # empty quotes register as empty _id when replaced @@ -73,13 +96,16 @@ def precmd(self, line): 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: + if ( + pline[0] == "{" + and pline[-1] == "}" + and type(eval(pline)) is dict + ): _args = pline else: - _args = pline.replace(',', '') + _args = pline.replace(",", "") # _args = _args.replace('\"', '') - line = ' '.join([_cmd, _cls, _id, _args]) + line = " ".join([_cmd, _cls, _id, _args]) except Exception as mess: pass @@ -89,57 +115,95 @@ def precmd(self, line): def postcmd(self, stop, line): """Prints if isatty is false""" if not sys.__stdin__.isatty(): - print('(hbnb) ', end='') + print("(hbnb) ", end="") return stop def do_quit(self, command): - """ Method to exit the HBNB console""" + """Method to exit the HBNB console""" exit() def help_quit(self): - """ Prints the help documentation for quit """ + """Prints the help documentation for quit""" print("Exits the program with formatting\n") def do_EOF(self, arg): - """ Handles EOF to exit program """ + """Handles EOF to exit program""" print() exit() def help_EOF(self): - """ Prints the help documentation for EOF """ + """Prints the help documentation for EOF""" print("Exits the program without formatting\n") def emptyline(self): - """ Overrides the emptyline method of CMD """ + """Overrides the emptyline method of CMD""" pass + def parse_value(self, value): + """cast string to float or int if possible""" + is_valid_value = True + # To be a valid string it must be of at least length 2 i.e. "" + # To be a valid string it must begin and end with + # double quoatation i.e. "sdsds" + if len(value) >= 2 and value[0] == '"'\ + and value[len(value) - 1] == '"': + value = value[1:-1] + value = value.replace("_", " ") + else: + try: + if "." in value: + value = float(value) + else: + value = int(value) + except ValueError: + is_valid_value = False + + if is_valid_value: + return value + else: + return None + def do_create(self, args): - """ Create an object of any class""" + """Create an object of any class""" if not args: print("** class name missing **") return - elif args not in HBNBCommand.classes: + args_array = args.split() + class_name = args_array[0] + if class_name not in HBNBCommand.classes: print("** class doesn't exist **") return - new_instance = HBNBCommand.classes[args]() - storage.save() + new_instance = HBNBCommand.classes[class_name]() + for param_index in range(1, len(args_array)): + param_array = args_array[param_index].split("=") + if len(param_array) == 2: + key = param_array[0] + if key not in HBNBCommand.valid_keys[class_name]: + continue + value = self.parse_value(param_array[1]) + if value is not None: + setattr(new_instance, key, value) + else: + pass + + new_instance.save() print(new_instance.id) - storage.save() def help_create(self): - """ Help information for the create method """ + """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 """ + """Method to show an individual object""" + import models 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 c_id and " " in c_id: + c_id = c_id.partition(" ")[0] if not c_name: print("** class name missing **") @@ -155,22 +219,23 @@ def do_show(self, args): key = c_name + "." + c_id try: - print(storage._FileStorage__objects[key]) + print(models.storage._FileStorage__objects[key]) except KeyError: print("** no instance found **") def help_show(self): - """ Help information for the show command """ + """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 """ + """Destroys a specified object""" + import models 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 c_id and " " in c_id: + c_id = c_id.partition(" ")[0] if not c_name: print("** class name missing **") @@ -187,44 +252,45 @@ def do_destroy(self, args): key = c_name + "." + c_id try: - del(storage.all()[key]) - storage.save() + del models.storage.all()[key] + models.storage.save() except KeyError: print("** no instance found **") def help_destroy(self): - """ Help information for the destroy command """ + """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""" + """Shows all objects, or all objects of a class""" + import models print_list = [] if args: - args = args.split(' ')[0] # remove possible trailing 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: + for k, v in models.storage.all(eval(args)).items(): + if k.split(".")[0] == args: print_list.append(str(v)) else: - for k, v in storage._FileStorage__objects.items(): + for k, v in models.storage.all().items(): print_list.append(str(v)) - print(print_list) def help_all(self): - """ Help information for the all command """ + """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""" + import models count = 0 - for k, v in storage._FileStorage__objects.items(): - if args == k.split('.')[0]: + for k, v in models.storage._FileStorage__objects.items(): + if args == k.split(".")[0]: count += 1 print(count) @@ -233,8 +299,9 @@ def help_count(self): print("Usage: count ") def do_update(self, args): - """ Updates a certain object with new info """ - c_name = c_id = att_name = att_val = kwargs = '' + """Updates a certain object with new info""" + import models + c_name = c_id = att_name = att_val = kwargs = "" # isolate cls from id/args, ex: (, delim, ) args = args.partition(" ") @@ -259,12 +326,12 @@ def do_update(self, args): key = c_name + "." + c_id # determine if key is present - if key not in storage.all(): + if key not in models.storage.all(): 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: + 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(): @@ -272,33 +339,33 @@ def do_update(self, args): args.append(v) else: # isolate args args = args[2] - if args and args[0] is '\"': # check for quoted arg - second_quote = args.find('\"', 1) + if args and args[0] == '"': # check for quoted arg + second_quote = args.find('"', 1) att_name = args[1:second_quote] args = args[second_quote + 1:] - args = args.partition(' ') + args = args.partition(" ") # if att_name was not quoted arg - if not att_name and args[0] is not ' ': + if not att_name and args[0] != " ": 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 args[2] and args[2][0] == '"': + 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] + att_val = args[2].partition(" ")[0] args = [att_name, att_val] # retrieve dictionary of current objects - new_dict = storage.all()[key] + new_dict = models.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): + 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 **") @@ -316,9 +383,10 @@ def do_update(self, args): new_dict.save() # save updates to file def help_update(self): - """ Help information for the update class """ + """Help information for the update class""" print("Updates an object with new information") print("Usage: update \n") + if __name__ == "__main__": HBNBCommand().cmdloop() diff --git a/models/__init__.py b/models/__init__.py old mode 100644 new mode 100755 index d3765c2bc603..caee775f0a83 --- a/models/__init__.py +++ b/models/__init__.py @@ -1,7 +1,15 @@ #!/usr/bin/python3 """This module instantiates an object of class FileStorage""" -from models.engine.file_storage import FileStorage +import os -storage = FileStorage() -storage.reload() +storetype = os.environ.get('HBNB_TYPE_STORAGE') + +if storetype == 'db': + from models.engine.db_storage import DBStorage + storage = DBStorage() + storage.reload() +else: + from models.engine.file_storage import FileStorage + storage = FileStorage() + storage.reload() diff --git a/models/amenity.py b/models/amenity.py old mode 100644 new mode 100755 index a181095e4170..a207e4ab55aa --- a/models/amenity.py +++ b/models/amenity.py @@ -1,7 +1,12 @@ #!/usr/bin/python3 """ State Module for HBNB project """ -from models.base_model import BaseModel +from models.base_model import BaseModel, Base +from sqlalchemy import Column, String +from sqlalchemy.orm import relationship +from models.place import place_amenity -class Amenity(BaseModel): - name = "" +class Amenity(BaseModel, Base): + name = Column('name', String(128), nullable=False) + __tablename__ = 'amenities' + place_amenities = relationship('Place', secondary=place_amenity) diff --git a/models/base_model.py b/models/base_model.py old mode 100644 new mode 100755 index 4856e9de421f..d9cb6d4149a1 --- a/models/base_model.py +++ b/models/base_model.py @@ -2,12 +2,29 @@ """This module defines a base class for all models in our hbnb clone""" import uuid from datetime import datetime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import Column, String, DateTime + +Base = declarative_base() class BaseModel: """A base class for all hbnb models""" + + id = Column('id', String(60), nullable=False, primary_key=True) + created_at = Column( + DateTime, + nullable=False, + default=datetime.utcnow() + ) + updated_at = Column( + DateTime, + nullable=False, + default=datetime.utcnow() + ) + def __init__(self, *args, **kwargs): - """Instatntiates a new model""" + """Instantiates a new model""" if not kwargs: from models import storage self.id = str(uuid.uuid4()) @@ -31,6 +48,7 @@ def save(self): """Updates updated_at with current time when instance is changed""" from models import storage self.updated_at = datetime.now() + storage.new(self) storage.save() def to_dict(self): @@ -41,4 +59,11 @@ def to_dict(self): (str(type(self)).split('.')[-1]).split('\'')[0]}) dictionary['created_at'] = self.created_at.isoformat() dictionary['updated_at'] = self.updated_at.isoformat() + if '_sa_instance_state' in dictionary: + del dictionary['_sa_instance_state'] return dictionary + + def delete(self): + """Deletes instance from storage""" + from models import storage + storage.delete(self) diff --git a/models/city.py b/models/city.py old mode 100644 new mode 100755 index b9b4fe221502..063074e532a7 --- a/models/city.py +++ b/models/city.py @@ -1,9 +1,22 @@ #!/usr/bin/python3 """ City Module for HBNB project """ -from models.base_model import BaseModel +from models.base_model import BaseModel, Base +from sqlalchemy import Column, String, ForeignKey +from sqlalchemy.orm import relationship -class City(BaseModel): +class City(BaseModel, Base): """ The city class, contains state ID and name """ - state_id = "" - name = "" + state_id = Column( + 'state_id', + String(60), + ForeignKey('states.id'), + nullable=False + ) + name = Column('name', String(128), nullable=False) + __tablename__ = 'cities' + places = relationship( + 'Place', + backref='cities', + cascade='all, delete, delete-orphan' + ) diff --git a/models/engine/db_storage.py b/models/engine/db_storage.py new file mode 100755 index 000000000000..c4b8b89e03ab --- /dev/null +++ b/models/engine/db_storage.py @@ -0,0 +1,90 @@ +#!/usr/bin/python3 +"""Module contains Class DBStorage""" +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker, scoped_session +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.review import Review +from models.state import State +from models.user import User + +user = os.environ.get('HBNB_MYSQL_USER') +pswd = os.environ.get('HBNB_MYSQL_PWD') +host = os.environ.get('HBNB_MYSQL_HOST') +db = os.environ.get('HBNB_MYSQL_DB') +dbEnv = os.environ.get('HBNB_ENV') + + +class DBStorage(): + """Database storage class + + Attributes: + __engine: SQLAlchemy engine + __session: SQLAlchemy database session + Methods: + all(): Returns class instances + new(): Adds instance to db session + save(): save session changes to database + delete(): deletes an object from the database + reload(): creates a session + """ + + __engine = None + __session = None + + def __init__(self): + cnct = f'mysql+mysqldb://{user}:{pswd}@{host}/{db}' + self.__engine = create_engine(cnct, pool_pre_ping=True) + if dbEnv == 'test': + Base.metadata.drop_all(bind=self.__engine, checkfirst=True) + + def all(self, cls=None): + """Return all instances depending on the classname given + + Args: + cls: class name argument + """ + dbDict = {} + if cls: + if type(cls) is str: + cls = eval(cls) + for d in self.__session.query(cls): + dky = f'{type(d).__name__}.{d.id}' + dbDict[dky] = d + else: + allClasses = [Amenity, City, Place, Review, State, User] + for eachCls in allClasses: + for dd in self.__session.query(eachCls): + ddky = f'{type(dd).__name__}.{dd.id}' + dbDict[ddky] = dd + return dbDict + + def new(self, obj): + """Add the instance to database + + Args: + obj: instance to add to database + """ + self.__session.add(obj) + + def save(self): + """Commit changes to database""" + self.__session.commit() + + def delete(self, obj=None): + """Delete object from database if it exists + + Args: + obj: object to delete from db + """ + if obj: + self.__session.delete(obj) + + def reload(self): + """Session creation""" + Base.metadata.create_all(self.__engine) + dbSession = sessionmaker(bind=self.__engine, expire_on_commit=False) + self.__session = scoped_session(dbSession) diff --git a/models/engine/file_storage.py b/models/engine/file_storage.py old mode 100644 new mode 100755 index 6f5d7f8d4680..497d5cea4924 --- a/models/engine/file_storage.py +++ b/models/engine/file_storage.py @@ -1,23 +1,57 @@ #!/usr/bin/python3 """This module defines a class to manage file storage for hbnb clone""" import json +import os +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 class FileStorage: - """This class manages storage of hbnb models in JSON format""" + """ + This class manages storage of hbnb models in JSON format + to a file + """ __file_path = 'file.json' __objects = {} + classes = { + 'BaseModel': BaseModel, + 'User': User, + 'Place': Place, + 'State': State, + 'City': City, + 'Amenity': Amenity, + 'Review': Review + } - def all(self): - """Returns a dictionary of models currently in storage""" - return FileStorage.__objects + def all(self, cls=None): + """ + Returns a dictionary of models currently in storage + """ + if cls is not None: + some_dict = {} + for key, value in self.__objects.items(): + if cls == value.__class__ or cls == value.__class__.__name__: + some_dict[key] = value + return some_dict + else: + return self.__objects def new(self, obj): - """Adds new object to storage dictionary""" - self.all().update({obj.to_dict()['__class__'] + '.' + obj.id: obj}) + """ + Adds new object to storage dictionary + """ + if obj: + self.all().update({obj.to_dict()['__class__'] + '.' + obj.id: obj}) def save(self): - """Saves storage dictionary to file""" + """ + Saves storage dictionary to file + """ with open(FileStorage.__file_path, 'w') as f: temp = {} temp.update(FileStorage.__objects) @@ -26,25 +60,35 @@ def save(self): json.dump(temp, 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 - } + """ + Loads stored dictionary from file + """ 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: + self.all()[key] = \ + FileStorage.classes[val['__class__']](**val) + except (FileNotFoundError, json.JSONDecodeError): pass + + def delete(self, obj=None): + """ + Delete an object from __objects + """ + if obj is not None: + key = "{}.{}".format(type(obj).__name__, obj.id) + if key in self.__objects: + del self.__objects[key] + self.save() + else: + pass + + def close(self): + """ + call the reload method to save changes back to JSON file + to maintain persistence when the application stops running + """ + self.reload() + """Nicholas/Hassan""" diff --git a/models/place.py b/models/place.py old mode 100644 new mode 100755 index 5221e8210d17..63116606bdcd --- a/models/place.py +++ b/models/place.py @@ -1,18 +1,109 @@ #!/usr/bin/python3 """ Place Module for HBNB project """ -from models.base_model import BaseModel +from models.base_model import BaseModel, Base +from sqlalchemy import Column, String, ForeignKey, Integer, Float, Table +import os +from sqlalchemy.orm import relationship -class Place(BaseModel): +storageType = os.environ.get('HBNB_TYPE_STORAGE') + +place_amenity = Table( + 'place_amenity', + Base.metadata, + Column( + 'place_id', + String(60), + ForeignKey('places.id'), + primary_key=True, + nullable=False + ), + Column( + 'amenity_id', + String(60), + ForeignKey('amenities.id'), + primary_key=True, + nullable=False + ) +) + + +class Place(BaseModel, Base): """ A place to stay """ - city_id = "" - user_id = "" - name = "" - description = "" - number_rooms = 0 - number_bathrooms = 0 - max_guest = 0 - price_by_night = 0 - latitude = 0.0 - longitude = 0.0 + city_id = Column( + 'city_id', + String(60), + ForeignKey('cities.id'), + nullable=False + ) + user_id = Column( + 'user_id', + String(60), + ForeignKey('users.id'), + nullable=False + ) + name = Column('name', String(128), nullable=False) + description = Column( + 'description', + String(1024), + nullable=True + ) + number_rooms = Column( + 'number_rooms', + Integer, + nullable=False, + default=0 + ) + number_bathrooms = Column( + 'number_bathrooms', + Integer, + nullable=False, + default=0 + ) + max_guest = Column('max_guest', Integer, nullable=False, default=0) + price_by_night = Column( + 'price_by_night', + Integer, + nullable=False, + default=0 + ) + latitude = Column('latitude', Float, nullable=True) + longitude = Column('longitude', Float, nullable=True) amenity_ids = [] + __tablename__ = 'places' + + if storageType == 'db': + reviews = relationship( + 'Review', + backref='place', + cascade='all, delete, delete-orphan' + ) + amenities = relationship( + 'Amenity', + secondary=place_amenity, + viewonly=False, + back_populates='place_amenities' + ) + else: + @property + def reviews(self): + """Returns review instances""" + from models import storage + revwList = [] + allRevws = storage.all('Review').values() + for rvw in allRevws: + if rvw.place_id == self.id: + revwList.append(rvw) + return revwList + + @property + def amenities(self): + """amenities getter attribute function""" + return self.amenity_ids + + @amenities.setter + def amenities(self, inst=None): + from models.amenity import Amenity + """amenities setter attribute function""" + if inst.__class__ == Amenity: + self.amenity_ids.append(Amenity.id) diff --git a/models/review.py b/models/review.py old mode 100644 new mode 100755 index c487d90d34f0..0215b01a39d9 --- a/models/review.py +++ b/models/review.py @@ -1,10 +1,22 @@ #!/usr/bin/python3 """ Review module for the HBNB project """ -from models.base_model import BaseModel +from models.base_model import BaseModel, Base +from sqlalchemy import Column, String, ForeignKey -class Review(BaseModel): +class Review(BaseModel, Base): """ Review classto store review information """ - place_id = "" - user_id = "" - text = "" + place_id = Column( + 'place_id', + String(60), + ForeignKey('places.id'), + nullable=False + ) + user_id = Column( + 'user_id', + String(60), + ForeignKey('users.id'), + nullable=False + ) + text = Column('text', String(1024), nullable=False) + __tablename__ = 'reviews' diff --git a/models/state.py b/models/state.py old mode 100644 new mode 100755 index 583f041f07e4..3c60c682d94e --- a/models/state.py +++ b/models/state.py @@ -1,8 +1,32 @@ #!/usr/bin/python3 """ State Module for HBNB project """ -from models.base_model import BaseModel +from models.base_model import BaseModel, Base +from sqlalchemy import Column, String +import os +from sqlalchemy.orm import relationship +from models.city import City +storeType = os.environ.get('HBNB_TYPE_STORAGE') -class State(BaseModel): + +class State(BaseModel, Base): """ State class """ - name = "" + name = Column('name', String(128), nullable=False) + __tablename__ = 'states' + cities = relationship( + 'City', + backref='state', + cascade='all, delete, delete-orphan', + passive_deletes=True + ) + + @property + def cities(self): + """cities filestorage getter function""" + import models + cityObjects = [] + results = models.storage.all('City').values() + for c in results: + if c.state_id == self.id: + cityObjects.append(c) + return cityObjects diff --git a/models/user.py b/models/user.py old mode 100644 new mode 100755 index 4b54a6d24120..3b56d9c5b863 --- a/models/user.py +++ b/models/user.py @@ -1,11 +1,26 @@ #!/usr/bin/python3 """This module defines a class User""" -from models.base_model import BaseModel +from models.base_model import BaseModel, Base +from sqlalchemy import Column, String +from sqlalchemy.orm import relationship +from models.review import Review +from models.place import Place -class User(BaseModel): +class User(BaseModel, Base): """This class defines a user by various attributes""" - email = '' - password = '' - first_name = '' - last_name = '' + email = Column('email', String(128), nullable=False) + password = Column('password', String(128), nullable=False) + first_name = Column('first_name', String(128), nullable=True) + last_name = Column('last_name', String(128), nullable=True) + __tablename__ = 'users' + places = relationship( + 'Place', + backref='user', + cascade='all, delete, delete-orphan' + ) + reviews = relationship( + 'Review', + backref='user', + cascade='all, delete, delete-orphan' + ) diff --git a/setup_mysql_dev.sql b/setup_mysql_dev.sql new file mode 100755 index 000000000000..30fc4cd108df --- /dev/null +++ b/setup_mysql_dev.sql @@ -0,0 +1,7 @@ +-- prepares a MySQL server for the project + +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'; +GRANT SELECT ON `performance_schema`.* TO 'hbnb_dev'@'localhost'; +FLUSH PRIVILEGES; diff --git a/setup_mysql_test.sql b/setup_mysql_test.sql new file mode 100755 index 000000000000..275f404f92bc --- /dev/null +++ b/setup_mysql_test.sql @@ -0,0 +1,7 @@ +-- prepares MySQL server for the project. + +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'; +GRANT SELECT ON `performance_schema`.* TO 'hbnb_test'@'localhost'; +FLUSH PRIVILEGES; diff --git a/tests/test_models/test_amenity.py b/tests/test_models/test_amenity.py index e47ab0d2e09a..6690d49a33fc 100755 --- a/tests/test_models/test_amenity.py +++ b/tests/test_models/test_amenity.py @@ -16,4 +16,5 @@ def __init__(self, *args, **kwargs): def test_name2(self): """ """ new = self.value() + new.name = 'Wifi' self.assertEqual(type(new.name), str) diff --git a/tests/test_models/test_city.py b/tests/test_models/test_city.py index 2673225808c0..7eb4a5c097e5 100755 --- a/tests/test_models/test_city.py +++ b/tests/test_models/test_city.py @@ -2,6 +2,7 @@ """ """ from tests.test_models.test_base_model import test_basemodel from models.city import City +import uuid class test_City(test_basemodel): @@ -16,9 +17,11 @@ def __init__(self, *args, **kwargs): def test_state_id(self): """ """ new = self.value() + new.state_id = str(uuid.uuid4()) self.assertEqual(type(new.state_id), str) def test_name(self): """ """ new = self.value() + new.name = 'Arusha' self.assertEqual(type(new.name), str) diff --git a/tests/test_models/test_place.py b/tests/test_models/test_place.py index ec133d104ef5..75bfca202b74 100755 --- a/tests/test_models/test_place.py +++ b/tests/test_models/test_place.py @@ -2,6 +2,7 @@ """ """ from tests.test_models.test_base_model import test_basemodel from models.place import Place +import uuid class test_Place(test_basemodel): @@ -16,54 +17,65 @@ def __init__(self, *args, **kwargs): def test_city_id(self): """ """ new = self.value() + new.city_id = str(uuid.uuid4()) self.assertEqual(type(new.city_id), str) def test_user_id(self): """ """ new = self.value() + new.user_id = str(uuid.uuid4()) self.assertEqual(type(new.user_id), str) def test_name(self): """ """ new = self.value() + new.name = 'Dodoma' self.assertEqual(type(new.name), str) def test_description(self): """ """ new = self.value() + new.description = 'Great location' self.assertEqual(type(new.description), str) def test_number_rooms(self): """ """ new = self.value() + new.number_rooms = 2 self.assertEqual(type(new.number_rooms), int) def test_number_bathrooms(self): """ """ new = self.value() + new.number_bathrooms = 1 self.assertEqual(type(new.number_bathrooms), int) def test_max_guest(self): """ """ new = self.value() + new.max_guest = 5 self.assertEqual(type(new.max_guest), int) def test_price_by_night(self): """ """ new = self.value() + new.price_by_night = 15 self.assertEqual(type(new.price_by_night), int) def test_latitude(self): """ """ new = self.value() + new.latitude = 1.343 self.assertEqual(type(new.latitude), float) def test_longitude(self): """ """ new = self.value() - self.assertEqual(type(new.latitude), float) + new.longitude = -122.431 + self.assertEqual(type(new.longitude), float) def test_amenity_ids(self): """ """ new = self.value() + new.amenity_ids = [] self.assertEqual(type(new.amenity_ids), list) diff --git a/tests/test_models/test_review.py b/tests/test_models/test_review.py index 23fbc61529e8..513e7e57236d 100755 --- a/tests/test_models/test_review.py +++ b/tests/test_models/test_review.py @@ -2,6 +2,7 @@ """ """ from tests.test_models.test_base_model import test_basemodel from models.review import Review +import uuid class test_review(test_basemodel): @@ -16,14 +17,17 @@ def __init__(self, *args, **kwargs): def test_place_id(self): """ """ new = self.value() + new.place_id = str(uuid.uuid4()) self.assertEqual(type(new.place_id), str) def test_user_id(self): """ """ new = self.value() + new.user_id = str(uuid.uuid4()) self.assertEqual(type(new.user_id), str) def test_text(self): """ """ new = self.value() + new.text = 'Loved it!' self.assertEqual(type(new.text), str) diff --git a/tests/test_models/test_state.py b/tests/test_models/test_state.py index 719e096d8633..41c79eaedf61 100755 --- a/tests/test_models/test_state.py +++ b/tests/test_models/test_state.py @@ -16,4 +16,5 @@ def __init__(self, *args, **kwargs): def test_name3(self): """ """ new = self.value() + new.name = 'Texas' self.assertEqual(type(new.name), str) diff --git a/tests/test_models/test_user.py b/tests/test_models/test_user.py index 8660300f8bbc..692e599c3d47 100755 --- a/tests/test_models/test_user.py +++ b/tests/test_models/test_user.py @@ -16,19 +16,23 @@ def __init__(self, *args, **kwargs): def test_first_name(self): """ """ new = self.value() + new.first_name = 'Julius' self.assertEqual(type(new.first_name), str) def test_last_name(self): """ """ new = self.value() + new.last_name = 'Irving' self.assertEqual(type(new.last_name), str) def test_email(self): """ """ new = self.value() + new.email = 'thedoctor@gmail.com' self.assertEqual(type(new.email), str) def test_password(self): """ """ new = self.value() + new.password = 'drJ6' self.assertEqual(type(new.password), str) diff --git a/web_flask/0-hello_route.py b/web_flask/0-hello_route.py new file mode 100755 index 000000000000..9d0ce2335666 --- /dev/null +++ b/web_flask/0-hello_route.py @@ -0,0 +1,15 @@ +#!/usr/bin/python3 +"""A simple Flask web application""" + +from flask import Flask + +app = Flask(__name__) + +@app.route('/airbnb-onepage/', strict_slashes=False) +def hello_airbnb(): + """Returns a simple message""" + return "Hello Airbnb!" + +if __name__ == "__main__": + app.run(host="0.0.0.0", port=5000) + diff --git a/web_flask/1-hbnb_route.py b/web_flask/1-hbnb_route.py new file mode 100755 index 000000000000..0b705b1c94b8 --- /dev/null +++ b/web_flask/1-hbnb_route.py @@ -0,0 +1,24 @@ +#!/usr/bin/python3 +"""starts a Flask web application listening on 0.0.0.0, port 5000""" +from flask import Flask + +app = Flask(__name__) + +# Define a route that displays "Hello HBNB!" + + +@app.route('/', strict_slashes=False) +def hello_hbnb(): + return "Hello HBNB!" + +# Define a route that displays "HBNB" + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + """print/display HBNB for /hbnb""" + return "HBNB" + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/web_flask/10-hbnb_filters.py b/web_flask/10-hbnb_filters.py new file mode 100755 index 000000000000..7bdbb83eb50c --- /dev/null +++ b/web_flask/10-hbnb_filters.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 +"""Script that starts a Flask web application""" +from flask import Flask, render_template +from models import storage +from models.state import State +from models.city import City +from models.amenity import Amenity + +app = Flask(__name__) + + +@app.route('/hbnb_filters', strict_slashes=False) +def hbnb_filters(): + """Displays a HTML page with filter options""" + states = sorted(storage.all(State).values(), key=lambda x: x.name) + cities = sorted(storage.all(City).values(), key=lambda x: x.name) + amenities = sorted(storage.all(Amenity).values(), key=lambda x: x.name) + return render_template( + '10-hbnb_filters.html', + states=states, + cities=cities, + amenities=amenities + ) + + +@app.teardown_appcontext +def teardown(exception): + """Closes the SQLAlchemy Session""" + storage.close() + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) + diff --git a/web_flask/100-hbnb.py b/web_flask/100-hbnb.py new file mode 100755 index 000000000000..92a1e64e11e4 --- /dev/null +++ b/web_flask/100-hbnb.py @@ -0,0 +1,41 @@ +#!/usr/bin/python3 +""" +Starts a Flask web application +""" +from flask import Flask, render_template +from models import storage +from models.state import State +from models.city import City +from models.amenity import Amenity +from models.place import Place + +app = Flask(__name__) + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + """ + Display HTML page like 8-index.html + """ + states = storage.all(State).values() + amenities = storage.all(Amenity).values() + places = storage.all(Place).values() + return render_template( + '100-hbnb.html', + states=states, + amenities=amenities, + places=places + ) + + +@app.teardown_appcontext +def teardown(exception): + """ + 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 100755 index 000000000000..8364d2a1a96d --- /dev/null +++ b/web_flask/2-c_route.py @@ -0,0 +1,34 @@ +#!/usr/bin/python3 + +"""This Script starts a flask web application:\ +listening to port 5000 with routes:\ +/ display "Hello HBNB", /hbnb: display "HBNB", :\ + /c/: display “C ” followed by the:\ + value of the text variable """ +from flask import Flask + +app = Flask(__name__) + +# Define a route that displays "Hello HBNB!" + + +@app.route('/', strict_slashes=False) +def hello_hbnb(): + return "Hello HBNB!" + +# Define a route that displays "HBNB" + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + return "HBNB" + + +@app.route('/c/', strict_slashes=False) +def c_text(text): + text = text.replace('_', ' ') # Replace underscores with spaces + return "C " + text + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/web_flask/3-python_route.py b/web_flask/3-python_route.py new file mode 100755 index 000000000000..5c2e5e3029bf --- /dev/null +++ b/web_flask/3-python_route.py @@ -0,0 +1,52 @@ +#!/usr/bin/python3 +""" script that starts a Flask web application :\ + which will be listening on 0.0.0.0, port 5000:\ + with routes /: display “Hello HBNB!”:\ + /hbnb: display “HBNB” :\ + /c/: display “C ”, followed by the value :\ + of the text variable /python/displaying “Python ”, :\ + followed by the value of the text variable""" + +from flask import Flask + +app = Flask(__name__) + +# Define a route that displays "Hello HBNB!" + + +@app.route('/', strict_slashes=False) +def hello_hbnb(): + return "Hello HBNB!" + +# Define a route that displays "HBNB" + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + return "HBNB" + + +""" Define a route that takes a text variable :\ + and displays "C " followed by the value """ + + +@app.route('/c/', strict_slashes=False) +def c_text(text): + text = text.replace('_', ' ') # Replace underscores with spaces + return "C " + text + + +""" Define a route that takes a text variable and displays:\ + "Python " followed by the value""" +# If no text is provided, use the default value "is cool" + + +@app.route('/python/', defaults={'text': 'is cool'}, strict_slashes=False) +@app.route('/python/', strict_slashes=False) +def python_text(text): + text = text.replace('_', ' ') # Replace underscores with spaces + return "Python " + text + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/web_flask/4-number_route.py b/web_flask/4-number_route.py new file mode 100755 index 000000000000..411828d05562 --- /dev/null +++ b/web_flask/4-number_route.py @@ -0,0 +1,54 @@ +#!/usr/bin/python3 + +"""Script that starts a Flask web application :\ + listening to port 5000 routes:\ + /displaying "Hello HBNB" "/hbnb:“HBNB” +/c/: displaying “C ”, followed by :\ + the value of the text variable +/python/() """ +from flask import Flask + +app = Flask(__name__) + +# Define a route that displays "Hello HBNB!" + + +@app.route('/', strict_slashes=False) +def hello_hbnb(): + return "Hello HBNB!" + +# Define a route that displays "HBNB" + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + return "HBNB" + +# Define a route that takes a text variable + + +@app.route('/c/', strict_slashes=False) +def c_text(text): + text = text.replace('_', ' ') + return "C " + text + +# Define a route that takes a text variable +# If no text is provided, use the default value "is cool" + + +@app.route('/python/', defaults={'text': 'is cool'}, strict_slashes=False) +@app.route('/python/', strict_slashes=False) +def python_text(text): + text = text.replace('_', ' ') + return "Python " + text + +# Define a route that takes an integer + + +@app.route('/number/', strict_slashes=False) +def number(n): + return f"{n} is a number" + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/web_flask/5-number_template.py b/web_flask/5-number_template.py new file mode 100755 index 000000000000..2d28a64b4a67 --- /dev/null +++ b/web_flask/5-number_template.py @@ -0,0 +1,64 @@ +#!/usr/bin/python3 +""" Script that starts a flask web application:\ + listening to port 5000:\ + routes + /: display “Hello HBNB!” + /hbnb: display “HBNB” + /c/: display “C ”, followed by the value :\ + of the text variable. + /python/(): display “Python ”, :\ + followed by the value of the text variable + The default value of text is “is cool”:\ + /number/: display “n is a number” only if n is an integer:\ + /number_template/: display a HTML page:\ + only if n is an integer: + H1 tag: “Number: n” inside the tag BODY """ +from flask import Flask, render_template + +app = Flask(__name__) + +# Define a route that displays "Hello HBNB!" + + +@app.route('/', strict_slashes=False) +def hello_hbnb(): + return "Hello HBNB!" + +# Define a route that displays "HBNB" + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + return "HBNB" + +# Define a route that takes a text variable and displays:\ +# "C " followed by the value + + +@app.route('/c/', strict_slashes=False) +def c_text(text): + text = text.replace('_', ' ') # Replace underscores with spaces + return "C " + text + +# Define a route that takes a text variable:\ +# and displays "Python " followed by the value +# If no text is provided, use the default value "is cool" + + +@app.route('/python/', defaults={'text': 'is cool'}, strict_slashes=False) +@app.route('/python/', strict_slashes=False) +def python_text(text): + text = text.replace('_', ' ') # Replace underscores with spaces + return "Python " + text + +# Define a route that takes an integer:\ +# and displays a template if n is an integer + + +@app.route('/number_template/', strict_slashes=False) +def number_template(n): + return render_template('5-number.html', n=n) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/web_flask/6-number_odd_or_even.py b/web_flask/6-number_odd_or_even.py new file mode 100755 index 000000000000..96865dc368ce --- /dev/null +++ b/web_flask/6-number_odd_or_even.py @@ -0,0 +1,72 @@ +#!/usr/bin/python3 +"""Script that starts a Flask web application:\ + listening on 0.0.0.0, port 5000""" + +from flask import Flask, render_template + +app = Flask(__name__) + +# Define a route that displays "Hello HBNB!" + + +@app.route('/', strict_slashes=False) +def hello_hbnb(): + return "Hello HBNB!" + +# Define a route that displays "HBNB" + + +@app.route('/hbnb', strict_slashes=False) +def hbnb(): + return "HBNB" + +# Define a route that takes a text variable. + + +@app.route('/c/', strict_slashes=False) +def c_text(text): + text = text.replace('_', ' ') + return "C " + text + +# Define a route that takes a text variable. +# If no text is provided, use the default value "is cool" + + +@app.route('/python/', defaults={'text': 'is cool'}, strict_slashes=False) +@app.route('/python/', strict_slashes=False) +def python_text(text): + text = text.replace('_', ' ') # Replace underscores with spaces + return "Python " + text + +# Define a route that takes an integer: +# and displays "n is a number" only if n is an integer + + +@app.route('/number/', strict_slashes=False) +def number(n): + return "{} is a number".format(n) + +# Define a route that takes an integer and: +# displays a template if n is an integer + + +@app.route('/number_template/', strict_slashes=False) +def number_template(n): + return render_template('6-number.html', n=n) + +# Define a route that takes an integer: +# and displays whether it's even or odd in a template + + +@app.route('/number_odd_or_even/', strict_slashes=False) +def number_odd_or_even(n): + if n % 2 == 0: + even_or_odd = "even" + else: + even_or_odd = "odd" + return render_template('6-number_odd_or_even.html', + n=n, even_or_odd=even_or_odd) + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) diff --git a/web_flask/7-states_list.py b/web_flask/7-states_list.py new file mode 100755 index 000000000000..a1afa41c8585 --- /dev/null +++ b/web_flask/7-states_list.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +"""__init__ - initializes the Flask web application""" +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_list(): + """Displays a list of all State objects sorted by name""" + states = storage.all(State).values() + states_sorted = sorted(states, key=lambda x: x.name) + return render_template('7-states_list.html', states=states_sorted) + + +@app.teardown_appcontext +def teardown(exception): + """Closes the 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 100755 index 000000000000..e48e48be04e5 --- /dev/null +++ b/web_flask/8-cities_by_states.py @@ -0,0 +1,26 @@ +#!/usr/bin/python3 +"""__init__ - initializes the Flask web application""" +from flask import Flask, render_template +from models import storage +from models.state import State + +app = Flask(__name__) + + +@app.route('/cities_by_states', strict_slashes=False) +def display_cities_by_states(): + """Displays a list of states and their cities""" + states = storage.all(State).values() + states_sorted = sorted(states, key=lambda x: x.name) + return render_template('8-cities_by_states.html', states=states_sorted) + + +@app.teardown_appcontext +def teardown(exception): + """Closes the SQLAlchemy Session""" + storage.close() + + +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 100755 index 000000000000..0e1e020cd50b --- /dev/null +++ b/web_flask/9-states.py @@ -0,0 +1,36 @@ +#!/usr/bin/python3 +"""__init__ - initializes the Flask web application""" +from flask import Flask, render_template +from models import storage +from models.state import State +from models.city import City + +app = Flask(__name__) + + +@app.route('/states', strict_slashes=False) +def display_states(): + """Displays a list of all State objects""" + states = storage.all(State).values() + states_sorted = sorted(states, key=lambda x: x.name) + return render_template('9-states.html', states=states_sorted) + + +@app.route('/states/', strict_slashes=False) +def display_cities_by_state(state_id): + """Displays cities of a specific State""" + state = storage.get(State, state_id) + if state: + return render_template('9-states.html', state=state) + return render_template('9-states.html', not_found=True) + + +@app.teardown_appcontext +def teardown(exception): + """Closes the SQLAlchemy Session""" + storage.close() + + +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 100755 index 000000000000..5eb09635035e --- /dev/null +++ b/web_flask/README.md @@ -0,0 +1,33 @@ +# 0x04. AirBnB clone - Web framework + +## Requirements + +### Python Scripts +- Allowed editors: vi, vim, emacs +- All files will be interpreted/compiled on Ubuntu 20.04 LTS using python3 (version 3.4.3) +- All files should end with a new line +- The first line of all files should be exactly `#!/usr/bin/python3` +- A README.md file, at the root of the folder of the project, is mandatory +- Code should use the PEP 8 style (version 1.7) +- All files must be executable +- The length of files will be tested using wc +- All modules should have documentation (`python3 -c 'print(__import__("my_module").__doc__)'`) +- All classes should have documentation (`python3 -c 'print(__import__("my_module").MyClass.__doc__)'`) +- All functions (inside and outside a class) should have documentation (`python3 -c 'print(__import__("my_module").my_function.__doc__)'` and `python3 -c 'print(__import__("my_module").MyClass.my_function.__doc__)'`) +- Documentation should be a real sentence explaining the purpose of the module, class, or method (the length of it will be verified) + +### HTML/CSS Files +- Allowed editors: vi, vim, emacs +- All files should end with a new line +- A README.md file at the root of the folder of the project is mandatory +- Code should be W3C compliant and validate with W3C-Validator (except for jinja template) +- All CSS files should be in the styles folder +- All images should be in the images folder +- You are not allowed to use `!important` or id (`#...` in the CSS file) +- All tags must be in uppercase +- Current screenshots have been done on Chrome 56.0.2924.87. + +## Install Flask +```bash +$ pip3 install Flask + diff --git a/web_flask/__init__.py b/web_flask/__init__.py new file mode 100755 index 000000000000..4188c74ca73a --- /dev/null +++ b/web_flask/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/python3 +"""__init__ - initializes the Flask web application""" +from flask import Flask + +app = Flask(__name__) + + +@app.route('/', strict_slashes=False) +def hello_hbnb(): + """Displays 'Hello HBNB!'""" + return 'Hello HBNB!' + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000) + diff --git a/web_flask/static/3-footer.css b/web_flask/static/3-footer.css new file mode 100755 index 000000000000..34e315c265f8 --- /dev/null +++ b/web_flask/static/3-footer.css @@ -0,0 +1,12 @@ +/* Styles for footer */ +.footer { + background-color: #333; + color: #fff; + text-align: center; + padding: 10px 0; +} + +.footer_title { + font-size: 14px; +} + diff --git a/web_flask/static/3-header.css b/web_flask/static/3-header.css new file mode 100755 index 000000000000..20daddb854a9 --- /dev/null +++ b/web_flask/static/3-header.css @@ -0,0 +1,46 @@ +/* Styles for header */ +.logo { + float: left; + font-size: 24px; +} + +.filters { + float: right; + margin-top: 15px; +} + +.dropdown { + display: inline-block; + margin-right: 10px; +} + +.dropdown-toggle { + background-color: #007bff; + color: #fff; + border: none; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; +} + +.dropdown-toggle:hover { + background-color: #0056b3; +} + +.dropdown-menu { + background-color: #f9f9f9; + padding: 5px 0; + border: 1px solid #ddd; + border-radius: 5px; + box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); +} + +.dropdown-menu li { + padding: 5px 20px; + cursor: pointer; +} + +.dropdown-menu li:hover { + background-color: #ddd; +} + diff --git a/web_flask/static/4-common.css b/web_flask/static/4-common.css new file mode 100755 index 000000000000..95e5953731cd --- /dev/null +++ b/web_flask/static/4-common.css @@ -0,0 +1,12 @@ +/* Common styles */ +.clearfix::after { + content: ""; + clear: both; + display: table; +} + +.container { + width: 80%; + margin: auto; +} + diff --git a/web_flask/static/6-filters.css b/web_flask/static/6-filters.css new file mode 100755 index 000000000000..633b2a8b779a --- /dev/null +++ b/web_flask/static/6-filters.css @@ -0,0 +1,6 @@ +/* Styles for filters */ +.dropdown-menu { + max-height: 300px; + overflow-y: auto; +} + diff --git a/web_flask/static/8-places.css b/web_flask/static/8-places.css new file mode 100755 index 000000000000..6ef06ff71e84 --- /dev/null +++ b/web_flask/static/8-places.css @@ -0,0 +1,6 @@ +/* CSS code for places section styling */ +.place-price { + position: absolute; + top: 0; + right: 0; +} diff --git a/web_flask/templates/10-hbnb_filters.html b/web_flask/templates/10-hbnb_filters.html new file mode 100755 index 000000000000..a0e239501b1a --- /dev/null +++ b/web_flask/templates/10-hbnb_filters.html @@ -0,0 +1,58 @@ + + + + HBNB + + + + + + +
+ +
+ + + +
+
+
+
+ +
+
+ + + diff --git a/web_flask/templates/100-hbnb.html b/web_flask/templates/100-hbnb.html new file mode 100755 index 000000000000..583154223985 --- /dev/null +++ b/web_flask/templates/100-hbnb.html @@ -0,0 +1,66 @@ + + + + + + HBNB + + + + + + +
+ + +
+
+

Places

+
+

States

+
+
    + {% for state in states %} +
  • {{ state.name }}
  • + {% endfor %} +
+
+

Amenities

+
+
    + {% for amenity in amenities %} +
  • {{ amenity.name }}
  • + {% endfor %} +
+
+ +
+
+
+ {% for place in places %} +
+
+

{{ place.name }}

+
${{ place.price_by_night }}
+
+
+
{{ place.max_guest }} Guest{% if place.max_guest != 1 %}s{% endif %}
+
{{ place.number_rooms }} Bedroom{% if place.number_rooms != 1 %}s{% endif %}
+
{{ place.number_bathrooms }} Bathroom{% if place.number_bathrooms != 1 %}s{% endif %}
+
+
{{ place.description }}
+
+ {% endfor %} +
+
+ +
+ + + diff --git a/web_flask/templates/5-number.html b/web_flask/templates/5-number.html new file mode 100755 index 000000000000..82e9bd99bfbc --- /dev/null +++ b/web_flask/templates/5-number.html @@ -0,0 +1,9 @@ + + + + HBNB + + +

Number: {{ n }} is {{ even_or_odd }}

+ + 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 100755 index 000000000000..82e9bd99bfbc --- /dev/null +++ b/web_flask/templates/6-number_odd_or_even.html @@ -0,0 +1,9 @@ + + + + HBNB + + +

Number: {{ n }} is {{ even_or_odd }}

+ + diff --git a/web_flask/templates/7-states_list.html b/web_flask/templates/7-states_list.html new file mode 100755 index 000000000000..2dd8e5d12107 --- /dev/null +++ b/web_flask/templates/7-states_list.html @@ -0,0 +1,15 @@ + + + + HBNB + + +

States

+
    + {% for state in states %} +
  • {{ 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 100755 index 000000000000..ae8482650859 --- /dev/null +++ b/web_flask/templates/8-cities_by_states.html @@ -0,0 +1,21 @@ + + + + HBNB + + +

States

+
    + {% for state in states %} +
  • {{ state.id }}: {{ state.name }} +
      + {% for city in state.cities %} +
    • {{ 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 100755 index 000000000000..028307fe0d05 --- /dev/null +++ b/web_flask/templates/9-states.html @@ -0,0 +1,28 @@ + + + + HBNB + + + {% if not_found %} +

Not found!

+ {% else %} +

States

+
    + {% for state in states %} +
  • {{ state.id }}: {{ state.name }}
  • + {% endfor %} +
+ {% endif %} + {% if state %} +

State: {{ state.name }}

+

Cities:

+
    + {% for city in state.cities %} +
  • {{ city.id }}: {{ city.name }}
  • + {% endfor %} +
+ {% endif %} + + + diff --git a/web_flask/wsgi.py b/web_flask/wsgi.py new file mode 100755 index 000000000000..ce9958ab2f15 --- /dev/null +++ b/web_flask/wsgi.py @@ -0,0 +1,8 @@ +#!/usr/bin/python3 +"""WSGI entry point for Gunicorn""" + +from 0-hello_route import app + +if __name__ == "__main__": + app.run() +