Djando小练习—云笔记项目
1、功能需求:
用户可在该系统中记录自己的日常生活等信息,用户的数据将被安全的存储在云笔记平台;
用户和用户之间数据为隔离数据(用户只有在登陆后才能使用相关笔记功能,并且只能查询自己的笔记内容。)
只实现了最基础的功能,前端界面没有修饰。
2、功能拆解
- 用户模块
- 注册:成为平台用户;
- 登陆:校验用户身份;
- 退出登录:退出登录状态;
- 笔记模块
- 增:创建新的笔记;
- 删:删除笔记;
- 改:修改笔记;
- 查:查看笔记列表;
3、开始的配置
-
创建项目:django-admin startproject 项目名(kevin_note)
-
常规配置:
- 禁止掉csrf,MIDDLEWARE中的 ‘django.middleware.csrf.CsrfViewMiddleware’,否则POST会出现403问题:
- 语言更改:LANGUAGE_CODE = ‘zh-Hans’
- 时区更改:TIME_ZONE = ‘Asia/Shanghai’
- 模板位置:修改settings.py文件中TEMPLATES中的’DIRS’: [os.path.join(BASE_DIR,‘templates’)],
-
数据库创建并配置:
- 创建:mysql -uroot -p进入MySQL,使用create database 数据库名(kevin_note) default charset utf8创建数据库;
- 配置:修改项目同名目录下的settings.py中的DATABASES:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'kevin_note', 'USER': 'root', 'PASSWORD': '你自己的密码', 'HOST': '127.0.0.1', 'PORT': '3306' } }
-
启动项目:python manage.py runserver
如果遇到端口占用情况,可以使用:sudo lsof -i:8000 查询出Django的进程id,执行kill -9 对应的Django进程id 将该进程关闭,再重新runserver;
推荐:配置完之后检查一下runserver,有错的话能及时改;
4、开发:
-
创建并注册应用user:
- 创建:在项目目录下python manage.py startapp 应用名(user)
- 注册:在项目同名目录下的settings.py中INSTALLED_APPS中添加创建的应用名,也就是’user’
-
用户的注册功能:目的就是将用户的数据添加到服务器端的用户表里;
-
创建模板类:在user应用的models文件中创建模板类之后,要记得更新数据库:
python manage.py makemigrations和python manage.py migrate,
更新完了可以在mysql命令行中检查:use kevin_note(数据库名); show tables; desc 表名;class User(models.Model): username = models.CharField("用户名", max_length=30, unique=True) password = models.CharField("密码", max_length=32) created_time = models.DateTimeField("创建时间", auto_now_add=True) updated_time = models.DateTimeField("更新时间", auto_now=True) # 调整django shell中print的格式 def __str__(self): return 'username %s'%(self.username)
-
路由:/user/reg,
使用分布式路由:在项目同名目录的urls.py文件中表明主路由:path(‘user/’,include(‘user.urls’)),
在应用文件夹下手动创建urls.py文件,结构和主路由中的是一致的,添加子路由:path(‘reg’,views.reg_view)
发get请求时拿到这个页面,在这个页面的form中写完信息之后POST提交,再提交给这个地址。
一个路由、一个视图,既接get,又接post,get返回页面,post处理数据。 -
视图函数:
def reg_view(request): #注册 #GET 返回页面 if request.method == 'GET': return render(request,'user/register.html') #POST 处理数据 elif request.method == 'POST': #拿到表单提交过来的数据 username = request.POST['username'] password_1 = request.POST['password_1'] password_2 = request.POST['password_2'] # 1、两个密码是否一致。 if password_1 != password_2: return HttpResponse('两次密码输入不一致!') #通过哈希对密码进行加密处理:定长输出、不可逆、输入改变的话输出变化很大 m = hashlib.md5() m.update(password_1.encode()) password_md5 = m.hexdigest() # 2、当前用户名是否已经被注册过 # 涉及到模型类的orm操作,要记得在上边import old_users = User.objects.filter(username = username) if old_users: return HttpResponse('用户名已注册') # 3、上面两个都过了的话,就插入数据,暂时先明文处理密码 try: user = User.objects.create(username = username,password = password_md5) except Exception as e: #有可能报错 - 重复插入【唯一索引注意并发写入问题】 print('--create user error %s'%(e)) return HttpResponse('用户名已注册') #4、注册之后一天免登陆 request.session['username'] = username request.session['user_id'] = user.id #在settings.py中修改session的存储时间为一天:SESSION_COOKIE_AGE = 60*60*24 #TODO 修改session存储时间为一天 return HttpResponseRedirect('/index')
-
模板 :也就是前端界面,user/templates/user/register.html;在应用目录下创建文件夹templates,在此之下再创建一个user文件夹。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>注册</title> </head> <body> {#action就是数据要提交到哪里去,因为涉及到密码,所以提交的方法是post#} <form action="/user/reg" method="post"> <P> {#因为这个数据要提交上来,所以要给个name#} 用户名:<input type="text" name="username"> </P> <p> 密码:<input type="text" name="password_1"> </p> <p> 确认密码:<input type="text" name="password_2"> </p> <p> <input type="submit" value="注册"> </p> </form> </body> </html>
-
优化:
- 密码处理: 使用哈希算法,m = hashlib.md5() m.update(password_1.encode()) password_md5 = m.hexdigest()
- 插入问题:有可能报错 - 重复插入【唯一索引注意并发写入问题】
- 注册之后一天内免登陆:在settings.py中修改session的存储时间为一天:SESSION_COOKIE_AGE = 606024
-
-
用户登录:
-
路由: /url/login,在项目文件夹下的urls.py文件中添加path(‘login’,views.login_view)
-
视图函数:在项目文件夹下的views.py 文件中编写login_view 函数,也是分get和post两部分进行处理,get负责返回页面,post进行数据处理。
def login_view(request): # GET 返回页面 if request.method == 'GET': #功能需求:检查登录状态,如果之前登陆了,还在有效期内,那么就不显示登录界面了,而是显示‘已登录’ #session是存一天,cookies存三天,所以先检查session,session中没有用户信息的话再检查cookies if request.session.get('username') and request.session.get('user_id'): # return HttpResponse('已登录') return HttpResponseRedirect('/index') #如果session没进去,那就检查一下cookie中是否有username和user_id #把这两个数据取出来是因为,如果cookies中有的话,就回写到session中。 cookies_username = request.COOKIES.get('username') cookies_user_id = request.COOKIES.get('user_id') if cookies_username and cookies_user_id: #回写session request.session['username'] = cookies_username request.session['user_id'] = cookies_user_id # return HttpResponse('已登录') return HttpResponseRedirect('/index') return render(request, 'user/login.html') #POST 处理数据 elif request.method == 'POST': #拿到提交的数据 username = request.POST['username'] password = request.POST['password'] #根据用户名从数据库中拿到对应的信息 try: user = User.objects.get(username=username) except Exception as e: print('--login user error %s'%(e)) return HttpResponse('用户或密码错误!') #进行密码比对 m = hashlib.md5() m.update(password.encode()) #必须得encode,因为需要是字节串 password_md5 = m.hexdigest() if user.password != password_md5: return HttpResponse('用户名或密码错误!') #用户和密码都对了,则记录会话状态 request.session['username'] = username request.session['user_id'] = user.id # 判断用户是否点选了“记住用户名”,没打勾的时候remember没有值,打了勾之后请求中多了个remember='on' # 没打钩,只在session层面保存,打了钩,在cookie层面也保存一份username和user_id,保存三天 resp = HttpResponseRedirect('/index') if 'remembet' in request.POST: resp.set_cookie('username', username, 3600*24*3) resp.set_cookie('user_id', user.id) return resp
-
模板位置:也就是前端界面,在项目文件夹下的templates下的,与应用同名的文件夹下创建html文件,这是为了防止路由出错。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>登录</title> </head> <body> <form action="/user/login" method="post"> <P> {#因为这个数据要提交上来,所以要给个name#} 用户名:<input type="text" name="username"> </P> <p> 密码:<input type="text" name="password"> </p> <p> <input type="checkbox" name="remembet">记住用户名 </p> <p> <input type="submit" value="登录"> </p> </form> </body> </html>
-
-
网站首页:
-
创建一个单独的应用负责首页:
python manage.py startapp index
在项目同名目录下的settings.py文件中注册该应用。
在应用文件夹中手动创建一个templates文件夹,其下再创建一个与应用同名的文件夹index,防止找页面时出错。
在 -
路由:/index,直接在项目同名目录下的urls,py 文件中添加path(‘index’,index_views.index_view)
-
模板位置:templates/index/index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> </head> <body> {% if request.session.username %} <p> 欢迎{{ request.session.username }} </p> <p> <a href="/user/logout">退出登录</a> </p> <p> <a href="/note/all_note">进入我的笔记</a> </p> {% else %} {% if request.COOKIES.username %} <p> 欢迎{{ request.COOKIES.username }} </p> <p> <a href="/user/logout">退出登录</a> </p> <p> <a href="/note/all_note">进入我的笔记</a> </p> {% else %} <p> <a href="/user/login">登录</a> </p> <p> <a href="/user/reg">注册</a> </p> {% endif %} {% endif %} </body> </html>
-
视图函数:
在该应用下的views.py文件下写视图函数:def index_view(request): return render(request,'index/index.html')
-
-
退出登录:删除用户的session和cookies信息
- 路由:/user/logout,在user应用中的urls.py文件中添加path(‘logout’,views.logout_view)
- 视图函数:logout_view
def logout_view(request): #删除session和cookies if 'username' in request.session: del request.session['username'] if 'user_id' in request.session: del request.session['user_id'] resp = HttpResponseRedirect('/index') if 'username' in request.COOKIES: resp.delete_cookie('username') if 'user_id' in request.COOKIES: resp.delete_cookie('user_id') #跳转到首页 return resp
-
笔记部分:
-
基础:创建单独的应用note,
python manage.py startapp note, 并在settings中注册。手动创建templates文件夹和urls.py 文件。
采用分布式路由,在主路由文件中配置主路由:path(‘note/’,include(‘note.urls’)) -
列表页:
-
路由: /note/all_note,在自己创建的urls.py文件中添加路由:path(‘all_note’,views.list_view),
-
视图函数:list_view,在note应用下的views.py文件中编写视图函数。
注意这里用到了一对多的映射关系。@check_login def list_view(request): #User.objects.get(username=request.session['username'])相当于拿到username对应的User对象, # User对象.note_set.filter(is_active=True)是根据一对多的映射关系,拿到该User对象对应的多个note。 all_note = User.objects.get(username=request.session['username']).note_set.filter(is_active=True) # all_note = User.objects.all() return render(request,'note/list_note.html',locals())
-
模板位置:templates/note/list_note.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>列表页</title> </head> <body> <div> <p> 欢迎{{ request.session.username }} </p> <p> <a href="/note/add_note">添加新笔记</a> </p> <p> <a href="/note/output_csv?page={{ current_page.number }}">导出</a> </p> <p> <a href="/index">返回首页</a> </p> </div> <table border="1"> <tr> <th>id</th> <th>title</th> <th>created_time</th> <th>updated_time</th> <th>op</th> </tr> {% for note in all_note %} <tr> <td>{{ note.id }}</td> <td>{{ note.title }}</td> <td>{{ note.created_time }}</td> <td>{{ note.updated_time }}</td> <td> {# 通过这里的链接,把note的id传给url的path转换器,views中的update_note通过path转换器获得note_id,从而找到对应的note进行修改#} <a href="/note/update_note/{{ note.id }}">修改</a> <a href="/note/delete_note?note_id={{ note.id }}">删除</a> </td> </tr> {% endfor %} </table> </body> </html>
-
加入分页机制:
-
视图函数:
@check_login def list_view(request): #User.objects.get(username=request.session['username'])相当于拿到username对应的User对象, # User对象.note_set.filter(is_active=True)是根据一对多的映射关系,拿到该User对象对应的多个note。 all_note = User.objects.get(username=request.session['username']).note_set.filter(is_active=True) # all_note = User.objects.all() # 拿到当前的页码,没有的话就默认是1 page_num = request.GET.get('page',1) # 初始化paginator # 第一个参数是总的数据,第二个参数是每页显示的条数 paginator = Paginator(all_note, 2) # 初始化具体页码对应的page对象,管具体的每一页 current_page = paginator.page(int(page_num)) return render(request,'note/list_note.html',locals())
-
模板:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>列表页</title> </head> <body> <div> <p> 欢迎{{ request.session.username }} </p> <p> <a href="/note/add_note">添加新笔记</a> </p> <p> <a href="/index">返回首页</a> </p> </div> <table border="1"> <tr> <th>id</th> <th>title</th> <th>created_time</th> <th>updated_time</th> <th>op</th> </tr> {% for note in current_page %} <tr> <td>{{ note.id }}</td> <td>{{ note.title }}</td> <td>{{ note.created_time }}</td> <td>{{ note.updated_time }}</td> <td> {# 通过这里的链接,把note的id传给url的path转换器,views中的update_note通过path转换器获得note_id,从而找到对应的note进行修改#} <a href="/note/update_note/{{ note.id }}">修改</a> <a href="/note/delete_note?note_id={{ note.id }}">删除</a> </td> </tr> {% endfor %} </table> {% if current_page.has_previous %} <a href="/note/all_note?page={{ current_page.previous_page_number }}">上一页</a> {% else %} 上一页 {% endif %} {#当前页显示页码,非当前页显示a标签#} {% for page_num in paginator.page_range %} {% if page_num == current_page.number %} {{ page_num }} {% else %} <a href="/note/all_note?page={{ page_num }}">{{ page_num }}</a> {% endif %} {% endfor %} {% if current_page.has_next %} <a href="/note/all_note?page={{ current_page.next_page_number }}">下一页</a> {% else %} 下一页 {% endif %} </body> </html>
-
-
-
修改笔记:
在views.py的list_view函数中,render页面的时候传给页面all_note这个参数,也就是该用户的全部笔记。
在列表页中,“修改”字段对应的url是href="/note/update_note/{{ note.id }}",这就可以把note的id传给url,再通过urls.py中的path转换器,就可以将要修改的note的id传给该path中的update_note函数,从而根据该参数拿到对应的note对象。
点“修改”的时候,发送的是get请求,这是进行的操作是render一个页面,把内容修改之后,点击“更新”,这里发送的是post请求,根据在html页面中,input标签中的name="title"属性,就可以拿到修改完的内容,进而修改note对象中对应的值。改完了记得调用save函数,最后在302跳转回列表页,形成一个回环。-
路由:path(‘update_note/<int:note_id>’,views.update_note),
-
视图函数:
@check_login #通过path转换器获得url中的note的id def update_note(request,note_id): #获取笔记的信息 try: note = Note.objects.get(id = note_id,is_active = True) except Exception as e: print('--update note error is %s' % (e)) return HttpResponse('--The note is not existed') # GET 返回页面 if request.method == 'GET': return render(request,'note/update_note.html',locals()) # post 处理数据 elif request.method == 'POST': #拿到修改页面中,表单提交过来的数据 title = request.POST['title'] content = request.POST['content'] #改 note.title = title note.content = content #保存 note.save() #跳转到列表页,形成一个回环 return HttpResponseRedirect('/note/all_note')
-
模板:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>修改笔记</title> </head> <body> {#action是负责表单的这个数据具体要提交到哪里去,还提交给这个地址#} <form action="/note/update_note/{{ note_id }}" method="post"> <p> title <input type="text" name="title" value="{{ note.title }}" > </p> <p> content <input type="text" name="content" value="{{ note.content }}" > </p> <p> <input type="submit" value="更新"> </p> </form> </body> </html>
-
-
删除笔记:
这里采用伪删除,在创建数据库表时,增加一个字段is_active,列表页显示的时候,filter出is_active为True的记录进行显示。要删除笔记时,就将该id对应的记录的is_active字段设置为False。
“删除”字段对应的a标签中是href="/note/delete_note?note_id={{ note.id }}",所以可以直接通过request.GET.get(‘note_id’)拿到要删除的note的id。
根据这个id拿到对应的note对象,note = Note.objects.get(id = note_id,is_active=True),修改该对象的is_active字段进行伪删除。
因为get方法比较霸道(0个或者多个都会报错,所以要在try-except中调用)。最后再302跳转回列表页即可。- 路由:在note应用的urls.py文件添加path(‘delete_note’,views.delete_note),
- 视图函数:
@check_login def delete_note(request): #通过url中的参数拿到note的id note_id = request.GET.get('note_id') if not note_id: return HttpResponse('---请求异常') #根据note_id拿到对应的、有效的note try: note = Note.objects.get(id = note_id,is_active=True) except Exception as e: print('---delete note get error %s' % (e)) return HttpResponse('--The note id is error') # 伪删除,修改该note的is_active字段 note.is_active = False #保存 note.save() #跳转到列表页,形成一个回环 return HttpResponseRedirect('/note/all_note')
-
添加笔记:
-
GET 返回界面,render一个界面,顺便在此应用的templates文件夹下的应用同名文件夹下创建html文件。创建页面的时候给“标题”和“内容”两个input 标签添加name属性,因为得在后台上拿到这两个数据。
-
配路由:在项目同名文件夹的路由文件urls.py中添加主路由:path(‘note/’,include(‘note.urls’)),
在应用文件夹下,自己创建的urls.py文件中添加子路由:path(‘add_note’,views.add_note)。
render完页面、做好了页面、添加完路由之后,就可以先使用该路由看看,能不能正常的查看该页面。 -
检查用户登录状态:使用装饰器检查用户登录状态是否有效。
-
POST处理数据:
用session拿到用户的id,也就是request.session[‘user_id’],因为修改时提交的是POST请求,所以通过request.POST[’’]拿到title和content的数据。
添加笔记就是在数据库中创建一条记录:Note.objects.create(title=title,content=content,user_id=user_id,is_active=True) 。
创建完了之后302跳转回列表页即可。 -
视图函数:
@check_login def add_note(request): #Get 返回页面 if request.method == 'GET': return render(request,'note/add_note.html') #post 处理数据 elif request.method == 'POST': #通过session拿到用户的id,为创建笔记的记录用到外键时做准备 user_id = request.session['user_id'] #拿到提交的数据 title = request.POST['title'] content = request.POST['content'] #创建一条笔记,默认值is_active=True得带着 Note.objects.create(title=title,content=content,user_id=user_id,is_active=True) #添加完之后302跳转到列表页,形成一个回环 return HttpResponseRedirect('/note/all_note')
-
模板:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>添加新笔记</title> </head> <body> <form action="/note/add_note" method="post"> <p> 标题:<input type="text" name="title"> <input type="submit" value="保存"> </p> <p> <textarea cols="30" rows="10" name="content"></textarea> </p> </form> </body> </html>
-
-
-
导出笔记
-
路由:path(‘output_csv’,views.output_csv)
-
模板:在列表页的html文件中添加:
<p> <a href="/note/output_csv?page={{ current_page.number }}">导出</a> </p>
-
视图函数:
@check_login def output_csv(request): all_note = User.objects.get(username=request.session['username']).note_set.filter(is_active=True) # all_note = User.objects.all() # 拿到当前的页码,没有的话就默认是1 page_num = request.GET.get('page', 1) # 初始化paginator # 第一个参数是总的数据,第二个参数是每页显示的条数 paginator = Paginator(all_note, 2) # 初始化具体页码对应的page对象,管具体的每一页 current_page = paginator.page(int(page_num)) response = HttpResponse(content_type='test/csv') response['Content-Disposition'] = 'attachment;filename="myNoteOnPage%s.csv"'%(page_num) writer = csv.writer(response) writer.writerow(['id','title','content','created_time','updated_time']) for note in current_page: writer.writerow([note.id,note.title,note.content,note.created_time,note.updated_time]) return response
-