Browse Source

Pre-remove numeric ID

master
Burathar 4 years ago
parent
commit
87443ba73a
  1. 6
      biscd/biscd/froms.py
  2. 3
      biscd/biscd/models/__init__.py
  3. 1
      biscd/biscd/models/project.py
  4. 30
      biscd/biscd/models/user.py
  5. 82
      biscd/biscd/models/yaml_serializable.py
  6. 6
      biscd/biscd/routes.py

6
biscd/biscd/froms.py

@ -1,6 +1,8 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import SubmitField, StringField, PasswordField, BooleanField from wtforms import SubmitField, StringField, PasswordField, BooleanField
from wtforms.validators import DataRequired, Email, EqualTo, Length from wtforms.validators import DataRequired, Email, EqualTo, Length, ValidationError
from .models import User
class LoginForm(FlaskForm): class LoginForm(FlaskForm):
username = StringField('Username', validators=[DataRequired()]) username = StringField('Username', validators=[DataRequired()])
@ -17,7 +19,7 @@ class RegistrationForm(FlaskForm):
submit = SubmitField('Register') submit = SubmitField('Register')
def validate_username(self, username): def validate_username(self, username):
user = User.query.filter_by(username=username.data).first() user = User(username.data)
if user is not None: if user is not None:
raise ValidationError('Please use a different username.') raise ValidationError('Please use a different username.')

3
biscd/biscd/models/__init__.py

@ -1,3 +1,6 @@
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()

1
biscd/biscd/models/project.py

@ -8,6 +8,7 @@ class Project(YamlSerializable):
return Path(config.config_dir()) / 'projects.yaml' return Path(config.config_dir()) / 'projects.yaml'
@property @property
@classmethod
def _yaml_object_name(self): def _yaml_object_name(self):
return 'projects' return 'projects'

30
biscd/biscd/models/user.py

@ -1,17 +1,39 @@
from pathlib import Path from pathlib import Path
from werkzeug.security import generate_password_hash, check_password_hash
from biscd import config from biscd import config
from biscd.models import YamlSerializable from .yaml_serializable import YamlSerializable
from biscd import login
class User(YamlSerializable):
class Project(YamlSerializable):
@property @property
def _storage_file(self): def _storage_file(self):
return Path(config.config_dir()) / 'users.yaml' return Path(config.config_dir()) / 'users.yaml'
@property @property
@classmethod
def _yaml_object_name(self): def _yaml_object_name(self):
return 'users' return 'users'
def __init__(self, name=None, password=None, email=None): def __init__(self, id=None, name=None, email=None, password=None, password_hash=None):
super().__init__(id)
self.name = name self.name = name
self.password = password self.password_hash = set_password(password, password_hash)
self.email = email self.email = email
def set_password(password, password_hash):
if password_hash:
return password_hash
if password:
return generate_password_hash(password)
return None
def check_password(self, password):
if not password:
return False
return check_password_hash(self.password_hash, password)
@login.user_loader
def load_user(id):
return super.get(int(id))

82
biscd/biscd/models/yaml_serializable.py

