From c9f0bbad6476b5f3338af072f5dfe2ce0da1960b Mon Sep 17 00:00:00 2001 From: MjSeven <33125422+MjSeven@users.noreply.github.com> Date: Thu, 27 Sep 2018 15:25:29 +0800 Subject: [PATCH 01/11] Delete 20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md --- ...eshark on Debian and Ubuntu 16.04_17.10.md | 185 ------------------ 1 file changed, 185 deletions(-) delete mode 100644 sources/tech/20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md diff --git a/sources/tech/20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md b/sources/tech/20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md deleted file mode 100644 index 05e441207f..0000000000 --- a/sources/tech/20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md +++ /dev/null @@ -1,185 +0,0 @@ -Translating by MjSeven - - -How to Install and Use Wireshark on Debian 9 / Ubuntu 16.04 / 17.10 -============================================================ - -by [Pradeep Kumar][1] · Published November 29, 2017 · Updated November 29, 2017 - - [![wireshark-Debian-9-Ubuntu 16.04 -17.10](https://www.linuxtechi.com/wp-content/uploads/2017/11/wireshark-Debian-9-Ubuntu-16.04-17.10.jpg)][2] - -Wireshark is free and open source, cross platform, GUI based Network packet analyzer that is available for Linux, Windows, MacOS, Solaris etc. It captures network packets in real time & presents them in human readable format. Wireshark allows us to monitor the network packets up to microscopic level. Wireshark also has a command line utility called ‘tshark‘ that performs the same functions as Wireshark but through terminal & not through GUI. - -Wireshark can be used for network troubleshooting, analyzing, software & communication protocol development & also for education purposed. Wireshark uses a library called ‘pcap‘ for capturing the network packets. - -Wireshark comes with a lot of features & some those features are; - -* Support for a hundreds of protocols for inspection, - -* Ability to capture packets in real time & save them for later offline analysis, - -* A number of filters to analyzing data, - -* Data captured can be compressed & uncompressed on the fly, - -* Various file formats for data analysis supported, output can also be saved to XML, CSV, plain text formats, - -* data can be captured from a number of interfaces like ethernet, wifi, bluetooth, USB, Frame relay , token rings etc. - -In this article, we will discuss how to install Wireshark on Ubuntu/Debain machines & will also learn to use Wireshark for capturing network packets. - -#### Installation of Wireshark on Ubuntu 16.04 / 17.10 - -Wireshark is available with default Ubuntu repositories & can be simply installed using the following command. But there might be chances that you will not get the latest version of wireshark. - -``` -linuxtechi@nixworld:~$ sudo apt-get update -linuxtechi@nixworld:~$ sudo apt-get install wireshark -y -``` - -So to install latest version of wireshark we have to enable or configure official wireshark repository. - -Use the beneath commands one after the another to configure repository and to install latest version of Wireshark utility - -``` -linuxtechi@nixworld:~$ sudo add-apt-repository ppa:wireshark-dev/stable -linuxtechi@nixworld:~$ sudo apt-get update -linuxtechi@nixworld:~$ sudo apt-get install wireshark -y -``` - -Once the Wireshark is installed execute the below command so that non-root users can capture live packets of interfaces, - -``` -linuxtechi@nixworld:~$ sudo setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' /usr/bin/dumpcap -``` - -#### Installation of Wireshark on Debian 9 - -Wireshark package and its dependencies are already present in the default debian 9 repositories, so to install latest and stable version of Wireshark on Debian 9, use the following command: - -``` -linuxtechi@nixhome:~$ sudo apt-get update -linuxtechi@nixhome:~$ sudo apt-get install wireshark -y -``` - -During the installation, it will prompt us to configure dumpcap for non-superusers, - -Select ‘yes’ and then hit enter. - - [![Configure-Wireshark-Debian9](https://www.linuxtechi.com/wp-content/uploads/2017/11/Configure-Wireshark-Debian9-1024x542.jpg)][3] - -Once the Installation is completed, execute the below command so that non-root users can also capture the live packets of the interfaces. - -``` -linuxtechi@nixhome:~$ sudo chmod +x /usr/bin/dumpcap -``` - -We can also use the latest source package to install the wireshark on Ubuntu/Debain & many other Linux distributions. - -#### Installing Wireshark using source code on Debian / Ubuntu Systems - -Firstly download the latest source package (which is 2.4.2 at the time for writing this article), use the following command, - -``` -linuxtechi@nixhome:~$ wget https://1.as.dl.wireshark.org/src/wireshark-2.4.2.tar.xz -``` - -Next extract the package & enter into the extracted directory, - -``` -linuxtechi@nixhome:~$ tar -xf wireshark-2.4.2.tar.xz -C /tmp -linuxtechi@nixhome:~$ cd /tmp/wireshark-2.4.2 -``` - -Now we will compile the code with the following commands, - -``` -linuxtechi@nixhome:/tmp/wireshark-2.4.2$ ./configure --enable-setcap-install -linuxtechi@nixhome:/tmp/wireshark-2.4.2$ make -``` - -Lastly install the compiled packages to install Wireshark on the system, - -``` -linuxtechi@nixhome:/tmp/wireshark-2.4.2$ sudo make install -linuxtechi@nixhome:/tmp/wireshark-2.4.2$ sudo ldconfig -``` - -Upon installation a separate group for Wireshark will also be created, we will now add our user to the group so that it can work with wireshark otherwise you might get ‘permission denied‘ error when starting wireshark. - -To add the user to the wireshark group, execute the following command, - -``` -linuxtechi@nixhome:~$ sudo usermod -a -G wireshark linuxtechi -``` - -Now we can start wireshark either from GUI Menu or from terminal with this command, - -``` -linuxtechi@nixhome:~$ wireshark -``` - -#### Access Wireshark on Debian 9 System - - [![Access-wireshark-debian9](https://www.linuxtechi.com/wp-content/uploads/2017/11/Access-wireshark-debian9-1024x664.jpg)][4] - -Click on Wireshark icon - - [![Wireshark-window-debian9](https://www.linuxtechi.com/wp-content/uploads/2017/11/Wireshark-window-debian9-1024x664.jpg)][5] - -#### Access Wireshark on Ubuntu 16.04 / 17.10 - - [![Access-wireshark-Ubuntu](https://www.linuxtechi.com/wp-content/uploads/2017/11/Access-wireshark-Ubuntu-1024x664.jpg)][6] - -Click on Wireshark icon - - [![Wireshark-window-Ubuntu](https://www.linuxtechi.com/wp-content/uploads/2017/11/Wireshark-window-Ubuntu-1024x664.jpg)][7] - -#### Capturing and Analyzing packets - -Once the wireshark has been started, we should be presented with the wireshark window, example is shown above for Ubuntu and Debian system. - - [![wireshark-Linux-system](https://www.linuxtechi.com/wp-content/uploads/2017/11/wireshark-Linux-system.jpg)][8] - -All these are the interfaces from where we can capture the network packets. Based on the interfaces you have on your system, this screen might be different for you. - -We are selecting ‘enp0s3’ for capturing the network traffic for that inteface. After selecting the inteface, network packets for all the devices on our network start to populate (refer to screenshot below) - - [![Capturing-Packet-from-enp0s3-Ubuntu-Wireshark](https://www.linuxtechi.com/wp-content/uploads/2017/11/Capturing-Packet-from-enp0s3-Ubuntu-Wireshark-1024x727.jpg)][9] - -First time we see this screen we might get overwhelmed by the data that is presented in this screen & might have thought how to sort out this data but worry not, one the best features of Wireshark is its filters. - -We can sort/filter out the data based on IP address, Port number, can also used source & destination filters, packet size etc & can also combine 2 or more filters together to create more comprehensive searches. We can either write our filters in ‘Apply a Display Filter‘ tab , or we can also select one of already created rules. To select pre-built filter, click on ‘flag‘ icon , next to ‘Apply a Display Filter‘ tab, - - [![Filter-in-wireshark-Ubuntu](https://www.linuxtechi.com/wp-content/uploads/2017/11/Filter-in-wireshark-Ubuntu-1024x727.jpg)][10] - -We can also filter data based on the color coding, By default, light purple is TCP traffic, light blue is UDP traffic, and black identifies packets with errors , to see what these codes mean, click View -> Coloring Rules, also we can change these codes. - - [![Packet-Colouring-Wireshark](https://www.linuxtechi.com/wp-content/uploads/2017/11/Packet-Colouring-Wireshark-1024x682.jpg)][11] - -After we have the results that we need, we can then click on any of the captured packets to get more details about that packet, this will show all the data about that network packet. - -Wireshark is an extremely powerful tool takes some time to getting used to & make a command over it, this tutorial will help you get started. Please feel free to drop in your queries or suggestions in the comment box below. - --------------------------------------------------------------------------------- - -via: https://www.linuxtechi.com - -作者:[Pradeep Kumar][a] -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://www.linuxtechi.com/author/pradeep/ -[1]:https://www.linuxtechi.com/author/pradeep/ -[2]:https://www.linuxtechi.com/wp-content/uploads/2017/11/wireshark-Debian-9-Ubuntu-16.04-17.10.jpg -[3]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Configure-Wireshark-Debian9.jpg -[4]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Access-wireshark-debian9.jpg -[5]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Wireshark-window-debian9.jpg -[6]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Access-wireshark-Ubuntu.jpg -[7]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Wireshark-window-Ubuntu.jpg -[8]:https://www.linuxtechi.com/wp-content/uploads/2017/11/wireshark-Linux-system.jpg -[9]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Capturing-Packet-from-enp0s3-Ubuntu-Wireshark.jpg -[10]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Filter-in-wireshark-Ubuntu.jpg -[11]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Packet-Colouring-Wireshark.jpg From 0962a592e420179802547cd37967a1878fd8f032 Mon Sep 17 00:00:00 2001 From: MjSeven <33125422+MjSeven@users.noreply.github.com> Date: Thu, 27 Sep 2018 15:25:50 +0800 Subject: [PATCH 02/11] Create 20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md --- ...eshark on Debian and Ubuntu 16.04_17.10.md | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 translated/tech/20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md diff --git a/translated/tech/20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md b/translated/tech/20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md new file mode 100644 index 0000000000..0bcbe0d3e5 --- /dev/null +++ b/translated/tech/20171129 How to Install and Use Wireshark on Debian and Ubuntu 16.04_17.10.md @@ -0,0 +1,183 @@ +在 Debian 9 / Ubuntu 16.04 / 17.10 中如何安装并使用 Wireshark +====== + +作者 [Pradeep Kumar][1],首发于 2017 年 11 月 29 日,更新于 2017 年 11 月 29 日 + +[![wireshark-Debian-9-Ubuntu 16.04 -17.10](https://www.linuxtechi.com/wp-content/uploads/2017/11/wireshark-Debian-9-Ubuntu-16.04-17.10.jpg)][2] + +Wireshark 是免费的,开源的,跨平台的基于 GUI 的网络数据包分析器,可用于 Linux, Windows, MacOS, Solaris 等。它可以实时捕获网络数据包,并以人性化的格式呈现。Wireshark 允许我们监控网络数据包上升到微观层面。Wireshark 还有一个名为 `tshark` 的命令行实用程序,它与 Wireshark 执行相同的功能,但它是通过终端而不是 GUI。 + +Wireshark 可用于网络故障排除,分析,软件和通信协议开发以及用于教育目的。Wireshark 使用 `pcap` 库来捕获网络数据包。 + +Wireshark 具有许多功能: + +* 支持数百项协议检查 + +* 能够实时捕获数据包并保存,以便以后进行离线分析 + +* 许多用于分析数据的过滤器 + +* 捕获的数据可以被压缩和解压缩(to 校正:on the fly 什么意思?) + +* 支持各种文件格式的数据分析,输出也可以保存为 XML, CSV 和纯文本格式 + +* 数据可以从以太网,wifi,蓝牙,USB,帧中继,令牌环等多个接口中捕获 + +在本文中,我们将讨论如何在 Ubuntu/Debian 上安装 Wireshark,并将学习如何使用 Wireshark 捕获网络数据包。 + +#### 在 Ubuntu 16.04 / 17.10 上安装 Wireshark + +Wireshark 在 Ubuntu 默认仓库中可用,只需使用以下命令即可安装。但有可能得不到最新版本的 wireshark。 + +``` +linuxtechi@nixworld:~$ sudo apt-get update +linuxtechi@nixworld:~$ sudo apt-get install wireshark -y +``` + +因此,要安装最新版本的 wireshark,我们必须启用或配置官方 wireshark 仓库。 + +使用下面的命令来配置仓库并安装最新版本的 wireshark 实用程序。 + +``` +linuxtechi@nixworld:~$ sudo add-apt-repository ppa:wireshark-dev/stable +linuxtechi@nixworld:~$ sudo apt-get update +linuxtechi@nixworld:~$ sudo apt-get install wireshark -y +``` + +一旦安装了 wireshark,执行以下命令,以便非 root 用户也可以捕获接口的实时数据包。 + +``` +linuxtechi@nixworld:~$ sudo setcap 'CAP_NET_RAW+eip CAP_NET_ADMIN+eip' /usr/bin/dumpcap +``` + +#### 在 Debian 9 上安装 Wireshark + +Wireshark 包及其依赖项已存在于 debian 9 的默认仓库中,因此要在 Debian 9 上安装最新且稳定版本的 Wireshark,请使用以下命令: + +``` +linuxtechi@nixhome:~$ sudo apt-get update +linuxtechi@nixhome:~$ sudo apt-get install wireshark -y +``` + +在安装过程中,它会提示我们为非超级用户配置 dumpcap, + +选择 `yes` 并回车。 + +[![Configure-Wireshark-Debian9](https://www.linuxtechi.com/wp-content/uploads/2017/11/Configure-Wireshark-Debian9-1024x542.jpg)][3] + +安装完成后,执行以下命令,以便非 root 用户也可以捕获接口的实时数据包。 + +``` +linuxtechi@nixhome:~$ sudo chmod +x /usr/bin/dumpcap +``` + +我们还可以使用最新的源代码包在 Ubuntu/Debian 和其它 Linux 发行版上安装 wireshark。 + +#### 在 Debian / Ubuntu 系统上使用源代码安装 Wireshark + +首先下载最新的源代码包(写这篇文章时它的最新版本是 2.4.2),使用以下命令: + +``` +linuxtechi@nixhome:~$ wget https://1.as.dl.wireshark.org/src/wireshark-2.4.2.tar.xz +``` + +然后解压缩包,进入解压缩的目录: + +``` +linuxtechi@nixhome:~$ tar -xf wireshark-2.4.2.tar.xz -C /tmp +linuxtechi@nixhome:~$ cd /tmp/wireshark-2.4.2 +``` + +现在我们使用以下命令编译代码: + +``` +linuxtechi@nixhome:/tmp/wireshark-2.4.2$ ./configure --enable-setcap-install +linuxtechi@nixhome:/tmp/wireshark-2.4.2$ make +``` + +最后安装已编译的软件包以便在系统上安装 Wireshark: + +``` +linuxtechi@nixhome:/tmp/wireshark-2.4.2$ sudo make install +linuxtechi@nixhome:/tmp/wireshark-2.4.2$ sudo ldconfig +``` + +在安装后,它将创建一个单独的 Wireshark 组,我们现在将我们的用户添加到组中,以便它可以与 Wireshark 一起使用,否则在启动 wireshark 时可能会出现 `permission denied(权限被拒绝)`错误。 + +要将用户添加到 wireshark 组,执行以下命令: + +``` +linuxtechi@nixhome:~$ sudo usermod -a -G wireshark linuxtechi +``` + +现在我们可以使用以下命令从 GUI 菜单或终端启动 wireshark: + +``` +linuxtechi@nixhome:~$ wireshark +``` + +#### 在 Debian 9 系统上使用 Wireshark + +[![Access-wireshark-debian9](https://www.linuxtechi.com/wp-content/uploads/2017/11/Access-wireshark-debian9-1024x664.jpg)][4] + +点击 Wireshark 图标 + +[![Wireshark-window-debian9](https://www.linuxtechi.com/wp-content/uploads/2017/11/Wireshark-window-debian9-1024x664.jpg)][5] + +#### 在 Ubuntu 16.04 / 17.10 上使用 Wireshark + +[![Access-wireshark-Ubuntu](https://www.linuxtechi.com/wp-content/uploads/2017/11/Access-wireshark-Ubuntu-1024x664.jpg)][6] + +点击 Wireshark 图标 + +[![Wireshark-window-Ubuntu](https://www.linuxtechi.com/wp-content/uploads/2017/11/Wireshark-window-Ubuntu-1024x664.jpg)][7] + +#### 捕获并分析数据包 + +一旦 wireshark 启动,我们就会看到 wireshark 窗口,上面有 Ubuntu 和 Debian 系统的示例。 + +[![wireshark-Linux-system](https://www.linuxtechi.com/wp-content/uploads/2017/11/wireshark-Linux-system.jpg)][8] + +所有这些都是我们可以捕获网络数据包的接口。根据你系统上的界面,此屏幕可能与你的不同。 + +我们选择 `enp0s3` 来捕获该接口的网络流量。选择接口后,在我们网络上所有设备的网络数据包开始填充(参考下面的屏幕截图): + +[![Capturing-Packet-from-enp0s3-Ubuntu-Wireshark](https://www.linuxtechi.com/wp-content/uploads/2017/11/Capturing-Packet-from-enp0s3-Ubuntu-Wireshark-1024x727.jpg)][9] + +第一次看到这个屏幕,我们可能会被这个屏幕上显示的数据所淹没,并且可能已经想过如何整理这些数据,但不用担心,Wireshark 的最佳功能之一就是它的过滤器。 + +我们可以根据 IP 地址,端口号,也可以使用来源和目标过滤器,数据包大小等对数据进行排序和过滤,也可以将两个或多个过滤器组合在一起以创建更全面的搜索。我们也可以在 `Apply a Display Filter(应用显示过滤器)`选项卡中编写过滤规则,也可以选择已创建的规则。要选择之前构建的过滤器,请单击 `Apply a Display Filter(应用显示过滤器)`选项卡旁边的 `flag` 图标。 + +[![Filter-in-wireshark-Ubuntu](https://www.linuxtechi.com/wp-content/uploads/2017/11/Filter-in-wireshark-Ubuntu-1024x727.jpg)][10] + +我们还可以根据颜色编码过滤数据,默认情况下,浅紫色是 TCP 流量,浅蓝色是 UDP 流量,黑色标识有错误的数据包,看看这些编码是什么意思,点击 `View -> Coloring Rules`,我们也可以改变这些编码。 + +[![Packet-Colouring-Wireshark](https://www.linuxtechi.com/wp-content/uploads/2017/11/Packet-Colouring-Wireshark-1024x682.jpg)][11] + +在我们得到我们需要的结果之后,我们可以点击任何捕获的数据包以获得有关该数据包的更多详细信息,这将显示该网络数据包的所有数据。 + +Wireshark 是一个非常强大的工具,需要一些时间来习惯并对其进行命令操作,本教程将帮助你入门。请随时在下面的评论框中提出你的疑问或建议。 + + +-------------------------------------------------------------------------------- + +via: https://www.linuxtechi.com + +作者:[Pradeep Kumar][a] +译者:[MjSeven](https://github.com/MjSeven) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://www.linuxtechi.com/author/pradeep/ +[1]:https://www.linuxtechi.com/author/pradeep/ +[2]:https://www.linuxtechi.com/wp-content/uploads/2017/11/wireshark-Debian-9-Ubuntu-16.04-17.10.jpg +[3]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Configure-Wireshark-Debian9.jpg +[4]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Access-wireshark-debian9.jpg +[5]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Wireshark-window-debian9.jpg +[6]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Access-wireshark-Ubuntu.jpg +[7]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Wireshark-window-Ubuntu.jpg +[8]:https://www.linuxtechi.com/wp-content/uploads/2017/11/wireshark-Linux-system.jpg +[9]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Capturing-Packet-from-enp0s3-Ubuntu-Wireshark.jpg +[10]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Filter-in-wireshark-Ubuntu.jpg +[11]:https://www.linuxtechi.com/wp-content/uploads/2017/11/Packet-Colouring-Wireshark.jpg From 5d353a7da0cbc1fc83a5784e69b4c32bdc5ece96 Mon Sep 17 00:00:00 2001 From: MjSeven <33125422+MjSeven@users.noreply.github.com> Date: Thu, 27 Sep 2018 16:15:49 +0800 Subject: [PATCH 03/11] Update 20180816 An introduction to the Django Python web app framework.md --- ...6 An introduction to the Django Python web app framework.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sources/tech/20180816 An introduction to the Django Python web app framework.md b/sources/tech/20180816 An introduction to the Django Python web app framework.md index 21ab9d21ae..ab7dba9526 100644 --- a/sources/tech/20180816 An introduction to the Django Python web app framework.md +++ b/sources/tech/20180816 An introduction to the Django Python web app framework.md @@ -1,3 +1,6 @@ +Translating by MjSeven + + An introduction to the Django Python web app framework ====== From 37a2496ab044261ba281a46df30f9e5b39232437 Mon Sep 17 00:00:00 2001 From: geekpi Date: Sun, 30 Sep 2018 09:18:10 +0800 Subject: [PATCH 04/11] translated (#10442) --- ... With browser-mpris2 (Chrome Extension).md | 76 ------------------- ... With browser-mpris2 (Chrome Extension).md | 76 +++++++++++++++++++ 2 files changed, 76 insertions(+), 76 deletions(-) delete mode 100644 sources/tech/20180816 Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension).md create mode 100644 translated/tech/20180816 Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension).md diff --git a/sources/tech/20180816 Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension).md b/sources/tech/20180816 Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension).md deleted file mode 100644 index acc8f56e0c..0000000000 --- a/sources/tech/20180816 Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension).md +++ /dev/null @@ -1,76 +0,0 @@ -translating---geekpi - -Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension) -====== -A Unity feature that I miss (it only actually worked for a short while though) is automatically getting player controls in the Ubuntu Sound Indicator when visiting a website like YouTube in a web browser, so you could pause or stop the video directly from the top bar, as well as see the video / song information and a preview. - -This Unity feature is long dead, but I was searching for something similar for Gnome Shell and I came across **[browser-mpris2][1], an extension that implements a MPRIS v2 interface for Google Chrome / Chromium, which currently only supports YouTube** , and I thought there might be some Linux Uprising readers who'll like this. - -**The extension also works with Chromium-based web browsers like Opera and Vivaldi.** -** -** **browser-mpris2 also supports Firefox but since loading extensions via about:debugging is temporary, and this is needed for browser-mpris2, this article doesn't include Firefox instructions. The developer[intends][2] to submit the extension to the Firefox addons website in the future.** - -**Using this Chrome extension you get YouTube media player controls (play, pause, stop and seeking) in MPRIS2-capable applets**. For example, if you use Gnome Shell, you get YouTube media player controls as a permanent notification or, you can use an extension like Media Player Indicator for this. In Cinnamon / Linux Mint with Cinnamon, it shows up in the Sound Applet. - -**It didn't work for me on Unity** , I'm not sure why. I didn't try this extension with other MPRIS2-capable applets available in various desktop environments (KDE, Xfce, MATE, etc.). If you give it a try, let us know if it works with your desktop environment / MPRIS2 enabled applet. - -Here is a screenshot with [Media Player Indicator][3] displaying information about the currently playing YouTube video, along with its controls (play/pause, stop and seeking), on Ubuntu 18.04 with Gnome Shell and Chromium browser: - -![](https://1.bp.blogspot.com/-rsc4FpYBSrI/W3VtPphfdOI/AAAAAAAABXY/YfKV6pBncs0LAwTwYSS0tKRJADDfZDBfwCLcBGAs/s640/browser-mpris2-gnome-shell-sound-indicator.png) - -And in Linux Mint 19 Cinnamon with its default sound applet and Chromium browser: - -![](https://2.bp.blogspot.com/-I2DuYetv7eQ/W3VtUUcg26I/AAAAAAAABXc/Tv-RemkyO60k6CC_mYUxewG-KfVgpFefACLcBGAs/s1600/browser-mpris2-cinnamon-linux-mint.png) - -### How to install browser-mpris2 for Google Chrome / Chromium - -**1\. Install Git if you haven't already.** - -In Debian / Ubuntu / Linux Mint, use this command to install git: -``` -sudo apt install git - -``` - -**2\. Download and install the[browser-mpris2][1] required files.** - -The commands below clone the browser-mpris2 Git repository and install the chrome-mpris2 file to `/usr/local/bin/` (run the "git clone..." command in a folder where you can continue to keep the browser-mpris2 folder because you can't remove it, as it will be used by Chrome / Chromium): -``` -git clone https://github.com/otommod/browser-mpris2 -sudo install browser-mpris2/native/chrome-mpris2 /usr/local/bin/ - -``` - -**3\. Load the extension in Chrome / Chromium-based web browsers.** - -![](https://3.bp.blogspot.com/-yEoNFj2wAXM/W3Vvewa979I/AAAAAAAABXo/dmltlNZk3J4sVa5jQenFFrT28ecklY92QCLcBGAs/s640/browser-mpris2-chrome-developer-load-unpacked.png) - -Open Google Chrome, Chromium, Opera or Vivaldi web browsers, go to the Extensions page (enter `chrome://extensions` in the URL bar), enable `Developer mode` using the toggle available in the top right-hand side of the screen, then select `Load Unpacked` and select the chrome-mpris2 directory (make sure to not select a subfolder). - -Copy the extension ID and save it because you'll need it later (it's something like: `emngjajgcmeiligomkgpngljimglhhii` but it's different for you so make sure to use the ID from your computer!) . - -**4\. Run** `install-chrome.py` (from the `browser-mpris2/native` folder), specifying the extension id and chrome-mpris2 path. - -Use this command in a terminal (replace `REPLACE-THIS-WITH-EXTENSION-ID` with the browser-mpris2 extension ID displayed under `chrome://extensions` from the previous step) to install this extension: -``` -browser-mpris2/native/install-chrome.py REPLACE-THIS-WITH-EXTENSION-ID /usr/local/bin/chrome-mpris2 - -``` - -You only need to run this command once, there's no need to add it to startup or anything like that. Any YouTube video you play in Google Chrome or Chromium browsers should show up in whatever MPRISv2 applet you're using. There's no need to restart the web browser. - --------------------------------------------------------------------------------- - -via: https://www.linuxuprising.com/2018/08/add-youtube-player-controls-to-your.html - -作者:[Logix][a] -选题:[lujun9972](https://github.com/lujun9972) -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://plus.google.com/118280394805678839070 -[1]:https://github.com/otommod/browser-mpris2 -[2]:https://github.com/otommod/browser-mpris2/issues/11 -[3]:https://extensions.gnome.org/extension/55/media-player-indicator/ diff --git a/translated/tech/20180816 Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension).md b/translated/tech/20180816 Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension).md new file mode 100644 index 0000000000..a72b4cdd8d --- /dev/null +++ b/translated/tech/20180816 Add YouTube Player Controls To Your Linux Desktop With browser-mpris2 (Chrome Extension).md @@ -0,0 +1,76 @@ +使用 browser-mpris2(Chrome 扩展)将 YouTube 播放器控件添加到 Linux 桌面 +====== +一个我怀念的 Unity 功能(虽然只使用了一小段时间)是在 Web 浏览器中访问 YouTube 等网站时自动获取 Ubuntu 声音指示器中的播放器控件,因此你可以直接从顶部栏暂停或停止视频,以及浏览视频/歌曲信息和预览。 + +这个 Unity 功能已经消失很久了,但我正在为 Gnome Shell 寻找类似的东西,然后我遇到了 **[browser-mpris2][1],这是一个为 Google Chrome/Chromium 实现 MPRIS v2 接口的扩展,目前只支持 YouTube**,我想可能会有一些 Linux Uprising 的读者会喜欢这个。 + +**该扩展还适用于 Opera 和 Vivaldi 等基于 Chromium 的 Web 浏览器。** +** +** **browser-mpris2 也支持 Firefox,但因为通过 about:debugging 加载扩展是临时的,而这是 browser-mpris2 所需要的,因此本文不包括 Firefox 的指导。开发人员[打算][2]将来将扩展提交到 Firefox 插件网站上。** + +**使用此 Chrome 扩展,你可以在支持 MPRIS2 的 applets 中获得 YouTube 媒体播放器控件(播放、暂停、停止和查找 +)**。例如,如果你使用 Gnome Shell,你可将 YouTube 媒体播放器控件作为永久通知,或者你可以使用 Media Player Indicator 之类的扩展来实现此目的。在 Cinnamon /Linux Mint with Cinnamon 中,它出现在声音 Applet 中。 + +**我无法在 Unity 上用它**,我不知道为什么。我没有在不同桌面环境(KDE、Xfce、MATE 等)中使用其他支持 MPRIS2 的 applet 尝试此扩展。如果你尝试过,请告诉我们它是否适用于你的桌面环境/支持 MPRIS2 的 applet。 + +以下是在使用 Gnome Shell 的 Ubuntu 18.04 并装有 Chromium 浏览器的[媒体播放器指示器][3]的截图,其中显示了有关当前正在播放的 YouTube 视频的信息及其控件(播放/暂停,停止和查找): + +![](https://1.bp.blogspot.com/-rsc4FpYBSrI/W3VtPphfdOI/AAAAAAAABXY/YfKV6pBncs0LAwTwYSS0tKRJADDfZDBfwCLcBGAs/s640/browser-mpris2-gnome-shell-sound-indicator.png) + +在 Linux Mint 19 Cinnamon 中使用其默认声音 applet 和 Chromium 浏览器的截图: + + +![](https://2.bp.blogspot.com/-I2DuYetv7eQ/W3VtUUcg26I/AAAAAAAABXc/Tv-RemkyO60k6CC_mYUxewG-KfVgpFefACLcBGAs/s1600/browser-mpris2-cinnamon-linux-mint.png) + +### 如何为 Google Chrom/Chromium安装 browser-mpris2 + +**1\. 如果你还没有安装 Git 就安装它** + +在 Debian/Ubuntu/Linux Mint 中,使用此命令安装 git: +``` +sudo apt install git + +``` + +**2\. 下载并安装 [browser-mpris2][1] 所需文件。** + +下面的命令克隆了 browser-mpris2 的 Git 仓库并将 chrome-mpris2 安装到 `/usr/local/bin/`(在一个你可以保存 browser-mpris2 文件夹的地方运行 “git clone ...” 命令,由于它会被 Chrome/Chromium 使用,你不能删除它): +``` +git clone https://github.com/otommod/browser-mpris2 +sudo install browser-mpris2/native/chrome-mpris2 /usr/local/bin/ + +``` + +**3\. 在基于 Chrome/Chromium 的 Web 浏览器中加载此扩展。** + +![](https://3.bp.blogspot.com/-yEoNFj2wAXM/W3Vvewa979I/AAAAAAAABXo/dmltlNZk3J4sVa5jQenFFrT28ecklY92QCLcBGAs/s640/browser-mpris2-chrome-developer-load-unpacked.png) + +打开 Goog​​le Chrome、Chromium、Opera 或 Vivaldi 浏览器,进入 Extensions 页面(在 URL 栏中输入 `chrome://extensions`),在屏幕右上角切换到`开发者模式`。然后选择 `Load Unpacked` 并选择 chrome-mpris2 目录(确保没有选择子文件夹)。 + +复制扩展 ID 并保存它,因为你以后需要它(它类似于这样:`emngjajgcmeiligomkgpngljimglhhii`,但它会与你的不一样,因此确保使用你计算机中的 ID!)。 + +**4\. 运行 **`install-chrome.py`**(在 `browser-mpris2/native` 文件夹中),指定扩展 id 和 chrome-mpris2 路径。 + +在终端中使用此命令(将 `REPLACE-THIS-WITH-EXTENSION-ID` 替换为上一步中 `chrome://extensions` 下显示的 browser-mpris2 扩展 ID)安装此扩展: +``` +browser-mpris2/native/install-chrome.py REPLACE-THIS-WITH-EXTENSION-ID /usr/local/bin/chrome-mpris2 + +``` + +你只需要运行此命令一次,无需将其添加到启动或其他类似的地方。你在 Google Chrome 或 Chromium 浏览器中播放的任何 YouTube 视频都应显示在你正在使用的任何 MPRISv2 applet 中。你无需重启 Web 浏览器。 + +-------------------------------------------------------------------------------- + +via: https://www.linuxuprising.com/2018/08/add-youtube-player-controls-to-your.html + +作者:[Logix][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[geekpi](https://github.com/geekpi) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://plus.google.com/118280394805678839070 +[1]:https://github.com/otommod/browser-mpris2 +[2]:https://github.com/otommod/browser-mpris2/issues/11 +[3]:https://extensions.gnome.org/extension/55/media-player-indicator/ From 709f1cf488fe0f0dc3d874bf186e56b4f9baa8c4 Mon Sep 17 00:00:00 2001 From: geekpi Date: Sun, 30 Sep 2018 09:19:47 +0800 Subject: [PATCH 05/11] translating (#10443) --- sources/tech/20180928 10 handy Bash aliases for Linux.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sources/tech/20180928 10 handy Bash aliases for Linux.md b/sources/tech/20180928 10 handy Bash aliases for Linux.md index b69b2f8aab..7ae1070997 100644 --- a/sources/tech/20180928 10 handy Bash aliases for Linux.md +++ b/sources/tech/20180928 10 handy Bash aliases for Linux.md @@ -1,3 +1,5 @@ +translating---geekpi + 10 handy Bash aliases for Linux ====== Get more efficient by using condensed versions of long Bash commands. From 1f620f8ed3d7b82d1a88f6fd6107bd2338ff2932 Mon Sep 17 00:00:00 2001 From: darksun Date: Sun, 30 Sep 2018 09:34:51 +0800 Subject: [PATCH 06/11] =?UTF-8?q?=E9=80=89=E9=A2=98:=20Quiet=20log=20noise?= =?UTF-8?q?=20with=20Python=20and=20machine=20learning?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ... noise with Python and machine learning.md | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 sources/tech/20180928 Quiet log noise with Python and machine learning.md diff --git a/sources/tech/20180928 Quiet log noise with Python and machine learning.md b/sources/tech/20180928 Quiet log noise with Python and machine learning.md new file mode 100644 index 0000000000..f1fe2f1b7f --- /dev/null +++ b/sources/tech/20180928 Quiet log noise with Python and machine learning.md @@ -0,0 +1,110 @@ +Quiet log noise with Python and machine learning +====== + +Logreduce saves debugging time by picking out anomalies from mountains of log data. + +![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/sound-radio-noise-communication.png?itok=KMNn9QrZ) + +Continuous integration (CI) jobs can generate massive volumes of data. When a job fails, figuring out what went wrong can be a tedious process that involves investigating logs to discover the root cause—which is often found in a fraction of the total job output. To make it easier to separate the most relevant data from the rest, the [Logreduce][1] machine learning model is trained using previous successful job runs to extract anomalies from failed runs' logs. + +This principle can also be applied to other use cases, for example, extracting anomalies from [Journald][2] or other systemwide regular log files. + +### Using machine learning to reduce noise + +A typical log file contains many nominal events ("baselines") along with a few exceptions that are relevant to the developer. Baselines may contain random elements such as timestamps or unique identifiers that are difficult to detect and remove. To remove the baseline events, we can use a [k-nearest neighbors pattern recognition algorithm][3] (k-NN). + +![](https://opensource.com/sites/default/files/uploads/ml-generic-workflow.png) + +Log events must be converted to numeric values for k-NN regression. Using the generic feature extraction tool [HashingVectorizer][4] enables the process to be applied to any type of log. It hashes each word and encodes each event in a sparse matrix. To further reduce the search space, tokenization removes known random words, such as dates or IP addresses. + +![](https://opensource.com/sites/default/files/uploads/hashing-vectorizer.png) + +Once the model is trained, the k-NN search tells us the distance of each new event from the baseline. + +![](https://opensource.com/sites/default/files/uploads/kneighbors.png) + +This [Jupyter notebook][5] demonstrates the process and graphs the sparse matrix vectors. + +![](https://opensource.com/sites/default/files/uploads/anomaly-detection-with-scikit-learn.png) + +### Introducing Logreduce + +The Logreduce Python software transparently implements this process. Logreduce's initial goal was to assist with [Zuul CI][6] job failure analyses using the build database, and it is now integrated into the [Software Factory][7] development forge's job logs process. + +At its simplest, Logreduce compares files or directories and removes lines that are similar. Logreduce builds a model for each source file and outputs any of the target's lines whose distances are above a defined threshold by using the following syntax: **distance | filename:line-number: line-content**. + +``` +$ logreduce diff /var/log/audit/audit.log.1 /var/log/audit/audit.log +INFO  logreduce.Classifier - Training took 21.982s at 0.364MB/s (1.314kl/s) (8.000 MB - 28.884 kilo-lines) +0.244 | audit.log:19963:        type=USER_AUTH acct="root" exe="/usr/bin/su" hostname=managesf.sftests.com +INFO  logreduce.Classifier - Testing took 18.297s at 0.306MB/s (1.094kl/s) (5.607 MB - 20.015 kilo-lines) +99.99% reduction (from 20015 lines to 1 + +``` + +A more advanced Logreduce use can train a model offline to be reused. Many variants of the baselines can be used to fit the k-NN search tree. + +``` +$ logreduce dir-train audit.clf /var/log/audit/audit.log.* +INFO  logreduce.Classifier - Training took 80.883s at 0.396MB/s (1.397kl/s) (32.001 MB - 112.977 kilo-lines) +DEBUG logreduce.Classifier - audit.clf: written +$ logreduce dir-run audit.clf /var/log/audit/audit.log +``` + +Logreduce also implements interfaces to discover baselines for Journald time ranges (days/weeks/months) and Zuul CI job build histories. It can also generate HTML reports that group anomalies found in multiple files in a simple interface. + +![](https://opensource.com/sites/default/files/uploads/html-report.png) + +### Managing baselines + +The key to using k-NN regression for anomaly detection is to have a database of known good baselines, which the model uses to detect lines that deviate too far. This method relies on the baselines containing all nominal events, as anything that isn't found in the baseline will be reported as anomalous. + +CI jobs are great targets for k-NN regression because the job outputs are often deterministic and previous runs can be automatically used as baselines. Logreduce features Zuul job roles that can be used as part of a failed job post task in order to issue a concise report (instead of the full job's logs). This principle can be applied to other cases, as long as baselines can be constructed in advance. For example, a nominal system's [SoS report][8] can be used to find issues in a defective deployment. + +![](https://opensource.com/sites/default/files/uploads/baselines.png) + +### Anomaly classification service + +The next version of Logreduce introduces a server mode to offload log processing to an external service where reports can be further analyzed. It also supports importing existing reports and requests to analyze a Zuul build. The services run analyses asynchronously and feature a web interface to adjust scores and remove false positives. + +![](https://opensource.com/sites/default/files/uploads/classification-interface.png) + +Reviewed reports can be archived as a standalone dataset with the target log files and the scores for anomalous lines recorded in a flat JSON file. + +### Project roadmap + +Logreduce is already being used effectively, but there are many opportunities for improving the tool. Plans for the future include: + + * Curating many annotated anomalies found in log files and producing a public domain dataset to enable further research. Anomaly detection in log files is a challenging topic, and having a common dataset to test new models would help identify new solutions. + * Reusing the annotated anomalies with the model to refine the distances reported. For example, when users mark lines as false positives by setting their distance to zero, the model could reduce the score of those lines in future reports. + * Fingerprinting archived anomalies to detect when a new report contains an already known anomaly. Thus, instead of reporting the anomaly's content, the service could notify the user that the job hit a known issue. When the issue is fixed, the service could automatically restart the job. + * Supporting more baseline discovery interfaces for targets such as SOS reports, Jenkins builds, Travis CI, and more. + + + +If you are interested in getting involved in this project, please contact us on the **#log-classify** Freenode IRC channel. Feedback is always appreciated! + +Tristan Cacqueray will present [Reduce your log noise using machine learning][9] at the [OpenStack Summit][10], November 13-15 in Berlin. + +-------------------------------------------------------------------------------- + +via: https://opensource.com/article/18/9/quiet-log-noise-python-and-machine-learning + +作者:[Tristan de Cacqueray][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[译者ID](https://github.com/译者ID) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://opensource.com/users/tristanc +[1]: https://pypi.org/project/logreduce/ +[2]: http://man7.org/linux/man-pages/man8/systemd-journald.service.8.html +[3]: https://en.wikipedia.org/wiki/K-nearest_neighbors_algorithm +[4]: http://scikit-learn.org/stable/modules/generated/sklearn.feature_extraction.text.HashingVectorizer.html +[5]: https://github.com/TristanCacqueray/anomaly-detection-workshop-opendev/blob/master/datasets/notebook/anomaly-detection-with-scikit-learn.ipynb +[6]: https://zuul-ci.org +[7]: https://www.softwarefactory-project.io +[8]: https://sos.readthedocs.io/en/latest/ +[9]: https://www.openstack.org/summit/berlin-2018/summit-schedule/speakers/4307 +[10]: https://www.openstack.org/summit/berlin-2018/ From 36da26f8765ede333fb7833a517c0a5a24f40394 Mon Sep 17 00:00:00 2001 From: sd886393 Date: Sun, 30 Sep 2018 09:39:26 +0800 Subject: [PATCH 07/11] =?UTF-8?q?=E3=80=90=E5=AE=8C=E6=88=90=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E3=80=9120180531=20How=20to=20create=20shortcuts=20in?= =?UTF-8?q?=20vi.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../20180531 How to create shortcuts in vi.md | 131 ----------------- .../20180531 How to create shortcuts in vi.md | 134 ++++++++++++++++++ 2 files changed, 134 insertions(+), 131 deletions(-) delete mode 100644 sources/tech/20180531 How to create shortcuts in vi.md create mode 100644 translated/tech/20180531 How to create shortcuts in vi.md diff --git a/sources/tech/20180531 How to create shortcuts in vi.md b/sources/tech/20180531 How to create shortcuts in vi.md deleted file mode 100644 index ba856e745a..0000000000 --- a/sources/tech/20180531 How to create shortcuts in vi.md +++ /dev/null @@ -1,131 +0,0 @@ -【sd886393认领翻译中】How to create shortcuts in vi -====== - -![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/documentation-type-keys-yearbook.png?itok=Q-ELM2rn) - -Learning the [vi text editor][1] takes some effort, but experienced vi users know that after a while, using basic commands becomes second nature. It's a form of what is known as muscle memory, which in this case might well be called finger memory. - -After you get a grasp of the main approach and basic commands, you can make editing with vi even more powerful and streamlined by using its customization options to create shortcuts. I hope that the techniques described below will facilitate your writing, programming, and data manipulation. - -Before proceeding, I'd like to thank Chris Hermansen (who recruited me to write this article) for checking my draft with [Vim][2], as I use another version of vi. I'm also grateful for Chris's helpful suggestions, which I incorporated here. - -First, let's review some conventions. I'll use to designate pressing the RETURN or ENTER key, and for the space bar. CTRL-x indicates simultaneously pressing the Control key and the x key (whatever x happens to be). - -Set up your own command abbreviations with the `map` command. My first example involves the `write` command, used to save the current state of the file you're working on: -``` -:w - -``` - -This is only three keystrokes, but since I do it so frequently, I'd rather use only one. The key I've chosen for this purpose is the comma, which is not part of the standard vi command set. The command to set this up is: -``` -:map , :wCTRL-v - -``` - -The CTRL-v is essential since without it the would signal the end of the map, and we want to include the as part of the mapped comma. In general, CTRL-v is used to enter the keystroke (or control character) that follows rather than being interpreted literally. - -In the above map, the part on the right will display on the screen as `:w^M`. The caret (`^`) indicates a control character, in this case CTRL-m, which is the system's form of . - -So far so good—sort of. If I write my current file about a dozen times while creating and/or editing it, this map could result in a savings of 2 x 12 keystrokes. But that doesn't account for the keystrokes needed to set up the map, which in the above example is 11 (counting CTRL-v and the shifted character `:` as one stroke each). Even with a net savings, it would be a bother to set up the map each time you start a vi session. - -Fortunately, there's a way to put maps and other abbreviations in a startup file that vi reads each time it is invoked: the `.exrc` file, or in Vim, the `.vimrc` file. Simply create this file in your home directory with a list of maps, one per line—without the colon—and the abbreviation is defined for all subsequent vi sessions until you delete or change it. - -Before going on to a variation of the `map` command and another type of abbreviation method, here are a few more examples of maps that I've found useful for streamlining my text editing: -``` -                                        Displays as - - - -:map X :xCTRL-v                    :x^M - - - -or - - - -:map X ,:qCTRL-v                   ,:q^M - -``` - -The above equivalent maps write and quit (exit) the file. The `:x` is the standard vi command for this, and the second version illustrates that a previously defined map may be used in a subsequent map. -``` -:map v :e                   :e - -``` - -The above starts the command to move to another file while remaining within vi; when using this, just follow the "v" with a filename, followed by . -``` -:map CTRL-vCTRL-e :e#CTRL-v    :e #^M - -``` - -The `#` here is the standard vi symbol for "the alternate file," which means the filename last used, so this shortcut is handy for switching back and forth between two files. Here's an example of how I use this: -``` -map CTRL-vCTRL-r :!spell %>err &CTRL-v     :!spell %>err&^M - -``` - -(Note: The first CTRL-v in both examples above is not needed in some versions of vi.) The `:!` is a way to run an external (non-vi) command. In this case (`spell`), `%` is the vi symbol denoting the current file, the `>` redirects the output of the spell-check to a file called `err`, and the `&` says to run this in the background so I can continue editing while `spell` completes its task. I can then type `verr` (using my previous shortcut, `v`, followed by `err`) to go the file of potential errors flagged by the `spell` command, then back to the file I'm working on with CTRL-e. After running the spell-check the first time, I can use CTRL-r repeatedly and return to the `err` file with just CTRL-e. - -A variation of the `map` command may be used to abbreviate text strings while inputting. For example, -``` -:map! CTRL-o \fI - -:map! CTRL-k \fP - -``` - -This will allow you to use CTRL-o as a shortcut for entering the `groff` command to italicize the word that follows, and this will allow you to use CTRL-k for the `groff` command reverts to the previous font. - -Here are two other examples of this technique: -``` -:map! rh rhinoceros - -:map! hi hippopotamus - -``` - -The above may instead be accomplished using the `ab` command, as follows (if you're trying these out in order, first use `unmap! rh` and `umap! hi`): -``` -:ab rh rhinoceros - -:ab hi hippopotamus - -``` - -In the `map!` method above, the abbreviation immediately expands to the defined word when typed (in Vim), whereas with the `ab` method, the expansion occurs when the abbreviation is followed by a space or punctuation mark (in both Vim and my version of vi, where the expansion also works like this for the `map!` method). - -To reverse any `map`, `map!`, or `ab` within a vi session, use `:unmap`, `:unmap!`, or `:unab`. - -In my version of vi, undefined letters that are good candidates for mapping include g, K, q, v, V, and Z; undefined control characters are CTRL-a, CTRL-c, CTRL-k, CTRL-n, CTRL-o, CTRL-p, and CTRL-x; some other undefined characters are `#` and `*`. You can also redefine characters that have meaning in vi but that you consider obscure and of little use; for example, the X that I chose for two examples in this article is a built-in vi command to delete the character to the immediate left of the current character (easily accomplished by the two-key command `hx`). - -Finally, the commands -``` -:map - -:map! - -:ab - -``` - -will show all the currently defined mappings and abbreviations. - -I hope that all of these tips will help you customize vi and make it easier and more efficient to use. - --------------------------------------------------------------------------------- - -via: https://opensource.com/article/18/5/shortcuts-vi-text-editor - -作者:[Dan Sonnenschein][a] -选题:[lujun9972](https://github.com/lujun9972) -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://opensource.com/users/dannyman -[1]:http://ex-vi.sourceforge.net/ -[2]:https://www.vim.org/ diff --git a/translated/tech/20180531 How to create shortcuts in vi.md b/translated/tech/20180531 How to create shortcuts in vi.md new file mode 100644 index 0000000000..8616013e96 --- /dev/null +++ b/translated/tech/20180531 How to create shortcuts in vi.md @@ -0,0 +1,134 @@ +如何在 vi 中创建快捷键 +====== + +![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/documentation-type-keys-yearbook.png?itok=Q-ELM2rn) + +学习使用 [vi 文本编辑器][1] 确实得花点功夫,不过 vi 的老手们都知道,经过一小会的锻炼,就可以将基本的 vi 操作融汇贯通。我们都知道“肌肉记忆”,那么学习 vi 的过程可以称之为“手指记忆”。 + +当你抓住了基础的操作窍门之后,你就可以定制化地配置 vi 的快捷键,从而让其处理的功能更为强大、流畅。 + +在开始之前,我想先感谢下 Chris Hermansen(他雇佣我写了这篇文章)仔细地检查了我的另一篇关于使用 vi 增强版本[Vim][2]的文章。当然还有他那些我未采纳的建议。 + +首先,我们来说明下面几个惯例设定。我会使用符号来代表按下 RETURN 或者 ENTER 键, 代表按下空格键,CTRL-x 表示一起按下 Control 键和 x 键 + +使用 `map` 命令来进行按键的映射。第一个例子是 `write` 命令,通常你之前保存使用这样的命令: + +``` +:w + +``` + +虽然这里只有三个键,不过考虑到我用这个命令实在是太频繁了,我更想“一键”搞定它。在这里我选择逗号键,比如这样: +``` +:map , :wCTRL-v + +``` + +这里的 CTRL-v 事实上是对 做了转义的操作,如果不加这个的话,默认 会作为这条映射指令的结束信号,而非映射中的一个操作。 CTRL-v 后面所跟的操作会翻译为用户的实际操作,而非该按键平常的操作。 + +在上面的映射中,右边的部分会在屏幕中显示为 `:w^M`,其中 `^` 字符就是指代 `control`,完整的意思就是 CTRL-m,表示就是系统中一行的结尾 + + +目前来说,就很不错了。如果我编辑、创建了十二次文件,这个键位映射就可以省掉了 2*12 次按键。不过这里没有计算你建立这个键位映射所花费的 11次按键(计算CTRL-v 和 冒号均为一次按键)。虽然这样已经省了很多次,但是每次打开 vi 都要重新建立这个映射也会觉得非常麻烦。 + +幸运的是,这里可以将这些键位映射放到 vi 的启动配置文件中,让其在每次启动的时候自动读取:文件为 `.exrc`,对于 vim 是 `.vimrc`。只需要将这些文件放在你的用户根目录中即可,并在文件中每行写入一个键位映射,之后就会在每次启动 vi 生效直到你删除对应的配置。 + +在继续说明 `map` 其他用法以及其他的缩写机制之前,这里在列举几个我常用提高文本处理效率的 map 设置: +``` +                                        Displays as + + + +:map X :xCTRL-v                    :x^M + + + +or + + + +:map X ,:qCTRL-v                   ,:q^M + +``` + +上面的 map 指令的意思是写入并关闭当前的编辑文件。其中 `:x` 是 vi 原本的命令,而下面的版本说明之前的 map 配置可以继续用作第二个 map 键位映射。 +``` +:map v :e                   :e + +``` + +上面的指令意思是在 vi 编辑器内部 切换文件,使用这个时候,只需要按 `v` 并跟着输入文件名,之后按 `` 键。 +``` +:map CTRL-vCTRL-e :e#CTRL-v    :e #^M + +``` + +`#` 在这里是 vi 中标准的符号,意思是最后使用的文件名。所以切换当前与上一个文件的方法就使用上面的映射。 +``` +map CTRL-vCTRL-r :!spell %>err &CTRL-v     :!spell %>err&^M + +``` + +(注意:在两个例子中出现的第一个 CRTL-v 在某些 vi 的版本中是不需要的)其中,`:!` 用来运行一个外部的(非 vi 内部的)命令。在这个拼写检查的例子中,`%` 是 vi 中的符号用来只带目前的文件, `>` 用来重定向拼写检查中的输出到 `err` 文件中,之后跟上 `&` 说明该命令是一个后台运行的任务,这样可以保证在拼写检查的同时还可以进行编辑文件的工作。这里我可以键入 `verr`(使用我之前定义的快捷键 `v` 跟上 `err`),进入 `spell` 输出结果的文件,之后再输入 `CTRL-e` 来回到刚才编辑的文件中。这样我就可以在拼写检查之后,使用 CTRL-r 来查看检查的错误,再通过 CTRL-e 返回刚才编辑的文件。 + +还用很多字符串输入的缩写,也使用了各种 map 命令,比如: +``` +:map! CTRL-o \fI + +:map! CTRL-k \fP + +``` + +这个映射允许你使用 CTRL-o 作为 `groff` 命令的缩写,从而让让接下来书写的单词有斜体的效果,并使用 CTRL-k 进行恢复 + +还有两个类似的映射: +``` +:map! rh rhinoceros + +:map! hi hippopotamus + +``` + +上面的也可以使用 `ab` 命令来替换,就像下面这样(如果想这么用的话,需要首先按顺序运行 1. `unmap! rh` 2. `umap! hi`): +``` +:ab rh rhinoceros + +:ab hi hippopotamus + +``` + +在上面 `map!` 的命令中,缩写会马上的展开成原有的单词,而在 `ab` 命令中,单词展开的操作会在输入了空格和标点之后才展开(不过在Vim 和 本机使用的 vi中,展开的形式与 `map!` 类似) + +想要取消刚才设定的按键映射,可以对应的输入 `:unmap`, `unmap!`, `:unab` + +在我使用的 vi 版本中,比较好用的候选映射按键包括 `g, K, q, v, V, Z`,控制字符包括:`CTRL-a, CTRL-c, CTRL-k, CTRL-n, CTRL-p, CTRL-x`;还有一些其他的字符如`#, *`,当然你也可以使用那些已经在 vi 中有过定义但不经常使用的字符,比如本文选择`X`和`I`,其中`X`表示删除左边的字符,并立刻左移当前字符。 + +最后,下面的命令 +``` +:map + +:map! + +:ab + +``` + +将会显示,目前所有的缩写和键位映射。 +will show all the currently defined mappings and abbreviations. + +希望上面的技巧能够更好地更高效地帮助你使用 vi。 + +-------------------------------------------------------------------------------- + +via: https://opensource.com/article/18/5/shortcuts-vi-text-editor + +作者:[Dan Sonnenschein][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[译者ID](https://github.com/sd886393) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://opensource.com/users/dannyman +[1]:http://ex-vi.sourceforge.net/ +[2]:https://www.vim.org/ From c15c7589ea070392cc72c0cc1e83c58df59a418a Mon Sep 17 00:00:00 2001 From: darksun Date: Sun, 30 Sep 2018 09:49:33 +0800 Subject: [PATCH 08/11] =?UTF-8?q?=E9=80=89=E9=A2=98:=20Use=20Cozy=20to=20P?= =?UTF-8?q?lay=20Audiobooks=20in=20Linux?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...29 Use Cozy to Play Audiobooks in Linux.md | 138 ++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 sources/tech/20180929 Use Cozy to Play Audiobooks in Linux.md diff --git a/sources/tech/20180929 Use Cozy to Play Audiobooks in Linux.md b/sources/tech/20180929 Use Cozy to Play Audiobooks in Linux.md new file mode 100644 index 0000000000..8e6583f046 --- /dev/null +++ b/sources/tech/20180929 Use Cozy to Play Audiobooks in Linux.md @@ -0,0 +1,138 @@ +Use Cozy to Play Audiobooks in Linux +====== +**We review Cozy, an audiobook player for Linux. Read to find out if it’s worth to install Cozy on your Linux system or not.** + +![Audiobook player for Linux][1] + +Audiobooks are a great way to consume literature. Many people who don’t have time to read, choose to listen. Most people, myself included, just use a regular media player like VLC or [MPV][2] for listening to audiobooks on Linux. + +Today, we will look at a Linux application built solely for listening to audiobooks. + +![][3]Cozy Audiobook Player + +### Cozy Audiobook Player for Linux + +The [Cozy Audiobook Player][4] is created by [Julian Geywitz][5] from Germany. It is built using both Python and GTK+ 3. According to the site, Julian wrote Cozy on Fedora and optimized it for [elementary OS][6]. + +The player borrows its layout from iTunes. The player controls are placed along the top of the application The library takes up the rest. You can sort all of your audiobooks based on the title, author and reader, and search very quickly. + +![][7]Initial setup + +When you first launch [Cozy][8], you are given the option to choose where you will store your audiobook files. Cozy will keep an eye on that folder and update your library as you add new audiobooks. You can also set it up to use an external or network drive. + +#### Features of Cozy + +Here is a full list of the features that [Cozy][9] has to offer. + + * Import all your audiobooks into Cozy to browse them comfortably + * Sort your audiobooks by author, reader & title + * Remembers your playback position + * Sleep timer + * Playback speed control + * Search your audiobook library + * Add multiple storage locations + * Drag & Drop to import new audio books + * Support for DRM free mp3, m4a (aac, ALAC, …), flac, ogg, wav files + * Mpris integration (Media keys & playback info for the desktop environment) + * Developed on Fedora and tested under elementaryOS + + + +#### Experiencing Cozy + +![][10]Audiobook library + +At first, I was excited to try our Cozy because I like to listen to audiobooks. However, I ran into a couple of issues. There is no way to edit the information of an audiobook. For example, I downloaded a couple audiobooks from [LibriVox][11] to test it. All three audiobooks were listed under “Unknown” for the reader. There was nothing to edit or change the audiobook info. I guess you could edit all of the files, but that would take quite a bit of time. + +When I listen to an audiobook, I like to know what track is currently playing. Cozy only has a single progress bar for the whole audiobook. I know that Cozy is designed to remember where you left off in an audiobook, but if I was going to continue to listen to the audiobook on my phone, I would like to know what track I am on. + +![][12]Settings + +There was also an option in the setting menu to turn on a dark theme. As you can see in the screenshots, the application has a black theme, to begin with. I turned the option on, but nothing happened. There isn’t even an option to add a theme or change any of the colors. Overall, the application had a feeling of not being finished. + +#### Installing Cozy on Linux + +If you would like to install Cozy, you have several options for different distros. + +##### Ubuntu, Debian, openSUSE, Fedora + +Julian used the [openSUSE Build Service][13] to create custom repos for Ubuntu, Debian, openSUSE and Fedora. Each one only takes a couple terminal commands to install. + +##### Install Cozy using Flatpak on any Linux distribution (including Ubuntu) + +If your [distro supports Flatpak][14], you can install Cozy using the following commands: + +``` +flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo +flatpak install --user flathub com.github.geigi.cozy +``` + +##### Install Cozy on elementary OS + +If you have elementary OS installed, you can install Cozy from the [built-in App Store][15]. + +##### Install Cozy on Arch Linux + +Cozy is available in the [Arch User Repository][16]. All you have to do is search for `cozy-audiobooks`. + +### Where to find free Audiobooks? + +In order to try out this application, you will need to find some audiobooks to listen to. My favorite site for audiobooks is [LibriVox][11]. Since [LibriVox][17] depends on volunteers to record audiobooks, the quality can vary. However, there are a number of very talented readers. + +Here is a list of free audiobook sources: + ++ [Open Culture][20] ++ [Project Gutenberg][21] ++ [Digitalbook.io][22] ++ [FreeClassicAudioBooks.com][23] ++ [MindWebs][24] ++ [Scribl][25] + + +### Final Thoughts on Cozy + +For now, I think I’ll stick with my preferred audiobook software (VLC) for now. Cozy just doesn’t add anything. I won’t call it a [must-have application for Linux][18] just yet. There is no compelling reason for me to switch. Maybe, I’ll revisit it again in the future, maybe when it hits 1.0. + +Take Cozy for a spin. You might come to a different conclusion. + +Have you ever used Cozy? If not, what is your favorite audiobook player? What is your favorite source for free audiobooks? Let us know in the comments below. + +If you found this article interesting, please take a minute to share it on social media, Hacker News or [Reddit][19]. + +-------------------------------------------------------------------------------- + +via: https://itsfoss.com/cozy-audiobook-player/ + +作者:[John Paul][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[译者ID](https://github.com/译者ID) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: https://itsfoss.com/author/john/ +[1]: https://4bds6hergc-flywheel.netdna-ssl.com/wp-content/uploads/2018/09/audiobook-player-linux.png +[2]: https://itsfoss.com/mpv-video-player/ +[3]: https://4bds6hergc-flywheel.netdna-ssl.com/wp-content/uploads/2018/09/cozy3.jpg +[4]: https://cozy.geigi.de/ +[5]: https://github.com/geigi +[6]: https://elementary.io/ +[7]: https://4bds6hergc-flywheel.netdna-ssl.com/wp-content/uploads/2018/09/cozy1.jpg +[8]: https://github.com/geigi/cozy +[9]: https://www.patreon.com/geigi +[10]: https://4bds6hergc-flywheel.netdna-ssl.com/wp-content/uploads/2018/09/cozy2.jpg +[11]: https://librivox.org/ +[12]: https://4bds6hergc-flywheel.netdna-ssl.com/wp-content/uploads/2018/09/cozy4.jpg +[13]: https://software.opensuse.org//download.html?project=home%3Ageigi&package=com.github.geigi.cozy +[14]: https://itsfoss.com/flatpak-guide/ +[15]: https://elementary.io/store/ +[16]: https://aur.archlinux.org/ +[17]: https://archive.org/details/librivoxaudio +[18]: https://itsfoss.com/essential-linux-applications/ +[19]: http://reddit.com/r/linuxusersgroup +[20]: http://www.openculture.com/freeaudiobooks +[21]: http://www.gutenberg.org/browse/categories/1 +[22]: https://www.digitalbook.io/ +[23]: http://freeclassicaudiobooks.com/ +[24]: https://archive.org/details/MindWebs_201410 +[25]: https://scribl.com/ From 8c14a12d4eb0196066cd94a38052fbe1f7a178f2 Mon Sep 17 00:00:00 2001 From: sd886393 Date: Sun, 30 Sep 2018 10:17:12 +0800 Subject: [PATCH 09/11] =?UTF-8?q?=E8=AE=A4=E9=A2=86=20by=20sd886393?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tech/20180928 What containers can teach us about DevOps.md | 1 + 1 file changed, 1 insertion(+) diff --git a/sources/tech/20180928 What containers can teach us about DevOps.md b/sources/tech/20180928 What containers can teach us about DevOps.md index 610a68b2d1..33f83fb0f7 100644 --- a/sources/tech/20180928 What containers can teach us about DevOps.md +++ b/sources/tech/20180928 What containers can teach us about DevOps.md @@ -1,3 +1,4 @@ +认领:by sd886393 What containers can teach us about DevOps ====== From 7bb6f3525e1d7a28d5676b2f41d242b1e27f66b6 Mon Sep 17 00:00:00 2001 From: MjSeven <33125422+MjSeven@users.noreply.github.com> Date: Sun, 30 Sep 2018 10:26:04 +0800 Subject: [PATCH 10/11] Delete 20180816 An introduction to the Django Python web app framework.md --- ... to the Django Python web app framework.md | 1250 ----------------- 1 file changed, 1250 deletions(-) delete mode 100644 sources/tech/20180816 An introduction to the Django Python web app framework.md diff --git a/sources/tech/20180816 An introduction to the Django Python web app framework.md b/sources/tech/20180816 An introduction to the Django Python web app framework.md deleted file mode 100644 index ab7dba9526..0000000000 --- a/sources/tech/20180816 An introduction to the Django Python web app framework.md +++ /dev/null @@ -1,1250 +0,0 @@ -Translating by MjSeven - - -An introduction to the Django Python web app framework -====== - -![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/web-spider-frame-framework.png?itok=Rl2AG2Dc) - -In the first three articles of this four-part series comparing different Python web frameworks, we covered the [Pyramid][1], [Flask][2], and [Tornado][3] web frameworks. We've built the same app three times and have finally made our way to [Django][4]. Django is, by and large, the major web framework for Python developers these days and it's not too hard to see why. It excels in hiding a lot of the configuration logic and letting you focus on being able to build big, quickly. - -That said, when it comes to small projects, like our To-Do List app, Django can be a bit like bringing a firehose to a water gun fight. Let's see how it all comes together. - -### About Django - -Django styles itself as "a high-level Python web framework that encourages rapid development and clean, pragmatic design. Built by experienced developers, it takes care of much of the hassle of web development, so you can focus on writing your app without needing to reinvent the wheel." And they really mean it! This massive web framework comes with so many batteries included that oftentimes during development it can be a mystery as to how everything manages to work together. - -In addition to the framework itself being large, the Django community is absolutely massive. In fact, it's so big and active that there's [a whole website][5] devoted to the third-party packages people have designed to plug into Django to do a whole host of things. This includes everything from authentication and authorization, to full-on Django-powered content management systems, to e-commerce add-ons, to integrations with Stripe. Talk about not re-inventing the wheel; chances are if you want something done with Django, someone has already done it and you can just pull it into your project. - -For this purpose, we want to build a REST API with Django, so we'll leverage the always popular [Django REST framework][6]. Its job is to turn the Django framework, which was made to serve fully rendered HTML pages built with Django's own templating engine, into a system specifically geared toward effectively handling REST interactions. Let's get going with that. - -### Django startup and configuration -``` -$ mkdir django_todo - -$ cd django_todo - -$ pipenv install --python 3.6 - -$ pipenv shell - -(django-someHash) $ pipenv install django djangorestframework - -``` - -For reference, we're working with `django-2.0.7` and `djangorestframework-3.8.2`. - -Unlike Flask, Tornado, and Pyramid, we don't need to write our own `setup.py` file. We're not making an installable Python distribution. As with many things, Django takes care of that for us in its own Django way. We'll still need a `requirements.txt` file to track all our necessary installs for deployment elsewhere. However, as far as targeting modules within our Django project goes, Django will let us list the subdirectories we want access to, then allow us to import from those directories as if they're installed packages. - -First, we have to create a Django project. - -When we installed Django, we also installed the command-line script `django-admin`. Its job is to manage all the various Django-related commands that help put our project together and maintain it as we continue to develop. Instead of having us build up the entire Django ecosystem from scratch, the `django-admin` will allow us to get started with all the absolutely necessary files (and more) we need for a standard Django project. - -The syntax for invoking `django-admin`'s start-project command is `django-admin startproject `. We want the files to exist in our current working directory, so: -``` -(django-someHash) $ django-admin startproject django_todo . - -``` - -Typing `ls` will show one new file and one new directory. -``` -(django-someHash) $ ls - -manage.py   django_todo - -``` - -`manage.py` is a command-line-executable Python file that ends up just being a wrapper around `django-admin`. As such, its job is the same: to help us manage our project. Hence the name `manage.py`. - -The directory it created, the `django_todo` inside of `django_todo`, represents the configuration root for our project. Let's dig into that now. - -### Configuring Django - -By calling the `django_todo` directory the "configuration root," we mean this directory holds the files necessary for generally configuring our Django project. Pretty much everything outside this directory will be focused solely on the "business logic" associated with the project's models, views, routes, etc. All points that connect the project together will lead here. - -Calling `ls` within `django_todo` reveals four files: -``` -(django-someHash) $ cd django_todo - -(django-someHash) $ ls - -__init__.py settings.py urls.py     wsgi.py - -``` - - * `__init__.py` is empty, solely existing to turn this directory into an importable Python package. - * `settings.py` is where most configuration items will be set, like whether the project's in DEBUG mode, what databases are in use, where Django should look for files, etc. It is the "main configuration" part of the configuration root, and we'll dig into that momentarily. - * `urls.py` is, as the name implies, where the URLs are set. While we don't have to explicitly write every URL for the project in this file, we **do** need to make this file aware of any other places where URLs have been declared. If this file doesn't point to other URLs, those URLs don't exist. **Period.** - * `wsgi.py` is for serving the application in production. Just like how Pyramid, Tornado, and Flask exposed some "app" object that was the configured application to be served, Django must also expose one. That's done here. It can then be served with something like [Gunicorn][7], [Waitress][8], or [uWSGI][9]. - - - -#### Setting the settings - -Taking a look inside `settings.py` will reveal its considerable size—and these are just the defaults! This doesn't even include hooks for the database, static files, media files, any cloud integration, or any of the other dozens of ways that a Django project can be configured. Let's see, top to bottom, what we've been given: - - * `BASE_DIR` sets the absolute path to the base directory, or the directory where `manage.py` is located. This is useful for locating files. - * `SECRET_KEY` is a key used for cryptographic signing within the Django project. In practice, it's used for things like sessions, cookies, CSRF protection, and auth tokens. As soon as possible, preferably before the first commit, the value for `SECRET_KEY` should be changed and moved into an environment variable. - * `DEBUG` tells Django whether to run the project in development mode or production mode. This is an extremely critical distinction. - * In development mode, when an error pops up, Django will show the full stack trace that led to the error, as well as all the settings and configurations involved in running the project. This can be a massive security issue if `DEBUG` was set to `True` in a production environment. - * In production, Django shows a plain error page when things go wrong. No information is given beyond an error code. - * A simple way to safeguard our project is to set `DEBUG` to an environment variable, like `bool(os.environ.get('DEBUG', ''))`. - * `ALLOWED_HOSTS` is the literal list of hostnames from which the application is being served. In development this can be empty, but in production our Django project will not run if the host that serves the project is not among the list of ALLOWED_HOSTS. Another thing for the box of environment variables. - * `INSTALLED_APPS` is the list of Django "apps" (think of them as subdirectories; more on this later) that our Django project has access to. We're given a few by default to provide… - * The built-in Django administrative website - * Django's built-in authentication system - * Django's one-size-fits-all manager for data models - * Session management - * Cookie and session-based messaging - * Usage of static files inherent to the site, like `css` files, `js` files, any images that are a part of our site's design, etc. - * `MIDDLEWARE` is as it sounds: the middleware that helps our Django project run. Much of it is for handling various types of security, although we can add others as we need them. - * `ROOT_URLCONF` sets the import path of our base-level URL configuration file. That `urls.py` that we saw before? By default, Django points to that file to gather all our URLs. If we want Django to look elsewhere, we'll set the import path to that location here. - * `TEMPLATES` is the list of template engines that Django would use for our site's frontend if we were relying on Django to build our HTML. Since we're not, it's irrelevant. - * `WSGI_APPLICATION` sets the import path of our WSGI application—the thing that gets served when in production. By default, it points to an `application` object in `wsgi.py`. This rarely, if ever, needs to be modified. - * `DATABASES` sets which databases our Django project will access. The `default` database must be set. We can set others by name, as long as we provide the `HOST`, `USER`, `PASSWORD`, `PORT`, database `NAME`, and appropriate `ENGINE`. As one might imagine, these are all sensitive pieces of information, so it's best to hide them away in environment variables. [Check the Django docs][10] for more details. - * Note: If instead of providing individual pieces of a database's location, you'd rather provide the full database URL, check out [dj_database_url][11]. - * `AUTH_PASSWORD_VALIDATORS` is effectively a list of functions that run to check input passwords. We get a few by default, but if we had other, more complex validation needs—more than merely checking if the password matches a user's attribute, if it exceeds the minimum length, if it's one of the 1,000 most common passwords, or if the password is entirely numeric—we could list them here. - * `LANGUAGE_CODE` will set the language for the site. By default it's US English, but we could switch it up to be other languages. - * `TIME_ZONE` is the time zone for any autogenerated timestamps in our Django project. I cannot stress enough how important it is that we stick to UTC and perform any time zone-specific processing elsewhere instead of trying to reconfigure this setting. As [this article][12] states, UTC is the common denominator among all time zones because there are no offsets to worry about. If offsets are that important, we could calculate them as needed with an appropriate offset from UTC. - * `USE_I18N` will let Django use its own translation services to translate strings for the front end. I18N = internationalization (18 characters between "i" and "n") - * `USE_L10N` (L10N = localization [10 characters between "l" and "n"]) will use the common local formatting of data if set to `True`. A great example is dates: in the US it's MM-DD-YYYY. In Europe, dates tend to be written DD-MM-YYYY - * `STATIC_URL` is part of a larger body of settings for serving static files. We'll be building a REST API, so we won't need to worry about static files. In general, this sets the root path after the domain name for every static file. So, if we had a logo image to serve, it'd be `http:////logo.gif` - - - -These settings are pretty much ready to go by default. One thing we'll have to change is the `DATABASES` setting. First, we create the database that we'll be using with: -``` -(django-someHash) $ createdb django_todo - -``` - -We want to use a PostgreSQL database like we did with Flask, Pyramid, and Tornado. That means we'll have to change the `DATABASES` setting to allow our server to access a PostgreSQL database. First: the engine. By default, the database engine is `django.db.backends.sqlite3`. We'll be changing that to `django.db.backends.postgresql`. - -For more information about Django's available engines, [check the docs][13]. Note that while it is technically possible to incorporate a NoSQL solution into a Django project, out of the box, Django is strongly biased toward SQL solutions. - -Next, we have to specify the key-value pairs for the different parts of the connection parameters. - - * `NAME` is the name of the database we just created. - * `USER` is an individual's Postgres database username - * `PASSWORD` is the password needed to access the database - * `HOST` is the host for the database. `localhost` or `127.0.0.1` will work, as we're developing locally. - * `PORT` is whatever PORT we have open for Postgres; it's typically `5432`. - - - -`settings.py` expects us to provide string values for each of these keys. However, this is highly sensitive information. That's not going to work for any responsible developer. There are several ways to address this problem, but we'll just set up environment variables. -``` -DATABASES = { - -    'default': { - -        'ENGINE': 'django.db.backends.postgresql', - -        'NAME': os.environ.get('DB_NAME', ''), - -        'USER': os.environ.get('DB_USER', ''), - -        'PASSWORD': os.environ.get('DB_PASS', ''), - -        'HOST': os.environ.get('DB_HOST', ''), - -        'PORT': os.environ.get('DB_PORT', ''), - -    } - -} - -``` - -Before going forward, make sure to set the environment variables or Django will not work. Also, we need to install `psycopg2` into this environment so we can talk to our database. - -### Django routes and views - -Let's make something function inside this project. We'll be using Django REST Framework to construct our REST API, so we have to make sure we can use it by adding `rest_framework` to the end of `INSTALLED_APPS` in `settings.py`. -``` -INSTALLED_APPS = [ - -    'django.contrib.admin', - -    'django.contrib.auth', - -    'django.contrib.contenttypes', - -    'django.contrib.sessions', - -    'django.contrib.messages', - -    'django.contrib.staticfiles', - -    'rest_framework' - -] - -``` - -While Django REST Framework doesn't exclusively require class-based views (like Tornado) to handle incoming requests, it is the preferred method for writing views. Let's define one. - -Let's create a file called `views.py` in `django_todo`. Within `views.py`, we'll create our "Hello, world!" view. -``` -# in django_todo/views.py - -from rest_framework.response import JsonResponse - -from rest_framework.views import APIView - - - -class HelloWorld(APIView): - -    def get(self, request, format=None): - -        """Print 'Hello, world!' as the response body.""" - -        return JsonResponse("Hello, world!") - -``` - -Every Django REST Framework class-based view inherits either directly or indirectly from `APIView`. `APIView` handles a ton of stuff, but for our purposes it does these specific things: - - * Sets up the methods needed to direct traffic based on the HTTP method (e.g. GET, POST, PUT, DELETE) - * Populates the `request` object with all the data and attributes we'll need for parsing and processing any incoming request - * Takes the `Response` or `JsonResponse` that every dispatch method (i.e., methods named `get`, `post`, `put`, `delete`) returns and constructs a properly formatted HTTP response. - - - -Yay, we have a view! On its own it does nothing. We need to connect it to a route. - -If we hop into `django_todo/urls.py`, we reach our default URL configuration file. As mentioned earlier: If a route in our Django project is not included here, it doesn't exist. - -We add desired URLs by adding them to the given `urlpatterns` list. By default, we get a whole set of URLs for Django's built-in site administration backend. We'll delete that completely. - -We also get some very helpful doc strings that tell us exactly how to add routes to our Django project. We'll need to provide a call to `path()` with three parameters: - - * The desired route, as a string (without the leading slash) - * The view function (only ever a function!) that will handle that route - * The name of the route in our Django project - - - -Let's import our `HelloWorld` view and attach it to the home route `"/"`. We can also remove the path to the `admin` from `urlpatterns`, as we won't be using it. -``` -# django_todo/urls.py, after the big doc string - -from django.urls import path - -from django_todo.views import HelloWorld - - - -urlpatterns = [ - -    path('', HelloWorld.as_view(), name="hello"), - -] - -``` - -Well, this is different. The route we specified is just a blank string. Why does that work? Django assumes that every path we declare begins with a leading slash. We're just specifying routes to resources after the initial domain name. If a route isn't going to a specific resource and is instead just the home page, the route is just `""`, or effectively "no resource." - -The `HelloWorld` view is imported from that `views.py` file we just created. In order to do this import, we need to update `settings.py` to include `django_todo` in the list of `INSTALLED_APPS`. Yeah, it's a bit weird. Here's one way to think about it. - -`INSTALLED_APPS` refers to the list of directories or packages that Django sees as importable. It's Django's way of treating individual components of a project like installed packages without going through a `setup.py`. We want the `django_todo` directory to be treated like an importable package, so we include that directory in `INSTALLED_APPS`. Now, any module within that directory is also importable. So we get our view. - -The `path` function will ONLY take a view function as that second argument, not just a class-based view on its own. Luckily, all valid Django class-based views include this `.as_view()` method. Its job is to roll up all the goodness of the class-based view into a view function and return that view function. So, we never have to worry about making that translation. Instead, we only have to think about the business logic, letting Django and Django REST Framework handle the rest. - -Let's crack this open in the browser! - -Django comes packaged with its own local development server, accessible through `manage.py`. Let's navigate to the directory containing `manage.py` and type: -``` -(django-someHash) $ ./manage.py runserver - -Performing system checks... - - - -System check identified no issues (0 silenced). - -August 01, 2018 - 16:47:24 - -Django version 2.0.7, using settings 'django_todo.settings' - -Starting development server at http://127.0.0.1:8000/ - -Quit the server with CONTROL-C. - -``` - -When `runserver` is executed, Django does a check to make sure the project is (more or less) wired together correctly. It's not fool-proof, but it does catch some glaring issues. It also notifies us if our database is out of sync with our code. Undoubtedly ours is because we haven't committed any of our application's stuff to our database, but that's fine for now. Let's visit `http://127.0.0.1:8000` to see the output of the `HelloWorld` view. - -Huh. That's not the plaintext data we saw in Pyramid, Flask, and Tornado. When Django REST Framework is used, the HTTP response (when viewed in the browser) is this sort of rendered HTML, showing our actual JSON response in red. - -But don't fret! If we do a quick `curl` looking at `http://127.0.0.1:8000` in the command line, we don't get any of that fancy HTML. Just the content. -``` -# Note: try this in a different terminal window, outside of the virtual environment above - -$ curl http://127.0.0.1:8000 - -"Hello, world!" - -``` - -Bueno! - -Django REST Framework wants us to have a human-friendly interface when using the browser. This makes sense; if JSON is viewed in the browser, it's typically because a human wants to check that it looks right or get a sense of what the JSON response will look like as they design some consumer of an API. It's a lot like what you'd get from a service like [Postman][14]. - -Either way, we know our view is working! Woo! Let's recap what we've done: - - 1. Started the project with `django-admin startproject ` - 2. Updated the `django_todo/settings.py` to use environment variables for `DEBUG`, `SECRET_KEY`, and values in the `DATABASES` dict - 3. Installed `Django REST Framework` and added it to the list of `INSTALLED_APPS` - 4. Created `django_todo/views.py` to include our first view class to say Hello to the World - 5. Updated `django_todo/urls.py` with a path to our new home route - 6. Updated `INSTALLED_APPS` in `django_todo/settings.py` to include the `django_todo` package - - - -### Creating models - -Let's create our data models now. - -A Django project's entire infrastructure is built around data models. It's written so each data model can have its own little universe with its own views, its own set of URLs that concern its resources, and even its own tests (if we are so inclined). - -If we wanted to build a simple Django project, we could circumvent this by just writing our own `models.py` file in the `django_todo` directory and importing it into our views. However, we're trying to write a Django project the "right" way, so we should divide up our models as best we can into their own little packages The Django Way™. - -The Django Way involves creating what are called Django "apps." Django "apps" aren't separate applications per se; they don't have their own settings and whatnot (although they can). They can, however, have just about everything else one might think of being in a standalone application: - - * Set of self-contained URLs - * Set of self-contained HTML templates (if we want to serve HTML) - * One or more data models - * Set of self-contained views - * Set of self-contained tests - - - -They are made to be independent so they can be easily shared like standalone applications. In fact, Django REST Framework is an example of a Django app. It comes packaged with its own views and HTML templates for serving up our JSON. We just leverage that Django app to turn our project into a full-on RESTful API with less hassle. - -To create the Django app for our To-Do List items, we'll want to use the `startapp` command with `manage.py`. -``` -(django-someHash) $ ./manage.py startapp todo - -``` - -The `startapp` command will succeed silently. We can check that it did what it should've done by using `ls`. -``` -(django-someHash) $ ls - -Pipfile      Pipfile.lock django_todo  manage.py    todo - -``` - -Look at that: We've got a brand new `todo` directory. Let's look inside! -``` -(django-someHash) $ ls todo - -__init__.py admin.py    apps.py     migrations  models.py   tests.py    views.py - -``` - -Here are the files that `manage.py startapp` created: - - * `__init__.py` is empty; it exists so this directory can be seen as a valid import path for models, views, etc. - * `admin.py` is not quite empty; it's used for formatting this app's models in the Django admin, which we're not getting into in this article. - * `apps.py` … not much work to do here either; it helps with formatting models for the Django admin. - * `migrations` is a directory that'll contain snapshots of our data models; it's used for updating our database. This is one of the few frameworks that comes with database management built-in, and part of that is allowing us to update our database instead of having to tear it down and rebuild it to change the schema. - * `models.py` is where the data models live. - * `tests.py` is where tests would go—if we wrote any. - * `views.py` is for the views we write that pertain to the models in this app. They don't have to be written here. We could, for example, write all our views in `django_todo/views.py`. It's here, however, so it's easier to separate our concerns. This becomes far more relevant with sprawling applications that cover many conceptual spaces. - - - -What hasn't been created for us is a `urls.py` file for this app. We can make that ourselves. -``` -(django-someHash) $ touch todo/urls.py - -``` - -Before moving forward we should do ourselves a favor and add this new Django app to our list of `INSTALLED_APPS` in `django_todo/settings.py`. -``` -# in settings.py - -INSTALLED_APPS = [ - -    'django.contrib.admin', - -    'django.contrib.auth', - -    'django.contrib.contenttypes', - -    'django.contrib.sessions', - -    'django.contrib.messages', - -    'django.contrib.staticfiles', - -    'rest_framework', - -    'django_todo', - -    'todo' # <--- the line was added - -] - -``` - -Inspecting `todo/models.py` shows that `manage.py` already wrote a bit of code for us to get started. Diverging from how models were created in the Flask, Tornado, and Pyramid implementations, Django doesn't leverage a third party to manage database sessions or the construction of its object instances. It's all rolled into Django's `django.db.models` submodule. - -The way a model is built, however, is more or less the same. To create a model in Django, we'll need to build a `class` that inherits from `models.Model`. All the fields that will apply to instances of that model should appear as class attributes. Instead of importing columns and field types from SQLAlchemy like we have in the past, all of our fields will come directly from `django.db.models`. -``` -# todo/models.py - -from django.db import models - - - -class Task(models.Model): - -    """Tasks for the To Do list.""" - -    name = models.CharField(max_length=256) - -    note = models.TextField(blank=True, null=True) - -    creation_date = models.DateTimeField(auto_now_add=True) - -    due_date = models.DateTimeField(blank=True, null=True) - -    completed = models.BooleanField(default=False) - -``` - -While there are some definite differences between what Django needs and what SQLAlchemy-based systems need, the overall contents and structure are more or less the same. Let's point out the differences. - -We no longer need to declare a separate field for an auto-incremented ID number for our object instances. Django builds one for us unless we specify a different field as the primary key. - -Instead of instantiating `Column` objects that are passed datatype objects, we just directly reference the datatypes as the columns themselves. - -The `Unicode` field became either `models.CharField` or `models.TextField`. `CharField` is for small text fields of a specific maximum length, whereas `TextField` is for any amount of text. - -The `TextField` should be able to be blank, and we specify this in TWO ways. `blank=True` says that when an instance of this model is constructed, and the data attached to this field is being validated, it's OK for that data to be empty. This is different from `null=True`, which says when the table for this model class is constructed, the column corresponding to `note` will allow for blank or `NULL` entries. So, to sum that all up, `blank=True` controls how data gets added to model instances while `null=True` controls how the database table holding that data is constructed in the first place. - -The `DateTime` field grew some muscle and became able to do some work for us instead of us having to modify the `__init__` method for the class. For the `creation_date` field, we specify `auto_now_add=True`. What this means in a practical sense is that when a new model instance is created Django will automatically record the date and time of now as that field's value. That's handy! - -When neither `auto_now_add` nor its close cousin `auto_now` are set to `True`, `DateTimeField` will expect data like any other field. It'll need to be fed with a proper `datetime` object to be valid. The `due_date` column has `blank` and `null` both set to `True` so that an item on the To-Do List can just be an item to be done at some point in the future, with no defined date or time. - -`BooleanField` just ends up being a field that can take one of two values: `True` or `False`. Here, the default value is set to be `False`. - -#### Managing the database - -As mentioned earlier, Django has its own way of doing database management. Instead of having to write… really any code at all regarding our database, we leverage the `manage.py` script that Django provided on construction. It'll manage not just the construction of the tables for our database, but also any updates we wish to make to those tables without necessarily having to blow the whole thing away! - -Because we've constructed a new model, we need to make our database aware of it. First, we need to put into code the schema that corresponds to this model. The `makemigrations` command of `manage.py` will take a snapshot of the model class we built and all its fields. It'll take that information and package it into a Python script that'll live in this particular Django app's `migrations` directory. There will never be a reason to run this migration script directly. It'll exist solely so that Django can use it as a basis to update our database table or to inherit information when we update our model class. -``` -(django-someHash) $ ./manage.py makemigrations - -Migrations for 'todo': - -  todo/migrations/0001_initial.py - -    - Create model Task - -``` - -This will look at every app listed in `INSTALLED_APPS` and check for models that exist in those apps. It'll then check the corresponding `migrations` directory for migration files and compare them to the models in each of those `INSTALLED_APPS` apps. If a model has been upgraded beyond what the latest migration says should exist, a new migration file will be created that inherits from the most recent one. It'll be automatically named and also be given a message that says what changed since the last migration. - -If it's been a while since you last worked on your Django project and can't remember if your models were in sync with your migrations, you have no need to fear. `makemigrations` is an idempotent operation; your `migrations` directory will have only one copy of the current model configuration whether you run `makemigrations` once or 20 times. Even better than that, when we run `./manage.py runserver`, Django will detect that our models are out of sync with our migrations, and it'll just flat out tell us in colored text so we can make the appropriate choice. - -This next point is something that trips everybody up at least once: Creating a migration file does not immediately affect our database. When we ran `makemigrations`, we prepared our Django project to define how a given table should be created and end up looking. It's still on us to apply those changes to our database. That's what the `migrate` command is for. -``` -(django-someHash) $ ./manage.py migrate - -Operations to perform: - -  Apply all migrations: admin, auth, contenttypes, sessions, todo - -Running 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 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 sessions.0001_initial... OK - -  Applying todo.0001_initial... OK - -``` - -When we apply our migrations, Django first checks to see if the other `INSTALLED_APPS` have migrations to be applied. It checks them in roughly the order they're listed. We want our app to be listed last, because we want to make sure that, in case our model depends on any of Django's built-in models, the database updates we make don't suffer from dependency problems. - -We have another model to build: the User model. However, the game has changed a bit since we're using Django. So many applications require some sort of User model that Django's `django.contrib.auth` package built its own for us to use. If it weren't for the authentication token we require for our users, we could just move on and use it instead of reinventing the wheel. - -However, we need that token. There are a couple of ways we can handle this. - - * Inherit from Django's `User` object, making our own object that extends it by adding a `token` field - * Create a new object that exists in a one-to-one relationship with Django's `User` object, whose only purpose is to hold a token - - - -I'm in the habit of building object relationships, so let's go with the second option. Let's call it an `Owner` as it basically has a similar connotation as a `User`, which is what we want. - -Out of sheer laziness, we could just include this new `Owner` object in `todo/models.py`, but let's refrain from that. `Owner` doesn't explicitly have to do with the creation or maintenance of items on the task list. Conceptually, the `Owner` is simply the owner of the task. There may even come a time where we want to expand this `Owner` to include other data that has absolutely nothing to do with tasks. - -Just to be safe, let's make an `owner` app whose job is to house and handle this `Owner` object. -``` -(django-someHash) $ ./manage.py startapp owner - -``` - -Don't forget to add it to the list of `INSTALLED_APPS` in `settings.py`. -``` -INSTALLED_APPS = [ - -    'django.contrib.admin', - -    'django.contrib.auth', - -    'django.contrib.contenttypes', - -    'django.contrib.sessions', - -    'django.contrib.messages', - -    'django.contrib.staticfiles', - -    'rest_framework', - -    'django_todo', - -    'todo', - -    'owner' - -] - -``` - -If we look at the root of our Django project, we now have two Django apps: -``` -(django-someHash) $ ls - -Pipfile      Pipfile.lock django_todo  manage.py    owner        todo - -``` - -In `owner/models.py`, let's build this `Owner` model. As mentioned earlier, it'll have a one-to-one relationship with Django's built-in `User` object. We can enforce this relationship with Django's `models.OneToOneField` -``` -# owner/models.py - -from django.db import models - -from django.contrib.auth.models import User - -import secrets - - - -class Owner(models.Model): - -    """The object that owns tasks.""" - -    user = models.OneToOneField(User, on_delete=models.CASCADE) - -    token = models.CharField(max_length=256) - - - -    def __init__(self, *args, **kwargs): - -        """On construction, set token.""" - -        self.token = secrets.token_urlsafe(64) - -        super().__init__(*args, **kwargs) - -``` - -This says the `Owner` object is linked to the `User` object, with one `owner` instance per `user` instance. `on_delete=models.CASCADE` dictates that if the corresponding `User` gets deleted, the `Owner` instance it's linked to will also get deleted. Let's run `makemigrations` and `migrate` to bake this new model into our database. -``` -(django-someHash) $ ./manage.py makemigrations - -Migrations for 'owner': - -  owner/migrations/0001_initial.py - -    - Create model Owner - -(django-someHash) $ ./manage.py migrate - -Operations to perform: - -  Apply all migrations: admin, auth, contenttypes, owner, sessions, todo - -Running migrations: - -  Applying owner.0001_initial... OK - -``` - -Now our `Owner` needs to own some `Task` objects. It'll be very similar to the `OneToOneField` seen above, except that we'll stick a `ForeignKey` field on the `Task` object pointing to an `Owner`. -``` -# todo/models.py - -from django.db import models - -from owner.models import Owner - - - -class Task(models.Model): - -    """Tasks for the To Do list.""" - -    name = models.CharField(max_length=256) - -    note = models.TextField(blank=True, null=True) - -    creation_date = models.DateTimeField(auto_now_add=True) - -    due_date = models.DateTimeField(blank=True, null=True) - -    completed = models.BooleanField(default=False) - -    owner = models.ForeignKey(Owner, on_delete=models.CASCADE) - -``` - -Every To-Do List task has exactly one owner who can own multiple tasks. When that owner is deleted, any task they own goes with them. - -Let's now run `makemigrations` to take a new snapshot of our data model setup, then `migrate` to apply those changes to our database. -``` -(django-someHash) django $ ./manage.py makemigrations - -You are trying to add a non-nullable field 'owner' to task without a default; we can't do that (the database needs something to populate existing rows). - -Please select a fix: - - 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) - - 2) Quit, and let me add a default in models.py - -``` - -Oh no! We have a problem! What happened? Well, when we created the `Owner` object and added it as a `ForeignKey` to `Task`, we basically required that every `Task` requires an `Owner`. However, the first migration we made for the `Task` object didn't include that requirement. So, even though there's no data in our database's table, Django is doing a pre-check on our migrations to make sure they're compatible and this new migration we're proposing is not. - -There are a few ways to deal with this sort of problem: - - 1. Blow away the current migration and build a new one that includes the current model configuration - 2. Add a default value to the `owner` field on the `Task` object - 3. Allow tasks to have `NULL` values for the `owner` field. - - - -Option 2 wouldn't make much sense here; we'd be proposing that any `Task` that was created would, by default, be linked to some default owner despite none necessarily existing. - -Option 1 would require us to destroy and rebuild our migrations. We should leave those alone. - -Let's go with option 3. In this circumstance, it won't be the end of the world if we allow the `Task` table to have null values for the owners; any tasks created from this point forward will necessarily have an owner. If you're in a situation where that isn't an acceptable schema for your database table, blow away your migrations, drop the table, and rebuild the migrations. -``` -# todo/models.py - -from django.db import models - -from owner.models import Owner - - - -class Task(models.Model): - -    """Tasks for the To Do list.""" - -    name = models.CharField(max_length=256) - -    note = models.TextField(blank=True, null=True) - -    creation_date = models.DateTimeField(auto_now_add=True) - -    due_date = models.DateTimeField(blank=True, null=True) - -    completed = models.BooleanField(default=False) - -    owner = models.ForeignKey(Owner, on_delete=models.CASCADE, null=True) - -(django-someHash) $ ./manage.py makemigrations - -Migrations for 'todo': - -  todo/migrations/0002_task_owner.py - -    - Add field owner to task - -(django-someHash) $ ./manage.py migrate - -Operations to perform: - -  Apply all migrations: admin, auth, contenttypes, owner, sessions, todo - -Running migrations: - -  Applying todo.0002_task_owner... OK - -``` - -Woo! We have our models! Welcome to the Django way of declaring objects. - -For good measure, let's ensure that whenever a `User` is made, it's automatically linked with a new `Owner` object. We can do this using Django's `signals` system. Basically, we say exactly what we intend: "When we get the signal that a new `User` has been constructed, construct a new `Owner` and set that new `User` as that `Owner`'s `user` field." In practice that looks like: -``` -# owner/models.py - -from django.contrib.auth.models import User - -from django.db import models - -from django.db.models.signals import post_save - -from django.dispatch import receiver - - - -import secrets - - - - - -class Owner(models.Model): - -    """The object that owns tasks.""" - -    user = models.OneToOneField(User, on_delete=models.CASCADE) - -    token = models.CharField(max_length=256) - - - -    def __init__(self, *args, **kwargs): - -        """On construction, set token.""" - -        self.token = secrets.token_urlsafe(64) - -        super().__init__(*args, **kwargs) - - - - - -@receiver(post_save, sender=User) - -def link_user_to_owner(sender, **kwargs): - -    """If a new User is saved, create a corresponding Owner.""" - -    if kwargs['created']: - -        owner = Owner(user=kwargs['instance']) - -        owner.save() - -``` - -We set up a function that listens for signals to be sent from the `User` object built into Django. It's waiting for just after a `User` object has been saved. This can come from either a new `User` or an update to an existing `User`; we discern between the two scenarios within the listening function. - -If the thing sending the signal was a newly created instance, `kwargs['created']` will have the value of `True`. We only want to do something if this is `True`. If it's a new instance, we create a new `Owner`, setting its `user` field to be the new `User` instance that was created. After that, we `save()` the new `Owner`. This will commit our change to the database if all is well. It'll fail if the data doesn't validate against the fields we declared. - -Now let's talk about how we're going to access the data. - -### Accessing model data - -In the Flask, Pyramid, and Tornado frameworks, we accessed model data by running queries against some database session. Maybe it was attached to a `request` object, maybe it was a standalone `session` object. Regardless, we had to establish a live connection to the database and query on that connection. - -This isn't the way Django works. Django, by default, doesn't leverage any third-party object-relational mapping (ORM) to converse with the database. Instead, Django allows the model classes to maintain their own conversations with the database. - -Every model class that inherits from `django.db.models.Model` will have attached to it an `objects` object. This will take the place of the `session` or `dbsession` we've become so familiar with. Let's open the special shell that Django gives us and investigate how this `objects` object works. -``` -(django-someHash) $ ./manage.py shell - -Python 3.7.0 (default, Jun 29 2018, 20:13:13) - -[Clang 9.1.0 (clang-902.0.39.2)] on darwin - -Type "help", "copyright", "credits" or "license" for more information. - -(InteractiveConsole) - ->>> - -``` - -The Django shell is different from a normal Python shell in that it's aware of the Django project we've been building and can do easy imports of our models, views, settings, etc. without having to worry about installing a package. We can access our models with a simple `import`. -``` ->>> from owner.models import Owner - ->>> Owner - - - -``` - -Currently, we have no `Owner` instances. We can tell by querying for them with `Owner.objects.all()`. -``` ->>> Owner.objects.all() - - - -``` - -Anytime we run a query method on the `.objects` object, we'll get a `QuerySet` back. For our purposes, it's effectively a `list`, and this `list` is showing us that it's empty. Let's make an `Owner` by making a `User`. -``` ->>> from django.contrib.auth.models import User - ->>> new_user = User(username='kenyattamurphy', email='kenyatta.murphy@gmail.com') - ->>> new_user.set_password('wakandaforever') - ->>> new_user.save() - -``` - -If we query for all of our `Owner`s now, we should find Kenyatta. -``` ->>> Owner.objects.all() - -]> - -``` - -Yay! We've got data! - -### Serializing models - -We'll be passing data back and forth beyond just "Hello World." As such, we'll want to see some sort of JSON-ified output that represents that data well. Taking that object's data and transforming it into a JSON object for submission across HTTP is a version of data serialization. In serializing data, we're taking the data we currently have and reformatting it to fit some standard, more-easily-digestible form. - -If I were doing this with Flask, Pyramid, and Tornado, I'd create a new method on each model to give the user direct access to call `to_json()`. The only job of `to_json()` would be to return a JSON-serializable (i.e. numbers, strings, lists, dicts) dictionary with whatever fields I want to be displayed for the object in question. - -It'd probably look something like this for the `Task` object: -``` -class Task(Base): - -    ...all the fields... - - - -    def to_json(self): - -        """Convert task attributes to a JSON-serializable dict.""" - -        return { - -            'id': self.id, - -            'name': self.name, - -            'note': self.note, - -            'creation_date': self.creation_date.strftime('%m/%d/%Y %H:%M:%S'), - -            'due_date': self.due_date.strftime('%m/%d/%Y %H:%M:%S'), - -            'completed': self.completed, - -            'user': self.user_id - -        } - -``` - -It's not fancy, but it does the job. - -Django REST Framework, however, provides us with an object that'll not only do that for us but also validate inputs when we want to create new object instances or update existing ones. It's called the [ModelSerializer][15]. - -Django REST Framework's `ModelSerializer` is effectively documentation for our models. They don't have lives of their own if there are no models attached (for that there's the [Serializer][16] class). Their main job is to accurately represent our model and make the conversion to JSON thoughtless when our model's data needs to be serialized and sent over a wire. - -Django REST Framework's `ModelSerializer` works best for simple objects. As an example, imagine that we didn't have that `ForeignKey` on the `Task` object. We could create a serializer for our `Task` that would convert its field values to JSON as necessary with the following declaration: -``` -# todo/serializers.py - -from rest_framework import serializers - -from todo.models import Task - - - -class TaskSerializer(serializers.ModelSerializer): - -    """Serializer for the Task model.""" - - - -    class Meta: - -        model = Task - -        fields = ('id', 'name', 'note', 'creation_date', 'due_date', 'completed') - -``` - -Inside our new `TaskSerializer`, we create a `Meta` class. `Meta`'s job here is just to hold information (or metadata) about the thing we're attempting to serialize. Then, we note the specific fields that we want to show. If we wanted to show all the fields, we could just shortcut the process and use `'__all__'`. We could, alternatively, use the `exclude` keyword instead of `fields` to tell Django REST Framework that we want every field except for a select few. We can have as many serializers as we like, so maybe we want one for a small subset of fields and one for all the fields? Go wild here. - -In our case, there is a relation between each `Task` and its owner `Owner` that must be reflected here. As such, we need to borrow the `serializers.PrimaryKeyRelatedField` object to specify that each `Task` will have an `Owner` and that relationship is one-to-one. Its owner will be found from the set of all owners that exists. We get that set by doing a query for those owners and returning the results we want to be associated with this serializer: `Owner.objects.all()`. We also need to include `owner` in the list of fields, as we always need an `Owner` associated with a `Task` -``` -# todo/serializers.py - -from rest_framework import serializers - -from todo.models import Task - -from owner.models import Owner - - - -class TaskSerializer(serializers.ModelSerializer): - -    """Serializer for the Task model.""" - -    owner = serializers.PrimaryKeyRelatedField(queryset=Owner.objects.all()) - - - -    class Meta: - -        model = Task - -        fields = ('id', 'name', 'note', 'creation_date', 'due_date', 'completed', 'owner') - -``` - -Now that this serializer is built, we can use it for all the CRUD operations we'd like to do for our objects: - - * If we want to `GET` a JSONified version of a specific `Task`, we can do `TaskSerializer(some_task).data` - * If we want to accept a `POST` with the appropriate data to create a new `Task`, we can use `TaskSerializer(data=new_data).save()` - * If we want to update some existing data with a `PUT`, we can say `TaskSerializer(existing_task, data=data).save()` - - - -We're not including `delete` because we don't really need to do anything with information for a `delete` operation. If you have access to an object you want to delete, just say `object_instance.delete()`. - -Here is an example of what some serialized data might look like: -``` ->>> from todo.models import Task - ->>> from todo.serializers import TaskSerializer - ->>> from owner.models import Owner - ->>> from django.contrib.auth.models import User - ->>> new_user = User(username='kenyatta', email='kenyatta@gmail.com') - ->>> new_user.save_password('wakandaforever') - ->>> new_user.save() # creating the User that builds the Owner - ->>> kenyatta = Owner.objects.first() # grabbing the Owner that is kenyatta - ->>> new_task = Task(name="Buy roast beef for the Sunday potluck", owner=kenyatta) - ->>> new_task.save() - ->>> TaskSerializer(new_task).data - -{'id': 1, 'name': 'Go to the supermarket', 'note': None, 'creation_date': '2018-07-31T06:00:25.165013Z', 'due_date': None, 'completed': False, 'owner': 1} - -``` - -There's a lot more you can do with the `ModelSerializer` objects, and I suggest checking [the docs][17] for those greater capabilities. Otherwise, this is as much as we need. It's time to dig into some views. - -### Views for reals - -We've built the models and the serializers, and now we need to set up the views and URLs for our application. After all, we can't do anything with an application that has no views. We've already seen an example with the `HelloWorld` view above. However, that's always a contrived, proof-of-concept example and doesn't really show what can be done with Django REST Framework's views. Let's clear out the `HelloWorld` view and URL so we can start fresh with our views. - -The first view we'll build is the `InfoView`. As in the previous frameworks, we just want to package and send out a dictionary of our proposed routes. The view itself can live in `django_todo.views` since it doesn't pertain to a specific model (and thus doesn't conceptually belong in a specific app). -``` -# django_todo/views.py - -from rest_framework.response import JsonResponse - -from rest_framework.views import APIView - - - -class InfoView(APIView): - -    """List of routes for this API.""" - -    def get(self, request): - -        output = { - -            'info': 'GET /api/v1', - -            'register': 'POST /api/v1/accounts', - -            'single profile detail': 'GET /api/v1/accounts/', - -            'edit profile': 'PUT /api/v1/accounts/', - -            'delete profile': 'DELETE /api/v1/accounts/', - -            'login': 'POST /api/v1/accounts/login', - -            'logout': 'GET /api/v1/accounts/logout', - -            "user's tasks": 'GET /api/v1/accounts//tasks', - -            "create task": 'POST /api/v1/accounts//tasks', - -            "task detail": 'GET /api/v1/accounts//tasks/', - -            "task update": 'PUT /api/v1/accounts//tasks/', - -            "delete task": 'DELETE /api/v1/accounts//tasks/' - -        } - -        return JsonResponse(output) - -``` - -This is pretty much identical to what we had in Tornado. Let's hook it up to an appropriate route and be on our way. For good measure, we'll also remove the `admin/` route, as we won't be using the Django administrative backend here. -``` -# in django_todo/urls.py - -from django_todo.views import InfoView - -from django.urls import path - - - -urlpatterns = [ - -    path('api/v1', InfoView.as_view(), name="info"), - -] - -``` - -#### Connecting models to views - -Let's figure out the next URL, which will be the endpoint for either creating a new `Task` or listing a user's existing tasks. This should exist in a `urls.py` in the `todo` app since this has to deal specifically with `Task` objects instead of being a part of the whole project. -``` -# in todo/urls.py - -from django.urls import path - -from todo.views import TaskListView - - - -urlpatterns = [ - -    path('', TaskListView.as_view(), name="list_tasks") - -] - -``` - -What's the deal with this route? We didn't specify a particular user or much of a path at all. Since there would be a couple of routes requiring the base path `/api/v1/accounts//tasks`, why write it again and again when we can just write it once? - -Django allows us to take a whole suite of URLs and import them into the base `django_todo/urls.py` file. We can then give every one of those imported URLs the same base path, only worrying about the variable parts when, you know, they vary. -``` -# in django_todo/urls.py - -from django.urls import include, path - -from django_todo.views import InfoView - - - -urlpatterns = [ - -    path('api/v1', InfoView.as_view(), name="info"), - -    path('api/v1/accounts//tasks', include('todo.urls')) - -] - -``` - -And now every URL coming from `todo/urls.py` will be prefixed with the path `api/v1/accounts//tasks`. - -Let's build out the view in `todo/views.py` -``` -# todo/views.py - -from django.shortcuts import get_object_or_404 - -from rest_framework.response import JsonResponse - -from rest_framework.views import APIView - - - -from owner.models import Owner - -from todo.models import Task - -from todo.serializers import TaskSerializer - - - - - -class TaskListView(APIView): - -    def get(self, request, username, format=None): - -        """Get all of the tasks for a given user.""" - -        owner = get_object_or_404(Owner, user__username=username) - -        tasks = Task.objects.filter(owner=owner).all() - -        serialized = TaskSerializer(tasks, many=True) - -        return JsonResponse({ - -            'username': username, - -            'tasks': serialized.data - -        }) - -``` - -There's a lot going on here in a little bit of code, so let's walk through it. - -We start out with the same inheritance of the `APIView` that we've been using, laying the groundwork for what will be our view. We override the same `get` method we've overridden before, adding a parameter that allows our view to receive the `username` from the incoming request. - -Our `get` method will then use that `username` to grab the `Owner` associated with that user. This `get_object_or_404` function allows us to do just that, with a little something special added for ease of use. - -It would make sense that there's no point in looking for tasks if the specified user can't be found. In fact, we'd want to return a 404 error. `get_object_or_404` gets a single object based on whatever criteria we pass in and either returns that object or raises an [Http404 exception][18]. We can set that criteria based on attributes of the object. The `Owner` objects are all attached to a `User` through their `user` attribute. We don't have a `User` object to search with, though. We only have a `username`. So, we say to `get_object_or_404` "when you look for an `Owner`, check to see that the `User` attached to it has the `username` that I want" by specifying `user__username`. That's TWO underscores. When filtering through a QuerySet, the two underscores mean "attribute of this nested object." Those attributes can be as deeply nested as needed. - -We now have the `Owner` corresponding to the given username. We use that `Owner` to filter through all the tasks, only retrieving the ones it owns with `Task.objects.filter`. We could've used the same nested-attribute pattern that we did with `get_object_or_404` to drill into the `User` connected to the `Owner` connected to the `Tasks` (`tasks = Task.objects.filter(owner__user__username=username).all()`) but there's no need to get that wild with it. - -`Task.objects.filter(owner=owner).all()` will provide us with a `QuerySet` of all the `Task` objects that match our query. Great. The `TaskSerializer` will then take that `QuerySet` and all its data, along with the flag of `many=True` to notify it as being a collection of items instead of just one item, and return a serialized set of results. Effectively a list of dictionaries. Finally, we provide the outgoing response with the JSON-serialized data and the username used for the query. - -#### Handling the POST request - -The `post` method will look somewhat different from what we've seen before. -``` -# still in todo/views.py - -# ...other imports... - -from rest_framework.parsers import JSONParser - -from datetime import datetime - - - -class TaskListView(APIView): - -    def get(self, request, username, format=None): - -        ... - - - -    def post(self, request, username, format=None): - -        """Create a new Task.""" - -        owner = get_object_or_404(Owner, user__username=username) - -        data = JSONParser().parse(request) - -        data['owner'] = owner.id - -        if data['due_date']: - -            data['due_date'] = datetime.strptime(data['due_date'], '%d/%m/%Y %H:%M:%S') - - - -        new_task = TaskSerializer(data=data) - -        if new_task.is_valid(): - -            new_task.save() - -            return JsonResponse({'msg': 'posted'}, status=201) - - - -        return JsonResponse(new_task.errors, status=400) - -``` - -When we receive data from the client, we parse it into a dictionary using `JSONParser().parse(request)`. We add the owner to the data and format the `due_date` for the task if one exists. - -Our `TaskSerializer` does the heavy lifting. It first takes in the incoming data and translates it into the fields we specified on the model. It then validates that data to make sure it fits the specified fields. If the data being attached to the new `Task` is valid, it constructs a new `Task` object with that data and commits it to the database. We then send back an appropriate "Yay! We made a new thing!" response. If not, we collect the errors that `TaskSerializer` generated and send those back to the client with a `400 Bad Request` status code. - -If we were to build out the `put` view for updating a `Task`, it would look very similar to this. The main difference would be that when we instantiate the `TaskSerializer`, instead of just passing in the new data, we'd pass in the old object and the new data for that object like `TaskSerializer(existing_task, data=data)`. We'd still do the validity check and send back the responses we want to send back. - -### Wrapping up - -Django as a framework is highly customizable, and everyone has their own way of stitching together a Django project. The way I've written it out here isn't necessarily the exact way that a Django project needs to be set up; it's just a) what I'm familiar with, and b) what leverages Django's management system. Django projects grow in complexity as you separate concepts into their own little silos. You do that so it's easier for multiple people to contribute to the overall project without stepping on each other's toes. - -The vast map of files that is a Django project, however, doesn't make it more performant or naturally predisposed to a microservice architecture. On the contrary, it can very easily become a confusing monolith. That may still be useful for your project. It may also make it harder for your project to be manageable, especially as it grows. - -Consider your options carefully and use the right tool for the right job. For a simple project like this, Django likely isn't the right tool. - -Django is meant to handle multiple sets of models that cover a variety of different project areas that may share some common ground. This project is a small, two-model project with a handful of routes. If we were to build this out more, we'd only have seven routes and still the same two models. It's hardly enough to justify a full Django project. - -It would be a great option if we expected this project to expand. This is not one of those projects. This is choosing a flamethrower to light a candle. It's absolute overkill. - -Still, a web framework is a web framework, regardless of which one you use for your project. It can take in requests and respond as well as any other, so you do as you wish. Just be aware of what overhead comes with your choice of framework. - -That's it! We've reached the end of this series! I hope it has been an enlightening adventure and will help you make more than just the most-familiar choice when you're thinking about how to build out your next project. Make sure to read the documentation for each framework to expand on anything covered in this series (as it's not even the least bit comprehensive). There's a wide world of stuff to get into for each. Happy coding! - --------------------------------------------------------------------------------- - -via: https://opensource.com/article/18/8/django-framework - -作者:[Nicholas Hunt-Walker][a] -选题:[lujun9972](https://github.com/lujun9972) -译者:[译者ID](https://github.com/译者ID) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]:https://opensource.com/users/nhuntwalker -[1]:https://opensource.com/article/18/5/pyramid-framework -[2]:https://opensource.com/article/18/4/flask -[3]:https://opensource.com/article/18/6/tornado-framework -[4]:https://www.djangoproject.com -[5]:https://djangopackages.org/ -[6]:http://www.django-rest-framework.org/ -[7]:http://gunicorn.org/ -[8]:https://docs.pylonsproject.org/projects/waitress/en/latest/ -[9]:https://uwsgi-docs.readthedocs.io/en/latest/ -[10]:https://docs.djangoproject.com/en/2.0/ref/settings/#databases -[11]:https://pypi.org/project/dj-database-url/ -[12]:http://yellerapp.com/posts/2015-01-12-the-worst-server-setup-you-can-make.html -[13]:https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-DATABASE-ENGINE -[14]:https://www.getpostman.com/ -[15]:http://www.django-rest-framework.org/api-guide/serializers/#modelserializer -[16]:http://www.django-rest-framework.org/api-guide/serializers/ -[17]:http://www.django-rest-framework.org/api-guide/serializers/#serializers -[18]:https://docs.djangoproject.com/en/2.0/topics/http/views/#the-http404-exception From 2e72e1d1d171f3dfbe0661aba0cbf0620e9c7b20 Mon Sep 17 00:00:00 2001 From: MjSeven <33125422+MjSeven@users.noreply.github.com> Date: Sun, 30 Sep 2018 10:26:45 +0800 Subject: [PATCH 11/11] Create 20180816 An introduction to the Django Python web app framework.md --- ... to the Django Python web app framework.md | 1219 +++++++++++++++++ 1 file changed, 1219 insertions(+) create mode 100644 translated/tech/20180816 An introduction to the Django Python web app framework.md diff --git a/translated/tech/20180816 An introduction to the Django Python web app framework.md b/translated/tech/20180816 An introduction to the Django Python web app framework.md new file mode 100644 index 0000000000..dc9fd20449 --- /dev/null +++ b/translated/tech/20180816 An introduction to the Django Python web app framework.md @@ -0,0 +1,1219 @@ +Python Web 应用程序 Django 框架简介 +===== + +![](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/web-spider-frame-framework.png?itok=Rl2AG2Dc) + +在本系列(由四部分组成)的前三篇文章中,我们讨论了 [Pyramid][1], [Flask][2] 和 [Tornado][3] 这 3 个 Web 框架。我们已经构建了三次相同的应用程序,最终我们遇到了 [Django][4]。总的来说,Django 是目前 Python 开发人员使用的主要 Web 框架,并且不难看出原因。它擅长隐藏大量的配置逻辑,让你专注于能过够快速构建大型应用程序。 + +也就是说,当涉及到小型项目时,比如我们的待办事项列表应用程序,Django 可能有点像用消防水管来进行水枪大战。让我们来看看它们是如何结合在一起的。 + +### 关于 Django + +Django 将自己定位为“一个高级的 Python Web 框架,它鼓励快速开发和干净,实用的设计。它由经验丰富的开发人员构建,解决了 Web 开发的很多麻烦,因此你可以专注于编写应用程序而无需重新发明轮子”。它真的做到了!这个庞大的 Web 框架附带了非常多的工具,通常在开发过程中,如何将所有内容组合在一起协同工作可能是个谜。 + +除了框架本身很大,Django 社区也是非常庞大的。事实上,它非常庞大和活跃,以至于有[一个网站][5]致力于为人们收集第三方包,这些第三方包可集成进 Django 来做一大堆事情。包括从身份验证和授权到完全基于 Django 的内容管理系统,电子商务附加组件以及与 Stripe(译注:美版“支付宝”)集成的所有内容。关于不要重新发明轮子:如果你想用 Django 完成一些事情,有人可能已经做过了,你只需将它集成进你的项目就行。 + +为此,我们希望使用 Django 构建 REST API,因此我们将利用流行的 [Django REST framework][6]。它的工作是将 Django 框架(Django 使用自己的模板引擎构建 HTML 页面)转换为专门用于有效地处理 REST 交互的系统。让我们开始吧。 + +### Django 启动和配置 + +``` +$ mkdir django_todo + +$ cd django_todo + +$ pipenv install --python 3.6 + +$ pipenv shell + +(django-someHash) $ pipenv install django djangorestframework + +``` + +作为参考,我们使用的是 `django-2.0.7` 和 `djangorestframework-3.8.2`。 + +与 Flask, Tornado 和 Pyramid 不同,我们不需要自己编写 `setup.py` 文件,我们并不是在做一个可安装的 Python 发行版。像很多事情一样,Django 以自己的方式处理这个问题。我们仍然需要一个 `requirements.txt` 文件来跟踪我们在其它地方部署的所有必要安装。但是,就 Django 项目中的目标模块而言,Django 会让我们列出我们想要访问的子目录,然后允许我们从这些目录中导入,就像它们是已安装的包一样。 + +首先,我们必须创建一个 Django 项目。 + +当我们安装了 Django 后,我们还安装了命令行脚本 `django-admin`。它的工作是管理所有与 Django 相关的命令,这些命令有助于我们将项目整合在一起,并在我们继续开发的过程中对其进行维护。`django-admin` 并不是让我们从头开始构建整个 Django 生态系统,而是让我们开始使用标准 Django 项目所需的所有必要文件(以及更多)。 + +调用 `django-admin` 的 `start-project` 命令的语法是 `django-admin startproject <项目名称> <存放目录>`。我们希望文件存于当前的工作目录中,所以: +``` +(django-someHash) $ django-admin startproject django_todo . + +``` + +输入 `ls` 将显示一个新文件和一个新目录。 +``` +(django-someHash) $ ls + +manage.py   django_todo + +``` + +`manage.py` 是一个可执行命令行 Python 文件,它最终成为 `django-admin` 的装饰器(to 校正:这里装饰器只是一个语义上的称呼,与 Python 的装饰器不同)。因此,它的工作与 `django-admin` 是一样的:帮助我们管理项目。因此得名 `manage.py`。 + +它在 `django_todo` 目录里创建了一个新目录 `django_todo`,其代表了我们项目的配置根目录。现在让我们深入研究一下。 + +### 配置 Django + +可以将 `django_todo` 目录称为“配置根”,我们的意思是这个目录包含了通常配置 Django 项目所需的文件。几乎所有这个目录之外的内容都只关注与项目模型,视图,路由等相关的“业务逻辑”。所有连接项目的点都将在这里出现。 + +在 `django_todo` 目录中调用 `ls` 会显示以下四个文件: +``` +(django-someHash) $ cd django_todo + +(django-someHash) $ ls + +__init__.py settings.py urls.py     wsgi.py + +``` + + * `__init__.py` 文件为空,之所以存在是为了将此目录转换为可导入的 Python 包。 + + * `settings.py` 是设置大多数配置项的地方。例如项目是否处于 DEBUG 模式,正在使用哪些数据库,Django 应该定位文件的位置等等。它是配置根目录的“主要配置”部分,我们将在一会深入研究。 + + * `urls.py` 顾名思义就是设置 URL 的地方。虽然我们不必在此文件中显式写入项目的每个 URL,但我们需要让此文件知道在其他任何地方已声明的 URL。如果此文件未指向其它 URL,则那些 URL 就不存在。 + + * `wsgi.py` 用于在生产环境中提供应用程序。就像 Pyramid, Tornado 和 Flask 暴露了一些 “app” 对象一样,它们用来提供配置好的应用程序,Django 也必须暴露一个,就是在这里完成的。它可以和 [Gunicorn][7], [Waitress][8] 或者 [uWSGI][9] 一起配合来提供服务。 + +#### 设置 settings + +看一看 `settings.py`,它里面有大量的配置项,那些只是默认值!这甚至不包括数据库,静态文件,媒体文件,任何集成的钩子,或者可以配置 Django 项目的任何其它几种方式。让我们从上到下看看有什么: + + * `BASE_DIR` 设置目录的绝对路径,或者是 `manage.py` 所在的目录。这对于定位文件非常有用。 + + * `SECRET_KEY` 是用于 Django 项目中加密签名的密钥。在实际中,它用于会话,cookie,CSRF 保护和身份验证令牌等。最好在第一次提交之前,尽快应该更改 `SECRET_KEY` 的值并将其放置到环境变量中。 + + * `DEBUG` 告诉 Django 是以开发模式还是生产模式运行项目。这是一个非常关键的区别。 + + * 在开发模式下,当弹出一个错误时,Django 将显示导致错误的完整堆栈跟踪,以及运行项目所涉及的所有设置和配置。如果在生产环境中将 `DEBUG` 设置为 `True`,这可能是一个巨大的安全问题。 + + * 在生产模式下,当出现问题时,Django 会显示一个简单的错误页面,即除错误代码外不提供任何信息。 + + * 保护我们项目的一个简单方法是将 `DEBUG` 设置为环境变量,如 `bool(os.environ.get('DEBUG', ''))`。 + * `ALLOWED_HOSTS` 是应用程序提供服务的主机名的列表。在开发模式中,这可能是空的,但是在生产中,如果为项目提供服务的主机不在 ALLOWED_HOSTS 列表中,Django 项目将无法运行。这是设置为环境变量的另一种情况。 + + * `INSTALLED_APPS` 是我们的 Django 项目可以访问的 Django "apps" 列表(将它们视为子目录,稍后会详细介绍)。默认情况下,它将提供: + * 内置的 Django admin 网站 + * Django 的内置认证系统 + * Django 的数据模型通用管理器 + * 会话管理 + * Cookie 和基于会话的消息传递 + * 站点固有的静态文件的用法,比如 `css` 文件,`js` 文件,任何属于我们网站设计的图片等。 + + * `MIDDLEWARE` 顾名思义:帮助 Django 项目运行的中间件。其中很大一部分用于处理各种类型的安全,尽管我们可以根据需要添加其它中间件。 + + * `ROOT_URLCONF` 设置基本 URL 配置文件的导入路径。还记得我们之前见过的那个 `urls.py` 吗?默认情况下,Django 指向该文件以此来收集所有的 URL。如果我们想让 Django 在其它地方寻找,我们将在这里设置 URL 位置的导入路径。 + + * `TEMPLATES` 是 Django 用于我们网站前端的模板引擎列表,假如我们依靠 Django 来构建我们的 HTML。我们在这里不需要,那就无关紧要了。 + + * `WSGI_APPLICATION` 设置我们的 WSGI 应用程序的导入路径 - 在生产环境下使用的东西。默认情况下,它指向 `wsgi.py` 中的 `application` 对象。这很少(如果有的话)需要修改。 + + * `DATABASES` 设置 Django 项目将访问那些数据库。必须设置 `default` 数据库。我们可以通过名称设置别的数据库,只要我们提供 `HOST`, `USER`, `PASSWORD`, `PORT`, 数据库名称 `NAME` 和合适的 `ENGINE`。可以想象,这些都是敏感的信息,因此最好将它们隐藏在环境变量中。[查看 Django 文档][10]了解更多详情。 + + * 注意:如果不是提供数据库的单个部分,而是提供完整的数据库 URL,请查看 [dj_database_url][11]。 + + * `AUTH_PASSWORD_VALIDATORS` 实际上是运行以检查输入密码的函数列表。默认情况下我们有一些,但是如果我们有其它更复杂的验证需求:不仅仅是检查密码是否与用户的属性匹配,是否超过最小长度,是否是 1000 个最常用的密码之一,或者密码完全是数字,我们可以在这里列出它们。 + + * `LANGUAGE_CODE` 设置网站的语言。默认情况下它是美国英语,但我们可以将其切换为其它语言。 + + * `TIME_ZONE` 是我们 Django 项目后中自动生成的时间戳的时区。我强调坚持使用 UTC 并在其它地方执行任何特定于时区的处理,而不是尝试重新配置此设置。正如[这篇文章][12] 所述,UTC 是所有时区的共同点,因为不需要担心偏移。如果偏移很重要,我们可以根据需要使用与 UTC 的适当偏移来计算它们。 + + * `USE_I18N` 将让 Django 使用自己的翻译服务来为前端翻译字符串。I18N = 国际化(“i” 和 “n” 之间的 18 个字符)。 + + * `USE_L10N` (L10N = 本地化[在 "l" 和 "n" 之间有 10 个字符]) 如果设置为 `True`,那么将使用数据的公共本地格式。一个很好的例子是日期:在美国它是 MM-DD-YYYY。在欧洲,日期往往写成 DD-MM-YYYY。 + + * `STATIC_URL` 是用于提供静态文件的大量设置的一部分。我们将构建一个 REST API,因此我们不需要担心静态文件。通常,这会为每个静态文件的域名设置根路径。所以,如果我们有一个徽标图像,那就是 `http:////logo.gif`。 + +默认情况下,这些设置已准备就绪。我们必须改变的一个选项是 `DATABASES` 设置。首先,我们创建将要使用的数据库: +``` +(django-someHash) $ createdb django_todo + +``` + +我们想要像使用 Flask, Pyramid 和 Tornado 一样使用 PostgreSQL 数据库,这意味着我们必须更改 `DATABASES` 设置以允许我们的服务器访问 PostgreSQL 数据库。首先是引擎。默认情况下,数据库引擎是 `django.db.backends.sqlite3`,我们把它改成 `django.db.backends.postgresql`。 + +有关 Django 可用引擎的更多信息,[查看文档][13]。请注意,尽管技术上可以将 NoSQL 解决方案整合到 Django 项目中,但为了开箱即用,Django 强烈偏向于 SQL 解决方案。 + +接下来,我们必须为连接参数的不同部分指定键值对。 + + * `NAME` 是我们刚刚创建的数据库的名称。 + * `USER` 是 Postgres 数据库用户名。 + * `PASSWORD` 是访问数据库所需的密码。 + * `HOST` 是数据库的主机。当我们在本地开发时,`localhost` 或 `127.0.0.1` 都将起作用。 + * `PORT` 是我们为 Postgres 开放的端口,它通常是 `5432`。 + +`settings.py` 希望我们为每个键提供字符串值。但是,这是高度敏感的信息。这对任何负责任的开发人员都不起作用。有几种方法可以解决这个问题,一种是我们需要设置环境变量。 +``` +DATABASES = { + +    'default': { + +        'ENGINE': 'django.db.backends.postgresql', + +        'NAME': os.environ.get('DB_NAME', ''), + +        'USER': os.environ.get('DB_USER', ''), + +        'PASSWORD': os.environ.get('DB_PASS', ''), + +        'HOST': os.environ.get('DB_HOST', ''), + +        'PORT': os.environ.get('DB_PORT', ''), + +    } + +} + +``` + +在继续之前,请确保设置环境变量或 Django 不起作用(to 校正:这里不清楚原文的意思,什么叫 django 不起作用)。此外,我们需要在此环境中安装 `psycopg2`,以便我们可以与数据库通信。 + +### Django 路由和视图 + +让我们在这个项目中实现一些函数。我们将使用 Django REST Framework 来构建 REST API,所以我们必须确保在 `settings.py` 中将 `rest_framework` 添加到 `INSTALLED_APPS` 的末尾。 +``` +INSTALLED_APPS = [ + +    'django.contrib.admin', + +    'django.contrib.auth', + +    'django.contrib.contenttypes', + +    'django.contrib.sessions', + +    'django.contrib.messages', + +    'django.contrib.staticfiles', + +    'rest_framework' + +] + +``` + +虽然 Django REST Framework 并不专门需要基于类的视图(如 Tornado)来处理传入的请求,但类是编写视图的首选方法。让我们来定义一个类视图。 + +让我们在 `django_todo` 创建一个名为 `views.py` 的文件。在 `views.py` 中,我们将创建 "Hello, world!" 视图。 +``` +# django_todo/views.py + +from rest_framework.response import JsonResponse + +from rest_framework.views import APIView + + +class HelloWorld(APIView): + +    def get(self, request, format=None): + +        """Print 'Hello, world!' as the response body.""" + +        return JsonResponse("Hello, world!") + +``` + +每个 Django REST Framework 基于类的视图都直接或间接地继承自 `APIView`。`APIView` 处理大量的东西,但为了达到我们的目的,它做了以下特定的事情: + + * 根据 HTTP 方法(例如 GET, POST, PUT, DELETE)来设置引导对应请求所需的方法 + + * 用我们需要的所有数据和属性来填充 `request` 对象,以便解析和处理传入的请求 + + * 采用 `Response` 或 `JsonResponse`,每个调度方法(即名为 `get`, `post`, `put`, `delete` 的方法)返回并构造格式正确的 HTTP 响应。 + +终于,我们有一个视图了!它本身没有任何作用,我们需要将它连接到路由。 + +如果我们跳转到 `django_todo/urls.py`,我们会到达默认的 URL 配置文件。如前所述:如果 Django 项目中的路由不包含在此处,则它不存在。 + +我们在给定的 `urlpatterns` 列表中添加所需的 URL。默认情况下,我们有一个 url,它里面包含一整套 URL 用于 Django 的内置管理后端系统,但是我们会删除它。 + +我们还得到一些非常有用的文档字符串,它告诉我们如何向 Django 项目添加路由。我们需要调用 `path()`,伴随三个参数: + + * 所需的路由,作为字符串(没有前导斜线) + * 处理该路由的视图函数(只能有一个函数!) + * 在 Django 项目中路由的名称 + +让我们导入 `HelloWorld` 视图并将其附加到主路径 `"/"` 。我们可以从 `urlpatterns` 中删除 `admin` 的路径,因为我们不会使用它。 + +``` +# django_todo/urls.py, after the big doc string + +from django.urls import path + +from django_todo.views import HelloWorld + + + +urlpatterns = [ + +    path('', HelloWorld.as_view(), name="hello"), + +] + +``` + +好吧,这里有一点不同。我们指定的路由只是一个空白字符串,为什么它会工作?Django 假设我们声明的每个路由都以一个前导斜杠开头,我们只是在初始域名后指定资源路由。如果一条路由没有去往一个特定的资源,而只是一个主页,那么该路由是 `""`,或者实际上是“没有资源”。 + +`HelloWorld` 视图是从我们刚刚创建的 `views.py` 文件导入的。为了执行此导入,我们需要更新 `settings.py` 中的 `INSTALLED_APPS` 列表使其包含 `django_todo`。是的,这有点奇怪。以下是一种理解方式。 + +`INSTALLED_APPS` 指的是 Django 认为可导入的目录或包的列表。它是 Django 处理项目的各个组件的方式,比如安装了一个包,而不需要经过 `setup.py` 的方式。我们希望将 `django_todo` 目录视为可导入的包,因此我们将该目录包含在 `INSTALLED_APPS` 中。现在,在该目录中的任何模块也是可导入的。所以我们得到了我们的视图。 + +`path` 函数只将视图函数作为第二个参数,而不仅仅是基于类的视图。幸运的是,所有有效的基于 Django 类的视图都包含 `.as_view()` 方法。它的工作是将基于类的视图的所有优点汇总到一个视图函数中并返回该视图函数。所以,我们永远不必担心转换的工作。相反,我们只需要考虑业务逻辑,让 Django 和 Django REST Framework 处理剩下的事情。 + +让我们在浏览器中打开它! + +Django 提供了自己的本地开发服务器,可通过 `manage.py` 访问。让我们切换到包含 `manage.py` 的目录并输入: +``` +(django-someHash) $ ./manage.py runserver +Performing system checks... + +System check identified no issues (0 silenced). + +August 01, 2018 - 16:47:24 + +Django version 2.0.7, using settings 'django_todo.settings' + +Starting development server at http://127.0.0.1:8000/ + +Quit the server with CONTROL-C. + +``` + +当 `runserver` 执行时,Django 会检查以确保项目(或多或少)正确连接在一起。这不是万无一失的,但确实会发现一些明显的问题。如果我们的数据库与代码不同步,它会通知我们。毫无遗问,因为我们没有将任何应用程序的东西提交到我们的数据库,但现在这样做还是可以的。让我们访问 `http://127.0.0.1:8000` 来查看 `HelloWorld` 视图的输出。 + +咦?这不是我们在 Pyramid, Flask 和 Tornado 中看到的明文数据。当使用 Django REST Framework 时,HTTP 响应(在浏览器中查看时)是这样呈现的 HTML,以红色显示我们的实际 JSON 响应。 + +但不要担心!如果我们在命令行中使用 `curl` 快速访问 `http://127.0.0.1:8000`,我们就不会得到任何花哨的 HTML,只有内容。 +``` +# 注意:在不同的终端口窗口中执行此操作,在虚拟环境之外 + +$ curl http://127.0.0.1:8000 + +"Hello, world!" + +``` + +棒极了! + +Django REST Framework 希望我们在使用浏览器浏览时拥有一个人性化的界面。这是有道理的,如果在浏览器中查看 JSON,通常是因为人们想要检查它是否正确,或者在设计一些消费者 API 时想要了解 JSON 响应。这很像你从 [Postman][14] 中获得的东西。 + +无论哪种方式,我们都知道我们的视图工作了!酷!让我们概括一下我们做过的事情: + + 1. 使用 `django-admin startproject <项目名称>` 开始一个项目 + 2. 使用环境变量来更新 `django_todo/settings.py` 中的 `DEBUG`, `SECRET_KEY`,还有 `DATABASES` 字典 + 3. 安装 `Django REST Framework`,并将它添加到 `INSTALLED_APPS` + 4. 创建 `django_todo/views.py` 来包含我们的第一个类视图,它返回响应 "Hello, world!" + 5. 更新 `django_todo/urls.py`,其中包含我们的根路由 + 6. 在 `django_todo/settings.py` 中更新 `INSTALLED_APPS` 以包含 `django_todo` 包 + +### 创建模型 + +现在让我们来创建数据模型吧。 + +Django 项目的整个基础架构都是围绕数据模型构建的,它是这样编写的,因此每个数据模型够可以拥有自己的小天地,拥有自己的视图,自己与其资源相关的 URL 集合,甚至是自己的测试(如果我们需要(to 校正:这里???))。 + +如果我们想构建一个简单的 Django 项目,我们可以通过在 `django_todo` 目录中编写我们自己的 `models.py` 文件并将其导入我们的视图来避免这种情况。但是,我们试图以“正确”的方式编写 Django 项目,因此我们应该尽可能地将模型分成 Django 方式的包(to 校正:这里 Django Way™ 有点懵)。 + +Django Way 涉及创建所谓的 Django “apps”,它本身并不是单独的应用程序,它们没有自己的设置和诸如此类的东西(虽然它们也可以)。但是,它们可以拥有一个人们可能认为属于独立应用程序的东西: + + * 一组自包含的 URL + * 一组自包含的 HTML 模板(如果我们想要提供 HTML) + * 一个或多个数据模型 + * 一套自包含的视图 + * 一套自包含的测试 + +它们是独立的,因此可以像独立应用程序一样轻松共享。实际上,Django REST Framework 是 Django app 的一个例子。它包含自己的视图和 HTML 模板,用于提供我们的 JSON。我们只是利用这个 Django app 将我们的项目变成一个全面的 RESTful API 而不用那么麻烦。 + +要为我们的待办事项列表项创建 Django app,我们将要使用 `manage.py` 伴随参数 `startapp`。 +``` +(django-someHash) $ ./manage.py startapp todo + +``` + +`startapp` 命令成功执行后没有输出。我们可以通过使用 `ls` 来检查它是否完成它应该做的事情。 +``` +(django-someHash) $ ls + +Pipfile      Pipfile.lock django_todo  manage.py    todo + +``` + +看看:我们有一个全新的 `todo` 目录。让我们看看里面! +``` +(django-someHash) $ ls todo + +__init__.py admin.py    apps.py     migrations  models.py   tests.py    views.py + +``` + +以下是 `manage.py startapp` 创建的文件: + + * `__init__.py` 是空文件。它之所以存在是因为此目录可看作是模型,视图等的有效导入路径。 + + * `admin.py` 不是空文件。它用于在 Django admin 中格式化(to 校正:格式化可能欠妥)这个应用程序的模型,我们在本文中没有涉及到它。 + + * `apps.py` 这里基本不起作用。它有助于格式化 Django admin 的模型。 + + * `migrations` 是一个包含我们数据模型快照的目录。它用于更新数据库。这是内置数据库管理的少数几个框架之一,其中一部分允许我们更新数据库,而不必拆除它并重建它以更改模式。 + + * `models.py` 是数据模型所在。 + + * `tests.py` 是测试所在的地方,如果我们需要写测试。 + + * `views.py` 用于我们编写的与此 app 中的模型相关的视图。它们不是一定得写在这里。例如,我们可以在 `django_todo/views.py` 中写下我们所有的视图。但是,它在这个 app 中更容易将我们的问题分开。这与覆盖许多概念空间的扩展应用程序之间的关系更加密切。 + +它并没有为这个 app 创建 `urls.py` 文件,但我们可以自己创建。 +``` +(django-someHash) $ touch todo/urls.py + +``` + +在继续之前,我们应该帮自己一个忙,将这个新 Django 应用程序添加到 `django_todo/settings.py` 中的 `INSTALLED_APPS` 列表中。 +``` +# settings.py + +INSTALLED_APPS = [ + +    'django.contrib.admin', + +    'django.contrib.auth', + +    'django.contrib.contenttypes', + +    'django.contrib.sessions', + +    'django.contrib.messages', + +    'django.contrib.staticfiles', + +    'rest_framework', + +    'django_todo', + +    'todo' # <--- 添加了这行 + +] + +``` + +检查 `todo / models.py` 发现 `manage.py` 已经为我们编写了一些代码。不同于在 Flask, Tornado 和 Pyramid 实现中创建模型的方式,Django 不利用第三方来管理数据库会话或构建其对象实例。它全部归入 Django 的 `django.db.models` 子模块。 + +然而,建立模型的方式或多或少是相同的。要在 Django 中创建模型,我们需要构建一个继承自 `models.Model` 的 `class`,将应用于该模型实例的所有字段都应视为类属性。我们不像过去那样从 SQLAlchemy 导入列和字段类型,而是直接从 `django.db.models` 导入。 +``` +# todo/models.py + +from django.db import models + + +class Task(models.Model): + +    """Tasks for the To Do list.""" + +    name = models.CharField(max_length=256) + +    note = models.TextField(blank=True, null=True) + +    creation_date = models.DateTimeField(auto_now_add=True) + +    due_date = models.DateTimeField(blank=True, null=True) + +    completed = models.BooleanField(default=False) + +``` + +虽然 Django 的需求和基于 SQLAlchemy 的系统之间存在一些明显的差异,但总体内容和结构或多或少相同。让我们来指出这些差异。 + +我们不再需要为对象实例声明自动递增 ID 的单独字段。除非我们指定一个不同的字段作为主键,否则 Django 会为我们构建一个。 + +我们只是直接引用数据类型作为列本身,而不是实例化传递数据类型对象的 `Column` 对象。 + +`Unicode` 字段变为 `models.CharField` 或 `models.TextField`。`CharField` 用于特定最大长度的小文本字段,而 `TextField` 用于任何数量的文本。 + +`TextField` 应该是空白的,我们以两种方式指定它。`blank = True` 表示当构建此模型的实例,并且正在验证附加到该字段的数据时,该数据是可以为空的。这与 `null = True` 不同,后者表示当构造此模型类的表时,对应于 `note` 的列将允许空白或为 `NULL`。因此,总而言之,`blank = True` 控制如何将数据添加到模型实例,而 `null = True` 控制如何构建保存该数据的数据库表。 + +`DateTime` 字段增加了一些属性,并且能够为我们做一些工作,使得我们不必修改类的 `__init__` 方法。对于 `creation_date` 字段,我们指定 `auto_now_add = True`。在实际意义上意味着,当创建一个新模型实例时,Django 将自动记录现在的日期和时间作为该字段的值。这非常方便! + +当 `auto_now_add` 及其类似属性 `auto_now` 都没被设置为 `True`时,`DateTimeField` 会像其它字段一样期待数据。它需要提供一个适当的 `datetime` 对象才能生效。`due_date` 列的 `blank` 和 `null` 属性都设置为 `True`,这样待办事项列表中的项目就可以成为将来某个时间点完成,没有确定的日期或时间。 + +`BooleanField` 最终可以取两个值:`True` 或 `False`。这里,默认值设置为 `False`。 + +#### 管理数据库 + +如前所述,Django 有自己的数据库管理方式。我们可以利用 Django 提供的 `manage.py` 脚本,而不必编写任何关于数据库的代码。它不仅可以管理我们数据库的表格构建,还可以管理我们希望对这些表格进行的任何更新,而不必将整个事情搞砸! + +因为我们构建了一个新模型,所以我们需要让数据库知道它。首先,我们需要将与此模型对应的模式放入代码中。`manage.py` 的 `makemigrations` 命令对我们构建的模型类及其所有字段进行快照。它将获取该信息并将其打包成一个 Python 脚本,该脚本将存在于特定 Django app 的 `migrations` 目录中。永远不会有理由直接运行这个迁移脚本。它的存在只是为了让 Django 可以使用它作为更新数据库表的基础,或者在我们更新模型类时继承信息。 +``` +(django-someHash) $ ./manage.py makemigrations + +Migrations for 'todo': + +  todo/migrations/0001_initial.py + +    - Create model Task + +``` + +这将查看 `INSTALLED_APPS` 中列出的每个应用程序,并检查这些应用程序中存在的模型。然后,它将检查相应的 `migrations` 目录中的迁移文件,并将它们与每个 `INSTALLED_APPS` 中的模型进行比较。如果模型升级已超出最新迁移所应存在的范围,则将创建一个继承自最新迁移文件的新迁移文件,它将自动命名,并且还会显示一条消息,说明自上次迁移以来发生了哪些更改。 + +如果你上次处理 Django 项目已经有一段时间了,并且不记得模型是否与迁移同步,那么你无需担心。`makemigrations` 是一个幂等操作。无论你运行 `makemigrations` 一次还是 20 次,`migrations` 目录只有一个与当前模型配置的副本。还有更棒的,当我们运行 `./manage.py runserver` 时,Django 检测到我们的模型与迁移不同步,它会用彩色文本告诉我们以便我们可以做出适当的选择。 + +下一个要点是至少让每个人访问一次:创建一个迁移文件不会立即影响我们的数据库。当我们运行 `makemigrations` 时,我们准备了 Django 项目来定义如何创建给定的表并最终查找。我们仍在将这些更改应用于数据库。这就是 `migrate` 命令的用途。 + +``` +(django-someHash) $ ./manage.py migrate + +Operations to perform: + +  Apply all migrations: admin, auth, contenttypes, sessions, todo + +Running 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 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 sessions.0001_initial... OK + +  Applying todo.0001_initial... OK + +``` + +当我们应用迁移时,Django 首先检查其他 `INSTALLED_APPS` 是否有要应用的迁移,它大致按照列出的顺序检查它们。我们希望我们的应用程序最后列出,因为我们希望确保,如果我们的模型依赖于任何 Django 的内置模型,我们所做的数据库更新不会受到依赖性问题的影响。 + +我们还有另一个要构建的模型:User 模型。但是,自从我们使用 Django 以来,游戏发生了一些变化。许多应用程序需要某种类型的用户模型,Django 的 `django.contrib.auth` 包构建了自己的用户模型供我们使用。如果它不是我们用户需要的身份验证令牌,我们可以继续使用它而不是重新发明轮子。 + +但是,我们需要那个令牌。我们可以通过两种方式来处理这个问题。 + + * 继承自 Django 的 `User` 对象,我们自己的对象通过添加 `token` 字段来扩展它 + * 创建一个与 Django 的 `User` 对象一对一关系的新对象,其唯一目的是持有一个令牌 + +我习惯于建立对象关系,所以让我们选择第二种选择。我们称之为 `Owner`,因为它基本上具有与 `User` 类似的内涵,这就是我们想要的。 + +出于纯粹的懒惰,我们可以在 `todo/models.py` 中包含这个新的 `Owner` 对象,但是不要这样做。`Owner` 没有明确地与任务列表上的项目的创建或维护有关。从概念上讲,`Owner` 只是任务的所有者。甚至有时候我们想要扩展这个 `Owner` 以包含与任务完全无关的其他数据。 + +为了安全起见,让我们创建一个 `owner` 应用程序,其工作是容纳和处理这个 `Owner` 对象。 +``` +(django-someHash) $ ./manage.py startapp owner + +``` + +不要忘记在 `settings.py` 文件中的 `INSTALLED_APPS` 中添加它。 +``` +INSTALLED_APPS = [ +    'django.contrib.admin', + +    'django.contrib.auth', + +    'django.contrib.contenttypes', + +    'django.contrib.sessions', + +    'django.contrib.messages', + +    'django.contrib.staticfiles', + +    'rest_framework', + +    'django_todo', + +    'todo', + +    'owner' +] + +``` + +如果我们查看 Django 项目的根目录,我们现在有两个 Django 应用程序: +``` +(django-someHash) $ ls + +Pipfile      Pipfile.lock django_todo  manage.py    owner        todo + +``` + +在 `owner/models.py` 中,让我们构建这个 `Owner` 模型。如前所述,它与 Django 的内置 `User` 对象有一对一的关系。我们可以用 Django 的 `models.OneToOneField` 强制实现这种关系。 +``` +# owner/models.py + +from django.db import models + +from django.contrib.auth.models import User + +import secrets + + +class Owner(models.Model): + +    """The object that owns tasks.""" + +    user = models.OneToOneField(User, on_delete=models.CASCADE) + +    token = models.CharField(max_length=256) + + +    def __init__(self, *args, **kwargs): + +        """On construction, set token.""" + +        self.token = secrets.token_urlsafe(64) + +        super().__init__(*args, **kwargs) + +``` + +这表示 `Owner` 对象对应到 `User` 对象,每个 `user` 实例有一个 `owner` 实例。`on_delete = models.CASCADE` 表示如果相应的 `User` 被删除,它所对应的 `Owner` 实例也将被删除。让我们运行 `makemigrations` 和 `migrate` 来将这个新模型放入到我们的数据库中。 +``` +(django-someHash) $ ./manage.py makemigrations + +Migrations for 'owner': + +  owner/migrations/0001_initial.py + +    - Create model Owner + +(django-someHash) $ ./manage.py migrate + +Operations to perform: + +  Apply all migrations: admin, auth, contenttypes, owner, sessions, todo + +Running migrations: + +  Applying owner.0001_initial... OK + +``` + +现在我们的 `Owner` 需要拥有一些 `Task` 对象。它与上面看到的 `OneToOneField` 非常相似,只不过我们会在 `Task` 对象上贴一个 `ForeignKey` 字段指向 `Owner`。 + +``` +# todo/models.py + +from django.db import models + +from owner.models import Owner + + +class Task(models.Model): + +    """Tasks for the To Do list.""" + +    name = models.CharField(max_length=256) + +    note = models.TextField(blank=True, null=True) + +    creation_date = models.DateTimeField(auto_now_add=True) + +    due_date = models.DateTimeField(blank=True, null=True) + +    completed = models.BooleanField(default=False) + +    owner = models.ForeignKey(Owner, on_delete=models.CASCADE) + +``` + +每个待办事项列表任务只有一个可以拥有多个任务的所有者。删除该所有者后,他们拥有的任务都会随之删除。 + +现在让我们运行 `makemigrations` 来获取我们的数据模型设置的新快照,然后运行 `migrate` 将这些更改应用到我们的数据库。 + +``` +(django-someHash) django $ ./manage.py makemigrations + +You are trying to add a non-nullable field 'owner' to task without a default; we can't do that (the database needs something to populate existing rows). + +Please select a fix: + + 1) Provide a one-off default now (will be set on all existing rows with a null value for this column) + + 2) Quit, and let me add a default in models.py + +``` + +不好了!出现了问题!发生了什么?其实,当我们创建 `Owner` 对象并将其作为 `ForeignKey` 添加到 `Task` 时,要求每个 `Task` 都需要一个 `Owner`。但是,我们为 `Task` 对象进行的第一次迁移不包括该要求。因此,即使我们的数据库表中没有数据,Django 也会对我们的迁移进行预先检查,以确保它们兼容,而我们提议的这种新迁移不是。 + +有几种方法可以解决这类问题: + + 1. 退出当前迁移并构建一个包含当前模型配置的新迁移 +  2. 将一个默认值添加到 `Task` 对象的 `owner` 字段 +  3. 允许任务为 `owner` 字段设置 `NULL` 值 + +方案 2 在这里没有多大意义。我们建议,任何创建的 `Task`,默认情况下都会对应到某个默认所有者,尽管不一定存在。(to 校正:后面这句发意义在哪里?既然它已经说了方案 2 没有意义) + +方案 1 要求我们销毁和重建我们的迁移,而我们应该把它们留下。 + +让我们考虑选项 3。在这种情况下,如果我们允许 `Task` 表为所有者提供空值,它不会很糟糕。从这一点开始创建的任何任务都必然拥有一个所有者。如果你的数据库表不是一个可重新架构的情况下,请删除迁移,删除表并重建迁移。 +``` +# todo/models.py + +from django.db import models + +from owner.models import Owner + + +class Task(models.Model): + +    """Tasks for the To Do list.""" + +    name = models.CharField(max_length=256) + +    note = models.TextField(blank=True, null=True) + +    creation_date = models.DateTimeField(auto_now_add=True) + +    due_date = models.DateTimeField(blank=True, null=True) + +    completed = models.BooleanField(default=False) + +    owner = models.ForeignKey(Owner, on_delete=models.CASCADE, null=True) + +(django-someHash) $ ./manage.py makemigrations + +Migrations for 'todo': + +  todo/migrations/0002_task_owner.py + +    - Add field owner to task + +(django-someHash) $ ./manage.py migrate + +Operations to perform: + +  Apply all migrations: admin, auth, contenttypes, owner, sessions, todo + +Running migrations: + +  Applying todo.0002_task_owner... OK + +``` + +酷!我们有模型了!欢迎使用 Django 声明对象的方式。 + +为了更好地衡量,让我们确保无论何时制作 `User`,它都会自动与新的 `Owner` 对象对应。我们可以使用 Django 的 `signals` 系统来做到这一点。基本上,我们确切地表达了意图:“当我们得到一个新的 `User` 被构造的信号时,构造一个新的 `Owner` 并将新的 `User` 设置为 `Owner` 的 `user` 字段。”在实践中看起来像这样: +``` +# owner/models.py + +from django.contrib.auth.models import User + +from django.db import models + +from django.db.models.signals import post_save + +from django.dispatch import receiver + +import secrets + + +class Owner(models.Model): + +    """The object that owns tasks.""" + +    user = models.OneToOneField(User, on_delete=models.CASCADE) + +    token = models.CharField(max_length=256) + + +    def __init__(self, *args, **kwargs): + +        """On construction, set token.""" + +        self.token = secrets.token_urlsafe(64) + +        super().__init__(*args, **kwargs) + + +@receiver(post_save, sender=User) +def link_user_to_owner(sender, **kwargs): + +    """If a new User is saved, create a corresponding Owner.""" + +    if kwargs['created']: + +        owner = Owner(user=kwargs['instance']) + +        owner.save() + +``` + +我们设置了一个函数,用于监听从 Django 中内置的 `User` 对象发送的信号。它正在等待 `User` 对象被保存之后的情况。这可以来自新的 `User` 或对现有 `User` 的更新。我们在监听功能中辨别出两种情况。 + +如果发送信号的东西是新创建的实例,`kwargs ['created']` 将具有值 `True`。如果是 `True` 的话,我们想做点事情。如果它是一个新实例,我们创建一个新的 `Owner`,将其 `user` 字段设置为创建的新 `User` 实例。之后,我们 `save()` 新的 `Owner`。如果一切正常,这将提交更改到数据库。如果数据没通过我们声明的字段的验证,它将失败。 + +现在让我们谈谈我们将如何访问数据。 + + +### 访问模型数据 + +在 Flask, Pyramid 和 Tornado 框架中,我们通过对某些数据库会话运行查询来访问模型数据。也许它被附加到 `request` 对象,也许它是一个独立的 `session` 对象。无论如何,我们必须建立与数据库的实时连接并在该连接上进行查询。 + +这不是 Django 的工作方式。默认情况下,Django 不利用任何第三方对象关系映射(ORM)与数据库进行通信。相反,Django 允许模型类维护自己与数据库的对话。 + +从 `django.db.models.Model` 继承的每个模型类都会附加一个 `objects` 对象。这将取代我们熟悉的 `session` 或 `dbsession`。让我们打开 Django 给我们的特殊 shell,并研究这个 `objects` 对象是如何工作的。 +``` +(django-someHash) $ ./manage.py shell + +Python 3.7.0 (default, Jun 29 2018, 20:13:13) +[Clang 9.1.0 (clang-902.0.39.2)] on darwin +Type "help", "copyright", "credits" or "license" for more information. +(InteractiveConsole) + +>>> + +``` + +Django shell 与普通的 Python shell 不同,因为它知道我们正在构建的 Django 项目,可以轻松导入我们的模型,视图,设置等,而不必担心安装包。我们可以通过简单的 `import` 访问我们的模型。 +``` +>>> from owner.models import Owner + +>>> Owner + + + +``` + +目前,我们没有 `Owner` 实例。我们可以通过 `Owner.objects.all()` 查询它们。 +``` +>>> Owner.objects.all() + + + +``` + +无论何时我们在 ` .objects` 对象上运行查询方法,我们都会得到 `QuerySet`。为了我们的目的,它实际上是一个 `list`,这个 `list` 向我们显示它是空的。让我们通过创建一个 `User` 来创建一个 `Owner`。 +``` +>>> from django.contrib.auth.models import User + +>>> new_user = User(username='kenyattamurphy', email='kenyatta.murphy@gmail.com') + +>>> new_user.set_password('wakandaforever') + +>>> new_user.save() + +``` + +如果我们现在查询所有的 `Owner`,我们应该会找到 Kenyatta。 +``` +>>> Owner.objects.all() + +]> + +``` + +棒极了!我们得到了数据! + +### 序列化模型 + +我们将在 “Hello World” 之外来回传递数据。因此,我们希望看到某种类似于 JSON 类型的输出,它可以很好地表示数据。获取该对象的数据并将其转换为 JSON 对象以通过 HTTP 提交是数据序列化的一种方式。在序列化数据时,我们正在获取我们目前拥有的数据并重新格式化以适应一些标准的,更易于理解的形式。 + +如果我用 Flask, Pyramid 和 Tornado 这样做,我会在每个模型上创建一个新方法,让用户可以直接调用 `to_json()`。`to_json()` 的唯一工作是返回一个 JSON 可序列化的(即数字,字符串,列表,词典)字典,其中包含我想要为所讨论的对象显示的任何字段。 + +对于 `Task` 对象,它可能看起来像这样: +``` +class Task(Base): + +    ...all the fields... + +    def to_json(self): + +        """Convert task attributes to a JSON-serializable dict.""" + +        return { + +            'id': self.id, + +            'name': self.name, + +            'note': self.note, + +            'creation_date': self.creation_date.strftime('%m/%d/%Y %H:%M:%S'), + +            'due_date': self.due_date.strftime('%m/%d/%Y %H:%M:%S'), + +            'completed': self.completed, + +            'user': self.user_id + +        } + +``` + +这不花哨,但它确实起到了作用。 + +然而,Django REST Framework 为我们提供了一个对象,它不仅可以为我们这样做,还可以在我们想要创建新对象实例或更新现有实例时验证输入,它被称为 [ModelSerializer][15]。 + +Django REST Framework 的 `ModelSerializer` 是我们模型的有效文档。如果没有附加模型,它们就没有自己的生命(因为那里有 [Serializer][16] 类)。它们的主要工作是准确地表示我们的模型,并在我们的模型数据需要序列化并通过线路发送时,将其转换为 JSON。 + +Django REST Framework 的 `ModelSerializer` 最适合简单对象。举个例子,假设我们在 `Task` 对象上没有 `ForeignKey`。我们可以为 `Task` 创建一个序列化器,它将根据需要将其字段值转换为 JSON,声明如下: +``` +# todo/serializers.py + +from rest_framework import serializers + +from todo.models import Task + + +class TaskSerializer(serializers.ModelSerializer): + +    """Serializer for the Task model.""" + +    class Meta: + +        model = Task + +        fields = ('id', 'name', 'note', 'creation_date', 'due_date', 'completed') + +``` + +在我们新的 `TaskSerializer` 中,我们创建了一个 `Meta` 类。`Meta` 的工作就是保存关于我们试图序列化的东西的信息(或元数据)。然后,我们会注意到要显示的特定字段。如果我们想要显示所有字段,我们可以简化过程并使用`'__all __'`。或者,我们可以使用 `exclude` 关键字而不是 `fields` 来告诉 Django REST Framework 我们想要除了少数几个字段以外的每个字段。我们可以拥有尽可能多的序列化器,所以也许我们想要一个用于一小部分字段而一个用于所有字段?在这里都可以。 + +在我们的例子中,每个 `Task` 和它的所有者 `Owner` 之间都有一个关系,必须在这里反映出来。因此,我们需要借用 `serializers.PrimaryKeyRelatedField` 对象来指定每个 `Task` 都有一个 `Owner`,并且该关系是一对一的。它的 owner 将从存在的所有 owners 的集合中找到。我们通过对这些 owners 进行查询并返回我们想要与此序列化程序关联的结果来获得该集合:`Owner.objects.all()`。我们还需要在字段列表中包含 `owner`,因为我们总是需要一个与 `Task` 相关联的 `Owner`。 +``` +# todo/serializers.py + +from rest_framework import serializers + +from todo.models import Task + +from owner.models import Owner + + +class TaskSerializer(serializers.ModelSerializer): + +    """Serializer for the Task model.""" + +    owner = serializers.PrimaryKeyRelatedField(queryset=Owner.objects.all()) + + +    class Meta: + +        model = Task + +        fields = ('id', 'name', 'note', 'creation_date', 'due_date', 'completed', 'owner') + +``` + +现在构建了这个序列化器,我们可以将它用于我们想要为我们的对象做的所有 CRUD 操作: + * 如果我们想要 `GET` 一个特定的 `Task` 的 JSON 类型版本,我们可以做 `TaskSerializer((some_task).data` + + * 如果我们想接受带有适当数据的 `POST` 来创建一个新的 `Task`,我们可以使用 `TaskSerializer(data = new_data).save()` + + * 如果我们想用 `PUT` 更新一些现有数据,我们可以用 `TaskSerializer(existing_task, data = data).save()` + +我们没有包括 `delete`,因为我们不需要对 `delete` 操作做任何事情。如果你可以删除一个对象,只需使用 `object_instance.delete()`。 + +以下是一些序列化数据的示例: +``` +>>> from todo.models import Task + +>>> from todo.serializers import TaskSerializer + +>>> from owner.models import Owner + +>>> from django.contrib.auth.models import User + +>>> new_user = User(username='kenyatta', email='kenyatta@gmail.com') + +>>> new_user.save_password('wakandaforever') + +>>> new_user.save() # creating the User that builds the Owner + +>>> kenyatta = Owner.objects.first() # 找到 kenyatta 的所有者 + +>>> new_task = Task(name="Buy roast beef for the Sunday potluck", owner=kenyatta) + +>>> new_task.save() + +>>> TaskSerializer(new_task).data + +{'id': 1, 'name': 'Go to the supermarket', 'note': None, 'creation_date': '2018-07-31T06:00:25.165013Z', 'due_date': None, 'completed': False, 'owner': 1} + +``` + +使用 `ModelSerializer` 对象可以做更多的事情,我建议查看[文档][17]以获得更强大的功能。否则,这就是我们所需要的。现在是时候深入视图了。 + +### 查看视图 + +我们已经构建了模型和序列化器,现在我们需要为我们的应用程序设置视图和 URL。毕竟,对于没有视图的应用程序,我们无法做任何事情。我们已经看到了上面的 `HelloWorld` 视图的示例。然而,这总是一个人为的,概念验证的例子,并没有真正展示 Django REST Framework 的视图可以做些什么。让我们清除 `HelloWorld` 视图和 URL,这样我们就可以从我们的视图重新开始。 + +我们要构建的第一个视图是 `InfoView`。与之前的框架一样,我们只想打包并发送一个字典到正确的路由。视图本身可以存在于 `django_todo.views` 中,因为它与特定模型无关(因此在概念上不属于特定应用程序)。 +``` +# django_todo/views.py + +from rest_framework.response import JsonResponse + +from rest_framework.views import APIView + + +class InfoView(APIView): + +    """List of routes for this API.""" + +    def get(self, request): + +        output = { + +            'info': 'GET /api/v1', + +            'register': 'POST /api/v1/accounts', + +            'single profile detail': 'GET /api/v1/accounts/', + +            'edit profile': 'PUT /api/v1/accounts/', + +            'delete profile': 'DELETE /api/v1/accounts/', + +            'login': 'POST /api/v1/accounts/login', + +            'logout': 'GET /api/v1/accounts/logout', + +            "user's tasks": 'GET /api/v1/accounts//tasks', + +            "create task": 'POST /api/v1/accounts//tasks', + +            "task detail": 'GET /api/v1/accounts//tasks/', + +            "task update": 'PUT /api/v1/accounts//tasks/', + +            "delete task": 'DELETE /api/v1/accounts//tasks/' + +        } + +        return JsonResponse(output) + +``` + +这与我们在 Tornado 中所拥有的完全相同。让我们将它放置到合适的路由并继续前行。为了更好的测试,我们还将删除 `admin/` 路由,因为我们不会在这里使用 Django 管理后端。 +``` +# in django_todo/urls.py + +from django_todo.views import InfoView + +from django.urls import path + + +urlpatterns = [ + +    path('api/v1', InfoView.as_view(), name="info"), + +] + +``` + +#### 连接模型与视图 + +让我们弄清楚下一个 URL,它将是创建新的 `Task` 或列出用户现有任务的入口。这应该存在于 `todo` 应用程序的 `urls.py` 中,因为它必须专门处理 `Task `对象而不是整个项目的一部分。 +``` +# in todo/urls.py + +from django.urls import path + +from todo.views import TaskListView + + +urlpatterns = [ + +    path('', TaskListView.as_view(), name="list_tasks") + +] + +``` + +这个路由处理的是什么?我们根本没有指定特定用户或路径。由于会有一些路由需要基本路径 `/api/v1/accounts//tasks`,为什么我们只需写一次就能一次又一次地写它? + +Django 允许我们获取一整套 URL 并将它们导入 `django_todo/urls.py` 文件。然后,我们可以为这些导入的 URL 中的每一个提供相同的基本路径,只关心可变部分,你知道它们是不同的。 +``` +# in django_todo/urls.py + +from django.urls import include, path + +from django_todo.views import InfoView + + +urlpatterns = [ + +    path('api/v1', InfoView.as_view(), name="info"), + +    path('api/v1/accounts//tasks', include('todo.urls')) + +] + +``` + +现在,来自 `todo/urls.py` 的每个 URL 都将以路径 `api/v1/accounts//tasks` 为前缀。 + +让我们在 `todo/views.py` 中构建视图。 +``` +# todo/views.py + +from django.shortcuts import get_object_or_404 + +from rest_framework.response import JsonResponse + +from rest_framework.views import APIView + + +from owner.models import Owner + +from todo.models import Task + +from todo.serializers import TaskSerializer + + +class TaskListView(APIView): + +    def get(self, request, username, format=None): + +        """Get all of the tasks for a given user.""" + +        owner = get_object_or_404(Owner, user__username=username) + +        tasks = Task.objects.filter(owner=owner).all() + +        serialized = TaskSerializer(tasks, many=True) + +        return JsonResponse({ + +            'username': username, + +            'tasks': serialized.data + +        }) + +``` + +这里有很多代码,让我们来看看吧。 + +我们从与我们一直使用的 `APIView` 的继承开始,为我们的视图奠定基础。我们覆盖了之前覆盖的相同 `get` 方法,添加了一个参数,允许我们的视图从传入的请求中接收 `username`。 + +然后我们的 `get` 方法将使用 `username` 来获取与该用户关联的 `Owner`。这个 `get_object_or_404` 函数允许我们这样做,添加一些特殊的东西以方便使用。 + +如果无法找到指定的用户,那么查找任务是没有意义的。实际上,我们想要返回 404 错误。`get_object_or_404` 根据我们传入的任何条件获取单个对象,并返回该对象或引发 [Http404 异常][18]。我们可以根据对象的属性设置该条件。`Owner` 对象都通过 `user` 属性附加到 `User`。但是,我们没有要搜索的 `User` 对象,我们只有一个 `username`。所以,当你寻找一个 `Owner` 时,我们对 `get_object_or_404` 说:通过指定 `user__username` 来检查附加到它的 `User` 是否具有我想要的 `username`。这是两个下划线。通过 QuerySet 过滤时,这两个下划线表示“此嵌套对象的属性”。这些属性可以根据需要进行深度嵌套。 + +我们现在拥有与给定用户名相对应的 `Owner`。我们使用 `Owner` 来过滤所有任务,只用 `Task.objects.filter` 检索它拥有的任务。我们可以使用与 `get_object_or_404` 相同的嵌套属性模式来钻入连接到 `Tasks` 的 `Owner` 的 `User`(`tasks = Task.objects.filter(owner__user__username = username)).all()`)但是没有必要那么宽松。 + +`Task.objects.filter(owner = owner).all()` 将为我们提供与我们的查询匹配的所有 `Task` 对象的`QuerySet`。大。然后,`TaskSerializer` 将获取 `QuerySet` 及其所有数据以及 `many = True` 标志,将其通知为项目集合而不是仅仅一个项目,并返回一系列序列化结果。实际上是一个词典列表。最后,我们使用 JSON 序列化数据和用于查询的用户名提供传出响应。 + +#### 处理 POST 请求 + +`post` 方法看起来与我们之前看到的有些不同。 +``` +# still in todo/views.py + +# ...other imports... + +from rest_framework.parsers import JSONParser + +from datetime import datetime + + +class TaskListView(APIView): + +    def get(self, request, username, format=None): + +        ... + + +    def post(self, request, username, format=None): + +        """Create a new Task.""" + +        owner = get_object_or_404(Owner, user__username=username) + +        data = JSONParser().parse(request) + +        data['owner'] = owner.id + +        if data['due_date']: + +            data['due_date'] = datetime.strptime(data['due_date'], '%d/%m/%Y %H:%M:%S') + + +        new_task = TaskSerializer(data=data) + +        if new_task.is_valid(): + +            new_task.save() + +            return JsonResponse({'msg': 'posted'}, status=201) + + +        return JsonResponse(new_task.errors, status=400) + +``` + +当我们从客户端接收数据时,我们使用 `JSONParser().parse(request)` 将其解析为字典。我们将所有者添加到数据中并格式化任务的 `due_date`(如果存在)。 + +我们的 `TaskSerializer` 完成了繁重的任务。它首先接收传入的数据并将其转换为我们在模型上指定的字段。然后验证该数据以确保它适合指定的字段。如果附加到新 `Task` 的数据有效,它将使用该数据构造一个新的 `Task` 对象并将其提交给数据库。然后我们发回适当的“耶!我们做了一件新事!”响应。如果没有,我们收集 `TaskSerializer` 生成的错误,并将这些错误发送回客户端,并返回 `400 Bad Request` 状态代码。 + +如果我们要构建 `put` 视图来更新 `Task`,它看起来会非常相似。主要区别在于,当我们实例化 `TaskSerializer` 时,我们将传递旧对象和该对象的新数据,如 `TaskSerializer(existing_task,data = data)`。我们仍然会进行有效性检查并发回我们想要发回的回复。 + +### 总结 + +Django 作为一个框架是高度可定制的,每个人都有自己的方式拼接 Django 项目。我在这里写出来的方式不一定是 Django 建立项目的确切方式。它只是 a) 我熟悉的方式,以及 b) 利用 Django 的管理系统。当你将概念分成他们自己的小筒仓时,Django 项目的复杂性会增加。这样做是为了让多个人更容易为整个项目做出贡献,而不会麻烦彼此。 + +然而,作为 Django 项目的大量文件映射并不能使其更高效或自然地偏向于微服务架构。相反,它很容易成为一个令人困惑的巨石,这可能对你的项目仍然有用,它也可能使你的项目难以管理,尤其是随着项目的增长。 + +仔细考虑你的需求并使用合适的工具来完成正确的工作。对于像这样的简单项目,Django 可能不是合适的工具。 + +Django 旨在处理多种模型,这些模型涵盖了不同的项目领域,但它们可能有一些共同点。这个项目是一个小型的双模型项目,有一些路由。如果我们要构建更多,我们只有七条路由,但仍然是相同的两个模型。这还不足以证明一个完整的 Django 项目。 + +如果我们期望这个项目能够扩展,那将是一个很好的选择。这不是其中一个项目。这是选择一个点燃蜡烛的火焰喷射器。这是绝对的矫枉过正。(to 校正:这里有点迷糊) + +尽管如此,Web 框架仍然是一个 Web 框架,无论你使用哪个框架。它都可以接收请求并做出任何响应,因此你可以按照自己的意愿进行操作。只需要注意你选择的框架所带来的开销。 + +就是这样!我们已经到了这个系列的最后!我希望这是一次启发性的冒险。当你在考虑如何构建你的下一个项目时,它将帮助你做出的不仅仅是最熟悉的选择。请务必阅读每个框架的文档,以扩展本系列中涉及的任何内容(因为它没有那么全面)。每个人都有一个广阔的世界。愉快地写代码吧! + + +-------------------------------------------------------------------------------- + +via: https://opensource.com/article/18/8/django-framework + +作者:[Nicholas Hunt-Walker][a] +选题:[lujun9972](https://github.com/lujun9972) +译者:[MjSeven](https://github.com/MjSeven) +校对:[校对者ID](https://github.com/校对者ID) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]:https://opensource.com/users/nhuntwalker +[1]:https://opensource.com/article/18/5/pyramid-framework +[2]:https://opensource.com/article/18/4/flask +[3]:https://opensource.com/article/18/6/tornado-framework +[4]:https://www.djangoproject.com +[5]:https://djangopackages.org/ +[6]:http://www.django-rest-framework.org/ +[7]:http://gunicorn.org/ +[8]:https://docs.pylonsproject.org/projects/waitress/en/latest/ +[9]:https://uwsgi-docs.readthedocs.io/en/latest/ +[10]:https://docs.djangoproject.com/en/2.0/ref/settings/#databases +[11]:https://pypi.org/project/dj-database-url/ +[12]:http://yellerapp.com/posts/2015-01-12-the-worst-server-setup-you-can-make.html +[13]:https://docs.djangoproject.com/en/2.0/ref/settings/#std:setting-DATABASE-ENGINE +[14]:https://www.getpostman.com/ +[15]:http://www.django-rest-framework.org/api-guide/serializers/#modelserializer +[16]:http://www.django-rest-framework.org/api-guide/serializers/ +[17]:http://www.django-rest-framework.org/api-guide/serializers/#serializers +[18]:https://docs.djangoproject.com/en/2.0/topics/http/views/#the-http404-exception