Merge pull request #182 from AyuntamientoMadrid/organizations-99

Organizations, closes #99
This commit is contained in:
Juanjo Bazán
2015-08-18 13:56:01 +02:00
43 changed files with 811 additions and 150 deletions

View File

@@ -11,6 +11,7 @@ class AccountController < ApplicationController
if @account.update(account_params) if @account.update(account_params)
redirect_to account_path, notice: t("flash.actions.save_changes.notice") redirect_to account_path, notice: t("flash.actions.save_changes.notice")
else else
@account.errors.messages.delete(:organization)
render :show render :show
end end
end end
@@ -22,7 +23,11 @@ class AccountController < ApplicationController
end end
def account_params def account_params
params.require(:account).permit(:first_name, :last_name, :nickname, :use_nickname, :email_on_debate_comment, :email_on_comment_reply) if @account.organization?
params.require(:account).permit(:phone_number, :email_on_debate_comment, :email_on_comment_reply, organization_attributes: [:name])
else
params.require(:account).permit(:first_name, :last_name, :phone_number, :nickname, :use_nickname, :email_on_debate_comment, :email_on_comment_reply)
end
end end
end end

View File

@@ -1,5 +1,4 @@
class Admin::DashboardController < Admin::BaseController class Admin::DashboardController < Admin::BaseController
layout 'admin'
def index def index
end end

View File

@@ -0,0 +1,32 @@
class Admin::OrganizationsController < Admin::BaseController
before_filter :set_valid_filters
before_filter :parse_filter
load_and_authorize_resource
def index
@organizations = @organizations.send(@filter)
@organizations = @organizations.includes(:user).order(:name, 'users.email')
end
def verify
@organization.verify
redirect_to action: :index, filter: @filter
end
def reject
@organization.reject
redirect_to action: :index, filter: @filter
end
private
def set_valid_filters
@valid_filters = %w{all pending verified rejected}
end
def parse_filter
@filter = params[:filter]
@filter = 'all' unless @valid_filters.include?(@filter)
end
end

View File

@@ -1,4 +1,6 @@
class Moderation::BaseController < ApplicationController class Moderation::BaseController < ApplicationController
layout 'admin'
before_action :authenticate_user! before_action :authenticate_user!
skip_authorization_check skip_authorization_check

View File

@@ -1,5 +1,4 @@
class Moderation::DashboardController < Moderation::BaseController class Moderation::DashboardController < Moderation::BaseController
layout 'admin'
def index def index
end end

View File

@@ -0,0 +1,26 @@
class Organizations::RegistrationsController < Devise::RegistrationsController
def new
super do |user|
user.build_organization
end
end
def create
build_resource(sign_up_params)
if resource.valid_with_captcha?
super do |user|
# Removes unuseful "organization is invalid" error message
user.errors.messages.delete(:organization)
end
else
render :new
end
end
private
def sign_up_params
params.require(:user).permit(:email, :password, :phone_number, :password_confirmation, :captcha, :captcha_key, organization_attributes: [:name])
end
end

View File

@@ -1,4 +1,4 @@
class RegistrationsController < Devise::RegistrationsController class Users::RegistrationsController < Devise::RegistrationsController
def create def create
build_resource(sign_up_params) build_resource(sign_up_params)
@@ -9,11 +9,10 @@ class RegistrationsController < Devise::RegistrationsController
end end
end end
private private
def sign_up_params def sign_up_params
params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation, :use_nickname, :nickname, :captcha, :captcha_key) params.require(:user).permit(:first_name, :last_name, :email, :phone_number, :password, :password_confirmation, :use_nickname, :nickname, :captcha, :captcha_key)
end end
end end

View File

@@ -8,20 +8,31 @@ class Ability
if user # logged-in users if user # logged-in users
can [:read, :update], User, id: user.id can [:read, :update], User, id: user.id
can [:read, :create, :vote], Debate can :read, Debate
can :update, Debate do |debate| can :update, Debate do |debate|
debate.editable_by?(user) debate.editable_by?(user)
end end
can [:create, :vote], Comment can :create, Comment
can :create, Debate
if user.moderator? unless user.organization?
can [:hide], Comment can :vote, Debate
can [:hide], Debate can :vote, Comment
end
elsif user.administrator? if user.moderator? || user.administrator?
can [:restore], Comment can :read, Organization
can [:restore], Debate can(:verify, Organization){ |o| !o.verified? }
can(:reject, Organization){ |o| !o.rejected? }
can :hide, Comment
can :hide, Debate
end
if user.administrator?
can :restore, Comment
can :restore, Debate
end end
end end
end end

View File

@@ -0,0 +1,31 @@
class Organization < ActiveRecord::Base
belongs_to :user
validates :name, presence: true
delegate :email, :phone_number, to: :user
scope :pending, -> { where(verified_at: nil, rejected_at: nil) }
scope :verified, -> { where("verified_at is not null and (rejected_at is null or rejected_at < verified_at)") }
scope :rejected, -> { where("rejected_at is not null and (verified_at is null or verified_at < rejected_at)") }
def verify
update(verified_at: Time.now)
end
def reject
update(rejected_at: Time.now)
end
def verified?
verified_at.present? &&
(rejected_at.blank? || rejected_at < verified_at)
end
def rejected?
rejected_at.present? &&
(verified_at.blank? || verified_at < rejected_at)
end
end

View File

