yaken

【Django】エラー文「ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory:」の対処法。

DjangoのアプリケーションをHerokuに上げようとした時に、パッケージのインストールエラーが起きてしまいました。

そのエラー内容と対処法をメモしておきます。

今回のエラー文

ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory:

エラーの対処法

このコマンドでエラー原因であるrequirementsファイルを適切な記載に書き換えてくれて解消されます

$ pip list --format=freeze > requirements.txt

エラーの原因:requirements.txtの記載ミス

今回のエラーは、requirements.txtの記載が間違っていることが原因でした。

エラー時:requirements.txt

エラー解消後:requirements.txt

【Googleフォーム】動的な初期値を設定したい。

サイト上にGoogleフォームリンクを設置する時に、ページに合わせて動的にフォームの初期値を変更したいなと思って設定してみました。

その設定した時のメモです。

Googleフォームから初期値設定ページを開く

Googleフォームを作成して、右上の三点リーダー(…)をクリックします。

その出てくる項目の中から「事前入力したURLを取得」をクリックします。

初期値を設定したい項目に値を入力する

「事前入力したURLを取得」を押して開いたフォームの、動的な初期値を入れたい項目に値を入力します。(値はなんでもOK)

入力したら「リンクを取得」をクリックします。

「リンクを取得」をクリックして出てきたURLをコピーします。

// コピーされたURL
https://docs.google.com/forms/d/e/[formのID]/viewform?usp=pp_url&entry.247309575=userId&entry.1732707192=userName

取得URLの中で「動的に初期値を変えたい部分」を変更する

取得したURLの中で、entry.[項目id]=[設定した値]の部分がそれぞれ初期値の内容になります。

この[設定した値]の部分が初期値を表しているので、これを変更すると初期値が変更します。(画像の「userID」と「userName」部分)

多次元配列のイメージ。4次元以降がわからなかったのでメモ。

PythonやJavascriptでよく見かける「◯次元配列」のイメージができなかったのでメモ。

考え方が合っているかわからないですが、データベースみたいに考えてみました。

(イラストで理解しようと思ったら4次元以降がイメージできなかった…。)

1次元のデータ

abc
// 1次元配列
const array = [a, b, c]
// 一番深い要素を取り出してみる
console.log(array[0])
// a

2次元のデータベース

012
0abc
1def
2ghi
// 2次元配列
const array = [
    [a, b, c],
    [d, e, f],
    [g, h, i],
]
// 一番深い要素を取り出してみる
array[0][0]
// a

3次元のデータベース

012
0[a,b,c][d][e]
1[f][g][h]
// 3次元配列
const array = [
    [[a,b,c], [d], [e]],
    [[f], [g], [h]],
]
// 一番深い要素を取り出してみる
array[0][0][0]
// a

4次元のデータベース

012
0[
[a,b],
[c,d],
[e,f],
]
[[g,h,j]][[k,l,m]]
1[[n,o,p]][[q,r,s]][[t,u,v]]
// 4次元配列
const array = [
    [
        [[a,b],[e,d],[e,f]],
        [[g,h,j]],
        [[k,l,m]]
    ],
    [
        [[n,o,p]],
        [[q,r,s]],
        [[t,u,v]],
    ]
]
// 一番深い要素を取り出してみる
array[0][0][0][0]
// a

まとめ

リストの中にリストが入ってる状態を「多次元」と呼ぶのかな。それで「どれだけこの入れ子が続いているか」で「何次元か」を表しているのかも…まだはっきりとわからない。

一番深い要素を取り出す時に[x]をいくつ使うかみたいな考え方でもいいのかもしれない。array[0][0][0][0]だったら[x]が4つだから4次元配列、みたいな。

もっと調べてわかったことや間違っているところを随時更新していきたいです。

【Javascript】DateのgetMonth()が1月ズレてしまう。原因と簡単な解消法。

JavascriptでDateを「xx月」表示に変換した時に1月前の数値が表示されてしまいました。

その原因と対策をメモしておきます。

const date = new Date('2022-08-09T13:00:15.604Z');

const month = date.getMonth();
console.log(month);

// 7  (データは8月なのに...)

解消法:getMonth()に「+1」する

getMonth()に「+1」をすることで解消できます。

const date = new Date('2022-08-09T13:00:15.604Z');

const month = date.getMonth() + 1;                // "+1"を追加
console.log(month);

// 8

年月日で表示したい場合

const date = new Date('2022-08-09T13:00:15.604Z');

