Browse Source

update 2020/01/30

aguryev 4 years ago
parent
commit
4244fa0539

+ 2 - 1
.gitignore

@@ -10,4 +10,5 @@ baangt.ini
 /docs/logs
 __pycache__
 *.pyc
-*.db
+*.db
+venv

+ 20 - 0
flask/app/__init__.py

@@ -0,0 +1,20 @@
+import os
+from flask import Flask
+from flask_login import LoginManager
+from flask_sqlalchemy import SQLAlchemy
+
+app = Flask(__name__)
+
+
+#
+# configurations
+#
+app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') or 'secret!key'
+app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DATABASE_URL') or 'sqlite:///testrun.db'
+app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
+
+db = SQLAlchemy(app)
+login_manager = LoginManager(app)
+login_manager.login_view = 'login'
+
+from app import views, models

+ 27 - 0
flask/app/forms.py

@@ -0,0 +1,27 @@
+from flask_wtf import FlaskForm
+from wtforms import StringField, PasswordField
+from wtforms.validators import ValidationError, DataRequired, EqualTo
+from app.models import User
+
+class LoginForm(FlaskForm):
+	username = StringField('Username', validators=[DataRequired()])
+	password = PasswordField('Password', validators=[DataRequired()])
+
+	def validate_username(self, username):
+		if User.query.filter_by(username=username.data).count() == 0:
+			raise ValidationError('Wrong username')
+
+	def validate_password(self, password):
+		user = User.query.filter_by(username=self.username.data).first()
+		if user and not user.verify_password(password.data):
+			raise ValidationError('Wrong password')
+
+
+class SingupForm(FlaskForm):
+	username = StringField('Username', validators=[DataRequired()])
+	password = PasswordField('Password', validators=[DataRequired()])
+	password2 = PasswordField('Password again', validators=[DataRequired(), EqualTo('password')])
+
+	def validate_username(self, username):
+		if User.query.filter_by(username=username.data).count() > 0:
+			raise ValidationError('This username is in use. Please try another username.')

+ 42 - 27
flask/models.py

@@ -1,19 +1,34 @@
 from flask_sqlalchemy import SQLAlchemy
+from flask_login import UserMixin
+from werkzeug.security import generate_password_hash, check_password_hash
 from datetime import datetime
-from application import db
+from app import db, login_manager
 
 #
 # user Model
 # TODO: extend user Model
 #
-class User(db.Model):
+class User(UserMixin, db.Model):
 	__tablename__ = 'users'
 	id = db.Column(db.Integer, primary_key=True)
-	username = db.Column(db.String, unique=True, nullable=False)
+	username = db.Column(db.String(64), unique=True, nullable=False)
+	password = db.Column(db.String(128), nullable=False)
+	created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
+	lastlogin = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
 
 	def __str__(self):
 		return self.username
 
+	def set_password(self, password):
+		self.password = generate_password_hash(password)
+
+	def verify_password(self, password):
+		return check_password_hash(self.password, password)
+
+@login_manager.user_loader
+def load_user(id):
+	return User.query.get(int(id))
+
 #
 # relation tables
 #
@@ -49,8 +64,8 @@ testcase_stepsequence = db.Table(
 class Testrun(db.Model):
 	__tablename__ = 'testruns'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 	created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
 	creator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 	edited = db.Column(db.DateTime, nullable=True)
@@ -64,8 +79,8 @@ class Testrun(db.Model):
 class TestCaseSequence(db.Model):
 	__tablename__ = 'testcase_sequences'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 	created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
 	creator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 	edited = db.Column(db.DateTime, nullable=True)
@@ -87,7 +102,7 @@ class TestCaseSequence(db.Model):
 class DataFile(db.Model):
 	__tablename__ = 'datafiles'
 	id = db.Column(db.Integer, primary_key=True)
-	fileName = db.Column(db.String, nullable=False)
+	filename = db.Column(db.String(64), nullable=False)
 	created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
 	creator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 	creator = db.relationship('User', backref='created_datafiles', lazy='immediate', foreign_keys=[creator_id])
@@ -99,13 +114,13 @@ class DataFile(db.Model):
 	)
 
 	def __str__(self):