@@ -5,15 +5,28 @@ class User < ActiveRecord::Base
acts_as_voter acts_as_voter
validates :first_name, presence: true, unless: :use_nickname? has_one :administrator
validates :last_name, presence: true, unless: :use_nickname? has_one :moderator
has_one :organization
validates :first_name, presence: true, if: :use_first_name?
validates :last_name, presence: true, if: :use_last_name?
validates :nickname, presence: true, if: :use_nickname? validates :nickname, presence: true, if: :use_nickname?
validates :official_level, inclusion: {in: 0..5} validates :official_level, inclusion: {in: 0..5}
validates_associated :organization, message: false
accepts_nested_attributes_for :organization
scope :administrators, -> { joins(:administrators) }
scope :moderators, -> { joins(:moderator) }
scope :organizations, -> { joins(:organization) }
scope :officials, -> { where("official_level > 0") } scope :officials, -> { where("official_level > 0") }
def name def name
use_nickname? ? nickname : "#{first_name} #{last_name}" return nickname if use_nickname?
return organization.name if organization?
"#{first_name} #{last_name}"
end end
def debate_votes(debates) def debate_votes(debates)
@@ -22,11 +35,15 @@ class User < ActiveRecord::Base
end end
def administrator? def administrator?
@is_administrator ||= Administrator.where(user_id: id).exists? administrator.present?
end end
def moderator? def moderator?
@is_moderator ||= Moderator.where(user_id: id).exists? moderator.present?
end
def organization?
organization.present?
end end
def official? def official?
@@ -45,4 +62,13 @@ class User < ActiveRecord::Base
def self.with_email(e) def self.with_email(e)
e.present? ? where(email: e) : none e.present? ? where(email: e) : none
end end
private
def use_first_name?
!organization? && !use_nickname?
end
def use_last_name?
use_first_name?
end
end end

View File

@@ -11,12 +11,24 @@
<div class="small-12 medium-6 column"> <div class="small-12 medium-6 column">
<h2><%= t("account.show.personal")%></h2> <h2><%= t("account.show.personal")%></h2>
<% if @account.organization? %>
<%= f.fields_for :organization do |fo| %>
<%= fo.text_field :name, autofocus: true, placeholder: t("account.show.organization_name_label") %>
<% end %>
<% else %>
<%= f.text_field :first_name, placeholder: t("account.show.first_name_label") %> <%= f.text_field :first_name, placeholder: t("account.show.first_name_label") %>
<%= f.text_field :last_name, placeholder: t("account.show.last_name_label") %> <%= f.text_field :last_name, placeholder: t("account.show.last_name_label") %>
<%= f.text_field :nickname, placeholder: t("account.show.nickname_label") %> <%= f.text_field :nickname, placeholder: t("account.show.nickname_label") %>
<%= f.check_box :use_nickname, label: false %> <%= f.check_box :use_nickname, label: false %>
<span class="checkbox"><%= t("account.show.use_nickname_label") %></span> <span class="checkbox"><%= t("account.show.use_nickname_label") %></span>
<% end %>
<%= f.text_field :phone_number, placeholder: t("account.show.phone_number_label") %>
</div> </div>
<div class="small-12 medium-6 column"> <div class="small-12 medium-6 column">
@@ -41,5 +53,6 @@
</div> </div>
</div> </div>
<% end %> <% end %>
</div> </div>
</div> </div>

View File

@@ -25,6 +25,13 @@
<% end %> <% end %>
</li> </li>
<li <%= 'class=active' if controller_name == 'organizations' %>>
<%= link_to admin_organizations_path do %>
<i class="icon-comment-quotes"></i>
<%= t('admin.menu.organizations') %>
<% end %>
</li>
<li <%= 'class=active' if controller_name == 'officials' %>> <li <%= 'class=active' if controller_name == 'officials' %>>
<%= link_to admin_officials_path do %> <%= link_to admin_officials_path do %>
<i class="icon-chat-bubble-two"></i> <i class="icon-chat-bubble-two"></i>

View File

@@ -0,0 +1,48 @@
<div class="left">
<h1><%= t('admin.organizations.index.title') %></h1>
<p>
<%= t('admin.organizations.index.filter') %>:
<% @valid_filters.each do |filter| %>
<% if @filter == filter %>
<%= t("admin.organizations.index.filters.#{filter}") %>
<% else %>
<%= link_to t("admin.organizations.index.filters.#{filter}"),
admin_organizations_path(filter: filter) %>
<% end %>
<% end %>
</p>
<table>
<% @organizations.each do |organization| %>
<tr>
<td><%= organization.name %></td>
<td><%= organization.email %></td>
<td><%= organization.phone_number %></td>
<% if organization.verified? %>
<td><%= t('admin.organizations.index.verified') %></td>
<% end %>
<% if can? :verify, organization %>
<td><%= link_to t('admin.organizations.index.verify'),
verify_admin_organization_path(organization, filter: @filter),
method: :put
%>
</td>
<% end %>
<% if organization.rejected? %>
<td><%= t('admin.organizations.index.rejected') %></td>
<% end %>
<% if can? :reject, organization %>
<td><%= link_to t('admin.organizations.index.reject'),
reject_admin_organization_path(organization, filter: @filter),
method: :put
%>
</td>
<% end %>
</tr>
<% end %>
</table>
</div>

View File

