decidim-sortitions 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +31 -0
  4. data/Rakefile +3 -0
  5. data/app/assets/config/decidim_sortitions_manifest.css +1 -0
  6. data/app/assets/config/decidim_sortitions_manifest.js +1 -0
  7. data/app/assets/images/decidim/sortitions/icon.svg +4 -0
  8. data/app/assets/javascripts/decidim/sortitions/admin/sortitions.js +2 -0
  9. data/app/assets/javascripts/decidim/sortitions/social_share.js +2 -0
  10. data/app/assets/stylesheets/decidim/sortitions/_sortitions.scss +45 -0
  11. data/app/assets/stylesheets/decidim/sortitions/social_share.css.scss +18 -0
  12. data/app/commands/decidim/sortitions/admin/create_sortition.rb +81 -0
  13. data/app/commands/decidim/sortitions/admin/destroy_sortition.rb +45 -0
  14. data/app/commands/decidim/sortitions/admin/update_sortition.rb +45 -0
  15. data/app/controllers/concerns/decidim/sortitions/orderable.rb +50 -0
  16. data/app/controllers/decidim/sortitions/admin/application_controller.rb +28 -0
  17. data/app/controllers/decidim/sortitions/admin/sortitions_controller.rb +104 -0
  18. data/app/controllers/decidim/sortitions/application_controller.rb +13 -0
  19. data/app/controllers/decidim/sortitions/sortition_widgets_controller.rb +20 -0
  20. data/app/controllers/decidim/sortitions/sortitions_controller.rb +45 -0
  21. data/app/events/decidim/sortitions/create_sortition_event.rb +8 -0
  22. data/app/forms/decidim/sortitions/admin/destroy_sortition_form.rb +20 -0
  23. data/app/forms/decidim/sortitions/admin/edit_sortition_form.rb +22 -0
  24. data/app/forms/decidim/sortitions/admin/sortition_form.rb +44 -0
  25. data/app/helpers/decidim/sortitions/admin/sortitions_helper.rb +24 -0
  26. data/app/helpers/decidim/sortitions/sortitions_helper.rb +38 -0
  27. data/app/models/decidim/sortitions/abilities/admin/admin_ability.rb +23 -0
  28. data/app/models/decidim/sortitions/abilities/admin/process_admin_ability.rb +46 -0
  29. data/app/models/decidim/sortitions/application_record.rb +9 -0
  30. data/app/models/decidim/sortitions/sortition.rb +81 -0
  31. data/app/queries/decidim/sortitions/admin/participatory_space_proposal_features.rb +29 -0
  32. data/app/queries/decidim/sortitions/admin/participatory_space_proposals.rb +49 -0
  33. data/app/queries/decidim/sortitions/filtered_sortitions.rb +37 -0
  34. data/app/services/decidim/sortitions/admin/draw.rb +31 -0
  35. data/app/services/decidim/sortitions/sortition_search.rb +47 -0
  36. data/app/views/decidim/sortitions/admin/sortitions/_form.html.erb +41 -0
  37. data/app/views/decidim/sortitions/admin/sortitions/confirm_destroy.html.erb +21 -0
  38. data/app/views/decidim/sortitions/admin/sortitions/edit.html.erb +27 -0
  39. data/app/views/decidim/sortitions/admin/sortitions/index.html.erb +57 -0
  40. data/app/views/decidim/sortitions/admin/sortitions/new.html.erb +11 -0
  41. data/app/views/decidim/sortitions/admin/sortitions/show.html.erb +55 -0
  42. data/app/views/decidim/sortitions/sortition_widgets/show.html.erb +4 -0
  43. data/app/views/decidim/sortitions/sortitions/_count.html.erb +1 -0
  44. data/app/views/decidim/sortitions/sortitions/_filters.html.erb +31 -0
  45. data/app/views/decidim/sortitions/sortitions/_filters_small_view.html.erb +18 -0
  46. data/app/views/decidim/sortitions/sortitions/_filters_small_view0.html.erb +18 -0
  47. data/app/views/decidim/sortitions/sortitions/_linked_sortitions.html.erb +29 -0
  48. data/app/views/decidim/sortitions/sortitions/_proposal.html.erb +24 -0
  49. data/app/views/decidim/sortitions/sortitions/_proposal_badge.html.erb +3 -0
  50. data/app/views/decidim/sortitions/sortitions/_results_count.html.erb +8 -0
  51. data/app/views/decidim/sortitions/sortitions/_sortition.html.erb +25 -0
  52. data/app/views/decidim/sortitions/sortitions/_sortition_author.html.erb +17 -0
  53. data/app/views/decidim/sortitions/sortitions/_sortition_cancel_author.html.erb +17 -0
  54. data/app/views/decidim/sortitions/sortitions/_sortitions.html.erb +14 -0
  55. data/app/views/decidim/sortitions/sortitions/_sortitions_count.html.erb +1 -0
  56. data/app/views/decidim/sortitions/sortitions/_tags.html.erb +16 -0
  57. data/app/views/decidim/sortitions/sortitions/index.html.erb +21 -0
  58. data/app/views/decidim/sortitions/sortitions/index.js.erb +10 -0
  59. data/app/views/decidim/sortitions/sortitions/show.html.erb +134 -0
  60. data/config/initializers/social_share_button.rb +5 -0
  61. data/config/locales/ca.yml +155 -0
  62. data/config/locales/en.yml +154 -0
  63. data/config/locales/es.yml +155 -0
  64. data/db/migrate/20171215161358_create_decidim_module_sortitions_sortitions.rb +23 -0
  65. data/db/migrate/20171220164658_add_witnesses_to_sortitions.rb +7 -0
  66. data/db/migrate/20171220164744_add_additional_info_to_sortitions.rb +7 -0
  67. data/db/migrate/20180102100101_add_author_to_sortitions.rb +7 -0
  68. data/db/migrate/20180102101128_add_reference_to_sortitions.rb +8 -0
  69. data/db/migrate/20180103082645_add_title_to_sortition.rb +7 -0
  70. data/db/migrate/20180103123055_drop_decidim_category_id_from_sortitions.rb +13 -0
  71. data/db/migrate/20180103160301_add_cancel_data_to_sortition.rb +9 -0
  72. data/db/migrate/20180104143054_make_sortition_reference_nullable.rb +7 -0
  73. data/db/migrate/20180104145344_add_candidate_proposals_to_sortitions.rb +7 -0
  74. data/db/migrate/20180108132729_rename_sortitions_table.rb +7 -0
  75. data/lib/decidim/sortitions.rb +24 -0
  76. data/lib/decidim/sortitions/admin.rb +10 -0
  77. data/lib/decidim/sortitions/admin_engine.rb +35 -0
  78. data/lib/decidim/sortitions/engine.rb +26 -0
  79. data/lib/decidim/sortitions/feature.rb +43 -0
  80. data/lib/decidim/sortitions/test/factories.rb +37 -0
  81. data/lib/decidim/sortitions/version.rb +7 -0
  82. metadata +208 -0
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ # This controller is the abstract class from which all other controllers of
6
+ # this engine inherit.
7
+ #
8
+ # Note that it inherits from `Decidim::Features::BaseController`, which
9
+ # override its layout and provide all kinds of useful methods.
10
+ class ApplicationController < Decidim::Features::BaseController
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ class SortitionWidgetsController < Decidim::WidgetsController
6
+ helper Decidim::SanitizeHelper
7
+ helper Sortitions::SortitionsHelper
8
+
9
+ private
10
+
11
+ def model
12
+ @model ||= Sortition.where(feature: params[:feature_id]).find(params[:sortition_id])
13
+ end
14
+
15
+ def iframe_url
16
+ @iframe_url ||= sortition_sortition_widget_url(model)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ # Exposes the sortition resource so users can view them
6
+ class SortitionsController < Decidim::Sortitions::ApplicationController
7
+ helper Decidim::WidgetUrlsHelper
8
+ include FilterResource
9
+ include Orderable
10
+ include Paginable
11
+
12
+ helper_method :sortition
13
+
14
+ helper Decidim::Proposals::ApplicationHelper
15
+
16
+ def index
17
+ @sortitions = search
18
+ .results
19
+ .includes(:author)
20
+ .includes(:category)
21
+
22
+ @sortitions = paginate(@sortitions)
23
+ @sortitions = reorder(@sortitions)
24
+ end
25
+
26
+ private
27
+
28
+ def sortition
29
+ Sortition.find(params[:id])
30
+ end
31
+
32
+ def search_klass
33
+ SortitionSearch
34
+ end
35
+
36
+ def default_filter_params
37
+ {
38
+ search_text: "",
39
+ category_id: "",
40
+ state: "active"
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ class CreateSortitionEvent < Decidim::Events::SimpleEvent
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ class DestroySortitionForm < Form
7
+ include TranslatableAttributes
8
+
9
+ mimic :sortition
10
+
11
+ translatable_attribute :cancel_reason, String
12
+
13
+ validates :cancel_reason, translatable_presence: true
14
+
15
+ delegate :current_participatory_space, to: :context
16
+ delegate :current_feature, to: :context
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ class EditSortitionForm < Form
7
+ include TranslatableAttributes
8
+
9
+ mimic :sortition
10
+
11
+ translatable_attribute :title, String
12
+ translatable_attribute :additional_info, String
13
+
14
+ validates :title, translatable_presence: true
15
+ validates :additional_info, translatable_presence: true
16
+
17
+ delegate :current_participatory_space, to: :context
18
+ delegate :current_feature, to: :context
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ class SortitionForm < Form
7
+ include TranslatableAttributes
8
+
9
+ mimic :sortition
10
+
11
+ translatable_attribute :title, String
12
+ attribute :decidim_proposals_feature_id, Integer
13
+ attribute :decidim_category_id, Integer
14
+ attribute :dice, Integer
15
+ attribute :target_items, Integer
16
+ translatable_attribute :witnesses, String
17
+ translatable_attribute :additional_info, String
18
+
19
+ validates :title, translatable_presence: true
20
+ validates :decidim_proposals_feature_id, presence: true
21
+ validates :witnesses, translatable_presence: true
22
+ validates :additional_info, translatable_presence: true
23
+ validates :dice,
24
+ presence: true,
25
+ numericality: {
26
+ only_integer: true,
27
+ greater_than_or_equal_to: 1,
28
+ less_than_or_equal_to: 6
29
+ }
30
+
31
+ validates :target_items,
32
+ presence: true,
33
+ numericality: {
34
+ only_integer: true,
35
+ greater_than_or_equal_to: 1
36
+ }
37
+
38
+ delegate :categories, to: :current_participatory_space
39
+ delegate :current_participatory_space, to: :context
40
+ delegate :current_feature, to: :context
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ module SortitionsHelper
7
+ include Decidim::TranslationsHelper
8
+
9
+ # Converst a list of features into a list of selectable options
10
+ def features_options(features)
11
+ features.map do |f|
12
+ [translated_attribute(f.name), f.id]
13
+ end
14
+ end
15
+
16
+ def sortition_category(sortition)
17
+ return translated_attribute sortition.category&.name if sortition.category
18
+
19
+ I18n.t("sortitions.form.all_categories", scope: "decidim.sortitions.admin")
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module SortitionsHelper
6
+ include Decidim::TranslationsHelper
7
+
8
+ def proposal_path(proposal)
9
+ EngineRouter.main_proxy(proposal.feature).proposal_path(proposal)
10
+ end
11
+
12
+ # Generates the sortition category label
13
+ def sortition_category_label(sortition)
14
+ if sortition.category.present?
15
+ return I18n.t("show.category",
16
+ scope: "decidim.sortitions.sortitions",
17
+ category: translated_attribute(sortition.category.name))
18
+ end
19
+
20
+ I18n.t("show.any_category", scope: "decidim.sortitions.sortitions")
21
+ end
22
+
23
+ # Show list of candidate proposals for a sortition. Selected sortition ids will appear with bold font.
24
+ def sortition_proposal_candidate_ids(sortition)
25
+ result = []
26
+ sortition.candidate_proposals.each do |proposal_id|
27
+ result << if sortition.selected_proposals.include? proposal_id
28
+ "<b>#{proposal_id}</b>"
29
+ else
30
+ proposal_id.to_s
31
+ end
32
+ end
33
+
34
+ result.join(", ").html_safe
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Abilities
6
+ module Admin
7
+ # Defines the abilities for a user in the admin section. Intended to be
8
+ # used with `cancancan`.
9
+ class AdminAbility < Decidim::Abilities::AdminAbility
10
+ def define_abilities
11
+ super
12
+
13
+ can :manage, Sortition
14
+ cannot :destroy, Sortition
15
+ can :destroy, Sortition do |sortition|
16
+ !sortition.cancelled?
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Abilities
6
+ module Admin
7
+ class ProcessAdminAbility
8
+ include CanCan::Ability
9
+
10
+ attr_reader :user, :context
11
+
12
+ def initialize(user, context)
13
+ return unless user
14
+ return if user.admin?
15
+
16
+ @user = user
17
+ @context = context
18
+
19
+ return unless process_administrator?
20
+
21
+ can :manage, Sortition
22
+ cannot :destroy, Sortition
23
+ can :destroy, Sortition do |sortition|
24
+ !sortition.cancelled?
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def process_administrator?
31
+ return false unless current_participatory_space.is_a? Decidim::ParticipatoryProcess
32
+
33
+ Decidim::ParticipatoryProcesses::Admin::AdminUsers
34
+ .for(current_participatory_space)
35
+ .where(id: user.id)
36
+ .any?
37
+ end
38
+
39
+ def current_participatory_space
40
+ @current_participatory_space ||= @context[:current_participatory_space]
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ class ApplicationRecord < ActiveRecord::Base
6
+ self.abstract_class = true
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ # Model that encapsulates the parameters of a sortion
6
+ class Sortition < ApplicationRecord
7
+ include Decidim::Resourceable
8
+ include Decidim::HasCategory
9
+ include Decidim::Authorable
10
+ include Decidim::HasFeature
11
+ include Decidim::HasReference
12
+ include Decidim::Comments::Commentable
13
+
14
+ feature_manifest_name "sortitions"
15
+
16
+ belongs_to :decidim_proposals_feature,
17
+ foreign_key: "decidim_proposals_feature_id",
18
+ class_name: "Decidim::Feature"
19
+
20
+ belongs_to :cancelled_by_user,
21
+ foreign_key: "cancelled_by_user_id",
22
+ class_name: "Decidim::User",
23
+ optional: true
24
+
25
+ scope :categorized_as, lambda { |category_id|
26
+ includes(:categorization)
27
+ .where("decidim_categorizations.decidim_category_id" => category_id)
28
+ }
29
+
30
+ scope :active, -> { where(cancelled_on: nil) }
31
+ scope :cancelled, -> { where.not(cancelled_on: nil) }
32
+
33
+ def proposals
34
+ Decidim::Proposals::Proposal.where(id: selected_proposals)
35
+ end
36
+
37
+ def similar_count
38
+ Sortition.where(feature: feature)
39
+ .where(decidim_proposals_feature: decidim_proposals_feature)
40
+ .categorized_as(category&.id)
41
+ .where(target_items: target_items)
42
+ .count
43
+ end
44
+
45
+ def seed
46
+ request_timestamp.to_i * dice
47
+ end
48
+
49
+ def cancelled?
50
+ cancelled_on.present?
51
+ end
52
+
53
+ # Public: Overrides the `commentable?` Commentable concern method.
54
+ def commentable?
55
+ feature.settings.comments_enabled?
56
+ end
57
+
58
+ # Public: Overrides the `accepts_new_comments?` Commentable concern method.
59
+ def accepts_new_comments?
60
+ commentable?
61
+ end
62
+
63
+ # Public: Overrides the `comments_have_alignment?` Commentable concern method.
64
+ def comments_have_alignment?
65
+ true
66
+ end
67
+
68
+ # Public: Overrides the `comments_have_votes?` Commentable concern method.
69
+ def comments_have_votes?
70
+ true
71
+ end
72
+
73
+ def self.order_randomly(seed)
74
+ transaction do
75
+ connection.execute("SELECT setseed(#{connection.quote(seed)})")
76
+ order("RANDOM()").load
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Sortitions
5
+ module Admin
6
+ # Query that retrieves a list of proposal features
7
+ class ParticipatorySpaceProposalFeatures < Rectify::Query
8
+ attr_reader :participatory_space
9
+
10
+ # Sugar syntax. Allow retrieving all proposal features for the
11
+ # given participatory space.
12
+ def self.for(participatory_space)
13
+ new(participatory_space).query
14
+ end
15
+
16
+ # Initializes the query
17
+ def initialize(participatory_space)
18
+ @participatory_space = participatory_space
19
+ end
20
+
21
+ def query
22
+ Feature
23
+ .where(participatory_space: participatory_space, manifest_name: "proposals")
24
+ .published
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end