Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
60 changes: 57 additions & 3 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 @@ -688,7 +694,9 @@ def browse():
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.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