[Git][noosfero/noosfero][master] 2 commits: Add custom field feature to core

Joenio Costa gitlab at mg.gitlab.com
Wed Nov 18 22:53:49 BRST 2015


Joenio Costa pushed to branch master at Noosfero / noosfero


Commits:
7be7e7c6 by Marcos Ronaldo at 2015-11-18T16:36:42Z
Add custom field feature to core

Custom fields can be added to any profile through the admin panel
in the 'Fields' section. They have the same behaviour as the current
Noosfero's fields (active, signup, required and privacy).

Signed-off-by: Fabio Teixeira <fabio1079 at gmail.com>
Signed-off-by: Gustavo Coelho <gust.rod.coelho at gmail.com>
Signed-off-by: Joenio Costa <joenio at colivre.coop.br>
Signed-off-by: Macartur de Sousa <macartur.sc at gmail.com>
Signed-off-by: Marcos Ramos <ms.ramos at outlook.com>
Signed-off-by: Marcos Ronaldo <marcos.rpj2 at gmail.com>
Signed-off-by: Pedro de Lyra <pedrodelyra at gmail.com>
Signed-off-by: Tallys Martins <tallysmartins at gmail.com>

- - - - -
84ca7256 by Joenio Costa at 2015-11-19T00:53:28Z
Merge branch 'customfields-rails4' into 'master'

Add custom field feature to core

Custom fields can be added to any profile through the admin panel
in the 'Fields' section. They have the same behavior as the current
Noosfero's fields (active, signup, required and privacy).

See merge request !730
- - - - -


52 changed files:

- app/controllers/admin/features_controller.rb
- app/controllers/my_profile/memberships_controller.rb
- app/controllers/my_profile/profile_editor_controller.rb
- app/controllers/public/account_controller.rb
- app/controllers/public/profile_controller.rb
- + app/helpers/custom_fields_helper.rb
- app/helpers/forms_helper.rb
- app/models/community.rb
- + app/models/custom_field.rb
- + app/models/custom_field_value.rb
- app/models/environment.rb
- app/models/profile.rb
- app/views/account/_signup_form.html.erb
- + app/views/custom_fields/_checkbox.html.erb
- + app/views/custom_fields/_date.html.erb
- + app/views/custom_fields/_link.html.erb
- + app/views/custom_fields/_list.html.erb
- + app/views/custom_fields/_numeric.html.erb
- + app/views/custom_fields/_string.html.erb
- + app/views/custom_fields/_text.html.erb
- app/views/features/_manage_community_fields.html.erb
- + app/views/features/_manage_custom_fields.html.erb
- app/views/features/_manage_enterprise_fields.html.erb
- app/views/features/_manage_person_fields.html.erb
- + app/views/features/custom_fields/_extras_field.html.erb
- + app/views/features/custom_fields/_form.html.erb
- + app/views/features/custom_fields/_view.html.erb
- + app/views/features/custom_fields/edit.html.erb
- + app/views/features/custom_fields/new.html.erb
- app/views/memberships/new_community.html.erb
- + app/views/profile/_custom_fields.html.erb
- app/views/profile/_organization_profile.html.erb
- app/views/profile/_person_profile.html.erb
- app/views/profile_editor/_organization.html.erb
- app/views/profile_editor/_person.html.erb
- + app/views/shared/_custom_fields.html.erb
- app/views/shared/_organization_custom_fields.html.erb
- config/initializers/dependencies.rb
- + db/migrate/20150921140802_create_custom_fields.rb
- + lib/acts_as_customizable.rb
- lib/noosfero/api/entities.rb
- lib/noosfero/api/v1/comments.rb
- lib/noosfero/api/v1/communities.rb
- lib/noosfero/api/v1/enterprises.rb
- lib/noosfero/api/v1/people.rb
- public/javascripts/manage-fields.js
- public/stylesheets/manage-fields.scss
- test/functional/features_controller_test.rb
- + test/unit/acts_as_customizable_test.rb
- test/unit/api/people_test.rb
- + test/unit/custom_field_test.rb
- + test/unit/custom_field_values_test.rb


Changes:

=====================================
app/controllers/admin/features_controller.rb
=====================================
--- a/app/controllers/admin/features_controller.rb
+++ b/app/controllers/admin/features_controller.rb
@@ -1,5 +1,6 @@
 class FeaturesController < AdminController
   protect 'edit_environment_features', :environment
+  helper CustomFieldsHelper
 
   def index
     @features = Environment.available_features.sort_by{|k,v|v}
@@ -51,6 +52,34 @@ class FeaturesController < AdminController
     redirect_to :action => 'manage_fields'
   end
 
+  def manage_custom_fields
+    custom_field_list = params[:custom_fields] || {}
+
+    custom_fields_to_destroy =
+      params[:customized_type].constantize.custom_fields(environment).map(&:id) - custom_field_list.keys.map(&:to_i)
+    CustomField.destroy(custom_fields_to_destroy)
+
+    custom_field_list.each_pair do |id, custom_field|
+      field = CustomField.find_by_id(id)
+      if not field.blank?
+        params_to_update = custom_field.except(:format, :extras, :customized_type,:environment)
+        field.update_attributes(params_to_update)
+      else
+        if !custom_field[:extras].nil?
+          tmp = []
+          custom_field[:extras].each_pair do |k, v|
+            tmp << v
+          end
+          custom_field[:extras] = tmp
+        end
+        field =  CustomField.new custom_field.except(:environment)
+        field.environment=environment
+        field.save if field.valid?
+      end
+    end
+    redirect_to :action => 'manage_fields'
+  end
+
   def search_members
     arg = params[:q].downcase
     result = environment.people.where('LOWER(name) LIKE ? OR identifier LIKE ?', "%#{arg}%", "%#{arg}%")


=====================================
app/controllers/my_profile/memberships_controller.rb
=====================================
--- a/app/controllers/my_profile/memberships_controller.rb
+++ b/app/controllers/my_profile/memberships_controller.rb
@@ -1,6 +1,7 @@
 class MembershipsController < MyProfileController
 
   protect 'manage_memberships', :profile
+  helper CustomFieldsHelper
 
   def index
     @roles = environment.roles.select do |role|


=====================================
app/controllers/my_profile/profile_editor_controller.rb
=====================================
--- a/app/controllers/my_profile/profile_editor_controller.rb
+++ b/app/controllers/my_profile/profile_editor_controller.rb
@@ -8,6 +8,7 @@ class ProfileEditorController < MyProfileController
   before_filter :forbid_destroy_profile, :only => [:destroy_profile]
   before_filter :check_user_can_edit_header_footer, :only => [:header_footer]
   helper_method :has_welcome_page
+  helper CustomFieldsHelper
 
   def index
     @pending_tasks = Task.to(profile).pending.without_spam.select{|i| user.has_permission?(i.permission, profile)}


=====================================
app/controllers/public/account_controller.rb
=====================================
--- a/app/controllers/public/account_controller.rb
+++ b/app/controllers/public/account_controller.rb
@@ -6,6 +6,7 @@ class AccountController < ApplicationController
   before_filter :redirect_if_logged_in, :only => [:login, :signup]
   before_filter :protect_from_bots, :only => :signup
 
+  helper CustomFieldsHelper
   # say something nice, you goof!  something sweet.
   def index
     unless logged_in?