@@ -1,49 +0,0 @@
<div class="auth row">
<div class="small-12 medium-8 large-5 column small-centered">
<div class="panel">
<i class="icon-angle-left left"></i>&nbsp;<%= link_to t("devise_views.registrations.edit.back_link"), :back, class: "left back" %>
<h2><%= t("devise_views.registrations.edit.edit") %> <%= resource_name.to_s.humanize %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= render 'shared/errors', resource: resource %>
<div class="row">
<div class="small-12 column">
<%= f.email_field :email, autofocus: true, placeholder: t("devise_views.registrations.edit.email_label") %>
</div>
<div class="small-12 column">
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div><%= t("devise_views.registrations.edit.waiting_for") %> <%= resource.unconfirmed_email %></div>
<% end %>
</div>
<div class="small-12 column">
<%= f.label :password, t("devise_views.registrations.edit.password_label") %>
<span class="note"><%= t("devise_views.registrations.edit.leave_blank") %></span>
<%= f.password_field :password, autocomplete: "off",
label: false,
placeholder: t("devise_views.registrations.edit.password_label") %>
</div>
<div class="small-12 column">
<%= f.password_field :password_confirmation, autocomplete: "off", placeholder: t("devise_views.registrations.edit.password_confirmation_label") %>
</div>
<div class="small-12 column">
<%= f.label :current_password, t("devise_views.registrations.edit.current_password_label") %>
<span class="note"><%= t("devise_views.registrations.edit.need_current") %></span>
<%= f.password_field :current_password, autocomplete: "off",
label: false,
placeholder: t("devise_views.registrations.edit.current_password_label") %>
</div>
<div class="small-12 column">
<%= f.submit t("devise_views.registrations.edit.update_submit"), class: "button radius" %>
</div>
</div>
<% end %>
</div>
</div>
</div>

View File

@@ -3,8 +3,14 @@
<%= link_to t("devise_views.shared.links.login"), new_session_path(resource_name) %><br> <%= link_to t("devise_views.shared.links.login"), new_session_path(resource_name) %><br>
<% end -%> <% end -%>
<%- if devise_mapping.registerable? && controller_name != 'registrations' %> <%- if devise_mapping.registerable? &&
<%= link_to t("devise_views.shared.links.signup"), new_registration_path(resource_name) %><br> controller_name != 'registrations' ||
controller_path != 'users/registrations' %>
<%= link_to t("devise_views.shared.links.signup"), new_user_registration_path %><br>
<% end -%>
<%- if devise_mapping.registerable? && controller_name == 'registrations' && controller_path != 'organizations/registrations' %>
<%= link_to t("devise_views.shared.links.organization_signup"), new_organization_registration_path %><br>
<% end -%> <% end -%>
<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %>
@@ -24,4 +30,5 @@
<%= link_to t("devise_views.shared.links.signin_with_provider", provider: provider.to_s.titleize), omniauth_authorize_path(resource_name, provider) %><br> <%= link_to t("devise_views.shared.links.signin_with_provider", provider: provider.to_s.titleize), omniauth_authorize_path(resource_name, provider) %><br>
<% end -%> <% end -%>
<% end -%> <% end -%>
</div> </div>

View File

@@ -0,0 +1,36 @@
<div class="auth row">
<div class="small-12 medium-8 large-5 column small-centered">
<div class="panel">
<h2><%= t("devise_views.organizations.registrations.new.title") %></h2>
<%= form_for(resource, as: :user, url: organization_registration_path) do |f| %>
<%= render 'shared/errors', resource: resource %>
<div class="row">
<div class="small-12 column">
<%= f.fields_for :organization do |fo| %>
<%= fo.text_field :name, autofocus: true, placeholder: t("devise_views.organizations.registrations.new.organization_name_label") %>
<% end %>
<%= f.email_field :email, placeholder: t("devise_views.organizations.registrations.new.email_label") %>
<%= f.text_field :phone_number, placeholder: t("devise_views.organizations.registrations.new.phone_number_label") %>
<%= f.password_field :password, autocomplete: "off",
placeholder: t("devise_views.organizations.registrations.new.password_label") %>
<%= f.password_field :password_confirmation, autocomplete: "off",
label: t("devise_views.organizations.registrations.new.password_confirmation_label"),
placeholder: t("devise_views.organizations.registrations.new.password_confirmation_label") %>
<%= f.simple_captcha input_html: {required: false} %>
<%= f.submit t("devise_views.organizations.registrations.new.submit"), class: "button radius expand" %>
</div>
</div>
<% end %>
<%= render "devise/shared/links" %>
</div>
</div>
</div>

View File

