01-django-入门项目

参考博客

URL调度器 | Django 文档 | Django
python引入模块报错ValueError: attempted relative import beyond top-level package
Django更新models数据库结构步骤
画图

代码地址:github仓库代码地址
书籍链接:书籍下载 密码:8888

git仓库

git 
git add . && git commit -m "new commit" && git push  -u origin master

创建一个django项目

cd 任意目录
django-admin startproject student_sys     #在该目录生成django项目目录结构

后台

  • 创建app(student),并创建数据库model.py,构造数据显示admin.py
  • 注册app(student)到主应用中

创建一个app,在app中写代码

cd student_sys
python manage.py startapp student

建立数据库app/models.py

# 修改app/models.py
from django.db import models

# Create your models here.
class Student(models.Model):
    # 列表里面套元祖,供choices中做选择
    SEX_ITEMS = [
        ('1','男'),
        ('2','女'),
        ('0','未知'),
    ]

    STATUS_ITEMS = [
        (0,'申请'),
        (1,'通过'),
        (2,'拒绝'),
    ]
    name = models.CharField(max_length=128,verbose_name='姓名')
    sex = models.CharField(max_length=12,choices=SEX_ITEMS,verbose_name='性别')
    profession = models.CharField(max_length=128,verbose_name='职业')
    email = models.EmailField(verbose_name="Email")
    qq = models.CharField(max_length=128,verbose_name="QQ")
    phone = models.CharField(max_length=128,verbose_name="电话")

    #choice选择列表套元祖,IntegerField()
    status = models.IntegerField(choices=STATUS_ITEMS,verbose_name="审核状态")
    created_time = models.DateTimeField(auto_now_add=True,editable=False,verbose_name="创建时间")

    def __unicode__(self):
        return '<Student: {}>'.format(self.name)
'''
如我们的Model中实现了``__unicode__``方法,保证在Python2中运行时,
直接print(或者直接在web界面展示) student对象时,
能看到``<Student: [name]>``这样的字样,而不是Python中的``object xxxxx``这样东西。
'''
    class Meta:
        verbose_name = verbose_name_plural = "学员信息"

构造数据库显示

#修改字段显示
from django.contrib import admin
from .models import Student
# Register your models here.

class StudentAdmin(admin.ModelAdmin):
    list_display = ('id','name','sex','profession')
    list_filter = ('sex','status','created_time')
    search_fields = ('name','profession')
    fieldsets = (
        (None, {
         'fields': (
                 'name',
                 ('sex', 'profession'),
                 ('email', 'qq', 'phone'),
                 'status',
                 )
         }),
    )

admin.site.register(Student, StudentAdmin)   #将数据库类Student与StudentAdmin类绑定

生成表

cd student_house/student_sys/
python manage.py makemigrations  # 创建迁移文件
python manage.py migrate    #创建表
python manage.py createsuperuser    #根据提示,输出用户名,邮箱,密码

启动项目

python manage.py runserver  #启动项目

访问: http://127.0.0.1:8000,看到一个提示页,这是因为我们还没开发首页。我们可以进入到admin的页面: http://127.0.0.1:8000/admin/。用你创建好的账户登录,就能看到一个完整的带有CURD的后台了。

  • 测试访问

配置中文

  • 修改settings
# settings最下面修改
LANGUAGE_CODE = 'zh-hans'  # 语言

TIME_ZONE = 'Asia/Shanghai'  # 时区

USE_I18N = True  # 语言

USE_L10N = True  # 数据和时间格式

USE_TZ = True  # 启用时区
# 浏览器刷新后访问的是中文界面

前台

  • app/views.py
<!DOCTYPE html>
<html>
    <head>
        <title>学员管理系统-by the5fire</title>
    </head>
    <body>
        Hello {{ words }}!
    </body>
</html>
  • app/templates/index.html
from django.shortcuts import render

# Create your views here.

def index(request):
    words = 'World!'
    return render(request,'index.html',context={'words': words})

路由配置

  • 主目录/urls.py
from django.contrib import admin
from django.urls import path,re_path
from student import views

urlpatterns = [
    re_path('^$', views.index, name='index'),
    path('admin/', admin.site.urls)
]

html遍历输出数据

html

<!DOCTYPE html>
<html>
    <head>
        <title>学员管理系统-by the5fire</title>
    </head>
    <body>
        <ul>
        {% for student in students %}
            <li>{{ student.name }} - {{ student.get_status_display }}</li>
        {% endfor %}
        </ul>
    </body>
