• Home
  • About
    • CodeLab photo

      CodeLab

      UnprettyCoder's Blog

    • Learn More
    • Github
  • Posts
    • All Posts
    • Computer Science
      • JAVA
      • C
      • PYTHON
      • JavaScript
      • DATABASE
      • Git
      • nodeJS
    • Industrial Engineering
      • O.R.
      • Statistics
      • E.E.
    • Others
      • NETWORK
      • ACCOUNTANCY
      • OTHERS
  • Projects

Bookmark Project

24 Mar 2020

Reading time ~7 minutes

BookMark Project


이번 프로젝트에서 만들어 볼 것은 간단한 북마크 서비스이다.

목록 페이지, 북마크 추가, 북마크 상세, 북마크 수정, 북마크 삭제, 관리자 페이지를 생성/조작한다.


Create Project

$ pip install django

$ django-admin startproject config .

$ python manage.py migrate

$ python manage.py createsuperuser

$ python manage.py runserver

Django 프로젝트 기본 생성/테스트 과정이다.

Add BookMark application

$ python manage.py startapp bookmark

Create Model

bookmark/models.py

from django.db import models;

class Bookmark(models.Model):
	site_name = models.CharField(max_length=100);
	url = models.URLField('Site URL');

config/settings.py

INSTALLED_APPS = [
	'django.contrib.admin',
	'django.contrib.auth',
	# ... ,
	'bookmark'
];

$ python manage.py makemigrations bookmark

$ python manage.py migrate bookmark

Enroll Models in Admin Page

bookmark/admin.py

from django.contrib import admin;

from .models import Bookmark;

admin.site.register(Bookmark);

$ python manage.py runserver

127.0.0.1:8000/admin에 접속하여 관리자 페이지에 모델이 정상적으로 등록되었는지 확인

(관리자 페이지에서 북마크를 추가할 수 있다. 그러나 원하는대로 출력되지는 않는다.)

Add __str__() method in Models

bookmark/models

from django.db import models;

class Bookmark(models.Model):
    # ...
    def __str__(self):
        # printed value when printing Object
        return "name : " + self.site_name + ", addr : " + self.url;

(이제 관리자 페이지에서 목록을 확인하면 name : google, addr : google.com과 같은 형식으로 출력된다.)

Create ListView

bookmark/views.py

from django.views.generic.list import ListView;

from .models import Bookmark;

class BookmarkListView(ListView):
	model = Bookmark;

Django에서 제공하는 Generic view를 이용하여 클래스형 뷰로 구성한다. [simple]

Connect URL

config/urls.py

from django.contrib import admin;
from django.urls import path, include;

urlpatterns = [
	path('bookmark/', include('bookmark.urls')),
	path('admin/', admin.site.urls)
];

bookmark/urls.py

from django.urls import path;
from .views import BookmarkListView;

urlpatterns = [
	path('', BookmarkListView.as_view(), name='list')
];

$ python manage.py runserver

(아직 localhost:8000/bookmark/에 접속해도 템플릿 파일이 없다는 오류 메시지를 출력한다.)

Make Templates [bookmark_list.html]

bookmark/templates/bookmark/bookmark_list.html


<!-- <html><head> ... -->
<body>
    <div class="btn-group">
        <a href="#" class="btn btn-info">Add Bookmark</a>
    </div>
    <p></p>
    <table class="table">
        <thread>
            <tr>
                <th scope="col">#</th>
                <th scope="col">Site</th>
                <th scope="col">URL</th>
                <th scope="col">Modify</th>
                <th scope="col">Delete</th>
            </tr>
        </thread>
        <tbody>
        	{% for bookmark in object_list %}
            	<tr>
            		<td>{{ forloop.counter }}</td>
                     <td><a href="#">{{ bookmark.site_name }}</a></td>
                     <td><a href="{{ bookmark.url }}" targer="_blank">{{ bookmark.url }}</a><</td>
                     <td><a href="#" class="btn btn-success btn-sm">Modify</a></td>
                     <td><a href="#" class="btn btn-danger btn-sm">Delete</a></td>
            	</tr>
            {% endfor %}
        </tbody>
    </table>