-		return self.fileName
+		return self.filename
 
 class TestCase(db.Model):
 	__tablename__ = 'testcases'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 	created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
 	creator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 	edited = db.Column(db.DateTime, nullable=True)
@@ -131,8 +146,8 @@ class TestCase(db.Model):
 class TestStepSequence(db.Model):
 	__tablename__ = 'teststep_sequences'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 	created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
 	creator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 	edited = db.Column(db.DateTime, nullable=True)
@@ -154,8 +169,8 @@ class TestStepSequence(db.Model):
 class TestStepExecution(db.Model):
 	__tablename__ = 'teststep_executions'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 	created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
 	creator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 	edited = db.Column(db.DateTime, nullable=True)
@@ -178,8 +193,8 @@ class TestStepExecution(db.Model):
 class GlobalTestStepExecution(db.Model):
 	__tablename__ = 'global_teststep_executions'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 	created = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
 	creator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
 	edited = db.Column(db.DateTime, nullable=True)
@@ -199,8 +214,8 @@ class GlobalTestStepExecution(db.Model):
 class ClassName(db.Model):
 	__tablename__ = 'classnames'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 
 	def __str__(self):
 		return self.name
@@ -208,8 +223,8 @@ class ClassName(db.Model):
 class BrowserType(db.Model):
 	__tablename__ = 'browser_types'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 
 	def __str__(self):
 		return self.name
@@ -217,8 +232,8 @@ class BrowserType(db.Model):
 class TestCaseType(db.Model):
 	__tablename__ = 'testcase_types'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 
 	def __str__(self):
 		return self.name
@@ -226,8 +241,8 @@ class TestCaseType(db.Model):
 class ActivityType(db.Model):
 	__tablename__ = 'activity_types'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 
 	def __str__(self):
 		return self.name
@@ -235,8 +250,8 @@ class ActivityType(db.Model):
 class LocatorType(db.Model):
 	__tablename__ = 'locator_types'
 	id = db.Column(db.Integer, primary_key=True)
-	name = db.Column(db.String, nullable=False)
-	description = db.Column(db.String, nullable=False)
+	name = db.Column(db.String(64), nullable=False)
+	description = db.Column(db.String(512), nullable=False)
 
 	def __str__(self):
 		return self.name

flask/static/testrun.js → flask/app/static/testrun.js


+ 12 - 4
flask/templates/base.html

@@ -17,12 +17,20 @@
   <body>
     <nav class="navbar navbar-expand navbar-dark fixed-top bg-dark flex-md-nowrap p-0 shadow">
         <a class="navbar-brand ml-3 py-2" href="/">Testruns Definition</a>
-        <ul class="navbar-nav ml-auto mr-3">
-            <li class="nav-item nav-link">
-                Username
+        <ul class="navbar-nav ml-auto mr-3">            
+            <li class="nav-item">
+                {% if current_user.is_authenticated %}
+                    <span class="nav-link">{{ current_user }}</span>
+                {% else %}
+                    <a class="nav-link" href="{{ url_for('login')}}">Login</a>
+                {% endif %}
             </li>
             <li class="nav-item">
-                <a class="nav-link" href="#">Sign out</a>
+                {% if current_user.is_authenticated %}
+                    <a class="nav-link" href="{{ url_for('logout')}}">Sign out</a>
+                {% else %}
+                    <a class="nav-link" href="{{ url_for('signup')}}">Sign up</a>
+                {% endif %}
             </li>
         </ul>
     </nav>

flask/templates/testrun/index.html → flask/app/templates/testrun/index.html


flask/templates/testrun/item.html → flask/app/templates/testrun/item.html


+ 38 - 0
flask/app/templates/testrun/login.html

