본문 바로가기
해킹/wiset 멘토링

[wiset 취업탐색 멘토링] 첫번째 과제 웹 개발하기

by yenua 2022. 6. 7.
반응형

wiset 취업탐색 멘토링의 첫번째 과제는 웹을 개발하는 것이었다. php나 python으로 개발을 하는 것을 추천한다는 멘토님으 말씀이 있으셨는데, 최근 php로 웹 사이트를 개발해본 적이 있어서 python으로 개발을 해보고자 하였다.

python으로 웹 개발하는 것이 처음이라 어떻게 해야할지 감이 안잡혀서 검색을 해보았더니 플라스크는 제약이 꽤 있다고 하여 장고로 개발을 시작하게 되었다.

처음에는 나랑 맞지 않는 레퍼런스를 찾아서 하나하나 이해하는데 되게 시간이 걸렸었다.. 그러다  과제 때문에 잊고 지내다 이번에 다시 이 과제의 존재를 깨닫게 되어서 다른 레퍼런스를 찾아봤고, wikidocs에서 실습형으로 너무 잘 정리해준 내용이 있어서 그걸 참고해서 사이트를 제작해보았다.

 

사용한 언어는 HTML과 파이썬 장고 프레임워크이다.

초기 화면

 

더보기

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>장고 페이지</title>
</head>
<body>
    {% load static %}
    <link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
    <br>
    <br>
    <center> 
        <h1>첫 장고 페이지! with wiset</h1>
        <br>
        <br>
        <button><a href="board/">게시글 목록</a></button>
        {% if user.is_authenticated %}
        <button><a href="{% url 'common:logout' %}">{{ user.username }} (로그아웃)</a></button>
        {% else %}
        <button><a href="{% url 'common:login' %}">로그인</a></button>
        <button><a class="nav-link" href="{% url 'common:signup' %}">회원가입</a></button>
        {% endif %}
    </center>
</body>
</html>

if문을 사용하여, login 하여 인증된 상태라면 로그아웃 버튼이 뜨도록, 아니라면 로그인 및 회원가입 버튼이 뜨도록 한다.

common 폴더 내 url.py를 통해 url 연결을 관리한다.

url.py

from django.urls import path
from django.contrib.auth import views as auth_views
from . import views

app_name = 'common'

urlpatterns = [
    path('login/', auth_views.LoginView.as_view(template_name='common/login.html'), name='login'),
    path('logout/', auth_views.LogoutView.as_view(), name='logout'),
    path('signup/', views.signup, name='signup'),
]

 

로그인 화면

더보기

login.html

{% extends "base.html" %}
{% block content %}
<div class="container my-3">
    <br>
    <br>
    <center>  <h1>로그인</h1> </center> 
    <form method="post" action="{% url 'common:login' %}">
        {% csrf_token %}
        <input type="hidden" name="next" value="{{ next }}">
        {% include "form_errors.html" %}
        <div class="mb-3">
            <label for="username">사용자ID</label>
            <input type="text" class="form-control" name="username" id="username"
                   value="{{ form.username.value|default_if_none:'' }}">
        </div>
        <div class="mb-3">
            <label for="password">비밀번호</label>
            <input type="password" class="form-control" name="password" id="password"
                   value="{{ form.password.value|default_if_none:'' }}">
        </div>
        <button type="submit" class="btn btn-primary">로그인</button>
    </form>
</div>
{% endblock %}

로그인 화면에서 base.html이라는 css 파일을 지정해둔 템플릿와 사용한다.

 

base.html

{% load static %}
<!doctype html>
<html lang="ko">
<head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <!-- Bootstrap CSS -->
    <link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
    <!-- pybo CSS -->
    <link rel="stylesheet" type="text/css" href="{% static 'style.css' %}">
    <title>Hello, user!</title>
</head>
<body>
<!-- 기본 템플릿 안에 삽입될 내용 Start -->
{% block content %}
{% endblock %}
<!-- 기본 템플릿 안에 삽입될 내용 End -->
</body>
</html>

회원가입 화면

