├── .DS_Store ├── README.md ├── code ├── *movie_rec.py : :home:anasdjebbari:deploy:movie_rec.py : Editor : anasdjebbari : PythonAnywhere.webarchive ├── .DS_Store ├── __pycache__ │ ├── dbconnect.cpython-35.pyc │ ├── scrap.cpython-35.pyc │ ├── search.cpython-35.pyc │ ├── topm.cpython-35.pyc │ ├── user_Rec.cpython-35.pyc │ └── youtube.cpython-35.pyc ├── dbconnect.py ├── login.py ├── movierecoendation ├── movies.py ├── scrap.py ├── search.py ├── static │ ├── .DS_Store │ ├── bgimg.png │ ├── css │ │ ├── .DS_Store │ │ ├── demo.css │ │ ├── jquery.gridder.min.css │ │ ├── jquery.gridder.min.css.map │ │ └── search │ │ │ ├── select2-bootstrap.css │ │ │ ├── select2-bootstrap.min.css │ │ │ ├── select2.css │ │ │ └── select2.min.css │ ├── font-awesome │ │ ├── HELP-US-OUT.txt │ │ ├── css │ │ │ ├── font-awesome.css │ │ │ └── font-awesome.min.css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ │ ├── less │ │ │ ├── animated.less │ │ │ ├── bordered-pulled.less │ │ │ ├── core.less │ │ │ ├── fixed-width.less │ │ │ ├── font-awesome.less │ │ │ ├── icons.less │ │ │ ├── larger.less │ │ │ ├── list.less │ │ │ ├── mixins.less │ │ │ ├── path.less │ │ │ ├── rotated-flipped.less │ │ │ ├── screen-reader.less │ │ │ ├── stacked.less │ │ │ └── variables.less │ │ └── scss │ │ │ ├── _animated.scss │ │ │ ├── _bordered-pulled.scss │ │ │ ├── _core.scss │ │ │ ├── _fixed-width.scss │ │ │ ├── _icons.scss │ │ │ ├── _larger.scss │ │ │ ├── _list.scss │ │ │ ├── _mixins.scss │ │ │ ├── _path.scss │ │ │ ├── _rotated-flipped.scss │ │ │ ├── _screen-reader.scss │ │ │ ├── _stacked.scss │ │ │ ├── _variables.scss │ │ │ └── font-awesome.scss │ ├── js │ │ ├── .DS_Store │ │ ├── jquery.gridder.js │ │ ├── jquery.gridder.min.js │ │ └── search │ │ │ ├── select2.full.js │ │ │ ├── select2.full.min.js │ │ │ ├── select2.js │ │ │ └── select2.min.js │ ├── profile_pics │ │ ├── .DS_Store │ │ ├── f │ │ │ ├── 1_18.png │ │ │ ├── 25.png │ │ │ ├── 35.png │ │ │ ├── 45.png │ │ │ ├── 50.png │ │ │ └── 56.png │ │ └── m │ │ │ ├── 1_18.png │ │ │ ├── 25.png │ │ │ ├── 35.png │ │ │ ├── 45.png │ │ │ ├── 50.png │ │ │ └── 56.png │ └── style.css ├── templates │ ├── genmov.html │ ├── genre.html │ ├── index.html │ ├── login.html │ ├── movie.html │ ├── profile.html │ ├── register.html │ ├── regout.html │ └── search.html ├── topm.py ├── user_Rec.py └── youtube.py ├── database.sql └── documentation ├── .DS_Store └── Document.pdf /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/.DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python-Movie-Recommendation 2 | Flask based Movie Recommendation System 3 | 4 | Final Year Project 5 | 6 | A Recommendation System which uses algorithms such as nearest neighbour and pearsons r correlation in order to come up with accurate 7 | and mathematicaly based recommendations 8 | 9 | Specifications: Python 3.5.3 10 | 11 | Project Report (document.pdf) contains all the details on how the project works along side the investigation carried out 12 | -------------------------------------------------------------------------------- /code/*movie_rec.py : :home:anasdjebbari:deploy:movie_rec.py : Editor : anasdjebbari : PythonAnywhere.webarchive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/*movie_rec.py : :home:anasdjebbari:deploy:movie_rec.py : Editor : anasdjebbari : PythonAnywhere.webarchive -------------------------------------------------------------------------------- /code/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/.DS_Store -------------------------------------------------------------------------------- /code/__pycache__/dbconnect.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/__pycache__/dbconnect.cpython-35.pyc -------------------------------------------------------------------------------- /code/__pycache__/scrap.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/__pycache__/scrap.cpython-35.pyc -------------------------------------------------------------------------------- /code/__pycache__/search.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/__pycache__/search.cpython-35.pyc -------------------------------------------------------------------------------- /code/__pycache__/topm.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/__pycache__/topm.cpython-35.pyc -------------------------------------------------------------------------------- /code/__pycache__/user_Rec.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/__pycache__/user_Rec.cpython-35.pyc -------------------------------------------------------------------------------- /code/__pycache__/youtube.cpython-35.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/__pycache__/youtube.cpython-35.pyc -------------------------------------------------------------------------------- /code/dbconnect.py: -------------------------------------------------------------------------------- 1 | import pymysql 2 | 3 | def connection(): 4 | config = pymysql.connect ( 5 | user = 'root', 6 | password = 'root', 7 | host = 'localhost', 8 | database = 'movie_rec', 9 | ) 10 | a = config.cursor() 11 | return a, config 12 | 13 | 14 | -------------------------------------------------------------------------------- /code/login.py: -------------------------------------------------------------------------------- 1 | from flask import Flask, session,Response, render_template, request, redirect, url_for, g 2 | import os 3 | import requests 4 | import time 5 | import json 6 | 7 | from topm import movie_info 8 | from user_Rec import get_user_rec 9 | from dbconnect import connection 10 | from scrap import get_rec, M,M1 11 | from search import srch_mov 12 | from youtube import get_genre,gen_movies 13 | 14 | a, config = connection() 15 | 16 | app = Flask(__name__) 17 | app.secret_key = os.urandom(24) 18 | 19 | 20 | @app.route('/', methods=['GET', 'POST']) 21 | def index(): 22 | 23 | if request.method == 'POST': 24 | a.execute('SELECT user_id FROM users WHERE name = %s', (request.form['username'])) 25 | userid = a.fetchone() 26 | userid = int(userid[0]) 27 | a.execute('SELECT password FROM users WHERE user_id = %s', (userid)) 28 | password = a.fetchone() 29 | passw = str(password[0]) 30 | 31 | if request.form['password'] == passw: 32 | session['user'] = userid 33 | session['password'] = request.form['password'] 34 | 35 | return redirect(url_for('protected')) 36 | 37 | return render_template("index.html") 38 | 39 | @app.route('/genre') 40 | def genre(): 41 | if g.user: 42 | gen = get_genre() 43 | return render_template('genre.html', genres = gen) 44 | 45 | @app.route('/genremovies/') 46 | def genremovies(mgenre): 47 | if g.user: 48 | x = str(mgenre) 49 | gen = gen_movies(x) 50 | print(gen) 51 | return render_template('genmov.html', movgen = gen) 52 | 53 | 54 | @app.route('/search', methods=['GET', 'POST']) 55 | def search(): 56 | result = [] 57 | if request.method == 'POST': 58 | search = request.form['title'] 59 | result = (srch_mov(search)) 60 | return render_template("search.html", res=result) 61 | 62 | 63 | @app.route('/srch', methods=['GET', 'POST']) 64 | def srch(): 65 | select = request.form.get('search') 66 | return redirect(url_for('.movie', movie_id=str(select))) 67 | 68 | @app.route('/logout') 69 | def logout(): 70 | dropsession() 71 | return redirect(url_for('index')) 72 | 73 | @app.route('/protected') 74 | def protected(): 75 | search = list() 76 | sqlsearch = 'SELECT DISTINCT movies.title, links.imdbId from movies, links WHERE links.movie_id = movies.movie_id LIMIT 9300' 77 | a.execute(sqlsearch) 78 | srchdata = a.fetchall() 79 | search.append(srchdata) 80 | if g.user: 81 | 82 | imdbid = get_user_rec(int(g.user)) 83 | noob_msg = "Your Recommendation:" 84 | 85 | 86 | 87 | return render_template('regout.html', nmsg = noob_msg,search=search, name=g.user, imdbid=imdbid) 88 | 89 | return redirect(url_for('index'), ) 90 | 91 | @app.route('/reg', methods=['GET', 'POST']) 92 | def reg(): 93 | if request.method == 'POST': 94 | session['name'] = request.form['username'] 95 | session['gender'] = request.form['gender'] 96 | session['age'] = request.form['age'] 97 | session['occupation'] = request.form['occupation'] 98 | session['password'] = request.form['password'] 99 | return redirect(url_for('register')) 100 | 101 | return render_template("register.html") 102 | 103 | @app.route('/register') 104 | def register(): 105 | a.execute('select MAX(user_id) from users') 106 | maxid = a.fetchone() 107 | a.execute('insert into users VALUES (%s,%s,%s,%s,%s,%s)', ( 108 | maxid[0] + 1, session['name'], session['gender'], session['age'], session['occupation'], session['password'])) 109 | config.commit() 110 | # maxid = maxid[0] +1 111 | # movie = 1 112 | # rate = 5 113 | # tstamp = 1260759151 114 | # a.execute('insert into ratings VALUES (%s,%s,%s,%s)', (maxid, movie, rate,tstamp )) 115 | # config.commit() 116 | return redirect(url_for('index'), ) 117 | 118 | @app.route('/profile') 119 | def profile(): 120 | if g.user: 121 | uid = g.user 122 | 123 | search = list() 124 | sqlsearch = 'SELECT DISTINCT movies.title, links.imdbId from movies, links WHERE links.movie_id = movies.movie_id LIMIT 9300' 125 | a.execute(sqlsearch) 126 | srchdata = a.fetchall() 127 | search.append(srchdata) 128 | 129 | profileArray = list() 130 | a.execute('SELECT movies.title,links.imdbId from movies, ratings, links WHERE user_id = %s AND movies.movie_id = ratings.movie_id AND ratings.movie_id=links.movie_id',(uid)) 131 | data = a.fetchall() 132 | profileArray.append(data) 133 | 134 | return render_template("profile.html",search=search, name=g.user, watched=profileArray) 135 | 136 | @app.route('/movie/') 137 | def movie(movie_id): 138 | movie_id = movie_id.replace("(", "").replace(",", "").replace(")", "") 139 | 140 | if g.user: 141 | a.execute('SELECT movie_id from links WHERE imdbId =%s', (movie_id)) 142 | m_id = a.fetchone() 143 | 144 | id = m_id[0] 145 | 146 | sql2 = 'select ratings,FROM_UNIXTIME(ratings.timestamp) from ratings where user_id = %s AND movie_id = %s' 147 | a.execute(sql2, (g.user, id)) 148 | sqlrating = a.fetchone() 149 | 150 | notification = list() 151 | sql3 = 'SELECT users.name, movies.title, ratings.ratings, FROM_UNIXTIME(ratings.timestamp), users.sex, users.age from ratings,movies, users WHERE ratings.movie_id=%s and movies.movie_id = ratings.movie_id and ratings.user_id = users.user_id LIMIT 10' 152 | a.execute(sql3, (id)) 153 | notidata = a.fetchall() 154 | notification.append(notidata) 155 | 156 | 157 | 158 | a.execute('SELECT Round(AVG(ratings)) from ratings where movie_id=%s', (m_id[0])) 159 | avgrating = a.fetchone() 160 | 161 | a.execute('SELECT * from ratings where user_id = %s and movie_id = %s', (g.user, m_id[0])) 162 | rated = a.fetchone() 163 | 164 | message = " " 165 | mesage_time = " " 166 | recmesg = " " 167 | if rated == None: 168 | rec =[] 169 | 170 | else: 171 | message = "You rated this movie: " 172 | mesage_time = "At: " 173 | rec = get_rec(id, M, M1, 1) 174 | recmesg = "" 175 | 176 | info = movie_info(movie_id) 177 | 178 | 179 | 180 | return render_template("movie.html", 181 | avg = avgrating[0], 182 | m = message, 183 | t = mesage_time, 184 | # link="static/"+link, 185 | rec = rec, 186 | recmesg=recmesg, 187 | minfo = info, 188 | sqlrating=sqlrating, 189 | movie=movie_id, 190 | name=g.user, 191 | notification=notification 192 | ) 193 | 194 | return redirect(url_for('index')) 195 | 196 | @app.route('/rate//') 197 | def rate(rating, movie_id): 198 | if g.user: 199 | rate = rating 200 | timestp = int(time.time()) 201 | movie_id = movie_id.replace("(", "").replace(",", "").replace(")", "") 202 | a.execute('SELECT movie_id from links WHERE imdbId =%s', (movie_id)) 203 | m_id = a.fetchone() 204 | 205 | a.execute('SELECT * from ratings where user_id = %s and movie_id = %s', (g.user, m_id[0])) 206 | works = a.fetchone() 207 | 208 | if works == None: 209 | a.execute('insert into ratings VALUES (%s,%s,%s,%s)', (g.user, m_id[0], rate, timestp)) 210 | config.commit() 211 | else: 212 | a.execute('update ratings set ratings = %s,timestamp = %s where user_id = %s and movie_id = %s', 213 | (rate, timestp, g.user, m_id[0])) 214 | config.commit() 215 | 216 | return redirect(url_for('.movie', movie_id=movie_id)) 217 | 218 | return redirect(url_for("index")) 219 | 220 | @app.route('/discover') 221 | def discover(): 222 | if g.user: 223 | return "" 224 | 225 | @app.before_request 226 | def before_request(): 227 | g.user = None 228 | if 'user' in session: 229 | g.user = session['user'] 230 | 231 | @app.route('/getsession') 232 | def getsession(): 233 | if 'user' in session: 234 | return session['user'] 235 | 236 | return 'not logged in yet' 237 | 238 | @app.route('/dropsession') 239 | def dropsession(): 240 | session.pop('user', None) 241 | return redirect(url_for('index')) 242 | 243 | 244 | 245 | if __name__ == '__main__': 246 | app.run(debug=False, host='0.0.0.0') 247 | -------------------------------------------------------------------------------- /code/movierecoendation: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/movierecoendation -------------------------------------------------------------------------------- /code/movies.py: -------------------------------------------------------------------------------- 1 | from numpy import * 2 | 3 | # this is just a prototype for testing purposes 4 | 5 | movies = 10 6 | users=5 7 | 8 | ratings = random.randint(11, size = (movies,users)) 9 | print (ratings) 10 | 11 | print 12 | 13 | # lets us know who rated a movie and who didnt 14 | did_rate = (ratings != 0) * 1 15 | print (did_rate) 16 | 17 | print 18 | 19 | # rate a movie 20 | my_rating = zeros((movies, 1)) 21 | print (my_rating) 22 | 23 | print 24 | 25 | my_rating[0] = 4 26 | my_rating[2] = 2 27 | my_rating[4] = 4 28 | my_rating[6] = 2 29 | print (my_rating) 30 | 31 | print 32 | 33 | # updating the matrix and adding my_rating to the matrix 34 | ratings = append (my_rating, ratings, axis = 1) 35 | did_rate = append (((my_rating != 0)*1), did_rate, axis = 1) 36 | 37 | print (ratings) 38 | print 39 | print (did_rate) 40 | 41 | print 42 | 43 | # normalization 44 | def normalize_ratings(ratings, did_rate): 45 | movies = ratings.shape[0] 46 | mean = zeros(shape=(movies, 1)) 47 | norm = zeros(shape=ratings.shape) 48 | 49 | for i in range(movies): 50 | # Get all the indexes where there is a 1 51 | idx = where(did_rate[i] == 1)[0] 52 | 53 | # Calculate mean rating of ith movie only from user's that gave a rating 54 | mean[i] = mean(ratings[i, idx]) 55 | norm[i, idx] = ratings[i, idx] - mean[i] 56 | 57 | return (norm, mean) 58 | 59 | 60 | # runing the normalisation function 61 | ratings, mean = normalize_ratings(ratings, did_rate) 62 | print (ratings) 63 | print 64 | print (mean) 65 | 66 | #update the number of users 67 | users = ratings.shape[1] 68 | #can be comdey etc. 69 | num_feature = 3 70 | 71 | # stopped at tutorial 7 72 | # https://www.youtube.com/watch?v=nuDp1tjrIXo&index=7&list=PLseNcwx1RJ4WdgtrMTXndw4B4nlf4-pgS -------------------------------------------------------------------------------- /code/scrap.py: -------------------------------------------------------------------------------- 1 | from dbconnect import connection 2 | import pandas as pd 3 | import multiprocessing as mp 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | 7 | 8 | 9 | a, config = connection() 10 | 11 | rating = 'SELECT * FROM ratings' 12 | ratings = pd.read_sql(rating, config) 13 | ratings.head() 14 | ratings.drop(['timestamp'],axis=1, inplace=True) 15 | 16 | M = ratings.pivot_table(index=['user_id'],columns=['movie_id'],values='ratings') 17 | M.shape 18 | 19 | rating2 = 'SELECT r.*, r1.size, r1.mean from ratings r INNER JOIN (SELECT movie_id, COUNT(1) size, AVG(ratings) mean FROM ratings GROUP BY movie_id HAVING COUNT(1) >= 20 ORDER BY 3 DESC) AS r1 ON r.movie_id = r1.movie_id' 20 | ratings2 = pd.read_sql(rating2, config) 21 | ratings2.head() 22 | ratings2.drop(['timestamp'],axis=1, inplace=True) 23 | ratings2.drop(['size'],axis=1, inplace=True) 24 | ratings2.drop(['mean'],axis=1, inplace=True) 25 | 26 | 27 | M1 = ratings2.pivot_table(index=['user_id'],columns=['movie_id'],values='ratings') 28 | M1.shape 29 | 30 | # print(M1) 31 | # mov = homerec() 32 | 33 | 34 | def pearson(m1, m2): 35 | m1_c = m1 - m1.mean() 36 | m2_c = m2 - m2.mean() 37 | cor = np.sum(m1_c*m2_c)/np.sqrt(np.sum(m1_c ** 2) * np.sum(m2_c **2)) 38 | return cor 39 | 40 | def get_rec(mid, M, M1,like): 41 | reviews = [] 42 | for title in M1.columns: 43 | if title == mid: 44 | continue 45 | cor = pearson(M[mid],M1[title]) 46 | 47 | if np.isnan(cor): 48 | continue 49 | else: 50 | a.execute('SELECT title from movies WHERE movie_id =%s', (int(title))) 51 | name = a.fetchone() 52 | a.execute('SELECT img from movies WHERE movie_id =%s', (int(title))) 53 | img = a.fetchone() 54 | a.execute('SELECT imdbid from links WHERE movie_id =%s', (int(title))) 55 | imdb = a.fetchone() 56 | 57 | reviews.append((title,cor,name[0],img[0],imdb[0])) 58 | 59 | if like == 1: 60 | reviews.sort(key=lambda tup: tup[1], reverse=True) 61 | return (reviews[:10]) 62 | 63 | if like == 2: 64 | reviews.sort(key=lambda tup: tup[1], reverse=False) 65 | return (reviews[10:]) 66 | 67 | 68 | 69 | 70 | def recmov(mov,set,rate): 71 | p = mp.Process(target=get_rec, args=(mov, set, rate)) 72 | p.start() 73 | p.join() 74 | 75 | # # 76 | # get = get_rec(1,M,M1,2) 77 | # # 78 | # print(get) 79 | # # get = recmov(1,M,1) 80 | # print(get) -------------------------------------------------------------------------------- /code/search.py: -------------------------------------------------------------------------------- 1 | from dbconnect import connection 2 | 3 | a, config = connection() 4 | 5 | def srch_mov(title): 6 | 7 | a.execute('SELECT DISTINCT links.imdbId, movies.title, movies.img from movies, links WHERE links.movie_id = movies.movie_id AND movies.title LIKE %s', ("%"+str(title)+"%")) 8 | name = a.fetchall() 9 | return name 10 | # 11 | # b = srch_mov("Bat") 12 | # 13 | # for item in b: 14 | # print(item[0]) -------------------------------------------------------------------------------- /code/static/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/static/.DS_Store -------------------------------------------------------------------------------- /code/static/bgimg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/static/bgimg.png -------------------------------------------------------------------------------- /code/static/css/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/code/static/css/.DS_Store -------------------------------------------------------------------------------- /code/static/css/demo.css: -------------------------------------------------------------------------------- 1 | body{padding-bottom:1000px}.gridder{margin:0px;padding:0px;list-style-type:none}.gridder-list{display:inline-block;vertical-align:top}.gridder-show{display:block;float:left;width:100%;position:relative;background:#EEE url("../images/loading-spin.svg") no-repeat center}.gridder-show.loading{background:#EEE url("../images/loading-spin.svg") no-repeat center}.gridder-content{display:none}.gridder-list{width:15.83333%}.gridder-list:nth-child(n){margin-bottom:1%;margin-right:1%}.gridder-list:nth-of-type(6n){margin-right:0;margin-bottom:0}.gridder-show{padding:20px;background:#EEE;margin-bottom:1%}.gridder-navigation .gridder-nav.disabled{opacity:.5}.gridder-list{cursor:pointer}.gridder-list:hover{opacity:0.8}.hasSelectedItem .gridder-list{opacity:.5}.hasSelectedItem .gridder-list.selectedItem{opacity:1} 2 | /*# sourceMappingURL=demo.css.map */ 3 | -------------------------------------------------------------------------------- /code/static/css/jquery.gridder.min.css: -------------------------------------------------------------------------------- 1 | .gridder{margin:0px;padding:0px;list-style-type:none;font-size:0}.gridder-list,.gridder-show{font-size:16px}.gridder-list{display:inline-block;vertical-align:top}.gridder-show{display:block;float:left;width:100%;position:relative}.gridder-content{display:none}.gridder-list{width:15.83333%}.gridder-list:nth-child(n){margin-bottom:1%;margin-right:1%}.gridder-list:nth-of-type(6n){margin-right:0;margin-bottom:0} 2 | /*# sourceMappingURL=jquery.gridder.min.css.map */ 3 | -------------------------------------------------------------------------------- /code/static/css/jquery.gridder.min.css.map: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "mappings": "AAGA,QAAQ,CACJ,MAAM,CAAE,GAAG,CACX,OAAO,CAAE,GAAG,CACZ,eAAe,CAAE,IAAI,CACrB,SAAS,CAAC,CAAC,CAGf,2BAA4B,CACxB,SAAS,CAAC,IAAI,CAGlB,aAAa,CACT,OAAO,CAAE,YAAY,CACrB,cAAc,CAAE,GAAG,CAEvB,aAAa,CACT,OAAO,CAAE,KAAK,CACd,KAAK,CAAE,IAAI,CACX,KAAK,CAAE,IAAI,CACX,QAAQ,CAAE,QAAQ,CAEtB,gBAAgB,CACZ,OAAO,CAAE,IAAI,CAGjB,aAAa,CC3BX,KAAK,CAAE,SAAoD,CAC3D,0BAAe,CACb,aAAa,CD0BS,EAAE,CCzBxB,YAAY,CDyBU,EAAE,CCvB1B,6BAA8B,CAC5B,YAAY,CAAE,CAAC,CACf,aAAa,CAAE,CAAC", 4 | "sources": ["../../scss/jquery.gridder.scss","../../scss/_mixins.scss"], 5 | "names": [], 6 | "file": "jquery.gridder.min.css" 7 | } 8 | -------------------------------------------------------------------------------- /code/static/css/search/select2-bootstrap.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Select2 Bootstrap Theme v0.1.0-beta.9 (https://select2.github.io/select2-bootstrap-theme) 3 | * Copyright 2015-2016 Florian Kissling and contributors (https://github.com/select2/select2-bootstrap-theme/graphs/contributors) 4 | * Licensed under MIT (https://github.com/select2/select2-bootstrap-theme/blob/master/LICENSE) 5 | */ 6 | 7 | .select2-container--bootstrap { 8 | display: block; 9 | /*------------------------------------* #COMMON STYLES 10 | \*------------------------------------*/ 11 | /** 12 | * Search field in the Select2 dropdown. 13 | */ 14 | /** 15 | * No outline for all search fields - in the dropdown 16 | * and inline in multi Select2s. 17 | */ 18 | /** 19 | * Adjust Select2's choices hover and selected styles to match 20 | * Bootstrap 3's default dropdown styles. 21 | * 22 | * @see http://getbootstrap.com/components/#dropdowns 23 | */ 24 | /** 25 | * Clear the selection. 26 | */ 27 | /** 28 | * Address disabled Select2 styles. 29 | * 30 | * @see https://select2.github.io/examples.html#disabled 31 | * @see http://getbootstrap.com/css/#forms-control-disabled 32 | */ 33 | /*------------------------------------* #DROPDOWN 34 | \*------------------------------------*/ 35 | /** 36 | * Dropdown border color and box-shadow. 37 | */ 38 | /** 39 | * Limit the dropdown height. 40 | */ 41 | /*------------------------------------* #SINGLE SELECT2 42 | \*------------------------------------*/ 43 | /*------------------------------------* #MULTIPLE SELECT2 44 | \*------------------------------------*/ 45 | /** 46 | * Address Bootstrap control sizing classes 47 | * 48 | * 1. Reset Bootstrap defaults. 49 | * 2. Adjust the dropdown arrow button icon position. 50 | * 51 | * @see http://getbootstrap.com/css/#forms-control-sizes 52 | */ 53 | /* 1 */ 54 | /*------------------------------------* #RTL SUPPORT 55 | \*------------------------------------*/ 56 | } 57 | 58 | .select2-container--bootstrap .select2-selection { 59 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 60 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 61 | background-color: #fff; 62 | border: 1px solid #ccc; 63 | border-radius: 4px; 64 | color: #555555; 65 | font-size: 14px; 66 | outline: 0; 67 | } 68 | 69 | .select2-container--bootstrap .select2-selection.form-control { 70 | border-radius: 4px; 71 | } 72 | 73 | .select2-container--bootstrap .select2-search--dropdown .select2-search__field { 74 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 75 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); 76 | background-color: #fff; 77 | border: 1px solid #ccc; 78 | border-radius: 4px; 79 | color: #555555; 80 | font-size: 14px; 81 | } 82 | 83 | .select2-container--bootstrap .select2-search__field { 84 | outline: 0; 85 | /* Firefox 18- */ 86 | /** 87 | * Firefox 19+ 88 | * 89 | * @see http://stackoverflow.com/questions/24236240/color-for-styled-placeholder-text-is-muted-in-firefox 90 | */ 91 | } 92 | 93 | .select2-container--bootstrap .select2-search__field::-webkit-input-placeholder { 94 | color: #999; 95 | } 96 | 97 | .select2-container--bootstrap .select2-search__field:-moz-placeholder { 98 | color: #999; 99 | } 100 | 101 | .select2-container--bootstrap .select2-search__field::-moz-placeholder { 102 | color: #999; 103 | opacity: 1; 104 | } 105 | 106 | .select2-container--bootstrap .select2-search__field:-ms-input-placeholder { 107 | color: #999; 108 | } 109 | 110 | .select2-container--bootstrap .select2-results__option { 111 | padding: 6px 12px; 112 | /** 113 | * Disabled results. 114 | * 115 | * @see https://select2.github.io/examples.html#disabled-results 116 | */ 117 | /** 118 | * Hover state. 119 | */ 120 | /** 121 | * Selected state. 122 | */ 123 | } 124 | 125 | .select2-container--bootstrap .select2-results__option[role=group] { 126 | padding: 0; 127 | } 128 | 129 | .select2-container--bootstrap .select2-results__option[aria-disabled=true] { 130 | color: #777777; 131 | cursor: not-allowed; 132 | } 133 | 134 | .select2-container--bootstrap .select2-results__option[aria-selected=true] { 135 | background-color: #f5f5f5; 136 | color: #262626; 137 | } 138 | 139 | .select2-container--bootstrap .select2-results__option--highlighted[aria-selected] { 140 | background-color: #c3362a; 141 | color: #fff; 142 | } 143 | 144 | .select2-container--bootstrap .select2-results__option .select2-results__option { 145 | padding: 6px 12px; 146 | } 147 | 148 | .select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__group { 149 | padding-left: 0; 150 | } 151 | 152 | .select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option { 153 | margin-left: -12px; 154 | padding-left: 24px; 155 | } 156 | 157 | .select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option { 158 | margin-left: -24px; 159 | padding-left: 36px; 160 | } 161 | 162 | .select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { 163 | margin-left: -36px; 164 | padding-left: 48px; 165 | } 166 | 167 | .select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { 168 | margin-left: -48px; 169 | padding-left: 60px; 170 | } 171 | 172 | .select2-container--bootstrap .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option { 173 | margin-left: -60px; 174 | padding-left: 72px; 175 | } 176 | 177 | .select2-container--bootstrap .select2-results__group { 178 | color: #777777; 179 | display: block; 180 | padding: 6px 12px; 181 | font-size: 12px; 182 | line-height: 1.42857143; 183 | white-space: nowrap; 184 | } 185 | 186 | .select2-container--bootstrap.select2-container--focus .select2-selection, .select2-container--bootstrap.select2-container--open .select2-selection { 187 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(182, 28, 0, 0.6); 188 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(182, 28, 0, 0.6); 189 | -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 190 | -o-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 191 | -webkit-transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; 192 | transition: border-color ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; 193 | transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; 194 | transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s, -webkit-box-shadow ease-in-out 0.15s; 195 | border-color: #b61c00; 196 | } 197 | 198 | .select2-container--bootstrap.select2-container--open { 199 | /** 200 | * Make the dropdown arrow point up while the dropdown is visible. 201 | */ 202 | /** 203 | * Handle border radii of the container when the dropdown is showing. 204 | */ 205 | } 206 | 207 | .select2-container--bootstrap.select2-container--open .select2-selection .select2-selection__arrow b { 208 | border-color: transparent transparent #999 transparent; 209 | border-width: 0 4px 4px 4px; 210 | } 211 | 212 | .select2-container--bootstrap.select2-container--open.select2-container--below .select2-selection { 213 | border-bottom-right-radius: 0; 214 | border-bottom-left-radius: 0; 215 | border-bottom-color: transparent; 216 | } 217 | 218 | .select2-container--bootstrap.select2-container--open.select2-container--above .select2-selection { 219 | border-top-right-radius: 0; 220 | border-top-left-radius: 0; 221 | border-top-color: transparent; 222 | } 223 | 224 | .select2-container--bootstrap .select2-selection__clear { 225 | color: #999; 226 | cursor: pointer; 227 | float: right; 228 | font-weight: bold; 229 | margin-right: 10px; 230 | } 231 | 232 | .select2-container--bootstrap .select2-selection__clear:hover { 233 | color: #333; 234 | } 235 | 236 | .select2-container--bootstrap.select2-container--disabled .select2-selection { 237 | border-color: #ccc; 238 | -webkit-box-shadow: none; 239 | box-shadow: none; 240 | } 241 | 242 | .select2-container--bootstrap.select2-container--disabled .select2-selection, 243 | .select2-container--bootstrap.select2-container--disabled .select2-search__field { 244 | cursor: not-allowed; 245 | } 246 | 247 | .select2-container--bootstrap.select2-container--disabled .select2-selection, 248 | .select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice { 249 | background-color: #eeeeee; 250 | } 251 | 252 | .select2-container--bootstrap.select2-container--disabled .select2-selection__clear, 253 | .select2-container--bootstrap.select2-container--disabled .select2-selection--multiple .select2-selection__choice__remove { 254 | display: none; 255 | } 256 | 257 | .select2-container--bootstrap .select2-dropdown { 258 | -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 259 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); 260 | border-color: #66afe9; 261 | overflow-x: hidden; 262 | margin-top: -1px; 263 | } 264 | 265 | .select2-container--bootstrap .select2-dropdown--above { 266 | -webkit-box-shadow: 0px -6px 12px rgba(0, 0, 0, 0.175); 267 | box-shadow: 0px -6px 12px rgba(0, 0, 0, 0.175); 268 | margin-top: 1px; 269 | } 270 | 271 | .select2-container--bootstrap .select2-results > .select2-results__options { 272 | max-height: 200px; 273 | overflow-y: auto; 274 | } 275 | 276 | .select2-container--bootstrap .select2-selection--single { 277 | height: 34px; 278 | line-height: 1.42857143; 279 | padding: 6px 24px 6px 12px; 280 | /** 281 | * Adjust the single Select2's dropdown arrow button appearance. 282 | */ 283 | } 284 | 285 | .select2-container--bootstrap .select2-selection--single .select2-selection__arrow { 286 | position: absolute; 287 | bottom: 0; 288 | right: 12px; 289 | top: 0; 290 | width: 4px; 291 | } 292 | 293 | .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b { 294 | border-color: #999 transparent transparent transparent; 295 | border-style: solid; 296 | border-width: 4px 4px 0 4px; 297 | height: 0; 298 | left: 0; 299 | margin-left: -4px; 300 | margin-top: -2px; 301 | position: absolute; 302 | top: 50%; 303 | width: 0; 304 | } 305 | 306 | .select2-container--bootstrap .select2-selection--single .select2-selection__rendered { 307 | color: #555555; 308 | padding: 0; 309 | } 310 | 311 | .select2-container--bootstrap .select2-selection--single .select2-selection__placeholder { 312 | color: #999; 313 | } 314 | 315 | .select2-container--bootstrap .select2-selection--multiple { 316 | min-height: 34px; 317 | padding: 0; 318 | height: auto; 319 | /** 320 | * Make Multi Select2's choices match Bootstrap 3's default button styles. 321 | */ 322 | /** 323 | * Minus 2px borders. 324 | */ 325 | /** 326 | * Clear the selection. 327 | */ 328 | } 329 | 330 | .select2-container--bootstrap .select2-selection--multiple .select2-selection__rendered { 331 | -webkit-box-sizing: border-box; 332 | -moz-box-sizing: border-box; 333 | box-sizing: border-box; 334 | display: block; 335 | line-height: 1.42857143; 336 | list-style: none; 337 | margin: 0; 338 | overflow: hidden; 339 | padding: 0; 340 | width: 100%; 341 | text-overflow: ellipsis; 342 | white-space: nowrap; 343 | } 344 | 345 | .select2-container--bootstrap .select2-selection--multiple .select2-selection__placeholder { 346 | color: #999; 347 | float: left; 348 | margin-top: 5px; 349 | } 350 | 351 | .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { 352 | color: #555555; 353 | background: #fff; 354 | border: 1px solid #ccc; 355 | border-radius: 4px; 356 | cursor: default; 357 | float: left; 358 | margin: 5px 0 0 6px; 359 | padding: 0 6px; 360 | } 361 | 362 | .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { 363 | background: transparent; 364 | padding: 0 12px; 365 | height: 32px; 366 | line-height: 1.42857143; 367 | margin-top: 0; 368 | min-width: 5em; 369 | } 370 | 371 | .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove { 372 | color: #999; 373 | cursor: pointer; 374 | display: inline-block; 375 | font-weight: bold; 376 | margin-right: 3px; 377 | } 378 | 379 | .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice__remove:hover { 380 | color: #333; 381 | } 382 | 383 | .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { 384 | margin-top: 6px; 385 | } 386 | 387 | .select2-container--bootstrap .select2-selection--single.input-sm, 388 | .input-group-sm .select2-container--bootstrap .select2-selection--single, 389 | .form-group-sm .select2-container--bootstrap .select2-selection--single { 390 | border-radius: 3px; 391 | font-size: 12px; 392 | height: 30px; 393 | line-height: 1.5; 394 | padding: 5px 22px 5px 10px; 395 | /* 2 */ 396 | } 397 | 398 | .select2-container--bootstrap .select2-selection--single.input-sm .select2-selection__arrow b, 399 | .input-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b, 400 | .form-group-sm .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b { 401 | margin-left: -5px; 402 | } 403 | 404 | .select2-container--bootstrap .select2-selection--multiple.input-sm, 405 | .input-group-sm .select2-container--bootstrap .select2-selection--multiple, 406 | .form-group-sm .select2-container--bootstrap .select2-selection--multiple { 407 | min-height: 30px; 408 | border-radius: 3px; 409 | } 410 | 411 | .select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__choice, 412 | .input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice, 413 | .form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { 414 | font-size: 12px; 415 | line-height: 1.5; 416 | margin: 4px 0 0 5px; 417 | padding: 0 5px; 418 | } 419 | 420 | .select2-container--bootstrap .select2-selection--multiple.input-sm .select2-search--inline .select2-search__field, 421 | .input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field, 422 | .form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { 423 | padding: 0 10px; 424 | font-size: 12px; 425 | height: 28px; 426 | line-height: 1.5; 427 | } 428 | 429 | .select2-container--bootstrap .select2-selection--multiple.input-sm .select2-selection__clear, 430 | .input-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear, 431 | .form-group-sm .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { 432 | margin-top: 5px; 433 | } 434 | 435 | .select2-container--bootstrap .select2-selection--single.input-lg, 436 | .input-group-lg .select2-container--bootstrap .select2-selection--single, 437 | .form-group-lg .select2-container--bootstrap .select2-selection--single { 438 | border-radius: 6px; 439 | font-size: 18px; 440 | height: 46px; 441 | line-height: 1.3333333; 442 | padding: 10px 31px 10px 16px; 443 | /* 1 */ 444 | } 445 | 446 | .select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow, 447 | .input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow, 448 | .form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow { 449 | width: 5px; 450 | } 451 | 452 | .select2-container--bootstrap .select2-selection--single.input-lg .select2-selection__arrow b, 453 | .input-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b, 454 | .form-group-lg .select2-container--bootstrap .select2-selection--single .select2-selection__arrow b { 455 | border-width: 5px 5px 0 5px; 456 | margin-left: -5px; 457 | margin-left: -10px; 458 | margin-top: -2.5px; 459 | } 460 | 461 | .select2-container--bootstrap .select2-selection--multiple.input-lg, 462 | .input-group-lg .select2-container--bootstrap .select2-selection--multiple, 463 | .form-group-lg .select2-container--bootstrap .select2-selection--multiple { 464 | min-height: 46px; 465 | border-radius: 6px; 466 | } 467 | 468 | .select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__choice, 469 | .input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice, 470 | .form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__choice { 471 | font-size: 18px; 472 | line-height: 1.3333333; 473 | border-radius: 4px; 474 | margin: 9px 0 0 8px; 475 | padding: 0 10px; 476 | } 477 | 478 | .select2-container--bootstrap .select2-selection--multiple.input-lg .select2-search--inline .select2-search__field, 479 | .input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field, 480 | .form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-search--inline .select2-search__field { 481 | padding: 0 16px; 482 | font-size: 18px; 483 | height: 44px; 484 | line-height: 1.3333333; 485 | } 486 | 487 | .select2-container--bootstrap .select2-selection--multiple.input-lg .select2-selection__clear, 488 | .input-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear, 489 | .form-group-lg .select2-container--bootstrap .select2-selection--multiple .select2-selection__clear { 490 | margin-top: 10px; 491 | } 492 | 493 | .select2-container--bootstrap .select2-selection.input-lg.select2-container--open .select2-selection--single { 494 | /** 495 | * Make the dropdown arrow point up while the dropdown is visible. 496 | */ 497 | } 498 | 499 | .select2-container--bootstrap .select2-selection.input-lg.select2-container--open .select2-selection--single .select2-selection__arrow b { 500 | border-color: transparent transparent #999 transparent; 501 | border-width: 0 5px 5px 5px; 502 | } 503 | 504 | .input-group-lg .select2-container--bootstrap .select2-selection.select2-container--open .select2-selection--single { 505 | /** 506 | * Make the dropdown arrow point up while the dropdown is visible. 507 | */ 508 | } 509 | 510 | .input-group-lg .select2-container--bootstrap .select2-selection.select2-container--open .select2-selection--single .select2-selection__arrow b { 511 | border-color: transparent transparent #999 transparent; 512 | border-width: 0 5px 5px 5px; 513 | } 514 | 515 | .select2-container--bootstrap[dir="rtl"] { 516 | /** 517 | * Single Select2 518 | * 519 | * 1. Makes sure that .select2-selection__placeholder is positioned 520 | * correctly. 521 | */ 522 | /** 523 | * Multiple Select2 524 | */ 525 | } 526 | 527 | .select2-container--bootstrap[dir="rtl"] .select2-selection--single { 528 | padding-left: 24px; 529 | padding-right: 12px; 530 | } 531 | 532 | .select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__rendered { 533 | padding-right: 0; 534 | padding-left: 0; 535 | text-align: right; 536 | /* 1 */ 537 | } 538 | 539 | .select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__clear { 540 | float: left; 541 | } 542 | 543 | .select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__arrow { 544 | left: 12px; 545 | right: auto; 546 | } 547 | 548 | .select2-container--bootstrap[dir="rtl"] .select2-selection--single .select2-selection__arrow b { 549 | margin-left: 0; 550 | } 551 | 552 | .select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice, 553 | .select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder { 554 | float: right; 555 | } 556 | 557 | .select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice { 558 | margin-left: 0; 559 | margin-right: 6px; 560 | } 561 | 562 | .select2-container--bootstrap[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove { 563 | margin-left: 2px; 564 | margin-right: auto; 565 | } 566 | 567 | /*------------------------------------* #ADDITIONAL GOODIES 568 | \*------------------------------------*/ 569 | /** 570 | * Address Bootstrap's validation states 571 | * 572 | * If a Select2 widget parent has one of Bootstrap's validation state modifier 573 | * classes, adjust Select2's border colors and focus states accordingly. 574 | * You may apply said classes to the Select2 dropdown (body > .select2-container) 575 | * via JavaScript match Bootstraps' to make its styles match. 576 | * 577 | * @see http://getbootstrap.com/css/#forms-control-validation 578 | */ 579 | .has-warning .select2-dropdown, 580 | .has-warning .select2-selection { 581 | border-color: #8a6d3b; 582 | } 583 | 584 | .has-warning .select2-container--focus .select2-selection, 585 | .has-warning .select2-container--open .select2-selection { 586 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; 587 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b; 588 | border-color: #66512c; 589 | } 590 | 591 | .has-warning.select2-drop-active { 592 | border-color: #66512c; 593 | } 594 | 595 | .has-warning.select2-drop-active.select2-drop.select2-drop-above { 596 | border-top-color: #66512c; 597 | } 598 | 599 | .has-error .select2-dropdown, 600 | .has-error .select2-selection { 601 | border-color: #a94442; 602 | } 603 | 604 | .has-error .select2-container--focus .select2-selection, 605 | .has-error .select2-container--open .select2-selection { 606 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; 607 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; 608 | border-color: #843534; 609 | } 610 | 611 | .has-error.select2-drop-active { 612 | border-color: #843534; 613 | } 614 | 615 | .has-error.select2-drop-active.select2-drop.select2-drop-above { 616 | border-top-color: #843534; 617 | } 618 | 619 | .has-success .select2-dropdown, 620 | .has-success .select2-selection { 621 | border-color: #3c763d; 622 | } 623 | 624 | .has-success .select2-container--focus .select2-selection, 625 | .has-success .select2-container--open .select2-selection { 626 | -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; 627 | box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; 628 | border-color: #2b542c; 629 | } 630 | 631 | .has-success.select2-drop-active { 632 | border-color: #2b542c; 633 | } 634 | 635 | .has-success.select2-drop-active.select2-drop.select2-drop-above { 636 | border-top-color: #2b542c; 637 | } 638 | 639 | /** 640 | * Select2 widgets in Bootstrap Input Groups 641 | * 642 | * When Select2 widgets are combined with other elements using Bootstraps 643 | * "Input Group" component, we don't want specific edges of the Select2 644 | * container to have a border-radius. 645 | * 646 | * Use .select2-bootstrap-prepend and .select2-bootstrap-append on 647 | * a Bootstrap 3 .input-group to let the contained Select2 widget know which 648 | * edges should not be rounded as they are directly followed by another element. 649 | * 650 | * @see http://getbootstrap.com/components/#input-groups 651 | */ 652 | /** 653 | * Mimick Bootstraps .input-group .form-control styles. 654 | * 655 | * @see https://github.com/twbs/bootstrap/blob/master/less/input-groups.less 656 | */ 657 | .input-group .select2-container--bootstrap { 658 | display: table; 659 | table-layout: fixed; 660 | position: relative; 661 | z-index: 2; 662 | float: left; 663 | width: 100%; 664 | margin-bottom: 0; 665 | /** 666 | * Adjust z-index like Bootstrap does to show the focus-box-shadow 667 | * above appended buttons in .input-group and .form-group. 668 | */ 669 | } 670 | 671 | .input-group .select2-container--bootstrap.select2-container--open, .input-group .select2-container--bootstrap.select2-container--focus { 672 | z-index: 3; 673 | } 674 | 675 | .input-group.select2-bootstrap-prepend .select2-container--bootstrap .select2-selection { 676 | border-bottom-left-radius: 0; 677 | border-top-left-radius: 0; 678 | } 679 | 680 | .input-group.select2-bootstrap-append .select2-container--bootstrap .select2-selection { 681 | border-bottom-right-radius: 0; 682 | border-top-right-radius: 0; 683 | } 684 | 685 | /** 686 | * Adjust alignment of Bootstrap buttons in Bootstrap Input Groups to address 687 | * Multi Select2's height which - depending on how many elements have been selected - 688 | * may grow taller than its initial size. 689 | * 690 | * @see http://getbootstrap.com/components/#input-groups 691 | */ 692 | .select2-bootstrap-append .select2-container--bootstrap, 693 | .select2-bootstrap-append .input-group-btn, 694 | .select2-bootstrap-append .input-group-btn .btn, 695 | .select2-bootstrap-prepend .select2-container--bootstrap, 696 | .select2-bootstrap-prepend .input-group-btn, 697 | .select2-bootstrap-prepend .input-group-btn .btn { 698 | vertical-align: top; 699 | } 700 | 701 | /** 702 | * Temporary fix for https://github.com/select2/select2-bootstrap-theme/issues/9 703 | * 704 | * Provides `!important` for certain properties of the class applied to the 705 | * original ` 20 | 21 | 22 |

Not registered? Create an account

23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /code/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "header.html" %} 2 | {% block body %} 3 | 4 | 5 |
6 |