@@ -0,0 +1,38 @@
+{% extends "base.html" %}
+
+{% block title %}
+Login
+{% endblock %}
+
+{% block content %}
+<div class="d-flex flex-column text-center mt-5">
+	<h1 class="py-5">Please Login</h1>
+	
+	<!-- login form -->
+	<form method="post">
+		{{ form.hidden_tag() }}
+
+		<p>
+			{% for error in form.username.errors %}
+				<p class="text-danger">{{ error }}</p>
+			{% endfor %}
+			{{ form.username(placeholder="Username") }}
+		</p>
+		<p>
+			{% for error in form.password.errors %}
+				<p class="text-danger">{{ error }}</p>
+			{% endfor %}
+			{{ form.password(placeholder="Password") }}
+		</p>
+		<p>
+			<button class="btn btn-primary px-5" type="submit">Login</button>
+		</p>
+	</form>
+
+	<div class="mt-2">
+		Not registered yet? <a href="/signup">Sign up here</a>
+	</div>
+
+</div>
+
+{% endblock %}

+ 43 - 0
flask/app/templates/testrun/signup.html

@@ -0,0 +1,43 @@
+{% extends "base.html" %}
+
+{% block title %}
+Sign up
+{% endblock %}
+
+{% block content %}
+<div class="d-flex flex-column text-center mt-5">
+	<h1 class="py-5">Register a new user</h1>
+	
+	<!-- sign up form -->
+	<form method="post">
+		{{ form.hidden_tag() }}
+		<p>
+			{% for error in form.username.errors %}
+				<p class="text-danger">{{ error }}</p>
+			{% endfor %}
+			{{ form.username(placeholder="Username") }}
+		</p>
+		<p>
+			{% for error in form.password.errors %}
+				<p class="text-danger">{{ error }}</p>
+			{% endfor %}
+			{{ form.password(placeholder="Password") }}
+		</p>
+		<p>
+			{% for error in form.password2.errors %}
+				<p class="text-danger">{{ error }}</p>
+			{% endfor %}
+			{{ form.password2(placeholder="Password again") }}
+		</p>
+		<p>
+			<button class="btn btn-primary px-5" type="submit">Sign up</button>
+		</p>
+	</form>
+
+	<div class="mt-2">
+		Already registered? <a href="/login">Log in here</a>
+	</div>
+
+</div>
+
+{% endblock %}

+ 79 - 0
flask/app/views.py

@@ -0,0 +1,79 @@
+
+from flask import render_template, redirect, request, url_for
+from flask_login import login_required, current_user, login_user, logout_user
+from app import app, db, models, forms
+
+@app.route("/")
+@login_required
+def index():
+	# get the whole bunch of items
+	items = {}
+	items['testruns'] = models.Testrun.query.all()
+	items['testcase_sequances'] = models.TestCaseSequence.query.all()
+	#items['datafiles'] = models.DataFile.query.all()
+	items['testcases'] = models.TestCase.query.all()
+	items['teststep_sequences'] = models.TestStepSequence.query.all()
+	items['teststeps'] = models.TestStepExecution.query.all()
+	return render_template("testrun/index.html", items=items)
+
+@app.route("/<string:item_type>/<int:item_id>")
+@login_required
+def testrun(item_type, item_id):
+	# get item by type and id
+	if item_type == 'testrun':
+		item = models.Testrun.query.get(item_id)
+	elif item_type == 'testcase_sequence':
+		item = models.TestCaseSequence.query.get(item_id)
+	elif item_type == 'testcase':
+		item = models.TestCase.query.get(item_id)
+	elif item_type == 'teststep_sequence':
+		item = models.TestStepSequence.query.get(item_id)
+	elif item_type == 'teststep':
+		item = models.TestStepExecution.query.get(item_id)
+	else:
+		return 'ERROR: Wrong Item'
+
+	return render_template("testrun/item.html", type=item_type, item=item)
+
+#
+# user authentication
+#
+
+@app.route('/signup', methods=['GET', 'POST'])
+def signup():
+	if current_user.is_authenticated:
+		return redirect(url_for('index'))
+
+	form = forms.SingupForm()
+	if form.validate_on_submit():
+		# create user
+		user = models.User(username=form.username.data)
+		user.set_password(form.password.data)
+		db.session.add(user)
+		db.session.commit()
+		# login
+		login_user(user, remember=True)
+		return redirect(url_for('index'))
+
+	return render_template('testrun/signup.html', form=form)
+
+@app.route('/login', methods=['GET', 'POST'])
+def login():
+	if current_user.is_authenticated:
+		return redirect(url_for('index'))
+
+	form = forms.LoginForm()
+	if form.validate_on_submit():
+		user = models.User.query.filter_by(username=form.username.data).first()
+		if user and user.verify_password(form.password.data):
+			login_user(user, remember=True)
+			return redirect(url_for('index'))
+
+	return render_template('testrun/login.html', form=form)
+
+@app.route('/logout')
+def logout():
+	logout_user()
+
+	return redirect(url_for('login'))
+	

