Browse Source

Add login functionality

master
Burathar 4 years ago
parent
commit
feffe779d2
  1. 8
      biscd/biscd/froms.py
  2. 3
      biscd/biscd/models/__init__.py
  3. 10
      biscd/biscd/models/project.py
  4. 20
      biscd/biscd/models/user.py
  5. 52
      biscd/biscd/models/yaml_serializable.py
  6. 5
      biscd/biscd/routes.py
  7. 2
      biscd/biscd/templates/index.html
  8. 23
      biscd/biscd/templates/login.html
  9. 15
      biscd/biscd/templates/register.html
  10. 2
      installation-files/projects_example.yaml
  11. 3
      installation-files/users_example.yaml

8
biscd/biscd/froms.py

@ -13,19 +13,19 @@ class LoginForm(FlaskForm):
class RegistrationForm(FlaskForm): class RegistrationForm(FlaskForm):
username = StringField('Username', validators=[DataRequired(), Length(min=4, max=64)]) username = StringField('Username', validators=[DataRequired(), Length(min=4, max=64)])
email = StringField('Email', validators=[DataRequired(), Email()]) email = StringField('Email', validators=[DataRequired(), Email()])
password = PasswordField('Password', validators=[DataRequired(), Length(min=10, max=128)]) password = PasswordField('Password', validators=[DataRequired(), Length(min=4, max=128)])
password2 = PasswordField( password2 = PasswordField(
'Repeat Password', validators=[DataRequired(), EqualTo('password')]) 'Repeat Password', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('Register') submit = SubmitField('Register')
def validate_username(self, username): def validate_username(self, username):
user = User.get(name=username.data) user = User.get(name=username.data)
if not any(user): if any(user):
raise ValidationError('Please use a different username.') raise ValidationError('Please use a different username.')
def validate_email(self, email): def validate_email(self, email):
user = User.get(email=email.data) user = User.get(email=email.data)
if not any(user): if any(user):
raise ValidationError('Please use a different email adress.') raise ValidationError('Please use a different email adress.')
class NewProjectForm(FlaskForm): class NewProjectForm(FlaskForm):
@ -34,5 +34,5 @@ class NewProjectForm(FlaskForm):
def validate_projectname(self, projectname): def validate_projectname(self, projectname):
project = Project.get(name=projectname.data) project = Project.get(name=projectname.data)
if not any(project): if any(project):
raise ValidationError('Please use a different projectname.') raise ValidationError('Please use a different projectname.')

3
biscd/biscd/models/__init__.py

@ -1,6 +1,3 @@
from .project import Project from .project import Project
from .user import User from .user import User
from .yaml_serializable import YamlSerializable from .yaml_serializable import YamlSerializable
User.initialize()
Project.initialize()

10
biscd/biscd/models/project.py

@ -3,13 +3,15 @@ from biscd import config
from .yaml_serializable import YamlSerializable from .yaml_serializable import YamlSerializable
class Project(YamlSerializable): class Project(YamlSerializable):
@property
def _storage_file(self): #@property
@classmethod
def _storage_file(cls):
return Path(config.config_dir()) / 'projects.yaml' return Path(config.config_dir()) / 'projects.yaml'
@property #@property
@classmethod @classmethod
def _yaml_object_name(self): def _yaml_object_name(cls):
return 'projects' return 'projects'
def __init__(self, name=None, branche='master'): def __init__(self, name=None, branche='master'):

20
biscd/biscd/models/user.py

@ -1,21 +1,27 @@
from pathlib import Path from pathlib import Path
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from flask_login import UserMixin
from biscd import config from biscd import config
from .yaml_serializable import YamlSerializable
from biscd import login from biscd import login
from .yaml_serializable import YamlSerializable
class User(YamlSerializable): class User(UserMixin, YamlSerializable):
@property #@property
def _storage_file(self): @classmethod
def _storage_file(cls):
return Path(config.config_dir()) / 'users.yaml' return Path(config.config_dir()) / 'users.yaml'
@property #@property
@classmethod @classmethod
def _yaml_object_name(self): def _yaml_object_name(cls):
return 'users' return 'users'
def get_id(self):
"""Override for UserMixin"""
return self.name
def __init__(self, name=None, email=None): def __init__(self, name=None, email=None):
self.name = name self.name = name
self.email = email self.email = email
@ -31,4 +37,4 @@ class User(YamlSerializable):
@login.user_loader @login.user_loader
def load_user(name): def load_user(name):
return super.get(name) return User.first(name=name)

52
biscd/biscd/models/yaml_serializable.py

@ -18,13 +18,13 @@ class YamlSerializable(object):
required_attributes = ['name'] required_attributes = ['name']
@classmethod @classmethod
@property #@property
@abstractmethod @abstractmethod
def _storage_file(cls): def _storage_file(cls):
pass pass
@classmethod @classmethod
@property #@property
@abstractmethod @abstractmethod
def _yaml_object_name(cls): def _yaml_object_name(cls):
pass pass
@ -41,7 +41,7 @@ class YamlSerializable(object):
if self.name is None: if self.name is None:
raise TypeError("Name cannot be None") raise TypeError("Name cannot be None")
ymlsls = self._get_all_from_file() ymlsls = self._get_all_from_file()
if self.name in ([*ymlsl][0] for ymlsl in ymlsls): if self.name in [[*ymlsl][0] for ymlsl in ymlsls]:
if overwrite: if overwrite:
ymlsls[self.name] = self.config_dict ymlsls[self.name] = self.config_dict
else: else:
@ -53,12 +53,18 @@ class YamlSerializable(object):
@classmethod @classmethod
def first_or_404(cls, **kwargs): def first_or_404(cls, **kwargs):
ymlsl = next(cls.get(**kwargs), None) ymlsl = cls.first(**kwargs)
if ymlsl is None: if ymlsl is None:
abort(404) abort(404)
else:
return ymlsl return ymlsl
@classmethod
def first(cls, **kwargs):
ymlsls = cls.get(**kwargs)
if not any(ymlsls):
return None
return ymlsls[0]
@classmethod @classmethod
def get(cls, **kwargs): def get(cls, **kwargs):
"""Returns any matching instances """Returns any matching instances
@ -73,12 +79,13 @@ class YamlSerializable(object):
for key, value in kwargs.items(): for key, value in kwargs.items():
# 'name' has to be evaluated separately; 'name' is the key of the entire object # 'name' has to be evaluated separately; 'name' is the key of the entire object
if key == 'name': if key == 'name':
ymlsl_dicts = (ymlsl_dict for ymlsl_dict in ymlsl_dicts ymlsl_dicts = [ymlsl_dict for ymlsl_dict in ymlsl_dicts
if [*ymlsl_dict][0] == value) if [*ymlsl_dict][0] == value]
else:
# For other keys, filter out any item that does not contain a key, # For other keys, filter out any item that does not contain a key,
# or that not match the key's value # or that not match the key's value
ymlsl_dicts = (ymlsl_dict for ymlsl_dict in ymlsl_dicts if ymlsl_dict.key == value) ymlsl_dicts = [ymlsl_dict for ymlsl_dict in ymlsl_dicts
if ymlsl_dict.get(key, None) == value]
# After each iteration: if no item is left, return None # After each iteration: if no item is left, return None
if not any(ymlsl_dicts): if not any(ymlsl_dicts):
@ -89,6 +96,15 @@ class YamlSerializable(object):
ymlsls.append(cls._ymlserializable_from_dict(ymlsl_dict)) ymlsls.append(cls._ymlserializable_from_dict(ymlsl_dict))
return ymlsls return ymlsls
@classmethod
def list_names(cls):
ymlsls = cls._get_all_from_file()
ymlserializables_list = []
for ymlsl in ymlsls:
name = [*ymlsl][0]
ymlserializables_list.append(name)
return ymlserializables_list
@classmethod @classmethod
def _ymlserializable_from_dict(cls, ymldict): def _ymlserializable_from_dict(cls, ymldict):
if ymldict is None: if ymldict is None:
@ -110,24 +126,14 @@ class YamlSerializable(object):
ymlsl.__dict__ = ymldict ymlsl.__dict__ = ymldict
return ymlsl return ymlsl
@classmethod
def list(cls):
ymlsls = cls._get_all_from_file()
ymlserializables_list = []
for ymlsl in ymlsls:
name = [*ymlsl][0]
ymlserializables_list.append(name)
return ymlserializables_list
@classmethod @classmethod
def _get_all_from_file(cls): def _get_all_from_file(cls):
with open(cls._storage_file) as file: with open(cls._storage_file()) as file:
ymlsls = yaml.load(file, yaml.FullLoader).get(cls._yaml_object_name) ymlsls = yaml.load(file, yaml.FullLoader).get(cls._yaml_object_name())
return ymlsls return ymlsls
@classmethod @classmethod
def _save_all_to_file(cls, ymlsls): def _save_all_to_file(cls, ymlsls):
ymlserializables_object = {cls._yaml_object_name : ymlsls} ymlserializables_object = {cls._yaml_object_name() : ymlsls}
with open(cls._storage_file, 'w') as file: with open(cls._storage_file(), 'w') as file:
yaml.dump(ymlserializables_object, file) yaml.dump(ymlserializables_object, file)

5
biscd/biscd/routes.py

@ -14,7 +14,7 @@ def index():
project = Project.first_or_404(name=form.projectname.data) project = Project.first_or_404(name=form.projectname.data)
project.save() project.save()
flash('You added a project!') flash('You added a project!')
project_names = Project.list() project_names = Project.list_names()
return render_template('index.html', form=form, project_names=project_names) return render_template('index.html', form=form, project_names=project_names)
@app.route('/login', methods=['GET', 'POST']) @app.route('/login', methods=['GET', 'POST'])
@ -23,7 +23,7 @@ def login():
return redirect(url_for('index')) return redirect(url_for('index'))
form = LoginForm() form = LoginForm()
if form.validate_on_submit(): if form.validate_on_submit():
user = User.first_or_404(name=form.username.data) user = User.first(name=form.username.data)
if user is None or not user.check_password(form.password.data): if user is None or not user.check_password(form.password.data):
flash('Invalid username or password') flash('Invalid username or password')
return redirect(url_for('login')) return redirect(url_for('login'))
@ -34,6 +34,7 @@ def login():
return redirect(next_page) return redirect(next_page)
return render_template('login.html', title='Sign In', form=form) return render_template('login.html', title='Sign In', form=form)
@login_required
@app.route('/logout') @app.route('/logout')
def logout(): def logout():
logout_user() logout_user()

2
biscd/biscd/templates/index.html

@ -25,7 +25,7 @@
<div class="row"> <div class="row">
<div class="col-md-4"> <div class="col-md-4">
{{ wtf.quick_form(form) }} {{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

23
biscd/biscd/templates/login.html

@ -0,0 +1,23 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<div class="row">
<div class="col-xs-0 col-md-1"></div>
<div class="col-xs-8 col-md-4">
<h1>Sign In</h1>
<br>
<form action="" method="post" class="form" role="form">
{{ form.hidden_tag() }}
{{ wtf.form_field(form.username) }}
{{ wtf.form_field(form.password) }}
{{ wtf.form_field(form.remember_me) }}
{{ wtf.form_field(form.submit, class='btn btn-primary') }}
</form>
<br>
<p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
</div>
<div class="col-xs-0 col-md-7"></div>
</div>
{% endblock %}

15
biscd/biscd/templates/register.html

@ -0,0 +1,15 @@
{% extends "base.html" %}
{% import 'bootstrap/wtf.html' as wtf %}
{% block app_content %}
<div class="row">
<div class="col-xs-0 col-md-1"></div>
<div class="col-xs-8 col-md-4">
<h1>Register</h1>
{{ wtf.quick_form(form, button_map={'submit': 'primary'}) }}
<br>
<p>Already have an account? <a href="{{ url_for('login') }}">Click to Sign In!</a></p>
</div>
<div class="col-xs-0 col-md-7"></div>
</div>
{% endblock %}

2
installation-files/projects_example.yaml

@ -13,5 +13,3 @@ projects:
secret: thisissecret secret: thisissecret
tests: tests.py tests: tests.py
url: bar url: bar
- Test:
branche: master

3
installation-files/users_example.yaml

@ -1,5 +1,4 @@
users: users:
- henk: - henk:
email: mymail@mail.com email: mymail@mail.com
password: hash password_hash: pbkdf2:sha256:150000$uuFRyvLs$ee9863f169db786e82b9e2abe0c2cf3434e925479d0919f3b4046ebbfa0aeb28
Loading…
Cancel
Save