diff --git a/project/__init__.py b/project/__init__.py index 1fe76c5a3c811955b94e4886a7105d0e19044fe4..5b1ba892afb65ec7c69465173e5284b2715bb8a6 100644 --- a/project/__init__.py +++ b/project/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- __version__ = '0.1' from functools import wraps -from flask import Flask, session, redirect, url_for, request +from flask import Flask, session, redirect, url_for, request, render_template from flask_debugtoolbar import DebugToolbarExtension from flask.ext.mongoengine import MongoEngine import sys @@ -49,6 +49,5 @@ def security(role=None): return func(*args,**kwargs); return security_check return wrapper - from project.controllers import * diff --git a/project/controllers/library.py b/project/controllers/library.py index 350403f62247853ae3d06cc8889e8109b8a80fee..904a47294abe7944063c95e626960fb263be75d1 100644 --- a/project/controllers/library.py +++ b/project/controllers/library.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- from project import app, security -from flask import render_template, request, session +from flask import render_template, request, session, redirect, url_for from flask.ext.wtf import Form, TextField, validators from project.model.Library import Library from project.model.User import User @@ -18,9 +18,31 @@ def libraries(user = None): def library(name,user=None): from project.model.Movie import Movie library = Library.objects(user=user,name=name,unit='Movie').first() - return render_template('library/library.html',library=library) + if not library: + return render_template('404.html',message='Unable to find given Library',user=user),404 + return render_template('library/library.html',library=library,user=user) @app.route('/libraries/<name>/<int:index>') @security('user') def libraryItem(name, index,user=None): - return render_template('library/libraryItem.html') + from project.model.Movie import Movie + library = Library.objects(user=user,name=name,unit='Movie').first() + if not library: + return render_template('404.html',message='Unable to find given Library',user=user),404 + movie = library.hydrateUnit(index) + if not movie: + return render_template('404.html',message='Unable to find given Movie',user=user),404 + return render_template('library/libraryItem.html',item=movie,user=user) + +@app.route('/libraries/<name>/remove/<int:index>', methods=['POST']) +@security('user') +def removelibraryItem(name, index,user=None): + from project.model.Movie import Movie + library = Library.objects(user=user,name=name,unit='Movie').first() + if not library: + return render_template('404.html',message='Unable to find given Library',user=user),404 + movie = library.hydrateUnit(index) + if not movie: + return render_template('404.html',message='Unable to find given Movie',user=user),404 + library.removeUnit(movie) + return jsonify(response='success',type='redirect',path=url_for(endpoint='library',name=name,_external=True)) \ No newline at end of file diff --git a/project/controllers/login.py b/project/controllers/login.py index 94102c871f001e34d24a19fe40cab3848865c380..9af30f6fbfd61dd79a2573a60ddcc94175153470 100644 --- a/project/controllers/login.py +++ b/project/controllers/login.py @@ -1,14 +1,11 @@ # -*- coding: utf-8 -*- from project import app -from flask import render_template, request, session, redirect, url_for +from flask import render_template, request, session, redirect, url_for, jsonify from flask.ext.wtf import Form, TextField, validators from project.model.User import User -@app.route('/login', methods=['GET', 'POST']) +@app.route('/login-ajax', methods=['POST']) def login(): - if request.method == 'GET': - return render_template('login/master.html') - if request.method == 'POST': email = request.form['email'] password = request.form['password'] @@ -16,40 +13,32 @@ def login(): if user is not None: session['user'] = user.toJSON() - return redirect(url_for('libraries')) + return jsonify(response='success',type='redirect',path=url_for(endpoint='libraries',_external=True)) else: error = "That email or password is not valid. Please check your credentials and try again." - return render_template('login/master.html', error=error) + return jsonify(response='error',message=error),401 - return render_template('login/master.html') - -@app.route('/signup', methods=['GET', 'POST']) +@app.route('/signup-ajax', methods=['POST']) def signup(): - if request.method == 'GET': - return render_template('login/signup.html') - if request.method == 'POST': email = request.form['email'] password = request.form['password'] passwordConfirm = request.form['passwordConfirm'] - if password == passwordConfirm: - #check unique user - if len(User.objects(email=email)) == 0: - from project.model.Library import Library - #if unique, create user - user = User.createUser(email, password) - user.addRole('user').save() - session['user'] = user.toJSON() - Library(user=user, unit='Movie', name='Master').save() - Library(user=user, unit='Movie', name='Borrowed').save() - return redirect(url_for('libraries')) - else: - error = "The email provided is already in use with another account." - return render_template('login/signup.html', error=error, email=email) - else: + if password != passwordConfirm: error = "The passwords you entered did not match. Please try again." - return render_template('login/signup.html', error=error, email=email) - return render_template('login/signup.html') + return jsonify(response='error',message=error),400 + + if len(User.objects(email=email)) > 0: + error = "The email provided is already in use with another account." + return jsonify(response='error',message=error),400 + + from project.model.Library import Library + user = User.createUser(email, password) + user.addRole('user').save() + session['user'] = user.toJSON() + Library(user=user, unit='Movie', name='Master').save() + Library(user=user, unit='Movie', name='Borrowed').save() + return jsonify(response='success',type='redirect',path=url_for(endpoint='libraries',_external=True)) @app.route('/logout', methods=['GET', 'POST']) def logout(): diff --git a/project/model/Library.py b/project/model/Library.py index 70c924013904e888d27ab69687d9912302520d09..b9ecf9b127a80c42d77ba1d33d2bbe01238d9871 100644 --- a/project/model/Library.py +++ b/project/model/Library.py @@ -24,7 +24,7 @@ class Library(db.Document): if self.unit == type(unit).__name__: value = unit[self.lookup_attribute] if value is not None and value in self.collection: - self.collection.remove("%s" % value) + self.collection.remove("%s" % value).save() else: return self else: @@ -34,7 +34,7 @@ class Library(db.Document): # @param index --represents the index in the Library collection of the object def hydrateUnit(self, index): if index < 0 or index > self.collection.count: - raise Exception("Invalid index for Library %s" % self.name) + return None attr = {} attr[self.lookup_attribute] = self.collection[index] model = getattr(sys.modules["project.model.%s" % self.unit], self.unit) diff --git a/project/static/css/style.css b/project/static/css/style.css index dcbbde0d5ebb604aeccc10df8bc2141259d8766c..177d079906c5af196cc6cad80a44b23bae915f64 100644 --- a/project/static/css/style.css +++ b/project/static/css/style.css @@ -1,71 +1,24 @@ -body { - font-family: sans-serif; - background: #eee; +html,body { + height: 100%; } -a,h1,h2 { - color: #377BA8; +#wrap { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -60px; } -h1,h2 { - font-family: 'Georgia', serif; - margin: 0; +#push, +#footer { + height: 60px; } -h1 { - border-bottom: 2px solid #eee; -} - -h2 { - font-size: 1.2em; -} - -.page { - margin: 2em auto; - width: 35em; - border: 5px solid #ccc; - padding: 0.8em; - background: white; -} - -.entries { - list-style: none; - margin: 0; - padding: 0; -} - -.entries li { - margin: 0.8em 1.2em; -} - -.entries li h2 { - margin-left: -1em; -} - -.add-entry { - font-size: 0.9em; - border-bottom: 1px solid #ccc; -} - -.add-entry dl { - font-weight: bold; -} - -.metanav { - text-align: right; - font-size: 0.8em; - padding: 0.3em; - margin-bottom: 1em; - background: #fafafa; -} - -.flash { - background: #CEE5F5; - padding: 0.5em; - border: 1px solid #AACBE2; -} - -.error { - background: #F0D6D6; - padding: 0.5em; +@media (max-width: 767px) { + #footer { + margin-left: -20px; + margin-right: -20px; + padding-left: 20px; + padding-right: 20px; + } } \ No newline at end of file diff --git a/project/templates/404.html b/project/templates/404.html new file mode 100644 index 0000000000000000000000000000000000000000..d9366f72ef9384b50cd1f376f1d65f47b99c4288 --- /dev/null +++ b/project/templates/404.html @@ -0,0 +1,14 @@ +{% extends "usernav.html" if user else 'index.html'%} +{% block title %}Page Not Found{% endblock %} +{% block content %} + <div style="width: 400px;margin: 0 auto;"> + <h1>Page Not Found</h1> + <p> + {% if message %} + {{ message }} + {% else %} + What you were looking for is just not there. + {% endif %} + </p> +</div> +{% endblock %} \ No newline at end of file diff --git a/project/templates/home/home.html b/project/templates/home/home.html index 534d0d9e5fc2ef1e50b0c0f4651cffda3d52ce29..b0442808195c905202477212fb0afef7af4ec419 100644 --- a/project/templates/home/home.html +++ b/project/templates/home/home.html @@ -27,3 +27,8 @@ </div> </div> {% endblock %} + +{% block modals %} + {% include "home/login_modal.html" %} + {% include "home/signup_modal.html" %} +{% endblock %} diff --git a/project/templates/home/login_modal.html b/project/templates/home/login_modal.html new file mode 100644 index 0000000000000000000000000000000000000000..d159d8dfc1108e00519f4ec923d23f060887ed0f --- /dev/null +++ b/project/templates/home/login_modal.html @@ -0,0 +1,32 @@ +{% extends "modal_base.html" %} +{% block title %} Login {% endblock %} +{% block modal_content %} + <form class="form-horizontal" id="login" role="form" method="post" action="{{ url_for('login') }}"> + <div class="form-group"> + <label for="userEmail" class="col-sm-3 control-label">Email</label> + <div class="col-xs-9 col-md-6"> + <input type="email" class="form-control" id="userEmail" name="email" placeholder="Email address"> + </div> + </div> + <div class="form-group"> + <label for="userPassword" class="col-sm-3 control-label">Password</label> + <div class="col-xs-9 col-md-6"> + <input type="password" class="form-control" id="userPassword" name="password" placeholder="Password"> + </div> + </div> + </form> +{% endblock %} +{% block accept_button_text %}Submit{% endblock %} +{% block ajax_url %}'{{ url_for('login') }}'{% endblock %} +{% block form_id %}login{% endblock %} +{% block validation_rules %} +rules: { + // simple rule, converted to {required:true} + password: "required", + // compound rule + email: { + required: true, + email: true + } +} +{% endblock %} \ No newline at end of file diff --git a/project/templates/home/signup_modal.html b/project/templates/home/signup_modal.html new file mode 100644 index 0000000000000000000000000000000000000000..db48d3c0a586be6bb74214b4cc5f205086393084 --- /dev/null +++ b/project/templates/home/signup_modal.html @@ -0,0 +1,41 @@ +{% extends "modal_base.html" %} +{% block title %} Sign Up {% endblock %} +{% block modal_content %} + <form class="form-horizontal" id="signup" role="form" method="post" action="{{ url_for('signup') }}"> + <div class="form-group"> + <label for="userEmail" class="col-sm-3 control-label">Email</label> + <div class="col-xs-9 col-md-6"> + <input type="email" class="form-control" id="userEmail" name="email" placeholder="Email address"> + </div> + </div> + <div class="form-group"> + <label for="userPassword" class="col-sm-3 control-label">Password</label> + <div class="col-xs-9 col-md-6"> + <input type="password" class="form-control" id="userPassword" name="password" placeholder="Password"> + </div> + </div> + <div class="form-group"> + <label for="userPasswordConfirm" class="col-sm-3 control-label">Confirm Password</label> + <div class="col-xs-9 col-md-6"> + <input type="password" class="form-control" id="userPasswordConfirm" name="passwordConfirm" placeholder="Password"> + </div> + </div> + </form> +{% endblock %} +{% block accept_button_text %}Submit{% endblock %} +{% block ajax_url %}'{{ url_for('signup') }}'{% endblock %} +{% block form_id %}signup{% endblock %} +{% block validation_rules %} +rules: { + password: "required", + passwordConfirm: { + required: true, + minlength: 5, + equalTo: "#userPassword" + }, + email: { + required: true, + email: true + } +} +{% endblock %} \ No newline at end of file diff --git a/project/templates/index.html b/project/templates/index.html index 285b5a0bffdf0b8171d2e184cda96efa1e37da9c..d28cb64e2edb6c9fe05f3efe83b611f1e48976d9 100644 --- a/project/templates/index.html +++ b/project/templates/index.html @@ -5,16 +5,17 @@ <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1"> - <title>My Movie Library</title> + <title>{% block title %}My Movie Library{% endblock %}</title> <link rel="shortcut icon" href="{{ url_for('static', filename='images/favicon.ico') }}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap-slate.min.css') }}"> + <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/style.css') }}"> <!-- holderJS --> <script src="{{ url_for('static', filename='lib/holder.js') }}"></script> </head> <body> - <div class="page"> + <div id="wrap" class="page"> <div class="container"> <!-- Begin the navbar --> <nav class="navbar navbar-default" role="navigation"> @@ -38,8 +39,8 @@ {% block usernav %} <ul class="nav navbar-nav navbar-right"> - <li><a href="{{ url_for('login') }}">Login</a></li> - <li><a href="{{ url_for('signup') }}">Sign-up</a></li> + <li><a data-target="#login-form" data-toggle="modal" >Login</a></li> + <li><a data-target="#signup-form" data-toggle="modal" >Sign-up</a></li> </ul> {% endblock %} </div><!-- /.navbar-collapse --> @@ -50,21 +51,28 @@ {% block content %}{% endblock %} </div> + <div id="push"></div> + </div> + </div> + <div id="footer"> + <div class="content"> <div class="panel panel-default"> - <div class="panel-body"> - Open Source by Brian Wood & Nathan Bills, 2014 - </div> + <div class="panel-body"> + Open Source by Brian Wood & Nathan Bills, 2014 </div> + </div> </div> + </div> + <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> + <script src="http://ajax.aspnetcdn.com/ajax/jquery.validate/1.12.0/jquery.validate.min.js"></script> + <!-- Include all compiled plugins (below), or include individual files as needed --> + <script src="{{ url_for('static', filename='lib/bootstrap.min.js') }}"></script> + {% block javascript %} + {% endblock %} - <!-- jQuery (necessary for Bootstrap's JavaScript plugins) --> - <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> - <!-- Include all compiled plugins (below), or include individual files as needed --> - <script src="{{ url_for('static', filename='lib/bootstrap.min.js') }}"></script> - - {% block javascript %} - {% endblock %} - </div> + {% block modals %} + {% endblock %} </body> </html> diff --git a/project/templates/library/master.html b/project/templates/library/master.html index 883ff8306d02ead92d8e3319eb7304f5c6231405..729eb10b0e719331ff7fde023427c6de37145e5c 100644 --- a/project/templates/library/master.html +++ b/project/templates/library/master.html @@ -43,6 +43,6 @@ var libname = $(this).data('name'); window.document.location = libraryPath.replace('__lib__',libname); }) - })(jQuery); + }); </script> {% endblock %} \ No newline at end of file diff --git a/project/templates/login/master.html b/project/templates/login/master.html deleted file mode 100644 index 0e2db7ccc65f381e5dbbcd94fbc881f0fba80ef5..0000000000000000000000000000000000000000 --- a/project/templates/login/master.html +++ /dev/null @@ -1,36 +0,0 @@ -{% extends "index.html" %} -{% block content %} - <!-- Login --> - <div class="col-sm-12"> - <div class="col-xs-6 col-md-6"> - {% if error %} - <div class="alert alert-danger"> - {{ error }} - </div> - {% endif %} - - <div class="col-sm-offset-3 col-sm-12"> - <h3>Login</h3> - </div> - <form class="form-horizontal" role="form" method="post" action="{{ url_for('login') }}"> - <div class="form-group"> - <label for="userEmail" class="col-sm-3 control-label">Email</label> - <div class="col-xs-9 col-md-6"> - <input type="email" class="form-control" id="userEmail" name="email" placeholder="Email address"> - </div> - </div> - <div class="form-group"> - <label for="userPassword" class="col-sm-3 control-label">Password</label> - <div class="col-xs-9 col-md-6"> - <input type="password" class="form-control" id="userPassword" name="password" placeholder="Password"> - </div> - </div> - <div class="form-group"> - <div class="col-sm-offset-3 col-sm-10"> - <button type="submit" class="btn btn-default">Login</button> - </div> - </div> - </form> - </div> - </div> -{% endblock %} \ No newline at end of file diff --git a/project/templates/login/signup.html b/project/templates/login/signup.html deleted file mode 100644 index 9ac427f280fa14e9d891a684ccdc93c84fe1d8e3..0000000000000000000000000000000000000000 --- a/project/templates/login/signup.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "index.html" %} -{% block content %} - <!-- Sign-up --> - <div class="col-sm-12"> - <div class="col-xs-6 col-md-6"> - {% if error %} - <div class="alert alert-danger"> - {{ error }} - </div> - {% endif %} - - <div class="col-sm-offset-3 col-sm-12"> - <h3>Sign-up</h3> - </div> - <form class="form-horizontal" role="form" method="post" action="{{ url_for('signup') }}"> - <div class="form-group"> - <label for="userEmail" class="col-sm-3 control-label">Email</label> - <div class="col-xs-9 col-md-6"> - <input type="email" class="form-control" id="userEmail" name="email" placeholder="Email address"> - </div> - </div> - <div class="form-group"> - <label for="userPassword" class="col-sm-3 control-label">Password</label> - <div class="col-xs-9 col-md-6"> - <input type="password" class="form-control" id="userPassword" name="password" placeholder="Password"> - </div> - </div> - <div class="form-group"> - <label for="userPasswordConfirm" class="col-sm-3 control-label">Confirm Password</label> - <div class="col-xs-9 col-md-6"> - <input type="password" class="form-control" id="userPasswordConfirm" name="passwordConfirm" placeholder="Password"> - </div> - </div> - <div class="form-group"> - <div class="col-sm-offset-3 col-sm-9"> - <button type="submit" class="btn btn-default">Sign up</button> - </div> - </div> - </form> - </div> - </div> -{% endblock %} \ No newline at end of file diff --git a/project/templates/modal_base.html b/project/templates/modal_base.html new file mode 100644 index 0000000000000000000000000000000000000000..af46d888a73dadacfa2fec0d8cb8ecb5eddabc35 --- /dev/null +++ b/project/templates/modal_base.html @@ -0,0 +1,80 @@ +{% set modal_body_class = modal_body_class|default ~ ' modal-body' %} +<div class="modal fade" id="{% block form_id %}{% endblock %}-form"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> + <h3 class="blue bigger">{% block title %}{% endblock %}</h3> + </div> + <div class="{{ modal_body_class }}"> + <div class="alert alert-danger hide {{ self.form_id()}}-error"> + </div> + {% block modal_content %}{% endblock %} + </div> + <div class="modal-footer"> + {% block footer %} + {% block cancel_button %} + <button class="btn btn-default" data-dismiss="modal" type="button"> + <i class="icon-remove"></i> + {% block cancel_button_text %}Cancel{% endblock %} + </button> + {% endblock %} + {% block accept_button %} + <button class="btn btn-small btn-primary" id="{{ self.form_id() }}-accept" type="submit"> + <i class="icon-ok"></i> + {% block accept_button_text %}Save Changes{% endblock %} + </button> + {% endblock %} + {% endblock %} + </div> + </div><!-- /.modal-content --> + </div><!-- /.modal-dialog --> +</div><!-- /.modal --> +{% block javascripts %} + <script type="text/javascript"> + $(function(){ + var $form = $(document.getElementById("{{ self.form_id() }}")); + var $acceptButton = $(document.getElementById("{{ self.form_id() }}-accept")); + $acceptButton.on('click',function(){ + $.ajax({ + url: {% block ajax_url %}{% endblock %}, + data: $form.serialize(), + method: 'POST', + beforeSend: function(){ + $form.validate({ + {% block validation_rules %} + + {% endblock %} + }); + + return $form.valid(); + }, + success: function(data,status,jqXHR){ + {% block ajax_success %} + if(data['response'] == 'success'){ + if(data['type'] == 'redirect'){ + window.document.location = data['path']; + } else if (data['type'] == 'reload') { + window.document.location.reload(); + } + } + {% endblock %} + }, + error: function(jqXHR, textStatus, errorThrown){ + {% block ajax_fail %} + response = jqXHR.responseJSON; + $(".{{ self.form_id() }}-error").html(response['message']).removeClass('hide'); + {% endblock %} + } + }); + }); + $("#{{ self.form_id() }} input").keyup(function(event){ + if(event.keyCode == 13){ + $acceptButton.click(); + } + }); + }); + </script> +{% endblock %} +{% block additional_javascripts %} +{% endblock %} diff --git a/project/templates/usernav.html b/project/templates/usernav.html index 56e88bbe5101bbb1ef786c18a7deb2454b91569d..b875e665e3537b8b35ca4c01e21d8492313ce3ba 100644 --- a/project/templates/usernav.html +++ b/project/templates/usernav.html @@ -1,6 +1,6 @@ {% extends "index.html" %} -{% block usernav if user %} +{% block usernav %} <p class="navbar-text">Hello, {{ user.email }}</p> <ul class="nav navbar-nav navbar-right"> <li><a href="{{ url_for('libraries') }}">Libraries</a></li>