TOP
class="layout-aside-left paging-number">
본문 바로가기
[파이썬 Projects]/<파이썬 웹개발>

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(4)

by 기록자_Recordian 2024. 8. 13.
728x90
반응형
시작에 앞서
해당 내용은 <가장 빠른 풀스택을 위한 Flask & FastAPI>, Dave Lee 지음. BJ Public 출판.
내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.

이전 내용(메모앱 1단계 - 3단계)
 

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(1)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.메모앱 만들기 - 1단계: 애플리케이션 생성 플라스크

puppy-foot-it.tistory.com

이전 내용(메모앱 4단계)
 

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(2)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.이전 내용(메모앱 1단계 - 3단계) [파이썬] 플라스크(Fl

puppy-foot-it.tistory.com

이전 내용(메모앱 5단계)
 

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(3)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.이전 내용(메모앱 1단계 - 3단계) [파이썬] 플라스크(Fl

puppy-foot-it.tistory.com


6단계: 웹페이지 개선

 

기존에는 프런트엔드 페이지가 없어 curl 명령으로만 테스트를 진행했는데, 프런트엔드 페이지를 개선 또는 추가해서 메모 프로젝트를 웹에서도 확인할 수 있도록 한다.

프런트엔드 페이지는 HTML, CSS, 자바스크립트로 작성하되, Jinja2 템플릿에 연동되어 플라스크 애플리케이션에서 동작할 수 있도록 한다.

 

개선 또는 추가할 파일은 memos.html 과 home.html 이다.

 

- home.html: 로그인 및 회원가입 페이지

- memos.html: 사용자별 메모를 나열하는 템플릿

- app.py: 애플리케이션 초기화 및 라우팅 설정

 


◆ home.html 작성

웹페이지를 통해 로그인 및 회원가입 기능을 제공하기 위한 코드 작성

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title> 마이 메모 앱 홈페이지</title>
    <style>
        body { font-family: Arial, 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>
    <div class="container">
        <h1> My Memo 앱에 오신 것을 환영합니다!</h1>
        <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" value="로그인">
                <a href="/signup">회원가입</a>
            </div>
        </form>
    </div>
</body>
</html>

◆ memos.html 작성

로그인 후 자신의 메모 리스트를 확인하고, 수정, 삭제 및 새로운 메모까지 추가할 수 있는 memos.html 파일을 추가

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <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>
        .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: #495057;
            transition: all 0.3s ease;
        }

        .edit-buttons .btn:hover {
            background-color: #e2e6ea;
            transform: scale(1.1);
        }

        .edit-buttons .btn-edit {
            background-color: #E74C3C;
            color: #fff;
        }

        .edit-buttons .btn-edit:hover {
            background-color: #C0392B;
        }

        .edit-buttons .btn-delete {
            background-color: #3498DB;
            color: #fff;
        }

        .edit-buttons .btn-delete:hover {
            background-color: #2980B9;
        }

        .btn-primary {
            background-color: #3F464D;
            border-color: #007bff;
        }

        .btn-primary:hover {
            background-color: #0056b3;
            border-color: #0056b3;
        }

        .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); /* 그림자효과 */
            animation: slideDown 0.5s ease-out; /* 슬라이드 다운 애니메이션 */
            margin: 10px;
            position: relative;
            display: flex; /* 플렉스 박스 레이아웃 적용 */
            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; /* 로그아웃 버튼과 사용자 ID 사이의 간격 */
        }

        .btn-sm {
            padding: 0.15rem 0.5rem;
            font-size: .8rem;
            line-height: 1.5;
            border-radius: 0.2rem;
        }

        /* 슬라이드 다운 애니메이션 효과 */
        @keyframes slideDown {
            from {
                transform: translateY(-100%);
                opacity: 0;
            }
            to {
                transform: translateY(0);
                opacity: 1;
            }
        }
    </style>
    <script>
        function createMemo() {
            var title = document.getElementById('new-title').value;
            var 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 titleEl = document.getElementById('title-' + id).value;
            var contentEl = 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>
            <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>
        <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>
                    <button onclick="deleteMemo({{ memo.id }})" class="btn btn-delete"><i class="fas fa-trash-alt"></i></button>
                </div>
            </div>
        </div>
        { % endfor % }
    </div>
</body>
</html>

다음 내용

 

[파이썬] 플라스크(Flask) - 플라스크 프로젝트 - 메모앱(5)

시작에 앞서해당 내용은 , Dave Lee 지음. BJ Public 출판.내용을 토대로 작성되었습니다. 보다 자세한 사항은 해당 교재를 참고하시기 바랍니다.이전 내용(메모앱 1단계 - 3단계) [파이썬] 플라스크(Fl

puppy-foot-it.tistory.com

 

728x90
반응형