Merge pull request #192 from AyuntamientoMadrid/official-position

Official positions
This commit is contained in:
Raimond Garcia
2015-08-17 18:49:22 +02:00
33 changed files with 551 additions and 74 deletions

View File

@@ -0,0 +1,32 @@
class Admin::OfficialsController < Admin::BaseController
def index
@officials = User.officials.page(params[:page])
end
def search
@users = User.with_email(params[:email]).page(params[:page])
end
def edit
@user = User.find(params[:id])
end
def update
@user = User.find(params[:id])
@user.update(user_params)
redirect_to admin_officials_path, notice: t("admin.officials.flash.official_updated")
end
def destroy
@official = User.officials.find(params[:id])
@official.remove_official_position!
redirect_to admin_officials_path, notice: t("admin.officials.flash.official_destroyed")
end
private
def user_params
params.require(:user).permit(:official_position, :official_level)
end
end

View File

@@ -0,0 +1,17 @@
class Admin::SettingsController < Admin::BaseController
def index
@settings = Setting.all
end
def update
@setting = Setting.find(params[:id])
@setting.update(settings_params)
redirect_to admin_settings_path, notice: t("admin.settings.flash.updated")
end
private
def settings_params
params.require(:setting).permit(:value)
end
end

View File

@@ -1,5 +1,4 @@
class Admin::TagsController < Admin::BaseController
layout 'admin'
before_action :find_tag, only: [:update, :destroy]
respond_to :html, :js

View File

@@ -4,6 +4,14 @@ module AdminHelper
render "/#{namespace}/menu"
end
def official_level_options
options = []
(0..5).each do |i|
options << [[t("admin.officials.level_#{i}"), Setting.value_for("official_level_#{i}_name")].compact.join(': '), i]
end
options
end
private
def namespace

7
app/models/setting.rb Normal file
View File

@@ -0,0 +1,7 @@
class Setting < ActiveRecord::Base
default_scope { order(key: :desc) }
def self.value_for(key)
where(key: key).pluck(:value).first
end
end

View File

@@ -8,6 +8,9 @@ class User < ActiveRecord::Base
validates :first_name, presence: true, unless: :use_nickname?
validates :last_name, presence: true, unless: :use_nickname?
validates :nickname, presence: true, if: :use_nickname?
validates :official_level, inclusion: {in: 0..5}
scope :officials, -> { where("official_level > 0") }
def name
use_nickname? ? nickname : "#{first_name} #{last_name}"
@@ -25,4 +28,21 @@ class User < ActiveRecord::Base
def moderator?
@is_moderator ||= Moderator.where(user_id: id).exists?
end
def official?
official_level && official_level > 0
end
def add_official_position!(position, level)
return if position.blank? || level.blank?
update official_position: position, official_level: level.to_i
end
def remove_official_position!
update official_position: nil, official_level: 0
end
def self.with_email(e)
e.present? ? where(email: e) : none
end
end

View File

@@ -1,5 +1,7 @@
<ul id="admin_menu">
<li><%= link_to t('admin.menu.settings'), admin_settings_path %></li>
<li><%= link_to t('admin.menu.debate_topics'), admin_tags_path %></li>
<li><%= link_to t('admin.menu.hidden_debates'), admin_debates_path %></li>
<li><%= link_to t('admin.menu.hidden_comments'), admin_comments_path %></li>
<li><%= link_to t('admin.menu.officials'), admin_officials_path %></li>
</ul>

View File

@@ -1,14 +1,12 @@
<div class="left">
<h1><%= t("admin.comments.index.title") %></h1>
<h1><%= t("admin.comments.index.title") %></h1>
<ul>
<% @comments.each do |comment| %>
<li id="<%= dom_id(comment) %>">
<%= comment.body %>
<ul>
<% @comments.each do |comment| %>
<li id="<%= dom_id(comment) %>">
<%= comment.body %>
<%= link_to t("admin.actions.restore"), restore_admin_comment_path(comment),
method: :put, data: { confirm: t("admin.actions.confirm") } %>
</li>
<% end %>
</ul>
</div>
<%= link_to t("admin.actions.restore"), restore_admin_comment_path(comment),
method: :put, data: { confirm: t("admin.actions.confirm") } %>
</li>
<% end %>
</ul>

View File