Please login:

7 |
8 |
9 | 11 | 13 | 14 |
15 | {% if error %} 16 |

Error: {{ error }}

17 | {% endif %} 18 | 19 | 20 |
21 | 22 | 23 | {% endblock %} -------------------------------------------------------------------------------- /code/templates/movie.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Movie Profile 8 | 9 | 10 | 11 | 12 | 13 | 86 | 87 | 88 | 124 | 125 | 126 | 127 | 128 | 129 |
130 |
131 | × 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 |
141 | 142 | 143 |
144 | 145 | 146 | ☰ Menu 147 | 148 |
149 | 150 | {%for a in range(0, 1)%} 151 | {% for item in minfo %} 152 |

{{item[4]}}

153 |
154 | 155 |
156 |

{{item[0]}}

157 |

{{item[1]}}

158 |

{{item[2]}}

159 | 160 |
161 |

{{m}}{{sqlrating[0]}}

162 |

{{t}}{{sqlrating[1]}}

163 |
164 |

{{avg}}

165 | 166 | 183 | 184 |
185 | {% endfor %} 186 | {% endfor %} 187 | 188 | 189 | 190 |
191 | 215 | 216 | 217 | 218 |
219 | 235 | 236 | 302 | 303 |
304 |
305 |
306 |
307 | 308 | 309 | 310 |
311 | {% for item in minfo %} 312 |

