くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Django/PythonでCSVファイルをアップロード(.csv)したり、ダウンロード(.csv)したり、ダウンロード(.zip)したりする方法

Django/Pytonを使ってCSVファイルをあれこれするために、いろいろ調べたので、その際の備忘録。

環境はDjango1.10.1とPython2.7

CSVファイルの扱いは、基本的にimport csvのパッケージ

okadateさんのQiita記事にあるとおり、PythonでのCSVファイルの操作は以下な感じ。

import csv

# 読み込み
with open('some.csv', 'r') as f:
  reader = csv.reader(f)
  header = next(reader)  # ヘッダーを読み飛ばしたい時
  for row in reader:
    print row          # 1行づつ取得できる

# 書き込み
with open('some.csv', 'w') as f:
  writer = csv.writer(f, lineterminator='\n') # 改行コード(\n)を指定しておく
  writer.writerow(list)     # list(1次元配列)の場合
  writer.writerows(array2d) # 2次元配列も書き込める

Django上でこれらを行うときは、リクエストとレスポンスも絡んでくる。

アップロードされたCSVファイルを読み込んで処理する

アップロードされたCSVファイルを読み込む場合は、open()request.FILES['file']になる感じ。

import csv
import traceback
from django.shortcuts import render

def upload_file(request):
  try:
    csv_file = request.FILES['file']

    reader = csv.reader(req_file)
    header = next(reader)

    for csv_row in reader:
      print csv_row 

    csv_file .close()
  except:
    traceback.format_exc()

  return render(request, "index.html")

CSVファイルをダウンロードする