더보기
{% extends "base.html" %}
{% block content %}
<div class="container my-3">
    <br>
    <br>
    <center>  <h1>회원가입</h1> </center> 
    <form method="post" action="{% url 'common:signup' %}">
        {% csrf_token %}
        {% include "form_errors.html" %}
        <div class="mb-3">
            <label for="username">사용자 이름</label>
            <input type="text" class="form-control" name="username" id="username"
                   value="{{ form.username.value|default_if_none:'' }}">
        </div>
        <div class="mb-3">
            <label for="password1">비밀번호</label>
            <input type="password" class="form-control" name="password1" id="password1"
                   value="{{ form.password1.value|default_if_none:'' }}">
        </div>
        <div class="mb-3">
            <label for="password2">비밀번호 확인</label>
            <input type="password" class="form-control" name="password2" id="password2"
                   value="{{ form.password2.value|default_if_none:'' }}">
        </div>
        <div class="mb-3">
            <label for="email">이메일</label>
            <input type="text" class="form-control" name="email" id="email"
                   value="{{ form.email.value|default_if_none:'' }}">
        </div>
        <button type="submit" class="btn btn-primary">생성하기</button>
    </form>
</div>
{% endblock %}

회원가입을 위해 필요한 내용들을 입력 받는다. 입력한 내용은 view.py에서 import하는 form.py을 통해 입력값을 검증한다.

view.py

from django.contrib.auth import authenticate, login
from django.shortcuts import render, redirect
from common.forms import UserForm


def signup(request):
    if request.method == "POST":
        form = UserForm(request.POST)
        if form.is_valid():
            form.save()
            username = form.cleaned_data.get('username')
            raw_password = form.cleaned_data.get('password1')
            user = authenticate(username=username, password=raw_password)  # 사용자 인증
            login(request, user)  # 로그인
            return redirect('index')
    else:
        form = UserForm()
    return render(request, 'common/signup.html', {'form': form})

form.py을 import하여 입력값을 검증하고, 이가 올바르면 회원가입을 시켜준다.

 

form.py

from django import forms
from django.contrib.auth.forms import UserCreationForm
from django.contrib.auth.models import User


class UserForm(UserCreationForm):
    email = forms.EmailField(label="이메일")

    class Meta:
        model = User
        fields = ("username", "password1", "password2", "email")

장고의 라이브러리를 이용해 입력값 검증을 편하게 할 수 있다.

 

더보기

board.html

<html>
    <head>
        <title>Board List</title>
    </head>
    <body>
        {% load static %}
        <link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
        <br>
        <br>
        <center>  <h1>게시글 목록</h1> </center> 
        <!-- 게시판(postlist)의 게시글(list)을 하나씩 보여줍니다 -->
        <!-- 괄호퍼센트 내부엔 파이썬이 사용됩니다 -->
        <div class="container my-3">
            <table class="table">
                <tr class="table-info">
                    <th>번호</th>
                    <th>제목</th>
                    <th>작성자</th>
                </tr>
                {% for list in postlist %}
                <!-- 게시글 클릭시 세부페이지로 넘어갑니다-->
                <tr>
                    <td>{{list.pk}}</td>
                    <td><a href="{{list.pk}}/">{{list.postname}}</a></td>
                    <td>{{list.author}}</td>
                </tr>
                {% endfor %}
            </table>
            <div class="mb-3">
                <button {% if not user.is_authenticated %}disabled{% endif %}><a href="new_post/">글쓰기</a></button>
            </div>
        </div>
    </body>
</html>

DB에서 게시글을 불러와서 보여준다.

글쓰기 버튼의 경우 로그인 된 사람만 누를 수 있도록 처리해주었다.

 

더보기

new_post.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>글쓰기</title>
</head>
<body>
    {% load static %}
    <link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
    <br>
    <br>
    <center>  <h1>글쓰기</h1> </center> 
    <div class="container my-3">
        <form method="POST">
            {% csrf_token %}
            <div class="mb-3">
                제목<br>
                <input type="text" class="form-control"name="postname"><br>
                
                내용<br>
                <textarea rows="10" cols="50" class="form-control" name="contents"></textarea><br>
                <input type="file" class="form-control" name="mainphoto"><br>
                <input type="submit" class="form-control" value="글쓰기">
            </div>
        </form>
    </div>
</body>
</html>

css를 적용시킨 글쓰기 화면이다. 파일을 올릴 수도 있다.

 

더보기

posting.html

<html>
    <head>
        <title>Posting!</title>
    </head>
    <body>
        {% load static %}
        <link rel="stylesheet" type="text/css" href="{% static 'bootstrap.min.css' %}">
        <br>
        <br>
        <center>  <h1>게시글 내용</h1> </center> 
        <div class="container my-3">
            <table class="table">
                <tr>
                    <td class="table-info" width="200"><b>제목</b></td>
                    <td>{{post.postname}}</td>
                </tr>
                    <td class="table-info" width="200"><b>내용</b></td>
                    <td>{{post.contents}}</td>
                    {% if post.mainphoto %}
                        <img src = "{{ post.mainphoto.url }}" alt="" height="600">
                        <br>
                    {% endif %}
                </tr>
            </table>
            <button><a href="/board/{{post.pk}}/remove">삭제</a></button>
            <button><a href="/board/">목록</a></button>
        </div>
    </body>
