Django 日本以外の場所からのアクセスを禁止する

備忘録です。

設定環境

CentOS

Apache

Django

mod_wsgi

 

今回はDjangoではなくApacheにて設定

 

http://www.cgis.biz/tools/access/

こちらのサイトからhtaccessをダウンロードさせていただきました。

 

そこから、

apacheの設定ファイルを書き換え。

 

今回djangoをApacheとmod_wsgiにて設定していた。

Apache側ではconf側のファイルをincludeするようにしていたので、ダウンロードした上記の設定ファイルに以下のように手を加えてincludeした。

 

冒頭に

<Directory “/var/www/html”>

まつびに

</Direvtory>

 

 

それをconf.dの中においてincludeさせた

 

 

 

ECサイト運営事業者向けハードコアの拡張現実で顧客価値を上げる

ハードコアAR

 

拡張現実を搭載した新しいECサイトの運用

 

近年、ECサイトの運営においてお客様への満足度を向上させるためのさまざまなアイディアが登場しています。

これまでのECサイトにおいては、店舗型のケースと違い、実際に商品を手に取ってみることができずに届いてからしか商品に触れることができませんでした。そのためにお客様におかれましては初回購入の際に「どんな商品なのだろう」「購入した後に後悔しないだろうか」というお悩みを抱き、それを解決することが難しいという課題がありました。

ECサイト自体は非常に便利なのですが、実際の店舗型の販売店と比較をしてみると、このような悩みは絶えず生じておりました

 

弊社株式会社ハードコアでは、このようなECサイトのいわば弱点を補うためにECサイト運営事業者様に向けて新たなサービス展開を行なっていくこととなりました。

 

早速ですが、サンプルをおきます。

トコブシを使用したARオブジェクト

 

こちらの商品は下記で販売されておられます。

トコブシの販売サイト

 

「トコブシ」というのは、地方名もさまざまですが「ナガレコ」や「ういど」などとも呼ばれているものになります。

さて、こうした商品は日頃からスーパーなどで見かけるものではない、非常にニッチな商品です。ただ、それでもこちらのトコブシをお求めになられるお客様はいらっしゃいまして、そういった際に「どんな商品なんだろう」「どれくらいの大きさなんだろう」などということはお客様の抱かれる不安要素として存在しているはずです。

このような不安要素を取り除くことができるのが、従来のスーパーなどで実際に店員さんとお話をしながら購入することができる店舗型の販売を行なっているところでした。

 

 

しかし、弊社のこのシステムを導入することで、お客様には実際にその場で商品を手に取ってみることができるような「商品との触れ合い」を体験していただくことが可能となるのです。

 

株式会社ハードコアEC事業者向けサービス
株式会社ハードコアEC事業者向けサービス

 

実際にはより多くのことが可能

1、商品を回転させる

先ほどの例によりますと、単純に商品が出てきてそれを左右上下に回すことが可能という程度でしたが、こちらは商品を自動的に動かすというアニメーションを実装することをしました。商品としての例は「カプセル」なのですが、拡大していくと赤い板の上でカプセルが回転していることがわかるかと思います。

回転カプセル

商品によっては動きのあるものが欲しいということもあるかと思います。そのような場合にも対応することが可能です。

 

2、特定の画像などを読み取ってから表示させることも可能

こちらで紹介するのは、下記の画像を読み取ることでその物体の上に商品を表示させるという技術です。実際には、テキストも表示されます(黒で見えにくいのですが)。

スマホの方は、下記URLからアクセスし、画像にカメラを合わせてください(その際、カメラへのアクセス許可を求められますので許可をしてください)。その他PC等の方は、QRコードを貼っておりますのでそちらを読み取ってから下記の画像へアクセスしてください。

 

tokobusi表示用

 

hardcoreAR-cocobat

うまく表示されましたでしょうか。

この機能を使って何ができるのかもう少し想像してみたいと思います。現在、ECサイトの運営において自社サイトのURLやSNSなどのQRコードを紙などに記載をして送るということは行われていると思われます。