{{recmsg}}{{item[4]}}:

313 | {% endfor %} 314 | 315 | 363 | 364 | 365 | {%for a in range(0, 1)%} 366 |
367 | {% for item in rec %} 368 | 369 | 370 |
371 | 372 |
373 |
{{item[2]}}
374 |
375 |
376 |
377 | {% endfor %} 378 | 379 | {%for a in range(0, 1)%} 380 |
    381 | {% for item in notification[a] %} 382 |
  • 383 | {{item[3]}} 384 |
    385 | {{item[0]}} 386 |
    387 | {{item[1]}} 388 |
    389 | Rate: {{item[2]}} 390 |
  • 391 |
    392 | {% endfor %} 393 |
394 | {% endfor %} 395 | 396 |
397 |
398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 |
419 | {% endfor %} 420 | 421 | 430 | 431 | 432 | 433 | 434 | 435 | 436 |
437 | 438 | 439 | 452 | 453 | 468 | 469 | 470 | 471 | -------------------------------------------------------------------------------- /code/templates/profile.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 76 | 77 | 78 | 79 | 80 | Profile 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 |
90 | × 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 |
100 | 101 | 102 | 103 | 104 | ☰ Menu 105 | 106 | 107 |
108 |
109 | {%for a in range(0, 1)%} 110 | 111 | 117 | {%endfor%} 118 | 119 | 120 |
121 |

