使用 Django 和 React 的 Python 聊天教程

2025-05-25

使用 Django 和 React 的 Python 聊天教程

本教程将解释如何使用 Python、Django 和 React 构建聊天应用程序。

与其他教程不同,我没有使用 Python/Django 进行 WebSocket 连接。虽然从技术角度来看这样做很酷,但它相当缓慢且昂贵——尤其是在用户数量尚可的情况下。C++、Go 和 Elixir 等语言在处理聊天核心方面要好得多。

在本教程中,我们将使用 Stream,这是一个用于聊天的 API,它使用 Go、Raft 和 RocksDB 来处理 WebSocket 连接和其他繁重的工作。

目录

  1. React Chat 演示 UI
  2. Django/Python 设置
  3. 用户授权
  4. Django Rest框架
  5. 生成令牌以访问 Stream 的聊天服务器
  6. 在 React 中集成 Auth
  7. 从 Python 服务器发送消息
  8. 最后的想法

以下代码的 GitHub 仓库可以在https://github.com/GetStream/python-chat-example找到。

开始写代码吧!🤓

步骤 1 – React Chat 演示 UI

在我们开始考虑 Python 聊天方面的事情之前,让我们先启动一个简单的 React 前端,这样我们就可以看到一些漂亮且直观的东西:

$ yarn global add create-react-app
$ brew install node && brew install yarn # skip if installed
$ create-react-app chat-frontend
$ cd chat-frontend
$ yarn add stream-chat-react
Enter fullscreen mode Exit fullscreen mode

将代码替换src/App.js为:

import React from "react";
import {
  Chat,
  Channel,
  ChannelHeader,
  Thread,
  Window
} from "stream-chat-react";
import { MessageList, MessageInput } from "stream-chat-react";
import { StreamChat } from "stream-chat";

import "stream-chat-react/dist/css/index.css";

const chatClient = new StreamChat("qk4nn7rpcn75"); // Demo Stream Key
const userToken =
  "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1za3ktOSJ9.mhikC6HPqPKoCP4aHHfuH9dFgPQ2Fth5QoRAfolJjC4"; // Demo Stream Token

chatClient.setUser(
  {
    id: "cool-sky-9",
    name: "Cool sky",
    image: "https://getstream.io/random_svg/?id=cool-sky-9&name=Cool+sky"
  },
  userToken
);

const channel = chatClient.channel("messaging", "godevs", {
  // image and name are required, however, you can add custom fields
  image:
    "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png",
  name: "Talk about Go"
});

const App = () => (
  <Chat client={chatClient} theme={"messaging light"}>
    <Channel channel={channel}>
      <Window>
        <ChannelHeader />
        <MessageList />
        <MessageInput />
      </Window>
      <Thread />
    </Channel>
  </Chat>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

接下来,运行yarn start看看聊天的实际效果!

第 2 步 - Django/Python 设置(如果已经设置,请跳过)

确保 Python 3.7 已启动并运行。

$ brew install python3

$ pip install virtualenv virtualenvwrapper
$ export WORKON_HOME=~/Envs
$ source /usr/local/bin/virtualenvwrapper.sh
$ mkvirtualenv chatexample -p `which python3`
$ workon chatexample
Enter fullscreen mode Exit fullscreen mode

如果这不起作用,请尝试这个代码片段:

$ python3 -m venv chatexample
$ source chatexample/bin/activate
Enter fullscreen mode Exit fullscreen mode

现在您已处于虚拟环境中,运行时您应该会看到 python 3:

$ python --version
Enter fullscreen mode Exit fullscreen mode

要启动一个新的 Django 项目,请使用以下代码片段:

$ pip install django
$ django-admin startproject mychat
Enter fullscreen mode Exit fullscreen mode

并启动您的应用程序:

$ cd mychat
$ python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

现在,当您打开时http://localhost:8000,您应该会看到以下内容:

Django

步骤 3 - 用户身份验证

下一步让我们设置 Django 的用户身份验证。

$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver
Enter fullscreen mode Exit fullscreen mode

访问http://localhost:8000/admin/并登录。瞧!

您应该会看到如下所示的 Django 管理屏幕:

Django 管理员

第 4 步 - Django Rest 框架

我最喜欢的用于将 React 与 Django 集成的软件包之一是 Django Rest Framework。为了使一切正常工作,我们需要创建以下端点:

  • 用户注册
  • 用户登录

我们可以自己构建这些;但是,有一个名为Djoser的包已经解决了这个问题。它配置了用户注册、登录、密码重置等所需的 API 端点。

要安装 Djoser,请使用以下代码片段:

$ pip install djangorestframework djoser
Enter fullscreen mode Exit fullscreen mode

然后,编辑urls.py并更改文件以包含以下内容:

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('auth/', include('djoser.urls')),
    path('auth/', include('djoser.urls.authtoken')),
]
Enter fullscreen mode Exit fullscreen mode

