Django+其他 学习笔记

最近因为一些原因。用Django写了个web平台。这里把一些东西记录一下。
首先,因为我不是做web渗透的,更不是web开发的。所以没有系统的学过css,js,http等等的内容。很惭愧,php也是不会的。所以只能用Django框架写web,因为毕竟python还是知道不少的。由于是初学,最近几年里大概也不打算继续深入研究。所以这次的笔记只是从一个初学者的角度来记录一些开发中的历程。其中肯定有不少错误和不求甚解的地方。
项目地址:https://github.com/zh-explorer/lock_server

技术栈

首先为什么选择django呢?原因很简单,写东西之前问大佬:“大佬,我打算写个web站。你看我这种菜鸡选啥好啊?”大佬答:“Django。”所以我就用Django写了。
好吧,其实原因是Django开发确实比较方便。我也用Flask写过一些东西。对比下来,感觉我这种新手想要快速开发东西还是需要Django这种大而全的框架来的。这样能节省不少的代码量。这次的需求里需要一个完善的后台管理,直接用Django的admin能剩下不少事情。所以这次用的Django2.0和python3.5开发。
后天框架确定了,然后是前端。很惭愧,我前端的技术还不如我写后台,所以又只能去问大佬了。大佬推荐了AdminLET,说不缺啥东西直接去里面抄就好了。所以前端框架就这样确定了。
最后是数据库,这次的东西毕竟是写着玩的。所以数据库就直接sqlite了。

start

装Django什么就不介绍了。因为直接用的pycharm,初始化直接Django project和venv全部解决。
其中项目名目录下的文件有3个文件。urls.py主要作用是定义项目的路由路径。和Flask使用装饰函数定义路由不同。我觉得Django这种在同一文件夹下定义路由的方法更加统一和便于查询修改,
然后settings.py里面就是各种项目的全局设置了,这个文件里面内容暂且不提。不过和老司机们讨论之后,都觉得这个setting.py最好不要随便传github上,现在已知的比较敏感的是SECRET_KEY。泄露之后会有一定的危害。
wsgi.py这个东西的作用我也不太清楚,只知道这个是连接web服务器用的。
然后还有一个manager.py,这个文件主要作用应该是提供各种shell命令。比如建立app,打开Django shell,迁移数据库等,我对各种命令研究不多。

第一个页面

Django下面通过app来隔离站点下面的各种组件,请用

python manager.py startapp appname

来建立一个新的app,命令会初始化app,并写创建新的app目录和一个常用的文件。
虽然我也看到一些教程不创建app直接在项目下直接写代码的,不过我感觉这些比较邪道。
创建好的app及得添加到settings.py的INSTALLED_APPS里。默认记得是不会添加的,别忘记了。
然后就是写测试页面了。一般来说所有的页面都是写到views.py文件里面的,虽说写别的地方也不会有错,不过为了不让其他和你一起写项目的人打你,还是老实写views下面吧。
一个常见的最简单的示例如下

1
2
3
4
from django.http import HttpResponse

def hello(request):
return HttpResponse("Hello world ! ")

一个hello函数通常就对应了一个可以访问到的页面。当然,还有一种用类来定义view的方式,不过我没怎么用过。这个函数的名字随意,不过我觉得和页面同名是个比较好的选择。
这个函数接受一个参数,一般叫request。这个参数基本上包含了一个页面请求以及其上下文一切内容了,包括cookie,get/post参数,session,请求的用户等等信息。基本上我碰到的有

request.user

这个是和Django用户模块联用的。代表了发起请求的用户(包括未登录的匿名用户)。用户的确认和查找细节都被隐藏了。

request.GET/POST

请求的post和get内容。是一个字典

request.session

也是个字典,里面是session。至于session的生成和储存,查找等,Django都包办了。

request.method

这个是请求方法,目前碰到的只有POST和GET。没有任何参数的算GET

函数接受request作为上下文参数。做一系列的处理。让返回一个页面。或者说一个respons。当然可以像这里这样用HttpResponse直接返回一些内容,也可以通过Django提供的各种函数返回json,html等数据,或者返回错误信息。当然最常用的是通过模板引擎渲染一个动态页面返回。

写完页面之后就是定义页面路由了。页面路由在Usrls.py中定义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.conf.urls import url
from django.contrib import admin
from user_interface import views as user_interface_view
from user import views as user_view
from pi_manager import views as pi_view
from django.contrib.auth import views

urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', user_interface_view.index, name="index"),
url(r'^login$', views.login, {'template_name': 'login.html'}, name='login'),
url(r"^register$", user_view.register, name='register'),
url(r'^index/unlock$', user_interface_view.ajax_unlock, name='ajax_unlock'),
url(r'^index/pi_table$', user_interface_view.ajax_pi_table, name='pi_table'),
url(r'^index/qrcode$', user_interface_view.qr_code, name='qrcode'),
url(r'^pi/verify$', pi_view.verify, name='verify'),
url(r'^pi/alive$', pi_view.alive, name='alive'),
]

这个是我项目中的一些url路由的定义。全部定义都写在urlpattterns里面。都是url函数的返回值。第一个参数是url的地址,可以看到是正则表达式匹配的地址,可以将一类的url都指向同一个view。在新版的Django里,还有在参数中用<int:num>这种写法来获取用户请求url中的特定部分。具体因为没有用到。就没有深入研究。

第二个参数是view函数。虽然传入字符串Django也能找到view对应的处理代码。不过好像Django还是比较推荐直接传入函数的。因为函数来自各个不同的view,这里将各个views通过重命名导入了进来。

