diff --git a/.github/workflows/codecoverage.yml b/.github/workflows/codecoverage.yml
index bf26a4e19..15fc1e8c7 100644
--- a/.github/workflows/codecoverage.yml
+++ b/.github/workflows/codecoverage.yml
@@ -8,29 +8,25 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v2
+ uses: actions/checkout@v3
- name: Set up Python
- uses: actions/setup-python@v2
+ uses: actions/setup-python@v3
with:
- python-version: 3.8 # Replace '3.x' with your desired Python version
+ python-version: 3.10.12 # Replace '3.x' with your desired Python version
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install pylint
- pip install pandas
- pip install -U Flask
- pip install -U flask-cors
- pip install pytest
- pip install pytest-cov
+ pip install -r requirements.txt
- name: Run tests and generate coverage
run: |
- cd test
- coverage run --source=test -m pytest test_*.py
+ cd app
+ coverage run --source=test -m pytest test/test_*.py
coverage xml -i
- name: Upload coverage report to Codecov
- run: |
- bash <(curl -s https://codecov.io/bash) -t ${{ secrets.CODECOV_TOKEN }}
+ uses: codecov/codecov-action@v3
+ env:
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml
index 3fc27ea95..5c539a13c 100644
--- a/.github/workflows/pylint.yml
+++ b/.github/workflows/pylint.yml
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.8", "3.9", "3.10"]
+ python-version: ["3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
@@ -17,10 +17,15 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install pylint
+ pip install -r requirements.txt
+ pip install flask
+ pip install flask_sqlalchemy
+ pip install flask_login
+ pip install flask_cors
pip install pandas
- pip install -U Flask
- pip install -U flask-cors
+ pip install numpy
+ pip install pylint
- name: Analysing the code with pylint
run: |
- pylint $(git ls-files '*.py')
+ cd app
+ pylint src
\ No newline at end of file
diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml
index 494dbd17f..bd9199998 100644
--- a/.github/workflows/unittest.yml
+++ b/.github/workflows/unittest.yml
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: ["3.8"]
+ python-version: ["3.10"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
@@ -17,14 +17,17 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
- pip install pylint
- pip install pandas
- pip install -U Flask
- pip install -U flask-cors
+ pip install -r requirements.txt
- name: Running test cases for predicting
- run: python test/test_predict.py
+ run: |
+ cd app
+ python test/test_predict.py
- name: Running test cases for searching
- run: python test/test_search.py
+ run: |
+ cd app
+ python test/test_search.py
- name: Running test cases for util
- run: python test/test_util.py
+ run: |
+ cd app
+ python test/test_util.py
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fe804a4da..91cb46221 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,8 +1,6 @@
## Contributing:
-* If you would like to contribute and collaborate with this repository, then please inform us through email at popcornpicks504@gmail.com.
-
-## Purpose of Contributing:
+### Purpose of Contributing:
* To develop our product and take it to the next level.
* To enhance the already existing feature.
@@ -13,46 +11,46 @@
* Help us to scale the system to a larger database
* To run the email notifier feature - make your own Gmail account and password and replace the same in the utils.py(function: send_email_to_user(recipient_email, categorized_data))
-## Our Table Of Contents
-
-- .github/workflows
- - all GitHub workflow YAML files
-- proj2
- - score_card.md
-
-- src
- - prediction_scripts
- - item_based.py
- - recommenderapp
- - static
- - script.js
- - stylesheet.css
- - templates
- - landing_page.html
- - search_page.html
- - success.html
- - app.py
- - search.py
- - utils.py
-
-- test
- - test_predict.py
- - test_search.py
- - test_util.py
-
-- .gitignore
-
-- CODE_OF_CONDUCT.md
-
-- CONTRIBUTING.md
-
-- LICENSE
-
-- README.md
-
-- requirements.txt
-
-- setup.py
+### Our Table Of Contents
+```
+.
+├── app
+│ ├── init_db.py
+│ ├── instance
+│ │ └── site.db
+│ ├── run.py
+│ ├── src
+│ │ ├── __init__.py
+│ │ ├── item_based.py
+│ │ ├── models.py
+│ │ ├── __pycache__
+│ │ ├── routes.py
+│ │ ├── search.py
+│ │ ├── static
+│ │ ├── templates
+│ │ └── utils.py
+│ └── test
+│ ├── __init__.py
+│ ├── __pycache__
+│ ├── test_predict.py
+│ ├── test_search.py
+│ └── test_util.py
+├── asset
+│ ├── demo.gif
+│ ├── email.png
+│ ├── execution.gif
+│ ├── group12.png
+│ ├── header_display.png
+│ ├── system_architecture.svg
+│ └── SystemArch.png
+├── CODE_OF_CONDUCT.md
+├── CONTRIBUTING.md
+├── data
+│ └── movies.csv
+├── LICENSE
+├── README.md
+└── requirements.txt
+```
## Suggesting Enhancements
@@ -89,7 +87,7 @@ Any suggested enhancements like adding new features or improving existing functi
## Code of Conduct:
-* Please go through the [Code of Conduct](https://github.com/adipai/PopcornPicks/blob/master/CODE_OF_CONDUCT.md) before you begin contributing. This project and everyone participating in it is governed by the Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to popcornpicks504@gmail.com.
+* Please go through the [Code of Conduct](https://github.com/tanmaypardeshi/PopcornPicks/blob/master/CODE_OF_CONDUCT.md) before you begin contributing. This project and everyone participating in it is governed by the Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to popcornpicks504@gmail.com.
## Pull Request Submission Guidelines
diff --git a/LICENSE b/LICENSE
index 05ec1acee..40ceab259 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2023 Aditya Pai, Ananya Mantravadi, Rishi Singhal, Samarth Shetty
+Copyright (c) 2023 Abhinav Sinha, Chandana Ray, Sam Kwiatkowski-Martin, Tanmay Pardeshi
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -15,7 +15,7 @@ copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- ORIGINAL AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
- ARISING FROM, OUT OF, OR IN CONNECTION WITH THE ORIGINAL SOFTWARE OR THE USE
- OR OTHER DEALINGS IN THE ORIGINAL SOFTWARE OR THE NEW EXTENSIONS.
\ No newline at end of file
+ORIGINAL AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE,
+ARISING FROM, OUT OF, OR IN CONNECTION WITH THE ORIGINAL SOFTWARE OR THE USE
+OR OTHER DEALINGS IN THE ORIGINAL SOFTWARE OR THE NEW EXTENSIONS.
\ No newline at end of file
diff --git a/README.md b/README.md
index 405bcd68f..b4b5ed9d4 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,21 @@
# PopcornPicks🍿: Your Destination for Movie Recommendations
-[](https://gitHub.com/adipai/PopcornPicks/graphs/commit-activity) [](https://github.com/adipai/PopcornPicks/pulse) [](https://github.com/adipai/PopcornPicks/issues?q=is%3Aopen+is%3Aissue) [](https://github.com/adipai/PopcornPicks/issues?q=is%3Aissue+is%3Aclosed)  [](http://makeapullrequest.com) [](https://opensource.org/licenses/MIT) [](https://doi.org/10.5281/zenodo.10023130) [](https://github.com/adipai/PopcornPicks/actions/workflows/unittest.yml) [](https://codecov.io/gh/adipai/PopcornPicks) [](https://GitHub.com/adipai/PopcornPicksreleases/) [](https://github.com/pylint-dev/pylint) [](https://hits.dwyl.com/adipai/PopcornPicks)   
-
-
-
-PopcornPicks is more than just a movie recommender system; it's a gateway to a world of cinematic adventures. With an ever-expanding library of films and a powerful recommendation algorithm, PopcornPicks is here to transform the way you discover, enjoy, and connect with movies.
+# [Check our Wiki Page for detailed information about the project, system architecture, installation steps, guides and a lot more!](https://github.com/tanmaypardeshi/CSC-510-Project3-PopcornPicks/wiki)
-# Contents
+[](https://gitHub.com/tanmaypardeshi/PopcornPicks/graphs/commit-activity) [](https://github.com/tanmaypardeshi/PopcornPicks/pulse) [](https://github.com/tanmaypardeshi/PopcornPicks/issues?q=is%3Aopen+is%3Aissue) [](https://github.com/tanmaypardeshi/PopcornPicks/issues?q=is%3Aissue+is%3Aclosed)  [](http://makeapullrequest.com) [](https://opensource.org/licenses/MIT) [](https://doi.org/10.5281/zenodo.10211655) [](https://github.com/tanmaypardeshi/PopcornPicks/actions/workflows/unittest.yml) [](https://codecov.io/gh/tanmaypardeshi/CSC-510-Project3-PopcornPicks)[](https://GitHub.com/tanmaypardeshi/PopcornPicks/releases/) [](https://github.com/pylint-dev/pylint) [](https://hits.dwyl.com/tanmaypardeshi/PopcornPicks)   
+
+
+PopcornPicks is more than just a movie recommender system; it's a gateway to a world of cinematic adventures. With an ever-expanding library of films, a powerful recommendation algorithm, an all-new chat room, a new state-of-the-art account system, a newly added robust review system, and more, PopcornPicks is here to transform the way you discover, enjoy, and connect with movies.
+
+# Contents
- [Why use PopcornPicks?](#why-use-popcornpicks)
- [Project Documentation](#documentation)
- [Project Presentation Video](#project-presentation-video)
- [Brief Overview of Project](#project-description)
- [Core Applications of the Project](#core-applications-of-the-project)
- - [Recommendation Mechanism](#movie-recommendation-mechanism)
- - [Email Notifier](#email-notifier)
+ - [Recommendation Mechanism](#movie-recommendation-mechanism)
+ - [Email Notifier](#email-notifier)
- [Improvements Made in the Project](#project-2-delta)
- [TechStack Used for the Development of Project](#tech-stack-used)
- [Steps for Execution](#getting-started)
@@ -27,47 +28,70 @@
## Why use PopcornPicks?
-PopcornPicks: Your movie recommender! Input movies, get tailored suggestions, and share via email. Elevate your movie choices effortlessly!
+**PopcornPicks: Your personalized movie guide!**
- **Efficient:** Lightning-fast recommendations for movie buffs! 🚀
- **Adaptable:** Tailor the recommendations to your taste.
- **Accessible:** Works across all platforms and shells.
- **Insightful:** Get movie insights at a glance.
-- **Comprehensive:** Supports a wide array of user-preferred movies.
-- **Simple:** Easy installation and setup – start discovering great movies in no time!"
+- **Comprehensive:** Includes the ability to review and chat about your favorite movies.
+- **Simple:** Easy installation and setup – start discovering great movies in no time!
+- **Robust:** Build your own account where you can store all the movies you've seen.
+
## Documentation
-Checkout for project documentation at our [wiki page](https://github.com/adipai/PopcornPicks/wiki)
+
+Checkout for project documentation at our [wiki page](https://github.com/tanmaypardeshi/PopcornPicks/wiki)
## Project Presentation Video
+
You can see the project presentation at - https://youtu.be/ymAo_zXfZ0c
## Project Description
-PopcornPicks is a user-friendly movie recommender that curates a tailored list of 10 movie predictions based on user-provided movie preferences. Users can input their favorite movies, and our algorithm refines recommendations based on feedback—Liked, Disliked, or Yet To Watch. Additionally, PopcornPicks offers the convenience of emailing the recommended movies, enhancing the movie-watching experience. For the system architecture and other details, please refer to our [wiki page](https://github.com/adipai/PopcornPicks/wiki)
+
+PopcornPicks is a user-friendly movie recommender that curates a tailored list of 10 movie predictions based on user-provided movie preferences. Users can input their favorite movies, and our algorithm refines recommendations based on feedback—Liked, Disliked, or Yet To Watch. Additionally, users can write reviews, save them, check out the latest movies out there and also check out the buzz going on about movies by viewing what others are writing for their reviews. For the system architecture and other details, please refer to our [wiki page](https://github.com/tanmaypardeshi/PopcornPicks/wiki)
## Core Applications of the Project
+### Engaging and Interactive UI
+
+
+### User Authentication
+
+#### Sign up: Users can register to PopcornPicks to get the best recommendations for their favourite movies and genres
+
+
+#### Login:
+
+
+### Personalised Profile Page for Users
+
+
+### Check Out New and Upcoming Movies
+
+
### Movie Recommendation Mechanism
-**The user selects upto 5 movies to get a tailored watchlist and provide feedback for the same**
-
-
+
+
+### Write Movie Reviews
+
-### Email Notifier
-**The user sends his/her movies feedback via an email (Notify Me button)**
-
-
-
-


-
-
-
+
+
@@ -93,49 +113,56 @@ Check out the significant changes that we made for Project 2 [here](https://gith
## Getting Started
- Step 1:
- Git Clone the Repository
-
- git clone https://github.com/adipai/PopcornPicks.git
-
- (OR) Download the .zip file on your local machine from the following link
-
- https://github.com/adipai/PopcornPicks
-
- Step 2:
- Follow the setup instructions in the wiki documentation
-
- https://github.com/adipai/PopcornPicks/wiki/Installation-and-setup
-
-
+Step 1:
+Git Clone the Repository
+
+ git clone https://github.com/tanmaypardeshi/PopcornPicks.git
+
+(OR) Download the .zip file on your local machine from the following link
+
+ https://github.com/tanmaypardeshi/PopcornPicks
+
+Step 2:
+Follow the setup instructions in the wiki documentation
+
+ https://github.com/tanmaypardeshi/PopcornPicks/wiki/Installation-and-setup
+
Finally, start enjoying personalized movie recommendations!
## Future Scope
-PopcornPicks is a dynamic project with endless possibilities for expansion and enhancement. Here are some exciting avenues for future development:
+PopcornPicks is a dynamic project with endless possibilities for expansion and enhancement. Here are some exciting avenues for future development:
-1. **User Profiles and Preferences**: Implement user profiles where users can save their preferences, ratings, and watch history for a more personalized movie discovery experience.
+1. **Enahncements on the User Profile**: Implement more advanced features to the user profile. For example, include edit
+or delete review option. Make validations for the duplicate reviews. These are a few examples of what can be enhanced in
+the future
2. **Integration with Streaming Services**: Integrate with popular streaming services to provide real-time availability information and seamless access to recommended movies.
-3. **Movie Reviews and Ratings**: Allow users to write and read movie reviews and ratings, fostering a community of film critics and enthusiasts.
-
+3. **Dynamic Chat Rooms**: The current design of the application supports only a single chat room which is global to the website. The chat rooms can be made dynamic to a movie.
+
4. **Improved Recommendation Algorithm**: Enhance the recommendation engine with more advanced machine learning models and collaborative filtering techniques to provide even more accurate and personalized movie suggestions.
-The future of PopcornPicks is full of potential, and we invite developers, movie lovers, and anyone passionate about cinema to join us in making this platform the ultimate movie companion.
+5. **Interactive Review System**: In this feature, users should be able to comment on other people's reviews so that the site can be more interactive and engaging for everyone
+
+6. **Page for TV Shows**: Add a seperate section for TV shows. This can include shows from all streaming websites and television. This can be a parallel to the movies section of the website.
+
+7. **Email Notifier**: Allow the user to send their favourite movie recommendations via email.
+
+8. **Wishlist Feature**: Implement a feature where users can wishlist the recommendations they like and make lists of movies based on their preferences.
+
+The future of PopcornPicks is full of potential, and we invite developers, movie lovers, and anyone passionate about cinema to join us in making this platform the ultimate movie companion.
## Contribute to the Project!
-Please refer to the [CONTRIBUTING.md](https://github.com/adipai/PopcornPicks/blob/master/CONTRIBUTING.md) if you want to contribute to the PopcornPicks source code. Follow all the guidelines mentioned in the same and raise a pull request, we would love to look at it ❤️❤️!
+Please refer to the [CONTRIBUTING.md](https://github.com/tanmaypardeshi/PopcornPicks/blob/master/CONTRIBUTING.md) if you want to contribute to the PopcornPicks source code. Follow all the guidelines mentioned in the same and raise a pull request, we would love to look at it ❤️❤️!
## Contributors
-[Aditya Pai Brahmavar](https://www.linkedin.com/in/adityapai16/)
-[Ananya Mantravadi](https://www.linkedin.com/in/ananya-mantravadi/)
-[Rishi Singhal](https://www.linkedin.com/in/rishi-singhal1101/)
-[Samarth Shetty](https://www.linkedin.com/in/samarthshetty09/)
-## Contact
-In case of any issues, please e-mail your queries to popcornpicks504@gmail.com or raise an issue on this repository.
+[Tanmay Pardeshi](https://github.com/tanmaypardeshi)
+[Chandana Ray](https://github.com/cray94)
+[Abhinav Sinha](https://github.com/abhinav110595)
+[Sam Kwiatkowski-Martin](https://github.com/ExtremeMachine12)
## Join the PopcornPicks Community:
@@ -145,5 +172,10 @@ Together, let's make PopcornPicks the ultimate movie companion!
PopcornPicks is more than just code; it's a passion for cinema, and we invite you to be a part of this exciting journey. Start exploring, sharing, and discovering movies like never before with PopcornPicks!
Let's make movie nights extraordinary together!
+## Contact Us
+
+If you have any concerns or questions related to PopcornPicks, you can reach out to us at popcornpickv2@gmail.com
+
## License
+
This project is under the MIT License.
diff --git a/app/init_db.py b/app/init_db.py
new file mode 100644
index 000000000..cbc6a01c2
--- /dev/null
+++ b/app/init_db.py
@@ -0,0 +1,3 @@
+from src import app, db
+with app.app_context():
+ db.create_all()
\ No newline at end of file
diff --git a/app/run.py b/app/run.py
new file mode 100644
index 000000000..3da4d5c7e
--- /dev/null
+++ b/app/run.py
@@ -0,0 +1,5 @@
+from src import app, socket
+
+if __name__ == '__main__':
+ socket.run(app=app, debug=True, port=8000, allow_unsafe_werkzeug=True)
+ #app.run(debug=True, port=8000)
\ No newline at end of file
diff --git a/app/src/__init__.py b/app/src/__init__.py
new file mode 100644
index 000000000..652cb4bd1
--- /dev/null
+++ b/app/src/__init__.py
@@ -0,0 +1,31 @@
+# pylint: disable=cyclic-import
+"""
+Copyright (c) 2023 Abhinav Sinha, Chandana Ray, Sam Kwiatkowski-Martin, Tanmay Pardeshi
+This code is licensed under MIT license (see LICENSE for details)
+
+@author: PopcornPicks
+"""
+from flask import Flask, render_template
+from flask_sqlalchemy import SQLAlchemy
+from flask_bcrypt import Bcrypt
+from flask_cors import CORS
+from flask_login import LoginManager
+from flask_socketio import SocketIO
+
+app = Flask(__name__)
+app.config['SECRET_KEY'] = '5791628bb0b13ce0c676dfde280ba245'
+app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
+db = SQLAlchemy(app)
+bcrypt = Bcrypt(app)
+socket = SocketIO(app)
+login_manager = LoginManager(app)
+login_manager.login_view = 'login'
+login_manager.login_message_category = 'info'
+cors = CORS(app, resources={
+ r"/*": {
+ "origins": "*"
+ }
+})
+
+#pylint: disable=wrong-import-position
+from src import routes
diff --git a/src/prediction_scripts/item_based.py b/app/src/item_based.py
similarity index 88%
rename from src/prediction_scripts/item_based.py
rename to app/src/item_based.py
index 40e677b6c..abc38f4cf 100644
--- a/src/prediction_scripts/item_based.py
+++ b/app/src/item_based.py
@@ -1,5 +1,5 @@
"""
-Copyright (c) 2023 Aditya Pai, Ananya Mantravadi, Rishi Singhal, Samarth Shetty
+Copyright (c) 2023 Abhinav Sinha, Chandana Ray, Sam Kwiatkowski-Martin, Tanmay Pardeshi
This code is licensed under MIT license (see LICENSE for details)
@author: PopcornPicks
@@ -50,7 +50,4 @@ def recommend_for_new_user(user_rating):
join_movies_and_recommendations.sort_values(
by="recommended", ascending=False, inplace=True
)
-
- return list(join_movies_and_recommendations["title"][:201]), \
- list(join_movies_and_recommendations["genres"][:201]), \
- list(join_movies_and_recommendations["imdb_id"][:201])
+ return join_movies_and_recommendations[:10]
diff --git a/app/src/models.py b/app/src/models.py
new file mode 100644
index 000000000..d408b1a29
--- /dev/null
+++ b/app/src/models.py
@@ -0,0 +1,62 @@
+"""
+Copyright (c) 2023 Abhinav Sinha, Chandana Ray, Sam Kwiatkowski-Martin, Tanmay Pardeshi
+This code is licensed under MIT license (see LICENSE for details)
+
+@author: PopcornPicks
+"""
+from flask_login import UserMixin
+from src import db, login_manager
+
+@login_manager.user_loader
+def load_user(user_id):
+ """
+ Function to get current user
+ """
+ return User.query.get(int(user_id))
+
+
+class User(db.Model, UserMixin):
+ """
+ User Model Table
+ """
+ id = db.Column(db.Integer, primary_key=True)
+ username = db.Column(db.String(20), unique=True, nullable=False)
+ first_name = db.Column(db.String(20), nullable=False)
+ last_name = db.Column(db.String(20), nullable=False)
+ email = db.Column(db.String(120), unique=True, nullable=False)
+ password = db.Column(db.String(60), nullable=False)
+ reviews = db.relationship('Review', backref='user_author', lazy=True)
+
+ def __repr__(self):
+ return f" {self.first_name} {self.last_name}"
+
+# pylint: disable=R0903
+class Movie(db.Model):
+ """
+ Movie Table
+ """
+ movieId = db.Column(db.Integer, primary_key=True, nullable=False)
+ title = db.Column(db.String(200), nullable=False)
+ runtime = db.Column(db.Integer, nullable=True)
+ overview = db.Column(db.Text, nullable=True)
+ genres = db.Column(db.String(500), nullable=False)
+ imdb_id = db.Column(db.String(20), nullable=False)
+ poster_path = db.Column(db.String(200), nullable=True)
+ reviews = db.relationship('Review', backref='movie_author', lazy=True)
+
+ def __repr__(self):
+ return f"{self.movieId} - {self.title}"
+
+# pylint: disable=R0903
+class Review(db.Model):
+ """
+ Review Table
+ """
+ review_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
+ review_text = db.Column(db.Text, nullable=False)
+ user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
+ movieId = db.Column(db.Integer, db.ForeignKey('movie.movieId'), nullable=False)
+
+ def __repr__(self):
+ return f"{self.user_id} - {self.movieId}"
+
\ No newline at end of file
diff --git a/app/src/routes.py b/app/src/routes.py
new file mode 100644
index 000000000..03869ac32
--- /dev/null
+++ b/app/src/routes.py
@@ -0,0 +1,346 @@
+"""
+Copyright (c) 2023 Abhinav Sinha, Chandana Ray, Sam Kwiatkowski-Martin, Tanmay Pardeshi
+This code is licensed under MIT license (see LICENSE for details)
+
+@author: PopcornPicks
+"""
+
+import json
+import os
+import requests
+
+from flask import render_template, url_for, redirect, request, jsonify
+from flask_login import login_user, current_user, logout_user, login_required
+from flask_socketio import emit
+from dotenv import load_dotenv
+from src import app, db, bcrypt, socket
+from src.search import Search
+from src.utils import beautify_feedback_data, send_email_to_user
+from src.item_based import recommend_for_new_user
+from src.models import User, Movie, Review
+
+
+
+
+load_dotenv()
+TMDB_API_KEY = os.getenv("TMDB_API_KEY")
+
+@app.route("/", methods={"GET"})
+@app.route("/home", methods={"GET"})
+def landing_page():
+ """
+ Renders the landing page with the user.
+ """
+ if current_user.is_authenticated:
+ return redirect(url_for('search_page'))
+ return render_template("landing_page.html")
+
+@app.route("/login", methods=["GET", "POST"])
+def login():
+ """
+ Login Page Flow
+ """
+ try:
+ # If user has already logged in earlier and has an active session
+ if current_user.is_authenticated:
+ return redirect(url_for('search_page'))
+ # If user has not logged in and a login request is sent by the user
+ if request.method == "POST":
+ username = request.form["username"]
+ password = request.form["password"]
+ user = User.query.filter_by(username=username).first()
+ # Successful Login
+ if user and bcrypt.check_password_hash(user.password, password):
+ login_user(user)
+ return redirect(url_for('search_page'))
+ # Invalid Credentials
+ show_message = True
+ message = "Invalid Credentials! Try again!"
+ return render_template("login.html", message=message, show_message=show_message)
+ # When the login page is hit
+ return render_template("login.html")
+ #pylint: disable=broad-except
+ except Exception as e:
+ print(f"Error is {e}")
+ return render_template('login.html', message=e, show_message=True)
+
+
+@app.route("/signup", methods=["GET", "POST"])
+def signup():
+ """
+ Signup Page Flow
+ """
+ username = ""
+ try:
+ # If user has already logged in earlier and has an active session
+ if current_user.is_authenticated:
+ return redirect(url_for('search_page'))
+ # If user has not logged in and a signup request is sent by the user
+ if request.method == "POST":
+ username = request.form['username']
+ first_name = request.form['first_name']
+ last_name = request.form['last_name']
+ email = request.form['email']
+ password = request.form['password']
+ hashed_password = bcrypt.generate_password_hash(password)
+ user = User(username=username, email=email, first_name=first_name,
+ last_name=last_name, password=hashed_password)
+ db.session.add(user)
+ db.session.commit()
+ login_user(user)
+ return redirect(url_for('search_page'))
+ # For GET method
+ return render_template('signup.html')
+ # If user already exists
+ #pylint: disable=broad-except
+ except Exception as e:
+ print(f"Error is {e}")
+ message = f"Username {username} already exists!"
+ return render_template('signup.html', message=message, show_message=True)
+
+@app.route("/profile_page", methods=["GET"])
+@login_required
+def profile_page():
+ """
+ Profile Page
+ """
+ reviews_objects = Review.query.filter_by(user_id=current_user.id).all()
+ reviews = []
+ for review in reviews_objects:
+ movie_object = Movie.query.filter_by(movieId=review.movieId).first()
+ obj = {
+ "title" : movie_object.title,
+ "runtime" : movie_object.runtime,
+ "overview" : movie_object.overview,
+ "genres" : movie_object.genres,
+ "imdb_id" : movie_object.imdb_id,
+ "review_text" : review.review_text
+ }
+ reviews.append(obj)
+ return render_template("profile.html", user=current_user, reviews=reviews, search=False)
+
+@app.route("/search_page")
+@login_required
+def search_page():
+ """
+ Search Page
+ """
+ if current_user.is_authenticated:
+ return render_template("search.html", user=current_user, search=True)
+ return redirect(url_for('landing_page'))
+
+@app.route("/chat")
+def chat_page():
+ """
+ Renders chat room page
+ """
+ if current_user.is_authenticated:
+ return render_template("movie_chat.html", user=current_user)
+ return redirect(url_for('landing_page'))
+
+@socket.on('connections')
+def show_connection(data):
+ """
+ Prints out if the connection to the chat page is successful
+ """
+ print('received message: ' + data)
+
+@socket.on('message')
+def broadcast_message(data):
+ """
+ Distributes messages sent to the server to all clients in real time
+ """
+ emit('message', {'username': data['username'], 'msg': data['msg']}, broadcast=True)
+
+####poster
+@app.route("/getPosterURL", methods=["GET"])
+def get_poster_url():
+ """
+ Retrieve the poster URL for the recommended movie based on IMDb ID.
+ return: JSON response containing the poster URL.
+ """
+ imdb_id = request.args.get("imdbID")
+ poster_url = fetch_poster_url(imdb_id)
+ return jsonify({"posterURL": poster_url})
+
+def fetch_poster_url(imdb_id):
+ """
+ Fetch the poster URL for a movie from The Movie Database (TMDB) API.
+ """
+ timeout = 100
+ url = f"https://api.themoviedb.org/3/find/{imdb_id}?"\
+ f"api_key={TMDB_API_KEY}&external_source=imdb_id"
+ response = requests.get(url, timeout=timeout)
+ data = response.json()
+ # Check if movie results are present and have a poster path
+ if "movie_results" in data and data["movie_results"]:
+ poster_path = data["movie_results"][0].get("poster_path")
+ return f"https://image.tmdb.org/t/p/w500{poster_path}" if poster_path else None
+ return None
+
+@app.route("/predict", methods=["POST"])
+def predict():
+ """
+ Predicts movie recommendations based on user ratings.
+ """
+ data = json.loads(request.data)
+ data1 = data["movie_list"]
+ training_data = []
+ for movie in data1:
+ movie_with_rating = {"title": movie, "rating": 5.0}
+ if movie_with_rating not in training_data:
+ training_data.append(movie_with_rating)
+ data = recommend_for_new_user(training_data)
+ data = data.to_json(orient="records")
+ return jsonify(data)
+
+@app.route("/search", methods=["POST"])
+def search():
+ """
+ Handles movie search requests.
+ """
+ term = request.form["q"]
+ finder = Search()
+ filtered_dict = finder.results_top_ten(term)
+ resp = jsonify(filtered_dict)
+ resp.status_code = 200
+ return resp
+
+@app.route("/feedback", methods=["POST"])
+def feedback():
+ """
+ Handles user feedback submission and mails the results.
+ """
+ data = json.loads(request.data)
+ return data
+
+@app.route("/sendMail", methods=["POST"])
+def send_mail():
+ """
+ Handles user feedback submission and mails the results.
+ """
+ data = json.loads(request.data)
+ user_email = data['email']
+ send_email_to_user(user_email, beautify_feedback_data(data))
+ return data
+
+@app.route("/success")
+def success():
+ """
+ Renders the success page.
+ """
+ return render_template("success.html", user=current_user)
+
+@app.route("/postReview", methods=["POST"])
+@login_required
+def post_review():
+ """
+ API for the user to submit a review
+ """
+ # Check if the movie already exists in the database.
+ # If it exists, fetch the movie ID and save the review
+ # If it does not, save the movie details and save the review
+
+ data = json.loads(request.data)
+ user_object = User.query.filter_by(username=current_user.username).first()
+ user_id = user_object.id
+ review_text = data['review_text']
+ movie_id = data["movieId"]
+ movie_object = Movie.query.filter_by(movieId=movie_id).first()
+ if movie_object is None:
+ # Create a new movie object
+ movie = Movie(
+ movieId = movie_id,
+ title = data['title'],
+ runtime = data['runtime'],
+ overview = data['overview'],
+ genres = data['genres'],
+ imdb_id = data['imdb_id'],
+ poster_path = data['poster_path']
+ )
+ db.session.add(movie)
+ db.session.commit()
+ review = Review(
+ review_text = review_text,
+ movieId = movie_id,
+ user_id = user_id
+ )
+ db.session.add(review)
+ db.session.commit()
+ return jsonify({"success": "success"})
+
+@app.route("/movies", methods=["GET"])
+@login_required
+def movie_page():
+ """
+ Get movies and their reviews
+ """
+ movies_ojbects = Movie.query.all()
+ movies = []
+ for movie_object in movies_ojbects:
+ reviews = []
+ obj1 = {
+ "title" : movie_object.title,
+ "runtime" : movie_object.runtime,
+ "overview" : movie_object.overview,
+ "genres" : movie_object.genres,
+ "imdb_id" : movie_object.imdb_id,
+ }
+ reviews_objects = Review.query.filter_by(movieId = movie_object.movieId).all()
+ for review_object in reviews_objects:
+ user = User.query.filter_by(id=review_object.user_id).first()
+ obj2 = {
+ "username": user.username,
+ "name": f"{user.first_name} {user.last_name}",
+ "review_text": review_object.review_text
+ }
+ reviews.append(obj2)
+ obj1["reviews"] = reviews
+ movies.append(obj1)
+ return render_template("movie.html", movies=movies, user=current_user)
+
+@app.route('/logout')
+def logout():
+ """
+ Logout Function
+ """
+ logout_user()
+ return redirect('/')
+
+@app.route('/new_movies', methods=["GET"])
+@login_required
+def new_movies():
+ """
+ API to fetch new movies
+ """
+ # Replace YOUR_TMDB_API_KEY with your actual TMDb API key
+ tmdb_api_key = TMDB_API_KEY
+ endpoint = 'https://api.themoviedb.org/3/movie/upcoming'
+
+ # Set up parameters for the request
+ params = {
+ 'api_key': tmdb_api_key,
+ 'language': 'en-US', # You can adjust the language as needed
+ 'page': 1 # You may want to paginate the results if there are many
+ }
+ try:
+ # Make the request to TMDb API
+ response = requests.get(endpoint, params=params, timeout=10)
+ except (requests.exceptions.HTTPError,
+ requests.exceptions.ConnectionError,
+ requests.exceptions.Timeout,
+ requests.exceptions.RequestException
+ ) as e:
+ return render_template('new_movies.html', show_message=True,
+ message=e)
+ if response.status_code == 200:
+ # Parse the JSON response
+ movie_data = response.json().get('results', [])
+
+ return render_template('new_movies.html', movies=movie_data, user=current_user)
+ return render_template('new_movies.html', show_message=True,
+ message='Error fetching movie data')
+
+
+if __name__ == "__main__":
+ app.run(port=5000, debug=True)
diff --git a/src/recommenderapp/search.py b/app/src/search.py
similarity index 94%
rename from src/recommenderapp/search.py
rename to app/src/search.py
index 20b8fb3be..17dab5fea 100644
--- a/src/recommenderapp/search.py
+++ b/app/src/search.py
@@ -1,5 +1,5 @@
"""
-Copyright (c) 2023 Aditya Pai, Ananya Mantravadi, Rishi Singhal, Samarth Shetty
+Copyright (c) 2023 Abhinav Sinha, Chandana Ray, Sam Kwiatkowski-Martin, Tanmay Pardeshi
This code is licensed under MIT license (see LICENSE for details)
@author: PopcornPicks
diff --git a/app/src/static/css/chat.css b/app/src/static/css/chat.css
new file mode 100644
index 000000000..a6626ccee
--- /dev/null
+++ b/app/src/static/css/chat.css
@@ -0,0 +1,9 @@
+#centralDivLanding {
+ margin-top: 0;
+}
+
+.card {
+ width: 50vw;
+ height: 70vh;
+ margin: auto;
+}
\ No newline at end of file
diff --git a/app/src/static/css/login.css b/app/src/static/css/login.css
new file mode 100644
index 000000000..97b14d964
--- /dev/null
+++ b/app/src/static/css/login.css
@@ -0,0 +1,17 @@
+#login-form {
+ margin: auto; /*so the form is in the center of it's container */
+}
+
+#Signup_text {
+ padding-top: 2%;
+ margin: auto;
+}
+
+#login_alert {
+ margin: 0 auto;
+ padding-bottom: 5px;
+ padding-top: 5px;
+ margin-bottom: 10px;
+ margin-top: 10px;
+ display: none;
+}
\ No newline at end of file
diff --git a/app/src/static/css/movie.css b/app/src/static/css/movie.css
new file mode 100644
index 000000000..db2e895f7
--- /dev/null
+++ b/app/src/static/css/movie.css
@@ -0,0 +1,8 @@
+.movie-card {
+ color: black;
+ margin: 1%;
+}
+
+.imdbId {
+ color:transparent;
+}
\ No newline at end of file
diff --git a/app/src/static/css/profile.css b/app/src/static/css/profile.css
new file mode 100644
index 000000000..5f57cb054
--- /dev/null
+++ b/app/src/static/css/profile.css
@@ -0,0 +1,28 @@
+.profile-card {
+ margin-top: 20px;
+ border: 1px solid #ccc;
+ }
+
+.profile-card h2 {
+margin-bottom: 10px;
+text-align: center;
+}
+
+.profile-card ul {
+list-style: none;
+padding: 0;
+margin: 0;
+}
+
+.profile-title {
+ color: black;
+}
+
+.movie-card {
+ color: black;
+ margin: 1%;
+}
+
+.imdbId {
+ color:transparent;
+}
\ No newline at end of file
diff --git a/app/src/static/css/search.css b/app/src/static/css/search.css
new file mode 100644
index 000000000..24612016d
--- /dev/null
+++ b/app/src/static/css/search.css
@@ -0,0 +1,22 @@
+.movie-grid-row {
+ margin-top: 2%;
+}
+
+.movie-card {
+ color: black;
+ margin: 1%;
+}
+
+.alert {
+ margin: 2%;
+}
+
+#reviewModaLabel {
+ color: black;
+}
+
+.boxsizingBorder {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
\ No newline at end of file
diff --git a/app/src/static/css/signup.css b/app/src/static/css/signup.css
new file mode 100644
index 000000000..9d4b052cf
--- /dev/null
+++ b/app/src/static/css/signup.css
@@ -0,0 +1,12 @@
+#signup-form {
+ margin: auto; /*so the form is in the center of it's container */
+}
+
+#failed_sign_in {
+ margin: 0 auto;
+ padding-bottom: 5px;
+ padding-top: 5px;
+ margin-bottom: 10px;
+ margin-top: 10px;
+ display: none;
+}
\ No newline at end of file
diff --git a/src/recommenderapp/static/stylesheet.css b/app/src/static/css/stylesheet.css
similarity index 95%
rename from src/recommenderapp/static/stylesheet.css
rename to app/src/static/css/stylesheet.css
index b70027375..e33786671 100644
--- a/src/recommenderapp/static/stylesheet.css
+++ b/app/src/static/css/stylesheet.css
@@ -1,5 +1,5 @@
.landing-page {
- background-image: url("image.jpg");
+ background-image: url("/static/images/image.jpg");
background-size: cover;
color: azure !important;
}
@@ -15,9 +15,9 @@
}
.highlighted-section {
- background-color: black;
color: white;
- width: 21rem;
+ font-weight: bold;
+ width: 25rem;
height: 4.5rem;
margin: auto;
display: flex;
@@ -26,7 +26,7 @@
}
body {
- background-image: url("image.jpg") !important;
+ background-image: url("/static/images/image.jpg") !important;
background-size: cover;
color: azure !important;
}
@@ -142,13 +142,6 @@ tr {
justify-content: center;
}
-.heading1 > div {
- display: flex;
- justify-content: center;
- margin-top: 6rem;
- margin-bottom: 6rem;
-}
-
body {
font-size: 10px;
font-family: Roboto, sans-serif;
diff --git a/src/recommenderapp/static/224299.jpg b/app/src/static/images/224299.jpg
similarity index 100%
rename from src/recommenderapp/static/224299.jpg
rename to app/src/static/images/224299.jpg
diff --git a/src/recommenderapp/static/Popcorn-icon.png b/app/src/static/images/Popcorn-icon.png
similarity index 100%
rename from src/recommenderapp/static/Popcorn-icon.png
rename to app/src/static/images/Popcorn-icon.png
diff --git a/src/recommenderapp/static/image.jpg b/app/src/static/images/image.jpg
similarity index 100%
rename from src/recommenderapp/static/image.jpg
rename to app/src/static/images/image.jpg
diff --git a/app/src/static/js/chat.js b/app/src/static/js/chat.js
new file mode 100644
index 000000000..5a66ce4b4
--- /dev/null
+++ b/app/src/static/js/chat.js
@@ -0,0 +1,21 @@
+var socket = io();
+socket.on('connect', function() {
+ socket.emit('connections', 'Connection successful');
+ /*
+ For debugging purposes this prints in python when someone connects
+ */
+});
+
+socket.on('message', function(data) {
+ /* Handles incoming message sent from server*/
+ var message_list = document.getElementById('message_list');
+ var li = document.createElement('li');
+ li.className = 'list-group-item';
+ var user = data['username']
+ var msg = data['msg']
+ var final = user + ": " + msg
+ li.appendChild(document.createTextNode(final));
+ message_list.appendChild(li);
+});
+
+
diff --git a/app/src/static/js/movie.js b/app/src/static/js/movie.js
new file mode 100644
index 000000000..b8ee3f635
--- /dev/null
+++ b/app/src/static/js/movie.js
@@ -0,0 +1,29 @@
+$(document).ready(function () {
+ function fetchPosterURL(obj) {
+ var posterURL = null;
+ $.ajax({
+ type: "GET",
+ url: "/getPosterURL",
+ dataType: "json",
+ data: { imdbID: obj.innerHTML },
+ async: false,
+ success: function (response) {
+ posterURL = response.posterURL;
+ var poster = ``
+ obj.innerHTML += poster;
+ },
+ error: function (error) {
+ console.log("Error fetching poster URL: " + error);
+ },
+ });
+ return posterURL;
+ };
+
+ $('.imdbId').map((index, obj) => {
+ fetchPosterURL(obj);
+ });
+
+
+});
+
\ No newline at end of file
diff --git a/app/src/static/js/profile.js b/app/src/static/js/profile.js
new file mode 100644
index 000000000..b8ee3f635
--- /dev/null
+++ b/app/src/static/js/profile.js
@@ -0,0 +1,29 @@
+$(document).ready(function () {
+ function fetchPosterURL(obj) {
+ var posterURL = null;
+ $.ajax({
+ type: "GET",
+ url: "/getPosterURL",
+ dataType: "json",
+ data: { imdbID: obj.innerHTML },
+ async: false,
+ success: function (response) {
+ posterURL = response.posterURL;
+ var poster = `
`
+ obj.innerHTML += poster;
+ },
+ error: function (error) {
+ console.log("Error fetching poster URL: " + error);
+ },
+ });
+ return posterURL;
+ };
+
+ $('.imdbId').map((index, obj) => {
+ fetchPosterURL(obj);
+ });
+
+
+});
+
\ No newline at end of file
diff --git a/src/recommenderapp/static/script.js b/app/src/static/js/script.js
similarity index 59%
rename from src/recommenderapp/static/script.js
rename to app/src/static/js/script.js
index e3a08cc9e..44c0316a2 100644
--- a/src/recommenderapp/static/script.js
+++ b/app/src/static/js/script.js
@@ -1,11 +1,10 @@
$(document).ready(function () {
-
$(function () {
$("#searchBox").autocomplete({
source: function (request, response) {
$.ajax({
type: "POST",
- url: "http://localhost:5000/search",
+ url: "/search",
dataType: "json",
cache: false,
data: {
@@ -52,12 +51,32 @@ $(document).ready(function () {
// Clear the existing recommendations
$("#predictedMovies").empty();
-
// if movies list empty then throw an error box saying select atleast 1 movie!!
if (movie_list.length == 0) {
alert("Select atleast 1 movie!!");
}
+ //fetching poster using /getposterurl
+
+ function fetchPosterURL(imdbID) {
+ var posterURL = null;
+ $.ajax({
+ type: "GET",
+ url: "/getPosterURL",
+ dataType: "json",
+ data: { imdbID: imdbID },
+ async: false,
+ success: function (response) {
+ posterURL = response.posterURL;
+ },
+ error: function (error) {
+ console.log("Error fetching poster URL: " + error);
+ },
+ });
+
+ return posterURL;
+ };
+
$.ajax({
type: "POST",
url: "/predict",
@@ -67,46 +86,59 @@ $(document).ready(function () {
cache: false,
data: JSON.stringify(movies),
success: function (response) {
- var ulList = $("#predictedMovies");
- var i = 0;
- var recommendations = response["recommendations"];
- var imdbIds = response["imdb_id"]
- for (var i = 0; i < recommendations.length; i++) {
- var element = recommendations[i];
- var imdbID = imdbIds[i]
- var diventry = $("
|
- - |
-
- - |
-
- - |
-
${data[i].overview}
+ Check out IMDb Link + +
+ Discover personalized movie recommendations by selecting up to 5 of your favorite films.
+
+ Add reviews, get an insight on latest movies and see what others are watching!
+
+ Enjoy movies at your own pace, on your terms.
+
Made with ❤️ by PopcornPicks
+ MIT License Copyright (c) 2023 PopcornPicks +Don't have an account with us? Click here to sign up.
+Movie Overview : {{movie.overview}}
+ Check out IMDb Link ++ Review: + {{review.review_text}} +
++{% endblock %} diff --git a/app/src/templates/movie_chat.html b/app/src/templates/movie_chat.html new file mode 100644 index 000000000..bf14778b8 --- /dev/null +++ b/app/src/templates/movie_chat.html @@ -0,0 +1,40 @@ +{% extends 'shared/layout.html' %} + +{% block extracss %} + +{% endblock %} + +{% block extrajs %} + + +{% endblock %} + +{% block maincontent %} +
+ +{% endblock %} \ No newline at end of file diff --git a/app/src/templates/new_movies.html b/app/src/templates/new_movies.html new file mode 100644 index 000000000..65bf25538 --- /dev/null +++ b/app/src/templates/new_movies.html @@ -0,0 +1,33 @@ +{% extends 'shared/layout.html' %} + +{% block extracss %} +
Release Date: {{ movie.release_date }}
+
+
+{% endblock %}
diff --git a/app/src/templates/profile.html b/app/src/templates/profile.html
new file mode 100644
index 000000000..be0ff542e
--- /dev/null
+++ b/app/src/templates/profile.html
@@ -0,0 +1,77 @@
+{% extends 'shared/layout.html' %}
+
+{% block extracss %}
+
+{% endblock %}
+
+{% block extrajs %}
+
+{% endblock %}
+
+
+{% block maincontent %}
+
Movie Overview : {{review.overview}}
+Your Review : {{review.review_text}}
+ Check out IMDb Link ++
+
+ +{% endblock %} diff --git a/app/src/templates/shared/layout.html b/app/src/templates/shared/layout.html new file mode 100644 index 000000000..c258d49a8 --- /dev/null +++ b/app/src/templates/shared/layout.html @@ -0,0 +1,83 @@ + + +
+ + +
+ + + + + + + + {% block extracss %}{% endblock %} + + + + + + + + {% block extrajs %}{% endblock %} + +
+ + {% block extra_html_scripts %}{% endblock %} +