aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoachim Filip Ignacy Bartosik <jbartosik@gmail.com>2011-05-13 18:53:18 +0200
committerJoachim Filip Ignacy Bartosik <jbartosik@gmail.com>2011-05-24 18:55:39 +0200
commit83a9df9a9daf85bd6b13bfc8fdedec8f8780ce13 (patch)
tree014c385bae8ede30631c9eb3fe6394d772963f3b
parentAgenda lifecycle (semi-automatically managed) (diff)
downloadcouncil-webapp-83a9df9a9daf85bd6b13bfc8fdedec8f8780ce13.tar.gz
council-webapp-83a9df9a9daf85bd6b13bfc8fdedec8f8780ce13.tar.bz2
council-webapp-83a9df9a9daf85bd6b13bfc8fdedec8f8780ce13.zip
Agenda items
-rw-r--r--site/app/controllers/agenda_items_controller.rb12
-rw-r--r--site/app/models/agenda.rb6
-rw-r--r--site/app/models/agenda_item.rb53
-rw-r--r--site/app/viewhints/agenda_hints.rb5
-rw-r--r--site/app/views/agenda_items/show.dryml14
-rw-r--r--site/app/views/agendas/show.dryml8
-rw-r--r--site/app/views/taglibs/application.dryml2
-rw-r--r--site/app/views/taglibs/main_nav.dryml7
-rw-r--r--site/config/hobo_routes.rb9
-rw-r--r--site/db/schema.rb16
-rw-r--r--site/features/agenda_items.feature39
-rw-r--r--site/features/step_definitions/agenda_item_steps.rb24
-rw-r--r--site/features/step_definitions/within_steps.rb1
-rw-r--r--site/features/support/paths.rb4
-rw-r--r--site/spec/factories.rb2
-rw-r--r--site/spec/models/agenda_item_spec.rb103
16 files changed, 304 insertions, 1 deletions
diff --git a/site/app/controllers/agenda_items_controller.rb b/site/app/controllers/agenda_items_controller.rb
new file mode 100644
index 0000000..4d28112
--- /dev/null
+++ b/site/app/controllers/agenda_items_controller.rb
@@ -0,0 +1,12 @@
+class AgendaItemsController < ApplicationController
+
+ hobo_model_controller
+
+ auto_actions :all, :except => :index
+ before_filter :login, :except => :show
+
+ protected
+ def login
+ redirect_to user_login_path unless current_user.signed_up?
+ end
+end
diff --git a/site/app/models/agenda.rb b/site/app/models/agenda.rb
index def1643..5998ae8 100644
--- a/site/app/models/agenda.rb
+++ b/site/app/models/agenda.rb
@@ -7,6 +7,8 @@ class Agenda < ActiveRecord::Base
timestamps
end
+ has_many :agenda_items
+
lifecycle do
state :open, :default => true
state :submissions_closed, :meeting_ongoing, :old
@@ -67,6 +69,10 @@ class Agenda < ActiveRecord::Base
end
end
+ def current?
+ ['open', 'submissions_closed'].include?(state.to_s)
+ end
+
protected
def there_is_only_one_non_archival_agenda
return if(state.to_s == 'old')
diff --git a/site/app/models/agenda_item.rb b/site/app/models/agenda_item.rb
new file mode 100644
index 0000000..1bb3a36
--- /dev/null
+++ b/site/app/models/agenda_item.rb
@@ -0,0 +1,53 @@
+class AgendaItem < ActiveRecord::Base
+
+ hobo_model # Don't put anything above this
+
+ fields do
+ title :string
+ discussion :string
+ body :text
+ rejected :boolean, :default => false
+ timestamps
+ end
+
+ belongs_to :user, :creator => true
+ belongs_to :agenda
+
+ # --- Permissions --- #
+ def create_permitted?
+ return false if acting_user.guest?
+ return false if user != acting_user
+ true
+ end
+
+ def update_permitted?
+ return false if agenda._?.state == 'archived'
+ return false if user_changed?
+ return true if acting_user.council_member?
+ return true if acting_user.administrator?
+ return false unless agenda.nil?
+ return true if acting_user == user
+ false
+ end
+
+ def destroy_permitted?
+ acting_user.administrator?
+ end
+
+ def view_permitted?(field)
+ true
+ end
+
+ # Not deduced properly
+ def edit_permitted?(field)
+ return false if field == :rejected && !agenda.nil?
+ return false if field == :agenda && rejected?
+ return false if agenda._?.state == 'archived'
+ return false if field == :user
+ return true if acting_user.administrator?
+ return true if acting_user.council_member?
+ return false unless agenda.nil?
+ return acting_user == user if [nil, :title, :discussion, :body].include?(field)
+ end
+
+end
diff --git a/site/app/viewhints/agenda_hints.rb b/site/app/viewhints/agenda_hints.rb
new file mode 100644
index 0000000..05c7b96
--- /dev/null
+++ b/site/app/viewhints/agenda_hints.rb
@@ -0,0 +1,5 @@
+class AgendaHints < Hobo::ViewHints
+
+ children :agenda_items
+
+end
diff --git a/site/app/views/agenda_items/show.dryml b/site/app/views/agenda_items/show.dryml
new file mode 100644
index 0000000..626298d
--- /dev/null
+++ b/site/app/views/agenda_items/show.dryml
@@ -0,0 +1,14 @@
+<show-page>
+ <append-content:>
+ <form if="&this.editable_by?(current_user, :agenda)">
+ <input value="&Agenda.current.id" type="hidden" name="agenda_item[agenda_id]"/>
+ <submit label="Add to current agenda"/>
+ </form>
+
+ <form if="&this.editable_by?(current_user, :rejected)">
+ <input value="&!this.rejected?" type="hidden" name="agenda_item[rejected]"/>
+ <submit label="Reject" unless="&this.rejected"/>
+ <submit label="Un-reject" if="&this.rejected"/>
+ </form>
+ </append-content:>
+</show-page>
diff --git a/site/app/views/agendas/show.dryml b/site/app/views/agendas/show.dryml
index 7df2b72..113edb4 100644
--- a/site/app/views/agendas/show.dryml
+++ b/site/app/views/agendas/show.dryml
@@ -1,4 +1,12 @@
<show-page>
+ <append-collection-section: if="&this.current?">
+ <h3 class="collection-heading">
+ Suggested items:
+ </h3>
+ <with with="&AgendaItem.all(:conditions => { :agenda_id => nil })">
+ <collection unless="&this.nil?" />
+ </with>
+ </append-collection-section:>
<append-content-body:>
<div class="transition" if="&Agenda.transitions_available(current_user)">
<collection:possible_transitions>
diff --git a/site/app/views/taglibs/application.dryml b/site/app/views/taglibs/application.dryml
index 2f08a8e..017831f 100644
--- a/site/app/views/taglibs/application.dryml
+++ b/site/app/views/taglibs/application.dryml
@@ -6,4 +6,6 @@
<include src="taglibs/auto/rapid/pages"/>
<include src="taglibs/auto/rapid/forms"/>
+<include src="taglibs/main_nav"/>
+
<set-theme name="clean"/>
diff --git a/site/app/views/taglibs/main_nav.dryml b/site/app/views/taglibs/main_nav.dryml
new file mode 100644
index 0000000..af60c6d
--- /dev/null
+++ b/site/app/views/taglibs/main_nav.dryml
@@ -0,0 +1,7 @@
+<def tag="main-nav">
+ <navigation class="main-nav" merge-attrs param="default">
+ <nav-item href="#{base_url}/">Home</nav-item>
+ <nav-item with="&Agenda"><ht key="agenda.nav_item" count="100"><model-name-human count="100"/></ht></nav-item>
+ <nav-item href="&new_agenda_item_path">Suggest agenda item</nav-item>
+ </navigation>
+</def>
diff --git a/site/config/hobo_routes.rb b/site/config/hobo_routes.rb
index 33b292b..11fb9b0 100644
--- a/site/config/hobo_routes.rb
+++ b/site/config/hobo_routes.rb
@@ -44,4 +44,13 @@ Council::Application.routes.draw do
put 'agendas/:id(.:format)' => 'agendas#update', :as => 'update_agenda', :constraints => { :id => %r([^/.?]+) }
delete 'agendas/:id(.:format)' => 'agendas#destroy', :as => 'destroy_agenda', :constraints => { :id => %r([^/.?]+) }
+
+ # Resource routes for controller "agenda_items"
+ get 'agenda_items/new(.:format)', :as => 'new_agenda_item'
+ get 'agenda_items/:id/edit(.:format)' => 'agenda_items#edit', :as => 'edit_agenda_item'
+ get 'agenda_items/:id(.:format)' => 'agenda_items#show', :as => 'agenda_item', :constraints => { :id => %r([^/.?]+) }
+ post 'agenda_items(.:format)' => 'agenda_items#create', :as => 'create_agenda_item'
+ put 'agenda_items/:id(.:format)' => 'agenda_items#update', :as => 'update_agenda_item', :constraints => { :id => %r([^/.?]+) }
+ delete 'agenda_items/:id(.:format)' => 'agenda_items#destroy', :as => 'destroy_agenda_item', :constraints => { :id => %r([^/.?]+) }
+
end
diff --git a/site/db/schema.rb b/site/db/schema.rb
index b308e67..edd7ee0 100644
--- a/site/db/schema.rb
+++ b/site/db/schema.rb
@@ -10,7 +10,21 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20110523175740) do
+ActiveRecord::Schema.define(:version => 20110523180314) do
+
+ create_table "agenda_items", :force => true do |t|
+ t.string "title"
+ t.string "discussion"
+ t.text "body"
+ t.boolean "rejected", :default => false
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.integer "user_id"
+ t.integer "agenda_id"
+ end
+
+ add_index "agenda_items", ["agenda_id"], :name => "index_agenda_items_on_agenda_id"
+ add_index "agenda_items", ["user_id"], :name => "index_agenda_items_on_user_id"
create_table "agendas", :force => true do |t|
t.datetime "meeting_time"
diff --git a/site/features/agenda_items.feature b/site/features/agenda_items.feature
new file mode 100644
index 0000000..e9b417c
--- /dev/null
+++ b/site/features/agenda_items.feature
@@ -0,0 +1,39 @@
+Feature: Suggest Agenda Items
+ As a user
+ I want to be able to suggest agenda items
+ so Council will consider my ideas
+
+ Scenario: Suggest Agenda item
+ Given I am logged in as example user
+ And an agenda
+ When I am on the home page
+ And I follow "Suggest agenda item"
+ And I fill in example agenda item data
+ And I press "Create Agenda item"
+ Then I should see "The Agenda item was created successfully" in the notices
+
+ Scenario: Approve Agenda items
+ Given example agenda item
+ And an agenda
+ And I am logged in as a council member
+ When I am on the current agenda page
+ And I follow first suggested agenda link
+ And I press "Add to current agenda"
+ And I should see current agenda as the agenda
+
+ Scenario: Reject Agenda item
+ Given example agenda item
+ And an agenda
+ And I am logged in as a council member
+ When I am on the first suggested agenda page
+ And I press "Reject"
+ Then I should see "Rejected"
+
+ When I press "Un-reject"
+ Then I should not see "Rejected"
+
+ Scenario: Don't list rejected agenda items
+ Given rejected agenda item
+ And an agenda
+ When I am on the current agenda page
+ Then I should not see "rejected"
diff --git a/site/features/step_definitions/agenda_item_steps.rb b/site/features/step_definitions/agenda_item_steps.rb
new file mode 100644
index 0000000..d68ee91
--- /dev/null
+++ b/site/features/step_definitions/agenda_item_steps.rb
@@ -0,0 +1,24 @@
+When /^I fill in example agenda item data$/ do
+ When "I fill in the following:", table(%{
+ |agenda_item_title|examle|
+ |agenda_item_discussion|http://example.com/mailinglist/example|
+ |agenda_item_body|example|
+ })
+end
+
+Given /^example agenda item$/ do
+ AgendaItem.new(:title => 'example', :discussion => '', :body => 'example').save!
+end
+
+Given /^rejected agenda item$/ do
+ AgendaItem.new(:title => 'Rejected', :discussion => '', :body => 'example', :rejected => true).save!
+end
+
+When /^I follow first suggested agenda link$/ do
+ firstItem = AgendaItem.first :conditions => {:agenda_id => nil, :rejected => false}
+ When "I follow \"#{firstItem.title}\""
+end
+
+When /^I should see current agenda as the agenda$/ do
+ When "I should see \"Agenda #{Agenda.current.id}\" within \".agenda-item-agenda\""
+end
diff --git a/site/features/step_definitions/within_steps.rb b/site/features/step_definitions/within_steps.rb
index 80bb8f0..7d56fc4 100644
--- a/site/features/step_definitions/within_steps.rb
+++ b/site/features/step_definitions/within_steps.rb
@@ -5,6 +5,7 @@
'in the notices' => '.flash.notice',
'in the errors' => '.error-messages',
'in the content body' => '.content-body',
+ 'in the agenda items' => '.agenda-items',
'in the agendas collection' => '.collection.agendas',
'as empty collection message' => '.empty-collection-message',
'as meeting time' => '.meeting-time-view',
diff --git a/site/features/support/paths.rb b/site/features/support/paths.rb
index 515a7ec..ab24b9e 100644
--- a/site/features/support/paths.rb
+++ b/site/features/support/paths.rb
@@ -19,6 +19,10 @@ module NavigationHelpers
when /the current agenda page/
agenda_path(Agenda.current)
+
+ when /the first suggested agenda page/
+ agenda_item_path(AgendaItem.first(:conditions => {:agenda_id => nil}))
+
# Add more mappings here.
# Here is an example that pulls values out of the Regexp:
#
diff --git a/site/spec/factories.rb b/site/spec/factories.rb
index b41c25c..3211032 100644
--- a/site/spec/factories.rb
+++ b/site/spec/factories.rb
@@ -9,3 +9,5 @@ Factory.define :user, :class => User do |u|
end
Factory.define :agenda do |a|; end
+
+Factory.define :agenda_item do |a|; end
diff --git a/site/spec/models/agenda_item_spec.rb b/site/spec/models/agenda_item_spec.rb
new file mode 100644
index 0000000..95d7659
--- /dev/null
+++ b/site/spec/models/agenda_item_spec.rb
@@ -0,0 +1,103 @@
+require 'spec_helper'
+
+describe AgendaItem do
+ it 'should allow all registered users to create' do
+ a = AgendaItem.new
+ for u in users_factory(:user, :council, :admin, :council_admin)
+ a.user = u
+ a.should be_creatable_by(u)
+ end
+ end
+
+ it 'should allow only administrators to destroy' do
+ a = Factory(:agenda_item)
+ for u in users_factory(:guest, :user, :council)
+ a.should_not be_destroyable_by(u)
+ end
+ for u in users_factory(:admin, :council_admin)
+ a.user = u
+ a.should be_destroyable_by(u)
+ end
+ end
+
+ it 'should allow owner, council members and administrators to edit and update unassigned items' do
+ owner = Factory(:user)
+ a = Factory(:agenda_item, :user => owner)
+ for u in users_factory(:guest, :user)
+ a.should_not be_editable_by(u)
+ a.should_not be_updatable_by(u)
+ end
+ for u in users_factory(:council, :admin, :council_admin) + [owner]
+ a.should be_editable_by(u)
+ a.should be_updatable_by(u)
+ end
+ end
+
+ it 'should allow only council members and administrators to edit and update assigned items' do
+ owner = Factory(:user)
+ agenda = Agenda.last || Factory(:agenda)
+ a = Factory(:agenda_item, :user => owner, :agenda => agenda)
+ for u in users_factory(:guest, :user) + [owner]
+ a.should_not be_editable_by(u)
+ a.should_not be_updatable_by(u)
+ end
+ for u in users_factory(:council, :admin, :council_admin)
+ a.should be_editable_by(u)
+ a.should be_updatable_by(u)
+ end
+ end
+
+ it 'should allow no one edit and update items assigned to archived agenda' do
+ owner = Factory(:user)
+ agenda = Factory(:agenda, :state => 'archived')
+ a = Factory(:agenda_item, :user => owner, :agenda => agenda)
+ for u in users_factory(AllRoles) + [owner]
+ a.should_not be_editable_by(u)
+ a.should_not be_updatable_by(u)
+ end
+ end
+
+ it 'should allow owner, council memebers and administrators to edit some fields' do
+ owner = Factory(:user)
+ a = Factory(:agenda_item, :user => owner)
+ for u in users_factory(:council, :admin, :council_admin) + [owner]
+ a.should be_editable_by(u, :title)
+ a.should be_editable_by(u, :discussion)
+ a.should be_editable_by(u, :body)
+ end
+ end
+
+ it 'should allow only council memebers and administrators to edit some fields' do
+ owner = Factory(:user)
+ a = Factory(:agenda_item, :user => owner)
+ for u in users_factory(:council, :admin, :council_admin)
+ a.should be_editable_by(u, :agenda)
+ a.should be_editable_by(u, :rejected)
+ end
+ a.should_not be_editable_by(owner, :agenda)
+ a.should_not be_editable_by(owner, :rejected)
+ end
+
+ it 'should allow no one to edit some fields' do
+ owner = Factory(:user)
+ a = Factory(:agenda_item, :user => owner)
+ for u in users_factory(:guest, :user, :council, :admin, :council_admin) + [owner]
+ a.should_not be_editable_by(u, :user)
+ end
+ end
+
+ it 'should not allow to edit agenda if rejected' do
+ agenda = Agenda.last || Factory(:agenda)
+ a = Factory(:agenda_item, :agenda => agenda)
+ for u in users_factory(:guest, :user, :council, :admin, :council_admin)
+ a.should_not be_editable_by(u, :rejected)
+ end
+ end
+
+ it 'should not allow to reject if assigned to agenda' do
+ a = Factory(:agenda_item, :rejected => true)
+ for u in users_factory(:guest, :user, :council, :admin, :council_admin)
+ a.should_not be_editable_by(u, :agenda)
+ end
+ end
+end