Start on organization conversion. Note that this does not yet move over the user's plan to an org plan

This commit is contained in:
Joseph Schorr 2013-11-07 16:33:56 -05:00
parent a7415ef4d3
commit 3f2d51651e
5 changed files with 241 additions and 1 deletions

View file

@ -99,6 +99,22 @@ def create_organization(name, email, creating_user):
raise InvalidOrganizationException('Invalid organization name: %s' % name)
def convert_user_to_organization(user, admin_user):
# Change the user to an organization.
user.organization = True
# TODO: disable this account for login.
user.password = ''
user.save()
# Create a team for the owners
owners_team = create_team('owners', user, 'admin')
# Add the user who will admin the org to the owners team
add_user_to_team(admin_user, owners_team)
return user
def create_team(name, org, team_role_name, description=''):
if not validate_username(name):
raise InvalidTeamException('Invalid team name: %s' % name)

View file

@ -96,6 +96,38 @@ def get_logged_in_user():
})
@app.route('/api/user/convert', methods=['POST'])
@api_login_required
def convert_user_to_organization():
user = current_user.db_user()
convert_data = request.get_json()
# Ensure that the new admin user is the not user being converted.
admin_username = convert_data['adminUser']
if admin_username == user.username:
error_resp = jsonify({
'reason': 'invaliduser'
})
error_resp.status_code = 400
return error_resp
# Ensure that the sign in credentials work.
admin_password = convert_data['adminPassword']
if not model.verify_user(admin_username, admin_password):
error_resp = jsonify({
'reason': 'invaliduser'
})
error_resp.status_code = 400
return error_resp
# Convert the user to an organization.
model.convert_user_to_organization(user, model.get_user(admin_username))
# And finally login with the admin credentials.
return conduct_signin(admin_username, admin_password)
@app.route('/api/user/', methods=['PUT'])
@api_login_required
def change_user_details():
@ -157,6 +189,10 @@ def signin_api():
username = signin_data['username']
password = signin_data['password']
return conduct_signin(username, password)
def conduct_signin(username, password):
#TODO Allow email login
needs_email_verification = False
invalid_credentials = False

View file

@ -1276,6 +1276,11 @@ p.editable:hover i {
border: inherit;
}
.user-admin #migrate .panel {
max-width: 600px;
text-align: center;
}
.user-admin .panel-plan {
text-align: center;
}
@ -1295,6 +1300,41 @@ p.editable:hover i {
margin-bottom: 12px;
}
.user-admin .convert-form h3 {
margin-bottom: 20px;
}
.user-admin #convertForm {
max-width: 500px;
}
.user-admin #convertForm .form-group {
margin-bottom: 20px;
}
.user-admin #convertForm input {
margin-bottom: 10px;
margin-left: 20px;
}
.user-admin #convertForm .existing-data {
font-size: 16px;
font-weight: bold;
}
.user-admin #convertForm .description {
margin-top: 10px;
display: block;
color: #888;
font-size: 12px;
margin-left: 20px;
}
.user-admin #convertForm .existing-data {
display: block;
padding-left: 20px;
margin-top: 10px;
}
#image-history-container {
overflow: hidden;

View file

