2019.08.23 장고에서 Lock을 사용하여 멀티 스레드의 경합 막기

Lock

  • 멀티 스레드 방식에서 경합을 막는 가장 일반적인 방법
    • 하나의 프로세스에서 2개 이상의 스레드가 동시에 수행되는 경우, 스레드간에 프로세스 공간의 메모리를 공유한다.
    • 여러 스레드가 잠금없이 같은 객체를 수정하면 프로그램 자료 구조가 오염될 수 있다.(race condition)
  • 그것을 방지하기 위해 lock을 이용한다.

Lock 객체

  • Lock 객체는 locked와 unlocked 2가지의 상태를 가지며, acquire()와 release()의 2가지 함수만을 제공한다.
  • unlocked 상태에서 acquire가 호출되면 locked 상태로 바뀌고, locked 상태에서 release가 호출되면 unlocked 상태로 바뀌게 된다.
  • 락은 자물쇠처럼 이를 선점한 스레드가 락을 획득하면, 자물쇠가 잠긴다. 이후에 접근하는 스레드들은 락이 열릴 때까지 그 앞에서 멈춰기다렸다가, 락을 선점한 스레드가 락을 풀어주면 차례로 획득 해제를 반복하면서 순차적으로 처리하는 모양을 만들게 된다. 가장 명료한 비유는 칸이 하나 밖에 없는 화장실을 생각하면 된다.

파이썬 스레드

기본 사용 방향

conn = redis.StrictRedis(REDIS_SERVER_IP)
lock_id = '{}'.format(id)
lock = redis_lock.Lock(conn, lock_id, expire=5)

if lock.acquire(blocking=False):
    try:    
        원하는 코드

    finally:
        lock.release()
else:
    실패 했을 때 실행할 코드
  • 해당 lock_id를 통해서 그 특정 것에 대해 lock을 걸게 되므로 lock_id는 하나의 개개인으로 존재하는 것이 좋다.
  • 실패했을 때 어떤 것을 실행하면 좋을까?
    • 실행하다가 실패하게 되면 그 전 것들을 다 되돌려주는 작업을 진행하거나 해야한다.

'Django' 카테고리의 다른 글

Django select_related, prefetch_related에 대해서  (0) 2019.12.28
Django queryset filter와 exists()  (0) 2019.12.28
Django model(default, blank, null)  (0) 2019.12.28
Django admin message  (1) 2019.12.28
Django super & after save  (0) 2019.12.28

2019.08.23 장고 모델의 default, blank, null 에 관하여

django 모델을 설정할 때, default와 blank, null에 관해서

  • 먼저 장고 model을 설정 할 떄 필드의 옵션에 들어가는 default와 blank, null이 각각 무엇을 의미하는지 궁금해졌다.
  • 해당 내용에 대해서 찾아보았다.
The blank option is used in the form validation, and the null is used when writing to database.
So you might add null=True to that field.
EDIT: continue the comment
Considering the two steps when saving object:
1. Validator(controlled by blank)
2. Database limitation(controlled by null)

For default option, take IntegerField for example,


default=5, blank=True, null=False, pass (1) even if you didn't assign a value(having blank=True), pass (2) because it has a default value(5) and writes 5 instead of None to DB.


blank=True, null=False, which pass (1) but not (2), because it attempts to write None to DB.

Thus, if you want to make a field optional, use either default=SOMETHING, blank=True, null=False or blank=True, null=True.

장고 모델에서 저장이 될 때

  • 장고에서 해당 모델 object를 저장 할 때 2가지 과정을 거친다.
    • 첫 번째는 validator(controlled by blank) 즉 검증에서 가능한지 파악하고
    • 두 번째는 Database limitation(controlled by null) 데이터베이스에 넣을 때이다.

예를 들어서 설명해보자

  • 만약에 default=5, blank=True, null=False라고 하면
  • 값(blank=True)을 주지 않아도 통과하게 될 것이다. 왜냐하면 blank=True 때문에 validation을 넘어가게 되고 , default value가 5이기 때문에 db에는 None 대신에 5가 저장될 것이기 때문이다.
  • 하지만 만약에 default value가 없이, blank=True 그리고 null=False만 있다고 하면 통과 할 수 없다. validation은 통과하지만 null을 DB에 저장하려고 시도할 때 null=False이므로 저장할 수가 없다.

따라서 기본적인 옵션을 만들때는

  1. default= something , blank=True, null=False
  2. blank=True, null=True
  3. 위의 2가지 조건 중에 한개를 선택해주어야지만 저장이 될 수 있다.

'Django' 카테고리의 다른 글

Django queryset filter와 exists()  (0) 2019.12.28
Django Lock에 관해서  (0) 2019.12.28
Django admin message  (1) 2019.12.28
Django super & after save  (0) 2019.12.28
Django 트랜젝션  (0) 2019.12.28

2019.08.06 장고 admin 메시지 관련

django admin 페이지 메시지 전송

  • django admin 어디서든 해당 메시지를 첨부하면, 해당 알림들을 위에서 띄울 수 있다.
  • 굉장히 편리한 기능으로 admin에서 해당 동작이 잘 작동하였는지 확인할 수 있다.
  • 특히 누군가가 사용해야 하는 admin 페이지를 만들고 있다면 해당 메시지를 꼭 넣어주면 좋겠다.
  • 성공 메시지와 info 메시지는 색상이 같다 ㅎ

message.success(request, '성공 메시지')

7A9F03A3-CE4D-4CA1-8B8E-51158A2F6EF4

message.info(request, '안내 관련 메시지')

64436267-4F43-43BE-8C1A-74E57C0C96D5

message.warning(request, '경고 메시지')

3A331318-47E4-4B67-88B3-0F9150D00CB9

message.error(request, '실패 메시지')