</html>
# student.get_status_display对应status = models.IntegerField(choices=STATUS_ITEMS,verbose_name="审核状态")
动态方法转静态调用,取出了status中的choices值

views.py/render

from django.shortcuts import render
from .models import Student   #导入model中的学生类,返回数据
# Create your views here.

# def index(request):
#     words = 'World!'
#     return render(request,'index.html',context={'words': words})


def index(request):
    students = Student.objects.all()
    return render(request,'index.html',context={'students': students})

forms.py提交数据

  • 方法1
from django import forms
from .models import Student
class StudentForm(forms.Form):
	name = forms.CharField(label='姓名', max_length=128)
	sex = forms.ChoiceField(label='性别', choices=Student.SEX_ITEMS)
	profession = forms.CharField(label='职业', max_length=128)
	email = forms.EmailField(label='邮箱', max_length=128)
	qq = forms.CharField(label='QQ', max_length=128)
	phone = forms.CharField(label='手机', max_length=128)
  • 方法2
    复用Model的代码
    model=Student(Student是)
from django import forms
from .models import Student
class StudentForm(forms.ModelForm):
    class Meta:
        model = Student
        fields = (
            'name', 'sex', 'profession',
            'email', 'qq', 'phone'
        )

项目文件结构


forms.py提交数据修改字段类型判断

from django import forms
from .models import Student
class StudentForm(forms.ModelForm):
	def clean_qq(self):
		cleaned_data = self.cleaned_data['qq']
		if not cleaned_data.isdigit():
			raise forms.ValidationError('必须是数字!')

		return int(cleaned_data)

	class Meta:
		model = Student
        fields = ('name', 'sex', 'profession','email', 'qq', 'phone')

'''
其中``clean_qq``就是Django的form会自动调用,来处理每个字段的方法,
比如在这个form中你可以通过定义``clean_phone``来处理电话号码,
可以定义``clean_email``来处理邮箱等等。如果验证失败,
可以通过``raise forms.ValidationError('必须是数字!')``的方式返回错误信息,
这个信息会存储在form中,最终会被我们渲染到页面上。
'''

form.py对应views.py

from django.http import HttpResponseRedirect
from django.urls import reverse
from django.shortcuts import render

from .models import Student
from .forms import StudentForm

def index(request):
	students = Student.objects.all()
	if request.method == 'POST':
		form = StudentForm(request.POST)
		if form.is_valid():
			cleaned_data = form.cleaned_data
			student = Student()
			student.name = cleaned_data['name']
			student.sex = cleaned_data['sex']
			student.email = cleaned_data['email']
			student.profession = cleaned_data['profession']
			student.qq = cleaned_data['qq']
			student.phone = cleaned_data['phone']
			student.save()
			return HttpResponseRedirect(reverse('index'))
	else:
		form = StudentForm()

	context = {
		'students': students,
		'form': form,
	}
	return render(request, 'index.html', context=context)

view.py展示form提交的数据

<!DOCTYPE html>
<html>
	<head>
		<title>学员管理系统-by the5fire</title>
	</head>
	<body>
		<h3><a href="/admin/">Admin</a></h3>
		<ul>
			{% for student in students %}
			<li>{{ student.name }} - {{ student.get_status_display }}</li>
			{% endfor %}
		</ul>
		<hr/>
		<form action="/" method="post">
			{% csrf_token %}
			{{ form }}
			<input type="submit" value="Submit" />
		</form>
	</body>
</html>


'''
其中 `{% csrf_token %}`是Django对提交数据安全性做的校验,
这意味着,如果没有这个token,提交过去的数据是无效的。
这是用来防止跨站伪造请求攻击的一个手段。

{{ form }}只需要这么写,Django就会帮我们自动把所有字段列出来。
当然,如果需要调整样式,那就要自己来增加css样式文件解决了。
'''
  • 测试访问
    异常:django.db.utils.IntegrityError: NOT NULL constraint failed: student_student.status
    form提交时没有status选项,数据库设置为非null,导致报错

分离post,get:Class Based View

'''
分离``get``和``post``的处理逻辑
回头看下上节``views.py``中的代码,其中有一个关于``request.method``的判断。我们来通过类级的View去掉层控制语句。
'''
from django.http import HttpResponseRedirect
from django.urls import reverse
from django.shortcuts import render
from django.views import View

from .models import Student
from .forms import StudentForm