@@ -0,0 +1,45 @@
<div class="row">
<div class="account small-12 medium-9 column small-centered">
<i class="icon-angle-left left"></i>&nbsp;<%= link_to t("devise_views.users.registrations.edit.back_link"), :back, class: "left back" %>
<h1><%= t("devise_views.users.registrations.edit.edit") %> <%= resource_name.to_s.humanize %></h1>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<div class="row">
<div class="small-12 column">
<%= f.label :email, t("devise_views.users.registrations.edit.email_label") %>
<%= f.email_field :email, autofocus: true, placeholder: t("devise_views.users.registrations.edit.email_label") %>
</div>
<div class="small-12 column">
<% if devise_mapping.confirmable? && resource.pending_reconfirmation? %>
<div><%= t("devise_views.users.registrations.edit.waiting_for") %> <%= resource.unconfirmed_email %></div>
<% end %>
</div>
<div class="small-12 column">
<%= f.label :password, t("devise_views.users.registrations.edit.password_label") %>
<span class="note"><%= t("devise_views.users.registrations.edit.leave_blank") %></span>
<%= f.password_field :password, autocomplete: "off", placeholder: t("devise_views.users.registrations.edit.password_label") %>
</div>
<div class="small-12 column">
<%= f.label :password_confirmation, t("devise_views.users.registrations.edit.password_confirmation_label") %>
<%= f.password_field :password_confirmation, autocomplete: "off", placeholder: t("devise_views.users.registrations.edit.password_confirmation_label") %>
</div>
<div class="small-12 column">
<%= f.label :current_password, t("devise_views.users.registrations.edit.current_password_label") %>
<span class="note"><%= t("devise_views.users.registrations.edit.need_current") %></span>
<%= f.password_field :current_password, autocomplete: "off", placeholder: t("devise_views.users.registrations.edit.current_password_label") %>
</div>
<div class="small-12 column">
<%= f.submit t("devise_views.users.registrations.edit.update_submit"), class: "button radius" %>
</div>
</div>
<% end %>
</div>
</div>

View File

@@ -1,31 +1,31 @@
<div class="auth row"> <div class="auth row">
<div class="small-12 medium-8 large-5 column small-centered"> <div class="small-12 medium-8 large-5 column small-centered">
<div class="panel"> <div class="panel">
<h2><%= t("devise_views.registrations.new.title") %></h2> <h2><%= t("devise_views.users.registrations.new.title") %></h2>
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<%= render 'shared/errors', resource: resource %> <%= render 'shared/errors', resource: resource %>
<div class="row"> <div class="row">
<div class="small-12 column"> <div class="small-12 column">
<%= f.text_field :first_name, autofocus: true, <%= f.text_field :first_name, autofocus: true,
placeholder: t("devise_views.registrations.new.first_name_label") %> placeholder: t("devise_views.users.registrations.new.first_name_label") %>
<%= f.text_field :last_name, placeholder: t("devise_views.registrations.new.last_name_label") %> <%= f.text_field :last_name, placeholder: t("devise_views.users.registrations.new.last_name_label") %>
<%= f.email_field :email, placeholder: t("devise_views.registrations.new.email_label") %> <%= f.email_field :email, placeholder: t("devise_views.users.registrations.new.email_label") %>
<%= f.text_field :nickname, placeholder: t("devise_views.registrations.new.nickname_label") %> <%= f.text_field :nickname, placeholder: t("devise_views.users.registrations.new.nickname_label") %>
<%= f.check_box :use_nickname, label: t("devise_views.registrations.new.use_nickname_label") %> <%= f.check_box :use_nickname, label: t("devise_views.users.registrations.new.use_nickname_label") %>
<%= f.text_field :phone_number, placeholder: t("devise_views.users.registrations.new.phone_number_label") %>
<%= f.password_field :password, autocomplete: "off", <%= f.password_field :password, autocomplete: "off",
placeholder: t("devise_views.registrations.new.password_label") %> placeholder: t("devise_views.users.registrations.new.password_label") %>
<%= f.password_field :password_confirmation, autocomplete: "off", <%= f.password_field :password_confirmation, autocomplete: "off",
label: t("devise_views.registrations.new.password_confirmation_label"), label: t("devise_views.users.registrations.new.password_confirmation_label"),
placeholder: t("devise_views.registrations.new.password_confirmation_label") %> placeholder: t("devise_views.users.registrations.new.password_confirmation_label") %>
<%= f.simple_captcha input_html: {required: false} %> <%= f.simple_captcha input_html: {required: false} %>
<%= f.submit t("devise_views.registrations.new.submit"), class: "button radius expand" %> <%= f.submit t("devise_views.users.registrations.new.submit"), class: "button radius expand" %>
</div> </div>
</div> </div>
<% end %> <% end %>

View File

@@ -93,6 +93,8 @@ ignore_missing:
## Consider these keys used: ## Consider these keys used:
ignore_unused: ignore_unused:
- 'activerecord.*'
- 'admin.organizations.index.filter.*'
- 'unauthorized.*' - 'unauthorized.*'
- 'simple_captcha.*' - 'simple_captcha.*'
- 'admin.officials.level_*' - 'admin.officials.level_*'

View File

@@ -211,7 +211,7 @@ Devise.setup do |config|
# Turn scoped views on. Before rendering "sessions/new", it will first check for # Turn scoped views on. Before rendering "sessions/new", it will first check for
# "users/sessions/new". It's turned off by default because it's slower if you # "users/sessions/new". It's turned off by default because it's slower if you
# are using only default views. # are using only default views.
# config.scoped_views = false config.scoped_views = true
# Configure the default scope given to Warden. By default it's the first # Configure the default scope given to Warden. By default it's the first
# devise role declared in your routes (usually :user). # devise role declared in your routes (usually :user).

View File

@@ -6,6 +6,7 @@ en:
tag: Topic tag: Topic
user: User user: User
vote: Vote vote: Vote
organization: Organization
attributes: attributes:
comment: comment:
body: Comment body: Comment
@@ -21,5 +22,8 @@ en:
last_name: "Last name" last_name: "Last name"
nickname: Nickname nickname: Nickname
password: Password password: Password
phone_number: Phone number
official_position: Official position official_position: Official position
official_level: Official level official_level: Official level
organization:
name: Organization name

View File

@@ -6,6 +6,7 @@ es:
tag: Tema tag: Tema
user: Usuario user: Usuario
vote: Voto vote: Voto
organization: Organización
attributes: attributes:
comment: comment:
body: Comentario body: Comentario
@@ -21,5 +22,8 @@ es:
last_name: Apellidos last_name: Apellidos
nickname: Pseudónimo nickname: Pseudónimo
password: Contraseña password: Contraseña
phone_number: Teléfono
official_position: Cargo público official_position: Cargo público
official_level: Nivel del cargo official_level: Nivel del cargo
organization:
name: Nombre de organización

View File

@@ -13,8 +13,22 @@ en:
debate_topics: Debate topics debate_topics: Debate topics
hidden_debates: Hidden debates hidden_debates: Hidden debates
hidden_comments: Hidden comments hidden_comments: Hidden comments
organizations: Organizations
officials: Officials officials: Officials
stats: Statistics stats: Statistics
organizations:
index:
title: Organizations
verify: Verify
reject: Reject
verified: Verified
rejected: Rejected
filter: Filter
filters:
all: All
pending: Pending
verified: Verified
rejected: Rejected
actions: actions:
hide: Hide hide: Hide
restore: Restore restore: Restore

View File

@@ -13,8 +13,22 @@ es:
debate_topics: Temas de debate debate_topics: Temas de debate
hidden_debates: Debates ocultos hidden_debates: Debates ocultos
hidden_comments: Comentarios ocultos hidden_comments: Comentarios ocultos
organizations: Organizaciones
officials: Cargos públicos officials: Cargos públicos
stats: Estadísticas stats: Estadísticas
organizations:
index:
title: Organizaciones
verify: Verificar
reject: Rechazar
verified: Verificada
rejected: Rechazada
filter: Filtro
filters:
all: Todas
pending: Pendientes
verified: Verificadas
rejected: Rechazadas
actions: actions:
hide: Ocultar hide: Ocultar
restore: Permitir restore: Permitir

View File

@@ -35,6 +35,7 @@ en:
title: "Forgot your password?" title: "Forgot your password?"
email_label: "Email" email_label: "Email"
send_submit: "Send me reset password instructions" send_submit: "Send me reset password instructions"
users:
registrations: registrations:
edit: edit:
edit: "Edit" edit: "Edit"
@@ -54,9 +55,20 @@ en:
nickname_label: "Nickname" nickname_label: "Nickname"
use_nickname_label: "Use nickname" use_nickname_label: "Use nickname"
email_label: "Email" email_label: "Email"
phone_number_label: "Phone number"
password_label: "Password" password_label: "Password"
password_confirmation_label: "Confirm password" password_confirmation_label: "Confirm password"
submit: "Sign up" submit: "Sign up"
organizations:
registrations:
new:
title: "Sign up as organization"
organization_name_label: "Organization name"
email_label: "Email"
password_label: "Password"
phone_number_label: "Phone number"
password_confirmation_label: "Confirm password"
submit: "Sign up"
sessions: sessions:
new: new:
title: "Log in" title: "Log in"
@@ -74,6 +86,7 @@ en:
login: "Log in" login: "Log in"
signup: "Sign up" signup: "Sign up"
signin_with_provider: "Sign in with %{provider}" signin_with_provider: "Sign in with %{provider}"
organization_signup: "Sign up as an organization"
new_password: "Forgot your password?" new_password: "Forgot your password?"
new_confirmation: "Didn't receive confirmation instructions?" new_confirmation: "Didn't receive confirmation instructions?"
new_unlock: "Didn't receive unlock instructions?" new_unlock: "Didn't receive unlock instructions?"

View File

@@ -8,7 +8,7 @@ es:
confirmation_instructions: confirmation_instructions:
welcome: "Bienvenido" welcome: "Bienvenido"
text: "Puedes confirmar tu cuenta de correo electrónico en el siguiente enlace:" text: "Puedes confirmar tu cuenta de correo electrónico en el siguiente enlace:"
confirm_link: "Confirmar my cuenta" confirm_link: "Confirmar mi cuenta"
reset_password_instructions: reset_password_instructions:
hello: "Hola" hello: "Hola"
text: "Se ha solicitado un enlace para cambiar tu contraseña, puedes hacerlo en el siguiente enlace:" text: "Se ha solicitado un enlace para cambiar tu contraseña, puedes hacerlo en el siguiente enlace:"
@@ -35,6 +35,7 @@ es:
title: "¿Has olvidado tu contraseña?" title: "¿Has olvidado tu contraseña?"
email_label: "Email" email_label: "Email"
send_submit: "Recibir instrucciones para recuperar mi contraseña" send_submit: "Recibir instrucciones para recuperar mi contraseña"
users:
registrations: registrations:
edit: edit:
edit: "Editar" edit: "Editar"
@@ -54,9 +55,20 @@ es:
nickname_label: "Pseudónimo" nickname_label: "Pseudónimo"
use_nickname_label: "Usar pseudónimo" use_nickname_label: "Usar pseudónimo"
email_label: "Email" email_label: "Email"
phone_number_label: "Teléfono"
password_label: "Contraseña" password_label: "Contraseña"
password_confirmation_label: "Confirmar contraseña" password_confirmation_label: "Confirmar contraseña"
submit: "Registrarse" submit: "Registrarse"
organizations:
registrations:
new:
title: "Registrarse como organización"
organization_name_label: "Nombre de la organización"
email_label: "Email"
password_label: "Contraseña"
phone_number_label: "Teléfono"
password_confirmation_label: "Confirmar contraseña"
submit: "Registrarse"
sessions: sessions:
new: new:
title: "Entrar" title: "Entrar"
@@ -73,6 +85,7 @@ es:
links: links:
login: "Entrar" login: "Entrar"
signup: "Registrarse" signup: "Registrarse"
organization_signup: "Registro para organizaciones"
signin_with_provider: "Entrar con %{provider}" signin_with_provider: "Entrar con %{provider}"
new_password: "¿Olvidaste tu contraseña?" new_password: "¿Olvidaste tu contraseña?"
new_confirmation: "¿No has recibido instrucciones para confirmar tu cuenta?" new_confirmation: "¿No has recibido instrucciones para confirmar tu cuenta?"