完成后,编辑settings.py并进行以下更改:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'djoser',
]

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.TokenAuthentication',
    )
}
Enter fullscreen mode Exit fullscreen mode

有关 Djoser 公开的 API 端点的更多信息,请查看:
https://djoser.readthedocs.io/en/latest/sample_usage.html

现在,让我们继续测试注册端点:

$ curl -X POST http://127.0.0.1:8000/auth/users/ --data 'username=djoser&password=alpine12'
Enter fullscreen mode Exit fullscreen mode

步骤 5 - 生成令牌以访问 Stream 的聊天服务器

现在我们需要自定义 Djoser 视图来为 Stream 生成 token。让我们开始吧。

让我们稍微组织一下文件,并在我们的项目中创建一个聊天应用程序文件夹(确保您在正确的目录中):

$ python manage.py startapp auth
Enter fullscreen mode Exit fullscreen mode

安装流聊天:

$ pip install stream-chat
Enter fullscreen mode Exit fullscreen mode

auth/serializers.py使用以下逻辑创建自定义序列化器:

from djoser.serializers import TokenSerializer
from rest_framework import serializers
from djoser.conf import settings as djoser_settings
from stream_chat import StreamChat
from django.conf import settings

class StreamTokenSerializer(TokenSerializer):
    stream_token = serializers.SerializerMethodField()

    class Meta:
        model = djoser_settings.TOKEN_MODEL
        fields = ('auth_token','stream_token')

    def get_stream_token(self, obj):
        client = StreamChat(api_key=settings.STREAM_API_KEY, api_secret=settings.STREAM_API_SECRET)
        token = client.create_token(obj.user.id)

        return token
Enter fullscreen mode Exit fullscreen mode

最后,通过更新settings.py文件来使用自定义序列化器:

STREAM_API_KEY = YOUR_STREAM_API_KEY # https://getstream.io/dashboard/
STREAM_API_SECRET = YOUR_STREAM_API_SECRET
DJOSER = {
    'SERIALIZERS': {
        'token': 'auth.serializers.StreamTokenSerializer',
    }
}
Enter fullscreen mode Exit fullscreen mode

重新运行迁移:

$ python manage.py migrate
Enter fullscreen mode Exit fullscreen mode

为了验证它是否有效,请使用 POST 请求访问登录端点:

$ curl -X POST http://127.0.0.1:8000/auth/token/login/ --data 'username=djoser&password=alpine12'
Enter fullscreen mode Exit fullscreen mode

auth_token都应stream_token归还。

步骤 6 - 在 React 中集成 Auth

出于显而易见的原因,稍后在前端添加身份验证至关重要。在我们的例子中,它特别有用,因为我们可以从后端 API(由 Python 提供支持)获取用户令牌,并在发送消息时动态使用它。

首先,为 Django 安装 CORS 中间件包:

$ pip install django-cors-headers
Enter fullscreen mode Exit fullscreen mode

然后,修改您的settings.py以引用djors-cors-header中间件:

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

MIDDLEWARE = [
    ...
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...
]
Enter fullscreen mode Exit fullscreen mode

最后,将以下内容添加到您的settings.py文件中:

CORS_ORIGIN_ALLOW_ALL = True
Enter fullscreen mode Exit fullscreen mode

下一步需要对你的前端进行一些修改。首先,你需要确保已经通过 yarn 安装了所有依赖项:

$ yarn add axios react-dom react-router-dom
Enter fullscreen mode Exit fullscreen mode

接下来,在目录中创建以下文件src/

  • AuthedRoute.js
  • UnauthedRoute.js
  • withSession.js
  • 登录.js
  • Chat.js

App.js

import React from "react";
import { BrowserRouter as Router, Switch } from "react-router-dom";

import Chat from "./Chat";
import Login from "./Login";

import UnauthedRoute from "./UnauthedRoute";
import AuthedRoute from "./AuthedRoute";

const App = () => (
  <Router>
    <Switch>
      <UnauthedRoute path="/auth/login" component={Login} />
      <AuthedRoute path="/" component={Chat} />
    </Switch>
  </Router>
);

export default App;
Enter fullscreen mode Exit fullscreen mode

AuthedRoute.js

import React from "react";
import { Redirect, Route } from "react-router-dom";

const AuthedRoute = ({ component: Component, loading, ...rest }) => {
  const isAuthed = Boolean(localStorage.getItem("token"));
  return (
    <Route
      {...rest}
      render={props =>
        loading ? (
          <p>Loading...</p>
        ) : isAuthed ? (
          <Component history={props.history} {...rest} />
        ) : (
          <Redirect
            to={{
              pathname: "/auth/login",
              state: { next: props.location }
            }}
          />
        )
      }
    />
  );
};

export default AuthedRoute;
Enter fullscreen mode Exit fullscreen mode

UnauthedRoute.js

import React from "react";
import { Redirect, Route } from "react-router-dom";

const AuthedRoute = ({ component: Component, loading, ...rest }) => {
  const isAuthed = Boolean(localStorage.getItem("token"));
  return (
    <Route
      {...rest}
      render={props =>
        loading ? (
          <p>Loading...</p>
        ) : !isAuthed ? (
          <Component history={props.history} {...rest} />
        ) : (
          <Redirect
            to={{
              pathname: "/"
            }}
          />
        )
      }
    />
  );
};

export default AuthedRoute;
Enter fullscreen mode Exit fullscreen mode

withSession.js

import React from "react";
import { withRouter } from "react-router";

export default (Component, unAuthed = false) => {
  const WithSession = ({ user = {}, streamToken, ...props }) =>
    user.id || unAuthed ? (
      <Component
        userId={user.id}
        user={user}
        session={window.streamSession}
        {...props}
      />
    ) : (
      <Component {...props} />
    );

  return withRouter(WithSession);
};
Enter fullscreen mode Exit fullscreen mode

登录.js

import React, { Component } from "react";
import axios from "axios";

class Login extends Component {
  constructor(props) {
    super(props);

    this.state = {
      loading: false,
      email: "",
      password: ""
    };

    this.initStream = this.initStream.bind(this);
  }

  async initStream() {
    await this.setState({
      loading: true
    });

    const base = "http://localhost:8000";

    const formData = new FormData();
    formData.set("username", this.state.email);
    formData.set("password", this.state.password);

    const registration = await axios({
      method: "POST",
      url: `${base}/auth/users/`,
      data: formData,
      config: {
        headers: { "Content-Type": "multipart/form-data" }
      }
    });

    const authorization = await axios({
      method: "POST",
      url: `${base}/auth/token/login/`,
      data: formData,
      config: {
        headers: { "Content-Type": "multipart/form-data" }
      }
    });

    localStorage.setItem("token", authorization.data.stream_token);

    await this.setState({
      loading: false
    });

    this.props.history.push("/");
  }

  handleChange = e => {
    this.setState({
      [e.target.name]: e.target.value
    });
  };

  render() {
    return (
      <div className="login-root">
        <div className="login-card">
          <h4>Login</h4>
          <input
            type="text"
            placeholder="Email"
            name="email"
            onChange={e => this.handleChange(e)}
          />
          <input
            type="password"
            placeholder="Password"
            name="password"
            onChange={e => this.handleChange(e)}
          />
          <button onClick={this.initStream}>Submit</button>
        </div>
      </div>
    );
  }
}