</body>

$ python manage.py runserver

localhost:8000/bookmark/에 접속하여 관리자 페이지에서 추가한 북마크가 정상 출력되는지 확인

Implement Sub-Functions of Bookmark

bookmark/views.py

from django.views.generic.edit import CreateView;
from django.urls import reverse_lazy;

class BookmarkCreateView(CreateView):
	model = Bookmark;
	fields = ['site_name', 'url'];
	success_url = reverse_lazy('list');
	template_name_suffix = '_create';

bookmark/urls.py

from .views import BookmarkListView, BookmarkCreateView;

urlpatterns = [
	path('', BookmarkLustView.as_view(), name='list'),
	path('add/', BookmarkCreateView.as_view(), name='add')
];

bookmark/templates/bookmark/bookmark_create.html


<body>
	<form action="" method="post">
		{% csrf_token %}
        <!-- CSRF[Cross Site Request Forgery] 공격으로부터 서버를 지키기 위한 옵션 -->
		{{ form.as_p }}
		<input type="submit" value="Add" class="btn btn-info btn-sm">
	</form>
</body>

$ python manage.py runserver

localhost:8000/bookmark/add/에 접속하면 북마크를 추가할 수 있는 페이지가 출력된다.

bookmark/templates/bookmark/bookmark_list.html


<a href="{% url 'add' %}" class="btn btn-info">Add Bookmark</a>

bookmark_list.html파일의 Add Bookmark 링크가 작동할 수 있도록 href를 설정해준다.

Create Bookmark Detail Page

bookmark/views.py

from django.views.generic.detail import DetailView;

class BookmarkDetailView(DetailView):
	model = Bookmark;

bookmark/urls.py

from django.urls import path;
from .views import *;

urlpatterns = [
	path('', BookmarkLustView.as_view(), name='list'),
	path('add/', BookmarkCreateView.as_view(), name='add'),
	path('detail/<int:pk>/', BookmarkDetailView.as_view(), name='detail')
];

bookmark/templates/bookmark/bookmark_detail.html


<body>
	{{ object.site_name }}<br>
	{{ object.url }}
</body>

bookmark/templates/bookmark/bookmark_list.html


<a href="{% url 'detail' pk=bookmark.id %}">{{ bookmark.site_name }}</a>

bookmark_list.html에 상세 정보를 보여줄 수 있도록 href를 수정한다.

Bookmark Edit-Function Implementation

bookmark/views.py

from django.views.generic.edit import CreateView, UpdateView;

class BookmarkUpdateView(UpdateView):
	model = Bookmark;
	fields = ['site_name', 'url'];
	template_name_suffix = '_update';

bookmark/urls.py

urlpatterns = [
	# ...
	path('update/<int:pk>', BookmarkUpdateView.as_view(), name='update')
]

bookmark/templates/bookmark/bookmark_update.html


<body>
	<form action="" method="post">
	{% csrf_token %}
	{{ form.as_p }}
	<input type="submit" value="Update" class="btn btn-info btn-sm">
</body>	

bookmark/templates/bookmark/bookmark_list.html


<a href="{% url 'update' pk=bookmark.id %}" class="btn btn-success btn-sm">Modity</a>

bookmark/models.py

from django.urls import reverse;

class Bookmark(models.Model):
	# ...
	def get_absolute_url(self):
		return reverse('detail', args=[str(self.id)]);

Bookmark Delete-Function Implementation

bookmark/views.py

from django.views.generic.edit import CreateView, UpdateView, DeleteView;

class BookmarkDeleteView(DeleteView):
	model = Bookmark;
	success_url = reverse_lazy('list');

bookmark/urls.py

urlpatterns = [
	# ...
	path('delete/<int:pk>', BookmarkDeleteView.as_view(), name='delete')
];

bookmark/templates/bookmark/bookmark_list.html


<a href="{% url 'delete' pk=bookmark.id %}" class="btn btn-danger btn-sm">Delete</a>

bookmark_list.html의 Delete 부분 href를 수정하여 목록 제거 작업을 수행할 수 있도록 한다.