それに加えて、このようなQRコードと読み込み用画像を添付しておくことで「贈答品」などのケースで、送り主様の元気な姿を動画で表現したり、あるいはテキストで表現したりすることが可能です。

 

ビデオの表示

 

 

 

新たなる顧客価値の想像を目指して

 

これらを実際に組み合わせることによって、より多くのお客様に商品の価値、お店としての価値を提供していくことが可能になるのではないでしょうか。

物体を平面上に表示させてインテリアを楽しむ、

商品を読み込むことで映像を表示させたいなど。

実際に店舗に行ってみた感覚でお客様へ商品価値をご提供することが可能になるはずです。

 

 

株式会社ハードコアでは、こちらのサービスについてより詳細にお伺いしたい方々へのお問合せをお待ちしております。

何かあればお気軽にご連絡ください。

 

[contact-form][contact-field label=”名前” type=”name” required=”true” /][contact-field label=”メール” type=”email” required=”true” /][contact-field label=”サイト” type=”url” /][contact-field label=”メッセージ” type=”textarea” /][/contact-form]

 

 

 

 

 

 

 

 

django all-auth でEmailAddress matching query does not exist.が出る

django all-auth EmailAddress matching query does not exist.
django all-auth EmailAddress matching query does not exist.

 

DoesNotExist at /rest-auth/login/

EmailAddress matching query does not exist.

Djangoのall-authを使用してAPI開発をしている時に起こったエラーです。

DjangoのUserモデルは変更していない、きちんとユーザーの登録もしているにも関わらずエラーが発生してしまうということになってしまったのです。

settings.pyの

ACCOUNT_EMAIL_REQUIRED = True

 

をFalseにするとなんとなくわかるのですが、

 

assert (
    not self.AUTHENTICATION_METHOD == self.AuthenticationMethod.EMAIL
) or self.EMAIL_REQUIRED

ここの部分でエラーが発生してしまうのです。

そうこうしていってみていってみると、

これはDjangoのデフォルトで入っているUserモデルの他の部分も使用してUserを管理しているためであることが原因です。

 

具体的なaccount_emialaddressテーブルを見てもらうとわかりますが、Emailはこちらの方でも管理されるようになっています。

そのため、まずここにも認証に使用するEmailが存在することが必要になるのです。

 

次に、ここにメールアドレスが追加されたとしても、アカウント作成時にEmailを使用した2段階認証を設定していると、

verifiedにフラグを立てる(1を設定する)必要があります。

 

 

こうして、実際にログインしてみると、

 

きちんとキーが発行されてくるというわけですね。

 

 

Django created_at などでTruncateをかける方法

DjangoでTruncateをかける方法について解説します。使い方としては、created_atなどのDatetimeFieldを使用している場合で、月別、日別、年別のデータが欲しい時などに使用できるかと思います。こういった時は大体グラフで表示すると思うのですが、今回はグラフ描画の方法については置いておきます。

 

例えば、以下のようにやりたいとします。

{

date: ‘2022年1月1日’,

count: 3,

date: ‘2022年1月2日’,

count: 1,

date: ‘2022年1月3日’,

count: 0,

date: ‘2022年1月4日’,

count: 5,

}

modelの構造が、例えば以下のような形で

class Message(models.Model):
    title = models.CharField(verbose_name='メッセージ件名', null=True, blank=True, max_length=400)
    content = models.TextField(verbose_name='メッセージ本文')
    created_at = models.DateTimeField(verbose_name='登録日', auto_now_add=True)
    updated_at = models.DateTimeField(verbose_name='更新日', auto_now=True)

Messageの件数をcreated_at、つまり登録日でGroup化して取得したいという場合です。

DjangoではTruncというものが用意されている。詳しくは公式ドキュメントを参照してください。

 

さて、こういった状況で、まずは月別データの取得ですが、基本的にはどのTruncを使用する場合も同じです。Model.annotate().values(Trunc).annotate()という記述をすることで目的を達成することができます。

下記に先立ってTruncに使用するものをimportする必要があります。

from django.db.models.functions import TruncMonth, TruncDay, TruncYear

 

  • 日別データの取得
