From 104900d343ddf09fa3fc8f57a43e125d6e879fa9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Fri, 11 Sep 2015 19:24:56 +0200 Subject: [PATCH 001/138] adds proposal model --- app/models/proposal.rb | 57 ++++++++++++++++++++ db/migrate/20150911171301_create_proposal.rb | 22 ++++++++ db/schema.rb | 24 +++++++-- 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 app/models/proposal.rb create mode 100644 db/migrate/20150911171301_create_proposal.rb diff --git a/app/models/proposal.rb b/app/models/proposal.rb new file mode 100644 index 000000000..2cf86115d --- /dev/null +++ b/app/models/proposal.rb @@ -0,0 +1,57 @@ +class Proposal < ActiveRecord::Base + apply_simple_captcha + + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + has_many :comments, as: :commentable + has_many :flags, as: :flaggable + + validates :title, presence: true + validates :description, presence: true + validates :author, presence: true + validates :question, presence: true + + validate :validate_title_length + validate :validate_description_length + + validates :terms_of_service, acceptance: { allow_nil: false }, on: :create + + before_validation :sanitize_description + before_validation :sanitize_tag_list + + def self.title_max_length + @@title_max_length ||= self.columns.find { |c| c.name == 'title' }.limit || 80 + end + + def self.description_max_length + 6000 + end + + protected + + def sanitize_description + self.description = WYSIWYGSanitizer.new.sanitize(description) + end + + def sanitize_tag_list + self.tag_list = TagSanitizer.new.sanitize_tag_list(self.tag_list) + end + + private + + def validate_description_length + validator = ActiveModel::Validations::LengthValidator.new( + attributes: :description, + minimum: 10, + maximum: Proposal.description_max_length) + validator.validate(self) + end + + def validate_title_length + validator = ActiveModel::Validations::LengthValidator.new( + attributes: :title, + minimum: 4, + maximum: Proposal.title_max_length) + validator.validate(self) + end + +end diff --git a/db/migrate/20150911171301_create_proposal.rb b/db/migrate/20150911171301_create_proposal.rb new file mode 100644 index 000000000..37dd1bb85 --- /dev/null +++ b/db/migrate/20150911171301_create_proposal.rb @@ -0,0 +1,22 @@ +class CreateProposal < ActiveRecord::Migration + def change + create_table :proposals do |t| + t.string "title", limit: 80 + t.text "description" + t.string "question" + t.string "external_url" + t.integer "author_id" + t.datetime "hidden_at" + t.integer "flags_count", default: 0 + t.datetime "ignored_flag_at" + t.integer "cached_votes_up", default: 0 + t.integer "comments_count", default: 0 + t.datetime "confirmed_hide_at" + t.integer "hot_score", limit: 8, default: 0 + t.integer "confidence_score", default: 0 + + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index fde03fe30..fa0b2c62d 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150910152734) do +ActiveRecord::Schema.define(version: 20150911171301) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -165,7 +165,7 @@ ActiveRecord::Schema.define(version: 20150910152734) do create_table "locks", force: :cascade do |t| t.integer "user_id" t.integer "tries", default: 0 - t.datetime "locked_until", default: '2015-09-10 13:46:11', null: false + t.datetime "locked_until", default: '2015-09-11 17:24:30', null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -187,6 +187,24 @@ ActiveRecord::Schema.define(version: 20150910152734) do add_index "organizations", ["user_id"], name: "index_organizations_on_user_id", using: :btree + create_table "proposals", force: :cascade do |t| + t.string "title", limit: 80 + t.text "description" + t.string "question" + t.string "external_url" + t.integer "author_id" + t.datetime "hidden_at" + t.integer "flags_count", default: 0 + t.datetime "ignored_flag_at" + t.integer "cached_votes_up", default: 0 + t.integer "comments_count", default: 0 + t.datetime "confirmed_hide_at" + t.integer "hot_score", limit: 8, default: 0 + t.integer "confidence_score", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + create_table "settings", force: :cascade do |t| t.string "key" t.string "value" @@ -246,7 +264,7 @@ ActiveRecord::Schema.define(version: 20150910152734) do t.integer "official_level", default: 0 t.datetime "hidden_at" t.string "sms_confirmation_code" - t.string "username", limit: 60 + t.string "username", limit: 60 t.string "document_number" t.string "document_type" t.datetime "residence_verified_at" From 3f7338a6ae5f5e167891a52a5796774ba21813be Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sat, 12 Sep 2015 11:38:22 +0200 Subject: [PATCH 002/138] adds proposal new and create --- app/controllers/proposals_controller.rb | 38 +++++++++++++++++ app/models/ability.rb | 6 +++ app/models/proposal.rb | 17 +++++++- app/views/proposals/_form.html.erb | 54 +++++++++++++++++++++++++ app/views/proposals/new.html.erb | 28 +++++++++++++ config/locales/activerecord.en.yml | 6 +++ config/locales/activerecord.es.yml | 6 +++ config/locales/en.yml | 25 ++++++++++++ config/locales/es.yml | 25 ++++++++++++ config/routes.rb | 2 + 10 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 app/controllers/proposals_controller.rb create mode 100644 app/views/proposals/_form.html.erb create mode 100644 app/views/proposals/new.html.erb diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb new file mode 100644 index 000000000..efad5783e --- /dev/null +++ b/app/controllers/proposals_controller.rb @@ -0,0 +1,38 @@ +class ProposalsController < ApplicationController + before_action :authenticate_user!, except: [:index, :show] + + load_and_authorize_resource + respond_to :html, :js + + def show + render text: "" + end + + def new + @proposal = Proposal.new + load_featured_tags + end + + def create + @proposal = Proposal.new(proposal_params) + @proposal.author = current_user + + if @proposal.save_with_captcha + ahoy.track :proposal_created, proposal_id: @proposal.id + redirect_to @proposal, notice: t('flash.actions.create.notice', resource_name: 'proposal') + else + load_featured_tags + render :new + end + end + + private + + def proposal_params + params.require(:proposal).permit(:title, :question, :description, :tag_list, :terms_of_service, :captcha, :captcha_key) + end + + def load_featured_tags + @featured_tags = ActsAsTaggableOn::Tag.where(featured: true) + end +end \ No newline at end of file diff --git a/app/models/ability.rb b/app/models/ability.rb index 0d2ac3f8b..8249197e5 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -19,8 +19,14 @@ class Ability debate.editable_by?(user) end + can :read, Proposal + can :update, Proposal do |proposal| + proposal.editable_by?(user) + end + can :create, Comment can :create, Debate + can :create, Proposal can [:flag, :unflag], Comment cannot [:flag, :unflag], Comment, user_id: user.id diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 2cf86115d..e956e64e8 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -5,12 +5,15 @@ class Proposal < ActiveRecord::Base has_many :comments, as: :commentable has_many :flags, as: :flaggable + acts_as_taggable + validates :title, presence: true + validates :question, presence: true validates :description, presence: true validates :author, presence: true - validates :question, presence: true validate :validate_title_length + validate :validate_question_length validate :validate_description_length validates :terms_of_service, acceptance: { allow_nil: false }, on: :create @@ -22,6 +25,10 @@ class Proposal < ActiveRecord::Base @@title_max_length ||= self.columns.find { |c| c.name == 'title' }.limit || 80 end + def self.question_max_length + 140 + end + def self.description_max_length 6000 end @@ -54,4 +61,12 @@ class Proposal < ActiveRecord::Base validator.validate(self) end + def validate_question_length + validator = ActiveModel::Validations::LengthValidator.new( + attributes: :title, + minimum: 10, + maximum: Proposal.question_max_length) + validator.validate(self) + end + end diff --git a/app/views/proposals/_form.html.erb b/app/views/proposals/_form.html.erb new file mode 100644 index 000000000..8d02b4a75 --- /dev/null +++ b/app/views/proposals/_form.html.erb @@ -0,0 +1,54 @@ +<%= form_for(@proposal) do |f| %> + <%= render 'shared/errors', resource: @proposal %> + +
+
+ <%= f.label :title, t("proposals.form.proposal_title") %> + <%= f.text_field :title, maxlength: Proposal.title_max_length, placeholder: t("proposals.form.proposal_title"), label: false %> +
+ +
+ <%= f.label :question, t("proposals.form.proposal_question") %> + <%= f.text_field :question, maxlength: Proposal.question_max_length, placeholder: t("proposals.form.proposal_question"), label: false %> +
+ +
+ <%= f.label :description, t("proposals.form.proposal_text") %> + <%= f.cktext_area :description, maxlength: Proposal.description_max_length, ckeditor: { language: I18n.locale }, label: false %> +
+ +
+ <%= f.label :tag_list, t("proposals.form.tags_label") %> + <%= t("proposals.form.tags_instructions") %> + + <% @featured_tags.each do |tag| %> + <%= tag.name %> + <% end %> + + <%= f.text_field :tag_list, value: @proposal.tag_list.to_s, label: false, placeholder: t("proposals.form.tags_placeholder") %> +
+ +
+ <% if @proposal.new_record? %> + <%= f.label :terms_of_service do %> + <%= f.check_box :terms_of_service, label: false %> + + <%= t("form.accept_terms", + policy: link_to(t("form.policy"), "/privacy", target: "blank"), + conditions: link_to(t("form.conditions"), "/conditions", target: "blank")).html_safe %> + + <% end %> + <% end %> +
+ +
+ <%= f.simple_captcha input_html: { required: false } %> +
+ +
+ <%= f.submit(class: "button radius", value: t("proposals.#{action_name}.form.submit_button")) %> +
+
+<% end %> + + diff --git a/app/views/proposals/new.html.erb b/app/views/proposals/new.html.erb new file mode 100644 index 000000000..f478598b0 --- /dev/null +++ b/app/views/proposals/new.html.erb @@ -0,0 +1,28 @@ +
+ +
+ <%= link_to proposals_path, class: "left back" do %> + + <%= t("proposals.new.back_link") %> + <% end %> +

<%= t("proposals.new.start_new") %>

+
+ <%= t("proposals.new.info")%> + <%= link_to "/more_information", target: "_blank" do %> + <%= t("proposals.new.more_info")%> + <% end %> +
+ <%= render "form" %> +
+ +
+ +

<%= t("proposals.new.recommendations_title") %>

+
    +
  • <%= t("proposals.new.recommendation_one") %>
  • +
  • <%= t("proposals.new.recommendation_two") %>
  • +
  • <%= t("proposals.new.recommendation_three") %>
  • +
  • <%= t("proposals.new.recommendation_four") %>
  • +
+
+
diff --git a/config/locales/activerecord.en.yml b/config/locales/activerecord.en.yml index 94b1fa0c9..bedaf93a4 100644 --- a/config/locales/activerecord.en.yml +++ b/config/locales/activerecord.en.yml @@ -16,6 +16,12 @@ en: description: Opinion terms_of_service: Terms of service title: Title + proposal: + author: Author + title: Title + question: Question + description: Description + terms_of_service: Terms of service user: email: Email username: Username diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index f566026de..e587a1f65 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -16,6 +16,12 @@ es: description: Opinión terms_of_service: Términos de servicio title: Título + propuesta: + author: Autor + title: Título + question: Pregunta + description: Descripción + terms_of_service: Términos de servicio user: email: Correo electrónico username: Nombre de usuario diff --git a/config/locales/en.yml b/config/locales/en.yml index 98afaeb4f..e4b5e64b9 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -56,6 +56,7 @@ en: conditions: "Terms of use" user: account debate: debate + proposal: proposal verification::sms: phone application: alert: @@ -144,6 +145,30 @@ en: update: form: submit_button: "Save changes" + debates: + new: + start_new: Start a proposal + info: "A proposal is a discussion forum, not a proposal. And soon... we opened the section of citizen proposals." + more_info: "More info" + back_link: Back + recommendations_title: Tips for creating a proposal + recommendation_one: "Do not write the title of the debate or whole sentences in capital. On the Internet this is considered shouting. And nobody likes to scream." + recommendation_two: "Any discussion or comment that involves an illegal act will be eliminated, also intending to sabotage the debate spaces, everything else is permitted." + recommendation_three: "The harsh criticism are very welcome. This is a space of thought but we recommend preserving the elegance and intelligence. The world is better with them present." + recommendation_four: "Enjoy this space, voices that fill it, it is yours too." + form: + submit_button: "Start a proposal" + form: + proposal_title: Proposal title + proposal_question: Proposal question + proposal_text: Initial text for proposal + tags_label: Topics + tags_instructions: > + Tag this proposal. You can choose among our proposals on the list or add any other topic you want. + tags_placeholder: "Add topics writing them separated by ','" + create: + form: + submit_button: "Start a proposal" comments: form: leave_comment: Write a comment diff --git a/config/locales/es.yml b/config/locales/es.yml index d2b51d551..e05eb48f9 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -56,6 +56,7 @@ es: conditions: "Condiciones de uso" user: la cuenta debate: el debate + proposal: la propuesta verification::sms: el teléfono application: alert: @@ -144,6 +145,30 @@ es: update: form: submit_button: "Guardar cambios" + proposals: + new: + start_new: Empezar una propuesta + info: "Una propuesta es un foro de discusión, no una propuesta ciudadana. Muy pronto abriremos la sección de propuestas ciudadanas, donde cualquiera podrá presentar propuestas y, si reciben apoyo, serán puestas en marcha por el Ayuntamiento." + more_info: "Más información" + back_link: Volver + recommendations_title: Recomendaciones para crear una propuesta + recommendation_one: "No escribas el título del debate o frases enteras en mayúsculas. En internet eso se considera gritar. Y a nadie le gusta que le griten." + recommendation_two: "Cualquier debate o comentario que implique una acción ilegal será eliminado, también los que tengan la intención de sabotear los espacios de debate, todo lo demás está permitido." + recommendation_three: "Las críticas despiadadas son muy bienvenidas. Este es un espacio de pensamiento. Pero te recomendamos conservar la elegancia y la inteligencia. El mundo es mejor con ellas presentes." + recommendation_four: "Disfruta de este espacio, de las voces que lo llenan, también es tuyo." + form: + submit_button: "Empieza una propuesta" + form: + proposal_title: Título de la propuesta + proposal_question: Pregunta de la propuesta + proposal_text: Texto inicial de la propuesta + tags_label: Temas + tags_instructions: > + Etiqueta este la propuesta. Puedes elegir entre nuestras propuestas o introducir las que desees. + tags_placeholder: "Escribe las etiquetas que desees separadas por ','" + create: + form: + submit_button: "Empieza una propuesta" comments: form: leave_comment: Deja tu comentario diff --git a/config/routes.rb b/config/routes.rb index e434d71e8..7246c0807 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -45,6 +45,8 @@ Rails.application.routes.draw do end end + resources :proposals + resource :account, controller: "account", only: [:show, :update] resource :verification, controller: "verification", only: [:show] From 6188e60b5ed7518f2c414d8f3cca7a005e6329d7 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 12:18:23 +0200 Subject: [PATCH 003/138] adds proposal factory --- spec/factories.rb | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/spec/factories.rb b/spec/factories.rb index ff3f60540..be2374eca 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -101,6 +101,48 @@ FactoryGirl.define do end end + factory :proposal do + sequence(:title) { |n| "Proposal #{n} title" } + description 'Proposal description' + question 'Proposal question' + external_url 'http://decide.madrid.es' + terms_of_service '1' + association :author, factory: :user + + trait :hidden do + hidden_at Time.now + end + + trait :with_ignored_flag do + ignored_flag_at Time.now + end + + trait :with_confirmed_hide do + confirmed_hide_at Time.now + end + + trait :flagged do + after :create do |debate| + Flag.flag(FactoryGirl.create(:user), debate) + end + end + + trait :with_hot_score do + before(:save) { |d| d.calculate_hot_score } + end + + trait :with_confidence_score do + before(:save) { |d| d.calculate_confidence_score } + end + + trait :conflictive do + after :create do |debate| + Flag.flag(FactoryGirl.create(:user), debate) + 4.times { create(:vote, votable: debate) } + end + end + end + factory :vote do association :votable, factory: :debate association :voter, factory: :user From f2b917d486617f08e9f5239d7e53dd10159620b4 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sat, 12 Sep 2015 12:30:20 +0200 Subject: [PATCH 004/138] adds proposal show --- app/controllers/application_controller.rb | 4 ++ app/controllers/proposals_controller.rb | 8 ++- app/helpers/votes_helper.rb | 9 +++ app/models/proposal.rb | 38 +++++++++- app/models/user.rb | 5 ++ app/views/proposals/_actions.html.erb | 10 +++ app/views/proposals/_comments.html.erb | 27 +++++++ app/views/proposals/_flag_actions.html.erb | 21 ++++++ app/views/proposals/_votes.html.erb | 37 ++++++++++ app/views/proposals/show.html.erb | 83 ++++++++++++++++++++++ app/views/shared/_tags.html.erb | 12 ++-- config/initializers/vote_extensions.rb | 4 ++ config/routes.rb | 16 ++++- 13 files changed, 265 insertions(+), 9 deletions(-) create mode 100644 app/views/proposals/_actions.html.erb create mode 100644 app/views/proposals/_comments.html.erb create mode 100644 app/views/proposals/_flag_actions.html.erb create mode 100644 app/views/proposals/_votes.html.erb create mode 100644 app/views/proposals/show.html.erb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index aa6da3a80..3ba96f0c8 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -78,6 +78,10 @@ class ApplicationController < ActionController::Base @debate_votes = current_user ? current_user.debate_votes(debates) : {} end + def set_proposal_votes(proposals) + @proposal_votes = current_user ? current_user.proposal_votes(proposals) : {} + end + def set_comment_flags(comments) @comment_flags = current_user ? current_user.comment_flags(comments) : {} end diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index efad5783e..c788d3ba9 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -5,7 +5,13 @@ class ProposalsController < ApplicationController respond_to :html, :js def show - render text: "" + set_proposal_votes(@proposal) + @commentable = @proposal + @root_comments = @proposal.comments.roots.recent.page(params[:page]).per(10).for_render + @comments = @root_comments.inject([]){|all, root| all + Comment.descendants_of(root).for_render} + + @all_visible_comments = @root_comments + @comments + set_comment_flags(@all_visible_comments) end def new diff --git a/app/helpers/votes_helper.rb b/app/helpers/votes_helper.rb index dfc1bbda0..fa25fcca7 100644 --- a/app/helpers/votes_helper.rb +++ b/app/helpers/votes_helper.rb @@ -11,4 +11,13 @@ module VotesHelper end end + def css_classes_for_proposal_vote(proposal_votes, proposal) + case proposal_votes[proposal.id] + when true + {in_favor: "voted"} + else + {in_favor: ""} + end + end + end diff --git a/app/models/proposal.rb b/app/models/proposal.rb index e956e64e8..3892686af 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -1,12 +1,12 @@ class Proposal < ActiveRecord::Base apply_simple_captcha + acts_as_votable + acts_as_taggable belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' has_many :comments, as: :commentable has_many :flags, as: :flaggable - acts_as_taggable - validates :title, presence: true validates :question, presence: true validates :description, presence: true @@ -21,6 +21,28 @@ class Proposal < ActiveRecord::Base before_validation :sanitize_description before_validation :sanitize_tag_list + def total_votes + cached_votes_up + end + + def conflictive? + return false unless flags_count > 0 && cached_votes_up > 0 + cached_votes_up/flags_count.to_f < 5 + end + + def tag_list_with_limit(limit = nil) + return tags if limit.blank? + + tags.sort{|a,b| b.taggings_count <=> a.taggings_count}[0, limit] + end + + def tags_count_out_of_limit(limit = nil) + return 0 unless limit + + count = tags.size - limit + count < 0 ? 0 : count + end + def self.title_max_length @@title_max_length ||= self.columns.find { |c| c.name == 'title' }.limit || 80 end @@ -33,6 +55,18 @@ class Proposal < ActiveRecord::Base 6000 end + def editable? + total_votes <= 1000 + end + + def editable_by?(user) + editable? && author == user + end + + def votable_by?(user) + user.level_two_verified? || !user.voted_for?(self) + end + protected def sanitize_description diff --git a/app/models/user.rb b/app/models/user.rb index 3ddf4df54..4bbd989f4 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -86,6 +86,11 @@ class User < ActiveRecord::Base voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } end + def proposal_votes(proposals) + voted = votes.for_proposals(proposals) + voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value } + end + def comment_flags(comments) comment_flags = flags.for_comments(comments) comment_flags.each_with_object({}){ |f, h| h[f.flaggable_id] = true } diff --git a/app/views/proposals/_actions.html.erb b/app/views/proposals/_actions.html.erb new file mode 100644 index 000000000..b2bc9f111 --- /dev/null +++ b/app/views/proposals/_actions.html.erb @@ -0,0 +1,10 @@ +<% if can? :hide, proposal %> + <%= link_to t("admin.actions.hide").capitalize, hide_moderation_proposal_path(proposal), + method: :put, remote: true, data: { confirm: t('admin.actions.confirm') } %> +<% end %> + +<% if can? :hide, proposal.author %> +  |  + <%= link_to t("admin.actions.hide_author").capitalize, hide_moderation_user_path(proposal.author_id), + method: :put, data: { confirm: t('admin.actions.confirm') } %> +<% end %> diff --git a/app/views/proposals/_comments.html.erb b/app/views/proposals/_comments.html.erb new file mode 100644 index 000000000..6270c58b6 --- /dev/null +++ b/app/views/proposals/_comments.html.erb @@ -0,0 +1,27 @@ +
+
+
+

+ <%= t("proposals.show.comments_title") %> + (<%= @proposal.comments_count %>) +

+ + <% if user_signed_in? %> + <%= render 'comments/form', {commentable: @proposal, parent_id: nil, toggeable: false} %> + <% else %> +
+ +
+ <%= t("proposals.show.login_to_comment", + signin: link_to(t("votes.signin"), new_user_session_path), + signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %> +
+ <% end %> + + <% @root_comments.each do |comment| %> + <%= render 'comments/comment', comment: comment %> + <% end %> + <%= paginate @root_comments %> +
+
+
\ No newline at end of file diff --git a/app/views/proposals/_flag_actions.html.erb b/app/views/proposals/_flag_actions.html.erb new file mode 100644 index 000000000..b7bea4f79 --- /dev/null +++ b/app/views/proposals/_flag_actions.html.erb @@ -0,0 +1,21 @@ +<% if show_flag_action? proposal %> + + +<% end %> + +<% if show_unflag_action? proposal %> + + +<% end %> diff --git a/app/views/proposals/_votes.html.erb b/app/views/proposals/_votes.html.erb new file mode 100644 index 000000000..9c6188b87 --- /dev/null +++ b/app/views/proposals/_votes.html.erb @@ -0,0 +1,37 @@ +<% voted_classes = css_classes_for_proposal_vote(@proposal_votes, proposal) %> +
+
+ <%= link_to vote_proposal_path(proposal, value: 'yes'), + class: "like #{voted_classes[:in_favor]}", title: t('votes.agree'), method: "post", remote: true do %> + + <%= percentage('likes', proposal) %> + <% end %> +
+ + + + + <%= t("proposals.proposal.votes", count: proposal.total_votes) %> + + + <% if user_signed_in? && current_user.organization? %> + + <% elsif user_signed_in? && !proposal.votable_by?(current_user)%> + + <% elsif !user_signed_in? %> + + <% end %> +
diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb new file mode 100644 index 000000000..b25bf3277 --- /dev/null +++ b/app/views/proposals/show.html.erb @@ -0,0 +1,83 @@ +
+
+
+   + <%= link_to t("proposals.show.back_link"), proposals_path, class: 'left back' %> + + <% if current_user && @proposal.editable_by?(current_user) %> + <%= link_to edit_proposal_path(@proposal), class: 'edit-proposal button success tiny radius right' do %> + + <%= t("proposals.show.edit_proposal_link") %> + <% end %> + <% end %> + +

<%= @proposal.title %>

+ <% if @proposal.conflictive? %> +
+ <%= t("proposals.show.flag") %> +
+ <% end %> + +
+ <%= avatar_image(@proposal.author, seed: @proposal.author_id, size: 32, class: 'author-photo') %> + + <% if @proposal.author.hidden? %> + + + <%= t("proposals.show.author_deleted") %> + + <% else %> + + <%= @proposal.author.name %> + + <% if @proposal.author.official? %> +  •  + + <%= @proposal.author.official_position %> + + <% end %> + <% end %> + + <% if @proposal.author.verified_organization? %> +  •  + + <%= t("shared.collective") %> + + <% end %> + +  •  + <%= l @proposal.created_at.to_date %> +  •  +   + <%= link_to t("proposals.show.comments", count: @proposal.comments_count), "#comments" %> +  •  + + <%= render 'proposals/flag_actions', proposal: @proposal %> + +
+ + <%= @proposal.description %> + + <%= render 'shared/tags', proposal: @proposal %> + +
+ <%= render 'actions', proposal: @proposal %> +
+
+ + +
+
+ +<%= render "comments" %> diff --git a/app/views/shared/_tags.html.erb b/app/views/shared/_tags.html.erb index ea3f65c6b..206b29759 100644 --- a/app/views/shared/_tags.html.erb +++ b/app/views/shared/_tags.html.erb @@ -1,13 +1,15 @@ <%- limit ||= nil %> +<%= taggable = defined?(debate) ? debate : proposal %> -<% if debate.tags.any? %> +<% if taggable.tags.any? %> - <% debate.tag_list_with_limit(limit).each do |tag| %> - <%= link_to sanitize(tag.name), debates_path(tag: tag.name) %> + <% taggable.tag_list_with_limit(limit).each do |tag| %> + <%= link_to sanitize(tag.name), send("#{taggable.class.to_s.downcase}_path", tag: tag.name) %> <% end %> - <% if debate.tags_count_out_of_limit(limit) > 0 %> - <%= link_to "#{debate.tags_count_out_of_limit(limit)}+", debate_path(debate) %> + <% if taggable.tags_count_out_of_limit(limit) > 0 %> + <%= link_to "#{taggable.tags_count_out_of_limit(limit)}+", + send("#{taggable.class.to_s.downcase}_path", taggable) %> <% end %> <% end %> \ No newline at end of file diff --git a/config/initializers/vote_extensions.rb b/config/initializers/vote_extensions.rb index c0a92ef40..2a77bbef6 100644 --- a/config/initializers/vote_extensions.rb +++ b/config/initializers/vote_extensions.rb @@ -3,6 +3,10 @@ ActsAsVotable::Vote.class_eval do where(votable_type: 'Debate', votable_id: debates) end + def self.for_proposals(proposals) + where(votable_type: 'Debate', votable_id: proposals) + end + def value vote_flag end diff --git a/config/routes.rb b/config/routes.rb index 7246c0807..65e5850aa 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -45,7 +45,21 @@ Rails.application.routes.draw do end end - resources :proposals + resources :proposals do + member do + post :vote + put :flag + put :unflag + end + + resources :comments, only: :create, shallow: true do + member do + post :vote + put :flag + put :unflag + end + end + end resource :account, controller: "account", only: [:show, :update] resource :verification, controller: "verification", only: [:show] From 845ca22152d7d475d3b360c8f810694e31882aa0 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sat, 12 Sep 2015 12:35:54 +0200 Subject: [PATCH 005/138] adds proposal edit --- app/controllers/proposals_controller.rb | 14 ++++++++++++++ app/views/proposals/edit.html.erb | 17 +++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 app/views/proposals/edit.html.erb diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index c788d3ba9..6a49a8deb 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -32,6 +32,20 @@ class ProposalsController < ApplicationController end end + def edit + load_featured_tags + end + + def update + @proposal.assign_attributes(proposal_params) + if @proposal.save_with_captcha + redirect_to @proposal, notice: t('flash.actions.update.notice', resource_name: 'Proposal') + else + load_featured_tags + render :edit + end + end + private def proposal_params diff --git a/app/views/proposals/edit.html.erb b/app/views/proposals/edit.html.erb new file mode 100644 index 000000000..7d7ce413b --- /dev/null +++ b/app/views/proposals/edit.html.erb @@ -0,0 +1,17 @@ +
+ +
+ <%= link_to proposals_path, class: "left back clear" do %> + + <%= t("proposals.edit.back_link") %> + <% end %> + +
+ <%= link_to t("proposals.edit.show_link"), @proposal %> +
+ +

<%= t("proposals.edit.editing") %>

+ + <%= render "form" %> +
+
From 68e58f492b6cc038f8584a37b043f0d5e8342542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 12:37:28 +0200 Subject: [PATCH 006/138] only create Admin the first time db is seeded --- db/seeds.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/db/seeds.rb b/db/seeds.rb index 7714981c7..3c51a947f 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -1,6 +1,8 @@ # Default admin user (change password after first deploy to a server!) -admin = User.create!(username: 'admin', email: 'admin@madrid.es', password: '12345678', password_confirmation: '12345678', confirmed_at: Time.now, terms_of_service: "1") -admin.create_administrator +if Administrator.count == 0 + admin = User.create!(username: 'admin', email: 'admin@madrid.es', password: '12345678', password_confirmation: '12345678', confirmed_at: Time.now, terms_of_service: "1") + admin.create_administrator +end # Names for the moderation console, as a hint for moderators # to know better how to assign users with official positions From 9c9d2c2ea85b9c5a05abd7fe22ac98ff22ecaa85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 12:38:12 +0200 Subject: [PATCH 007/138] adds max_votes_for_proposal_edit to Settings --- config/locales/settings.en.yml | 3 ++- config/locales/settings.es.yml | 3 ++- db/dev_seeds.rb | 1 + db/seeds.rb | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/config/locales/settings.en.yml b/config/locales/settings.en.yml index 78e1a9e9e..0a6ddfa0a 100644 --- a/config/locales/settings.en.yml +++ b/config/locales/settings.en.yml @@ -5,4 +5,5 @@ en: official_level_3_name: "Level 3 official positions" official_level_4_name: "Level 4 official positions" official_level_5_name: "Level 5 official positions" - max_ratio_anon_votes_on_debates: "Max allowed percentage of anonymous votes per debate" \ No newline at end of file + max_ratio_anon_votes_on_debates: "Max allowed percentage of anonymous votes per Debate" + max_votes_for_proposal_edit: "Number of votes where a Proposal is not editable anymore" \ No newline at end of file diff --git a/config/locales/settings.es.yml b/config/locales/settings.es.yml index 36adb02af..59193a286 100644 --- a/config/locales/settings.es.yml +++ b/config/locales/settings.es.yml @@ -5,4 +5,5 @@ es: official_level_3_name: "Cargos públicos de nivel 3" official_level_4_name: "Cargos públicos de nivel 4" official_level_5_name: "Cargos públicos de nivel 5" - max_ratio_anon_votes_on_debates: "Porcentaje máximo de votos anónimos por debate" \ No newline at end of file + max_ratio_anon_votes_on_debates: "Porcentaje máximo de votos anónimos por Debate" + max_votes_for_proposal_edit: "Número de votos en que una Propuesta deja de poderse editar" \ No newline at end of file diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 64cc190cd..0c312f2a3 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -9,6 +9,7 @@ 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: 'Alcaldesa') Setting.create(key: 'max_ratio_anon_votes_on_debates', value: '50') +Setting.create(key: 'max_votes_for_proposal_edit', value: '1000') puts "Creating Users" diff --git a/db/seeds.rb b/db/seeds.rb index 3c51a947f..69baa97e0 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -14,3 +14,6 @@ Setting.create(key: 'official_level_5_name', value: 'Alcaldesa') # Max percentage of allowed anonymous votes on a debate Setting.create(key: 'max_ratio_anon_votes_on_debates', value: '50') + +# Max votes where a proposal is still editable +Setting.create(key: 'max_votes_for_proposal_edit', value: '1000') From 3fc4cf0c1b2e2b3dc1cfc898f3f9faee52a653f6 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sat, 12 Sep 2015 12:47:28 +0200 Subject: [PATCH 008/138] adds proposal's index --- app/controllers/proposals_controller.rb | 26 ++++++++++++ app/models/proposal.rb | 7 ++++ app/views/proposals/_proposal.html.erb | 30 ++++++++++++++ app/views/proposals/index.html.erb | 55 +++++++++++++++++++++++++ app/views/shared/_tags.html.erb | 2 +- 5 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 app/views/proposals/_proposal.html.erb create mode 100644 app/views/proposals/index.html.erb diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 6a49a8deb..938e38574 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -1,9 +1,20 @@ class ProposalsController < ApplicationController + before_action :parse_order, only: :index + before_action :parse_tag_filter, only: :index + before_action :parse_search_terms, only: :index before_action :authenticate_user!, except: [:index, :show] load_and_authorize_resource respond_to :html, :js + def index + @proposals = @search_terms.present? ? Proposal.search(@search_terms) : Proposal.all + @proposals = @proposals.tagged_with(@tag_filter) if @tag_filter + @proposals = @proposals.page(params[:page]).for_render.send("sort_by_#{@order}") + @tag_cloud = Proposal.tag_counts.order(taggings_count: :desc, name: :asc).limit(20) + set_proposal_votes(@proposals) + end + def show set_proposal_votes(@proposal) @commentable = @proposal @@ -55,4 +66,19 @@ class ProposalsController < ApplicationController def load_featured_tags @featured_tags = ActsAsTaggableOn::Tag.where(featured: true) end + + def parse_order + @valid_orders = ['confidence_score', 'hot_score', 'created_at', 'most_commented', 'random'] + @order = @valid_orders.include?(params[:order]) ? params[:order] : @valid_orders.first + end + + def parse_tag_filter + if params[:tag].present? + @tag_filter = params[:tag] if ActsAsTaggableOn::Tag.where(name: params[:tag]).exists? + end + end + + def parse_search_terms + @search_terms = params[:search] if params[:search].present? + end end \ No newline at end of file diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 3892686af..69e0a70a7 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -21,6 +21,13 @@ class Proposal < ActiveRecord::Base before_validation :sanitize_description before_validation :sanitize_tag_list + scope :for_render, -> { includes(:tags) } + scope :sort_by_hot_score , -> { order(hot_score: :desc) } + scope :sort_by_confidence_score , -> { order(confidence_score: :desc) } + scope :sort_by_created_at, -> { order(created_at: :desc) } + scope :sort_by_most_commented, -> { order(comments_count: :desc) } + scope :sort_by_random, -> { order("RANDOM()") } + def total_votes cached_votes_up end diff --git a/app/views/proposals/_proposal.html.erb b/app/views/proposals/_proposal.html.erb new file mode 100644 index 000000000..cba2523cc --- /dev/null +++ b/app/views/proposals/_proposal.html.erb @@ -0,0 +1,30 @@ +
+
+
+ +
+
+ <%= t("proposals.proposal.proposal") %> + +

<%= link_to proposal.title, proposal %>

+

+   + <%= link_to t("proposals.proposal.comments", count: proposal.comments_count), proposal_path(proposal, anchor: "comments") %> +  •  + <%= l proposal.created_at.to_date %> +

+
+ <%= link_to proposal.description, proposal %> +
+
+ <%= render "shared/tags", proposal: proposal, limit: 5 %> +
+
+ +
+ <%= render 'proposals/votes', proposal: proposal %> +
+ +
+
+
diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb new file mode 100644 index 000000000..227f8c230 --- /dev/null +++ b/app/views/proposals/index.html.erb @@ -0,0 +1,55 @@ +
+ +
+
+
+
+ <% if @search_terms %> +

+ <%= page_entries_info @proposals %> + <%= t("proposals.index.search_results", count: @proposals.size, search_term: @search_terms) %> +

+ <% elsif @tag_filter %> +

+ <%= page_entries_info @proposals %> + <%= t("proposals.index.filter_topic", count: @proposals.size, topic: @tag_filter) %> +

+ <% end %> +
+ <% if @tag_filter || @search_terms %> +
+
+ <%= t("proposals.index.select_order") %> +
+ <% else %> +
+

+ <%= t("proposals.index.select_order_long") %> +

+ <% end %> +
+ +
+
+
+
+ <%= link_to t("proposals.index.start_proposal"), new_proposal_path, class: 'button radius expand' %> +
+ <%= render @proposals %> + <%= paginate @proposals %> +
+
+ +
+
+
diff --git a/app/views/shared/_tags.html.erb b/app/views/shared/_tags.html.erb index 206b29759..9bb7537cf 100644 --- a/app/views/shared/_tags.html.erb +++ b/app/views/shared/_tags.html.erb @@ -4,7 +4,7 @@ <% if taggable.tags.any? %> <% taggable.tag_list_with_limit(limit).each do |tag| %> - <%= link_to sanitize(tag.name), send("#{taggable.class.to_s.downcase}_path", tag: tag.name) %> + <%= link_to sanitize(tag.name), send("#{taggable.class.to_s.downcase.pluralize}_path", tag: tag.name) %> <% end %> <% if taggable.tags_count_out_of_limit(limit) > 0 %> From 68790ac8e5fd4fee70c810fa35382f5f247b166d Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 12:48:47 +0200 Subject: [PATCH 009/138] adds abilities for proposals --- app/models/ability.rb | 25 ++++++++-- app/models/proposal.rb | 2 +- spec/models/ability_spec.rb | 95 ++++++++++++++++++++++++++++++------- 3 files changed, 100 insertions(+), 22 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 8249197e5..f6a3fbd16 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -9,7 +9,7 @@ class Ability # Not logged in users can :read, Debate - + can :read, Proposal if user # logged-in users can [:read, :update], User, id: user.id @@ -34,11 +34,18 @@ class Ability can [:flag, :unflag], Debate cannot [:flag, :unflag], Debate, author_id: user.id + can [:flag, :unflag], Proposal + cannot [:flag, :unflag], Proposal, author_id: user.id + unless user.organization? can :vote, Debate can :vote, Comment end + if user.level_two_verified? + can :vote, Proposal + end + if user.moderator? || user.administrator? can :read, Organization can(:verify, Organization){ |o| !o.verified? } @@ -58,12 +65,18 @@ class Ability can :ignore_flag, Debate, ignored_flag_at: nil, hidden_at: nil cannot :ignore_flag, Debate, author_id: user.id + can :hide, Proposal, hidden_at: nil + cannot :hide, Proposal, author_id: user.id + + can :ignore_flag, Proposal, ignored_flag_at: nil, hidden_at: nil + cannot :ignore_flag, Proposal, author_id: user.id + can :hide, User cannot :hide, User, id: user.id end if user.moderator? - can :comment_as_moderator, [Debate, Comment] + can :comment_as_moderator, [Debate, Comment, Proposal] end if user.administrator? @@ -73,6 +86,9 @@ class Ability can :restore, Debate cannot :restore, Debate, hidden_at: nil + can :restore, Proposal + cannot :restore, Proposal, hidden_at: nil + can :restore, User cannot :restore, User, hidden_at: nil @@ -82,10 +98,13 @@ class Ability can :confirm_hide, Debate cannot :confirm_hide, Debate, hidden_at: nil + can :confirm_hide, Proposal + cannot :confirm_hide, Proposal, hidden_at: nil + can :confirm_hide, User cannot :confirm_hide, User, hidden_at: nil - can :comment_as_administrator, [Debate, Comment] + can :comment_as_administrator, [Debate, Comment, Proposal] can :manage, Moderator end diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 69e0a70a7..b92ab6ef3 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -67,7 +67,7 @@ class Proposal < ActiveRecord::Base end def editable_by?(user) - editable? && author == user + editable? && author_id == user.id end def votable_by?(user) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index f106ef14e..b9379d90a 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -3,8 +3,17 @@ require 'cancan/matchers' describe Ability do subject(:ability) { Ability.new(user) } - let(:debate) { Debate.new } + let(:debate) { create(:debate) } let(:comment) { create(:comment) } + let(:proposal) { create(:proposal) } + + let(:own_debate) { create(:debate, author: user) } + let(:own_comment) { create(:comment, author: user) } + let(:own_proposal) { create(:proposal, author: user) } + + let(:hidden_debate) { create(:debate, :hidden) } + let(:hidden_comment) { create(:comment, :hidden) } + let(:hidden_proposal) { create(:proposal, :hidden) } describe "Non-logged in user" do let(:user) { nil } @@ -13,6 +22,15 @@ describe Ability do it { should be_able_to(:show, debate) } it { should_not be_able_to(:edit, Debate) } it { should_not be_able_to(:vote, Debate) } + it { should_not be_able_to(:flag, Debate) } + it { should_not be_able_to(:unflag, Debate) } + + it { should be_able_to(:index, Proposal) } + it { should be_able_to(:show, proposal) } + it { should_not be_able_to(:edit, Proposal) } + it { should_not be_able_to(:vote, Proposal) } + it { should_not be_able_to(:flag, Proposal) } + it { should_not be_able_to(:unflag, Proposal) } end describe "Citizen" do @@ -28,8 +46,14 @@ describe Ability do it { should be_able_to(:create, Comment) } it { should be_able_to(:vote, Comment) } + it { should be_able_to(:index, Proposal) } + it { should be_able_to(:show, proposal) } + it { should_not be_able_to(:vote, Proposal) } + it { should_not be_able_to(:comment_as_administrator, debate) } it { should_not be_able_to(:comment_as_moderator, debate) } + it { should_not be_able_to(:comment_as_administrator, proposal) } + it { should_not be_able_to(:comment_as_moderator, proposal) } describe 'flagging content' do it { should be_able_to(:flag, debate) } @@ -38,18 +62,18 @@ describe Ability do it { should be_able_to(:flag, comment) } it { should be_able_to(:unflag, comment) } - describe "own comments" do - let(:own_comment) { create(:comment, author: user) } + it { should be_able_to(:flag, proposal) } + it { should be_able_to(:unflag, proposal) } + describe "own content" do it { should_not be_able_to(:flag, own_comment) } it { should_not be_able_to(:unflag, own_comment) } - end - - describe "own debates" do - let(:own_debate) { create(:debate, author: user) } it { should_not be_able_to(:flag, own_debate) } it { should_not be_able_to(:unflag, own_debate) } + + it { should_not be_able_to(:flag, own_proposal) } + it { should_not be_able_to(:unflag, own_proposal) } end end @@ -60,15 +84,28 @@ describe Ability do end describe "editing debates" do - let(:own_debate) { create(:debate, author: user) } let(:own_debate_non_editable) { create(:debate, author: user) } - before { allow(own_debate_non_editable).to receive(:editable?).and_return(false) } it { should be_able_to(:edit, own_debate) } it { should_not be_able_to(:edit, debate) } # Not his it { should_not be_able_to(:edit, own_debate_non_editable) } end + + describe "editing proposals" do + let(:own_proposal_non_editable) { create(:proposal, author: user) } + before { allow(own_proposal_non_editable).to receive(:editable?).and_return(false) } + + it { should be_able_to(:edit, own_proposal) } + it { should_not be_able_to(:edit, proposal) } # Not his + it { should_not be_able_to(:edit, own_proposal_non_editable) } + end + + describe "when level 2 verified" do + before{ user.update(residence_verified_at: Time.now, confirmed_phone: "1") } + + it { should be_able_to(:vote, Proposal) } + end end describe "Organization" do @@ -82,6 +119,10 @@ describe Ability do it { should be_able_to(:show, debate) } it { should_not be_able_to(:vote, debate) } + it { should be_able_to(:index, Proposal) } + it { should be_able_to(:show, proposal) } + it { should_not be_able_to(:vote, Proposal) } + it { should be_able_to(:create, Comment) } it { should_not be_able_to(:vote, Comment) } end @@ -96,6 +137,9 @@ describe Ability do it { should be_able_to(:show, debate) } it { should be_able_to(:vote, debate) } + it { should be_able_to(:index, Proposal) } + it { should be_able_to(:show, proposal) } + it { should be_able_to(:read, Organization) } describe "organizations" do @@ -114,12 +158,9 @@ describe Ability do end describe "hiding, reviewing and restoring" do - let(:own_comment) { create(:comment, author: user) } - let(:own_debate) { create(:debate, author: user) } - let(:hidden_comment) { create(:comment, :hidden) } - let(:hidden_debate) { create(:debate, :hidden) } let(:ignored_comment) { create(:comment, :with_ignored_flag) } let(:ignored_debate) { create(:debate, :with_ignored_flag) } + let(:ignored_proposal) { create(:proposal,:with_ignored_flag) } it { should be_able_to(:hide, comment) } it { should be_able_to(:hide_in_moderation_screen, comment) } @@ -131,6 +172,11 @@ describe Ability do it { should_not be_able_to(:hide, hidden_debate) } it { should_not be_able_to(:hide, own_debate) } + it { should be_able_to(:hide, proposal) } + it { should be_able_to(:hide_in_moderation_screen, proposal) } + it { should_not be_able_to(:hide, hidden_proposal) } + it { should_not be_able_to(:hide, own_proposal) } + it { should be_able_to(:ignore_flag, comment) } it { should_not be_able_to(:ignore_flag, hidden_comment) } it { should_not be_able_to(:ignore_flag, ignored_comment) } @@ -141,15 +187,23 @@ describe Ability do it { should_not be_able_to(:ignore_flag, ignored_debate) } it { should_not be_able_to(:ignore_flag, own_debate) } + it { should be_able_to(:ignore_flag, proposal) } + it { should_not be_able_to(:ignore_flag, hidden_proposal) } + it { should_not be_able_to(:ignore_flag, ignored_proposal) } + it { should_not be_able_to(:ignore_flag, own_proposal) } + it { should_not be_able_to(:hide, user) } it { should be_able_to(:hide, other_user) } it { should_not be_able_to(:restore, comment) } it { should_not be_able_to(:restore, debate) } + it { should_not be_able_to(:restore, proposal) } it { should_not be_able_to(:restore, other_user) } it { should be_able_to(:comment_as_moderator, debate) } + it { should be_able_to(:comment_as_moderator, proposal) } it { should_not be_able_to(:comment_as_administrator, debate) } + it { should_not be_able_to(:comment_as_administrator, proposal) } end end @@ -160,32 +214,37 @@ describe Ability do let(:other_user) { create(:user) } let(:hidden_user) { create(:user, :hidden) } - let(:hidden_debate) { create(:debate, :hidden) } - let(:hidden_comment) { create(:comment, :hidden) } - let(:own_debate) { create(:debate, author: user)} - let(:own_comment) { create(:comment, author: user)} - it { should be_able_to(:index, Debate) } it { should be_able_to(:show, debate) } it { should be_able_to(:vote, debate) } + it { should be_able_to(:index, Proposal) } + it { should be_able_to(:show, proposal) } + it { should_not be_able_to(:restore, comment) } it { should_not be_able_to(:restore, debate) } + it { should_not be_able_to(:restore, proposal) } it { should_not be_able_to(:restore, other_user) } it { should be_able_to(:restore, hidden_comment) } it { should be_able_to(:restore, hidden_debate) } + it { should be_able_to(:restore, hidden_proposal) } it { should be_able_to(:restore, hidden_user) } it { should_not be_able_to(:confirm_hide, comment) } it { should_not be_able_to(:confirm_hide, debate) } + it { should_not be_able_to(:confirm_hide, proposal) } it { should_not be_able_to(:confirm_hide, other_user) } it { should be_able_to(:confirm_hide, hidden_comment) } it { should be_able_to(:confirm_hide, hidden_debate) } + it { should be_able_to(:confirm_hide, hidden_proposal) } it { should be_able_to(:confirm_hide, hidden_user) } it { should be_able_to(:comment_as_administrator, debate) } it { should_not be_able_to(:comment_as_moderator, debate) } + + it { should be_able_to(:comment_as_administrator, proposal) } + it { should_not be_able_to(:comment_as_moderator, proposal) } end end From 8111d523ce3325d4d67ca5210532345344cb5494 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 12:51:31 +0200 Subject: [PATCH 010/138] adds Proposal's model spec --- spec/models/proposal_spec.rb | 43 ++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 spec/models/proposal_spec.rb diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb new file mode 100644 index 000000000..b4eaeeb56 --- /dev/null +++ b/spec/models/proposal_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +describe Proposal do + let(:proposal) { build(:proposal) } + + it "should be valid" do + expect(proposal).to be_valid + end + + it "should not be valid without an author" do + proposal.author = nil + expect(proposal).to_not be_valid + end + + it "should not be valid without a title" do + proposal.title = nil + expect(proposal).to_not be_valid + end + + describe "#description" do + it "should be mandatory" do + proposal.description = nil + expect(proposal).to_not be_valid + end + + it "should be sanitized" do + proposal.description = "" + proposal.valid? + expect(proposal.description).to eq("alert('danger');") + end + end + + it "should sanitize the tag list" do + proposal.tag_list = "user_id=1" + proposal.valid? + expect(proposal.tag_list).to eq(['user_id1']) + end + + it "should not be valid without accepting terms of service" do + proposal.terms_of_service = nil + expect(proposal).to_not be_valid + end +end \ No newline at end of file From 26113cac962f839698044acfa6faaad642ced127 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 12:53:22 +0200 Subject: [PATCH 011/138] adds setting value check in Proposal#editable? --- app/models/proposal.rb | 2 +- spec/models/proposal_spec.rb | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/models/proposal.rb b/app/models/proposal.rb index b92ab6ef3..8f59585e4 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -63,7 +63,7 @@ class Proposal < ActiveRecord::Base end def editable? - total_votes <= 1000 + total_votes <= Setting.value_for("max_votes_for_proposal_edit").to_i end def editable_by?(user) diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index b4eaeeb56..3200c7123 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -40,4 +40,26 @@ describe Proposal do proposal.terms_of_service = nil expect(proposal).to_not be_valid end + + describe "#editable?" do + let(:proposal) { create(:proposal) } + before(:each) {Setting.find_by(key: "max_votes_for_proposal_edit").update(value: 100)} + + it "should be true if proposal has no votes yet" do + expect(proposal.total_votes).to eq(0) + expect(proposal.editable?).to be true + end + + it "should be true if proposal has less than limit votes" do + create_list(:vote, 91, votable: proposal) + expect(proposal.total_votes).to eq(91) + expect(proposal.editable?).to be true + end + + it "should be false if proposal has more than limit votes" do + create_list(:vote, 102, votable: proposal) + expect(proposal.total_votes).to eq(102) + expect(proposal.editable?).to be false + end + end end \ No newline at end of file From 8df5ed0e02b4457e803992efe0303193a68dcb14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 12:55:08 +0200 Subject: [PATCH 012/138] adds test for question presence --- spec/models/proposal_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index 3200c7123..52896e0da 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -12,6 +12,11 @@ describe Proposal do expect(proposal).to_not be_valid end + it "should not be valid without an question" do + proposal.question = nil + expect(proposal).to_not be_valid + end + it "should not be valid without a title" do proposal.title = nil expect(proposal).to_not be_valid From 90744e65888179040802bf2a46cd51f9ee2086ce Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sat, 12 Sep 2015 13:02:37 +0200 Subject: [PATCH 013/138] adds external link --- app/controllers/proposals_controller.rb | 2 +- app/views/proposals/_form.html.erb | 5 +++++ app/views/proposals/show.html.erb | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index 938e38574..c54954d4e 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -60,7 +60,7 @@ class ProposalsController < ApplicationController private def proposal_params - params.require(:proposal).permit(:title, :question, :description, :tag_list, :terms_of_service, :captcha, :captcha_key) + params.require(:proposal).permit(:title, :question, :description, :external_url, :tag_list, :terms_of_service, :captcha, :captcha_key) end def load_featured_tags diff --git a/app/views/proposals/_form.html.erb b/app/views/proposals/_form.html.erb index 8d02b4a75..5607354fc 100644 --- a/app/views/proposals/_form.html.erb +++ b/app/views/proposals/_form.html.erb @@ -17,6 +17,11 @@ <%= f.cktext_area :description, maxlength: Proposal.description_max_length, ckeditor: { language: I18n.locale }, label: false %> +
+ <%= f.label :external_url, t("proposals.form.proposal_external_url") %> + <%= f.text_field :external_url, placeholder: t("proposals.form.proposal_external_url"), label: false %> +
+
<%= f.label :tag_list, t("proposals.form.tags_label") %> <%= t("proposals.form.tags_instructions") %> diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb index b25bf3277..82089fa41 100644 --- a/app/views/proposals/show.html.erb +++ b/app/views/proposals/show.html.erb @@ -57,6 +57,7 @@
<%= @proposal.description %> + <%= @proposal.external_url %> <%= render 'shared/tags', proposal: @proposal %> From 388918443224c7c850d8ce917ade23bdce383713 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 13:14:45 +0200 Subject: [PATCH 014/138] adds proposal_code_prefix to Settings --- config/locales/settings.en.yml | 3 ++- config/locales/settings.es.yml | 3 ++- db/dev_seeds.rb | 1 + db/seeds.rb | 3 +++ 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/config/locales/settings.en.yml b/config/locales/settings.en.yml index 0a6ddfa0a..6ef8d4053 100644 --- a/config/locales/settings.en.yml +++ b/config/locales/settings.en.yml @@ -6,4 +6,5 @@ en: official_level_4_name: "Level 4 official positions" official_level_5_name: "Level 5 official positions" max_ratio_anon_votes_on_debates: "Max allowed percentage of anonymous votes per Debate" - max_votes_for_proposal_edit: "Number of votes where a Proposal is not editable anymore" \ No newline at end of file + max_votes_for_proposal_edit: "Number of votes where a Proposal is not editable anymore" + proposal_code_prefix: "Prefix for Proposals codes" \ No newline at end of file diff --git a/config/locales/settings.es.yml b/config/locales/settings.es.yml index 59193a286..96e485069 100644 --- a/config/locales/settings.es.yml +++ b/config/locales/settings.es.yml @@ -6,4 +6,5 @@ es: official_level_4_name: "Cargos públicos de nivel 4" official_level_5_name: "Cargos públicos de nivel 5" max_ratio_anon_votes_on_debates: "Porcentaje máximo de votos anónimos por Debate" - max_votes_for_proposal_edit: "Número de votos en que una Propuesta deja de poderse editar" \ No newline at end of file + max_votes_for_proposal_edit: "Número de votos en que una Propuesta deja de poderse editar" + proposal_code_prefix: "Prefijo para los códigos de Propuestas" \ No newline at end of file diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index 0c312f2a3..d7943fcf3 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -10,6 +10,7 @@ Setting.create(key: 'official_level_4_name', value: 'Concejales') Setting.create(key: 'official_level_5_name', value: 'Alcaldesa') Setting.create(key: 'max_ratio_anon_votes_on_debates', value: '50') Setting.create(key: 'max_votes_for_proposal_edit', value: '1000') +Setting.create(key: 'proposal_code_prefix', value: 'MAD') puts "Creating Users" diff --git a/db/seeds.rb b/db/seeds.rb index 69baa97e0..181386c4c 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -17,3 +17,6 @@ Setting.create(key: 'max_ratio_anon_votes_on_debates', value: '50') # Max votes where a proposal is still editable Setting.create(key: 'max_votes_for_proposal_edit', value: '1000') + +# Prefix for the Proposal codes +Setting.create(key: 'proposal_code_prefix', value: 'MAD') From e407173e4b2705aada84e74077d7ffce047b2835 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 13:14:59 +0200 Subject: [PATCH 015/138] adds code to proposals --- app/models/proposal.rb | 4 ++++ spec/models/proposal_spec.rb | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 8f59585e4..9eb05f766 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -74,6 +74,10 @@ class Proposal < ActiveRecord::Base user.level_two_verified? || !user.voted_for?(self) end + def code + "#{Setting.value_for("proposal_code_prefix")}-#{created_at.strftime('%Y-%M')}-#{id}" + end + protected def sanitize_description diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index 52896e0da..cfb9b48fa 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -46,6 +46,12 @@ describe Proposal do expect(proposal).to_not be_valid end + it "should have a code" do + Setting.find_by(key: "proposal_code_prefix").update(value: "TEST") + proposal = create(:proposal) + expect(proposal.code).to eq "TEST-#{proposal.created_at.strftime('%Y-%M')}-#{proposal.id}" + end + describe "#editable?" do let(:proposal) { create(:proposal) } before(:each) {Setting.find_by(key: "max_votes_for_proposal_edit").update(value: 100)} From b46d23c46b10855cf1b38e4c747b1c97b70dd9fe Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sat, 12 Sep 2015 13:18:33 +0200 Subject: [PATCH 016/138] proposal translations --- config/locales/en.yml | 84 ++++++++++++++++++++++++++++++----- config/locales/es.yml | 100 ++++++++++++++++++++++++++++++++++-------- 2 files changed, 154 insertions(+), 30 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index e4b5e64b9..2c8509ac8 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -145,30 +145,92 @@ en: update: form: submit_button: "Save changes" - debates: + proposals: + index: + start_debate: Start a proposal + select_order: Order by + select_order_long: Order proposals by + orders: + confidence_score: best rated + hot_score: most active + created_at: newest + most_commented: most commented + random: random + filter_topic: + one: " with the topic '%{topic}'" + other: " with the topic '%{topic}'" + search_results: + one: " containing '%{search_term}'" + other: " containing '%{search_term}'" + proposal: + proposal: Proposal + comments: + zero: No comments + one: 1 Comment + other: "%{count} Comments" + votes: + zero: No votes + one: 1 vote + other: "%{count} votes" + comment: + author: Proposal's author + moderator: Moderator + admin: Administrator + deleted: This comment has been deleted + user_deleted: Deleted user + responses: + zero: No Responses + one: 1 Response + other: "%{count} Responses" + votes: + zero: No votes + one: 1 vote + other: "%{count} votes" + form: + proposal_title: Proposal title + proposal_question: Proposal question + proposal_text: Initial text for proposal + proposal_external_url: Link to additional documentation + tags_label: Topics + tags_instructions: > + Tag this proposal. You can choose among our proposals on the list or add any other topic you want. + tags_placeholder: "Add topics writing them separated by ','" + show: + back_link: Back + author_deleted: Deleted user + comments_title: Comments + comments: + zero: No comments + one: 1 Comment + other: "%{count} Comments" + login_to_comment: "You need to %{signin} or %{signup} to comment." + edit_proposal_link: Edit + share: Share + flag: "This proposal has been flag as innapropiate for some users." + edit: + editing: Edit proposal + show_link: Show proposal + back_link: Back + form: + submit_button: "Save changes" new: start_new: Start a proposal info: "A proposal is a discussion forum, not a proposal. And soon... we opened the section of citizen proposals." more_info: "More info" back_link: Back recommendations_title: Tips for creating a proposal - recommendation_one: "Do not write the title of the debate or whole sentences in capital. On the Internet this is considered shouting. And nobody likes to scream." - recommendation_two: "Any discussion or comment that involves an illegal act will be eliminated, also intending to sabotage the debate spaces, everything else is permitted." + recommendation_one: "Do not write the title of the proposal or whole sentences in capital. On the Internet this is considered shouting. And nobody likes to scream." + recommendation_two: "Any discussion or comment that involves an illegal act will be eliminated, also intending to sabotage the proposal spaces, everything else is permitted." recommendation_three: "The harsh criticism are very welcome. This is a space of thought but we recommend preserving the elegance and intelligence. The world is better with them present." recommendation_four: "Enjoy this space, voices that fill it, it is yours too." form: submit_button: "Start a proposal" - form: - proposal_title: Proposal title - proposal_question: Proposal question - proposal_text: Initial text for proposal - tags_label: Topics - tags_instructions: > - Tag this proposal. You can choose among our proposals on the list or add any other topic you want. - tags_placeholder: "Add topics writing them separated by ','" create: form: submit_button: "Start a proposal" + update: + form: + submit_button: "Save changes" comments: form: leave_comment: Write a comment diff --git a/config/locales/es.yml b/config/locales/es.yml index e05eb48f9..e42b7eea1 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -146,29 +146,91 @@ es: form: submit_button: "Guardar cambios" proposals: - new: - start_new: Empezar una propuesta - info: "Una propuesta es un foro de discusión, no una propuesta ciudadana. Muy pronto abriremos la sección de propuestas ciudadanas, donde cualquiera podrá presentar propuestas y, si reciben apoyo, serán puestas en marcha por el Ayuntamiento." - more_info: "Más información" - back_link: Volver - recommendations_title: Recomendaciones para crear una propuesta - recommendation_one: "No escribas el título del debate o frases enteras en mayúsculas. En internet eso se considera gritar. Y a nadie le gusta que le griten." - recommendation_two: "Cualquier debate o comentario que implique una acción ilegal será eliminado, también los que tengan la intención de sabotear los espacios de debate, todo lo demás está permitido." - recommendation_three: "Las críticas despiadadas son muy bienvenidas. Este es un espacio de pensamiento. Pero te recomendamos conservar la elegancia y la inteligencia. El mundo es mejor con ellas presentes." - recommendation_four: "Disfruta de este espacio, de las voces que lo llenan, también es tuyo." - form: - submit_button: "Empieza una propuesta" + index: + start_proposal: Empieza una propuesta + select_order: Ordenar por + select_order_long: Estás viendo las propuestas + orders: + confidence_score: "mejor valorados" + hot_score: "más activos" + created_at: "más nuevos" + most_commented: "más comentados" + random: "aleatorio" + filter_topic: + one: " con el tema '%{topic}'" + other: " con el tema '%{topic}'" + search_results: + one: " que contiene '%{search_term}'" + other: " que contienen '%{search_term}'" + proposal: + proposal: Propuesta + comments: + zero: Sin comentarios + one: 1 Comentario + other: "%{count} Comentarios" + votes: + zero: Sin votos + one: 1 voto + other: "%{count} votos" + comment: + author: Autor de la propuesta + moderator: Moderador + admin: Administrador + deleted: Este comentario ha sido eliminado + user_deleted: Usuario eliminado + responses: + zero: Sin respuestas + one: 1 Respuesta + other: "%{count} Respuestas" + votes: + zero: Sin votos + one: 1 voto + other: "%{count} votos" form: proposal_title: Título de la propuesta proposal_question: Pregunta de la propuesta proposal_text: Texto inicial de la propuesta + proposal_external_url: Enlace a documentación adicional tags_label: Temas tags_instructions: > - Etiqueta este la propuesta. Puedes elegir entre nuestras propuestas o introducir las que desees. + Etiqueta esta propuesta. Puedes elegir entre nuestras propuestas o introducir las que desees. tags_placeholder: "Escribe las etiquetas que desees separadas por ','" + show: + back_link: Volver + author_deleted: Usuario eliminado + comments_title: Comentarios + comments: + zero: Sin comentarios + one: 1 Comentario + other: "%{count} Comentarios" + login_to_comment: "Necesitas %{signin} o %{signup} para comentar." + edit_proposal_link: Editar + share: Compartir + flag: "Esta propuesta ha sido marcado como inapropiado por varios usuarios." + edit: + editing: Editar propuesta + show_link: Ver propuesta + back_link: Volver + form: + submit_button: "Guardar cambios" + new: + start_new: Empezar una propuesta + info: "una propuesta es un foro de discusión, no una propuesta ciudadana. Muy pronto abriremos la sección de propuestas ciudadanas, donde cualquiera podrá presentar propuestas y, si reciben apoyo, serán puestas en marcha por el Ayuntamiento." + more_info: "Más información" + back_link: Volver + recommendations_title: Recomendaciones para crear una propuesta + recommendation_one: "No escribas el título de la propuesta o frases enteras en mayúsculas. En internet eso se considera gritar. Y a nadie le gusta que le griten." + recommendation_two: "Cualquier propuesta o comentario que implique una acción ilegal será eliminado, también los que tengan la intención de sabotear los espacios de propuesta, todo lo demás está permitido." + recommendation_three: "Las críticas despiadadas son muy bienvenidas. Este es un espacio de pensamiento. Pero te recomendamos conservar la elegancia y la inteligencia. El mundo es mejor con ellas presentes." + recommendation_four: "Disfruta de este espacio, de las voces que lo llenan, también es tuyo." + form: + submit_button: "Empieza una propuesta" create: form: submit_button: "Empieza una propuesta" + update: + form: + submit_button: "Guardar cambios" comments: form: leave_comment: Deja tu comentario @@ -186,7 +248,7 @@ es: unauthenticated: "Necesitas %{signin} o %{signup} para continuar." signin: iniciar sesión signup: registrarte - anonymous: "Este debate ya tiene demasiados votos anónimos, para poder votar %{verify_account}." + anonymous: "Esta propuesta ya tiene demasiados votos anónimos, para poder votar %{verify_account}." verify_account: verifica tu cuenta organizations: Las organizaciones no pueden votar account: @@ -194,7 +256,7 @@ es: title: "Mi cuenta" save_changes_submit: "Guardar cambios" change_credentials_link: "Cambiar mis datos de acceso" - email_on_debate_comment_label: "Recibir un email cuando alguien comenta en mis debates" + email_on_proposal_comment_label: "Recibir un email cuando alguien comenta en mis debates" email_on_comment_reply_label: "Recibir un email cuando alguien contesta a mis comentarios" avatar: "Avatar" personal: "Datos personales" @@ -211,7 +273,7 @@ es: refresh_button_text: "Refrescar" message: user: "el código secreto no coincide con la imagen" - debate: "el código secreto no coincide con la imagen" + proposal: "el código secreto no coincide con la imagen" shared: tags_cloud: tags: Tendencias @@ -221,10 +283,10 @@ es: search_form: search_title: Buscar search_button: Buscar - search_placeholder: "Buscar en debates..." + search_placeholder: "Buscar en propuestas..." mailer: comment: - subject: Alguien ha comentado en tu debate + subject: Alguien ha comentado en tu propuesta reply: subject: Alguien ha respondido a tu comentario unauthorized: @@ -232,7 +294,7 @@ es: manage: all: "No tienes permiso para realizar la acción '%{action}' sobre %{subject}." welcome: - last_debates: Últimos debates + last_proposals: Últimas propuestas welcome: title: Verificación de cuenta instructions_1_html: "Bienvenido a la página de participación ciudadana" From 4aeb2a69ad7f57ee619cd168b40e8e24a353b56c Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sat, 12 Sep 2015 13:32:02 +0200 Subject: [PATCH 017/138] adds question to show and spaces out external link --- app/views/proposals/show.html.erb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/proposals/show.html.erb b/app/views/proposals/show.html.erb index 82089fa41..35a84438d 100644 --- a/app/views/proposals/show.html.erb +++ b/app/views/proposals/show.html.erb @@ -18,6 +18,8 @@ <% end %> +

<%= @proposal.question %>

+
<%= avatar_image(@proposal.author, seed: @proposal.author_id, size: 32, class: 'author-photo') %> @@ -57,7 +59,7 @@
<%= @proposal.description %> - <%= @proposal.external_url %> +
<%= @proposal.external_url %>
<%= render 'shared/tags', proposal: @proposal %> From 1a18895760d06e45c17d04673348b1117d982d0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 13:33:56 +0200 Subject: [PATCH 018/138] changes proposal/new info text --- config/locales/en.yml | 2 +- config/locales/es.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 2c8509ac8..448fca950 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -215,7 +215,7 @@ en: submit_button: "Save changes" new: start_new: Start a proposal - info: "A proposal is a discussion forum, not a proposal. And soon... we opened the section of citizen proposals." + info: "Please explain your proposal crearly. If the proposal get enough support and advances to referendum phase, it must be condensed to a question with a Yes/No anwser. If your proposal is not clear or you just have to share your opinion please consider starting a debate instead" more_info: "More info" back_link: Back recommendations_title: Tips for creating a proposal diff --git a/config/locales/es.yml b/config/locales/es.yml index e42b7eea1..bb72a64e9 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -215,7 +215,7 @@ es: submit_button: "Guardar cambios" new: start_new: Empezar una propuesta - info: "una propuesta es un foro de discusión, no una propuesta ciudadana. Muy pronto abriremos la sección de propuestas ciudadanas, donde cualquiera podrá presentar propuestas y, si reciben apoyo, serán puestas en marcha por el Ayuntamiento." + info: "Si quieres que tu propuesta consiga apoyos has de explicarla claramente y de forma que se entienda. Si consigue el número adecuado de apoyos y es llevada a consulta debe ser resumida en una pregunta cuya respuesta sea Sí o No. Si tu propuesta no es suficientemente clara o solo quieres opinar sobre un tema es preferible que inicies un debate." more_info: "Más información" back_link: Volver recommendations_title: Recomendaciones para crear una propuesta From ea52402691fa6d22a6d0b40c59d054bfdedb0e8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 13:42:23 +0200 Subject: [PATCH 019/138] adds initial proposal's feature spec --- app/views/proposals/index.html.erb | 2 +- spec/features/proposals_spec.rb | 52 ++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 spec/features/proposals_spec.rb diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb index 227f8c230..8466e522d 100644 --- a/app/views/proposals/index.html.erb +++ b/app/views/proposals/index.html.erb @@ -1,7 +1,7 @@
-
+
<% if @search_terms %> diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb new file mode 100644 index 000000000..8ae165d05 --- /dev/null +++ b/spec/features/proposals_spec.rb @@ -0,0 +1,52 @@ +require 'rails_helper' + +feature 'Proposals' do + + scenario 'Index' do + proposal = [create(:proposal), create(:proposal), create(:proposal)] + + visit proposals_path + + expect(page).to have_selector('#proposals .proposal', count: 3) + proposal.each do |proposal| + within('#proposals') do + expect(page).to have_content proposal.title + expect(page).to have_css("a[href='#{proposal_path(proposal)}']", text: proposal.description) + end + end + end + + scenario 'Paginated Index' do + per_page = Kaminari.config.default_per_page + (per_page + 2).times { create(:proposal) } + + visit proposals_path + + expect(page).to have_selector('#proposals .proposal', count: per_page) + + within("ul.pagination") do + expect(page).to have_content("1") + expect(page).to have_content("2") + expect(page).to_not have_content("3") + click_link "Next", exact: false + end + + expect(page).to have_selector('#proposals .proposal', count: 2) + end + + scenario 'Show' do + proposal = create(:proposal) + + visit proposal_path(proposal) + + expect(page).to have_content proposal.title + expect(page).to have_content "Proposal description" + expect(page).to have_content proposal.author.name + expect(page).to have_content I18n.l(proposal.created_at.to_date) + expect(page).to have_selector(avatar(proposal.author.name)) + + within('.social-share-button') do + expect(page.all('a').count).to be(3) # Twitter, Facebook, Google+ + end + end +end \ No newline at end of file From 2acf23c1b2c63d30f111bf759878806a8bae288a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 13:56:26 +0200 Subject: [PATCH 020/138] fixes i18n missing keys --- config/locales/en.yml | 5 ++++- config/locales/es.yml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 448fca950..c37037501 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -147,7 +147,7 @@ en: submit_button: "Save changes" proposals: index: - start_debate: Start a proposal + start_proposal: Start a proposal select_order: Order by select_order_long: Order proposals by orders: @@ -256,6 +256,7 @@ en: title: "My account" save_changes_submit: "Save changes" email_on_debate_comment_label: "Receive email when someone comments on my debates" + email_on_proposal_comment_label: "Receive email when someone comments on my proposals" email_on_comment_reply_label: "Receive email when someone replies to my comments" change_credentials_link: "Change my credentials" avatar: "Avatar" @@ -274,6 +275,7 @@ en: message: user: "secret code did not match with the image" debate: "secret code did not match with the image" + proposal: "secret code did not match with the image" shared: tags_cloud: tags: Trend @@ -295,6 +297,7 @@ en: all: "You are not authorized to %{action} %{subject}." welcome: last_debates: Last debates + last_proposals: Last proposals welcome: title: Account verification instructions_1_html: "Welcome to the public participation website." diff --git a/config/locales/es.yml b/config/locales/es.yml index bb72a64e9..8c7e397c4 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -256,7 +256,8 @@ es: title: "Mi cuenta" save_changes_submit: "Guardar cambios" change_credentials_link: "Cambiar mis datos de acceso" - email_on_proposal_comment_label: "Recibir un email cuando alguien comenta en mis debates" + email_on_debate_comment_label: "Recibir un email cuando alguien comenta en mis debates" + email_on_proposal_comment_label: "Recibir un email cuando alguien comenta en mis propuestas" email_on_comment_reply_label: "Recibir un email cuando alguien contesta a mis comentarios" avatar: "Avatar" personal: "Datos personales" @@ -273,6 +274,7 @@ es: refresh_button_text: "Refrescar" message: user: "el código secreto no coincide con la imagen" + debate: "el código secreto no coincide con la imagen" proposal: "el código secreto no coincide con la imagen" shared: tags_cloud: @@ -294,6 +296,7 @@ es: manage: all: "No tienes permiso para realizar la acción '%{action}' sobre %{subject}." welcome: + last_debates: Últimos debates last_proposals: Últimas propuestas welcome: title: Verificación de cuenta From 800fa7c47f5f7ec39cc1e9e395ad435fda818ffd Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 14:02:47 +0200 Subject: [PATCH 021/138] Adds some methods to to Verification module --- lib/verification.rb | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/verification.rb b/lib/verification.rb index 67b0f4422..4b3419b94 100644 --- a/lib/verification.rb +++ b/lib/verification.rb @@ -1,4 +1,12 @@ module Verification + extend ActiveSupport::Concern + + included do + scope :level_three_verified, -> { where.not(verified_at: nil) } + scope :level_two_verified, -> { where("users.confirmed_phone IS NOT NULL AND users.residence_verified_at IS NOT NULL") } + scope :level_two_or_three_verified, -> { where("users.verified_at IS NOT NULL OR (users.confirmed_phone IS NOT NULL AND users.residence_verified_at IS NOT NULL)") } + scope :unverified, -> { where("users.verified_at IS NULL AND (users.confirmed_phone IS NULL OR users.residence_verified_at IS NOT NULL)") } + end def verification_email_sent? email_verification_token.present? @@ -28,9 +36,13 @@ module Verification verified_at.present? end + def level_two_or_three_verified? + level_two_verified? || level_three_verified? + end + def unverified? !level_two_verified? && !level_three_verified? end -end \ No newline at end of file +end From d57d538f3b80eb151fc2e52f6c25bf8ef47611b7 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 14:03:38 +0200 Subject: [PATCH 022/138] Extracts Flammable module from comment & debate --- app/models/comment.rb | 14 +------------- app/models/debate.rb | 13 +------------ lib/flaggable.rb | 19 +++++++++++++++++++ 3 files changed, 21 insertions(+), 25 deletions(-) create mode 100644 lib/flaggable.rb diff --git a/app/models/comment.rb b/app/models/comment.rb index 73bd0b732..ae2734e23 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -1,4 +1,5 @@ class Comment < ActiveRecord::Base + include Flaggable acts_as_paranoid column: :hidden_at include ActsAsParanoidAliases @@ -16,14 +17,9 @@ class Comment < ActiveRecord::Base belongs_to :commentable, -> { with_hidden }, polymorphic: true, counter_cache: true belongs_to :user, -> { with_hidden } - has_many :flags, as: :flaggable - scope :recent, -> { order(id: :desc) } scope :sort_for_moderation, -> { order(flags_count: :desc, updated_at: :desc) } - scope :pending_flag_review, -> { where(ignored_flag_at: nil, hidden_at: nil) } - scope :with_ignored_flag, -> { where(hidden_at: nil).where.not(ignored_flag_at: nil) } - scope :flagged, -> { where("flags_count > 0") } scope :for_render, -> { with_hidden.includes(user: :organization) } @@ -68,14 +64,6 @@ class Comment < ActiveRecord::Base cached_votes_down end - def ignored_flag? - ignored_flag_at.present? - end - - def ignore_flag - update(ignored_flag_at: Time.now) - end - def as_administrator? administrator_id.present? end diff --git a/app/models/debate.rb b/app/models/debate.rb index f1e9a621e..c2391a41f 100644 --- a/app/models/debate.rb +++ b/app/models/debate.rb @@ -1,5 +1,6 @@ require 'numeric' class Debate < ActiveRecord::Base + include Flaggable apply_simple_captcha acts_as_votable @@ -9,7 +10,6 @@ class Debate < ActiveRecord::Base belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' has_many :comments, as: :commentable - has_many :flags, as: :flaggable validates :title, presence: true validates :description, presence: true @@ -26,9 +26,6 @@ class Debate < ActiveRecord::Base before_save :calculate_hot_score, :calculate_confidence_score scope :sort_for_moderation, -> { order(flags_count: :desc, updated_at: :desc) } - scope :pending_flag_review, -> { where(ignored_flag_at: nil, hidden_at: nil) } - scope :with_ignored_flag, -> { where.not(ignored_flag_at: nil).where(hidden_at: nil) } - scope :flagged, -> { where("flags_count > 0") } scope :for_render, -> { includes(:tags) } scope :sort_by_hot_score , -> { order(hot_score: :desc) } scope :sort_by_confidence_score , -> { order(confidence_score: :desc) } @@ -100,14 +97,6 @@ class Debate < ActiveRecord::Base count < 0 ? 0 : count end - def ignored_flag? - ignored_flag_at.present? - end - - def ignore_flag - update(ignored_flag_at: Time.now) - end - def after_commented save # updates the hot_score because there is a before_save end diff --git a/lib/flaggable.rb b/lib/flaggable.rb new file mode 100644 index 000000000..613ce360b --- /dev/null +++ b/lib/flaggable.rb @@ -0,0 +1,19 @@ +module Flaggable + extend ActiveSupport::Concern + + included do + has_many :flags, as: :flaggable + scope :flagged, -> { where("flags_count > 0") } + scope :pending_flag_review, -> { where(ignored_flag_at: nil, hidden_at: nil) } + scope :with_ignored_flag, -> { where.not(ignored_flag_at: nil).where(hidden_at: nil) } + end + + def ignored_flag? + ignored_flag_at.present? + end + + def ignore_flag + update(ignored_flag_at: Time.now) + end + +end From f80d7771a14c0aa58c40f907d835b955e342fae9 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 14:04:32 +0200 Subject: [PATCH 023/138] Adds paranoia & Flammable to proposal --- app/models/proposal.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 9eb05f766..63de7fcdc 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -1,11 +1,14 @@ class Proposal < ActiveRecord::Base + include Flaggable + apply_simple_captcha acts_as_votable acts_as_taggable + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' has_many :comments, as: :commentable - has_many :flags, as: :flaggable validates :title, presence: true validates :question, presence: true From 231665020ecdca02e08394edafe11fdc638392aa Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 14:04:50 +0200 Subject: [PATCH 024/138] Allows commenting Proposals in Comment validation --- app/models/comment.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/comment.rb b/app/models/comment.rb index ae2734e23..227448da2 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -10,7 +10,7 @@ class Comment < ActiveRecord::Base validates :body, presence: true validates :user, presence: true - validates_inclusion_of :commentable_type, in: ["Debate"] + validates_inclusion_of :commentable_type, in: ["Debate", "Proposal"] validate :validate_body_length From fca2662923cea391bbb260d905e34ef3e34d7de8 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 14:05:19 +0200 Subject: [PATCH 025/138] adds proposals to dev_seeds --- db/dev_seeds.rb | 63 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 6 deletions(-) diff --git a/db/dev_seeds.rb b/db/dev_seeds.rb index d7943fcf3..52a606457 100644 --- a/db/dev_seeds.rb +++ b/db/dev_seeds.rb @@ -45,7 +45,13 @@ end end (1..40).each do |i| - create_user("user#{i}@madrid.es") + user = create_user("user#{i}@madrid.es") + level = [1,2,3].sample + if level == 2 then + user.update(residence_verified_at: Time.now, confirmed_phone: Faker::PhoneNumber.phone_number ) + elsif level == 3 then + user.update(verified_at: Time.now) + end end org_user_ids = User.organizations.pluck(:id) @@ -68,6 +74,24 @@ tags = Faker::Lorem.words(25) puts " #{debate.title}" end +puts "Creating Proposals" + +tags = Faker::Lorem.words(25) + +(1..30).each do |i| + author = User.reorder("RANDOM()").first + description = "

#{Faker::Lorem.paragraphs.join('

')}

" + proposal = Proposal.create!(author: author, + title: Faker::Lorem.sentence(3), + question: Faker::Lorem.sentence(3), + external_url: Faker::Internet.url, + description: description, + created_at: rand((Time.now - 1.week) .. Time.now), + tag_list: tags.sample(3).join(','), + terms_of_service: "1") + puts " #{proposal.title}" +end + puts "Commenting Debates" @@ -81,9 +105,21 @@ puts "Commenting Debates" end -puts "Commenting Comments" +puts "Commenting Proposals" (1..100).each do |i| + author = User.reorder("RANDOM()").first + proposal = Proposal.reorder("RANDOM()").first + Comment.create!(user: author, + created_at: rand(proposal.created_at .. Time.now), + commentable: proposal, + body: Faker::Lorem.sentence) +end + + +puts "Commenting Comments" + +(1..200).each do |i| author = User.reorder("RANDOM()").first parent = Comment.reorder("RANDOM()").first Comment.create!(user: author, @@ -95,7 +131,7 @@ puts "Commenting Comments" end -puts "Voting Debates & Comments" +puts "Voting Debates, Proposals & Comments" (1..100).each do |i| voter = not_org_users.reorder("RANDOM()").first @@ -111,6 +147,12 @@ end comment.vote_by(voter: voter, vote: vote) end +(1..100).each do |i| + voter = User.level_two_or_three_verified.reorder("RANDOM()").first + proposal = Proposal.reorder("RANDOM()").first + proposal.vote_by(voter: voter, vote: true) +end + puts "Flagging Debates & Comments" @@ -126,23 +168,32 @@ end Flag.flag(flagger, comment) end +(1..40).each do |i| + proposal = Proposal.reorder("RANDOM()").first + flagger = User.where(["users.id <> ?", proposal.author_id]).reorder("RANDOM()").first + Flag.flag(flagger, proposal) +end -puts "Ignoring flags in Debates & comments" + +puts "Ignoring flags in Debates, comments & proposals" Debate.flagged.reorder("RANDOM()").limit(10).each(&:ignore_flag) Comment.flagged.reorder("RANDOM()").limit(30).each(&:ignore_flag) +Proposal.flagged.reorder("RANDOM()").limit(10).each(&:ignore_flag) -puts "Hiding debates & comments" +puts "Hiding debates, comments & proposals" Comment.with_hidden.flagged.reorder("RANDOM()").limit(30).each(&:hide) Debate.with_hidden.flagged.reorder("RANDOM()").limit(5).each(&:hide) +Proposal.with_hidden.flagged.reorder("RANDOM()").limit(10).each(&:hide) -puts "Confirming hiding in debates & comments" +puts "Confirming hiding in debates, comments & proposals" Comment.only_hidden.flagged.reorder("RANDOM()").limit(10).each(&:confirm_hide) Debate.only_hidden.flagged.reorder("RANDOM()").limit(5).each(&:confirm_hide) +Proposal.only_hidden.flagged.reorder("RANDOM()").limit(5).each(&:confirm_hide) From 65d86d8b5fa9be2a01b223e77091a1e2d451c46d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 13:57:10 +0200 Subject: [PATCH 026/138] makes comments controller commentable agnostic --- app/controllers/comments_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb index 1573ed590..35406faf0 100644 --- a/app/controllers/comments_controller.rb +++ b/app/controllers/comments_controller.rb @@ -55,11 +55,11 @@ class CommentsController < ApplicationController end def administrator_comment? - ["1", true].include?(comment_params[:as_administrator]) && can?(:comment_as_administrator, Debate) + ["1", true].include?(comment_params[:as_administrator]) && can?(:comment_as_administrator, @commentable) end def moderator_comment? - ["1", true].include?(comment_params[:as_moderator]) && can?(:comment_as_moderator, Debate) + ["1", true].include?(comment_params[:as_moderator]) && can?(:comment_as_moderator, @commentable) end end From 48b3e8c45033798c4da0d5cca2c830ebdbe5079a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 14:11:34 +0200 Subject: [PATCH 027/138] removes commentable dependency from comment form --- app/views/comments/_form.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/comments/_form.html.erb b/app/views/comments/_form.html.erb index d14f582e3..0d2a75eff 100644 --- a/app/views/comments/_form.html.erb +++ b/app/views/comments/_form.html.erb @@ -1,7 +1,7 @@ <% cache [locale_and_user_status, parent_id, commentable_cache_key(commentable)] do %> <% css_id = parent_or_commentable_dom_id(parent_id, commentable) %>
> - <%= form_for [commentable, Comment.new], remote: true do |f| %> + <%= form_for Comment.new, remote: true do |f| %> <%= label_tag "comment-body-#{css_id}", t("comments.form.leave_comment") %> <%= f.text_area :body, id: "comment-body-#{css_id}", maxlength: Comment.body_max_length, label: false %> <%= f.hidden_field :commentable_type, value: commentable.class.name %> From 6e27917d6e1333b87e40cf31842872f9516e2b96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 14:12:00 +0200 Subject: [PATCH 028/138] moves comments out of commentables routes scopes --- config/routes.rb | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index 65e5850aa..34560ea77 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -35,14 +35,6 @@ Rails.application.routes.draw do put :flag put :unflag end - - resources :comments, only: :create, shallow: true do - member do - post :vote - put :flag - put :unflag - end - end end resources :proposals do @@ -51,13 +43,13 @@ Rails.application.routes.draw do put :flag put :unflag end + end - resources :comments, only: :create, shallow: true do - member do - post :vote - put :flag - put :unflag - end + resources :comments, only: :create, shallow: true do + member do + post :vote + put :flag + put :unflag end end From 22e8d615aab3089df0cb44801e360ea3483a5143 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 14:13:24 +0200 Subject: [PATCH 029/138] fixes abilities: level 3 users can vote proposals, not just level 2 --- app/models/ability.rb | 2 +- spec/models/ability_spec.rb | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index f6a3fbd16..40a8f5f69 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -42,7 +42,7 @@ class Ability can :vote, Comment end - if user.level_two_verified? + if user.level_two_or_three_verified? can :vote, Proposal end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index b9379d90a..064bb6335 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -106,6 +106,12 @@ describe Ability do it { should be_able_to(:vote, Proposal) } end + + describe "when level 3 verified" do + before{ user.update(verified_at: Time.now) } + + it { should be_able_to(:vote, Proposal) } + end end describe "Organization" do From 3da68c0a922db907aaff1cccbc367f175fb3a29b Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 14:29:24 +0200 Subject: [PATCH 030/138] fixes typo which printed Debate#123213 when rendering the tags --- app/views/shared/_tags.html.erb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_tags.html.erb b/app/views/shared/_tags.html.erb index 9bb7537cf..6c6d87096 100644 --- a/app/views/shared/_tags.html.erb +++ b/app/views/shared/_tags.html.erb @@ -1,5 +1,5 @@ <%- limit ||= nil %> -<%= taggable = defined?(debate) ? debate : proposal %> +<%- taggable = defined?(debate) ? debate : proposal %> <% if taggable.tags.any? %> @@ -12,4 +12,4 @@ send("#{taggable.class.to_s.downcase}_path", taggable) %> <% end %> -<% end %> \ No newline at end of file +<% end %> From d11a6a039af859f185fedfb731f2edb5971c87b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 14:29:26 +0200 Subject: [PATCH 031/138] refactors method to keep consistency in sync --- app/helpers/cache_keys_helper.rb | 2 +- lib/verification.rb | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/helpers/cache_keys_helper.rb b/app/helpers/cache_keys_helper.rb index be23439dd..566b3cd81 100644 --- a/app/helpers/cache_keys_helper.rb +++ b/app/helpers/cache_keys_helper.rb @@ -10,7 +10,7 @@ module CacheKeysHelper if user_signed_in? user_status += ":signed" - user_status += ":verified" if current_user.verified_at.present? + user_status += ":verified" if current_user.level_two_or_three_verified? user_status += ":org" if current_user.organization? user_status += ":admin" if current_user.administrator? user_status += ":moderator" if current_user.moderator? diff --git a/lib/verification.rb b/lib/verification.rb index 4b3419b94..fe5172c65 100644 --- a/lib/verification.rb +++ b/lib/verification.rb @@ -41,8 +41,7 @@ module Verification end def unverified? - !level_two_verified? && !level_three_verified? + !level_two_or_three_verified? end - end From 6fc7a4a62abcc083dd2717986c5d605080fece54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 16:19:08 +0200 Subject: [PATCH 032/138] adds proposals to header --- app/views/layouts/_header.html.erb | 1 + config/locales/en.yml | 2 +- config/locales/es.yml | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index e4e38674d..5239fef8f 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -35,6 +35,7 @@ <%#= link_to t("layouts.header.initiatives"), "#" %>
<%= link_to t("layouts.header.debates"), debates_path, class: ("active" if current_page?(controller: "/debates")) %> + <%= link_to t("layouts.header.proposals"), proposals_path, class: ("active" if current_page?(controller: "/proposals")) %> <%= link_to t("layouts.header.more_information"), "/more_information", class: ("active" if current_page?("/more_information")) %> <%= link_to t("layouts.header.external_link_blog_url"), target: "_blank" do %> <%= t("layouts.header.external_link_blog") %> diff --git a/config/locales/en.yml b/config/locales/en.yml index c37037501..a29bf5307 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -29,7 +29,7 @@ en: # welcome: Welcome more_information: "More information" debates: Debates - # initiatives: Initiatives + proposals: Proposals footer: description: "This portal use %{consul}, is %{open_source}. Madrid, for the whole world." open_source: "software libre" diff --git a/config/locales/es.yml b/config/locales/es.yml index 8c7e397c4..cddc24fd9 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -29,7 +29,7 @@ es: # welcome: Portada more_information: "Más información" debates: Debates - # initiatives: Iniciativas + proposals: Propuestas ciudadanas footer: description: "Este portal usa la %{consul} que es %{open_source}. De Madrid, para el mundo entero." open_source: "software libre" @@ -285,7 +285,7 @@ es: search_form: search_title: Buscar search_button: Buscar - search_placeholder: "Buscar en propuestas..." + search_placeholder: "Buscar en debates..." mailer: comment: subject: Alguien ha comentado en tu propuesta From 815ee0ad62d5fa21129044b7cd7c15f60e5e61f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 16:19:17 +0200 Subject: [PATCH 033/138] saves a query --- app/models/proposal.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 63de7fcdc..361f072cc 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -70,7 +70,7 @@ class Proposal < ActiveRecord::Base end def editable_by?(user) - editable? && author_id == user.id + author_id == user.id && editable? end def votable_by?(user) From 2681eb5caab4e653ca6285bb165ccb2c37091c11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baza=CC=81n?= Date: Sat, 12 Sep 2015 16:32:40 +0200 Subject: [PATCH 034/138] adds button to proposals' index --- app/views/layouts/_header.html.erb | 1 + config/locales/en.yml | 1 + config/locales/es.yml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/views/layouts/_header.html.erb b/app/views/layouts/_header.html.erb index 5239fef8f..abd661659 100644 --- a/app/views/layouts/_header.html.erb +++ b/app/views/layouts/_header.html.erb @@ -54,6 +54,7 @@

<%= t("layouts.header.open_city_title") %>

<%= t("layouts.header.open_city_slogan") %>

<%= link_to t("layouts.header.see_all_debates"), debates_path, class: "button radius" %> + <%= link_to t("layouts.header.see_all_proposals"), proposals_path, class: "button radius" %> <%= link_to t("layouts.header.more_information"), "/more_information", class: "more-info" %>
diff --git a/config/locales/en.yml b/config/locales/en.yml index a29bf5307..e612a5b9e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -22,6 +22,7 @@ en: # the same level as everyone else. Because the Madrid City Council works for its citizens, and must respond to them." # open_city_soon: "And soon... we'll open the section for citizen proposals." see_all_debates: See all debates + see_all_proposals: See all proposals my_account_link: My account locale: "Language:" administration: Administration diff --git a/config/locales/es.yml b/config/locales/es.yml index cddc24fd9..1b9198ef4 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -22,6 +22,7 @@ es: # al mismo nivel que todos los demás." # open_city_soon: "Muy pronto abriremos la sección de propuestas ciudadanas, donde cualquiera podrá presentar propuestas y, si reciben apoyo, serán puestas en marcha por el Ayuntamiento." see_all_debates: Ver todos los debates + see_all_proposals: Ver todas las propuestas my_account_link: Mi cuenta locale: "Idioma:" administration: Administrar From 1d16f788e65fc0924d0484a1244b79556b750909 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 16:33:08 +0200 Subject: [PATCH 035/138] adds proposals text to info page --- app/views/pages/how_it_works.html.erb | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/views/pages/how_it_works.html.erb b/app/views/pages/how_it_works.html.erb index ab6bac050..c55eabec0 100644 --- a/app/views/pages/how_it_works.html.erb +++ b/app/views/pages/how_it_works.html.erb @@ -7,6 +7,7 @@ I. Participación @@ -39,6 +40,14 @@

Tanto los hilos, como los comentarios podrán ser valorados por cualquiera, de tal manera que será la propia ciudadanía, y nadie en su nombre, la que decida cuáles son los temas más importantes en cada momento. Estos serán presentados en la portada del espacio, pudiendo por supuesto accederse a todos los demás temas en páginas posteriores, o usando otros criterios de ordenación (los temas con más comentarios, los más nuevos, los más controvertidos, etc.).

Cada uno de los trabajadores del Ayuntamiento tiene un usuario propio, que será resaltado como tal, permitiendo que participen en los debates al mismo nivel que todos los demás ciudadanos. Esto permitirá crear espacios de comunicación directos entre unos y otros, evitando los inconvenientes que implica la comunicación medidada, y respondiendo a un planteamiento claro por parte del nuevo gobierno de Madrid por el cual el Ayuntamiento trabaja para la ciudadanía, y ante ella debe responder.

+ +

I.I. Espacio de propuestas

+ +

En este espacio, cualquier persona puede proponer una iniciativa con la intención de recabar los suficientes apoyos como para que la idea pase a ser consultada a toda la ciudadanía con caracter vinculante.

+ +

Las propuestas pueden ser apoyadas por ciudadanos empadronados en Madrid que hayan verificado su cuenta en la plataforma de participación, de tal manera que será la propia ciudadanía, y nadie en su nombre, la que decida cuáles son las propuestas que merecen la pena ser llevadas a cabo.

+ +

Una vez que una propuesta alcance una cantidad de apoyos equivalente al 2% del censo de Madrid (unos 53000), automaticamente pasa a ser estudiada por un grupo de trabajo del ayuntamiento y pasará a la siguiente fase de consulta popular, en la que la ciudadanía de Madrid votará si se lleva a cabo o no. El plazo máximo para recabar los apoyos necesarios será de 12 meses.

From 7aaf16a6834ce75a959b226566758e29496262ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juanjo=20Baz=C3=A1n?= Date: Sat, 12 Sep 2015 16:41:13 +0200 Subject: [PATCH 036/138] updates i18n for proposals --- config/locales/es.yml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/config/locales/es.yml b/config/locales/es.yml index 1b9198ef4..6e31ea175 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -109,7 +109,7 @@ es: tags_label: Temas tags_instructions: > Etiqueta este debate. Puedes elegir entre nuestras propuestas o introducir las que desees. - tags_placeholder: "Escribe las etiquetas que desees separadas por ','" + tags_placeholder: "Escribe las etiquetas que desees separadas por coma (',')" show: back_link: Volver author_deleted: Usuario eliminado @@ -152,11 +152,11 @@ es: select_order: Ordenar por select_order_long: Estás viendo las propuestas orders: - confidence_score: "mejor valorados" - hot_score: "más activos" - created_at: "más nuevos" - most_commented: "más comentados" - random: "aleatorio" + confidence_score: "mejor valoradas" + hot_score: "más activas" + created_at: "más nuevas" + most_commented: "más comentadas" + random: "aleatorias" filter_topic: one: " con el tema '%{topic}'" other: " con el tema '%{topic}'" @@ -195,7 +195,7 @@ es: tags_label: Temas tags_instructions: > Etiqueta esta propuesta. Puedes elegir entre nuestras propuestas o introducir las que desees. - tags_placeholder: "Escribe las etiquetas que desees separadas por ','" + tags_placeholder: "Escribe las etiquetas que desees separadas por una coma (',')" show: back_link: Volver author_deleted: Usuario eliminado @@ -207,7 +207,7 @@ es: login_to_comment: "Necesitas %{signin} o %{signup} para comentar." edit_proposal_link: Editar share: Compartir - flag: "Esta propuesta ha sido marcado como inapropiado por varios usuarios." + flag: "Esta propuesta ha sido marcada como inapropiada por varios usuarios." edit: editing: Editar propuesta show_link: Ver propuesta @@ -221,7 +221,7 @@ es: back_link: Volver recommendations_title: Recomendaciones para crear una propuesta recommendation_one: "No escribas el título de la propuesta o frases enteras en mayúsculas. En internet eso se considera gritar. Y a nadie le gusta que le griten." - recommendation_two: "Cualquier propuesta o comentario que implique una acción ilegal será eliminado, también los que tengan la intención de sabotear los espacios de propuesta, todo lo demás está permitido." + recommendation_two: "Cualquier propuesta o comentario que implique una acción ilegal será eliminada, también las que tengan la intención de sabotear los espacios de propuesta, todo lo demás está permitido." recommendation_three: "Las críticas despiadadas son muy bienvenidas. Este es un espacio de pensamiento. Pero te recomendamos conservar la elegancia y la inteligencia. El mundo es mejor con ellas presentes." recommendation_four: "Disfruta de este espacio, de las voces que lo llenan, también es tuyo." form: From 1caf6348a961a1db4df793358a349f1577244313 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 15:59:24 +0200 Subject: [PATCH 037/138] Extract HasOrders + partial from DebatesController --- app/controllers/application_controller.rb | 1 + app/controllers/concerns/has_orders.rb | 12 +++++++ app/controllers/debates_controller.rb | 10 ++---- app/views/debates/index.html.erb | 14 ++------ app/views/shared/_order_select.html.erb | 15 ++++++++ spec/controllers/concerns/has_orders_spec.rb | 37 ++++++++++++++++++++ 6 files changed, 70 insertions(+), 19 deletions(-) create mode 100644 app/controllers/concerns/has_orders.rb create mode 100644 app/views/shared/_order_select.html.erb create mode 100644 spec/controllers/concerns/has_orders_spec.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3ba96f0c8..d5d351038 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -3,6 +3,7 @@ require "application_responder" class ApplicationController < ActionController::Base include SimpleCaptcha::ControllerHelpers include HasFilters + include HasOrders before_action :authenticate_http_basic, if: :http_basic_auth_site? before_action :authenticate_user!, unless: :devise_controller?, if: :beta_site? diff --git a/app/controllers/concerns/has_orders.rb b/app/controllers/concerns/has_orders.rb new file mode 100644 index 000000000..31a98e850 --- /dev/null +++ b/app/controllers/concerns/has_orders.rb @@ -0,0 +1,12 @@ +module HasOrders + extend ActiveSupport::Concern + + class_methods do + def has_orders(valid_orders, *args) + before_action(*args) do + @valid_orders = valid_orders + @current_order = @valid_orders.include?(params[:order]) ? params[:order] : @valid_orders.first + end + end + end +end diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb index b336a924a..849091afc 100644 --- a/app/controllers/debates_controller.rb +++ b/app/controllers/debates_controller.rb @@ -1,16 +1,17 @@ class DebatesController < ApplicationController - before_action :parse_order, only: :index before_action :parse_tag_filter, only: :index before_action :parse_search_terms, only: :index before_action :authenticate_user!, except: [:index, :show] + has_orders %w{confidence_score hot_score created_at most_commented random}, only: :index load_and_authorize_resource + respond_to :html, :js def index @debates = @search_terms.present? ? Debate.search(@search_terms) : Debate.all @debates = @debates.tagged_with(@tag_filter) if @tag_filter - @debates = @debates.page(params[:page]).for_render.send("sort_by_#{@order}") + @debates = @debates.page(params[:page]).for_render.send("sort_by_#{@current_order}") @tag_cloud = Debate.tag_counts.order(taggings_count: :desc, name: :asc).limit(20) set_debate_votes(@debates) end @@ -82,11 +83,6 @@ class DebatesController < ApplicationController @featured_tags = ActsAsTaggableOn::Tag.where(featured: true) end - def parse_order - @valid_orders = ['confidence_score', 'hot_score', 'created_at', 'most_commented', 'random'] - @order = @valid_orders.include?(params[:order]) ? params[:order] : @valid_orders.first - end - def parse_tag_filter if params[:tag].present? @tag_filter = params[:tag] if ActsAsTaggableOn::Tag.where(name: params[:tag]).exists? diff --git a/app/views/debates/index.html.erb b/app/views/debates/index.html.erb index bbd77577c..d099f8fd0 100644 --- a/app/views/debates/index.html.erb +++ b/app/views/debates/index.html.erb @@ -1,7 +1,7 @@
-
+
<% if @search_terms %> @@ -27,17 +27,7 @@ <%= t("debates.index.select_order_long") %> <% end %> -
- -
-
+ <%= render 'shared/order_select', i18n_namespace: "debates.index" %>
<%= link_to t("debates.index.start_debate"), new_debate_path, class: 'button radius expand' %> diff --git a/app/views/shared/_order_select.html.erb b/app/views/shared/_order_select.html.erb new file mode 100644 index 000000000..de1576199 --- /dev/null +++ b/app/views/shared/_order_select.html.erb @@ -0,0 +1,15 @@ +<% # Params: + # + # i18n_namespace: for example "moderation.debates.index" +%> + +
+ +
diff --git a/spec/controllers/concerns/has_orders_spec.rb b/spec/controllers/concerns/has_orders_spec.rb new file mode 100644 index 000000000..082c4c068 --- /dev/null +++ b/spec/controllers/concerns/has_orders_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +describe 'HasOrders' do + + class FakeController < ActionController::Base; end + + controller(FakeController) do + include HasOrders + has_orders ['created_at', 'votes_count', 'flags_count'], only: :index + + def index + render text: "#{@current_order} (#{@valid_orders.join(' ')})" + end + end + + it "has the valid orders set up" do + get :index + expect(response.body).to eq('created_at (created_at votes_count flags_count)') + end + + describe "the current order" do + it "defaults to the first one on the list" do + get :index + expect(response.body).to eq('created_at (created_at votes_count flags_count)') + end + + it "can be changed by the order param" do + get :index, order: 'votes_count' + expect(response.body).to eq('votes_count (created_at votes_count flags_count)') + end + + it "defaults to the first one on the list if given a bogus order" do + get :index, order: 'foobar' + expect(response.body).to eq('created_at (created_at votes_count flags_count)') + end + end +end From 390b5bb5f10cc1915521913c7a7451415478cbfe Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 16:09:36 +0200 Subject: [PATCH 038/138] Adds order specs to proposals feature --- app/controllers/proposals_controller.rb | 11 +--- app/views/proposals/index.html.erb | 14 +---- spec/features/proposals_spec.rb | 84 ++++++++++++++++++++++++- 3 files changed, 88 insertions(+), 21 deletions(-) diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index c54954d4e..cf18ef51f 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -1,8 +1,8 @@ class ProposalsController < ApplicationController - before_action :parse_order, only: :index before_action :parse_tag_filter, only: :index before_action :parse_search_terms, only: :index before_action :authenticate_user!, except: [:index, :show] + has_orders %w{confidence_score hot_score created_at most_commented random}, only: :index load_and_authorize_resource respond_to :html, :js @@ -10,7 +10,7 @@ class ProposalsController < ApplicationController def index @proposals = @search_terms.present? ? Proposal.search(@search_terms) : Proposal.all @proposals = @proposals.tagged_with(@tag_filter) if @tag_filter - @proposals = @proposals.page(params[:page]).for_render.send("sort_by_#{@order}") + @proposals = @proposals.page(params[:page]).for_render.send("sort_by_#{@current_order}") @tag_cloud = Proposal.tag_counts.order(taggings_count: :desc, name: :asc).limit(20) set_proposal_votes(@proposals) end @@ -67,11 +67,6 @@ class ProposalsController < ApplicationController @featured_tags = ActsAsTaggableOn::Tag.where(featured: true) end - def parse_order - @valid_orders = ['confidence_score', 'hot_score', 'created_at', 'most_commented', 'random'] - @order = @valid_orders.include?(params[:order]) ? params[:order] : @valid_orders.first - end - def parse_tag_filter if params[:tag].present? @tag_filter = params[:tag] if ActsAsTaggableOn::Tag.where(name: params[:tag]).exists? @@ -81,4 +76,4 @@ class ProposalsController < ApplicationController def parse_search_terms @search_terms = params[:search] if params[:search].present? end -end \ No newline at end of file +end diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb index 8466e522d..963f41428 100644 --- a/app/views/proposals/index.html.erb +++ b/app/views/proposals/index.html.erb @@ -1,7 +1,7 @@
-
+
<% if @search_terms %> @@ -27,17 +27,7 @@ <%= t("proposals.index.select_order_long") %> <% end %> -
- -
-
+ <%= render 'shared/order_select', i18n_namespace: "proposals.index" %>
<%= link_to t("proposals.index.start_proposal"), new_proposal_path, class: 'button radius expand' %> diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 8ae165d05..2db0d899f 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -49,4 +49,86 @@ feature 'Proposals' do expect(page.all('a').count).to be(3) # Twitter, Facebook, Google+ end end -end \ No newline at end of file + + feature 'Proposal index order filters' do + + scenario 'Default order is confidence_score', :js do + create(:proposal, title: 'Best Proposal').update_column(:confidence_score, 10) + create(:proposal, title: 'Worst Proposal').update_column(:confidence_score, 2) + create(:proposal, title: 'Medium Proposal').update_column(:confidence_score, 5) + + visit proposals_path + + expect('Best Proposal').to appear_before('Medium Proposal') + expect('Medium Proposal').to appear_before('Worst Proposal') + end + + scenario 'Proposals are ordered by hot_score', :js do + create(:proposal, title: 'Best Proposal').update_column(:hot_score, 10) + create(:proposal, title: 'Worst Proposal').update_column(:hot_score, 2) + create(:proposal, title: 'Medium Proposal').update_column(:hot_score, 5) + + visit proposals_path + select 'most active', from: 'order-selector' + + within '#proposals.js-order-hot-score' do + expect('Best Proposal').to appear_before('Medium Proposal') + expect('Medium Proposal').to appear_before('Worst Proposal') + end + + expect(current_url).to include('order=hot_score') + expect(current_url).to include('page=1') + end + + scenario 'Proposals are ordered by most commented', :js do + create(:proposal, title: 'Best Proposal', comments_count: 10) + create(:proposal, title: 'Medium Proposal', comments_count: 5) + create(:proposal, title: 'Worst Proposal', comments_count: 2) + + visit proposals_path + select 'most commented', from: 'order-selector' + + within '#proposals.js-order-most-commented' do + expect('Best Proposal').to appear_before('Medium Proposal') + expect('Medium Proposal').to appear_before('Worst Proposal') + end + + expect(current_url).to include('order=most_commented') + expect(current_url).to include('page=1') + end + + scenario 'Proposals are ordered by newest', :js do + create(:proposal, title: 'Best Proposal', created_at: Time.now) + create(:proposal, title: 'Medium Proposal', created_at: Time.now - 1.hour) + create(:proposal, title: 'Worst Proposal', created_at: Time.now - 1.day) + + visit proposals_path + select 'newest', from: 'order-selector' + + within '#proposals.js-order-created-at' do + expect('Best Proposal').to appear_before('Medium Proposal') + expect('Medium Proposal').to appear_before('Worst Proposal') + end + + expect(current_url).to include('order=created_at') + expect(current_url).to include('page=1') + end + + scenario 'Proposals are ordered randomly', :js do + create_list(:proposal, 12) + visit proposals_path + + select 'random', from: 'order-selector' + proposals_first_time = find("#proposals.js-order-random").text + + select 'most commented', from: 'order-selector' + expect(page).to have_selector('#proposals.js-order-most-commented') + + select 'random', from: 'order-selector' + proposals_second_time = find("#proposals.js-order-random").text + + expect(proposals_first_time).to_not eq(proposals_second_time) + expect(current_url).to include('page=1') + end + end +end From a2163c068a01136f2d1da8f55058a19b1e99f043 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 16:39:55 +0200 Subject: [PATCH 039/138] Fixes error in view: proposals have no likes --- app/views/proposals/_votes.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/proposals/_votes.html.erb b/app/views/proposals/_votes.html.erb index 9c6188b87..7ac0e703e 100644 --- a/app/views/proposals/_votes.html.erb +++ b/app/views/proposals/_votes.html.erb @@ -4,7 +4,7 @@ <%= link_to vote_proposal_path(proposal, value: 'yes'), class: "like #{voted_classes[:in_favor]}", title: t('votes.agree'), method: "post", remote: true do %> - <%= percentage('likes', proposal) %> + <%= percentage('total_votes', proposal) %> <% end %>
From d300bb6bcbdc005d27b6ea3fa5642473cd57c2cd Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 16:48:31 +0200 Subject: [PATCH 040/138] Refactors order_selector Moves the information about the current order to the selector, not to the container --- app/views/debates/index.html.erb | 4 ++-- app/views/proposals/index.html.erb | 4 ++-- ...lect.html.erb => _order_selector.html.erb} | 2 +- spec/features/debates_spec.rb | 20 +++++++++++++------ spec/features/proposals_spec.rb | 20 +++++++++++++------ 5 files changed, 33 insertions(+), 17 deletions(-) rename app/views/shared/{_order_select.html.erb => _order_selector.html.erb} (78%) diff --git a/app/views/debates/index.html.erb b/app/views/debates/index.html.erb index d099f8fd0..12dd4e684 100644 --- a/app/views/debates/index.html.erb +++ b/app/views/debates/index.html.erb @@ -1,7 +1,7 @@
-
+
<% if @search_terms %> @@ -27,7 +27,7 @@ <%= t("debates.index.select_order_long") %> <% end %> - <%= render 'shared/order_select', i18n_namespace: "debates.index" %> + <%= render 'shared/order_selector', i18n_namespace: "debates.index" %>
<%= link_to t("debates.index.start_debate"), new_debate_path, class: 'button radius expand' %> diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb index 963f41428..691b2ad58 100644 --- a/app/views/proposals/index.html.erb +++ b/app/views/proposals/index.html.erb @@ -1,7 +1,7 @@
-
+
<% if @search_terms %> @@ -27,7 +27,7 @@ <%= t("proposals.index.select_order_long") %> <% end %> - <%= render 'shared/order_select', i18n_namespace: "proposals.index" %> + <%= render 'shared/order_selector', i18n_namespace: "proposals.index" %>
<%= link_to t("proposals.index.start_proposal"), new_proposal_path, class: 'button radius expand' %> diff --git a/app/views/shared/_order_select.html.erb b/app/views/shared/_order_selector.html.erb similarity index 78% rename from app/views/shared/_order_select.html.erb rename to app/views/shared/_order_selector.html.erb index de1576199..489977718 100644 --- a/app/views/shared/_order_select.html.erb +++ b/app/views/shared/_order_selector.html.erb @@ -4,7 +4,7 @@ %>
- <% @valid_orders.each do |order| %>
diff --git a/app/views/shared/_search_form.html.erb b/app/views/shared/_search_form.html.erb index 1c7a0eb45..1c0344993 100644 --- a/app/views/shared/_search_form.html.erb +++ b/app/views/shared/_search_form.html.erb @@ -1,21 +1,27 @@ +<% # Params: + # + # search_path: for example debates_path + # i18n_namespace: for example "debates.index.search_form" +%> +
-