@@ -1,13 +1,11 @@
<div class="left">
<h1><%= t("admin.debates.index.title") %></h1>
<h1><%= t("admin.debates.index.title") %></h1>
<ul>
<% @debates.each do |debate| %>
<%= link_to admin_debate_path(debate) do %>
<li id="<%= dom_id(debate) %>">
<%= link_to debate.title, admin_debate_path(debate) %>
</li>
<% end %>
<ul>
<% @debates.each do |debate| %>
<%= link_to admin_debate_path(debate) do %>
<li id="<%= dom_id(debate) %>">
<%= link_to debate.title, admin_debate_path(debate) %>
</li>
<% end %>
</ul>
</div>
<% end %>
</ul>

View File

@@ -1,13 +1,11 @@
<div class="left">
<h1><%= t("admin.debates.index.title") %></h1>
<h1><%= t("admin.debates.index.title") %></h1>
<div>
<div><%= @debate.title %></div>
<div><%= @debate.description %></div>
<div>
<div><%= @debate.title %></div>
<div><%= @debate.description %></div>
<div>
<%= link_to t('admin.actions.restore'), restore_admin_debate_path(@debate),
method: :put, data: { confirm: t('admin.actions.confirm') } %>
</div>
<%= link_to t('admin.actions.restore'), restore_admin_debate_path(@debate),
method: :put, data: { confirm: t('admin.actions.confirm') } %>
</div>
</div>

View File

@@ -0,0 +1,14 @@
<h1><%= t("admin.officials.edit.title") %></h1>
<%= @user.name %> (<%= @user.email %>)
<%= form_for @user, url: admin_official_path(@user) do |f| %>
<%= f.text_field :official_position %>
<%= f.select :official_level, official_level_options %>
<%= f.submit %>
<% if @user.official? %>
<%= link_to t("admin.officials.edit.destroy"), admin_official_path(@user), method: :delete, class: 'button tiny alert' %>
<% else %>
<%= link_to t("admin.officials.edit.cancel"), admin_officials_path, class: 'button tiny alert' %>
<% end %>
<% end %>

View File

@@ -0,0 +1,25 @@
<h1><%= t("admin.officials.index.title") %></h1>
<div>
<%= form_for(User.new, url: search_admin_officials_path, as: :user, method: :get) do |f| %>
<%= text_field_tag :email, "", label: false, placeholder: t("admin.officials.index.search_email_placeholder") %>
<%= f.submit t("admin.officials.index.search") %>
<% end %>
</div>
<div>
<%= page_entries_info @officials %>
</div>
<div>
<% @officials.each do |official| %>
<%= link_to official.name, edit_admin_official_path(official) %>
<%= official.official_position %>
<%= t("admin.officials.level_#{official.official_level}") %>
<br/><br/>
<% end %>
</div>
<div>
<%= paginate @officials %>
</div>

View File

@@ -0,0 +1,21 @@
<h1><%= t("admin.officials.search.title") %></h1>
<div>
<%= form_for(User.new, url: search_admin_officials_path, as: :user, method: :get) do |f| %>
<%= text_field_tag :email, "", label: false, placeholder: t("admin.officials.index.search_email_placeholder") %>
<%= f.submit t("admin.officials.search.search") %>
<% end %>
</div>
<div>
<%= page_entries_info @users %>
</div>
<div>
<% @users.each do |user| %>
<%= link_to user.name, edit_admin_official_path(user) %>
<%= user.official_position %>
<%= t("admin.officials.level_#{user.official_level}") %>
<%= link_to user.official? ? t("admin.officials.search.edit_official") : t("admin.officials.search.make_official"), edit_admin_official_path(user) %>
<% end %>
</div>

View File

@@ -0,0 +1,15 @@
<h1><%= t("admin.settings.index.title") %></h1>
<ul>
<% @settings.each do |setting| %>
<li>
<strong><%= setting.key.classify %></strong>
<%= form_for(setting, url: admin_setting_path(setting), html: { id: "edit_#{dom_id(setting)}"}) do |f| %>
<%= f.text_field :value, label: false, id: dom_id(setting) %>
<%= f.submit(class: "button radius tiny") %>
<% end %>
</li>
<% end %>
</ul>

View File