messages = self.queryset.annotate(
            day=TruncDay('created_at')
        ).values('day').annotate(count=Count('pk'))

 

  • 月別データの取得
messages = self.queryset.annotate(
            day= TruncMonth('created_at')
        ).values('day').annotate(count=Count('pk'))

 

  • 年別データの取得
messages = self.queryset.annotate(
            day= TruncYear('created_at')
        ).values('day').annotate(count=Count('pk'))

 

 

このように記述をすると、下記のようにデータを取得することができます(下記は、日別データの取得になります)。

[{"day":"2021年12月30日","count":8},{"day":"2021年12月31日","count":1}]

 

なお、上記はDatetimeFieldをdatetime.datetime.strftimeで文字列に変換しています。

        for message in messages:
            message['day'] = datetime.datetime.strftime(message['day'], '%Y年%m月%d日')

 

グラフにて描画する(実は今回はvuetifyとvue-chartjsを使用しています)ためには、API側で日付の操作をしておいた方が楽なので(フロント側で行ってもいいのですが、Pythonの方が楽なため)上記のような実装になっています。

 

もし誰かのお役に立てれば幸いです。

 

====================================================

株式会社ハードコアでは、営業ツールとして麻波25を開発中です。

こういった機能が欲しいなどご要望があればお気軽にご連絡ください。

営業ツール:麻波25
営業ツール:麻波25

Djangoでcanvasに描いたものを画像ファイルとして保存する

題名にもあるとおり、ドンピシャな記事がなかったので記事にしておこうと思います。

要は、DjangoのRestFrameWorkでAPIにてcanvasで描かれたものを画像データとして保存するというものになります。

まずは、HTMLですがこちらは単純にcanvasを使う形ですね。

<canvas id="canvas" width="1024"></canvas>

で、ここで書かれたデータを取得するのは、

const canvasImage = canvas[orbiter.getClientID()].toDataURL("image/png")

こんな感じで、toDataURLというものを使用することでcanvasのデータを取得することができます。

しかし、これはbase64なのでこのままAPIで送ってしまうと例えばDjangoのModelでカラムに画像のパスだけを保存しておくという場合には使用することができません。それはさておき、APIを飛ばす側ですが、jQueryで実装すると下記のような形。

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
    // these HTTP methods do not require CSRF protection
    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
                beforeSend: function (xhr, settings) {
                    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                        xhr.setRequestHeader("X-CSRFToken", csrftoken);
                    }
                }
            });


let fd = new FormData();
fd.append("filepath",canvasImageで取得したcanvasのURL);

$.ajax({
    'url': 'アップロード先のAPIのURI',
    'type': 'POST',
    'data': fd,
    'processData': false,
    'contentType': false,
    'cache': false,
}).done(response => {
    console.log('成功');
}).fail(function (data) {
    console.log('失敗');
});

 

DjangoでPOSTをする場合、CSRFTOKENがないとデータを投げれないので最初の部分ではその設定をしています。

なお、ajaxSetupはjQueryのslimなどでは動かないので注意が必要です。

 

で、肝心のDjango側を紹介しますと(モジュールのimport系は省略します)、

models.py

class CanvasImages(models.Model):
    file_path = models.ImageField(verbose_name='file upload', upload_to='lesson/canvas/')

こんな感じにしています。ImageFieldなのでBloBではなく画像のファイルパスをカラムに設定する一般的なものかと思います。

urls.py

router = routers.DefaultRouter()
router.register(r'upload_file', ImagefileuploadViewset)

urlpatterns = [
    path('', include(router.urls)),
]

RestFrameWorkの純正な書き方にしたがっています。

serializers.py

class ImagefilesCanvasSerializer(serializers.ModelSerializer, serializers.ImageField):
    class Meta:
        model = CanvasImages
        fields = ['file_path']

ModelSerializerを使用しています。

views.py

