diff options
author | Joachim Filip Ignacy Bartosik <jbartosik@gmail.com> | 2011-05-13 18:53:18 +0200 |
---|---|---|
committer | Joachim Filip Ignacy Bartosik <jbartosik@gmail.com> | 2011-05-24 18:55:39 +0200 |
commit | 83a9df9a9daf85bd6b13bfc8fdedec8f8780ce13 (patch) | |
tree | 014c385bae8ede30631c9eb3fe6394d772963f3b | |
parent | Agenda lifecycle (semi-automatically managed) (diff) | |
download | council-webapp-83a9df9a9daf85bd6b13bfc8fdedec8f8780ce13.tar.gz council-webapp-83a9df9a9daf85bd6b13bfc8fdedec8f8780ce13.tar.bz2 council-webapp-83a9df9a9daf85bd6b13bfc8fdedec8f8780ce13.zip |
Agenda items
-rw-r--r-- | site/app/controllers/agenda_items_controller.rb | 12 | ||||
-rw-r--r-- | site/app/models/agenda.rb | 6 | ||||
-rw-r--r-- | site/app/models/agenda_item.rb | 53 | ||||
-rw-r--r-- | site/app/viewhints/agenda_hints.rb | 5 | ||||
-rw-r--r-- | site/app/views/agenda_items/show.dryml | 14 | ||||
-rw-r--r-- | site/app/views/agendas/show.dryml | 8 | ||||
-rw-r--r-- | site/app/views/taglibs/application.dryml | 2 | ||||
-rw-r--r-- | site/app/views/taglibs/main_nav.dryml | 7 | ||||
-rw-r--r-- | site/config/hobo_routes.rb | 9 | ||||
-rw-r--r-- | site/db/schema.rb | 16 | ||||
-rw-r--r-- | site/features/agenda_items.feature | 39 | ||||
-rw-r--r-- | site/features/step_definitions/agenda_item_steps.rb | 24 | ||||
-rw-r--r-- | site/features/step_definitions/within_steps.rb | 1 | ||||
-rw-r--r-- | site/features/support/paths.rb | 4 | ||||
-rw-r--r-- | site/spec/factories.rb | 2 | ||||
-rw-r--r-- | site/spec/models/agenda_item_spec.rb | 103 |
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 |