@@ -1,29 +1,26 @@
<div class="left">
<h1><%= t("admin.tags.index.add_tag") %></h1>
<h1><%= t("admin.tags.index.add_tag") %></h1>
<%= form_for(@tag, url: admin_tags_path, as: :tag) do |f| %>
<%= f.text_field :name, placeholder: t("admin.tags.name.placeholder") %>
<%= f.check_box :featured, label: false %>
<%= t("admin.tags.mark_as_featured") %>
<%= f.submit(class: "button radius small") %>
<%= form_for(@tag, url: admin_tags_path, as: :tag) do |f| %>
<%= f.text_field :name, placeholder: t("admin.tags.name.placeholder") %>
<%= f.check_box :featured, label: false %>
<%= t("admin.tags.mark_as_featured") %>
<%= f.submit(class: "button radius small") %>
<% end %>
<h1><%= t("admin.tags.index.title") %></h1>
<ul>
<% @tags.each do |tag| %>
<li>
<strong><%= tag.name %></strong>
<%= form_for(tag, url: admin_tag_path(tag), as: :tag, html: { id: "edit_tag_#{tag.id}"}) do |f| %>
<%= f.check_box :featured, label: false, id: "tag_featured_#{tag.id}" %>
<%= t("admin.tags.mark_as_featured") %>
<%= f.submit(class: "button radius tiny") %>
<%= link_to t("admin.tags.destroy"), admin_tag_path(tag), method: :delete, class: 'button tiny alert' %>
<% end %>
</li>
<% end %>
<h1><%= t("admin.tags.index.title") %></h1>
<ul>
<% @tags.each do |tag| %>
<li>
<strong><%= tag.name %></strong>
<%= form_for(tag, url: admin_tag_path(tag), as: :tag, html: { id: "edit_tag_#{tag.id}"}) do |f| %>
<%= f.check_box :featured, label: false, id: "tag_featured_#{tag.id}" %>
<%= t("admin.tags.mark_as_featured") %>
<%= f.submit(class: "button radius tiny") %>
<%= link_to t("admin.tags.destroy"), admin_tag_path(tag), method: :delete, class: 'button tiny alert' %>
<% end %>
</li>
<% end %>
</ul>
</div>
</ul>

View File

@@ -27,7 +27,10 @@
<%= side_menu %>
</div>
<%= yield %>
<div class="left">
<%= yield %>
</div>
</body>
</html>

View File

@@ -93,14 +93,13 @@ ignore_missing:
## Consider these keys used:
ignore_unused:
- 'activerecord.*'
- 'unauthorized.*'
- 'simple_captcha.*'
- 'admin.officials.level_*'
# - '{devise,kaminari,will_paginate}.*'
# - 'simple_form.{yes,no}'
# - 'simple_form.{placeholders,hints,labels}.*'
# - 'simple_form.{error_notification,required}.:'
ignore_unused:
- 'unauthorized.*'
- 'simple_captcha.*'
## Exclude these keys from the `i18n-tasks eq-base' report:
# ignore_eq_base:

View File

@@ -0,0 +1,10 @@
Kaminari.configure do |config|
# config.default_per_page = 25
# config.max_per_page = nil
# config.window = 4
# config.outer_window = 0
# config.left = 0
# config.right = 0
# config.page_method_name = :page
# config.param_name = :page
end

View File

@@ -21,3 +21,5 @@ en:
last_name: "Last name"
nickname: Nickname
password: Password
official_position: Official position
official_level: Official level

View File

@@ -21,3 +21,5 @@ es:
last_name: Apellidos
nickname: Pseudónimo
password: Contraseña
official_position: Cargo público
official_level: Nivel del cargo

View File

@@ -1,12 +1,19 @@
en:
admin:
settings:
index:
title: Global settings
flash:
updated: 'Setting updated!'
dashboard:
index:
title: Administration
menu:
settings: Global settings
debate_topics: Debate topics
hidden_debates: Hidden debates
hidden_comments: Hidden comments
officials: Officials
actions:
hide: Hide
restore: Restore
@@ -29,4 +36,26 @@ en:
title: Hidden debates
restore:
success: The debate has been restored
officials:
level_0: Level 0
level_1: Level 1
level_2: Level 2
level_3: Level 3
level_4: Level 4
level_5: Level 5
index:
title: Officials
search_email_placeholder: 'Search user by email'
search: Search
search:
title: 'Officials: Search users'
edit_official: Edit official
make_official: Make this user an official
search: Search
edit:
title: 'Officials: edit user'
destroy: "Remove 'Official' condition"
cancel: "Cancel"
flash:
official_updated: 'Official position saved!'
official_destroyed: 'User is not an official anymore'

View File

