Merge branch 'budget' into budget-public-controllers
This commit is contained in:
214
CUSTOMIZE_ES.md
Normal file
214
CUSTOMIZE_ES.md
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
# Personalización
|
||||||
|
|
||||||
|
Puedes modificar consul y ponerle tu propia imagen, para esto debes primero hacer un fork de https://github.com/consul/consul creando un repositorio nuevo en Github. Puedes usar otro servicio como Gitlab, pero no te olvides de poner el enlace en el footer a tu repositorio en cumplimiento con la licencia de este proyecto (GPL Affero 3).
|
||||||
|
|
||||||
|
Hemos creado una estructura específica donde puedes sobreescribir y personalizar la aplicación para que puedas actualizar sin que tengas problemas al hacer merge y se sobreescriban por error tus cambios. Intentamos que Consul sea una aplicación Ruby on Rails lo más plain vanilla posible para facilitar el acceso de nuevas desarrolladoras.
|
||||||
|
|
||||||
|
## Ficheros y directorios especiales
|
||||||
|
|
||||||
|
Para adaptarlo puedes hacerlo a través de los directorios que están en custom dentro de:
|
||||||
|
|
||||||
|
* config/locales/custom/
|
||||||
|
* app/assets/images/custom/
|
||||||
|
* app/views/custom/
|
||||||
|
* app/controllers/custom/
|
||||||
|
* app/models/custom/
|
||||||
|
|
||||||
|
Aparte de estos directorios también cuentas con ciertos ficheros para:
|
||||||
|
|
||||||
|
* app/assets/stylesheets/custom.css
|
||||||
|
* app/assets/javascripts/custom.js
|
||||||
|
* Gemfile_custom
|
||||||
|
* config/application.custom.rb
|
||||||
|
|
||||||
|
### Internacionalización
|
||||||
|
|
||||||
|
Si quieres modificar algún texto de la web deberías encontrarlos en los ficheros formato YML disponibles en *config/locales/*. Puedes leer la [guía de internacionalización](http://guides.rubyonrails.org/i18n.html) de Ruby on Rails sobre como funciona este sistema.
|
||||||
|
|
||||||
|
Las adaptaciones los debes poner en el directorio *config/locales/custom/*, recomendamos poner solo los textos que quieras personalizar. Por ejemplo si quieres personalizar el texto de "Ayuntamiento de Madrid, 2016" que se encuentra en el footer en todas las páginas, primero debemos ubicar en que plantilla se encuentra (app/views/layouts/_footer.html.erb), vemos que en el código pone lo siguiente:
|
||||||
|
```
|
||||||
|
<%= t("layouts.footer.copyright", year: Time.now.year) %>
|
||||||
|
```
|
||||||
|
|
||||||
|
Y que en el fichero config/locales/es.yml sigue esta estructura (solo ponemos lo relevante para este caso):
|
||||||
|
|
||||||
|
```
|
||||||
|
es:
|
||||||
|
layouts:
|
||||||
|
footer:
|
||||||
|
copyright: Ayuntamiento de Madrid, %{year}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Si creamos el fichero config/locales/custom/es.yml y modificamos "Ayuntamiento de Madrid" por el nombre de la organización que se este haciendo la modificación. Recomendamos directamente copiar los ficheros config/locales/ e ir revisando y corrigiendo las que querramos, borrando las líneas que no querramos traducir.
|
||||||
|
|
||||||
|
### Imágenes
|
||||||
|
|
||||||
|
Si quieres sobreescribir alguna imagen debes primero fijarte el nombre que tiene, por defecto se encuentran en *app/assets/images*. Por ejemplo si quieres modificar *app/assets/images/logo_header.png* debes poner otra con ese mismo nombre en el directorio app/assets/images/custom. Los iconos que seguramente quieras modificar son:
|
||||||
|
|
||||||
|
* apple-touch-icon-200.png
|
||||||
|
* icon_home.png
|
||||||
|
* logo_email.png
|
||||||
|
* logo_header.png
|
||||||
|
* map.jpg
|
||||||
|
* social-media-icon.png
|
||||||
|
|
||||||
|
### Vistas (HTML)
|
||||||
|
|
||||||
|
Si quieres modificar el HTML de alguna página puedes hacerlo copiando el HTML de *app/views* y poniendolo en *app/views/custom* respetando los subdirectorios que encuentres ahí. Por ejemplo si quieres modificar *app/views/pages/conditions.html* debes copiarlo y modificarla en app/views/custom/pages/conditions.html.erb
|
||||||
|
|
||||||
|
### CSS
|
||||||
|
|
||||||
|
Si quieres cambiar algun selector CSS (de las hojas de estilo) puedes hacerlo en el fichero *app/assets/stylesheets/custom.scss*. Por ejemplo si quieres cambiar el color del header (.top-links) puedes hacerlo agregando:
|
||||||
|
|
||||||
|
```
|
||||||
|
.top-links {
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Usamos un preprocesador de CSS, [SASS, con la sintaxis SCSS](http://sass-lang.com/guide).
|
||||||
|
|
||||||
|
### Javascript
|
||||||
|
|
||||||
|
Si quieres agregar código Javascript puedes hacerlo en el fichero *app/assets/javascripts/custom.js". Por ejemplo si quieres que salga una alerta puedes poner lo siguiente:
|
||||||
|
|
||||||
|
```
|
||||||
|
$(function(){
|
||||||
|
alert('foobar');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Modelos
|
||||||
|
|
||||||
|
Si quieres agregar modelos nuevos, o modificar o agregar métodos a uno ya existente puedes hacerlo en *app/models/custom*. En el caso de los modelos antiguos debes primero hacer un require de la dependencia.
|
||||||
|
|
||||||
|
Por ejemplo en el caso del Ayuntamiento de Madrid se requiere comprobar que el código postal durante la verificación sigue un cierto formato (empieza con 280). Esto se realiza creando este fichero en *app/models/custom/verification/residence.rb*:
|
||||||
|
|
||||||
|
```
|
||||||
|
require_dependency Rails.root.join('app', 'models', 'verification', 'residence').to_s
|
||||||
|
|
||||||
|
class Verification::Residence
|
||||||
|
|
||||||
|
validate :postal_code_in_madrid
|
||||||
|
validate :residence_in_madrid
|
||||||
|
|
||||||
|
def postal_code_in_madrid
|
||||||
|
errors.add(:postal_code, I18n.t('verification.residence.new.error_not_allowed_postal_code')) unless valid_postal_code?
|
||||||
|
end
|
||||||
|
|
||||||
|
def residence_in_madrid
|
||||||
|
return if errors.any?
|
||||||
|
|
||||||
|
unless residency_valid?
|
||||||
|
errors.add(:residence_in_madrid, false)
|
||||||
|
store_failed_attempt
|
||||||
|
Lock.increase_tries(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid_postal_code?
|
||||||
|
postal_code =~ /^280/
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
No olvides poner los tests relevantes en *spec/models/custom*, siguiendo con el ejemplo pondriamos lo siguiente en *spec/models/custom/residence_spec.rb*:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe Verification::Residence do
|
||||||
|
|
||||||
|
let(:residence) { build(:verification_residence, document_number: "12345678Z") }
|
||||||
|
|
||||||
|
describe "verification" do
|
||||||
|
|
||||||
|
describe "postal code" do
|
||||||
|
it "should be valid with postal codes starting with 280" do
|
||||||
|
residence.postal_code = "28012"
|
||||||
|
residence.valid?
|
||||||
|
expect(residence.errors[:postal_code].size).to eq(0)
|
||||||
|
|
||||||
|
residence.postal_code = "28023"
|
||||||
|
residence.valid?
|
||||||
|
expect(residence.errors[:postal_code].size).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid with postal codes not starting with 280" do
|
||||||
|
residence.postal_code = "12345"
|
||||||
|
residence.valid?
|
||||||
|
expect(residence.errors[:postal_code].size).to eq(1)
|
||||||
|
|
||||||
|
residence.postal_code = "13280"
|
||||||
|
residence.valid?
|
||||||
|
expect(residence.errors[:postal_code].size).to eq(1)
|
||||||
|
expect(residence.errors[:postal_code]).to include("In order to be verified, you must be registered in the municipality of Madrid.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Controladores
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### Gemfile
|
||||||
|
|
||||||
|
Para agregar librerías (gems) nuevas puedes hacerlo en el fichero *Gemfile_custom*. Por ejemplo si quieres agregar la gema [rails-footnotes](https://github.com/josevalim/rails-footnotes) debes hacerlo agregandole
|
||||||
|
|
||||||
|
```
|
||||||
|
gem 'rails-footnotes', '~> 4.0'
|
||||||
|
```
|
||||||
|
|
||||||
|
Y siguiendo el flujo clásico en Ruby on Rails (bundle install y seguir con los pasos específicos de la gema en la documentación)
|
||||||
|
|
||||||
|
### application.rb
|
||||||
|
|
||||||
|
Cuando necesites extender o modificar el *config/application.rb* puedes hacerlo a través del fichero *config/application_custom.rb*. Por ejemplo si quieres modificar el idioma por defecto al inglés pondrías lo siguiente:
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
module Consul
|
||||||
|
class Application < Rails::Application
|
||||||
|
config.i18n.default_locale = :en
|
||||||
|
config.i18n.available_locales = [:en, :es]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Recuerda que para ver reflejado estos cambios debes reiniciar el servidor de desarrollo.
|
||||||
|
|
||||||
|
### lib/
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### public/
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
### Seeds
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Actualizar
|
||||||
|
|
||||||
|
Te recomendamos que agregues el remote de consul para facilitar este proceso de merge:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ git remote add consul https://github.com/consul/consul
|
||||||
|
```
|
||||||
|
|
||||||
|
Con esto puedes actualizarte con
|
||||||
|
|
||||||
|
```
|
||||||
|
git checkout -b consul_update
|
||||||
|
git pull consul master
|
||||||
|
```
|
||||||
7
Gemfile
7
Gemfile
@@ -1,7 +1,7 @@
|
|||||||
source 'https://rubygems.org'
|
source 'https://rubygems.org'
|
||||||
|
|
||||||
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
|
||||||
gem 'rails', '4.2.7'
|
gem 'rails', '4.2.7.1'
|
||||||
# Use PostgreSQL
|
# Use PostgreSQL
|
||||||
gem 'pg'
|
gem 'pg'
|
||||||
# Use SCSS for stylesheets
|
# Use SCSS for stylesheets
|
||||||
@@ -19,6 +19,9 @@ gem 'jquery-ui-rails'
|
|||||||
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
|
# Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks
|
||||||
gem 'turbolinks'
|
gem 'turbolinks'
|
||||||
|
|
||||||
|
# Fix sprockets on the
|
||||||
|
gem 'sprockets', '~> 3.6.3'
|
||||||
|
|
||||||
gem 'devise', '~> 3.5.7'
|
gem 'devise', '~> 3.5.7'
|
||||||
# Use ActiveModel has_secure_password
|
# Use ActiveModel has_secure_password
|
||||||
# gem 'bcrypt', '~> 3.1.7'
|
# gem 'bcrypt', '~> 3.1.7'
|
||||||
@@ -94,3 +97,5 @@ group :development do
|
|||||||
# Access an IRB console on exception pages or by using <%= console %> in views
|
# Access an IRB console on exception pages or by using <%= console %> in views
|
||||||
gem 'web-console', '3.3.0'
|
gem 'web-console', '3.3.0'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
eval_gemfile './Gemfile_custom'
|
||||||
|
|||||||
77
Gemfile.lock
77
Gemfile.lock
@@ -1,36 +1,36 @@
|
|||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
actionmailer (4.2.7)
|
actionmailer (4.2.7.1)
|
||||||
actionpack (= 4.2.7)
|
actionpack (= 4.2.7.1)
|
||||||
actionview (= 4.2.7)
|
actionview (= 4.2.7.1)
|
||||||
activejob (= 4.2.7)
|
activejob (= 4.2.7.1)
|
||||||
mail (~> 2.5, >= 2.5.4)
|
mail (~> 2.5, >= 2.5.4)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
actionpack (4.2.7)
|
actionpack (4.2.7.1)
|
||||||
actionview (= 4.2.7)
|
actionview (= 4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
rack (~> 1.6)
|
rack (~> 1.6)
|
||||||
rack-test (~> 0.6.2)
|
rack-test (~> 0.6.2)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
actionview (4.2.7)
|
actionview (4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
erubis (~> 2.7.0)
|
erubis (~> 2.7.0)
|
||||||
rails-dom-testing (~> 1.0, >= 1.0.5)
|
rails-dom-testing (~> 1.0, >= 1.0.5)
|
||||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||||
activejob (4.2.7)
|
activejob (4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
globalid (>= 0.3.0)
|
globalid (>= 0.3.0)
|
||||||
activemodel (4.2.7)
|
activemodel (4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
builder (~> 3.1)
|
builder (~> 3.1)
|
||||||
activerecord (4.2.7)
|
activerecord (4.2.7.1)
|
||||||
activemodel (= 4.2.7)
|
activemodel (= 4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
arel (~> 6.0)
|
arel (~> 6.0)
|
||||||
activesupport (4.2.7)
|
activesupport (4.2.7.1)
|
||||||
i18n (~> 0.7)
|
i18n (~> 0.7)
|
||||||
json (~> 1.7, >= 1.7.7)
|
json (~> 1.7, >= 1.7.7)
|
||||||
minitest (~> 5.1)
|
minitest (~> 5.1)
|
||||||
@@ -67,7 +67,7 @@ GEM
|
|||||||
bcrypt (3.1.11)
|
bcrypt (3.1.11)
|
||||||
browser (2.2.0)
|
browser (2.2.0)
|
||||||
builder (3.2.2)
|
builder (3.2.2)
|
||||||
bullet (5.1.1)
|
bullet (5.2.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
uniform_notifier (~> 1.10.0)
|
uniform_notifier (~> 1.10.0)
|
||||||
byebug (9.0.5)
|
byebug (9.0.5)
|
||||||
@@ -114,12 +114,12 @@ GEM
|
|||||||
execjs
|
execjs
|
||||||
coffee-script-source (1.10.0)
|
coffee-script-source (1.10.0)
|
||||||
concurrent-ruby (1.0.2)
|
concurrent-ruby (1.0.2)
|
||||||
coveralls (0.8.14)
|
coveralls (0.8.15)
|
||||||
json (>= 1.8, < 3)
|
json (>= 1.8, < 3)
|
||||||
simplecov (~> 0.12.0)
|
simplecov (~> 0.12.0)
|
||||||
term-ansicolor (~> 1.3)
|
term-ansicolor (~> 1.3)
|
||||||
thor (~> 0.19.1)
|
thor (~> 0.19.1)
|
||||||
tins (~> 1.6.0)
|
tins (>= 1.6.0, < 2)
|
||||||
daemons (1.2.3)
|
daemons (1.2.3)
|
||||||
dalli (2.7.6)
|
dalli (2.7.6)
|
||||||
database_cleaner (1.5.3)
|
database_cleaner (1.5.3)
|
||||||
@@ -156,7 +156,7 @@ GEM
|
|||||||
factory_girl_rails (4.7.0)
|
factory_girl_rails (4.7.0)
|
||||||
factory_girl (~> 4.7.0)
|
factory_girl (~> 4.7.0)
|
||||||
railties (>= 3.0.0)
|
railties (>= 3.0.0)
|
||||||
faker (1.6.5)
|
faker (1.6.6)
|
||||||
i18n (~> 0.5)
|
i18n (~> 0.5)
|
||||||
faraday (0.9.2)
|
faraday (0.9.2)
|
||||||
multipart-post (>= 1.2, < 3)
|
multipart-post (>= 1.2, < 3)
|
||||||
@@ -174,7 +174,7 @@ GEM
|
|||||||
rspec (~> 3.0)
|
rspec (~> 3.0)
|
||||||
ruby-progressbar (~> 1.4)
|
ruby-progressbar (~> 1.4)
|
||||||
geocoder (1.3.7)
|
geocoder (1.3.7)
|
||||||
globalid (0.3.6)
|
globalid (0.3.7)
|
||||||
activesupport (>= 4.1.0)
|
activesupport (>= 4.1.0)
|
||||||
groupdate (3.0.1)
|
groupdate (3.0.1)
|
||||||
activesupport (>= 3)
|
activesupport (>= 3)
|
||||||
@@ -290,16 +290,16 @@ GEM
|
|||||||
rack
|
rack
|
||||||
rack-test (0.6.3)
|
rack-test (0.6.3)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
rails (4.2.7)
|
rails (4.2.7.1)
|
||||||
actionmailer (= 4.2.7)
|
actionmailer (= 4.2.7.1)
|
||||||
actionpack (= 4.2.7)
|
actionpack (= 4.2.7.1)
|
||||||
actionview (= 4.2.7)
|
actionview (= 4.2.7.1)
|
||||||
activejob (= 4.2.7)
|
activejob (= 4.2.7.1)
|
||||||
activemodel (= 4.2.7)
|
activemodel (= 4.2.7.1)
|
||||||
activerecord (= 4.2.7)
|
activerecord (= 4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
bundler (>= 1.3.0, < 2.0)
|
bundler (>= 1.3.0, < 2.0)
|
||||||
railties (= 4.2.7)
|
railties (= 4.2.7.1)
|
||||||
sprockets-rails
|
sprockets-rails
|
||||||
rails-deprecated_sanitizer (1.0.3)
|
rails-deprecated_sanitizer (1.0.3)
|
||||||
activesupport (>= 4.2.0.alpha)
|
activesupport (>= 4.2.0.alpha)
|
||||||
@@ -309,9 +309,9 @@ GEM
|
|||||||
rails-deprecated_sanitizer (>= 1.0.1)
|
rails-deprecated_sanitizer (>= 1.0.1)
|
||||||
rails-html-sanitizer (1.0.3)
|
rails-html-sanitizer (1.0.3)
|
||||||
loofah (~> 2.0)
|
loofah (~> 2.0)
|
||||||
railties (4.2.7)
|
railties (4.2.7.1)
|
||||||
actionpack (= 4.2.7)
|
actionpack (= 4.2.7.1)
|
||||||
activesupport (= 4.2.7)
|
activesupport (= 4.2.7.1)
|
||||||
rake (>= 0.8.7)
|
rake (>= 0.8.7)
|
||||||
thor (>= 0.18.1, < 2.0)
|
thor (>= 0.18.1, < 2.0)
|
||||||
raindrops (0.16.0)
|
raindrops (0.16.0)
|
||||||
@@ -350,7 +350,7 @@ GEM
|
|||||||
safely_block (0.1.1)
|
safely_block (0.1.1)
|
||||||
errbase
|
errbase
|
||||||
sass (3.4.22)
|
sass (3.4.22)
|
||||||
sass-rails (5.0.5)
|
sass-rails (5.0.6)
|
||||||
railties (>= 4.0.0, < 6)
|
railties (>= 4.0.0, < 6)
|
||||||
sass (~> 3.1)
|
sass (~> 3.1)
|
||||||
sprockets (>= 2.8, < 4.0)
|
sprockets (>= 2.8, < 4.0)
|
||||||
@@ -396,7 +396,7 @@ GEM
|
|||||||
thread (0.2.2)
|
thread (0.2.2)
|
||||||
thread_safe (0.3.5)
|
thread_safe (0.3.5)
|
||||||
tilt (2.0.5)
|
tilt (2.0.5)
|
||||||
tins (1.6.0)
|
tins (1.11.0)
|
||||||
tolk (1.9.3)
|
tolk (1.9.3)
|
||||||
rails (>= 4.0, < 4.3)
|
rails (>= 4.0, < 4.3)
|
||||||
safe_yaml (>= 0.8.6)
|
safe_yaml (>= 0.8.6)
|
||||||
@@ -408,7 +408,7 @@ GEM
|
|||||||
tilt (>= 1.4, < 3)
|
tilt (>= 1.4, < 3)
|
||||||
tzinfo (1.2.2)
|
tzinfo (1.2.2)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
uglifier (3.0.0)
|
uglifier (3.0.1)
|
||||||
execjs (>= 0.3.0, < 3)
|
execjs (>= 0.3.0, < 3)
|
||||||
unicorn (5.1.0)
|
unicorn (5.1.0)
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
@@ -485,7 +485,7 @@ DEPENDENCIES
|
|||||||
pg_search
|
pg_search
|
||||||
poltergeist
|
poltergeist
|
||||||
quiet_assets
|
quiet_assets
|
||||||
rails (= 4.2.7)
|
rails (= 4.2.7.1)
|
||||||
redcarpet
|
redcarpet
|
||||||
responders
|
responders
|
||||||
rinku
|
rinku
|
||||||
@@ -496,6 +496,7 @@ DEPENDENCIES
|
|||||||
social-share-button
|
social-share-button
|
||||||
spring
|
spring
|
||||||
spring-commands-rspec
|
spring-commands-rspec
|
||||||
|
sprockets (~> 3.6.3)
|
||||||
tolk
|
tolk
|
||||||
turbolinks
|
turbolinks
|
||||||
turnout
|
turnout
|
||||||
|
|||||||
5
Gemfile_custom
Normal file
5
Gemfile_custom
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Overrides and adds customized gems in this file
|
||||||
|
# Read more on documentation:
|
||||||
|
# * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md#gemfile
|
||||||
|
# * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md#gemfile
|
||||||
|
#
|
||||||
@@ -62,6 +62,10 @@ But for some actions like voting, you will need a verified user, the seeds file
|
|||||||
**user:** verified@consul.dev
|
**user:** verified@consul.dev
|
||||||
**pass:** 12345678
|
**pass:** 12345678
|
||||||
|
|
||||||
|
### Customization
|
||||||
|
|
||||||
|
See [CUSTOMIZE_ES.md](CUSTOMIZE_ES.md)
|
||||||
|
|
||||||
### OAuth
|
### OAuth
|
||||||
|
|
||||||
To test authentication services with external OAuth suppliers - right now Twitter, Facebook and Google - you'll need to create an "application" in each of the supported platforms and set the *key* and *secret* provided in your *secrets.yml*
|
To test authentication services with external OAuth suppliers - right now Twitter, Facebook and Google - you'll need to create an "application" in each of the supported platforms and set the *key* and *secret* provided in your *secrets.yml*
|
||||||
|
|||||||
@@ -61,6 +61,9 @@ Pero para ciertas acciones, como apoyar, necesitarás un usuario verificado, el
|
|||||||
**user:** verified@consul.dev
|
**user:** verified@consul.dev
|
||||||
**pass:** 12345678
|
**pass:** 12345678
|
||||||
|
|
||||||
|
### Customización
|
||||||
|
|
||||||
|
Ver fichero [CUSTOMIZE_ES.md](CUSTOMIZE_ES.md)
|
||||||
|
|
||||||
### OAuth
|
### OAuth
|
||||||
|
|
||||||
|
|||||||
0
app/assets/images/custom/.keep
Normal file
0
app/assets/images/custom/.keep
Normal file
@@ -16,7 +16,7 @@
|
|||||||
//= require jquery-ui/datepicker-es
|
//= require jquery-ui/datepicker-es
|
||||||
//= require foundation
|
//= require foundation
|
||||||
//= require turbolinks
|
//= require turbolinks
|
||||||
//= require ckeditor/init
|
//= require ckeditor/loader
|
||||||
//= require_directory ./ckeditor
|
//= require_directory ./ckeditor
|
||||||
//= require social-share-button
|
//= require social-share-button
|
||||||
//= require initial
|
//= require initial
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
//= require valuation_spending_proposal_form
|
//= require valuation_spending_proposal_form
|
||||||
//= require embed_video
|
//= require embed_video
|
||||||
//= require banners
|
//= require banners
|
||||||
|
//= require custom
|
||||||
|
|
||||||
var initialize_modules = function() {
|
var initialize_modules = function() {
|
||||||
App.Comments.initialize();
|
App.Comments.initialize();
|
||||||
|
|||||||
3
app/assets/javascripts/ckeditor/loader.js.erb
Normal file
3
app/assets/javascripts/ckeditor/loader.js.erb
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
//= require ckeditor/init
|
||||||
|
|
||||||
|
CKEDITOR.config.customConfig = '<%= javascript_path 'ckeditor/config.js' %>';
|
||||||
7
app/assets/javascripts/custom.js
Normal file
7
app/assets/javascripts/custom.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Overrides and adds customized javascripts in this file
|
||||||
|
// Read more on documentation:
|
||||||
|
// * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md#javascript
|
||||||
|
// * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md#javascript
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
@@ -36,12 +36,21 @@ body.admin {
|
|||||||
input[type="text"], textarea {
|
input[type="text"], textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-group input[type="text"] {
|
||||||
|
border-radius: 0;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
table {
|
table {
|
||||||
|
|
||||||
th {
|
th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
|
&.with-button {
|
||||||
|
line-height: $line-height*2;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tr {
|
tr {
|
||||||
|
|||||||
@@ -1,2 +1,5 @@
|
|||||||
// Overrides and adds customized styles in this file
|
// Overrides and adds customized styles in this file
|
||||||
|
// Read more on documentation:
|
||||||
|
// * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md#css
|
||||||
|
// * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md#css
|
||||||
//
|
//
|
||||||
15
app/controllers/admin/budget_groups_controller.rb
Normal file
15
app/controllers/admin/budget_groups_controller.rb
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
class Admin::BudgetGroupsController < Admin::BaseController
|
||||||
|
|
||||||
|
def create
|
||||||
|
@budget = Budget.find params[:budget_id]
|
||||||
|
@budget.groups.create(budget_group_params)
|
||||||
|
@groups = @budget.groups.includes(:headings)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def budget_group_params
|
||||||
|
params.require(:budget_group).permit(:name)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
16
app/controllers/admin/budget_headings_controller.rb
Normal file
16
app/controllers/admin/budget_headings_controller.rb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
class Admin::BudgetHeadingsController < Admin::BaseController
|
||||||
|
|
||||||
|
def create
|
||||||
|
@budget = Budget.find params[:budget_id]
|
||||||
|
@budget_group = @budget.groups.find params[:budget_group_id]
|
||||||
|
@budget_group.headings.create(budget_heading_params)
|
||||||
|
@headings = @budget_group.headings
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def budget_heading_params
|
||||||
|
params.require(:budget_heading).permit(:name, :price, :geozone_id)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
34
app/controllers/admin/budgets_controller.rb
Normal file
34
app/controllers/admin/budgets_controller.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
class Admin::BudgetsController < Admin::BaseController
|
||||||
|
|
||||||
|
has_filters %w{open finished}, only: :index
|
||||||
|
|
||||||
|
load_and_authorize_resource
|
||||||
|
|
||||||
|
def index
|
||||||
|
@budgets = Budget.send(@current_filter).order(created_at: :desc).page(params[:page])
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@budget = Budget.includes(groups: :headings).find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@budget = Budget.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@budget = Budget.new(budget_params)
|
||||||
|
if @budget.save
|
||||||
|
redirect_to admin_budget_path(@budget), notice: t('admin.budgets.create.notice')
|
||||||
|
else
|
||||||
|
render :new
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def budget_params
|
||||||
|
params.require(:budget).permit(:name, :description, :phase, :currency_symbol)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
11
app/helpers/budgets_helper.rb
Normal file
11
app/helpers/budgets_helper.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
module BudgetsHelper
|
||||||
|
|
||||||
|
def budget_phases_select_options
|
||||||
|
Budget::VALID_PHASES.map { |ph| [ t("budget.phase.#{ph}"), ph ] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def budget_currency_symbol_select_options
|
||||||
|
Budget::CURRENCY_SYMBOLS.map { |cs| [ cs, cs ] }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -8,4 +8,9 @@ module GeozonesHelper
|
|||||||
Geozone.all.order(name: :asc).collect { |g| [ g.name, g.id ] }
|
Geozone.all.order(name: :asc).collect { |g| [ g.name, g.id ] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def geozone_name_from_id(g_id)
|
||||||
|
@all_geozones ||= Geozone.all.collect{ |g| [ g.id, g.name ] }.to_h
|
||||||
|
@all_geozones[g_id] || t("geozones.none")
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -60,8 +60,8 @@ class Mailer < ApplicationMailer
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def proposal_notification_digest(user)
|
def proposal_notification_digest(user, notifications)
|
||||||
@notifications = user.notifications.where(notifiable_type: "ProposalNotification")
|
@notifications = notifications
|
||||||
|
|
||||||
with_user(user) do
|
with_user(user) do
|
||||||
mail(to: user.email, subject: t('mailers.proposal_notification_digest.title', org_name: Setting['org_name']))
|
mail(to: user.email, subject: t('mailers.proposal_notification_digest.title', org_name: Setting['org_name']))
|
||||||
|
|||||||
@@ -42,7 +42,9 @@ module Abilities
|
|||||||
|
|
||||||
can [:read, :update, :valuate, :destroy, :summary], SpendingProposal
|
can [:read, :update, :valuate, :destroy, :summary], SpendingProposal
|
||||||
|
|
||||||
can [:create, :update], Budget
|
can [:index, :read, :new, :create, :update, :destroy], Budget
|
||||||
|
can [:read, :create, :update, :destroy], Budget::Group
|
||||||
|
can [:read, :create, :update, :destroy], Budget::Heading
|
||||||
can [:hide, :update], Budget::Investment
|
can [:hide, :update], Budget::Investment
|
||||||
can :valuate, Budget::Investment, budget: { valuating: true }
|
can :valuate, Budget::Investment, budget: { valuating: true }
|
||||||
can :create, Budget::ValuatorAssignment
|
can :create, Budget::ValuatorAssignment
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ class Budget < ActiveRecord::Base
|
|||||||
include Sanitizable
|
include Sanitizable
|
||||||
|
|
||||||
VALID_PHASES = %W{on_hold accepting selecting balloting finished}
|
VALID_PHASES = %W{on_hold accepting selecting balloting finished}
|
||||||
|
CURRENCY_SYMBOLS = %W{€ $ £ ¥}
|
||||||
|
|
||||||
|
validates :name, presence: true
|
||||||
validates :phase, inclusion: { in: VALID_PHASES }
|
validates :phase, inclusion: { in: VALID_PHASES }
|
||||||
validates :currency_symbol, presence: true
|
validates :currency_symbol, presence: true
|
||||||
|
|
||||||
@@ -13,6 +15,9 @@ class Budget < ActiveRecord::Base
|
|||||||
has_many :headings, through: :groups
|
has_many :headings, through: :groups
|
||||||
has_many :investments, through: :headings
|
has_many :investments, through: :headings
|
||||||
|
|
||||||
|
scope :open, -> { where.not(phase: "finished") }
|
||||||
|
scope :finished, -> { where(phase: "finished") }
|
||||||
|
|
||||||
def on_hold?
|
def on_hold?
|
||||||
phase == "on_hold"
|
phase == "on_hold"
|
||||||
end
|
end
|
||||||
|
|||||||
0
app/models/custom/.keep
Normal file
0
app/models/custom/.keep
Normal file
29
app/models/custom/verification/residence.rb
Normal file
29
app/models/custom/verification/residence.rb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
require_dependency Rails.root.join('app', 'models', 'verification', 'residence').to_s
|
||||||
|
|
||||||
|
class Verification::Residence
|
||||||
|
|
||||||
|
validate :postal_code_in_madrid
|
||||||
|
validate :residence_in_madrid
|
||||||
|
|
||||||
|
def postal_code_in_madrid
|
||||||
|
errors.add(:postal_code, I18n.t('verification.residence.new.error_not_allowed_postal_code')) unless valid_postal_code?
|
||||||
|
end
|
||||||
|
|
||||||
|
def residence_in_madrid
|
||||||
|
return if errors.any?
|
||||||
|
|
||||||
|
unless residency_valid?
|
||||||
|
errors.add(:residence_in_madrid, false)
|
||||||
|
store_failed_attempt
|
||||||
|
Lock.increase_tries(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid_postal_code?
|
||||||
|
postal_code =~ /^280/
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -2,9 +2,11 @@ class Notification < ActiveRecord::Base
|
|||||||
belongs_to :user, counter_cache: true
|
belongs_to :user, counter_cache: true
|
||||||
belongs_to :notifiable, polymorphic: true
|
belongs_to :notifiable, polymorphic: true
|
||||||
|
|
||||||
scope :unread, -> { all }
|
scope :unread, -> { all }
|
||||||
scope :recent, -> { order(id: :desc) }
|
scope :recent, -> { order(id: :desc) }
|
||||||
scope :for_render, -> { includes(:notifiable) }
|
scope :not_emailed, -> { where(emailed_at: nil) }
|
||||||
|
scope :for_render, -> { includes(:notifiable) }
|
||||||
|
|
||||||
|
|
||||||
def timestamp
|
def timestamp
|
||||||
notifiable.created_at
|
notifiable.created_at
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class Proposal < ActiveRecord::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def voters
|
def voters
|
||||||
votes_for.voters
|
User.active.where(id: votes_for.voters)
|
||||||
end
|
end
|
||||||
|
|
||||||
def editable?
|
def editable?
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ class User < ActiveRecord::Base
|
|||||||
scope :for_render, -> { includes(:organization) }
|
scope :for_render, -> { includes(:organization) }
|
||||||
scope :by_document, -> (document_type, document_number) { where(document_type: document_type, document_number: document_number) }
|
scope :by_document, -> (document_type, document_number) { where(document_type: document_type, document_number: document_number) }
|
||||||
scope :email_digest, -> { where(email_digest: true) }
|
scope :email_digest, -> { where(email_digest: true) }
|
||||||
|
scope :active, -> { where(erased_at: nil) }
|
||||||
|
|
||||||
before_validation :clean_document_number
|
before_validation :clean_document_number
|
||||||
|
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ class Verification::Residence
|
|||||||
|
|
||||||
validate :allowed_age
|
validate :allowed_age
|
||||||
validate :document_number_uniqueness
|
validate :document_number_uniqueness
|
||||||
validate :postal_code_in_madrid
|
|
||||||
validate :residence_in_madrid
|
|
||||||
|
|
||||||
def initialize(attrs={})
|
def initialize(attrs={})
|
||||||
self.date_of_birth = parse_date('date_of_birth', attrs)
|
self.date_of_birth = parse_date('date_of_birth', attrs)
|
||||||
@@ -45,20 +43,6 @@ class Verification::Residence
|
|||||||
errors.add(:document_number, I18n.t('errors.messages.taken')) if User.where(document_number: document_number).any?
|
errors.add(:document_number, I18n.t('errors.messages.taken')) if User.where(document_number: document_number).any?
|
||||||
end
|
end
|
||||||
|
|
||||||
def postal_code_in_madrid
|
|
||||||
errors.add(:postal_code, I18n.t('verification.residence.new.error_not_allowed_postal_code')) unless valid_postal_code?
|
|
||||||
end
|
|
||||||
|
|
||||||
def residence_in_madrid
|
|
||||||
return if errors.any?
|
|
||||||
|
|
||||||
unless residency_valid?
|
|
||||||
errors.add(:residence_in_madrid, false)
|
|
||||||
store_failed_attempt
|
|
||||||
Lock.increase_tries(user)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def store_failed_attempt
|
def store_failed_attempt
|
||||||
FailedCensusCall.create({
|
FailedCensusCall.create({
|
||||||
user: user,
|
user: user,
|
||||||
@@ -97,8 +81,4 @@ class Verification::Residence
|
|||||||
self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase unless self.document_number.blank?
|
self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase unless self.document_number.blank?
|
||||||
end
|
end
|
||||||
|
|
||||||
def valid_postal_code?
|
|
||||||
postal_code =~ /^280/
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -35,6 +35,14 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<%# if feature?(:budgets) %>
|
||||||
|
<li <%= "class=active" if controller_name == "budgets" %>>
|
||||||
|
<%= link_to admin_budgets_path do %>
|
||||||
|
<span class="icon-budget"></span><%= t("admin.menu.budgets") %>
|
||||||
|
<% end %>
|
||||||
|
</li>
|
||||||
|
<%# end %>
|
||||||
|
|
||||||
<li <%= "class=active" if controller_name == "banners" %>>
|
<li <%= "class=active" if controller_name == "banners" %>>
|
||||||
<%= link_to admin_banners_path do %>
|
<%= link_to admin_banners_path do %>
|
||||||
<span class="icon-eye"></span><%= t("admin.menu.banner") %>
|
<span class="icon-eye"></span><%= t("admin.menu.banner") %>
|
||||||
|
|||||||
2
app/views/admin/budget_groups/create.js.erb
Normal file
2
app/views/admin/budget_groups/create.js.erb
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
$("#<%= dom_id(@budget) %>_groups").html('<%= j render("admin/budgets/groups", groups: @groups) %>');
|
||||||
|
App.Forms.toggleLink();
|
||||||
2
app/views/admin/budget_headings/create.js.erb
Normal file
2
app/views/admin/budget_headings/create.js.erb
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
$("#<%= dom_id(@budget_group) %>").html('<%= j render("admin/budgets/group", group: @budget_group, headings: @headings) %>');
|
||||||
|
App.Forms.toggleLink();
|
||||||
76
app/views/admin/budgets/_group.html.erb
Normal file
76
app/views/admin/budgets/_group.html.erb
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
<div class="small-12 column">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th colspan="3" class="with-button">
|
||||||
|
<%= group.name %>
|
||||||
|
<%= link_to t("admin.budgets.form.add_heading"), "#", class: "button float-right js-toggle-link", data: { "toggle-selector" => "#group-#{group.id}-new-heading-form" } %>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<% if headings.blank? %>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">
|
||||||
|
<div class="callout primary">
|
||||||
|
<%= t("admin.budgets.form.no_heading") %>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% else %>
|
||||||
|
<tr>
|
||||||
|
<th><%= t("admin.budgets.form.table_heading") %></th>
|
||||||
|
<th><%= t("admin.budgets.form.table_amount") %></th>
|
||||||
|
<th><%= t("admin.budgets.form.table_geozone") %></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<!-- new heading form -->
|
||||||
|
<tr id="group-<%= group.id %>-new-heading-form" style="display:none">
|
||||||
|
<td colspan="3">
|
||||||
|
<%= form_for [:admin, @budget, group, Budget::Heading.new], remote: true do |f| %>
|
||||||
|
<label><%= t("admin.budgets.form.heading") %></label>
|
||||||
|
<%= f.text_field :name,
|
||||||
|
label: false,
|
||||||
|
maxlength: 50,
|
||||||
|
placeholder: t("admin.budgets.form.heading") %>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="small-12 medium-6 column">
|
||||||
|
<label><%= t("admin.budgets.form.amount") %></label>
|
||||||
|
<%= f.text_field :price,
|
||||||
|
label: false,
|
||||||
|
maxlength: 8,
|
||||||
|
placeholder: t("admin.budgets.form.amount") %>
|
||||||
|
</div>
|
||||||
|
<div class="small-12 medium-6 column">
|
||||||
|
<label><%= t("admin.budgets.form.geozone") %></label>
|
||||||
|
<%= f.select :geozone_id, geozone_select_options, {include_blank: t("geozones.none"), label: false} %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<%= f.submit t("admin.budgets.form.save_heading"), class: "button success" %>
|
||||||
|
<% end %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<!-- /. new heading form -->
|
||||||
|
<!-- headings list -->
|
||||||
|
<% headings.each do |heading| %>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<%= heading.name %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= heading.price %>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<%= geozone_name_from_id heading.geozone_id %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
<!-- /. headings list -->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
34
app/views/admin/budgets/_groups.html.erb
Normal file
34
app/views/admin/budgets/_groups.html.erb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
<div class="small-12 column">
|
||||||
|
<h3 class="inline-block"><%= t('admin.budgets.show.groups') %></h3>
|
||||||
|
<% if groups.blank? %>
|
||||||
|
<div class="callout primary">
|
||||||
|
<%= t("admin.budgets.form.no_groups") %>
|
||||||
|
<strong><%= link_to t("admin.budgets.form.add_group"), "#",
|
||||||
|
class: "js-toggle-link",
|
||||||
|
data: { "toggle-selector" => "#new-group-form" } %></strong>
|
||||||
|
</div>
|
||||||
|
<% else %>
|
||||||
|
<%= link_to t("admin.budgets.form.add_group"), "#", class: "button float-right js-toggle-link", data: { "toggle-selector" => "#new-group-form" } %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<%= form_for [:admin, @budget, Budget::Group.new], html: {id: "new-group-form", style: "display:none"}, remote: true do |f| %>
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-label">
|
||||||
|
<label><%= f.label :name,t("admin.budgets.form.group") %></label>
|
||||||
|
</span>
|
||||||
|
<%= f.text_field :name,
|
||||||
|
label: false,
|
||||||
|
maxlength: 50,
|
||||||
|
placeholder: t("admin.budgets.form.group") %>
|
||||||
|
<div class="input-group-button">
|
||||||
|
<%= f.submit t("admin.budgets.form.create_group"), class: "button success" %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% groups.each do |group| %>
|
||||||
|
<div class="row" id="<%= dom_id(group) %>">
|
||||||
|
<%= render "admin/budgets/group", group: group, headings: group.headings %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
25
app/views/admin/budgets/index.html.erb
Normal file
25
app/views/admin/budgets/index.html.erb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<h2 class="inline-block"><%= t("admin.budgets.index.title") %></h2>
|
||||||
|
|
||||||
|
<%= link_to t("admin.budgets.index.new_link"),
|
||||||
|
new_admin_budget_path,
|
||||||
|
class: "button float-right margin-right" %>
|
||||||
|
|
||||||
|
<%= render 'shared/filter_subnav', i18n_namespace: "admin.budgets.index" %>
|
||||||
|
|
||||||
|
|
||||||
|
<h3><%= page_entries_info @budgets %></h3>
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<% @budgets.each do |budget| %>
|
||||||
|
<tr id="<%= dom_id(budget) %>" class="budget">
|
||||||
|
<td>
|
||||||
|
<%= link_to budget.name, admin_budget_path(budget) %>
|
||||||
|
</td>
|
||||||
|
<td class="small">
|
||||||
|
<%= t("budget.phase.#{budget.phase}") %>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<% end %>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<%= paginate @budgets %>
|
||||||
29
app/views/admin/budgets/new.html.erb
Normal file
29
app/views/admin/budgets/new.html.erb
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="small-12 medium-9 column">
|
||||||
|
<h2><%= t("admin.budgets.new.title") %></h2>
|
||||||
|
|
||||||
|
<%= form_for [:admin, @budget] do |f| %>
|
||||||
|
|
||||||
|
<%= f.label :name, t("admin.budgets.new.name") %>
|
||||||
|
<%= f.text_field :name,
|
||||||
|
label: false,
|
||||||
|
maxlength: 30,
|
||||||
|
placeholder: t("admin.budgets.new.name") %>
|
||||||
|
|
||||||
|
<%= f.label :description, t("admin.budgets.new.description") %>
|
||||||
|
<%= f.text_area :description, rows: 3, maxlength: 6000, label: false, placeholder: t("admin.budgets.new.description") %>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="small-12 medium-9 column">
|
||||||
|
<%= f.label :description, t("admin.budgets.new.phase") %>
|
||||||
|
<%= f.select :phase, budget_phases_select_options, {label: false} %>
|
||||||
|
</div>
|
||||||
|
<div class="small-12 medium-3 column">
|
||||||
|
<%= f.label :description, t("admin.budgets.new.currency") %>
|
||||||
|
<%= f.select :currency_symbol, budget_currency_symbol_select_options, {label: false} %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<%= f.submit t("admin.budgets.new.create"), class: "button success" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
16
app/views/admin/budgets/show.html.erb
Normal file
16
app/views/admin/budgets/show.html.erb
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="row">
|
||||||
|
<div class="small-12 medium-9 column">
|
||||||
|
<h2><%= @budget.name %></h2>
|
||||||
|
|
||||||
|
<%= simple_format(text_with_links(@budget.description), {}, sanitize: false) %>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<strong><%= t('admin.budgets.show.phase') %>:</strong> <%= t("budget.phase.#{@budget.phase}") %> |
|
||||||
|
<strong><%= t('admin.budgets.show.currency') %>:</strong> <%= @budget.currency_symbol %>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="<%= dom_id @budget %>_groups" class="row">
|
||||||
|
<%= render "groups", groups: @budget.groups %>
|
||||||
|
</div>
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
|
|
||||||
<% if comment.children.size > 0 %>
|
<% if comment.children.size > 0 %>
|
||||||
<%= link_to "", class: "js-toggle-children relative", data: {'id': "#{dom_id(comment)}"} do %>
|
<%= link_to "", class: "js-toggle-children relative", data: {'id': "#{dom_id(comment)}"} do %>
|
||||||
|
<span class="sr-only"><%= t("shared.show") %></span>
|
||||||
<span id="<%= dom_id(comment) %>_children_arrow" class="icon-arrow-down"></span> <%= t("comments.comment.responses", count: comment.children.size) %>
|
<span id="<%= dom_id(comment) %>_children_arrow" class="icon-arrow-down"></span> <%= t("comments.comment.responses", count: comment.children.size) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
<% if can?(:vote, comment) %>
|
<% if can?(:vote, comment) %>
|
||||||
<%= link_to vote_comment_path(comment, value: 'yes'),
|
<%= link_to vote_comment_path(comment, value: 'yes'),
|
||||||
method: "post", remote: true do %>
|
method: "post", remote: true do %>
|
||||||
<span class="icon-angle-up"></span>
|
<span class="icon-angle-up">
|
||||||
|
<span class="sr-only"><%= t('votes.agree') %></span>
|
||||||
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="icon-angle-up"></span>
|
<span class="icon-angle-up"></span>
|
||||||
@@ -19,7 +21,9 @@
|
|||||||
<% if can?(:vote, comment) %>
|
<% if can?(:vote, comment) %>
|
||||||
<%= link_to vote_comment_path(comment, value: 'no'),
|
<%= link_to vote_comment_path(comment, value: 'no'),
|
||||||
method: "post", remote: true do %>
|
method: "post", remote: true do %>
|
||||||
<span class="icon-angle-down"></span>
|
<span class="icon-angle-down">
|
||||||
|
<span class="sr-only"><%= t('votes.disagree') %></span>
|
||||||
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="icon-angle-down"></span>
|
<span class="icon-angle-down"></span>
|
||||||
|
|||||||
0
app/views/custom/.keep
Normal file
0
app/views/custom/.keep
Normal file
@@ -3,7 +3,9 @@
|
|||||||
<div class="in-favor inline-block">
|
<div class="in-favor inline-block">
|
||||||
<%= link_to vote_debate_path(debate, value: 'yes'),
|
<%= link_to vote_debate_path(debate, value: 'yes'),
|
||||||
class: "like #{voted_classes[:in_favor]}", title: t('votes.agree'), method: "post", remote: true do %>
|
class: "like #{voted_classes[:in_favor]}", title: t('votes.agree'), method: "post", remote: true do %>
|
||||||
<span class="icon-like"></span>
|
<span class="icon-like">
|
||||||
|
<span class="sr-only"><%= t('votes.agree') %></span>
|
||||||
|
</span>
|
||||||
<span class="percentage"><%= votes_percentage('likes', debate) %></span>
|
<span class="percentage"><%= votes_percentage('likes', debate) %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -12,7 +14,9 @@
|
|||||||
|
|
||||||
<div class="against inline-block">
|
<div class="against inline-block">
|
||||||
<%= link_to vote_debate_path(debate, value: 'no'), class: "unlike #{voted_classes[:against]}", title: t('votes.disagree'), method: "post", remote: true do %>
|
<%= link_to vote_debate_path(debate, value: 'no'), class: "unlike #{voted_classes[:against]}", title: t('votes.disagree'), method: "post", remote: true do %>
|
||||||
<span class="icon-unlike"></span>
|
<span class="icon-unlike">
|
||||||
|
<span class="sr-only"><%= t('votes.disagree') %></span>
|
||||||
|
</span>
|
||||||
<span class="percentage"><%= votes_percentage('dislikes', debate) %></span>
|
<span class="percentage"><%= votes_percentage('dislikes', debate) %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<% if user_signed_in? %>
|
<% if user_signed_in? %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to notifications_path, class: "notifications", accesskey: "n" do %>
|
<%= link_to notifications_path, class: "notifications", accesskey: "n" do %>
|
||||||
|
<span class="sr-only"><%= t("layouts.header.notifications") %></span>
|
||||||
<% if current_user.notifications_count > 0 %>
|
<% if current_user.notifications_count > 0 %>
|
||||||
<span class="icon-circle" aria-hidden="true"></span>
|
<span class="icon-circle" aria-hidden="true"></span>
|
||||||
<span class="icon-notification" aria-hidden="true" title="<%= t('layouts.header.new_notifications', count: current_user.notifications_count).html_safe %>">
|
<span class="icon-notification" aria-hidden="true" title="<%= t('layouts.header.new_notifications', count: current_user.notifications_count).html_safe %>">
|
||||||
|
|||||||
@@ -5,13 +5,13 @@
|
|||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if current_user.moderator? || current_user.administrator? %>
|
<% if current_user.administrator? || current_user.moderator? %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to t("layouts.header.moderation"), moderation_root_path %>
|
<%= link_to t("layouts.header.moderation"), moderation_root_path %>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if feature?(:spending_proposals) && (current_user.valuator? || current_user.administrator?) %>
|
<% if feature?(:spending_proposals) && (current_user.administrator? || current_user.valuator?) %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to t("layouts.header.valuation"), valuation_root_path %>
|
<%= link_to t("layouts.header.valuation"), valuation_root_path %>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -21,24 +21,24 @@
|
|||||||
<main>
|
<main>
|
||||||
<div class="row text-center margin">
|
<div class="row text-center margin">
|
||||||
<div class="small-12 medium-3 column">
|
<div class="small-12 medium-3 column">
|
||||||
<%= image_tag("icon_home_debate.png", size: "168x168", alt: t("welcome.debates.alt"), title: t("welcome.debates.title")) %>
|
<%= image_tag("icon_home_debate.png", size: "168x168", alt: "", title: t("welcome.debates.title")) %>
|
||||||
<h2><%= t("welcome.debates.title") %></h2>
|
<h2><%= t("welcome.debates.title") %></h2>
|
||||||
<p><%= t("welcome.debates.description") %></p>
|
<p><%= t("welcome.debates.description") %></p>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="small-12 medium-3 column">
|
<div class="small-12 medium-3 column">
|
||||||
<%= image_tag("icon_home_proposal.png", size: "168x168", alt: t("welcome.proposal.alt"), title: t("welcome.proposal.title")) %>
|
<%= image_tag("icon_home_proposal.png", size: "168x168", alt: "", title: t("welcome.proposal.title")) %>
|
||||||
<h2><%= t("welcome.proposal.title") %></h2>
|
<h2><%= t("welcome.proposal.title") %></h2>
|
||||||
<p><%= t("welcome.proposal.description") %></p>
|
<p><%= t("welcome.proposal.description") %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="small-12 medium-3 column">
|
<div class="small-12 medium-3 column">
|
||||||
<%= image_tag("icon_home_decide.png", size: "168x168", alt: t("welcome.decide.alt"), title: t("welcome.decide.title")) %>
|
<%= image_tag("icon_home_decide.png", size: "168x168", alt: "", title: t("welcome.decide.title")) %>
|
||||||
<h2><%= t("welcome.decide.title") %></h2>
|
<h2><%= t("welcome.decide.title") %></h2>
|
||||||
<p><%= t("welcome.decide.description") %></p>
|
<p><%= t("welcome.decide.description") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-12 medium-3 column">
|
<div class="small-12 medium-3 column">
|
||||||
<%= image_tag("icon_home_do.png", size: "168x168", alt: t("welcome.do.alt"), title: t("welcome.do.title")) %>
|
<%= image_tag("icon_home_do.png", size: "168x168", alt: "", title: t("welcome.do.title")) %>
|
||||||
<h2><%= t("welcome.do.title") %></h2>
|
<h2><%= t("welcome.do.title") %></h2>
|
||||||
<p><%= t("welcome.do.description") %></p>
|
<p><%= t("welcome.do.description") %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
require File.expand_path('../boot', __FILE__)
|
require File.expand_path('../boot', __FILE__)
|
||||||
|
|
||||||
require 'rails/all'
|
require 'rails/all'
|
||||||
@@ -34,5 +35,17 @@ module Consul
|
|||||||
config.autoload_paths << Rails.root.join('lib')
|
config.autoload_paths << Rails.root.join('lib')
|
||||||
config.time_zone = 'Madrid'
|
config.time_zone = 'Madrid'
|
||||||
config.active_job.queue_adapter = :delayed_job
|
config.active_job.queue_adapter = :delayed_job
|
||||||
|
|
||||||
|
# Consul specific custom overrides
|
||||||
|
# Read more on documentation:
|
||||||
|
# * English: https://github.com/consul/consul/blob/master/CUSTOMIZE_EN.md
|
||||||
|
# * Spanish: https://github.com/consul/consul/blob/master/CUSTOMIZE_ES.md
|
||||||
|
#
|
||||||
|
config.autoload_paths << "#{Rails.root}/app/controllers/custom"
|
||||||
|
config.autoload_paths << "#{Rails.root}/app/models/custom"
|
||||||
|
config.paths['app/views'].unshift(Rails.root.join('app', 'views', 'custom'))
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require "./config/application_custom.rb"
|
||||||
|
|||||||
4
config/application_custom.rb
Normal file
4
config/application_custom.rb
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module Consul
|
||||||
|
class Application < Rails::Application
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -112,6 +112,7 @@ ignore_unused:
|
|||||||
- 'admin.banners.index.filters.*'
|
- 'admin.banners.index.filters.*'
|
||||||
- 'admin.debates.index.filter*'
|
- 'admin.debates.index.filter*'
|
||||||
- 'admin.proposals.index.filter*'
|
- 'admin.proposals.index.filter*'
|
||||||
|
- 'admin.budgets.index.filter*'
|
||||||
- 'admin.spending_proposals.index.filter*'
|
- 'admin.spending_proposals.index.filter*'
|
||||||
- 'admin.organizations.index.filter*'
|
- 'admin.organizations.index.filter*'
|
||||||
- 'admin.users.index.filter*'
|
- 'admin.users.index.filter*'
|
||||||
|
|||||||
@@ -9,8 +9,12 @@ Rails.application.config.assets.version = '1.0'
|
|||||||
# Precompile additional assets.
|
# Precompile additional assets.
|
||||||
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
|
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
|
||||||
# Rails.application.config.assets.precompile += %w( search.js )
|
# Rails.application.config.assets.precompile += %w( search.js )
|
||||||
Rails.application.config.assets.precompile += %w( ckeditor/* )
|
Rails.application.config.assets.precompile += %w( ckeditor/config.js )
|
||||||
Rails.application.config.assets.precompile += %w( ie_lt9.js )
|
Rails.application.config.assets.precompile += %w( ie_lt9.js )
|
||||||
Rails.application.config.assets.precompile += %w( stat_graphs.js )
|
Rails.application.config.assets.precompile += %w( stat_graphs.js )
|
||||||
Rails.application.config.assets.precompile += %w( print.css )
|
Rails.application.config.assets.precompile += %w( print.css )
|
||||||
Rails.application.config.assets.precompile += %w( ie.css )
|
Rails.application.config.assets.precompile += %w( ie.css )
|
||||||
|
|
||||||
|
# Loads app/assets/images/custom before app/assets/images
|
||||||
|
images_path = Rails.application.config.assets.paths
|
||||||
|
images_path = images_path.insert(0, Rails.root.join("app", "assets", "images", "custom").to_s)
|
||||||
|
|||||||
4
config/initializers/ckeditor.rb
Normal file
4
config/initializers/ckeditor.rb
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
Ckeditor.setup do |config|
|
||||||
|
config.assets_languages = I18n.available_locales.map(&:to_s)
|
||||||
|
config.assets_plugins = []
|
||||||
|
end
|
||||||
@@ -4,6 +4,9 @@ en:
|
|||||||
activity:
|
activity:
|
||||||
one: "activity"
|
one: "activity"
|
||||||
other: "activities"
|
other: "activities"
|
||||||
|
budget:
|
||||||
|
one: "Participatory budget"
|
||||||
|
other: "Participatory budgets"
|
||||||
comment:
|
comment:
|
||||||
one: "Comment"
|
one: "Comment"
|
||||||
other: "Comments"
|
other: "Comments"
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ es:
|
|||||||
activity:
|
activity:
|
||||||
one: "actividad"
|
one: "actividad"
|
||||||
other: "actividades"
|
other: "actividades"
|
||||||
|
budget:
|
||||||
|
one: "Presupuesto participativo"
|
||||||
|
other: "Presupuestos participativos"
|
||||||
comment:
|
comment:
|
||||||
one: "Comentario"
|
one: "Comentario"
|
||||||
other: "Comentarios"
|
other: "Comentarios"
|
||||||
|
|||||||
@@ -32,8 +32,6 @@ en:
|
|||||||
editing: Edit banner
|
editing: Edit banner
|
||||||
form:
|
form:
|
||||||
submit_button: Save changes
|
submit_button: Save changes
|
||||||
errors:
|
|
||||||
form:
|
|
||||||
errors:
|
errors:
|
||||||
form:
|
form:
|
||||||
error:
|
error:
|
||||||
@@ -60,6 +58,40 @@ en:
|
|||||||
on_users: Users
|
on_users: Users
|
||||||
title: Moderator activity
|
title: Moderator activity
|
||||||
type: Type
|
type: Type
|
||||||
|
budgets:
|
||||||
|
index:
|
||||||
|
title: Participatory budgets
|
||||||
|
new_link: Create new
|
||||||
|
filters:
|
||||||
|
open: Open
|
||||||
|
finished: Finished
|
||||||
|
create:
|
||||||
|
notice: New participatory budget created successfully!
|
||||||
|
new:
|
||||||
|
title: New participatory budget
|
||||||
|
create: Create budget
|
||||||
|
name: Budget's name
|
||||||
|
description: Description
|
||||||
|
phase: Phase
|
||||||
|
currency: Currency
|
||||||
|
show:
|
||||||
|
phase: Current phase
|
||||||
|
currency: Currency
|
||||||
|
groups: Groups of budget headings
|
||||||
|
form:
|
||||||
|
group: Group's name
|
||||||
|
no_groups: No groups created yet. Each user will be able to vote in only one heading per group.
|
||||||
|
add_group: Add new group
|
||||||
|
create_group: Create group
|
||||||
|
heading: Heading's name
|
||||||
|
add_heading: Add heading
|
||||||
|
amount: Amount
|
||||||
|
save_heading: Save heading
|
||||||
|
no_heading: This group has no assigned heading.
|
||||||
|
geozone: Scope of operation
|
||||||
|
table_heading: Heading
|
||||||
|
table_amount: Amount
|
||||||
|
table_geozone: Scope of operation
|
||||||
comments:
|
comments:
|
||||||
index:
|
index:
|
||||||
filter: Filter
|
filter: Filter
|
||||||
@@ -96,6 +128,7 @@ en:
|
|||||||
activity: Moderator activity
|
activity: Moderator activity
|
||||||
admin: Admin menu
|
admin: Admin menu
|
||||||
banner: Manage banners
|
banner: Manage banners
|
||||||
|
budgets: Participatory budgets
|
||||||
debate_topics: Debate topics
|
debate_topics: Debate topics
|
||||||
hidden_comments: Hidden comments
|
hidden_comments: Hidden comments
|
||||||
hidden_debates: Hidden debates
|
hidden_debates: Hidden debates
|
||||||
|
|||||||
@@ -58,6 +58,40 @@ es:
|
|||||||
on_users: Usuarios
|
on_users: Usuarios
|
||||||
title: Actividad de los Moderadores
|
title: Actividad de los Moderadores
|
||||||
type: Tipo
|
type: Tipo
|
||||||
|
budgets:
|
||||||
|
index:
|
||||||
|
title: Presupuestos participativos
|
||||||
|
new_link: Crear nuevo
|
||||||
|
filters:
|
||||||
|
open: Abiertos
|
||||||
|
finished: Terminados
|
||||||
|
create:
|
||||||
|
notice: ¡Nueva campaña de presupuestos participativos creada con éxito!
|
||||||
|
new:
|
||||||
|
title: Nuevo presupuesto ciudadano
|
||||||
|
create: Crear presupuesto
|
||||||
|
name: Nombre del presupuesto
|
||||||
|
description: Descripción
|
||||||
|
phase: Fase
|
||||||
|
currency: Divisa
|
||||||
|
show:
|
||||||
|
phase: Fase actual
|
||||||
|
currency: Divisa
|
||||||
|
groups: Grupos de partidas presupuestarias
|
||||||
|
form:
|
||||||
|
group: Nombre del grupo
|
||||||
|
no_groups: No hay grupos creados todavía. Cada usuario podrá votar en una sola partida de cada grupo.
|
||||||
|
add_group: Añadir nuevo grupo
|
||||||
|
create_group: Crear grupo
|
||||||
|
heading: Nombre de la partida
|
||||||
|
add_heading: Añadir partida
|
||||||
|
amount: Cantidad
|
||||||
|
save_heading: Guardar partida
|
||||||
|
no_heading: Este grupo no tiene ninguna partida asignada.
|
||||||
|
geozone: Ámbito de actuación
|
||||||
|
table_heading: Partida
|
||||||
|
table_amount: Cantidad
|
||||||
|
table_geozone: Ámbito de actuación
|
||||||
comments:
|
comments:
|
||||||
index:
|
index:
|
||||||
filter: Filtro
|
filter: Filtro
|
||||||
@@ -94,6 +128,7 @@ es:
|
|||||||
activity: Actividad de moderadores
|
activity: Actividad de moderadores
|
||||||
admin: Menú de administración
|
admin: Menú de administración
|
||||||
banner: Gestionar banners
|
banner: Gestionar banners
|
||||||
|
budgets: Presupuestos participativos
|
||||||
debate_topics: Temas de debate
|
debate_topics: Temas de debate
|
||||||
hidden_comments: Comentarios ocultos
|
hidden_comments: Comentarios ocultos
|
||||||
hidden_debates: Debates ocultos
|
hidden_debates: Debates ocultos
|
||||||
|
|||||||
0
config/locales/custom/.keep
Normal file
0
config/locales/custom/.keep
Normal file
@@ -33,6 +33,13 @@ en:
|
|||||||
application:
|
application:
|
||||||
close: Close
|
close: Close
|
||||||
menu: Menu
|
menu: Menu
|
||||||
|
budget:
|
||||||
|
phase:
|
||||||
|
on_hold: On hold
|
||||||
|
accepting: Accepting proposals
|
||||||
|
selecting: Selecting
|
||||||
|
balloting: Balloting
|
||||||
|
finished: Finished
|
||||||
comments:
|
comments:
|
||||||
comment:
|
comment:
|
||||||
admin: Administrator
|
admin: Administrator
|
||||||
@@ -198,6 +205,7 @@ en:
|
|||||||
more_information: More information
|
more_information: More information
|
||||||
my_account_link: My account
|
my_account_link: My account
|
||||||
my_activity_link: My activity
|
my_activity_link: My activity
|
||||||
|
notifications: Notifications
|
||||||
new_notifications:
|
new_notifications:
|
||||||
one: You have a new notification
|
one: You have a new notification
|
||||||
other: You have %{count} new notifications
|
other: You have %{count} new notifications
|
||||||
@@ -413,6 +421,7 @@ en:
|
|||||||
flag: Flag as inappropriate
|
flag: Flag as inappropriate
|
||||||
print:
|
print:
|
||||||
print_button: Print this info
|
print_button: Print this info
|
||||||
|
show: Show
|
||||||
suggest:
|
suggest:
|
||||||
debate:
|
debate:
|
||||||
found:
|
found:
|
||||||
@@ -653,19 +662,15 @@ en:
|
|||||||
not_voting_allowed: Voting phase is closed
|
not_voting_allowed: Voting phase is closed
|
||||||
welcome:
|
welcome:
|
||||||
debates:
|
debates:
|
||||||
alt: Icon debates
|
|
||||||
description: For meeting, discussing and sharing the things that matter to us in our city.
|
description: For meeting, discussing and sharing the things that matter to us in our city.
|
||||||
title: Debates
|
title: Debates
|
||||||
decide:
|
decide:
|
||||||
alt: Icon decide
|
|
||||||
description: The public decides if it accepts or rejects the most supported proposals.
|
description: The public decides if it accepts or rejects the most supported proposals.
|
||||||
title: You decide
|
title: You decide
|
||||||
do:
|
do:
|
||||||
alt: Icon it gets done
|
|
||||||
description: If the proposal is accepted by the majority, the City Council accepts it as its own and it gets done.
|
description: If the proposal is accepted by the majority, the City Council accepts it as its own and it gets done.
|
||||||
title: It gets done
|
title: It gets done
|
||||||
proposal:
|
proposal:
|
||||||
alt: Icon propose
|
|
||||||
description: Open space for citizen proposals about the kind of city we want to live in.
|
description: Open space for citizen proposals about the kind of city we want to live in.
|
||||||
title: You propose
|
title: You propose
|
||||||
verification:
|
verification:
|
||||||
|
|||||||
@@ -33,6 +33,13 @@ es:
|
|||||||
application:
|
application:
|
||||||
close: Cerrar
|
close: Cerrar
|
||||||
menu: Menú
|
menu: Menú
|
||||||
|
budget:
|
||||||
|
phase:
|
||||||
|
on_hold: En pausa
|
||||||
|
accepting: Aceptando propuestas
|
||||||
|
selecting: Fase de selección
|
||||||
|
balloting: Fase de Votación
|
||||||
|
finished: Terminado
|
||||||
comments:
|
comments:
|
||||||
comment:
|
comment:
|
||||||
admin: Administrador
|
admin: Administrador
|
||||||
@@ -198,6 +205,7 @@ es:
|
|||||||
more_information: Más información
|
more_information: Más información
|
||||||
my_account_link: Mi cuenta
|
my_account_link: Mi cuenta
|
||||||
my_activity_link: Mi actividad
|
my_activity_link: Mi actividad
|
||||||
|
notifications: Notificaciones
|
||||||
new_notifications:
|
new_notifications:
|
||||||
one: Tienes una nueva notificación
|
one: Tienes una nueva notificación
|
||||||
other: Tienes %{count} notificaciones nuevas
|
other: Tienes %{count} notificaciones nuevas
|
||||||
@@ -413,6 +421,7 @@ es:
|
|||||||
flag: Denunciar como inapropiado
|
flag: Denunciar como inapropiado
|
||||||
print:
|
print:
|
||||||
print_button: Imprimir esta información
|
print_button: Imprimir esta información
|
||||||
|
show: Mostrar
|
||||||
suggest:
|
suggest:
|
||||||
debate:
|
debate:
|
||||||
found:
|
found:
|
||||||
@@ -653,19 +662,15 @@ es:
|
|||||||
not_voting_allowed: El periodo de votación está cerrado.
|
not_voting_allowed: El periodo de votación está cerrado.
|
||||||
welcome:
|
welcome:
|
||||||
debates:
|
debates:
|
||||||
alt: Icono debates
|
|
||||||
description: Encontrarnos, debatir y compartir lo que nos parece importante en nuestra ciudad.
|
description: Encontrarnos, debatir y compartir lo que nos parece importante en nuestra ciudad.
|
||||||
title: Debates
|
title: Debates
|
||||||
decide:
|
decide:
|
||||||
alt: Icono decides
|
|
||||||
description: La ciudadanía decide si acepta o rechaza las propuestas más apoyadas.
|
description: La ciudadanía decide si acepta o rechaza las propuestas más apoyadas.
|
||||||
title: Decides
|
title: Decides
|
||||||
do:
|
do:
|
||||||
alt: Icono se hace
|
|
||||||
description: Si la propuesta es aceptada mayoritariamente, el Ayuntamiento la asume como propia y se hace.
|
description: Si la propuesta es aceptada mayoritariamente, el Ayuntamiento la asume como propia y se hace.
|
||||||
title: Se hace
|
title: Se hace
|
||||||
proposal:
|
proposal:
|
||||||
alt: Icono propones
|
|
||||||
description: Espacio abierto para propuestas ciudadanas sobre el tipo de ciudad en el que queremos vivir.
|
description: Espacio abierto para propuestas ciudadanas sobre el tipo de ciudad en el que queremos vivir.
|
||||||
title: Propones
|
title: Propones
|
||||||
verification:
|
verification:
|
||||||
|
|||||||
@@ -156,6 +156,13 @@ Rails.application.routes.draw do
|
|||||||
get :summary, on: :collection
|
get :summary, on: :collection
|
||||||
end
|
end
|
||||||
|
|
||||||
|
resources :budgets do
|
||||||
|
resources :budget_groups do
|
||||||
|
resources :budget_headings do
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do
|
resources :banners, only: [:index, :new, :create, :edit, :update, :destroy] do
|
||||||
collection { get :search}
|
collection { get :search}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class AddEmailedAtToNotifications < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :notifications, :emailed_at, :datetime
|
||||||
|
end
|
||||||
|
end
|
||||||
13
db/schema.rb
13
db/schema.rb
@@ -11,7 +11,7 @@
|
|||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20160617172616) do
|
ActiveRecord::Schema.define(version: 20160803154011) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
@@ -210,10 +210,10 @@ ActiveRecord::Schema.define(version: 20160617172616) do
|
|||||||
t.string "visit_id"
|
t.string "visit_id"
|
||||||
t.datetime "hidden_at"
|
t.datetime "hidden_at"
|
||||||
t.integer "flags_count", default: 0
|
t.integer "flags_count", default: 0
|
||||||
t.datetime "ignored_flag_at"
|
|
||||||
t.integer "cached_votes_total", default: 0
|
t.integer "cached_votes_total", default: 0
|
||||||
t.integer "cached_votes_up", default: 0
|
t.integer "cached_votes_up", default: 0
|
||||||
t.integer "cached_votes_down", default: 0
|
t.integer "cached_votes_down", default: 0
|
||||||
|
t.datetime "ignored_flag_at"
|
||||||
t.integer "comments_count", default: 0
|
t.integer "comments_count", default: 0
|
||||||
t.datetime "confirmed_hide_at"
|
t.datetime "confirmed_hide_at"
|
||||||
t.integer "cached_anonymous_votes_total", default: 0
|
t.integer "cached_anonymous_votes_total", default: 0
|
||||||
@@ -337,10 +337,11 @@ ActiveRecord::Schema.define(version: 20160617172616) do
|
|||||||
add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree
|
add_index "moderators", ["user_id"], name: "index_moderators_on_user_id", using: :btree
|
||||||
|
|
||||||
create_table "notifications", force: :cascade do |t|
|
create_table "notifications", force: :cascade do |t|
|
||||||
t.integer "user_id"
|
t.integer "user_id"
|
||||||
t.integer "notifiable_id"
|
t.integer "notifiable_id"
|
||||||
t.string "notifiable_type"
|
t.string "notifiable_type"
|
||||||
t.integer "counter", default: 1
|
t.integer "counter", default: 1
|
||||||
|
t.datetime "emailed_at"
|
||||||
end
|
end
|
||||||
|
|
||||||
add_index "notifications", ["user_id"], name: "index_notifications_on_user_id", using: :btree
|
add_index "notifications", ["user_id"], name: "index_notifications_on_user_id", using: :btree
|
||||||
|
|||||||
@@ -1,14 +1,27 @@
|
|||||||
class EmailDigest
|
class EmailDigest
|
||||||
|
|
||||||
def initialize
|
attr_accessor :user, :notifications
|
||||||
|
|
||||||
|
def initialize(user)
|
||||||
|
@user = user
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def notifications
|
||||||
User.email_digest.each do |user|
|
user.notifications.not_emailed.where(notifiable_type: "ProposalNotification")
|
||||||
if user.notifications.where(notifiable_type: "ProposalNotification").any?
|
end
|
||||||
Mailer.proposal_notification_digest(user).deliver_later
|
|
||||||
end
|
def pending_notifications?
|
||||||
|
notifications.any?
|
||||||
|
end
|
||||||
|
|
||||||
|
def deliver
|
||||||
|
if pending_notifications?
|
||||||
|
Mailer.proposal_notification_digest(user, notifications.to_a).deliver_later
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mark_as_emailed
|
||||||
|
notifications.update_all(emailed_at: Time.now)
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
@@ -2,8 +2,11 @@ namespace :emails do
|
|||||||
|
|
||||||
desc "Sends email digest of proposal notifications to each user"
|
desc "Sends email digest of proposal notifications to each user"
|
||||||
task digest: :environment do
|
task digest: :environment do
|
||||||
email_digest = EmailDigest.new
|
User.email_digest.find_each do |user|
|
||||||
email_digest.create
|
email_digest = EmailDigest.new(user)
|
||||||
|
email_digest.deliver
|
||||||
|
email_digest.mark_as_emailed
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -193,6 +193,10 @@ FactoryGirl.define do
|
|||||||
currency_symbol "€"
|
currency_symbol "€"
|
||||||
phase 'on_hold'
|
phase 'on_hold'
|
||||||
|
|
||||||
|
trait :accepting do
|
||||||
|
phase 'accepting'
|
||||||
|
end
|
||||||
|
|
||||||
trait :selecting do
|
trait :selecting do
|
||||||
phase 'selecting'
|
phase 'selecting'
|
||||||
end
|
end
|
||||||
|
|||||||
158
spec/features/admin/budgets_spec.rb
Normal file
158
spec/features/admin/budgets_spec.rb
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
feature 'Admin budgets' do
|
||||||
|
|
||||||
|
background do
|
||||||
|
admin = create(:administrator)
|
||||||
|
login_as(admin.user)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Feature flag' do
|
||||||
|
|
||||||
|
xscenario 'Disabled with a feature flag' do
|
||||||
|
Setting['feature.budgets'] = nil
|
||||||
|
expect{ visit admin_budgets_path }.to raise_exception(FeatureFlags::FeatureDisabled)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Index' do
|
||||||
|
|
||||||
|
scenario 'Displaying budgets' do
|
||||||
|
budget = create(:budget)
|
||||||
|
visit admin_budgets_path
|
||||||
|
|
||||||
|
expect(page).to have_content(budget.name)
|
||||||
|
expect(page).to have_content(I18n.t("budget.phase.#{budget.phase}"))
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Filters by phase' do
|
||||||
|
budget1 = create(:budget)
|
||||||
|
budget2 = create(:budget, :accepting)
|
||||||
|
budget3 = create(:budget, :selecting)
|
||||||
|
budget4 = create(:budget, :balloting)
|
||||||
|
budget5 = create(:budget, :finished)
|
||||||
|
|
||||||
|
visit admin_budgets_path
|
||||||
|
expect(page).to have_content(budget1.name)
|
||||||
|
expect(page).to have_content(budget2.name)
|
||||||
|
expect(page).to have_content(budget3.name)
|
||||||
|
expect(page).to have_content(budget4.name)
|
||||||
|
expect(page).to_not have_content(budget5.name)
|
||||||
|
|
||||||
|
click_link 'Finished'
|
||||||
|
expect(page).to_not have_content(budget1.name)
|
||||||
|
expect(page).to_not have_content(budget2.name)
|
||||||
|
expect(page).to_not have_content(budget3.name)
|
||||||
|
expect(page).to_not have_content(budget4.name)
|
||||||
|
expect(page).to have_content(budget5.name)
|
||||||
|
|
||||||
|
click_link 'Open'
|
||||||
|
expect(page).to have_content(budget1.name)
|
||||||
|
expect(page).to have_content(budget2.name)
|
||||||
|
expect(page).to have_content(budget3.name)
|
||||||
|
expect(page).to have_content(budget4.name)
|
||||||
|
expect(page).to_not have_content(budget5.name)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Current filter is properly highlighted' do
|
||||||
|
filters_links = {'open' => 'Open', 'finished' => 'Finished'}
|
||||||
|
|
||||||
|
visit admin_budgets_path
|
||||||
|
|
||||||
|
expect(page).to_not have_link(filters_links.values.first)
|
||||||
|
filters_links.keys.drop(1).each { |filter| expect(page).to have_link(filters_links[filter]) }
|
||||||
|
|
||||||
|
filters_links.each_pair do |current_filter, link|
|
||||||
|
visit admin_budgets_path(filter: current_filter)
|
||||||
|
|
||||||
|
expect(page).to_not have_link(link)
|
||||||
|
|
||||||
|
(filters_links.keys - [current_filter]).each do |filter|
|
||||||
|
expect(page).to have_link(filters_links[filter])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'New' do
|
||||||
|
|
||||||
|
scenario 'Create budget' do
|
||||||
|
visit admin_budgets_path
|
||||||
|
click_link 'Create new'
|
||||||
|
|
||||||
|
fill_in 'budget_name', with: 'M30 - Summer campaign'
|
||||||
|
fill_in 'budget_description', with: 'Budgeting for summer 2017 maintenance and improvements of the road M-30'
|
||||||
|
select 'Accepting proposals', from: 'budget[phase]'
|
||||||
|
|
||||||
|
click_button 'Create budget'
|
||||||
|
|
||||||
|
expect(page).to have_content 'New participatory budget created successfully!'
|
||||||
|
expect(page).to have_content 'M30 - Summer campaign'
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Name is mandatory' do
|
||||||
|
visit new_admin_budget_path
|
||||||
|
click_button 'Create budget'
|
||||||
|
|
||||||
|
expect(page).to_not have_content 'New participatory budget created successfully!'
|
||||||
|
expect(page).to have_css("label.error", text: "Budget's name")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'Manage groups and headings' do
|
||||||
|
|
||||||
|
scenario 'Create group', :js do
|
||||||
|
create(:budget, name: 'Yearly participatory budget')
|
||||||
|
|
||||||
|
visit admin_budgets_path
|
||||||
|
click_link 'Yearly participatory budget'
|
||||||
|
|
||||||
|
expect(page).to have_content 'No groups created yet.'
|
||||||
|
|
||||||
|
click_link 'Add new group'
|
||||||
|
|
||||||
|
fill_in 'budget_group_name', with: 'General improvments'
|
||||||
|
click_button 'Create group'
|
||||||
|
|
||||||
|
expect(page).to have_content 'Yearly participatory budget'
|
||||||
|
expect(page).to_not have_content 'No groups created yet.'
|
||||||
|
|
||||||
|
visit admin_budgets_path
|
||||||
|
click_link 'Yearly participatory budget'
|
||||||
|
|
||||||
|
expect(page).to have_content 'Yearly participatory budget'
|
||||||
|
expect(page).to_not have_content 'No groups created yet.'
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario 'Create heading', :js do
|
||||||
|
budget = create(:budget, name: 'Yearly participatory budget')
|
||||||
|
group = create(:budget_group, budget: budget, name: 'Districts improvments')
|
||||||
|
|
||||||
|
visit admin_budget_path(budget)
|
||||||
|
|
||||||
|
within("#budget_group_#{group.id}") do
|
||||||
|
expect(page).to have_content 'This group has no assigned heading.'
|
||||||
|
click_link 'Add heading'
|
||||||
|
|
||||||
|
fill_in 'budget_heading_name', with: 'District 9 reconstruction'
|
||||||
|
fill_in 'budget_heading_price', with: '6785'
|
||||||
|
click_button 'Save heading'
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(page).to_not have_content 'This group has no assigned heading.'
|
||||||
|
|
||||||
|
visit admin_budget_path(budget)
|
||||||
|
within("#budget_group_#{group.id}") do
|
||||||
|
expect(page).to_not have_content 'This group has no assigned heading.'
|
||||||
|
|
||||||
|
expect(page).to have_content 'District 9 reconstruction'
|
||||||
|
expect(page).to have_content '6785'
|
||||||
|
expect(page).to have_content 'All city'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -201,8 +201,9 @@ feature 'Emails' do
|
|||||||
notification2 = create_proposal_notification(proposal2)
|
notification2 = create_proposal_notification(proposal2)
|
||||||
notification3 = create_proposal_notification(proposal3)
|
notification3 = create_proposal_notification(proposal3)
|
||||||
|
|
||||||
email_digest = EmailDigest.new
|
email_digest = EmailDigest.new(user)
|
||||||
email_digest.create
|
email_digest.deliver
|
||||||
|
email_digest.mark_as_emailed
|
||||||
|
|
||||||
email = open_last_email
|
email = open_last_email
|
||||||
expect(email).to have_subject("Proposal notifications in Consul")
|
expect(email).to have_subject("Proposal notifications in Consul")
|
||||||
@@ -227,6 +228,11 @@ feature 'Emails' do
|
|||||||
|
|
||||||
expect(email).to_not have_body_text(proposal3.title)
|
expect(email).to_not have_body_text(proposal3.title)
|
||||||
expect(email).to have_body_text(/#{account_path}/)
|
expect(email).to have_body_text(/#{account_path}/)
|
||||||
|
|
||||||
|
notification1.reload
|
||||||
|
notification2.reload
|
||||||
|
expect(notification1.emailed_at).to be
|
||||||
|
expect(notification2.emailed_at).to be
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -180,9 +180,10 @@ feature "Notifications" do
|
|||||||
|
|
||||||
find(".icon-notification").click
|
find(".icon-notification").click
|
||||||
|
|
||||||
|
notification_for_user1 = Notification.where(user: user1).first
|
||||||
expect(page).to have_css ".notification", count: 1
|
expect(page).to have_css ".notification", count: 1
|
||||||
expect(page).to have_content "There is one new notification on #{proposal.title}"
|
expect(page).to have_content "There is one new notification on #{proposal.title}"
|
||||||
expect(page).to have_xpath "//a[@href='#{notification_path(Notification.last)}']"
|
expect(page).to have_xpath "//a[@href='#{notification_path(notification_for_user1)}']"
|
||||||
|
|
||||||
logout
|
logout
|
||||||
login_as user2
|
login_as user2
|
||||||
@@ -190,9 +191,10 @@ feature "Notifications" do
|
|||||||
|
|
||||||
find(".icon-notification").click
|
find(".icon-notification").click
|
||||||
|
|
||||||
|
notification_for_user2 = Notification.where(user: user2).first
|
||||||
expect(page).to have_css ".notification", count: 1
|
expect(page).to have_css ".notification", count: 1
|
||||||
expect(page).to have_content "There is one new notification on #{proposal.title}"
|
expect(page).to have_content "There is one new notification on #{proposal.title}"
|
||||||
expect(page).to have_xpath "//a[@href='#{notification_path(Notification.first)}']"
|
expect(page).to have_xpath "//a[@href='#{notification_path(notification_for_user2)}']"
|
||||||
|
|
||||||
logout
|
logout
|
||||||
login_as user3
|
login_as user3
|
||||||
|
|||||||
@@ -24,6 +24,44 @@ feature 'Proposal Notifications' do
|
|||||||
expect(page).to have_content "Please share it with others so we can make it happen!"
|
expect(page).to have_content "Please share it with others so we can make it happen!"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
scenario "Send a notification (Active voter)" do
|
||||||
|
author = create(:user)
|
||||||
|
proposal = create(:proposal, author: author)
|
||||||
|
|
||||||
|
voter = create(:user, :level_two)
|
||||||
|
create(:vote, voter: voter, votable: proposal)
|
||||||
|
|
||||||
|
create_proposal_notification(proposal)
|
||||||
|
|
||||||
|
expect(Notification.count).to eq(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Send a notification (Blocked voter)" do
|
||||||
|
author = create(:user)
|
||||||
|
proposal = create(:proposal, author: author)
|
||||||
|
|
||||||
|
voter = create(:user, :level_two)
|
||||||
|
create(:vote, voter: voter, votable: proposal)
|
||||||
|
voter.block
|
||||||
|
|
||||||
|
create_proposal_notification(proposal)
|
||||||
|
|
||||||
|
expect(Notification.count).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
scenario "Send a notification (Erased voter)" do
|
||||||
|
author = create(:user)
|
||||||
|
proposal = create(:proposal, author: author)
|
||||||
|
|
||||||
|
voter = create(:user, :level_two)
|
||||||
|
create(:vote, voter: voter, votable: proposal)
|
||||||
|
voter.erase
|
||||||
|
|
||||||
|
create_proposal_notification(proposal)
|
||||||
|
|
||||||
|
expect(Notification.count).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
scenario "Show notifications" do
|
scenario "Show notifications" do
|
||||||
proposal = create(:proposal)
|
proposal = create(:proposal)
|
||||||
notification1 = create(:proposal_notification, proposal: proposal, title: "Hey guys", body: "Just wanted to let you know that...")
|
notification1 = create(:proposal_notification, proposal: proposal, title: "Hey guys", body: "Just wanted to let you know that...")
|
||||||
|
|||||||
@@ -31,4 +31,19 @@ describe GeozonesHelper do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#geozone_name_from_id" do
|
||||||
|
|
||||||
|
it "returns geozone name if present" do
|
||||||
|
g1 = create(:geozone, name: "AAA")
|
||||||
|
g2 = create(:geozone, name: "BBB")
|
||||||
|
|
||||||
|
expect(geozone_name_from_id(g1.id)).to eq "AAA"
|
||||||
|
expect(geozone_name_from_id(g2.id)).to eq "BBB"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns default string for no geozone if geozone is blank" do
|
||||||
|
expect(geozone_name_from_id(nil)).to eq "All city"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,8 +2,126 @@ require 'rails_helper'
|
|||||||
|
|
||||||
describe EmailDigest do
|
describe EmailDigest do
|
||||||
|
|
||||||
describe "create" do
|
describe "notifications" do
|
||||||
pending "only send unread notifications"
|
|
||||||
|
it "returns notifications for a user" do
|
||||||
|
user1 = create(:user)
|
||||||
|
user2 = create(:user)
|
||||||
|
|
||||||
|
proposal_notification = create(:proposal_notification)
|
||||||
|
notification1 = create(:notification, notifiable: proposal_notification, user: user1)
|
||||||
|
notification2 = create(:notification, notifiable: proposal_notification, user: user2)
|
||||||
|
|
||||||
|
email_digest = EmailDigest.new(user1)
|
||||||
|
|
||||||
|
expect(email_digest.notifications).to include(notification1)
|
||||||
|
expect(email_digest.notifications).to_not include(notification2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns only proposal notifications" do
|
||||||
|
user = create(:user)
|
||||||
|
|
||||||
|
proposal_notification = create(:proposal_notification)
|
||||||
|
comment = create(:comment)
|
||||||
|
|
||||||
|
notification1 = create(:notification, notifiable: proposal_notification, user: user)
|
||||||
|
notification2 = create(:notification, notifiable: comment, user: user)
|
||||||
|
|
||||||
|
email_digest = EmailDigest.new(user)
|
||||||
|
|
||||||
|
expect(email_digest.notifications).to include(notification1)
|
||||||
|
expect(email_digest.notifications).to_not include(notification2)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "pending_notifications?" do
|
||||||
|
|
||||||
|
it "returns true when notifications have not been emailed" do
|
||||||
|
user = create(:user)
|
||||||
|
|
||||||
|
proposal_notification = create(:proposal_notification)
|
||||||
|
notification = create(:notification, notifiable: proposal_notification, user: user)
|
||||||
|
|
||||||
|
email_digest = EmailDigest.new(user)
|
||||||
|
expect(email_digest.pending_notifications?).to be
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false when notifications have been emailed" do
|
||||||
|
user = create(:user)
|
||||||
|
|
||||||
|
proposal_notification = create(:proposal_notification)
|
||||||
|
notification = create(:notification, notifiable: proposal_notification, user: user, emailed_at: Time.now)
|
||||||
|
|
||||||
|
email_digest = EmailDigest.new(user)
|
||||||
|
expect(email_digest.pending_notifications?).to_not be
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns false when there are no notifications for a user" do
|
||||||
|
user = create(:user)
|
||||||
|
email_digest = EmailDigest.new(user)
|
||||||
|
expect(email_digest.pending_notifications?).to_not be
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "deliver" do
|
||||||
|
|
||||||
|
it "delivers email if notifications pending" do
|
||||||
|
user = create(:user)
|
||||||
|
|
||||||
|
proposal_notification = create(:proposal_notification)
|
||||||
|
notification = create(:notification, notifiable: proposal_notification, user: user)
|
||||||
|
|
||||||
|
reset_mailer
|
||||||
|
email_digest = EmailDigest.new(user)
|
||||||
|
email_digest.deliver
|
||||||
|
|
||||||
|
email = open_last_email
|
||||||
|
expect(email).to have_subject("Proposal notifications in Consul")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not deliver email if no notifications pending" do
|
||||||
|
user = create(:user)
|
||||||
|
|
||||||
|
proposal_notification = create(:proposal_notification)
|
||||||
|
notification = create(:notification, notifiable: proposal_notification, user: user, emailed_at: Time.now)
|
||||||
|
|
||||||
|
reset_mailer
|
||||||
|
email_digest = EmailDigest.new(user)
|
||||||
|
email_digest.deliver
|
||||||
|
|
||||||
|
expect(all_emails.count).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "mark_as_emailed" do
|
||||||
|
|
||||||
|
it "marks notifications as emailed" do
|
||||||
|
user1 = create(:user)
|
||||||
|
user2 = create(:user)
|
||||||
|
|
||||||
|
proposal_notification = create(:proposal_notification)
|
||||||
|
notification1 = create(:notification, notifiable: proposal_notification, user: user1)
|
||||||
|
notification2 = create(:notification, notifiable: proposal_notification, user: user1)
|
||||||
|
notification3 = create(:notification, notifiable: proposal_notification, user: user2)
|
||||||
|
|
||||||
|
expect(notification1.emailed_at).to_not be
|
||||||
|
expect(notification2.emailed_at).to_not be
|
||||||
|
expect(notification3.emailed_at).to_not be
|
||||||
|
|
||||||
|
email_digest = EmailDigest.new(user1)
|
||||||
|
email_digest.mark_as_emailed
|
||||||
|
|
||||||
|
notification1.reload
|
||||||
|
notification2.reload
|
||||||
|
notification3.reload
|
||||||
|
expect(notification1.emailed_at).to be
|
||||||
|
expect(notification2.emailed_at).to be
|
||||||
|
expect(notification3.emailed_at).to_not be
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
0
spec/models/custom/.keep
Normal file
0
spec/models/custom/.keep
Normal file
34
spec/models/custom/residence_spec.rb
Normal file
34
spec/models/custom/residence_spec.rb
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe Verification::Residence do
|
||||||
|
|
||||||
|
let(:residence) { build(:verification_residence, document_number: "12345678Z") }
|
||||||
|
|
||||||
|
describe "verification" do
|
||||||
|
|
||||||
|
describe "postal code" do
|
||||||
|
it "should be valid with postal codes starting with 280" do
|
||||||
|
residence.postal_code = "28012"
|
||||||
|
residence.valid?
|
||||||
|
expect(residence.errors[:postal_code].size).to eq(0)
|
||||||
|
|
||||||
|
residence.postal_code = "28023"
|
||||||
|
residence.valid?
|
||||||
|
expect(residence.errors[:postal_code].size).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should not be valid with postal codes not starting with 280" do
|
||||||
|
residence.postal_code = "12345"
|
||||||
|
residence.valid?
|
||||||
|
expect(residence.errors[:postal_code].size).to eq(1)
|
||||||
|
|
||||||
|
residence.postal_code = "13280"
|
||||||
|
residence.valid?
|
||||||
|
expect(residence.errors[:postal_code].size).to eq(1)
|
||||||
|
expect(residence.errors[:postal_code]).to include("In order to be verified, you must be registered.")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
@@ -367,6 +367,50 @@ describe Proposal do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "voters" do
|
||||||
|
|
||||||
|
it "returns users that have voted for the proposal" do
|
||||||
|
proposal = create(:proposal)
|
||||||
|
voter1 = create(:user, :level_two)
|
||||||
|
voter2 = create(:user, :level_two)
|
||||||
|
voter3 = create(:user, :level_two)
|
||||||
|
|
||||||
|
create(:vote, voter: voter1, votable: proposal)
|
||||||
|
create(:vote, voter: voter2, votable: proposal)
|
||||||
|
|
||||||
|
expect(proposal.voters).to include(voter1)
|
||||||
|
expect(proposal.voters).to include(voter2)
|
||||||
|
expect(proposal.voters).to_not include(voter3)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not return users that have been erased" do
|
||||||
|
proposal = create(:proposal)
|
||||||
|
voter1 = create(:user, :level_two)
|
||||||
|
voter2 = create(:user, :level_two)
|
||||||
|
|
||||||
|
create(:vote, voter: voter1, votable: proposal)
|
||||||
|
create(:vote, voter: voter2, votable: proposal)
|
||||||
|
voter2.erase
|
||||||
|
|
||||||
|
expect(proposal.voters).to include(voter1)
|
||||||
|
expect(proposal.voters).to_not include(voter2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not return users that have been blocked" do
|
||||||
|
proposal = create(:proposal)
|
||||||
|
voter1 = create(:user, :level_two)
|
||||||
|
voter2 = create(:user, :level_two)
|
||||||
|
|
||||||
|
create(:vote, voter: voter1, votable: proposal)
|
||||||
|
create(:vote, voter: voter2, votable: proposal)
|
||||||
|
voter2.block
|
||||||
|
|
||||||
|
expect(proposal.voters).to include(voter1)
|
||||||
|
expect(proposal.voters).to_not include(voter2)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
describe "search" do
|
describe "search" do
|
||||||
|
|
||||||
context "attributes" do
|
context "attributes" do
|
||||||
|
|||||||
@@ -30,29 +30,6 @@ describe Verification::Residence do
|
|||||||
expect(residence.errors[:date_of_birth]).to include("You must be at least 16 years old")
|
expect(residence.errors[:date_of_birth]).to include("You must be at least 16 years old")
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "postal code" do
|
|
||||||
it "should be valid with postal codes starting with 280" do
|
|
||||||
residence.postal_code = "28012"
|
|
||||||
residence.valid?
|
|
||||||
expect(residence.errors[:postal_code].size).to eq(0)
|
|
||||||
|
|
||||||
residence.postal_code = "28023"
|
|
||||||
residence.valid?
|
|
||||||
expect(residence.errors[:postal_code].size).to eq(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should not be valid with postal codes not starting with 280" do
|
|
||||||
residence.postal_code = "12345"
|
|
||||||
residence.valid?
|
|
||||||
expect(residence.errors[:postal_code].size).to eq(1)
|
|
||||||
|
|
||||||
residence.postal_code = "13280"
|
|
||||||
residence.valid?
|
|
||||||
expect(residence.errors[:postal_code].size).to eq(1)
|
|
||||||
expect(residence.errors[:postal_code]).to include("In order to be verified, you must be registered.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should validate uniquness of document_number" do
|
it "should validate uniquness of document_number" do
|
||||||
user = create(:user)
|
user = create(:user)
|
||||||
residence.user = user
|
residence.user = user
|
||||||
|
|||||||
@@ -325,6 +325,34 @@ describe User do
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "scopes" do
|
||||||
|
|
||||||
|
describe "active" do
|
||||||
|
|
||||||
|
it "returns users that have not been erased" do
|
||||||
|
user1 = create(:user, erased_at: nil)
|
||||||
|
user2 = create(:user, erased_at: nil)
|
||||||
|
user3 = create(:user, erased_at: Time.now)
|
||||||
|
|
||||||
|
expect(User.active).to include(user1)
|
||||||
|
expect(User.active).to include(user2)
|
||||||
|
expect(User.active).to_not include(user3)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns users that have not been blocked" do
|
||||||
|
user1 = create(:user)
|
||||||
|
user2 = create(:user)
|
||||||
|
user3 = create(:user)
|
||||||
|
user3.block
|
||||||
|
|
||||||
|
expect(User.active).to include(user1)
|
||||||
|
expect(User.active).to include(user2)
|
||||||
|
expect(User.active).to_not include(user3)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "self.search" do
|
describe "self.search" do
|
||||||
it "find users by email" do
|
it "find users by email" do
|
||||||
user1 = create(:user, email: "larry@consul.dev")
|
user1 = create(:user, email: "larry@consul.dev")
|
||||||
|
|||||||
Reference in New Issue
Block a user