Merge branch 'recurrent'
This commit is contained in:
commit
dfc18d452d
2
Gemfile
2
Gemfile
@ -2,6 +2,8 @@ source 'https://rubygems.org'
|
|||||||
|
|
||||||
# The central piece of this application: the month calendar view
|
# The central piece of this application: the month calendar view
|
||||||
gem 'simple_calendar'
|
gem 'simple_calendar'
|
||||||
|
# The recurrence management library
|
||||||
|
gem 'ice_cube'
|
||||||
|
|
||||||
gem 'rails'
|
gem 'rails'
|
||||||
gem 'has_scope'
|
gem 'has_scope'
|
||||||
|
@ -210,6 +210,7 @@ GEM
|
|||||||
http_accept_language (2.0.5)
|
http_accept_language (2.0.5)
|
||||||
http_parser.rb (0.6.0)
|
http_parser.rb (0.6.0)
|
||||||
i18n (0.7.0)
|
i18n (0.7.0)
|
||||||
|
ice_cube (0.14.0)
|
||||||
inherited_resources (1.6.0)
|
inherited_resources (1.6.0)
|
||||||
actionpack (>= 3.2, < 5)
|
actionpack (>= 3.2, < 5)
|
||||||
has_scope (~> 0.6.0.rc)
|
has_scope (~> 0.6.0.rc)
|
||||||
@ -445,6 +446,7 @@ DEPENDENCIES
|
|||||||
has_scope
|
has_scope
|
||||||
http_accept_language
|
http_accept_language
|
||||||
i18n-active_record!
|
i18n-active_record!
|
||||||
|
ice_cube
|
||||||
jbuilder
|
jbuilder
|
||||||
jquery-rails (< 4.1)
|
jquery-rails (< 4.1)
|
||||||
jquery-sparkline-rails!
|
jquery-sparkline-rails!
|
||||||
|
@ -1,4 +1,14 @@
|
|||||||
$(document).on 'turbolinks:load', ->
|
$(document).on 'turbolinks:load', ->
|
||||||
|
# Quick mechanism so that the ice cube rule only appears when useful
|
||||||
|
if $('#event_repeat').val() == '0'
|
||||||
|
$('.field.rule').hide()
|
||||||
|
|
||||||
|
$('#event_repeat').change ->
|
||||||
|
if $(this).val() > 0
|
||||||
|
$('.field.rule').show()
|
||||||
|
else
|
||||||
|
$('.field.rule').hide()
|
||||||
|
|
||||||
# Manage event tags edition
|
# Manage event tags edition
|
||||||
$('#event_tags').each ->
|
$('#event_tags').each ->
|
||||||
elt = $(this)
|
elt = $(this)
|
||||||
|
@ -52,6 +52,10 @@
|
|||||||
content: $fa-var-toggle-on
|
content: $fa-var-toggle-on
|
||||||
.field.end_time label:before
|
.field.end_time label:before
|
||||||
content: $fa-var-toggle-off
|
content: $fa-var-toggle-off
|
||||||
|
.field.repeat label:before
|
||||||
|
content: $fa-var-repeat
|
||||||
|
.field.rule > label:before
|
||||||
|
content: $fa-var-calculator
|
||||||
.field.description label:before
|
.field.description label:before
|
||||||
content: $fa-var-pencil-square-o
|
content: $fa-var-pencil-square-o
|
||||||
.field.place_name label:before
|
.field.place_name label:before
|
||||||
|
@ -102,9 +102,9 @@ class EventsController < ApplicationController
|
|||||||
# through
|
# through
|
||||||
def event_params
|
def event_params
|
||||||
params.require(:event)
|
params.require(:event)
|
||||||
.permit :lock_version, :title, :start_time, :end_time, :description,
|
.permit :lock_version, :title, :start_time, :end_time, :repeat, :rule,
|
||||||
:place_name, :address, :city, :region_id, :locality, :url,
|
:description, :place_name, :address, :city, :region_id,
|
||||||
:contact, :submitter, :tags
|
:locality, :url, :contact, :submitter, :tags
|
||||||
end
|
end
|
||||||
|
|
||||||
def locked
|
def locked
|
||||||
|
@ -65,9 +65,9 @@ class ModerationsController < ApplicationController
|
|||||||
# through.
|
# through.
|
||||||
def moderation_params
|
def moderation_params
|
||||||
params.require(:event)
|
params.require(:event)
|
||||||
.permit :lock_version, :title, :start_time, :end_time, :description,
|
.permit :lock_version, :title, :start_time, :end_time, :repeat, :rule,
|
||||||
:place_name, :address, :city, :region_id, :locality, :url,
|
:description, :place_name, :address, :city, :region_id,
|
||||||
:contact, :submitter, :tags
|
:locality, :url, :contact, :submitter, :tags
|
||||||
end
|
end
|
||||||
|
|
||||||
# Useful to manage absolute url in mails
|
# Useful to manage absolute url in mails
|
||||||
|
@ -2,14 +2,20 @@
|
|||||||
class Event < ActiveRecord::Base
|
class Event < ActiveRecord::Base
|
||||||
extend SimpleCalendar
|
extend SimpleCalendar
|
||||||
strip_attributes
|
strip_attributes
|
||||||
has_paper_trail ignore: [:last_updated, :secret, :submitter, :decision_time,
|
has_paper_trail ignore: [:last_updated, :lock_version, :secret,
|
||||||
:lock_version, :latitude, :longitude]
|
:submitter, :decision_time,
|
||||||
|
:latitude, :longitude]
|
||||||
|
|
||||||
belongs_to :region
|
belongs_to :region
|
||||||
|
# This is the scheduled first event
|
||||||
|
belongs_to :event
|
||||||
has_many :notes, dependent: :destroy
|
has_many :notes, dependent: :destroy
|
||||||
|
has_many :events, dependent: :destroy
|
||||||
|
|
||||||
validates :title, presence: true
|
validates :title, presence: true
|
||||||
validate :end_after_start
|
validate :end_after_start
|
||||||
|
RULES = %w(daily weekly monthly).freeze
|
||||||
|
validates :rule, inclusion: RULES
|
||||||
validates :description, presence: true
|
validates :description, presence: true
|
||||||
validates :city, presence: true
|
validates :city, presence: true
|
||||||
validates :region, presence: true
|
validates :region, presence: true
|
||||||
@ -25,8 +31,14 @@ class Event < ActiveRecord::Base
|
|||||||
# Mechanism to store some reason which can be used when sending notifications
|
# Mechanism to store some reason which can be used when sending notifications
|
||||||
attr_accessor :reason
|
attr_accessor :reason
|
||||||
|
|
||||||
|
before_validation EventCallbacks
|
||||||
|
|
||||||
|
before_create EventCallbacks
|
||||||
after_create EventCallbacks
|
after_create EventCallbacks
|
||||||
|
|
||||||
|
before_update EventCallbacks
|
||||||
after_update EventCallbacks
|
after_update EventCallbacks
|
||||||
|
|
||||||
after_destroy EventCallbacks
|
after_destroy EventCallbacks
|
||||||
|
|
||||||
scope :moderated, ->(*) { where moderated: true }
|
scope :moderated, ->(*) { where moderated: true }
|
||||||
@ -55,11 +67,6 @@ class Event < ActiveRecord::Base
|
|||||||
scope :tag, ->(tag) { where 'tags like ?', "%#{tag}%" }
|
scope :tag, ->(tag) { where 'tags like ?', "%#{tag}%" }
|
||||||
scope :geo, -> { where 'latitude is not null and longitude is not null' }
|
scope :geo, -> { where 'latitude is not null and longitude is not null' }
|
||||||
|
|
||||||
before_validation do
|
|
||||||
# Tags are always downcased
|
|
||||||
self.tags = tags.mb_chars.downcase if tags
|
|
||||||
end
|
|
||||||
|
|
||||||
before_validation on: :create do
|
before_validation on: :create do
|
||||||
self.submission_time = Time.zone.now
|
self.submission_time = Time.zone.now
|
||||||
self.decision_time = Time.zone.now
|
self.decision_time = Time.zone.now
|
||||||
@ -73,22 +80,11 @@ class Event < ActiveRecord::Base
|
|||||||
self.longitude = nil if address_changed?
|
self.longitude = nil if address_changed?
|
||||||
end
|
end
|
||||||
|
|
||||||
before_create do
|
|
||||||
self.secret = SecureRandom.urlsafe_base64(32)[0...32]
|
|
||||||
self.moderator_mail_id = SecureRandom.urlsafe_base64(32)[0...32]
|
|
||||||
self.submitter_mail_id = SecureRandom.urlsafe_base64(32)[0...32]
|
|
||||||
end
|
|
||||||
|
|
||||||
before_update do
|
|
||||||
self.decision_time = Time.zone.now if moderated? && moderated_changed?
|
|
||||||
end
|
|
||||||
|
|
||||||
def as_json(_options = {})
|
def as_json(_options = {})
|
||||||
{ type: 'Feature', properties: {
|
{ type: 'Feature', properties: {
|
||||||
id: id, name: title, start_time: start_time, end_time: end_time,
|
id: id, name: title, start_time: start_time, end_time: end_time,
|
||||||
place_name: place_name, address: address, city: city, locality: locality,
|
place_name: place_name, address: address, city: city, locality: locality,
|
||||||
tags: tags,
|
tags: tags, popupContent: "<a href=\"/events/#{id}\">#{self}</a>"
|
||||||
popupContent: "<a href=\"/events/#{id}\">#{self}</a>"
|
|
||||||
}, geometry: { type: 'Point', coordinates: [longitude, latitude] } }
|
}, geometry: { type: 'Point', coordinates: [longitude, latitude] } }
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -98,6 +94,12 @@ class Event < ActiveRecord::Base
|
|||||||
[address, city].compact.join ', '
|
[address, city].compact.join ', '
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def schedule
|
||||||
|
IceCube::Schedule.new(start_time, end_time: end_time) do |s|
|
||||||
|
s.add_recurrence_rule IceCube::Rule.send(rule).count(repeat + 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def hashtags
|
def hashtags
|
||||||
tags.split.map { |tag| "##{tag.tr('-', '_').camelize :lower}" }
|
tags.split.map { |tag| "##{tag.tr('-', '_').camelize :lower}" }
|
||||||
end
|
end
|
||||||
|
@ -1,28 +1,66 @@
|
|||||||
# All the mail and tweet related callbacks to event's lifecycle
|
# All the mail and tweet related callbacks to event's lifecycle
|
||||||
|
# also the scheduled events
|
||||||
class EventCallbacks
|
class EventCallbacks
|
||||||
|
def self.before_validation(event)
|
||||||
|
# Tags are always downcased
|
||||||
|
event.tags = event.tags.mb_chars.downcase if event.tags
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.before_create(event)
|
||||||
|
event.secret = SecureRandom.urlsafe_base64(32)[0...32]
|
||||||
|
event.moderator_mail_id = SecureRandom.urlsafe_base64(32)[0...32]
|
||||||
|
event.submitter_mail_id = SecureRandom.urlsafe_base64(32)[0...32]
|
||||||
|
end
|
||||||
|
|
||||||
def self.after_create(event)
|
def self.after_create(event)
|
||||||
EventMailer.create(event).deliver_now!
|
EventMailer.create(event).deliver_now!
|
||||||
ModerationMailer.create(event).deliver_now!
|
ModerationMailer.create(event).deliver_now!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.before_update(event)
|
||||||
|
if event.moderated_changed? && event.moderated?
|
||||||
|
event.decision_time = Time.zone.now
|
||||||
|
create_repeats event if event.repeat > 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def self.after_update(event)
|
def self.after_update(event)
|
||||||
if event.moderated_changed?
|
if event.moderated_changed?
|
||||||
tweet(event)
|
tweet event
|
||||||
|
|
||||||
# Send an acceptation mail to its author
|
if ActionMailer::Base.default_url_options[:host]
|
||||||
EventMailer.accept(event).deliver_now
|
# Send an acceptation mail to its author
|
||||||
|
EventMailer.accept(event).deliver_now
|
||||||
|
|
||||||
# Send an acceptation mail to moderators
|
# Send an acceptation mail to moderators
|
||||||
ModerationMailer.accept(event).deliver_now
|
ModerationMailer.accept(event).deliver_now
|
||||||
else
|
end
|
||||||
|
|
||||||
|
elsif ActionMailer::Base.default_url_options[:host]
|
||||||
# Send an update mail to moderators
|
# Send an update mail to moderators
|
||||||
ModerationMailer.update(event).deliver_now
|
ModerationMailer.update(event).deliver_now
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.after_destroy(event)
|
def self.after_destroy(event)
|
||||||
EventMailer.destroy(event).deliver_now
|
if ActionMailer::Base.default_url_options[:host]
|
||||||
ModerationMailer.destroy(event).deliver_now
|
EventMailer.destroy(event).deliver_now
|
||||||
|
ModerationMailer.destroy(event).deliver_now
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create multiple events corresponding to a repetition
|
||||||
|
def self.create_repeats(event)
|
||||||
|
event.schedule.last(event.repeat).each do |schedule|
|
||||||
|
event.events.build create_sub_event(event, schedule)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.create_sub_event(event, schedule)
|
||||||
|
att = event.attributes.reject { |a| a == 'id' || a == 'lock_version' }
|
||||||
|
att[:start_time] = schedule.start_time
|
||||||
|
att[:end_time] = schedule.end_time
|
||||||
|
att
|
||||||
end
|
end
|
||||||
|
|
||||||
# Tweet this event, if configured using apache/system variables!
|
# Tweet this event, if configured using apache/system variables!
|
||||||
|
@ -7,6 +7,13 @@
|
|||||||
= t '.delete_link'
|
= t '.delete_link'
|
||||||
= cancel_event_url @event, secret: @event.secret
|
= cancel_event_url @event, secret: @event.secret
|
||||||
\
|
\
|
||||||
|
- if @event.repeat > 0 && !@event.event_id
|
||||||
|
= t '.repeat_helper', count: @event.repeat
|
||||||
|
- @event.events.each do |e|
|
||||||
|
= e
|
||||||
|
= edit_event_url e, secret: e.secret
|
||||||
|
= cancel_event_url e, secret: e.secret
|
||||||
|
\
|
||||||
= render file: '/events/show'
|
= render file: '/events/show'
|
||||||
\
|
\
|
||||||
= t '.signature'
|
= t '.signature'
|
||||||
|
@ -6,3 +6,5 @@
|
|||||||
= link_to event do
|
= link_to event do
|
||||||
%strong.city{ title: event.address }= event.city
|
%strong.city{ title: event.address }= event.city
|
||||||
= event.title
|
= event.title
|
||||||
|
- if event.repeat > 0
|
||||||
|
%em.fa.fa-repeat(title="#{event.repeat} - #{t event.rule, scope: 'activerecord.attributes.event.rule_values'}")
|
||||||
|
@ -12,13 +12,30 @@
|
|||||||
|
|
||||||
.field.title
|
.field.title
|
||||||
= f.label :title
|
= f.label :title
|
||||||
= f.text_field :title, required: true, placeholder: "#{t '.title_helper'}"
|
= f.text_field :title, required: true, placeholder: t('.title_helper')
|
||||||
.field.start_time
|
.field.start_time
|
||||||
= f.label :start_time
|
= f.label :start_time
|
||||||
= f.datetime_local_field :start_time, required: true
|
= f.datetime_local_field :start_time, required: true
|
||||||
.field.end_time
|
.field.end_time
|
||||||
= f.label :end_time
|
= f.label :end_time
|
||||||
= f.datetime_local_field :end_time, required: true
|
= f.datetime_local_field :end_time, required: true
|
||||||
|
|
||||||
|
- unless @event.moderated?
|
||||||
|
.field.repeat
|
||||||
|
= f.label :repeat
|
||||||
|
= f.number_field :repeat, in: 0..40, maxlength: 2, size: 2
|
||||||
|
|
||||||
|
.field.rule
|
||||||
|
.helper
|
||||||
|
:markdown
|
||||||
|
#{t '.rule_helper'}
|
||||||
|
= f.label :rule
|
||||||
|
%span.radios
|
||||||
|
- Event::RULES.each do |rule|
|
||||||
|
= f.radio_button :rule, rule
|
||||||
|
= f.label "rule_#{rule}",
|
||||||
|
t(rule, scope: 'activerecord.attributes.event.rule_values')
|
||||||
|
|
||||||
.field.description
|
.field.description
|
||||||
.helper
|
.helper
|
||||||
:markdown
|
:markdown
|
||||||
@ -46,7 +63,8 @@
|
|||||||
%option= city
|
%option= city
|
||||||
.field.region
|
.field.region
|
||||||
= f.label :region
|
= f.label :region
|
||||||
= f.collection_select :region_id, Region.all, :id, :name, { include_blank: true }
|
= f.collection_select :region_id, Region.all, :id, :name,
|
||||||
|
include_blank: true
|
||||||
.field.locality
|
.field.locality
|
||||||
= f.label :locality
|
= f.label :locality
|
||||||
%span.radios
|
%span.radios
|
||||||
|
@ -77,8 +77,8 @@
|
|||||||
"http://fr.wikipedia.org/wiki/#{url_encode @event.region.try :name}"
|
"http://fr.wikipedia.org/wiki/#{url_encode @event.region.try :name}"
|
||||||
|
|
||||||
- if @event.latitude && @event.longitude
|
- if @event.latitude && @event.longitude
|
||||||
.event#map{ data: { url: "#{maps_path format: :json}",
|
.event#map{ data: { url: maps_path(format: :json),
|
||||||
latitude: "#{@event.latitude}", longitude: "#{@event.longitude}" } }
|
latitude: @event.latitude, longitude: @event.longitude } }
|
||||||
|
|
||||||
- elsif controller.action_name != 'show'
|
- elsif controller.action_name != 'show'
|
||||||
%em.fa.fa-compress
|
%em.fa.fa-compress
|
||||||
@ -114,3 +114,13 @@
|
|||||||
%span.label= Event.human_attribute_name :tags
|
%span.label= Event.human_attribute_name :tags
|
||||||
- @event.tags.split.each do |tag|
|
- @event.tags.split.each do |tag|
|
||||||
= link_to tag, tag_path(tag), rel: :tag
|
= link_to tag, tag_path(tag), rel: :tag
|
||||||
|
|
||||||
|
- if @event.repeat > 0
|
||||||
|
%h3
|
||||||
|
%em.fa.fa-repeat
|
||||||
|
= t @event.rule, scope: 'activerecord.attributes.event.rule_values'
|
||||||
|
|
||||||
|
%ul
|
||||||
|
%li= link_to_unless_current @event.event || @event, @event.event || @event
|
||||||
|
- (@event.event || @event).events.each do |e|
|
||||||
|
%li= link_to_unless_current e, e
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
#{Event.human_attribute_name(:title).concat(':').ljust 12 } #{@event.title}
|
#{Event.human_attribute_name(:title).concat(':').ljust 12 } #{@event.title}
|
||||||
#{Event.human_attribute_name(:start_time).concat(':').ljust 12 } #{l @event.start_time, format: :at}
|
#{Event.human_attribute_name(:start_time).concat(':').ljust 12 } #{l @event.start_time, format: :at}
|
||||||
#{Event.human_attribute_name(:end_time).concat(':').ljust 12 } #{l @event.end_time, format: :at}
|
#{Event.human_attribute_name(:end_time).concat(':').ljust 12 } #{l @event.end_time, format: :at}
|
||||||
|
-if @event.repeat > 0
|
||||||
|
#{Event.human_attribute_name(:repeat).concat(':').ljust 12 } #{@event.repeat}
|
||||||
|
#{Event.human_attribute_name(:rule).concat(':').ljust 12 } #{t @event.rule, scope: 'activerecord.attributes.event.rule_values'}
|
||||||
#{Event.human_attribute_name(:place_name).concat(':').ljust 12 } #{@event.place_name}
|
#{Event.human_attribute_name(:place_name).concat(':').ljust 12 } #{@event.place_name}
|
||||||
#{Event.human_attribute_name(:address).concat(':').ljust 12 } #{@event.address}
|
#{Event.human_attribute_name(:address).concat(':').ljust 12 } #{@event.address}
|
||||||
#{Event.human_attribute_name(:city).concat(':').ljust 12 } #{@event.city}
|
#{Event.human_attribute_name(:city).concat(':').ljust 12 } #{@event.city}
|
||||||
|
@ -24,6 +24,18 @@
|
|||||||
|
|
||||||
= @event.to_tweet
|
= @event.to_tweet
|
||||||
|
|
||||||
|
- if @event.repeat > 0
|
||||||
|
%fieldset
|
||||||
|
%legend
|
||||||
|
%em.fa.fa-repeat
|
||||||
|
= Event.human_attribute_name :repeat
|
||||||
|
|
||||||
|
%h3= t '.repeat_helper', count: @event.repeat
|
||||||
|
|
||||||
|
%p.rule
|
||||||
|
= Event.human_attribute_name :rule
|
||||||
|
= t @event.rule, scope: 'activerecord.attributes.event.rule_values'
|
||||||
|
|
||||||
%fieldset
|
%fieldset
|
||||||
%legend
|
%legend
|
||||||
%em.fa.fa-calendar
|
%em.fa.fa-calendar
|
||||||
|
173
config/locales/ice_cube.fr.yml
Normal file
173
config/locales/ice_cube.fr.yml
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
fr:
|
||||||
|
ice_cube:
|
||||||
|
pieces_connector: ' / '
|
||||||
|
not: 'pas %{target}'
|
||||||
|
not_on: 'pas durant %{target}'
|
||||||
|
date:
|
||||||
|
formats:
|
||||||
|
default: '%d %B %Y'
|
||||||
|
month_names:
|
||||||
|
-
|
||||||
|
- janvier
|
||||||
|
- février
|
||||||
|
- mars
|
||||||
|
- avril
|
||||||
|
- mai
|
||||||
|
- juin
|
||||||
|
- juillet
|
||||||
|
- août
|
||||||
|
- septembre
|
||||||
|
- octobre
|
||||||
|
- novembre
|
||||||
|
- décembre
|
||||||
|
day_names:
|
||||||
|
- dimanche
|
||||||
|
- lundi
|
||||||
|
- mardi
|
||||||
|
- mercredi
|
||||||
|
- jeudi
|
||||||
|
- vendredi
|
||||||
|
- samedi
|
||||||
|
times:
|
||||||
|
other: '%{count} fois'
|
||||||
|
one: '%{count} fois'
|
||||||
|
until: "jusqu'au %{date}"
|
||||||
|
days_of_week: '%{segments} %{day}'
|
||||||
|
days_of_month:
|
||||||
|
other: '%{segments} jours du mois'
|
||||||
|
one: '%{segments} jours du mois'
|
||||||
|
days_of_year:
|
||||||
|
other: "%{segments} jours de l'année"
|
||||||
|
one: "%{segments} jours de l'année"
|
||||||
|
at_hours_of_the_day:
|
||||||
|
other: aux %{segments} heures de la journée
|
||||||
|
one: à %{segments}h
|
||||||
|
on_minutes_of_hour:
|
||||||
|
other: aux %{segments} minutes de l'heure
|
||||||
|
one: à la %{segments} minute de l'heure
|
||||||
|
at_seconds_of_minute:
|
||||||
|
other: aux %{segments} secondes
|
||||||
|
one: à la %{segments} seconde
|
||||||
|
on_seconds_of_minute:
|
||||||
|
other: aux %{segments} secondes de la minute
|
||||||
|
one: à la %{segments} seconde de la minute
|
||||||
|
each_second:
|
||||||
|
one: Toutes les secondes
|
||||||
|
other: Toutes les %{count} secondes
|
||||||
|
each_minute:
|
||||||
|
one: Toutes les minutes
|
||||||
|
other: Toutes les %{count} minutes
|
||||||
|
each_hour:
|
||||||
|
one: Toutes les heures
|
||||||
|
other: Toutes les %{count} heures
|
||||||
|
each_day:
|
||||||
|
one: Quotidien
|
||||||
|
other: Tous les %{count} jours
|
||||||
|
each_week:
|
||||||
|
one: Hebdomadaire
|
||||||
|
other: Toutes les %{count} semaines
|
||||||
|
each_month:
|
||||||
|
one: Mensuel
|
||||||
|
other: Tous les %{count} mois
|
||||||
|
each_year:
|
||||||
|
one: Annuel
|
||||||
|
other: Tous les %{count} ans
|
||||||
|
'on': les %{sentence}
|
||||||
|
in: 'en %{target}'
|
||||||
|
integer:
|
||||||
|
negative: '%{ordinal} depuis la fin'
|
||||||
|
literal_ordinals:
|
||||||
|
-1: derniers
|
||||||
|
-2: avant-derniers
|
||||||
|
ordinal: '%{number}%{ordinal}'
|
||||||
|
ordinals:
|
||||||
|
default: '°'
|
||||||
|
1: °
|
||||||
|
on_weekends: pendant les weekends
|
||||||
|
on_weekdays: pendant les jours ouvrés
|
||||||
|
days_on:
|
||||||
|
- dimanches
|
||||||
|
- lundis
|
||||||
|
- mardis
|
||||||
|
- mercredis
|
||||||
|
- jeudis
|
||||||
|
- vendredis
|
||||||
|
- samedis
|
||||||
|
on_days: les %{days}
|
||||||
|
array:
|
||||||
|
last_word_connector: ', et '
|
||||||
|
two_words_connector: ' et '
|
||||||
|
words_connector: ', '
|
||||||
|
string:
|
||||||
|
format:
|
||||||
|
day: '%{rest} %{current}'
|
||||||
|
day_of_week: '%{rest} %{current}'
|
||||||
|
day_of_month: '%{rest} %{current}'
|
||||||
|
day_of_year: '%{rest} %{current}'
|
||||||
|
hour_of_day: '%{rest} %{current}'
|
||||||
|
minute_of_hour: '%{rest} %{current}'
|
||||||
|
until: '%{rest} %{current}'
|
||||||
|
count: '%{rest} %{current}'
|
||||||
|
default: '%{rest} %{current}'
|
||||||
|
|
||||||
|
date:
|
||||||
|
abbr_day_names:
|
||||||
|
- Dim
|
||||||
|
- Lun
|
||||||
|
- Mar
|
||||||
|
- Mer
|
||||||
|
- Jeu
|
||||||
|
- Ven
|
||||||
|
- Sam
|
||||||
|
abbr_month_names:
|
||||||
|
-
|
||||||
|
- Jan
|
||||||
|
- Fév
|
||||||
|
- Mar
|
||||||
|
- Avr
|
||||||
|
- Mai
|
||||||
|
- Jun
|
||||||
|
- Jul
|
||||||
|
- Aou
|
||||||
|
- Sep
|
||||||
|
- Oct
|
||||||
|
- Nov
|
||||||
|
- Déc
|
||||||
|
day_names:
|
||||||
|
- dimanche
|
||||||
|
- lundi
|
||||||
|
- mardi
|
||||||
|
- mecredi
|
||||||
|
- jeudi
|
||||||
|
- vendredi
|
||||||
|
- samedi
|
||||||
|
formats:
|
||||||
|
default: "%d-%m-%Y"
|
||||||
|
long: "%d %B %Y"
|
||||||
|
short: "%d %b"
|
||||||
|
month_names:
|
||||||
|
-
|
||||||
|
- janvier
|
||||||
|
- février
|
||||||
|
- mars
|
||||||
|
- avril
|
||||||
|
- mai
|
||||||
|
- juin
|
||||||
|
- juillet
|
||||||
|
- août
|
||||||
|
- septembre
|
||||||
|
- octobre
|
||||||
|
- novembre
|
||||||
|
- décembre
|
||||||
|
order:
|
||||||
|
- :year
|
||||||
|
- :month
|
||||||
|
- :day
|
||||||
|
|
||||||
|
time:
|
||||||
|
am: am
|
||||||
|
formats:
|
||||||
|
default: "%a, %d %b %Y %H:%M:%S %z"
|
||||||
|
long: "%d %B %Y %H:%M"
|
||||||
|
short: "%d %b %H:%M"
|
||||||
|
pm: pm
|
@ -63,6 +63,13 @@ en:
|
|||||||
title: Title
|
title: Title
|
||||||
start_time: Start
|
start_time: Start
|
||||||
end_time: End
|
end_time: End
|
||||||
|
repeat: Repeat
|
||||||
|
rule: Règle
|
||||||
|
rule_values:
|
||||||
|
daily: Daily
|
||||||
|
weekly: Weekly
|
||||||
|
monthly: Monthly
|
||||||
|
yearly: Yearly
|
||||||
description: Description
|
description: Description
|
||||||
place_name: Place name
|
place_name: Place name
|
||||||
address: Address
|
address: Address
|
||||||
|
@ -63,6 +63,13 @@ fr:
|
|||||||
title: Titre
|
title: Titre
|
||||||
start_time: Début
|
start_time: Début
|
||||||
end_time: Fin
|
end_time: Fin
|
||||||
|
repeat: Répéter
|
||||||
|
rule: Règle
|
||||||
|
rule_values:
|
||||||
|
daily: Journalière
|
||||||
|
weekly: Hebdomadaire
|
||||||
|
monthly: Mensuelle
|
||||||
|
yearly: Annuelle
|
||||||
description: Description
|
description: Description
|
||||||
place_name: Nom du lieu
|
place_name: Nom du lieu
|
||||||
address: Adresse
|
address: Adresse
|
||||||
|
@ -89,6 +89,8 @@ it more readable or agreable.
|
|||||||
ok: Your event was updated
|
ok: Your event was updated
|
||||||
form:
|
form:
|
||||||
title_helper: Less than 5 words, without address or date
|
title_helper: Less than 5 words, without address or date
|
||||||
|
rule_helper: Repeated events will be generated during validation. You
|
||||||
|
will receive by mail edition and cancellation links
|
||||||
description_helper: Describe with as much precision as possible your event
|
description_helper: Describe with as much precision as possible your event
|
||||||
address_helper: Associated to the city and region, it will generate an
|
address_helper: Associated to the city and region, it will generate an
|
||||||
[OpenStreetMap](http://www.openstreetmap.org) map, displayed alongside
|
[OpenStreetMap](http://www.openstreetmap.org) map, displayed alongside
|
||||||
@ -179,6 +181,10 @@ it more readable or agreable.
|
|||||||
ok: Yes
|
ok: Yes
|
||||||
ko: Moderation
|
ko: Moderation
|
||||||
tweet_helper: A tweet will be published, here is its content
|
tweet_helper: A tweet will be published, here is its content
|
||||||
|
repeat_helper:
|
||||||
|
zero:
|
||||||
|
one: One other event will be generated
|
||||||
|
other: "%{count} events will be generated"
|
||||||
accept:
|
accept:
|
||||||
ok: Event accepted
|
ok: Event accepted
|
||||||
refuse:
|
refuse:
|
||||||
@ -318,6 +324,10 @@ description."
|
|||||||
edit_link: "You can modify this event later to add details at the
|
edit_link: "You can modify this event later to add details at the
|
||||||
address:"
|
address:"
|
||||||
delete_link: "You can can also cancel it at the address:"
|
delete_link: "You can can also cancel it at the address:"
|
||||||
|
repeat_helper:
|
||||||
|
zero:
|
||||||
|
one: Another event was genereated, here are the edition and cancellation links
|
||||||
|
other: "%{count} other events were generated, here are the edition and cancellation links"
|
||||||
signature: Thank you for your contribution and see you soon!
|
signature: Thank you for your contribution and see you soon!
|
||||||
destroy:
|
destroy:
|
||||||
subject: "Event '%{subject}' refused"
|
subject: "Event '%{subject}' refused"
|
||||||
|
@ -80,6 +80,8 @@ fr:
|
|||||||
ok: Votre événement a été mis à jour
|
ok: Votre événement a été mis à jour
|
||||||
form:
|
form:
|
||||||
title_helper: Moins de 5 mots, sans lieu ou date
|
title_helper: Moins de 5 mots, sans lieu ou date
|
||||||
|
rule_helper: Les événements répétés seront générés lors de la
|
||||||
|
validation. Vous recevrez par mail les liens d'édition et d'annulation
|
||||||
description_helper: Décrivez de la manière la plus complète possible
|
description_helper: Décrivez de la manière la plus complète possible
|
||||||
votre événement
|
votre événement
|
||||||
address_helper: "*Associée à la ville et la région, elle générera une
|
address_helper: "*Associée à la ville et la région, elle générera une
|
||||||
@ -176,6 +178,10 @@ fr:
|
|||||||
ok: Oui
|
ok: Oui
|
||||||
ko: Modération
|
ko: Modération
|
||||||
tweet_helper: Un tweet sera publié, dont voici le contenu
|
tweet_helper: Un tweet sera publié, dont voici le contenu
|
||||||
|
repeat_helper:
|
||||||
|
zero:
|
||||||
|
one: Un autre événement sera généré
|
||||||
|
other: "%{count} autres événements seront générés"
|
||||||
accept:
|
accept:
|
||||||
ok: Événement accepté
|
ok: Événement accepté
|
||||||
refuse:
|
refuse:
|
||||||
@ -324,6 +330,10 @@ est maintenant visible à l'adresse:"
|
|||||||
ajouter des précisions en vous rendant à l'adresse:"
|
ajouter des précisions en vous rendant à l'adresse:"
|
||||||
delete_link: "Vous pouvez également l'annuler en vous rendant à
|
delete_link: "Vous pouvez également l'annuler en vous rendant à
|
||||||
l'adresse:"
|
l'adresse:"
|
||||||
|
repeat_helper:
|
||||||
|
zero:
|
||||||
|
one: Un autre événement a été généré, voici les liens d'édition et annulation
|
||||||
|
other: "%{count} autres événements ont été générés, voici les liens d'édition et annulation"
|
||||||
signature: Merci de votre contribution et à bientôt!
|
signature: Merci de votre contribution et à bientôt!
|
||||||
destroy:
|
destroy:
|
||||||
subject: "Événement '%{subject}' refusé"
|
subject: "Événement '%{subject}' refusé"
|
||||||
|
9
db/migrate/20160616190823_create_schedules.rb
Normal file
9
db/migrate/20160616190823_create_schedules.rb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# Manage a schedule for events, that will let adl create recurring events
|
||||||
|
class CreateSchedules < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
add_column :events, :repeat, :integer, default: 0
|
||||||
|
add_column :events, :rule, :text, default: 'daily'
|
||||||
|
# This column is there to manage scheduled events
|
||||||
|
add_reference :events, :event, index: true
|
||||||
|
end
|
||||||
|
end
|
33
db/schema.rb
33
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: 20160409131029) do
|
ActiveRecord::Schema.define(version: 20160616190823) do
|
||||||
|
|
||||||
create_table "active_admin_comments", force: :cascade do |t|
|
create_table "active_admin_comments", force: :cascade do |t|
|
||||||
t.string "namespace", limit: 255
|
t.string "namespace", limit: 255
|
||||||
@ -59,19 +59,19 @@ ActiveRecord::Schema.define(version: 20160409131029) do
|
|||||||
add_index "cities", ["name"], name: "cities_name"
|
add_index "cities", ["name"], name: "cities_name"
|
||||||
|
|
||||||
create_table "events", force: :cascade do |t|
|
create_table "events", force: :cascade do |t|
|
||||||
t.string "title", limit: 255, default: "", null: false
|
t.string "title", limit: 255, default: "", null: false
|
||||||
t.text "description", limit: 65535, null: false
|
t.text "description", limit: 65535, null: false
|
||||||
t.datetime "start_time", null: false
|
t.datetime "start_time", null: false
|
||||||
t.datetime "end_time", null: false
|
t.datetime "end_time", null: false
|
||||||
t.string "city", limit: 255, default: ""
|
t.string "city", limit: 255, default: ""
|
||||||
t.integer "region_id", limit: 4, default: 0, null: false
|
t.integer "region_id", limit: 4, default: 0, null: false
|
||||||
t.integer "locality", limit: 4, default: 0, null: false
|
t.integer "locality", limit: 4, default: 0, null: false
|
||||||
t.string "url", limit: 255, default: "", null: false
|
t.string "url", limit: 255, default: "", null: false
|
||||||
t.string "contact", limit: 255, default: "", null: false
|
t.string "contact", limit: 255, default: "", null: false
|
||||||
t.string "submitter", limit: 255, default: "", null: false
|
t.string "submitter", limit: 255, default: "", null: false
|
||||||
t.integer "moderated", limit: 4, default: 0, null: false
|
t.integer "moderated", limit: 4, default: 0, null: false
|
||||||
t.string "tags", limit: 255, default: "", null: false
|
t.string "tags", limit: 255, default: "", null: false
|
||||||
t.string "secret", limit: 255, default: "", null: false
|
t.string "secret", limit: 255, default: "", null: false
|
||||||
t.datetime "decision_time"
|
t.datetime "decision_time"
|
||||||
t.datetime "submission_time"
|
t.datetime "submission_time"
|
||||||
t.string "moderator_mail_id", limit: 32
|
t.string "moderator_mail_id", limit: 32
|
||||||
@ -79,10 +79,15 @@ ActiveRecord::Schema.define(version: 20160409131029) do
|
|||||||
t.text "address", limit: 65535
|
t.text "address", limit: 65535
|
||||||
t.float "latitude", limit: 24
|
t.float "latitude", limit: 24
|
||||||
t.float "longitude", limit: 24
|
t.float "longitude", limit: 24
|
||||||
t.integer "lock_version", limit: 4, default: 0, null: false
|
t.integer "lock_version", limit: 4, default: 0, null: false
|
||||||
t.string "place_name", limit: 255
|
t.string "place_name", limit: 255
|
||||||
|
t.integer "count", default: 1
|
||||||
|
t.integer "repeat", default: 0
|
||||||
|
t.text "rule", default: "daily"
|
||||||
|
t.integer "event_id"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
add_index "events", ["event_id"], name: "index_events_on_event_id"
|
||||||
add_index "events", ["start_time", "end_time"], name: "events_date"
|
add_index "events", ["start_time", "end_time"], name: "events_date"
|
||||||
|
|
||||||
create_table "kinds", force: :cascade do |t|
|
create_table "kinds", force: :cascade do |t|
|
||||||
|
47
test/models/event_callbacks_test.rb
Normal file
47
test/models/event_callbacks_test.rb
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
|
||||||
|
# Test event callbacks
|
||||||
|
class EventCallbacksTest < ActiveSupport::TestCase
|
||||||
|
setup do
|
||||||
|
ActionMailer::Base.default_url_options[:host] = 'localhost:3000'
|
||||||
|
|
||||||
|
@event = events :one
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'schedule' do
|
||||||
|
@event = Event.new(
|
||||||
|
title: 'hello world',
|
||||||
|
start_time: Time.zone.now, end_time: Time.zone.now + 1.hour,
|
||||||
|
description: 'et hop!',
|
||||||
|
city: City.first, region: Region.first,
|
||||||
|
url: 'http://example.com',
|
||||||
|
contact: 'contact@example.com',
|
||||||
|
tags: 'hello world'
|
||||||
|
)
|
||||||
|
assert_difference 'Event.count' do
|
||||||
|
assert @event.save, @event.errors.messages
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test 'moderation' do
|
||||||
|
@event = Event.new(
|
||||||
|
title: 'hello world',
|
||||||
|
start_time: Time.zone.now + 1.hour, end_time: Time.zone.now + 2.hours,
|
||||||
|
repeat: 1, rule: 'monthly',
|
||||||
|
description: 'et hop!',
|
||||||
|
city: City.first, region: Region.first,
|
||||||
|
url: 'http://example.com',
|
||||||
|
contact: 'contact@example.com',
|
||||||
|
tags: 'hello world'
|
||||||
|
)
|
||||||
|
|
||||||
|
assert @event.save, @event.errors.messages
|
||||||
|
assert !@event.moderated?
|
||||||
|
|
||||||
|
assert_difference 'Event.count' do
|
||||||
|
@event.update moderated: 1
|
||||||
|
end
|
||||||
|
|
||||||
|
assert @event.moderated?, @event.errors.messages
|
||||||
|
end
|
||||||
|
end
|
@ -21,7 +21,9 @@ class EventTest < ActiveSupport::TestCase
|
|||||||
submitter: 'submitter@example.com',
|
submitter: 'submitter@example.com',
|
||||||
tags: 'hello world'
|
tags: 'hello world'
|
||||||
)
|
)
|
||||||
assert @event.save, @event.errors.messages
|
assert_difference 'Event.count' do
|
||||||
|
assert @event.save, @event.errors.messages
|
||||||
|
end
|
||||||
|
|
||||||
assert_equal 32, @event.secret.size
|
assert_equal 32, @event.secret.size
|
||||||
assert_equal 32, @event.moderator_mail_id.size
|
assert_equal 32, @event.moderator_mail_id.size
|
||||||
|
Loading…
Reference in New Issue
Block a user