Flask + SQLAlchemyで作る音楽共有SNS「DailyBeat」の開発と今後の展望 - コード詳細解説付き

## はじめに

こんにちは。今回は、FlaskとSQLAlchemyを使用して開発中の「DailyBeat」というWebアプリケーションについて、コードの詳細な解説を交えながら紹介します。このアプリは、ユーザーが毎日1曲を共有し、音楽を通じて他のユーザーとつながることができる音楽特化型のSNSプラットフォームです。

## アプリケーションの概要

「DailyBeat」は以下の主要機能を持っています：

1. ユーザー登録・ログイン
2. 毎日の楽曲投稿
3. タイムライン表示
4. ユーザーフォロー機能
5. 楽曲再生機能（部分的に実装）

それでは、各機能の実装について詳しく見ていきましょう。

## 1. ユーザー登録とログイン

ユーザー登録とログイン機能は、Flask-Loginを使用して実装しています。

```python
@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        if User.query.filter_by(username=username).first():
            flash('Username already exists')
        else:
            new_user = User(username=username,
                            password=generate_password_hash(password))
            db.session.add(new_user)
            db.session.commit()
            flash('Registration successful')
            return redirect(url_for('login'))
    return render_template('register.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form.get('username')
        password = request.form.get('password')
        user = User.query.filter_by(username=username).first()
        if user and check_password_hash(user.password, password):
            login_user(user)
            return redirect(url_for('mypage'))
        flash('Invalid username or password')
    return render_template('login.html')
```

### コード解説

- `register`関数:
  - POSTリクエストの場合、フォームからユーザー名とパスワードを取得します。
  - ユーザー名の重複をチェックし、新規の場合はパスワードをハッシュ化して保存します。
  - `db.session.add(new_user)`で新しいユーザーをセッションに追加し、`db.session.commit()`でデータベースに反映します。

- `login`関数:
  - POSTリクエストの場合、ユーザー名とパスワードを検証します。
  - `check_password_hash`関数でパスワードを照合し、正しい場合は`login_user`関数でユーザーをログイン状態にします。

## 2. 毎日の楽曲投稿

ユーザーが毎日1曲を投稿できる機能を実装しています。

```python
@app.route('/post_daily_song', methods=['GET', 'POST'])
@login_required
def post_daily_song():
    form = DailySongForm()
    if form.validate_on_submit():
        today = date.today()
        DailySong.query.filter_by(
            user_id=current_user.id, date_posted=today, is_current=True).update({'is_current': False})
        new_song = DailySong(
            user_id=current_user.id,
            title=form.title.data,
            artist=form.artist.data,
            genre=form.genre.data,
            music_url=form.music_url.data,
            is_current=True
        )
        db.session.add(new_song)
        db.session.commit()
        flash('今日の1曲を投稿しました！', 'success')
        return redirect(url_for('mypage'))
    return render_template('post_daily_song.html', form=form)
```

### コード解説

- `@login_required`デコレータにより、ログインユーザーのみがアクセスできるようになっています。
- `form.validate_on_submit()`でフォームのバリデーションを行います。
- 既存の今日の投稿がある場合、`is_current`フラグをFalseに更新します。
- 新しい`DailySong`オブジェクトを作成し、データベースに追加します。
- `flash`関数で成功メッセージを設定し、マイページにリダイレクトします。

## 3. タイムライン表示

ユーザーの投稿を時系列順に表示するタイムライン機能を実装しています。

```python
@app.route('/all_posts')
def all_posts():
    sort_order = request.args.get('sort', 'desc')
    if sort_order == 'asc':
        posts = DailySong.query.order_by(DailySong.date_posted.asc()).all()
    else:
        posts = DailySong.query.order_by(DailySong.date_posted.desc()).all()

    followed_posts = []
    if current_user.is_authenticated:
        followed_users = [user.id for user in current_user.followed]
        if sort_order == 'asc':
            followed_posts = DailySong.query.filter(DailySong.user_id.in_(
                followed_users)).order_by(DailySong.date_posted.asc()).all()
        else:
            followed_posts = DailySong.query.filter(DailySong.user_id.in_(
                followed_users)).order_by(DailySong.date_posted.desc()).all()

    return render_template('all_posts.html', posts=posts, followed_posts=followed_posts, current_sort=sort_order)
```

### コード解説

- `request.args.get('sort', 'desc')`でURLパラメータから並び順を取得します。デフォルトは降順です。
- `DailySong.query.order_by()`で投稿を日付順にソートします。
- ログインユーザーの場合、フォローしているユーザーの投稿も取得します。
- `DailySong.user_id.in_(followed_users)`で、フォローしているユーザーの投稿をフィルタリングします。

## 4. ユーザーフォロー機能

ユーザー同士のつながりを強化するためのフォロー機能を実装しています。