const year = date.getFullYear() + '年';
const month = (date.getMonth() + 1) + '月';
const day = date.getDate() + '日';
console.log(year + month + day);

// 2022年8月9日

原因:getMonth()の返り値は「0〜11」

Javascriptの.getMonth()では、月の値を「0〜11」で返されます。

つまり1月は「0」で、12月は「11」で返ってくることになり、これが原因で1月ズレることになっています。

【Django】”__init__() takes 1 positional argument but 2 were given”の対処法。

DjangoでAPIを作成していたら"init() takes 1 positional argument but 2 were given"というエラーが出てしまいました。

その際に解消できた対処法のメモです。

init() takes 1 positional argument but 2 were given

原因のコード

from django.urls import path, include
from .views import ArticleListView

urlpatterns = [
    path('articles/', IndexArticles, name='articles'),
]
class ArticleListView(generics.ListAPIView):
	queryset = Article.objects.all()
	serializer_class = ArticleSerializer
	permission_classes = (AllowAny,)

解決法 → urls.pyに.as_view()を記載する

urls.pyのに.as_view()を記載することで解消されました。

from django.urls import path, include
from .views import ArticleListView

urlpatterns = [
    path('articles/', IndexArticles.as_view(), name='articles'),  # 変更
]

“.as_view()”はどんな時に必要?

ビューには「関数ビュー」と「クラスビュー(class)」の2つがあり、クラスビューの場合は.as_view()の記載が必要なようです。

関数ビュー「.as_view()はいらない」

# 関数ビュー
def IndexView(request):
	
	params = {
		...,
	}
	return render(request, 'pages/index.html', params)
urlpatterns = [
    path('/', IndexView, name='index'),
    ...,
]

クラスビュー「.as_view()が必要」

# クラスビュー
class ArticleListView(generics.ListAPIView):
	queryset = Article.objects.all()
	serializer_class = ArticleSerializer
	permission_classes = (AllowAny,)
urlpatterns = [
    path('articles/', IndexArticles.as_view(), name='articles'),
    ...,
]

まとめ

ビューに「クラスビュー」と「関数ビュー」の2種類あるなんて知りませんでした…。

もっといろいろと触りながら少しずつDjangoの基本を探っていきたいなと思います!

【Django】django-filterが使えない原因と解決法。

Django REST Frameworkのdjango-filterを使って「記事タイトルのキーワード検索するためのフィルタ機能」を作ろうとしていました。

ところが上手くdjango-filterが動かず少しハマってしまいました。

最終的に今回の場合は、一箇所の記述を変えるだけで動作したのですがその時のメモです。

django-filterが動作しない原因・解決法

views内で使用フィルタを指定するfilter_classfilterset_classに変更することで動作しました。

つまり原因はこのviews内のフィルタ指定の書き方が間違っていたみたいです。

ただ、以前作成したサービスではfilter_classでも動作しているので、もしかするとバージョンによる違いなのかもしれません。

動作しなかったコード

class Blog(models.Model):
    title = models.CharField(blank=True, null=True, max_length=255, verbose_name='タイトル')
    created_at = models.DateTimeField(auto_now_add=True, blank=True, null=True, verbose_name='作成日時')
    updated_at = models.DateTimeField(auto_now=True, blank=True, null=True, verbose_name='更新日時')
# 【Blog】リスト取得View
class BlogListView(generics.ListAPIView):
    queryset = Blog.objects.all()       # 1: クエリセット
    permission_classes = (AllowAny,)    # 2: アクセス許可範囲を指定
    serializer_class = BlogSerializer   # 3: 利用するSerializer指定
    filter_class = BlogFilter           # 4: 利用するFilter指定


# 【Blog】シリアライザー
class BlogSerializer(serializers.ModelSerializer):

    class Meta:
        model = Blog
        fields = '__all__'


# 【Blog】フィルタ
class BlogFilter(serializers.ModelSerializer):
    title = filters.CharFilter(name="title", lookup_expr='contains')

    class Meta:
        model = Blog
        fields = '__all__'

修正後コード

# 【Blog】リスト取得View
class BlogListView(generics.ListAPIView):
    queryset = Blog.objects.all()       # 1: クエリセット
    permission_classes = (AllowAny,)    # 2: アクセス許可範囲を指定
    serializer_class = BlogSerializer   # 3: 利用するSerializer指定
    filterset_class = BlogFilter        # 【修正箇所】4: 利用するFilter指定


