decidim-core 0.0.1.alpha7 → 0.0.1.alpha8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +619 -0
  3. data/README.md +1 -1
  4. data/app/assets/javascripts/decidim.js.es6 +13 -0
  5. data/app/assets/stylesheets/decidim/_variables.scss +23 -0
  6. data/app/assets/stylesheets/decidim/application.sass +2 -0
  7. data/app/assets/stylesheets/decidim/extras/_turbolinks.scss +3 -0
  8. data/app/assets/stylesheets/decidim/modules/_cards.scss +0 -5
  9. data/app/assets/stylesheets/decidim/utils/_settings.scss +5 -11
  10. data/app/commands/decidim/authorize_user.rb +37 -0
  11. data/app/controllers/concerns/decidim/needs_authorization.rb +36 -0
  12. data/app/controllers/decidim/account_controller.rb +26 -0
  13. data/app/controllers/decidim/application_controller.rb +5 -0
  14. data/app/controllers/decidim/authorizations_controller.rb +76 -0
  15. data/app/controllers/decidim/devise/registrations_controller.rb +1 -1
  16. data/app/controllers/decidim/devise/sessions_controller.rb +8 -0
  17. data/app/controllers/decidim/locales_controller.rb +2 -0
  18. data/app/controllers/decidim/pages_controller.rb +10 -0
  19. data/app/controllers/decidim/participatory_processes_controller.rb +32 -0
  20. data/app/helpers/decidim/authorization_form_helper.rb +25 -0
  21. data/app/helpers/decidim/humanize_booleans_helper.rb +14 -0
  22. data/app/mailers/decidim/application_mailer.rb +1 -1
  23. data/app/models/decidim/abilities/everyone.rb +16 -0
  24. data/app/models/decidim/ability.rb +32 -0
  25. data/app/models/decidim/authorization.rb +25 -0
  26. data/app/models/decidim/participatory_process.rb +25 -0
  27. data/app/models/decidim/participatory_process_step.rb +38 -0
  28. data/app/models/decidim/user.rb +19 -0
  29. data/app/services/decidim/authorization_handler.rb +94 -0
  30. data/app/uploaders/decidim/application_uploader.rb +0 -2
  31. data/app/views/decidim/account/_authorizations.html.erb +52 -0
  32. data/app/views/decidim/account/show.html.erb +32 -0
  33. data/app/views/decidim/authorizations/index.html.erb +22 -0
  34. data/app/views/decidim/authorizations/new.html.erb +29 -0
  35. data/app/views/decidim/devise/confirmations/new.html.erb +27 -11
  36. data/app/views/decidim/devise/invitations/edit.html.erb +25 -10
  37. data/app/views/decidim/devise/passwords/edit.html.erb +30 -15
  38. data/app/views/decidim/devise/passwords/new.html.erb +25 -11
  39. data/app/views/decidim/devise/registrations/new.html.erb +48 -20
  40. data/app/views/decidim/devise/sessions/new.html.erb +40 -21
  41. data/app/views/decidim/devise/shared/_links.html.erb +18 -6
  42. data/app/views/decidim/participatory_processes/_no_processes_yet.html.erb +3 -0
  43. data/app/views/decidim/participatory_processes/_order_by_processes.html.erb +3 -0
  44. data/app/views/decidim/participatory_processes/_participatory_process.html.erb +26 -0
  45. data/app/views/decidim/participatory_processes/_process_header_home.html.erb +18 -0
  46. data/app/views/decidim/participatory_processes/_promoted_process.html.erb +32 -0
  47. data/app/views/decidim/participatory_processes/index.html.erb +15 -0
  48. data/app/views/decidim/participatory_processes/show.html.erb +15 -0
  49. data/app/views/devise/mailer/organization_admin_invitation_instructions.html.erb +1 -1
  50. data/app/views/devise/mailer/organization_admin_invitation_instructions.text.erb +1 -1
  51. data/app/views/layouts/decidim/_footer.html.erb +3 -3
  52. data/app/views/layouts/decidim/_header.html.erb +5 -2
  53. data/app/views/pages/404.html.erb +13 -0
  54. data/app/views/pages/500.html.erb +10 -0
  55. data/app/views/{decidim/home/show.html.erb → pages/home.html.erb} +0 -0
  56. data/app/views/pages/terms.ca.html.erb +4 -0
  57. data/app/views/pages/terms.en.html.erb +4 -0
  58. data/app/views/pages/terms.es.html.erb +4 -0
  59. data/config/i18n-tasks.yml +3 -0
  60. data/config/initializers/devise.rb +1 -1
  61. data/config/locales/ca.yml +84 -10
  62. data/config/locales/en.yml +84 -10
  63. data/config/locales/es.yml +84 -10
  64. data/config/routes.rb +14 -1
  65. data/db/migrate/20161013134732_add_promoted_flag_to_processes.rb +5 -0
  66. data/db/migrate/20161017085822_add_participatory_process_steps.rb +16 -0
  67. data/db/migrate/20161018091013_create_decidim_authorizations.rb +13 -0
  68. data/db/migrate/20161019072016_add_active_flag_to_step.rb +7 -0
  69. data/db/migrate/20161020080756_add_position_to_steps.rb +7 -0
  70. data/db/migrate/20161025125300_add_published_at_to_processes.rb +5 -0
  71. data/db/seeds.rb +81 -27
  72. data/lib/decidim/authorization_form_builder.rb +70 -0
  73. data/lib/decidim/core.rb +23 -5
  74. data/lib/decidim/core/engine.rb +21 -0
  75. data/lib/decidim/core/version.rb +1 -1
  76. data/lib/decidim/faker/localized.rb +162 -0
  77. data/lib/decidim/form_builder.rb +1 -1
  78. data/lib/tasks/decidim_tasks.rake +1 -1
  79. metadata +121 -15
  80. data/MIT-LICENSE +0 -20
  81. data/app/assets/javascripts/decidim.js +0 -23
  82. data/app/assets/stylesheets/decidim.scss +0 -16
  83. data/app/assets/stylesheets/decidim/utils/_variables.scss +0 -25
  84. data/app/controllers/decidim/home_controller.rb +0 -7
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ # Defines the abilities for a User. Intended to be used with `cancancan`.
4
+ class Ability
5
+ include CanCan::Ability
6
+
7
+ # Initializes the ability class for the given user. Automatically merges
8
+ # injected abilities fmor the configuration. In order to inject more
9
+ # abilities, add this code in the `engine.rb` file of your own engine, for
10
+ # example, inside an initializer:
11
+ #
12
+ # Decidim.configure do |config|
13
+ # config.abilities << Decidim::MyEngine::Abilities::MyAbility
14
+ # end
15
+ #
16
+ # Note that, in development, this will force you to restart the server
17
+ # every time you change things in your ability classes.
18
+ #
19
+ # user - the User that needs its abilities checked.
20
+ def initialize(user)
21
+ Decidim.abilities.each do |ability|
22
+ merge ability.new(user)
23
+ end
24
+
25
+ can :manage, Authorization do |authorization|
26
+ authorization.user == user
27
+ end
28
+
29
+ can :read, :user_account if user
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ # An authorization is a record that a User has been authorized somehow. Other
4
+ # models in the system can use different kind of authorizations to allow a
5
+ # user to perform actions.
6
+ #
7
+ # To create an authorization for a user we need to use an
8
+ # AuthorizationHandler that validates the user against a set of rules. An
9
+ # example could be a handler that validates a user email against an API and
10
+ # depending on the response it allows the creation of the authorization or
11
+ # not.
12
+ class Authorization < ApplicationRecord
13
+ belongs_to :user, foreign_key: "decidim_user_id", class_name: Decidim::User, inverse_of: :authorizations
14
+
15
+ validates :name, :user, :handler, presence: true
16
+ validates :name, uniqueness: { scope: :decidim_user_id }
17
+
18
+ # The handler that created this authorization.
19
+ #
20
+ # Returns an instance that inherits from Decidim::AuthorizationHandler.
21
+ def handler
22
+ @handler ||= AuthorizationHandler.handler_for(name, metadata)
23
+ end
24
+ end
25
+ end
@@ -7,11 +7,36 @@ module Decidim
7
7
  # active.
