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()
Example #3
0
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()