=====================================
app/controllers/public/profile_controller.rb
=====================================
--- a/app/controllers/public/profile_controller.rb
+++ b/app/controllers/public/profile_controller.rb
@@ -7,6 +7,7 @@ class ProfileController < PublicController
 
   helper TagsHelper
   helper ActionTrackerHelper
+  helper CustomFieldsHelper
 
   protect 'send_mail_to_members', :profile, :only => [:send_mail]
 


=====================================
app/helpers/custom_fields_helper.rb
=====================================
--- /dev/null
+++ b/app/helpers/custom_fields_helper.rb
@@ -0,0 +1,58 @@
+module CustomFieldsHelper
+
+  def format_name(format)
+    names = {}
+    names['string'] = _('String')
+    names['text'] = _('Text')
+    names['date'] = _('Date')
+    names['numeric'] = _('Numeric')
+    names['link'] = _('Link')
+    names['list'] = _('List')
+    names['checkbox'] = _('Checkbox')
+    names[format]
+  end
+
+  def custom_field_forms(customized_type)
+    forms = []
+    forms << [_('String'), form_for_format(customized_type,'string')]
+    forms << [_('Text'), form_for_format(customized_type,'text')]
+    forms << [_('Date'), form_for_format(customized_type,'date')]
+    forms << [_('Numeric'), form_for_format(customized_type,'numeric')]
+    forms << [_('Link'), form_for_format(customized_type,'link')]
+    forms << [_('List'), form_for_format(customized_type,'list')]
+    forms << [_('Checkbox'), form_for_format(customized_type,'checkbox')]
+    forms
+  end
+
+  def render_extras_field(id, extra=nil, field=nil)
+    if extra.nil?
+      CGI::escapeHTML((render(:partial => 'features/custom_fields/extras_field', :locals => {:id => id, :extra => nil, :field => field})))
+    else
+      render :partial => 'features/custom_fields/extras_field', :locals => {:id => id, :extra => extra, :field => field}
+    end
+  end
+
+  def form_for_field(field, customized_type)
+    render :partial => 'features/custom_fields/form', :locals => {:field => field}
+  end
+
+  def display_custom_field_value(custom_field_value)
+    value = profile.custom_value(custom_field_value.custom_field.name)
+    case custom_field_value.custom_field.format
+    when 'text', 'list', 'numeric', 'date', 'string'
+      value
+    when 'checkbox'
+      value == "1" ? _('Yes') : _('No')
+    when 'link'
+      url = value[/\Ahttps?:\/\//i] ? value : "http://#{value}"
+      link_to(value, url, :target => '_blank')
+    end
+  end
+
+  private
+
+  def form_for_format(customized_type, format)
+    field = CustomField.new(:format => format, :customized_type => customized_type, :environment => environment)
+    CGI::escapeHTML((render(:partial => 'features/custom_fields/form', :locals => {:field => field})))
+  end
+end


=====================================
app/helpers/forms_helper.rb
=====================================
--- a/app/helpers/forms_helper.rb
+++ b/app/helpers/forms_helper.rb
@@ -186,6 +186,7 @@ module FormsHelper
     element_id = html_options[:id] || 'datepicker-date'
     value = value.strftime(format) if value.present?
     method = datepicker_options[:time] ? 'datetimepicker' : 'datepicker'
+    current_date_or_nil = value.present? ? "new Date('#{value}')" : "null"
     result = text_field_tag(name, value, html_options)
     result +=
     "
@@ -236,7 +237,7 @@ module FormsHelper
         weekHeader: #{datepicker_options[:week_header].to_json},
         yearRange: #{datepicker_options[:year_range].to_json},
         yearSuffix: #{datepicker_options[:year_suffix].to_json}
-      }).datepicker('setDate', new Date('#{value}'))
+      }).datepicker('setDate', current_date_or_nil)
     </script>
     ".html_safe
     result


=====================================
app/models/community.rb
=====================================
--- a/app/models/community.rb
+++ b/app/models/community.rb
@@ -29,7 +29,7 @@ class Community < Organization
   # places that call this method are safe from mass-assignment by setting the
   # environment key themselves.
   def self.create_after_moderation(requestor, attributes = {})
-    environment = attributes.delete(:environment)
+    environment = attributes[:environment]
     community = Community.new(attributes)
     community.environment = environment
     if community.environment.enabled?('admin_must_approve_new_communities')


=====================================
app/models/custom_field.rb
=====================================
--- /dev/null
+++ b/app/models/custom_field.rb
@@ -0,0 +1,34 @@
+class CustomField < ActiveRecord::Base
+  attr_accessible :name, :default_value, :format, :extras, :customized_type, :active, :required, :signup, :environment
+  serialize :customized_type
+  serialize :extras
+  has_many :custom_field_values, :dependent => :delete_all
+  belongs_to :environment
+
+  validates_presence_of :name, :format, :customized_type, :environment
+  validate :related_to_other?
+  validate :unique?
+
+  def unique?
+    if environment.custom_fields.any?{|cf| cf.name==name && cf.environment == environment && cf.customized_type==customized_type && new_record?}
+      errors.add(:body, N_("There is a field with the same name for this type in this environment"))
+      return false
+    end
+    true
+  end
+
+  def related_to_other?
+    environment.custom_fields.any? do |cf|
+      if cf.name == name && cf.customized_type != customized_type
+        ancestor = cf.customized_type.constantize < customized_type.constantize
+        descendant = cf.customized_type.constantize > customized_type.constantize
+        if ancestor || descendant
+          errors.add(:body, N_("New field related to existent one with same name"))
+          return false
+        end
+      end
+    end
+    true
+  end
+end
+


=====================================
app/models/custom_field_value.rb
=====================================
--- /dev/null
+++ b/app/models/custom_field_value.rb
@@ -0,0 +1,14 @@
+class CustomFieldValue < ActiveRecord::Base
+  belongs_to :custom_field
+  belongs_to :customized, :polymorphic => true
+  attr_accessible :value, :public, :customized, :custom_field, :customized_type
+  validate :can_save?
+
+  def can_save?
+    if value.blank? && custom_field.required
+      errors.add(custom_field.name, _("can't be blank"))
+      return false
+    end
+    return true
+  end
+end


=====================================
app/models/environment.rb
=====================================
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -24,6 +24,7 @@ class Environment < ActiveRecord::Base
 
   has_many :tasks, :dependent => :destroy, :as => 'target'
   has_many :search_terms, :as => :context
+  has_many :custom_fields, :dependent => :destroy
 
   IDENTIFY_SCRIPTS = /(php[0-9s]?|[sp]htm[l]?|pl|py|cgi|rb)/
 


=====================================
app/models/profile.rb
=====================================
--- a/app/models/profile.rb
+++ b/app/models/profile.rb
@@ -84,6 +84,7 @@ class Profile < ActiveRecord::Base
   }
 
   acts_as_accessible
+  acts_as_customizable
 
   include Noosfero::Plugin::HotSpot
 


=====================================
app/views/account/_signup_form.html.erb
=====================================
--- a/app/views/account/_signup_form.html.erb
+++ b/app/views/account/_signup_form.html.erb
@@ -126,6 +126,7 @@
 </div>
 
 <%= recaptcha_tags :ajax => true, :display => {:theme => 'clean'} if @block_bot %>