View File

@@ -115,6 +115,8 @@ en:
personal: "Personal data" personal: "Personal data"
first_name_label: "First Name" first_name_label: "First Name"
last_name_label: "Last Name" last_name_label: "Last Name"
phone_number_label: "Phone number"
organization_name_label: "Organization name"
use_nickname_label: "Use nickname" use_nickname_label: "Use nickname"
nickname_label: "Nickname" nickname_label: "Nickname"
notifications: Notifications notifications: Notifications

View File

@@ -115,6 +115,8 @@ es:
personal: "Datos personales" personal: "Datos personales"
first_name_label: "Nombre" first_name_label: "Nombre"
last_name_label: "Apellidos" last_name_label: "Apellidos"
phone_number_label: "Teléfono"
organization_name_label: "Nombre de la organización"
use_nickname_label: "Usar pseudónimo" use_nickname_label: "Usar pseudónimo"
nickname_label: "Pseudónimo" nickname_label: "Pseudónimo"
notifications: Notificaciones notifications: Notificaciones

View File

@@ -3,3 +3,4 @@ en:
dashboard: dashboard:
index: index:
title: Moderation title: Moderation

View File

@@ -3,3 +3,5 @@ es:
dashboard: dashboard:
index: index:
title: Moderación title: Moderación

View File

@@ -1,5 +1,10 @@
Rails.application.routes.draw do Rails.application.routes.draw do
devise_for :users, controllers: { registrations: 'registrations' } devise_for :users, controllers: { registrations: 'users/registrations' }
devise_for :organizations, class_name: 'User',
controllers: {
registrations: 'organizations/registrations',
sessions: 'devise/sessions'
}
# The priority is based upon order of creation: first created -> highest priority. # The priority is based upon order of creation: first created -> highest priority.
# See how all your routes lay out with "rake routes". # See how all your routes lay out with "rake routes".
@@ -24,6 +29,12 @@ Rails.application.routes.draw do
namespace :admin do namespace :admin do
root to: "dashboard#index" root to: "dashboard#index"
resources :organizations, only: :index do
member do
put :verify
put :reject
end
end
resources :debates, only: [:index, :show] do resources :debates, only: [:index, :show] do
member { put :restore } member { put :restore }

View File

@@ -0,0 +1,10 @@
class CreateOrganizations < ActiveRecord::Migration
def change
create_table :organizations do |t|
t.belongs_to :user, index: true, foreign_key: true
t.string :name, limit: 80
t.datetime :verified_at
t.datetime :rejected_at
end
end
end

View File

@@ -0,0 +1,5 @@
class AddPhoneNumberToUsers < ActiveRecord::Migration
def change
add_column :users, :phone_number, :string, limit: 30
end
end

View File

@@ -73,6 +73,15 @@ ActiveRecord::Schema.define(version: 20150817150457) do
add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree
create_table "organizations", force: :cascade do |t|
t.integer "user_id"
t.string "name", limit: 80
t.datetime "verified_at"
t.datetime "rejected_at"
end
add_index "organizations", ["user_id"], name: "index_organizations_on_user_id", using: :btree
create_table "settings", force: :cascade do |t| create_table "settings", force: :cascade do |t|
t.string "key" t.string "key"
t.string "value" t.string "value"
@@ -128,6 +137,7 @@ ActiveRecord::Schema.define(version: 20150817150457) do
t.datetime "confirmation_sent_at" t.datetime "confirmation_sent_at"
t.string "unconfirmed_email" t.string "unconfirmed_email"
t.string "nickname" t.string "nickname"
t.string "phone_number", limit: 30
t.boolean "use_nickname", default: false, null: false t.boolean "use_nickname", default: false, null: false
t.boolean "email_on_debate_comment", default: false t.boolean "email_on_debate_comment", default: false
t.boolean "email_on_comment_reply", default: false t.boolean "email_on_comment_reply", default: false
@@ -186,4 +196,5 @@ ActiveRecord::Schema.define(version: 20150817150457) do
add_foreign_key "administrators", "users" add_foreign_key "administrators", "users"
add_foreign_key "moderators", "users" add_foreign_key "moderators", "users"
add_foreign_key "organizations", "users"
end end

View File

@@ -43,6 +43,19 @@ FactoryGirl.define do
user user
end end
factory :organization do
user
sequence(:name) { |n| "org#{n}" }
end
factory :verified_organization, parent: :organization do
verified_at { Time.now}
end
factory :rejected_organization, parent: :organization do
rejected_at { Time.now}
end
factory :tag, class: 'ActsAsTaggableOn::Tag' do factory :tag, class: 'ActsAsTaggableOn::Tag' do
sequence(:name) { |n| "Tag #{n} name" } sequence(:name) { |n| "Tag #{n} name" }