8
8
  class ParticipatoryProcess < ApplicationRecord
9
9
  belongs_to :organization, foreign_key: "decidim_organization_id", class_name: Decidim::Organization
10
+ has_many :steps, -> { order(position: :asc) }, foreign_key: "decidim_participatory_process_id", class_name: Decidim::ParticipatoryProcessStep
11
+ has_one :active_step, -> { where(active: true) }, foreign_key: "decidim_participatory_process_id", class_name: Decidim::ParticipatoryProcessStep
12
+
13
+ attr_readonly :active_step
10
14
 
11
15
  validates :slug, presence: true
12
16
  validates :slug, uniqueness: true
13
17
 
14
18
  mount_uploader :hero_image, Decidim::HeroImageUploader
15
19
  mount_uploader :banner_image, Decidim::BannerImageUploader
20
+
21
+ # Scope to return only the published processes.
22
+ #
23
+ # Returns an ActiveRecord::Relation.
24
+ def self.published
25
+ where.not(published_at: nil)
26
+ end
27
+
28
+ # Scope to return only the promoted processes.
29
+ #
30
+ # Returns an ActiveRecord::Relation.
31
+ def self.promoted
32
+ where(promoted: true)
33
+ end
34
+
35
+ # Checks whether the process has been published or not.
36
+ #
37
+ # Returns a boolean.
38
+ def published?
39
+ published_at.present?
40
+ end
16
41
  end
