mirror of
https://github.com/TracksApp/tracks.git
synced 2026-03-03 11:30:15 +01:00
Made TodosController more RESTful and use it to fulfill all feeds, eliminating the need for the FeedController and it's helper and views. Also added an ATOM feed (not linked in the UI anywhere, just substitute .atom for .rss).
I also ran rcov on unit tests and added tests to improve test coverage, uncovering a couple of bugs along the way. git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@476 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
parent
84357b67d5
commit
106d5ee448
38 changed files with 1007 additions and 680 deletions
|
|
@ -13,7 +13,7 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
helper :application
|
||||
include LoginSystem
|
||||
|
||||
|
||||
layout 'standard'
|
||||
|
||||
before_filter :set_session_expiration
|
||||
|
|
@ -21,6 +21,9 @@ class ApplicationController < ActionController::Base
|
|||
|
||||
after_filter :set_charset
|
||||
|
||||
include ActionView::Helpers::TextHelper
|
||||
helper_method :format_date, :markdown
|
||||
|
||||
# By default, sets the charset to UTF-8 if it isn't already set
|
||||
def set_charset
|
||||
headers["Content-Type"] ||= "text/html; charset=UTF-8"
|
||||
|
|
@ -78,13 +81,35 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def count_undone_todos(todos_parent)
|
||||
if (todos_parent.is_a?(Project) && todos_parent.hidden?)
|
||||
if todos_parent.nil?
|
||||
count = 0
|
||||
elsif (todos_parent.is_a?(Project) && todos_parent.hidden?)
|
||||
count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]"
|
||||
else
|
||||
count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]"
|
||||
end
|
||||
count = 0 if count == nil
|
||||
count
|
||||
count || 0
|
||||
end
|
||||
|
||||
# Convert a date object to the format specified
|
||||
# in config/settings.yml
|
||||
#
|
||||
def format_date(date)
|
||||
if date
|
||||
date_format = @user.prefs.date_format
|
||||
formatted_date = date.strftime("#{date_format}")
|
||||
else
|
||||
formatted_date = ''
|
||||
end
|
||||
formatted_date
|
||||
end
|
||||
|
||||
# Uses RedCloth to transform text using either Textile or Markdown
|
||||
# Need to require redcloth above
|
||||
# RedCloth 3.0 or greater is needed to use Markdown, otherwise it only handles Textile
|
||||
#
|
||||
def markdown(text)
|
||||
RedCloth.new(text).to_html
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
|
|||
|
|
@ -1,133 +0,0 @@
|
|||
# Produces an feeds of the next actions, both RSS and plain text
|
||||
#
|
||||
class FeedController < ApplicationController
|
||||
|
||||
helper :feed
|
||||
layout nil
|
||||
|
||||
session :disabled => true # Prevents session control from interfering with feed
|
||||
|
||||
skip_before_filter :login_required
|
||||
before_filter :check_token_against_user_word
|
||||
before_filter :prepare_for_feed, :only => [:rss, :text, :ical]
|
||||
before_filter :identify_contexts, :only => [:text, :ical]
|
||||
|
||||
# Build an RSS feed
|
||||
def rss
|
||||
headers["Content-Type"] = "text/xml; charset=utf-8"
|
||||
end
|
||||
|
||||
# Builds a plain text page listing incomplete next actions,
|
||||
# grouped by context (contexts are sorted by position, as on the home page).
|
||||
# Showing notes doesn't make much sense here so they are omitted.
|
||||
# Hidden contexts are also hidden in the text view
|
||||
# You can use this with GeekTool to get your next actions
|
||||
# on the desktop:
|
||||
# curl [url from "TXT" link on todo/list]
|
||||
#
|
||||
def text
|
||||
headers["Content-Type"] = "text/plain; charset=utf-8"
|
||||
end
|
||||
|
||||
# Builds an iCal compatible export of incomplete todos
|
||||
# so that each action forms a VTODO in your iCal calendar.
|
||||
# Due dates are supported, and notes are included.
|
||||
#
|
||||
def ical
|
||||
headers["Content-Type"] = "text/calendar"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Check whether the token in the URL matches the word in the User's table
|
||||
def check_token_against_user_word
|
||||
@user = User.find_by_login( params['login'] )
|
||||
unless ( params['token'] == @user.word)
|
||||
render :text => "Sorry, you don't have permission to view this page."
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def identify_contexts
|
||||
if params.key?('context')
|
||||
@contexts = [ @user.contexts.find_by_params(params) ]
|
||||
else
|
||||
@contexts = @user.contexts.find_all_by_hide(false, "position ASC")
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_for_feed
|
||||
condition_builder = FindConditionBuilder.new
|
||||
options = Hash.new
|
||||
|
||||
if params.key?('done')
|
||||
condition_builder.add 'todos.state = ?', 'completed'
|
||||
else
|
||||
condition_builder.add 'todos.state = ?', 'active'
|
||||
end
|
||||
|
||||
if params.key?('limit')
|
||||
options[:limit] = limit = params['limit']
|
||||
@description = limit ? "Lists the last #{limit} incomplete next actions" : "Lists incomplete next actions"
|
||||
end
|
||||
@title = "Tracks - Next Actions"
|
||||
@description = "Filter: "
|
||||
|
||||
if params.key?('due')
|
||||
due_within = params['due'].to_i
|
||||
due_within_when = @user.time + due_within.days
|
||||
condition_builder.add('todos.due <= ?', due_within_when)
|
||||
due_within_date_s = due_within_when.strftime("%Y-%m-%d")
|
||||
@title << " due today" if (due_within == 0)
|
||||
@title << " due within a week" if (due_within == 6)
|
||||
@description << " with a due date #{due_within_date_s} or earlier"
|
||||
end
|
||||
|
||||
if params.key?('done')
|
||||
done_in_last = params['done'].to_i
|
||||
condition_builder.add('todos.completed_at >= ?', @user.time - done_in_last.days)
|
||||
@title << " actions completed"
|
||||
@description << " in the last #{done_in_last.to_s} days"
|
||||
end
|
||||
|
||||
if params.key?('context')
|
||||
context = @user.contexts.find_by_params(params)
|
||||
condition_builder.add('todos.context_id = ?', context.id)
|
||||
@title << " in #{context.name}"
|
||||
@description << " in context '#{context.name}'"
|
||||
end
|
||||
|
||||
if params.key?('project')
|
||||
project = @user.projects.find_by_params(params)
|
||||
condition_builder.add('todos.project_id = ?', project.id)
|
||||
@title << " for #{project.name}"
|
||||
@description << " for project '#{project.name}'"
|
||||
end
|
||||
|
||||
options[:conditions] = condition_builder.to_conditions
|
||||
options[:order] = "todos.due IS NULL, todos.due ASC, todos.created_at ASC"
|
||||
options[:include] = :project
|
||||
|
||||
@todos = @user.todos.find(:all, options )
|
||||
|
||||
end
|
||||
|
||||
class FindConditionBuilder
|
||||
|
||||
def initialize
|
||||
@queries = Array.new
|
||||
@params = Array.new
|
||||
end
|
||||
|
||||
def add(query, param)
|
||||
@queries << query
|
||||
@params << param
|
||||
end
|
||||
|
||||
def to_conditions
|
||||
[@queries.join(' AND ')] + @params
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
@ -138,14 +138,15 @@ class ProjectsController < ApplicationController
|
|||
def render_rss_feed
|
||||
lambda do
|
||||
render_rss_feed_for @projects, :feed => Project.feed_options(@user),
|
||||
:item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) } }
|
||||
:item => { :title => :name, :description => lambda { |p| summary(p) } }
|
||||
end
|
||||
end
|
||||
|
||||
def render_atom_feed
|
||||
lambda do
|
||||
render_atom_feed_for @projects, :feed => Project.feed_options(@user),
|
||||
:item => { :description => lambda { |p| p.summary(count_undone_todos_phrase(p)) },
|
||||
:item => { :description => lambda { |p| summary(p) },
|
||||
:title => :name,
|
||||
:author => lambda { |p| nil } }
|
||||
end
|
||||
end
|
||||
|
|
@ -181,4 +182,13 @@ class ProjectsController < ApplicationController
|
|||
init_data_for_sidebar
|
||||
end
|
||||
|
||||
def summary(project)
|
||||
project_description = ''
|
||||
project_description += sanitize(markdown( project.description )) unless project.description.blank?
|
||||
project_description += "<p>#{count_undone_todos_phrase(p)}. "
|
||||
project_description += "Project is #{project.state}."
|
||||
project_description += "</p>"
|
||||
project_description
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,33 +3,28 @@ class TodosController < ApplicationController
|
|||
helper :todos
|
||||
|
||||
append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred ]
|
||||
skip_before_filter :login_required, :only => [:index]
|
||||
prepend_before_filter :login_or_feed_token_required, :only => [:index]
|
||||
session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
|
||||
|
||||
layout 'standard'
|
||||
|
||||
# Main method for listing tasks
|
||||
# Set page title, and fill variables with contexts and done and not-done tasks
|
||||
# Number of completed actions to show is determined by a setting in settings.yml
|
||||
def index
|
||||
@projects = @user.projects.find(:all, :include => [ :todos ])
|
||||
@contexts = @user.contexts.find(:all, :include => [ :todos ])
|
||||
|
||||
@page_title = "TRACKS::List tasks"
|
||||
|
||||
# If you've set no_completed to zero, the completed items box
|
||||
# isn't shown on the home page
|
||||
max_completed = @user.prefs.show_number_completed
|
||||
@done = @user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
|
||||
|
||||
|
||||
@contexts_to_show = @contexts.reject {|x| x.hide? }
|
||||
|
||||
# Set count badge to number of not-done, not hidden context items
|
||||
@count = @todos.reject { |x| !x.active? || x.context.hide? }.size
|
||||
|
||||
respond_to do |wants|
|
||||
wants.html
|
||||
wants.xml { render :action => 'list.rxml', :layout => false }
|
||||
respond_to do |format|
|
||||
format.html &render_todos_html
|
||||
format.xml { render :action => 'list.rxml', :layout => false }
|
||||
format.rss &render_rss_feed
|
||||
format.atom &render_atom_feed
|
||||
format.text &render_text_feed
|
||||
format.ics &render_ical_feed
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def create
|
||||
@todo = @user.todos.build
|
||||
p = params['request'] || params
|
||||
|
|
@ -100,7 +95,7 @@ class TodosController < ApplicationController
|
|||
#
|
||||
def toggle_check
|
||||
@todo = check_user_return_todo
|
||||
@todo.toggle_completion()
|
||||
@todo.toggle_completion!
|
||||
@saved = @todo.save
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
|
|
@ -279,13 +274,89 @@ class TodosController < ApplicationController
|
|||
init_data_for_sidebar
|
||||
init_todos
|
||||
end
|
||||
|
||||
def init_todos
|
||||
# Exclude hidden projects from count on home page
|
||||
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ])
|
||||
|
||||
# Exclude hidden projects from the home page
|
||||
@not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ])
|
||||
def with_feed_query_scope(&block)
|
||||
unless TodosController.is_feed_request(request)
|
||||
yield
|
||||
return
|
||||
end
|
||||
|
||||
condition_builder = FindConditionBuilder.new
|
||||
options = Hash.new
|
||||
|
||||
if params.key?('done')
|
||||
condition_builder.add 'todos.state = ?', 'completed'
|
||||
else
|
||||
condition_builder.add 'todos.state = ?', 'active'
|
||||
end
|
||||
|
||||
@title = "Tracks - Next Actions"
|
||||
@description = "Filter: "
|
||||
|
||||
if params.key?('due')
|
||||
due_within = params['due'].to_i
|
||||
due_within_when = @user.time + due_within.days
|
||||
condition_builder.add('todos.due <= ?', due_within_when)
|
||||
due_within_date_s = due_within_when.strftime("%Y-%m-%d")
|
||||
@title << " due today" if (due_within == 0)
|
||||
@title << " due within a week" if (due_within == 6)
|
||||
@description << " with a due date #{due_within_date_s} or earlier"
|
||||
end
|
||||
|
||||
if params.key?('done')
|
||||
done_in_last = params['done'].to_i
|
||||
condition_builder.add('todos.completed_at >= ?', @user.time - done_in_last.days)
|
||||
@title << " actions completed"
|
||||
@description << " in the last #{done_in_last.to_s} days"
|
||||
end
|
||||
|
||||
Todo.with_scope :find => {:conditions => condition_builder.to_conditions} do
|
||||
yield
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def with_parent_resource_scope(&block)
|
||||
if (params[:context_id])
|
||||
context = @user.contexts.find_by_params(params)
|
||||
Todo.with_scope :find => {:conditions => ['todos.context_id = ?', context.id]} do
|
||||
yield
|
||||
end
|
||||
elsif (params[:project_id])
|
||||
project = @user.projects.find_by_params(params)
|
||||
Todo.with_scope :find => {:conditions => ['todos.project_id = ?', project.id]} do
|
||||
yield
|
||||
end
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def with_limit_scope(&block)
|
||||
if params.key?('limit')
|
||||
Todo.with_scope :find => {:limit => params['limit']} do
|
||||
yield
|
||||
end
|
||||
#@description = limit ? "Lists the last #{limit} incomplete next actions" : "Lists incomplete next actions"
|
||||
else
|
||||
yield
|
||||
end
|
||||
end
|
||||
|
||||
def init_todos
|
||||
with_feed_query_scope do
|
||||
with_parent_resource_scope do
|
||||
with_limit_scope do
|
||||
|
||||
# Exclude hidden projects from count on home page
|
||||
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ])
|
||||
|
||||
# Exclude hidden projects from the home page
|
||||
@not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ])
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def determine_down_count
|
||||
|
|
@ -323,5 +394,92 @@ class TodosController < ApplicationController
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def render_todos_html
|
||||
lambda do
|
||||
@page_title = "TRACKS::List tasks"
|
||||
|
||||
# If you've set no_completed to zero, the completed items box
|
||||
# isn't shown on the home page
|
||||
max_completed = @user.prefs.show_number_completed
|
||||
@done = @user.completed_todos.find(:all, :limit => max_completed, :include => [ :context, :project, :tags ]) unless max_completed == 0
|
||||
|
||||
# Set count badge to number of not-done, not hidden context items
|
||||
@count = @todos.reject { |x| !x.active? || x.context.hide? }.size
|
||||
|
||||
render
|
||||
end
|
||||
end
|
||||
|
||||
def render_rss_feed
|
||||
lambda do
|
||||
render_rss_feed_for @todos, :feed => Todo.feed_options(@user),
|
||||
:item => {
|
||||
:title => :description,
|
||||
:link => lambda { |t| context_url(t.context) },
|
||||
:description => todo_feed_content
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def todo_feed_content
|
||||
lambda do |i|
|
||||
item_notes = sanitize(markdown( i.notes )) if i.notes?
|
||||
due = "<div>Due: #{format_date(i.due)}</div>\n" if i.due?
|
||||
done = "<div>Completed: #{format_date(i.completed_at)}</div>\n" if i.completed?
|
||||
context_link = "<a href=\"#{ context_url(i.context) }\">#{ i.context.name }</a>"
|
||||
if i.project_id?
|
||||
project_link = "<a href=\"#{ project_url(i.project) }\">#{ i.project.name }</a>"
|
||||
else
|
||||
project_link = "<em>none</em>"
|
||||
end
|
||||
"#{done||''}#{due||''}#{item_notes||''}\n<div>Project: #{project_link}</div>\n<div>Context: #{context_link}</div>"
|
||||
end
|
||||
end
|
||||
|
||||
def render_atom_feed
|
||||
lambda do
|
||||
render_atom_feed_for @todos, :feed => Todo.feed_options(@user),
|
||||
:item => {
|
||||
:title => :description,
|
||||
:link => lambda { |t| context_url(t.context) },
|
||||
:description => todo_feed_content,
|
||||
:author => lambda { |p| nil }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def render_text_feed
|
||||
lambda do
|
||||
render :action => 'index_text', :layout => false, :content_type => Mime::TEXT
|
||||
end
|
||||
end
|
||||
|
||||
def render_ical_feed
|
||||
lambda do
|
||||
render :action => 'index_ical', :layout => false, :content_type => Mime::ICS
|
||||
end
|
||||
end
|
||||
|
||||
def self.is_feed_request(req)
|
||||
['rss','atom','txt','ics'].include?(req.parameters[:format])
|
||||
end
|
||||
|
||||
class FindConditionBuilder
|
||||
|
||||
def initialize
|
||||
@queries = Array.new
|
||||
@params = Array.new
|
||||
end
|
||||
|
||||
def add(query, param)
|
||||
@queries << query
|
||||
@params << param
|
||||
end
|
||||
|
||||
def to_conditions
|
||||
[@queries.join(' AND ')] + @params
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue