From 0238d4941530febe1fb00be2161a10d04073494e Mon Sep 17 00:00:00 2001 From: Coleman Date: Wed, 15 Oct 2008 01:31:50 -0500 Subject: [PATCH] adding users back into photos for better tracking, adding approve, flag and moderate photos, adding moderate link for administrators in application layout, improving photo controls, improving photo routing, adding a few indexes for photos table --- app/controllers/photos.rb | 40 ++++++++++++++---- app/models/photo.rb | 26 +++++++++++- app/models/photo_flag.rb | 5 +++ app/models/user.rb | 4 +- app/views/layout/application.html.haml | 5 ++- app/views/photos/_photo.html.haml | 41 ++++++++++--------- app/views/photos/moderate.html.haml | 22 ++++++++++ app/views/photos/show.html.haml | 2 +- config/router.rb | 8 +++- public/stylesheets/ba.css | 6 ++- .../migrations/002_create_photos_migration.rb | 5 ++- schema/migrations/007_photo_flag_migration.rb | 16 ++++++++ .../migrations/008_photo_indexes_migration.rb | 13 ++++++ schema/schema.rb | 23 ++++++++--- spec/models/photo_flag_spec.rb | 7 ++++ 15 files changed, 184 insertions(+), 39 deletions(-) create mode 100644 app/models/photo_flag.rb create mode 100644 app/views/photos/moderate.html.haml create mode 100644 schema/migrations/007_photo_flag_migration.rb create mode 100644 schema/migrations/008_photo_indexes_migration.rb create mode 100644 spec/models/photo_flag_spec.rb diff --git a/app/controllers/photos.rb b/app/controllers/photos.rb index a1ed74a..d600174 100644 --- a/app/controllers/photos.rb +++ b/app/controllers/photos.rb @@ -1,8 +1,8 @@ class Photos < Application - before :logged_in?, :only => [ :new, :create, :delete ] - before :administrator?, :only => [ :delete ] + before :logged_in?, :only => [ :new, :create, :destroy ] + before :administrator?, :only => [ :destroy, :approve, :moderate ] before :make_photo, :only => [ :new, :create ] - before :fetch_photo, :only => [ :show, :delete, :thumbnail ] + before :fetch_photo, :only => [ :show, :destroy, :thumbnail, :approve, :flag ] def index @page = params[:page].to_i @@ -25,6 +25,7 @@ class Photos < Application end def create + @photo.user = current_user if @photo.save flash[:notice] = 'Great success' redirect url(:photo, @photo) @@ -33,12 +34,12 @@ class Photos < Application end end - def delete + def destroy raise NotAcceptable unless request.xhr? - if current_user and current_user.administrator? - render + if @photo.destroy + render '', :status => 200 else - redirect '/' + render '', :status => 401 end end @@ -55,6 +56,31 @@ class Photos < Application end end + def approve + raise NotAllowed unless request.xhr? + @photo.approved = true + if @photo.save + render '', :status => 200 + else + render '', :status => 401 + end + end + + def flag + raise NotAllowed unless request.xhr? + pf = PhotoFlag.new :photo_id => @photo.id, :user_id => current_user.id + if pf.save + render '', :status => 200 + else + render '', :status => 401 + end + end + + def moderate + @photos = Photo.find :all, :order => "photo_flags_count DESC, id ASC", :conditions => [ 'approved = ?', false ] + render + end + protected def make_photo diff --git a/app/models/photo.rb b/app/models/photo.rb index 3243e39..fb79081 100644 --- a/app/models/photo.rb +++ b/app/models/photo.rb @@ -3,8 +3,12 @@ class Photo < ActiveRecord::Base attr_accessor :file attr_protected :email_hash + validates_presence_of :user_id + + belongs_to :user has_many :votes, :dependent => :destroy has_many :photo_favorites, :dependent => :destroy + has_many :photo_flags, :dependent => :destroy before_create :validate_image_sanity before_create :hashify_email @@ -41,6 +45,10 @@ class Photo < ActiveRecord::Base "#{self.relative_directory}/#{self.filename}" end + ## + # Finds a Photo that the requested user can vote on by checking it against + # all of the photos previously voted. + # def self.next_available_votable_photo(user) pids = Vote.voted_photo_ids(user) c = if pids.empty? @@ -51,6 +59,18 @@ class Photo < ActiveRecord::Base self.find :first, :conditions => c, :order => 'id ASC' end + ## + # Checks to see if a User or anonymous user has flagged a Photo as unsuitable. + # + def flagged?(user) + f = if user.respond_to?('id') + self.photo_flags.find :first, :conditions => "photo_flags.user_id = #{user.id}" + else + self.photo_flags.find :first, :conditions => "photo_flags.session_id = #{user}" + end + !f.nil? + end + protected ## @@ -132,6 +152,10 @@ class Photo < ActiveRecord::Base # Regenerates the calculated value of oneness. # def set_oneness - self.oneness = (self.one_votes.to_f / self.votes_count.to_f * 100.0) + if self.votes_count == 0 + self.oneness = 0 + else + self.oneness = (self.one_votes.to_f / self.votes_count.to_f * 100.0) + end end end diff --git a/app/models/photo_flag.rb b/app/models/photo_flag.rb new file mode 100644 index 0000000..bbfa518 --- /dev/null +++ b/app/models/photo_flag.rb @@ -0,0 +1,5 @@ +class PhotoFlag < ActiveRecord::Base + belongs_to :photo, :counter_cache => true + belongs_to :user + validates_uniqueness_of :user_id +end diff --git a/app/models/user.rb b/app/models/user.rb index 6724b92..cb2942f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,10 +9,12 @@ class User < ActiveRecord::Base validates_uniqueness_of :user_name validates_format_of :user_name, :with => /[\w_-]+/ + has_many :photos, :dependent => :destroy has_many :votes, :dependent => :destroy, :order => 'votes.photo_id ASC' - has_many :photos, :dependent => :destroy, :through => :votes + has_many :voted_photos, :through => :votes, :class_name => 'Photo', :source => :photo has_many :photo_favorites, :dependent => :destroy has_many :favorite_photos, :through => :photo_favorites, :class_name => 'Photo', :source => :photo + has_many :photo_flags, :dependent => :destroy before_validation :saltify_password diff --git a/app/views/layout/application.html.haml b/app/views/layout/application.html.haml index 9d0a766..191f2aa 100644 --- a/app/views/layout/application.html.haml +++ b/app/views/layout/application.html.haml @@ -21,12 +21,15 @@ - if logged_in? #header_user %p - Welcome, + Welcome, == #{current_user.user_name} %br %a{ :href => url(:delete_session, :id => session[:user_id]), :title => 'Log out of B.A.' } Log out %img{ :src => '/images/system-log-out.png' } + - if administrator? + %br + %a{ :href => url(:moderate_photos) } Moderate #tool_bar - menu_items.each do |menu_item| %a{ :href => menu_item[:href], :title => menu_item[:title] } diff --git a/app/views/photos/_photo.html.haml b/app/views/photos/_photo.html.haml index 1de616d..abec122 100644 --- a/app/views/photos/_photo.html.haml +++ b/app/views/photos/_photo.html.haml @@ -1,22 +1,14 @@ :javascript - function add_favorite(pid) - { - new Ajax.Request('#{url(:favorites)}?photo_id=' + pid, { onSuccess: function(){ $('favorite_add').hide(); $('favorite_remove').show(); new Effect.Highlight($('favorites')); } }); - } - function remove_favorite(del_url) - { - new Ajax.Request(del_url, { onSuccess: function(){ $('favorite_remove').hide(); $('favorite_add').show(); new Effect.Highlight($('favorites')); } }); - } - function transition_out_controls() + function transition_voting_controls() { if($('to_be_unvoted').style.display == 'none') return false; new Effect.DropOut($('to_be_unvoted')); - setTimeout('transition_in_controls()', 1000); + setTimeout("new Effect.Appear($('to_be_voted'));", 1000); } - function transition_in_controls() + function notify_controls() { - new Effect.Appear($('to_be_voted')); + new Effect.Highlight($('photo_controls')); } %style{ :type => "text/css" } @@ -42,18 +34,29 @@ %div.centered{ :style => "width: #{@photo.width rescue 50}px" } - if @photo and @photo.exist? - #photo_controls - - unless Vote.voted_for?(@photo, current_user) or params[:controller] =~ /vote/i - %span#to_be_unvoted - %a{ :href => '#', :onclick => "transition_out_controls(); return false;" } Vote! + - if logged_in? + #photo_controls + - unless Vote.voted_for?(@photo, current_user) or params[:controller] =~ /vote/i + %span#to_be_unvoted + %a{ :href => '#', :onclick => "transition_voting_controls(); return false;" } Vote! + | + - if !@photo.approved? and !@photo.flagged?(current_user) + %span#to_be_flagged + %a{ :href => '#', :onclick => "new Ajax.Request('#{url(:flag_photo, @photo)}', { method: 'get', onSuccess: function() { $('to_be_flagged').hide(); notify_controls(); } }); return false;" } Flag inappropriate + | + - if !@photo.approved? and administrator? + %span#to_be_approved + %a{ :href => '#', :onclick => "new Ajax.Request('#{url(:approve_photo, @photo)}', { method: 'get', onSuccess: function() { $('to_be_approved').hide(); if($('to_be_flagged')){$('to_be_flagged').hide();}; notify_controls(); } }); return false;" } Approve + | + - if administrator? + %a{ :href => '#', :onclick => "if(confirm('Are you sure you want to destroy this photo?')){ new Ajax.Request('#{url(:photo, @photo)}', { method: 'delete', onSuccess: function() { window.location.href = '#{url(:photos)}'; } }); return false; }" } Destroy | - - if logged_in? %span#favorites %span#favorite_add{ :style => (current_user.photo_favorites.detect { |f| f.photo_id == @photo.id }.nil? ? '' : "display: none;") } - %a{ :href => '#', :onclick => "add_favorite(#{@photo.id}); return false;" } + %a{ :href => '#', :onclick => "new Ajax.Request('#{url(:favorites, :photo_id => @photo.id)}', { onSuccess: function(){ $('favorite_add').hide(); new Effect.Appear($('favorite_remove')); notify_controls(); } }); return false;" } Add as favorite %span#favorite_remove{ :style => (current_user.photo_favorites.detect { |f| f.photo_id == @photo.id }.nil? ? "display: none;" : '') } - %a{ :href => '#', :onclick => "remove_favorite('#{url(:favorite, :id => @photo.id)}?_method=delete'); return false;" } + %a{ :href => '#', :onclick => "new Ajax.Request('#{url(:favorite, :id => @photo.id)}', { method: 'delete', onSuccess: function(){ $('favorite_remove').hide(); new Effect.Appear($('favorite_add')); notify_controls(); } }); return false;" } Remove favorite %img{ :src => @photo.pathname, :alt => @photo.filename, :width => @photo.width, :height => @photo.height } - else diff --git a/app/views/photos/moderate.html.haml b/app/views/photos/moderate.html.haml new file mode 100644 index 0000000..9aedfc5 --- /dev/null +++ b/app/views/photos/moderate.html.haml @@ -0,0 +1,22 @@ +%style{ :type => 'text/css' } + :sass + .moderate_box + :width 310px + :height 320px + :margin 10px auto + :text-align center + img + :display block + :margin-left auto + :margin-right auto + +%h1 Photo moderation panel + +- @photos.each do |photo| + %div.moderate_box{ :id => "photo_#{photo.id}" } + %img{ :src => photo_url(photo, 300, 300) } + %a{ :href => '#', :onclick => "new Ajax.Request('#{url(:approve_photo, photo)}', { method: 'get', onSuccess: function() { new Effect.DropOut($('photo_#{photo.id}')); } }); return false;" } Approve + | + == #{photo.photo_flags_count} Flags + | + %a{ :href => '#', :onclick => "if(confirm('Are you sure you want to destroy this photo?')){ new Ajax.Request('#{url(:photo, photo)}', { method: 'delete', onSuccess: function() { new Effect.DropOut($('photo_#{photo.id}')); } }); return false; }" } Destroy diff --git a/app/views/photos/show.html.haml b/app/views/photos/show.html.haml index 73fed15..512527a 100644 --- a/app/views/photos/show.html.haml +++ b/app/views/photos/show.html.haml @@ -1,6 +1,6 @@ #main_photo_container = partial 'photos/photo' - + - v = Vote.voted_for? @photo, current_user - if v #mini_container.centered diff --git a/config/router.rb b/config/router.rb index 22bd717..6d16863 100644 --- a/config/router.rb +++ b/config/router.rb @@ -11,5 +11,11 @@ Merb::Router.prepare do |r| r.resources :users r.resources :votes r.resources :favorites - r.resources :photos, :member => { :thumbnail => :get } + r.resources :photos, :member => { + :thumbnail => :get, + :flag => :get, + :approve => :get + }, :collection => { + :moderate => :get + } end diff --git a/public/stylesheets/ba.css b/public/stylesheets/ba.css index fe7fe06..340f31d 100644 --- a/public/stylesheets/ba.css +++ b/public/stylesheets/ba.css @@ -85,6 +85,10 @@ h1 img { top: 10px; } +#header_user a:hover { + font-weight: bold; +} + #tool_bar { margin-top: 7px; margin-bottom: 10x; @@ -118,7 +122,7 @@ h1 img { #footer { font-size: 12px; height: 25px; - width: 90%; + width: 700px; border-top: 2px solid #e8e8e8; text-align: right; padding: 5px 0 10px 0; diff --git a/schema/migrations/002_create_photos_migration.rb b/schema/migrations/002_create_photos_migration.rb index cb426f2..6f93288 100644 --- a/schema/migrations/002_create_photos_migration.rb +++ b/schema/migrations/002_create_photos_migration.rb @@ -2,11 +2,12 @@ class CreatePhotosMigration < ActiveRecord::Migration def self.up create_table :photos do |t| t.string :filename, :content_type, :email_hash - t.integer :width, :height + t.integer :width, :height, :user_id t.datetime :created_at - t.boolean :approved + t.boolean :approved, :default => false end add_index :photos, :email_hash + add_index :photos, :user_id end def self.down diff --git a/schema/migrations/007_photo_flag_migration.rb b/schema/migrations/007_photo_flag_migration.rb new file mode 100644 index 0000000..3d1007c --- /dev/null +++ b/schema/migrations/007_photo_flag_migration.rb @@ -0,0 +1,16 @@ +class PhotoFlagMigration < ActiveRecord::Migration + def self.up + create_table :photo_flags do |t| + t.integer :user_id, :photo_id + t.string :session_id + end + add_index :photo_flags, :user_id + add_index :photo_flags, :photo_id + add_column :photos, :photo_flags_count, :integer, :default => 0 + end + + def self.down + drop_table :photo_flags + remove_column :photos, :photo_flags_count + end +end diff --git a/schema/migrations/008_photo_indexes_migration.rb b/schema/migrations/008_photo_indexes_migration.rb new file mode 100644 index 0000000..cfe96b2 --- /dev/null +++ b/schema/migrations/008_photo_indexes_migration.rb @@ -0,0 +1,13 @@ +class PhotoIndexesMigration < ActiveRecord::Migration + def self.up + add_index :photos, :votes_count + add_index :photos, :oneness + add_index :photos, :approved + end + + def self.down + remove_index :photos, :votes_count + remove_index :photos, :oneness + remove_index :photos, :approved + end +end diff --git a/schema/schema.rb b/schema/schema.rb index 3df8c8e..e7de130 100644 --- a/schema/schema.rb +++ b/schema/schema.rb @@ -9,7 +9,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 6) do +ActiveRecord::Schema.define(:version => 8) do create_table "photo_favorites", :force => true do |t| t.integer "photo_id" @@ -19,6 +19,15 @@ ActiveRecord::Schema.define(:version => 6) do add_index "photo_favorites", ["user_id"], :name => "index_photo_favorites_on_user_id" add_index "photo_favorites", ["photo_id"], :name => "index_photo_favorites_on_photo_id" + create_table "photo_flags", :force => true do |t| + t.integer "user_id" + t.integer "photo_id" + t.string "session_id" + end + + add_index "photo_flags", ["photo_id"], :name => "index_photo_flags_on_photo_id" + add_index "photo_flags", ["user_id"], :name => "index_photo_flags_on_user_id" + create_table "photos", :force => true do |t| t.string "filename" t.string "content_type" @@ -26,13 +35,17 @@ ActiveRecord::Schema.define(:version => 6) do t.integer "width" t.integer "height" t.datetime "created_at" - t.boolean "approved" - t.integer "votes_count", :default => 0 - t.integer "one_votes", :default => 0 - t.integer "zero_votes", :default => 0 + t.boolean "approved", :default => false + t.integer "votes_count", :default => 0 + t.integer "one_votes", :default => 0 + t.integer "zero_votes", :default => 0 t.float "oneness" + t.integer "photo_flags_count", :default => 0 end + add_index "photos", ["approved"], :name => "index_photos_on_approved" + add_index "photos", ["oneness"], :name => "index_photos_on_oneness" + add_index "photos", ["votes_count"], :name => "index_photos_on_votes_count" add_index "photos", ["email_hash"], :name => "index_photos_on_email_hash" create_table "sessions", :force => true do |t| diff --git a/spec/models/photo_flag_spec.rb b/spec/models/photo_flag_spec.rb new file mode 100644 index 0000000..a173605 --- /dev/null +++ b/spec/models/photo_flag_spec.rb @@ -0,0 +1,7 @@ +require File.join( File.dirname(__FILE__), '..', "spec_helper" ) + +describe PhotoFlag do + + it "should have specs" + +end \ No newline at end of file