828A35E5-63DE-47DD-9C49-2B97D33D9AAD

'Django' 카테고리의 다른 글

Django Lock에 관해서  (0) 2019.12.28
Django model(default, blank, null)  (0) 2019.12.28
Django super & after save  (0) 2019.12.28
Django 트랜젝션  (0) 2019.12.28
Django Django Rest API 흐름 읽기  (0) 2019.12.28

2019.08.01 장고 super와 after save

장고 super에 대해서

예시 장고 admin

class MyAdminView(admin.ModelAdmin):
       def save_model(self, request, obj, form, change):
           super(MyAdminView, self).save_model(request, obj, form, change)
  • def save_model을 오버라이딩 할 때
  • 오버라이딩은 기존에 만들어져있던 것을 바탕으로 덮어쓰기 한다는 것이 크다.
  • super를 써주는 이유는 super를 통해 기존에 구현되어 있던 save_model을 가지고 온다는 것이다.
  • super를 통해 기존에 있던 save_model을 지칭해주는 것이다.

after save

'Django' 카테고리의 다른 글

Django model(default, blank, null)  (0) 2019.12.28
Django admin message  (1) 2019.12.28
Django 트랜젝션  (0) 2019.12.28
Django Django Rest API 흐름 읽기  (0) 2019.12.28
Django Django Rest API 기본설명  (0) 2019.12.28

2019.07.31 장고 트랜젝션 사용하기

장고 트렌젝션 활용

트랜젝션

  • 정의 : 트랜잭션(Transaction 이하 트랜잭션)이란, 데이터베이스의 상태를 변화시키기 해서 수행하는 작업의 단위를 뜻한다.

  • 즉 하나의 실행 단위로, 존재하는 중간 과정들에 대해서 모두 성공하면 데이터베이스의 상태가 변하게 되고, 중간에 실패하면 기존에 작업들 모두 Rollback 된다.

  • 가장 중요한 부분은 특히 금융 부분이라고 생각한다. 단순한 계좌 이체 과정 중에도 굉장히 많은 과정들이 필요로 한다. 예를 들면 A가 B에게 입금 처리를 하게 되면 간단한 로직은 "A의 통장에서 돈을 인출한다. 그리고 그 돈을 B의 통장에 넣는다."일 것이다. 하지만 만약에 A의 통장에서 돈을 인출한다까지 실행 된 이후에 오류로 인해 B의 통장에 돈 입금이 안되는 상황이 발생될 수 있다. 그런 경우 A에서는 돈이 빠져나갔으나 B는 돈을 받지 못하는 상황이 발생하게 된다. 이런 경우를 대비하여 이 과정을 하나의 단위로 묶고 중간 과정에서 실패할 경우 롤백을 통해 A에도 돈이 빠지지 않도록 처리한다.

트랜젝션의 4가지 특성

79ACD57B-AFEA-4849-97E8-F68FBF0CEC4A

참고 : [DataBase] 트랜잭션이란? (Transaction)

Save Point

  • 트랜잭션의 길이가 길면 트랜잭션의 중간 지점에 수정내용을 반영하는 포인트를 만드는데, 이를 SAVEPOINT(저장점)라고 한다. 트랜잭션이 잘못되어 처음부터 다시 실행해야 할 경우 트랜잭션의 처음이 아니라 SAVEPOINT까지 되돌아가면 트랜잭션 전체가 ROLLBACK되는 것을 막을 수 있다. SAVEPOINT는 트랜잭션 안에 여러 개 만들 수 있다.

장고에서 트랜젝션 활용

'Django' 카테고리의 다른 글

Django admin message  (1) 2019.12.28
Django super & after save  (0) 2019.12.28
Django Django Rest API 흐름 읽기  (0) 2019.12.28
Django Django Rest API 기본설명  (0) 2019.12.28
Django 장고 related_name 설정방법  (1) 2019.12.28

2019.06.01 장고 rest api 흐름 읽기

흐름 순서

1. 모델 만들기

  1. 일반 우리가 진행하던 장고 프로젝트에 모델을 생성한다.

2. 시리얼화 하기

  1. serializers.py 생성하기
  2. serializer는 queryset과 모델 인스턴스와 같이 복잡한 데이터를 JSON, XML 또는 다른 콘텐츠 유형으로 쉽게 변환할 수 있다. 또한 serializer는 받은 데이터의 유효성을 검사한 다음 복잡한 타입으로 형변환을 할 수 있도록 serialization을 제공한다. REST framework의 serializer는 django의 modelform 클래스와 유사하게 동작한다.
  3. [Django REST Framework] Serializers :: 개인적인공간
  4. 시리얼화 학기는 Serializer클래스와 ModelSerializer 클래스 2가지가 있다.
  5. 시리얼화를 통해 우리는 JSON형태로 자료를 전송 혹은 저장할 수 있게 된다.

3. views.py 만들기

  1. 여러 개의 views를 작성하지 않고, 공통적인 행위들을 ViewSet에 하나로 그룹화하여 간결하게 사용할 수 있다.

4. URLs

  1. views에서 작성한 Viewset을 Router에 연결하면 url을 자동으로 맵핑해준다.

5. 참고

  1. [RESTful API in Django – WASD – Medium] https://medium.com/wasd/restful-api-in-django-16fc3fb1a238
  2. [[Django] Django Rest Framework(DRF) 알아보기 1부 :: 게임회사에서 살아남기] https://whatisthenext.tistory.com/126
  3. [django REST framework로 간단한 api 만들기] https://jamanbbo.tistory.com/43
  4. [개발자, Trend를 파헤치다. :: Django REST Framework를 사용하다 - 3] https://show-me-the-money.tistory.com/36
  5. [Django Restframework ModelSerializer 활용 방법] https://yunhookim.tistory.com/8?category=798766