+<%= render :partial => 'shared/custom_fields', :locals => {:f => f, :profile => @person, :signup => true} %>
 
 <p style="text-align: center">
   <%= submit_button('save', _('Create my account')) %>


=====================================
app/views/custom_fields/_checkbox.html.erb
=====================================
--- /dev/null
+++ b/app/views/custom_fields/_checkbox.html.erb
@@ -0,0 +1 @@
+<%= labelled_check_box(field.name, name, 1, profile.custom_value(field.name) == '1' )%>


=====================================
app/views/custom_fields/_date.html.erb
=====================================
--- /dev/null
+++ b/app/views/custom_fields/_date.html.erb
@@ -0,0 +1 @@
+<%= labelled_form_field(field.name, date_field(name, profile.custom_value(field.name).to_date, '%Y-%m-%d', {:change_month => true, :change_year => true, :year_range => '-100:-5', :date_format => 'yy-mm-dd'}, {:id => field.name.parameterize.underscore}))%>


=====================================
app/views/custom_fields/_link.html.erb
=====================================
--- /dev/null
+++ b/app/views/custom_fields/_link.html.erb
@@ -0,0 +1 @@
+<%= render :partial => "custom_fields/string", :locals => {:field => field, :profile => profile, :name => name} %>


=====================================
app/views/custom_fields/_list.html.erb
=====================================
--- /dev/null
+++ b/app/views/custom_fields/_list.html.erb
@@ -0,0 +1,2 @@
+<%= label_tag field.name, nil, class: 'formlabel'%>
+<%= select_tag name, options_for_select(field.extras.map{|v| [v,v]}, profile.custom_value(field.name)), {:prompt => _('[Select ...]') } %>


=====================================
app/views/custom_fields/_numeric.html.erb
=====================================
--- /dev/null
+++ b/app/views/custom_fields/_numeric.html.erb
@@ -0,0 +1 @@
+<%= labelled_form_field field.name, number_field_tag(name,profile.custom_value(field.name))%>


=====================================
app/views/custom_fields/_string.html.erb
=====================================
--- /dev/null
+++ b/app/views/custom_fields/_string.html.erb
@@ -0,0 +1 @@
+<%= labelled_form_field field.name, text_field_tag(name, profile.custom_value(field.name), :size => 30) %>


=====================================
app/views/custom_fields/_text.html.erb
=====================================
--- /dev/null
+++ b/app/views/custom_fields/_text.html.erb
@@ -0,0 +1 @@
+<%= labelled_form_field field.name, text_area_tag(name,profile.custom_value(field.name), :rows => 5, :cols => 40)%>


=====================================
app/views/features/_manage_community_fields.html.erb
=====================================
--- a/app/views/features/_manage_community_fields.html.erb
+++ b/app/views/features/_manage_community_fields.html.erb
@@ -63,6 +63,4 @@
 
 <% end %>
 
-
-
-
+<%= render :partial => "manage_custom_fields", :locals => {:customized_type => Community} %>


=====================================
app/views/features/_manage_custom_fields.html.erb
=====================================
--- /dev/null
+++ b/app/views/features/_manage_custom_fields.html.erb
@@ -0,0 +1,29 @@
+<h1><%= _("Custom Fields") %></h1><hr>
+
+<% form_id = "#{customized_type.to_s.downcase}-custom-fields-form" %>
+<% fields_id = "#{customized_type.to_s.downcase}-custom-fields" %>
+<% format_values_id = "#{customized_type.to_s.downcase}-formats" %>
+
+<%= form_tag({:action => 'manage_custom_fields'}, :id => "#{form_id}")  do %>
+
+<%= hidden_field_tag 'customized_type', customized_type.to_s %>
+
+<div id="<%= fields_id %>">
+  <% customized_type.custom_fields(environment).each do |field|%>
+    <%= form_for_field(field, customized_type.to_s) %>
+  <% end %>
+</div>
+
+<div class="addition-buttons">
+  <fieldset>
+    <legend><%= _('New field') %></legend>
+      <%= select_tag _('Type: '), options_for_select(custom_field_forms(customized_type.to_s)), {:id => "#{format_values_id}"} %>
+      <%= button(:add, _('Add'), 'javascript: void()', :onClick => "add_content('##{fields_id}',$('##{format_values_id} option:selected').val(), 'NEW_FIELD_ID');") %>
+  </fieldset>
+</div>
+
+<% button_bar do %>
+  <%= button(:save, _('Save'), 'javascript: void()', :onClick => "submit_custom_field_form('##{format_values_id}', '##{form_id}');") %>
+<% end %>
+
+<% end %>


=====================================
app/views/features/_manage_enterprise_fields.html.erb
=====================================
--- a/app/views/features/_manage_enterprise_fields.html.erb
+++ b/app/views/features/_manage_enterprise_fields.html.erb
@@ -63,6 +63,4 @@
 
 <% end %>
 
-
-
-
+<%= render :partial => "manage_custom_fields", :locals => {:customized_type => Enterprise} %>


=====================================
app/views/features/_manage_person_fields.html.erb
=====================================
--- a/app/views/features/_manage_person_fields.html.erb
+++ b/app/views/features/_manage_person_fields.html.erb
@@ -60,9 +60,7 @@
     <%= button :back, _('Back to admin panel'), :controller => 'admin_panel', :action => 'index' %>
   <% end %>
 </div>
-
+<br>
 <% end %>
 
-
-
-
+<%= render :partial => "manage_custom_fields", :locals => {:customized_type => Person} %>


=====================================
app/views/features/custom_fields/_extras_field.html.erb
=====================================
--- /dev/null
+++ b/app/views/features/custom_fields/_extras_field.html.erb
@@ -0,0 +1,15 @@
+<% field_id = extra.nil? ? 'EXTRAS_ID' : "#{Time.now.usec}" %>
+<% def_value = field.nil? ? '' : field.default_value%>
+<tr id='<%= "extras_#{field_id}" %>' >
+  <td>
+    <%= text_field_tag "custom_fields[#{id}][extras][#{field_id}]", (extra.nil? ? '' : extra), :onkeyup => "update_default_value($(this).val(), '#custom_fields_#{id}_extras_#{field_id}_default')" %>
+  </td>
+  <td>
+    <%= check_box_tag "custom_fields[#{id}][default_value]", extra.blank? ? '' : extra,  extra.blank? ? false : def_value == extra, :id => "custom_fields_#{id}_extras_#{field_id}_default", :onclick => "manage_default_option(this);" %>
+  </td>
+  <td>
+    <% if extra.nil? %>
+    <%= button_to_function_without_text :remove, _('Remove'), "remove_content('#extras_#{field_id}'); ", :class => 'remove-field', :title => 'Remove alternative' %>
+    <% end %>
+  </td>
+</tr>