17
42
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ # A ParticipatoryProcess is composed of many steps that hold different
4
+ # features that will show up in the depending on what step is currently
5
+ # active.
6
+ class ParticipatoryProcessStep < ApplicationRecord
7
+ belongs_to :participatory_process, foreign_key: "decidim_participatory_process_id", class_name: Decidim::ParticipatoryProcess
8
+ has_one :organization, through: :participatory_process
9
+
10
+ validates :start_date, date: { before: :end_date, allow_blank: true, if: proc { |obj| obj.end_date.present? } }
11
+ validates :end_date, date: { after: :start_date, allow_blank: true, if: proc { |obj| obj.start_date.present? } }
12
+
13
+ validates :active, uniqueness: { scope: :decidim_participatory_process_id }, if: proc { |step| step.active? }
14
+
15
+ validates :position, numericality: { greater_than_or_equal_to: 0, only_integer: true }, allow_blank: true
16
+ validates :position, uniqueness: { scope: :decidim_participatory_process_id }
17
+
18
+ before_create :set_position
19
+
20
+ private
21
+
22
+ # Internal: Sets the position of the step if it is `nil`. That means that
23
+ # when the step is the first of the process, and no position is set
24
+ # manually, it will be set to 0, and when it is not the only one it will be
25
+ # set to the last step's position + 1.
26
+ #
27
+ # Note: This allows manual positioning, but there is a validation that
28
+ # forbids two steps from the same proccess to have the same position. Take
29
+ # that into account. It would be best if you did not use manual
30
+ # positioning.
31
+ def set_position
32
+ return if position.present?
33
+ return self.position = 0 if participatory_process.steps.empty?
34
+
35
+ self.position = participatory_process.steps.pluck(:position).last + 1
36
+ end
37
+ end
38
+ end
@@ -8,11 +8,13 @@ module Decidim
8
8
  :recoverable, :rememberable, :trackable, :decidim_validatable
9
9
 
10
10
  belongs_to :organization, foreign_key: "decidim_organization_id", class_name: Decidim::Organization
11
+ has_many :authorizations, foreign_key: "decidim_user_id", class_name: Decidim::Authorization, inverse_of: :user
11
12
 
12
13
  ROLES = %w(admin moderator official).freeze
