티스토리 뷰

IT

Flask - 애플리케이션(메모앱) 만들어보기 #6

고래(부와 성공) 2024. 11. 23. 15:38

목차



    반응형

    앞에서 만들어본 메모앱은 프론트엔드 페이지를 거의 만들지 않고 curl 명령으로 테스트를 진행했었습니다.

     

    여기서 프런트엔드 데이지를 개선 및 추가해서 메모앱을 웹에서 작동이 되도록 해보겠습니다.

     

    프론트엔드 페이지는 HTML, CSS, 자바스크립트로 작성하고, Jinja2 이라 불리는 템플릿에 연동되어 동작할 수 있게 만들어 볼께요

     

    개선 하고 추가할 파일은 templates 폴더 아래에 있는 memos.html 과 index.html 입니다.

     

    1. index.html 작성하기

    index.html 은 http://127.0.0.1:5000/과 같이 루트 라우트에서 보여지는 페이지입니다.

     

    이 페이지에서 로그인 및 회원가입 기능을 구현하기 위해 아래와 같이 코드를 변경합니다.

     

    <!DOCTYPE html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>온라인 메모 앱 v1.0</title>
        <style>
            body { font-family: Arial, Helvetica, sans-serif; }
            .container {
                width: 300px;
                margin: auto;
                border: 1px solid #ddd;
                padding: 20px;
            }
            .form-group {
                margin-bottom: 10px;
            }
            .form-group label, .form-group input {
                display: block;
                width: 100%;
            }
            .form-group input {
                padding: 5px;
                margin-top: 5px;
            }
            .buttons {
                display: flex;
                justify-content: space-between;
                margin-top: 20px;
            }
        </style>
    </head>
    <body>
        <h2>나의 메모 앱에 오신것을 환영해요</h2>
        <p> 이것은 온라인 메모장 앱입니다.</p>
    
        <form action="/login" method="post">
            <div class="form-group">
                <label for="username">사용자 이름:</label>
                <input type="text" id="username" name="username" required>
            </div>
            <div class="form-group">
                <label for="password">비밀번호:</label>
                <input type="password" id="password" name="password" required>
            </div>
            <div class="buttons">
                <input type="submit" type="submit" value="로그인">
                <a href="/signup">회원가입</a>
            </div>
        </form>
    </body>
    </html>

     

     

    2. memos.html 작성하기

    또한 memos.html도 아래와 같이 작성합니다.

     

    memos.html 은 로그인 한 후에 자신의 메모 리스트 보여지며, 이를 수정, 삭제하는 것은 물론 새로운 메모를 새로 추가하는 기능을 구현하는 페이지가 되겠습니다.

     

    <!DOCTYPE html>
    <html lang="ko">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>내 메모 관리 페이지</title>
        <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
        <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.1/css/all.min.css" rel="stylesheet">
        <style>
            /* CSS 스타일 시작 */
            .container {
                margin-top: 20px;
                max-width: 800px;
            }
    
            .card {
                margin-bottom: 20px;
                border: none;
                box-shadow: 0 4px 8px rgba(0,0,0,.1);
                background-color: #fff;
            }
    
            .card-body {
                position: relative;
                padding: 10px;
            }
    
            .memo-title, .memo-content {
                width: 100%;
                margin-bottom: 10px;
                border: 1px solid #ddd;
                background-color: #fff;
                padding: 10px;
            }
    
            .memo-title {
                font-size: 1.1rem;
            }
    
            .memo-content {
                min-height: 100px;
            }
    
            .edit-buttons {
                margin: 10px;
                text-align: right;
                margin-right: 0px;
                margin-bottom: 0px;
            }
    
            .edit-buttons .btn {
                background-color: #f8f9fa;
                border: none;
                border-radius: 5px;
                margin-left: 5px;
                padding: 5px 10px;
                color: #495056;
                transition: all 0.3s ease;
            }
    
            .edit-buttons .btn:hover {
                background-color: #e2e6ee;
                transform: scale(1.1);
            }
    
            .edit-buttons .btn-edit {
                background-color: #e74c3a;  
                color: #fff;
            }
    
            .edit-buttons .btn-edit:hover {
                background-color: #c0392a;  
            }
    
            .edit-buttons .btn-delete {
                background-color: #3498BB;  
                color: #fff;
            }
    
            .edit-buttons .btn-delete:hover {
                background-color: #2980BB;  
            }
    
            .btn-primary {
                background-color: #3f4644;  
                color: #fff;
            }
    
            .btn-primary:hover {
                background-color: #0056BB;  
            }
    
            .btn-block {
                display: block;
                width: 100%
            }
    
            .header-bar {
                background-color: #ff8066;
                padding: 10px 0;
                text-align: center;
                border-radius: 10px; 
                box-shadow: 0 4px 6px rgba(0,0,0,.1);  /* 그림자 effect */
                animation: slideDown 0.5s ease-out;
                margin: 10px;
                position: relative;
                display: flex; /* flexbox layout */
                justify-content: center;  /* 가로 중앙 정렬 */
                align-items: center; /* 세로 중앙 정렬 */
            }
    
            .header-item {
                position: absolute;
                top: 50%;
                transform: translateY(-50%);
            }
    
            .header-item:first-child {
                left: 20px;
            }
    
            .header-item:last-child {
                right: 20px;
            }
    
            .username-button, .logout-button {
                display: flex;
                align-items: center;
            }
    
            .username-button i, .logout-button i {
                margin-right: 5px;
            }
    
            .header-bar h1 {
                color: white;
                margin: 0;
                font-size: 1.3em;
                font-weight: bold;
                transition: all 0.3s ease-in-out;  /* 부드러운 효과 변화 */
            }
    
            .header-content {
                text-align: center;
            }
    
            .user-info {
                position: absolute;
                top: 10px;
                right: 20px;
                font-size: 0.9rem;
            }
    
            .logout-button {
                margin-left: 10px;
            }
    
            .btn-sm {
                padding: 0.15rem 0.5rem;
                font-size: .8rem;
                line-height: 1.5;
                border-radius: 0.2rem;
            }
    
            /* 슬라이드 다운 애니메이션 effect */
            @keyframes slideDown {
                from {
                    transform: translateY(-100%);
                    opacity: 0;
                }
                to {
                    transform: translateY(0);
                    opacity: 1;
                }
            }
        </style>
        <script>
            function createMemo() {
                let title = document.getElementById('new-title').value;
                let content = document.getElementById('new-content').value;
                
                fetch('/memos/create', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ title: title, content: content })
                })
                .then(response => response.json())
                .then(data => {
                    console.log(data);
                    window.location.reload(); // 페이지 새로 고침
                })
                .catch((error) => {
                    console.error('Error:', error);
                });
            }    
    
            function toggleEdit(id) {
                var titleEl = document.getElementById('title-' + id);
                var contentEl = document.getElementById('content-' + id)
                var isReadOnly = titleEl.readOnly;
    
                titleEl.readOnly = !isReadOnly;
                contentEl.readOnly = !isReadOnly;
    
                if (!isReadOnly) {
                    updateMemo(id);
                }
            }
    
            function updateMemo(id) {
                var title = document.getElementById('title-' + id).value;
                var content = document.getElementById('content-' + id).value;
                
                fetch('/memos/update/' + id, {
                    method: 'PUT',
                    headers: {
                        'Content-Type': 'application/json',
                    },
                    body: JSON.stringify({ title: title, content: content})
                })
                .then(response => response.json())
                .then(data => {
                    console.log(data);
                    alert('메모가 업데이트되었습니다.');
                })
                .catch((error) => {
                    console.error('Error:', error);
                })
            }
    
            function deleteMemo(id) {
                if (!confirm('메모를 정말로 삭제하시겠습니까?')) return;
    
                fetch('/memos/delete/' + id, {
                    method: 'DELETE',
                })
                .then(response => response.json())
                .then(data => {
                    console.log(data);
                    window.location.reload(); // 페이지 새로고침
                })
                .catch((error) => {
                    console.error('Error:', error);
                })
            }
        </script>
    </head>
    
    <body>
        <div class="container">
            <!-- 헤더바 추가 -->
            <div class="header-bar" >
                <div class="header-item">
                    <a href="#" class="btn btn-sm btn-danger username-button">
                        <i class="fas fa-user"></i> {{ username }}
                    </a>
                </div>
            </div>
            <h1>나의 메모</h1>
            <div class="header-item">
                <a href="/logout" class="btn btn-sm btn-danger logout-button">
                    <i class="fas fa-sign-out-alt"></i> 로그아웃
                </a>
            </div>
            <div class="card">
                <div class="card-body">
                    <input type="text" id="new-title" placeholder="새 메모 제목" class="form-control memo-title">
                    <textarea id="new-content" placeholder="내용을 입력하세요" class="form-control memo-content"></textarea>
                    <button onclick="createMemo()" class="btn btn-primary btn-block">메모 추가</button>
                </div>
            </div>
    
            {% for memo in memos %}
            <div class="card memo">
                <div class="card-body">
                    <input type="text" id="title-{{ memo.id }}" value="{{ memo.title }}" class="form-control memo-title" readonly>
                    <textarea id="content-{{ memo.id }}" class="form-control memo-content" readonly>{{ memo.content }}</textarea>
                    <div class="edit-buttons">
                        <button onclick="toggleEdit({{ memo.id }})" class="btn btn-edit"><i class="fas fa-edit"></i></button>
                    </div>
                    <div class="edit-buttons">
                        <button onclick="deleteMemo({{ memo.id }})" class="btn btn-delete"><i class="fas fa-trash-alt"></i></button>
                    </div>
                </div>
            </div>
            {% endfor %}
        </div>
    </body>
    </html>

     

     

    3. 테스트해보기

    1) http://127.0.0.1:5000/ 로 접속해서 회원 가입하기

     

    2) http://127.0.0.1:5000/ 로 재접속해서 로그인 하기

    3) http://127.0.0.1:5000/memos에 접속해서 글쓰고 수정 삭제하기

     

     

     

    4) 동일 페이지에서 수정하기

     

     

    5) 삭제하기

    파란색 휴지통을 클릭하면 다음과 같이 메시지를 띄웁니다.

     

     

     

    => 삭제 완료 되었네요

     

    오늘은 여기까지 입니다.

     

    감사합니다.