170322_TIL

|

오늘 한 일

  • 생활코딩 MySQL 수업을 통해 테이블 JOIN을 배웠다. 장고를 사용하면서 접했던 foreign key 개념과 비슷하다는 생각이 들었다.
  • AskDjango 수업에서 admin 커스터마이징, ModelAdmin에 대해서 배웠다. 장고의 강점인 admin을 잘 활용하려면 원하는대로 커스터마이징을 할 수 있어야겠다고 생각했다.
  • 간단한 알고리즘 문제를 풀었다.
  • 점프투 파이썬 모듈 부분을 읽었다.

내일 할 일

MySQL 06. MySQL - Table (조회5 join)

|

생활코딩 - MySQL

여러개의 테이블 사용하기

  • 데이터의 규모가 커지면서 하나의 테이블로 정보를 수용하기가 어려워지면 테이블을 분활하고 테이블 간의 관계성을 부여한다.

예제 테이블 1

  • 예제 중 address는 distnace와 관련되어 있기 때문에 location이라는 별도의 테이블로 분할 할 수 있다.
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` tinyint(4) NOT NULL,
  `name` char(4) NOT NULL,
  `sex` enum('남자','여자') NOT NULL,
  `address` varchar(50) NOT NULL,
  `distance` INT NOT NULL,
  `birthday` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

INSERT INTO `student` VALUES (2, '박재숙', '남자', '서울',  10, '1985-10-26 00:00:00');
INSERT INTO `student` VALUES (1, '이숙경', '여자', '청주', 200, '1982-11-16 00:00:00');
INSERT INTO `student` VALUES (3, '백태호', '남자', '경주', 350, '1989-2-10 00:00:00');
INSERT INTO `student` VALUES (4, '김경훈', '남자', '제천', 190, '1979-11-4 00:00:00');
INSERT INTO `student` VALUES (8, '김정인', '남자', '제주', 400, '1990-10-1 00:00:00');
INSERT INTO `student` VALUES (6, '김경진', '여자', '제주', 400, '1985-1-1 00:00:00');
INSERT INTO `student` VALUES (7, '박경호', '남자', '영동', 310, '1981-2-3 00:00:00');

예제 테이블 2

  • address, distance를 묶어서 별도의 테이블(location)로 만든다.
# student 테이블
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` tinyint(4) NOT NULL,
  `name` char(4) NOT NULL,
  `sex` enum('남자','여자') NOT NULL,
  `location_id` tinyint(4) NOT NULL,
  `birthday` datetime NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