class ImagefileuploadViewset(viewsets.ModelViewSet):
    queryset = CanvasImages.objects.all()
    serializer_class = ImagefilesCanvasSerializer

    def create(self, request, *args, **kwargs):
        data = request.data.get('filepath')
        if data:
            data += "=" * ((4 - len(data) % 4) % 4)
            format, imgstr = data.split(';base64,')
            ext = format.split('/')[-1]
            canvas = CanvasImages()
            canvas.file_path = ContentFile(base64.b64decode(imgstr), name=str(uuid.uuid4()) + '.' + ext)
            canvas.save()
        return Response(data, status=status.HTTP_201_CREATED)

 

こんな感じになっています。createメソッドを拡張する形にはなっていますが、saveを行っています。

情報として少なかったのは、

data += “=” * ((4 – len(data) % 4) % 4)
format, imgstr = data.split(‘;base64,’)

ここら辺ですね。

実際にはファイル名にはuuidを用いて保存を行っています。

 

何かの参考になれば幸いです。

 

 

 

 

 

 

FlutterでPOSTデータを投げてデータ登録する

前回、FlutterでAPIを叩いてデータを取得するという記事を掲載しました。今回は、FlutterでPOSTデータを送信してデータ登録を行うところをやってみたいと思います。

前回のソースからの続きなので、前回分も参照して行われると分かりやすいと思います。

=> 前回の記事はこちらから

 

1.main.dart の変更

まずは、データ登録のための画面遷移を行いたいと思いますので、前回のソース(main.dart)を改修します。

import 'dart:async'; //非同期処理用
import 'dart:convert'; //httpレスポンスをJSON形式に変換用

import 'package:flutter/material.dart'; //google提供のUIデザイン
import 'package:http/http.dart' as http;

// 変更①
import 'post.dart';

void main() {
  runApp(MaterialApp(
    // 変更②router追加
    initialRoute: '/',
    routes: {
      '/': (context) => HomePage(),
      '/postpage': (context) => PostPage(),
    },
  ));
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  late Map data;
  late List userData;

  Future getData() async {
    final response =
        await http.get(Uri.parse("<GET API URI>"));

    final body = json.decode(response.body);
    final articles = body.map((dynamic item) => item).toList();
    setState(() {
      userData = articles;
    });
  }

  @override
  void initState() {
    super.initState();
    getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("sample API"),
          backgroundColor: Colors.green,
        ),
        body: ListView.builder(
          itemCount: userData == null ? 0 : userData.length,
          itemBuilder: (BuildContext context, int index) {
            return Card(
              child: Row(
                children: <Widget>[
                  CircleAvatar(
                    child: Text(
// 変更③
                      "${userData[index]["event_name"]}",
                      style: TextStyle(color: Colors.black),
                    ),
                  ),
 // 変更④
                  Text(
                    "${userData[index]["event_name"]} ${userData[index]["date_and_time"]}",
                    textAlign: TextAlign.center,
                  ),
                  // 変更⑤画面遷移用のボタンを追加
                  RaisedButton(
                    onPressed: () {
                      Navigator.pushNamed(context, '/postpage');
                    },
                    child: Text(
                      '参加する',
                    ),
                    color: Colors.green,
                  ),
                ],
              ),
            );
          },
        ));
  }
}す

少し解説していきます。

 

// 変更① 
import 'post.dart';

この部分は、新たに作成するページ(CLASS)を読み込んでいます。

 

// 変更②router追加 
initialRoute: '/', 
routes: 
{ '/': (context) => HomePage(), 
  '/postpage': (context) => PostPage(), 
}, 
)
);

この部分は見ればなんとなくわかるかと思いますが、routerの役割に変更しました。

 

child: Text(
                      "${userData[index]["event_name"]}",
                      style: TextStyle(color: Colors.black),
                    ),

変更③と④は、出力データを変更しました(見栄えの問題なのであまり気にしなくてもいいかも)。

// 画面遷移用のボタンを追加
                  RaisedButton(
                    onPressed: () {
                      Navigator.pushNamed(context, '/postpage');
                    },
                    child: Text(
                      '参加する',
                    ),
                    color: Colors.green,
                  ),

変更⑤の部分はボタンクリックで画面遷移を行うように変更しました。

ここでちょっとだけ覚えておきたいのは、routerを変更①のように記述した場合には、Navigator.pushではなく、Navigator.pushNamedにする必要があるということです。

 

