diff --git a/lumi2/static/js/group_create.js b/lumi2/static/js/group_create.js new file mode 100644 index 0000000..6c0e846 --- /dev/null +++ b/lumi2/static/js/group_create.js @@ -0,0 +1,164 @@ +class UserEntry { + constructor(username, isMember, rowElement) { + this.username = username; + this.isMember = isMember; + this.rowElement = rowElement; + this.buttonElement = $(rowElement).find(".toggleMembershipButton"); + if (isMember) { + $(this.buttonElement).click(() => this.onClickLeave()); + } else { + $(this.buttonElement).click(() => this.onClickJoin()); + } + } + + onClickLeave() { + this.isMember = false; + $(this.buttonElement).off("click"); + $(this.buttonElement).click(() => this.onClickJoin()); + $(this.rowElement).prependTo($("#tableNonMembers").find("tbody")); + this.setButtonAppearanceJoinGroup(); + if (membersTableIsEmpty()) { + showEmptyTableNotice(); + } + disableCreateGroupButton(); + } + + onClickJoin() { + this.isMember = true; + $(this.buttonElement).off("click"); + $(this.buttonElement).click(() => this.onClickLeave()); + $(this.rowElement).prependTo($("#tableMembers").find("tbody")); + this.setButtonAppearanceLeaveGroup(); + hideEmptyTableNotice(); + enableCreateGroupButton(); + } + + setButtonAppearanceLeaveGroup() { + this.buttonElement.removeClass("btn-danger btn-success btn-secondary"); + this.buttonElement.addClass("btn-danger"); + this.buttonElement.empty(); + this.buttonElement.html( + ' Cancel' + ); + } + + setButtonAppearanceJoinGroup() { + this.buttonElement.removeClass("btn-danger btn-success btn-secondary"); + this.buttonElement.addClass("btn-success"); + this.buttonElement.empty(); + this.buttonElement.html( + ' Add' + ); + } +} + +function showErrorMessage(message) { + $("nav").after([ + '' + ].join('')); +} + +function getGroupMembers() { + let groupMembers = []; + for (entry of $(membersTable).find(".userEntry")) { + groupMembers.push(entry.id); + } + return groupMembers; +} + +function getGroupName() { + return $(groupNameInput).val(); +} + +function getUserEntries() { + let userEntries = []; + for (let entry of nonMembersTable.find("tbody").find(".userEntry")) { + userEntries.push(new UserEntry( + entry.id, + false, + entry + )); + } + + return userEntries; +} + +function membersTableIsEmpty() { + return $(membersTable).find("tbody").find(".userEntry").length === 0 +} + +function hideEmptyTableNotice() { + if (emptyTableNotice.parent().length != 0) { + emptyTableNotice.remove(); + } +} + +function showEmptyTableNotice() { + if (emptyTableNotice.parent().length == 0) { + $(membersTable).find("tbody").append($(emptyTableNotice)); + } +} + +function disableCreateGroupButton() { + if ($(groupNameInput).val().length == 0 || membersTableIsEmpty()) { + createGroupButton[0].disabled = true; + } +} + +function enableCreateGroupButton() { + if ($(groupNameInput).val().length > 0 && !membersTableIsEmpty()) { + createGroupButton[0].disabled = false; + } +} + +function createGroupButtonOnClick() { + $.ajax({ + url: `/api/group/${getGroupName()}`, + type: "POST", + contentType: 'application/json', + data: JSON.stringify({ + "members": getGroupMembers() + }), + dataType: "json", + }).done(function() { + window.location.replace(window.location.origin + `/groups/update/${getGroupName()}`); + }).fail(function(xhr, status, errorThrown) { + console.log(`Error: ${errorThrown}`); + console.log(`Status: ${status}`); + displayTextInputError(xhr.responseJSON['message']); + }); +} + +function displayTextInputError(message) { + $(createGroupButton).after([ + '' + ].join('')); +} + +let nonMembersTable = undefined; +let membersTable = undefined; +let entries = undefined; +let emptyTableNotice = undefined; +let createGroupButton = undefined; +let groupNameInput = undefined; + +$(document).ready(function() { + nonMembersTable = $("#tableNonMembers"); + membersTable = $("#tableMembers"); + entries = getUserEntries(); + emptyTableNotice = $("#emptyTableNotice"); + createGroupButton = $("#createGroupButton"); + groupNameInput = $("#groupNameInput"); + disableCreateGroupButton(); + $(groupNameInput).on("input", function() { + enableCreateGroupButton(); + disableCreateGroupButton(); + }); + createGroupButton.click(createGroupButtonOnClick); +}); diff --git a/lumi2/templates/usermanager/group_create.html b/lumi2/templates/usermanager/group_create.html new file mode 100644 index 0000000..f3d4cce --- /dev/null +++ b/lumi2/templates/usermanager/group_create.html @@ -0,0 +1,63 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Create a new Group

+
+
+
+ + + Cancel + +

+

+
+ + + + + + + + + + + +
Members
+

You must add at least one member.

+
+
+
+ + + + + + + + {% for user in users|sort %} + + + + + + {% endfor %} + +
Other Users
+ Profile picture for user {{ user.username }} + + {{ user.username }} + + +
+
+ +{% endblock content %} diff --git a/lumi2/usermanager.py b/lumi2/usermanager.py index 8e5c2ed..0467dd8 100644 --- a/lumi2/usermanager.py +++ b/lumi2/usermanager.py @@ -343,18 +343,25 @@ def group_list(): ) -@bp.route("/groups/create", methods=("GET", "POST")) +@bp.route("/groups/create") def group_create(): - """Displays a form allowing group creation.""" + """Creation view for a new group. + + Shows a table allowing adding members to this group. + """ try: conn = ldap.get_connection() except Exception: abort(500) + users = ldap.get_users(conn) conn.unbind() - # TODO implement - abort(404) + + return render_template( + 'usermanager/group_create.html', + users=users, + ) @bp.route("/groups/update/") diff --git a/lumi2/usermodel.py b/lumi2/usermodel.py index 53db474..626c0a2 100644 --- a/lumi2/usermodel.py +++ b/lumi2/usermodel.py @@ -462,8 +462,8 @@ class Group: def assert_is_valid_groupname(input_str: str) -> None: """Checks whether the input string is a valid group name. - A valid group name consists of only alphanumeric characters, starts with - a latin character, has minimum length 1 and maximim length 64. + A valid group name consists of only alphabetic characters, has minimum + length 1 and maximum length 64. Parameters ---------- @@ -485,12 +485,8 @@ class Group: raise InvalidStringFormatException( "Invalid group name: must contain at least one character." ) - if input_str[0] not in ascii_lowercase + ascii_uppercase: - raise InvalidStringFormatException( - "Invalid group name: must start with a letter." - ) for char in input_str: - if not char in ascii_uppercase + ascii_lowercase + digits: + if not char in ascii_uppercase + ascii_lowercase: raise InvalidStringFormatException( f"Invalid character in group name: '{char}'." )