django 中使用react_前后端分离框架django+vue与django+react - CSDN
  • django reactI know it’s a very comprehensive title so I’ll keep it as simple as possible. 我知道这是一个非常全面的标题,所以我将使其尽可能简单。 In this article, we will make a book application ...

    django react

    I know it’s a very comprehensive title so I’ll keep it as simple as possible.

    我知道这是一个非常全面的标题,所以我将使其尽可能简单。

    In this article, we will make a book application from installation to the final product using Django and React. I hope to make a nice and simple introduction-development-product series for a CRUD (Create, Read, Update, Delete) application. I want to share the notes I took and the steps I followed while making an app without getting overwhelmed by a lot of information from both sides (back end and front end).

    在本文中,我们将使用Django和React从安装到最终产品的制作一个书本应用程序。 我希望为CRUD(创建,读取,更新,删除)应用程序制作一个简单,漂亮的介绍-开发-产品系列。 我想分享我在制作应用程序时所采取的笔记和所采取的步骤,而又不会因为双方(后端和前端)的大量信息而感到不知所措。

    I will try not to dwell on the questions such as what and why, but to explain it purposefully. For every command I use, I will cite since there may be people who do not know, in this way you can quickly get an idea from their documentation pages and return to the article.

    我将不去赘述诸如什么以及为什么之类的问题,而是有目的地对其进行解释。 我会引用我使用的每个命令,因为可能有些人不知道,这样您就可以从他们的文档页面中快速了解想法并返回本文。

    Django is a Python-based free and open-source web library based on the model-template-view architecture.

    Django是一个基于Python的免费开源Web库,它基于model-template-view体系结构。

    React is a JavaScript library that allows you to create user interfaces for your web projects on a component basis.

    React是一个JavaScript库,允许您基于组件为Web项目创建用户界面。

    入门#1 (Getting started #1)

    Here’s what you need to have installed on your computer before keeping to read the article:

    在继续阅读本文之前,您需要在计算机上安装以下文件:

    Python3PipNodeJS

    While I’m writing this article I use the following versions : Python 3.6~, pip 20.1.1, NodeJS 12.18.2 . If the scripts I use not work or give an error on different versions, please do not hesitate to ask me.

    在撰写本文时,我使用以下版本: Python 3.6~ NodeJS 12.18.2pip 20.1.1NodeJS 12.18.2 。 如果我使用的脚本无法正常工作或在不同版本上出现错误,请随时询问我。

    Let’s start by setting up a working environment. I created a folder named as DjangoReact. First step is installing pipenv. Pipenv is a tool that brings packaging processes(bundler, npm, yarn, composer, etc.) to python world. It automatically creates and manages a virtualenv for your projects, as well as adds/removes packages from your Pipfile as you install/uninstall packages.

    让我们从设置工作环境开始。 我创建了一个名为DjangoReact的文件夹。 第一步是安装pipenv。 Pipenv是一种工具,可将打包过程(捆绑程序,npm,纱线,作曲家等)引入python world。 它会自动为您的项目创建和管理virtualenv,并在您安装/卸载软件包时从Pipfile中添加/删除软件包。

    Paste the below code to the command line after going into the workspace:

    进入工作区后,将以下代码粘贴到命令行:

    python3.6 -m pip install pipenv --upgrade

    Afterward, via the below code we’ll install django 3.0.8(latest version) to our virtualenv via the below code:

    然后,通过以下代码,我们将通过以下代码将django 3.0.8(最新版本)安装到我们的virtualenv中:

    pipenv install --python 3.6 django==3.0.8

    After installation is done, our folder tree must be as follows:

    安装完成后,我们的文件夹树必须如下所示:

    DjangoReact
    ├── Pipfile
    └── Pipfile.lock

    To be activated virtualenv run pipenv shell code on the command line.

    要激活virtualenv,请在命令行上运行pipenv shell代码。

    Now, we can start to create our first Django project. You may see the parameters you could use by typing django-adminon the command line. For now, we just interested in a parameter called startproject. Since we want to install to the folder we are in (we have already created a folder at the beginning of the article), we are creating a project called DjangoReact to the folder we are in.

    现在,我们可以开始创建第一个Django项目。 通过在命令行上输入django-admin ,您可能会看到可以使用的参数。 现在,我们只对一个名为startproject的参数startproject 。 由于我们要安装到我们所在的文件夹中(我们已经在本文开头创建了一个文件夹),因此我们要在我们所在的文件夹中创建一个名为DjangoReact的项目。

    django-admin startproject DjangoReact .

    Here is the final folder tree:

    这是最终的文件夹树:

    DjangoReact/
    manage.py
    DjangoReact/
    __init__.py
    settings.py
    urls.py
    asgi.py
    wsgi.py

    I want to take a note here that projects and applications are completely different kinds of things. The application is a web application that serves a specific purpose — blog, todolist, database records, etc. while the project is a collection of applications and configurations within a website. The project can contain more than one application.

    我想在此说明项目和应用程序是完全不同的事物。 该应用程序是一个服务于特定目的的Web应用程序-博客,待办事项列表,数据库记录等,而项目则是网站内应用程序和配置的集合。 该项目可以包含多个应用程序。

    To check, we run our server and check that it is working properly. When we run the python manage.py runserver code, the output in the terminal should be as follows.

    为了进行检查,我们运行服务器并检查它是否正常运行。 当我们运行python manage.py runserver代码时,终端中的输出应如下所示。

    (DjangoReact) $~ python manage.py runserverWatching for file changes with StatReloader
    Performing system checks…
    System check identified no issues (0 silenced).You have 17 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions.
    Run ‘python manage.py migrate’ to apply them.July 17, 2020–14:22:01
    Django version 3.0.8, using settings ‘backend.settings’
    Starting development server at http://127.0.0.1:8000/
    Quit the server with CONTROL-C.

    When we enter our localhost, the screen below appears. Yes! It works!

    当我们输入本地主机时,将显示以下屏幕。 是! 有用!

    Image for post
    127.0.0.1
    127.0.0.1

    One last little touch.

    最后一点点触摸。

    Image for post

    Yes! Now we can add Django to our skills.

    是! 现在,我们可以将Django添加到我们的技能中。

    Image for post

    模型,视图,路由…URL#2 (Model, View, Routing…URLs #2)

    Now, it’s time to create our first application. Our to-do list is as follows:

    现在,是时候创建我们的第一个应用程序了。 我们的任务清单如下:

    1. Books-> Creating-> Name?-> Author?-> Description?-> Image?-> Search-> Delete-> Update2. Feed-> List of all books

    We come into DjangoReact folder and create an application with ./manage.py startapp books command. After running the command, it will create a folder named ‘books’ inside our folder and install it. The final version of our folder is as follows:

    我们进入DjangoReact文件夹,并使用./manage.py startapp books命令创建一个应用程序。 运行该命令后,它将在我们的文件夹中创建一个名为“ books”的文件夹并进行安装。 我们文件夹的最终版本如下:

    DjangoReact/
    ├── books
    │ ├── admin.py
    │ ├── apps.py
    │ ├── __init__.py
    │ ├── migrations
    │ │ └── __init__.py
    │ ├── models.py
    │ ├── tests.py
    │ └── views.py
    ├── db.sqlite3
    ├── DjangoReact
    │ ├── asgi.py
    │ ├── __init__.py
    │ ├── __pycache__
    │ │ ├── __init__.cpython-36.pyc
    │ │ ├── settings.cpython-36.pyc
    │ │ ├── urls.cpython-36.pyc
    │ │ └── wsgi.cpython-36.pyc
    │ ├── settings.py
    │ ├── urls.py
    │ └── wsgi.py
    ├── manage.py
    ├── Pipfile
    ├── Pipfile.lock
    └── todo.md

    模型 (Model)

    We should configure our model.py file in books application as follows.

    我们应该按以下方式在books应用程序中配置model.py文件。

    from django.db import models
    class Books(models.Model):
       # id = models.AutoField(primary_key=True)
       name = models.TextField(blank=False, null=False)
       author = models.TextField(blank=False, null=False)
       description = models.TextField(blank=True, null=True)
       image = models.FileField(upload_to='images/', blank=True, null=True)

    With models.TextField(), we define the name, author and description fields. blank = False and null = False fields need to be filled, even if others are empty. Since the id field will increase automatically and will be used as a selector, we have defined it as AutoField and primary key. In order to get our cover images, we determined the file path using FileField and said that this field can be left blank.

    使用models.TextField() ,我们定义名称,作者和描述字段。 blank = Falsenull = False字段需要填写,即使其他字段为空。 由于id字段将自动增加并将用作选择器,因此我们将其定义为AutoField和主键。 为了获得封面图像,我们使用FileField确定了文件路径,并说此字段可以留为空白。

    迁移 (Migrate)

    In order to recognize our application when the project is running, we need to find the INSTALLED_APPS in the settings.py file under our project folder, add and save our model name 'books' . After doing this, we run python manage.py makemigrations command to write the changes we made in our model to the database scheme. After completion, we apply the changes we made with python manage.py migrate command.

    为了在项目运行时识别我们的应用程序,我们需要在项目文件夹下的settings.py文件中找到INSTALLED_APPS,添加并保存模型名称'books' 。 完成此操作后,我们运行python manage.py makemigrations命令将我们在模型中所做的更改写入数据库方案。 完成后,我们将应用通过python manage.py migrate命令进行的更改。

    $~ python manage.py migrateOperations to perform:
    Apply all migrations: admin, auth, books, contenttypes, sessionsRunning migrations:
    Applying contenttypes.0001_initial... OK
    Applying auth.0001_initial... OK
    Applying admin.0001_initial... OK
    Applying admin.0002_logentry_remove_auto_add... OK
    Applying admin.0003_logentry_add_action_flag_choices... OK
    Applying contenttypes.0002_remove_content_type_name... OK
    Applying auth.0002_alter_permission_name_max_length... OK
    Applying auth.0003_alter_user_email_max_length... OK
    Applying auth.0004_alter_user_username_opts... OK
    Applying auth.0005_alter_user_last_login_null... OK
    Applying auth.0006_require_contenttypes_0002... OK
    Applying auth.0007_alter_validators_add_error_messages... OK
    Applying auth.0008_alter_user_username_max_length... OK
    Applying auth.0009_alter_user_last_name_max_length... OK
    Applying auth.0010_alter_group_name_max_length... OK
    Applying auth.0011_update_proxy_permissions... OK
    Applying books.0001_initial... OK
    Applying sessions.0001_initial... OK

    Let’s test our model on the terminal and see how it works. Run the shell writing ./manage.py shell and apply the codes below.

    让我们在终端上测试我们的模型,看看它是如何工作的。 运行编写./manage.py shell并应用以下代码。

    >>> from books.models import Books
    >>> obj = Books()
    >>> obj.name = "Otostopçunun Galaksi Rehberi"
    >>> obj.author = "Douglas Adams"
    >>> obj.description = "Uzaylı gören masum köylü" # null veya blank olabilir dediğimiz için bu kısmı boş geçebilirsiniz.
    >>> obj.save()
    >>> exit()

    If you don’t get any error messages re-enter the shell and run the below codes to see either your records have been saved or not.

    如果没有收到任何错误消息,请重新输入外壳程序并运行以下代码以查看记录是否已保存。

    >>> from books.models import Books
    >>> firstBook = Books.objects.get(id=1)
    >>> firstBook.name
    'Otostopçunun Galaksi Rehberi'
    >>> exit()

    视图 (View)

    Let’s mess with the view side a bit. Open views.py file under our ‘books’ folder and print “Hello World” message to perform a ritual.

    让我们把视图端弄乱一点。 打开“ books”文件夹下的views.py文件,并打印“ Hello World”消息以执行仪式。

    from django.http import HttpResponse
    from django.shortcuts import render
    def home_view(request, *args, **kwargs):
       return HttpResponse("<h1>Hello World!</h1>")

    And we define the paths to the urls.py file as following:

    并且我们定义urls.py文件的路径如下:

    from django.contrib import admin
    from django.urls import path
    from books.views import home_view
    urlpatterns = [
       path('admin/', admin.site.urls),
       path('', home_view),
    ]

    I know it’s hard to guess. Who knows what will come across when we enter 127.0.0.1. Yes. It was a joke, a bad one. And now we see “Hello world!” message on our screen again.

    我知道这很难猜。 谁知道当我们输入127.0.0.1时会发生什么。 是。 这是一个玩笑,是一个坏玩笑。 现在我们看到“ Hello world!” 消息再次出现在我们的屏幕上。

    Image for post

    动态网址 (Dynamic URLs)

    Let’s create one more function to the view file and print parameters coming with URL.

    让我们为视图文件创建另一个函数,并打印URL附带的参数。

    def book_detail_view(request, book_id, *args, **kwargs):
       return HttpResponse(f"<h1>Hey {book_id} </h1>")

    Of course, there’s no router to run this code. So, add path('books/<int:book_id>', book_detail_view) into urlpatterns. And do not forget to importbook_detail_view from book.view

    当然,没有路由器可以运行此代码。 因此,将path('books/<int:book_id>', book_detail_view)到urlpatterns中。 而且不要忘记导入book_detail_viewbook.view

    Now, we can see Hey 1 message when we enter http://127.0.0.1:8000/books/1 address. We used url parameter as a string now. Let’s try to use it to bring book information from our database.

    现在,当我们输入http://127.0.0.1:8000/books/1地址时,我们可以看到Hey 1消息。 我们现在使用url参数作为字符串。 让我们尝试使用它来从我们的数据库中获取书籍信息。

    def book_detail_view(request, book_id, *args, **kwargs):
       obj = Books.objects.get(id=book_id)
       return HttpResponse(f"Kitap Adı: {obj.name} Yazarı: {obj.author}")

    It’s easy, isn’t it? Now, when we visit http://127.0.0.1:8000/books/1 address on browser, we’ll see the name and author of a book which its id is 1 in database.

    很简单,不是吗? 现在,当我们在浏览器上访问http://127.0.0.1:8000/books/1地址时,我们将在数据库中看到ID为1的一本书的名称和作者。

    Image for post

    If we enter an id that doesn’t exist in our database, it will show up an error that there’s no data matched with that id. Let’s catch that error up and return a 404 page.

    如果我们输入数据库中不存在的ID,则会显示错误,提示没有与该ID匹配的数据。 让我们赶上该错误并返回404页面。

    from django.http import HttpResponse, Http404
    # import HttpResponse and Http404 from django.http
    
    
    # in book_detail_view function 
    try:
       obj = Books.objects.get(id=book_id)
    except:
       raise Http404
    ####
    Image for post
    Default 404 page
    默认404页面

    As I said before, it should be a CRUD application so let’s create a form for the book creation part. Create a file named /templates/components/form.htmland create one more file named form.py under our application folder.

    如前所述,它应该是CRUD应用程序,因此让我们为书籍创建部分创建一个表单。 在我们的应用程序文件夹下创建一个名为/templates/components/form.html的文件,并再创建一个名为form.py的文件。

    from django import forms
    from .models import Books
    class BooksForm(forms.modelForm):
       class Meta:
          model = Books
          field = ['name', 'author', 'description', 'image']
       def clean_description(self):
          description = self.cleaned_data.get('description')
          if len(description) > 300:
             raise forms.ValidationError('This is too long')
          return description

    We wrote the above code into theform.py file. When we started to our project and created a model, we defined name, author, and description fields as TextField. In the model we created by calling Django’s ‘forms’ class, we want it to create a form based on the data we want. I wanted to give an example of how we can control the data by limiting the book description. I just wanted to take advantage of Django’s benefits.

    我们将上面的代码写入了form.py文件。 当我们开始我们的项目并创建模型时,我们将名称,作者和描述字段定义为TextField。 在我们通过调用Django的“表单”类创建的模型中,我们希望它根据所需数据创建表单。 我想举一个例子,说明如何通过限制书籍的描述来控制数据。 我只是想利用Django的优势。

    Now, we can use our form in views. Create a function named book_create_view , add it onto urls.py in order to run it when create-book action comes.

    现在,我们可以在视图中使用表单了。 创建一个名为book_create_view的函数,将其添加到urls.py以便在执行create-book操作时运行它。

    from .form import BooksForm
    def book_create_view(request, *args, **kwargs):
       form = BooksForm(request.POST or None)
       if form.is_valid():
          obj = form.save(commit=False)
          obj.save()
          form = BooksForm()
       return render(request, 'components/form.html', 
       context={'form':form})

    On the code above, we imported BooksForm from form.py file. While creating a model, we determined which fields should be filled or not. Accordingly, we obtain control from the form class of Django, if there is no data entry other than the expected data, we save the incoming form.

    在上面的代码中,我们从form.py文件导入了BooksForm。 在创建模型时,我们确定应填充或不填充哪些字段。 因此,我们从Django的表单类中获取控件,如果除了期望的数据之外没有其他数据输入,则保存传入的表单。

    Let’s see our form template on the interface. form.html file as follows:

    让我们在界面上查看表单模板。 form.html文件如下:

    <form method="POST"> {% csrf_token %}
       {{ form.as_p }}
       <button type="submit">Save</button>
    </form>

    form.as_p means, wrap all elements between <p> tags. Cross-site request forgery (CSRF) is a web security vulnerability that allows an attacker to induce users to perform actions that they do not intend to perform. To block these kind of attacks, it creates a token and checks it on back-end. This is an digressive of our subject but useful information.

    form.as_p表示将所有元素包装在<p>标记之间。 跨站点请求伪造(CSRF)是一个Web安全漏洞,攻击者可以利用该漏洞诱使用户执行他们不打算执行的操作。 为了阻止此类攻击,它将创建令牌并在后端对其进行检查。 这与我们的主题无关,但有用的信息。

    I did not want to make an explanation for the deletion and updating process, in order to avoid the word crowd. To delete a data, you can use Books.object.filter(id=book_id).delete() and here is the update method:

    我不想解释删除和更新过程,以避免出现“拥挤”一词。 要删除数据,可以使用Books.object.filter(id=book_id).delete() ,这是更新方法:

    book = Books.object.get(id=1)
    book.name = "Yeni isim"
    book.save()

    模板,REST API,Djangorestframework ve测试#3 (Templates, REST API, Djangorestframework ve Tests #3)

    Now, we will send the data we have in an HTML Template file and print it on the screen more regularly.

    现在,我们将发送HTML模板文件中的数据,并更定期地将其打印在屏幕上。

    模板 (Template)

    First of all, we prepare the template folder. We create a folder named templates in the main directory. In order to determine the template path, we find the TEMPLATESarray in our settings.py file and edit the ‘DIRS’ into [os.path.join(BASE_DIR, "templates")] . Now we have specified our template path. We create an HTML file named /pages/home.html in our templates folder and you can write anything indicating that file is there. I wrote “Hello World from home.html file”. Next, we configure the home_view function in the views section, which we define on the home page, not directly to the screen, but to call our HTML file.

    首先,我们准备模板文件夹。 我们在主目录中创建一个名为template的文件夹。 为了确定模板路径,我们在settings.py文件中找到TEMPLATES数组,然后将'DIRS'编辑到[os.path.join(BASE_DIR, "templates")] 。 现在,我们已经指定了模板路径。 我们在模板文件夹中创建一个名为/pages/home.htmlHTML文件,您可以编写任何指示该文件存在的文件。 我写了“来自home.html文件的Hello World”。 接下来,我们在视图部分中配置home_view函数,该函数在主页上定义,而不是直接在屏幕上定义,而是调用HTML文件。

    def home_view(request, *args, **kwargs): 
       return render(request, 'pages/home.html', context={} status=200)

    To run away from repeated-code let’s create a file named base.html and fill it with the codes we’ll use in all pages such as meta tags, titles, etc.

    为了避免重复代码,我们创建一个名为base.html的文件,并在其中填充我们将在所有页面中使用的代码,例如meta标签,标题等。

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>DjangoReact</title>
       </head>
       <body>
          {% block content %}
          {% endblock content %}
       </body>
    </html>

    If you have ever used a template engine such as twig and blade, this idea won’t sound strange to you. If you have never used it, you may take a look at the documentation pages and see the basic usage and come back. Basically, the template engines allow us to compile data that comes from the backend in dynamic pages, which we break into pieces — menu, header, meta tags, sidebar, etc. — allows us to run our files according to our needs. I continue without distracting the subject.

    如果您曾经使用过诸如树枝和刀片之类的模板引擎,这个想法对您来说听起来并不奇怪。 如果您从未使用过它,则可以查看文档页面,查看基本用法然后再回来。 基本上,模板引擎允许我们在动态页面中编译来自后端的数据,我们将这些数据分成几部分-菜单,标题,元标记,侧边栏等,从而使我们能够根据需要运行文件。 我继续讲,没有分散注意力。

    Let’s use our base.html file into home.html as follows:

    让我们如下将我们的base.html文件使用到home.html中:

    {% extends 'base.html' %}
    {% block content %}
       DjangoReact
       <!--- Buraya yazdıklarımız base.html sayfası çağrılarak block content kısmına yerleştirilecek -->
    {% endblock content %}
    Image for post

    As you can see above, when we enter the main page, it now calls our home.html page. When the page is compiled, as I mentioned above, we integrate it into base.html and get an output like this.

    如上所示,当我们进入主页时,它现在称为home.html页面。 如前所述,在编译页面时,我们将其集成到base.html中并获得类似的输出。

    REST API — DjangoRestFramework (REST API — DjangoRestFramework)

    We ran our template and dug a bit. Now it’s better to return our data as json since we use it on front-end. We made our return value as HttpResponse in book_detail_view and home_view. In home_view, we rendered our page over HTML, and in the book_detail_view section, we directly printed it on the screen. We call the JsonResponse class from django.http. We will print our data in a slightly more useful way, rather than directly.

    我们运行了模板并进行了一些挖掘。 现在最好将数据作为json返回,因为我们在前端使用了它。 我们在book_detail_view和home_view中将返回值设为HttpResponse。 在home_view中,我们通过HTML渲染页面,在book_detail_view部分中,我们将其直接打印在屏幕上。 我们从django.http调用JsonResponse类。 我们将以一种稍微有用的方式而不是直接地打印数据。

    def book_detail_view(request, book_id, *args, **kwargs):
       data = {
           "id" : book_id
       }
       status = 200
       try:
         obj = Books.objects.get(id=book_id)
         data['name'] = obj.name
         data['author'] = obj.author
         data['description'] = obj.description
       except:
         data['message'] = "Not Found"
         status = 404
       return JsonResponse(data,status=status)

    In the code above, if searched data exist it returns data object filled with our results. if it’s not it returns an error message. Instead of returning the Http404 when it was not found, we printed an error code and a message on the screen, because we now provide API.

    在上面的代码中,如果存在搜索到的数据,它将返回填充有我们结果的数据对象。 如果不是,则返回错误消息。 因为现在提供了API,所以没有在未找到Http404的情况下返回Http404,而是在屏幕上打印了错误代码和消息。

    Image for post

    Let’s write our code that lists all the books.

    让我们编写列出所有书籍的代码。

    def books_list(request, *args, **kwargs):
       bist = Books.object.all()
       books = [{"id":x.id, "name":x.name, "author":x.author, "description":x.description} for x in blist]
    data = {
            "response" : books
       }
       return JsonResponse(data)

    Before moving on to the React section, let’s use the API we created with JavaScript. We don’t always have to use react after all. Our process is to write a very simple JavaScript code. We will send an Http request and print the returned data on the screen. We use the code below to print all the books we have on the homepage as a list.

    在进入React部分之前,让我们使用通过JavaScript创建的API。 毕竟,我们不必总是使用react。 我们的过程是编写一个非常简单JavaScript代码。 我们将发送Http请求,并将返回的数据打印在屏幕上。 我们使用下面的代码将主页上所有的书籍作为列表打印。

    {% extends 'base.html' %}
    {% block content %}
      <h1>DjangoReact</h1>
      <div id="books"></div>
      <script>
        const booksElement = document.getElementById('books')
        booksElement.innerHTML = 'Just a moment'
        const xhr = new XMLHttpRequest()
        const method = 'GET' // or POST
        const url = '/books/'
        const responseType = 'json'
        xhr.responseType = responseType
        xhr.open(method, url)
        xhr.onload = function(){
          const urlResponse = xhr.response
          const Items = urlResponse.response
          let finalExecution = "" 
          let i;
          for(i = 0; i < Items.length; i++){
            let currentItem = "<h3>" + Items[i].name + "</h3>"
            currentItem += "<h4>" + Items[i].author + "<h4>"
            currentItem += "<p>" + Items[i].description + "</p>"
            finalExecution += currentItem 
          }
        booksElement.innerHTML = finalExecution
        }
    xhr.send()
    </script>
    {% endblock content %}

    I didn’t do anything about styling because our aim is to understand the main idea.

    我没有做任何关于样式的事情,因为我们的目的是理解主要思想。

    We did book creation page with book_create_view function and it creates a form according to model rules. Let’s create our own form and send it to API.

    我们使用book_create_view函数完成了书创建页面,并根据模型规则创建了一个表单。 让我们创建自己的表单并将其发送到API。

    <form method="POST" action="/create-book">
       <input type="hidden" value="/" name="next"/>
       <input type="text" name="name" />
       <input type="text" name="author" />
       <textarea type="text" name="description"></textarea>
       <button type="submit">Save</button>
    </form>

    After adding the form into home page, fill the form and click the save button. Yes, you see an error message as CSRF verification failed. Request aborted.

    将表单添加到主页后,填写表单并单击保存按钮。 是的,由于CSRF verification failed. Request aborted. ,您会看到一条错误消息CSRF verification failed. Request aborted. CSRF verification failed. Request aborted.

    When we created our first form we sent a csrf_token. We should apply the same thing here also. We add our {% csrf_token %}code inside the form. After adding it, we made our form working.

    创建第一个表单时,我们发送了一个csrf_token。 我们也应该在这里应用相同的内容。 我们在表单内添加{% csrf_token %}代码。 添加完后,我们使表格生效。

    Now it stays in the same page after saved our form but it’s better to redirect to the previous page. We already prepared our plan in the above code by adding an input named next. We’ll use that as a redirection page if creation is done. Let’s call the redirect argument from the django.shortcuts library to our view page ( from django.shortcuts import render, redirect). We will use our incoming ‘next ’ value as the page to be redirected and, if the recording has occurred, we will redirect it.

    保存表单后,它现在保留在同一页面上,但是最好重定向到上一页。 通过添加名为next的输入,我们已经在上面的代码中准备了计划。 如果创建完成,我们将其用作重定向页面。 让我们将重定向参数从django.shortcuts库调用到我们的视图页面( from django.shortcuts import render, redirect )。 我们将使用传入的“ next”值作为要重定向的页面,如果发生了记录,我们将对其进行重定向。

    def book_create_view(request, *args, **kwargs):
      form = BooksForm(request.POST or None)
      next_url = request.POST.geT('next') or None
      if form.is_valid()
        obj = form.save(commit=False)
        obj.save()
        if next_url != None:
          return redirect(next_url)
        form = BooksForm()
      return render(request, 'components/form.html', context={'form': form}

    n order to take a little precaution, let’s make sure that the address to be routed is secure. If somehow a different address is sent inside as the next value, the user will not be directed to an unwanted address. To do it, let’s add our secure addresses to the settings.py file with the ALLOWED_HOSTS array.

    n为了采取一些预防措施,请确保要路由的地址是安全的。 如果以某种方式将另一个地址作为下一个值发送到内部,则不会将用户定向到不需要的地址。 为此,让我们使用ALLOWED_HOSTS数组将安全地址添加到settings.py文件中。

    ALLOWED_HOSTS = ['127.0.0.1', 'yourdomainname.com', 'localhost']

    To be able to check these addresses, we’ll use Django’s features. Import the safe_url from django.utils.http import is_safe_url code to the page. This code will check the page which wanted to redirect is safe or not by comparing the URLs in ALLOWED_HOSTS and the redirection page.

    为了能够检查这些地址,我们将使用Django的功能。 from django.utils.http import is_safe_urlfrom django.utils.http import is_safe_url代码from django.utils.http import is_safe_url页面。 此代码将通过比较ALLOWED_HOSTS中的URL和重定向页面来检查要重定向的页面是否安全。

    We add another check to the line where we control if next_url != None and save it as if next_url != None and is_safe_url(next_url) . When we run this form and submit form, we will see an error, because we did not call our settings to the page and show our accepted addresses. We import our settings to our page with from django.conf import settings . Within the page, we define ALLOWED_HOSTS = settings. We define ALLOWED_HOSTS variable and send it as the second parameter into is_safe_url() (next_url, ALLOWED_HOSTS) .The final version of our view code is as follows:

    我们在控制if next_url != None的行中添加另一个检查,并将其保存为if next_url != None and is_safe_url(next_url) 。 当我们运行此表单并提交表单时,会看到一个错误,因为我们没有在页面上调用设置并显示我们接受的地址。 我们使用from django.conf import settings到页面。 在页面中,我们定义ALLOWED_HOSTS =设置。 我们定义ALLOWED_HOSTS变量并将其作为第二个参数发送到is_safe_url()(next_url,ALLOWED_HOSTS)中。我们的视图代码的最终版本如下:

    def book_create_view(request, *args, **kwargs):
      form = BooksForm(request.POST or None)
      next_url = request.POST.geT('next') or None
      if form.is_valid()
        obj = form.save(commit=False)
        obj.save()
        if next_url != None and is_safe_url(next_url):
          return redirect(next_url)
        form = BooksForm()
      return render(request, 'components/form.html', context={'form': form}

    To test our safe_url code, you may change any other address as next value via form.

    要测试我们的safe_url代码,您可以通过表格将其他任何地址更改为下一个值。

    序列化器 (Serializer)

    The serialization process is the process of converting the class or objects that we have into the format to be stored or sent. In cases where we do not know the types of objects or classes we have registered, we can increase the usability for later use by serializing. This process will bring increased performance and our data to shrink.

    序列化过程是将我们拥有的类或对象转换为要存储或发送的格式的过程。 如果我们不知道已注册的对象或类的类型,则可以通过序列化来增加以后使用的可用性。 此过程将带来更高的性能,并且我们的数据将减少。

    Let’s install djangorestframework, the django library which will enable us to play on the REST API and help us with serialization, by writing pipenv install djangorestframework code on the console and install it in our project. Add rest_framework to our INSTALLED_APPS array in settings.py file. We can now use this library by calling our page.

    让我们安装djangorestframework,这是django库,通过在控制台上编写pipenv install djangorestframework代码并将其安装在我们的项目中,它将使我们能够在REST API上播放并帮助我们进行序列化。 在settings.py文件rest_framework添加到我们的INSTALLED_APPS数组中。 现在,我们可以通过调用页面来使用该库。

    Actually, we did the serialization process since we definebooks = [{'id':x.id, 'name':x.name, 'author':x.author, 'description': x.description} for x in blist] and now separating this process let’s add some little changes.

    实际上,由于我们books = [{'id':x.id, 'name':x.name, 'author':x.author, 'description': x.description} for x in blist]定义了books = [{'id':x.id, 'name':x.name, 'author':x.author, 'description': x.description} for x in blist] ,所以我们进行了序列化过程。 books = [{'id':x.id, 'name':x.name, 'author':x.author, 'description': x.description} for x in blist]然后分离此过程,让我们添加一些小的更改。

    from django.db import models
    
    
    # Create your models here.
    class Books(models.Model):
        # id = models.AutoField(primary_key=True)
        name = models.TextField(blank=False, null=False)
        author = models.TextField(blank=False, null=False)
        description = models.TextField()
        image = models.FileField(upload_to='images/', blank=True, null=True)
        
        def serialize(self):
            return {
                "id" : self.id,
                "name": self.name,
                "author": self.author,
                "description" : self.description,
                "image" : self.image
            }

    After defining serialize function inside of Model, we can clarify books_list view.

    在Model内部定义了序列化函数后,我们可以澄清books_list视图。

    def books_list(request, *args, **kwargs):
      blist = Books.objects.all()
      books = [x.serialize() for x in blist]
      data = {
        'response' : books
      }
      return JsonResponse(data)

    Now we have got rid of the code clutter we had while defining our variable.

    现在,我们摆脱了定义变量时的代码混乱情况。

    We did book creation process by using Form before. Now let’s rewrite it with the serialization process in djangorestframework. Create a file named serializer.py and adapt the code we wrote in form.py here.

    我们之前通过使用Form进行过书的创建过程。 现在,让我们使用djangorestframework中的序列化过程重写它。 创建一个名为serializer.py的文件,并修改此处在form.py中编写的代码。

    from django.conf import settings 
    from rest_framework import serializers
    from .models import Books
    
    
    
    
    class BooksSerializer(serializers.ModelSerializer):
        class Meta:
            model = Books
            fields = ['name','author','description']
    
    
        def validate_description(self, value):
            if len(value) > 300:
                raise serializers.ValidationError("This is too long")
            return value
    from django import forms 
    
    
    from .models import Books
    
    
    class BooksForm(forms.ModelForm):
        class Meta:
            model = Books
            fields = ['name','author','description']
        def clean_description(self):
            description = self.cleaned_data.get('description')
            if len(description) > 300:
                raise forms.ValidationError("This is too long")
            return description

    Serializer file ready-to-use so we’ll use it in view side.

    序列化器文件可立即使用,因此我们将在视图侧使用它。

    from .serializer import BooksSerializer
    
    
    def book_crete_view(request, *args, **kwargs):
        serializer = BooksSerializer(data=request.POST or None)
        if serializer.is_valid():
            serializer.save()
            return JsonResponse(serializer.data, status=200=
        return JsonResponse({}, status=400)
    
    
    def book_create_view_pure(request, *args, **kwargs):
        form = BooksForm(request.POST or None)
        next_url = request.POST.get('next') or None
        if form.is_valid():
            obj = form.save(commit=False)	   
            obj.save()
            if next_url != None and is_safe_url(next_url,ALLOWED_HOSTS):
                return redirect(next_url)
            form = BooksForm()	        
        return render(request, 'components/form.html', context={'form': form})

    I changed book_create_view function we created earlier by adding _pure. I rewrote this function with the serializer I created. Previously we had rendered an html file. Now, we have printed our return result as json to be able to use the results on the front-end side.

    我通过添加_pure更改了我们先前创建的book_create_view函数。 我用我创建的序列化器重写了此功能。 以前我们已经渲染了一个html文件。 现在,我们将返回结果打印为json以便能够在前端使用结果。

    Let’s roll out from Django Views to Django Rest Framework Views starting from changing book_create_view function.

    让我们从更改book_create_view函数开始,从Django Views扩展到Django Rest Framework Views。

    @api_view(['POST'])
    def book_create_view(request, *args, **kwargs):
       serializer = BooksSerializer(data=request.POST or None)
       if serializer.is_valid(raise_exception = True):
          serializer.save()
          return Response(serializer.data)
       return Response({}, status=400)

    Adding the@api_view() code above our function, we provide the function view as an API. We can determine the accessibility of this function by giving parameters such as POST-GET. Our return method is Response instead of JsonResponse now.

    在函数上方添加@api_view()代码,我们将函数视图作为API提供。 我们可以通过提供诸如POST-GET之类的参数来确定此功能的可访问性。 我们的返回方法是Response而不是JsonResponse。

    That’s all! — not, of course. Well, we’ve learned what we need to keep practicing so far. To learn more about Djangorestframework you may visit its documentation page, you can learn which parameters you can use.

    就这样! —当然不是。 好了,到目前为止,我们已经了解了继续练习所需要的知识。 要了解有关Djangorestframework的更多信息,请访问其文档页面,您可以了解可以使用哪些参数。

    测验 (Tests)

    The last operation will be done on django side is testing. Even you have never done testing before, it’s time to take a step to TDD(Test Driven Development). If we get used to doing this — even if it’s in another language — it will save us time.

    最后的操作将在Django端进行测试。 即使您以前从未进行过测试,也该是迈向TDD(测试驱动开发)的时候了。 如果我们习惯于这样做(即使是另一种语言),也可以节省我们的时间。

    When we install our application, it brings our test file with it. When we write ./manage.py tests books on the command line, it will run our test and our first output will be as follows:

    当我们安装应用程序时,它会附带我们的测试文件。 当我们在命令行上编写./manage.py tests books时,它将运行我们的测试,并且我们的第一个输出如下:

    System check identified no issues (0 silenced).----------------------------------------------------------------------
    Ran 0 tests in 0.000sOK

    The results are incredible, because there is no test code in our file. Hoping to keep this incredibly we keep moving. Let’s write a test code for Books model into test.py file.

    结果令人难以置信,因为我们的文件中没有测试代码。 希望我们能继续保持下去。 让我们将Books模型的测试代码写入test.py文件。

    from django.test import TestCase
    from .models import Books
    
    
    
    
    class BookTestCase(TestCase):
       def test_book_created(self):
           book_obj = Books.objects.create(name='Anonymous', author='John Doe', description='lorem ipsum sit door amet.')
           self.assertEqual(book_obj.id, 1)

    In this case, we created a book and when we run the test it will create a new book and will be tested its id. If it did not save it or the data its saved is different than it should be, it will show up an error message.

    在这种情况下,我们创建了一本书,并且在运行测试时它将创建一本新书并对其ID进行测试。 如果它没有保存它或保存的数据与应该保存的数据不同,它将显示一条错误消息。

    Creating test database for alias 'default'...
    System check identified no issues (0 silenced).
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.003sOK
    Destroying test database for alias 'default'...

    It ran successfully. If we have done self.assertEqual(book_obj.id, 5) it would show up an error as AssertionError: 1 != 5 when we run our test. While doing this for the first time, the first data given to database should have id 1, we say check it for 5. The same error would come even if the id of the created object is not 1. You can see more usages of Django’s documentation on testing.

    运行成功。 如果我们完成了self.assertEqual(book_obj.id, 5)则在运行测试时,它将显示为AssertionError: 1 != 5的错误。 第一次执行此操作时,提供给数据库的第一个数据应具有ID 1,我们说检查它是否为5。即使所创建对象的ID不为1,也会出现相同的错误。您可以看到Django的更多用法测试文档。

    Well, after we did everything how we can deploy our project? In this case, there’s information in almost every service providers’ pages. I’ll give you resources directly since it has no side can be interpreted. You may run Django in cloud-based platforms. The most popular about it is Heroku. You may publish your first project by following its own instructions. If you want to use your own server there are instructions on DigitalOcean. These processes will be exactly same in servers you can access with ssh.

    好吧,在完成所有工作之后,我们如何部署项目? 在这种情况下,几乎每个服务提供商的页面中都有信息。 我将直接为您提供资源,因为它没有任何一方可以解释。 您可以在基于云的平台上运行Django。 最受欢迎的是Heroku。 您可以按照自己的说明发布第一个项目。 如果要使用自己的服务器,请参阅DigitalOcean。 在您可以通过ssh访问的服务器中,这些过程将完全相同。

    So far, we have completed the operations we will do with Django. Adding books, listing, and printing the required book on the screen. I did not add the repetitive codes to avoid extending the article. Of course, not everything ends with this much. There is an alternative to every line of code we write. Each title goes deeper within itself. Starting with the simplest, we will continue to improve ourselves with plenty of practice. You can see the source codes I use in this article on Github, and contribute if you wish.

    到目前为止,我们已经完成了对Django的操作。 在屏幕上添加书本,列出并打印所需的书本。 我没有添加重复代码以避免扩展本文。 当然,并非一切都以这么多结尾。 我们编写的每一行代码都有替代方法。 每个标题本身都有更深的含义。 从最简单的开始,我们将通过大量实践不断提高自己。 您可以在Github上查看我在本文中使用的源代码,并根据需要提供帮助。

    You can contact me if you have any questions or about anything you need via my Twitter account.

    如果您有任何疑问或需要任何东西,可以通过我的Twitter帐户与我联系。

    I also have another article which is a continuation of this article named Full-Stack with Django and React — React.

    我还有另一篇文章,是该文章的继续,名为Django和React — React的Full-Stack

    Happy codding! :)

    祝大家高兴! :)

    Github, Twitter, Instagram: @bari5d

    Github,Twitter,Instagram:@ bari5d

    翻译自: https://medium.com/swlh/full-stack-with-django-and-react-django-4dcd87d57356

    django react

    展开全文
  • 描述了从零开始创建react+Django项目的过程,附有详细步骤及成品项目地址。

    环境准备

    react项目的环境准备和配置参考(后面会需要修改react项目的一些配置,建议先了解):

    https://blog.csdn.net/sysukehan/article/details/104872248

    可以直接使用的react项目地址:

    https://github.com/sysu-kehan/first-react-and-webpack-project.git

    使用Django需要python环境,pycharm IDE。

    python下载安装:https://www.python.org/downloads/

    pycharm社区版下载安装:

    https://www.jetbrains.com/pycharm/download/#section=windows

     

    安装Django

    python安装完成后,在命令行中输入:

    pip install Django

    完成安装后需要进一步校验是否安装成功。打开命令行,输入“python”后按回车键,进入python交互解释器,在交互解释器下输入校验代码:

    >>> import django
    >>> django.__version__

    能查询到版本说明安装成功。 

     

    创建Django项目

    在命令行中执行

    django-admin startproject firstReactAndDjangoProject

    创建项目成功后,进入目录,在命令行执行:

    python manage.py runserver 80

    在浏览器窗口打开http://127.0.0.1:80/,可以看到Django项目的默认html界面。

     

    用pycharm启动项目

    用pycharm打开项目,点击上方的Add Configurations...

     

    点击左上角的+号,选择python。在右边的配置中,Script path用右边的选择按钮选择项目根目录下的manage.py,parameters中手动填入runserver 80,点击OK保存。

     

    之后刚刚右上角会出现Add Configurations...的位置会出现manage,绿色三角箭头也亮起来了。

     

    在开始运行之前把所有的命令行窗口关闭,避免刚刚命令行启动的进程占用端口导致启动失败。点击绿色箭头启动项目,在pycharm中可以看到提示是一样的,项目也启动了。

     

    替换html页面为react生成的html页面

    在项目根目录下启动命令行,执行:

    python manage.py startapp index

    在项目文件夹下有一个和根目录同名的文件夹,是创建Django项目的时候一起创建的,里面包含了一些配置文件。其中urls.py文件用于查找该URL是属于哪个App,然后再从App的urls.py找到具体的URL信息。在其中添加加粗部分内容:

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

    urlpatterns = [
        path('admin/', admin.site.urls),
        path('', include('index.urls'))
    ]

     

    from django.urls import path,include:导入URL编写模块

    urlpatterns:整个项目的URL集合,每个元素代表一条URL信息。

    path(‘’, include(‘index.urls’)):URL为空,代表为网站的域名,即127.0.0.1:80,通常是网站的首页;include将该URL分发给index的urls.py处理。

     

    进入index文件夹,新建一个urls.py文件,内容如下:

    from django.urls import path
    from . import views
    urlpatterns = [
        path('', views.index)
    ]

    上述代码导入了同一目录下的views.py文件,该文件用于编写视图函数,处理URL请求信息并返回网页内容给用户。

    编辑views.py文件,添加如下内容:

    def index(request):
        return render(request, 'index.html')

    index函数必须设置参数request,该参数代表当前用户的请求对象,该对象包含用户名、请求内容和请求方式等信息,视图函数执行完成后必须使用return将处理结果返回,否则程序会抛出异常。

    index.html就是要显示的首页,也就是在react项目中编译出来的index.html。把配置好的react项目放到根目录下,重命名react项目文件夹名为webproject,然后配置views.py到这个目录中去寻找index.html。

    存放配置文件的目录下还有一个settings.py文件,设置了一些项目参数,有一个配置模板目录的参数DIRS,默认是[]。

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]
    
    

    修改为:

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'webproject/dist')],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]

    这样Django就会到webproject目录下的dist目录去寻找html文件了。BASE_DIR是项目根目录,在settings.py文件中已有定义:

    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

    启动项目后,发现html是空白的,浏览器控制台报错找不到资源,pycharm控制台报错找不到bundle.js。

    在浏览器的Elements页签中可以看到这种情况下Django是以127.0.0.1/bundle.js方式去寻找bundle.js的。但是因为没有设置寻找静态资源的路径,所以找不到。

    在settings.py中有一个参数STATIC_URL,默认值为/static/,意思是Django会到各个App目录下的static文件夹中去找静态资源,但是bundle.js并不在App里面(在这个实例中,也就是不在index/static文件夹里面),是在webproject/dist文件夹下,所以需要配置STATICFILES_DIRS属性。

    STATICFILES_DIRS = [os.path.join(BASE_DIR, 'webproject/dist')]

    注意:

    1. STATIC_URL是必须配置的属性而且属性值不能为空。如果没有配置STATICFILES_DIRS,则STATIC_URL只能识别App里的static静态资源文件夹。
    2. STATICFILES_DIRS是可选配置属性,属性值为列表或元组格式,每个列表(元组)元素代表一个静态资源文件夹,这些文件夹可自行命名。
    3. 在浏览器上访问项目的静态资源时,无论项目的静态资源文件夹是如何命名的,在浏览器上,静态资源的上级目录必须为static,而static是STATIC_URL的属性值,因为STATIC_URL也是静态资源的起始URL。

    因为在引用静态资源时上级目录必须为static,所以在html中引用js资源要在原有的资源前加上/static/,这样Django才能访问到指定的静态资源。这种情况下只能先在模板html文件中把js引用写死来规避了。修改webproject/src/index.html文件:

    <!DOCTYPE html>
    
    <html>
    
    <head>
      <meta charset="UTF-8">
      <title>first project</title>
    </head>
    
    <body>
      <div id="root"></div>
      <script type="text/javascript" src="/static/bundle.js"></script>
    </body>
    
    </html>
    
    

    这样启动后就可以访问到正确的html页面,内容也可以正确加载了。 

     

    图片引用问题

    html页面正确了,但是图片加载不出来,还是静态资源引用的问题。在webpack中,图片被打包成hash命名的资源然后在js中被引用:

    function(e,t,n){"use strict";n.r(t);var r=n(0),l=n.n(r),i=n(4),o=n.n(i),a=(n(10),n.p+"5d5d9eefa31e5e13a6610d9fa7a283bb.svg");

    这种引用方式在Django的框架下就不适用了,所以图片资源要打包到js中去,要改用url-loader来解析图片。

    安装:

    npm i url-loader -D

    修改webpack.config.js的配置(把原先的file-loader替换为url-loader):

    {
      test: /\.(png|svg|jpg|gif)$/,
        use: [
          'url-loader'
        ]
    },

    然后重新编译,新的html页面图片可以正常加载了。

     

    js引用问题

    在前面的配置过程中,为了使Django能正确引用到js,把js的引用路径写死在模板html中,这其实已经违背了使用html-webpack-plugin插件的初衷。因此可以不使用这个插件,只生成js,然后在模板html写死引用js的方法,展示页面直接使用模板html而不是编译后的html。

    在webpack.config.js中删除html-webpack-plugin和clean-webpack-plugin插件的使用(- 部分)。

    在Django项目根目录创建static文件夹,在static文件夹中再创建js和html文件夹,把webpack的打包js输出路径指定到static/js目录下。同时把模板html放到static/html目录下。

    调整Django项目settings.py的配置,使项目到static/js目录下寻找引用的js文件。

    STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static/html')]

    调整Django项目settings.py的配置,使项目到static/js目录下寻找模板的html文件(加粗部分需要调整)。

    TEMPLATES = [
        {
            'BACKEND': 'django.template.backends.django.DjangoTemplates',
            'DIRS': [os.path.join(BASE_DIR, 'static/html')],
            'APP_DIRS': True,
            'OPTIONS': {
                'context_processors': [
                    'django.template.context_processors.debug',
                    'django.template.context_processors.request',
                    'django.contrib.auth.context_processors.auth',
                    'django.contrib.messages.context_processors.messages',
                ],
            },
        },
    ]

    启动项目,运行正常。

     

    项目地址

    https://github.com/sysu-kehan/firstReactAndDjangoProject.git

     

    参考资料

    《玩转Django 2.0》黄永祥/著,清华大学出版社

     

    展开全文
  • django reactWeb development has grown rapidly over the last decade, and there's a long list of frameworks to choose from when building your projects. A developer’s decision on what framework(s) to ...

    django react

    Web development has grown rapidly over the last decade, and there's a long list of frameworks to choose from when building your projects. A developer’s decision on what framework(s) to use for a project is usually influenced by a number of specific factors; one of which is the complexity of the framework in defining the separate tasks that make up that project.

    Web开发在过去十年中发展Swift,在构建项目时,有很多框架可供选择。 开发人员对项目使用哪种框架的决定通常受许多特定因素的影响; 其中之一是框架的复杂性,难以定义组成该项目的单独任务。

    In this tutorial, we will look at how to build a Todo application using Django and React. I have chosen these frameworks because:

    在本教程中,我们将研究如何使用Django和React构建Todo应用程序。 我选择这些框架是因为:

    1. React is a framework of JavaScript that is great for developing SPAs (single page applications) and it has a large developer community, so I don’t have to waste good weeks stuck on an error.

      React是一个JavaScript框架,非常适合开发SPA(单页应用程序),并且拥有庞大的开发人员社区,因此我不必浪费很多时间来解决错误。
    2. Django is a web framework of Python that simplifies common practices in web development. Django has you covered, from authentication to session management, it will save you loads of time.

      Django是Python的网络框架,可简化网络开发中的常见做法。 Django涵盖了从身份验证到会话管理的所有内容,它将为您节省大量时间。

    Django and React make an awesome combination to build this application with, owing to React’s SPA optimizations, and Django’s long list of helpful libraries. For this application to work correctly, the frontend (React) will have to interact with the backend i.e retrieve and store data. To create the interface for interaction, we will build an API (Application Programming Interface) on the backend, using the Django REST framework (DRF).

    由于React的SPA优化以及Django的有用库的长列表,Django和React进行了很棒的组合来构建此应用程序。 为了使该应用程序正常工作,前端(React)将必须与后端进行交互,即检索和存储数据。 为了创建交互界面,我们将使用Django REST框架(DRF)在后端构建一个API(应用程序编程接口)。

    At the end of this tutorial, we will have the final application that looks like this:

    在本教程的最后,我们将拥有最终的应用程序,如下所示:

    The source code for this tutorial is available here on GitHub.

    本教程的源代码可以在这里 GitHub上。

    先决条件 ( Prerequisites )

    To follow along with this tutorial, you will need the following installed on your machine:

    要继续学习本教程,您需要在计算机上安装以下软件:

    1. Python.

      Python。
    2. Pip.

      点子
    3. Pipenv.

      Pipenv。

    Pipenv is a production-ready tool that aims to bring the best of all packaging worlds to the Python world. It harnesses Pipfile, pip, and virtualenv into one single command.

    Pipenv是可用于生产的工具,旨在将所有包装领域的精华带入Python世界。 它将Pipfile,pip和virtualenv整合到一个命令中。

    Let’s dive in and get started!

    让我们开始吧!

    设置后端 ( Setting up the Backend )

    In this section, we will set up the backend and create all the folders that we need to get things up and running, so launch a new instance of a terminal and create the project’s directory by running this command:

    在本节中,我们将设置后端并创建启动和运行所需的所有文件夹,因此启动终端的新实例并通过运行以下命令创建项目的目录:

    $mkdir django-todo-react

    Next, we will navigate into the directory:

    接下来,我们将导航到目录:

    $cd django-todo-react

    Now we will install Pipenv using pip and activate a new virtual environment:

    现在,我们将使用pip安装Pipenv并激活一个新的虚拟环境:

    $ pipinstall pipenv
    $ pipenv shell

    Note: You should skip the first command if you already have Pipenv installed.

    注意:如果已经安装了Pipenv,则应跳过第一个命令。

    Let’s install Django using Pipenv then create a new project called backend:

    让我们使用Pipenv安装Django,然后创建一个名为backend的新项目:

    $ pipenvinstall django
    $ django-admin startproject backend

    Next, we will navigate into the newly created backend folder and start a new application called todo. We will also run migrations and start up the server:

    接下来,我们将导航到新创建的后端文件夹并启动一个名为todo的新应用程序。 我们还将运行迁移并启动服务器:

    $cd backend
    $ python manage.py startapp todo
    $ python manage.py migrate
    $ python manage.py runserver

    At this point, if all the commands were entered correctly, we should see an instance of a Django application running on this address — http://localhost:8000

    此时,如果所有命令输入正确,我们应该看到在该地址上运行的Django应用程序实例— http:// localhost:8000

    注册Todo应用程序 (Registering the Todo application)

    We are done with the basic setup for the backend, let’s start with the more advanced things like registering the todo application as an installed app so that Django can recognise it. Open the backend/settings.py file and update the INSTALLED_APPS section as so:

    我们已经完成了后端的基本设置,让我们从更高级的事情开始,例如将todo应用程序注册为已安装的应用程序,以便Django可以识别它。 打开backend/settings.py文件,并按以下方式更新INSTALLED_APPS部分:

    # backend/settings.py
    
        # Application definition
        INSTALLED_APPS = [
            'django.contrib.admin',
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'todo' # add this 
          ]

    定义Todo模型 (Defining the Todo model)

    Let's create a model to define how the Todo items should be stored in the database, open the todo/models.py file and update it with this snippet:

    让我们创建一个模型来定义Todo项应如何存储在数据库中,打开todo/models.py文件并使用以下代码片段对其进行更新:

    # todo/models.py
    
        from django.db import models
        # Create your models here.
    
        # add this
        class Todo(models.Model):
          title = models.CharField(max_length=120)
          description = models.TextField()
          completed = models.BooleanField(default=False)
    
          def _str_(self):
            return self.title

    The code snippet above describes three properties on the Todo model:

    上面的代码段描述了Todo模型的三个属性:

    • Title

      标题

    • Description

      描述

    • Completed

      已完成

    The completed property is the status of a task; a task will either be completed or not completed at any time. Because we have created a Todo model, we need to create a migration file and apply the changes to the database, so let’s run these commands:

    完成的属性是任务的状态; 任务将随时完成或未完成。 因为我们已经创建了Todo模型,所以我们需要创建一个迁移文件并将更改应用到数据库,因此让我们运行以下命令:

    $ python manage.py makemigrations todo
    $ python manage.py migrate todo

    We can test to see that CRUD operations work on the Todo model we created using the admin interface that Django provides out of the box, but first, we will do a little configuration.

    我们可以测试一下CRUD操作是否可以在使用Django开箱即用的管理界面创建的Todo模型上运行,但是首先,我们将进行一些配置。

    Open the todo/admin.py file and update it accordingly:

    打开todo/admin.py文件并相应地更新它:

    # todo/admin.py
    
        from django.contrib import admin
        from .models import Todo # add this
    
        class TodoAdmin(admin.ModelAdmin):  # add this
          list_display = ('title', 'description', 'completed') # add this
    
        # Register your models here.
        admin.site.register(Todo, TodoAdmin) # add this

    We will create a superuser account to access the admin interface with this command:

    我们将使用以下命令创建一个超级用户帐户来访问管理界面:

    $ python manage.py createsuperuser

    You will be prompted to enter a username, email and password for the superuser. Be sure to enter details that you can remember because you will need them to log in to the admin dashboard shortly.

    系统将提示您输入超级用户的用户名,电子邮件和密码。 确保输入您可以记住的详细信息,因为您将需要它们很快登录到管理仪表板。

    Let’s start the server once more and log in on the address — http://localhost:8000/admin:

    让我们再次启动服务器并登录以下地址: http:// localhost:8000 / admin:

    $ python manage.py runserver

    We can create, edit and delete Todo items using this interface. Let’s go ahead and create some:

    我们可以使用此界面创建,编辑和删除待办事项。 让我们继续创建一些:

    Awesome work so far, be proud of what you’ve done! In the next section, we will see how we can create the API using the Django REST framework.

    迄今为止的出色工作,为您所做的一切感到自豪! 在下一节中,我们将看到如何使用Django REST框架创建API。

    设置API ( Setting up the APIs )

    Now, we will quit the server (CONTROL-C) then install the djangorestframework and django-cors-headers using Pipenv:

    现在,我们将退出服务器(CONTROL-C),然后使用Pipenv安装djangorestframeworkdjango-cors-headers

    $ pipenvinstall djangorestframework django-cors-headers

    We need to add rest_framework and corsheaders to the list of installed applications, so open the backend/settings.py file and update the INSTALLED_APPS and MIDDLEWARE sections accordingly:

    我们需要将rest_frameworkcorsheaders添加到已安装的应用程序列表中,因此打开backend/settings.py文件并相应地更新INSTALLED_APPSMIDDLEWARE部分:

    # backend/settings.py
    
        # Application definition
        INSTALLED_APPS = [
            'django.contrib.admin',
            'django.contrib.auth',
            'django.contrib.contenttypes',
            'django.contrib.sessions',
            'django.contrib.messages',
            'django.contrib.staticfiles',
            'corsheaders',            # add this
            'rest_framework',         # add this 
            'todo',
          ]
    
        MIDDLEWARE = [
            'corsheaders.middleware.CorsMiddleware',    # add this
            'django.middleware.security.SecurityMiddleware',
            'django.contrib.sessions.middleware.SessionMiddleware',
            'django.middleware.common.CommonMiddleware',
            'django.middleware.csrf.CsrfViewMiddleware',
            'django.contrib.auth.middleware.AuthenticationMiddleware',
            'django.contrib.messages.middleware.MessageMiddleware',
            'django.middleware.clickjacking.XFrameOptionsMiddleware',
        ]

    Add this code snippet to the bottom of the backend/settings.py file:

    将此代码段添加到backend/settings.py文件的底部:

    # we whitelist localhost:3000 because that's where frontend will be served
        CORS_ORIGIN_WHITELIST = (
             'localhost:3000/'
         )

    Django-cors-headers is a python library that will help in preventing the errors that we would normally get due to CORS. rules. In the CORS_ORIGIN_WHITELIST snippet, we whitelisted localhost:3000 because we want the frontend (which will be served on that port) of the application to interact with the API.

    Django-cors-headers是一个python库,它将有助于防止我们通常会由于CORS而收到错误。 规则。 在CORS_ORIGIN_WHITELIST片段中,我们将localhost:3000列入了白名单,因为我们希望应用程序的前端(将在该端口上提供服务)与API进行交互。

    为Todo模型创建序列化器 (Creating serializers for the Todo model)

    We need serializers to convert model instances to JSON so that the frontend can work with the received data easily. We will create a todo/serializers.py file:

    我们需要序列化程序将模型实例转换为JSON,以便前端可以轻松处理接收到的数据。 我们将创建一个todo/serializers.py文件:

    $touch todo/serializers.py

    Open the serializers.py file and update it with the following code.

    打开serializers.py文件,并使用以下代码对其进行更新。

    # todo/serializers.py
    
        from rest_framework import serializers
        from .models import Todo
    
        class TodoSerializer(serializers.ModelSerializer):
          class Meta:
            model = Todo
            fields = ('id', 'title', 'description', 'completed')

    In the code snippet above, we specified the model to work with and the fields we want to be converted to JSON.

    在上面的代码片段中,我们指定了要使用的模型以及要转换为JSON的字段。

    创建视图 (Creating the View)

    We will create a TodoView class in the todo/views.py file, so update it with the following code:

    我们将在todo/views.py文件中创建一个TodoView类,因此请使用以下代码对其进行更新:

    # todo/views.py
    
        from django.shortcuts import render
        from rest_framework import viewsets          # add this
        from .serializers import TodoSerializer      # add this
        from .models import Todo                     # add this
    
        class TodoView(viewsets.ModelViewSet):       # add this
          serializer_class = TodoSerializer          # add this
          queryset = Todo.objects.all()              # add this

    The viewsets base class provides the implementation for CRUD operations by default, what we had to do was specify the serializer class and the query set.

    默认情况下, viewsets基类提供CRUD操作的实现,我们要做的是指定序列化器类和查询集。

    Head over to the backend/urls.py file and completely replace it with the code below. This code specifies the URL path for the API:

    转至backend/urls.py文件,并将其完全替换为以下代码。 此代码指定API的URL路径:

    # backend/urls.py
    
        from django.contrib import admin
        from django.urls import path, include                 # add this
        from rest_framework import routers                    # add this
        from todo import views                            # add this
    
        router = routers.DefaultRouter()                      # add this
        router.register(r'todos', views.TodoView, 'todo')     # add this
    
        urlpatterns = [
            path('admin/', admin.site.urls),         path('api/', include(router.urls))                # add this
        ]

    This is the final step that completes the building of the API, we can now perform CRUD operations on the Todo model. The router class allows us to make the following queries:

    这是完成API构建的最后一步,我们现在可以在Todo模型上执行CRUD操作。 路由器类使我们可以进行以下查询:

    • /todos/ - This returns a list of all the Todo items (Create and Read operations can be done here).

      /todos/ -这将返回所有Todo项目的列表(可以在此处完成创建和读取操作)。

    • /todos/id - this returns a single Todo item using the id primary key (Update and Delete operations can be done here).

      /todos/id id-使用id主键返回一个Todo项(可以在此处完成Update和Delete操作)。

    Let’s restart the server and visit this address — http://localhost:8000/api/todos:

    让我们重新启动服务器并访问该地址-http:// localhost:8000 / api / todos

    $ python manage.py runserver

    We can create a new todo item using the interface:

    我们可以使用界面创建一个新的待办事项:

    If the Todo item is created successfully, you will see a screen like this:

    如果“待办事项”项创建成功,您将看到类似以下的屏幕:

    We can also perform DELETE and UPDATE operations on specific Todo items using their id primary keys. To do this, we will visit an address with this structure /api/todos/id. Let’s try with this address — http://localhost:8000/api/todos/1:

    我们还可以使用其id主键对特定的Todo项目执行DELETE和UPDATE操作。 为此,我们将访问具有/api/todos/id.结构的地址/api/todos/id. 让我们尝试使用该地址-http:// localhost:8000 / api / todos / 1

    That’s all for the backend of the application, now we can move on to fleshing out the frontend.

    这就是应用程序后端的全部内容,现在我们可以继续充实前端。

    设置前端 ( Setting up the frontend )

    We have our backend running as it should, now we will create our frontend and make it communicate with the backend over the interface that we created.

    我们让后端正常运行,现在我们将创建前端,并使其通过我们创建的接口与后端进行通信。

    Since we are building our frontend using React, we want to use the create-react-app CLI tool because it registers optimal settings and several benefits such as Hot reloading and Service workers. We will install the create-react-app CLI (command line interface) tool globally with this command:

    由于我们使用React构建前端,因此我们希望使用create-react-app CLI工具,因为它注册了最佳设置并具有诸如热重载和服务工作者之类的诸多好处。 我们将使用以下命令在全局安装create-react-app CLI(命令行界面)工具:

    $npm install -g create-react-app

    Let’s navigate back into the parent working directory — django-todo-react — of our application and create a new React application called frontend:

    让我们导航回应用程序的父工作目录django-todo-react ,并创建一个名为frontend的新React应用程序:

    $ create-react-app frontend

    It will probably take a while for all of the dependencies to be installed, once it’s over, your terminal should look something like this:

    安装所有依赖项可能需要一段时间,一旦结束,终端应该看起来像这样:

    Run the following commands to navigate into the working directory and start the frontend server

    运行以下命令以导航到工作目录并启动前端服务器

    $cd frontend
    $ yarn start

    Note: If you don’t have Yarn installed, you can find installation instructions here.

    注意:如果您尚未安装Yarn,则可以在此处找到安装说明。

    We can now visit this address — http://localhost:3000 — and we will see the default React screen:

    现在,我们可以访问该地址-http:// localhost:3000- ,我们将看到默认的React屏幕:

    We will pull in bootstrap and reactstrap to spice the UI up a bit:

    我们将reactstrap bootstrapreactstrap来为UI reactstrap

    $ yarn add bootstrap reactstrap

    Let’s open the src/index.css file and replace the styles there with this one:

    让我们打开src/index.css文件,并用此替换其中的样式:

    /__ frontend/src/index.css  __/
    
        body {
          margin: 0;
          padding: 0;
          font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
            "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
            sans-serif;
          -webkit-font-smoothing: antialiased;
          -moz-osx-font-smoothing: grayscale;
          background-color: #282c34;
        }
        .todo-title {
          cursor: pointer;
        }
        .completed-todo {
          text-decoration: line-through;
        }
        .tab-list > span {
          padding: 5px 8px;
          border: 1px solid #282c34;
          border-radius: 10px;
          margin-right: 5px;
          cursor: pointer;
        }
        .tab-list > span.active {
          background-color: #282c34;
          color: #ffffff;
        }

    We will import Bootstrap’s stylesheet in src/index.js so that we can use Bootstrap’s classes:

    我们将在src/index.js导入Bootstrap的样式表,以便我们可以使用Bootstrap的类:

    // frontend/src/index.js
    
          import React from 'react';
          import ReactDOM from 'react-dom';
          import 'bootstrap/dist/css/bootstrap.min.css';       // add this
          import './index.css';
          import App from './App';
          import * as serviceWorker from './serviceWorker';
    
          ReactDOM.render(<App />, document.getElementById('root'));
          // If you want your app to work offline and load faster, you can change
          // unregister() to register() below. Note this comes with some pitfalls.
          // Learn more about service workers: http://bit.ly/CRA-PWA
          serviceWorker.unregister();

    Let’s replace the code in src/App.js with this one:

    让我们用以下代码替换src/App.js的代码:

    // frontend/src/App.js
    
        import React, { Component } from "react";
        const todoItems = [
          {
            id: 1,
            title: "Go to Market",
            description: "Buy ingredients to prepare dinner",
            completed: true
          },
          {
            id: 2,
            title: "Study",
            description: "Read Algebra and History textbook for upcoming test",
            completed: false
          },
          {
            id: 3,
            title: "Sally's books",
            description: "Go to library to rent sally's books",
            completed: true
          },
          {
            id: 4,
            title: "Article",
            description: "Write article on how to use django with react",
            completed: false
          }
        ];
        class App extends Component {
          constructor(props) {
            super(props);
            this.state = {
              viewCompleted: false,
              todoList: todoItems
            };
          }
          displayCompleted = status => {
            if (status) {
              return this.setState({ viewCompleted: true });
            }
            return this.setState({ viewCompleted: false });
          };
          renderTabList = () => {
            return (
              <div className="my-5 tab-list">
                <span
                  onClick={() => this.displayCompleted(true)}
                  className={this.state.viewCompleted ? "active" : ""}
                >
                  complete
                </span>
                <span
                  onClick={() => this.displayCompleted(false)}
                  className={this.state.viewCompleted ? "" : "active"}
                >
                  Incomplete
                </span>
              </div>
            );
          };
          renderItems = () => {
            const { viewCompleted } = this.state;
            const newItems = this.state.todoList.filter(
              item => item.completed == viewCompleted
            );
            return newItems.map(item => (
              <li
                key={item.id}
                className="list-group-item d-flex justify-content-between align-items-center"
              >
                <span
                  className={`todo-title mr-2 ${
                    this.state.viewCompleted ? "completed-todo" : ""
                  }`}
                  title={item.description}
                >
                  {item.title}
                </span>
                <span>
                  <button className="btn btn-secondary mr-2"> Edit </button>
                  <button className="btn btn-danger">Delete </button>
                </span>
              </li>
            ));
          };
          render() {
            return (
              <main className="content">
                <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
                <div className="row ">
                  <div className="col-md-6 col-sm-10 mx-auto p-0">
                    <div className="card p-3">
                      <div className="">
                        <button className="btn btn-primary">Add task</button>
                      </div>
                      {this.renderTabList()}
                      <ul className="list-group list-group-flush">
                        {this.renderItems()}
                      </ul>
                    </div>
                  </div>
                </div>
              </main>
            );
          }
        }
        export default App;

    Okay, that’s a lot of code 😅, but there’s no need to be afraid now, we haven’t started interacting with the backend API, so we included default values to populate the Todo list. The `renderTabList()` function renders two spans which help control which set of items are displayed i.e clicking on the completed tab shows completed tasks and the same for the incomplete tab.

    好的,有很多代码😅,但是现在不必担心,我们还没有开始与后端API进行交互,因此我们包括了默认值以填充待办事项列表。 “ renderTabList()”函数呈现两个范围,以帮助控制显示哪些项目集,即,单击“完成”选项卡上的将显示已完成的任务,而单击“未完成”选项卡则显示相同的任务。

    If we visit the React frontend application now, it will look like this:

    如果我们现在访问React前端应用程序,它将看起来像这样:

    To handle actions such as adding and editing tasks, we will use a modal, so let's create a Modal component in a components folder.

    为了处理诸如添加和编辑任务之类的操作,我们将使用模态,因此让我们在components文件夹中创建一个Modal组件。

    Create a components folder in the src directory:

    src目录中创建一个components文件夹:

    $mkdir src/components

    Create a Modal.js file in the components folder:

    在components文件夹中创建一个Modal.js file

    $touch src/components/Modal.js

    Open the Modal.js file and populate it with the code snippet below:

    打开Modal.js文件,并使用下面的代码片段进行填充:

    // frontend/src/components/Modal.js
    
        import React, { Component } from "react";
        import {
          Button,
          Modal,
          ModalHeader,
          ModalBody,
          ModalFooter,
          Form,
          FormGroup,
          Input,
          Label
        } from "reactstrap";
    
        export default class CustomModal extends Component {
          constructor(props) {
            super(props);
            this.state = {
              activeItem: this.props.activeItem
            };
          }
          handleChange = e => {
            let { name, value } = e.target;
            if (e.target.type === "checkbox") {
              value = e.target.checked;
            }
            const activeItem = { ...this.state.activeItem, [name]: value };
            this.setState({ activeItem });
          };
          render() {
            const { toggle, onSave } = this.props;
            return (
              <Modal isOpen={true} toggle={toggle}>
                <ModalHeader toggle={toggle}> Todo Item </ModalHeader>
                <ModalBody>
                  <Form>
                    <FormGroup>
                      <Label for="title">Title</Label>
                      <Input
                        type="text"
                        name="title"
                        value={this.state.activeItem.title}
                        onChange={this.handleChange}
                        placeholder="Enter Todo Title"
                      />
                    </FormGroup>
                    <FormGroup>
                      <Label for="description">Description</Label>
                      <Input
                        type="text"
                        name="description"
                        value={this.state.activeItem.description}
                        onChange={this.handleChange}
                        placeholder="Enter Todo description"
                      />
                    </FormGroup>
                    <FormGroup check>
                      <Label for="completed">
                        <Input
                          type="checkbox"
                          name="completed"
                          checked={this.state.activeItem.completed}
                          onChange={this.handleChange}
                        />
                        Completed
                      </Label>
                    </FormGroup>
                  </Form>
                </ModalBody>
                <ModalFooter>
                  <Button color="success" onClick={() => onSave(this.state.activeItem)}>
                    Save
                  </Button>
                </ModalFooter>
              </Modal>
            );
          }
        }

    We created a CustomModal class and it nests the Modal component that is derived from the reactstrap library. We also defined three fields in the form:

    我们创建了一个CustomModal类,它嵌套了从reactstrap库派生的Modal组件。 我们还以以下形式定义了三个字段:

    • Title

      标题

    • Description

      描述

    • Completed

      已完成

    These are the same fields that we defined as properties on the Todo model in the backend.

    这些是我们在后端的Todo模型上定义为属性的字段。

    Here’s how the CustomModal works, it receives activeItem, toggle and onSave as props.

    这是CustomModal工作方式,它接收activeItemtoggle和onSave作为道具。

    1. activeItem represents the Todo item to be edited.

      activeItem表示要编辑的待办事项。
    2. toggle is a function used to control the Modal’s state i.e open or close the modal.

      toggle是用于控制模态的功能,即打开或关闭模态。
    3. onSave is a function that is called to save the edited values of the Todo item.

      onSave是一个用于保存Todo项的已编辑值的函数。

    Next, we will import the CustomModal component into the App.js file. Head over to the src/App.js file and replace it completely with this updated version:

    接下来,我们将CustomModal组件导入App.js文件。 转至src/App.js文件,并将其完全替换为此更新版本:

    // frontend/src/App.js
    
        import React, { Component } from "react";
        import Modal from "./components/Modal";
    
        const todoItems = [
          {
            id: 1,
            title: "Go to Market",
            description: "Buy ingredients to prepare dinner",
            completed: true
          },
          {
            id: 2,
            title: "Study",
            description: "Read Algebra and History textbook for upcoming test",
            completed: false
          },
          {
            id: 3,
            title: "Sally's books",
            description: "Go to library to rent sally's books",
            completed: true
          },
          {
            id: 4,
            title: "Article",
            description: "Write article on how to use django with react",
            completed: false
          }
        ];
        class App extends Component {
          constructor(props) {
            super(props);
            this.state = {
              modal: false,
              viewCompleted: false,
              activeItem: {
                title: "",
                description: "",
                completed: false
              },
              todoList: todoItems
            };
          }
          toggle = () => {
            this.setState({ modal: !this.state.modal });
          };
          handleSubmit = item => {
            this.toggle();
            alert("save" + JSON.stringify(item));
          };
          handleDelete = item => {
            alert("delete" + JSON.stringify(item));
          };
          createItem = () => {
            const item = { title: "", description: "", completed: false };
            this.setState({ activeItem: item, modal: !this.state.modal });
          };
          editItem = item => {
            this.setState({ activeItem: item, modal: !this.state.modal });
          };
          displayCompleted = status => {
            if (status) {
              return this.setState({ viewCompleted: true });
            }
            return this.setState({ viewCompleted: false });
          };
          renderTabList = () => {
            return (
              <div className="my-5 tab-list">
                <span
                  onClick={() => this.displayCompleted(true)}
                  className={this.state.viewCompleted ? "active" : ""}
                >
                  complete
                </span>
                <span
                  onClick={() => this.displayCompleted(false)}
                  className={this.state.viewCompleted ? "" : "active"}
                >
                  Incomplete
                </span>
              </div>
            );
          };
          renderItems = () => {
            const { viewCompleted } = this.state;
            const newItems = this.state.todoList.filter(
              item => item.completed === viewCompleted
            );
            return newItems.map(item => (
              <li
                key={item.id}
                className="list-group-item d-flex justify-content-between align-items-center"
              >
                <span
                  className={`todo-title mr-2 ${
                    this.state.viewCompleted ? "completed-todo" : ""
                  }`}
                  title={item.description}
                >
                  {item.title}
                </span>
                <span>
                  <button
                    onClick={() => this.editItem(item)}
                    className="btn btn-secondary mr-2"
                  >
                    Edit
                  </button>
                  <button
                    onClick={() => this.handleDelete(item)}
                    className="btn btn-danger"
                  >
                    Delete
                  </button>
                </span>
              </li>
            ));
          };
          render() {
            return (
              <main className="content">
                <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
                <div className="row ">
                  <div className="col-md-6 col-sm-10 mx-auto p-0">
                    <div className="card p-3">
                      <div className="">
                        <button onClick={this.createItem} className="btn btn-primary">
                          Add task
                        </button>
                      </div>
                      {this.renderTabList()}
                      <ul className="list-group list-group-flush">
                        {this.renderItems()}
                      </ul>
                    </div>
                  </div>
                </div>
                {this.state.modal ? (
                  <Modal
                    activeItem={this.state.activeItem}
                    toggle={this.toggle}
                    onSave={this.handleSubmit}
                  />
                ) : null}
              </main>
            );
          }
        }
        export default App;

    We can now revisit the React frontend, this is what the application should resemble at this point:

    现在,我们可以重新访问React前端,这是应用程序此时的外观:

    If we attempt to edit and save a Todo item, we will get an alert showing the Todo item’s object. Clicking on save, and delete will perform the fitting actions on the Todo item.

    如果我们尝试编辑和保存Todo项,则会收到显示Todo项对象的警报。 单击保存并删除将对“待办事项”项执行拟合操作。

    We will now modify the application so that it interacts with the Django API we built in the previous section. Let’s start by starting up the backend server (on a different instance of the terminal) if it isn’t already running:

    现在,我们将修改应用程序,使其与上一节中构建的Django API交互。 让我们首先启动后端服务器(在终端的另一个实例上)(如果尚未运行):

    $ python manage.py runserver

    Note: This command has to be run in the `backend` directory in a virtual Pipenv shell.

    注意:该命令必须在虚拟Pipenv shell的“后端”目录中运行。

    For us to make requests to the API endpoints on the backend server, we will install a JavaScript library called axios. Let’s pull in `axios using Yarn:

    为了向后端服务器上的API端点发出请求,我们将安装一个名为axios.JavaScript库axios. 让我们使用Yarn来引入`axios:

    $ yarn add axios

    Once axios is successfully installed, head over to the frontend/package.json file and add a proxy like so:

    成功安装axios frontend/package.json文件并添加一个代理,如下所示:

    // frontend/package.json[...]       "name": "frontend",
          "version": "0.1.0",
          "private": true,
          "proxy": "http://localhost:8000",
          "dependencies": {
            "axios": "^0.18.0",
            "bootstrap": "^4.1.3",
            "react": "^16.5.2",
            "react-dom": "^16.5.2",
            "react-scripts": "2.0.5",
            "reactstrap": "^6.5.0"
          },
          [...]

    The proxy will help in tunnelling API requests to http://localhost:8000 where the Django application will handle them, so we can write the requests like this in the frontend:

    代理将帮助将API请求隧穿到Django应用程序将在其中处理的http:// localhost:8000 ,因此我们可以在前端中编写如下请求:

    axios.get("/api/todos/")

    Instead of this:

    代替这个:

    axios.get("http://localhost:8000/api/todos/")

    Note: You might need to restart the development server for the proxy to register with the application.

    注意:您可能需要重新启动开发服务器,代理才能向应用程序注册。

    We will modify the frontend/src/App.js one last time so that it doesn’t use the hardcoded items from the array anymore, but requests data from the backend server and lists them instead. We want to also ensure that all CRUD operations send requests to the backend server instead of interacting with the dummy data.

    我们将最后一次修改frontend/src/App.js以便它不再使用数组中的硬编码项目,而是从后端服务器请求数据并列出它们。 我们还希望确保所有CRUD操作都将请求发送到后端服务器,而不是与虚拟数据进行交互。

    Open the file and replace it with this final version:

    打开文件并将其替换为最终版本:

    // frontend/src/App.js
    
        import React, { Component } from "react";
        import Modal from "./components/Modal";
        import axios from "axios";
    
        class App extends Component {
          constructor(props) {
            super(props);
            this.state = {
              viewCompleted: false,
              activeItem: {
                title: "",
                description: "",
                completed: false
              },
              todoList: []
            };
          }
          componentDidMount() {
            this.refreshList();
          }
          refreshList = () => {
            axios
              .get("http://localhost:8000/api/todos/")
              .then(res => this.setState({ todoList: res.data }))
              .catch(err => console.log(err));
          };
          displayCompleted = status => {
            if (status) {
              return this.setState({ viewCompleted: true });
            }
            return this.setState({ viewCompleted: false });
          };
          renderTabList = () => {
            return (
              <div className="my-5 tab-list">
                <span
                  onClick={() => this.displayCompleted(true)}
                  className={this.state.viewCompleted ? "active" : ""}
                >
                  complete
                </span>
                <span
                  onClick={() => this.displayCompleted(false)}
                  className={this.state.viewCompleted ? "" : "active"}
                >
                  Incomplete
                </span>
              </div>
            );
          };
          renderItems = () => {
            const { viewCompleted } = this.state;
            const newItems = this.state.todoList.filter(
              item => item.completed === viewCompleted
            );
            return newItems.map(item => (
              <li
                key={item.id}
                className="list-group-item d-flex justify-content-between align-items-center"
              >
                <span
                  className={`todo-title mr-2 ${
                    this.state.viewCompleted ? "completed-todo" : ""
                  }`}
                  title={item.description}
                >
                  {item.title}
                </span>
                <span>
                  <button
                    onClick={() => this.editItem(item)}
                    className="btn btn-secondary mr-2"
                  >
                    {" "}
                    Edit{" "}
                  </button>
                  <button
                    onClick={() => this.handleDelete(item)}
                    className="btn btn-danger"
                  >
                    Delete{" "}
                  </button>
                </span>
              </li>
            ));
          };
          toggle = () => {
            this.setState({ modal: !this.state.modal });
          };
          handleSubmit = item => {
            this.toggle();
            if (item.id) {
              axios
                .put(`http://localhost:8000/api/todos/${item.id}/`, item)
                .then(res => this.refreshList());
              return;
            }
            axios
              .post("http://localhost:8000/api/todos/", item)
              .then(res => this.refreshList());
          };
          handleDelete = item => {
            axios
              .delete(`http://localhost:8000/api/todos/${item.id}`)
              .then(res => this.refreshList());
          };
          createItem = () => {
            const item = { title: "", description: "", completed: false };
            this.setState({ activeItem: item, modal: !this.state.modal });
          };
          editItem = item => {
            this.setState({ activeItem: item, modal: !this.state.modal });
          };
          render() {
            return (
              <main className="content">
                <h1 className="text-white text-uppercase text-center my-4">Todo app</h1>
                <div className="row ">
                  <div className="col-md-6 col-sm-10 mx-auto p-0">
                    <div className="card p-3">
                      <div className="">
                        <button onClick={this.createItem} className="btn btn-primary">
                          Add task
                        </button>
                      </div>
                      {this.renderTabList()}
                      <ul className="list-group list-group-flush">
                        {this.renderItems()}
                      </ul>
                    </div>
                  </div>
                </div>
                {this.state.modal ? (
                  <Modal
                    activeItem={this.state.activeItem}
                    toggle={this.toggle}
                    onSave={this.handleSubmit}
                  />
                ) : null}
              </main>
            );
          }
        }
        export default App;

    The refreshList() function is reusable that is called each time an API request is completed. It updates the Todo list to display the most recent list of added items.

    每次完成API请求时,都会调用refreshList()函数可重用。 它将更新“待办事项”列表以显示最近添加的项目列表。

    The handleSubmit() function takes care of both the create and update operations. If the item passed as the parameter doesn’t have an id, then it has probably not been created, so the function creates it.

    handleSubmit()函数负责创建和更新操作。 如果作为参数传递的项目没有ID,则可能尚未创建,因此该函数将创建它。

    Congratulations! We have just built the fontend successfully.

    恭喜你! 我们已经成功构建了fontend。

    测试应用程序 ( Testing the application )

    Let’s start the backend server on a terminal instance that’s sourced into the Pipenv virtual shell and pointed to the backend directory:

    让我们在源于Pipenv虚拟外壳程序并指向后端目录的终端实例上启动后端服务器:

    $ python manage.py runserver

    We also need to start the frontend development server:

    我们还需要启动前端开发服务器:

    $ yarn start

    We can visit the application on this address — http://localhost:3000 — to see that it works:

    我们可以在以下地址( http:// localhost:3000)上访问该应用程序,以查看它是否有效:

    We’ve come to the end of this tutorial and learnt how to configure Django and React to interact correctly with each other. We also saw some of the benefits that come with bootstrapping a React application using the create-react-app tool, such as Hot-reloading which is basically the feature that makes it possible for the web app to reload on its own whenever a change is detected.

    我们已经到了本教程的结尾,学习了如何配置Django和React使其正确交互。 我们还看到了使用create-react-app工具引导React应用程序带来的一些好处,例如热重载,它基本上是使更改后的Web应用程序能够自行重载的功能检测到。

    The source code for this tutorial is available here on GitHub.

    本教程的源代码可以在这里 GitHub上。

    翻译自: https://scotch.io/tutorials/build-a-to-do-application-using-django-and-react

    django react

    展开全文
  • 刚刚在本地测试环境写完项目,目前本地测试是一切...后端Python3.6 + Django1.10.1 脚手架用的是create-react-app所以我们无需 用webpack来编译 1: 前端打包: 项目用的是yarn,所以我们编译的话直接执行yarn bui...

    刚刚在本地测试环境写完项目,目前本地测试是一切顺利,未发现异常,准备打包到生产环境服务器上.

    1. 前端React + antd + React-Router + axios
    2. 后端Python3.6 + Django1.10.1
    3. 脚手架用的是create-react-app所以我们无需 用webpack来编译

    #####1: 前端打包:
    这里写图片描述
    项目用的是yarn,所以我们编译的话直接执行yarn build 这个命令在我们的package.json的文件里面定义的
    这里写图片描述
    执行之后如下:
    这里写图片描述
    ###注意:
    1:大家用过Django的都知道普遍的模式是mtvhtml css js文件也是在后端中,当用了前端之后,前端的文件会独立出来,也就是我刚刚编译之后的内容 ⇒ build目录 ,这个目录非常重要,我们需要把这个目录拷贝到生产服务器上,他用做我们最终的前端配置文件

    #####2: 后端打包:
    到我们后端项目Backend目录中,将数据库里面的数据统一倒入到.json文件中:
    这里写图片描述
    除了json文件命名无所谓以外,其他格式是严格要求这个写法(除了python版本)

    然后将后端目录拷贝到生产服务器上

    #####3: uwsgi:
    服务器上的一些包什么的这里就不一一说了,大家肯定有缺少的,少什么就pip装什么,推荐pip国内镜像:

    mkdir ~/.pip
    touch ~/.pip/pip.conf
    vim ~/.pip/pip.conf
    
    [global]
    timeout = 6000
    index-url = http://pypi.douban.com/simple/
    [install]
    use-mirrors = true
    mirrors = http://pypi.douban.com/simple/
    trusted-host = pypi.douban.com
    

    然后将开发环境打包的.json文件倒入到本地数据库中:
    这里写图片描述
    安装uwsgi:

    pip3.6 install uwsgi
    

    安装之后我们现在本地创建一个脚本文件进行uwsgi测试 :

    vim /root/test.py
    
    def application(env, start_response):
            start_response('200 OK',[('Content-Type', 'text/html')])
            #return ['Hello world'] # Python2
            return [b'Hello world'] # Python3
    执行测试: uwsgi --http 127.0.0.1:9090 --wsgi-file test.py #返回200即OK
    

    然后我们进入后端目录的settings.py的同级目录下创建uwsgi.ini文件 (本地是以配置文件形式,以sock文件形式输出)

    [uwsgi]
    socket = 127.0.0.1:8000  				"这部分是uwsgi开启的端口号,注意不能和nginx占用同一个端口,同时也是nginx 代理的地址"
    chdir =  /src/Backend/   				"后端项目目录"
    wsgi-file = /src/Backend/back/wsgi.py 	"后端项目的wsgi.py 位置都是一样的"
    master = True							"启动一个master进程来管理其他进程"
    processes = 2							"同时启动uwsgi进程的个数"			
    daemonize = wsgi.log					"uwsgi的日志输出文件"
    threads = 4								"同时启动的线程个数"
    pidfile = uwsgi.pid						"uwsgi的pid文件"
    buffer-size  = 30000					"此处可不写,我这buffer较高,所以我设置了"
    

    启动uwsgi

    uwsgi --ini uwsgi.ini 
    

    查看日志输出是否正常:
    这里写图片描述
    一般看到我画箭头的那块就差不多成功启动了

    #####4: nginx:
    现在我们需要做的就是ngixn的配置了,首先需要将你的前端编译过的目录build放到nginx根目录下:
    这里写图片描述
    编辑nginx配置文件随便命名.conf结尾

    cd conf/conf.d/
    vim api.conf
    
    前端配置server
    server {
    	listen 80;
    	server_name www.project.com; "对外访问的加速域名,不可与uwsgi端口冲突"
    
    	location / {
    		"一些跨域头"
    		add_header 'Access-Control-Allow-Origin' '*'; 
    		add_header 'Access-Control-Allow-Credentials' 'true';
    		add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    		add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
    		root build; "这里就要指定你的前端目录文件了,也就是刚刚放进nginx根目录的文件夹"
    		index html index.html; "build 目录下默认有index.html 指定默认文件"
    		try_files $uri /index.html; "这块分重要,曾经不加尝试过,当我访问login路径时,他不会自动跳转,具体自行百度"
    	}
    }
    后端配置server
    server {
    	"这部分是我前端axios 请求的时候的地址也就是每次前端异步请求是http://www.igolang.cn:9000"
    	listen 9000;
    	server_name www.igolang.cn; 
    	
    	"项目app 叫api,也就是匹配到api路径,加载uwsgi模块 代理到本地地址"
    	location  ~ /api/ {
    		include uwsgi_params;
    		uwsgi_pass 127.0.0.1:8000; "这里就是uwsgi.ini配置文件中的地址"
    	}
    }
    

    有问题欢迎前来咨询:
    QQ:1301927919
    QQ群: 340164542

    个人博客: https://igolang.cn

    展开全文
  • django-admin startproject robotserver //settings.py """ Django settings for robotserver project. Generated by 'django-admin startproject' using Django 1.11.20. For more information on this file, ...
  • django中使用react项目

    千次阅读 2018-02-11 16:36:27
    django准备工作1.创建一个django项目2. 安装djangorestframework,并将其注册到settings.py的INSTALLED_APPS里 pip install djangorestframework3. 解决跨域访问的问题,django-cors-middleware能够为我们解决此...
  • 搭建一个django+react的博客

    千次阅读 2016-10-31 16:22:08
    最近还没找到工作,就在学校磨练下自己的...因为之前在的公司是用react在做项目,所以打算用react+django搭建一个前后分离的博客。 这样各玩各的,互相的干扰做到最小,只有之间数据的连接。 这个blog的项目在这htt
  • djangoreact的结合

    千次阅读 2018-05-05 20:57:37
    初学react,在学完基本的语法之后,想要做一个基本的小demo出来,首先分别建好一个python的项目,我使用的框架为django,然后再创建好一个react的项目,接下来就是djangoreact的结合了(嗯,困扰了我一天多的问题)...
  • 自学react,作为一个只有后端python方面知识的人,看过一点点前端教程基础,学习react还是不轻松的。 记过两天的学习,自制一个简单的登录,注册界面,组件的划分用的还不是熟悉。 登录界面:...
  • 本文重点是建立restframework提供REST的DRF项目,并且完成与REACT的前端交互(加上必要的中间插件) 1、后端的搭建 1、按照官方教程安装相关依赖 http://www.django-rest-framework.org/ sudo pip3 i...
  • django-react-integration-又一个 django-react 展示实现项目
  • django reactThis article is a continuation of Django and React Full Stack — Django. If you have not read that part or your backend environment is not ready, you can find the article I developed using...
  • 翻译版实践教程: Django Rest 与 React(Django2.1 加 一点小测试 加一点译者的小额外功能) 最终构建了一个有后台管理 + 提供api服务 + Mysql数据库 + 在线api文档的Lead系统。 一个实用(自认为)的介绍: 实用Django...
  • Table of Contents 需求 思路&原理 如何做 有坑咋办 ...1 发布到IIS上后,静态资源无法加载 ...最近要做一个project,打算前后端分离,但服务器端口号有限,对nginx 这种端口复用的手段也...后端我们使用的是djan...
  • 平时上班大家都知道没时间干别的,乘着这两天周末,决定把之前搭好的开发环境,给弄到线上去,其实开发环境也搞了一两天,因为django的csrf的坑,还有就是因为前后端要分离吗,还要搭建前端开发环境。前端是webapck ...
  • Django REST 框架/React快速入门
  • 企业开发,写项目都是需要创建独立的开发环境的。 这样的好处是: 可以避免多个项目之间库的冲突 完整导出库的列表 引入python虚拟环境库 先用pip导入虚拟环境库。 pip install virtualenv 创建虚拟环境 在你的...
  • 本文介绍了如何快速入门 基于 DjangoReact/Redux 的 SPA (single page web application:单页面 web 应用) 应用开发。 很难想象一个没有用户认证和授权的 Django 应用。因此,我们从一个简单的应用程序开始,...
  • 服务器是 CentOS 7 系统 部署Django项目 安装Gunicorn ...# 使用 gunicorn 运行项目 $ cd [Django项目路径] $ gunicorn -w 3 -b 0.0.0.0:8001 --log-level=info [Django项目同名文件夹名称].wsgi [202
1 2 3 4 5 ... 20
收藏数 3,638
精华内容 1,455
关键字:

django 中使用react