class IndexView(View):
	template_name = 'index.html'

	def get_context(self):
		students = Student.objects.all()
		context = {
			'students': students,
		}
		return context

	def get(self, request):
		context = self.get_context()
		form = StudentForm()
		context.update({
			'form': form
		})
		return render(request, self.template_name, context=context)

	def post(self, request):
		form = StudentForm(request.POST)
		if form.is_valid():
			cleaned_data = form.cleaned_data
			student = Student()
			student.name = cleaned_data['name']
			student.sex = cleaned_data['sex']
			student.email = cleaned_data['email']
			student.profession = cleaned_data['profession']
			student.qq = cleaned_data['qq']
			student.phone = cleaned_data['phone']
			student.save()
			return HttpResponseRedirect(reverse('index'))
		context = self.get_context()
		context.update({
			'form': form
		})
		return render(request, self.template_name, context=context)

修改urls

    from django.conf.urls import url
    from django.contrib import admin

    from student.views import IndexView

    urlpatterns = [
        url(r'^$', IndexView.as_view(), name='index'),
        url(r'^admin/', admin.site.urls),
    ]
'''
IndexView.as_view()调用的是父类方法(django.views.view.as_view())
用来判断request对象中是否是get还是post方法,再调用对应方法
'''

配置 middleware

创建一个middlewares.py,位于views.py的同级目录中
目标:统计首页每次访问所消耗的时间,也就是wsgi接口或者socket接口接到请求,到最终返回的时间

from django.utils.deprecation import MiddlewareMixin
from django.urls import reverse
import time

class TimeItMiddleware(MiddlewareMixin):
    def process_request(self, request):
        self.start_time = time.time()
        return

    def process_view(self, request, func, *args, **kwargs):
        if request.path != reverse('index'):
            return None

        start = time.time()
        response = func(request)
        costed = time.time() - start
        print('{:.2f}s'.format(costed))
        return response

    def process_exception(self, request, exception):
        pass

    def process_template_response(self, request, response):
        return response

    def process_response(self, request, response):
        costed = time.time() - self.start_time
        print('request to response cose: {:.2f}s'.format(costed))
        return response

settings文件中注册middleware
--- 注意路径

MIDDLEWARE = [
    ''student.middlewares.TimeItMiddleware',  #增加当前行
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
  • process_request - 一个请求来到middelware层,进入的第一个方法。一般情况我们可以在这里做一些校验,比如用户登录,或者HTTP中是否有认证头之类的验证。这个方法需要两种返回值,HttpResponse或者None,如果返回HttpResponse,那么接下来的处理方法只会执行process_response,其他的方法将不会被执行。这里需要注意的是,如果你的middleware在settings配置的MIDDLEWARE_CLASS的第一个的话,那么剩下的middleware也不会被执行。另外一个返回值是None,如果返回None,那么Django会继续执行其他的方法。

  • process_view - 这个方法是在process_request之后执行的,参数如上面代码所示,其中的func就是我们将要执行的view方法,因此我们要统计一个view的执行时间,可以在这里来做。它的返回值跟process_request一样,HttpResponse/None,逻辑也是一样。如果返回None,那么Django会帮你执行view函数,从而得到最终的Response。

  • process_template_response - 执行完上面的方法,并且Django帮忙我们执行完view之后,拿到最终的response,如果是使用了模板的Response(是指通过return render(request, 'index.html', context={})的方式返回Response,就会来到这个方法中。这个方法中我们可以对response做一下操作,比如Content-Type设置,或者其他HEADER的修改/增加。

  • process_response - 当所有流程都处理完毕,就来到了这个方法,这个方法的逻辑跟process_template_response是完全一样的。只是process_template_response是针对带有模板的response的处理。

  • process_exception - 上面的所有处理方法是按顺序介绍的,而这个不太一样。只有在发生异常时,才会进入到这个方法。哪个阶段发生的异常呢?可以简单的理解为在将要调用的view中出现异常(就是在process_viewfunc函数中)或者返回的模板Response在render时发生的异常,会进入到这个方法中。但是需要注意的是,如果你在process_view中手动调用了func,就像我们上面做的那样,那就不会触发process_exception了。这个方法接收到异常之后,可以选择处理异常,然后返回一个含有异常信息的HttpResponse,或者直接返回None,不处理,这种情况Django会使用自己的异常模板。

  • Middleware中所有方法的执行顺序和说明

单元测试

TestCase中几个方法的说明

在Django中运行测试用例时,如果我们用的是sqlite数据库,Django会帮我们创建一个基于内存的测试数据库,用来测试。这意味着我们测试中所创建的数据,对我们的开发环境或者线上环境是没有影响的。

但是对于MySQL数据库,Django会直接用配置的数据库用户和密码创建一个test_student_db的数据库,用于测试,因此需要保证有建表和建库的权限。

你也可以定义测试用的数据库的名称,通过settings配置:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'USER': 'mydatabaseuser',
        'NAME': 'mydatabase',
        'TEST': {
            'NAME': 'mytestdatabase',  ## 这里配置
        },
    },
}