基本的には、csv.writerを使って、`responseに書き込んでいく感じ。
ただ、日本語の場合は、ファイル名とファイルの中身の文字コードに注意!!

  • ファイル名が日本語の場合、UTF8でURLエンコードする
  • ファイルの中身が日本語の場合、環境に合わせる。(例はWindows用としてSJISに変換)
import csv
import urllib
from django.http.response import HttpResponse

def download_csv(request):
  filename = urllib.quote((u'CSVファイル.csv').encode("utf8"))

  response = HttpResponse(content_type='text/csv')  
  response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}'.format(filename)

  writer = csv.writer(response)

  writer.writerow(map(lambda n:toSJIS(n), [u'概要', u'詳細']))
  writer.writerow(map(lambda n:toSJIS(n), [u'ああああ', u'いいい']))
  writer.writerow(map(lambda n:toSJIS(n), [u'かかかか', u'ききき']))

  return response

def toSJIS(s=""):
  u'{}'.format(s).encode("sjis")

複数のCSVファイルをzipにまとめてダウンロードする

こちらも基本はcsv.writerを使ってzip_fileに書き込み、zip_fileの内容をresponseに設定する感じ。
注意点はこちらも文字コード周り。。。

  • ZIPファイル名が日本語の場合、UTF8でURLエンコードする
  • CSVファイル名が日本語の場合、環境に合わせる。URLエンコードは不要(例はWindows用としてSJISに変換)
  • CSVファイルの中身が日本語の場合、環境に合わせる。(例はWindows用としてSJISに変換)
import csv
import urllib
import zipfile
from django.http.response import HttpResponse

def download_csv_zip(request):
  #### 書き込むzipファイルの準備
  memory_file = BytesIO()
  zip_file = zipfile.ZipFile(memory_file, 'w')

  #### CSVファイルの書き込み。1ファイル目
  csv_file1 = BytesIO()
  filename1 = (u'CSVファイル1.csv').encode("sjis")
  writer1 = csv.writer(csv_file1)

  writer1.writerow(map(lambda n:toSJIS(n), [u'概要', u'詳細']))
  writer1.writerow(map(lambda n:toSJIS(n), [u'ああああ', u'いいい']))

  ## CSVファイルの内容をzip_fileに書き込む。
  zip_file.writestr(filename1, csv_file1.getvalue())
  csv_file1.close()

  #### CSVファイルの書き込み。2ファイル目
  csv_file2 = BytesIO()
  filename2 = (u'CSVファイル2.csv').encode("sjis")
  writer2 = csv.writer(csv_file2)

  writer2.writerow(map(lambda n:toSJIS(n), [u'概要', u'詳細']))
  writer2.writerow(map(lambda n:toSJIS(n), [u'ああああ', u'いいい']))

  ## CSVファイルの内容をzip_fileに書き込む。
  zip_file.writestr(filename2, csv_file2.getvalue())
  csv_file2.close()
  

  #### zipファイルの内容をreponseに設定
  zip_file.close()
  zip_filename = urllib.quote((u'CSVファイル(複数).zip').encode("utf8"))
  response = HttpResponse(memory_file.getvalue(), content_type='application/zip')
  response['Content-Disposition'] = 'attachment; filename*=UTF-8\'\'{}'.format(urllib.quote(zip_file_name))

  return response

def toSJIS(s=""):
  u'{}'.format(s).encode("sjis")

以上!!

Python、さくっと書くのはいいんだけど、静的型付け言語が好きなので、結構困る。。。

参考にしたサイト様

Django x jQuery x Ajaxで非同期にファイルをアップロードしてサーバ側で処理をする

前に記事で書いた処理のファイルバージョン。

wannabe-jellyfish.hatenablog.com

見た目的にはこんな感じ。

f:id:wannabe-jellyfish:20170625085507p:plain

HTML側

<!-- フォーム部分 -->
<form id="form" class="form" action="upload_file" method="post" enctype="multipart/form-data">
  {% csrf_token %}
  <div class="input-group">
    <input type="text" class="form-control" readonly="">
    <label class="input-group-btn">
      <span class="btn btn-success">
          Choose File<input type="file" name="file" style="display:none">
      </span>
    </label>
  </div>
</form>

<!-- Ajaxで返ってきた結果を挿入する部分 -->
<div id="result-table" class="row result-table"></div>

<!-- 読み込み中のローディング画像 -->
<div id="loading-div" class="row" style="display:none">
  <img src="{% static 'svg/loading.svg' %}" class="center-block">
</div>

ローディング画像はLOADING.IOで作成
ファイルのフォームは、ynkさんのQiitaを参考に良い感じに。

javascript

//formのinput[type="file"]が変更時の処理
$(document).on('change', ':file', function() {
  // input[type="file"]のファイル名をinput[type="text"]に表示する処理
  var input = $(this);
  numFiles = input.get(0).files ? input.get(0).files.length : 1;
  label = input.val().replace(/\\/g, '/').replace(/.*\//, '');
  input.parent().parent().prev(':text').val(label);

  //Ajaxはここから
  $.ajax({
      url: $("#form").attr("action"),
      type: 'POST',
      data: new FormData($("#form").get(0)),
      processData: false,
      contentType: false,
      beforeSend: function(xhr, settings) {
         //リクエスト送信前の処理
         // CSRFTokenを設定したり、前の結果を削除したり、ローディングを表示したり
         xhr.setRequestHeader("X-CSRFToken", $("input[name='csrfmiddlewaretoken']").val());
         $('#result-table').empty();
         $("#loading-div").show();
      },
  }).done(function(data, textStatus, jqXHR){
      // 成功したら、結果を追加する
      $tbody.append($(data));
  }).fail(function(jqXHR, textStatus, errorThrown){
      // 失敗したら、コンソールにログを吐く
      console.log(jqXHR + "\n" + textStatus + "\n" + errorThrown);
  }).always(function(data, textStatus, jqXHR){
      // 成功しても、失敗しても、レスポンスが返ってきたら、ローディングを非表示にする
      $("#loading-div").hide();
  });
});

ここの記事曰く、

ここでポイントになるのが、
processData と contentType を false にすることです。

processData は data に指定した値を文字列に変換するかどうか設定する項目です。
初期値は true となっており、このままですとURLエンコードされた値が送信されます。
ファイルの送信時は変換不要ですから、false にしておきます。

Django/Python

@require_POST
def upload_file(request):
    try:
        req_file = request.FILES['file']
        # お好みの処理をして、
        params = {
          # お好みのパラメタを用意して、
        }
        # お好みのテンプレートを使って、HTMLをレスポンスとして返す
        response = render(request, 'ajax_result.html', params);
        return HttpResponse(response)
    except:
        # 例外が発生したら、404を返す
        traceback.format_exc()
        return Http404("message")

以上!!

参考にしたサイト様

SpringBootをはじめたあとにやるはじめの一歩(Heroku使う編)

前の記事の続き。ひさびさにSpringBootでアプリを作ろうと思ったけど、だいぶ記憶が。。。
思い出してやったことの備忘録φ(..)メモメモ

wannabe-jellyfish.hatenablog.com

前の記事で以下まで終わっているイメージ。ただ、言語はKotlinになりました。

  1. Spring Initializrでひな形プロジェクトを作成
  2. giboで.gitignoreを作成

「パッケージ構成の設定」のディレクトリ構成を作るスクリプト

GUIでぽちぽちディレクトリ構成を作るのがめんどくさいので、スクリプト化。
propertiesもgistにあげて、wgetを取得するようにした。

#!/bin/bash

ROOT='.'
SRC_ROOT="${ROOT}/src/main/kotlin/com/example"
RES_ROOT="${ROOT}/src/main/resources"

### create base directories
mkdir ${SRC_ROOT}/app
mkdir ${SRC_ROOT}/domain
mkdir ${SRC_ROOT}/domain/entitiy
mkdir ${SRC_ROOT}/domain/repository
mkdir ${SRC_ROOT}/domain/service
mkdir ${SRC_ROOT}/domain/utils

### download resource via Gist

# 本番環境用の設定&共通設定
wget https://gist.github.com/memory-lovers/c5e291c4120d0e8f70b7b112e56982f4/raw/48b48bcae1cd513c9aebd01b71d7e07cff2620f3/application.properties -O ${RES_ROOT}/application.properties

# ローカル環境用のDB設定。H2を使う感じ。
wget https://gist.github.com/memory-lovers/2dc35d8b09b004f24199dc21e8b0cf85/raw/c827450e7ee77b3f04352507bf23b4c5775a9b2a/application-local.properties -O ${RES_ROOT}/application-local.properties

一部、仮当てなので以下の設定が必要。

  1. application.propertiesのログ設定をcom.exampleから自分のルートパッケージに変更
  2. .propertiesにあるDBの設定を追記

Heroku用の設定を追加

とりあえず、公開するよう場として、Herokuを使うので、それ用の設定。HerokuのDBを使ったりとか。
これもGist&スクリプト化。

#!/bin/bash

ROOT='.'
SRC_ROOT="${ROOT}/src/main/kotlin/com/example"
RES_ROOT="${ROOT}/src/main/resources"

# 本番環境用のHerokuのDB設定
wget https://gist.github.com/memory-lovers/191f79c32d50eca012df96e58f0ab746/raw/a739d16e7b1d748414f68a1c8b2c67da58b439c4/application-heroku.properties -O ${RES_ROOT}/application-heroku.properties

# 試験環境用のHerokuのDB設定
wget https://gist.github.com/memory-lovers/db31a6f7ffc3ec47eba5382aeccd0db6/raw/0de61ad65dba07eca22edf76fa448ce97c93edc3/application-heroku_dev.properties -O ${RES_ROOT}/application-heroku_dev.properties

# HerokuのDBを使うようのConfig
wget https://gist.github.com/memory-lovers/352aa0c33070c2acd842fe9aa596e1b7/raw/f46909b7f9a5e80630aa59ac764ceb1a142cd6bd/HerokuConfig -O ${SRC_ROOT}/HerokuConfig.java

# HerokuのProfile。デフォルトでは、試験環境用の設定を使う形
wget https://gist.github.com/memory-lovers/7ac33e32e8d2afc9bd13af34f8b8022b/raw/f625f7b76597fd6bdb195c43bf04fb74ae8ed6ea/Procfile -O ${ROOT}/Procfile

こっちも仮当てなので、以下の設定が必要。

  1. .propertiesにあるDBの設定を追記

以上!!

Javaのリフレクションを使ってBeanをPrettyPrintするライブラリをつくってみた(PP4j)

Javaで開発してるときに、大きめなObjectの中身を確認したいなぁーと思い、
きれいに整形してくれるプリティプリントするライブラリを探してみたけど、
なかなかいいのがなかったので、自分で作った時の備忘録

作ったライブラリはこちら。

github.com

SetとかMapとかArarryとかは対応してないけど、いずれ。。MavenCentralにも公開できてないけど、いずれ。。。

表示のされ方はこんな感じ。

こんなBeanに対して、

@Setter
@Getter
public class Sample {
    private String str;
    private List<String> strList;
    private List<Sample> children;
}

こんなインスタンスを作って、

Sample obj = new Sample();
obj.setStr("aaa");
obj.setStrList(Arrays.asList(new String[] {
    "AAA", "BBB", "CCC"
}));

Sample child1 = new Sample();
child1.setStr("child1");
child1.setChildren(Collections.emptyList());

Sample child2 = new Sample();
child2.setStr("child2");

obj.setChildren(Arrays.asList(new Sample[] {
    child1, child2
}));

こんな感じに呼び出すと、

System.out.println(PP4j.pp(obj));

こんな感じにインデントしてくれます。

Sample {
  str = aaa
  strList = [AAA, BBB, CCC]
  children = [
    Sample {
      str = child1
      strList = <null>
      children = []
    }
    Sample {
      str = child2
      strList = <null>
      children = <null>
    }
    Sample {
      str = <null>
      strList = <null>
      children = <null>
    }
  ]
}

使ったリフレクションまとめ

Class<?>系

// java.lang.Classの取得
Class<?> cls = obj.getClass();

// ClassのFQCNの取得
String fqcn = cls.getName();

// Class名の取得
String className = cls.getSimpleName();

// Classに定義されているFieldを取得
Field[] fields = cls.getDeclaredFields();

// cls.getFields()もあるが、こっちはpublicなものしかとれない

Field系

// Fieldに対してアクセス可能に設定。これでprivateフィールドの値も取れる
field.setAccessible(true);

// フィールド名を取得
String fieldName = field.getName();

// オブジェクトのインスタンスから実際のFieldのインスタンスを取得
Object field = field.get(obj);

// FieldのTypeを取得。ListなどGenericsの場合の判定に利用。
Type type = field.getType()

// FieldがList<String>だった場合に、Stringの部分を取得する
if (type == List.class) {
  // Listの場合、総称型を取得
  Type gType = field.getGenericType();

  if (gType instanceof ParameterizedType) {
    // 総称型だった場合、実際の型を取得
    Type[] pType = ((ParameterizedType) gType).getActualTypeArguments();
    // やっとここで、Stringである、pType[0]を取得できる。。。
  }
}

はまったところ。。。

クラス自体が総称型だった場合、実際の型の取得は、要素を確認しないとわからない。

なぜかクラス自体(Class<?>)は、実際の型パラメタの情報を持っていない。。。
なので、実際なんの型なのかは、要素を入る必要がある。

こんな感じだが、要素がnullだったりすると、もはやわからない。。

List<Sample> objList = new ArrayList<>();
objList.add(new Sample());


Object elm = objList.get(0);
Class cls = elm.getClass();

if (cls == String.class) {

}
Filedでも総称の型パラメタを調べるのがめんどう。。。

上記に書いているが、めんどくさい。。
Type自体の型階層がこんな感じ。

java.lang.reflect.Type                  ... インターフェース
  - java.lang.reflect.GenericArrayType  ... サブインタ―フェース
  - java.lang.reflect.ParameterizedType ... サブインタ―フェース
  - java.lang.reflect.TypeVariable<D>   ... サブインタ―フェース
  - java.lang.reflect.WildcardType      ... サブインタ―フェース
  - java.lang.Class<T>                  ... 実装されたクラス

何故作ったか。Commons-LangとかCommons-BeanUtilsとかあるけど。。。

調べてみると、Commonsライブラリにあるらしい。
すこし触ってみると、ObjectのToStringをオーバライドしないといけなく、めんどくさい。。

表示も1行にまとまっていたり、中途半端に改行されたりとなんだかなぁと。。
もう好きに表示できるように作ればいいのかと思いたつ。

Apache Commons Langでの設定

全部のBeanのToStringをオーバライドする
 public String toString() {
   return ToStringBuilder.reflectionToString(this);
 }
表示はこんな感じで1行になる。。。
sample.Sample@7ab2bfe1[str=aaa,strList=[AAA, BBB, CCC],children=[sample.Sample@6438a396[str=child1,strList=<null>,children=[]], sample.Sample@e2144e4[str=child2,strList=<null>,children=<null>]]]

Apache Commons BeanUtilsでの設定

こっちも全部のBeanのToStringを..(ry
public String toString() {
  return ReflectionToStringBuilder.toString(this, ToStringStyle.MULTI_LINE_STYLE);
}
改行はしてくれたけど、インデント。。。
sample.Sample@7ab2bfe1[
  str=aaa
  strList=[AAA, BBB, CCC]
  children=[sample.Sample@6438a396[
  str=child1
  strList=<null>
  children=[]
], sample.Sample@e2144e4[
  str=child2
  strList=<null>
  children=<null>
]]
]

参考にしたサイト様

Javaで一時停止(Sleep/Wait)する

実行中にちょっと待ちたい時があったので、その時の備忘録。

try {
    int sleepTime = 3000; 
    Thread.sleep(sleepTime); // 3000ms
} catch (InterruptedException e) {
    // 例外ハンドリング
}

参考にしたサイト様

Mavenでよくする設定とFatJarとFatJarのクラスパス設定でハマる

javaでライブラリとかをまとめるFatJar(実行可能jar)は、Javaを知らない人に渡すときに便利。
でも、いろいろとはまったので、その備忘録。

ハマりまくって、学んだことは、2つ。

  1. 実行可能jarのクラスパスは、実行時に指定できない
    • jar内のMETA-INF/MANIFEST.MFに設定があるため。
  2. 実行可能jarを直接実行すると、標準出力が表示されない
    • $ ./xxxx.jarを実行すると、$ javaw -jar xxxx.jarとして実行されるため。

コンパイラJavaバージョンを指定

<project>

    <properties>
        <!-- 文字コードの指定 --> 
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

        <!-- コンパイルするJavaのバージョンを指定。デフォルトだと1.5になるのでハマる -->
        <java.version>1.8</java.version>
        <maven.compiler.target>${java.version}</maven.compiler.target>
        <maven.compiler.source>${java.version}</maven.compiler.source>
    </properties>

</project>

clean時にローカルのjarをインストールする設定

そんなときは、Apache Maven Install Pluginを使う。

<project>
  <build>
    <plugins>

      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-install-plugin</artifactId>
        <executions>
          <execution>
            <id>install-external</id>
            <phase>clean</phase>
            <configuration>
              <file>${project.basedir}/src/main/resources/mylib-1.0.0.jar</file>
              <repositoryLayout>default</repositoryLayout>
              <groupId>jp.my.libs</groupId>
              <artifactId>mylib</artifactId>
              <version>1.0.0</version>
              <packaging>jar</packaging>
              <generatePom>true</generatePom>
            </configuration>
            <goals>
              <goal>install-file</goal>
            </goals>
          </execution>
        </executions>
      </plugin>

    </plugins>
  </build>
</project>

package時にFatJarを作る設定

<project>
  <build>
    <plugins>

      <plugin>
        <artifactId>maven-assembly-plugin</artifactId>
        <version>2.2</version>
        <executions>
          <execution>
            <id>make-assembly</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
          </execution>
        </executions>
        <configuration>
          <descriptorRefs>
            <descriptorRef>jar-with-dependencies</descriptorRef>
          </descriptorRefs>
          <archive>
            <manifest>
              <mainClass>MyMain</mainClass>
            </manifest>
            <manifestEntries>
              <!-- クラスパスの設定はここでする!!。 起動オプションでは有効にならない。 -->
              <Class-Path>./</Class-Path>
            </manifestEntries>
          </archive>
        </configuration>
      </plugin>

    </plugins>
  </build>
</project>

ここが最大のハマリポイントだった。。

プロパティファイルをローカルに配置して読み込むが、読み込まれず。。。
クラスパスを見ても、カレントディレクトリが含まれておらず。。。

jarの中に同梱されるMETA-INF/MANIFEST.MFを変更しないといけないらしい。。

ちなみに、実行しているjarの、クラスパスの確認方法は以下な感じ。(つさんの記事から引用)

System.out.println("classpath : " + System.getProperty("java.class.path"));

とりあえず、<manifestEntries><Class-Path>./</Class-Path></manifestEntries>を追加して、読み込めるようになったが、
実行してみると別の問題が。。。

jarを直接実行すると、標準出力が表示されない

$ java -jar xxxx.jarで実行すると表示されるが、$ ./xxxx.jarとすると表示されないよう。。
この記事をみると以下のように書いてある。

ただし これは「javaw -jar hello.jar」が裏で実行されているだけ。
すなわち(javaでなくjavawだから)ウィンドウが開かないので、System.out.println()等でコンソール入出力をしているプログラムは何も表示されない。

おぉぉ。とりあえず、jreをインストールしてもらう or 同梱して配布するか。。。

以上!!

参考にしたサイト様

JavaでListを固定サイズに分割するとListを逆順にする(commons-collections4: Apache Commons Collection4)

Listをサイズを10ごとに処理したいなぁというときに、いろいろ調べたので、その時の備忘録。

dependenciesの設定

    <dependency>
      <groupId>org.apache.commons</groupId>
      <artifactId>commons-collections4</artifactId>
      <version>4.1</version>
    </dependency>

使い方

List<String> list = Arrays.asList({"A", "B", "C", "D,"});;


int SPLIT_SIZE = 3;
List<List<String>> splitList = ListUtils.partition(list , SPLIT_SIZE);
//=> [ ["A", "B", "C"], ["D"] ]

//逆順にする
Collections.reverse(list);
//=> ["D", "C", "B", "A"]

参考にしたサイト様