# location 테이블
DROP TABLE IF EXISTS `location`;
CREATE TABLE `location` (
`id`  tinyint UNSIGNED NOT NULL AUTO_INCREMENT ,
`name`  varchar(20) NOT NULL ,
`distance`  tinyint UNSIGNED NOT NULL ,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;;
INSERT INTO `location` VALUES (1, '서울', 10);
INSERT INTO `location` VALUES (2, '청주', 200);
INSERT INTO `location` VALUES (3, '경주', 255);
INSERT INTO `location` VALUES (4, '제천', 190);
INSERT INTO `location` VALUES (5, '대전', 200);
INSERT INTO `location` VALUES (6, '제주', 255);
INSERT INTO `location` VALUES (7, '영동', 255);
INSERT INTO `location` VALUES (8, '광주', 255);
INSERT INTO `student` VALUES (1, '이숙경', '여자', 1, '1982-11-16 00:00:00');
INSERT INTO `student` VALUES (2, '박재숙', '남자', 2, '1985-10-26 00:00:00');
INSERT INTO `student` VALUES (3, '백태호', '남자', 3, '1989-2-10 00:00:00');
INSERT INTO `student` VALUES (4, '김경훈', '남자', 4, '1979-11-4 00:00:00');
INSERT INTO `student` VALUES (6, '김경진', '여자', 5, '1985-1-1 00:00:00');
INSERT INTO `student` VALUES (7, '박경호', '남자', 6, '1981-2-3 00:00:00');
INSERT INTO `student` VALUES (8, '김정인', '남자', 5, '1990-10-1 00:00:00');

join

테이블간의 관계성에 따라서 복수의 테이블을 결합, 하나의 테이블인 것 처럼 결과를 출력

join의 종류

  • OUTTER JOIN : 매칭되는 행이 없어도 결과를 가져오고, 매칭되는 행이 없는 경우 NULL로 표시
    • LEFT JOIN : 왼쪽에 있는 테이블을 기준으로 오른쪽에 있는 테이블의 데이터를 가져온다.
    • RIGHT JOIN : 오른쪽에 있는 테이블을 기준으로 왼쪽에 있는 테이블의 데이터를 가져온다.
  • INNER JOIN : 매칭되는 행이 있는 경우만 표시한다.

LEFT JOIN

  • 가장 많이 사용되는 조인의 형태
  • AS : alias 의 약자
  • 문법 : 기준 테이블 AS 별명 LEFT JOIN 결합할 테이블 AS 별명 ON 결합기준
SELECT s.name, s.location_id, l.name AS address, l.distance  FROM student AS s LEFT JOIN location AS l ON s.location_id = l.id;

OUTTER JOIN과 INNER JOIN의 차이

  • location 테이블에서 제주를 삭제 후 OUTTER JOIN(LEFT JOIN)과 INNER JOIN의 차이를 비교
# location 테이블에서 제주 레코드 삭제
DELETE FROM location WHERE name='제주';

# LEFT JOIN
SELECT s.name, s.location_id, l.name AS address, l.distance  FROM student AS s LEFT JOIN location AS l ON s.location_id = l.id;

+-----------+--------+---------+----------+
| name      | sex    | address | distance |
+-----------+--------+---------+----------+
| 이숙경      | 여자    | 서울     |       10 |
| 박재숙      | 남자    | 청주     |      200 |
| 백태호      | 남자    | 경주     |      255 |
| 김경훈      | 남자    | 제천     |      190 |
| 김경진      | 여자    | 대전     |      200 |
| 박경호      | 남자    | NULL    |     NULL |
| 김정인      | 남자    | 대전     |      200 |
+-----------+--------+---------+----------+

# INNER JOIN
SELECT s.name, s.location_id, l.name AS address, l.distance  FROM student AS s INNER JOIN location AS l ON s.location_id = l.id;

+-----------+--------+---------+----------+
| name      | sex    | address | distance |
+-----------+--------+---------+----------+
| 이숙경      | 여자    | 서울     |       10 |
| 박재숙      | 남자    | 청주     |      200 |
| 백태호      | 남자    | 경주     |      255 |
| 김경훈      | 남자    | 제천     |      190 |
| 김경진      | 여자    | 대전     |      200 |
| 김정인      | 남자    | 대전     |      200 |
+-----------+--------+---------+----------+

Django 기본 07 - admin 커스터마이징, Model Admin

|

AskDjango 수업을 듣고 중요한 내용을 정리하였습니다.

Django Admin

  • 장고가 제공하는 기본 앱 (settings.py 내 INSTALLED_APPS 에서 확인 가능)
INSTALLED_APPS = [
    'django.contrib.admin',
]
  • staff/superuser 계정에 한해 접근 가능 : admin에서 users 목록을 통해서 permissions 수정
  • 모델 클래스만 등록하면, 조회/추가/수정/삭제 웹 인터페이스를 admin에서 제공
  • 예를 들어 맛집등록 서비스라면 관리자 맛집 등록은 어드민에서 우선 진행하고, 엔드유저를 위한 인터페이스를 우선적으로 먼저 만들 수 있다. (초기 개발시 시간 절약)

Model 클래스 등록

  • ModelAdmin objects
  • 특정 모델클래스를 admin에 등록하면, 해당 모델을 GUI 환경에서 관리 가능
  • admin.py 파일 내에 원하는 모델을 import, register, unregister 진행
  • admin.site.unregister 기능은 기본 유저 모델의 등록을 해제하는 등의 용도로 사용

Model Admin 등록법 1

  • 기본 ModelAdmin으로 등록
# myapp/admin.py
from django.contrib import admin
from blog.models import Post


admin.site.register(Post) # 기본 ModelAdmin으로 등록

Model Admin 등록법 2

  • admin.ModelAdmin 상속을 통해 커스터마이징이 가능하다.
# myapp/admin.py
from django.contrib import admin
from blog.models import Post

class PostAdmin(admin.ModelAdmin):
    list_display = ['id', 'title', 'created_at', 'updated_at' ]

admin.site.register(Post, PostAdmin)

Model Admin 등록법 3

  • 장식자(decorator) 형태로 등록이 가능하다. (이런 방식을 더 선호)
# myapp/admin.py
from django.contrib import admin
from blog.models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['id', 'title', 'content']
    list_display_links = ['id', 'title']
views
ModelAdmin 옵션 적용화면

ModelAdmin 옵션

  • list_display : Admin 목록에 보여질 필드 목록
  • list_display_links : 목록 내에서 링크로 지정할 필드 목록 (이를 지정하지 않으면, 첫번째 필드에만 링크가 적용)
  • list_editable : 목록 상에서 수정할 필드 목록
  • list_per_page : 페이지 별로 보여질 최대 갯수 (디폴트 : 100)
  • list_filter : 필터 옵션을 제공할 필드 목록
  • actions : 목록에서 수행할 action 목록

옵션 적용 예시

views
# blog/admin.py
from django.contrib import admin
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['id', 'title', 'author', 'created_at', 'updated_at' ]
    list_display_links = ['id', 'title']
    list_editable = ['author']
    list_per_page = 3
    list_filter = ['author', 'created_at']

아래 옵션은 장고 form에 대한 이해가 필요

list_display 옵션

  • 모델 인스턴스 필드명/속성명/함수명 뿐만 아니라, ModelAdmin 내 멤버 함수도 지정 가능
  • 외래키를 지정한다면, 관련 object의 __str__() 값이 노출
  • ManyToManyField 미지원
# blog/admin.py
from django.contrib import admin
from django.utils.safestring import mark_safe
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['id', 'title', 'content_size', 'created_at', 'updated_at' ]

    def content_size(self, post):
        return mark_safe('<u>{}</u>글자'.format(len(post.content)))
    content_size.short_description = '글자수'

views
글자수, mark_safe 적용화면

Tag Escape

  • mark_safe()
  • Django 에서는 파이썬 코드/변수를 통해 보여지는 Html Tag 에 대해 Auto Escape 를 수행
  • a/img/script 태그 등으로 인한 허용치않은 코드 실행 방지
  • 특징 문자열에 한해, 이를 해제하기 위해 autoescape off template tag를 적용하거나, 문자열에 format_html(), format_html_join(), mark_safe() 적용

Admin actions

  • Admin actions
  • 대개 선택된 Model Instance 들에 대해 Bulk Update 용도 구현

모델에 status 컬럼 추가 후 migration

# myapp/models.py
class Post(models.Model):
    STATUS_CHOICES = (
        ('d', 'Draft'),
        ('p', 'Published'),
        ('w', 'Withdrawn')
    )
    status = models.CharField(max_length=1, choices=STATUS_CHOICES)

# migration 진행
$ python3 manage.py makemigrations myapp
$ python3 manage.py migrate

admin.py에 admin actions 추가

  • ModelAdmin 클래스내 멤버함수로 action 함수를 구현
  • 멤버함수.short_description 을 통해, action 설명 추가
  • ModelAdmin actions 내에 등록
# myapp/admin.py
from django.contrib import admin
from django.utils.safestring import mark_safe
from .models import Post

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
    list_display = ['id', 'title', 'content_size', 'status', 'created_at', 'updated_at' ]
    actions = ['make_published', 'make_draft']

    # 글자수 컬럼 추가
    def content_size(self, post):
        return mark_safe('<u>{}</u>글자'.format(len(post.content)))
    content_size.short_description = '글자수'

    # admin action 추가
    def make_published(self, request, queryset):
        updated_count = queryset.update(status='p') #queryset.update
        self.message_user(request, '{}건의 포스팅을 Published 상태로 변경'.format(updated_count)) #django message framework 활용
    make_published.short_description = '지정 포스팅을 Published 상태로 변경'

    def make_draft(self, request, queryset):
        updated_count = queryset.update(status='d') #queryset.update
        self.message_user(request, '{}건의 포스팅을 draft 상태로 변경'.format(updated_count)) #django message framework 활용
    make_draft.short_description = '지정 포스팅을 draft 상태로 변경'
views
admin actions 적용화면

170321_TIL

|

오늘 한 일

  • 간단한 알고리즘 문제를 풀었다.
  • 생활코딩 MySQL 수업을 들었다. 테이블에서 데이터의 조회를 위한 필터링 방식 (group by, order by, index key)을 배웠다.
  • 점프투 파이썬 클래스 부분을 읽었다. 자바스크립트에는 없는 개념이라 처음는 이해하는게 어려웠지만, 알고나니 객체지향, 상속이라는 점에서 편리한 기능이라는 생각이 든다.
  • AskDjango 수업에서 django shell, jupyter notebook에 대해서 배웠다.

내일 할 일

MySQL 06. MySQL - Table (조회4 index, where)

|

생활코딩 - MySQL

index

  • 색인, 조회할 때 원하는 행을 빠르게 찾을 수 있게 준비해둔 데이터
  • 데이터베이스의 성능이 중요한 경우 인덱스 설계가 중요하다.

인덱스 정의 방법

  • 자주 조회되는 칼럼에 적용
  • 조회 시 오랜시간을 소모하는 컬럼에 적용
  • 데이터가 긴 경우 인덱스를 사용하지 않는다.

인덱스의 종류

  • primary key: 중복되지 않는 유일한 키
  • unique key : 중복을 허용하지 않는 유일한 키
  • normal key : 중복을 허용하는 인덱스
  • foreign key : 다른 테이블과의 관계성을 부여하는 키
  • full text : 자연어 검색, myisam에서만 지원

예제에서 사용한 테이블

DROP TABLE IF EXISTS `student`;
CREATE TABLE `student` (
  `id` tinyint(4) NOT NULL AUTO_INCREMENT,
  `name` char(4) NOT NULL,
  `address` varchar(50) NOT NULL,
  `department` enum('국문과','영문과','컴퓨터공학과','전자공학과','물리학과') NOT NULL,
  `introduction` text NOT NULL,
  `number` char(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_number` (`number`) USING BTREE,
  KEY `idx_department` (`department`),
  KEY `idx_department_name` (`department`,`address`),
  FULLTEXT KEY `idx_introduction` (`introduction`)
) ENGINE=MyISAM AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

INSERT INTO `student` VALUES (1, '이숙경', '청주', '컴퓨터공학과', '저는 컴퓨터 공학과에 다닙니다. computer', '0212031');
INSERT INTO `student` VALUES (2, '박재숙', '서울', '영문과', '저는 영문과에 다닙니다.', '0512321');
INSERT INTO `student` VALUES (3, '백태호', '경주', '컴퓨터공학과', '저는 컴퓨터 공학과에 다니고 경주에서 왔습니다.', '0913134');
INSERT INTO `student` VALUES (4, '김경훈', '제천', '국문과', '제천이 고향이고 국문과에 다닙니다.', '9813413');
INSERT INTO `student` VALUES (6, '김경진', '제주', '국문과', '이번에 국문과에 입학한 김경진이라고 합니다. 제주에서 왔어요.', '0534543');
INSERT INTO `student` VALUES (7, '박경호', '제주', '국문과', '박경호입니다. 잘 부탁드립니다.', '0134511');
INSERT INTO `student` VALUES (8, '김정인', '대전', '영문과', '김정인입니다. 대전에서 왔고, 영문과에 다닙니다.', '0034543');

primary key

  • 테이블 전체를 통틀어서 중복되지 않는 값을 지정해야 한다.
  • where 문을 이용해서 데이터를 조회할 때 가장 고속으로 데이터를 가져올 수 있다.
  • 테이블마다 딱 하나의 primary key를 가질 수 있다.
# 설정
PRIMARY KEY (`id`)

# 예시
SELECT * FROM student WHERE id=3;

unique key

  • 테이블 전체를 통틀어서 중복되지 않는 값을 지정해야 한다. (== primary key)
  • 고속으로 데이터를 가져올 수 있다.
  • 여러개의 unique key를 지정할 수 있다.
# 설정
UNIQUE KEY `idx_number` (`number`) USING BTREE

# 예시
SELECT * FROM student WHERE number=0534543;

normal key, 중복키

  • 중복을 허용한다.
  • primary, unique 보다 속도가 느리다.
  • 여러개의 키를 지정할 수 있다.
# 설정
KEY `idx_department` (`department`),
KEY `idx_department_name` (`department`,`address`)

# 예시
SELECT * FROM student WHERE department='국문과';
SELECT * FROM student WHERE department='국문과' AND address='제주';

Full Text

  • mysql의 기본설정(ft_min_word_len)이 4로 되어 있기 때문에 최소 4글자 이상을 입력하거나 이 값을 조정해야 한다.
  • mysql은 전문 검색 엔진이 아니기 때문에 한글 검색이 잘 안된다.
  • 전문검색엔진으로 lucene, sphinx 참고 (무료, 성능 좋음)
  • 스토리지 엔진 중 myisam에서만 사용가능
# 설정
ENGINE=MyISAM

# 예시
SELECT introduction, MATCH(introduction) AGAINST('영문과에') FROM student WHERE MATCH (introduction) AGAINST('영문과에');