Djando小练习—云笔记项目

Djando小练习—云笔记项目

1、功能需求:

  用户可在该系统中记录自己的日常生活等信息,用户的数据将被安全的存储在云笔记平台;
  用户和用户之间数据为隔离数据(用户只有在登陆后才能使用相关笔记功能,并且只能查询自己的笔记内容。)
  只实现了最基础的功能,前端界面没有修饰。

2、功能拆解

  • 用户模块
    1. 注册:成为平台用户;
    2. 登陆:校验用户身份;
    3. 退出登录:退出登录状态;
  • 笔记模块
    1. 增:创建新的笔记;
    2. 删:删除笔记;
    3. 改:修改笔记;
    4. 查:查看笔记列表;

3、开始的配置

  1. 创建项目:django-admin startproject 项目名(kevin_note)

  2. 常规配置:

    • 禁止掉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’)],
  3. 数据库创建并配置:

    • 创建: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'
                                  }
                              }
      
  4. 启动项目:python manage.py runserver
    如果遇到端口占用情况,可以使用:sudo lsof -i:8000 查询出Django的进程id,执行kill -9 对应的Django进程id 将该进程关闭,再重新runserver;
    推荐:配置完之后检查一下runserver,有错的话能及时改;

4、开发:

  1. 创建并注册应用user:

    • 创建:在项目目录下python manage.py startapp 应用名(user)
    • 注册:在项目同名目录下的settings.py中INSTALLED_APPS中添加创建的应用名,也就是’user’
  2. 用户的注册功能:目的就是将用户的数据添加到服务器端的用户表里;

    1. 创建模板类:在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)
      
    2. 路由:/user/reg,
      使用分布式路由:在项目同名目录的urls.py文件中表明主路由:path(‘user/’,include(‘user.urls’)),
      在应用文件夹下手动创建urls.py文件,结构和主路由中的是一致的,添加子路由:path(‘reg’,views.reg_view)
      发get请求时拿到这个页面,在这个页面的form中写完信息之后POST提交,再提交给这个地址。
      一个路由、一个视图,既接get,又接post,get返回页面,post处理数据。

    3. 视图函数:

      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')
      
    4. 模板 :也就是前端界面,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>
      
    5. 优化:

      • 密码处理: 使用哈希算法,m = hashlib.md5() m.update(password_1.encode()) password_md5 = m.hexdigest()
      • 插入问题:有可能报错 - 重复插入【唯一索引注意并发写入问题】
      • 注册之后一天内免登陆:在settings.py中修改session的存储时间为一天:SESSION_COOKIE_AGE = 606024
  3. 用户登录:

    1. 路由: /url/login,在项目文件夹下的urls.py文件中添加path(‘login’,views.login_view)

    2. 视图函数:在项目文件夹下的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
      
    3. 模板位置:也就是前端界面,在项目文件夹下的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>
      
  4. 网站首页:

    1. 创建一个单独的应用负责首页:
      python manage.py startapp index
      在项目同名目录下的settings.py文件中注册该应用。
      在应用文件夹中手动创建一个templates文件夹,其下再创建一个与应用同名的文件夹index,防止找页面时出错。

    2. 路由:/index,直接在项目同名目录下的urls,py 文件中添加path(‘index’,index_views.index_view)

    3. 模板位置: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>
      
    4. 视图函数:
      在该应用下的views.py文件下写视图函数:

      def index_view(request):
      
          return render(request,'index/index.html')
      
  5. 退出登录:删除用户的session和cookies信息

    1. 路由:/user/logout,在user应用中的urls.py文件中添加path(‘logout’,views.logout_view)
    2. 视图函数: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
      
  6. 笔记部分:

    1. 基础:创建单独的应用note,
      python manage.py startapp note, 并在settings中注册。手动创建templates文件夹和urls.py 文件。
      采用分布式路由,在主路由文件中配置主路由:path(‘note/’,include(‘note.urls’))

    2. 列表页:

      1. 路由: /note/all_note,在自己创建的urls.py文件中添加路由:path(‘all_note’,views.list_view),

      2. 视图函数: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())
        
      3. 模板位置: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>
        
      4. 加入分页机制:

        1. 视图函数:

          @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())
          
        2. 模板:

          <!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>
          
    3. 修改笔记:
      在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跳转回列表页,形成一个回环。

      1. 路由:path(‘update_note/<int:note_id>’,views.update_note),

      2. 视图函数:

        @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')
        
      3. 模板:

        <!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>
        
    4. 删除笔记:
      这里采用伪删除,在创建数据库表时,增加一个字段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跳转回列表页即可。

      1. 路由:在note应用的urls.py文件添加path(‘delete_note’,views.delete_note),
      2. 视图函数:
        @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')
        
    5. 添加笔记:

      1. GET 返回界面,render一个界面,顺便在此应用的templates文件夹下的应用同名文件夹下创建html文件。创建页面的时候给“标题”和“内容”两个input 标签添加name属性,因为得在后台上拿到这两个数据。

      2. 配路由:在项目同名文件夹的路由文件urls.py中添加主路由:path(‘note/’,include(‘note.urls’)),
        在应用文件夹下,自己创建的urls.py文件中添加子路由:path(‘add_note’,views.add_note)。
        render完页面、做好了页面、添加完路由之后,就可以先使用该路由看看,能不能正常的查看该页面。

      3. 检查用户登录状态:使用装饰器检查用户登录状态是否有效。

      4. 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跳转回列表页即可。

      5. 视图函数:

        @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')
        
      6. 模板:

        <!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>
        
  7. 导出笔记

    1. 路由:path(‘output_csv’,views.output_csv)

    2. 模板:在列表页的html文件中添加:

      <p>
              <a href="/note/output_csv?page={{ current_page.number }}">导出</a>
      </p>
      
    3. 视图函数:

      @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
      
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值