import Adsense from ”@/components/Adsense.astro”;
Django ORM 기술 글 모음
장고에서 ORM을 이용하는 게 가장 기본인데, 필요한 글들을 모아본다. 계속 구글링 해서 지친다. 그냥 정리해 두는게 가장 좋은 방법이다.
기본 CRUD 기능
기본 Queryset 사용법을 정리한 블로그입니다.
[Django] Django ORM queryset 정리(model, filter, all, get, filter, exists, create, save) ## F(), Func(), and, or, not 필터 사용법
기본적인 설명이 아주 잘 되어 있어요 굿페이지
Django Query Expressions (1) – F(), Func(), Q(), 그리고 and, or, not 보너스 하나더,
[Django] F Expressions ## Transaction, Update 관련 글
[django] model update - 객체 다시 DB 에서 읽어오는 method – refresh_from_db()
3. 모델 객체를 데이터베이스에서 다시 읽어들일 수 있나요? — Django ORM Cookbook 2.0 documentation
장고 ORM Transaction 써보기
여러 테이블에 걸쳐서 동시에 수행이 딱딱 같이 되어야 하는 몇개의 일련적인 동작의 일치성을 위해 transaction 기능을 써보도록 하자! 관련하여 아주 좋은 블로그 글 링크가 있어서 일단 소개합니다.
Django DB Transaction 1편 – Request와 DB Transaction 묶기(Feat. ATOMIC_REQUESTS) 다른건 몰라도, 사용법은 간단하게 아래와 같이 구성했다.
with transaction.atomic() 이 구문이 전부이다.
try: with transaction.atomic(): query1 query2 query3 ... except Exception as e: print("re", str(e)) return 400, make_message_response(400, str(e))
쿼리들 사이에 오류가 발생하거나 사용자 에러가 발생하면 그냥 단순하게 raise Exception("에러메시지")
이렇게 하면 이미 수행된 쿼리들의 결과를 다시 롤백해주는 효과를 가져다 준다.
위의 블로그에서 소개한데로 request 별로 아예 ATOMIC 을 잡아주는 옵션이 있지만 자기가 원하는 대로 설계하는 것이 좋지 않나 싶다… 암튼 굿럭~
많은 곳에서 언급되지만, try except 는 with transaction.atomic() 바깥으로 해야 한다고 강력히 권고된다. 주의하세요. 😊
Django migration 취소 제거 방법
그냥 migrations 폴더에서 스크립트 파일들을 막 지우면 문제가 생긴다. 다음 순서대로 차분하게 정리해 보자.
참고로 장고에서 migration 이란?
from https://tibetsandfox.tistory.com/24
> DB 스키마를 git처럼 버전으로 나눠서 관리 할 수 있게 해 주는 시스템
Show me the migrations
먼저 migrations history 를 보자
python manage.py showmigrations
그럼 다음 화면처럼 지금까지 한 역사가 나타난다.
<figure class=“kg-card kg-image-card”></figure>앱이름과 적용한 migrations 내용이 나타난다.
특정 위치로 돌아가기
여기서 돌아가고 싶은 곳으로 취소하고 싶다면, (이미 적용된 것을 취소하고 싶다면) 아래 커맨드를 참고한다. 즉, testapp의 0001번으로 돌아가고 싶다면,
python manage.py migrate testapp 0001
아예 0001 까지 없애고 싶다면, 돌아갈 곳이 없는데 어떻게 하느냐?
python manage.py migrate testapp zeroORpython manage.py migrate --fake testapp zero
삭제하기
migrations 폴더로 가서 원하는 파일을 순서대로 지우면 된다. 의존성이 있으니깐 뒤에서 원하는 지점까지는 전부 지워서 없애는게 나을 듯,
<div class=“kg-card kg-callout-card kg-callout-card-grey”><div class=“kg-callout-emoji”>😊</div><div class=“kg-callout-text”>__init__.py 파일을 안 지우는게 좋을 듯 합니다. DB를 다 날릴 거 아니라면</div>``</div>from https://tibetsandfox.tistory.com/24
이 때 0002 마이그레이션 파일을 삭제하고 싶다면,
1. python manage.py migrate app_name 0001 명령어로 이전 버전으로 적용시킨후 삭제
2. python manage.py migrate app_name zero 명령어로 마이그레이션을 초기화 시킨 수 삭제
둘 중 하나의 절차를 거친 후 삭제</u>하서야 합니다.
초기화
DB에는 스키마가 남아 있으니 혹시 재 초기화 하고 싶다면 fake-initial 을 …
python manage.py migrate --fake-initial
관련된 내용은 여기 참고 (https://velog.io/@kho5420/Django-Django-마이그레이션-초기화 )
참고 페이지
훨씬 자세한 내용 😁 못이겨 못이겨
05) 마이그레이션 파일 제거 - 딱 필요한 것만 요점 정리
Django – makemigrations 취소하는 방법 · 지혜의 개발공부로그 - migrations 에 대한 전반적인 내용
장고(Django) – 마이그레이션(Migration)
Django template – list empty 처리
빈 리스트를 장고 템플릿에서 표현하는 방법은 아래 처럼 <strong>\{\% empty \%}</strong> 구문을 활용하자!
{% raw %}
{ % for item in itemlist % } { % if item . is_deleted == 1 % } { % else % } { % now " Y-m-d " as todays_date % } < tr class= " align-middle " > < td class= " white-space-nowrap ps-0 " > [ {{ item . id }} ] ( { % url ' orderitem_view ' pk=item.id % } ) </ td > < td class= " align-middle " > < p class= " mb-0 " > {{ item . product }} </ p > </ td > < td class= " align-middle text-end " > < p class= " mb-0 " > {{ item . date_due | date : " m-d " }} </ p > </ td > < td class= " align-middle text-end " > < p class= " mb-0 " > {{ item . quantity | intcomma }} </ p > </ td > < td class= " align-middle text-end " > < p class= " mb-0 " > {{ item . workline }} </ p > </ td > { % endif % } </ tr > { % empty % } < tr class= " align-middle " > < td colspan = " 5 " class= " align-middle text-center " > 출고예정 주문이 없습니다 . </ td > </ tr > { % endfor % }
코드에 편리한 게 하나 더 있네 😀 intcomma 도 활용하면 숫자에 콤마를 찍어줘서 읽기 편하게 해준다. 이건 다른 포스팅에서 참고하세요.
혹은 <strong> {% if %} </strong> 구문으로도 판단할 순 있다. 원본은 여기 https://docs.djangoproject.com/en/4.0/ref/templates/builtins/
{ % if athlete_list % } Number of athletes : {{ athlete_list | length }}{ % else % } No athletes .{ % endif % }
{% endraw %}
Django Template – url 함수 호출
url 함수로 템플릿에서 원하는 함수를 호출할 수 있다. 호출 방법은 다음과 같다. 이 페이지에 나오는 예제 구문은 여기(https://wikidocs.net/70741 ) 페이지를 방문해서 더 자세한 내용을 살펴보시면 좋겠네요.
{% raw %}
{% url 'detail' question.id %}
함수에 넘겨주는 인자가 있다면 다음과 같이 사용할 수 있다. 인자명 없이 그냥 순서대로 넣어줘도 동작한다. 대신 순서가 틀려지면 안된다. 😁
{% url 'detail' question_id=question.id %}
{% url 'detail' question_id=question.id page=2 %}
{% endraw %}
이상.
Django – ORM Coalesce() 활용
읽기도 어려워 Coalesce() 함수를 사용해 보자. 엄청 유용한데 잘 안 알려져 있는 듯
None, Null 이 리턴되는 경우 Exception 이 많이 나오는데, 이를 다 처리해 두지 않은 경우 의도치 않는 상황에 빠지게 되는 경우가 많다. 특히 aggregation 함수들을 사용하는 경우 기본적으로 어떤 값을 리턴해 주도록 해 주는 것이 좋다. 0이나 1 이런 값들을 기본값으로
당연히 잘 써둔 글들이 있다 😁
Django – Coalesce를 사용하여 aggregate가 None을 반환하는 것을 방지하기 · 초보몽키의 개발공부로그 “python
total_price = WishBook.objects.filter(created_at__year=‘2017’, created_at__month=‘09’).aggregate(total=Sum(‘price’))[‘total’] or 0# ORfrom django.db.models.functions import Coalescetotal_price = cls.objects.filter(created_at__year=year, created_at__month=month).aggregate(total=Coalesce(Sum(‘price’), 0))[‘total’]
예제에서 제공되는 or 도 괜찮은 방법 같다.
**기본적으로 값이 없을 경우가 있는 Count(), Aggregate() 이런 함수들을 쓸 때 기본적으로 무조건 쓰도록 하자!**
## 참고 페이지
- 반드시 좀 읽어서 습득하자!
[[Django] 반드시 알아야 할 5가지ORM 쿼리](https://chrisjune-13837.medium.com/django-%EB%8B%B9%EC%8B%A0%EC%9D%B4-%EB%AA%B0%EB%9E%90%EB%8D%98-orm-%EA%B8%B0%EC%B4%88%EC%99%80-%EC%8B%AC%ED%99%94-592a6017b5f5)..@vdekr9
---
### Django – ORM union 사용해보기
말로만 듣던 union() 을 사용해 보려 찾아본다. 사실은 쿼리셋 2개를 하나로 합치기 위해서 찾아봄. 아래 링크에서 정답을 찾을 수 있고, union 부분만 발췌
<figure class="kg-card kg-bookmark-card">[<div class="kg-bookmark-content"><div class="kg-bookmark-title">[Django] queryset 결과 합치기`</div>`<div class="kg-bookmark-description">합칠 대상의 결과가 2개 이상인 경우 union() union()를 사용해서 1개로 합칠 수 있습니다. union()의 2번째 인자는 중복을 허용할지에 대한 여부인데 기본값은 False로 중복을 허용하지 않습니다. (중복데이터일..`</div>`<div class="kg-bookmark-metadata"><span class="kg-bookmark-author">TISTORY`</span>`<span class="kg-bookmark-publisher">불곰1`</span>``</div>``</div>`<div class="kg-bookmark-thumbnail">`</div>`](https://brownbears.tistory.com/426)`</figure>`> union()를 사용해서 1개로 합칠 수 있습니다. union()의 2번째 인자는 중복을 허용할지에 대한 여부인데 기본값은 False로 중복을 허용하지 않습니다. (중복데이터일 경우 중복제거)
`result = a.union(b, all=True)`
그냥 장고 파이썬 코드에서 (ORM이 아닌) 쿼리셋 결과들끼리 `|` 연산자로 합쳐도 된다고 하네요.
`result = a | b`
@vdekr9
---
### Django – ORM values, values_list, list 결과
맨날 헷갈리는데 ORM Queryset 의 결과를 리스트로 받아 쓰는 경우가 많아 **values(), values\_list(), list()를 자주 쓰게 되는데 확실히 결과가 어떻게 다른지 알아보자.**
말보단 코드를 짜서 결과를 보고 비교해 보는 게 확실히 이해하는데 크게 도움이 된다.
```python
result_by_orderitem = Result.objects.filter(starttime__year=s_year).order_by("orderitem", "-starttime").distinct("orderitem").values("orderitem") print(result_by_orderitem) result_by_orderitem = Result.objects.filter(starttime__year=s_year).order_by("orderitem", "-starttime").distinct("orderitem").values_list("orderitem") print(result_by_orderitem) result_by_orderitem = Result.objects.filter(starttime__year=s_year).order_by("orderitem", "-starttime").distinct("orderitem").values_list("orderitem", flat=True) print(result_by_orderitem) result_by_orderitem = list(result_by_orderitem) print(result_by_orderitem)
예제로 짠 파이썬 코드의 결과를 보면,
< QuerySet [{ ' orderitem ' : 278 }, { ' orderitem ' : 279 }, { ' orderitem ' : 280 }, { ' orderitem ' : 284 }, { ' orderitem ' : 287 }, { ' orderitem ' : 288 }]><QuerySet [( 278 , ), ( 279 , ), ( 280 , ), ( 284 , ), ( 287 , ), ( 288 , )]><QuerySet [278, 279, 280, 284, 287, 288] > [278, 279, 280, 284, 287, 288]
values 는 {필드명:값} 형태의 딕셔너리 형태의 Queryset 의 리스트 형태로 리턴 되어 있고
values_list 는 필드명을 지정해 둬서 튜플 형태의 (값, 값, …) 의 Queryset 리스트로 리턴되고,
plat=True 옵션을 주면 튜플 형태가 없어지고 그냥 값들의 리스트 형태와 유사한 Queryset 리스트를 리턴하고,
맨 마지막 값을 그냥 list(queryset) 형태로 호출하면, 그냥 순수한 값들의 리스트로 리턴되고 있음을 볼 수 있다.
..@vdekr9
Ajax html 페이지 가져오기
간단한 Ajax 로 페이지 가져오기 예제
자세한 내용은 Ajax + Django로 한 페이지에서 다른 html 띄우기 에서 읽어보세요!
< button id='listButton' >< /button >< div id='example' >< /div >
해당 페이지에 아래 스크립트를 추가하면 , example 로 지정한 div에 결과 내용이 출력되게 되어 있다. 접속 주소만 쓰면, 활용할 수 있다.
로딩중 처리를 위해서 “LoadingOverlay”라는 라이브러리를 활용하는 것도 같이 넣어 봤다.
{% raw %}
``` text
$('#listButton').click(function() {// Show full page LoadingOverlay $.LoadingOverlay("show"); $.ajax({ type: "GET", url:"{% url 'result_list' %}", // list url을 불러옴 dataType: 'html', // list의 형태는 html success: function(data){ // 성공했을 때 일단 good을 alert로 띄운다. alert('good'); $('#example').html(data) // 그 이후에 example div에 list html의 data를 가져온다. }, error: function(request, status, error){ // 실패했을 때 bad alert, 에러가 무엇이었는지, 그리고 간단한 html 출력 alert('bad'); alert(error); $('#example').html('AJAX 통신에 실패했습니다.'); } }) $.LoadingOverlay("hide");});
`{% endraw %}`
## 페이지 로딩 중 자바스크립트
JQuery를 이용한 로딩 처리, 아래 페이지에 예와 라이브러리 참고하자!
<figure class="kg-card kg-bookmark-card">[<div class="kg-bookmark-content"><div class="kg-bookmark-title">jQuery LoadingOverlay – Gaspare Sganga`</div>`<div class="kg-bookmark-description">A flexible loading overlay jQuery plugin`</div>`<div class="kg-bookmark-metadata"><span class="kg-bookmark-author">Gaspare Sganga`</span>`<span class="kg-bookmark-publisher">Gaspare Sganga`</span>``</div>``</div>`<div class="kg-bookmark-thumbnail">`</div>`](https://gasparesganga.com/labs/jquery-loading-overlay/#examples)`</figure>`
---
### Django – ForeignKey display text\n변경하기
modelforms 에서 자동으로 값을 가져오긴 하는데 \_\_str\_\_ 에 정의된 글자나 키 값을 기본으로 보여준다. 부가적으로 정보를 더 보여주고 싶은데 `__str__` 을 바꾸면 전체에 이 모델을 접근하는 부분이 다 바뀌므로, 딱 combo에 올라가는 글자만 변경하고 싶다면,
> label\_from\_instance 속성을 건드리면 된다.
자세한 설명은 아래 링크를 참고해 보시고,
[How to change ForeignKey display text in the Django Admin?](https://stackoverflow.com/questions/6836740/how-to-change-foreignkey-display-text-in-the-django-admin)
예제 코드를 살펴보면,
```python
subform.fields["product_profile"].queryset = get_user_productprofile(companyid=_companyid, is_superuser=_superuser).order_by("name")subform.fields["product_profile"].label_from_instance = lambda obj: "%s(%s)" % (obj.name, obj.product.name)
queryset 을 통해 필터링 결과를 넣어 줄 수 있고
label_from_instance 통해 display text 를 변경할 수 있다. 예제에서 필드명(상품명) 상태로 보이도록 수정한 버전이다.
` `## 참고 사이트
람다 lambda 를 사용하고 있는데, 관련 기초 정보는 여기를 가보세요.
[3.5 람다(lambda)`
`
오늘은 람다 형식과 그것을 이용하는 여러 가지 함수들에 대해서 알아보겠습니다. 당장 완벽하게 소화하실 필요는 없을 것 같구요, 가벼운 마음으로 이런 것이 있다는 정도만 아셔 …`
`
위키독스` ``
``
``
`](https://wikidocs.net/64)` `
model form에서 추가로 별도의 사용자 필드를 추가하고 싶다면, 아래처럼 추가로 forms.필드타입 으로 선언해서 사용하면 된다.
label, widget 용례도 참고해 보면 좋다. required=False 도 옵션으로 주면, 필수 항목으로 추가 되지 않는다.
class FirmwareForm ( forms . ModelForm ): is_fileinclue = " enctype=multipart/form-data " autogen = forms . BooleanField ( label = _ ( " 중복시 자동변경 " ) , widget = forms. CheckboxInput ( attrs = { " class " : " form-check-input " , " type " : " checkbox " } ) , required = False ) class Meta : model = Firmware fields = [ " company " , " dtype " , " content " ] labels = { " company " : _ ( " *회사 " ), " dtype " : _ ( " 타입 " ), " content " : _ ( " 파일 " ), } widgets = { " company " : forms . Select ( attrs = { " class " : " form-select " } ), " dtype " : forms . Select ( attrs = { " class " : " form-select " , " required " : True , " placeholder " : " 타입 " } ), # "content": forms.FileInput(attrs={"class": "form-control", "style":"width: 100px;"}), # "content": forms.FileField('첨부 파일', upload_to='uploads/'), }
사용할 때는 일반 필드와 동일하다.
if form.cleaned_data["autogen"]:
이런 식으로 값을 바로 보면 된다.
참고 사이트
예제 코드를 볼 수 있다. Django Forms BooleanField 예제 코드들
class CopyPermissionForm ( forms . Form ): """ Holds the specific field for permissions """ copy_permissions = forms . BooleanField ( label = _ ( ' Copy permissions ' ) , required = False , initial = True , )
[django.forms BooleanField Python Code Examples`
`
Python code examples to show how to use the BooleanField class within the forms module of the Django open source project.`
`
Full Stack Python` ``
``
``
`](https://www.fullstackpython.com/django-forms-booleanfield-examples.html)` `
Django ORM – order_by 리스트로
쿼리셋에서 order_by() 를 필드 하나만 하는 경우는 머 고려할 것도 없이 그냥 필드명을 적어주면 된다.
2개 이상은
queryset.order_by("필드1", "필드2")
이렇게 주면 된다.
파이썬 코딩에서 필드명을 리스트를 만들고 그 리스트를 바로 넣어주면 편하다고 생각했는데, 그대로 되네
order_field = " name " order_field = s_sort_order + order_field order_field_array = [] order_field_array . append ( " company " ) order_field_array . append ( order_field ) search_itemlist = product_list . order_by ( * order_field_array )
*필드배열 을 넣어주는 것이 핵심!
Django ORM – 쿼리셋 합치기
가장 기본적인 것 같은데, 막상 하려면 다 찾아봐야 해
장고 ORM 쿼리셋 2개를 하나로 합치고 싶다면
union() or “|” 를 이용하면 된다. 자세한 내용은 아래 링크로
result = a.union(b, all=True)
[[Django] queryset 결과 합치기`
`
합칠 대상의 결과가 2개 이상인 경우 union() union()를 사용해서 1개로 합칠 수 있습니다. union()의 2번째 인자는 중복을 허용할지에 대한 여부인데 기본값은 False로 중복을 허용하지 않습니다. (중복데이터일..`
`
TISTORY` `불곰1` ``
``
``
`](https://brownbears.tistory.com/426)` `아니면, 쿼리셋 2개를 OR 하자
queryset = User . objects . filter ( first_name__startswith = ' R ' ) | User . objects . filter ( last_name__startswith = ' D ' ) queryset < QuerySet [ < User : Ricky > , < User : Ritesh > , < User : Radha > , < User : Raghu > , < User : rishab > ] >
참고 사이트는 여기
[2. OR 연산으로 일부 조건을 하나라도 만족하는 항목을 구하려면 어떻게 하나요? — Django ORM Cookbook 2.0 documentation`
`
`
`
`
``
``
`](https://django-orm-cookbook-ko.readthedocs.io/en/latest/or_query.html)` `
Django Template – with 변수 선언
변수가 너무 길어서 엄청 불편함, 좀 줄인 변수에 넣어서 해보고 싶어서 좀 찾아봄, 예를 들어 request.user.profile.company.name 이런 식의 변수를 간단하게 쓰면 좋겠다 😁
with 구문으로 페이지에 쓰일 사용자 변수를 선언해서 쓸 수 있네.
{% raw %}
```text
{% with total=business.employees.count %} {{ total }} employee{{ total|pluralize }}{% endwith %}
복수 개를 선언 하려면, 한 칸 띄우고 선언을 쭉 하면 된다.
> You can assign more than one context variable:
```django
```text
{% with alpha=1 beta=2 %} ... {% endwith %}
참고로, 이전에 `with as` 구문으로 사용했다고 하네.
<div class="kg-card kg-callout-card kg-callout-card-blue"><div class="kg-callout-emoji">😊`</div>`<div class="kg-callout-text">The previous more verbose format is still supported:
`<strong>`{% with business.employees.count as total %}`</strong>`</div>`</div>`## 참고 페이지
`{% endraw %}`
장고 사이트에서 with 구문 위치
<figure class="kg-card kg-bookmark-card">[<div class="kg-bookmark-content"><div class="kg-bookmark-title">Built-in template tags and filters | Django documentation | Django`</div>`<div class="kg-bookmark-description">`</div>`<div class="kg-bookmark-metadata"><span class="kg-bookmark-author">Django`</span>``</div>``</div>`<div class="kg-bookmark-thumbnail">`</div>`](https://docs.djangoproject.com/en/4.1/ref/templates/builtins/#with)`</figure>`
---
### Django – 유효성 검사에 대한 글
수많은 정보를 저장하고 꺼내오고 해야 하는 시대에 Django 로 서비를 만들고 하다보면, 각각 필드의 유효성 검사에 대한 고민이 엄청 많다.
폼에서 하나 자바스크립트로 하나 model 에서 하나…등등등 아래 글에서 인사이트를 얻어보자 잘 정리해 두셨네. 강의를 들으면서 정리하신 내용 같은데 많은 도움이 되었어요 😊
<figure class="kg-card kg-bookmark-card">[<div class="kg-bookmark-content"><div class="kg-bookmark-title">Django Form Validation`</div>`<div class="kg-bookmark-description">출처 : https://www.inflearn.com/course/%ED%8C%8C%EC%9D%B4%EC%8D%AC-%EC%9E%A5%EA%B3%A0-%EC%9B%B9%EC%84%9C%EB%B9%84%EC%8A%A4/dashboard 장고(Django)를 배우기 시작한 입문자이시거나, 또는 배우고 싶은 생..`</div>`<div class="kg-bookmark-metadata"><span class="kg-bookmark-author">TISTORY`</span>`<span class="kg-bookmark-publisher">개발자Dongbaek`</span>``</div>``</div>`<div class="kg-bookmark-thumbnail">`</div>`](https://devdongbaek.tistory.com/88)`</figure>`## 내가 만든 예제코드
```python
from django.core.validators import MinLengthValidatordef validate_isdecimal(value): if not value.isdecimal(): raise ValidationError( _("%(value)s is not an deciamal number"), params={"value": value}, )class Company(models.Model): name = models.CharField(max_length=255) vatid = models.CharField(max_length=255, unique=True, validators=[validate_isdecimal, MinLengthValidator(10)]) # 숫자가 아니거나, 10자리 이하는 에러 반환
결론
아니… 그러면 언제 Validators를 써야하고, 언제 clean을 써야해???
가급적이면 모든 validators는 model에 정의하고, ModelForm을 통해서 model validators 정보도 같이 가져와야 한다.
validators는 웬만하면 model에 정의하자!! </u>
😊`
`
clean() 을 오버라이드해서, validator에 위배되는 행동을 만약 clean() 함수 내에서 허용하면, 암만 모델에 정해둬도 유효성을 무시하고 데이터가 처리됨을 명심하시길!! form.is_valid() 이전에 호출 되기 때문 같네.`
``
`**clean이 필요할 때는,**
특정 Form에서 1회성 유효성 검사 루틴 이 필요할 때
다수 필드값에 걸쳐서, 유효성 검사 가 필요할 때
필드 값을 변경 할 필요가 있을 때 #validators는 값만 체크하고, 변경할 수는 없다.
빌트인 유효성 검사를 사용해
공식 사이트에서 제공하는 이미 준비된 Validators 들이 있다. 보고 활용을 하자.
[Validators | Django documentation | Django`
`
`
`
Django` ``
``
``
`](https://docs.djangoproject.com/en/4.0/ref/validators/#built-in-validators)` `### 예제
from django.core.validators import MinLengthValidatorclass GameUserSignupForm(forms.ModelForm): class Meta: model = GameUser fields = ['server','username'] def clean_username(self): # 필드명에 clean_을 붙여서 각 필드별로 유효성 처리및 데이터 변경을 수행함 return self.cleaned_data.get('username', '').strip()
Django – UpdateView pk 없이 사용하기
url 로 UpdateView 의 PK 값을 넘겨주는 방식이 일반적인데, 사용자 정보 같은 것을 넘겨줄 때 사용자에 대한 pk 값이 노출되는게 부담스럽다.
꼭 넘겨줘야 하나? 내꺼만 고치면 되는데…
이때는 내장 함수를 통해 자기가 로딩 되면서 가져오도록 하면 된다. 자기 객체를 불러오는 방식이다. 주인공은 바로,
get_object(self)
@method_decorator(login_required, name="dispatch")class profile_update_view(UpdateView): model = Profile # fields = "__all__" fields = ["level", "phone"] template_name = "profile.html" def get_object(self): print("profile_update_view get_object()", self.request.user) return Profile.objects.get(user=self.request.user) # return self.request.user def get_success_url(self): return self.request.META["HTTP_REFERER"]
쓸데없이 url 로 넘기지 않고 바로 자기가 호출될때 필요한 객체를 get_objct() 함수에서 가져와서 return 해주면 끝!
참고 페이지
[Django: DetailView, UpdateView pk나 slug 없이 사용하기`
`
CBV를 주로 사용하면서, 참 잘 만들었다고 감탄하고 있다. DetailView, UpdateView 등 pk나 slug가 기본으로 필요한 CBV를 사용할 때, pk나 slug 없이 특정 object를 전달하는 방법을 공유한다. get_object 에 직..`
`
TISTORY` `seul chan` ``
``
``
`](https://seulcode.tistory.com/281)` `
Django – UpdateView pk 있을때 없을때 다 처리하기
UpdateView pk 없이 사용하는 방법을 전 포스트에서 알아 봤는데 PK 값이 url에 있든 없든 하나의 View로 다 처리하고 싶어졌다.
일반 사용자는 자기꺼만 처리하면 되지만, superuser 혹은 admin은 다른 사용자 정보를 수정하고 싶을 수 도 있기 때문에 pk 값을 넘겨 받아서 사용하는 것도 같은 UpdateView에서 처리하고 싶었다.
그냥 get_object() 함수 내에서 pk 유무를 보고 처리하면 안되나……된다! 코드를 올려둔다.
@method_decorator(login_required, name="dispatch")class profile_update_view(UpdateView): model = Profile # fields = "__all__" fields = ["phone"] template_name = "profile.html" def get_object(self): print("profile_update_view get_object()", self.request.user, self.kwargs.get("pk")) if self.kwargs.get("pk"): if self.request.user.is_superuser: return Profile.objects.get(user=self.kwargs.get("pk")) else: set_page_message(self.request, messages.INFO, f"접근 권한이 없습니다!") return Profile.objects.get(user=self.request.user) # return self.request.user def get_success_url(self): return self.request.META["HTTP_REFERER"]
self.kwargs.get("pk") 로 pk 값이 url을 통해 넘어오는지 유무를 알수 있고 그 상황에 맞게 각자 원하는 코드를 집어 넣으면 될듯!
단!!!! urls.py 함수에서 pk 가 있는 것, 없는 것 다 같은 view를 호출하도록 꼭 해줘야 한다.
path("profile", profile_update_view.as_view(), name="profile_list_1"), path("profile/<int:pk>", profile_update_view.as_view(), name="profile_list"),
참고 사이트
아래 글은 한번 정독을 권고!!
[Deep Dive into Class-Based Views — Python 401 2.1 documentation`
`
`
`
Code Fellows` ``
``
``
`](https://codefellows.github.io/sea-python-401d5/lectures/django_cbv2.html#the-get-object-method)` `장고 소스도 읽어보자. 오버라이딩 하려면 먼가 알아야징
slug_url_kwarg, pk_url_kwarg 를 보면 왜 ‘pk’ ‘slug’ 라는 키워드를 그냥 써도 되는지 알 수 있다. 여기에 적힌 문자열을 인식하는 것 뿐이다 😊
# django/views/generic/detail.pyclass SingleObjectMixin(ContextMixin): """ Provides the ability to retrieve a single object for further manipulation. """ model = None queryset = None slug_field = 'slug' context_object_name = None slug_url_kwarg = 'slug' pk_url_kwarg = 'pk' query_pk_and_slug = False def get_object(self, queryset=None): """ Returns the object the view is displaying. By default this requires `self.queryset` and a `pk` or `slug` argument in the URLconf, but subclasses can override this to return any object. """ # Use a custom queryset if provided; this is required for subclasses # like DateDetailView if queryset is None: queryset = self.get_queryset() # Next, try looking up by primary key. pk = self.kwargs.get(self.pk_url_kwarg, None) slug = self.kwargs.get(self.slug_url_kwarg, None) if pk is not None: queryset = queryset.filter(pk=pk) # Next, try looking up by slug. if slug is not None and (pk is None or self.query_pk_and_slug): slug_field = self.get_slug_field() queryset = queryset.filter(**{slug_field: slug}) # If none of those are defined, it's an error. if pk is None and slug is None: raise AttributeError("Generic detail view %s must be called with " "either an object pk or a slug." % self.__class__.__name__) try: # Get the single item from the filtered queryset obj = queryset.get() except queryset.model.DoesNotExist: raise Http404(_("No %(verbose_name)s found matching the query") % {'verbose_name': queryset.model._meta.verbose_name}) return obj
UpdateView 를 이용하여 업데이트 할때, 혹여나 form_valid() 에서 Exception 이 발생하면 그냥 오류 페이지가 떡 떠버린다.
이런거 무시하고 특정 페이지로 무조건 리턴되도록, 즉 예외가 발생하면 update 없이 머 success url 로 이동시키자 이런 코드를 넣어보자. 단순하게 아래처럼 예외를 pass 해버리고 무조건 success_url() 로 이동시키도록 해봤다.
def form_valid(self, form): try: super().form_valid(form) except Exception as e: pass return redirect(self.get_success_url())
form_valid 에서 예외가 나든 안나든 처리가 잘된다. 🙂
끝.
그냥 예제 ModelForm init() 함수에서 수행할 것을 몇 가지 저장해 둔다.
원하는 필드만 보여주기 fields
필요 없는 필드 제거해서 보여주기 exclude
labels 를 이용한 원하는 라벨 표기
widgets 으로 원하는 form 객체 가져다 놓기
필드에 필터링 해서 데이터를 올려줄 수 있는 queryset
empty label 보여주지 않게 하기
class ProductProfileForm(forms.ModelForm): class Meta: model = ProductProfile fields = ["company", "name", "product", "fwver1"] exclude = ["updated_at", "created_at"] labels = { "company": _("*회사"), "name": _("프로파일명"), "product": _("상품명"), "fwver1": _("펌웨어"), } widgets = { "company": forms.Select(attrs={"class": "form-select"}), "name": forms.TextInput(attrs={"class": "form-control flex-fill", "placeholder": "프로파일명 (중복허용안함)"}), "product": forms.Select(attrs={"class": "form-select flex-fill", "required": True}), "fwver1": forms.Select(attrs={"class": "form-select flex-fill", "required": False}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.fields['fwver1'].queryset = Firmware.objects.filter(dtype='F') # empty field label --------- 없애기 self.fields['product'].empty_label = None
끝.
CreateView 저장 작업을 할때 커스텀 동작이 필요하다면, form_valid()를 오버라이딩해서 그냥 여기서 저장해 버리는 방법이 있다.
View를 쓴다는게 좀 커스텀 동작을 제약하는 경우와 편리성을 바꾸는 것이니 만큼 좀 불편해도 이렇게라도 변경해서 쓰자. 기본 코드는 아래와 같다.
def form_valid(self, form): self.object = form.save() # do something with self.object # remember the import: from django.http import HttpResponseRedirect return HttpResponseRedirect(self.get_success_url())
self.object 에 form 데이터가 일단 저장되고, 이 값을 이용해서 원하는 동작을 수행한다. 그리고 코드에는 get_success_url() 로 redirect 하고 있지만 원하는 어디든 보내면 된다.
내 작업에 작성한 코드도 일단 올려둔다. 많이 쓰이는 request 객체는 self.request 로 접근이 가능해서 코드 작성이 용이했다. </u>
def form_valid(self, form): print("FirmwareCreateView form_valid override") self.object = form.save(commit=False) print("firmware_list request.FILES -", self.request.FILES) for filename, file in self.request.FILES.items(): print("firmware_list", filename, file, file.name, file.content_type, file.size) self.object.filename = file.name if form.cleaned_data["desc"]: self.object.desc = form.cleaned_data["desc"] else: self.object.desc = file.name self.object = form.save(commit=True) kind = self.request.GET.get("kind") print("firmware_list kind", kind) if not kind: kind = definitions.FILE_TYPE_FIRMWARE redirect_url = reverse_lazy("firmware_list") + "?kind=" + kind print(redirect_url) return redirect(redirect_url)
코드 참고한 글은 아래 링크이다.
[Django CreateView: How to perform action upon save`
`
I’m using a custom CreateView (CourseCreate) and UpdateView (CourseUpdate) to save and update a Course. I want to take an action when the Course is saved. I will create a new many-to-many relations…`
`
Stack Overflow` `Carrie` ``
``
``
`](https://stackoverflow.com/questions/32998300/django-createview-how-to-perform-action-upon-save)` `
폼 객체에서 파일이 넘어 올 때 처리하는 코드 일부를 남겨둔다. enctype=multipart/form-data 로 지정해서 넘어올 때 말이다.
파일 이름은 어떻게 가져오는지, 어떤 정보가 있는지 참고가 될만하다.
print(request.FILES)print(request.FILES.items())for filename, file in request.FILES.items(): print(filename, file, file.name, file.content_type, file.size) # file = request.FILES['filename'] # file.name # Gives name # file.content_type # Gives Content type text/html etc # file.size # Gives file's size in byte # file.read() # Reads file
request.FILES 에 첨부파일이 담겨온다
file.name – 파일명
file.content_type – 파일 타입, 첨부파일이 그림파일인 경우 유용할 듯
file.size – 파일 사이즈
file.read() – 실제 content 내용을 읽어온다.
ModelForm init 함수에서 특정 필드값 초기값 주는 방법, 아래 글에 자세히 나와 있습니다.
Django set field value after a form is initialized
핵심은 initial 이라는 키워드
form.field["필드이름"].initial = 원하는값
이렇게 주면 된다.
많이들 아는 queryset 으로 Foreign Key로 연결된 테이블 값을 필터링 하는 방법은 많이 알고 있으니 머 생략하지만, 초기값을 막상 주려니 또 검색하게 되었다. 여기에 남겨둠
__init__ 오버라이딩 코드도 참고로 남겨둔다.
class ChooseProjectForm(forms.Form): project = forms.ModelChoiceField(queryset=project_qs) my_projects = forms.BooleanField() def __init__(self, *args, **kwargs): super(ChooseProjectForm, self).__init__(*args, **kwargs) self.data = self.data.copy() # IMPORTANT, self.data is immutable # any condition: if self.data.get('my_projects'): my_projects = self.fields['project'].queryset.filter(my=True) self.fields['project'].queryset = my_projects self.fields['project'].initial = my_projects.first().pk self.fields['project'].empty_label = None # disable "-----" self.data.update(project=my_projects.first().pk) # Update Form data self.fields['project'].widget = forms.HiddenInput() # Hide if you want
empty_label – 빈 값 조정
forms.HiddenInput()
widget 지정
코드에서 이런 내용들을 살펴볼 수 있다. 공부하자!!
Django – redirect with param 코드조각
말이 필요없다. redirect 하는데 파라메터 있는 경우 처리를 위한 코드 조각을 저장해 둔다.
def redirect_params(url, pk, params=None): response = redirect(url, pk=pk) if params: query_string = urllib.urlencode(params) response["Location"] += "?" + query_string return response
url/pk?querystring 모두를 다 처리하는 기본 코드 되겠다.
참고만 하시길, 근데 무식하게 이렇게 해도 되긴 하더라만
redirect_url = reverse_lazy("firmware_list") + "?kind=" + kindprint(redirect_url)return redirect(redirect_url)
Django – Ninja api querystring 처리
Django ninja api 엔진을 쓰고 있는데, 쿼리스트링을 처리하는 함수를 만들려고 한다. 주소에서 파라메터를 뽑아 쓰는 것은 직관적이었는데…
역시 똑같다. 차이가 없다.
@api.get("/weapons")def list_weapons(request, limit: int = 10, offset: int = 0): return weapons[offset: offset + limit]
파라메터에 기본값을 넣고 추가해 준다. 이건 path parameter 와 완전 동일하다.
호출의 형태는 아래와 같다. So, going to the URL:
http://localhost:8000/api/weapons
same as http://localhost:8000/api/weapons?offset=0&limit=10
http://localhost:8000/api/weapons?offset=20
기본 값을 줬기 때문에 아무것도 주지 않으면 default value 로 처리된다.
설명서가 포함된 자세한 내용은 아래 글을 참고하자.
[Query parameters – Django Ninja`
`
Django Ninja – Django REST framework with high performance, easy to learn, fast to code.`
`
logo` ``
``
``
`](https://django-ninja.rest-framework.com/guides/input/query-params/)` `참고로 path parameter 도 선언 부분이나 함수 사용 부분은 완전 동일하다
@api.get("/items/{item_id}")def read_item(request, item_id: int): return {"item_id": item_id}
그냥 대충 쓰자. loose coupled 😅
Django – ORM filter NULL 체크
가장 기본이긴 한데, 막상 쓰려니 또 찾아보네
정리해두자 코드 조각으로
Name.objects.exclude(alias__isnull=True).exclude(alias__exact='')
__isnull = True
__exact = ”
Django – resolve_url, reverse 코드 예제
modal 창을 띄워서 UpdateView, CreateView 를 처리하고 난 다음 success_url의 경우 호출한 detail 화면으로 보내는 것이 일반적이다.
그럼 detail url 을 success_url 로 리턴할 수 있어야 한다. get_success_url() 을 overriding 해준다.
간단하게 resolve_url() 함수를 쓰면, path parameter 도 같이 처리할 수 있다. 작성한 코드 조각을 남겨둔다.
urls.py 파일에는,
path("orderitem/<int:pk>", orderitem_view, name="orderitem_view"),
이렇게 되어 있는 url 로 돌아가고 싶다고 가정하면, 아래 처럼 path parameter 를 주면 된다.
class OrderItemWorklineUpdateView(BSModalUpdateView): model = OrderItem template_name = "_modal_update.html" form_class = OrderItemWorklineForm success_message = "변경완료" # success_url = reverse_lazy('orderitem_list') def get_success_url(self): print("OrderItemWorklineUpdateView get_success_url override", self.object) redirect_url = resolve_url("orderitem_view", pk=self.object.id) return redirect_url
self.object.id – UpdateView 의 넘겨진 object 는 self.object 에 이미 저장되어 있다.
resolve_url 리턴값은 스트링이다.
참고 페이지
아래 링크는 자세히 읽어 보는 게 좋다. reverse, redirect, resolve_url, get_absolute_url 용례가 잘 설명되어 있다.
[URL Reverse, 아는 사람은 꼭 쓴다는 get_absolute_url() · 초보몽키의 개발공부로그`
`
`
`
`
``
``
`](https://wayhome25.github.io/django/2017/05/05/django-url-reverse/)` `
Django – settings.py 변수 상수 사용하기
전역변수나 상수처럼 settings.py 에 정의된 놈들을 쓰고 싶다면 이렇게 하자.
settings.py 에 다음과 같은 변수를 정의했다고 치자.
SLACKBOT_TOKEN = os.environ.get("SLACKBOT_TOKEN", "")SLACKBOT_CHANNEL = os.environ.get("SLACKBOT_CHANNEL", "")
app 이나 장고 코드 어디선가 이 상수 변수를 쓰고 싶다면 다음과 같이 사용하면 된다.
from django.conf import settingsSLACKBOT_TOKEN = getattr(settings, "SLACKBOT_TOKEN", "")print(SLACKBOT_TOKEN)
getattr() 안쓰고 그냥 settings.SLACKBOT_TOKEN 해도 되는 것 아닌가? => 되는 것 같다. 😁
참고 사이트
예제는 아래 글에서 가져왔어요. 감사합니다. 👍
[Django2, settings.py에 설정한 변수를 APP에서 사용하는 방법`
`
settings.py에 설정한 변수를 APP에서 사용하는 방법 Django 프로그램을 작성하다보면 특정 변수를 모든 app 에서 사용하면 좋은 경우가 생긴다. 이런 경우에, settings.py에 변수를 설정하면 원하는 곳에서 불러서 사용할 수 있다. 먼저, settings.py 에 사용하고자 하는 변수(GATHER_INTERVAL)를 넣는다. set…`
`
Blog-boxcorea` `snowffox` ``
``
``
`](https://blog.boxcorea.com/wp/archives/2692)` `
Django – slack bot 에 메시지 전송
장고 혹은 파이썬에서 슬랙봇으로 메시지를 보내는 것을 해보자.
용도는 엄청 다양하다. 일단은 장고 동작 중에 admin에게 알리는 메시지를 이메일로 처리하곤 했는데 이놈의 smtp 가 참 여기저기 문제다.
그냥 Slack 을 믿고 slack message로 처리하도록 해보자. 아래 글을 참고하면 아주 쉽게 메시지를 보낼 수 있다.
[slacker.Error: invalid_auth 에러 해결방법`
`
안녕하세요 유튜버 조코딩입니다. 제 채널의 크레온 API를 활용한 파이썬 주식 투자 자동화 강의 들으시는 분들 중 slacker를 이용하실 때 2021년2월24일 이후 invalid_auth에러가 떠서 진행이 안되시는 분들이 있..`
`
TISTORY` `조코딩` ``
``
``
`](https://developerdk.tistory.com/96)` `- 아무것도 필요 없고, 접속 토큰과 requests 모듈이면 충분한다.
- bearer token에 슬랙봇 토큰 정보를 사용하면 된다.
- data={“channel”: channel,”text”: text} 이 형태로 보내면 끝
Django model forms (https://github.com/trco/django-bootstrap-modal-forms/blob/master/README.rst ) 이용하고 있는데, 모달 창이 뜬 상태에서 form submit 을 수행하고, 에러가 발생하면 해당 모달창에 바로 띄워주고 싶어서 시도!
원래 제공되는 기능인데, 그냥 쓴 방법을 정리해 둔다.
모달창 템플릿 코드는 아래와 유사하게 되어 있다. 여기서 div class=”invalid” 부분이 중요하다. 여기서 class invalid 부분을 빼 버리면, 모달창에 표시되는게 아니라 전체 페이지가 모달 창을 띄우는 페이지로 바뀌어 버려 브라우저 전체에 모달창 템플릿만 표기되는 문제가 있다.
<div class="modal-body">
<div class="invalid mb-2">
```text
{% for error in form.non_field_errors %}
<p class="help-block text-sm text-danger mb-2">{{ error }}</p>
{% endfor %}
</div>
{% for field in form %}
<div class="row mb-1 align-middle">
<div class="col-3">
<div class="text-sm text-right" for="{{ field.id_for_label }}">{{ field.label }}</div>
</div>
<div class="col-auto">
{{field}}
<div class="{% if field.errors %}invalid{% endif %}">
{% for error in field.errors %}
<p class="help-block text-sm text-danger mb-2">{{ error }}</p>
{% endfor %}
</div>
</div>
</div>
{% endfor %}
</div>
설명이 어려워서 그림으로 남겨둔다.
<figure class="wp-block-image">`</figure>`이렇게 표기되기를 원하는데, 빨간색 표기는 <p> 태그에 의해서 표기되는 것이라 div class invalid 를 빼고 실행했더니, 쩝 모달창이 사라지고 전체페이지를 잡아 먹는 모양새가 되버리네. class css 에 먼가가 정리되어 있나 보다.
<figure class="wp-block-image">`</figure>`일단 그럼 에러를 띄워주는 부분의 코드는 아래를 참고
class ShippingProductUpdateForm(BSModalModelForm):
title = ""
# update_firmware_version = forms.CharField(label=_(“업데이트버전”), widget=forms.TextInput(attrs={“readonly”: True}))
class Meta:
model = ShippingProduct
# fields = ["product", "serial", "status", "version", "update_firmware_version", "date_shipping", "shipping_company"]
fields = ["product", "serial", "status", "date_shipping", "shipping_company"]
labels = {
"shipping_company": _("출고회사"),
"product": _("*상품"),
"serial": _("*시리얼"),
"status": _("*상태"),
"date_shipping": _("*출고일"),
}
widgets = {
"shipping_company": forms.Select(attrs={"class": ""}),
"product": forms.Select(attrs={"class": "", "placeholder": "상품"}),
"serial": forms.TextInput(attrs={"class": ""}),
"status": forms.Select(attrs={"class": ""}),
"date_shipping": forms.DateInput(attrs={"type": "date", "required": True}),
}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print("ShippingProductUpdateForm init", self.request.user.profile.level, self.request.user.profile.company)
self.title = "출고상품 수정"
self.fields["product"].queryset = Product.objects.filter(company_id=self.request.user.profile.company, is_deleted=0)
# if self.instance.update_firmware:
# self.fields["update_firmware_version"].initial = self.instance.update_firmware.version
# else:
# self.fields["update_firmware_version"].initial = "No update"
if self.request.user.is_superuser:
self.fields["product"].label_from_instance = lambda obj: "%s (%s)" % (obj.name, obj.company.name)
def clean(self):
cd = self.cleaned_data
print("ShippingProductUpdateForm", cd)
# 에러발생 시키기 예제
raise ValidationError(
_("첨부파일을 입력하세요: %(value)s"),
code="invalid",
params={"value": cd.get("product")},
)
# return cd
- clean 부분에 강제로 에러를 띄워 줬더니 잘 동작하는 것을 확인했다.
그리고, form submit 할 때 Validation check 를 미리 하지 않고 view, form function 에서 걸러내고 싶다면, 템플릿에서 아래와 같은 키워드를 submit 부분에 추가해 준다. `formnovalidate="formnovalidate"`
```
참고 사이트
일반 함수에서는 @login_required 데코레이터를 사용하지만, FormView 같은 곳은 아래와 같이 @method_decorator(login_required … 이런식으로 사용한다.
from django.utils.decorators
import method_decorator
@method_decorator(login_required, name='dispatch')
class CollaboratorView(View):
기본적인 함수 사용법은 많이 있지만, Formview 마다 로그인이 안되었을 때 각각 다른 페이지로 보내고 싶을 때가 있다. 이때 페이지를 지정하는 방법을 검색해서 남겨둔다.
@method_decorator(login_required(login_url="login_message"), name="dispatch")
class ShippingProductFWUpdateView(BSModalUpdateView):
login_url=”url이름” 을 파라메터로 넣어주면 된다.
물론 url이름 에 정의된 부분은 url.py 등에 아래처럼 정의가 미리 되어 있어야 한다.
urlpatterns = [
path("", index, name="index"),
...
path("login_message", login_message_view, name="login_message"),
참고 페이지
그냥 model 에 사용된 필드만 쓰면 별 고민이 없는데, 꼭 쓰다보면 사용자 정의 필드를 넣어줘야 할 때가 있다. 이때마다 생각이 안나서 일단 정리
필드를 그냥 변수 정의하듯이 정의해서 사용하면 된다.
class ShippingProductUpdateForm(BSModalModelForm): title = "" update_firmware_version = forms.CharField(label=_("*업데이트버전"), widget=forms.TextInput(attrs={"readonly": True})) class Meta: model = ShippingProduct fields = ["product", "serial", "status", "version", "update_firmware_version", "date_shipping", "shipping_company"] labels = { "shipping_company": _("출고회사"), "product": _("*상품"), "serial": _("*시리얼"), "status": _("*상태"), "date_shipping": _("*출고일"), } widgets = { "shipping_company": forms.Select(attrs={"class": ""}), "product": forms.Select(attrs={"class": "", "placeholder": "상품"}), "serial": forms.TextInput(attrs={"class": ""}), "version": forms.TextInput(attrs={"readonly": True}), "update_firmware": forms.TextInput(attrs={"readonly": True}), "status": forms.Select(attrs={"class": ""}), "date_shipping": forms.DateInput(attrs={"type": "date", "required": True}), } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.title = "출고상품 수정" self.fields["update_firmware_version"].initial = self.instance.update_firmware.version
필드 순서는 class Meta: 부분에 fields 에 적힌 순서대로 보여주니까 사용자 정의 필드도 여기에 이름을 추가하면 된다. 간단
참고 사이트
Django – Cron, command 처리
일단 정리하기 전에 참고 페이지만 등록
https://eunjin3786.tistory.com/284
장고 프로젝트에서 manage.py 를 이용해 프로젝트 관련 명령을 실행할 때가 있다. 등등 테스트용 서버를 띄우거나 …`
`
잘 읽고 정리해 보자.` ``
``
``
``
```검색 키워드는 `class Command(BaseCommand): cron 등록`
Django – label_from_instance
label_from_instance 예제 남겨두기
실제 저장된 필드 값을 이용하여 라벨을 표기하고 싶을 때 사용하는 패턴인 듯
if not kwargs["request"].user.is_superuser:
self.fields["group"].queryset = get_user_productgroup(kwargs["request"].user.profile.company.id).order_by("company", "name")
self.fields["group"].label_from_instance = lambda obj: "%s (%s)" % (obj.name, obj.company.name)
저장용으로 남겨둠.
Django – model.py 선언 규칙
model.py 에서 모델들을 정의할 때 함수나 Meta 선언의 위치가 정해지면 좋겠다고 생각하고 있었는데, 자신의 프로젝트에 사용 중인 룰을 공개해 주신 분이 있네요.
제목은 아닌 것 같지만, 방문해 보면
▎모델 클래스 내 코드 순서 정리
섹션에 같은 룰을 명기해 두셨네요. 감사한 일이죠! 내용은 다음과 같습니다. 이런 룰을 토대로 model.py 파일을 재 정비 해볼까? 🙂
1. 상수 (EX. choices 인자로 설정할 값) : 대문자로 작성
2. 이미지 필드의 upload_to 인자로 설정할 메소드
3. 필드
4. 프로퍼티 : get_ 형태의 이름 사용 불가능
5. 메소드 : get_ 형태의 이름 사용 가능
6. save() 메소드
7. delete() 메소드
8. __str__() 메소드 : 필수 (모델 자체의 verbose_name 역할)
9. Meta 클래스
끝.
Django – reset password 구현
패스워드 초기화 구현하기 – 보통 메일로 보내고, 링크 받아서 초기화 하는 과정을 거치게 된다.
머 이거 테스트하려다 SMTP 설정하다 시간 다 낭비 할 거 같았는데, 파일로 SMTP 메일 내용을 적어주는 방법도 소개해 주고, 아주 유용하네 😁
To configure this, update our django_project/settings.py file by adding the following two lines at the bottom under our redirect URLs.
# django_project/settings.py
EMAIL_BACKEND = "django.core.mail.backends.filebased.EmailBackend"
EMAIL_FILE_PATH = BASE_DIR / "sent_emails"
패스워드 초기화도 튜토리얼로 잘 되어 있는 링크를 발견!!
` `따라만 하면 끝!
소스코드도 공개 되어 있네 – https://github.com/wsvincent/django-auth-tutorial
Django – Template 에서 Empty object 처리
빈 오브젝트를 받을 때 처리하는게 계속 나온다. 프로젝트를 진행하면
아래와 같은 구조로 처리하는 것을 기본으로 하면 좋을 듯
None 인지 아닌지 미리 보고,
for로 iteration 하는 구문에서도 empty 인 경우에는 별도로 처리하고, 기본 틀로 사용하시길
{% raw %}
```text
{% if objects is not None %}
{% for obj in objects %}
{{obj}} // Do your stuff here
{% empty %}
No results. // No results case
{% endfor %}
{% else %}
// None case
{% endif %}
`{% endraw %}`
---
### Django – CSV 엑셀 출력 방법 한글깨짐
**Django – 관리자 페이지에서 엑셀 출력 방법**
[https://cocook.tistory.com/21](https://cocook.tistory.com/21)
euc-kr 이 키워드이다
---
### Django – send_mail SMTP 메일 보내기
장고 기반의 프로젝트에서 이메일로 처리해야 할 일이 제법 있다.
가장 간단하게는 비밀번호를 잊었을 때 복구 링크 보내주는 것이 대표적인 예로 볼 수 있다.
장고 문서 공식 사이트에서는, 아래 링크에서 정보를 찾을 수 있다.
[https://docs.djangoproject.com/en/4.1/topics/email/](https://docs.djangoproject.com/en/4.1/topics/email/)
## settings.py
아래 링크에 가면 아주 자세하게 설정해서 보내는 샘플 프로젝트가 잘 되어 있다.
[https://opensource.com/article/22/12/django-send-emails-smtp](https://opensource.com/article/22/12/django-send-emails-smtp)
Scroll to the end of the code and update the file with the following code:
EMAIL_BACKEND = ‘django.core.mail.backends.smtp.EmailBackend’
EMAIL_HOST = ‘smtp.yourserver.com’
EMAIL_USE_TLS = False
EMAIL_PORT = 465
EMAIL_USE_SSL = True
EMAIL_HOST_USER = ‘your@djangoapp.com ’
EMAIL_HOST_PASSWORD = ‘your password’
## send\_mail()
함수 호출은 단순하다.
from django.core.mail import send_mail
send_mail(
'Subject here',
'Here is the message.',
'from@example.com',
['to@example.com'],
fail_silently=False ,
)
```
sending email` `` `
---
### Django – Delete old image when update ImageField?
기존 이미지 파일을 지우고, 새로운 업데이트 된 것만 남겨두고 싶은게 인지상정
[https://stackoverflow.com/questions/2878490/how-to-delete-old-image-when-update-imagefield](https://stackoverflow.com/questions/2878490/how-to-delete-old-image-when-update-imagefield)
해결책은 아주 간단하게
Use [https://github.com/un1t/django-cleanup](https://github.com/un1t/django-cleanup)
```
pip install django-cleanup
```
settings.py
```
INSTALLED_APPS = (
...
'django_cleanup.apps.CleanupConfig', # should be placed after your apps
)
```
---
### Django – logging RotatingFileHandler 에러 발생시
머지 다 잘 따라 했는데, 당황스럽게 파일이 커지면서 자동으로 Rotating 되어라고 RotatingFileHandler 를 사용했는데, 파일이 생길때 마다 Permission error 가 발생하니 당황스럽다.
> PermissionError: \[WinError 32\] 다른 프로세스가 파일을 사용 중이기 때문에 프로세스가 액세스 할 수 없습니다: ‘C:\\django\\logs\\mysite.log’ -> ‘C:\\django\\logs\\mysite.log.1’
>
> 말도 안되는 에러``
해결책은 settings.py 파일 맨 아래에 아래 코드를 추가하는 것
```
if DEBUG and os.environ.get("RUN_MAIN", None) != "true":
LOGGING = {}
```
여기서 가져온 솔루션이다.
[Django – Rotating File Handler stuck when file is equal to maxBytes](https://stackoverflow.com/questions/26682413/django-rotating-file-handler-stuck-when-file-is-equal-to-maxbytes)
이렇게 해도 된다고 하네요. 아예 실행할 때 –noreload 옵션을 주도록
```
python manage.py runserver --noreload
```
` `
---
### Django forms fields widgets 사용 예제
항상 하는데 항상 헷갈려
Fields 와 Widget 은 궁합이 있는데 쌍으로 움직여야 제대로 나온다.
` `아래 코드로 나온 모양들이다. 여러가지 위젯이 쓰이고 있다.
```
class FirmwareUpdateForm(BSModalModelForm):
is_fileinclue = "enctype=multipart/form-data"
title = ""
class Meta:
model = Firmware
fields = ["company", "desc", "dtype", "content", "filename", "version", "publish", "channelfw_type", "date_release", "products"]
labels = {
"company": _("*회사명"),
"desc": _("설명"),
"dtype": _("타입"),
"filename": _("파일명"),
"content": _("파일"),
}
widgets = {
"company": forms.Select(attrs={"class": "form-select"}),
"desc": forms.TextInput(attrs={"class": "form-control", "placeholder": "설명"}),
"dtype": forms.Select(attrs={"class": "form-select", "placeholder": "타입"}),
"filename": forms.TextInput(attrs={"class": "form-control", "placeholder": "읽기전용-자동입력됩니다", "readonly": "readonly"}),
# "content": forms.FileInput(attrs={"class": "form-control", "placeholder": "첨부파일"}),
"version": forms.TextInput(attrs={"class": "form-control"}),
"publish": forms.CheckboxInput(attrs={"class": "form-check"}),
"channelfw_type": forms.Select(attrs={"class": "form-select"}),
"date_release": forms.DateTimeInput(attrs={"type": "datetime-local", "class": "form-control"}),
"products": forms.SelectMultiple(attrs={"class": "form-control"}),
}
```
특히, 날짜 시간 입력 받는 부분을 참고해두고 나중에 활용하자.
type을 datetime-local 이런게 있는줄 몰랐네
보통 date 는 많이 썼는데,
**“date\_release”: forms.DateTimeInput(attrs={“type”: “datetime-local”, “class”: “form-control”})**
참고한 사이트는 여기 [https://www.letscodemore.com/blog/how-to-add-date-input-widget-in-django-forms/](https://www.letscodemore.com/blog/how-to-add-date-input-widget-in-django-forms/)
실제 자세한 내용은 당연하게도 공식 사이트로
[https://docs.djangoproject.com/en/4.1/ref/forms/widgets/](https://docs.djangoproject.com/en/4.1/ref/forms/widgets/)
- Widgets handling input of text
TextInput
NumberInput
EmailInput
URLInput
PasswordInput
HiddenInput
DateInput
DateTimeInput
TimeInput
Textarea
- Selector and checkbox widgets
CheckboxInput
Select
NullBooleanSelect
SelectMultiple
RadioSelect
CheckboxSelectMultiple
- File upload widgets
FileInput
ClearableFileInput
- Composite widgets
MultipleHiddenInput
SplitDateTimeWidget
SplitHiddenDateTimeWidget
SelectDateWidget
이름만 알아도 머 상상은 쉽게 되니깐
Django forms fields 와 헷갈리면 안된다. 필드의 속성을 정의하는 fields 도 맞춰서 잘 써야 한다.
[https://docs.djangoproject.com/en/4.1/ref/forms/fields/](https://docs.djangoproject.com/en/4.1/ref/forms/fields/)
맵핑 테이블을 아래와 같이 정리해둔 곳이 있네, 이 테이블을 보는게 편하겠다.
[https://www.letscodemore.com/blog/how-to-add-date-input-widget-in-django-forms/](https://www.letscodemore.com/blog/how-to-add-date-input-widget-in-django-forms/)
| Name | Class | HTML Input |
|---|---|---|
| [BooleanField](https://www.geeksforgeeks.org/booleanfield-django-forms/) | class BooleanField(\*\*kwargs) | CheckboxInput |
| [CharField](https://www.geeksforgeeks.org/charfield-django-forms/) | class CharField(\*\*kwargs) | TextInput |
| [ChoiceField](https://www.geeksforgeeks.org/choicefield-django-forms/) | class ChoiceField(\*\*kwargs) | Select |
| [TypedChoiceField](https://www.geeksforgeeks.org/typedchoicefield-django-forms/) | class TypedChoiceField(\*\*kwargs) | Select |
| [DateField](https://www.geeksforgeeks.org/datefield-django-forms/) | class DateField(\*\*kwargs) | DateInput |
| [DateTimeField](https://www.geeksforgeeks.org/datetimefield-django-forms/) | class DateTimeField(\*\*kwargs) | DateTimeInput |
| [DecimalField](https://www.geeksforgeeks.org/decimalfield-django-forms/) | class DecimalField(\*\*kwargs) | NumberInput when Field.localize is False, else TextInput |
| [DurationField](https://www.geeksforgeeks.org/durationfield-django-forms/) | class DurationField(\*\*kwargs) | TextInput |
| [EmailField](https://www.geeksforgeeks.org/emailfield-django-forms/) | class EmailField(\*\*kwargs | EmailInput |
| [FileField](https://www.geeksforgeeks.org/filefield-django-forms/) | class FileField(\*\*kwargs) | ClearableFileInput |
| [FilePathField](https://www.geeksforgeeks.org/filepathfield-django-forms/) | class FilePathField(\*\*kwargs) | Select |
| [FloatField](https://www.geeksforgeeks.org/floatfield-django-forms/) | class FloatField(\*\*kwargs) | NumberInput when Field.localize is False, else TextInput |
| [ImageField](https://www.geeksforgeeks.org/imagefield-django-forms/) | class ImageField(\*\*kwargs) | ClearableFileInput |
| [IntegerField](https://www.geeksforgeeks.org/integerfield-django-forms/) | class IntegerField(\*\*kwargs) | NumberInput when Field.localize is False, else TextInput |
| [GenericIPAddressField](https://www.geeksforgeeks.org/genericipaddressfield-django-forms/) | class GenericIPAddressField(\*\*kwargs) | TextInput |
| [MultipleChoiceField](https://www.geeksforgeeks.org/multiplechoicefield-django-forms/) | class MultipleChoiceField(\*\*kwargs) | SelectMultiple |
| [TypedMultipleChoiceField](https://www.geeksforgeeks.org/typedmultiplechoicefield-django-forms/) | class TypedMultipleChoiceField(\*\*kwargs) | SelectMultiple |
| [NullBooleanField](https://www.geeksforgeeks.org/nullbooleanfield-django-forms/) | class NullBooleanField(\*\*kwargs) | NullBooleanSelect |
| [RegexField](https://www.geeksforgeeks.org/regexfield-django-forms/) | class RegexField(\*\*kwargs) | TextInput |
| [SlugField](https://www.geeksforgeeks.org/slugfield-django-models/) | class SlugField(\*\*kwargs) | TextInput |
| [TimeField](https://www.geeksforgeeks.org/timefield-django-forms/) | class TimeField(\*\*kwargs) | TimeInput |
| [URLField](https://www.geeksforgeeks.org/urlfield-django-forms/) | class URLField(\*\*kwargs) | URLInput |
| [UUIDField](https://www.geeksforgeeks.org/uuidfield-django-forms/) | class UUIDField(\*\*kwargs) | TextInput |
---
### Django FormView 에 kwargs 사용자변수 추가하기
가끔 초기화 할때, 내가 원하는 변수 값을 넘기고 싶을때 kwargs 에 넣어줘서 넘겨줄 수 있다.
코드를 일단 보자
```
class ProductUpdateView(BSModalUpdateView):
model = Product
template_name = "_modal_update.html"
form_class = ProductUpdateForm
success_message = "변경완료"
# success_url = reverse_lazy('orderitem_list')
def get_success_url(self):
return self.request.META["HTTP_REFERER"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
print("===여기 들어옴 ProductUpdateView ===", args, kwargs)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs["request"] = self.request
return kwargs
```
일단, get\_form\_kwargs(self) 함수가 있다는 사실이 중요하다.
여기에서 기본 값들을 받고, 사용자 변수를 추가하고 리턴해주면 사용자 값이 추가된 kwargs 를 넘길 수 있다.
사용되는 부부은 이런 곳이다.
```
class ProductUpdateForm(BSModalModelForm):
title = ""
class Meta:
model = Product
fields = ["company", "name"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# print("=== ProductUpdateForm ===", args, kwargs, kwargs["request"])
```
\_\_init\_\_() 함수가 불려질때 넘어오게 되는 kwargs 에 내가 추가한 변수가 떡하니 들어 있다.
유용하게 활용하자.
` `## 참고페이지
[https://ggodong.tistory.com/209](https://ggodong.tistory.com/209) – Get, Post 요청을 모두 알아서 처리하는 FormView !
[https://stackoverflow.com/questions/18605008/curious-about-get-form-kwargs-in-formview](https://stackoverflow.com/questions/18605008/curious-about-get-form-kwargs-in-formview)
---
### Django – prefetch_related, select_related 알아보기
쿼리 수를 줄이기 위해 Foreign Key 가 포함된 테이블, 모델에서는 join 을 써야 한다.
헷갈리는 거니깐, 정리해 둔 글을 보자.
[\[Django\] 간단하게 알아보는 N+1 문제 해결! (select\_related, prefetch\_related)](https://bio-info.tistory.com/174)
> **prefetch\_related와 select\_related의 차이점**은 prefetch\_related는 select\_related 처럼 **조인을 하는게 아니라, 필터링을 한다는 점**입니다. 즉, **Post에서 id를 쭉 불러오고, 이를 tag에서 필터링한다는 점이 select\_related와의 차이점** 입니다. 다만, 여기서 tag에서 post\_id로 필터링 하려면, tag와 tag\_set이 조인된 테이블이 필요하기 때문에 조인이 일어납니다.
>
> https://bio-info.tistory.com/174``
```
for comment in Comment.objects.all().select_related("post"):
print(comment.post.title)
```
post 라는 필드는 Post 라는 테이블을 Foreign key 로 Comment 오브젝트에 선언되어 있다.
용례만 알면 되지 🙂
암튼 HTML 템플릿에서 리스트 수 만큼 쿼리가 수행되던 것이 확 줄어 버리게 된다.
외부 키로 값을 가져오는 경우에는 반드시 select\_related 를 선언해서 SQL 수를 줄이자!
` `## 참고사이트
\[Django\] 테이블(모델)의 JOIN(3) ; prefetch\_related() – [https://engineer-mole.tistory.com/147](https://engineer-mole.tistory.com/147)
---
### [Django: How to set a field to NULL?](https://stackoverflow.com/questions/4446652/django-how-to-set-a-field-to-null)
[Django: How to set a field to NULL?](https://stackoverflow.com/questions/4446652/django-how-to-set-a-field-to-null)
정답은 None 을 저장하면 된다.
NULL로 다시 만들고 싶은 필드가 있다면, 파이썬 코드에서 어떻게 하는지 궁금해서 찾아봄
그냥 None 넣기
```
oitem.workline = None
```
---
### Django – ModelForm field 사용자 정의 data- 속성 추가하기
Form field 가 html 로 렌더링 될 때 원하는 속성값을 부가 하고 싶은데, 장고 코드에서 자동으로 하고 싶다.
이래 저래 찾다가 코드가 발견
```
class CompanySelect(forms.Select):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super().create_option(name, value, label, selected, index, subindex, attrs)
if value:
option["attrs"]["data-company"] = value.instance.company_id
return option
class ProductProfileForm(forms.ModelForm):
class Meta:
model = ProductProfile
fields = ["company", "name", "product", "fwver1", "fwver2", "fwver3", "fwver4", "testset", "boxlabel", "boxpq"]
# exclude = ["order", "status", "date_done", "elapsed_time", "mode", "desc", "is_deleted", "updated_at", "created_at"]
# fields = ["order", "product", "workline", "fwver", "testset", "quantity", "date_due", "box", "boxpq", "status"]
labels = {
"company": _("*회사명"),
"product": _("상품명"),
...}
widgets = {
"company": forms.Select(attrs={"class": "form-select"}),
# "product": forms.Select(attrs={"class": "form-select flex-fill", "required": True, "style": "width: 100px;"}),
"product": CompanySelect(attrs={"class": "form-select flex-fill companyselect", "required": True}),
...
```
원래 그냥 forms.Select 만 쓰면 특별히 해당 필드 값을 가지고 속성을 별도로 주는 것이 어렵다. 클래스 처럼 한꺼번에 줄 수 는 있어도 해당 값에 맞는 값을 DB 에서 가져와서 넣어주는게 힘들다. 예를 들면 해당 레코드별로 있는 ID 값을 data-code 속성에 넣어주는 그런것은 하기 어렵다.
이때는 forms.Select 를 오버라이딩 해서 사용하면 된다.
CompanySelect 클래스를 참고하면 된다. 원하는 DB instance 의 값을 바로 쓸 수 있다.
아래 처럼 data-company 값이 떡 하니 적혀져서 렌더링 된다. 이값을 이용해서 코드를 작성하면 편리하게 활용할 수 있다.
```
---------
test1 (twareLAB)
test11 (twareLAB)
TJ100 (twareLAB)
TW100XR (twareLAB)
Product1 (WIZnet)
Test (WIZnet)
Product1-1 (TEST Company)
Product1-111 (TEST Company)
Product1-123 (TEST Company)
```
빙에게서 받은 예제도 여기에 첨부해 둡니다.
Django의 `forms.Select` 위젯을 오버라이딩하는 예제를 제공해 드리겠습니다. `forms.Select` 위젯은 `ChoiceField`의 기본 위젯으로 HTML의 `` 요소를 생성합니다.
예를 들어, 다음과 같은 `ModelForm`이 있다고 가정해봅시다.
```
from django import forms
from myapp.models import MyModel
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['myfield']
```
이 경우 `myfield`가 `ChoiceField`인 경우, 다음과 같이 `forms.Select` 위젯을 오버라이딩하여 사용자 정의 위젯을 만들 수 있습니다.
```
from django import forms
from myapp.models import MyModel
class MySelect(forms.Select):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super(MySelect, self).create_option(name, value, label, selected, index, subindex=subindex, attrs=attrs)
option['attrs']['data-myattribute'] = 'myvalue'
return option
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['myfield']
widgets = {
'myfield': MySelect
}
```
위와 같이 수정하면 `myfield` 필드의 `` 요소에 `data-myattribute="myvalue"` 속성이 추가됩니다.
`create_option` 메서드는 Django의 `forms.Select` 위젯에서 ` ` 요소를 생성하는 데 사용됩니다. 이 메서드는 다음과 같은 인수를 받습니다.
- `name`: 필드의 이름입니다.
- `value`: 옵션의 값입니다.
- `label`: 옵션의 레이블입니다.
- `selected`: 옵션이 선택되었는지 여부를 나타내는 불리언 값입니다.
- `index`: 옵션의 인덱스입니다.
- `subindex`: 옵션의 하위 인덱스입니다. 기본값은 `None`입니다.
- `attrs`: 옵션의 HTML 속성을 나타내는 딕셔너리입니다. 기본값은 `None`입니다.
`create_option` 메서드는 위와 같은 인수를 받아서 딕셔너리 형태의 옵션 객체를 반환합니다. 이 딕셔너리에는 다음과 같은 키가 있습니다.
- `name`: 필드의 이름입니다.
- `value`: 옵션의 값입니다.
- `label`: 옵션의 레이블입니다.
- `selected`: 옵션이 선택되었는지 여부를 나타내는 불리언 값입니다.
- `index`: 옵션의 인덱스입니다.
- `attrs`: 옵션의 HTML 속성을 나타내는 딕셔너리입니다.
`create_option` 메서드를 오버라이딩하여 위와 같은 인수를 받고 딕셔너리 형태의 옵션 객체를 반환하는 사용자 정의 메서드를 만들 수 있습니다. 이렇게 하면 ` ` 요소의 속성을 동적으로 변경할 수 있습니다.
`create_option` 메서드를 오버라이딩하는 예제를 더 제공해 드리겠습니다. 이전 예제에서는 `create_option` 메서드를 오버라이딩하여 ` ` 요소에 `data-myattribute="myvalue"` 속성을 추가하는 방법을 보여드렸습니다.
이번에는 `create_option` 메서드를 오버라이딩하여 ` ` 요소의 레이블을 동적으로 변경하는 방법을 보여드리겠습니다.
```
from django import forms
from myapp.models import MyModel
class MySelect(forms.Select):
def create_option(self, name, value, label, selected, index, subindex=None, attrs=None):
option = super(MySelect, self).create_option(name, value, label, selected, index, subindex=subindex, attrs=attrs)
option['label'] = label.upper()
return option
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['myfield']
widgets = {
'myfield': MySelect
}
```
위와 같이 수정하면 `myfield` 필드의 ` ` 요소의 레이블이 모두 대문자로 변경됩니다.
## 위젯을 오버라이딩하는 이유
Django의 위젯은 폼 필드의 HTML 표현을 담당합니다. 기본적으로 Django는 각 필드 유형에 대해 기본 위젯을 제공하지만, 때로는 이러한 기본 위젯이 원하는 요구 사항을 충족시키지 못할 수 있습니다. 이 경우 위젯을 오버라이딩하여 사용자 정의 위젯을 만들 수 있습니다.
위젯을 오버라이딩하는 이유는 다음과 같습니다.
- HTML 요소의 속성을 동적으로 변경하려는 경우
- HTML 요소의 레이아웃이나 스타일을 변경하려는 경우
- JavaScript 이벤트 핸들러를 추가하려는 경우
- 기타 사용자 정의 요구 사항을 충족시키기 위해
위와 같은 이유로 위젯을 오버라이딩하여 사용자 정의 위젯을 만들 수 있습니다.
``
---
### Django – response redirect 쿼리스트링 추가
쿼리 스트링을 넣어서 return response 를 하고 싶다면,
```
response = redirect("order_view", pk=order.id)
response["Location"] += "?" + request.POST.get("querystring")
return response
```
머 그냥 로직으로 이런 예외없이 막아주는게 최고지만, 검색 플래그 같은 거 들고 다닐수는 있으니깐
가끔 쓰일때가 있다.
---
### Django ModelForm 특정 필드만 업데이트하기
Modelform을 써서 View 를 구현해서 하다보면,
Modelform 에서 선언된 모든 field 값이 한꺼번에 업데이트 되는 것이 일반적이다.
이것도 하다 보면, 특정 필드만 업데이트 하고자 하는 경우가 있는데
save() 함수를 호출할 때 update\_fields 값을 넘겨주면, 해당 필드만 갱신할 수 있다.
```
for changed_field in item.changed_data:
if changed_field == "date_due" or changed_field == "desc":
orderitem.save(update_fields=[changed_field])
```
변경된 필드중, “date\_due”, “desc” 필드인 경우만 save 하는 샘플 코드를 남겨둔다.
### 참고 페이지
[https://velog.io/@ggg/modelform-%EC%97%90%EC%84%9C-form-field-%EB%A7%8C-update-%ED%95%98%EA%B8%B0](https://velog.io/@ggg/modelform-%EC%97%90%EC%84%9C-form-field-%EB%A7%8C-update-%ED%95%98%EA%B8%B0)
``
---
### Django Ninja API – ModelSchema 활용 예제
테이블을 그대로 스키마를 만들어 줄때는 그냥 ModelSchema를 사용하면 편리하다.
테이블 예제를 하나 들어 보자.
```
class Firmware(TimeStampedModel):
# 고객의 소속을 위한 필드로 둠
tenant = models.ForeignKey(Tenant, on_delete=models.PROTECT, null=False, blank=False)
desc = models.CharField(max_length=255, default="", null=True, blank=True)
filename = models.CharField(max_length=255)
version = models.CharField(max_length=255)
content = models.FileField(upload_to="uploads/", null=True)
date_release = models.DateTimeField(default=now)
products = models.ManyToManyField(Product, blank=True)
is_deleted = models.IntegerField(default=0, null=True)
```
아래와 같이 출력해주는 Schema를 그냥 ModelSchema를 통해 자동으로 생성해 낼 수 있다.
class config: 부분을 살펴보면, 원하는 테이블을 model=테이블명 형태로 정의 하고 있다.
```
class FirmwareOutSchema(ModelSchema):
# option field 혹은 None 값을 가지고 있는 필드의 경우, 아래 처럼 미리 선언해서 None이라도 대입해둬야 한다.
desc: str = None
# manytomany 필드의 경우 아예 해당 테이블의 out schema 로 정의해 두면, 아래 스키마에 맞게 데이터를 다 가져온다.
products: List[ProductOutSchema] = None
class Config:
model = Firmware
# model_fields = ['id', 'username', 'first_name', 'last_name']
model_exclude = ["updated_at", "created_at"]
```
원하는 필드만을 뽑아 낼때는 model\_fields 를 사용하고,
원하지 않는 필드만 정의하려면 model\_exclude 를 사용하면 된다.
이제 API를 만들어서 JSON 형태로 답변을 주도록 만들어 보면
```
@router.get("/firmware", response=List[FirmwareOutSchema])
@paginate(CustomPagination)
def list_testset(request):
# print("=== list_testset ===", request, q.dict())
tenant_id = 1
releases = get_user_firmware(tenantid=tenant_id).order_by("-id")
if releases is None:
return 404, make_message_response(404)
return releases
```
페이지 기능도 넣기 위해
@paginate() 라는 wrapper 를 사용했다.
필드값들 중에 None 이나 NULL 이 들어 있으면 아래와 같은 에러가 발생하기도 한다.
```
File "pydantic\main.py", line 579, in pydantic.main.BaseModel.from_orm
pydantic.error_wrappers.ValidationError: 1 validation error for NinjaResponseSchema
response -> items -> 0 -> desc
none is not an allowed value (type=type_error.none.not_allowed)
```
사실 None 같은 값이 들어 있을 수도 있는데 에러를 띄우는 것을 예상하지 못했다. 그래서 귀찮아도 옵션필드들은
Out Schema 정의할때 동일한 필드명의 변수를 선언하고, 기본 값을 넣어 주도록 하자.
위의 코드를 참고하시고, 적용하면 아래와 같은 결과가 제대로 리턴된다.
```
{
"total_count": 2,
"total_pages": 1,
"per_page": 10,
"current_page": 1,
"items": [
{
"id": 2,
"tenant": 1,
"desc": null,
"filename": "openapi1.json",
"version": "v1.0.0",
"content": "/media/uploads/openapi_hZuZumc.json",
"date_release": "2023-08-31T08:42:06Z",
"is_deleted": 0,
"products": [
{
"id": 1,
"tenant": 1,
"name": "TW100",
"is_deleted": 0
},
{
"id": 3,
"tenant": 1,
"name": "TW100-EVB",
"is_deleted": 0
}
]
},
{
"id": 1,
...
}
]
}
Response headers
```
``
---
### Django – ManyToManyField 에 리스트 값 할당
아래와 같은 ManyToManyField 필드에 값을 대입하고 싶을때 그냥,
products = models.ManyToManyField(Product, blank=True)
item.products = body.products
이런식으로 했더니, 아래와 같이 에러 발생
The direct assignment to the forward side of a many-to-many set is not allowed. Instead, it is recommended to use the `products.set()` method for assigning values.
즉, 해결하려면 아래와 같은 구문을 사용해야 함.
item.products.set(body.products)
[https://stackoverflow.com/questions/50015204/direct-assignment-to-the-forward-side-of-a-many-to-many-set-is-prohibited-use-e](https://stackoverflow.com/questions/50015204/direct-assignment-to-the-forward-side-of-a-many-to-many-set-is-prohibited-use-e)
---
### Django – JSONFields 사용법 orm
편리하게 사용할 수 있는 JSONFields 사용법
PostgreSQL 데이터베이스는 지원하는 타입으로 옵션 필드 처리를 위해 사용한다.
사용법은 아래 내용 참고.
[https://ianjang.github.io/django-orm-postgresql-jsonfield%EC%97%90-%EB%8C%80%ED%95%B4-exclude-%EA%B5%AC%EB%AC%B8-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD/#json-field-%ED%95%84%ED%84%B0%EB%A7%81-%ED%85%8C%EC%8A%A4%ED%8A%B8](https://ianjang.github.io/django-orm-postgresql-jsonfield%EC%97%90-%EB%8C%80%ED%95%B4-exclude-%EA%B5%AC%EB%AC%B8-%EC%82%AC%EC%9A%A9%EC%8B%9C-%EC%A3%BC%EC%9D%98%EC%82%AC%ED%95%AD/#json-field-%ED%95%84%ED%84%B0%EB%A7%81-%ED%85%8C%EC%8A%A4%ED%8A%B8)
[https://awstip.com/querying-djangos-jsonfield-a2914f5d4b91](https://awstip.com/querying-djangos-jsonfield-a2914f5d4b91)
## 선언부
```
class Book(models.Model):
# (...생략...)
data = JSONField(null=True, blank=True)
```
## 데이터 생성
```
class TestJsonFieldFiltering(TestCase):
def setUp(self):
Book.objects.create(id=1, data=None)
Book.objects.create(id=2, data={})
Book.objects.create(id=3, data={'title': 'django'})
Book.objects.create(id=5, data={'title': 'django', 'is_published': True})
```
## 활용법
- 키값이 있는지 조사 \_\_has\_key
Results in the query:
`r = Person.objects.filter(data__has_key="name").all()`
- Contain
**r = Person.objects.filter(data\_\_contains={“name”: “Peter”}).all()**
Assuming a **Person** had the following data
`data = {“name”:"Peter”, “classes”: ["MATH", "ENG", "SCI"]}```
We can answer the question, *Does the student take* ***“MATH”*** *classes?*
`r = Person.objects.filter(data__classes__contains=["MATH"]).all()`
- 필터링 조건
```
# is_published=True 조건 조회 시, 아래 두 ORM 구문 모두 예상한 결과가 나옵니다.
Book.objects.filter(data__is_published=True) # O
Book.objects.filter(Q(data__is_published=True) & Q(data__is_published__isnull=False)) # O
# is_published=False 조건 조회 시, 아래 두 ORM 구문 모두 예상한 결과가 나옵니다.
Book.objects.filter(data__is_published=False) # O
Book.objects.filter(Q(data__is_published=False) & Q(data__is_published__isnull=False)) # O
# is_published=True 만 제외하고 조회하려면 주의가 필요합니다. (예상 결과: 1, 2, 3, 4, 7, 8)
Book.objects.exclude(data__is_published=True) # 예상과 다르 결과가 나옵니다.
Book.objects.exclude(Q(data__is_published=True) & Q(data__is_published__isnull=False) # 예상한 결과가 나옵니다.
Book.objects.filter(Q(data__is_published=False) | Q(data__is_published__isnull=True) # 예상한 결과가 나오며 가장 추천하는 방법입니다.
```
---
### Django Error – ‘module’ object is not callable 오류
오류가 아래와 같이 발생했다.
> File “C:\\Users\\User1\\Documents\\Work\\tx-msa-test-service\\django\\app\_test\_data\_manager\\api\_v1\\apis\\api\_order.py”, line 100, in post\_order
> start = time()
> TypeError: ‘module’ object is not callable
>
> 무슨 에러인가``
여기 저기 찾다가 이게 특별한 에러가 아니었고,
> import time
이라는 문장을 어떤 파일에 실수로 위쪽에 집어 넣었는데, 이 이후 부터 발생하기 시작했다.
에러만 보고는 알수 없는 아주 이상한 에러.
자꾸 코드를 이상하게 수정할 뻔, 실수를 유의하자
C/C++ 와 다르게 인터프리트 비슷한 언어는 실수를 잘 못잡아 낼 경우가 많다.
---
### Django – FileField upload_to 커스텀
아래 처럼 함수를 이용하여 업로드 위치를 지정할 수 있다. 실행타임 즉 런타임에 폴더 위치를 변경할 수 있다는 말이다.
그럼 과연 django에는 upload\_to를 어떻게 커스텀해서 적용할까?
이하 django filefield 예시
```
def user_directory_path(instance, filename):
# file will be uploaded to MEDIA_ROOT/user_/
return 'user_{0}/{1}'.format(instance.user.id, filename)
```
위와 같이 instance와 filename가 넘어오는데 instance는 model에서 생성된 instance 내용물들을 말한다
그래서 그 내용물의 필드들, 왜래키의 필드들에 접근할 수 있다.
모델에는 다음과 같이 설정한다.
```
class Content(models.Model):
name = models.CharField(max_length=200)
user = models.ForeignKey(User)
file = models.FileField(upload_to=user_directory_path)
```
## 참고자료
[https://stackoverflow.com/questions/1190697/django-filefield-with-upload-to-determined-at-runtime](https://stackoverflow.com/questions/1190697/django-filefield-with-upload-to-determined-at-runtime)
[https://mixedprograming.tistory.com/26](https://mixedprograming.tistory.com/26)
---
### Django QuerySet 리스트, 딕셔너리 변환
아래 원본 글에서는 model_to_dict 사용해서 excludes 이런것도 해결되어 있는데,``에러가 나서 그냥 fields 만 해서 리턴하는 함수로 변경``\# Note: Usage``\# queryset_to_list(releases, [“is_success”, “done_count”])``def queryset_to_list(qs, fields=None):`` if fields:`` qs = qs.values(*fields)`` return list(qs)``````````참고 사이트
> [https://stackoverflow.com/questions/7811556/how-do-i-convert-a-django-queryset-into-list-of-dicts](https://stackoverflow.com/questions/7811556/how-do-i-convert-a-django-queryset-into-list-of-dicts)
````You could define a function using model_to_dict as follows:```````
```python
from`` django.forms.models import`` model_to_dict
def`` queryset_to_list``(qs,fields=None``, exclude=None````):
return`` [model_to_dict(x,fields,exclude) for`` x in`` qs]
```
Suppose your Model has the following fields
```
```python
id``
name
email
```
Run the following commands in Django shell
```
```python
>>>qs=.objects.all``()
>>>list``=queryset_to_list(qs)
>>>list``
[{'id'``:1``, 'name'``:'abc'``, 'email'``:'abc@ab.co'``},{'id'``:2``, 'name'``:'xyz'``, 'email'``:'xy@xy.co'``}]
```
Say you want only the id and the name in the list of queryset dictionary
```
```python
>>>qs=.objects.all``()
>>>list``=queryset_to_list(qs,fields=['id'``,'name'``])
>>>list``
[{'id'``:1``, 'name'``:'abc'``},{'id'``:2``, 'name'``:'xyz'``}]
```
Similarly, you can exclude fields in your output.
</div></div>
---
### Django – ArrayField 비교 방법 filter
리스트끼리 비교 해야 한다는게 핵심,
비교 대상이 리스트여야지 된다 ArrayField 를 사용한 경우에는
\_\_contains 혹은 서브셋 중 하나라도 있으면을 찾는 \_\_contained\_by
[https://docs.djangoproject.com/en/4.2/ref/contrib/postgres/fields/#contained-by](https://docs.djangoproject.com/en/4.2/ref/contrib/postgres/fields/#contained-by)
```
contains¶
The contains lookup is overridden on ArrayField. The returned objects will be those where the values passed are a subset of the data. It uses the SQL operator @>. For example:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])
>>> Post.objects.filter(tags__contains=['thoughts'])
, ]>
>>> Post.objects.filter(tags__contains=['django'])
, ]>
>>> Post.objects.filter(tags__contains=['django', 'thoughts'])
]>
contained_by¶
This is the inverse of the contains lookup - the objects returned will be those where the data is a subset of the values passed. It uses the SQL operator <@. For example:
>>> Post.objects.create(name='First post', tags=['thoughts', 'django'])
>>> Post.objects.create(name='Second post', tags=['thoughts'])
>>> Post.objects.create(name='Third post', tags=['tutorial', 'django'])
>>> Post.objects.filter(tags__contained_by=['thoughts', 'django'])
, ]>
>>> Post.objects.filter(tags__contained_by=['thoughts', 'django', 'tutorial'])
, , ]>
```
---
### Django ArrayField 사용법 – 단순 저장용
원본글: [https://velog.io/@heka1024/Django-ArrayField-%EC%9A%B0%EC%95%84%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0](https://velog.io/@heka1024/Django-ArrayField-%EC%9A%B0%EC%95%84%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0)
제 공부용으로 혹시나 해서 단순 저장용입니다. 글 참고는 원본글로 가서 보세요.
# ArrayField?
`PostgreSQL`은 다양한 형태의 필드를 지원한다. 특히 편리한 기능 중 하나는 `ArrayField`를 지원한다는 것이다. `Django`는 이러한 형태의 필드를 지원한다. 다음과 같이 사용하면 된다.
```
# board/models.py
from django.contrib.postgres.fields import ArrayField
from django.db import models
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField(blank=True)
tags = ArrayField(models.CharField(max_length=20), blank=True)
def __str__(self):
return f"Post({self.id}, {self.title})"
```
위 구문에서 `tags = ArrayField(models.CharField(max_length=20), blank=True)`이 `ArrayField`의 선언이다. 실제 DDL을 확인해보면 다음과 같다.
```
create table board_post
(
id bigserial primary key,
title varchar(50) not null,
content text not null,
tags varchar(20)[] not null
);
```
# 사용법
간단히 사용례를 테스트코드와 함꼐 살펴보면 다음과 같다.
```
# tests/board/models/test_post.py
from django.test import TestCase
from board.models import Post
class PostTest(TestCase):
def test_생성시에_태그를_넣을수있다(self):
post = Post.objects.create(title="title", content="content", tags=["a", "b"])
assert Post.objects.count() == 1
assert post.tags == ["a", "b"]
def test_태그로_필터를_할_수_있다(self):
Post.objects.create(title="title", content="content", tags=["a", "b", "c"])
Post.objects.create(title="title", content="content", tags=["a", "b"])
Post.objects.create(title="title", content="content", tags=["a"])
assert Post.objects.count() == 3
assert Post.objects.filter(tags__contains=["a"]).count() == 3
assert Post.objects.filter(tags__contains=["b"]).count() == 2
assert Post.objects.filter(tags__contains=["c"]).count() == 1
def test_태그의_길이를_알_수_있다(self):
Post.objects.create(title="title", content="content", tags=["a", "b", "c"])
Post.objects.create(title="title", content="content", tags=["a", "b"])
Post.objects.create(title="title", content="content", tags=["a", "c"])
Post.objects.create(title="title", content="content", tags=["a"])
qs = Post.objects.filter(tags__len=2)
assert qs.count() == 2
```
- 생성 및 쿼리
- 태그 기반 필터 (`contains`)
- 태그 길이 기반 필터 (`__len`)
에 대한 학습 테스트 케이스다. 위와 같이 작성하면 내용 이해에는 문제가 없을 것이다.

세 가지 경우 모두 잘 실행되는 것을 확인할 수 있다.
# 인덱싱
## 팩토리 선언
아무리 표현력이 좋아도 성능이 안 좋으면 쓸 수가 없다. 기존 원시 데이터 타입의 경우는 `B-Tree`같은 일반적인 인덱싱을 통해 성능 문제를 해결했다. 그렇다면, `ArrayField`는 어떻게 인덱싱을 해야할까? 우선 성능 검증을 위해 실험을 준비해보자.
```
# tests/board/factories.py
import random
import faker
from factory import Faker, LazyFunction
from factory.django import DjangoModelFactory
from board.models import Post
def _random_tags():
_faker = faker.Faker()
return _faker.words(nb=random.randrange(1, 10), unique=True)
class PostFactory(DjangoModelFactory):
class Meta:
model = Post
title = Faker('text', max_nb_chars=20)
content = Faker('text', max_nb_chars=100)
tags = LazyFunction(_random_tags)
```
위와 같이 `Factory`를 만들자.
```
def _random_tags():
_faker = faker.Faker()
return _faker.words(nb=random.randrange(1, 10), unique=True)
```
이렇게 `_random_tags`를 분리하지 않으면 한번의 `create_batch`에서 같은 길이의 배열들만 생성이 된다. `nb`에 `random.randrange(1, 10)`등을 넘겨줄 경우에는 그 시점에 평가가 일어나 결국 `Faker('words', nb=5`와 같은 구문이 전달되어 버리기 때문이다. 따라서 함수를 만들어 `LazyFunction`으로 감싸 주어야 한다.
하지만 대량의 Post를 만들 때는 위 구문이 심각하게 느려서
```
class PostFactory(DjangoModelFactory):
class Meta:
model = Post
title = Faker('text', max_nb_chars=20)
content = Faker('text', max_nb_chars=100)
tags = Faker('words', nb=random.randrange(1, 10), unique=True)
```
위와 같은 간단한 구현으로 다시 돌아갔다.
## TC 다시쓰기
이왕 팩토리를 만들었으니 테스트코드도 리팩토링을 해 보자.
```
# tests/board/models/test_post.py
from django.test import TestCase
from board.models import Post
from tests.board.factories import PostFactory
class PostTest(TestCase):
def test_생성시에_태그를_넣을수있다(self):
post = PostFactory.create(tags=["a", "b"])
assert Post.objects.count() == 1
assert post.tags == ["a", "b"]
def test_태그로_필터를_할_수_있다(self):
PostFactory.create(tags=["a", "b", "c"])
PostFactory.create(tags=["a", "b"])
PostFactory.create(tags=["a"])
assert Post.objects.filter(tags__contains=["a"]).count() == 3
assert Post.objects.filter(tags__contains=["b"]).count() == 2
assert Post.objects.filter(tags__contains=["c"]).count() == 1
def test_태그의_길이를_알_수_있다(self):
PostFactory.create(tags=["a", "b", "c"])
PostFactory.create(tags=["a", "b"])
PostFactory.create(tags=["a", "c"])
PostFactory.create(tags=["a"])
qs = Post.objects.filter(tags__len=2)
assert qs.count() == 2
```
기존 코드의 경우 `Post`의 선언에서 `title`이나 `content`같이 부수적인 정보들이 들어있어서 초점을 흐렸다. 하지만 이제 진짜 보고 싶은 부분인 `tags`에만 선언부가 집중되므로 훨씬 읽기 좋은 테스트코드가 되었다.
## DB seeds
다시 실험으로 돌아와서, 100만개 정도의 row를 준비해보자.
```
# board/management/commands/seed_post.py
from django.core.management import BaseCommand
from board.models import Post
from tests.board.factories import PostFactory
class Command(BaseCommand):
help = "100만개의 Post 준비"
def handle(self, *args, **options):
for _ in range(1000):
ps = PostFactory.build_batch(1000)
Post.objects.bulk_create(ps)
```
위와 같은 커맨드를 준비하고, `python manage.py seed_post`와 같이 실행해서 실험을 준비할 수 있다.
그런데 왜 굳이 `build_batch`와 `bulk_create`를 나누어서 실행했을까? 바로 `create_batch`가 비효율적으로 실행되기 때문이다.
`build_batch`의 경우 내부 구현은 다음과 같다.
```
@classmethod
def create_batch(cls, size, **kwargs):
"""Create a batch of instances of the given class, with overridden attrs.
Args:
size (int): the number of instances to create
Returns:
object list: the created instances """
return [cls.create(**kwargs) for _ in range(size)]
```
위와 같이 단순히 `create`를 여러 번 호출하는 것임을 알 수 있다.

실제로 위와 같이 `PostFactory.create_batch(10)`을 실행하면 `insert` 구문이 10번 찍히는 걸 볼 수 있다. 따라서 `build`를 통해 객체의 어레이를 만들고, `bulk_create`를 하는 것이 효율적임을 알 수 있다.
## 실제 인덱싱
### 구현
```
class Post(models.Model):
# ...
class Meta:
indexes = [
GinIndex(fields=["tags"]),
]
```
`GinIndex`를 추가하자. ArrayField의 경우 `GinIndex`를 추가해야 한다. [공식문서](https://www.postgresql.org/docs/current/arrays.html)에서는 이러한 설계를 권장하지는 않는다.
> **Arrays are not sets**
> searching for specific array elements can be a sign of database misdesign.
> Consider using a separate table with a row for each item that would be an array element.
> This will be easier to search, and is likely to scale better for a large number of elements.
하지만 현실적인 이유로 ArrayField + Index를 사용해야하는 경우가 있다. 어쨌든 인덱싱을 해보자. 실제 운영중인 DB의 경우 인덱스로 인해 락을 잡는게 무리인 경우가 있다. 그런 경우 마이그레이션 파일을 다음과 같이 고치자.
```
import django.contrib.postgres.indexes
from django.contrib.postgres.operations import AddIndexConcurrently
from django.db import migrations
class Migration(migrations.Migration):
atomic = False
dependencies = [
('board', '0004_alter_post_tags'),
]
operations = [
AddIndexConcurrently(
model_name='post',
index=django.contrib.postgres.indexes.GinIndex(fields=['tags'], name='board_post_tags_59756a_gin'),
),
]
```
평소와 달라진 점은 `atomic = False`와 `migrations.AddIndex` 대신 `AddIndexConcurrently`를 사용했다는 것이다. 평소보다 인덱스 생성에 걸리는 시간이 길지만 실제 운용시에는 훨씬 유용하다.
실험에는 다음과 같은 구문을 이용했다.
```
SELECT "board_post"."id", "board_post"."title", "board_post"."content", "board_post"."tags"
FROM "board_post"
WHERE "board_post"."tags" @> (ARRAY ['site', 'perform'])::varchar(20)[]
LIMIT 21;
```
### Before

풀 스캔을 돈다.
### After

인덱스를 잘 활용하는 것을 볼 수 있다.
하단 표에 정리해두었다. 인덱스를 두는 쪽이 확실히 성능이 뛰어난 것을 볼 수 있다.
<figure class="wp-block-table">| | ~Index | Index |
|---|---|---|
| 걸린 시간(ms) | 130.818 | 0.904 |
| 비용 | 41299.93 | 95.27 |
`</figure>`# 인터페이스 노출하기
위와 같이 실제 인덱싱까지 살펴보았다. 하지만 실제로 위 코드를 프로덕션에서 쓰기에는 부족함이 있다. 바로 추상화이다. 위 `ArrayField`를 사용하려면 실제 구현에 대해 너무 많이 알아야 하기 때문이다. 가령, 태그를 더하고 빼는 과정을 생각해보자. “
## 메소드 만들기
```
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField(blank=True)
tags = ArrayField(models.CharField(max_length=20), blank=True)
def has_tag(self, tag: str):
return tag in self.tags
def add_tag(self, tag: str):
if self.has_tag(tag):
return
self.tags.append(tag)
self.save(update_fields=["tags"])
return self.tags
def remove_tag(self, tag: str):
if not self.has_tag(tag):
return
self.tags.remove(tag)
self.save(update_fields=["tags"])
return self.tags
def __str__(self):
return f"Post({self.id}, {self.title})"
```
위와 같이 `has_tag`, `add_tag`, `remove_tag` 세 개의 메소드로 `setter`를 대신할 수 있다. 이제 좀 더 의미있는 이름과 안정적인 인터페이스를 제공하게 된 것이다. 이제 극단적으로 내부에서 `ArraryField`대신 구분자와 `CharField`를 사용해도 문제가 되지 않는다.
다음과 같이 TC로 검증해보자.
```
from django.test import TestCase
from tests.board.factories import PostFactory
class PostTagTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.post = PostFactory.create(tags=["a", "b"])
def test_태그가_있는지_검사할수있다(self):
assert self.post.has_tag("a")
assert not self.post.has_tag("nono")
def test_태그를_추가할_수_있다(self):
self.post.add_tag("word")
assert self.post.tags == ["a", "b", "word"]
def test_태그를_삭제할_수_있다(self):
self.post.remove_tag("a")
assert self.post.tags == ["b"]
```
## 클래스 도입
하지만 위와 같이 쓰다보면 의문이 생긴다. *정말 이게 다 Post의 책임일까?* 맞는 거 같기도 하고… 아닌 거 같기도 하다. 만약 Post가 아니라면, 누가 책임을 져야 하나? ***바로 Tag 자신이 지면 되지 않을까?*** 다음과 같이 작성해보자.
```
class Tags:
def __init__(self, tags: List[str], post: "Post"):
self.tags = set(tags)
self._post = post
@property
def set(self) -> Set[str]:
return self.tags
def contains(self, tag: str):
return tag in self.tags
def add(self, tag: str):
self.tags.add(tag)
self._commit()
def remove(self, tag: str):
if not self.contains(tag):
return
self.tags.remove(tag)
self._commit()
def _commit(self):
self._post._tags = list(self.tags)
self._post.save(update_fields=["_tags"])
```
위와 같이 파이썬 객체를 하나 만들어보자. 영속성과 관련된 부분은 프라이빗 필드 `post`와 `_commit`에서만 알고 있다. 그리고 `Post` 본문은 아래와 같이 고쳐보자.
```
class Post(models.Model):
title = models.CharField(max_length=50)
content = models.TextField(blank=True)
_tags = ArrayField(models.CharField(max_length=20), blank=True, db_column="tags")
_tag = None
@property
def tags(self):
self._tag = self._tag or Tags(self._tags, self)
return self._tag
```
필드가 변경되었기 때문에 `migrate`를 해줘야 한다. DB에는 변화가 없다. 실제로는 파이썬 객체의 변경일 뿐이기 때문이다. 이제 다음과 같이 좀 더 유창하게 사용할 수 있다.
```
from django.test import TestCase
from tests.board.factories import PostFactory
class PostTagTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.post = PostFactory.create(tags=["a", "b"])
def test_태그가_있는지_검사할수있다(self):
assert self.post.tags.contains("a")
assert not self.post.tags.contains("nono")
def test_태그를_추가할_수_있다(self):
self.post.tags.add("word")
assert self.post.tags.set == {"a", "b", "word"}
def test_태그를_삭제할_수_있다(self):
self.post.tags.remove("a")
assert self.post.tags.set == {"b"}
```
그런데, 사실 생각해보니 위 과정은 필드를 커스텀한 게 아닐뿐인가 하는 의문이 든다. 실제로 그렇다. 아예 명시적으로 위 과정을 Field로 만들어버리자.
## 필드로 만들기
```
from typing import Set, List
from django.contrib.postgres.fields import ArrayField
class Tags:
def __init__(self, tags: List[str]):
self.tags = set(tags)
@property
def set(self) -> Set[str]:
return self.tags
def contains(self, tag: str):
return tag in self.tags
def add(self, tag: str):
self.tags.add(tag)
def remove(self, tag: str):
if not self.contains(tag):
return
self.tags.remove(tag)
class TagField(ArrayField):
def from_db_value(self, value, expression, connection):
if value is None:
return None
return Tags(value)
def to_python(self, value):
if isinstance(value, Tags):
return value
if value is None:
return Tags([])
return Tags(value)
def get_db_prep_value(self, value, connection, prepared=False):
if isinstance(value, Tags):
return list(value.set)
return value
```
위와 같이 쓰면 아예 새로운 `Field`로 만들어버릴 수 있다. 그리고 `Tags`도 영속성 관리에서 벗어나 순수한 파이썬 객체로 변했다. 하지만 이제 다시 명시적인 `save`의 책임을 가지게 된다. 아래와 같이 테스트케이스를 작성할 수 있다.
```
class PostTagTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.post = PostFactory.create(tags=Tags(["a", "b"]))
def test_태그가_있는지_검사할수있다(self):
assert self.post.tags.contains("a")
assert not self.post.tags.contains("nono")
def test_태그를_추가할_수_있다(self):
self.post.tags.add("word")
self.post.save(update_fields=["tags"])
assert Post.objects.first().tags.set == {"a", "b", "word"}
def test_태그를_삭제할_수_있다(self):
self.post.tags.remove("a")
self.post.save(update_fields=["tags"])
assert self.post.tags.set == {"b"}
```
# 결론
공식 문서에서 나왔듯 `ArrayField`는 좋지 못한 설계일 수 있다. 하지만 편리한 것도 사실이고 무책임한 말이지만 좋은 곳에 쓰면 좋다. 나쁜 점만 있다면, `PostgreSQL`이 지원할 이유가 없지 않겠는가. 기본적인 사용법과 인덱싱, 그리고 객체지향적인 설계에 대한 고민도 함께 해보았다. 설계는 보였던 세 개 (`model`의 메소드, 영속성 관리 책임을 갖는 클래스로 분리, `CustomField`) 중에 고민해보면 좋을 것이다. 각각 장단점이 있으며 물론 `save`를 직접 호출하는 것도 하나의 방법이 될 수 있다.
---
### Django admin timeouts – 외부키 로딩 시간 길어짐
Django admin 모델을 로드하는데 ForeignKey 로 선언한 부분에서, 외부키 데이터가 엄청 많아져서 이걸 셀렉트 선택창으로 로딩하는데 시간이 소모되어 admin 창이 안뜨고 timeout으로 에러 나는 경우가 발생!!!!
해결책이 다 있다. 수정이 안되게, readonly field 로 만들어서 로딩하는 것으로 해결!
<figure class="wp-block-pullquote">> **<mark class="has-inline-color" style="background-color:rgba(0, 0, 0, 0);color:#ff002b"><kbd>readonly\_fields or raw\_id\_fields `</kbd>``</mark>`**
`</figure>`수정도 필요하다면, 아래 글 참고
How to fix Django admin timeouts
> Have you ever noticed some Django admin page taking a long time to load? Maybe even ending in a timeout (http 504)?
>
> [https://engineering.loadsmart.com/blog/how-to-fix-django-admin-timeouts](https://engineering.loadsmart.com/blog/how-to-fix-django-admin-timeouts)``
## 원본글 일부 발췌
### Use `readonly_fields`
In this situation, the easiest solution would be to just set those fields as [`readonly_fields`](https://docs.djangoproject.com/en/4.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields)in the page definition.
```
class CustomAdmin(admin.ModelAdmin):
readonly_fields = (
"some_field", # ForeignKey with lots of instances
)
```
With it, the field will be rendered on the admin page just as its current value, statically.
## Must be editable?
If having a foreign key as an editable field on the admin is a must for your use case, consider these options:
### Use `raw_id_fields`
Setting the field as a [raw id field](https://docs.djangoproject.com/en/4.1/ref/contrib/admin/#django.contrib.admin.ModelAdmin.raw_id_fields) still allows its editing as just the id value of the related entity:
```
class CustomAdmin(admin.ModelAdmin):
raw_id_fields = (
"some_field", # ForeignKey with lots of instances
)
```
It will render an input field on the admin with the possibility of opening a popup to search for the desired instance
---
### Django ORM – on_delete 옵션
on\_delete 는 Foreign Key로 지정된 row, 항목이 삭제될때의 조건을 붙여두는 것이다.
좋은 설명글이 있어 옮겨 둔다.
> 아래는 on\_delete 옵션의 개념을 다른 사이트에서 쉽게 풀어놓아 붙여보았다.
>
> CASCADE : “안녕 세상에, 나는 article\_B 없이는 살 수 없다”고 자살한다.
> PROTECT : “아니오! 제발! 하지마! 난 너 없이는 살 수 없어!”
> SET\_NULL : “좋아, 내가 너의 사람이 아니라면 난 아무도 아니야”
> SET\_DEFAULT, SET() : “괜찮아, 여분의 애인이있어, 지금부터 article\_C를 참조 할 것이다”
> DO\_NOTHING : “나는 현실에 직면 할 수 없다, 그것이 나에게 남아있는 유일한 것이더라도 당신의 이름을 계속 부르겠다!”
>
> [https://velog.io/@byoungju1012/TIL29.-ondelete-%EC%98%B5%EC%85%98](https://velog.io/@byoungju1012/TIL29.-ondelete-%EC%98%B5%EC%85%98)``
---
### Django ninja schema – UUID 선언 방법
UUID 타입을 선언하는 방법
```
import uuid
class SumOutSchema(Schema):
tenant: uuid.UUID
class UserOut(Schema):
email: str
telephone: str
uuid: uuid.UUID
```
도움이 되길…
[https://github.com/vitalik/django-ninja/issues/46](https://github.com/vitalik/django-ninja/issues/46)
---
### Django – empty queryset 만들기
빈 QuerySet 을 만들고 싶다. 어렵다. 왜 이렇게 ㅋㅋㅋ
한줄이네.
You can have an empty queryset by doing this:
```
MyModel.objects.none()
```
참고 사이트는 여기
[https://stackoverflow.com/questions/11243559/create-empty-queryset-by-default-in-django-form-fields](https://stackoverflow.com/questions/11243559/create-empty-queryset-by-default-in-django-form-fields)
#### `none`()
Calling `none()` will create a queryset that never returns any objects and no query will be executed when accessing the results. A `qs.none()` queryset is an instance of `EmptyQuerySet`.
Examples:
```
>>> Entry.objects.none()
>>> from django.db.models.query import EmptyQuerySet
>>> isinstance(Entry.objects.none(), EmptyQuerySet)
True
```
---
### Django – app runserver 한번만 실행하기 noreload
Django run 하면 app 이 2번 실행된다.
싫으면
```
./manage.py runserver -noreload
```
혹은 다음 코드를 app 에 입력하라는 좋은 글이 있어 킵
```
import os
if os.environ.get('RUN_MAIN', None) != 'true':
default_app_config = 'server.apps.ServerAppConfig'
```
<figure class="wp-block-embed is-type-wp-embed"><div class="wp-block-embed__wrapper">https://h.apoolog.com/django%EC%9D%98-appconfig-%EB%91%90%EB%B2%88-%EC%8B%A4%ED%96%89-%EB%AC%B4%ED%9A%A8%ED%99%94%ED%95%98%EA%B8%B0/ `</div>``</figure>`
---
### Django – 파이썬 날짜함수 기본
파이썬 datetime 기본 함수들이 자꾸 찾아보게 되네
비슷한 분들이 많은가봐 정리를 잘 해두셨네.
파이썬에서 날짜와 시간을 다루기 위한 기본 함수는 datetime 모듈에 정의되어 있습니다. datetime 모듈은 다음과 같은 클래스를 제공합니다.
- datetime.date: 연, 월, 일을 나타내는 날짜 객체입니다. date.today() 함수를 사용하면 현재 날짜를 반환합니다.
- datetime.time: 시, 분, 초, 마이크로초를 나타내는 시간 객체입니다. time 객체는 시간대 정보를 가질 수 있습니다. time.hour, time.minute 등의 속성을 사용하면 시간의 각 요소에 접근할 수 있습니다.
오늘 날짜를 가져오고 싶다면, 년도, 달, 일, 시간 각각을 바로 가져오려면
```
from datetime import datetime
datetime.today() # 현재 날짜 가져오기
datetime.today().year # 현재 연도 가져오기
datetime.today().month # 현재 월 가져오기
datetime.today().day # 현재 일 가져오기
datetime.today().hour # 현재 시간 가져오기
```
- datetime.datetime: 날짜와 시간을 모두 나타내는 객체입니다. datetime.now() 함수를 사용하면 현재 날짜와 시간을 반환합니다. datetime 객체는 date 객체와 time 객체의 모든 속성과 메서드를 상속합니다. 또한 datetime.strptime() 함수를 사용하면 문자열을 datetime 객체로 변환할 수 있습니다.
- datetime.timedelta: 두 날짜나 시간의 차이를 나타내는 기간 객체입니다. timedelta 객체는 일, 시간, 분, 초, 마이크로초 등의 단위로 생성할 수 있습니다. timedelta 객체는 날짜나 시간 객체와 산술 연산을 할 수 있습니다. 예를 들어, date + timedelta 는 date 보다 timedelta 만큼 늦은 날짜를 반환합니다.
- datetime.tzinfo: 시간대 정보를 나타내는 추상 베이스 클래스입니다. 이 클래스를 상속하여 시간대를 정의할 수 있습니다. datetime.timezone 클래스는 tzinfo 클래스를 구현한 구체적인 클래스로, UTC로부터의 고정 오프셋을 가지는 시간대를 나타냅니다.
자세한 내용은 [파이썬 공식 문서](https://docs.python.org/ko/3/library/datetime.html)를 참고하십시오. 또한, [일잘러의 비밀, 엑셀 대신 파이썬으로 업무 자동화하기](https://www.hanbit.co.kr/store/books/look.php?p_code=B3346434043)라는 책에서도 파이썬의 날짜와 시간 함수를 활용하는 방법을 알려줍니다.
---
### Django – AWS Cognito 연동 예제 글 (초안)
Django – AWS Cognito 연동 예제 만들어 보기 – 초안
안녕하세요. 오늘은 장고(Django) 웹 프레임워크와 AWS Cognito 서비스를 연동하는 예제를 소개하겠습니다. Cognito는 AWS에서 제공하는 사용자 인증 및 권한 관리 서비스입니다. 장고에서 Cognito를 사용하면, 사용자가 손쉽게 회원가입, 로그인, 비밀번호 변경 등의 기능을 이용할 수 있습니다. 또한, Cognito는 다양한 소셜 로그인을 지원하므로, 페이스북, 구글, 카카오 등의 계정으로도 로그인할 수 있습니다.
이번 예제에서는 장고 프로젝트를 생성하고, Cognito 사용자 풀(User Pool)을 생성하고 설정하는 방법을 알아보겠습니다. 그리고, django-cognito-jwt 라이브러리를 이용하여 장고와 Cognito를 연동하는 방법을 설명하겠습니다. 마지막으로, 장고의 admin 페이지에 로그인하는 방법과 장고의 views.py에서 Cognito 사용자 정보에 접근하는 방법을 보여드리겠습니다.
이 글은 장고와 AWS에 대한 기본적인 지식이 있는 분들을 대상으로 합니다. 장고와 AWS에 익숙하지 않으신 분들은 다른 자료를 참고하시기 바랍니다.
## 장고 프로젝트 생성하기
먼저, 장고 프로젝트를 생성해야 합니다. 새로운 폴더를 만들고, 가상 환경을 활성화한 후, pip 명령어로 장고와 django-cognito-jwt 라이브러리를 설치합니다.
```
$ mkdir django-cognito-example
$ cd django-cognito-example
$ python -m venv venv
$ source venv/bin/activate
(venv) $ pip install django django-cognito-jwt
```
다음으로, django-admin 명령어로 새로운 프로젝트와 앱을 생성합니다.
```
(venv) $ django-admin startproject config .
(venv) $ python manage.py startapp accounts
```
이제, config/settings.py 파일을 열고, INSTALLED\_APPS에 accounts 앱을 추가합니다.
```
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'accounts', # 추가
]
```
그리고, config/urls.py 파일을 열고, accounts 앱의 urls.py 파일을 포함시킵니다.
```
from django.contrib import admin
from django.urls import path, include # 추가
urlpatterns = [
path('admin/', admin.site.urls),
path('accounts/', include('accounts.urls')), # 추가
]
```
마지막으로, accounts/urls.py 파일을 생성하고, 다음과 같이 작성합니다.
```
from django.urls import path
from . import views
urlpatterns = [
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
]
```
여기까지 장고 프로젝트의 기본적인 구조를 만들었습니다. 다음으로, Cognito 사용자 풀을 생성하고 설정하는 방법을 알아보겠습니다.
---
### Django – HttpResponse() 예제
간단한 결과 확인을 위한 HttpResponse 예제를 하나 정리해둔다.
공식 사이트는 여기 => [https://docs.djangoproject.com/en/4.2/ref/request-response/#httpresponse-objects](https://docs.djangoproject.com/en/4.2/ref/request-response/#httpresponse-objects)
```
def index_page(request):
latest_question_list = Company.objects.order_by("-created_at")[:5]
response = HttpResponse()
response.write("Here's the text of the web page.")
response.write("Here's another paragraph.")
for item in latest_question_list:
response.write("")
response.write(item)
response.write("")
return response
```
---
### Django Ninja 개발 블로그 추천
Django ninja 사용자가 별로 없어 보이는데, 꼭 링크를 저장해두고 보자! 잘 정리해둔 사이트
[https://yubi5050.tistory.com/category/Python%20%28with.%20Code%29/Django-ninja](https://yubi5050.tistory.com/category/Python%20%28with.%20Code%29/Django-ninja)
[**\[Django Ninja\] Validation 구현 방법 (with. Pydantic)**](https://yubi5050.tistory.com/272)
[](https://yubi5050.tistory.com/261)
[**\[Django Ninja\] 설계 패턴 정하기 (feat. 리팩토링)**](https://yubi5050.tistory.com/261)
[](https://yubi5050.tistory.com/253)
[**\[Django Ninja\] Path Variable & Query Parameter 사용 방법**](https://yubi5050.tistory.com/253)
[](https://yubi5050.tistory.com/250)
[**\[Django Ninja\] CRUD 예제 (+bulk)**](https://yubi5050.tistory.com/250)
[](https://yubi5050.tistory.com/247)
[**\[Django Ninja\] GIS 데이터 처리 관련 정리**](https://yubi5050.tistory.com/247)
[](https://yubi5050.tistory.com/244)
[**\[Django Ninja\] 정참조, 역참조 Schema 구현**](https://yubi5050.tistory.com/244)