decidim-decidim_geo 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE-AGPLv3.txt +661 -0
- data/README.md +172 -0
- data/Rakefile +119 -0
- data/app/cells/decidim/geo/content_blocks/geo_maps/show.erb +15 -0
- data/app/cells/decidim/geo/content_blocks/geo_maps_cell.rb +114 -0
- data/app/cells/decidim/geo/content_blocks/geo_maps_settings_form_cell.rb +14 -0
- data/app/cells/decidim/geo/geo_collection_map/show.erb +4 -0
- data/app/cells/decidim/geo/geo_collection_map_cell.rb +102 -0
- data/app/commands/decidim/admin/create_scope.rb +56 -0
- data/app/commands/decidim/admin/create_scope_type.rb +47 -0
- data/app/commands/decidim/admin/update_scope.rb +60 -0
- data/app/commands/decidim/admin/update_scope_type.rb +62 -0
- data/app/commands/decidim/geo/admin/create_shapefile.rb +51 -0
- data/app/commands/decidim/geo/admin/update_geo_config.rb +54 -0
- data/app/commands/decidim/geo/command.rb +14 -0
- data/app/controllers/decidim/admin/scopes_controller.rb +115 -0
- data/app/controllers/decidim/geo/admin/application_controller.rb +21 -0
- data/app/controllers/decidim/geo/admin/geo_configs_controller.rb +44 -0
- data/app/controllers/decidim/geo/admin/shapefiles_controller.rb +41 -0
- data/app/controllers/decidim/geo/application_controller.rb +13 -0
- data/app/controllers/decidim/geo/shapefile_controller.rb +11 -0
- data/app/forms/decidim/admin/scope_form.rb +43 -0
- data/app/forms/decidim/admin/scope_type_form.rb +27 -0
- data/app/forms/decidim/geo/admin/geo_config_form.rb +20 -0
- data/app/forms/decidim/geo/admin/shapefile_form.rb +23 -0
- data/app/helpers/decidim/admin/scopes_helper.rb +72 -0
- data/app/jobs/decidim/geo/shp2pgsql_job.rb +14 -0
- data/app/models/decidim/debates/debate.rb +226 -0
- data/app/models/decidim/geo/application_record.rb +10 -0
- data/app/models/decidim/geo/geo_config.rb +28 -0
- data/app/models/decidim/geo/shapedata.rb +23 -0
- data/app/models/decidim/geo/shapefile.rb +27 -0
- data/app/models/decidim/geo/space_location.rb +16 -0
- data/app/models/decidim/scope.rb +111 -0
- data/app/models/decidim/scope_type.rb +26 -0
- data/app/overrides/decidim/assemblies/admin/assemblies/_form/insert_location_form.html.erb.deface +13 -0
- data/app/overrides/decidim/assemblies/assemblies/show/insert_location.html.erb.deface +7 -0
- data/app/overrides/decidim/participatory_processes/admin/participatory_processes/_form/insert_location_form.html.erb.deface +13 -0
- data/app/overrides/decidim/participatory_processes/participatory_process_groups/show/insert_map.html.erb.deface +6 -0
- data/app/overrides/layouts/decidim/_assembly_header/insert_map.html.erb.deface +25 -0
- data/app/overrides/layouts/decidim/_process_header/insert_map.html.erb.deface +22 -0
- data/app/overrides/layouts/decidim/_wrapper/insert_map.html.erb.deface +5 -0
- data/app/overrides/remove_default_meetings_map.rb +4 -0
- data/app/overrides/remove_default_proposals_map.rb +4 -0
- data/app/packs/entrypoints/decidim_geo.js +3 -0
- data/app/packs/images/decidim/geo/not-geolocated.svg +3 -0
- data/app/packs/src/decidim/geo/actions/index.js +25 -0
- data/app/packs/src/decidim/geo/api/index.js +53 -0
- data/app/packs/src/decidim/geo/api/queries.js +77 -0
- data/app/packs/src/decidim/geo/bootstrap.js +6 -0
- data/app/packs/src/decidim/geo/index.js +101 -0
- data/app/packs/src/decidim/geo/models/configStore.js +44 -0
- data/app/packs/src/decidim/geo/models/dropdownFilterStore.js +65 -0
- data/app/packs/src/decidim/geo/models/filterStore.js +121 -0
- data/app/packs/src/decidim/geo/models/geoDatasourceNode/GeoDatasourceNode.js +124 -0
- data/app/packs/src/decidim/geo/models/geoDatasourceNode/index.js +1 -0
- data/app/packs/src/decidim/geo/models/geoScope/GeoScope.js +128 -0
- data/app/packs/src/decidim/geo/models/geoScope/index.js +1 -0
- data/app/packs/src/decidim/geo/models/geoStore.js +118 -0
- data/app/packs/src/decidim/geo/models/pointStore.js +134 -0
- data/app/packs/src/decidim/geo/models/scopeDropdownStore.js +20 -0
- data/app/packs/src/decidim/geo/ui/DrawerDetail/fallback.js +42 -0
- data/app/packs/src/decidim/geo/ui/DrawerDetail/index.js +2 -0
- data/app/packs/src/decidim/geo/ui/DrawerDetail/meetings.js +75 -0
- data/app/packs/src/decidim/geo/ui/DrawerMenuItem/fallback.js +45 -0
- data/app/packs/src/decidim/geo/ui/DrawerMenuItem/index.js +2 -0
- data/app/packs/src/decidim/geo/ui/DrawerMenuItem/meetings.js +62 -0
- data/app/packs/src/decidim/geo/ui/createClasses.js +7 -0
- data/app/packs/src/decidim/geo/ui/createCustomMarker.js +14 -0
- data/app/packs/src/decidim/geo/ui/createDomElement.js +2 -0
- data/app/packs/src/decidim/geo/ui/createDrawer.js +170 -0
- data/app/packs/src/decidim/geo/ui/createDrawerActions.js +197 -0
- data/app/packs/src/decidim/geo/ui/createFilterDropdown.js +320 -0
- data/app/packs/src/decidim/geo/ui/createGeoScopeLayer.js +20 -0
- data/app/packs/src/decidim/geo/ui/createGeoScopeMenuItem.js +8 -0
- data/app/packs/src/decidim/geo/ui/createNodeMarker.js +9 -0
- data/app/packs/src/decidim/geo/ui/createNodeMenuItem.js +13 -0
- data/app/packs/src/decidim/geo/ui/index.js +8 -0
- data/app/packs/src/decidim/geo/ui/initMap.js +22 -0
- data/app/packs/src/decidim/geo/utils/index.js +30 -0
- data/app/packs/src/decidim/geo/utils/saveConfig.js +15 -0
- data/app/packs/stylesheets/decidim/geo/_geo.scss +2 -0
- data/app/packs/stylesheets/decidim/geo/geo/_decidim_geo_decidimGeo.scss +476 -0
- data/app/packs/stylesheets/decidim/geo/geo/_decidim_geo_override.scss +65 -0
- data/app/permissions/decidim/geo/admin/permissions.rb +24 -0
- data/app/permissions/decidim/geo/permissions.rb +15 -0
- data/app/uploaders/decidim/geo/shapefile_uploader.rb +14 -0
- data/app/validators/geocoding_validator.rb +30 -0
- data/app/views/decidim/admin/scope_types/_form.html.erb +10 -0
- data/app/views/decidim/admin/scope_types/index.html.erb +46 -0
- data/app/views/decidim/admin/scopes/_form.html.erb +20 -0
- data/app/views/decidim/admin/scopes/index.html.erb +55 -0
- data/app/views/decidim/geo/admin/geo_configs/_form.html.erb +47 -0
- data/app/views/decidim/geo/admin/geo_configs/index.html.erb +8 -0
- data/app/views/decidim/geo/admin/shapefiles/_form.html.erb +29 -0
- data/app/views/decidim/geo/admin/shapefiles/index.html.erb +38 -0
- data/app/views/decidim/geo/admin/shapefiles/new.html.erb +8 -0
- data/app/views/layouts/decidim/decidim_geo/admin/application.html.erb +19 -0
- data/config/assets.rb +9 -0
- data/config/i18n-tasks.yml +10 -0
- data/config/locales/de.yml +102 -0
- data/config/locales/en.yml +108 -0
- data/config/locales/fr.yml +102 -0
- data/db/migrate/20221019184712_add_postgis_extension_to_database.rb +5 -0
- data/db/migrate/20221025195520_create_decidim_geo.rb +22 -0
- data/db/migrate/20231013082325_create_decidim_geo_config.rb +11 -0
- data/db/migrate/20231206115531_add_spaces_config_to_decidim_geo_configs.rb +6 -0
- data/db/migrate/20240309004347_add_organization_to_shapefiles.rb +6 -0
- data/db/migrate/20240326052727_create_space_locations.rb +18 -0
- data/lib/decidim/api/geo_config_type.rb +15 -0
- data/lib/decidim/api/geo_coordinates_type.rb +22 -0
- data/lib/decidim/api/geo_datasource_type.rb +147 -0
- data/lib/decidim/api/geo_dates_type.rb +15 -0
- data/lib/decidim/api/geo_query_extension.rb +25 -0
- data/lib/decidim/api/geo_scope_api_type.rb +26 -0
- data/lib/decidim/api/geo_shapedata_type.rb +18 -0
- data/lib/decidim/api/geo_shapefile_type.rb +22 -0
- data/lib/decidim/api/input_filters/assembly_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/geo_datasource_input_filter.rb +18 -0
- data/lib/decidim/api/input_filters/geoencoded_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/has_scopeable_input_filter.rb +17 -0
- data/lib/decidim/api/input_filters/process_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/resource_type_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/scope_input_filter.rb +13 -0
- data/lib/decidim/api/input_filters/time_input_filter.rb +13 -0
- data/lib/decidim/api/meetings_input_filter.rb +14 -0
- data/lib/decidim/api/query_extension.rb +320 -0
- data/lib/decidim/api/shapefile_query_extention.rb +18 -0
- data/lib/decidim/api/shapefile_type.rb +18 -0
- data/lib/decidim/decidim_geo/admin.rb +10 -0
- data/lib/decidim/decidim_geo/admin_engine.rb +69 -0
- data/lib/decidim/decidim_geo/api.rb +23 -0
- data/lib/decidim/decidim_geo/engine.rb +58 -0
- data/lib/decidim/decidim_geo/load_shp/app_load_shp.rb +85 -0
- data/lib/decidim/decidim_geo/space_location/assembly_create_command_override.rb +26 -0
- data/lib/decidim/decidim_geo/space_location/assembly_form_override.rb +33 -0
- data/lib/decidim/decidim_geo/space_location/assembly_update_command_override.rb +21 -0
- data/lib/decidim/decidim_geo/space_location/participatory_process_command_override.rb +21 -0
- data/lib/decidim/decidim_geo/space_location/participatory_process_form_override.rb +33 -0
- data/lib/decidim/decidim_geo/space_location/space_override.rb +15 -0
- data/lib/decidim/decidim_geo/test/factories.rb +13 -0
- data/lib/decidim/decidim_geo/version.rb +14 -0
- data/lib/decidim/decidim_geo.rb +25 -0
- data/lib/tasks/decidim_geo_webpacker_tasks.rake +61 -0
- metadata +335 -0
@@ -0,0 +1,226 @@
|
|
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::HasComponent
|
10
|
+
include Decidim::HasCategory
|
11
|
+
include Decidim::Resourceable
|
12
|
+
include Decidim::Followable
|
13
|
+
include Decidim::Comments::CommentableWithComponent
|
14
|
+
include Decidim::Comments::HasAvailabilityAttributes
|
15
|
+
include Decidim::ScopableResource
|
16
|
+
include Decidim::Authorable
|
17
|
+
include Decidim::Reportable
|
18
|
+
include Decidim::HasReference
|
19
|
+
include Decidim::Traceable
|
20
|
+
include Decidim::Loggable
|
21
|
+
include Decidim::NewsletterParticipant
|
22
|
+
include Decidim::Searchable
|
23
|
+
include Decidim::TranslatableResource
|
24
|
+
include Decidim::TranslatableAttributes
|
25
|
+
include Decidim::Endorsable
|
26
|
+
include Decidim::Randomable
|
27
|
+
|
28
|
+
if Decidim.version == '0.27.4'
|
29
|
+
include Decidim::DownloadYourData
|
30
|
+
include Decidim::FilterableResource
|
31
|
+
end
|
32
|
+
|
33
|
+
belongs_to :last_comment_by, polymorphic: true, foreign_type: "last_comment_by_type", optional: true
|
34
|
+
component_manifest_name "debates"
|
35
|
+
|
36
|
+
validates :title, presence: true
|
37
|
+
|
38
|
+
translatable_fields :title, :description, :instructions, :information_updates
|
39
|
+
searchable_fields({
|
40
|
+
scope_id: :decidim_scope_id,
|
41
|
+
participatory_space: { component: :participatory_space },
|
42
|
+
A: :title,
|
43
|
+
D: :description,
|
44
|
+
datetime: :start_time
|
45
|
+
},
|
46
|
+
index_on_create: ->(debate) { debate.visible? },
|
47
|
+
index_on_update: ->(debate) { debate.visible? })
|
48
|
+
|
49
|
+
scope :open, -> { where(closed_at: nil) }
|
50
|
+
scope :closed, -> { where.not(closed_at: nil) }
|
51
|
+
scope :authored_by, ->(author) { where(author: author) }
|
52
|
+
scope :commented_by, lambda { |author|
|
53
|
+
joins(:comments).where(
|
54
|
+
decidim_comments_comments:
|
55
|
+
{
|
56
|
+
decidim_author_id: author.id,
|
57
|
+
decidim_author_type: author.class.base_class.name
|
58
|
+
}
|
59
|
+
)
|
60
|
+
}
|
61
|
+
|
62
|
+
if Decidim.version == '0.27.4'
|
63
|
+
scope_search_multi :with_any_state, [:open, :closed]
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.log_presenter_class_for(_log)
|
67
|
+
Decidim::Debates::AdminLog::DebatePresenter
|
68
|
+
end
|
69
|
+
|
70
|
+
def comments_start_time
|
71
|
+
start_time
|
72
|
+
end
|
73
|
+
|
74
|
+
def comments_end_time
|
75
|
+
end_time
|
76
|
+
end
|
77
|
+
|
78
|
+
# Public: Overrides the `reported_content_url` Reportable concern method.
|
79
|
+
def reported_content_url
|
80
|
+
ResourceLocatorPresenter.new(self).url
|
81
|
+
end
|
82
|
+
|
83
|
+
# Public: Overrides the `reported_attributes` Reportable concern method.
|
84
|
+
def reported_attributes
|
85
|
+
[:title, :description]
|
86
|
+
end
|
87
|
+
|
88
|
+
# Public: Overrides the `reported_searchable_content_extras` Reportable concern method.
|
89
|
+
def reported_searchable_content_extras
|
90
|
+
[normalized_author.name]
|
91
|
+
end
|
92
|
+
|
93
|
+
# Public: Calculates whether the current debate is an AMA-styled one or not.
|
94
|
+
# AMA-styled debates are those that have a start and end time set, and comments
|
95
|
+
# are only open during that timelapse. AMA stands for Ask Me Anything, a type
|
96
|
+
# of debate inspired by Reddit.
|
97
|
+
#
|
98
|
+
# Returns a Boolean.
|
99
|
+
def ama?
|
100
|
+
start_time.present? && end_time.present?
|
101
|
+
end
|
102
|
+
|
103
|
+
# Public: Checks whether the debate is an AMA-styled one and is open.
|
104
|
+
#
|
105
|
+
# Returns a boolean.
|
106
|
+
def open_ama?
|
107
|
+
ama? && Time.current.between?(start_time, end_time)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Public: Checks if the debate is open or not.
|
111
|
+
#
|
112
|
+
# Returns a boolean.
|
113
|
+
def open?
|
114
|
+
(ama? && open_ama?) || !ama?
|
115
|
+
end
|
116
|
+
|
117
|
+
# Public: Overrides the `accepts_new_comments?` CommentableWithComponent concern method.
|
118
|
+
def accepts_new_comments?
|
119
|
+
return false unless open?
|
120
|
+
return false if closed?
|
121
|
+
|
122
|
+
commentable? && !comments_blocked? && comments_allowed?
|
123
|
+
end
|
124
|
+
|
125
|
+
# Public: Overrides the `comments_have_alignment?` Commentable concern method.
|
126
|
+
def comments_have_alignment?
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
# Public: Overrides the `comments_have_votes?` Commentable concern method.
|
131
|
+
def comments_have_votes?
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
# Public: Identifies the commentable type in the API.
|
136
|
+
def commentable_type
|
137
|
+
self.class.name
|
138
|
+
end
|
139
|
+
|
140
|
+
# Public: Override Commentable concern method `users_to_notify_on_comment_created`
|
141
|
+
def users_to_notify_on_comment_created
|
142
|
+
return Decidim::User.where(id: followers).or(Decidim::User.where(id: component.participatory_space.admins)).distinct if official?
|
143
|
+
|
144
|
+
followers
|
145
|
+
end
|
146
|
+
|
147
|
+
# Public: Overrides the `allow_resource_permissions?` Resourceable concern method.
|
148
|
+
def allow_resource_permissions?
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
def self.export_serializer
|
153
|
+
Decidim::Debates::DownloadYourDataDebateSerializer
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.newsletter_participant_ids(component)
|
157
|
+
authors_ids = Decidim::Debates::Debate.where(component: component)
|
158
|
+
.where(decidim_author_type: Decidim::UserBaseEntity.name)
|
159
|
+
.where.not(author: nil)
|
160
|
+
.group(:decidim_author_id)
|
161
|
+
.pluck(:decidim_author_id).flatten.compact
|
162
|
+
commentators_ids = Decidim::Comments::Comment.user_commentators_ids_in(Decidim::Debates::Debate.where(component: component))
|
163
|
+
(authors_ids + commentators_ids).flatten.compact.uniq
|
164
|
+
end
|
165
|
+
|
166
|
+
# Checks whether the user can edit the debate.
|
167
|
+
#
|
168
|
+
# user - the user to check for authorship
|
169
|
+
def editable_by?(user)
|
170
|
+
!closed? && authored_by?(user)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Checks whether the debate is closed or not.
|
174
|
+
#
|
175
|
+
def closed?
|
176
|
+
closed_at.present? && conclusions.present?
|
177
|
+
end
|
178
|
+
|
179
|
+
# Checks whether the user can edit the debate.
|
180
|
+
#
|
181
|
+
# user - the user to check for authorship
|
182
|
+
def closeable_by?(user)
|
183
|
+
authored_by?(user)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Public: Updates the comments counter cache. We have to do it these
|
187
|
+
# way in order to properly calculate the counter with hidden
|
188
|
+
# comments.
|
189
|
+
#
|
190
|
+
# rubocop:disable Rails/SkipsModelValidations
|
191
|
+
def update_comments_count
|
192
|
+
comments_count = comments.not_hidden.not_deleted.count
|
193
|
+
last_comment = comments.not_hidden.not_deleted.order("created_at DESC").first
|
194
|
+
|
195
|
+
update_columns(
|
196
|
+
last_comment_at: last_comment&.created_at,
|
197
|
+
last_comment_by_id: last_comment&.decidim_user_group_id || last_comment&.decidim_author_id,
|
198
|
+
last_comment_by_type: last_comment&.decidim_author_type,
|
199
|
+
comments_count: comments_count,
|
200
|
+
updated_at: Time.current
|
201
|
+
)
|
202
|
+
end
|
203
|
+
# rubocop:enable Rails/SkipsModelValidations
|
204
|
+
|
205
|
+
if Decidim.version == '0.27.4'
|
206
|
+
# Create i18n ransackers for :title and :description.
|
207
|
+
# Create the :search_text ransacker alias for searching from both of these.
|
208
|
+
ransacker_i18n_multi :search_text, [:title, :description]
|
209
|
+
end
|
210
|
+
|
211
|
+
def self.ransackable_scopes(_auth_object = nil)
|
212
|
+
[:with_any_state, :with_any_origin, :with_any_category, :with_any_scope]
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.ransack(params = {}, options = {})
|
216
|
+
DebateSearch.new(self, params, options)
|
217
|
+
end
|
218
|
+
|
219
|
+
private
|
220
|
+
|
221
|
+
def comments_blocked?
|
222
|
+
component.current_settings.comments_blocked
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Geo
|
5
|
+
# The data store for geo configuration default.
|
6
|
+
class GeoConfig < ApplicationRecord
|
7
|
+
|
8
|
+
self.table_name = 'decidim_geo_configs'
|
9
|
+
|
10
|
+
validates :longitude, :latitude, :zoom, :presence => true
|
11
|
+
|
12
|
+
def self.geo_config_default
|
13
|
+
Decidim::Geo::GeoConfig
|
14
|
+
.first_or_create(latitude: 0,
|
15
|
+
longitude: 0,
|
16
|
+
zoom: 13,
|
17
|
+
tile: tile_layer_default)
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.tile_layer_default
|
21
|
+
return "" unless Decidim.config.maps.present?
|
22
|
+
|
23
|
+
Decidim.config.maps[:dynamic][:tile_layer][:url]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Geo
|
5
|
+
# Data fo the shapfiles uploaded
|
6
|
+
class Shapedata < ApplicationRecord
|
7
|
+
|
8
|
+
self.table_name = 'decidim_geo_shapefile_datas'
|
9
|
+
validates :data, :presence => true
|
10
|
+
|
11
|
+
belongs_to :scope, inverse_of: :shapedata, optional: true, foreign_key: "decidim_scopes_id"
|
12
|
+
|
13
|
+
def execute_statement(sql)
|
14
|
+
Shapedata.connection.exec_query(sql)
|
15
|
+
end
|
16
|
+
|
17
|
+
def shapedata?
|
18
|
+
return false
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Geo
|
5
|
+
# The data store for a Proposal in the Decidim::Proposals component.
|
6
|
+
class Shapefile < ApplicationRecord
|
7
|
+
#include Decidim::TranslatableResource
|
8
|
+
include Decidim::HasUploadValidations
|
9
|
+
|
10
|
+
self.table_name = 'decidim_geo_shapefiles'
|
11
|
+
|
12
|
+
has_many :shapedatas, foreign_key: "decidim_geo_shapefiles_id"
|
13
|
+
|
14
|
+
belongs_to :scope_type, inverse_of: :shapefile, optional: true, foreign_key: "decidim_scope_types_id"
|
15
|
+
|
16
|
+
#translatable_fields :title, :description
|
17
|
+
|
18
|
+
validates :title, :description, :presence => true
|
19
|
+
|
20
|
+
has_one_attached :shapefile
|
21
|
+
validates_upload :shapefile, uploader: Decidim::Geo::ShapefileUploader
|
22
|
+
belongs_to :organization,
|
23
|
+
foreign_key: "decidim_organization_id",
|
24
|
+
class_name: "Decidim::Organization"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Geo
|
5
|
+
# Data fo the shapfiles uploaded
|
6
|
+
class SpaceLocation < ApplicationRecord
|
7
|
+
self.table_name = 'decidim_geo_space_locations'
|
8
|
+
belongs_to :decidim_geo_space, polymorphic: true
|
9
|
+
|
10
|
+
def has_address?
|
11
|
+
!latitude.nil? && !longitude.nil?
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
# Scopes are used in some entities through Decidim to help users know which is
|
5
|
+
# the scope of a participatory process.
|
6
|
+
# (i.e. does it affect the whole city or just a district?)
|
7
|
+
class Scope < ApplicationRecord
|
8
|
+
include Decidim::Traceable
|
9
|
+
include Decidim::Loggable
|
10
|
+
include Decidim::TranslatableResource
|
11
|
+
|
12
|
+
translatable_fields :name
|
13
|
+
|
14
|
+
belongs_to :organization,
|
15
|
+
foreign_key: "decidim_organization_id",
|
16
|
+
class_name: "Decidim::Organization",
|
17
|
+
inverse_of: :scopes
|
18
|
+
|
19
|
+
belongs_to :scope_type,
|
20
|
+
class_name: "Decidim::ScopeType",
|
21
|
+
inverse_of: :scopes,
|
22
|
+
optional: true
|
23
|
+
|
24
|
+
belongs_to :parent,
|
25
|
+
class_name: "Decidim::Scope",
|
26
|
+
inverse_of: :children,
|
27
|
+
optional: true
|
28
|
+
|
29
|
+
has_many :children,
|
30
|
+
foreign_key: "parent_id",
|
31
|
+
class_name: "Decidim::Scope",
|
32
|
+
inverse_of: :parent,
|
33
|
+
dependent: :destroy
|
34
|
+
|
35
|
+
has_one :shapedata,
|
36
|
+
foreign_key: "decidim_scopes_id",
|
37
|
+
class_name: "Decidim::Geo::Shapedata",
|
38
|
+
inverse_of: :scope,
|
39
|
+
dependent: :nullify
|
40
|
+
|
41
|
+
before_validation :update_part_of, on: :update
|
42
|
+
|
43
|
+
validates :name, :code, :organization, presence: true
|
44
|
+
validates :code, uniqueness: { scope: :organization }
|
45
|
+
validate :forbid_cycles
|
46
|
+
|
47
|
+
after_create_commit :create_part_of
|
48
|
+
|
49
|
+
# Scope to return only the top level scopes.
|
50
|
+
#
|
51
|
+
# Returns an ActiveRecord::Relation.
|
52
|
+
def self.top_level(organization_id = nil)
|
53
|
+
query = where parent_id: nil
|
54
|
+
query = query.where(decidim_organization_id: organization_id) if organization_id
|
55
|
+
query
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.log_presenter_class_for(_log)
|
59
|
+
Decidim::AdminLog::ScopePresenter
|
60
|
+
end
|
61
|
+
|
62
|
+
def descendants
|
63
|
+
@descendants ||= organization.scopes.where("? = ANY(decidim_scopes.part_of)", id)
|
64
|
+
end
|
65
|
+
|
66
|
+
def ancestor_of?(scope)
|
67
|
+
scope && scope.part_of.member?(id)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Gets the scopes from the part_of list in descending order (first the top level scope, last itself)
|
71
|
+
#
|
72
|
+
# root - The root scope to start retrieval. If present, ignores top level scopes until reaching the root scope.
|
73
|
+
#
|
74
|
+
# Returns an array of Scope objects
|
75
|
+
def part_of_scopes(root = nil)
|
76
|
+
scope_ids = part_of
|
77
|
+
scope_ids.select! { |id| id == root.id || !root.part_of.member?(id) } if root
|
78
|
+
organization.scopes.where(id: scope_ids).sort { |s1, s2| part_of.index(s2.id) <=> part_of.index(s1.id) }
|
79
|
+
end
|
80
|
+
|
81
|
+
# Allow ransacker to search for a key in a hstore column (`name`.`en`)
|
82
|
+
ransacker :name do |parent|
|
83
|
+
Arel::Nodes::InfixOperation.new("->>", parent.table[:name], Arel::Nodes.build_quoted(I18n.locale.to_s))
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def forbid_cycles
|
89
|
+
return unless parent
|
90
|
+
|
91
|
+
errors.add(:parent_id, :cycle_detected) if parent.part_of.include?(id)
|
92
|
+
end
|
93
|
+
|
94
|
+
def create_part_of
|
95
|
+
build_part_of
|
96
|
+
save if changed?
|
97
|
+
end
|
98
|
+
|
99
|
+
def update_part_of
|
100
|
+
build_part_of
|
101
|
+
end
|
102
|
+
|
103
|
+
def build_part_of
|
104
|
+
if parent
|
105
|
+
part_of.clear.append(id).concat(parent.reload.part_of)
|
106
|
+
else
|
107
|
+
part_of.clear.append(id)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
# Scope types allows to use different types of scopes in participatory process
|
5
|
+
# (municipalities, provinces, states, countries, etc.)
|
6
|
+
# Override to include shapefiles field
|
7
|
+
class ScopeType < ApplicationRecord
|
8
|
+
include Decidim::TranslatableResource
|
9
|
+
|
10
|
+
translatable_fields :name, :plural
|
11
|
+
belongs_to :organization,
|
12
|
+
foreign_key: "decidim_organization_id",
|
13
|
+
class_name: "Decidim::Organization",
|
14
|
+
inverse_of: :scope_types
|
15
|
+
|
16
|
+
has_one :shapefile,
|
17
|
+
class_name: "Decidim::Geo::Shapefile",
|
18
|
+
inverse_of: :scope_type,
|
19
|
+
foreign_key: "decidim_scope_types_id",
|
20
|
+
dependent: :nullify
|
21
|
+
|
22
|
+
has_many :scopes, class_name: "Decidim::Scope", inverse_of: :scope_type, dependent: :nullify
|
23
|
+
|
24
|
+
validates :name, presence: true
|
25
|
+
end
|
26
|
+
end
|
data/app/overrides/decidim/assemblies/admin/assemblies/_form/insert_location_form.html.erb.deface
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
<!-- insert_after ".card-section:nth-child(2)" -->
|
2
|
+
<% if Decidim::Map.available?(:geocoding) %>
|
3
|
+
<div class="card-divider">
|
4
|
+
<h2 class="card-title"><%= t(".decidim_geo_space_address") %></h2>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<div class="card-section">
|
8
|
+
<div class="row column">
|
9
|
+
<%= form.geocoding_field :decidim_geo_space_address, autocomplete: true %>
|
10
|
+
<p class="help-text"><%= t(".decidim_geo_space_address_help") %></p>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<% end %>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
<!-- insert_before "erb[loud]:contains('social_handler_links')" -->
|
2
|
+
<% if current_participatory_space.decidim_geo_space_location && current_participatory_space.decidim_geo_space_location.has_address?%>
|
3
|
+
<div class="definition-data__item scope">
|
4
|
+
<span class="definition-data__title"><%= t("decidim_geo_space_location", scope: "decidim.assemblies.show") %></span>
|
5
|
+
<%= current_participatory_space.decidim_geo_space_location.address %>
|
6
|
+
</div>
|
7
|
+
<% end %>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!-- insert_after ".card-section:nth-child(2)" -->
|
2
|
+
<% if Decidim::Map.available?(:geocoding) %>
|
3
|
+
<div class="card-divider">
|
4
|
+
<h2 class="card-title"><%= t(".decidim_geo_space_address") %></h2>
|
5
|
+
</div>
|
6
|
+
|
7
|
+
<div class="card-section">
|
8
|
+
<div class="row column">
|
9
|
+
<%= form.geocoding_field :decidim_geo_space_address, autocomplete: true %>
|
10
|
+
<p class="help-text"><%= t(".decidim_geo_space_address_help") %></p>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
<% end %>
|
@@ -0,0 +1,6 @@
|
|
1
|
+
<!-- insert_before '.wrapper' -->
|
2
|
+
<%= cell("decidim/geo/content_blocks/geo_maps", @group,
|
3
|
+
id: "ProcessGroup",
|
4
|
+
filters: @group.participatory_processes.map {|process| { processFilter: { processId: process.id } } },
|
5
|
+
scopes: @group.participatory_processes.scope.map{|s| s.id}
|
6
|
+
) %>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!-- insert_after '.process-header' -->
|
2
|
+
<%
|
3
|
+
assemblies = []
|
4
|
+
# recursivly get all the children assemblies.
|
5
|
+
def iter_children(assembly, collection)
|
6
|
+
assembly.children.map {|ch| iter_children(ch, collection) } if assembly.children
|
7
|
+
collection.push(assembly.id)
|
8
|
+
end
|
9
|
+
iter_children(current_participatory_space, assemblies) unless current_participatory_space.scope.present?
|
10
|
+
%>
|
11
|
+
<%= cell("decidim/geo/content_blocks/geo_maps", current_participatory_space,
|
12
|
+
id: "Assembly",
|
13
|
+
filters: if current_participatory_space.scope.present?
|
14
|
+
[
|
15
|
+
{ scopeFilter: { scopeId: current_participatory_space.scope.id } }
|
16
|
+
]
|
17
|
+
else
|
18
|
+
assemblies.map { |assembly_id| { assemblyFilter: { assemblyId: assembly_id } } }
|
19
|
+
end,
|
20
|
+
scopes: if current_participatory_space.scope.present?
|
21
|
+
[current_participatory_space.scope.id]
|
22
|
+
else
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
) %>
|
@@ -0,0 +1,22 @@
|
|
1
|
+
<!-- insert_after '.process-header' -->
|
2
|
+
<% if controller.controller_name != "participatory_process_steps" %>
|
3
|
+
<%= cell("decidim/geo/content_blocks/geo_maps", current_participatory_space,
|
4
|
+
id: "Process",
|
5
|
+
filters: if current_participatory_space.scope.present?
|
6
|
+
# If the process has a scope, filter by its scope
|
7
|
+
[
|
8
|
+
{ scopeFilter: { scopeId: current_participatory_space.scope.id } }
|
9
|
+
]
|
10
|
+
else
|
11
|
+
# Else filter on the process data
|
12
|
+
[
|
13
|
+
{ processFilter: { processId: current_participatory_space.id } }
|
14
|
+
]
|
15
|
+
end,
|
16
|
+
scopes: if current_participatory_space.scope.present?
|
17
|
+
[current_participatory_space.scope.id]
|
18
|
+
else
|
19
|
+
[]
|
20
|
+
end
|
21
|
+
) %>
|
22
|
+
<% end %>
|
@@ -0,0 +1,4 @@
|
|
1
|
+
Deface::Override.new(:virtual_path => "decidim/meetings/meetings/index",
|
2
|
+
:name => "remove_default_meetings_map",
|
3
|
+
:remove => "erb[silent]:contains('if current_component.settings.maps_enabled? && search.result.not_online.exists?')",
|
4
|
+
:closing_selector => "erb[silent]:contains('end')")
|
@@ -0,0 +1,4 @@
|
|
1
|
+
Deface::Override.new(:virtual_path => "decidim/proposals/proposals/index",
|
2
|
+
:name => "remove_default_proposals_map",
|
3
|
+
:remove => "erb[loud]:contains('dynamic_map_for proposals_data_for_map(@all_geocoded_proposals) do')",
|
4
|
+
:closing_selector => "erb[silent]:contains('end')")
|
@@ -0,0 +1,3 @@
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="16" viewBox="0 0 17 16" fill="none">
|
2
|
+
<path d="M11.4334 10.7334L8.35339 7.64671L8.28006 7.57337L2.70006 2.00004L1.85339 2.84671L3.97339 4.96671C3.89339 5.30004 3.85339 5.64004 3.85339 6.00004C3.85339 9.50004 8.52006 14.6667 8.52006 14.6667C8.52006 14.6667 9.63339 13.4334 10.7667 11.7667L13.0067 14L13.8534 13.1467M8.52006 4.33337C8.96209 4.33337 9.38601 4.50897 9.69857 4.82153C10.0111 5.13409 10.1867 5.55801 10.1867 6.00004C10.1867 6.48671 9.96673 6.92671 9.63339 7.23337L12.0534 9.66671C12.7067 8.41337 13.1867 7.12004 13.1867 6.00004C13.1867 4.76236 12.6951 3.57538 11.8199 2.70021C10.9447 1.82504 9.75774 1.33337 8.52006 1.33337C7.18673 1.33337 6.01339 1.88004 5.16006 2.76004L7.28673 4.88671C7.59339 4.54671 8.02673 4.33337 8.52006 4.33337Z" fill="#808080"/>
|
3
|
+
</svg>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
export const displayNestedLayers = (leafletContainer, checked) => {
|
2
|
+
if (
|
3
|
+
leafletContainer.classList.contains("decidimGeo__customControl__input") &&
|
4
|
+
leafletContainer.checked !== checked
|
5
|
+
) {
|
6
|
+
leafletContainer.dispatchEvent(
|
7
|
+
new MouseEvent("click", {
|
8
|
+
view: window,
|
9
|
+
bubbles: false,
|
10
|
+
cancelable: false
|
11
|
+
})
|
12
|
+
);
|
13
|
+
//leafletContainer.checked = checked;
|
14
|
+
//This leads layers to unsynchronize with the input state.
|
15
|
+
// Bubbling the click seems to ensure leaflet events are correctly triggered.
|
16
|
+
}
|
17
|
+
|
18
|
+
if (leafletContainer.children) {
|
19
|
+
return [...leafletContainer.children].map((child) =>
|
20
|
+
displayNestedLayers(child, checked)
|
21
|
+
);
|
22
|
+
} else {
|
23
|
+
return;
|
24
|
+
}
|
25
|
+
};
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import * as queries from "./queries";
|
2
|
+
import { getDecidimData } from "../utils";
|
3
|
+
import configStore from "../models/configStore";
|
4
|
+
|
5
|
+
const makeQuery =
|
6
|
+
(queryName, responseKey = undefined) =>
|
7
|
+
async (params = {}) => {
|
8
|
+
const _responseKey = responseKey ? responseKey : queryName;
|
9
|
+
const { locale } = configStore.getState();
|
10
|
+
const variables = { ...(params.variables || {}), locale: locale };
|
11
|
+
const queryParams = { ...params, variables };
|
12
|
+
const response = await getDecidimData(queries[queryName], queryParams);
|
13
|
+
return response.data[_responseKey];
|
14
|
+
};
|
15
|
+
|
16
|
+
const _getGeoDatasource = makeQuery("geoDatasource");
|
17
|
+
const _getGeoDatasourceIds = makeQuery("geoDatasourceIds", "geoDatasource");
|
18
|
+
|
19
|
+
export const getGeoDatasource = async (params = {}, fetchAll = true) => {
|
20
|
+
let results = [];
|
21
|
+
if (!params.variables) {
|
22
|
+
params.variables = {};
|
23
|
+
}
|
24
|
+
const apiQuery = fetchAll ? _getGeoDatasource : _getGeoDatasourceIds;
|
25
|
+
let page;
|
26
|
+
try {
|
27
|
+
page = await apiQuery(params);
|
28
|
+
} catch (error) {
|
29
|
+
console.error(error);
|
30
|
+
throw error;
|
31
|
+
}
|
32
|
+
if (!page) return [];
|
33
|
+
const { hasNextPage = false, endCursor = "" } = page?.pageInfo || {};
|
34
|
+
results = results.concat(page.nodes);
|
35
|
+
let hasMore = hasNextPage;
|
36
|
+
params.variables.after = endCursor;
|
37
|
+
while (hasMore) {
|
38
|
+
try {
|
39
|
+
page = await apiQuery(params);
|
40
|
+
} catch (error) {
|
41
|
+
console.error(error);
|
42
|
+
return { nodes: results, edges: page.edge };
|
43
|
+
}
|
44
|
+
const { endCursor = params.variables.after, hasNextPage } = page.pageInfo || {};
|
45
|
+
results = results.concat(page.nodes);
|
46
|
+
hasMore = hasNextPage;
|
47
|
+
params.variables.after = endCursor;
|
48
|
+
}
|
49
|
+
|
50
|
+
return { nodes: results, edges: page.edge };
|
51
|
+
};
|
52
|
+
export const getGeoConfig = makeQuery("geoConfig");
|
53
|
+
export const getGeoScopes = makeQuery("geoScope");
|