# 【Blog】シリアライザー
class BlogSerializer(serializers.ModelSerializer):

    class Meta:
        model = Blog
        fields = '__all__'


# 【Blog】フィルタ
class BlogFilter(serializers.ModelSerializer):
    title = filters.CharFilter(name="title", lookup_expr='contains')

    class Meta:
        model = Blog
        fields = '__all__'

まとめ

やっぱり常に公式ドキュメントを見る癖をつけないといけないですね。

あと他環境でfilter_classが動作して、今回は動作しなかった詳しい原因がわからないままですが、バージョン管理の重要さについても実感することができました。

【Javascript】if文内で変数を定義する場合のコード。

まずif文内で変数定義する時考えること

if文内で定義した変数を「if文外で使用するかどうか」という点です。

if文中だけで使用する場合は、通常通り「const」で問題ありません。

一方で、if文外でも使用したい場合は次の2つの方法で処理します。

if文中にif文外でも利用可能な変数の書き方

方法1:if文外で「let」「var」で定義する【推奨】

constは再代入ができないので、「let」または「var」でif文外に変数定義します。

ただし、「var」は意図しないバグが起こりやすいため「let」

let b;      // 変数定義

if (〇〇) {
    b = 1;  // 再代入
};

console.log(b)
// 1

方法2:if文中に「var」で定義する

「var」はバグを起こしやすいので、

if (〇〇) {
    var a = 1;  // 変数定義
};

console.log(a);
// 1

おまけ:「var」はできるだけ使わないほうがいい

varは再宣言ができてしまったり、巻き上げによる影響を受けたりによって、意図しない挙動になってしまう場合があるのであまり使わない方がいいそうです。

varが良くないとされる理由は、おおむね以下の2点に集約されると思います。

1. 変数を簡単に書き換えられてしまうと、意図しないバグが発生するため
2. letやconstに比べて、varは巻き上げ時のバグを生み出しやすいため

参考:JavaScriptでvarが非推奨な理由を整理してみた

【Next.js】faviconを本番環境と開発環境で出し分ける。

やりたいこと

サイトのfaviconを本番環境では「💫」開発環境では「🚧」(ローカル環境)のように出し分けたい。

これでぱっと見でどの環境か区別することができるようになる。

⑴ faviconを準備する

publicフォルダ内にprod.svgdev.svgの2つのファイルを作成します。

  • 本番環境の「prod.svg」
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
    <text x="50%" y="50%" style="dominant-baseline:central;text-anchor:middle;font-size:90px;">💫</text>
</svg>
  • 開発(ローカル)環境の「dev.svg」
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
    <text x="50%" y="50%" style="dominant-baseline:central;text-anchor:middle;font-size:90px;">🚧</text>
</svg>

⑵ faviconの表示設定をする

faviconは<Head>内に<link rel="icon" href="[faviconにしたい画像]">を追加することで表示することができます。

今回は出し分けをしたいのでprocess.env.FAVICON_URLを指定して、環境ごとにこのprocess.env.FAVICON_URLが変わる処理を行なっていきます。

import React from 'react';
import Head from "next/head";


export default function Layout({ children }) {
    return (
        <div>
            <Head>
                <title>サイトタイトル</title>
                <link rel="icon" href={process.env.FAVICON_URL} />  // faviconの指定
            </Head>
            <div>
                { children }
            </div>
        </div>
    );
};

⑶ 本番環境か開発環境か区別する

環境変数を設定する

.envファイルにNODE_ENV=developmentを追加します。

これで開発環境の場合に、環境変数”NODE_ENV”は「development」になります。

NODE_ENV=development

(※本番環境のNODE_ENVにはproductionなどを入れましょう。)

環境変数によってfaviconのファイルを出し分ける

next.config.jsファイルで、環境変数NODE_ENVによって異なるFAVICON_URLに指定する処理を追加します。

module.exports = {
    env: {
        // 環境変数(.env)のNODE_ENVに応じて、FAVICON_URLを出し分け
        FAVICON_URL: process.env.NODE_ENV === `development` ? `dev.svg` : `prod.svg`,
    }
}

完成

これで開いているページが本番環境か開発環境か調べるために毎回URLを確認する、みたいな手間が省けます。

【NextAuth.js】ソーシャルログインの実装。

GoogleやTwitter、FacebookなどのSNSアカウントを使ってログインできる「ソーシャルログイン」の実装方法をまとめていきたいと思います。