@ -5,36 +5,56 @@ class MyMeta(metaclass=ABCMeta):
required_attributes = [] required_attributes = []
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
# pylint: disable=no-member
obj = super(MyMeta, self).__call__(*args, **kwargs) obj = super(MyMeta, self).__call__(*args, **kwargs)
for attr_name in obj.required_attributes: for attr_name in obj.required_attributes:
if not getattr(obj, attr_name): if not getattr(obj, attr_name):
raise ValueError('required attribute (%s) not set' % attr_name) raise ValueError('required attribute (%s) not set' % attr_name)
return obj return obj
class YamlSerializable(object, metaclass=MyMeta): class YamlSerializable(object):
__metaclass__ = MyMeta
required_attributes = ['name'] required_attributes = ['name']
_id_counter = 0
@classmethod
def initialize(cls):
ymlserializables = cls._get_all_from_file()
cls._id_counter = max(ymlserializable.id for ymlserializable in ymlserializables) + 1
@abstractmethod @abstractmethod
def __init__(self): def __init__(self, id = None):
pass self.id = self.set_id(id)
@classmethod
def set_id(cls, id):
if id is not None:
return id
id = cls._id_counter
cls._id_counter += 1
return id
@classmethod
@property @property
@abstractmethod @abstractmethod
def _storage_file(self): def _storage_file(cls):
pass pass
@classmethod
@property @property
@abstractmethod @abstractmethod
def _yaml_object_name(self): def _yaml_object_name(cls):
pass pass
@property @property
def config_dict(self): def config_dict(self):
# pylint: disable=no-member
ymlserializable_dict = self.__dict__.copy() ymlserializable_dict = self.__dict__.copy()
ymlserializable_dict.pop('name') ymlserializable_dict.pop('name')
return {self.name: ymlserializable_dict} return {self.name: ymlserializable_dict}
def save(self): def save(self):
# pylint: disable=no-member
if self.name is None: if self.name is None:
raise TypeError("Name cannot be None") raise TypeError("Name cannot be None")
ymlserializables = self._get_all_from_file() ymlserializables = self._get_all_from_file()
@ -46,19 +66,45 @@ class YamlSerializable(object, metaclass=MyMeta):
self._save_all_to_file(ymlserializables) self._save_all_to_file(ymlserializables)
@classmethod @classmethod
def get(cls, name): def get(cls, identifier):
ymlserializable_dict = next(ymlserializable for ymlserializable in cls._get_all_from_file() if [*ymlserializable][0] == name) if isinstance(identifier, int):
if ymlserializable_dict is None: id = identifier
ymlserializable_dict = next(
ymlserializable for ymlserializable in
cls._get_all_from_file() if int(ymlserializable['id']) == id
)
return cls._ymlserializable_from_dict(ymlserializable_dict)
if isinstance(identifier, str):
name = identifier
ymlserializable_dict = next(
ymlserializable for ymlserializable in
cls._get_all_from_file() if [*ymlserializable][0] == name
)
return cls._ymlserializable_from_dict(ymlserializable_dict)
return None
@classmethod
def _ymlserializable_from_dict(cls, ymldict):
if ymldict is None:
return None return None
ymlserializable_name = [*ymlserializable_dict][0]
ymlserializable_dict = ymlserializable_dict.get(ymlserializable_name)
ymlserializable_dict['name'] = ymlserializable_name # Extract the name
ymlserializable_name = [*ymldict][0]
# Step into object
ymldict = ymldict.get(ymlserializable_name)
# Add name to dict
ymldict['name'] = ymlserializable_name
# Create empty instance
ymlserializable = cls() ymlserializable = cls()
ymlserializable.__dict__ = ymlserializable_dict
return ymlserializable
# Fill instance with dict
ymlserializable.__dict__ = ymldict
return ymlserializable
@classmethod @classmethod
def list(cls): def list(cls):
@ -72,11 +118,15 @@ class YamlSerializable(object, metaclass=MyMeta):
@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:
ymlserializables = yaml.load(file, yaml.FullLoader).get(_yaml_object_name) ymlserializables = yaml.load(file, yaml.FullLoader).get(cls._yaml_object_name)
return ymlserializables
highest_id = max(ymlserializable.id for ymlserializable in ymlserializables) + 1
if highest_id > cls._id_counter: cls._id_counter = highest_id
return ymlserializables
@classmethod @classmethod
def _save_all_to_file(cls, ymlserializables): def _save_all_to_file(cls, ymlserializables):
ymlserializables_object = {_yaml_object_name : ymlserializables} ymlserializables_object = {cls._yaml_object_name : ymlserializables}
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)

6
biscd/biscd/routes.py

@ -1,7 +1,7 @@
from flask import render_template, flash, abort from flask import render_template, flash, abort, redirect, request, url_for, url_parse
from flask_login import current_user, login_user, logout_user, login_required from flask_login import current_user, login_user, logout_user, login_required
from biscd import app from biscd import app
from .models import Project from .models import Project, User
from .froms import NewProjectForm, LoginForm from .froms import NewProjectForm, LoginForm
@app.route('/', methods=['GET', 'POST']) @app.route('/', methods=['GET', 'POST'])
@ -22,7 +22,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.query.filter_by(username=form.username.data).first() user = User.get(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'))

Loading…
Cancel
Save