View File

@@ -19,6 +19,18 @@ feature 'Account' do
expect(page).to have_selector(avatar('Manuela Colau'), count: 1) expect(page).to have_selector(avatar('Manuela Colau'), count: 1)
end end
scenario 'Show organization' do
create(:organization, user: @user, name: "Manuela Corp")
visit account_path
expect(page).to have_selector("input[value='Manuela Corp']")
expect(page).to_not have_selector("input[value='Manuela']")
expect(page).to_not have_selector("input[value='Colau']")
expect(page).to have_selector(avatar('Manuela Corp'), count: 1)
end
scenario 'Edit' do scenario 'Edit' do
visit account_path visit account_path
@@ -38,6 +50,24 @@ feature 'Account' do
expect(page).to have_selector("input[id='account_email_on_comment_reply'][value='1']") expect(page).to have_selector("input[id='account_email_on_comment_reply'][value='1']")
end end
scenario 'Edit Organization' do
create(:organization, user: @user, name: "Manuela Corp")
visit account_path
fill_in 'account_organization_attributes_name', with: 'Google'
check 'account_email_on_debate_comment'
check 'account_email_on_comment_reply'
click_button 'Save changes'
expect(page).to have_content "Saved"
visit account_path
expect(page).to have_selector("input[value='Google']")
expect(page).to have_selector("input[id='account_email_on_debate_comment'][value='1']")
expect(page).to have_selector("input[id='account_email_on_comment_reply'][value='1']")
end
scenario "Errors on edit" do scenario "Errors on edit" do
visit account_path visit account_path

View File

@@ -0,0 +1,114 @@
require 'rails_helper'
feature 'Moderations::Organizations' do
background do
administrator = create(:user)
create(:administrator, user: administrator)
login_as(administrator)
end
scenario "pending organizations have links to verify and reject" do
organization = create(:organization)
visit admin_organizations_path
expect(page).to have_link('Verify')
expect(page).to have_link('Reject')
click_on 'Verify'
expect(current_path).to eq(admin_organizations_path)
expect(page).to have_content ('Verified')
expect(organization.reload.verified?).to eq(true)
end
scenario "verified organizations have link to reject" do
organization = create(:verified_organization)
visit admin_organizations_path
expect(page).to have_content ('Verified')
expect(page).to_not have_link('Verify')
expect(page).to have_link('Reject')
click_on 'Reject'
expect(current_path).to eq(admin_organizations_path)
expect(page).to have_content ('Rejected')
expect(organization.reload.rejected?).to eq(true)
end
scenario "rejected organizations have link to verify" do
organization = create(:rejected_organization)
visit admin_organizations_path
expect(page).to have_link('Verify')
expect(page).to_not have_link('Reject', exact: true)
click_on 'Verify'
expect(current_path).to eq(admin_organizations_path)
expect(page).to have_content ('Verified')
expect(organization.reload.verified?).to eq(true)
end
scenario "Current filter is properly highlighted" do
visit admin_organizations_path
expect(page).to_not have_link('All')
expect(page).to have_link('Pending')
expect(page).to have_link('Verified')
expect(page).to have_link('Rejected')
visit admin_organizations_path(filter: 'all')
expect(page).to_not have_link('All')
expect(page).to have_link('Pending')
expect(page).to have_link('Verified')
expect(page).to have_link('Rejected')
visit admin_organizations_path(filter: 'pending')
expect(page).to have_link('All')
expect(page).to_not have_link('Pending')
expect(page).to have_link('Verified')
expect(page).to have_link('Rejected')
visit admin_organizations_path(filter: 'verified')
expect(page).to have_link('All')
expect(page).to have_link('Pending')
expect(page).to_not have_link('Verified')
expect(page).to have_link('Rejected')
visit admin_organizations_path(filter: 'rejected')
expect(page).to have_link('All')
expect(page).to have_link('Pending')
expect(page).to have_link('Verified')
expect(page).to_not have_link('Rejected')
end
scenario "Filtering organizations" do
create(:organization, name: "Pending Organization")
create(:rejected_organization, name: "Rejected Organization")
create(:verified_organization, name: "Verified Organization")
visit admin_organizations_path(filter: 'all')
expect(page).to have_content('Pending Organization')
expect(page).to have_content('Rejected Organization')
expect(page).to have_content('Verified Organization')
visit admin_organizations_path(filter: 'pending')
expect(page).to have_content('Pending Organization')
expect(page).to_not have_content('Rejected Organization')
expect(page).to_not have_content('Verified Organization')
visit admin_organizations_path(filter: 'verified')
expect(page).to_not have_content('Pending Organization')
expect(page).to_not have_content('Rejected Organization')
expect(page).to have_content('Verified Organization')
visit admin_organizations_path(filter: 'rejected')
expect(page).to_not have_content('Pending Organization')
expect(page).to have_content('Rejected Organization')
expect(page).to_not have_content('Verified Organization')
end
end

View File

