def run_tests(): """Test MovieCollection class.""" # Test empty MovieCollection (defaults) print("Test empty MovieCollection:") movie_collection = MovieCollection() print(movie_collection) assert not movie_collection.movies # an empty list is considered False # Test loading movies print("Test loading movies:") movie_collection.load_movies('movies.csv') print(movie_collection) assert movie_collection.movies # assuming CSV file is non-empty, non-empty list is considered True # Test adding a new Movie with values print("Test adding new movie:") movie_collection.add_movie(Movie("Chinatown", 1974, "Film Noir", False)) print(movie_collection) # Test sorting movies print("Test sorting - year:") sorted_list = movie_collection.sort_movies("year", desc=True) print(MovieCollection.format_data(sorted_list)) # TODO: Add more sorting tests # TODO: Test saving movies (check CSV file manually to see results) print("Test saving movie:") movie_collection.save_movies('movies.csv') # TODO: Add more tests, as appropriate, for each method print("Test unwatched count:") print(movie_collection.count_unwatch()) print("Test watched count:") print(movie_collection.count_watch()) # Add filter method print('Test filter method:') movie_collection.search_movies('star', 1977, 'action') print(MovieCollection.format_data(movie_collection.search_list))
class MoviesToWatchApp(App): """Watchmovies app GUI version""" sort_by = StringProperty() category = ListProperty() order = ListProperty() current_order = StringProperty() def __init__(self, **kwargs): """Creat the core of Watchmovies app""" Window.size = (900, 700) super(MoviesToWatchApp, self).__init__(**kwargs) self.my_collection = MovieCollection() self.movie_to_show = [] def build(self): """Build kivy GUI file""" self.title = "Movie To Watch 2.0 by JialeTang " self.root = Builder.load_file('app.kv') # Setting category lists self.category = ['Title', 'Year', 'Category'] self.sort_by = self.category[0] return self.root def on_start(self): self.my_collection.load_movies('movies.csv') self.movie_to_show = self.my_collection.movies self.root.ids.movie_list.bind( minimum_height=self.root.ids.movie_list.setter('height')) # Show the message self.root.ids.message.text = 'Let\'s watch some movies :)' # Load movies self.load_movies() # Showing watched or unwatch self.count_watch() def on_stop(self): """Saving the data to csv when close the app""" self.my_collection.save_movies('movies.csv') def count_watch(self): self.root.ids.watch_count.text = 'To watch: {}. Watched: {}'.format( self.my_collection.count_unwatch(), self.my_collection.count_watch()) def sort_movies(self, key): """Sort movie based on key""" self.sort_by = key self.load_movies() def handle_order(self, element): """Sort movie based on order""" self.current_order = element self.load_movies() def handle_add_movie(self, title, year, category): """Add movies to the movies list""" # Only add movie when title, year, category are provided if title and year and category: title_check = self.handle_input(title, is_title=True) category_check = self.handle_input(category, is_category=True) year_check = self.handle_input(year, is_year=True) if year_check and category_check and title_check: clean_title = ' '.join(title.split()) pretty_title = capwords(clean_title) if self.check_exist(title_check, year_check, category_check): self.show_popup_message('The movie is already exist') else: self.my_collection.add_movie( Movie(title_check, year_check, category_check)) self.load_movies() self.show_popup_message( '{} have been add to movie list'.format(pretty_title)) self.handle_clear_button(is_add=True) else: # Showing error message if any field is blank self.show_popup_message('All fields are required') def load_movies(self): self.root.ids.movie_list.clear_widgets() desc = self.current_order == 'Descending Order' self.movie_to_show = self.my_collection.sort_movies(self.sort_by, desc) for index, movie in enumerate(self.movie_to_show): watch_mark = 'watched' if movie.is_watched else '' btn = Button(text='{} ({} from {}) {}'.format( movie.title, movie.category, movie.year, watch_mark), size_hint_y=None, height=200) btn.movie = movie btn.bind(on_press=self.handle_watch_movie) # background color if watch_mark: btn.background_color = (0.5, 0.25, 1.0, 1.0) else: btn.background_color = (1, 0, 0, 5) self.root.ids.movie_list.add_widget(btn) def handle_watch_movie(self, instance): """Handle watch movie if user click on movie""" current_movie = instance.movie current_movie.is_watched = not current_movie.is_watched self.load_movies() watch_mark = 'watched' if current_movie.is_watched else 'unwatched' self.root.ids.message.text = 'You have {} {}'.format( watch_mark, current_movie.title) self.count_watch() def show_popup_message(self, text): """show popup message""" self.root.ids.popup_message.text = text self.root.ids.popup.open() def handle_close_popup(self): """Close the popup""" self.root.ids.popup_message.text = '' self.root.ids.popup.dismiss() def handle_clear_button(self, is_add=False, is_search=False): """Clear input when pressed""" if is_add: self.root.ids.title.text = '' self.root.ids.year.text = '' self.root.ids.category.text = '' elif is_search: self.root.ids.title_search.text = '' self.root.ids.year_search.text = '' self.root.ids.category_search.text = '' self.root.ids.watch_search.text = '' # Else clear all else: self.root.ids.title.text = '' self.root.ids.year.text = '' self.root.ids.category.text = '' self.root.ids.title_search.text = '' self.root.ids.year_search.text = '' self.root.ids.category_search.text = '' self.root.ids.watch_search.text = '' def check_exist(self, title, year, category): """Check if movie is existed""" def find_duplicate(movie): filter_title = title.lower() == movie.title.lower() filter_year = int(movie.year) == int(year) filter_category = movie.category.lower() == category.lower() return filter_title and filter_year and filter_category return list(filter(find_duplicate, self.my_collection.movies)) def handle_input(self, input_data, is_title=False, is_year=False, is_category=False, is_watch=False, blank=False): """Handle input data""" if blank and not input_data: return True else: if is_year: try: year = int(input_data) if year < 0: raise ValueError() return input_data.strip() except ValueError: self.show_popup_message( 'Your year must be a number and greater than 0') elif is_category: if input_data.lower().strip() not in [ 'action', 'comedy', 'documentary', 'drama', 'fantasy', 'thriller' ]: self.show_popup_message( 'Please enter a correct category ' '(Action, Comedy, Documentary, Drama, Fantasy, Thriller)' ) else: return input_data.strip() elif is_watch: if input_data.lower() not in ['y', 'n']: self.show_popup_message('Your watch field must be Y or N') else: return True elif not input_data.strip() and is_title: self.show_popup_message('Your title must not be blank') else: return input_data.strip()
class MoviesToWatchApp(App): """Class for GUI movie app""" # Initiate string and list properties sort_by = StringProperty() category = ListProperty() order = ListProperty() current_order = StringProperty() def __init__(self, **kwargs): """Constructor that initiate my collection and movie list""" super(MoviesToWatchApp, self).__init__(**kwargs) self.my_collection = MovieCollection() # Create another list to not TOUCH the original list, in case for back-up or error self.movie_to_show = [] def build(self): """Build from the root kv file""" Window.size = (1000, 800) self.title = "Movie To Watch 2.0 by Van Phuong Nguyen" self.root = Builder.load_file('app.kv') # Set category list self.category = ['Title', 'Year', 'Category', 'Unwatch'] # Default sorting option self.sort_by = self.category[0] # Set order list self.order = ['Ascending Order', 'Descending Order'] # Set default order self.current_order = self.order[0] return self.root def on_start(self): """Start when program start""" self.my_collection.load_movies('movies.csv') # Separate movie list to not change to original list self.movie_to_show = self.my_collection.movies # Make sure the height is such that there is something to scroll. self.root.ids.movie_list.bind( minimum_height=self.root.ids.movie_list.setter('height')) # Show welcome message self.root.ids.message.text = 'Let\'s watch some movies :)' # Load movies self.load_movies() # Show watch and unwatch count self.count_watch() def on_stop(self): """Close program and save movies to the file""" self.my_collection.save_movies('movies.csv') def count_watch(self): """Count movie based on watch and unwatch""" self.root.ids.watch_count.text = 'To watch: {}. Watched: {}'.format( self.my_collection.count_unwatch(), self.my_collection.count_watch()) def sort_movies(self, key): """Sort movie based on key""" # Change current sort_by key based on the key from the sort spinner self.sort_by = key # Load movies based on key provided self.load_movies() def handle_order(self, element): """Sort movie based on order""" # Change current order for loading self.current_order = element # Load movie based on current order self.load_movies() def handle_add_movie(self, title, year, category): """Add movie to movie list""" # Only add movie when title, year, category are provided if title and year and category: # Make sure that title input is correct title_check = self.handle_input(title, is_title=True) # Make sure that category is on category list category_check = self.handle_input(category, is_category=True) # Make sure that year is a number >= 0 year_check = self.handle_input(year, is_year=True) if year_check and category_check and title_check: # Make the input prettier clean_title = ' '.join(title.split()) pretty_title = capwords(clean_title) pretty_category = capwords(category) # Check if movie is already exist if self.check_exist(title_check, year_check, category_check): self.show_popup_message('The movie is already exist') else: # Add movie to list, then reload movie list self.my_collection.add_movie( Movie(title_check, year_check, category_check)) self.load_movies() self.show_popup_message( '{} have been add to movie list'.format(pretty_title)) self.handle_clear_button(is_add=True) else: # Show error if any field blank self.show_popup_message('All fields are required') def load_movies(self): """Load movie to the GUI movie list""" # First clear the current movie on list self.root.ids.movie_list.clear_widgets() # Check the current order desc = self.current_order == 'Descending Order' # Add movies based on current sort_by and order to movie to show list self.movie_to_show = self.my_collection.sort_movies(self.sort_by, desc) # Add buttons based on movie list for index, movie in enumerate(self.movie_to_show): watch_mark = 'watched' if movie.is_watched else '' btn = Button(text='{} ({} from {}) {}'.format( movie.title, movie.category, movie.year, watch_mark), size_hint_y=None, height=30) # Save movie object to btn btn.movie = movie # If pressed, execute handle_watch_movie function btn.bind(on_press=self.handle_watch_movie) # If movie is watched, change background color if watch_mark: btn.background_color = (1, 0.5, 0.5, 1) # Add btn to movie_list id self.root.ids.movie_list.add_widget(btn) def handle_watch_movie(self, instance): """Handle watch movie if user click on movie""" # Movie object is saved to btn.movie >> instance.movie current_movie = instance.movie # Toggle between watch/unwatch current_movie.is_watched = not current_movie.is_watched # Load movie to the GUI list for immediate sorting self.load_movies() # Show message and reload the count watch watch_mark = 'watched' if current_movie.is_watched else 'unwatched' self.root.ids.message.text = 'You have {} {}'.format( watch_mark, current_movie.title) self.count_watch() def show_popup_message(self, text): """Handle show popup message""" self.root.ids.popup_message.text = text self.root.ids.popup.open() def handle_close_popup(self): """Close the popup""" self.root.ids.popup_message.text = '' self.root.ids.popup.dismiss() def handle_clear_button(self, is_add=False, is_search=False): """Clear input when pressed""" # If is_add, clear the add movie input if is_add: self.root.ids.title.text = '' self.root.ids.year.text = '' self.root.ids.category.text = '' # If is_search, clear the search input elif is_search: self.root.ids.title_search.text = '' self.root.ids.year_search.text = '' self.root.ids.category_search.text = '' self.root.ids.watch_search.text = '' # Else clear all else: self.root.ids.title.text = '' self.root.ids.year.text = '' self.root.ids.category.text = '' self.root.ids.title_search.text = '' self.root.ids.year_search.text = '' self.root.ids.category_search.text = '' self.root.ids.watch_search.text = '' def handle_search(self, title, year, category, watch): """Search for movie in list""" # Only search when at least on field is provided if title or category or watch or year: # Check for valid year, category and title title_check = self.handle_input(title, is_title=True, blank=True) year_check = self.handle_input(year, is_year=True, blank=True) category_check = self.handle_input(category, is_category=True, blank=True) watch_check = self.handle_input(watch, is_watch=True, blank=True) # If all are valid, then search if title_check and year_check and category_check and watch_check: # If is_watched is provided, change to bool, else None is_watched = None if watch: is_watched = watch.lower() == 'y' # Search movie method self.my_collection.search_movies(title.strip(), year, category, is_watched) # If found, show movie count and display in GUI movie list if self.my_collection.search_list: self.movie_to_show = self.my_collection.search_list self.show_popup_message('We have found: {} movies'.format( len(self.movie_to_show))) self.load_movies() # Clear the input when completed self.handle_clear_button(is_search=True) # If no movie found, show error message else: self.show_popup_message('No movie found!') else: # If no field is fill in, show error message self.show_popup_message('Your must at least fill in one field') def handle_clear_search(self): """Clear the search and return the original list""" # Set search list to empty self.my_collection.search_list = [] self.handle_clear_button(is_search=True) self.load_movies() def check_exist(self, title, year, category): """Check if movie is existed""" # Filter method based on title, year, category def find_duplicate(movie): filter_title = title.lower() == movie.title.lower() filter_year = int(movie.year) == int(year) filter_category = movie.category.lower() == category.lower() return filter_title and filter_year and filter_category return list(filter(find_duplicate, self.my_collection.movies)) def handle_input(self, input_data, is_title=False, is_year=False, is_category=False, is_watch=False, blank=False): """Handle input data""" # Check if year > 0 and is a number if blank and not input_data: return True else: if is_year: try: year = int(input_data) if year < 0: raise ValueError() return input_data.strip() except ValueError: self.show_popup_message( 'Your year must be a number and greater than 0') # Check if input in category list elif is_category: # Check if category is in the category list if input_data.lower().strip() not in [ 'action', 'comedy', 'documentary', 'drama', 'fantasy', 'thriller' ]: self.show_popup_message( 'Please enter a correct category ' '(Action, Comedy, Documentary, Drama, Fantasy, Thriller)' ) else: return input_data.strip() # Check if valid watch elif is_watch: if input_data.lower() not in ['y', 'n']: self.show_popup_message('Your watch field must be Y or N') else: return True # Check if title is blank elif not input_data.strip() and is_title: self.show_popup_message('Your title must not be blank') else: return input_data.strip()