一旦このようにmain.dartを変更したら、変更①のあたりでエラーが生じていると思います。

それもそのはずで、まだpost.dartにclassを作成していないからです。

それではここかからが本題で、内容に入っていきたいと思います。

 

2.post.dartを作成

まず、post.dartというファイルを作成します。

 

お約束ですが、importするモジュールを呼び出します。

import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;こ

これはmain.dartと一緒です。

 

先にソース全体をお見せしたいと思います。

import 'dart:convert';

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

class PostPage extends StatelessWidget {
  // Formを使うために必ず必要
  final _formKey = GlobalKey<FormState>();
  // データを取得するために必要
  // String email = '';
  final EmailFieldController = TextEditingController();

  // 画面を作っていきます。
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text('Post Page'),
        ),
        body: Form(
          key: _formKey,
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: <Widget>[
              TextFormField(
                decoration: const InputDecoration(
                  border: OutlineInputBorder(),
                  filled: true,
                  icon: Icon(Icons.person),
                  hintText: '名前を入力してください。',
                  labelText: '名前',
                ),
                controller: EmailFieldController,
                // validator: (value) {
                //   if (value is Null) {
                //     return '必須です。';
                //   }
                //   return null;
                // },
                // 三項演算子だとこうなる
                validator: (value) =>
                    value is Null || value.isEmpty ? '必須です' : null,
                // onSaved: (value) => () {
                //   // var email = value!;
                //   print('$value');
                // },
              ),
              Padding(
                padding: const EdgeInsets.symmetric(vertical: 16.0),
                child: RaisedButton(
                  onPressed: () async {
                    if (_formKey.currentState!.validate()) {
                      // post APIを投げる
                      final String url = "<YOUR POST API URI>";
                      Map<String, String> headers = {
                        'content-type': 'application/json'
                      };
                      String body =
                          json.encode({'email': EmailFieldController.text});
                      http.Response resp = await http.post(Uri.parse(url),
                          headers: headers, body: body);
                      if (resp.statusCode != 201) {
                        int statusCode = resp.statusCode;
                        var content = "Failed to post $statusCode";
                        return;
                      }
                      var content = resp.body;
                    }
                  },
                  child: Text('Submit'),
                ),
              ),
            ],
          ),
        ));
  }
}

 

初学の頃に陥りやすいところも記述しています。

・フォームの入力値の取得

例えば、

入力値を取得するためにプロパティを設定する以下の部分

// データを取得するために必要
  // String email = '';

こんな感じで記述をすることが多いのではないでしょうか。

これだと入力値を取得することができません。

入力値を取得するためには、

final EmailFieldController = TextEditingController();

と記述して、TextEditingControllerを使用するのです。そうすると、値を取得する時には、

EmailFieldController.text

として、変数名.textで取得することができるようになります。

※ EmailなのにTextEditingControllerで取得しています。ツッコミはあると思いますが、適宜の方法に置き換えてください。

・validation部分の書き方

// validator: (value) {
                //   if (value is Null) {
                //     return '必須です。';
                //   }
                //   return null;
                // },

 

公式サイトでもこのような記述がされています。

ですが、このくらいであれば三項演算子で記述した方がソースが短くて可読性も上がるかと思います。

 

・レスポンスの受け取り

if (resp.statusCode != 201) {
                        int statusCode = resp.statusCode;
                        var content = "Failed to post $statusCode";
                        return;
                      }
                      var content = resp.body;

レスポンスの受け取りでステータスをどのように返却するのかは仕様によって変わってくると思います。その場合は、ステータスコードを書き換える必要があると思います。

 

3.その他注意点

開発するときのバージョンには注意してください。

現在のdart、Flutterのバージョン(2021/6/27)ではNull Safetyにより、以下の書き方はエラーとなります。

validator: (value){
                  if (value.isEmpty){
                    return '必須です';
                  }
                  return null;
                },

この場合、valueがnullの可能性があるのです。そのため、if文の中の条件式を以下のようにする必要があります。