Watched List

122 | 123 | 124 | {%for a in range(0, 1)%} 125 |
    126 | {% for item in watched [a]%} 127 |
  • {{item[0]}}
  • 128 | {% endfor %} 129 | {% endfor %} 130 |
131 | 132 |
133 | 134 | 149 | 150 | 151 | 152 | 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /code/templates/register.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | REGISTER 7 | 8 | 9 | 10 |
11 |
12 |
13 |

Register

14 |
15 | 16 | 17 | 27 | 28 |
29 |
    30 |
  • 31 | 32 | 33 |
    34 |
  • 35 |
  • 36 | 37 | 38 |
    39 |
  • 40 | 41 |
42 |
43 | 44 | 67 | 68 | 69 |

Already registered? Sign In

70 | 71 |
72 |
73 |
74 |
75 | 76 | -------------------------------------------------------------------------------- /code/templates/regout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Demo 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | × 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 |
107 | 108 |
109 |
110 | {%for a in range(0, 1)%} 111 | 112 | 118 | {%endfor%} 119 | 120 | 121 |
122 | ☰ Menu 123 | 124 |

{{nmsg}}

125 | 126 | 127 | 128 |
129 |
    130 | {% for item in imdbid %} 131 |
  • 132 | 133 |
  • 134 | {% endfor %} 135 |
