From 2194956a33501f12cc068adc3b210ef74248643a Mon Sep 17 00:00:00 2001 From: ioBridge Date: Tue, 5 Apr 2011 16:26:52 -0400 Subject: [PATCH] adding csv import to channels --- app/controllers/channels_controller.rb | 145 +++++++++++++++++++++++++ app/views/channels/import.html.erb | 16 +++ app/views/channels/show.html.erb | 21 ++-- config/routes.rb | 4 + 4 files changed, 179 insertions(+), 7 deletions(-) create mode 100644 app/views/channels/import.html.erb diff --git a/app/controllers/channels_controller.rb b/app/controllers/channels_controller.rb index baf6434..eb1e2ac 100644 --- a/app/controllers/channels_controller.rb +++ b/app/controllers/channels_controller.rb @@ -2,6 +2,7 @@ class ChannelsController < ApplicationController before_filter :require_user, :except => [ :show, :post_data ] before_filter :set_channels_menu protect_from_forgery :except => :post_data + require 'csv' def index @channels = current_user.channels @@ -72,6 +73,10 @@ class ChannelsController < ApplicationController f.delete end + # set the channel's last_entry_id to nil + channel.last_entry_id = nil + channel.save + redirect_to channels_path end @@ -146,4 +151,144 @@ class ChannelsController < ApplicationController render :text => status end + + # import view + def import + get_channel_data + end + + # upload csv file to channel + def upload + # if no data + render :text => t(:select_file) and return if params[:upload].blank? or params[:upload][:csv].blank? + + channel = Channel.find(params[:channel_id]) + channel_id = channel.id + # make sure channel belongs to current user + check_permissions(channel) + + # set time zone + Time.zone = params[:feed][:time_zone] + + # read data from uploaded file + csv_array = CSV.parse(params[:upload][:csv].read) + + # does the column have headers + headers = has_headers?(csv_array) + + # remember the column positions + entry_id_column = -1 + latitude_column = -1 + longitude_column = -1 + elevation_column = -1 + status_column = -1 + if headers + csv_array[0].each_with_index do |column, index| + entry_id_column = index if column.downcase == 'entry_id' + latitude_column = index if column.downcase == 'latitude' + longitude_column = index if column.downcase == 'longitude' + elevation_column = index if column.downcase == 'elevation' + status_column = index if column.downcase == 'status' + end + end + + # delete the first row if it contains headers + csv_array.delete_at(0) if headers + + # determine if the date can be parsed + parse_date = date_parsable?(csv_array[0][0]) + + # if 2 or more rows + if !csv_array[1].blank? + date1 = parse_date ? Time.parse(csv_array[0][0]) : Time.at(csv_array[0][0]) + date2 = parse_date ? Time.parse(csv_array[1][0]) : Time.at(csv_array[1][0]) + + # reverse the array if 1st date is larger than 2nd date + csv_array = csv_array.reverse if date1 > date2 + end + + # loop through each row + csv_array.each do |row| + # if row isn't blank + if !row.blank? + feed = Feed.new + + # set location and status then delete the rows + # these 4 deletes must be performed in the proper (reverse) order + feed.status = row.delete_at(status_column) if status_column > 0 + feed.elevation = row.delete_at(elevation_column) if elevation_column > 0 + feed.longitude = row.delete_at(longitude_column) if longitude_column > 0 + feed.latitude = row.delete_at(latitude_column) if latitude_column > 0 + + # remove entry_id column if necessary + row.delete_at(entry_id_column) if entry_id_column > 0 + + # update entry_id for channel and feed + entry_id = channel.last_entry_id.nil? ? 1 : channel.last_entry_id + 1 + channel.last_entry_id = entry_id + feed.entry_id = entry_id + + # set feed data + feed.channel_id = channel_id + feed.created_at = parse_date ? Time.zone.parse(row[0]) : Time.zone.at(row[0].to_f) + feed.raw_data = row.to_s + feed.field1 = row[1] + feed.field2 = row[2] + feed.field3 = row[3] + feed.field4 = row[4] + feed.field5 = row[5] + feed.field6 = row[6] + feed.field7 = row[7] + feed.field8 = row[8] + + # save channel and feed + feed.save + channel.save + + end + end + + # set the user's time zone back + set_time_zone(params) + + # redirect + redirect_to channel_path(channel.id) + end + + # determine if the date can be parsed + def date_parsable?(date) + return !is_a_number?(date) + end + + # determine if the csv file has headers + def has_headers?(csv_array) + headers = false + + # if there are at least 2 rows + if (csv_array[0] and csv_array[1]) + row0_integers = 0 + row1_integers = 0 + + # if first row, first value contains 'create' or 'date', assume it has headers + if (csv_array[0][0].downcase.include?('create') or csv_array[0][0].downcase.include?('date')) + headers = true + else + # count integers in row0 + csv_array[0].each_with_index do |value, i| + row0_integers += 1 if is_a_number?(value) + end + + # count integers in row1 + csv_array[1].each_with_index do |value, i| + row1_integers += 1 if is_a_number?(value) + end + + # if row1 has more integers, assume row0 is headers + headers = true if row1_integers > row0_integers + end + end + + return headers + end + end \ No newline at end of file diff --git a/app/views/channels/import.html.erb b/app/views/channels/import.html.erb new file mode 100644 index 0000000..3b4d433 --- /dev/null +++ b/app/views/channels/import.html.erb @@ -0,0 +1,16 @@ +