validator: (value) {
                   if (value is Null) {
                     return '必須です。';
                   }
                   return null;
                 },

 

 

同じことがonPressの際にも生じます。

if (_formKey.currentState.validate()) {
                      // post APIを投げる

これだと、_formKey.currentStateでエラーになると思います。

条件式を以下のように変更します。

if (_formKey.currentState!.validate()) {
                      // post APIを投げる

単純に「!」を入れただけですが、これでNull Safetyになるということです。

swiftを経験されたことがあればなんとなくわかるかと思います。

 

 

 

こうすると、うまくいくかと思います。

後は、登録時の挙動(画面遷移など)を記述していけば問題なく出来上がりますね。

 

4.全体APP

こんな感じで画面が出ていればOKかなと思います。

株式会社ハードコアFlutter GETデータ画面
株式会社ハードコアFlutter POSTデータ画面
株式会社ハードコアFlutter POSTデータ画面
株式会社ハードコアFlutter POSTデータ画面

 

 

Flutter を使ったアプリ開発(APIでデータ取得とその表示)

IOS、Androidアプリを一括で開発するという要求に応えてくれるFlutter。

弊社ではこのFlutterを使用してスマホ対応のアプリ開発を行っています。

 

今回は、そんなFlutterを使用したアプリ開発、特にAPIでデータを取得し表示するところ(いわゆるget)の部分をご紹介していきます。

 

まずは、必要なライブラリーを入れていきます。

公式サイトはこちら

 

 

私の場合は、flutterへのパスを通していないので、展開先から呼び出します。

/Users/<USRNAME>/development/flutter/bin/flutter pub add http

pabspec.ymlに以下が追加されていれば成功です。

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  http: ^0.13.3

次に、必要なモジュール群を読み込みます。

import 'package:flutter/material.dart'; 
import 'package:http/http.dart' as http; // API通信
import 'dart:async'; //非同期処理用
import 'dart:convert'; //httpレスポンスをJSON形式に変換用

 

そしたら後は書いていくだけです。今回はmain.dartに記述していきます。

まずは、お決まりの

void main() {
  // main.dartは基本これだけ
  runApp(MaterialApp(
    home: HomePage(),
  ));
}

ルーティングについては別箇所に記述する方法もありますが、今回はAPIを試すだけなので一旦これで。

 

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

void mainで呼ばれているHomePageクラスを定義しています。ここでは更に_HomePageStateを見にいくようにしました。

class _HomePageState extends State<HomePage> {
  late Map data;
  late List userData;

  Future getData() async {
    //Future xxx async{} という記法
    final response =
        await http.get(Uri.parse("<リクエストを投げるURL>"));
    final body = json.decode(response.body);
        // Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'Map<dynamic, dynamic>' エラーに対処
    final articles = body.map((dynamic item) => item).toList();
    setState(() {
      userData = articles;
    });
  }

  // 非同期処理は、デフォルトでは呼び出し元は処理の完了を待ちませんが、
  // await キーワードをつけると完了を待つことができる。
  @override
  void initState() {
    super.initState();
    getData();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("sample API"),
          backgroundColor: Colors.green,
        ),
        body: ListView.builder(
          //スクロール可能な可変リストを作る
          itemCount: userData == null ? 0 : userData.length, //受け取る数の定義
          itemBuilder: (BuildContext context, int index) {
            //ここに表示したい内容をindexに応じて表示させる
            return Card(
              //cardデザインを定義:material_design
              child: Row(
                children: <Widget>[
                  CircleAvatar(
                      child: Text(
// 今回はevent_nameとdate_and_timeを表示 ApI responseに応じて変換してください。
                    "${userData[index]["event_name"]} ${userData[index]["date_and_time"]}",
                    style: TextStyle(color: Colors.black),
                  )
                      )
                ],
              ),
            );
          },
        ));
  }
}

 

 

長くなってしまいましたが、こんな感じで進めていきます。

+++ 補足 +++

API開発をしていると、日本語が文字化けをするという問題が生じてしまうことがあります。