136 | 137 | {% for item in imdbid %} 138 |
139 |
140 |
141 | 142 |
143 |
144 |

{{item[1]}}

145 |

{{item[3]}}

146 |
147 |
148 |
149 | {% endfor %} 150 | 151 | 152 |
153 | 154 |
155 | 156 | 157 | 158 | 159 | 184 | 199 | 200 | 201 | 202 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /code/templates/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Genre 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 86 | 87 | 88 | 89 | 90 |
91 | × 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 | 102 |
103 |

Search

104 | 105 | ☰ Menu 106 | 107 | 108 |
109 | 110 |
111 | 112 | 113 | 161 | 162 | {% for item in res%} 163 | 171 | {% endfor %} 172 |
173 | 174 | 175 | 176 | 177 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /code/topm.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from dbconnect import connection 3 | 4 | a, config = connection() 5 | 6 | def plot(x): 7 | info = [] 8 | if len(str((x))) == 7: 9 | url = 'https://www.omdbapi.com/?i=tt' + str(x) + '&plot=full&r=json' 10 | 11 | elif len(str((x))) == 6: 12 | url = 'https://www.omdbapi.com/?i=tt0' + str(x) + '&plot=full&r=json' 13 | 14 | 15 | elif len(str((x))) == 5: 16 | url = 'https://www.omdbapi.com/?i=tt00' + str(x) + '&plot=full&r=json' 17 | 18 | elif len(str((x))) == 4: 19 | url = 'https://www.omdbapi.com/?i=tt000' + str(x) + '&plot=full&r=json' 20 | 21 | 22 | elif len(str((x))) == 3: 23 | url = 'https://www.omdbapi.com/?i=tt000' + str(x) + '&plot=full&r=json' 24 | 25 | response = requests.get(url) 26 | if response.json()['Response'] == "True": 27 | results = response.json()['Plot'] 28 | return results 29 | 30 | else: 31 | return "nothing is wokring" 32 | 33 | return info 34 | 35 | 36 | def movie_info(x): 37 | info = [] 38 | if len(str((x))) == 7: 39 | url = 'https://www.omdbapi.com/?i=tt'+str(x)+'&plot=full&r=json' 40 | 41 | elif len(str((x))) == 6: 42 | url = 'https://www.omdbapi.com/?i=tt0' + str(x) + '&plot=full&r=json' 43 | 44 | 45 | elif len(str((x))) == 5: 46 | url = 'https://www.omdbapi.com/?i=tt00' + str(x) + '&plot=full&r=json' 47 | 48 | elif len(str((x))) == 4: 49 | url = 'https://www.omdbapi.com/?i=tt000' + str(x) + '&plot=full&r=json' 50 | 51 | 52 | elif len(str((x))) == 3: 53 | url = 'https://www.omdbapi.com/?i=tt000' + str(x) + '&plot=full&r=json' 54 | 55 | response = requests.get(url) 56 | if response.json()['Response'] == "True": 57 | results = response.json()['Plot'] 58 | genre = response.json()['Genre'] 59 | poster = response.json()['Poster'] 60 | runtime = response.json()['Runtime'] 61 | title = response.json()['Title'] 62 | 63 | 64 | info.append((results, genre, runtime, poster,title)) 65 | 66 | else: 67 | return "nothing is wokring" 68 | 69 | return info 70 | 71 | 72 | # print(movie_info(1375666)) -------------------------------------------------------------------------------- /code/user_Rec.py: -------------------------------------------------------------------------------- 1 | from dbconnect import connection 2 | import pandas as pd 3 | import numpy as np 4 | from topm import plot 5 | 6 | a, config = connection() 7 | 8 | rating = 'SELECT * FROM ratings' 9 | ratings = pd.read_sql(rating, config) 10 | ratings.head() 11 | 12 | del ratings['timestamp'] 13 | 14 | 15 | movie = 'SELECT * FROM movies' 16 | movies = pd.read_sql(movie, config) 17 | movies.head() 18 | 19 | user = 'SELECT * FROM users' 20 | users = pd.read_sql(user, config) 21 | users.head() 22 | 23 | ratings_df = pd.merge(ratings, movies, on='movie_id')[['user_id', 'title', 'movie_id','ratings']] 24 | 25 | ratings_mrtx_df = ratings_df.pivot_table(values='ratings', index='user_id', columns='title') 26 | ratings_mrtx_df.fillna(0, inplace=True) 27 | 28 | movie_index = ratings_mrtx_df.columns 29 | 30 | 31 | ratings_mrtx_df.head() 32 | 33 | corr_matrix = np.corrcoef(ratings_mrtx_df.T) 34 | corr_matrix.shape 35 | 36 | 37 | def get_similar_movies(movie_title): 38 | '''Returns correlation vector for a movie''' 39 | movie_idx = list(movie_index).index(movie_title) 40 | return corr_matrix[movie_idx] 41 | 42 | def get_movie_recommendations(user_movies): 43 | '''given a set of movies, it returns all the movies sorted by their correlation with the user''' 44 | movie_similarities = np.zeros(corr_matrix.shape[0]) 45 | for movie_id in user_movies: 46 | movie_similarities = movie_similarities + get_similar_movies(movie_id) 47 | 48 | 49 | similar_movies_df = pd.DataFrame({ 50 | 'title': movie_index, 51 | 'sum_similarity': movie_similarities 52 | }) 53 | # similar_movies_df = similar_movies_df.drop.loc[similar_movies_df['title'].str.contains('Gladiator')] 54 | # similar_movies_df.drop(similar_movies_df['title'].str.contains('Star Wars: Episode IV - A New Hope'), inplace=True) 55 | 56 | # for index, row in similar_movies_df.iterrows(): 57 | # if index == 4963: 58 | # # print(index) 59 | # similar_movies_df.drop(index, inplace=True) 60 | 61 | # similar_movies_df = similar_movies_df.loc[similar_movies_df['title'].str.contains('Gladiator') 62 | similar_movies_df = similar_movies_df.sort_values(by=['sum_similarity'], ascending=False) 63 | # print(similar_movies_df) 64 | return similar_movies_df 65 | 66 | # 67 | # smpl_user = 685 68 | # ss = ratings_df[ratings_df.user_id==smpl_user].sort_values(by=['ratings'], ascending=False) 69 | # print(ss) 70 | # 71 | # smpl_user_movies = ratings_df[ratings_df.user_id == smpl_user].title.tolist() 72 | # recommendations = get_movie_recommendations(smpl_user_movies) 73 | # l= len(smpl_user_movies) 74 | # #We get the top 20 recommended movies 75 | # innerl = l+20 76 | # s = recommendations.title.head(20) 77 | # 78 | # print(s) 79 | 80 | # # # 81 | 82 | def get_user_rec(smpl_user): 83 | ratings_df[ratings_df.user_id==smpl_user].sort_values(by=['ratings'], ascending=False) 84 | 85 | smpl_user_movies = ratings_df[ratings_df.user_id==smpl_user].title.tolist() 86 | recommendations = get_movie_recommendations(smpl_user_movies) 87 | l= 20 88 | 89 | #We get the top 20 recommended movies 90 | innerl = l+24 91 | rec = recommendations.title.head(innerl)[l:] 92 | # 93 | reviews = [] 94 | 95 | for item in rec: 96 | a.execute('SELECT img from movies WHERE title =%s', (str(item))) 97 | img = a.fetchone() 98 | a.execute('SELECT movie_id from movies WHERE title =%s', (str(item))) 99 | mid = a.fetchone() 100 | a.execute('SELECT imdbid from links WHERE movie_id =%s', (int(mid[0]))) 101 | imdb = a.fetchone() 102 | 103 | x = plot(int(imdb[0])) 104 | 105 | reviews.append((int(imdb[0]), item, img[0], str(x))) 106 | 107 | 108 | return reviews 109 | 110 | # # print(get_user_rec(9)) 111 | # 112 | # 113 | # 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /code/youtube.py: -------------------------------------------------------------------------------- 1 | from dbconnect import connection 2 | import pandas as pd 3 | import numpy as np 4 | 5 | 6 | a, config = connection() 7 | 8 | movie = 'SELECT * FROM movies' 9 | movies_df = pd.read_sql(movie, config) 10 | movies_df.head() 11 | movies_df = pd.concat([movies_df, movies_df.genre.str.get_dummies(sep='|')], axis=1) 12 | movies_df.head() 13 | 14 | movies_df.drop(['img'], axis=1, inplace=True) 15 | movies_df.drop(['title'], axis=1, inplace=True) 16 | movies_df.drop(['movie_id'], axis=1, inplace=True) 17 | movies_df.drop(['genre'], axis=1, inplace=True) 18 | movies_df.drop(['year'], axis=1, inplace=True) 19 | # for genre in genres: 20 | # print(genre) 21 | 22 | def get_genre(): 23 | 24 | genres = list(movies_df) 25 | del genres[0] 26 | 27 | return genres 28 | 29 | def gen_movies(x): 30 | # a.execute('SELECT title from movies where genre LIKE %s',("%" +"action" +"%")) 31 | # mov = a.fetchall() 32 | gen = str(x) 33 | movies = 'SELECT * FROM movies' 34 | mov = pd.read_sql(movies, config) 35 | mov.head() 36 | 37 | rate = 'SELECT * FROM ratings' 38 | rt_df = pd.read_sql(rate, config) 39 | rt_df.head() 40 | 41 | lens = pd.merge(mov, rt_df) 42 | 43 | lens.drop(['year'], axis=1, inplace=True) 44 | lens.drop(['img'], axis=1, inplace=True) 45 | # lens = lens[lens['genre'].str.contains("Crime")] 46 | 47 | if gen == "Action": 48 | lens = lens[lens['genre'].str.contains("Action")] 49 | 50 | elif gen == "Adventure": 51 | lens = lens[lens['genre'].str.contains("Adventure")] 52 | 53 | elif gen == "Animation": 54 | lens = lens[lens['genre'].str.contains("Animation")] 55 | 56 | elif gen == "Children": 57 | lens = lens[lens['genre'].str.contains("Children")] 58 | 59 | elif gen == "Comedy": 60 | lens = lens[lens['genre'].str.contains("Comedy")] 61 | 62 | elif gen == "Crime": 63 | lens = lens[lens['genre'].str.contains("Crime")] 64 | 65 | elif gen == "Documentary": 66 | lens = lens[lens['genre'].str.contains("Documentary")] 67 | 68 | elif gen == "Drama": 69 | lens = lens[lens['genre'].str.contains("Drama")] 70 | 71 | elif gen == "Fantasy": 72 | lens = lens[lens['genre'].str.contains("Fantasy")] 73 | 74 | elif gen == "Film-Noir": 75 | lens = lens[lens['genre'].str.contains("Film-Noir")] 76 | 77 | elif gen == "Horror": 78 | lens = lens[lens['genre'].str.contains("Horror")] 79 | 80 | elif gen == "IMAX": 81 | lens = lens[lens['genre'].str.contains("IMAX")] 82 | 83 | elif gen == "Musical": 84 | lens = lens[lens['genre'].str.contains("Musical")] 85 | 86 | elif gen == "Mystery": 87 | lens = lens[lens['genre'].str.contains("Mystery")] 88 | 89 | elif gen == "Romance": 90 | lens = lens[lens['genre'].str.contains("Romance")] 91 | 92 | elif gen == "Sci-Fi": 93 | lens = lens[lens['genre'].str.contains("Sci-Fi")] 94 | 95 | elif gen == "Thriller": 96 | lens = lens[lens['genre'].str.contains("Thriller")] 97 | 98 | elif gen == "War": 99 | lens = lens[lens['genre'].str.contains("War")] 100 | 101 | elif gen == "Western": 102 | lens = lens[lens['genre'].str.contains("Western")] 103 | else: 104 | return "notworking" 105 | 106 | 107 | 108 | movie_stats = lens.groupby('title').agg({'ratings': [np.size, np.mean]}) 109 | movie_stats.head() 110 | atleast_100 = movie_stats['ratings']['size'] >= 30 111 | movie_stats = movie_stats[atleast_100].sort_values([('ratings', 'mean')], ascending=False)[:20] 112 | movie_stats.head() 113 | 114 | # print(movie_stats) 115 | 116 | # movie_stats.drop(['size'], axis=1, inplace=True) 117 | topgenmov = [] 118 | for index, row in movie_stats.iterrows(): 119 | a.execute('SELECT img from movies WHERE title =%s', (str(index))) 120 | img = a.fetchone() 121 | a.execute('SELECT movie_id from movies WHERE title =%s', (str(index))) 122 | mid = a.fetchone() 123 | a.execute('SELECT imdbid from links WHERE movie_id =%s', (int(mid[0]))) 124 | imdb = a.fetchone() 125 | 126 | # print(imdb[0]) 127 | topgenmov.append((index,img[0],imdb[0])) 128 | 129 | 130 | # print(topgenmov) 131 | # list(mov.movie_id) 132 | # print(name) 133 | return topgenmov 134 | 135 | # print(gen_movies("Action")) -------------------------------------------------------------------------------- /documentation/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/documentation/.DS_Store -------------------------------------------------------------------------------- /documentation/Document.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/anasdjebbari/Python-Movie-Recommendation/c9512aa9f1c8ddf44bf033189420911e227918d2/documentation/Document.pdf --------------------------------------------------------------------------------