@@ -1,12 +1,19 @@
es:
admin:
settings:
index:
title: Configuración global
flash:
updated: 'Valor actualizado'
dashboard:
index:
title: Administración
menu:
settings: Configuración global
debate_topics: Temas de debate
hidden_debates: Debates ocultos
hidden_comments: Comentarios ocultos
officials: Cargos públicos
actions:
hide: Ocultar
restore: Permitir
@@ -29,3 +36,26 @@ es:
title: Debates ocultos
restore:
success: El debate ha sido permitido
officials:
level_0: Nivel 0
level_1: Nivel 1
level_2: Nivel 2
level_3: Nivel 3
level_4: Nivel 4
level_5: Nivel 5
index:
title: Cargos Públicos
search_email_placeholder: 'Buscar usuario por email'
search: Buscar
search:
title: 'Cargos Públicos: Búsqueda de usuarios'
edit_official: Editar cargo público
make_official: Convertir en cargo público
search: Buscar
edit:
title: 'Cargos Públicos: Editar usuario'
destroy: "Eliminar condición de 'Cargo Público'"
cancel: "Cancelar"
flash:
official_updated: 'Datos del cargo público guardados'
official_destroyed: 'Datos guardados: el usuario ya no es cargo público'

View File

@@ -0,0 +1,17 @@
en:
views:
pagination:
first: "&laquo; First"
last: "Last &raquo;"
previous: "&lsaquo; Prev"
next: "Next &rsaquo;"
truncate: "&hellip;"
helpers:
page_entries_info:
one_page:
display_entries:
zero: "No %{entry_name} found"
one: "Displaying <b>1</b> %{entry_name}"
other: "Displaying <b>all %{count}</b> %{entry_name}"
more_pages:
display_entries: "Displaying %{entry_name} <b>%{first}&nbsp;-&nbsp;%{last}</b> of <b>%{total}</b> in total"

View File

@@ -0,0 +1,17 @@
es:
views:
pagination:
first: "&laquo; Primera"
last: "Última &raquo;"
previous: "&lsaquo; Anterior"
next: "Siguiente &rsaquo;"
truncate: "&hellip;"
helpers:
page_entries_info:
one_page:
display_entries:
zero: "No se han encontrado %{entry_name}"
one: "Encontrado <b>1</b> %{entry_name}"
other: "Encontrados <b> %{count}</b> %{entry_name}"
more_pages:
display_entries: "Se muestran <b> del %{first}&nbsp;al&nbsp;%{last}</b> de un total de <b>%{total}</b> %{entry_name}"

View File

@@ -29,6 +29,11 @@ Rails.application.routes.draw do
end
resources :tags, only: [:index, :create, :update, :destroy]
resources :officials, only: [:index, :edit, :update, :destroy] do
collection { get :search}
end
resources :settings, only: [:index, :update]
end
namespace :moderation do

View File

@@ -0,0 +1,6 @@
class AddOfficialPositionToUser < ActiveRecord::Migration
def change
add_column :users, :official_position, :string
add_column :users, :official_level, :integer, default: 0
end
end

View File

@@ -0,0 +1,8 @@
class AddSettings < ActiveRecord::Migration
def change
create_table :settings do |t|
t.string :key
t.string :value
end
end
end

View File

@@ -11,7 +11,8 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150815154430) do
ActiveRecord::Schema.define(version: 20150817150457) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -58,6 +59,11 @@ ActiveRecord::Schema.define(version: 20150815154430) do
add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree
create_table "settings", force: :cascade do |t|
t.string "key"
t.string "value"
end
create_table "simple_captcha_data", force: :cascade do |t|
t.string "key", limit: 40
t.string "value", limit: 6
@@ -111,6 +117,8 @@ ActiveRecord::Schema.define(version: 20150815154430) do
t.boolean "use_nickname", default: false, null: false
t.boolean "email_on_debate_comment", default: false
t.boolean "email_on_comment_reply", default: false
t.string "official_position"
t.integer "official_level", default: 0
end
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree

View File

@@ -1,7 +1,8 @@
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)
# Names for the moderation console, as a hint for moderators
# to know better how to assign users with official positions
Setting.create(key: 'official_level_0_name', value: 'No cargo público')
Setting.create(key: 'official_level_1_name', value: 'Organización Municipal')
Setting.create(key: 'official_level_2_name', value: 'Funcionariado')
Setting.create(key: 'official_level_3_name', value: 'Directores generales')
Setting.create(key: 'official_level_4_name', value: 'Concejales')
Setting.create(key: 'official_level_5_name', value: 'Alcaldes')

View File

@@ -55,4 +55,9 @@ FactoryGirl.define do
end
end
factory :setting do
sequence(:key) { |n| "setting key number #{n}" }
sequence(:value) { |n| "setting number #{n} value" }
end
end

View File