13
14
 
14
15
  validates :organization, :name, presence: true
15
16
  validates :locale, inclusion: { in: I18n.available_locales.map(&:to_s) }, allow_blank: true
17
+ validates :tos_agreement, acceptance: true, allow_nil: false, on: :create
16
18
  validate :all_roles_are_valid
17
19
 
18
20
  # Public: Allows customizing the invitation instruction email content when
@@ -21,6 +23,23 @@ module Decidim
21
23
  # Returns a String.
22
24
  attr_accessor :invitation_instructions
23
25
 
26
+ delegate :can?, to: :ability
27
+
28
+ # Gets the ability instance for the given user.
29
+ def ability
30
+ @ability ||= Ability.new(self)
31
+ end
32
+
33
+ # Checks if the user has the given `role` or not.
34
+ #
35
+ # role - a String or a Symbol that represents the role that is being
36
+ # checked
37
+ #
38
+ # Returns a boolean.
39
+ def role?(role)
40
+ roles.include?(role.to_s)
41
+ end
42
+
24
43
  private
25
44
 
26
45
  def all_roles_are_valid
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+ module Decidim
3
+ # This is the base class for authorization handlers, all implementations
4
+ # should inherit from it.
5
+ # Each AuthorizationHandler must define an `authorized?` method that will be
6
+ # used to check if the authorization is valid or not. When it is valid a new
7
+ # authorization will be created for the user.
8
+ #
9
+ # It also sets two default attributes, `user` and `handler_name`.
10
+ class AuthorizationHandler < Rectify::Form
11
+ # The user that is trying to authorize, it's initialized with the
12
+ # `current_user` from the controller.
13
+ attribute :user, Decidim::User
14
+ # The String name of the handler, should not be modified since it's used to
15
+ # infer the class name of the authorization handler.
16
+ attribute :handler_name, String
17
+
18
+ # THe attributes of the handler that should be exposed as form input when
19
+ # rendering the handler in a form.
20
+ #
21
+ # Returns an Array of Strings.
22
+ def form_attributes
23
+ attributes.except(:id, :user).keys
24
+ end
25
+
26
+ # Whether the authorization is valid or not. Each AuthorizationHandler
27
+ # implementation must implemement this method. This is were you check for
28
+ # validation errors or against third party systems to validate the
29
+ # authorization.
30
+ #
31
+ # Returns a Boolean.
32
+ def authorized?
33
+ raise NotImplementedError
34
+ end
35
+
36
+ # The String partial path so Rails can render the handler as a form. This
37
+ # is useful if you want to have a custom view to render the form instead of
38
+ # the default view.
39
+ #
40
+ # Example:
41
+ #
42
+ # A handler named Decidim::CensusHandler would look for its partial in:
43
+ # decidim/census/form
44
+ #
45
+ # Returns a String.
46
+ def to_partial_path
47
+ handler_name.sub!(/_handler$/, "") + "/form"
48
+ end
49
+
50
+ # Any data that the developer would like to inject to the `metadata` field
51
+ # of an authorization when it's created. Can be useful if some of the
52
+ # params the user sent with the authorization form want to be persisted for
53
+ # future use.
54
+ #
55
+ # Returns a Hash.
56
+ def metadata
57
+ {}
58
+ end
59
+
60
+ # A serialized version of the handler's name.
61
+ #
62
+ # Returns a String.
63
+ def self.handler_name
64
+ name.underscore
65
+ end
66
+
67
+ # Same as the class method but accessible from the instance.
68
+ #
69
+ # Returns a String.
70
+ def handler_name
71
+ self.class.handler_name
72
+ end
73
+
74
+ # Finds a handler class from a String. This is necessary when processing
75
+ # the form data. It will only look for valid handlers that have also been
76
+ # configured in `Decidim.authorization_handlers`.
77
+ #
78
+ # name - The String name of the class to find, usually in the same shape as
79
+ # the one returned by `handler_name`.
80
+ # params - An optional Hash with params to initialize the handler.
81
+ #
82
+ # Returns an AuthorizationHandler descendant.
83
+ # Returns nil when no handlers could be found.
84
+ def self.handler_for(name, params = {})
85
+ handler_klass = name.classify.constantize
86
+
87
+ return unless Decidim.authorization_handlers.include?(handler_klass)
88
+
89
+ handler_klass.new(params || {})
90
+ rescue NameError
91
+ nil
92
+ end
93
+ end
94
+ end
@@ -4,8 +4,6 @@ module Decidim
4
4
  # hold the uploads configuration, so you should inherit from this class and
