mirror of
https://github.com/codeninjasllc/discourse.git
synced 2025-04-13 07:24:38 -04:00
FEATURE: Show user fields when the user is signing up
This commit is contained in:
parent
872d8fce58
commit
edb34c178a
42 changed files with 476 additions and 141 deletions
app
assets
javascripts
admin
controllers
models
templates
discourse
components
controllers
models
templates
views
stylesheets
controllers
models
serializers
services
config/locales
db/migrate
spec
test/javascripts
|
@ -13,10 +13,12 @@ export default Ember.ObjectController.extend(BufferedContent, {
|
|||
save: function() {
|
||||
var self = this;
|
||||
|
||||
this.commitBuffer();
|
||||
this.get('model').save().then(function(res) {
|
||||
var attrs = this.get('buffered').getProperties('name', 'field_type', 'editable');
|
||||
|
||||
this.get('model').save(attrs).then(function(res) {
|
||||
self.set('model.id', res.user_field.id);
|
||||
self.set('editing', false);
|
||||
self.commitBuffer();
|
||||
}).catch(function() {
|
||||
bootbox.alert(I18n.t('generic_error'));
|
||||
});
|
||||
|
|
|
@ -2,8 +2,10 @@ import UserField from 'admin/models/user-field';
|
|||
|
||||
export default Ember.ArrayController.extend({
|
||||
fieldTypes: null,
|
||||
|
||||
createDisabled: Em.computed.gte('model.length', 3),
|
||||
userFieldsName: function() {
|
||||
return I18n.t('admin.user_fields.name');
|
||||
}.property(),
|
||||
|
||||
_performDestroy: function(f, model) {
|
||||
return f.destroy().then(function() {
|
||||
|
@ -13,10 +15,7 @@ export default Ember.ArrayController.extend({
|
|||
|
||||
actions: {
|
||||
createField: function() {
|
||||
this.pushObject(UserField.create({
|
||||
field_type: 'text',
|
||||
name: I18n.t('admin.user_fields.untitled')
|
||||
}));
|
||||
this.pushObject(UserField.create({ field_type: 'text' }));
|
||||
},
|
||||
|
||||
destroy: function(f) {
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
import ObjectController from 'discourse/controllers/object';
|
||||
import CanCheckEmails from 'discourse/mixins/can-check-emails';
|
||||
|
||||
/**
|
||||
A controller related to viewing a user in the admin section
|
||||
|
||||
@class AdminUserIndexController
|
||||
@extends ObjectController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default ObjectController.extend(CanCheckEmails, {
|
||||
editingTitle: false,
|
||||
originalPrimaryGroupId: null,
|
||||
|
@ -23,6 +15,19 @@ export default ObjectController.extend(CanCheckEmails, {
|
|||
return (!g.automatic && g.visible);
|
||||
}),
|
||||
|
||||
userFields: function() {
|
||||
var siteUserFields = this.site.get('user_fields'),
|
||||
userFields = this.get('user_fields');
|
||||
|
||||
if (!Ember.empty(siteUserFields)) {
|
||||
return siteUserFields.map(function(uf) {
|
||||
var value = userFields ? userFields[uf.get('id').toString()] : null;
|
||||
return {name: uf.get('name'), value: value};
|
||||
});
|
||||
}
|
||||
return [];
|
||||
}.property('user_fields.@each'),
|
||||
|
||||
actions: {
|
||||
toggleTitleEdit: function() {
|
||||
this.toggleProperty('editingTitle');
|
||||
|
|
|
@ -1,12 +1,3 @@
|
|||
import ObjectController from 'discourse/controllers/object';
|
||||
|
||||
/**
|
||||
The top-level controller for user pages in admin.
|
||||
Ember assertion says that this class needs to be defined even if it's empty.
|
||||
|
||||
@class AdminUserController
|
||||
@extends ObjectController
|
||||
@namespace Discourse
|
||||
@module Discourse
|
||||
**/
|
||||
export default ObjectController.extend();
|
||||
|
|
|
@ -17,17 +17,17 @@ var UserField = Ember.Object.extend({
|
|||
});
|
||||
},
|
||||
|
||||
save: function() {
|
||||
save: function(attrs) {
|
||||
var id = this.get('id');
|
||||
if (!id) {
|
||||
return Discourse.ajax("/admin/customize/user_fields", {
|
||||
type: "POST",
|
||||
data: { user_field: this.getProperties('name', 'field_type') }
|
||||
data: { user_field: attrs }
|
||||
});
|
||||
} else {
|
||||
return Discourse.ajax("/admin/customize/user_fields/" + id, {
|
||||
type: "PUT",
|
||||
data: { user_field: this.getProperties('name', 'field_type') }
|
||||
data: { user_field: attrs }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
<li>{{#link-to 'adminCustomize.colors'}}{{i18n admin.customize.colors.title}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminCustomize.css_html'}}{{i18n admin.customize.css_html.title}}{{/link-to}}</li>
|
||||
<li>{{#link-to 'adminSiteText'}}{{i18n admin.site_text.title}}{{/link-to}}</li>
|
||||
{{#if userFieldFeatureComplete}}
|
||||
<li>{{#link-to 'adminUserFields'}}{{i18n admin.user_fields.title}}{{/link-to}}</li>
|
||||
{{/if}}
|
||||
<li>{{#link-to 'adminUserFields'}}{{i18n admin.user_fields.title}}{{/link-to}}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -7,13 +7,14 @@
|
|||
{{#each f in model itemController="admin-user-field-item" itemView="admin-user-field-item"}}
|
||||
{{#if f.editing}}
|
||||
<div class='form-element'>
|
||||
<label>{{i18n admin.user_fields.name}}
|
||||
{{input value=f.buffered.name class="user-field-name"}}
|
||||
</label>
|
||||
{{input value=f.buffered.name class="user-field-name" placeholder=userFieldsName}}
|
||||
</div>
|
||||
<div class='form-element'>
|
||||
<label>{{i18n admin.user_fields.type}}
|
||||
{{combo-box content=fieldTypes valueAttribute="id" value=f.buffered.field_type}}
|
||||
{{combo-box content=fieldTypes valueAttribute="id" value=f.buffered.field_type}}
|
||||
</div>
|
||||
<div class='form-element'>
|
||||
<label>
|
||||
{{input type="checkbox" checked=f.buffered.editable}} {{i18n admin.user_fields.editable.title}}
|
||||
</label>
|
||||
</div>
|
||||
<div class='form-element controls'>
|
||||
|
@ -21,11 +22,14 @@
|
|||
<button {{action "cancel"}} class='btn btn-danger'>{{fa-icon 'times'}} {{i18n admin.user_fields.cancel}}</button>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class='form-display'>{{f.name}}</div>
|
||||
<div class='form-display'>{{f.fieldName}}</div>
|
||||
<div class='form-display'>
|
||||
{{f.name}}
|
||||
</div>
|
||||
<div class='form-display'>
|
||||
{{f.fieldName}}
|
||||
{{#if f.editable}}
|
||||
{{i18n admin.user_fields.editable.enabled}}
|
||||
{{else}}
|
||||
{{i18n admin.user_fields.editable.disabled}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class='form-element controls'>
|
||||
<button {{action "edit"}}class='btn btn-default'>{{fa-icon 'pencil'}} {{i18n admin.user_fields.edit}}</button>
|
||||
|
|
|
@ -85,22 +85,22 @@
|
|||
|
||||
|
||||
{{#if currentUser.admin}}
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.groups.title}}</div>
|
||||
<div class='value'>
|
||||
{{admin-group-selector selected=model.groups available=availableGroups}}
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{i18n admin.groups.title}}</div>
|
||||
<div class='value'>
|
||||
{{admin-group-selector selected=model.groups available=availableGroups}}
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if custom_groups}}
|
||||
{{i18n admin.groups.primary}}
|
||||
{{combo-box content=custom_groups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{/if}}
|
||||
{{#if primaryGroupDirty}}
|
||||
<button class='btn ok no-text' {{action savePrimaryGroup}}><i class='fa fa-check'></i></button>
|
||||
<button class='btn cancel no-text' {{action resetPrimaryGroup}}><i class='fa fa-times'></i></button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
<div class='controls'>
|
||||
{{#if custom_groups}}
|
||||
{{i18n admin.groups.primary}}
|
||||
{{combo-box content=custom_groups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
|
||||
{{/if}}
|
||||
{{#if primaryGroupDirty}}
|
||||
<button class='btn ok no-text' {{action savePrimaryGroup}}><i class='fa fa-check'></i></button>
|
||||
<button class='btn cancel no-text' {{action resetPrimaryGroup}}><i class='fa fa-times'></i></button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class='display-row'>
|
||||
|
@ -137,9 +137,25 @@
|
|||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</section>
|
||||
|
||||
{{#if userFields}}
|
||||
<section class='details'>
|
||||
{{#each userFields}}
|
||||
<div class='display-row'>
|
||||
<div class='field'>{{name}}</div>
|
||||
<div class='value'>
|
||||
{{#if value}}
|
||||
{{value}}
|
||||
{{else}}
|
||||
—
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
|
||||
<section class='details'>
|
||||
<h1>{{i18n admin.user.permissions}}</h1>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
export default Ember.Component.extend({
|
||||
classNameBindings: [':user-field'],
|
||||
layoutName: function() {
|
||||
return "components/user-fields/" + this.get('field.field_type');
|
||||
}.property('field.field_type')
|
||||
});
|
|
@ -15,6 +15,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
|||
rejectedPasswords: Em.A([]),
|
||||
prefilledUsername: null,
|
||||
tosAccepted: false,
|
||||
userFields: null,
|
||||
|
||||
hasAuthOptions: Em.computed.notEmpty('authOptions'),
|
||||
canCreateLocal: Discourse.computed.setting('enable_local_logins'),
|
||||
|
@ -22,6 +23,8 @@ export default DiscourseController.extend(ModalFunctionality, {
|
|||
maxUsernameLength: Discourse.computed.setting('max_username_length'),
|
||||
|
||||
resetForm: function() {
|
||||
|
||||
// We wrap the fields in a structure so we can assign a value
|
||||
this.setProperties({
|
||||
accountName: '',
|
||||
accountEmail: '',
|
||||
|
@ -31,10 +34,11 @@ export default DiscourseController.extend(ModalFunctionality, {
|
|||
globalNicknameExists: false,
|
||||
complete: false,
|
||||
formSubmitted: false,
|
||||
rejectedEmails: Em.A([]),
|
||||
rejectedPasswords: Em.A([]),
|
||||
prefilledUsername: null
|
||||
rejectedEmails: [],
|
||||
rejectedPasswords: [],
|
||||
prefilledUsername: null,
|
||||
});
|
||||
this._createUserFields();
|
||||
},
|
||||
|
||||
submitDisabled: function() {
|
||||
|
@ -47,8 +51,18 @@ export default DiscourseController.extend(ModalFunctionality, {
|
|||
if (this.get('emailValidation.failed')) return true;
|
||||
if (this.get('usernameValidation.failed')) return true;
|
||||
if (this.get('passwordValidation.failed')) return true;
|
||||
|
||||
// Validate required fields
|
||||
var userFields = this.get('userFields');
|
||||
if (!Ember.empty(userFields)) {
|
||||
var anyEmpty = userFields.any(function(uf) {
|
||||
var val = uf.get('value');
|
||||
return !val || Ember.empty(val);
|
||||
});
|
||||
if (anyEmpty) { return true; }
|
||||
}
|
||||
return false;
|
||||
}.property('passwordRequired', 'nameValidation.failed', 'emailValidation.failed', 'usernameValidation.failed', 'passwordValidation.failed', 'formSubmitted', 'tosAccepted'),
|
||||
}.property('passwordRequired', 'nameValidation.failed', 'emailValidation.failed', 'usernameValidation.failed', 'passwordValidation.failed', 'formSubmitted', 'tosAccepted', 'userFields.@each.value'),
|
||||
|
||||
passwordRequired: function() {
|
||||
return this.blank('authOptions.auth_provider');
|
||||
|
@ -337,20 +351,25 @@ export default DiscourseController.extend(ModalFunctionality, {
|
|||
},
|
||||
|
||||
createAccount: function() {
|
||||
var self = this;
|
||||
var self = this,
|
||||
attrs = this.getProperties('accountName', 'accountEmail', 'accountPassword', 'accountUsername', 'accountPasswordConfirm', 'accountChallenge'),
|
||||
userFields = this.get('userFields');
|
||||
|
||||
// Add the userfields to the data
|
||||
if (!Em.empty(userFields)) {
|
||||
attrs.userFields = {};
|
||||
userFields.forEach(function(f) {
|
||||
attrs.userFields[f.get('field.id')] = f.get('value');
|
||||
});
|
||||
}
|
||||
|
||||
this.set('formSubmitted', true);
|
||||
var name = this.get('accountName');
|
||||
var email = this.get('accountEmail');
|
||||
var password = this.get('accountPassword');
|
||||
var username = this.get('accountUsername');
|
||||
var passwordConfirm = this.get('accountPasswordConfirm');
|
||||
var challenge = this.get('accountChallenge');
|
||||
return Discourse.User.createAccount(name, email, password, username, passwordConfirm, challenge).then(function(result) {
|
||||
return Discourse.User.createAccount(attrs).then(function(result) {
|
||||
if (result.success) {
|
||||
// Trigger the browser's password manager using the hidden static login form:
|
||||
var $hidden_login_form = $('#hidden-login-form');
|
||||
$hidden_login_form.find('input[name=username]').val(self.get('accountName'));
|
||||
$hidden_login_form.find('input[name=password]').val(self.get('accountPassword'));
|
||||
$hidden_login_form.find('input[name=username]').val(attrs.accountName);
|
||||
$hidden_login_form.find('input[name=password]').val(attrs.accountPassword);
|
||||
$hidden_login_form.find('input[name=redirect]').val(Discourse.getURL('/users/account-created'));
|
||||
$hidden_login_form.submit();
|
||||
} else {
|
||||
|
@ -359,7 +378,7 @@ export default DiscourseController.extend(ModalFunctionality, {
|
|||
self.get('rejectedEmails').pushObject(result.values.email);
|
||||
}
|
||||
if (result.errors && result.errors.password && result.errors.password.length > 0) {
|
||||
self.get('rejectedPasswords').pushObject(password);
|
||||
self.get('rejectedPasswords').pushObject(attrs.accountPassword);
|
||||
}
|
||||
self.set('formSubmitted', false);
|
||||
}
|
||||
|
@ -371,5 +390,21 @@ export default DiscourseController.extend(ModalFunctionality, {
|
|||
return self.flash(I18n.t('create_account.failed'), 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_createUserFields: function() {
|
||||
if (!this.site) { return; }
|
||||
|
||||
var userFields = this.site.get('user_fields');
|
||||
if (userFields) {
|
||||
userFields = userFields.map(function(f) {
|
||||
return Ember.Object.create({
|
||||
value: null,
|
||||
field: f
|
||||
});
|
||||
});
|
||||
}
|
||||
this.set('userFields', userFields);
|
||||
}.on('init')
|
||||
|
||||
});
|
||||
|
|
|
@ -18,6 +18,17 @@ export default ObjectController.extend(CanCheckEmails, {
|
|||
|
||||
newNameInput: null,
|
||||
|
||||
userFields: function() {
|
||||
var siteUserFields = this.site.get('user_fields');
|
||||
if (!Ember.empty(siteUserFields)) {
|
||||
var userFields = this.get('user_fields');
|
||||
return siteUserFields.filterProperty('editable', true).map(function(uf) {
|
||||
var val = userFields ? userFields[uf.get('id').toString()] : null;
|
||||
return Ember.Object.create({value: val, field: uf});
|
||||
});
|
||||
}
|
||||
}.property('user_fields.@each.value'),
|
||||
|
||||
cannotDeleteAccount: Em.computed.not('can_delete_account'),
|
||||
deleteDisabled: Em.computed.or('saving', 'deleting', 'cannotDeleteAccount'),
|
||||
|
||||
|
@ -70,8 +81,20 @@ export default ObjectController.extend(CanCheckEmails, {
|
|||
var self = this;
|
||||
this.setProperties({ saving: true, saved: false });
|
||||
|
||||
var model = this.get('model'),
|
||||
userFields = this.get('userFields');
|
||||
|
||||
// Update the user fields
|
||||
if (!Em.empty(userFields)) {
|
||||
var modelFields = model.get('user_fields');
|
||||
if (!Em.empty(modelFields)) {
|
||||
userFields.forEach(function(uf) {
|
||||
modelFields[uf.get('field.id').toString()] = uf.get('value');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Cook the bio for preview
|
||||
var model = this.get('model');
|
||||
model.set('name', this.get('newNameInput'));
|
||||
return model.save().then(function() {
|
||||
// model was saved
|
||||
|
|
|
@ -133,6 +133,12 @@ Discourse.Site.reopenClass(Discourse.Singleton, {
|
|||
});
|
||||
}
|
||||
|
||||
if (result.user_fields) {
|
||||
result.user_fields = result.user_fields.map(function(uf) {
|
||||
return Ember.Object.create(uf);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -188,8 +188,8 @@ Discourse.User = Discourse.Model.extend({
|
|||
@returns {Promise} the result of the operation
|
||||
**/
|
||||
save: function() {
|
||||
var user = this;
|
||||
var data = this.getProperties('auto_track_topics_after_msecs',
|
||||
var self = this,
|
||||
data = this.getProperties('auto_track_topics_after_msecs',
|
||||
'bio_raw',
|
||||
'website',
|
||||
'location',
|
||||
|
@ -206,10 +206,11 @@ Discourse.User = Discourse.Model.extend({
|
|||
'mailing_list_mode',
|
||||
'enable_quoting',
|
||||
'disable_jump_reply',
|
||||
'custom_fields');
|
||||
'custom_fields',
|
||||
'user_fields');
|
||||
|
||||
_.each(['muted','watched','tracked'], function(s){
|
||||
var cats = user.get(s + 'Categories').map(function(c){ return c.get('id')});
|
||||
['muted','watched','tracked'].forEach(function(s){
|
||||
var cats = self.get(s + 'Categories').map(function(c){ return c.get('id')});
|
||||
// HACK: denote lack of categories
|
||||
if(cats.length === 0) { cats = [-1]; }
|
||||
data[s + '_category_ids'] = cats;
|
||||
|
@ -223,13 +224,10 @@ Discourse.User = Discourse.Model.extend({
|
|||
data: data,
|
||||
type: 'PUT'
|
||||
}).then(function(data) {
|
||||
user.set('bio_excerpt',data.user.bio_excerpt);
|
||||
self.set('bio_excerpt',data.user.bio_excerpt);
|
||||
|
||||
_.each([
|
||||
'enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon'
|
||||
], function(preference) {
|
||||
Discourse.User.current().set(preference, user.get(preference));
|
||||
});
|
||||
var userProps = self.getProperties('enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon');
|
||||
Discourse.User.current().setProperties(userProps);
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -542,26 +540,18 @@ Discourse.User.reopenClass(Discourse.Singleton, {
|
|||
},
|
||||
|
||||
/**
|
||||
Creates a new account over POST
|
||||
|
||||
@method createAccount
|
||||
@param {String} name This user's name
|
||||
@param {String} email This user's email
|
||||
@param {String} password This user's password
|
||||
@param {String} username This user's username
|
||||
@param {String} passwordConfirm This user's confirmed password
|
||||
@param {String} challenge
|
||||
@returns Result of ajax call
|
||||
Creates a new account
|
||||
**/
|
||||
createAccount: function(name, email, password, username, passwordConfirm, challenge) {
|
||||
createAccount: function(attrs) {
|
||||
return Discourse.ajax("/users", {
|
||||
data: {
|
||||
name: name,
|
||||
email: email,
|
||||
password: password,
|
||||
username: username,
|
||||
password_confirmation: passwordConfirm,
|
||||
challenge: challenge
|
||||
name: attrs.accountName,
|
||||
email: attrs.accountEmail,
|
||||
password: attrs.accountPassword,
|
||||
username: attrs.accountUsername,
|
||||
password_confirmation: attrs.accountPasswordConfirm,
|
||||
challenge: attrs.accountChallenge,
|
||||
user_fields: attrs.userFields
|
||||
},
|
||||
type: 'POST'
|
||||
});
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
<label>
|
||||
{{input checked=value type="checkbox"}} {{field.name}}
|
||||
</label>
|
|
@ -0,0 +1,4 @@
|
|||
<label>
|
||||
{{field.name}}
|
||||
{{input value=value}}
|
||||
</label>
|
|
@ -62,14 +62,25 @@
|
|||
{{/if}}
|
||||
|
||||
<tr class="password-confirmation">
|
||||
<td><label for='new-account-password-confirmation'>{{i18n user.password_confirmation.title}}</label></td>
|
||||
<td>
|
||||
<td><label for='new-account-password-confirmation'>{{i18n user.password_confirmation.title}}</label></td>
|
||||
<td>
|
||||
{{input type="password" value=accountPasswordConfirm id="new-account-confirmation"}}
|
||||
{{input value=accountChallenge id="new-account-challenge"}}
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
|
||||
{{#if userFields}}
|
||||
<div class='user-fields'>
|
||||
<h3>{{i18n create_account.required_information}}</h3>
|
||||
|
||||
{{#each userFields}}
|
||||
{{user-field field=field value=value}}
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
</form>
|
||||
</div>
|
||||
{{/if}}
|
|
@ -178,6 +178,11 @@
|
|||
{{#unless editHistoryVisible}}
|
||||
{{preference-checkbox labelKey="user.edit_history_public" checked=edit_history_public}}
|
||||
{{/unless}}
|
||||
|
||||
{{#each userFields}}
|
||||
{{user-field field=field value=value}}
|
||||
{{/each}}
|
||||
|
||||
{{plugin-outlet "user_custom_preferences"}}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default Discourse.ModalBodyView.extend({
|
||||
templateName: 'modal/create_account',
|
||||
templateName: 'modal/create-account',
|
||||
title: I18n.t('create_account.title'),
|
||||
classNames: ['create-account'],
|
||||
|
||||
|
|
|
@ -1331,21 +1331,14 @@ tr.not-activated {
|
|||
border-bottom: 1px solid scale-color-diff();
|
||||
|
||||
.form-display {
|
||||
width: 35%;
|
||||
width: 25%;
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.form-element {
|
||||
float: left;
|
||||
width: 35%;
|
||||
margin-right: 10px;
|
||||
label {
|
||||
margin-right: 10px;
|
||||
}
|
||||
input, div.combobox {
|
||||
margin-left: 10px;
|
||||
}
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
|
|
|
@ -10,4 +10,29 @@
|
|||
|
||||
.discourse-touch .caps-lock-warning {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.user-fields {
|
||||
|
||||
h3 {
|
||||
line-height: 1.5em;
|
||||
color: scale-color($primary, $lightness: 20%);
|
||||
border-bottom: 1px solid scale-color($primary, $lightness: 50%);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.user-field {
|
||||
label: {
|
||||
display: block;
|
||||
}
|
||||
input[type=text] {
|
||||
width: 80%;
|
||||
display: block;
|
||||
}
|
||||
input[type=checkbox] {
|
||||
margin-right: 5px;
|
||||
}
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,3 +63,4 @@
|
|||
margin: 5px 10px 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -426,4 +426,13 @@
|
|||
.suspensions {
|
||||
background-color: #c22020;
|
||||
}
|
||||
|
||||
.user-field {
|
||||
margin-left: 160px;
|
||||
margin-top: 10px;
|
||||
input[type=text] {
|
||||
width: 540px;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class Admin::UserFieldsController < Admin::AdminController
|
||||
|
||||
def create
|
||||
field = UserField.create!(params.require(:user_field).permit(:name, :field_type))
|
||||
field = UserField.create!(params.require(:user_field).permit(:name, :field_type, :editable))
|
||||
render_serialized(field, UserFieldSerializer)
|
||||
end
|
||||
|
||||
|
@ -15,7 +15,8 @@ class Admin::UserFieldsController < Admin::AdminController
|
|||
field = UserField.where(id: params.require(:id)).first
|
||||
field.name = field_params[:name]
|
||||
field.field_type = field_params[:field_type]
|
||||
field.save
|
||||
field.editable = field_params[:editable] == "true"
|
||||
field.save!
|
||||
|
||||
render_serialized(field, UserFieldSerializer)
|
||||
end
|
||||
|
|
|
@ -46,6 +46,16 @@ class UsersController < ApplicationController
|
|||
def update
|
||||
user = fetch_user_from_params
|
||||
guardian.ensure_can_edit!(user)
|
||||
|
||||
if params[:user_fields].present?
|
||||
params[:custom_fields] ||= {}
|
||||
UserField.where(editable: true).pluck(:id).each do |fid|
|
||||
val = params[:user_fields][fid.to_s]
|
||||
return render_json_error(I18n.t("login.missing_user_field")) if val.blank?
|
||||
params[:custom_fields]["user_field_#{fid}"] = val
|
||||
end
|
||||
end
|
||||
|
||||
json_result(user, serializer: UserSerializer, additional_errors: [:user_profile]) do |u|
|
||||
updater = UserUpdater.new(current_user, user)
|
||||
updater.update(params)
|
||||
|
@ -162,18 +172,34 @@ class UsersController < ApplicationController
|
|||
end
|
||||
|
||||
def create
|
||||
params.permit(:user_fields)
|
||||
|
||||
unless SiteSetting.allow_new_registrations
|
||||
render json: { success: false, message: I18n.t("login.new_registrations_disabled") }
|
||||
return
|
||||
return fail_with("login.new_registrations_disabled")
|
||||
end
|
||||
|
||||
if params[:password] && params[:password].length > User.max_password_length
|
||||
render json: { success: false, message: I18n.t("login.password_too_long") }
|
||||
return
|
||||
return fail_with("login.password_too_long")
|
||||
end
|
||||
|
||||
user = User.new(user_params)
|
||||
|
||||
# Handle custom fields
|
||||
user_field_ids = UserField.pluck(:id)
|
||||
if user_field_ids.present?
|
||||
if params[:user_fields].blank?
|
||||
return fail_with("login.missing_user_field")
|
||||
else
|
||||
fields = user.custom_fields
|
||||
user_field_ids.each do |fid|
|
||||
field_val = params[:user_fields][fid.to_s]
|
||||
return fail_with("login.missing_user_field") if field_val.blank?
|
||||
fields["user_field_#{fid}"] = field_val
|
||||
end
|
||||
user.custom_fields = fields
|
||||
end
|
||||
end
|
||||
|
||||
authentication = UserAuthenticator.new(user, session)
|
||||
|
||||
if !authentication.has_authenticator? && !SiteSetting.enable_local_logins
|
||||
|
@ -194,6 +220,7 @@ class UsersController < ApplicationController
|
|||
authentication.finish
|
||||
activation.finish
|
||||
|
||||
|
||||
render json: {
|
||||
success: true,
|
||||
active: user.active?,
|
||||
|
@ -550,4 +577,9 @@ class UsersController < ApplicationController
|
|||
:active
|
||||
).merge(ip_address: request.ip, registration_ip_address: request.ip)
|
||||
end
|
||||
|
||||
def fail_with(key)
|
||||
render json: { success: false, message: I18n.t(key) }
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -33,6 +33,10 @@ class Site
|
|||
@groups ||= Group.order(:name).map { |g| {:id => g.id, :name => g.name}}
|
||||
end
|
||||
|
||||
def user_fields
|
||||
UserField.all
|
||||
end
|
||||
|
||||
def categories
|
||||
@categories ||= begin
|
||||
categories = Category
|
||||
|
|
|
@ -657,6 +657,18 @@ class User < ActiveRecord::Base
|
|||
result.empty? ? I18n.t("user.no_accounts_associated") : result.join(", ")
|
||||
end
|
||||
|
||||
def user_fields
|
||||
return @user_fields if @user_fields
|
||||
user_field_ids = UserField.pluck(:id)
|
||||
if user_field_ids.present?
|
||||
@user_fields = {}
|
||||
user_field_ids.each do |fid|
|
||||
@user_fields[fid.to_s] = custom_fields["user_field_#{fid}"]
|
||||
end
|
||||
end
|
||||
@user_fields
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def badge_grant
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
class UserFieldSerializer < ApplicationSerializer
|
||||
attributes :id, :name, :field_type
|
||||
attributes :id, :name, :field_type, :editable
|
||||
end
|
||||
|
|
|
@ -18,7 +18,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
|||
:suspend_reason,
|
||||
:primary_group_id,
|
||||
:badge_count,
|
||||
:warnings_received_count
|
||||
:warnings_received_count,
|
||||
:user_fields
|
||||
|
||||
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
|
||||
has_one :api_key, serializer: ApiKeySerializer, embed: :objects
|
||||
|
@ -74,4 +75,12 @@ class AdminDetailedUserSerializer < AdminUserSerializer
|
|||
object.has_trust_level?(TrustLevel[2])
|
||||
end
|
||||
|
||||
def user_fields
|
||||
object.user_fields
|
||||
end
|
||||
|
||||
def include_user_fields?
|
||||
object.user_fields.present?
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ class SiteSerializer < ApplicationSerializer
|
|||
has_many :topic_flag_types, serializer: TopicFlagTypeSerializer, embed: :objects
|
||||
has_many :trust_levels, embed: :objects
|
||||
has_many :archetypes, embed: :objects, serializer: ArchetypeSerializer
|
||||
has_many :user_fields, embed: :objects, serialzer: UserFieldSerializer
|
||||
|
||||
|
||||
def default_archetype
|
||||
|
|
|
@ -46,7 +46,8 @@ class UserSerializer < BasicUserSerializer
|
|||
:notification_count,
|
||||
:has_title_badges,
|
||||
:edit_history_public,
|
||||
:custom_fields
|
||||
:custom_fields,
|
||||
:user_fields
|
||||
|
||||
has_one :invited_by, embed: :object, serializer: BasicUserSerializer
|
||||
has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer
|
||||
|
@ -253,6 +254,14 @@ class UserSerializer < BasicUserSerializer
|
|||
can_edit && !SiteSetting.edit_history_visible_to_public
|
||||
end
|
||||
|
||||
def user_fields
|
||||
object.user_fields
|
||||
end
|
||||
|
||||
def include_user_fields?
|
||||
user_fields.present?
|
||||
end
|
||||
|
||||
def custom_fields
|
||||
fields = nil
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ class UserUpdater
|
|||
|
||||
fields = attributes[:custom_fields]
|
||||
if fields.present?
|
||||
user.custom_fields = fields
|
||||
user.custom_fields = user.custom_fields.merge(fields)
|
||||
end
|
||||
|
||||
User.transaction do
|
||||
|
|
|
@ -579,6 +579,7 @@ en:
|
|||
create_account:
|
||||
title: "Create New Account"
|
||||
failed: "Something went wrong, perhaps this email is already registered, try the forgot password link"
|
||||
required_information: "Required Information"
|
||||
|
||||
forgot_password:
|
||||
title: "Forgot Password"
|
||||
|
@ -2006,6 +2007,10 @@ en:
|
|||
delete: "Delete"
|
||||
cancel: "Cancel"
|
||||
delete_confirm: "Are you sure you want to delete that user field?"
|
||||
editable:
|
||||
title: "Editable after signup?"
|
||||
enabled: "editable"
|
||||
disabled: "not editable"
|
||||
|
||||
field_types:
|
||||
text: 'Text Field'
|
||||
|
|
|
@ -1103,6 +1103,7 @@ en:
|
|||
omniauth_error_unknown: "Something went wrong processing your log in, please try again."
|
||||
new_registrations_disabled: "New account registrations are not allowed at this time."
|
||||
password_too_long: "Passwords are limited to 200 characters."
|
||||
missing_user_field: "You have not completed all the user fields"
|
||||
|
||||
user:
|
||||
no_accounts_associated: "No accounts associated"
|
||||
|
|
5
db/migrate/20140929181930_add_editable_to_user_fields.rb
Normal file
5
db/migrate/20140929181930_add_editable_to_user_fields.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class AddEditableToUserFields < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :user_fields, :editable, :boolean, default: false, null: false
|
||||
end
|
||||
end
|
|
@ -531,7 +531,6 @@ describe UsersController do
|
|||
end
|
||||
|
||||
context 'when an Exception is raised' do
|
||||
|
||||
[ ActiveRecord::StatementInvalid,
|
||||
RestClient::Forbidden ].each do |exception|
|
||||
before { User.any_instance.stubs(:save).raises(exception) }
|
||||
|
@ -545,6 +544,40 @@ describe UsersController do
|
|||
end
|
||||
end
|
||||
|
||||
context "with custom fields" do
|
||||
let!(:user_field) { Fabricate(:user_field) }
|
||||
let!(:another_field) { Fabricate(:user_field) }
|
||||
|
||||
context "without a value for the fields" do
|
||||
let(:create_params) { {name: @user.name, password: 'watwatwat', username: @user.username, email: @user.email} }
|
||||
include_examples 'failed signup'
|
||||
end
|
||||
|
||||
context "with values for the fields" do
|
||||
let(:create_params) { {
|
||||
name: @user.name,
|
||||
password: 'watwatwat',
|
||||
username: @user.username,
|
||||
email: @user.email,
|
||||
user_fields: {
|
||||
user_field.id.to_s => 'value1',
|
||||
another_field.id.to_s => 'value2',
|
||||
}
|
||||
} }
|
||||
|
||||
it "should succeed" do
|
||||
xhr :post, :create, create_params
|
||||
response.should be_success
|
||||
inserted = User.where(email: @user.email).first
|
||||
inserted.should be_present
|
||||
inserted.custom_fields.should be_present
|
||||
inserted.custom_fields["user_field_#{user_field.id}"].should == 'value1'
|
||||
inserted.custom_fields["user_field_#{another_field.id}"].should == 'value2'
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context '.username' do
|
||||
|
@ -844,12 +877,10 @@ describe UsersController do
|
|||
|
||||
context 'with authenticated user' do
|
||||
context 'with permission to update' do
|
||||
let!(:user) { log_in(:user) }
|
||||
|
||||
it 'allows the update' do
|
||||
user = Fabricate(:user, name: 'Billy Bob')
|
||||
log_in_user(user)
|
||||
|
||||
put :update, username: user.username, name: 'Jim Tom', custom_fields: {test: :it}
|
||||
|
||||
expect(response).to be_success
|
||||
|
||||
user.reload
|
||||
|
@ -858,14 +889,42 @@ describe UsersController do
|
|||
expect(user.custom_fields['test']).to eq 'it'
|
||||
end
|
||||
|
||||
it 'returns user JSON' do
|
||||
user = log_in
|
||||
context "with user fields" do
|
||||
context "an editable field" do
|
||||
let!(:user_field) { Fabricate(:user_field) }
|
||||
|
||||
it "should update the user field" do
|
||||
put :update, username: user.username, name: 'Jim Tom', user_fields: { user_field.id.to_s => 'happy' }
|
||||
expect(response).to be_success
|
||||
expect(user.user_fields[user_field.id.to_s]).to eq 'happy'
|
||||
end
|
||||
|
||||
it "cannot be updated to blank" do
|
||||
put :update, username: user.username, name: 'Jim Tom', user_fields: { user_field.id.to_s => '' }
|
||||
response.should_not be_success
|
||||
user.user_fields[user_field.id.to_s].should_not == 'happy'
|
||||
end
|
||||
end
|
||||
|
||||
context "uneditable field" do
|
||||
let!(:user_field) { Fabricate(:user_field, editable: false) }
|
||||
|
||||
it "does not update the user field" do
|
||||
put :update, username: user.username, name: 'Jim Tom', user_fields: { user_field.id.to_s => 'happy' }
|
||||
expect(response).to be_success
|
||||
expect(user.user_fields[user_field.id.to_s]).to be_blank
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
it 'returns user JSON' do
|
||||
put :update, username: user.username
|
||||
|
||||
json = JSON.parse(response.body)
|
||||
expect(json['user']['id']).to eq user.id
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
context 'without permission to update' do
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
Fabricator(:user_field) do
|
||||
name { sequence(:name) {|i| "field_#{i}" } }
|
||||
field_type 'text'
|
||||
editable true
|
||||
end
|
||||
|
|
3
test/javascripts/fixtures/site_fixtures.js.es6
Normal file
3
test/javascripts/fixtures/site_fixtures.js.es6
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,9 +1,13 @@
|
|||
/* global asyncTest */
|
||||
/* exported integration, testController, controllerFor, asyncTestDiscourse, fixture */
|
||||
function integration(name, options) {
|
||||
|
||||
import siteFixtures from 'fixtures/site_fixtures';
|
||||
|
||||
export function integration(name, options) {
|
||||
module("Integration: " + name, {
|
||||
setup: function() {
|
||||
Ember.run(Discourse, Discourse.advanceReadiness);
|
||||
|
||||
var siteJson = siteFixtures['site.json'].site;
|
||||
if (options) {
|
||||
if (options.setup) {
|
||||
options.setup.call(this);
|
||||
|
@ -16,7 +20,12 @@ function integration(name, options) {
|
|||
if (options.settings) {
|
||||
Discourse.SiteSettings = jQuery.extend(true, Discourse.SiteSettings, options.settings);
|
||||
}
|
||||
|
||||
if (options.site) {
|
||||
Discourse.Site.resetCurrent(Discourse.Site.create(jQuery.extend(true, {}, siteJson, options.site)));
|
||||
}
|
||||
}
|
||||
|
||||
Discourse.reset();
|
||||
},
|
||||
|
||||
|
@ -30,13 +39,13 @@ function integration(name, options) {
|
|||
});
|
||||
}
|
||||
|
||||
function controllerFor(controller, model) {
|
||||
export function controllerFor(controller, model) {
|
||||
controller = Discourse.__container__.lookup('controller:' + controller);
|
||||
if (model) { controller.set('model', model ); }
|
||||
return controller;
|
||||
}
|
||||
|
||||
function asyncTestDiscourse(text, func) {
|
||||
export function asyncTestDiscourse(text, func) {
|
||||
asyncTest(text, function () {
|
||||
var self = this;
|
||||
Ember.run(function () {
|
||||
|
@ -45,7 +54,7 @@ function asyncTestDiscourse(text, func) {
|
|||
});
|
||||
}
|
||||
|
||||
function fixture(selector) {
|
||||
export function fixture(selector) {
|
||||
if (selector) {
|
||||
return $("#qunit-fixture").find(selector);
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import { integration } from "helpers/qunit-helpers";
|
||||
|
||||
integration("Create Account - User Fields", {
|
||||
site: {
|
||||
user_fields: [{"id":34,"name":"I've read the terms of service","field_type":"confirm"},
|
||||
{"id":35,"name":"What is your pet's name?","field_type":"text"}]
|
||||
}
|
||||
});
|
||||
|
||||
test("create account with user fields", function() {
|
||||
visit("/");
|
||||
click("header .sign-up-button");
|
||||
|
||||
andThen(function() {
|
||||
ok(exists('.create-account'), "it shows the create account modal");
|
||||
ok(exists('.user-field'), "it has at least one user field");
|
||||
ok(exists('.modal-footer .btn-primary:disabled'), 'create account is disabled at first');
|
||||
});
|
||||
|
||||
fillIn('#new-account-name', 'Dr. Good Tuna');
|
||||
fillIn('#new-account-password', 'cool password bro');
|
||||
fillIn('#new-account-email', 'good.tuna@test.com');
|
||||
fillIn('#new-account-username', 'goodtuna');
|
||||
|
||||
andThen(function() {
|
||||
ok(exists('#username-validation.good'), 'the username validation is good');
|
||||
ok(exists('.modal-footer .btn-primary:disabled'), 'create account is still disabled due to lack of user fields');
|
||||
});
|
||||
|
||||
fillIn(".user-field input[type=text]", "Barky");
|
||||
|
||||
andThen(function() {
|
||||
ok(exists('.modal-footer .btn-primary:disabled'), 'create account is disabled because field is not checked');
|
||||
});
|
||||
|
||||
click(".user-field input[type=checkbox]");
|
||||
andThen(function() {
|
||||
not(exists('.modal-footer .btn-primary:disabled'), 'create account is disabled because field is not checked');
|
||||
});
|
||||
|
||||
click(".user-field input[type=checkbox]");
|
||||
andThen(function() {
|
||||
ok(exists('.modal-footer .btn-primary:disabled'), 'unclicking the checkbox disables the submit');
|
||||
});
|
||||
|
||||
});
|
|
@ -20,16 +20,18 @@ test('has a postStream', function() {
|
|||
equal(postStream.get('topic'), topic, "the postStream has a reference back to the topic");
|
||||
});
|
||||
|
||||
var category = _.first(Discourse.Category.list());
|
||||
|
||||
test('category relationship', function() {
|
||||
// It finds the category by id
|
||||
var topic = Discourse.Topic.create({id: 1111, category_id: category.get('id') });
|
||||
var category = Discourse.Category.list()[0],
|
||||
topic = Discourse.Topic.create({id: 1111, category_id: category.get('id') });
|
||||
|
||||
equal(topic.get('category'), category);
|
||||
});
|
||||
|
||||
test("updateFromJson", function() {
|
||||
var topic = Discourse.Topic.create({id: 1234});
|
||||
var topic = Discourse.Topic.create({id: 1234}),
|
||||
category = Discourse.Category.list()[0];
|
||||
|
||||
topic.updateFromJson({
|
||||
post_stream: [1,2,3],
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
//= require sinon-qunit-1.0.0
|
||||
//= require jshint
|
||||
|
||||
//= require helpers/qunit_helpers
|
||||
//= require helpers/qunit-helpers
|
||||
//= require helpers/assertions
|
||||
|
||||
//= require helpers/init-ember-qunit
|
||||
|
@ -50,7 +50,6 @@
|
|||
//= require_tree ./lib
|
||||
//= require_tree .
|
||||
//= require_self
|
||||
//= require jshint_all
|
||||
|
||||
// sinon settings
|
||||
sinon.config = {
|
||||
|
@ -87,6 +86,7 @@ if (window.Logster) {
|
|||
|
||||
var origDebounce = Ember.run.debounce,
|
||||
createPretendServer = require('helpers/create-pretender', null, null, false).default,
|
||||
fixtures = require('fixtures/site_fixtures', null, null, false).default,
|
||||
server;
|
||||
|
||||
QUnit.testStart(function(ctx) {
|
||||
|
@ -97,6 +97,7 @@ QUnit.testStart(function(ctx) {
|
|||
Discourse.BaseUri = "/";
|
||||
Discourse.BaseUrl = "";
|
||||
Discourse.User.resetCurrent();
|
||||
Discourse.Site.resetCurrent(Discourse.Site.create(fixtures['site.json'].site));
|
||||
PreloadStore.reset();
|
||||
|
||||
window.sandbox = sinon.sandbox.create();
|
||||
|
@ -121,6 +122,15 @@ QUnit.testDone(function() {
|
|||
});
|
||||
|
||||
// Load ES6 tests
|
||||
var helpers = require("helpers/qunit-helpers");
|
||||
|
||||
// TODO: Replace with proper imports rather than globals
|
||||
window.asyncTestDiscourse = helpers.asyncTestDiscourse;
|
||||
window.controllerFor = helpers.controllerFor;
|
||||
window.fixture = helpers.fixture;
|
||||
window.integration = helpers.integration;
|
||||
|
||||
|
||||
Ember.keys(requirejs.entries).forEach(function(entry) {
|
||||
if ((/\-test/).test(entry)) {
|
||||
require(entry, null, null, true);
|
||||
|
|
Loading…
Add table
Reference in a new issue