+ 1 - 45
flask/application.py

@@ -1,45 +1 @@
-import os
-from flask import Flask, render_template, request
-from models import *
-
-app = Flask(__name__)
-app.config["SQLALCHEMY_DATABASE_URI"] = os.getenv("DATABASE_URL") or 'sqlite:///testrun.db'
-app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
-db = SQLAlchemy(app)
-
-@app.route("/")
-def index():
-	# get the whole bunch of items
-	items = {}
-	items['testruns'] = Testrun.query.all()
-	items['testcase_sequances'] = TestCaseSequence.query.all()
-	items['datafiles'] = DataFile.query.all()
-	items['testcases'] = TestCase.query.all()
-	items['teststep_sequences'] = TestStepSequence.query.all()
-	items['teststeps'] = TestStepExecution.query.all()
-	return render_template("testrun/index.html", items=items)
-
-@app.route("/<string:item_type>/<int:item_id>") 
-def testrun(item_type, item_id):
-	# get item by type and id
-	if item_type == 'testrun':
-		item = Testrun.query.get(item_id)
-	elif item_type == 'testcase_sequence':
-		item = TestCaseSequence.query.get(item_id)
-	elif item_type == 'testcase':
-		item = TestCase.query.get(item_id)
-	elif item_type == 'teststep_sequences':
-		item = TestStepSequence.query.get(item_id)
-	elif item_type == 'teststep':
-		item = TestStepExecution.query.get(item_id)
-	else:
-		return 'ERROR: Wrong Item'
-
-	return render_template("testrun/item.html", type=item_type, item=item)
-
-
-	
-
-if __name__ == '__main__':
-	app.run()
-
+from app import app

+ 10 - 2
flask/populate_db.py

@@ -12,13 +12,18 @@ db.drop_all()
 db.create_all()
 
 # create users
+print('Creating users...')
 admin = User(username='admin')
+admin.set_password('12345')
 user = User(username='simple_user')
+user.set_password('12345')
 db.session.add(admin)
 db.session.add(user)
 db.session.commit()
+print('Done.')
 
 # create supports
+print('Creating supports...')
 browsers = {
 	'FF': 'Mozilla Firefox',
 	'Chrome': 'Google Chrome',
@@ -63,8 +68,10 @@ classname = ClassName(name='ClassName A', description='A Simple Class Name')
 db.session.add(classname)
 
 db.session.commit()
+print('Done.')
 
 # create mains
+print('Creating mains...')
 for i in range(5):
 	testrun  = Testrun(
 		name=f'Testrun #{i}',
@@ -99,7 +106,7 @@ for i in range(5):
 	else:
 		u = user
 	datafile  = DataFile(
-		fileName=f'data_file_{i}.xlsx',
+		filename=f'data_file_{i}.xlsx',
 		creator=u,
 	)
 	testseq = TestCaseSequence.query.get(i+1)
@@ -164,4 +171,5 @@ for i in range(4):
 	)
 	db.session.add(testcase)
 
-db.session.commit()
+db.session.commit()
+print('Done.')

+ 3 - 1
flask/requirements.txt

@@ -1,3 +1,5 @@
 flask
 sqlalchemy
-flask_sqlalchemy
+flask_sqlalchemy
+flask-login
+flask-wtf