본문 바로가기
웹 개발/Back End

[Python Flask] - 파일 서버 만들기

by L3m0n S0ju 2021. 11. 13.

 

 

위와 같이 간단한 파일 서버를 구현했습니다. 파일 이름을 클릭하면 다운로드되고 삭제를 누르면 파일이 삭제됩니다. 파일을 선택하고 제출을 누르면 파일이 업로드됩니다. 코드는 아래와 같습니다. 플라스크와 신사2 템플릿 그리고 자바스크립트를 사용하여 코딩했습니다.

 

 

 

 

 

app.py


from flask import Flask
from flask import render_template
from flask import request
from flask import redirect, url_for
from flask import send_file    # 파일 다운로드를 위한 모듈

from flask_wtf import FlaskForm    # 플라스크 폼
from flask_wtf.file import FileField, FileRequired    # FileField -> 파일 입력하는 필드, FileRequired -> 파일 유효성 검사

import os
import datetime    
import time

app = Flask(__name__)
app.config['SECRET_KEY'] = "secret"    # CSRF 공격 방지 토큰

class FileForm(FlaskForm):    # 파일 유효성 검사하는 폼 클래스 생성
    files = FileField(validators=[FileRequired('업로드할 파일을 넣어주세요')])

def stamp2real(stamp):
    return datetime.datetime.fromtimestamp(stamp)

def info(filename):
    ctime = os.path.getctime(filename) # 만든시간
    mtime = os.path.getmtime(filename) # 수정시간
    size = os.path.getsize(filename) # 파일크기 (단위: bytes)
    return ctime, mtime, size

@app.errorhandler(404) # 404에러 처리
def page_not_found(error):
     return render_template('deny.html', pwd=os.getcwd() + "\\uploads"), 404

@app.route('/', methods = ['GET', 'POST'])
def main_page():
    form = FileForm()    # 파일 유효성 폼 클래스 인스턴스 생성
    if form.validate_on_submit():    # 양식 유효성 검사 + POST인 경우
        f = form.files.data
        f.save('./uploads/' + f.filename)     # 파일 업로드
        return render_template('check.html', pwd=os.getcwd() + "\\uploads")

    filelist = os.listdir('./uploads')    # 파일 리스트 가져오기
    infos = []
    for name in filelist:
        fileinfo = {}
        ctime, mtime, size = info('./uploads/' + name)
        fileinfo["name"] = name
        fileinfo["create"] = stamp2real(ctime)
        fileinfo["modify"] = stamp2real(mtime)
        if(size <= 1000000):
            fileinfo["size"] = "%.2f KB" % (size / 1024)
        else:
            fileinfo["size"] = "%.2f MB" % (size / (1024.0 * 1024.0))
        infos.append(fileinfo)    # 각 파일 정보를 모두 리스트로 입력
    return render_template('home.html', form=form,
                            pwd=os.getcwd() + "\\uploads", infos = infos)

@app.route('/down/<path:filename>')
def down_page(filename):
    return send_file('uploads/' + filename,    # 다운로드할 파일 uploads/ 경로에서 가져와서 반환 
                    attachment_filename = filename,
                    as_attachment=True)

@app.route('/del/<path:filename>')
def delete_page(filename):
   os.remove('uploads/'+ filename)    # 파일 삭제
   return "<script>location.href = \"/\";</script>"    # 루트 페이지로 이동

if __name__ == '__main__':
    #서버 실행
    app.run()

 

 

 

 

 

 

 

 

 

 

home.html


