decidim-sortitions 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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