この問題を解決するためには、APIレスポンスを受け取る側(今回はFlutter側)で対応するケースと、API側(今回はDjangoのRestFrameWork)で対応するケースとがあります。

Flutter側で対応しようとすると毎度同じようなことでソースを書かないといけないので、API側で対応するのが良いかと思います。

ちなみに、DjangoのRestFrameWorkで日本語も文字化けが起こる時には、以下のように対応します。

from rest_framework.renderers import JSONRenderer

class UTF8CharsetJSONRenderer(JSONRenderer):
    charset = 'utf-8'

こんなファイルをアプリ側などにおき、

settings.pyに以下を追加あるいは変更します。

'DEFAULT_RENDERER_CLASSES': (
    '<YOUR APP PATH>.<上記のファイル名>.UTF8CharsetJSONRenderer',
)

 

これで日本語の文字化けもなく表示されるようになります。

 

 

今回は、FlutterからAPIで情報を取得する方法について書いていきました。

次回は、FlutterからAPI経由でPOSTするケースについて書いていこうと思います。

 

 

 

 

dockerの不要なimegeなどをまとめて削除する

最近ではdockerでの開発環境の構築はほとんど当然のこととされてきました。

今回は、便利なコマンドとして追加しておきます。

 

docker images | grep -v IMAGE | awk ‘{print $3}’ | xargs docker rmi

 

あるいは、

全コンテナ削除

docker ps -aq | xargs docker rm

 

 

全イメージ削除

docker images -aq | xargs docker rmi

 

 

Error response from daemon: conflict: unable to delete ad43cb0267d9 (must be forced) – image is referenced in multiple repositories

こんなエラーが出てきたら -f オプションを使用する。

docker rmi id名 -f

Django 日本語ファイルのアップロードでエラー(’ascii’ codec can’t encode characters in position)

centOSとApacheで構成しているサーバー環境で、Djangoで日本語ファイル(画像)をアップロードしようとすると、エラーが出力されてしまいました。

ちょっと苦戦したので、今回記事にしました。

 

【 エラー内容 】

エラー内容は、

‘ascii’ codec can’t encode characters in position

 

こんな感じ。

エラー見ただけでもわかりますが、encodeできないという内容みたい。もちろん、ローカルだと問題なく動いている。日本語のファイルのアップロードも問題ない。じゃあ、これをどうすればいいのかっていうのを調べてみた。

 

・Fixing UnicodeEncodeError for file uploads

https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/modwsgi/

Djangoの公式サイトにも記載されている通り、こういった文字コードの設定が必要ということ。

加えて、

・mod_wsgiで動かす時に、UnicodeDecodeErrorが出てしまう

https://qiita.com/kanjirz50/items/81104b60049ad7140b30

こちらの記事も参考にさせていただいた。

 

【 自分の環境だとどうするか 】

今回は、Apacheの設定環境などは特段難しいことはしていない。

でも、単純に参考サイトのように設定してもうまくいかなかった。

そこで、

LANG = 'ja_JP.UTF-8'

LC_ALL='ja_JP.UTF-8'

を設定した。これを設定する時のファイルは、[  /etc/sysconfig/httpd  ]

これをvimで開いて上記の設定を行った。

 

 

【 所感 】

このDjangoでの日本語ファイルのアップロードに関しては実はこれまでも悩まされていたところだった。フロント側のjsで日本語から英語へとファイル名を変更する方法も視野に入れて検討していたがスマートではないので、なんとかサーバー側にあげるときに日本語ファイルもアップロードさせたかった。

もし、同じ悩みを抱えている人がいるのであれば、上記の方法を試してみて欲しい。

 

 

【 追記 】

上記の内容からは若干それてしまうが、mediaをアップロードする先のディレクトリの権限も変更しておかなければならなかったので、併せて追記。

どのサーバーに設置するのかという点も併せてそれぞれの環境で違うとは思いますが、今回使用しているさくらVPSだと、mediaをアップロードする先のディレクトリの権限を変更しておかないといけなかった(GCPとかだとそもそもバケット側に保存するので不要か???)ので、この対応をして、きちんと日本語ファイルのアップロードができるようになりました。

めでたしめでたし。