{% extends "base.html" %}
{% block content %}
  <body>
    <div class="container text-center">
      <div class="row">
        <div class="col-12">
          <form action="/" method="POST" enctype="multipart/form-data">
            {{ form.csrf_token() }}    <!-- CSRF 토큰 값 가져옴 -->
            {{ form.files(size=20) }}    <!-- 파일 선택 창 파일 폼 출력 -->
            <input type="submit" class="btn btn-outline-dark"/>
          </form>
          </div>
        </div>
    </div>
    <br>
    <div class="col-md-auto ml-5 mr-5">
      <table class="table table-sm table-responsive-md table-hover text-center">    <!-- 파일 테이블 출력 -->
        <thead>
          <tr>
            <th></th>
            <th>이름</th>
            <th></th>
            <th>만든 날짜</th>
            <th>마지막으로 수정한 날짜</th>
            <th>파일 크기</th>
          </tr>
        </thead>

        <tbody>
          {% for info in infos %}
          <tr>
            {% if info['name'].split('.')[1].lower() == "png"
              or info['name'].split('.')[1].lower() == "jpg"
              or info['name'].split('.')[1].lower() == "jpeg"
              or info['name'].split('.')[1].lower() == "gif" %}
              <td class="badge badge-pill badge-success align-middle">{{info['name'].split(".")[1].lower()}}</td>    # 초록색 확장자 이미지

            {% elif info['name'].split('.')[1].lower() == "7z"
              or info['name'].split('.')[1].lower() == "zip" %}
              <td class="badge badge-pill badge-primary align-middle">{{info['name'].split(".")[1].lower()}}</td>

            {% else %}
            <td class="badge badge-pill badge-light align-middle">{{info['name'].split(".")[1].lower()}}</td>
            {% endif %}

            <td class="text-left"><a href="down/{{ info['name']}}" name="download" class="text-reset">{{ info['name'] }}</a></td>    <!-- 다운로드 하이퍼링크 -->
            <td><a href="del/{{ info['name'] }}" name="delete">[삭제]</a></td>    <!-- 삭제 하이퍼링크 -->
            <td>{{ info['create'].strftime("%Y-%m-%d") }}</td>    # 만든 날짜 출력
            <td>{{ info['modify'].strftime("%Y-%m-%d") }}</td>    # 수정 날짜 출력
            <td>{{ info['size'] }}</td>
          </tr>
          {% endfor %}
        </tbody>
 
      </table>
    </div>
  </body>
{% endblock %}

 

 

 

 

 

 

 

 

 

 

base.html


<!DOCTYPE html>
<html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Server</title>
        <link rel="icon" href="/static/images/favicon.png">    <!-- 파비콘 이미지 설정 -->
 
 
<!-- CDN으로 불러오기 -->
        <!-- Latest compiled and minified CSS -->
        <!-- jQuery library -->
        <!-- Popper JS -->
        <!-- Latest compiled JavaScript -->

        <!-- fontawesome -->
        <link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
    </head>

    <body>
        <div class="col-md-4 mt-3">
            <h3><i class="fa fa-folder-open fa-lg"></i>&nbsp;FileServer</h3>
        </div>
        <hr width="100%" class="divider"/>
       
        <div class="mr-5 text-md-right">
            <span class="float-left mt-2 ml-5">
                <h6>폴더: {{ pwd }}</h6>
            </span>
            <img src="/static/images/upload.png", width="50">
            <img src="/static/images/download.png", width="50">
        </div>
        <hr width="100%" class="divider"/>
       
        {% block content %}{% endblock %}    <!-- 상속 -->
    </body>
</html>
 
 
 
 
 
 
 
 
 
 
 
 

check.html


{% extends "base.html" %}    <!-- 상속 -->
{% block content %}
<body onload="javascript:onload()">    <!-- 페이지 로드되면 onload 함수 호출 -->
  <br>
  <div class="jumbotron text-center">
    <h3>파일 업로드 성공!</h3>
    <img src="{{ url_for('static',filename='images/tick.png') }}", width="100")/>
  </div>
  <script language="javascript">
    function onload(){
      setTimeout('go_home()',3000)
    }
    function go_home(){
      location.href="/"    <!-- 루트 디렉토리로 이동 -->
    }
  </script>
</body>
{% endblock %}
 
 
 
 
 
 
 
 
 
 
 

deny.html


{% extends "base.html" %}
{% block content %}
<body onload="javascript:onload()">
  <br>
  <div class="jumbotron text-center">
    <h2>잘못 접근하셨어요 :(</h2>
  </div>
  <script language="javascript">
    function onload(){
      setTimeout('go_home()',5000)
    }
    function go_home(){
      location.href="/"
    }
  </script>
</body>
{% endblock %}

 

 

 

 

 

 

 

댓글