@ -690,7 +690,7 @@ function RepoAdminCtrl($scope, Restangular, $routeParams, $rootScope) {
}
function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService, KeyService, $routeParams) {
function UserAdminCtrl($scope, $timeout, $location, Restangular, PlanService, UserService, KeyService, $routeParams) {
$scope.$watch(function () { return UserService.currentUser(); }, function (currentUser) {
$scope.askForPassword = currentUser.askForPassword;
if (!currentUser.anonymous) {
@ -704,12 +704,48 @@ function UserAdminCtrl($scope, $timeout, Restangular, PlanService, UserService,
return $routeParams['plan'];
};
if ($routeParams['migrate']) {
$('#migrateTab').tab('show')
}
$scope.loading = true;
$scope.updatingUser = false;
$scope.changePasswordSuccess = false;
$scope.convertStep = 0;
$('.form-change-pw').popover();
$scope.showConvertForm = function() {
$scope.convertStep = 1;
};
$scope.convertToOrg = function() {
$('#reallyconvertModal').modal({});
};
$scope.reallyConvert = function() {
$scope.loading = true;
var data = {
'adminUser': $scope.org.adminUser,
'adminPassword': $scope.org.adminPassword
};
var convertAccount = Restangular.one('user/convert');
convertAccount.customPOST(data).then(function(resp) {
UserService.load();
$location.path('/');
}, function(resp) {
$scope.loading = false;
if (resp.data.reason == 'invaliduser') {
$('#invalidadminModal').modal({});
} else {
$('#cannotconvertModal').modal({});
}
});
};
$scope.changePassword = function() {
$('.form-change-pw').popover('hide');
$scope.updatingUser = true;

View file

@ -28,6 +28,7 @@
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="javascript:void(0)" data-toggle="tab" data-target="#plan">Plan and Usage</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#password">Set Password</a></li>
<li><a href="javascript:void(0)" data-toggle="tab" data-target="#migrate" id="migrateTab">Convert to Organization</a></li>
</ul>
</div>
@ -55,8 +56,119 @@
<span class="help-block" ng-show="changePasswordSuccess">Password changed successfully</span>
</form>
</div>
<!-- Convert to organization tab -->
<div id="migrate" class="tab-pane">
<!-- Step 0 -->
<div class="panel" ng-show="convertStep == 0">
<div class="panel-body" ng-show="user.organizations.length > 0">
<div class="alert alert-info">
Cannot convert this account into an organization, as it is a member of {{user.organizations.length}} other
organization{{user.organizations.length > 1 ? 's' : ''}}. Please leave
{{user.organizations.length > 1 ? 'those organizations' : 'that organization'}} first.
</div>
</div>
<div class="panel-body" ng-show="user.organizations.length == 0">
<div class="alert alert-danger">
Converting a user account into an organization <b>cannot be undone</b>.<br> Here be many fire-breathing dragons!
</div>
<button class="btn btn-danger" ng-click="showConvertForm()">Start conversion process</button>
</div>
</div>
<!-- Step 1 -->
<div class="convert-form" ng-show="convertStep == 1">
<h3>Convert to organization</h3>
<form method="post" name="convertForm" id="convertForm" ng-submit="convertToOrg()">
<div class="form-group">
<label for="orgName">Organization Name</label>
<div class="existing-data">
<img src="//www.gravatar.com/avatar/{{ user.gravatar }}?s=24&amp;d=identicon">
{{ user.username }}</div>
<span class="description">This will continue to be the namespace for your repositories</span>
</div>
<div class="form-group">
<label for="orgName">Admin User</label>
<input id="adminUsername" name="adminUsername" type="text" class="form-control" placeholder="Admin Username"
ng-model="org.adminUser" required autofocus>
<input id="adminPassword" name="adminPassword" type="password" class="form-control" placeholder="Admin Password"
ng-model="org.adminPassword" required>
<span class="description">The username and password for an <b>existing account</b> that will become administrator of the organization</span>
</div>
<div class="button-bar">
<button class="btn btn-large btn-danger" type="submit" ng-disabled="convertForm.$invalid">
Convert To Organization
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Modal message dialog -->
<div class="modal fade" id="cannotconvertModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Cannot convert account</h4>
</div>
<div class="modal-body">
Your account could not be converted. Please try again in a moment.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Modal message dialog -->
<div class="modal fade" id="invalidadminModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Username or password invalid</h4>
</div>
<div class="modal-body">
The username or password specified for the admin account is not valid.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<!-- Modal message dialog -->
<div class="modal fade" id="reallyconvertModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Convert to organization?</h4>
</div>
<div class="modal-body">
<div class="alert alert-danger">You will not be able to login to this account once converted</div>
<div>Are you <b>absolutely sure</b> you would like to convert this account to an organization? Once done, there is no going back.</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal" ng-click="reallyConvert()">Absolutely: Convert Now</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->