(Delete버튼을 누르면 삭제 페이지로 이동하지만 아직 오류 메시지 출력)

bookmark/templates/bookmark/bookmark_confirm_delete.html


<body>
	<form action="" method="post">
		{% csrf_token %}
		<div class="alert alert-danger">Do you wnate to delete Bookmark "{{ object }}"?</div>
		<input type="submit" value="Delete" class="btn btn-danger">
	</form>
</body>

삭제를 실수로 누를 때를 대비하여 재확인 페이지를 구현하여 확인한다.


Set Design

Expend Template

config/setting.py

TEMPLATES = [
	{
		'BACKEND': 'django.template.backends.django.DjangoTemplates',
		'DIRS': [os.path.join(BASE_DIR, "templates")],
		'APP_DIRS': True,
		'OPTIONS': {
			'context_processors': [
				'django.template.context_processors.debug',
				'django.template.context_processors.request',
				'django.contrib.auth.context_processors.auth',
				'django.contrib.messages.context_processors.messages'
			]
		}
	}
];

templates/base.html


<!DOCTYPE html>
<html lang="en">
<head>
	<meta charset="UTF-8">
	<title>{% block title %}{% endblock %}</title>
</head>
<body>
	{% block content %}
	
	{% endblock %}
</body>
</html>

block: 기준 템플릿에 다른 템플릿에서 껴넣을 공간을 block 태크를 사용해 만들어두고 하위 템플릿에서는 이 블록에 껴넣을 내용을 결정하여 내용을 채운다.

bookmark/templates/bookmark/bookmark_confirm_delete.html


{% extends 'base.html' %}

{% block title %}Confirm Delete{% endblock %}

{% block content %}
<form action="" method="post">
	{% csrf_token %}
	<div class="alert alert-danger">Do you want to delete Bookmark "{{object}}"?</div>
	<input type="submit" value="Delete" class="btn btn-danger">
</form>

{% endblock %}

extends: 상속해 올 기준이 되는 템플릿을 지정

bookmark/templates/bookmark/bookmark_create.html


{% extends 'base.html' %}

{% block title %}Bookmark Add{% endblock %}

{% block content %}
<form action="" method="post">
	{% csrf_token %}
	{{ form.as_p }}
</form>
{% endblock %}

bookmark/templates/bookmark/bookmark_detail.html


{% extends 'base.html' %}

{% block title %}Detail{% endblock %}

{% block content %}
{{ object.site_name }}<br>
{{ object.url }}
{% endblock %}

bookmark/templates/bookmark/bookmark_list.html


{% extends 'base.html' %}

{% block title %}Bookmark List{% endblock %}

{% block content %}
<div class="btn-group">
	<a href="{% url 'add' %}" class="btn btn-info">Add Bookmark</a>
</div>
<p></p>
<table class="table">
        <thread>
            <tr>
                <th scope="col">#</th>
                <th scope="col">Site</th>
                <th scope="col">URL</th>
                <th scope="col">Modify</th>
                <th scope="col">Delete</th>
            </tr>
        </thread>
        <tbody>
        	{% for bookmark in object_list %}
            	<tr>
            		<td>{{ forloop.counter }}</td>
                     <td><a href="{% url 'detail' pk=bookmark.id %}">{{ bookmark.site_name }}</a></td>
                     <td><a href="{{ bookmark.url }}" targer="_blank">{{ bookmark.url }}</a><</td>
                     <td><a href="{% url 'update' pk=bookmark.id %}" class="btn btn-success btn-sm">Modity</a></td>
                     <td><a href="{% url 'delete' pk=bookmark.id %}" class="btn btn-danger btn-sm">Delete</a></td>
            	</tr>
            {% endfor %}
        </tbody>
</table>
{% endblock %}

bookmark/templates/bookmark/bookmark_update.html


{% extends 'base.html' %}

{% block title %}Bookmark Update{% endblock %}

{% block content %}
<form action="" method="post">
	{% csrf_token %}
	{{ form.as_p }}
	<input type="submit" value="Update" class="btn btn-info btn-sm">
</form>
{% endblock %}

지금까지 템플릿의 확장을 이용하지 않고 작성했던 html파일들을 모두 base.html에서 확장한 형태로 수정한다.