@@ -0,0 +1,45 @@
require 'rails_helper'
feature 'Organizations' do
scenario 'Organizations can be created' do
user = User.organizations.where(email: 'green@peace.com').first
expect(user).to_not be
visit new_organization_registration_path
fill_in 'user_organization_attributes_name', with: 'Greenpeace'
fill_in 'user_email', with: 'green@peace.com'
fill_in 'user_password', with: 'greenpeace'
fill_in 'user_password_confirmation', with: 'greenpeace'
fill_in 'user_captcha', with: correct_captcha_text
click_button 'Sign up'
user = User.organizations.where(email: 'green@peace.com').first
expect(user).to be
expect(user).to be_organization
expect(user.organization).to_not be_verified
end
scenario 'Errors on create' do
visit new_organization_registration_path
click_button 'Sign up'
expect(page).to have_content error_message
end
scenario 'Shared links' do
visit new_user_registration_path
expect(page).to have_link "Sign up as an organization"
visit new_organization_registration_path
expect(page).to have_link "Sign up"
visit new_user_session_path
expect(page).to have_link "Sign up"
expect(page).to_not have_link "Sign up as an organization"
end
end

View File

@@ -46,6 +46,21 @@ describe Ability do
end end
end end
describe "Organization" do
let(:user) { create(:user) }
before(:each) { create(:organization, user: user) }
it { should be_able_to(:show, user) }
it { should be_able_to(:edit, user) }
it { should be_able_to(:index, Debate) }
it { should be_able_to(:show, debate) }
it { should_not be_able_to(:vote, debate) }
it { should be_able_to(:create, Comment) }
it { should_not be_able_to(:vote, Comment) }
end
describe "Moderator" do describe "Moderator" do
let(:user) { create(:user) } let(:user) { create(:user) }
before { create(:moderator, user: user) } before { create(:moderator, user: user) }
@@ -54,6 +69,23 @@ describe Ability do
it { should be_able_to(:show, debate) } it { should be_able_to(:show, debate) }
it { should be_able_to(:vote, debate) } it { should be_able_to(:vote, debate) }
it { should be_able_to(:read, Organization) }
describe "organizations" do
let(:pending_organization) { create(:organization) }
let(:rejected_organization) { create(:rejected_organization) }
let(:verified_organization) { create(:verified_organization) }
it { should be_able_to( :verify, pending_organization) }
it { should be_able_to( :reject, pending_organization) }
it { should_not be_able_to(:verify, verified_organization) }
it { should be_able_to( :reject, verified_organization) }
it { should_not be_able_to(:reject, rejected_organization) }
it { should be_able_to( :verify, rejected_organization) }
end
it { should be_able_to(:hide, comment) } it { should be_able_to(:hide, comment) }
it { should be_able_to(:hide, debate) } it { should be_able_to(:hide, debate) }

View File

@@ -0,0 +1,46 @@
require 'rails_helper'
describe Organization do
subject { create(:organization) }
describe "verified?" do
it "is false when verified_at? is blank" do
expect(subject.verified?).to be false
end
it "is true when verified_at? exists" do
subject.verified_at = Time.now
expect(subject.verified?).to be true
end
it "is false when the organization was verified and then rejected" do
subject.verified_at = Time.now
subject.rejected_at = Time.now + 1
expect(subject.verified?).to be false
end
it "is true when the organization was rejected and then verified" do
subject.rejected_at = Time.now
subject.verified_at = Time.now + 1
expect(subject.verified?).to be true
end
end
describe "rejected?" do
it "is false when rejected_at? is blank" do
expect(subject.rejected?).to be false
end
it "is true when rejected_at? exists" do
subject.rejected_at = Time.now
expect(subject.rejected?).to be true
end
it "is true when the organization was verified and then rejected" do
subject.verified_at = Time.now
subject.rejected_at = Time.now + 1
expect(subject.rejected?).to be true
end
it "is false when the organization was rejected and then verified" do
subject.rejected_at = Time.now
subject.verified_at = Time.now + 1
expect(subject.rejected?).to be false
end
end
end

View File

@@ -107,6 +107,42 @@ describe User do
end end
end end
describe "organization?" do
it "is false when the user is not an organization" do
expect(subject.organization?).to be false
end
describe 'when it is an organization' do
before(:each) { create(:organization, user: subject) }
it "is true when the user is an organization" do
expect(subject.organization?).to be true
end
it "calculates the name using the organization name" do
expect(subject.name).to eq(subject.organization.name)
end
end
end
describe "organization_attributes" do
before(:each) { subject.organization_attributes = {name: 'org'} }
it "triggers the creation of an associated organization" do
expect(subject.organization).to be
expect(subject.organization.name).to eq('org')
end
it "deactivates the validation of first_name and last_name, and activates the validation of organization" do
subject.first_name = nil
subject.last_name = nil
expect(subject).to be_valid
subject.organization.name= nil
expect(subject).to_not be_valid
end
end
describe "official?" do describe "official?" do
it "is false when the user is not an official" do it "is false when the user is not an official" do
expect(subject.official_level).to eq(0) expect(subject.official_level).to eq(0)
@@ -172,7 +208,7 @@ describe User do
describe "self.with_email" do describe "self.with_email" do
it "find users by email" do it "find users by email" do
user1 = create(:user, email: "larry@madrid.es") user1 = create(:user, email: "larry@madrid.es")
user2 = create(:user, email: "bird@madrid.es") create(:user, email: "bird@madrid.es")
search = User.with_email("larry@madrid.es") search = User.with_email("larry@madrid.es")
expect(search.size).to eq(1) expect(search.size).to eq(1)
expect(search.first).to eq(user1) expect(search.first).to eq(user1)