@@ -0,0 +1,78 @@
require 'rails_helper'
feature 'Admin officials' do
background do
@citizen = create(:user, first_name: "Citizen", last_name: "Kane")
@official = create(:user, official_position: "Mayor", official_level: 5)
@admin = create(:administrator)
login_as(@admin.user)
end
scenario 'Index' do
visit admin_officials_path
expect(page).to have_content @official.name
expect(page).to_not have_content @citizen.name
expect(page).to have_content @official.official_position
expect(page).to have_content @official.official_level
end
scenario 'Edit an official' do
visit admin_officials_path
click_link @official.name
expect(current_path).to eq(edit_admin_official_path(@official))
expect(page).to_not have_content @citizen.name
expect(page).to have_content @official.name
expect(page).to have_content @official.email
fill_in 'user_official_position', with: 'School Teacher'
select '3', from: 'user_official_level'
click_button 'Update User'
expect(page).to have_content 'Official position saved!'
visit admin_officials_path
expect(page).to have_content @official.name
expect(page).to have_content 'School Teacher'
expect(page).to have_content '3'
end
scenario 'Create an official' do
visit admin_officials_path
fill_in 'email', with: @citizen.email
click_button 'Search'
expect(current_path).to eq(search_admin_officials_path)
expect(page).to_not have_content @official.name
click_link @citizen.name
fill_in 'user_official_position', with: 'Hospital manager'
select '4', from: 'user_official_level'
click_button 'Update User'
expect(page).to have_content 'Official position saved!'
visit admin_officials_path
expect(page).to have_content @official.name
expect(page).to have_content @citizen.name
expect(page).to have_content 'Hospital manager'
expect(page).to have_content '4'
end
scenario 'Destroy' do
visit edit_admin_official_path(@official)
click_link "Remove 'Official' condition"
expect(page).to have_content 'User is not an official anymore'
expect(current_path).to eq(admin_officials_path)
expect(page).to_not have_content @citizen.name
expect(page).to_not have_content @official.name
end
end

View File

@@ -0,0 +1,30 @@
require 'rails_helper'
feature 'Admin settings' do
background do
@setting1 = create(:setting)
@setting2 = create(:setting)
@setting3 = create(:setting)
login_as(create(:administrator).user)
end
scenario 'Index' do
visit admin_settings_path
expect(page).to have_content @setting1.key.classify
expect(page).to have_content @setting2.key.classify
expect(page).to have_content @setting3.key.classify
end
scenario 'Update' do
visit admin_settings_path
within("#edit_setting_#{@setting2.id}") do
fill_in "setting_#{@setting2.id}", with: 'Super Users of level 2'
click_button 'Update Setting'
end
expect(page).to have_content 'Setting updated!'
end
end

View File

@@ -107,4 +107,80 @@ describe User do
end
end
describe "official?" do
it "is false when the user is not an official" do
expect(subject.official_level).to eq(0)
expect(subject.official?).to be false
end
it "is true when the user is an official" do
subject.official_level = 3
subject.save
expect(subject.official?).to be true
end
end
describe "add_official_position!" do
it "is false when level not valid" do
expect(subject.add_official_position!("Boss", 89)).to be false
end
it "updates official position fields" do
expect(subject).not_to be_official
subject.add_official_position!("Veterinarian", 2)
expect(subject).to be_official
expect(subject.official_position).to eq("Veterinarian")
expect(subject.official_level).to eq(2)
subject.add_official_position!("Brain surgeon", 3)
expect(subject.official_position).to eq("Brain surgeon")
expect(subject.official_level).to eq(3)
end
end
describe "remove_official_position!" do
it "updates official position fields" do
subject.add_official_position!("Brain surgeon", 3)
expect(subject).to be_official
subject.remove_official_position!
expect(subject).not_to be_official
expect(subject.official_position).to be_nil
expect(subject.official_level).to eq(0)
end
end
describe "officials scope" do
it "returns only users with official positions" do
create(:user, official_position: "Mayor", official_level: 1)
create(:user, official_position: "Director", official_level: 3)
create(:user, official_position: "Math Teacher", official_level: 4)
create(:user, official_position: "Manager", official_level: 5)
2.times { create(:user) }
officials = User.officials
expect(officials.size).to eq(4)
officials.each do |user|
expect(user.official_level).to be > 0
expect(user.official_position).to be_present
end
end
end
describe "self.with_email" do
it "find users by email" do
user1 = create(:user, email: "larry@madrid.es")
user2 = create(:user, email: "bird@madrid.es")
search = User.with_email("larry@madrid.es")
expect(search.size).to eq(1)
expect(search.first).to eq(user1)
end
it "returns no results if no email provided" do
expect(User.with_email(" ").size).to eq(0)
end
end
end