其他还有许多可选的参数,比如name这个参数可以指定url路由的名字。通过这个名字也可以找到view的位置。在模板引擎等地方用的到。再比如login这条路由中,因为后台处理程序是Django自带的login view。我传入了我自己写的模板的名字,可以改变前端的样式。

用户控制

Django提供了内置的用户功能。虽然其自带的用户能够满足大多数通用的情况。但是总会有有一些奇奇怪怪的需求需要自定义。这就需要对django的用户进行一定的扩展。这里我对用户做了如下的扩展。

1.扩展字段

有很多种方法可以扩展一个用户的字段。各有各的优劣。这里用了一种最简单粗暴的方法,就是继承了from django.contrib.auth.models中的AbstractUser类。这个类其实就是整个user的完整实现了。其实自带的User类中并有代码。
我们通过继承他,就可以扩展需要的字段

1
2
3
4
5
6
7
8
9
10
11
from django.db import models
from django.contrib.auth.models import AbstractUser
from django.conf import settings


# Create your models here.
class User(AbstractUser):
open_permisson = models.ManyToManyField("pi_manager.Point", blank=True, db_table="point_open_permisson",
related_name='open_users')
auth_permisson = models.ManyToManyField("pi_manager.Point", blank=True, db_table="point_auth_permisson",
related_name='auth_users')

这里我扩展了两个字段open_permissonauth_permisson。这两个字段都是ManyToMany类型。具体的介绍放在后面的数据库部分。
注意扩展之后的user模型必须填入settings.py中的AUTH_USER_MODEL字段。这样才让让Django用自定义User表来代替默认的。其他用户要使用User模型,也最好使用这个字段中的值。

2.重写登陆页面

因为Django的默认登陆页面实在是不好看。所以需要重写。重写的方法有很多,包括直接丢掉整个自带的login部分。直接自己重写,因为使用django.contrib.auth中的loginlogout函数传入request就可以直接重写这部分了。
不过这里我还是复用的login的后台部分。只是将传入的前端模板引擎改成自己的而已。只要表单的post地址和传入的数据正确就没问题。我这里只传入的usernamepassword。具体方法在前面usrls介绍中已经说明。

3.重写注册页面

本来重写注册页面和重写登陆页面的过程是差不多了。不过在查资料的途中发现了另外一个好玩的写法。就拿出来试了试。
这里注册我重写的前台和后台,然后扩展了Djang自带的注册form。发现也是挺不错的选择。
下面是我的代码,可以看到当请求为GET的时候,就直接无参数实例化RegisterForm表单返回,表示一张空表单。然后等注册信息post过来之后,用POST信息来实例化RegisterForm类。然后用is_valid方法校验表单是否正确。正确直接调用save方法写入数据库,重定向到首页,错误则返回空表单和错误信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from django.shortcuts import render, redirect
from .foms import RegisterForm

def register(request):
context = {"error": {}}
if request.method == 'POST':
form = RegisterForm(request.POST)
print(request.POST)
if form.is_valid():
form.save()
return redirect("/")
else:
for key in form.errors:
context['error'][key] = str(form.errors[key])
else:
form = RegisterForm()
context['form'] = form
return render(request, "register.html", context=context)

RegisterForm的内容很简单,继承了默认的UserCreationForm表单。然后通过Meta改写了用户表为我们自己的用户表,并且限定注册时候需要username和email两个字段。

1
2
3
4
5
6
from django.contrib.auth.forms import UserCreationForm
from .models import User
class RegisterForm(UserCreationForm):
class Meta(UserCreationForm.Meta):
model = User
fields = ("username", "email")

然后来看看前端模板引擎中怎么渲染这个表单。这里我因为对表单了解不够深入,所以个个元素的名字都是直接用debug调试找的。

1
2
3
4
5
6
7
8
9
10
11
12
<form action="{% url 'register' %}" method="post">
{% csrf_token %}
<input type="text" name="username" id="FullName">
{{ form.errors.username }}
<input type="email" name="email" id="Email">
{{ form.errors.email }}
<input type="password" name="password1" id="Password">
{{ form.errors.password1 }}
<input type="password" id="Repassword" name="password2">
{{ form.errors.password2 }}
<button type="submit" class="btn btn-primary btn-block btn-flat">Register</button>
</form>

当然,实际的html中还有大量的前端代码和内容校验,这里隐去了。其实input的name也应该用模板引擎来替换的,不过我直接填进去了。这样其实不是个好习惯。不过我没找到一个好的只显示指定字段的方法,Django模板好像只能用for迭代出所有的字段。所以只能这样了。这里的form.error就是表单校验失败之后传过来的提示信息。

4.重写注销页面

好吧,这里其实是我犯懒癌了。默认Django的注销页面是Django Admin的注销页面。所以不能用。但是又不想再写一个注销页面的模板(而且也不会)。所以这里偷了一个懒。注销逻辑完全是自己实现的。在urls.py中注册一个logout的路由。然后在views.py中我这样写

1
2
3
4
5
from django.shortcuts import render, redirect
from django.contrib.auth import logout as user_logout
def logout(request):
user_logout(request)
return HttpResponseRedirect('/')

直接用logout函数注销,然后直接重定向到首页上。这样就直接把登出页面忽略掉了。

5.访问控制

这个部分我其实并没有重写。直接用的Django自带的逻辑。主要是使用from django.contrib.auth.decorators中的login_required装饰函数来控制一些页面必须登录之后访问。虽然这个函数是有额外参数的。不过这里没有用到。就没有深究。

好吧,东西写的有点多了。这里分个段。剩下的内容之后再写。