使用するパッケージをインストールする

「next-auth」をインストール

$ npm install next-auth

プロバイダ側(SNSサービス側)の設定をする

ソーシャルログインを使うためには、GoogleやTwitterなど認証機能を使いたいサービスごとに「⑴ clientId」と「⑵ clientSecret」を事前に取得する必要があります。

Googleアカウントでログインしたい場合

Google Cloud Platform(GCP)で「clientId」と「clientSecret」の2つを取得します。

こちらは下記記事がとても参考になりました 。

Twitterアカウントでログインしたい場合

※分かり次第更新します。

Next.js側の設定をする

「.env」ファイル

プロバイダ側の設定で取得した「clientId」と「clientSecret」を.envファイル内記載します。

(もしトップディレクトリに.envファイルがない場合は作成してください。)

GOOGLE_CLIENT_ID=[取得したクライアントID]
GOOGLE_CLIENT_SECRET=[取得したクライアントシークレット]

「pages/api/[…nextauth].js」ファイル

pages/apiディレクトリ内に[...nextauth].jsを作成します。

[…nextauth].jsでは「使用するプロバイダの指定」をします。

import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';

export default NextAuth({
    // 1: ログインに使用するプロバイダを指定
    providers: [
        GoogleProvider({
            clientId: process.env.GOOGLE_CLIENT_ID,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET,
        }),
    ],
    secret: 'secret',
});

プロバイダごとに記載方法が異なるので、こちらの記事を参考に選んで記載してみてください。

「_app.js」ファイル

_app.jsのComponentをSessionProviderで囲います。

import {SessionProvider} from 'next-auth/react';

function MyApp({ Component, pageProps: { session, ...pageProps } }) {
    return(
        <SessionProvider session={session}>
            <Component {...pageProps} />
        </SessionProvider>
    )};
export default MyApp;

ログインボタンを作る

NextAuthでは、signIn()でログイン、signOut()でログアウトの処理を実装することができます。

import { useSession, signIn, signOut } from "next-auth/react"

export default function LoginButton(props) {
    // セッション情報を取得
    const { data: session } = useSession()

    return (
        {session ?  // ログイン判定
            <button onClick={() => signOut()}>ログアウト</button>:
            <button onClick={() => signIn()}>ログイン</button>
        }
    );
};

ログインユーザー情報をデータベースに保存する

ログインしたユーザー情報をデータベースへ保存したい場合は、「Prisma」を利用すると比較的簡単に実装することができます。

詳しくはこちらの記事を参考にしてみてください。

【DRF】M2Mを含むインスタンスをcreate()する時に出たエラー。

今回新しくManyToManyフィールド(Tag)を含む投稿(Post)インスタンスを作成しようとしたところエラーが出たのでその解消法メモ。

M2M含むインスタンス作成時に出たエラー

実行したこと

class Tag(models.Model):
    name = models.CharField(blank=True, null=True, max_length=255)

class Post(models.Model):
    title = models.CharField(blank=True, null=True, max_length=255)
    tags = models.ManyToManyField("Tag", through="PostTagRelation", blank=True, null=True)
def create(self, validated_data):
    # 1: validated_dataからtagsを取り出す処理
    tags_data = self.validated_data.pop('tags')

    # 2: Postインスタンスを作成する処理
    post = Post.objects.create(**validated_data)
    
    # 3: tagsを一つずつPostへ追加する処理
    for tag_data in tags_data:
        tag_qs = Tag.objects.filter(id=tag_data['id'])
        if tag_qs.exists():
            tag = tag_qs.first()
        else:
            tag = Tag.objects.create(**tag_data)
			post.tags.add(tag)

    post.save()
    return post

実際のエラー文

TypeError: Direct assignment to the forward side of a many-to-many set is prohibited. Use tags.set() instead.

解決法

def create(self, validated_data):
    # 1: validated_dataからtagsを取り出す処理
    tags_data = self.validated_data.pop('tags')

    # 2: Postインスタンスを作成する処理
    post = Post.objects.create(
        title=validated_data.get('title'),  # 追加: フィールドを指定
    )

    # 3: tagsを一つずつPostへ追加する処理
    for tag_data in tags_data:
        tag_qs = Tag.objects.filter(id=tag_data['id'])
        if tag_qs.exists():
            tag = tag_qs.first()
        else:
            tag = Tag.objects.create(**tag_data)
			post.tags.add(tag)

    post.save()
    return post