+ <%= link_to t(:channels), channels_path %> » + <%= link_to channel_path(@channel.id) do %> <%= t(:channel) %> <%= @channel.id %><% end %> » + <%= t(:import) %> +

+ +<%= t(:upload_select) %> +

+<%= form_for :upload, :url => {:controller => 'channels', :action => 'upload', :channel_id => params[:channel_id]}, :html => { :multipart => true } do |f| %> + <%= f.file_field :csv %> +

+ <%= t(:time_zone) %> + <%= time_zone_select 'feed', 'time_zone', nil, :default => 'UTC' %> +

+ <%= f.submit t(:upload), :disable_with => t(:uploading) %> +<% end %> diff --git a/app/views/channels/show.html.erb b/app/views/channels/show.html.erb index 87c9de1..4c09d9f 100644 --- a/app/views/channels/show.html.erb +++ b/app/views/channels/show.html.erb @@ -4,13 +4,16 @@ <% if current_user && current_user.id == @channel.user_id %> - <%= link_to t(:channel_edit), edit_channel_path(@channel.id) %> | - <%= link_to t(:api_keys_manage), channel_api_keys_path(@channel) %> | - <%= link_to "#{t(:charts_view)}", channel_charts_path(@channel) %> | - <%= link_to "#{t(:channel_feed)} (json)", channel_feed_index_path(@channel, :key => @key, :format => :json) %> | - <%= link_to "#{t(:channel_feed)} (xml)", channel_feed_index_path(@channel, :key => @key, :format => :xml) %> | - <%= link_to "#{t(:channel_feed)} (csv)", channel_feed_index_path(@channel, :key => @key, :format => :csv) %> - + +

@@ -30,6 +33,10 @@ + + + + diff --git a/config/routes.rb b/config/routes.rb index 2f16e30..b3f5880 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,6 +17,10 @@ Thingspeak::Application.routes.draw do match 'channels/:channel_id/field/:field_id(.:format)' => 'feed#index' match 'channels/:channel_id/feed/entry/:id(.:format)' => 'feed#show' + # import + match 'channels/:channel_id/import' => 'channels#import', :as => 'channel_import' + match 'channels/:channel_id/upload' => 'channels#upload' + # nest feeds into channels resources :channels do resources :feed
<%= t(:channel_description) %>: <%= @channel.description %>
<%= t(:entries) %>:<%= (@channel.last_entry_id) ? @channel.last_entry_id : '0' %>
<%= t(:created) %>: <%= l @channel.created_at, :format => :pretty %>