diff --git a/app/assets/javascripts/admin/controllers/admin_user_controller.js b/app/assets/javascripts/admin/controllers/admin_user_controller.js
index c90516fc4fd..c0674fd6d04 100644
--- a/app/assets/javascripts/admin/controllers/admin_user_controller.js
+++ b/app/assets/javascripts/admin/controllers/admin_user_controller.js
@@ -24,6 +24,8 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({
return Discourse.SiteSettings.must_approve_users;
}.property(),
+ primaryGroupDirty: Discourse.computed.propertyNotEqual('originalPrimaryGroupId', 'primary_group_id'),
+
actions: {
toggleTitleEdit: function() {
this.toggleProperty('editingTitle');
@@ -44,6 +46,22 @@ Discourse.AdminUserIndexController = Discourse.ObjectController.extend({
this.get('model').generateApiKey();
},
+ savePrimaryGroup: function() {
+ var self = this;
+ Discourse.ajax("/admin/users/" + this.get('id') + "/primary_group", {
+ type: 'PUT',
+ data: {primary_group_id: this.get('primary_group_id')}
+ }).then(function () {
+ self.set('originalPrimaryGroupId', self.get('primary_group_id'));
+ }).catch(function() {
+ bootbox.alert(I18n.t('generic_error'));
+ });
+ },
+
+ resetPrimaryGroup: function() {
+ this.set('primary_group_id', this.get('originalPrimaryGroupId'));
+ },
+
regenerateApiKey: function() {
var self = this;
bootbox.confirm(I18n.t("admin.api.confirm_regen"), I18n.t("no_value"), I18n.t("yes_value"), function(result) {
diff --git a/app/assets/javascripts/admin/routes/admin_user_route.js b/app/assets/javascripts/admin/routes/admin_user_route.js
index 4a2086c7db4..f3e74174c03 100644
--- a/app/assets/javascripts/admin/routes/admin_user_route.js
+++ b/app/assets/javascripts/admin/routes/admin_user_route.js
@@ -23,10 +23,16 @@ Discourse.AdminUserRoute = Discourse.Route.extend({
afterModel: function(adminUser) {
var controller = this.controllerFor('adminUser');
- adminUser.loadDetails().then(function () {
+ return adminUser.loadDetails().then(function () {
adminUser.setOriginalTrustLevel();
controller.set('model', adminUser);
- window.scrollTo(0, 0);
+ });
+ },
+
+ setupController: function(controller, model) {
+ controller.setProperties({
+ originalPrimaryGroupId: model.get('primary_group_id'),
+ model: model
});
},
diff --git a/app/assets/javascripts/admin/templates/user_index.js.handlebars b/app/assets/javascripts/admin/templates/user_index.js.handlebars
index a764d4cf322..37383757ec9 100644
--- a/app/assets/javascripts/admin/templates/user_index.js.handlebars
+++ b/app/assets/javascripts/admin/templates/user_index.js.handlebars
@@ -46,6 +46,25 @@
+
+
{{i18n admin.groups.primary}}
+
+ {{#if custom_groups}}
+ {{combobox content=custom_groups value=primary_group_id nameProperty="name" none="admin.groups.no_primary"}}
+ {{else}}
+ —
+ {{/if}}
+
+
+ {{#if primaryGroupDirty}}
+
+
+
+
+ {{/if}}
+
+
+
{{i18n user.ip_address.title}}
{{ip_address}}
@@ -317,7 +336,7 @@
-
{{else}}
diff --git a/app/assets/javascripts/discourse/views/post_view.js b/app/assets/javascripts/discourse/views/post_view.js
index 3181db43cd0..dd3d70db3a6 100644
--- a/app/assets/javascripts/discourse/views/post_view.js
+++ b/app/assets/javascripts/discourse/views/post_view.js
@@ -12,13 +12,21 @@ Discourse.PostView = Discourse.GroupedView.extend(Ember.Evented, {
classNameBindings: ['postTypeClass',
'selected',
'post.hidden:post-hidden',
- 'post.deleted'],
+ 'post.deleted',
+ 'groupNameClass'],
postBinding: 'content',
postTypeClass: function() {
return this.get('post.post_type') === Discourse.Site.currentProp('post_types.moderator_action') ? 'moderator' : 'regular';
}.property('post.post_type'),
+ groupNameClass: function() {
+ var primaryGroupName = this.get('post.primary_group_name');
+ if (primaryGroupName) {
+ return "group-" + primaryGroupName;
+ }
+ }.property('post.primary_group_name'),
+
// If the cooked content changed, add the quote controls
cookedChanged: function() {
var postView = this;
diff --git a/app/assets/stylesheets/desktop/topic-post.scss b/app/assets/stylesheets/desktop/topic-post.scss
index 01684918b00..989efdc0d63 100644
--- a/app/assets/stylesheets/desktop/topic-post.scss
+++ b/app/assets/stylesheets/desktop/topic-post.scss
@@ -461,17 +461,26 @@ iframe {
width: 45px;
height: 45px;
}
-
- .contents {
+
+ .contents {
text-align: center;
- a {
- display: block;
+ a {
+ display: block;
margin: 0 auto;
width: 45px;
}
+ a.user-group {
+ margin: 4px 0 0 0;
+ padding: 0px;
+ color: $primary_light;
+ font-size: 80%;
+ width: 100%;
+ line-height: 13px;
+ }
+
h3 a {
display: inline;
width: auto;
@@ -614,6 +623,7 @@ position: relative;
}
}
+
.user-title {
margin-top: 8px;
color: $primary_light;
diff --git a/app/assets/stylesheets/mobile/topic-post.scss b/app/assets/stylesheets/mobile/topic-post.scss
index e4ccdfdf970..70dec9c7ccd 100644
--- a/app/assets/stylesheets/mobile/topic-post.scss
+++ b/app/assets/stylesheets/mobile/topic-post.scss
@@ -442,6 +442,7 @@ iframe {
float: left;
}
+
.user-title {
color: #aaa;
padding-top: 2px;
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 27aec1e007d..b7abad65b06 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -17,6 +17,7 @@ class Admin::UsersController < Admin::AdminController
:block,
:unblock,
:trust_level,
+ :primary_group,
:generate_api_key,
:revoke_api_key]
@@ -94,6 +95,13 @@ class Admin::UsersController < Admin::AdminController
render_serialized(@user, AdminUserSerializer)
end
+ def primary_group
+ guardian.ensure_can_change_primary_group!(@user)
+ @user.primary_group_id = params[:primary_group_id]
+ @user.save!
+ render nothing: true
+ end
+
def trust_level
guardian.ensure_can_change_trust_level!(@user)
logger = StaffActionLogger.new(current_user)
diff --git a/app/models/group.rb b/app/models/group.rb
index ef2c3060c0f..d2f6cfb0d0e 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -220,6 +220,7 @@ class Group < ActiveRecord::Base
if @deletions
@deletions.each do |gu|
gu.destroy
+ User.update_all 'primary_group_id = NULL', ['id = ? AND primary_group_id = ?', gu.user_id, gu.group_id]
end
end
@deletions = nil
diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb
index 975c92ea6f7..685c34c1c45 100644
--- a/app/serializers/admin_detailed_user_serializer.rb
+++ b/app/serializers/admin_detailed_user_serializer.rb
@@ -14,12 +14,14 @@ class AdminDetailedUserSerializer < AdminUserSerializer
:private_topics_count,
:can_delete_all_posts,
:can_be_deleted,
- :suspend_reason
+ :suspend_reason,
+ :primary_group_id
has_one :approved_by, serializer: BasicUserSerializer, embed: :objects
has_one :api_key, serializer: ApiKeySerializer, embed: :objects
has_one :suspended_by, serializer: BasicUserSerializer, embed: :objects
has_one :leader_requirements, serializer: LeaderRequirementsSerializer, embed: :objects
+ has_many :custom_groups, embed: :object, serializer: BasicGroupSerializer
def can_revoke_admin
scope.can_revoke_admin?(object)
diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb
index 5f6253afaf9..9a334d1b66b 100644
--- a/app/serializers/post_serializer.rb
+++ b/app/serializers/post_serializer.rb
@@ -23,6 +23,7 @@ class PostSerializer < BasicPostSerializer
:topic_slug,
:topic_id,
:display_username,
+ :primary_group_name,
:version,
:can_edit,
:can_delete,
@@ -75,6 +76,11 @@ class PostSerializer < BasicPostSerializer
object.user.try(:name)
end
+ def primary_group_name
+ return nil unless object.user
+ return @topic_view.primary_group_names[object.user.primary_group_id] if object.user.primary_group_id
+ end
+
def link_counts
return @single_post_link_counts if @single_post_link_counts.present?
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index a2261108a13..3b818c733d8 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -1267,6 +1267,8 @@ en:
other: "spam x{{count}}"
groups:
+ primary: "Primary Group"
+ no_primary: "(no primary group)"
title: "Groups"
edit: "Edit Groups"
selector_placeholder: "add users"
diff --git a/config/routes.rb b/config/routes.rb
index e19f563f4c2..4235cfbb32f 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -58,6 +58,7 @@ Discourse::Application.routes.draw do
put "block"
put "unblock"
put "trust_level"
+ put "primary_group"
get "leader_requirements"
end
diff --git a/db/migrate/20140210194146_add_primary_group_id_to_users.rb b/db/migrate/20140210194146_add_primary_group_id_to_users.rb
new file mode 100644
index 00000000000..84d328ff2c5
--- /dev/null
+++ b/db/migrate/20140210194146_add_primary_group_id_to_users.rb
@@ -0,0 +1,5 @@
+class AddPrimaryGroupIdToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :primary_group_id, :integer, null: true
+ end
+end
diff --git a/lib/guardian.rb b/lib/guardian.rb
index 4ebe2804bf1..8867fd7232d 100644
--- a/lib/guardian.rb
+++ b/lib/guardian.rb
@@ -133,6 +133,10 @@ class Guardian
user && is_staff?
end
+ def can_change_primary_group?(user)
+ user && is_staff?
+ end
+
def can_change_trust_level?(user)
user && is_staff?
end
diff --git a/lib/topic_view.rb b/lib/topic_view.rb
index cfccb8c6649..1cbec56ab5b 100644
--- a/lib/topic_view.rb
+++ b/lib/topic_view.rb
@@ -111,6 +111,22 @@ class TopicView
filter_posts_paged(opts[:page].to_i)
end
+ def primary_group_names
+ return @group_names if @group_names
+
+ primary_group_ids = Set.new
+ @posts.each do |p|
+ primary_group_ids << p.user.primary_group_id if p.user.try(:primary_group_id)
+ end
+
+ result = {}
+ unless primary_group_ids.empty?
+ Group.where(id: primary_group_ids.to_a).pluck(:id, :name).each do |g|
+ result[g[0]] = g[1]
+ end
+ end
+ result
+ end
# Find the sort order for a post in the topic
def sort_order_for_post_number(post_number)
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 358aefbe2ba..cf9aa4423eb 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -140,6 +140,29 @@ describe Admin::UsersController do
end
end
+ context '.primary_group' do
+ before do
+ @another_user = Fabricate(:coding_horror)
+ end
+
+ it "raises an error when the user doesn't have permission" do
+ Guardian.any_instance.expects(:can_change_primary_group?).with(@another_user).returns(false)
+ xhr :put, :primary_group, user_id: @another_user.id
+ response.should be_forbidden
+ end
+
+ it "returns a 404 if the user doesn't exist" do
+ xhr :put, :primary_group, user_id: 123123
+ response.should be_forbidden
+ end
+
+ it "chagnes the user's trust level" do
+ xhr :put, :primary_group, user_id: @another_user.id, primary_group_id: 2
+ @another_user.reload
+ @another_user.primary_group_id.should == 2
+ end
+ end
+
context '.trust_level' do
before do
@another_user = Fabricate(:coding_horror)
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 7d09fdbc0bb..8a6b2e4251e 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1050,4 +1050,35 @@ describe User do
end
end
+ describe "primary_group_id" do
+ let!(:user) { Fabricate(:user) }
+
+ it "has no primary_group_id by default" do
+ user.primary_group_id.should be_nil
+ end
+
+ context "when the user has a group" do
+ let!(:group) { Fabricate(:group) }
+
+ before do
+ group.usernames = user.username
+ group.save
+ user.primary_group_id = group.id
+ user.save
+ user.reload
+ end
+
+ it "should allow us to use it as a primary group" do
+ user.primary_group_id.should == group.id
+
+ # If we remove the user from the group
+ group.usernames = ""
+ group.save
+
+ # It should unset it from the primary_group_id
+ user.reload
+ user.primary_group_id.should be_nil
+ end
+ end
+ end
end