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
parent
6580414459
commit
0238d49415
|
@ -1,8 +1,8 @@
|
||||||
class Photos < Application
|
class Photos < Application
|
||||||
before :logged_in?, :only => [ :new, :create, :delete ]
|
before :logged_in?, :only => [ :new, :create, :destroy ]
|
||||||
before :administrator?, :only => [ :delete ]
|
before :administrator?, :only => [ :destroy, :approve, :moderate ]
|
||||||
before :make_photo, :only => [ :new, :create ]
|
before :make_photo, :only => [ :new, :create ]
|
||||||
before :fetch_photo, :only => [ :show, :delete, :thumbnail ]
|
before :fetch_photo, :only => [ :show, :destroy, :thumbnail, :approve, :flag ]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@page = params[:page].to_i
|
@page = params[:page].to_i
|
||||||
|
@ -25,6 +25,7 @@ class Photos < Application
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@photo.user = current_user
|
||||||
if @photo.save
|
if @photo.save
|
||||||
flash[:notice] = 'Great success'
|
flash[:notice] = 'Great success'
|
||||||
redirect url(:photo, @photo)
|
redirect url(:photo, @photo)
|
||||||
|
@ -33,12 +34,12 @@ class Photos < Application
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete
|
def destroy
|
||||||
raise NotAcceptable unless request.xhr?
|
raise NotAcceptable unless request.xhr?
|
||||||
if current_user and current_user.administrator?
|
if @photo.destroy
|
||||||
render
|
render '', :status => 200
|
||||||
else
|
else
|
||||||
redirect '/'
|
render '', :status => 401
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -55,6 +56,31 @@ class Photos < Application
|
||||||
end
|
end
|
||||||
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
|
protected
|
||||||
|
|
||||||
def make_photo
|
def make_photo
|
||||||
|
|
|
@ -3,8 +3,12 @@ class Photo < ActiveRecord::Base
|
||||||
attr_accessor :file
|
attr_accessor :file
|
||||||
attr_protected :email_hash
|
attr_protected :email_hash
|
||||||
|
|
||||||
|
validates_presence_of :user_id
|
||||||
|
|
||||||
|
belongs_to :user
|
||||||
has_many :votes, :dependent => :destroy
|
has_many :votes, :dependent => :destroy
|
||||||
has_many :photo_favorites, :dependent => :destroy
|
has_many :photo_favorites, :dependent => :destroy
|
||||||
|
has_many :photo_flags, :dependent => :destroy
|
||||||
|
|
||||||
before_create :validate_image_sanity
|
before_create :validate_image_sanity
|
||||||
before_create :hashify_email
|
before_create :hashify_email
|
||||||
|
@ -41,6 +45,10 @@ class Photo < ActiveRecord::Base
|
||||||
"#{self.relative_directory}/#{self.filename}"
|
"#{self.relative_directory}/#{self.filename}"
|
||||||
end
|
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)
|
def self.next_available_votable_photo(user)
|
||||||
pids = Vote.voted_photo_ids(user)
|
pids = Vote.voted_photo_ids(user)
|
||||||
c = if pids.empty?
|
c = if pids.empty?
|
||||||
|
@ -51,6 +59,18 @@ class Photo < ActiveRecord::Base
|
||||||
self.find :first, :conditions => c, :order => 'id ASC'
|
self.find :first, :conditions => c, :order => 'id ASC'
|
||||||
end
|
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
|
protected
|
||||||
|
|
||||||
##
|
##
|
||||||
|
@ -132,6 +152,10 @@ class Photo < ActiveRecord::Base
|
||||||
# Regenerates the calculated value of oneness.
|
# Regenerates the calculated value of oneness.
|
||||||
#
|
#
|
||||||
def set_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
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
class PhotoFlag < ActiveRecord::Base
|
||||||
|
belongs_to :photo, :counter_cache => true
|
||||||
|
belongs_to :user
|
||||||
|
validates_uniqueness_of :user_id
|
||||||
|
end
|
|
@ -9,10 +9,12 @@ class User < ActiveRecord::Base
|
||||||
validates_uniqueness_of :user_name
|
validates_uniqueness_of :user_name
|
||||||
validates_format_of :user_name, :with => /[\w_-]+/
|
validates_format_of :user_name, :with => /[\w_-]+/
|
||||||
|
|
||||||
|
has_many :photos, :dependent => :destroy
|
||||||
has_many :votes, :dependent => :destroy, :order => 'votes.photo_id ASC'
|
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 :photo_favorites, :dependent => :destroy
|
||||||
has_many :favorite_photos, :through => :photo_favorites, :class_name => 'Photo', :source => :photo
|
has_many :favorite_photos, :through => :photo_favorites, :class_name => 'Photo', :source => :photo
|
||||||
|
has_many :photo_flags, :dependent => :destroy
|
||||||
|
|
||||||
before_validation :saltify_password
|
before_validation :saltify_password
|
||||||
|
|
||||||
|
|
|
@ -21,12 +21,15 @@
|
||||||
- if logged_in?
|
- if logged_in?
|
||||||
#header_user
|
#header_user
|
||||||
%p
|
%p
|
||||||
Welcome,
|
Welcome,
|
||||||
== <strong>#{current_user.user_name}</strong>
|
== <strong>#{current_user.user_name}</strong>
|
||||||
%br
|
%br
|
||||||
%a{ :href => url(:delete_session, :id => session[:user_id]), :title => 'Log out of B.A.' }
|
%a{ :href => url(:delete_session, :id => session[:user_id]), :title => 'Log out of B.A.' }
|
||||||
Log out
|
Log out
|
||||||
%img{ :src => '/images/system-log-out.png' }
|
%img{ :src => '/images/system-log-out.png' }
|
||||||
|
- if administrator?
|
||||||
|
%br
|
||||||
|
%a{ :href => url(:moderate_photos) } Moderate
|
||||||
#tool_bar
|
#tool_bar
|
||||||
- menu_items.each do |menu_item|
|
- menu_items.each do |menu_item|
|
||||||
%a{ :href => menu_item[:href], :title => menu_item[:title] }
|
%a{ :href => menu_item[:href], :title => menu_item[:title] }
|
||||||
|
|
|
@ -1,22 +1,14 @@
|
||||||
:javascript
|
:javascript
|
||||||
function add_favorite(pid)
|
function transition_voting_controls()
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
if($('to_be_unvoted').style.display == 'none')
|
if($('to_be_unvoted').style.display == 'none')
|
||||||
return false;
|
return false;
|
||||||
new Effect.DropOut($('to_be_unvoted'));
|
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" }
|
%style{ :type => "text/css" }
|
||||||
|
@ -42,18 +34,29 @@
|
||||||
|
|
||||||
%div.centered{ :style => "width: #{@photo.width rescue 50}px" }
|
%div.centered{ :style => "width: #{@photo.width rescue 50}px" }
|
||||||
- if @photo and @photo.exist?
|
- if @photo and @photo.exist?
|
||||||
#photo_controls
|
- if logged_in?
|
||||||
- unless Vote.voted_for?(@photo, current_user) or params[:controller] =~ /vote/i
|
#photo_controls
|
||||||
%span#to_be_unvoted
|
- unless Vote.voted_for?(@photo, current_user) or params[:controller] =~ /vote/i
|
||||||
%a{ :href => '#', :onclick => "transition_out_controls(); return false;" } Vote!
|
%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#favorites
|
||||||
%span#favorite_add{ :style => (current_user.photo_favorites.detect { |f| f.photo_id == @photo.id }.nil? ? '' : "display: none;") }
|
%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
|
Add as favorite
|
||||||
%span#favorite_remove{ :style => (current_user.photo_favorites.detect { |f| f.photo_id == @photo.id }.nil? ? "display: none;" : '') }
|
%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
|
Remove favorite
|
||||||
%img{ :src => @photo.pathname, :alt => @photo.filename, :width => @photo.width, :height => @photo.height }
|
%img{ :src => @photo.pathname, :alt => @photo.filename, :width => @photo.width, :height => @photo.height }
|
||||||
- else
|
- else
|
||||||
|
|
|
@ -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
|
|
@ -1,6 +1,6 @@
|
||||||
#main_photo_container
|
#main_photo_container
|
||||||
= partial 'photos/photo'
|
= partial 'photos/photo'
|
||||||
|
|
||||||
- v = Vote.voted_for? @photo, current_user
|
- v = Vote.voted_for? @photo, current_user
|
||||||
- if v
|
- if v
|
||||||
#mini_container.centered
|
#mini_container.centered
|
||||||
|
|
|
@ -11,5 +11,11 @@ Merb::Router.prepare do |r|
|
||||||
r.resources :users
|
r.resources :users
|
||||||
r.resources :votes
|
r.resources :votes
|
||||||
r.resources :favorites
|
r.resources :favorites
|
||||||
r.resources :photos, :member => { :thumbnail => :get }
|
r.resources :photos, :member => {
|
||||||
|
:thumbnail => :get,
|
||||||
|
:flag => :get,
|
||||||
|
:approve => :get
|
||||||
|
}, :collection => {
|
||||||
|
:moderate => :get
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -85,6 +85,10 @@ h1 img {
|
||||||
top: 10px;
|
top: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#header_user a:hover {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
#tool_bar {
|
#tool_bar {
|
||||||
margin-top: 7px;
|
margin-top: 7px;
|
||||||
margin-bottom: 10x;
|
margin-bottom: 10x;
|
||||||
|
@ -118,7 +122,7 @@ h1 img {
|
||||||
#footer {
|
#footer {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
height: 25px;
|
height: 25px;
|
||||||
width: 90%;
|
width: 700px;
|
||||||
border-top: 2px solid #e8e8e8;
|
border-top: 2px solid #e8e8e8;
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding: 5px 0 10px 0;
|
padding: 5px 0 10px 0;
|
||||||
|
|
|
@ -2,11 +2,12 @@ class CreatePhotosMigration < ActiveRecord::Migration
|
||||||
def self.up
|
def self.up
|
||||||
create_table :photos do |t|
|
create_table :photos do |t|
|
||||||
t.string :filename, :content_type, :email_hash
|
t.string :filename, :content_type, :email_hash
|
||||||
t.integer :width, :height
|
t.integer :width, :height, :user_id
|
||||||
t.datetime :created_at
|
t.datetime :created_at
|
||||||
t.boolean :approved
|
t.boolean :approved, :default => false
|
||||||
end
|
end
|
||||||
add_index :photos, :email_hash
|
add_index :photos, :email_hash
|
||||||
|
add_index :photos, :user_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.down
|
def self.down
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -9,7 +9,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended to check this file into your version control system.
|
# 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|
|
create_table "photo_favorites", :force => true do |t|
|
||||||
t.integer "photo_id"
|
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", ["user_id"], :name => "index_photo_favorites_on_user_id"
|
||||||
add_index "photo_favorites", ["photo_id"], :name => "index_photo_favorites_on_photo_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|
|
create_table "photos", :force => true do |t|
|
||||||
t.string "filename"
|
t.string "filename"
|
||||||
t.string "content_type"
|
t.string "content_type"
|
||||||
|
@ -26,13 +35,17 @@ ActiveRecord::Schema.define(:version => 6) do
|
||||||
t.integer "width"
|
t.integer "width"
|
||||||
t.integer "height"
|
t.integer "height"
|
||||||
t.datetime "created_at"
|
t.datetime "created_at"
|
||||||
t.boolean "approved"
|
t.boolean "approved", :default => false
|
||||||
t.integer "votes_count", :default => 0
|
t.integer "votes_count", :default => 0
|
||||||
t.integer "one_votes", :default => 0
|
t.integer "one_votes", :default => 0
|
||||||
t.integer "zero_votes", :default => 0
|
t.integer "zero_votes", :default => 0
|
||||||
t.float "oneness"
|
t.float "oneness"
|
||||||
|
t.integer "photo_flags_count", :default => 0
|
||||||
end
|
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"
|
add_index "photos", ["email_hash"], :name => "index_photos_on_email_hash"
|
||||||
|
|
||||||
create_table "sessions", :force => true do |t|
|
create_table "sessions", :force => true do |t|
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
require File.join( File.dirname(__FILE__), '..', "spec_helper" )
|
||||||
|
|
||||||
|
describe PhotoFlag do
|
||||||
|
|
||||||
|
it "should have specs"
|
||||||
|
|
||||||
|
end
|
Reference in New Issue