<%= t("shared.search_form.search_title") %>

+

<%= t("#{i18n_namespace}.title") %>


- <%= form_tag debates_path, method: :get do %> + <%= form_tag search_path, method: :get do %>
- " class="search-form"> + " class="search-form">
- + ">
<% end %>
-
\ No newline at end of file +
diff --git a/app/views/shared/_search_form_header.html.erb b/app/views/shared/_search_form_header.html.erb index 343b8d3e6..cf84656e6 100644 --- a/app/views/shared/_search_form_header.html.erb +++ b/app/views/shared/_search_form_header.html.erb @@ -1,14 +1,20 @@ +<% # Params: + # + # i18n_namespace: for example "debates.index.search_form" + # search_path: for example debates_path +%> +
- <%= form_tag debates_path, method: :get do %> + <%= form_tag search_path, method: :get do %>
- " class="search-form"> + " class="search-form">
-
diff --git a/config/locales/en.yml b/config/locales/en.yml index e612a5b9e..1788393dc 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -76,6 +76,10 @@ en: filter_topic: one: " with the topic '%{topic}'" other: " with the topic '%{topic}'" + search_form: + title: Search + button: Search + placeholder: "Search debates..." search_results: one: " containing '%{search_term}'" other: " containing '%{search_term}'" @@ -283,10 +287,6 @@ en: flag: Flag as inappropriate unflag: Undo flag collective: Collective - search_form: - search_title: Search - search_button: Search - search_placeholder: "Search on debates..." mailer: comment: subject: Someone has commented on your debate diff --git a/config/locales/es.yml b/config/locales/es.yml index 6e31ea175..cc797184f 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -76,6 +76,10 @@ es: filter_topic: one: " con el tema '%{topic}'" other: " con el tema '%{topic}'" + search_form: + title: Buscar + button: Buscar + placeholder: "Buscar debates..." search_results: one: " que contiene '%{search_term}'" other: " que contienen '%{search_term}'" @@ -283,10 +287,6 @@ es: flag: Denunciar como inapropiado unflag: Deshacer denuncia collective: Colectivo - search_form: - search_title: Buscar - search_button: Buscar - search_placeholder: "Buscar en debates..." mailer: comment: subject: Alguien ha comentado en tu propuesta From 849a2c83a8b9b432e09c406b65c69d10a74cdf59 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 18:35:45 +0200 Subject: [PATCH 043/138] Implements searching in proposals --- app/models/proposal.rb | 4 ++++ app/views/proposals/index.html.erb | 6 ++++++ config/locales/en.yml | 4 ++++ config/locales/es.yml | 4 ++++ spec/features/proposals_spec.rb | 23 +++++++++++++++++++++++ 5 files changed, 41 insertions(+) diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 361f072cc..6d94b5a79 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -65,6 +65,10 @@ class Proposal < ActiveRecord::Base 6000 end + def self.search(terms) + terms.present? ? where("title ILIKE ? OR description ILIKE ? OR question ILIKE ?", "%#{terms}%", "%#{terms}%", "%#{terms}%") : none + end + def editable? total_votes <= Setting.value_for("max_votes_for_proposal_edit").to_i end diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb index 691b2ad58..e0deb7ac9 100644 --- a/app/views/proposals/index.html.erb +++ b/app/views/proposals/index.html.erb @@ -1,3 +1,9 @@ +<% content_for :header_addon do %> + <%= render "shared/search_form_header", + search_path: proposals_path(page: 1), + i18n_namespace: "proposals.index.search_form" %> +<% end %> +
diff --git a/config/locales/en.yml b/config/locales/en.yml index 1788393dc..580993fe5 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -164,6 +164,10 @@ en: filter_topic: one: " with the topic '%{topic}'" other: " with the topic '%{topic}'" + search_form: + title: Search + button: Search + placeholder: "Search proposals..." search_results: one: " containing '%{search_term}'" other: " containing '%{search_term}'" diff --git a/config/locales/es.yml b/config/locales/es.yml index cc797184f..c56e46299 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -164,6 +164,10 @@ es: filter_topic: one: " con el tema '%{topic}'" other: " con el tema '%{topic}'" + search_form: + title: Search + button: Search + placeholder: "Buscar propuestas ciudadanas..." search_results: one: " que contiene '%{search_term}'" other: " que contienen '%{search_term}'" diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 85d0178f6..0f70b4ecc 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -139,4 +139,27 @@ feature 'Proposals' do expect(current_url).to include('page=1') end end + + scenario 'proposal index search' do + proposal1 = create(:proposal, title: "Show me what you got") + proposal2 = create(:proposal, title: "Get Schwifty") + proposal3 = create(:proposal) + proposal4 = create(:proposal, description: "Schwifty in here") + proposal5 = create(:proposal, question: "Schwifty in here") + + visit proposals_path + fill_in "search", with: "Schwifty" + click_button "Search" + + expect(current_path).to eq(proposals_path) + + within("#proposals") do + expect(page).to have_css('.proposal', count: 3) + expect(page).to have_content(proposal2.title) + expect(page).to have_content(proposal4.title) + expect(page).to have_content(proposal5.title) + expect(page).to_not have_content(proposal1.title) + expect(page).to_not have_content(proposal3.title) + end + end end From 07b99fcf19566d962c2ae1c59bfafead59afaa63 Mon Sep 17 00:00:00 2001 From: kikito Date: Sat, 12 Sep 2015 18:39:29 +0200 Subject: [PATCH 044/138] fixes kaminari inability to pluralize spanish names --- config/initializers/kaminari_config.rb | 47 ++++++++++++++++++++++++++ config/locales/activerecord.es.yml | 35 +++++++++++++++---- config/locales/kaminari.en.yml | 6 +++- config/locales/kaminari.es.yml | 10 ++++-- 4 files changed, 87 insertions(+), 11 deletions(-) diff --git a/config/initializers/kaminari_config.rb b/config/initializers/kaminari_config.rb index b1d87b01b..45c2b70e4 100644 --- a/config/initializers/kaminari_config.rb +++ b/config/initializers/kaminari_config.rb @@ -8,3 +8,50 @@ Kaminari.configure do |config| # config.page_method_name = :page # config.param_name = :page end + + +# Overrides for making Kaminari handle i18n pluralization correctly +# +# Remove everything below once https://github.com/amatsuda/kaminari/pull/694 is +# merged in Kaminari and we have updated +module Kaminari + + module ActionViewExtension + def page_entries_info(collection, options = {}) + entry_name = if options[:entry_name] + options[:entry_name].pluralize(collection.size) + else + collection.entry_name(:count => collection.size).downcase + end + + if collection.total_pages < 2 + t('helpers.page_entries_info.one_page.display_entries', entry_name: entry_name, count: collection.total_count) + else + first = collection.offset_value + 1 + last = collection.last_page? ? collection.total_count : collection.offset_value + collection.limit_value + t('helpers.page_entries_info.more_pages.display_entries', entry_name: entry_name, first: first, last: last, total: collection.total_count) + end.html_safe + end + end + + module ActiveRecordRelationMethods + def entry_name(options = {}) + model_name.human(options.reverse_merge(default: model_name.human.pluralize(options[:count]))) + end + end + + module MongoidCriteriaMethods + def entry_name(options = {}) + model_name.human(options.reverse_merge(default: model_name.human.pluralize(options[:count]))) + end + end + + class PaginatableArray < Array + ENTRY = 'entry'.freeze + + def entry_name(options = {}) + I18n.t('helpers.page_entries_info.entry', options.reverse_merge(default: ENTRY.pluralize(options[:count]))) + end + end + +end diff --git a/config/locales/activerecord.es.yml b/config/locales/activerecord.es.yml index e587a1f65..004ec04f8 100644 --- a/config/locales/activerecord.es.yml +++ b/config/locales/activerecord.es.yml @@ -1,12 +1,33 @@ es: activerecord: models: - comment: Comentario - debate: Debate - tag: Tema - user: Usuario - vote: Voto - organization: Organización + comment: + one: Comentario + other: Comentarios + debate: + one: Debate + other: Debates + tag: + one: Tema + other: Temas + user: + one: Usuario + other: Usuarios + moderator: + one: Moderador + other: Moderadores + administrator: + one: Administrador + other: Administradores + vote: + one: Voto + other: Voto + organization: + one: Organización + other: Organizaciones + proposal: + one: Propuesta ciudadana + other: Propuestas ciudadanas attributes: comment: body: Comentario @@ -16,7 +37,7 @@ es: description: Opinión terms_of_service: Términos de servicio title: Título - propuesta: + proposal: author: Autor title: Título question: Pregunta diff --git a/config/locales/kaminari.en.yml b/config/locales/kaminari.en.yml index e73c82637..e2a79c6b1 100644 --- a/config/locales/kaminari.en.yml +++ b/config/locales/kaminari.en.yml @@ -8,10 +8,14 @@ en: truncate: "…" helpers: page_entries_info: + entry: + zero: "entries" + one: "entry" + other: "entries" one_page: display_entries: zero: "No %{entry_name} found" one: "Displaying 1 %{entry_name}" other: "Displaying all %{count} %{entry_name}" more_pages: - display_entries: "Displaying %{entry_name} %{first} - %{last} of %{total} in total" \ No newline at end of file + display_entries: "Displaying %{entry_name} %{first} - %{last} of %{total} in total" diff --git a/config/locales/kaminari.es.yml b/config/locales/kaminari.es.yml index 50a2fe90c..ab3f8b71f 100644 --- a/config/locales/kaminari.es.yml +++ b/config/locales/kaminari.es.yml @@ -8,10 +8,14 @@ es: truncate: "…" helpers: page_entries_info: + entry: + zero: "entradas" + one: "entrada" + other: "entradas" one_page: display_entries: zero: "No se han encontrado %{entry_name}" - one: "Encontrado 1 %{entry_name}" - other: "Encontrados %{count} %{entry_name}" + one: "Hay 1 %{entry_name}" + other: "Hay %{count} %{entry_name}" more_pages: - display_entries: "Se muestran del %{first} al %{last} de un total de %{total} %{entry_name}" \ No newline at end of file + display_entries: "Mostrando %{first} - %{last} de un total de %{total} %{entry_name}" From d983f9e26d44adb9f0157bf0780fcfa8e9605861 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sun, 13 Sep 2015 10:31:06 +0200 Subject: [PATCH 045/138] adds question and external link to show spec --- spec/factories.rb | 2 +- spec/features/proposals_spec.rb | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/factories.rb b/spec/factories.rb index 8667cf663..c9047c675 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -105,7 +105,7 @@ FactoryGirl.define do sequence(:title) { |n| "Proposal #{n} title" } description 'Proposal description' question 'Proposal question' - external_url 'http://decide.madrid.es' + external_url 'http://external_documention.es' terms_of_service '1' association :author, factory: :user diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 0f70b4ecc..064299ba8 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -40,7 +40,9 @@ feature 'Proposals' do visit proposal_path(proposal) expect(page).to have_content proposal.title + expect(page).to have_content "Proposal question" expect(page).to have_content "Proposal description" + expect(page).to have_content "http://external_documention.es" expect(page).to have_content proposal.author.name expect(page).to have_content I18n.l(proposal.created_at.to_date) expect(page).to have_selector(avatar(proposal.author.name)) From c2750a8461b3f0295a949ac8441b734031c756b1 Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sun, 13 Sep 2015 10:31:44 +0200 Subject: [PATCH 046/138] fixes typo --- spec/models/proposal_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/proposal_spec.rb b/spec/models/proposal_spec.rb index cfb9b48fa..5ae9c3318 100644 --- a/spec/models/proposal_spec.rb +++ b/spec/models/proposal_spec.rb @@ -12,7 +12,7 @@ describe Proposal do expect(proposal).to_not be_valid end - it "should not be valid without an question" do + it "should not be valid without a question" do proposal.question = nil expect(proposal).to_not be_valid end From f3af263e68dacabf304383a5ace9fae09643540a Mon Sep 17 00:00:00 2001 From: rgarcia Date: Sun, 13 Sep 2015 11:47:57 +0200 Subject: [PATCH 047/138] adds crud specs for proposals --- app/assets/javascripts/tags.js.coffee | 2 +- app/controllers/proposals_controller.rb | 2 +- app/views/debates/_form.html.erb | 2 +- app/views/proposals/_form.html.erb | 2 +- spec/features/ckeditor_spec.rb | 19 ++ spec/features/debates_spec.rb | 22 +- spec/features/proposals_spec.rb | 287 ++++++++++++++++++++++++ spec/support/common_actions.rb | 1 + 8 files changed, 315 insertions(+), 22 deletions(-) create mode 100644 spec/features/ckeditor_spec.rb diff --git a/app/assets/javascripts/tags.js.coffee b/app/assets/javascripts/tags.js.coffee index 12c4c3115..adbcad3e8 100644 --- a/app/assets/javascripts/tags.js.coffee +++ b/app/assets/javascripts/tags.js.coffee @@ -1,7 +1,7 @@ App.Tags = initialize: -> - $tag_input = $('input#debate_tag_list') + $tag_input = $('input.js-tag-list') $('body .js-add-tag-link').each -> $this = $(this) diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index cf18ef51f..a527138e3 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -36,7 +36,7 @@ class ProposalsController < ApplicationController if @proposal.save_with_captcha ahoy.track :proposal_created, proposal_id: @proposal.id - redirect_to @proposal, notice: t('flash.actions.create.notice', resource_name: 'proposal') + redirect_to @proposal, notice: t('flash.actions.create.notice', resource_name: 'Proposal') else load_featured_tags render :new diff --git a/app/views/debates/_form.html.erb b/app/views/debates/_form.html.erb index 870f5081d..379ea60c2 100644 --- a/app/views/debates/_form.html.erb +++ b/app/views/debates/_form.html.erb @@ -20,7 +20,7 @@ <%= tag.name %> <% end %> - <%= f.text_field :tag_list, value: @debate.tag_list.to_s, label: false, placeholder: t("debates.form.tags_placeholder") %> + <%= f.text_field :tag_list, value: @debate.tag_list.to_s, label: false, placeholder: t("debates.form.tags_placeholder"), class: 'js-tag-list' %>
diff --git a/app/views/proposals/_form.html.erb b/app/views/proposals/_form.html.erb index 5607354fc..ce2714954 100644 --- a/app/views/proposals/_form.html.erb +++ b/app/views/proposals/_form.html.erb @@ -30,7 +30,7 @@ <%= tag.name %> <% end %> - <%= f.text_field :tag_list, value: @proposal.tag_list.to_s, label: false, placeholder: t("proposals.form.tags_placeholder") %> + <%= f.text_field :tag_list, value: @proposal.tag_list.to_s, label: false, placeholder: t("proposals.form.tags_placeholder"), class: 'js-tag-list' %>
diff --git a/spec/features/ckeditor_spec.rb b/spec/features/ckeditor_spec.rb new file mode 100644 index 000000000..7f5edc6e8 --- /dev/null +++ b/spec/features/ckeditor_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' + +feature 'CKEditor' do + + scenario 'is present before & after turbolinks update page', :js do + author = create(:user) + login_as(author) + + visit new_debate_path + + expect(page).to have_css "#cke_debate_description" + + click_link 'Debates' + click_link 'Start a debate' + + expect(page).to have_css "#cke_debate_description" + end + +end \ No newline at end of file diff --git a/spec/features/debates_spec.rb b/spec/features/debates_spec.rb index cc72a1b7d..79e6486c0 100644 --- a/spec/features/debates_spec.rb +++ b/spec/features/debates_spec.rb @@ -55,34 +55,20 @@ feature 'Debates' do login_as(author) visit new_debate_path - fill_in 'debate_title', with: 'Acabar con los desahucios' - fill_in 'debate_description', with: 'Esto es un tema muy importante porque...' + fill_in 'debate_title', with: 'End evictions' + fill_in 'debate_description', with: 'This is very important because...' fill_in 'debate_captcha', with: correct_captcha_text check 'debate_terms_of_service' click_button 'Start a debate' expect(page).to have_content 'Debate was successfully created.' - expect(page).to have_content 'Acabar con los desahucios' - expect(page).to have_content 'Esto es un tema muy importante porque...' + expect(page).to have_content 'End evictions' + expect(page).to have_content 'This is very important because...' expect(page).to have_content author.name expect(page).to have_content I18n.l(Debate.last.created_at.to_date) end - scenario 'CKEditor is present before & after turbolinks update page', :js do - author = create(:user) - login_as(author) - - visit new_debate_path - - expect(page).to have_css "#cke_debate_description" - - click_link 'Debates' - click_link 'Start a debate' - - expect(page).to have_css "#cke_debate_description" - end - scenario 'Captcha is required for debate creation' do login_as(create(:user)) diff --git a/spec/features/proposals_spec.rb b/spec/features/proposals_spec.rb index 064299ba8..c2d5201cd 100644 --- a/spec/features/proposals_spec.rb +++ b/spec/features/proposals_spec.rb @@ -52,6 +52,282 @@ feature 'Proposals' do end end + scenario 'Create' do + author = create(:user) + login_as(author) + + visit new_proposal_path + fill_in 'proposal_title', with: 'Help refugees' + fill_in 'proposal_question', with: '¿Would you like to give assistance to war refugees?' + fill_in 'proposal_description', with: 'This is very important because...' + fill_in 'proposal_external_url', with: 'http://rescue.org/refugees' + fill_in 'proposal_captcha', with: correct_captcha_text + check 'proposal_terms_of_service' + + click_button 'Start a proposal' + + expect(page).to have_content 'Proposal was successfully created.' + expect(page).to have_content 'Help refugees' + expect(page).to have_content '¿Would you like to give assistance to war refugees?' + expect(page).to have_content 'This is very important because...' + expect(page).to have_content 'http://rescue.org/refugees' + expect(page).to have_content author.name + expect(page).to have_content I18n.l(Proposal.last.created_at.to_date) + end + + scenario 'Captcha is required for proposal creation' do + login_as(create(:user)) + + visit new_proposal_path + fill_in 'proposal_title', with: "Great title" + fill_in 'proposal_question', with: '¿Would you like to give assistance to war refugees?' + fill_in 'proposal_description', with: 'Very important issue...' + fill_in 'proposal_external_url', with: 'http://rescue.org/refugees' + fill_in 'proposal_captcha', with: "wrongText!" + check 'proposal_terms_of_service' + + click_button "Start a proposal" + + expect(page).to_not have_content "Proposal was successfully created." + expect(page).to have_content "1 error" + + fill_in 'proposal_captcha', with: correct_captcha_text + click_button "Start a proposal" + + expect(page).to have_content "Proposal was successfully created." + end + + scenario 'Failed creation goes back to new showing featured tags' do + featured_tag = create(:tag, :featured) + tag = create(:tag) + login_as(create(:user)) + + visit new_proposal_path + fill_in 'proposal_title', with: "" + fill_in 'proposal_question', with: '¿Would you like to give assistance to war refugees?' + fill_in 'proposal_description', with: 'Very important issue...' + fill_in 'proposal_external_url', with: 'http://rescue.org/refugees' + fill_in 'proposal_captcha', with: correct_captcha_text + check 'proposal_terms_of_service' + + click_button "Start a proposal" + + expect(page).to_not have_content "Proposal was successfully created." + expect(page).to have_content "error" + within(".tags") do + expect(page).to have_content featured_tag.name + expect(page).to_not have_content tag.name + end + end + + scenario 'Errors on create' do + author = create(:user) + login_as(author) + + visit new_proposal_path + click_button 'Start a proposal' + expect(page).to have_content error_message + end + + scenario 'JS injection is prevented but safe html is respected' do + author = create(:user) + login_as(author) + + visit new_proposal_path + fill_in 'proposal_title', with: 'Testing an attack' + fill_in 'proposal_question', with: '¿Would you like to give assistance to war refugees?' + fill_in 'proposal_description', with: '