export default Login;
Enter fullscreen mode Exit fullscreen mode

Chat.js

import React, { Component } from "react";
import {
  Chat,
  Channel,
  ChannelHeader,
  Thread,
  Window
} from "stream-chat-react";
import { MessageList, MessageInput } from "stream-chat-react";
import { StreamChat } from "stream-chat";

import "stream-chat-react/dist/css/index.css";

class App extends Component {
  constructor(props) {
    super(props);
    this.client = new StreamChat("<YOUR_STREAM_APP_ID>");

    this.client.setUser(
      {
        id: "cool-sky-9",
        name: "Cool Sky",
        image: "https://getstream.io/random_svg/?id=cool-sky-9&name=Cool+sky"
      },
      localStorage.getItem("token")
    );

    this.channel = this.client.channel("messaging", "godevs", {
      image:
        "https://cdn.chrisshort.net/testing-certificate-chains-in-go/GOPHER_MIC_DROP.png",
      name: "Talk about Go"
    });
  }

  render() {
    return (
      <Chat client={this.client} theme={"messaging light"}>
        <Channel channel={this.channel}>
          <Window>
            <ChannelHeader />
            <MessageList />
            <MessageInput />
          </Window>
          <Thread />
        </Channel>
      </Chat>
    );
  }
}

export default App;
Enter fullscreen mode Exit fullscreen mode

请确保将其替换为可在仪表板上YOUR_STREAM_APP_ID找到的有效 Stream App ID

重启你的前端应用程序,你应该会遇到身份验证墙!输入你的邮箱和密码,系统会请求一个令牌并将其存储在本地存储中。

步骤 7 - 从 Python 聊天服务器发送消息

有时,您需要使用基于 Python 的后端服务器写入聊天 API。您可以使用以下快速管理命令:

验证已安装的应用程序是否如下所示settings.py

INSTALLED_APPS = [
    'corsheaders',
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'rest_framework.authtoken',
    'djoser',
]
Enter fullscreen mode Exit fullscreen mode

接下来,创建目录chat/management/commands。在该目录中,添加一个名为的文件,broadcast.py内容如下:

from django.core.management.base import BaseCommand, CommandError
from django.conf import settings
from stream_chat import StreamChat

class Command(BaseCommand):
    help = 'Broadcast the message on your channel'

    def add_arguments(self, parser):
        parser.add_argument('--message')

    def handle(self, *args, **options):
        client = StreamChat(api_key=settings.STREAM_API_KEY, api_secret=settings.STREAM_API_SECRET)
        client.update_user({"id": "system", "name": "The Server"})
        channel = client.channel("messaging", "kung-fu")
        channel.create("system")
        response = channel.send_message({"text": "AMA about kung-fu"}, 'system')
        self.stdout.write(self.style.SUCCESS('Successfully posted a message with id "%s"' % response['message']['id']))
Enter fullscreen mode Exit fullscreen mode

您可以尝试像这样向聊天发送消息:

$ python manage.py broadcast --message hello
Enter fullscreen mode Exit fullscreen mode

您应该会看到如下响应:

播送

最后的想法

我希望您喜欢本教程,使用 Django、Python 和 React 构建聊天应用程序!

如需体验Stream Chat的交互式体验,请参阅Stream 网站上的API 教程。如果您有兴趣深入了解 Stream Chat React Components 的代码,完整文档可在此处找到。如果您有兴趣在 Stream 上构建聊天功能,我们为各种热门语言和框架提供了SDK,其中最新的是iOS (Swift)

祝你编码愉快!✌

文章来源:https://dev.to/nickparsons/tutorial-chat-with-python-django-and-react-1cpk
PREV
Typescript Partial<T>,我的一生中你去哪儿了?
NEXT
JavaScript:Promises 以及 Async/Await 为何胜出 Promises vs. Callbacks 🥊 Promises 🤞 Async/Await? 🤔 最后的想法 📃