'Django' 카테고리의 다른 글

Django super & after save  (0) 2019.12.28
Django 트랜젝션  (0) 2019.12.28
Django Django Rest API 기본설명  (0) 2019.12.28
Django 장고 related_name 설정방법  (1) 2019.12.28
Django django extentions 설치하기  (0) 2019.12.28

2019.05.30 장고 rest api 기본 설명

REST API란

  1. Reprresentational State Transfer라는 용어의 약자로 그대로 해석하면 대표적 상태 전송이다.
  2. REST API를 통해 REST 서버는 API를 제공, 클라이언트는 사용자 인증이나 컨텐스트(세션, 로그인정보)등을 직접 관리하는 구조로 각각의 역할이 확실히 구분되기 때문에 클라이언트와 서버에서 개발해야 할 내용이 명확해지고 서로간 의존성이 줄어들게 된다.
  3. 프론트앤드 개발자와의 협업을 위해서 꼭 필수!!

REST API 설계가이드

  1. URI는 정보의 자원을 표현해야 한다.
  2. 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현한다.
F653CAD8-7D3D-4DFB-BF9F-5289F9C26628

출처 : REST API 제대로 알고 사용하기 : TOAST Meetup

django REST API 처음 시작하기

API 설계의 장점

  • 보통 장고에서 프로젝트를 진행하면 views에서 하나의 템플릿에 하나의 클래스 혹은 함수가 할당되어 돌아가게 된다.
  • 그렇다보니 코드의 재활용성이 굉장히 떨어지게 된다.
  • 하지만 Django REST Framework를 사용하게 되면 그런 부분을 막을 수 있다.
  • 잘 표현된 사진이 있어서 가지고 왔다.!
FBCD0B97-DB7E-49FA-8BFE-966045F4E0DA

출처 : Django REST API의 필요성과 간단한 사용 방법 – 왕형준 – Medium

  • 핵심은 한 템플릿이 여러 API에서 정보를 받을 수 있게 된다.
  • 반대로 이야기하면 1개의 API가 여러 페이지에 활용될 수 있다는 것이다.
  • 코드의 재활용성이 파격적으로 높아진다.

REST API 사용의 장점

  1. 백엔드와 프론트엔드의 완전한 분리가 가능해진다는 것
  2. 정보 송수신이 자유로워져서 생산성이 급격하게 상승한다는 것

2019.05.27 장고 related_name 설정 방법

장고 related_name

장고에서 모델을 설정할 때 Foreign키를 설정해주고 뒤에 related_name을 설정해준다.
related_name을 설정해주는 것은 장고의 ORM기능을 활용하기 위해서이다.

하지만 이 related_name을 잘못 설정해주는 경우가 많다.
따라서 한 가지예를 들어가며 related_name에 대해 설멍해보려고 한다.

USER와 USER에 대한 Comment 설정

사용자들은 계속 생성될 수 있고 그 사용자 밑에 Comment(댓글)를 달아줄 수 있다고 생각해보자.
그럼 장고 모델의 형태는 이렇게 될 것이다.

13A89726-279D-4F18-8685-62D98770681D

User모델을 통해 User 객체는 1, 2, 3 지속적으로 생성될 것이고, (primary key)
Comment모델을 통해 Comment 객체 역시 지속적으로 생성 될 것이다. (foreign key)

여기에서 중요한 것은 Commnet모델에서 Foreignkey를 통해 User모델로 연결을 해놓았고
따라서 각각의 Comment들은 User_id를 통해 어떤 User의 커멘트인지 구분 할 것이다.

그럼 Comment의 모델을 한번 적어보자.

class Comment(models.Model):
    User = models.ForeignKey(User, on_delete=models.CASCADE, related_name= ? )

단순히 위와 같이 Comment 모델을 만들어 준다고 하면 related_name에는 무엇이 들어가야 할까?
처음 내가 프로젝트를 진행할 때 나는 related_name에 너무나도 자연스럽게 user를 넣어주었다.
related_name이 무언가 Comment의 User를 사용할 때 연관된 이름으로 무엇을 쓸 것이냐고
해석하였기 때문에 필드명과 똑같이 설정해준 것이다.

하지만 내가 새롭게 알게 된 것에 따르면
위에 적어놓았다시피 related_name은 장고 ORM모델을 위한 것이며,
ORM모델은 쿼리문 없이 장고에서 데이터베이스와 소통하기 위한 것이다.

따라서, 만약에 내가 User 필드의 related_name을 user라고 해놓았을 경우
해당 User의 Comment를 불러오기 위해서는 어떻게 해야 할까?

comment = user.user.all() 이라는 이상한 상황이 발생한다....

위의 내용은 comment는 해당 유저객체의 comment달린 것의 all (모든 것) 이라는
것인데 내가 comment객체에서 user에 대한 foreignkey의 related_name을 user로 설정해놓아
user.user.all()이라는 이상한 ORM이 만들어버린 것이다.

따라서 몇 가지 related_name을 생성해 줄 때 알아야 할 것이 있다.

  1. 폴인키로 연결시켜 놓은 클래스에 related_name = comment라고 해놓으면 user.comment라는 것이 comment모델에 생기는 것이 아나리 comment가 참조하고 있는 user모델에 생긴다.
  2. 따라서 참조해준 객체 입장에서 related_name을 설정해줘야 한다.

따라서 위의 경우 다시 한번 Comment모델을 적어보면

class Comment(models.Model):
    User = models.ForeignKey(User, on_delete=models.CASCADE, related_name= 'comment' )

라고 하는 것이 올바른 related_name을 설정해준 것이고 해당 유저의 comment를 모두 가지고 오고 싶을 때 사용할 ORM은 comment= user.comment.all()이 된다.

2019.05.21 장고 django extentions 설치하기