</html>

DB에서 게시글 내용을 불러와 보여주는 코드이다.

 

게시글 삭제

더보기

remove_post.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>글 삭제</title>
</head>
<body>
    <form method="POST">
        {% csrf_token %}
        <h3>{{ Post.postname }} - 삭제하기</h3>
        <button>삭제</button>
    </form>
</body>
</html>

실제로 해당 게시글을 지울 지 물어보는 코드이다.

여기서 post는 게시글에 대해 설정해둔 모델이며, model.py에 정의되어 있다.

from django.db import models
from django.contrib.auth.models import User

# Create your models here.
# 게시글(Post)엔 제목(postname), 내용(contents)이 존재합니다
class Post(models.Model):
    postname = models.CharField(max_length=50)
    # 게시글 Post에 이미지 추가
    mainphoto = models.ImageField(blank=True, null=True)
    contents = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    # postname이 Post object 대신 나오기
    def __str__(self):
        return self.postname

 

게시글 관련 파이썬 코드들

더보기

urls.py

from django.contrib import admin
from django.urls import path
# index는 대문, board는 게시판
from main.views import *

app_name='main'

# 이미지를 업로드하자
from django.conf.urls.static import static
from django.conf import settings

urlpatterns = [
    path('admin/', admin.site.urls),
    # 웹사이트의 첫화면은 index 페이지이다 + URL이름은 index이다
    path('', index, name='index'),
    # URL:80/board에 접속하면 board 페이지 + URL이름은 board이다
    path('board/', board, name='board'),
    # URL:80/board/숫자로 접속하면 게시글-세부페이지(posting)
    path('board/<int:pk>/', posting, name="posting"),
    path('board/new_post/', new_post),
    path('board/<int:pk>/remove/', remove_post),
]

# 이미지 URL 설정
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

urls의 연결을 관리하는 파이썬 파일이다.

 

views.py

from django.shortcuts import render
# View에 Model(Post 게시글) 가져오기
from .models import Post
from django.shortcuts import redirect
from django.contrib.auth.decorators import login_required

# index.html 페이지를 부르는 index 함수
def index(request):
    return render(request, 'main/index.html')

# board.html 페이지를 부르는 board 함수
def board(request):
    # 모든 Post를 가져와 postlist에 저장합니다
    postlist = Post.objects.all()
    # board.html 페이지를 열 때, 모든 Post인 postlist도 같이 가져옵니다 
    return render(request, 'main/board.html', {'postlist':postlist})

# board의 게시글(posting)을 부르는 posting 함수
def posting(request, pk):
    # 게시글(Post) 중 pk(primary_key)를 이용해 하나의 게시글(post)를 검색
    post = Post.objects.get(pk=pk)
    # posting.html 페이지를 열 때, 찾아낸 게시글(post)을 post라는 이름으로 가져옴
    return render(request, 'main/posting.html', {'post':post})

@login_required(login_url='common:login')
def new_post(request):
    if request.method == 'POST':
        if request.POST['mainphoto']:
            new_article=Post.objects.create(
                postname=request.POST['postname'],
                author=request.user,
                contents=request.POST['contents'],
                mainphoto=request.POST['mainphoto'],
            )
        else:
            new_article=Post.objects.create(
                postname=request.POST['postname'],
                author=request.user,
                contents=request.POST['contents'],
                mainphoto=request.POST['mainphoto'],
            )
        return redirect('/board/')
    return render(request, 'main/new_post.html')

def remove_post(request, pk):
    post = Post.objects.get(pk=pk)
    if request.method == 'POST':
        post.delete()
        return redirect('/board/')
    return render(request, 'main/remove_post.html', {'Post': post})

DB를 어떻게 처리하는지 등 페이지의 작동 부분을 관리하는 파이썬 파일이다. board는 게시글 목록을 조회할 때 쓰는 함수이고, posting은 선택한 게시글의 내용을 조회할 때 쓰는 함수이다.

 

웹 서버 디렉토리 구조

 

/admin/main/post/

로그인 기능 및 게시판 기능에 사용한 DB 는 sqlite 이다.

php의 phpadmin 페이지처럼 장고에서도 DB를 관리할 수 있는 페이지를 제공하고 있었다.

 

참고 자료

https://wikidocs.net/70588

https://wikidocs.net/91422

 

반응형