=====================================
app/views/features/custom_fields/_form.html.erb
=====================================
--- /dev/null
+++ b/app/views/features/custom_fields/_form.html.erb
@@ -0,0 +1,43 @@
+<% id = field.new_record? ? "NEW_FIELD_ID" : field.id %>
+
+<div id="<%= id %>" class="custom-field-item">
+<fieldset class="fieldbox">
+  <legend><%= format_name(field.format) %></legend>
+  <%= required labelled_form_field _('Name'), text_field_tag("custom_fields[#{id}][name]", field.name, :size => 30) %>
+    <%= button_to_function :delete, _('Remove field'), "remove_content('##{id}');" %>
+
+    <% if field.format != "list" %>
+      <%= labelled_form_field  _('Default value'), text_field_tag("custom_fields[#{id}][default_value]", field.default_value, :size => 30) %>
+    <% end %>
+    <%= hidden_field_tag "custom_fields[#{id}][customized_type]", field.customized_type.to_s %>
+    <%= hidden_field_tag "custom_fields[#{id}][format]", field.format %>
+
+    <div>
+      <%= labelled_check_box _('Active'), "custom_fields[#{id}][active]", 1, field.active, :id => "active_checkbox", :onclick  => "active_action('custom_fields[#{id}][active]','custom_fields[#{id}][required]', 'custom_fields[#{id}][signup]')" %>
+      <%= labelled_check_box _('Required'), "custom_fields[#{id}][required]", 1, field.required, :id => "required_checkbox", :onclick  => "required_action('custom_fields[#{id}][active]','custom_fields[#{id}][required]', 'custom_fields[#{id}][signup]')" %>
+      <%= labelled_check_box _('Display on creation?'), "custom_fields[#{id}][signup]", 1, field.signup, :id => "signup_checkbox",:onclick  => "signup_action('custom_fields[#{id}][active]','custom_fields[#{id}][required]', 'custom_fields[#{id}][signup]')" %>
+    </div>
+
+    <% if field.format == "list" %>
+      <table>
+        <thead>
+          <tr>
+            <th><%= _("Alternative") %></th>
+            <th><%= _("Default") %></th>
+            <th><%= _("Delete") %></th>
+          </tr>
+        </thead>
+        <tfoot>
+          <tr><td colspan=3><%= button(:add, _('Add option'), 'javascript: void()', :id => "btn_opt_#{id}", :onclick => "add_content('##{id} .custom-field-extras', $('#btn_opt_#{id}').attr('value'), 'EXTRAS_ID');", :value => "#{render_extras_field(id)}") %></td></tr>
+        </tfoot>
+        <tbody class="custom-field-extras">
+          <% if !field.extras.blank?%>
+            <% field.extras.each do |extra|%>
+              <%= render_extras_field id, extra, field %>
+            <% end %>
+          <% end %>
+        </tbody>
+      </table>
+    <% end %>
+</fieldset>
+</div>


=====================================
app/views/features/custom_fields/_view.html.erb
=====================================
--- /dev/null
+++ b/app/views/features/custom_fields/_view.html.erb


=====================================
app/views/features/custom_fields/edit.html.erb
=====================================
--- /dev/null
+++ b/app/views/features/custom_fields/edit.html.erb
@@ -0,0 +1 @@
+<%= render :partial => 'features/custom_fields/form' %>


=====================================
app/views/features/custom_fields/new.html.erb
=====================================
--- /dev/null
+++ b/app/views/features/custom_fields/new.html.erb
@@ -0,0 +1 @@
+<%= render :partial => 'features/custom_fields/form' %>


=====================================
app/views/memberships/new_community.html.erb
=====================================
--- a/app/views/memberships/new_community.html.erb
+++ b/app/views/memberships/new_community.html.erb
@@ -13,7 +13,6 @@
 <%= labelled_form_for :community, :html => { :multipart => true } do |f| %>
 
   <%= required_fields_message %>
-
   <%= required f.text_field(:name) %>
 
   <% @plugins.dispatch(:new_community_hidden_fields).each do |field| %>
@@ -23,6 +22,7 @@
   <% end %>
 
   <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'community', :profile => @community } %>
+  <%= render :partial => 'shared/custom_fields', :locals => { :f => f, :profile => @community, :signup => true } %>
 
   <%= f.fields_for :image_builder, @community.image do |i| %>
     <%= file_field_or_thumbnail(_('Image:'), @community.image, i) %>
@@ -56,7 +56,3 @@
 <% end %>
 
 </div>
-
-
-
-


=====================================
app/views/profile/_custom_fields.html.erb
=====================================
--- /dev/null
+++ b/app/views/profile/_custom_fields.html.erb
@@ -0,0 +1,11 @@
+<% public_values = profile.public_values %>
+<% if !public_values.blank?%>
+  <tr> <th colspan="2"><%= _('Others') %> </th></tr>
+  <% profile.public_values.each do |cv|%>
+    <tr>
+      <td class="field-name"><%= cv.custom_field.name %></td>
+      <td><%= display_custom_field_value(cv) %></td>
+    </tr>
+  <% end%>
+<% end %>
+


=====================================
app/views/profile/_organization_profile.html.erb
=====================================
--- a/app/views/profile/_organization_profile.html.erb
+++ b/app/views/profile/_organization_profile.html.erb
@@ -3,4 +3,5 @@
   <%= display_contact %>
   <%= display_economic %>
   <%= render :partial => 'common' %>
+  <%= render :partial => 'custom_fields' %>
 </table>


=====================================
app/views/profile/_person_profile.html.erb
=====================================
--- a/app/views/profile/_person_profile.html.erb
+++ b/app/views/profile/_person_profile.html.erb
@@ -10,5 +10,6 @@
 
     <%= render :partial => 'common' %>
   <% end %>
-</table>
 
+  <%= render :partial => 'custom_fields'%>
+</table>


=====================================
app/views/profile_editor/_organization.html.erb
=====================================
--- a/app/views/profile_editor/_organization.html.erb
+++ b/app/views/profile_editor/_organization.html.erb
@@ -61,6 +61,7 @@
 <% end %>
 
 <%= render :partial => 'shared/organization_custom_fields', :locals => { :f => f, :object_name => 'profile_data', :profile => @profile } %>
+<%= render :partial => 'shared/custom_fields', :locals => {:f => f, :profile => @profile, :editing_profile => true} %>
 
 <%= labelled_check_box(_('Enable "contact us"'), 'profile_data[enable_contact_us]', "1", @profile.enable_contact_us) if @profile.enterprise? %>
 


=====================================
app/views/profile_editor/_person.html.erb
=====================================
--- a/app/views/profile_editor/_person.html.erb
+++ b/app/views/profile_editor/_person.html.erb
@@ -27,6 +27,7 @@
   <%= link_to("Reset token", {:controller => :profile_editor, :action => :reset_private_token, :id => @profile.id}, :class => "button with-text") %>
 
   <%= render :partial => 'person_form', :locals => {:f => f} %>
+  <%= render :partial => 'shared/custom_fields', :locals => {:f => f, :profile => @profile, :editing_profile => true} %>
 
   <h2><%= _('Notification options') %></h2>
   <div>


=====================================
app/views/shared/_custom_fields.html.erb
=====================================
--- /dev/null
+++ b/app/views/shared/_custom_fields.html.erb
@@ -0,0 +1,25 @@
+<% if signup ||= false %>
+  <% fields = profile.class.signup_custom_fields environment%>
+<% else %>
+  <% fields = profile.class.active_custom_fields environment%>
+<% end %>
+<% editing_profile ||= false %>
+<% fields.each do |field| %>
+  <% rendered = render(:partial => "custom_fields/#{field.format}", :locals => {:field => field, :profile => profile, :name => "profile_data[custom_values[#{field.name}[value]]]"})%>
+  <div class="<%= 'field-with-privacy-selector' if editing_profile %>">
+
+  <% if field.required%>
+    <%= required rendered%>
+  <% else %>
+    <%= rendered %>
+  <% end %>
+
+  <% if editing_profile %>
+    <div class="field-privacy-selector">
+      <%= labelled_check_box(_('Public'),"profile_data[custom_values[#{field.name}[public]]]", "true", profile.is_public(field.name))%>
+    </div>
+  <% end %>
+
+  </div>
+<% end %>
+