django extentions

  • django extentions 모듈은 장고의 기본 명령들의 기능을 확장해 주고 여러 부가 기능을 추가해주는 모듈로 장고 프로젝트의 필수 설치 모듈로 불리고 있다. 그 중에서 단연 편리한 기능 중에 한 가지는 모델 간의 관계도를 그려주는 기능과 데이터베이스를 초기화 하는 기능이다.

django extention 모듈 설치하기

pip install django-extensions

INSTALLED_APPS 에 추가해주기


INSTALLED_APPS = [ 

     'django_extensions'

]

설정값을 settings.py에 추가해주기

GRAPH_MODELS = {
    'all_applications' : True,
    'group_models' : True,
}

그래프 출력 기능을 위한 추가 모듈 설치하기

MAC

  1. xcode-select --install
  2. brew install graphviz
  3. pip install --install-option="--include-path=/usr/local/include/" --install-option="--libary-path=/usr/local/lib/" pygraphviz

설치 이후 그래프 생성 명령 입력하기

  1. 전체 모델에 대한 그래프 출력
    1. python manage.py graph_models -a -g -o model_graph.png
  2. 특정 앱에 대한 그래프 출력
    1. python manage.py graph_models board -o models.png

나온 관계도 확인하기

스크린샷 2019-05-21 오후 5 43 51

2019.05.21 장고 debug tool bar 설치하기

debug tool bar

  • 장고 프로젝트에서 성능 측정과 개선을 위해서 많이 사용하는 모듈
  • 여러가지 성능 측정 툴을 별도로 설치하거나 화면을 별도로 볼 필요없이 디버그 모드에서는 기능을 편리하게 사용할 수 있다.
  • 참고 : Installation — Django Debug Toolbar 1.11 documentation

debug tool bar 설치하기