5
5
  # then tweak any configuration you need.
6
6
  class ApplicationUploader < CarrierWave::Uploader::Base
7
- storage :file
8
-
9
7
  # Override the directory where uploaded files will be stored.
10
8
  # This is a sensible default for uploaders that are meant to be mounted:
11
9
  def store_dir
@@ -0,0 +1,52 @@
1
+ <div class="tabs-panel" id="authorizations">
2
+ <div class="row column">
3
+ <% if authorizations.any? %>
4
+ <section class="section">
5
+ <div class="card card--list">
6
+ <% authorizations.each do |authorization| %>
7
+ <div class="card--list__item">
8
+ <div class="card--list__text">
9
+ <%= icon "lock-unlocked", class: "card--list__icon" %>
10
+ <div>
11
+ <h5 class="card--list__heading">
12
+ <%= t(authorization.name, scope: "decidim.authorization_handlers") %>
13
+ </h5>
14
+ <span class="text-small"><%= l(authorization.created_at, format: :long) %></span>
15
+ </div>
16
+ </div>
17
+ <div class="card--list__data">
18
+ <%= link_to authorization, method: :delete, class: "card--list__data__icon", data: { confirm: t(".authorization_confirm_destroy") } do %>
19
+ <%= icon "circle-x" %>
20
+ <% end %>
21
+ </div>
22
+ </div>
23
+ <% end %>
24
+ </div>
25
+ <% end %>
26
+ <% if handlers.any? %>
27
+ <div class="card card--list">
28
+ <% handlers.each do |handler| %>
29
+ <div class="card--list__item">
30
+ <div class="card--list__text">
31
+ <a href="#">
32
+ <%= icon "lock-locked", class: "card--list__icon" %>
33
+ </a>
34
+ <div>
35
+ <h5 class="card--list__heading">
36
+ <%= link_to t(handler.handler_name, scope: "decidim.authorization_handlers"), new_authorization_path(handler: handler.handler_name) %>
37
+ </h5>
38
+ </div>
39
+ </div>
40
+ <div class="card--list__data">
41
+ <%= link_to new_authorization_path(handler: handler.handler_name), class: "card--list__data__icon" do %>
42
+ <%= icon "chevron-right" %>
43
+ <% end %>
44
+ </div>
45
+ </div>
46
+ <% end %>
47
+ </div>
48
+ </section>
49
+ <% end %>
50
+ </div>
51
+ </div>
52
+
@@ -0,0 +1,32 @@
1
+ <main class="wrapper">
2
+ <div class="row collapse">
3
+ <div class="columns">
4
+ <h1 class="heading1 user-header"><%= t(".title") %></h1>
5
+ </div>
6
+ </div>
7
+ <div class="row collapse">
8
+ <div class="main-container">
9
+ <div class="row collapse main-container--side-panel">
10
+ <div class="columns medium-4 large-3">
11
+ <div class="side-panel">
12
+ <ul class="tabs vertical side-panel__tabs" id="user-settings-tabs"
13
+ data-tabs>
14
+ <% if handlers.any? || authorizations.any? %>
15
+ <li class="tabs-title">
16
+ <a href="#authorizations"><%= t(".authorizations") %></a>
17
+ </li>
18
+ <% end %>
19
+ </ul>
20
+ </div>
21
+ </div>
22
+ <div class="columns medium-8 large-9">
23
+ <div class="main-container__content">
24
+ <div class="tabs-content vertical" data-tabs-content="user-settings-tabs">
25
+ <%= render partial: "authorizations" %>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </div>
32
+ </main>
@@ -0,0 +1,22 @@
1
+ <main class="wrapper">
2
+ <div class="row collapse">
3
+ <div class="row collapse">
4
+ <div class="columns large-8 large-centered text-center">
5
+ <h1 class="heading1 page-title"><%= t(".title") %></h1>
6
+ <p class="heading5"><%= t(".verify_with_these_options") %></p>
7
+ </div>
8
+ </div>
9
+ <div class="row">
10
+ <div class="columns medium-7 large-5 medium-centered">
11
+ <div class="card">
12
+ <div class="card__content">
13
+ <% handlers.each do |handler| %>
14
+ <%= link_to t(handler.handler_name, scope: "decidim.authorizations.index.actions"), new_authorization_path(handler: handler.handler_name), class: "button expanded" %>
15
+ <% end %>
16
+ <p class="text-center skip"><%= t("decidim.authorizations.skip_verification", link: link_to(t("decidim.authorizations.current_participatory_processes"), participatory_processes_path).html_safe).html_safe %>.</p>
17
+ </div>
18
+ </div>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </main>
@@ -0,0 +1,29 @@
1
+ <main class="wrapper">
2
+ <div class="row collapse">
3
+ <div class="row collapse">
4
+ <div class="columns large-8 large-centered text-center page-title">
5
+ <h1><%= t(".authorize_with", authorizer: t(handler.handler_name, scope: "decidim.authorization_handlers")) %></h1>
6
+ </div>
7
+ </div>
8
+
9
+ <div class="row">
10
+ <div class="columns large-6 medium-centered">
11
+ <div class="card">
12
+ <div class="card__content">
13
+ <%= authorization_form_for(handler) do |form| %>
14
+ <% if lookup_context.exists?(handler.to_partial_path, [], true) %>
15
+ <%= render partial: handler, as: "handler", locals: { form: form } %>
16
+ <% else %>
17
+ <%= form.all_fields %>
18
+ <div class="actions">
19
+ <%= form.submit t(".authorize"), class: "button expanded" %>
20
+ </div>
21
+ <% end %>
22
+ <% end %>
23
+ <p class="text-center skip"><%= t("decidim.authorizations.skip_verification", link: link_to(t("decidim.authorizations.current_participatory_processes"), participatory_processes_path).html_safe).html_safe %>.</p>
24
+ </div>
25
+ </div>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </main>
@@ -1,15 +1,31 @@
1
- <h2><%= t("devise.confirmations.new.resend_confirmation_instructions") %></h2>
1
+ <main class="wrapper">
2
+ <div class="row collapse">
3
+ <div class="row collapse">
4
+ <div class="columns large-8 large-centered text-center page-title">
5
+ <h1><%= t("devise.confirmations.new.resend_confirmation_instructions") %></h1>
6
+ </div>
7
+ </div>
2
8
 
3
- <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %>
4
- <%= devise_error_messages! %>
9
+ <div class="row">
10
+ <div class="columns medium-7 large-5 medium-centered">
11
+ <div class="card">
12
+ <div class="card__content">
13
+ <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post, class: "register-form new_user" }) do |f| %>
14
+ <%= devise_error_messages! %>
5
15
 
6
- <div class="field">
7
- <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
8
- </div>
16
+ <div class="field">
17
+ <%= f.email_field :email, autofocus: true, value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %>
18
+ </div>
9
19
 
10
- <div class="actions">
11
- <%= f.submit t("devise.confirmations.new.resend_confirmation_instructions") %>
12
- </div>
13
- <% end %>
20
+ <div class="actions">
21
+ <%= f.submit t("devise.confirmations.new.resend_confirmation_instructions"), class: "button expanded" %>
22
+ </div>
23
+ <% end %>
14
24
 
15
- <%= render "decidim/devise/shared/links" %>
25
+ <%= render "decidim/devise/shared/links" %>
26
+ </div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </main>