Using Bootstrap

https://getbootstrap.com/(부트스트랩 공식 홈페이지)에 접속하여 부트스트랩을 이용하도록 한다.

Bootstrap: One of css Frameworks

홈페이지 Nav의 Documentation에 들어가서 CSS, JS 참고 코드를 긁어서 base.html의 <head>에 추가한다.

templates/base.html


<head>
	<!-- skip other contents -->
	<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
	<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
	<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
	<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
</head>

이것을 적용한 것 만으로도 페이지의 디자인이 훨씬 더 아름다워진다.

bootstrap은 HTML 태그의 class 속성을 기준으로 css를 적용한다. [class= “btn btn-info btn-sm” 등]

Create Navigation Bar in base.html

templates/base.html


<!-- <body> 내부 -->
<div class="container">
	<nav class="navbar navbar-expand-lg navbar-light bg-light">
		<a class="navbar-brand" href="#">Django bookmark</a>
        <button class="navbar-toggler" type="button" data-toggle-"collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggle-icon"></span>
        </button>
        
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item aciton">
                	<a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
                </li>
            </ul>
        </div>
    </nav>
    <p></p>
    <div class="row">
        <div class="col">
            {% block content %}
            {% endblock %}
            
            {% block pagination %}
            {% endblock %}
        </div>
    </div>
</div>

Add Paging-Function

bookmark/views.py

class BookmarkListView(ListView):
	model = Bookmark;
	paginate_by = 6; # how many objects printed at one page [6]

bookmark/templates/bookmark/bookmark_list.html


{% block pagination %}
	{% if is_paginated %}
		<ul class="pagination justify-content-center pagination-sm">
		{% if page_obj.has_previous %}
			<li class="page-item">
				<a class="page-link" href="{% url 'list' %}? page={{ page_obj.previous_page_number }}" tabindex="-1">Previous</a>
			</li>
		{% else %}
			<li class="page-item disabled">
				<a class="page=link" href="#" tabindex="-1">Previous</a>
			</li>
		{% endif %}
		
		{% for object in page_obj.paginator.page_range %}
			<li class="page-item {% if page_obj.number == forloop.counter %}disabled{% endif %}">
				<a class="page-link" href="{{ request.path }}?page={{ forloop.counter }}">{{ forloop.counter }}</a>
			</li>
		{% endfor %}
		
		{% if page_obj.has_next %}
			<li class="page-item">
				<a class="page-link" href="{% url 'list' %}?page={{ page_obj.next_page_number }}"> Next</a>
			</li>
		{% else %}
			<li class="page-item disabled">
				<a class="page-link" href="#">Next</a>
			</li>
		{% endif %}
		</ul>
	{% endif %}
{% endblock %}

이제 북마크 목록 페이지에 1 페이지에 6 항목씩 출력되는 페이징 기능이 추가되었다.

Using Static Files

config/settings.py

STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')];

이후 프로젝트 하위 디렉터리로 static 디렉터리를 생성한다.

static/style.css

body {
	width: 100%;
}

templates/base.html


{% load static %}
<link rel="stylesheet" href="{% static 'style.css' %}">


Deploy Service

https://github.com/[깃허브]에서 배포하는 과정

깃허브를 통해 배포하기 위해 프로젝트 하위에 .gitignore파일을 생성하고, 다음과 같이 작성한다.

.gitignore (여기에 입력된 파일은 커밋하지 않는다. [와일드카드 문자 사용가능])

*.pyc
*~
/venv
__pycache__
db.sqlite3
.DS_Store

.config/settings.py

DEBUG = False;

ALLOWED_HOSTS = ['*'];

$ git init

$ git add -A

$ git commit -m "Bookmark Service"

$ git remote add origin https://github.com/unprettycoder/bookmark.git

$ git push -u origin master

로그인 후 코드 업로드

pythonanywhere[파이썬 애니웨어]를 이용하여 배포할 수도 있다.

Address : https://www.pythonanywhere.com/

PythonAnywhere에 대해서는 차후 다시 공부하도록 한다.



Share Tweet +1