=====================================
app/views/shared/_organization_custom_fields.html.erb
=====================================
--- a/app/views/shared/_organization_custom_fields.html.erb
+++ b/app/views/shared/_organization_custom_fields.html.erb
@@ -29,4 +29,7 @@
   <%= optional_field(profile, 'acronym', f.text_field(:acronym)) %>
   <%= optional_field(profile, 'foundation_year', f.text_field(:foundation_year)) %>
 <% end %>
+
+<%= render :partial => 'shared/custom_fields', :locals => {:f => f, :profile => profile, :signup => true} %>
+
 <%= javascript_include_tag('city_state_validation') %>


=====================================
config/initializers/dependencies.rb
=====================================
--- a/config/initializers/dependencies.rb
+++ b/config/initializers/dependencies.rb
@@ -18,6 +18,7 @@ require 'acts_as_having_settings'
 require 'acts_as_having_boxes'
 require 'acts_as_having_image'
 require 'acts_as_having_posts'
+require 'acts_as_customizable'
 require 'route_if'
 require 'maybe_add_http'
 require 'set_profile_region_from_city_state'


=====================================
db/migrate/20150921140802_create_custom_fields.rb
=====================================
--- /dev/null
+++ b/db/migrate/20150921140802_create_custom_fields.rb
@@ -0,0 +1,31 @@
+class CreateCustomFields < ActiveRecord::Migration
+  def change
+    create_table :custom_fields do |t|
+      t.string :name
+      t.string :format, :default => ""
+      t.text :default_value, :default => ""
+      t.string :customized_type
+      t.text :extras, :default => ""
+      t.boolean :active, :default => false
+      t.boolean :required, :default => false
+      t.boolean :signup, :default => false
+      t.integer :environment_id
+      t.timestamps
+    end
+
+    create_table :custom_field_values do |t|
+      t.column "customized_type", :string, :default => "", :null => false
+      t.column "customized_id", :integer, :default => 0, :null => false
+      t.column "public", :boolean, :default => false, :null => false
+      t.column "custom_field_id", :integer, :default => 0, :null => false
+      t.column "value", :text, :default => ""
+      t.timestamps
+    end
+
+
+    add_index :custom_field_values, ["customized_type", "customized_id","custom_field_id"], :unique => true, :name => 'index_custom_field_values'
+    add_index :custom_fields, ["customized_type","name","environment_id"], :unique => true, :name => 'index_custom_field'
+
+  end
+end
+


=====================================
lib/acts_as_customizable.rb
=====================================
--- /dev/null
+++ b/lib/acts_as_customizable.rb
@@ -0,0 +1,125 @@
+module Customizable
+
+  def self.included(base)
+    base.attr_accessible :custom_values
+    base.extend ClassMethods
+  end
+
+  module ClassMethods
+    def acts_as_customizable(options = {})
+      attr_accessor :custom_values
+      has_many :custom_field_values, :dependent => :delete_all, :as => :customized
+      send :include, Customizable::InstanceMethods
+      after_save :save_custom_values
+      validate :valid_custom_values?
+    end
+
+    def active_custom_fields environment
+      environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.active}
+    end
+
+    def required_custom_fields environment
+      environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.required}
+    end
+
+    def signup_custom_fields environment
+      environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type) && cf.signup}
+    end
+
+    def custom_fields environment
+      environment.custom_fields.select{|cf| customized_ancestors_list.include?(cf.customized_type)}
+    end
+
+    def customized_ancestors_list
+      current=self
+      result=[]
+      while current.instance_methods.include? :custom_value do
+        result << current.name
+        current=current.superclass
+      end
+      result
+    end
+
+  end
+
+  module InstanceMethods
+
+    def valid_custom_values?
+      is_valid = true
+      parse_custom_values.each do |cv|
+        unless cv.valid?
+          name = cv.custom_field.name
+          errors.add(name, cv.errors.messages[name.to_sym].first)
+          is_valid = false
+        end
+      end
+      is_valid
+    end
+
+    def customized_class
+      current=self.class
+      while current.instance_methods.include? :custom_fields do
+        result=current
+        current=current.superclass
+      end
+      result.name
+    end
+
+    def is_public(field_name)
+      cv = self.custom_field_values.detect{|cv| cv.custom_field.name==field_name}
+      cv.nil? ? false : cv.public
+    end
+
+    def public_values
+      self.custom_field_values.select{|cv| cv.public}
+    end
+
+    def custom_value(field_name)
+      cv = self.custom_field_values.detect{|cv| cv.custom_field.name==field_name}
+      cv.nil? ? default_value_for(field_name) : cv.value
+    end
+
+    def default_value_for(field_name)
+      field=self.class.custom_fields(environment).detect {|c| c.name == field_name}
+      field.nil? ? nil : field.default_value
+    end
+
+    def parse_custom_values
+      return_list = []
+      return return_list if custom_values.blank?
+      custom_values.each_pair do |key, value|
+        custom_field = environment.custom_fields.detect{|cf|cf.name==key}
+        next if custom_field.blank?
+        custom_field_value = self.custom_field_values.detect{|cv| cv.custom_field.name==key}
+
+        if custom_field_value.nil?
+          custom_field_value = CustomFieldValue.new
+          custom_field_value.custom_field = custom_field
+          custom_field_value.customized = self
+        end
+
+        if value.is_a?(Hash)
+          custom_field_value.value = value['value'].to_s
+          if value.has_key?('public')
+            is_public = value['public']=="true" || value['public']==true
+            custom_field_value.public = is_public
+          else
+            custom_field_value.public = false
+          end
+        else
+          custom_field_value.value = value.to_s
+          custom_field_value.public = false
+        end
+        return_list << custom_field_value
+      end
+      return_list
+    end
+
+    def save_custom_values
+      parse_custom_values.each(&:save)
+    end
+
+  end
+end
+
+ActiveRecord::Base.send(:include, Customizable)


=====================================
lib/noosfero/api/entities.rb
=====================================
--- a/lib/noosfero/api/entities.rb
+++ b/lib/noosfero/api/entities.rb
@@ -15,7 +15,7 @@ module Noosfero
       }
 
       def self.can_display? profile, options, field, permission = :friend
-        return true if profile.public_fields.include?(field)
+        return true if profile.public_fields.map{|f| f.to_sym}.include?(field.to_sym)
         current_person = options[:current_person]
 
         current_permission = if current_person.present?
@@ -31,7 +31,6 @@ module Noosfero
         else
           :anonymous
         end
-
         PERMISSIONS[current_permission] <= PERMISSIONS[permission]
       end
 
@@ -84,6 +83,20 @@ module Noosfero
         expose :identifier, :name, :id
         expose :created_at, :format_with => :timestamp
         expose :updated_at, :format_with => :timestamp
