Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mslib/mscolab/_tests/test_file_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ def test_list_projects(self):
self.fm.create_project("first", "info about first", self.user)
self.fm.create_project("second", "info about second", self.user)
expected_result = [{'access_level': 'creator',
'category': 'default',
'description': 'info about first',
'p_id': 1,
'path': 'first'},
{'access_level': 'creator',
'category': 'default',
'description': 'info about second',
'p_id': 2,
'path': 'second'}]
Expand Down
12 changes: 8 additions & 4 deletions mslib/mscolab/_tests/test_seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def test_add_all_users_default_project_viewer(self):
assert add_user(self.userdata_1[0], self.userdata_1[1], self.userdata_1[2])
# viewer
add_all_users_default_project(path='XYZ', description="Project to keep all users", access_level='viewer')
expected_result = [{'access_level': 'viewer', 'description': 'Template', 'p_id': 7, 'path': 'XYZ'}]
expected_result = [{'access_level': 'viewer', 'category': 'default',
'description': 'Template', 'p_id': 7, 'path': 'XYZ'}]
user = User.query.filter_by(emailid=self.userdata_1[0]).first()
assert user is not None
result = self.fm.list_projects(user)
Expand All @@ -101,7 +102,8 @@ def test_add_all_users_default_project_collaborator(self):
assert add_user(self.userdata_1[0], self.userdata_1[1], self.userdata_1[2])
add_all_users_default_project(path='XYZ', description="Project to keep all users",
access_level='collaborator')
expected_result = [{'access_level': 'collaborator', 'description': 'Template', 'p_id': 7, 'path': 'XYZ'}]
expected_result = [{'access_level': 'collaborator', 'category': 'default',
'description': 'Template', 'p_id': 7, 'path': 'XYZ'}]
user = User.query.filter_by(emailid=self.userdata_1[0]).first()
assert user is not None
result = self.fm.list_projects(user)
Expand All @@ -115,7 +117,8 @@ def test_add_all_users_default_project_creator(self):
# creator
add_all_users_default_project(path='XYZ', description="Project to keep all users",
access_level='creator')
expected_result = [{'access_level': 'creator', 'description': 'Template', 'p_id': 7, 'path': 'XYZ'}]
expected_result = [{'access_level': 'creator', 'category': 'default',
'description': 'Template', 'p_id': 7, 'path': 'XYZ'}]
user = User.query.filter_by(emailid=self.userdata_1[0]).first()
result = self.fm.list_projects(user)
# we don't care here for p_id
Expand All @@ -128,7 +131,8 @@ def test_add_all_users_default_project_creator_unknown_project(self):
# creator added to new project
add_all_users_default_project(path='UVXYZ', description="Project to keep all users",
access_level='creator')
expected_result = [{'access_level': 'creator', 'description': 'Project to keep all users',
expected_result = [{'access_level': 'creator', 'category': 'default',
'description': 'Project to keep all users',
'p_id': 7, 'path': 'UVXYZ'}]
user = User.query.filter_by(emailid=self.userdata_1[0]).first()
result = self.fm.list_projects(user)
Expand Down
9 changes: 5 additions & 4 deletions mslib/mscolab/file_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class FileManager(object):
def __init__(self, data_dir):
self.data_dir = data_dir

def create_project(self, path, description, user, content=None):
def create_project(self, path, description, user, content=None, category="default"):
"""
path: path to the project
description: description of the project
Expand All @@ -49,9 +49,9 @@ def create_project(self, path, description, user, content=None):
logging.debug("malicious request: %s", user)
return False
proj_available = Project.query.filter_by(path=path).first()
if proj_available:
if proj_available is not None:
return False
project = Project(path, description)
project = Project(path, description, category)
db.session.add(project)
db.session.flush()
project_id = project.id
Expand Down Expand Up @@ -99,7 +99,8 @@ def list_projects(self, user):
"p_id": permission.p_id,
"access_level": permission.access_level,
"path": project.path,
"description": project.description
"description": project.description,
"category": project.category
})
return projects

Expand Down
7 changes: 5 additions & 2 deletions mslib/mscolab/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,21 @@ class Project(db.Model):
__tablename__ = "projects"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
path = db.Column(db.String(255), unique=True)
category = db.Column(db.String(255))
description = db.Column(db.String(255))

def __init__(self, path, description):
def __init__(self, path, description, category="default"):
"""
path: path to the project
description: small description of project
category: name of category
"""
self.path = path
self.description = description
self.category = category

def __repr__(self):
return f'<Project path: {self.path}, desc: {self.description}>'
return f'<Project path: {self.path}, desc: {self.description}, cat: {self.category}>'


class MessageType(enum.IntEnum):
Expand Down
20 changes: 13 additions & 7 deletions mslib/mscolab/seed.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,30 +282,36 @@ def seed_data():
projects = [{
'id': 1,
'path': 'one',
'description': 'a, b'
'description': 'a, b',
'category': 'default'
}, {
'id': 2,
'path': 'two',
'description': 'b, c'
'description': 'b, c',
'category': 'default'
}, {
'id': 3,
'path': 'three',
'description': 'a, c'
'description': 'a, c',
'category': 'default'
}, {
'id': 4,
'path': 'four',
'description': 'd'
'description': 'd',
'category': 'default'
}, {
'id': 5,
'path': 'Admin_Test',
'description': 'Project for testing admin window'
'description': 'Project for testing admin window',
'category': 'default'
}, {
'id': 6,
'path': 'test_mscolab',
'description': 'Project for testing mscolab main window'
'description': 'Project for testing mscolab main window',
'category': 'default'
}]
for project in projects:
db_project = Project(project['path'], project['description'])
db_project = Project(project['path'], project['description'], project['category'])
db_project.id = project['id']
db.session.add(db_project)

Expand Down
3 changes: 2 additions & 1 deletion mslib/mscolab/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,8 +278,9 @@ def create_project():
path = request.form['path']
content = request.form.get('content', None)
description = request.form.get('description', None)
category = request.form.get('category', "default")
user = g.user
return str(fm.create_project(path, description, user, content=content))
return str(fm.create_project(path, description, user, content=content, category=category))


@APP.route('/get_project_by_id', methods=['GET'])
Expand Down
4 changes: 4 additions & 0 deletions mslib/msui/_tests/test_mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,8 @@ def test_browse_add_project(self, mockopen, mockmessage):
QtWidgets.QApplication.processEvents()
self.window.mscolab.add_proj_dialog.description.setText(str("example"))
QtWidgets.QApplication.processEvents()
self.window.mscolab.add_proj_dialog.category.setText(str("example"))
QtWidgets.QApplication.processEvents()
QtTest.QTest.mouseClick(self.window.mscolab.add_proj_dialog.browse, QtCore.Qt.LeftButton)
QtWidgets.QApplication.processEvents()
okWidget = self.window.mscolab.add_proj_dialog.buttonBox.button(
Expand Down Expand Up @@ -536,6 +538,8 @@ def _create_project(self, path, description, mockbox):
QtWidgets.QApplication.processEvents()
self.window.mscolab.add_proj_dialog.description.setText(str(description))
QtWidgets.QApplication.processEvents()
self.window.mscolab.add_proj_dialog.category.setText("example")
QtWidgets.QApplication.processEvents()
okWidget = self.window.mscolab.add_proj_dialog.buttonBox.button(
self.window.mscolab.add_proj_dialog.buttonBox.Ok)
QtTest.QTest.mouseClick(okWidget, QtCore.Qt.LeftButton)
Expand Down
62 changes: 58 additions & 4 deletions mslib/msui/mscolab.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
from mslib.msui import mscolab_project as mp
from mslib.msui import mscolab_admin_window as maw
from mslib.msui import mscolab_version_history as mvh
# from mslib.msui import sideview, tableview, topview, linearview
from mslib.msui import socket_control as sc

from PyQt5 import QtCore, QtGui, QtWidgets
Expand Down Expand Up @@ -377,6 +376,7 @@ def __init__(self, parent=None, data_dir=None):
self.ui.actionManageUsers.triggered.connect(self.project_options_handler)
self.ui.actionDeleteProject.triggered.connect(self.project_options_handler)

self.ui.filterCategoryCb.currentIndexChanged.connect(self.project_category_handler)
# connect slot for handling project options combobox
self.ui.workLocallyCheckbox.stateChanged.connect(self.handle_work_locally_toggle)
self.ui.serverOptionsCb.currentIndexChanged.connect(self.server_options_handler)
Expand Down Expand Up @@ -425,6 +425,7 @@ def __init__(self, parent=None, data_dir=None):
self.mscolab_server_url = None
# User email
self.email = None
self.selected_category = "ANY"

# set data dir, uri
if data_dir is None:
Expand Down Expand Up @@ -509,6 +510,9 @@ def after_login(self, emailid, url, r):
# Populate open projects list
self.add_projects_to_ui()

# Show category list
self.show_categories_to_ui()

def verify_user_token(self):
data = {
"token": self.token
Expand Down Expand Up @@ -654,7 +658,9 @@ def delete_account(self):
def add_project_handler(self):
if self.verify_user_token():
def check_and_enable_project_accept():
if self.add_proj_dialog.path.text() != "" and self.add_proj_dialog.description.toPlainText() != "":
if (self.add_proj_dialog.path.text() != "" and
self.add_proj_dialog.description.toPlainText() != "" and
self.add_proj_dialog.category.text() != ""):
self.add_proj_dialog.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(True)
else:
self.add_proj_dialog.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
Expand Down Expand Up @@ -687,8 +693,10 @@ def browse():
self.add_proj_dialog.buttonBox.accepted.connect(self.add_project)
self.add_proj_dialog.buttonBox.button(QtWidgets.QDialogButtonBox.Ok).setEnabled(False)
self.add_proj_dialog.path.textChanged.connect(check_and_enable_project_accept)
self.add_proj_dialog.description.textChanged.connect(check_and_enable_project_accept)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not have been removed.?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch :)

self.add_proj_dialog.description.textChanged.connect(check_and_enable_project)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"_accept" is missing

self.add_proj_dialog.category.textChanged.connect(check_and_enable_project_accept)
self.add_proj_dialog.browse.clicked.connect(browse)
self.add_proj_dialog.category.setText(config_loader(dataset="MSCOLAB_category"))
self.proj_diag.show()
else:
show_popup(self.ui, "Error", "Your Connection is expired. New Login required!")
Expand All @@ -697,6 +705,7 @@ def browse():
def add_project(self):
path = self.add_proj_dialog.path.text()
description = self.add_proj_dialog.description.toPlainText()
category = self.add_proj_dialog.category.text()
if not path:
self.error_dialog = QtWidgets.QErrorMessage()
self.error_dialog.showMessage('Path can\'t be empty')
Expand All @@ -705,6 +714,11 @@ def add_project(self):
self.error_dialog = QtWidgets.QErrorMessage()
self.error_dialog.showMessage('Description can\'t be empty')
return
# same regex as for path validation
elif not re.match("^[a-zA-Z0-9_-]*$", category):
self.error_dialog = QtWidgets.QErrorMessage()
self.error_dialog.showMessage('Category can\'t contain spaces or special characters')
return
# regex checks if the whole path from beginning to end only contains alphanumerical characters or _ and -
elif not re.match("^[a-zA-Z0-9_-]*$", path):
self.error_dialog = QtWidgets.QErrorMessage()
Expand All @@ -714,7 +728,8 @@ def add_project(self):
data = {
"token": self.token,
"path": path,
"description": description
"description": description,
"category": category
}
if self.add_proj_dialog.f_content is not None:
data["content"] = self.add_proj_dialog.f_content
Expand All @@ -723,6 +738,12 @@ def add_project(self):
self.error_dialog = QtWidgets.QErrorMessage()
self.error_dialog.showMessage('Your project was created successfully')
self.add_projects_to_ui()
selected_category = self.ui.filterCategoryCb.currentText()
self.show_categories_to_ui()
self.project_category_handler()
index = self.ui.filterCategoryCb.findText(selected_category, QtCore.Qt.MatchFixedString)
if index >= 0:
self.ui.filterCategoryCb.setCurrentIndex(index)
p_id = self.get_recent_pid()
self.conn.handle_new_room(p_id)
else:
Expand Down Expand Up @@ -936,6 +957,20 @@ def reload_local_wp(self):
self.waypoints_model.dataChanged.connect(self.handle_waypoints_changed)
self.reload_view_windows()

def project_category_handler(self):
self.selected_category = self.ui.filterCategoryCb.currentText()
if self.selected_category != "ANY":
self.add_projects_to_ui()
items = [self.ui.listProjectsMSC.item(i) for i in range(self.ui.listProjectsMSC.count())]
row = 0
for item in items:
if item.project_category != self.selected_category:
self.ui.listProjectsMSC.takeItem(row)
else:
row += 1
else:
self.add_projects_to_ui()

def server_options_handler(self, index):
selected_option = self.ui.serverOptionsCb.currentText()
self.ui.serverOptionsCb.blockSignals(True)
Expand Down Expand Up @@ -1123,6 +1158,24 @@ def handle_project_deleted(self, p_id):
project_name = self.delete_project_from_list(p_id)
show_popup(self.ui, "Success", f'Project "{project_name}" was deleted!', icon=1)

def show_categories_to_ui(self):
if self.verify_user_token():
data = {
"token": self.token
}
r = requests.get(f'{self.mscolab_server_url}/projects', data=data)
if r.text != "False":
_json = json.loads(r.text)
projects = _json["projects"]
self.ui.filterCategoryCb.clear()
categories = set(["ANY"])
for project in projects:
categories.add(project["category"])
categories.remove("ANY")
categories = list(categories)
categories.insert(0, "ANY")
self.ui.filterCategoryCb.addItems(categories)

def add_projects_to_ui(self):
if self.verify_user_token():
data = {
Expand All @@ -1142,6 +1195,7 @@ def add_projects_to_ui(self):
widgetItem.p_id = project["p_id"]
widgetItem.access_level = project["access_level"]
widgetItem.project_path = project["path"]
widgetItem.project_category = project["category"]
if widgetItem.p_id == self.active_pid:
selectedProject = widgetItem
self.ui.listProjectsMSC.addItem(widgetItem)
Expand Down
25 changes: 17 additions & 8 deletions mslib/msui/qt5/ui_add_project_dialog.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'mslib/msui/ui/ui_add_project.ui'
# Form implementation generated from reading ui file 'ui_add_project.ui'
#
# Created by: PyQt5 UI code generator 5.12.3
#
Expand All @@ -13,9 +13,9 @@
class Ui_addProjectDialog(object):
def setupUi(self, addProjectDialog):
addProjectDialog.setObjectName("addProjectDialog")
addProjectDialog.resize(467, 256)
addProjectDialog.resize(467, 303)
self.buttonBox = QtWidgets.QDialogButtonBox(addProjectDialog)
self.buttonBox.setGeometry(QtCore.QRect(280, 210, 171, 32))
self.buttonBox.setGeometry(QtCore.QRect(280, 250, 171, 32))
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok)
self.buttonBox.setObjectName("buttonBox")
Expand All @@ -29,19 +29,25 @@ def setupUi(self, addProjectDialog):
self.description.setGeometry(QtCore.QRect(110, 60, 341, 59))
self.description.setObjectName("description")
self.browse = QtWidgets.QPushButton(addProjectDialog)
self.browse.setGeometry(QtCore.QRect(350, 150, 100, 30))
self.browse.setGeometry(QtCore.QRect(350, 210, 100, 30))
self.browse.setObjectName("browse")
self.label_3 = QtWidgets.QLabel(addProjectDialog)
self.label_3.setGeometry(QtCore.QRect(20, 134, 201, 16))
self.label_3.setGeometry(QtCore.QRect(20, 180, 201, 16))
self.label_3.setObjectName("label_3")
self.label_2 = QtWidgets.QLabel(addProjectDialog)
self.label_2.setGeometry(QtCore.QRect(20, 60, 80, 16))
self.label_2.setObjectName("label_2")
self.selectedFile = QtWidgets.QLineEdit(addProjectDialog)
self.selectedFile.setEnabled(False)
self.selectedFile.setGeometry(QtCore.QRect(20, 150, 320, 30))
self.selectedFile.setGeometry(QtCore.QRect(20, 210, 320, 30))
self.selectedFile.setReadOnly(True)
self.selectedFile.setObjectName("selectedFile")
self.label_4 = QtWidgets.QLabel(addProjectDialog)
self.label_4.setGeometry(QtCore.QRect(40, 130, 60, 16))
self.label_4.setObjectName("label_4")
self.category = QtWidgets.QLineEdit(addProjectDialog)
self.category.setGeometry(QtCore.QRect(110, 130, 341, 23))
self.category.setObjectName("category")

self.retranslateUi(addProjectDialog)
self.buttonBox.accepted.connect(addProjectDialog.accept)
Expand All @@ -55,6 +61,9 @@ def retranslateUi(self, addProjectDialog):
self.path.setPlaceholderText(_translate("addProjectDialog", "Project Name (No spaces or special characters)"))
self.description.setPlaceholderText(_translate("addProjectDialog", "Project Descriptions"))
self.browse.setText(_translate("addProjectDialog", "browse..."))
self.label_3.setText(_translate("addProjectDialog", "Choose Flighttrack File (Optional)"))
self.label_2.setText(_translate("addProjectDialog", "Description"))
self.label_3.setText(_translate("addProjectDialog", "Choose Flighttrack File (Optional):"))
self.label_2.setText(_translate("addProjectDialog", "Description:"))
self.selectedFile.setPlaceholderText(_translate("addProjectDialog", "(use browse to pick a file)"))
self.label_4.setText(_translate("addProjectDialog", "Category:"))
self.category.setText(_translate("addProjectDialog", "default"))
self.category.setPlaceholderText(_translate("addProjectDialog", "Category (ANY)"))
Loading