decidim-debates 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +15 -0
  3. data/Rakefile +3 -0
  4. data/app/assets/images/decidim/debates/icon.svg +1 -0
  5. data/app/commands/decidim/debates/admin/create_debate.rb +57 -0
  6. data/app/commands/decidim/debates/admin/update_debate.rb +46 -0
  7. data/app/commands/decidim/debates/create_debate.rb +68 -0
  8. data/app/controllers/decidim/debates/admin/application_controller.rb +15 -0
  9. data/app/controllers/decidim/debates/admin/debates_controller.rb +73 -0
  10. data/app/controllers/decidim/debates/application_controller.rb +13 -0
  11. data/app/controllers/decidim/debates/debates_controller.rb +78 -0
  12. data/app/events/decidim/debates/create_debate_event.rb +42 -0
  13. data/app/forms/decidim/debates/admin/debate_form.rb +33 -0
  14. data/app/forms/decidim/debates/debate_form.rb +25 -0
  15. data/app/helpers/decidim/debates/application_helper.rb +12 -0
  16. data/app/models/decidim/debates/abilities/admin/admin_ability.rb +21 -0
  17. data/app/models/decidim/debates/abilities/admin/participatory_process_admin_ability.rb +23 -0
  18. data/app/models/decidim/debates/abilities/admin/participatory_process_moderator_ability.rb +21 -0
  19. data/app/models/decidim/debates/abilities/current_user_ability.rb +49 -0
  20. data/app/models/decidim/debates/application_record.rb +10 -0
  21. data/app/models/decidim/debates/debate.rb +103 -0
  22. data/app/presenters/decidim/debates/debate_presenter.rb +20 -0
  23. data/app/presenters/decidim/debates/official_author_presenter.rb +34 -0
  24. data/app/services/decidim/debates/debate_search.rb +38 -0
  25. data/app/views/decidim/debates/admin/debates/_form.html.erb +36 -0
  26. data/app/views/decidim/debates/admin/debates/edit.html.erb +7 -0
  27. data/app/views/decidim/debates/admin/debates/index.html.erb +52 -0
  28. data/app/views/decidim/debates/admin/debates/new.html.erb +7 -0
  29. data/app/views/decidim/debates/debates/_count.html.erb +1 -0
  30. data/app/views/decidim/debates/debates/_debate.html.erb +42 -0
  31. data/app/views/decidim/debates/debates/_debates.html.erb +4 -0
  32. data/app/views/decidim/debates/debates/_filters.html.erb +20 -0
  33. data/app/views/decidim/debates/debates/_filters_small_view.html.erb +18 -0
  34. data/app/views/decidim/debates/debates/_share.html.erb +33 -0
  35. data/app/views/decidim/debates/debates/index.html.erb +27 -0
  36. data/app/views/decidim/debates/debates/index.js.erb +9 -0
  37. data/app/views/decidim/debates/debates/new.html.erb +43 -0
  38. data/app/views/decidim/debates/debates/show.html.erb +80 -0
  39. data/config/locales/ca.yml +114 -0
  40. data/config/locales/en.yml +117 -0
  41. data/config/locales/es.yml +114 -0
  42. data/config/locales/eu.yml +114 -0
  43. data/config/locales/fi.yml +114 -0
  44. data/config/locales/fr.yml +114 -0
  45. data/config/locales/gl.yml +114 -0
  46. data/config/locales/it.yml +114 -0
  47. data/config/locales/nl.yml +114 -0
  48. data/config/locales/pl.yml +116 -0
  49. data/config/locales/pt-BR.yml +114 -0
  50. data/config/locales/pt.yml +114 -0
  51. data/config/locales/ru.yml +5 -0
  52. data/config/locales/sv.yml +114 -0
  53. data/config/locales/uk.yml +5 -0
  54. data/db/migrate/20170118141619_create_debates.rb +18 -0
  55. data/db/migrate/20180117100413_add_debate_information_updates.rb +7 -0
  56. data/db/migrate/20180118132243_add_author_to_debates.rb +7 -0
  57. data/db/migrate/20180119150434_add_reference_to_debates.rb +12 -0
  58. data/db/migrate/20180122090505_add_user_group_author_to_debates.rb +7 -0
  59. data/lib/decidim/debates/admin.rb +10 -0
  60. data/lib/decidim/debates/admin_engine.rb +33 -0
  61. data/lib/decidim/debates/engine.rb +26 -0
  62. data/lib/decidim/debates/feature.rb +55 -0
  63. data/lib/decidim/debates/test/factories.rb +52 -0
  64. data/lib/decidim/debates/version.rb +10 -0
  65. data/lib/decidim/debates.rb +12 -0
  66. metadata +197 -0
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # This class holds a Form to create/update debates from Decidim's admin panel.
6
+ class DebateForm < Decidim::Form
7
+ include TranslatableAttributes
8
+
9
+ attribute :title, String
10
+ attribute :description, String
11
+ attribute :category_id, Integer
12
+ attribute :user_group_id, Integer
13
+
14
+ validates :title, presence: true
15
+ validates :description, presence: true
16
+
17
+ validates :category, presence: true, if: ->(form) { form.category_id.present? }
18
+
19
+ def category
20
+ return unless current_feature
21
+ @category ||= current_feature.categories.where(id: category_id).first
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # Custom helpers, scoped to the debates engine.
6
+ #
7
+ module ApplicationHelper
8
+ include PaginateHelper
9
+ include Decidim::Comments::CommentsHelper
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Abilities
6
+ module Admin
7
+ # Defines the abilities related to debates for an admin user.
8
+ # Intended to be used with `cancancan`.
9
+ class AdminAbility < Decidim::Abilities::AdminAbility
10
+ def define_abilities
11
+ can :manage, Debate do |debate|
12
+ debate.author.blank?
13
+ end
14
+ can :unreport, Debate
15
+ can :hide, Debate
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Abilities
6
+ module Admin
7
+ # Defines the abilities related to debates for a logged in process admin user.
8
+ # Intended to be used with `cancancan`.
9
+ class ParticipatoryProcessAdminAbility < Decidim::Abilities::ParticipatoryProcessAdminAbility
10
+ def define_participatory_process_abilities
11
+ super
12
+
13
+ can :manage, Debate do |debate|
14
+ debate.author.blank?
15
+ end
16
+ can :unreport, Debate
17
+ can :hide, Debate
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Abilities
6
+ module Admin
7
+ # Defines the abilities related to Debate for a logged in process admin user.
8
+ # Intended to be used with `cancancan`.
9
+ class ParticipatoryProcessModeratorAbility < Decidim::Abilities::ParticipatoryProcessModeratorAbility
10
+ def define_participatory_process_abilities
11
+ super
12
+
13
+ can [:unreport, :hide], Debate do |debate|
14
+ can_manage_process?(debate.feature.participatory_space)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ module Abilities
6
+ # Defines the abilities related to debates for a logged in user.
7
+ # Intended to be used with `cancancan`.
8
+ class CurrentUserAbility
9
+ include CanCan::Ability
10
+
11
+ attr_reader :user, :context
12
+
13
+ def initialize(user, context)
14
+ return unless user
15
+
16
+ @user = user
17
+ @context = context
18
+
19
+ can :create, Debate if authorized?(:create) && creation_enabled?
20
+ can :report, Debate
21
+ end
22
+
23
+ private
24
+
25
+ def authorized?(action)
26
+ return unless feature
27
+
28
+ ActionAuthorizer.new(user, feature, action).authorize.ok?
29
+ end
30
+
31
+ def creation_enabled?
32
+ return unless current_settings
33
+ current_settings.creation_enabled?
34
+ end
35
+
36
+ def current_settings
37
+ context.fetch(:current_settings, nil)
38
+ end
39
+
40
+ def feature
41
+ feature = context.fetch(:current_feature, nil)
42
+ return nil unless feature && feature.manifest.name == :debates
43
+
44
+ feature
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # Abstract class from which all models in this engine inherit.
6
+ class ApplicationRecord < ActiveRecord::Base
7
+ self.abstract_class = true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # The data store for a Debate in the Decidim::Debates component. It stores a
6
+ # title, description and any other useful information to render a custom
7
+ # debate.
8
+ class Debate < Debates::ApplicationRecord
9
+ include Decidim::HasFeature
10
+ include Decidim::HasCategory
11
+ include Decidim::Resourceable
12
+ include Decidim::Followable
13
+ include Decidim::Comments::Commentable
14
+ include Decidim::HasScope
15
+ include Decidim::Authorable
16
+ include Decidim::Reportable
17
+ include Decidim::HasReference
18
+
19
+ feature_manifest_name "debates"
20
+
21
+ validates :title, presence: true
22
+
23
+ # Public: Checks whether the debate is official or not.
24
+ #
25
+ # Returns a boolean.
26
+ def official?
27
+ author.blank?
28
+ end
29
+
30
+ # Public: Overrides the `reported_content_url` Reportable concern method.
31
+ def reported_content_url
32
+ ResourceLocatorPresenter.new(self).url
33
+ end
34
+
35
+ # Public: Calculates whether the current debate is an AMA-styled one or not.
36
+ # AMA-styled debates are those that have a start and end time set, and comments
37
+ # are only open during that timelapse. AMA stands for Ask Me Anything, a type
38
+ # of debate inspired by Reddit.
39
+ #
40
+ # Returns a Boolean.
41
+ def ama?
42
+ start_time.present? && end_time.present?
43
+ end
44
+
45
+ # Public: Checks whether the debate is an AMA-styled one and is open.
46
+ #
47
+ # Returns a boolean.
48
+ def open_ama?
49
+ ama? && Time.current.between?(start_time, end_time)
50
+ end
51
+
52
+ # Public: Checks if the debate is open or not.
53
+ #
54
+ # Returns a boolean.
55
+ def open?
56
+ (ama? && open_ama?) || !ama?
57
+ end
58
+
59
+ # Public: Overrides the `commentable?` Commentable concern method.
60
+ def commentable?
61
+ feature.settings.comments_enabled?
62
+ end
63
+
64
+ # Public: Overrides the `accepts_new_comments?` Commentable concern method.
65
+ def accepts_new_comments?
66
+ return false unless open?
67
+ commentable? && !comments_blocked?
68
+ end
69
+
70
+ # Public: Overrides the `comments_have_alignment?` Commentable concern method.
71
+ def comments_have_alignment?
72
+ true
73
+ end
74
+
75
+ # Public: Overrides the `comments_have_votes?` Commentable concern method.
76
+ def comments_have_votes?
77
+ true
78
+ end
79
+
80
+ # Public: Identifies the commentable type in the API.
81
+ def commentable_type
82
+ self.class.name
83
+ end
84
+
85
+ # Public: Overrides the `notifiable?` Notifiable concern method.
86
+ def notifiable?(_context)
87
+ false
88
+ end
89
+
90
+ # Public: Override Commentable concern method `users_to_notify_on_comment_created`
91
+ def users_to_notify_on_comment_created
92
+ return (followers | feature.participatory_space.admins).uniq if official?
93
+ followers
94
+ end
95
+
96
+ private
97
+
98
+ def comments_blocked?
99
+ feature.current_settings.comments_blocked
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ #
6
+ # Decorator for debates
7
+ #
8
+ class DebatePresenter < SimpleDelegator
9
+ def author
10
+ @author ||= if official?
11
+ Decidim::Debates::OfficialAuthorPresenter.new
12
+ elsif user_group
13
+ Decidim::UserGroupPresenter.new(user_group)
14
+ else
15
+ Decidim::UserPresenter.new(super)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ #
6
+ # A dummy presenter to abstract out the author of an official debate.
7
+ #
8
+ class OfficialAuthorPresenter
9
+ def name
10
+ I18n.t("decidim.debates.models.debate.fields.official_debate")
11
+ end
12
+
13
+ def nickname
14
+ ""
15
+ end
16
+
17
+ def badge
18
+ ""
19
+ end
20
+
21
+ def profile_path
22
+ ""
23
+ end
24
+
25
+ def avatar_url
26
+ ActionController::Base.helpers.asset_path("decidim/default-avatar.svg")
27
+ end
28
+
29
+ def deleted?
30
+ false
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Debates
5
+ # This class handles search and filtering of debates. Needs a
6
+ # `current_feature` param with a `Decidim::Feature` in order to
7
+ # find the debates.
8
+ class DebateSearch < ResourceSearch
9
+ # Public: Initializes the service.
10
+ # feature - A Decidim::Feature to get the debates from.
11
+ # page - The page number to paginate the results.
12
+ # per_page - The number of proposals to return per page.
13
+ def initialize(options = {})
14
+ super(Debate.not_hidden, options)
15
+ end
16
+
17
+ # Handle the search_text filter. We have to cast the JSONB columns
18
+ # into a `text` type so that we can search.
19
+ def search_search_text
20
+ query
21
+ .where("title::text ILIKE ?", "%#{search_text}%")
22
+ .or(query.where("description::text ILIKE ?", "%#{search_text}%"))
23
+ end
24
+
25
+ # Handle the origin filter
26
+ # The 'official' debates don't have an author ID
27
+ def search_origin
28
+ if origin == "official"
29
+ query.where(decidim_author_id: nil)
30
+ elsif origin == "citizens"
31
+ query.where.not(decidim_author_id: nil)
32
+ else # Assume 'all'
33
+ query
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,36 @@
1
+ <div class="card">
2
+ <div class="card-divider">
3
+ <h2 class="card-title"><%= title %></h2>
4
+ </div>
5
+
6
+ <div class="card-section">
7
+ <div class="row column" >
8
+ <%= form.translated :text_field, :title, autofocus: true %>
9
+ </div>
10
+
11
+ <div class="row column" >
12
+ <%= form.translated :editor, :description %>
13
+ </div>
14
+
15
+ <div class="row column" >
16
+ <%= form.translated :editor, :instructions %>
17
+ </div>
18
+
19
+ <div class="row column" >
20
+ <%= form.translated :editor, :information_updates %>
21
+ </div>
22
+
23
+ <div class="row" >
24
+ <div class="column xlarge-6">
25
+ <%= form.datetime_field :start_time %>
26
+ </div>
27
+ <div class="column xlarge-6">
28
+ <%= form.datetime_field :end_time %>
29
+ </div>
30
+ </div>
31
+
32
+ <div class="row column" >
33
+ <%= form.categories_select :decidim_category_id, current_participatory_space.categories, prompt: "", disable_parents: false %>
34
+ </div>
35
+ </div>
36
+ </div>
@@ -0,0 +1,7 @@
1
+ <%= decidim_form_for(@form, html: { class: "form edit_debate" }) do |f| %>
2
+ <%= render partial: 'form', object: f, locals: { title: t('.title') } %>
3
+
4
+ <div class="button--double form-general-submit">
5
+ <%= f.submit t(".update") %>
6
+ </div>
7
+ <% end %>
@@ -0,0 +1,52 @@
1
+ <div class="card">
2
+ <div class="card-divider">
3
+ <h2 class="card-title">
4
+ <%= t(".title") %>
5
+
6
+ <%= link_to t("actions.new", scope: "decidim.debates", name: t("models.debate.name", scope: "decidim.debates.admin")), new_debate_path, class: 'button tiny button--title' if can? :manage, current_feature %>
7
+ </h2>
8
+ </div>
9
+
10
+ <div class="card-section">
11
+ <div class="table-scroll">
12
+ <table class="table-list">
13
+ <thead>
14
+ <tr>
15
+ <th><%= t("models.debate.fields.title", scope: "decidim.debates") %></th>
16
+ <th><%= t("models.debate.fields.start_time", scope: "decidim.debates") %></th>
17
+ <th><%= t("models.debate.fields.end_time", scope: "decidim.debates") %></th>
18
+ <th class="actions"><%= t("actions.title", scope: "decidim.debates") %></th>
19
+ </tr>
20
+ </thead>
21
+ <tbody>
22
+ <% debates.each do |debate| %>
23
+ <tr data-id="<%= debate.id %>">
24
+ <td>
25
+ <%= link_to translated_attribute(debate.title), resource_locator(debate).path, target: :blank %><br />
26
+ </td>
27
+ <td>
28
+ <% if debate.start_time %>
29
+ <%= l debate.start_time, format: :long %>
30
+ <% end%>
31
+ </td>
32
+ <td>
33
+ <% if debate.end_time %>
34
+ <%= l debate.end_time, format: :long %>
35
+ <% end%>
36
+ </td>
37
+ <td class="table-list__actions">
38
+ <% if can? :update, debate %>
39
+ <%= icon_link_to "pencil", edit_debate_path(debate), t("actions.edit", scope: "decidim.debates"), class: "action-icon--edit" %>
40
+ <% end %>
41
+
42
+ <% if can? :destroy, debate %>
43
+ <%= icon_link_to "circle-x", debate_path(debate), t("actions.destroy", scope: "decidim.debates"), method: :delete, class: "action-icon--remove", data: { confirm: t("actions.confirm_destroy", scope: "decidim.debates") } %>
44
+ <% end %>
45
+ </td>
46
+ </tr>
47
+ <% end %>
48
+ </tbody>
49
+ </table>
50
+ </div>
51
+ </div>
52
+ </div>