+        expose :additional_data do |profile, options|
+          hash ={}
+          profile.public_values.each do |value|
+            hash[value.custom_field.name]=value.value
+          end
+
+          private_values = profile.custom_field_values - profile.public_values
+          private_values.each do |value|
+            if Entities.can_display?(profile,options,:custom_field)
+              hash[value.custom_field.name]=value.value
+            end
+          end
+          hash
+        end
         expose :image, :using => Image
         expose :region, :using => Region
       end


=====================================
lib/noosfero/api/v1/comments.rb
=====================================
--- a/lib/noosfero/api/v1/comments.rb
+++ b/lib/noosfero/api/v1/comments.rb
@@ -18,12 +18,12 @@ module Noosfero
             article = find_article(environment.articles, params[:id])
             comments = select_filtered_collection_of(article, :comments, params)
 
-            present comments, :with => Entities::Comment
+            present comments, :with => Entities::Comment, :current_person => current_person
           end
 
           get ":id/comments/:comment_id" do
             article = find_article(environment.articles, params[:id])
-            present article.comments.find(params[:comment_id]), :with => Entities::Comment
+            present article.comments.find(params[:comment_id]), :with => Entities::Comment, :current_person => current_person
           end
 
           # Example Request:
@@ -31,7 +31,7 @@ module Noosfero
           post ":id/comments" do
             article = find_article(environment.articles, params[:id])
             options = params.select { |key,v| !['id','private_token'].include?(key) }.merge(:author => current_person, :source => article)
-            present Comment.create(options), :with => Entities::Comment
+            present Comment.create(options), :with => Entities::Comment, :current_person => current_person
           end
         end
 


=====================================
lib/noosfero/api/v1/communities.rb
=====================================
--- a/lib/noosfero/api/v1/communities.rb
+++ b/lib/noosfero/api/v1/communities.rb
@@ -20,14 +20,21 @@ module Noosfero
             communities = select_filtered_collection_of(environment, 'communities', params)
             communities = communities.visible_for_person(current_person)
             communities = communities.by_location(params) # Must be the last. May return Exception obj.
-            present communities, :with => Entities::Community
+            present communities, :with => Entities::Community, :current_person => current_person
           end
 
 
           # Example Request:
           #  POST api/v1/communties?private_token=234298743290432&community[name]=some_name
+          #  for each custom field for community, add &community[field_name]=field_value to the request
           post do
             params[:community] ||= {}
+
+            params[:community][:custom_values]={}
+            params[:community].keys.each do |key|
+              params[:community][:custom_values][key]=params[:community].delete(key) if Community.custom_fields(environment).any?{|cf| cf.name==key}
+            end
+
             begin
               community = Community.create_after_moderation(current_person, params[:community].merge({:environment => environment}))
             rescue
@@ -38,12 +45,12 @@ module Noosfero
               render_api_errors!(community.errors.full_messages)
             end
 
-            present community, :with => Entities::Community
+            present community, :with => Entities::Community, :current_person => current_person
           end
 
           get ':id' do
             community = environment.communities.visible_for_person(current_person).find_by_id(params[:id])
-            present community, :with => Entities::Community
+            present community, :with => Entities::Community, :current_person => current_person
           end
 
         end
@@ -58,7 +65,7 @@ module Noosfero
                 person = environment.people.find(params[:person_id])
                 communities = select_filtered_collection_of(person, 'communities', params)
                 communities = communities.visible
-                present communities, :with => Entities::Community
+                present communities, :with => Entities::Community, :current_person => current_person
               end
 
             end


=====================================
lib/noosfero/api/v1/enterprises.rb
=====================================
--- a/lib/noosfero/api/v1/enterprises.rb
+++ b/lib/noosfero/api/v1/enterprises.rb
@@ -21,13 +21,13 @@ module Noosfero
             enterprises = select_filtered_collection_of(environment, 'enterprises', params)
             enterprises = enterprises.visible_for_person(current_person)
             enterprises = enterprises.by_location(params) # Must be the last. May return Exception obj.
-            present enterprises, :with => Entities::Enterprise
+            present enterprises, :with => Entities::Enterprise, :current_person => current_person
           end
 
           desc "Return one enterprise by id"
           get ':id' do
             enterprise = environment.enterprises.visible_for_person(current_person).find_by_id(params[:id])
-            present enterprise, :with => Entities::Enterprise
+            present enterprise, :with => Entities::Enterprise, :current_person => current_person
           end
 
         end
@@ -42,7 +42,7 @@ module Noosfero
                 person = environment.people.find(params[:person_id])
                 enterprises = select_filtered_collection_of(person, 'enterprises', params)
                 enterprises = enterprises.visible.by_location(params)
-                present enterprises, :with => Entities::Enterprise
+                present enterprises, :with => Entities::Enterprise, :current_person => current_person
               end
 
             end


=====================================
lib/noosfero/api/v1/people.rb
=====================================
--- a/lib/noosfero/api/v1/people.rb
+++ b/lib/noosfero/api/v1/people.rb
@@ -33,30 +33,31 @@ module Noosfero
           get do
             people = select_filtered_collection_of(environment, 'people', params)
             people = people.visible_for_person(current_person)
-            present people, :with => Entities::Person
+            present people, :with => Entities::Person, :current_person => current_person
           end
 
           desc "Return the logged user information"
           get "/me" do
-            present current_person, :with => Entities::Person
+            present current_person, :with => Entities::Person, :current_person => current_person
           end
 
           desc "Return the person information"
           get ':id' do
             person = environment.people.visible_for_person(current_person).find_by_id(params[:id])
             return not_found! if person.blank?
-            present person, :with => Entities::Person
+            present person, :with => Entities::Person, :current_person => current_person
           end
 
           desc "Update person information"
           post ':id' do
             return forbidden! if current_person.id.to_s != params[:id]
             current_person.update_attributes!(params[:person])
-            present current_person, :with => Entities::Person
+            present current_person, :with => Entities::Person, :current_person => current_person
           end
 
           # Example Request:
           #  POST api/v1/people?person[login]=some_login&person[password]=some_password&person[name]=Jack
+          #  for each custom field for person, add &person[field_name]=field_value to the request
           desc "Create person"
           post do
             user_data = {}
@@ -64,14 +65,21 @@ module Noosfero
             user_data[:email] = params[:person].delete(:email)
             user_data[:password] = params[:person].delete(:password)
             user_data[:password_confirmation] = params[:person].delete(:password_confirmation)
+
+            params[:person][:custom_values]={}
+            params[:person].keys.each do |key|
+              params[:person][:custom_values][key]=params[:person].delete(key) if Person.custom_fields(environment).any?{|cf| cf.name==key}
+            end
+
             user = User.build(user_data, params[:person], environment)
+
             begin
               user.signup!
             rescue ActiveRecord::RecordInvalid
               render_api_errors!(user.errors.full_messages)
             end
 
-            present user.person, :with => Entities::Person
+            present user.person, :with => Entities::Person, :current_person => user.person
           end
 
           desc "Return the person friends"
@@ -79,7 +87,7 @@ module Noosfero
             person = environment.people.visible_for_person(current_person).find_by_id(params[:id])
             return not_found! if person.blank?
             friends = person.friends.visible