下面对需要用到的几个方法做下说明:

  • def setUp(self) - 如其名,用来初始化环境,包括创建初始化的数据,或者做一些其他的准备的工作。
  • def test_xxxx(self) - 方法后面的xxxx可以是任意的东西,以test_开头的方法,会被认为是需要测试的方法,跑测试时会被执行。每个需要被测试的方法是相互独立的。
  • def tearDown(self) - 跟setUp相对,用来清理测试环境和测试数据。在Django中,我们可以不关心这个。

Model层测试

这一层的测试,主要是来保证数据的写入和查询是可用的,同时也需要保证我们在Model层所提供的方法是符合预期的。比如我们的Model中实现了__unicode__方法,保证在Python2中运行时,直接print(或者直接在web界面展示) student对象时,能看到<Student: [name]>这样的字样,而不是Python中的object xxxxx这样东西。

我们来看下代码:

# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.test import TestCase

from .models import Student


class StudentTestCase(TestCase):
    def setUp(self):
        Student.objects.create(
            name='test',
            sex=1,
            email='333@dd.com',
            profession='程序员',
            qq='3333',
            phone='32222',
        )

    def test_create_and_unicode(self):
        student = Student.objects.create(
            name='test',
            sex=1,
            email='333@dd.com',
            profession='程序员',
            qq='3333',
            phone='32222',
        )
        student_name = '<Student: test>'
        self.assertEqual(unicode(student), student_name, 'student __unicode__ must be {}'.format(student_name))

    def test_filter(self):
        students = Student.objects.filter(name='test')
        self.assertEqual(students.count(), 1, 'only one is right')

setUp我们创建了一条数据用于测试。test_create_and_unicode用来测试数据创建和自定义的__unicode__方法有效,test_filter测试查询可用。

view层测试

这一层更多的是功能上的测试,也是我们一定要写的,功能上的可用是比什么都重要的事情。当然这事你可以通过手动浏览器访问来测试,但是如果你有几百个页面呢?

这部分的测试逻辑依赖Django提供的Django.test.Client对象。在上面的文件中tests.py中,我们增加下面两个函数:

	def test_get_index(self):
		client = Client()
		response = client.get('/')
		self.assertEqual(response.status_code, 200, 'status code must be 200!')

	def test_post_student(self):
		client = Client()
		data = dict(
			name='test_for_post',
			sex=1,
			email='333@dd.com',
			profession='程序员',
			qq='3333',
			phone='32222',
		)
		response = client.post('/', data)
		self.assertEqual(response.status_code, 302, 'status code must be 302!')

		response = client.get('/')
		self.assertTrue(b'test_for_post' in response.content, 'response content must contain `test_for_post`')

test_get_index的作用是请求首页,并且得到正确的响应——status code = 200,test_post_student的作用是提交数据,然后请求首页,检查数据是否存在。

总结

这一部分中的三个技能点的使用,有助于你更好的理解Django,但是如果你需要更多的掌握着三个部分的内容,需要进一步的实践才行。这是我们之后要做的事了。不过关于测试部分,不仅仅是Django方面的只是,测试是一个单独的话题/领域,有兴趣的话可以找更专业的书籍来看。

小试牛刀部分就到这,其中的代码建议读者手敲一遍,在自己的Linux或者Mac上运行一下,改改代码,再次运行。别怕麻烦,也别赶进度。我经常说,所谓捷径就是一步一个脚印,每步都能前进/提高。

下一部分开始,我们将进入正式的开发阶段,请系好安全带,握紧键盘,跟上。

posted @ 2021-02-23 14:01  zxhy哦  阅读(75)  评论(0编辑  收藏  举报
// 侧边栏目录 // https://blog-static.cnblogs.com/files/douzujun/marvin.nav.my1502.css