This is

' + fill_in 'proposal_external_url', with: 'http://rescue.org/refugees' + fill_in 'proposal_captcha', with: correct_captcha_text + check 'proposal_terms_of_service' + + click_button 'Start a proposal' + + expect(page).to have_content 'Proposal was successfully created.' + expect(page).to have_content 'Testing an attack' + expect(page.html).to include '

This is alert("an attack");

' + expect(page.html).to_not include '' + expect(page.html).to_not include '<p>This is' + end + + context 'Tagging proposals' do + let(:author) { create(:user) } + + background do + login_as(author) + end + + scenario 'using featured tags', :js do + ['Medio Ambiente', 'Ciencia'].each do |tag_name| + create(:tag, :featured, name: tag_name) + end + + visit new_proposal_path + + fill_in 'proposal_title', with: 'A test with enough characters' + fill_in 'proposal_question', with: '¿Would you like to give assistance to war refugees?' + fill_in_ckeditor 'proposal_description', with: 'A description with enough characters' + fill_in 'proposal_external_url', with: 'http://rescue.org/refugees' + fill_in 'proposal_captcha', with: correct_captcha_text + check 'proposal_terms_of_service' + + ['Medio Ambiente', 'Ciencia'].each do |tag_name| + find('.js-add-tag-link', text: tag_name).click + end + + click_button 'Start a proposal' + + expect(page).to have_content 'Proposal was successfully created.' + ['Medio Ambiente', 'Ciencia'].each do |tag_name| + expect(page).to have_content tag_name + end + end + + scenario 'using dangerous strings' do + visit new_proposal_path + + fill_in 'proposal_title', with: 'A test of dangerous strings' + fill_in 'proposal_question', with: '¿Would you like to give assistance to war refugees?' + fill_in 'proposal_description', with: 'A description suitable for this test' + fill_in 'proposal_external_url', with: 'http://rescue.org/refugees' + fill_in 'proposal_captcha', with: correct_captcha_text + check 'proposal_terms_of_service' + + fill_in 'proposal_tag_list', with: 'user_id=1, &a=3, ' + + click_button 'Start a proposal' + + expect(page).to have_content 'Proposal was successfully created.' + expect(page).to have_content 'user_id1' + expect(page).to have_content 'a3' + expect(page).to have_content 'scriptalert("hey");script' + expect(page.html).to_not include 'user_id=1, &a=3, ' + end + end + + scenario 'Update should not be posible if logged user is not the author' do + proposal = create(:proposal) + expect(proposal).to be_editable + login_as(create(:user)) + + visit edit_proposal_path(proposal) + expect(current_path).to eq(root_path) + expect(page).to have_content 'not authorized' + end + + scenario 'Update should not be posible if proposal is not editable' do + proposal = create(:proposal) + Setting.find_by(key: "max_votes_for_proposal_edit").update(value: 10) + 11.times { create(:vote, votable: proposal) } + + expect(proposal).to_not be_editable + + login_as(proposal.author) + visit edit_proposal_path(proposal) + + expect(current_path).to eq(root_path) + expect(page).to have_content 'not authorized' + end + + scenario 'Update should be posible for the author of an editable proposal' do + proposal = create(:proposal) + login_as(proposal.author) + + visit edit_proposal_path(proposal) + expect(current_path).to eq(edit_proposal_path(proposal)) + + fill_in 'proposal_title', with: "End child poverty" + fill_in 'proposal_question', with: '¿Would you like to give assistance to war refugees?' + fill_in 'proposal_description', with: "Let's do something to end child poverty" + fill_in 'proposal_external_url', with: 'http://rescue.org/refugees' + fill_in 'proposal_captcha', with: correct_captcha_text + + click_button "Save changes" + + expect(page).to have_content "Proposal was successfully updated." + expect(page).to have_content "End child poverty" + expect(page).to have_content "Let's do something to end child poverty" + end + + scenario 'Errors on update' do + proposal = create(:proposal) + login_as(proposal.author) + + visit edit_proposal_path(proposal) + fill_in 'proposal_title', with: "" + click_button "Save changes" + + expect(page).to have_content error_message + end + + scenario 'Captcha is required to update a proposal' do + proposal = create(:proposal) + login_as(proposal.author) + + visit edit_proposal_path(proposal) + expect(current_path).to eq(edit_proposal_path(proposal)) + + fill_in 'proposal_title', with: "New cool title" + fill_in 'proposal_captcha', with: "wrong!" + click_button "Save changes" + + expect(page).to_not have_content "Proposal was successfully updated." + expect(page).to have_content "error" + + fill_in 'proposal_captcha', with: correct_captcha_text + click_button "Save changes" + + expect(page).to have_content "Proposal was successfully updated." + end + + scenario 'Failed update goes back to edit showing featured tags' do + proposal = create(:proposal) + featured_tag = create(:tag, :featured) + tag = create(:tag) + login_as(proposal.author) + + visit edit_proposal_path(proposal) + expect(current_path).to eq(edit_proposal_path(proposal)) + + fill_in 'proposal_title', with: "" + fill_in 'proposal_captcha', with: correct_captcha_text + click_button "Save changes" + + expect(page).to_not have_content "Proposal was successfully updated." + expect(page).to have_content "error" + within(".tags") do + expect(page).to have_content featured_tag.name + expect(page).to_not have_content tag.name + end + end + + describe 'Limiting tags shown' do + scenario 'Index page shows up to 5 tags per proposal' do + tag_list = ["Hacienda", "Economía", "Medio Ambiente", "Corrupción", "Fiestas populares", "Prensa", "Huelgas"] + create :proposal, tag_list: tag_list + + visit proposals_path + + within('.proposal .tags') do + expect(page).to have_content '2+' + end + end + + scenario 'Index page shows 3 tags with no plus link' do + tag_list = ["Medio Ambiente", "Corrupción", "Fiestas populares"] + create :proposal, tag_list: tag_list + + visit proposals_path + + within('.proposal .tags') do + tag_list.each do |tag| + expect(page).to have_content tag + end + expect(page).not_to have_content '+' + end + end + end + feature 'Proposal index order filters' do scenario 'Default order is confidence_score', :js do @@ -164,4 +440,15 @@ feature 'Proposals' do expect(page).to_not have_content(proposal3.title) end end + + scenario 'Conflictive' do + good_proposal = create(:proposal) + conflictive_proposal = create(:proposal, :conflictive) + + visit proposal_path(conflictive_proposal) + expect(page).to have_content "This proposal has been flag as innapropiate for some users." + + visit proposal_path(good_proposal) + expect(page).to_not have_content "This proposal has been flag as innapropiate for some users." + end end diff --git a/spec/support/common_actions.rb b/spec/support/common_actions.rb index 2de06ce92..ff0e861ca 100644 --- a/spec/support/common_actions.rb +++ b/spec/support/common_actions.rb @@ -134,4 +134,5 @@ module CommonActions expect(page).to have_content 'Correct code' end + end From e11609a13160bb914287aca79b2ab27095174d18 Mon Sep 17 00:00:00 2001 From: kikito Date: Sun, 13 Sep 2015 11:56:54 +0200 Subject: [PATCH 048/138] fixes unclosed divs in debates/index and proposals/index --- app/views/debates/index.html.erb | 23 ++++++++++------ app/views/proposals/index.html.erb | 43 +++++++++++++++++------------- 2 files changed, 40 insertions(+), 26 deletions(-) diff --git a/app/views/debates/index.html.erb b/app/views/debates/index.html.erb index 5e7fafe0e..b910a0c0c 100644 --- a/app/views/debates/index.html.erb +++ b/app/views/debates/index.html.erb @@ -5,9 +5,9 @@ <% end %>
-
+
<% if @search_terms %> @@ -22,25 +22,32 @@ <% end %>
+ <% if @tag_filter || @search_terms %> -
+
<%= t("debates.index.select_order") %>
+ <%= render 'shared/order_selector', i18n_namespace: "debates.index" %> +
<% else %>

<%= t("debates.index.select_order_long") %>

+ <%= render 'shared/order_selector', i18n_namespace: "debates.index" %> +
<% end %> - <%= render 'shared/order_selector', i18n_namespace: "debates.index" %> + +
+ <%= link_to t("debates.index.start_debate"), new_debate_path, class: 'button radius expand' %> +
+ + <%= render @debates %> + <%= paginate @debates %>
-
- <%= link_to t("debates.index.start_debate"), new_debate_path, class: 'button radius expand' %> -
- <%= render @debates %> - <%= paginate @debates %>
+