-            present friends, :with => Entities::Person
+            present friends, :with => Entities::Person, :current_person => current_person
           end
 
           desc "Return the person permissions on other profiles"


=====================================
public/javascripts/manage-fields.js
=====================================
--- a/public/javascripts/manage-fields.js
+++ b/public/javascripts/manage-fields.js
@@ -36,6 +36,34 @@ function signup_action(name_active, name_required, name_signup) {
   update_active(name_active, name_required, name_signup)
 }
 
+function add_content(target_id, content, mask) {
+  var id = new Date().getTime();
+  var regexp = new RegExp(mask, "g");
+  content = content.replace(regexp, id);
+  $(target_id).append(content);
+  $('#' + id).hide().slideDown();
+}
+
+function remove_content(target) {
+  $(target).remove();
+}
+
+function submit_custom_field_form(selector_id, form_id, customized_type) {
+  $(selector_id).attr('disabled', true);
+  $(form_id).submit();
+}
+
+function manage_default_option(source) {
+  var th = $(source);
+  var name = th.prop('name');
+  if(th.is(':checked')){
+      $(':checkbox[name="'  + name + '"]').not($(source)).prop('checked',false);
+  }
+}
+
+function update_default_value(source, target) {
+    $(target).val(source);
+}
 
 jQuery(document).ready(function(){
   function check_fields(check, table_id, start) {


=====================================
public/stylesheets/manage-fields.scss
=====================================
--- a/public/stylesheets/manage-fields.scss
+++ b/public/stylesheets/manage-fields.scss
@@ -9,3 +9,11 @@
   font-style: italic;
 }
 
+.custom-field-item {
+  position: relative;
+  a.icon-delete {
+    position: absolute;
+    top: 20px;
+    right: 20px;
+  }
+}


=====================================
test/functional/features_controller_test.rb
=====================================
--- a/test/functional/features_controller_test.rb
+++ b/test/functional/features_controller_test.rb
@@ -8,6 +8,7 @@ class FeaturesControllerTest < ActionController::TestCase
     @controller = FeaturesController.new
     @request    = ActionController::TestRequest.new
     @response   = ActionController::TestResponse.new
+
     login_as(create_admin_user(Environment.find(2)))
   end
 
@@ -159,4 +160,51 @@ class FeaturesControllerTest < ActionController::TestCase
     assert_includes json_response, {"id"=>person.id, "name"=>person.name}
   end
 
+  should 'create custom field' do
+    uses_host 'anhetegua.net'
+    assert_nil Environment.find(2).custom_fields.find_by_name('foo')
+    post :manage_custom_fields, :customized_type => 'Person', :custom_fields => {
+      Time.now.to_i => {
+        :name => 'foo',
+        :default_value => 'foobar',
+        :format => 'string',
+        :customized_type => 'Person',
+        :active => true,
+        :required => true,
+        :signup => true
+      }
+    }
+    assert_redirected_to :action => 'manage_fields'
+    assert_not_nil Environment.find(2).custom_fields.find_by_name('foo')
+  end
+
+  should 'update custom field' do
+    uses_host 'anhetegua.net'
+
+    field = CustomField.create! :name => 'foo', :default_value => 'foobar', :format => 'string', :extras => '', :customized_type => 'Enterprise', :active => true, :required => true, :signup => true, :environment => Environment.find(2)
+    post :manage_custom_fields, :customized_type => 'Enterprise', :custom_fields => {
+      field.id => {
+        :name => 'foo bar',
+        :default_value => 'foobar',
+        :active => true,
+        :required => true,
+        :signup => true
+      }
+    }
+    field.reload
+    assert_redirected_to :action => 'manage_fields'
+    assert_equal 'foo bar', field.name
+  end
+
+  should 'destroy custom field' do
+    uses_host 'anhetegua.net'
+
+    field = CustomField.create! :name => 'foo', :default_value => 'foobar', :format => 'string', :extras => '', :customized_type => 'Enterprise', :active => true, :required => true, :signup => true, :environment => Environment.find(2)
+
+    post :manage_custom_fields, :customized_type => 'Enterprise'
+
+    assert_redirected_to :action => 'manage_fields'
+    assert_nil Environment.find(2).custom_fields.find_by_name('foo')
+  end
+
 end


=====================================
test/unit/acts_as_customizable_test.rb
=====================================
--- /dev/null
+++ b/test/unit/acts_as_customizable_test.rb
@@ -0,0 +1,23 @@
+require_relative "../test_helper"
+
+class ActsAsCustomizableTest < ActiveSupport::TestCase
+
+  should 'save custom field values for person' do
+    CustomField.create!(:name => "Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default)
+    person = create_user('testinguser').person
+    assert_difference 'CustomFieldValue.count' do
+      person.custom_values = { "Blog" => { "value" => "www.blog.org", "public" => "0"} }
+      person.save!
+      assert_equal 'www.blog.org', CustomFieldValue.find(:last, :conditions => {:customized_id => person.id}).value
+    end
+  end
+
+  should 'not be valid when required custom field not filled' do
+    CustomField.create!(:name => "Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default, :required => true)
+    person = create_user('testinguser').person
+
+    person.custom_values = { "Blog" => { "value" => "", "public" => "0"} }
+    refute person.valid?
+  end
+
+end


=====================================
test/unit/api/people_test.rb
=====================================
--- a/test/unit/api/people_test.rb
+++ b/test/unit/api/people_test.rb
@@ -165,4 +165,44 @@ class PeopleTest < ActiveSupport::TestCase
     assert_equal another_name, person.name
   end
 
+  should 'display public custom fields' do
+    CustomField.create!(:name => "Custom Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default)
+    some_person = create_user('some-person').person
+    some_person.custom_values = { "Custom Blog" => { "value" => "www.blog.org", "public" => "true"} }
+    some_person.save!
+
+    get "/api/v1/people/#{some_person.id}?#{params.to_query}"
+    json = JSON.parse(last_response.body)
+    assert json['person']['additional_data'].has_key?('Custom Blog')
+    assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog']
+  end
+
+  should 'not display non-public custom fields' do
+    CustomField.create!(:name => "Custom Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default)
+    some_person = create_user('some-person').person
+    some_person.custom_values = { "Custom Blog" => { "value" => "www.blog.org", "public" => "0"} }
+    some_person.save!
+
+    get "/api/v1/people/#{some_person.id}?#{params.to_query}"
+    json = JSON.parse(last_response.body)
+    assert_equal json['person']['additional_data'], {}
+  end
+
+  should 'display non-public custom fields to friend' do
+    CustomField.create!(:name => "Custom Blog", :format => "string", :customized_type => "Person", :active => true, :environment => Environment.default)
+    some_person = create_user('some-person').person
+    some_person.custom_values = { "Custom Blog" => { "value" => "www.blog.org", "public" => "0"} }
+    some_person.save!
+
+    f = Friendship.new
+    f.friend = some_person
+    f.person = person
+    f.save!
+
+    get "/api/v1/people/#{some_person.id}?#{params.to_query}"
+    json = JSON.parse(last_response.body)
+    assert json['person']['additional_data'].has_key?("Custom Blog")
+    assert_equal "www.blog.org", json['person']['additional_data']['Custom Blog']
+  end
+
 end


=====================================
test/unit/custom_field_test.rb
=====================================
--- /dev/null
+++ b/test/unit/custom_field_test.rb
@@ -0,0 +1,153 @@
+require_relative "../test_helper"
+
+class CustomFieldTest < ActiveSupport::TestCase
+
+  def setup
+    @person = create_user('test_user').person
+
+    @e1 = Environment.default
+    @e2 = fast_create(Environment)
+
+    @community = create(Community, :environment => @e1, :name => 'my new community')
+
+    @community_custom_field = CustomField.create(:name => "community_field", :format=>"myFormat", :default_value => "value for community", :customized_type=>"Community", :active => true, :environment => @e1)
+    @person_custom_field = CustomField.create(:name => "person_field", :format=>"myFormat", :default_value => "value for person", :customized_type=>"Person", :active => true, :environment => @e1)
+    @profile_custom_field = CustomField.create(:name => "profile_field", :format=>"myFormat", :default_value => "value for any profile", :customized_type=>"Profile", :active => true, :environment => @e1)
+
+    @e1.reload
+  end
+
+  should 'not access another environments custom fields' do
+    @e2_custom_field = CustomField.create(:name => "another_field", :format=>"anoherFormat", :default_value => "default value for e2", :customized_type=>"Profile", :active => true, :environment => @e2)
+    @e2.reload
+
+    assert_equal 1, Profile.custom_fields(@e1).size
+    assert_equal @profile_custom_field, Profile.custom_fields(@e1).first
+
+    assert_equal 1, Profile.custom_fields(@e2).size
+    assert_equal @e2_custom_field, Profile.custom_fields(@e2).first
+
+  end
+
+  should 'no access to custom field on sibling' do
+    refute (Person.custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name})
+    refute (Community.custom_fields(@e1).any?{|cf| cf.name == @person_custom_field.name})
+  end
+
+  should 'inheritance of custom_field' do
+    assert Community.custom_fields(@e1).any?{|cf| cf.name == @profile_custom_field.name}
+    assert Person.custom_fields(@e1).any?{|cf| cf.name == @profile_custom_field.name}
+  end
+
+  should 'save custom_field_values' do
+    @community.custom_values = {"community_field" => "new_value!", "profile_field"=> "another_value!"}
+    @community.save
+
+    assert CustomFieldValue.all.any?{|cv| cv.custom_field_id == @community_custom_field.id && cv.customized_id == @community.id && cv.value == "new_value!"}
+    assert CustomFieldValue.all.any?{|cv| cv.custom_field_id == @profile_custom_field.id && cv.customized_id == @community.id && cv.value = "another_value!"}
+  end
+
+  should 'delete custom field and its values' do
+    @community.custom_values = {"community_field" => "new_value!", "profile_field"=> "another_value!"}
+    @community.save
+
+    old_id = @community_custom_field.id
+    @community_custom_field.destroy
+
+    @e1.reload
+    refute (@e1.custom_fields.any?{|cf| cf.id == old_id})
+    refute (Community.custom_fields(@e1).any?{|cf| cf.name == "community_field"})
+    refute (CustomFieldValue.all.any?{|cv| cv.custom_field_id == old_id})
+  end
+
+  should 'not save related custom field' do
+    another_field = CustomField.create(:name => "profile_field", :format=>"myFormat", :default_value => "value for any profile", :customized_type=>"Community", :environment => @e1)
+    assert another_field.id.nil?
+  end
+
+  should 'not save same custom field twice in the same environment' do
+    field = CustomField.create(:name => "the new field", :format=>"myFormat", :customized_type=>"Community", :environment => @e1)
+    refute field.id.nil?
+    @e1.reload
+    another = CustomField.new(:name => "the new field", :format=>"myFormat", :customized_type=>"Community", :environment => @e1)
+    refute another.valid?
+  end
+
+  should 'save same custom field in another environment' do
+    field = CustomField.create(:name => "the new field", :format=>"myFormat", :customized_type=>"Community", :environment => @e1)
+    refute field.id.nil?
+    another_field = CustomField.create(:name => "the new field", :format=>"myFormat", :customized_type=>"Community", :environment => @e2)
+    refute another_field.id.nil?
+  end
+
+  should 'not return inactive fields' do
+    @community_custom_field.update_attributes(:active=>false)
+    @e1.reload
+    refute Community.active_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
+  end
+
+  should 'delete a model and its custom field values' do
+    @community.custom_values = {"community_field" => "new_value!", "profile_field"=> "another_value!"}
+    @community.save
+
+    old_id = @community.id
+    @community.destroy
+    refute (Community.all.any?{|c| c.id == old_id})
+    refute (CustomFieldValue.all.any?{|cv| cv.customized_id == old_id && cv.customized_type == "Community"})
+  end
+
+  should 'keep field value if the field is reactivated' do
+
+    @community.custom_values = {"community_field" => "new_value!"}
+    @community.save
+
+    @community_custom_field.update_attributes(:active=>false)
+    @e1.reload
+    refute Community.active_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
+
+    @community_custom_field.update_attributes(:active=>true)
+
+    @e1.reload
+    @community.reload
+    assert Community.active_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
+    assert_equal @community.custom_value("community_field"), "new_value!"
+  end
+
+  should 'list of required fields' do
+    refute Community.required_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
+
+    @community_custom_field.update_attributes(:required=>true)
+    @community.reload
+    @e1.reload
+    assert Community.required_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
+  end
+
+  should 'list of signup fields' do
+    refute Community.signup_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
+
+    @community_custom_field.update_attributes(:signup=>true)
+    @community.reload
+    @e1.reload
+    assert Community.signup_custom_fields(@e1).any?{|cf| cf.name == @community_custom_field.name}
+  end
+
+  should 'public values handling' do
+    refute @community.is_public("community_field")
+    @community.custom_values = {"community_field" => {"value" => "new_value!", "public"=>"true"}, "profile_field"=> "another_value!"}
+    @community.save
+    @community.reload
+
+    assert @community.is_public("community_field")
+    refute @community.is_public("profile_field")
+  end
+
+  should 'complete list of fields' do
+    assert Person.custom_fields(@e1).include? @profile_custom_field
+    assert Person.custom_fields(@e1).include? @person_custom_field
+  end
+
+  should 'get correct customized ancestors list' do
+    assert (Person.customized_ancestors_list-["Person","Profile"]).blank?
+  end
+end
+


=====================================
test/unit/custom_field_values_test.rb
=====================================
--- /dev/null
+++ b/test/unit/custom_field_values_test.rb
@@ -0,0 +1,12 @@
+require_relative "../test_helper"
+
+class CustomFieldValuesTest < ActiveSupport::TestCase
+
+  should 'custom field value not be valid' do
+    c = CustomField.create!(:name => "Blog", :format => "string", :customized_type => "Person", :active => true, :required => true, :environment => Environment.default)
+    person = create_user('testinguser').person
+
+    cv=CustomFieldValue.new(:customized => person, :custom_field => c, :value => "")
+    refute cv.valid?
+  end
+end



View it on GitLab: https://gitlab.com/noosfero/noosfero/compare/75f13387c60641e1863d67d7b154ee290486e7f2...84ca7256e30e2ac68264f248c8c15457cb134771
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://listas.softwarelivre.org/pipermail/noosfero-dev/attachments/20151119/ab1695e3/attachment-0001.html>


More information about the Noosfero-dev mailing list