`pip install django-debug-toolbar'

settings.py에 installed_apps와 middleware에 관련 항목 추가하기

INSTALLED_APPS = [

    'debug_toolbar'

]

MIDDLEWARE = [
    'debug_toolbar.middleware.DebugToolbarMiddleware'
]

settings.py에 패널 설정값과 internal_ips값 추가하기

INTERNAL_IPS = ['127.0.0.1']

DEBUG_TOOLBAR_PANELS = [
       'debug_toolbar.panels.versions.VersionsPanel',
    'debug_toolbar.panels.timer.TimerPanel',
    'debug_toolbar.panels.settings.SettingsPanel',
    'debug_toolbar.panels.headers.HeadersPanel',
    'debug_toolbar.panels.request.RequestPanel',
    'debug_toolbar.panels.sql.SQLPanel',
    'debug_toolbar.panels.staticfiles.StaticFilesPanel',
    'debug_toolbar.panels.templates.TemplatesPanel',
    'debug_toolbar.panels.cache.CachePanel',
    'debug_toolbar.panels.signals.SignalsPanel',
    'debug_toolbar.panels.logging.LoggingPanel',
    'debug_toolbar.panels.redirects.RedirectsPanel',
]

Root urls.py 파일에 내용 추가

if settings.DEBUG:
    import debug_toolbar
    urlpatterns = [
        path('__debug__/', include(debug_toolbar.urls)),

        # For django versions before 2.0:
        # url(r'^__debug__/', include(debug_toolbar.urls)),

    ] + urlpatterns

스태틱 파일 업로드 명령 실행

python manage.py collectstatic

해당 페이지에 접속하여 잘 되는지 확인해보기

B3FEB1F5-260B-41AF-9603-C952446D128E

  • 오른 편에 관련 내용이 잘 나오는 것을 확인할 수 있다.

2019.05.16 장고 댓글 삭제 및 수정 구현하기

댓글 수정 및 삭제 구현하기

  1. views.py 만들기
  2. 템플릿 만들기
  3. urls.py 만들기

update , delete 페이지에 접근했을 때 구동되는 로직들

함수형 뷰 구현하기


def comment_update(request, comment_id):

    comment = get_object_or_404(Comment, pk=comment_id)
    document = get_object_or_404(Document, pk=comment.document.id)

    if request.user != comment.author:
        messages.warning(request, "권한 없음")
        return redirect(document)

    if request.method == "POST":
        form = CommentForm(request.POST, instance=comment)
        if form.is_valid():
            form.save()
            return redirect(document)
    else:
        form = CommentForm(instance=comment)
    return render(request,'board/comment/comment_update.html',{'form':form})


def comment_delete(request, comment_id):

    comment = get_object_or_404(Comment, pk=comment_id)
    document = get_object_or_404(Document, pk=comment.document.id)

    if request.user != comment.author and not request.user.is_staff and request.user != document.author:
        messages.warning(request, '권한 없음')
        return redirect(document)

    if request.method == "POST":
        comment.delete()
        return redirect(document)
    else:
        return render(request, 'board/comment/comment_delete.html', {'object':comment})
  1. 해당 객체가 있는지 확인 : get_object_or_404, objects.get , objects.filter.exists
  2. 객체에 대한 권한 체크 - 작성자, 관리자
  3. get : 해당 페이지에 필요한 값 입력 받기
  4. post : 입력 받은 값에 대한 처리 -> 삭제 , 업데이트
  5. 처리 후 페이지 이동
  6. 여기서 중요한 것은 instance = comment를 넣어주어서 기존에 등록되었던 값이 보이도록 해줘야 한다.
  7. instance를 등록해주지 않으면 새로운 값으로 생각하고 새롭게 생성하게 된다.

클래스 뷰


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

class CommentUpdate(UpdateView):
    model = Comment
    fields = [ 'text']
    template_name_suffix = '_update'
    # success_url = '/'


    def dispatch(self, request, *args, **kwargs):
        object = self.get_object()
        if object.author != request.user:
            messages.warning(request, '수정할 권한이 없습니다.')
            return HttpResponseRedirect('/')
            # 삭제 페이지에서 권한이 없다! 라고 띄우거나
            # detail페이지로 들어가서 삭제에 실패했습니다. 라고 띄우거나
        else:
            return super(CommentUpdate, self).dispatch(request, *args, **kwargs)

from django.http import HttpResponseRedirect
from django.contrib import messages


class CommentDelete(DeleteView):
    model = Comment
    template_name_suffix = '_delete' 
    success_url = '/'

    def dispatch(self, request, *args, **kwargs):
        object = self.get_object()
        if object.author != request.user:
            messages.warning(request, '삭제할 권한이 없습니다.')
            return HttpResponseRedirect('/')
        else:
            return super(CommentDelete, self).dispatch(request, *args, **kwargs)
  1. 객체에 대한 권한 체크 - 작성자, 관리자 - dispatch
  2. 해당 객체가 있는지 확인 : get_object, get_queryset
  3. get : 해당 페이지에 필요한 값 입력 받기 - def get
  4. post : 입력 받은 값에 대한 처리 -> 삭제 , 업데이트 - def post
  5. dispatch를 통해 get과 post를 한번에 처리할 수 있고 class generic 뷰를 상속받아서 더 간단하게 구현 가능하다.
  6. 처리 후 페이지 이동

def dispatch에서 POST와 GET나누어서 처리하기

def dispatch(self, request, *args, **kwargs):
    object = self.get_object()
    if request.method == "POST":
        super().post(request, *args, **kwargs)
    else:   
        super().post(request, *args, **kwargs)

템플릿 만들기

delet.html (board/comment/comment_delete.html)


{ % extends 'base.html' % }
{ % block content % }
<!--진짜 지울건지 확인을 받아야 한다. form을 또 보여줄 필요가 없다.-->
<div class="alert alert-primary" role="alert">
  진짜로 {{object}}를 지우실 건가요?
</div>
<form action="" method ="post">
    { % csrf_token% }
    <input type="submit" value="Delete OK">
</form>
{ % endblock % }
  • form에 따로 받을 양식은 없다. post 형태로 오면 그냥 지우면 된다.

update.html (board/comment/comment_update.html)


{ % extends 'base.html' % }


{ % block content % }

<form action="" method = "post" enctype="multipart/form-data">
    { % csrf_token % }
    <table>
    {{form.as_table}}
        </table>
    <input type="submit" value="Write">

</form>

{ % endblock % }
  • update를 할 때 중요한 것은 바로 전체 자료가 남아 있도록 해야 한다.
 if request.method == "POST":
        form = CommentForm(request.POST, instance=comment)
        if form.is_valid():
            form.save()
            return redirect(document)
    else:
        form = CommentForm(instance=comment)
  • 여기서 알 수 있듯이 함수형 뷰로 해당 로직을 처리해줄 때는 꼭 instance= comment를 남겨서 기존에 데이터를 넘어오도록 해야 한다.

urls.py 연결하기 (함수형 뷰에서 구현했다고 가정)


from .views import comment_delete, comment_update

app_name = 'board'

urlpatterns = [
    path('comment/delete/<int:comment_id>/', comment_delete, name="comment_delete"),
    path('comment/update/<int:comment_id>/', comment_update, name="comment_update"),

]

잘 되는지 확인해보기

  • 해당 내용에 대해 잘 작동하는지 확인해본다.

2019.05.16 장고 댓글 기능 구현하기 함수형 뷰

댓글 기능 구현하기(함수형 뷰에서 구현하기)

  1. 모델이 있다고 꼭 그 모델의 뷰가 필요한 것은 아니다.

  2. 댓글 같은 경우 그 컨텐츠의 아래에 적히는 것이기 때문에 그 컨텐츠의 뷰에서 처리한다.

  3. 폼이 있어야 한다. form.py를 만들거나 템플릿에서 만들어줘야 한다.

  4. 한 뷰에서 처리하기(detail 뷰에서 처리하기)

    1. 모델 구현하기
    2. form.py 구현하기
    3. 뷰 구현하기
    4. 템플릿 구현하기
  5. 댓글 기능을 하나의 뷰로 빼서 처리하기

    1. 모델 구현하기
    2. form.py구현하기
    3. 뷰 구현하기
    4. 템플릿 구현하기
      1. form 의 action에 view를 연결하기 위해 url을 설정해줘야한다.
    5. url 연결해주기
      1. url을 뷰로 연결시켜준다.

모델 구현하기


class Comment(models.Model):
    document = models.ForeignKey(Document, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey(get_user_model(), on_delete=models.SET_NULL, null=True, blank=True, related_name='comments')
    text = models.TextField()
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    like = models.IntegerField(default=0)
    dislike = models.IntegerField(default=0)

    def __str__(self):
        return (self.author.username if self.author else "무명")+ "의 댓글"
  • get_user_model : 커스텀 모델 , 커스텀 USER를 넣거나 없으면 그냥 USER를 받음
  • author : models.SET_NULL : 연결된 foreignkey 즉 USER가 삭제되어도 Comment를 null로 바꾸고 남겨놓는다. 단 null=True가 필요하다.

form.py 구현하기

from .models import Document, Comment

class CommentForm(forms.ModelForm):
    #text = forms.TextInput(label = '댓글')

    class Meta:
        model = Comment
        fields = ['text']

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields['text'].label = "댓글"
  • text필드를 댓글이라는 문구로 바꾸어 줄 수 있다.
  • text = forms.TextInput(label = '댓글') 혹은
  • 아래의 def _init_ 을 통해 구현하기

views.py 뷰 구현하기

document_detail 뷰에서 구현할 때

def document_detail(request, document_id):

    document = get_object_or_404(Document, pk=document_id)

    #만약 post일때만 댓글 입력에 관한 처리를 더한다.

    if request.method == "POST":
        comment_form = CommentForm(request.POST)
        comment_form.instance.author_id = request.user.id
        comment_form.instance.document_id = document_id
        if comment_form.is_valid():
            comment = comment_form.save()


    #models.py에서 document의 related_name을 comments로 해놓았다.

    comment_form = CommentForm()
    comments = document.comments.all()

    return render(request, 'board/document_detail.html', {'object':document, "comments":comments, "comment_form":comment_form})
  • detail에서는 get_object_or_404로 하나만 뽑아내야 하고 따라서 pk키도 필요하다.

    • list에서는 get_list_or_404로 전체를 뽑아낸다.
  • document.comments.all() 아래에 와야지 논리오류가 발생하지 않는다.

  • comment_form 의 위치에 따라서 글자가 바뀐다.

    • comment_form.save 아래에 위치하게 되면 저장된 상태로 그대로 넘어가서 정보가 남아있게 된다.
  • self.instance = 상속받은 객체 그 자체

댓글 기능 따로 빼서 구현하기


def comment(request, document_id):

    if request.method == "POST":
        comment_form = CommentForm(request.POST)
        comment_form.instance.author_id = request.user.id
        comment_form.instance.document_id = document_id
        if comment_form.is_valid():
            comment = comment_form.save()

    return HttpResponseRedirect(reverse_lazy('board:detail', args=[document_id]))

# get_absolute_url을 설정해 놓았을 시(in models)
    def get_absolute_url(self):
        return reverse('board:detail', args=[self.id])

 제일 마지막 문장    return redirect(document)로  대체가능
  • 하지만 이렇게 해주면 해당 뷰로 접근하기 위한 url이 필요하게 되고 탬플릿의 form에서 action에 url을 설정해줘야 한다.
  • 그리기 위해서 또 urls.py에 url을 연결하여 해당 views로 접근하도록 한다.
  • 그리고 그 뷰를 실행하고 redirect를 활용하여 detail 페이지로 가도록 구현해준다.

템플릿 구현하기

  • document_detail.html

{ % extends 'base.html' % }


{ % block content % }

{{object.title}} 

{{object.text}}

{{object.image.url}}

{{object.author.username}}



<form action="{ % url "board:comment" object.id % }" method="POST">
{ % csrf_token % }

    {{comment_form.as_p}}
    <input type="submit" value="Comment" class="btn btn-outline-primary">

</form>

<table class="table table-striped">
    { % for comment in comments % }
    <tr>
        <td>{{ comment.text }}</td>
        <td>{{ comment.author.username}}</td>
        <td>{{ comment.created }}</td>
        <td><a href="{ % url 'board:comment_delete' comment.id % }">삭제하기</a></td>
        <td><a href="{ % url 'board:comment_update' comment.id % }">수정하기</a></td>
    </tr>
    { % endfor % }
</table>

{ % endblock % }
  • 만들어 놓은 comment_form을 가지고 온다.
  • 댓글 기능을 따로 구현하였다면 위와 같이 form의 action이 필요하고 그렇지 않다면 없어도 된다.
  • comments를 돌면서 관련 댓글을 뽑아오는데 이 comments는 detail 뷰에서 받아왔다.
    • document = get_object_or_404(Document, pk=document_id) 를 통해 특정 document에 대한 것들만 다 가지고 온다음에
    • comments = document.comments.all()를 통해 해당 document의 comment를 모두 가지고 왔다.
  • 옛날에 나는 일단 모든 comment를 다 가지고 온 다음에 템플릿 단에서 각 객체들의 id를 비교하여 보여줄 수 있도록 했었고
  • 그 다음에는 filter를 통해 구현해보려고 헀었다.
  • 하지만 그럴 필요 없이 미리 document를 pk값으로 특정 document를 가지고 오고 그 모든 comment를 템플릿으로 넘겨줌으로서 해결 할 수 있다.

2019.05.13 장고 페이지 서칭 기능 구현하기

서칭 기능 구현하기

  1. 데이터베이스에 질의문 (query 문)/ 애초에 해당애들만 뽑아오기
    1. documents = Document.objects.filter(title__contains = "1")
    2. documents = get_list_or_404(Document, title__contains = "1")
  2. 코드레벨 검색 (DB에서 다 가지고 온 다음에 뽑아내기 )
    1. 파이썬 코드레벨에서 검색
    2. 다 가지고 온 다음에 for문 같은 것을 돌려야 한다.
  3. DB에서 가지고 오는게 제일 빠른데 항상 병목 현상은 db에서 발생한다.
    1. 서비스가 느려지고 그럴 수 있다.
    2. 데이터베이스의 쿼리질의문을 줄인다.?
      1. 하나씩 물어보는 방법과 한번에 물어보는 방법이 있다.
      2. 하지만 그렇게 하면 질의문이 길어져서 해석하는데 시간이 오래 걸릴 수 있다.

로직 생각

  1. 검색어가 들어온다.
  2. 검색어를 받아서 제대로 받았는지 확인한다.
  3. filter 메소드를 사용한다.
    1. 어떤 항목을 검색할 것이냐?
      1. 내용/ 작성자 등
    2. 어떤 옵션으로 검색할 것이냐?
      1. 특정단어가 들어가있는지/ 처음에 시작하는지/ 대문자 / 소문자 등등

쉘에서 filter 연습하기

  1. objects는 manage이다. 모델명.objects. 인 경우
  2. filter( a, b) : a이고 b 인것
  3. filter(filed이름) = 매칭
  4. filter(필드이름__옵션)
  5. filter(title__startswitch = "") : 제목이 특정 문구로 시작하는 것
    1. (title__istartswitch) : 제목이 대소문자 구분 없이 특정 문구로 시작
    2. "" % : 뒤에 특정 애들이 붙을 수 있다.
  6. filter(title__endswitch = "") : 특정 문구로 끝나는지
  7. filter(title__iexact=="" ) : 대소문자 구분없이 특정 제목이 있나?
  8. filter(title__icontains = "") : 대소문자 구분없이 중간에 포함하고 있나?
  9. 우리가 이렇게 매칭한 것은 로컬필드가 검색이 가능하다.
    1. 기본 필드들이 로컬 필드 이다.
    2. 그럼 연결된 foreign key혹은 primary key는 어떻게 해야하나?
  10. filter(category__name="c1") : 카테고리 필드의 이름이 c1인 것
  11. filter(category__ name__endswith =" ") : 카테고리의 이름이 "" 으로 끝나는 애들
    1. %endswith : 앞에 특정 애들이 붙을 수 있다.
  12. 여러개 모델에서 찾는 것도 추가적으로 구현할 수 있다.
    1. by 일래스틱서치 - 우리의 스팩이 한줄 더 늘 수 있다.
    2. 한글 검색이 특히

정리(위의 예시를 바탕으로 정리한 것입니다)

  1. 필드명 = "값" 매칭
  2. 필드명__exact = "값" 매칭
  3. 필드명__iexact = "값" 대소문자 구분 없이 매칭
  4. 필드명__ startswich,필드명 __istartswich : "값"으로 시작
  5. 필드명__ endswitch, 필드명__iendswitch : "값"으로 끝
  6. 필드명__ contains , 필드명__icontains : "값"을 포함하느냐?
  7. Foreignkey 매칭
    1. 필드명__해당모델의 필드명 : 매칭
    2. 필드명__ 해당모델의 필드명__ 옵션 : 해당 모델의 필드명이 해당 옵션으로 매칭
  8. 필드명__ gt=값, 필드명__gte= 값 크다, 크거나 같다.
    1. ex) created__gt = 오늘 : 작성일이 오늘보다 크다. (오늘 00시 이후)
  9. 필드명__ lt =값 , 필드명__lte = 값 작다, 작거나 같다.
    1. ex) created__lt = 오늘 : 작성일이 오늘보다 이전이다. (datetime.now())
    2. ex) 판매시작일 __lt = 오늘 : 판매시작일 설정값이 오늘보다 작거나 같으면 판매시작

서칭 버튼 구현 (템플릿)

스크린샷 2019-05-14 오후 12 02 22

서칭 views.py 구현

26EFCA46-24B9-400F-BA1F-FA2653B79856
  • 서칭은 기본적으로 list를 띄어주는 상황에서 가능하다.
  • search_key가 없으면 그냥 get_list를 통해 모든 list를 가지고 온다.

서칭 심화 구현 - OR 구현하기

  • 특정 필드가 아닌 제목, 본문, 사용자이름과 관련해서 동시에 서칭하고 싶을 때!

Q 객체 사용하기

>>> from django.db.models import Q
>>> Q(title__icontains='1')
<Q: (AND: ('title__icontains', '1'))>
>>> default_q = Q(title__icontains='1')
>>> second_q = Q(text__icontains='1')
>>> default_q | second__q
<Q: (OR: ('title__icontains', '1'), ('text__icontains', '1'))>
>>> Document.objects.filter(default_q|second_q)

Q객체는 우리가 기존에 쓰던 필터를 그대로 쓰게 해주는데, 일반 필더안에서는 and만 가능해서 Q객체를 활용해서 or을 사용가능하게 된다.

  1. objects.filter() : filter 매서드에 들어가는 매개변수들은 항상 and 연산을 한다.
  2. or 연산을 하고 싶퍼서 Q객체를 사용한다.
  3. 사용법은 filter에 들어가는 매개변수의 작성법과 똑같다.
    1. Q() | Q() : OR
    2. Q() & Q() : AND
    3. ~Q() :NOT
    4. 구글 검색 연산자
      1. 작성자, 제목, 본문에 대해서 다 검색할 수 있도록
      2. 체크박스 보이기
      3. 체크박스 값확인
      4. Q객체를 어떻게 만들 것인가?

제목, 본문에서 해당 내용 같이 검색하도록 하기 (by Q객체 - views.py)

13E315E3-4C12-4F4F-85F3-D58C61BB2C93
  • title_q와 text_q를 변수로 받고 Q 객체를 활용하여 OR 필터가 가능하도록 한다.
  • 그 이외는 모두 한개의 필터를 사용할 때와 같다.

체크박스로 작성자/ 제목/ 내용 등을 구분할 때

views.py 구현하기

4C77E9AE-3BE4-4663-BBCD-309372DBBD94
  1. request.METHOD.get : 마지막 들어오는 1개만 표시
  2. request.METHOD.getlist : 리스트 형태로 모두 가지고 온다.
    1. search_type = request.GET.getlist('search_type',None)
  3. 기본적으로 search_type이 없으면 text에서 검색하도록 한다.
  4. 각각 title/ text/ username이 들어왔을 때에 따라서 분기하여 구현한다.

templates 구현하기

스크린샷 2019-05-14 오후 8 45 29
  • form을 get 방식으로 받아오고 3가지를 모두 search_type이라는 이름으로 받아온다.
  • 따라서 views.py에서 getlist를 통해 모두 가지고 와야 한다.

2019.05.13 장고 페이징 기능 구현하기

페이징

  • 컨텐츠를 5개 혹은 10개 등 정해진 갯수에 따라 페이지를 나누어 구현하는 방법

페이징을 배우기 전에 알면 좋은 상식

list에서 불러올 때 컨텐츠가 없을 때

  • Document.objects.all() == get_list
  • 따라서 get_list_or_404를 하면 2가지의 이점을 얻을 수 있다.
    • 해당 Document를 모두 불러올 수 있고, 404 페이지 역시 띄어줄 수 있음

list

  • 폼을 통해서 들어오는 애를 request.POST.get('')을 통해 정보를 가져오고
  • 그냥 들어오는 애들은 get방식 request.GET.get('')을 통해 정보를 가져온다.
    • 이게 주소 뒤에 따라오는 애들을 얻는 방식이다.

페이징 기능 구현 (페이지를 쪼개는 것)

  • 페이지당 몇개를 보여줄것이냐? 그리고 현재 페이지가 중요하다.
  • page = request.GET.get('page',1)
    • 현재 페이지 번호를 가지고 오고 없으면 1을 반환한다.
    • int로 받아와 이것을 start_index와 end_index에도 활용
  • paginated_by = 3 : 한 번에 최대 3개를 보여준다.
  • QuerySet 객체를 슬라이싱 할 때 [시작번호:끝번호]
    • 첫 번째 페이지 0 : 2 == >[ paginated_by x (page-1) : paginated_by x (page) ]
    • 두 번째 페이지 2 : 5 == >[ paginated_by x (page-1) : paginated_by x (page) ]
    • 세 번째 페이지 5 : 8 == >[ paginated_by x (page-1) : paginated_by x (page) ]
    • 만약에 한 페이지에 2개씩 띄어준다고 하면
    • 첫 번째 페이지는 0번에서 1번까지 가지고 오고 [0:2]
    • 두 번째 페이지는 2번에서 3번까지 가지고 오고 [2:4]

페이징 views.py 설계하기

EDAE0AF3-DA6A-4202-9D6F-18AF73EC05C9
  • 이후 입력 url : http://127.0.0.1:8000/?page=1
  • list로 처음에 접속하게 되므로 request.GET.get을 통해 페이지 넘버를 가지고 오고
  • 그 페이지넘버를 통해 documents의 인덱싱으로 노출될 부분을 결정하고 그것을 render를 통해서 반환을 해준다.

페이징 views.py 심화 설계하기(아래 페이지 넘버 구현하기)

9285CCA0-F190-4902-86F6-955B9C5F2E03
  • total_count와 total_page, page_range를 document_list에 보내줌으로서 document_list에서 훨씬 많은 것을 구현 할 수 있다.
  • page_range를 통해 이후 템플릿에서 페이지 넘버를 출력하도록 구현한다.

페이징 템플릿 구현하기

C4EA49CD-90ED-4A62-AA99-1457B2489F97
  • 기본적으로 한번 document 인덱싱을 통해 넘어온 자료이므로 그대로 출력을 해주면 된다.
  • 추가적으로 아래에 pagination을 (bootstrap) 달아서 해당 버튼을 클릭할 때 board:index url로 이동하도록 하고 그 주소 뒤에 page={{page}}를 통해 해당 페이지의 url을 연결한다.
  • 또한 {{page}}를 통해 번호도 띄어준다.
  • 이렇게 구현하면 알아서 컨텐츠가 많아져서 페이지가 넘어가면 추가 된다.

2019.05.10 장고 정적파일에 관한 이야기

장고 정적 파일 : Static file 과 Media file

참고 : 6. Django 정적 파일 기능 이해하기 · Kay on the rails

  1. 장고는 정적 파일을 제공하는 실 서비스용 기능을 제공하지 않는다.
843801B8-F61F-492C-8523-0C854876DF52
  • 정적 파일을 제공하는 것은 웹 서버의 전문 영역이기 때문에 개발 단계에서 쓸 정적 파일을 제공하는 기능을 한다.
  • 장고를 이때까지 웹 서버라고 생각했는데 장고는 웹 애플리케이션 서버였구나!!

장고의 정적 파일의 2종류

Static file

  1. 웹 서비스에서 사용하려고 미리 준비해 놓은 정적 파일
  2. setting.py에 관련 항목 3가지 사용
    1. STATICFILES_DIRS
      1. 개발 단계에서 사용하는 정적 파일이 위치한 경로들을 지정하는 설정 항목
    2. STATIC_URL
      1. 웹 페이지에서 사용할 정적 파일의 최상위 URL
    3. STATIC ROOT
      1. Django 프로젝트에서 사용하는 모든 정적 파일을 한 곳에 모아 넣는 경로
      2. 개발과정에서 DEBUG가 True가 되어있다면 동작하지 않고 별 문제가 없지만 실 서비스 환경에서는 중요하다
    4. python manage.py collectstatic 이라는 명령어를 통해 모든 Static file을 모은다.
    5. 여기에 모인 정적 파일들은 장고가 아닌 웹서버에서 활용한다..

Media file

  1. 이용자가 웹에서 올리는 upload 파일
  2. setting.py에 관련 항목 2가지 사용
    1. MEDIA_ROOT
      1. 업로드가 끝난 파일을 배치할 최상위 경로를 지정
    2. MEDIA_URL
      1. Media file의 최상위 URL
    3. MEDIA_URL + MEDIA_ROOT : 실제 접근 주소
  3. urls.py에 urlpatterns를 추가해줘야지 접근할 수 있다.
  4. urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

추가 사항

  1. Static file와 Media file은 정적 파일이라는 점에서 같지만 정적 파일을 제공하는 상황을 예측할 수 있는지 여부는 다르다.
  2. 개발단계에서 쓸 정적파일을 제공하는 기능(DEBUG=True인 상황)
    1. urls.py에 urlpatterns에 URL패턴을 추가해준다. (for media file)
    2. Static file의 경우 위와 같은 처리를 하지 않아도 개발 단계에서 잘 제공되나, media 파일은 위의 단계를 꼭 거쳐줘야 한다.

+ Recent posts