diff --git a/V5.py b/V5.py index 3c32c5e..c255ae5 100644 --- a/V5.py +++ b/V5.py @@ -1,7 +1,8 @@ from app import app, db -from app.models import User, Post +from app.models.users import User +# from app.models.models import Post @app.shell_context_processor def make_shell_context(): - return {'db': db, 'User': User, 'Post': Post} + return {'db': db, 'User': User} diff --git a/app/forms.py b/app/forms.py index dfc523e..ccf5c4a 100644 --- a/app/forms.py +++ b/app/forms.py @@ -1,7 +1,7 @@ from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, BooleanField, SubmitField from wtforms.validators import ValidationError, DataRequired, Email, EqualTo -from app.models import User +from app.models.users import Member class LoginForm(FlaskForm): @@ -16,14 +16,21 @@ class RegistrationForm(FlaskForm): email = StringField('Adresse Email :', validators=[DataRequired(), Email()]) password = PasswordField('Mot de passe :', validators=[DataRequired()]) password2 = PasswordField('Répéter le mot de passe', validators=[DataRequired(), EqualTo('password')]) + guidelines = BooleanField('J’accepte les CGU', validators=[DataRequired()]) + newsletter = BooleanField('Inscription à la newsletter', description='Un mail par trimestre environ, pour être prévenu des concours, évènements et nouveautés.') submit = SubmitField('S\'enregistrer') def validate_username(self, username): - user = User.query.filter_by(username=username.data).first() - if user is not None: + member = Member.query.filter_by(username=username.data).first() + if member is not None: raise ValidationError('Pseudo indisponible.') def validate_email(self, email): - user = User.query.filter_by(email=email.data).first() - if user is not None: - raise ValidationError('Adresse email déjà utilisé.') + member = Member.query.filter_by(email=email.data).first() + if member is not None: + raise ValidationError('Adresse email déjà utilisée.') + + def validate_password(self, password): + if len(password.data) < 10: + raise ValidationError('Mot de passe est trop court (10 caractères minimum)') + # TODO: add more rules >:] diff --git a/app/models/contents.py b/app/models/contents.py new file mode 100644 index 0000000..83ea859 --- /dev/null +++ b/app/models/contents.py @@ -0,0 +1,19 @@ +from datetime import datetime +from app import db +from app.models.users import * + +class Content(db.Model): + __tablename__ = 'content' + id = db.Column(db.Integer, primary_key=True) + type = db.Column(db.String(20)) + __mapper_args__ = { + 'polymorphic_identity':__tablename__, + 'polymorphic_on': type + } + # Standalone properties + data = db.Column(db.Text(convert_unicode=True)) + date_created = db.Column(db.DateTime, default=datetime.now) + date_modified = db.Column(db.DateTime, default=datetime.now) + # Relationships + author_id = db.Column(db.ForeignKey('user.id')) + author = db.relationship("User", back_populates="contents") \ No newline at end of file diff --git a/app/relationship.py b/app/relationship.py new file mode 100644 index 0000000..7fdd30b --- /dev/null +++ b/app/relationship.py @@ -0,0 +1,15 @@ +from app import db + +class Parent(): + __tablename__ = 'user' + id = db.Column(db.Integer, primary_key=True) + type = db.Column(db.String(20)) + __mapper_args__ = { + 'polymorphic_identity': __tablename__, + 'polymorphic_on': type + } + +class Children(Parent): + __tablename__ = 'member' + id = db.Column(db.Integer, db.ForeignKey('user.id'), primary_key=True) + __mapper_args__ = { 'polymorphic_identity': __tablename__ } \ No newline at end of file diff --git a/app/routes.py b/app/routes.py index a327b31..6f889c3 100644 --- a/app/routes.py +++ b/app/routes.py @@ -3,53 +3,54 @@ from flask_login import login_user, logout_user, current_user, login_required from werkzeug.urls import url_parse from app import app, db from app.forms import LoginForm, RegistrationForm -from app.models import User +from app.models.users import Member @app.route('/', methods=['GET', 'POST']) def index(): + form = LoginForm() + flash('pseudo ou mot de passe invalide', 'error') + flash('tout ça c\'est ok !', 'ok') + flash('mais ça bof', 'warning') + flash('et une info', 'info') + if form.validate_on_submit(): + flash('test', 'ok') + member = Member.query.filter_by(username=form.username.data).first() + if member is None or not member.check_password(form.password.data): + flash('pseudo ou mot de passe invalide') + return redirect(url_for('index')) + flash('Connexion réussie') + login_user(member, remember=form.remember_me.data) - form = LoginForm() - flash('pseudo ou mot de passe invalide', 'error') - flash('tout ça c\'est ok !', 'ok') - flash('mais ça non', 'error') - if form.validate_on_submit(): - user = User.query.filter_by(username=form.username.data).first() - if user is None or not user.check_password(form.password.data): - flash('pseudo ou mot de passe invalide') - return redirect(url_for('index')) - login_user(user, remember=form.remember_me.data) - - return render_template('index.html', form=form) + return render_template('index.html', form=form) @app.route('/logout/') def logout(): - logout_user() - return redirect(url_for('index')) + logout_user() + return redirect(url_for('index')) @app.route('/register', methods=['GET', 'POST']) def register(): - if current_user.is_authenticated: - return redirect(url_for('index')) - form = LoginForm() - form2 = RegistrationForm() - if form2.validate_on_submit(): - user = User(username=form2.username.data, email=form2.email.data) - user.set_password(form2.password.data) - db.session.add(user) - db.session.commit() - flash('Congratulations, you are now a registered user!') - return redirect(url_for('validation')) - return render_template('register.html', title='Register', form=form, form2 = form2) + if current_user.is_authenticated: + return redirect(url_for('index')) + form = LoginForm() + form2 = RegistrationForm() + if form2.validate_on_submit(): + member = Member(form2.username.data, form2.email.data, form2.password.data) + db.session.add(member) + db.session.commit() + flash('Congratulations, you are now a registered member!') + return redirect(url_for('validation')) + return render_template('register.html', title='Register', form=form, form2 = form2) @app.route('/register/validation/') def validation(): - if current_user.is_authenticated : - return redirect(url_for('index')) - form = LoginForm() - return render_template('validation.html', form = form) + if current_user.is_authenticated : + return redirect(url_for('index')) + form = LoginForm() + return render_template('validation.html', form = form) diff --git a/app/static/css/container.css b/app/static/css/container.css index 4e2176b..f390b72 100644 --- a/app/static/css/container.css +++ b/app/static/css/container.css @@ -2,6 +2,16 @@ margin-left: 60px; } +section { + margin: 10px 5%; +} + +section h1 { + border-bottom: 1px solid #a0a0a0; + font-family: Raleway; font-size: 32px; + font-weight: 200; color: #242424; +} + /* #container h1 { margin-left: 5%; font-family: Raleway; font-size: 24px; diff --git a/app/static/css/flash.css b/app/static/css/flash.css new file mode 100644 index 0000000..e66a1a1 --- /dev/null +++ b/app/static/css/flash.css @@ -0,0 +1,45 @@ +/* + flash overlay +*/ + +.flash { + position: fixed; left: 15%; + display: flex; align-items: center; + width: 70%; z-index: 10; + font-family: NotoSans; font-size: 14px; color: #212121; + background: #ffffff; + border-bottom: 5px solid #4caf50; + border-radius: 1px; box-shadow: 0 1px 12px rgba(0, 0, 0, 0.3); + transition: opacity .15s ease; + transition: top .2s ease; +} +.flash.info { + border-color: #2e7aec; +} +.flash.ok { + border-color: #4caf50; +} +.flash.warning { + border-color: #fbbc26; +} +.flash.error { + border-color: #f44336; +} +.flash span { + flex-grow: 1; margin: 15px 10px 10px 0; +} +.flash input[type="button"] { + margin: 3px 30px 0 0; padding: 10px 15px; + border: none; + background: rgba(0, 0, 0, 0); color: #727272; +} +.flash input[type="button"]:hover { + background: rgba(0, 0, 0, .1); +} +.flash input[type="button"]:focus { + background: rgba(0, 0, 0, .2); +} + +.flash svg { + margin: 15px 20px 10px 30px; +} diff --git a/app/static/css/footer.css b/app/static/css/footer.css index 7f497b8..153c068 100644 --- a/app/static/css/footer.css +++ b/app/static/css/footer.css @@ -1,5 +1,5 @@ /* - footer + Footer */ footer { @@ -10,4 +10,4 @@ footer { } footer p { margin: 3px 0; -} +} \ No newline at end of file diff --git a/app/static/css/global.css b/app/static/css/global.css index e03131d..4bca62e 100644 --- a/app/static/css/global.css +++ b/app/static/css/global.css @@ -7,6 +7,14 @@ @font-face { font-family: Raleway; font-weight: 300; src: url(../fonts/raleway_300.ttf); } +/* + ALL +*/ +* { + box-sizing: border-box; + transition: .15s ease; +} + /* body @@ -19,69 +27,6 @@ body { } -/* - header -*/ - -header { - height: 50px; margin: 0; padding: 0 30px; - display: flex; align-items: center; justify-content: space-between; - background: #f8f8fa; border-bottom: 1px solid #d0d0d0; -} - -header h1 { - font-family: Raleway; font-weight: 200; -} - -header svg { - width: 24px; height: 24px; vertical-align: middle; - transition: .15s ease; -} -header a:hover > svg, header a:focus > svg { - filter: brightness(.5); -} - -header input[type="search"] { - width: 250px; - padding: 5px 35px 5px 10px; - border: 0; border-radius: 1px; - font-family: "Segoe UI", Helvetica, "Droid Sans", Arial,sans-serif; - box-shadow: 0 0 1px rgba(0, 0, 0, .4); transition: .15s ease; -} -header input[type="search"] ~ a { - position: relative; left: -33px; -} -header input[type="search"]:focus { - box-shadow: 0 0 4px rgba(0, 102, 255, .9); -} -header input[type="search"] ~ a > svg > path { - fill: #cccccc; transition: .15s ease; -} -header input[type="search"]:focus ~ a > svg > path { - fill: #333333; -} - -#spotlight a { - padding: 8px 18px 6px 18px; - color: #727272; font-size: 15px; - border-bottom: 2px solid rgba(93, 123, 141, 0); - transition: border .15s ease; -} -#spotlight a:hover, header #spotlight a:focus { - border-bottom: 2px solid rgba(93, 123, 141, 1); -} - - -footer { - margin: 20px 10% 5px 10%; padding: 10px 0; - text-align: center; font-size: 11px; font-style: italic; - color: #a0a0a0; - border-top: 1px solid rgba(0, 0, 0, .1); -} -footer p { - margin: 3px 0; -} - /* links */ @@ -95,75 +40,19 @@ a:focus { -/* - alert overlay -*/ - -.alert { - position: fixed; left: 15%; - display: flex; align-items: center; - width: 70%; z-index: 10; - font-family: NotoSans; font-size: 14px; color: #212121; - background: #ffffff; - border-bottom: 5px solid #4caf50; - border-radius: 1px; box-shadow: 0 1px 12px rgba(0, 0, 0, 0.3); - transition: opacity .15s ease; -} -.alert.ok { - border-color: #4caf50; -} -.alert.error { - border-color: #f44336; -} -.alert span { - flex-grow: 1; margin: 15px 10px 10px 0; -} -.alert input[type="button"] { - margin: 3px 30px 0 0; +/* Buttons */ +input[type="button"], +input[type="submit"] { + padding: 6px 0; + border-radius: 3px; + font-size: 14px; + font-weight: 400; + border: 1px solid transparent; } -.alert svg { - margin: 15px 20px 10px 30px; -} - - - -/* - buttons -*/ - -input[type="button"] { - font-family: NotoSans; font-size: 14px; /*font-weight: bold;*/ - text-align: center; - padding: 5px 15px; - transition: .1s ease; -} - -/* flat */ -input[type="button"].flat { - border: none; - background: rgba(0, 0, 0, 0); color: #727272; -} -input[type="button"].flat:hover { - background: rgba(0, 0, 0, .1); -} -input[type="button"].flat:focus { - background: rgba(0, 0, 0, .2); -} - -/* raised */ -input[type="button"].raised { - border: none; - background: #e0e0e0; color: #212121; - box-shadow: 0 1px 2px rgba(0, 0, 0, .3); -} -input[type="button"].raised:hover, -input[type="button"].raised:focus { - background: #d5d5d5; -} -input[type="button"].raised:active { - background: #d6d6d6; - box-shadow: 0 1px 8px rgba(0, 0, 0, .3); +/* Checkbox */ +input[type="checkbox"] { + vertical-align: middle; } /* Input text */ @@ -180,23 +69,20 @@ input[type="password"]:focus { } - -section { - margin: 10px 5%; -} - -section h1 { - border-bottom: 1px solid #a0a0a0; - font-family: Raleway; font-size: 24px; - font-weight: 200; color: #242424; -} - -section * { - transition: .15s ease; -} - - /* Bootstrap-style rules */ .flex { display: flex; } + +.bg-green, +.bg-green { + background-color: #149641; + border-color: #0e692d; + color: #ffffff; +} +.bg-green:hover, +.bg-green:focus, +.bg-green:active { + background-color: #0f7331; + border-color: #073617; +} diff --git a/app/static/css/header.css b/app/static/css/header.css index 72996d7..07a0035 100644 --- a/app/static/css/header.css +++ b/app/static/css/header.css @@ -3,31 +3,29 @@ */ header { - margin: 0; padding: 10px 16px; - display: flex; align-items: center; justify-content: flex-end; - background: #ffffff; border-bottom: 1px solid #e0e0e0; + height: 50px; margin: 0; padding: 0 30px; + display: flex; align-items: center; justify-content: space-between; + background: #f8f8fa; border-bottom: 1px solid #d0d0d0; } header h1 { font-family: Raleway; font-weight: 200; } -header input + span { position: relative; left: -32px; cursor: pointer; } -header span > svg { +header svg { width: 24px; height: 24px; vertical-align: middle; - transition: .15s ease; fill: #969696; + transition: .15s ease; } -header span:hover > svg, header span:focus > svg { - border-color: rgba(32, 128, 255, .6); - fill: #484848; +header a:hover > svg, header a:focus > svg { + filter: brightness(.5); } -header form { - flex-shrink: 0; margin-right: -23px; -} header input[type="search"] { - width: 220px; height: 30px; padding: 4px 30px 4px 8px; - border: 1px solid rgba(0, 0, 0, .2); border-radius: 2px; + width: 250px; + padding: 5px 35px 5px 10px; + border: 0; border-radius: 1px; + font-family: "Segoe UI", Helvetica, "Droid Sans", Arial,sans-serif; + box-shadow: 0 0 1px rgba(0, 0, 0, .4); transition: .15s ease; } header input[type="search"] ~ a { position: relative; left: -33px; @@ -42,54 +40,13 @@ header input[type="search"]:focus ~ a > svg > path { fill: #333333; } -#spotlight { - flex-shrink: 0; +#spotlight a { + padding: 8px 18px 6px 18px; + color: #727272; font-size: 15px; + border-bottom: 2px solid rgba(93, 123, 141, 0); + transition: border .15s ease; } -#spotlight a { - display: inline-block; - height: 24px; line-height: 24px; - padding: 2px 10px; - background: #728bf6; color: white; font-size: 12px; - border-radius: 2px; border: 1px solid rgba(0, 0, 255, .05); -} -#spotlight a:hover { border-bottom: 1px solid rgba(128, 128, 255, .05); - background: #7a93ff; border-color: rgba(128, 128, 255, .03); -} - - -/* - subheader -*/ - -#subheader { - margin: 0; padding: 0 32px; - flex-grow: 1; -} -#subheader * { - margin: 0; padding: 0; -} - -#subheader li { - height: 30px; margin: 5px 0; - display: inline; - /*display: flex; align-items: center;*/ -} -#subheader li:after { - content: " »"; - opacity: 0.3; -} -#subheader li:last-child:after { - content: none; -} - -#subheader a { - padding: 0 3px; - /*border-bottom: 1px solid rgba(93, 123, 141, 0);*/ - color: #727272; -} -#subheader a:hover, #subheader a:focus { - /*border-bottom: 1px solid rgba(93, 123, 141, 1);*/ - color: #22292c; - text-decoration: none +#spotlight a:hover, header #spotlight a:focus { + border-bottom: 2px solid rgba(93, 123, 141, 1); } diff --git a/app/static/css/navbar.css b/app/static/css/navbar.css index 1b36e6f..f3cdb40 100644 --- a/app/static/css/navbar.css +++ b/app/static/css/navbar.css @@ -183,34 +183,31 @@ nav a:focus { #menu form div { padding: 0 5%; } -#menu form input[type="text"] { - display: block; width: 96%; +#menu form input[type="text"], +#menu form input[type="password"] { + display: block; width: 100%; margin: 0; padding: 5px 2%; font-size: 14px; color: inherit; background: #e8e8e8; transition: background .15s ease; border: none; - border-top-left-radius: 5px; - border-top-right-radius: 5px; -} -#menu form input[type="password"]{ - display: block; width: 96%; - margin: 0; padding: 5px 2%; - font-size: 14px; color: inherit; - background: #e8e8e8; transition: background .15s ease; - border: none; border-top: 1px solid #dddddd; - border-bottom-left-radius: 5px; - border-bottom-right-radius: 5px; } #menu form input[type="text"]:focus, #menu form input[type="password"]:focus { background: #ffffff; } +#menu form input[type="text"] { + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} +#menu form input[type="password"] { + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} +#menu form input[type="submit"] { + width: 100%; + margin-top: 10px; margin-bottom: 5px; + border-radius: 5px; +} #menu form label { font-size: 13px; color: #FFFFFF; opacity: .7; } -#menu form input[type="checkbox"] { - vertical-align: middle; -} -#menu form input[type="submit"] { - width: 100%; padding: 5px; -} diff --git a/app/static/css/register.css b/app/static/css/register.css index 835cde3..3de1823 100644 --- a/app/static/css/register.css +++ b/app/static/css/register.css @@ -1,50 +1,45 @@ -#form { +#register { width: 30%; min-width: 350px; margin: auto; } -form > div { +#register form > div { margin-bottom: 15px; } -form > div > * { - display: block; -} - -form > div > label { +#register form > div > label { + display: inline-block; margin-bottom: 5px; } -form > div > input[type='text'], -form > div > input[type='email'], -form > div > input[type='password'] { - width: 96%; padding: 6px 2%; +#register form > div > input[type='text'], +#register form > div > input[type='email'], +#register form > div > input[type='password'] { + display: block; + width: 100%; padding: 6px 2.5%; border: 1px solid #abcdef; } -form > div > input[type='text']:focus, -form > div > input[type='email']:focus, -form > div > input[type='password']:focus { - box-shadow: 0px 0px 3px 0px #0033ff; +#register form > div > input[type='text']:focus, +#register form > div > input[type='email']:focus, +#register form > div > input[type='password']:focus { + box-shadow: 0 0 4px rgba(0, 102, 255, .9); } -form > div > input[type='submit'] { - width: 100%; padding: 6px 0; - background-color: #168f48; - border-color: #12753a; - color: #fff; - border-radius: 3px; - font-size: 14px; - font-weight: 400; - border: 1px solid transparent; +#register input[type="submit"] { + width: 100%; + background-color: #149641; + border-color: #0e692d; + color: #ffffff; +} +#register input[type="submit"]:hover, +#register input[type="submit"]:focus, +#register input[type="submit"]:active { + background-color: #0f7331; + border-color: #073617; } -form > div > input[type='submit']:hover { - background-color: #168f48; - border-color: #12753a; -} - -form .msgerror { +#register form .msgerror { color: red; font-weight: 400; margin-top: 5px; diff --git a/app/static/fonts/raleway_200.ttf b/app/static/fonts/raleway_200.ttf new file mode 100644 index 0000000..281a001 Binary files /dev/null and b/app/static/fonts/raleway_200.ttf differ diff --git a/app/static/scripts/pc-utils.js b/app/static/scripts/pc-utils.js index f3dd880..fbb1341 100644 --- a/app/static/scripts/pc-utils.js +++ b/app/static/scripts/pc-utils.js @@ -12,9 +12,42 @@ function getCookie(name) { return unescape( document.cookie.substring( debut+name.length+1, end ) ); } -function close_important(element) { +/* + Flash messages +*/ +function flash_add(type, message) { + template = `
+ + {{ icon }} + + + {{ message }} + + +
`; + paths = { + 'error': '', + 'warning': '', + 'ok': '', + 'info': '' + }; + var top = (document.getElementsByClassName('flash').length + 1) * 70 - 45; + template = template.replace("{{ category }}", type); + template = template.replace("{{ top }}", top); + template = template.replace("{{ icon }}", paths[type]); + template = template.replace("{{ message }}", message); + document.body.innerHTML += template; +} +function flash_close(element) { element.style.opacity = 0; - setTimeout(function(){ element.parentNode.removeChild(element); }, 200); + setTimeout(function(){ + var parent = element.parentNode; + parent.removeChild(element); + var childs = parent.getElementsByClassName('flash'); + for(var i = 0; i < childs.length; i++) { + childs[i].style.top = ((i + 1) * 70 - 45) + 'px'; + } + }, 200); } /* diff --git a/app/templates/base/alerts.html b/app/templates/base/alerts.html deleted file mode 100644 index 276cab1..0000000 --- a/app/templates/base/alerts.html +++ /dev/null @@ -1,15 +0,0 @@ -{% with messages = get_flashed_messages(with_categories=true) %} - {% if messages %} - {% for category, message in messages %} -
- - - - - {{ message }} - - -
- {% endfor %} - {% endif %} -{% endwith %} diff --git a/app/templates/base/base.html b/app/templates/base/base.html index fb755d1..aedf74f 100644 --- a/app/templates/base/base.html +++ b/app/templates/base/base.html @@ -9,7 +9,7 @@ {% include "base/footer.html" %} - {% include "base/alerts.html" %} + {% include "base/flash.html" %} {% include "base/scripts.html" %} diff --git a/app/templates/base/flash.html b/app/templates/base/flash.html new file mode 100644 index 0000000..795fd74 --- /dev/null +++ b/app/templates/base/flash.html @@ -0,0 +1,18 @@ +{% with messages = get_flashed_messages(with_categories=true) %} + {% if messages %} + {% for category, message in messages %} +
+ + {% if category=="error" %}{% endif %} + {% if category=="warning" %}{% endif %} + {% if category=="ok" %}{% endif %} + {% if category=="info" %}{% endif %} + + + {{ message }} + + +
+ {% endfor %} + {% endif %} +{% endwith %} diff --git a/app/templates/base/head.html b/app/templates/base/head.html index 17f728c..0b24d3b 100644 --- a/app/templates/base/head.html +++ b/app/templates/base/head.html @@ -6,7 +6,10 @@ + + + diff --git a/app/templates/base/navbar/account.html b/app/templates/base/navbar/account.html index 529a9bc..6758b89 100644 --- a/app/templates/base/navbar/account.html +++ b/app/templates/base/navbar/account.html @@ -55,7 +55,7 @@ {{ form.username(size=32, placeholder="Identifiant") }} {{ form.password(size=32, placeholder="Mot de passe") }} -
{{ form.submit() }}
+
{{ form.submit(class_="bg-green") }}
{{ form.remember_me.label }} {{ form.remember_me() }}

diff --git a/app/templates/register.html b/app/templates/register.html index bda7bc7..846b7dc 100644 --- a/app/templates/register.html +++ b/app/templates/register.html @@ -2,7 +2,7 @@ {% block content %}
-
+

Inscription :

@@ -35,7 +35,22 @@ {{ error }} {% endfor %}
-
{{ form2.submit() }}
+
+ {{ form2.guidelines.label }} + {{ form2.guidelines() }} + {% for error in form2.guidelines.errors %} + {{ error }} + {% endfor %} +
+
+ {{ form2.newsletter.label }} + {{ form2.newsletter() }} +
{{ form2.newsletter.description }}
+ {% for error in form2.newsletter.errors %} + {{ error }} + {% endfor %} +
+
{{ form2.submit(class_="bg-green") }}