```python
@app.route('/follow/<username>')
@login_required
def follow(username):
    user = User.query.filter_by(username=username).first()
    if user is None:
        flash('ユーザーが見つかりません。')
        return redirect(url_for('index'))
    if user == current_user:
        flash('自分自身をフォローすることはできません。')
        return redirect(url_for('user', username=username))
    current_user.follow(user)
    db.session.commit()
    flash(f'{username} をフォローしました。')
    return redirect(url_for('user', username=username))

@app.route('/unfollow/<username>')
@login_required
def unfollow(username):
    user = User.query.filter_by(username=username).first()
    if user is None:
        flash('ユーザーが見つかりません。')
        return redirect(url_for('index'))
    if user == current_user:
        flash('自分自身をアンフォローすることはできません。')
        return redirect(url_for('user', username=username))
    current_user.unfollow(user)
    db.session.commit()
    flash(f'{username} のフォローを解除しました。')
    return redirect(url_for('user', username=username))
```

### コード解説

- `@login_required`デコレータで、ログインユーザーのみがアクセスできるようにしています。
- `User.query.filter_by(username=username).first()`で対象ユーザーを取得します。
- 自分自身をフォロー/アンフォローしようとした場合はエラーメッセージを表示します。
- `current_user.follow(user)`と`current_user.unfollow(user)`メソッドで、フォロー関係を更新します。
- 変更をデータベースに反映するために`db.session.commit()`を呼び出しています。

## 5. 楽曲再生機能（部分的に実装）

現在、楽曲再生機能は部分的に実装されています。Spotify APIを使用してアルバムアートを取得する機能が実装されていますが、実際の楽曲再生機能は未実装です。

```python
def get_album_art(artist, title):
    client_id = 'your_spotify_client_id'
    client_secret = 'your_spotify_client_secret'

    auth_response = requests.post('https://accounts.spotify.com/api/token', {
        'grant_type': 'client_credentials',
        'client_id': client_id,
        'client_secret': client_secret,
    })
    auth_response_data = auth_response.json()
    access_token = auth_response_data['access_token']

    headers = {
        'Authorization': f'Bearer {access_token}'
    }
    params = {
        'q': f'artist:{artist} track:{title}',
        'type': 'track',
        'limit': 1
    }
    response = requests.get(
        'https://api.spotify.com/v1/search', headers=headers, params=params)
    response_data = response.json()

    if 'tracks' in response_data and response_data['tracks']['items']:
        return response_data['tracks']['items'][0]['album']['images'][0]['url']
    else:
        return None

@app.route('/get_album_art/<artist>/<title>')
def get_album_art_api(artist, title):
    album_art_url = get_album_art(artist, title)
    return jsonify({'album_art_url': album_art_url})
```

### コード解説

- `get_album_art`関数:
  - Spotify APIの認証トークンを取得します。
  - 楽曲の検索クエリを作成し、APIにリクエストを送信します。
  - レスポンスから、アルバムアートのURLを抽出して返します。

- `get_album_art_api`関数:
  - URLパラメータからアーティスト名と曲名を受け取ります。
  - `get_album_art`関数を呼び出してアルバムアートのURLを取得します。
  - 結果をJSONで返します。

## 今後の展望

DailyBeatの開発は現在進行中であり、以下の機能の実装や改善を計画しています：

1. 楽曲再生機能の完全実装
2. ユーザープロフィールページの充実
3. 楽曲推薦システムの導入
4. モバイルアプリ版の開発
5. ソーシャルシェア機能の強化

これらの機能を追加することで、ユーザーエクスペリエンスをさらに向上させ、より魅力的な音楽共有プラットフォームを目指しています。

## まとめ

「DailyBeat」は、FlaskとSQLAlchemyを使用して構築された音楽共有SNSです。現在のバージョンでは、ユーザー登録、ログイン、楽曲投稿、タイムライン表示、フォロー機能が実装されており、部分的な楽曲情報取得機能も含まれています。

このプロジェクトでは、以下のような技術的なポイントが特徴的です：

1. Flask-Loginを使用した安全なユーザー認証
2. SQLAlchemyを活用した効率的なデータベース操作
3. フォーム処理とバリデーション（WTFormsを使用）
4. 外部API（Spotify）との連携

今後は、楽曲再生機能の完全実装や新機能の追加を通じて、ユーザーにとってより魅力的なプラットフォームを目指して開発を続けていきます。

このプロジェクトは、Pythonの強力な機能やFlaskのシンプルさを活かしつつ、SQLAlchemyを使用した効率的なデータベース操作を実現しています。特に、日々の楽曲投稿やフォロー機能の実装は、実際のSNSの基本的な機能を網羅しており、学習教材としても有用です。

皆さんも、自分のプロジェクトでFlaskとSQLAlchemyを使ってこのような機能豊富なWebアプリケーションを構築する際の参考にしていただければ幸いです。

## 参考リンク

- []()