json.loads
json형식을 python의 dictionary로 바꾸어 return
한다.
null
과 blank
Django 공식문서를 통해 null
과 blanck
의 차이 *를 이해하고 User
모델 중 CharField
에서 null
를 단독으로 사용하지 않고 blank
와 함께 사용하였다.
* : null
과 empty string 간에 모호성의 문제가 존재하는데, string-based fields에서 empty string은 blank=True
로 한다 왜냐하면 empty string을 가능한 입력값으로 고려하고 null
과 구분을 두는 것이다.
자세한 migration 방법과 순서는 정리해둔 것을 참고
python manage.py makemigrations <app>
<app>
을 지정하는 이유는 모델 간 참조 관계가 있을 때 순서가 중요하기 때문python manage.py sqlmigrate <app> 0001
(?=.*[a-z])
는 영문 소문자 조건을 의미한다.
상수는 함수 바깥 혹은 임포트 아래에 선언해주는 것이 좋다.
mozilla 참고하니 이미 있는 파일보다 오래된 파일을 업로드할 때 주로 사용된다고 하는데, 이것이 이미 존재하는 email을 기입했을 때 반환하는 status로 알맞은 것인지 햇갈렸다. 구글링해보니 무엇을 사용하는가에 대한 논란이 꽤 있었던 내용이고, 409를 활용하는 사람들이 있는 편인 것 같아서 그대로 409 status를 활용하기로 했다.
filter()
안에 여러개의 kwargs를 다룰 때, 각 kwarg를 Q object
로 캡슐화하여 연산자 &
, |
와 같이 활용한다.
django 공식 문서 참고
없는 ID 입력시 인증(Authentication: 누구인지 확인)되지 않았다는 status로 응답한다. 로그인에 실패하거나 비회원이 회원의 기능을 요구할때 사용된다고 한다. 가입되지 않은 ID나 잘못된 패스워드를 입력하는 것이 클라이언트가 잘못된 요청을 보내는 경우 사용하는 400 Bad Request status를 사용하는 것이 아닌가 고민했다. 하지만 입력된 값들이 ID pattern이나 password pattern에 맞지 않는, 즉 API 스펙에 맞지 않는 것은 아니니 401 status가 더 바람직해 보인다.
post method를 이용하여 구현하는 것이 의문이었다. 이와 관련된 블로그 글 찾아 읽고 정리해보면, 캐싱을 사용하며 쿼리문에 사용자 정보가 들어나는 GET보단 캐싱을 사용하지 않는 POST가 나으며, https를 함께 사용하는 것이 더욱 안전하다는 결론. 캐싱을 사용하지 않으므로 동일한 상태를 보여주지 않고 다수의 로그인 실패 카운트를 고려한 처리를 할 수 있다.
id
보단 log_in_id
로 Manager를 사용하자모델 객체가 생성될때마다 django는 자동으로 id를 카운팅하여 저장한다고 알고 있다.
모호성을 방지하고자 login_id = data.get("id", None)
보단 login_id = data.get("login_id", None)
라고 코딩하였다.
httpie 는 python 으로 개발된 콘솔용 http client 유틸리티로 터미널에서 클라이언트의 회원가입과 로그인 request를 실행해볼 수 있었다.
httpie을 통해 post request를 할 시 CSRF verification failed.
메시지와 함께 403 Forbidden 승인 거부 오류가 발생한다. 따라서 settings.py에서 CSRF protection을 비활성화했다. 이렇게 되면 보안상으로 취약해질 수 있다고 생각할 수 있으나, 충분히 RESTful하다면 문제될 것 없다고 한다.
현재까지 프로젝트에서 쿠키를 사용하지 않으므로 CSRF protection을 잠시 배제하는 것으로 하겠다. 이후 쿠키를 추가하게 된다면, 테스트 때만 잠시 CSRF protection을 배제하겠다.
models.ForeignKey
모델 간 One-to-many(1:N)관계가 있다면 N에 해당하는 모델에서 models.ForeignKey
를 사용하여 1에 해당하는 모델에 연결한다.
# 1:N = User:Posting
class Posting(models.Model):
user = models.ForeignKey('user.User', on_delete=models.CASCADE)
# 1:N = Posting:Image
class Image(models.Model):
posting = models.ForeignKey('Posting', on_delete=models.CASCADE)
models.ForeignKey
로 연결된 다른 모델은 DB에 자동으로 <모델이름>_id
라는 데이터가 추가된다. % python manage.py sqlmigrate posting 0001
BEGIN;
--
-- Create model Posting
--
CREATE TABLE "postings" (
(...생략...)
"user_id" bigint NOT NULL REFERENCES "users" ("id") DEFERRABLE INITIALLY DEFERRED
);
위 예시에서 Posting
은 user
앱의 User
모델을 참조하고 있기에 다음과 같은 방법으로 참조하였다.
# import 하기
from user.models import User
# 'user.User'로 명시하기
class Posting(models.Model):
user = models.ForeignKey('user.User', on_delete=models.CASCADE)
여기서 from user.models import User
로 import해 ForeignKey
에 앱의 이름을 반드시 명시해야한다. 만약 아래와 같이 앱의 이름을 밝혀주지 않으면 에러가 발생한다.
class Posting(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
% python manage.py check
SystemCheckError: System check identified some issues:
ERRORS:
posting.Posting.user: (fields.E300) Field defines a relation with model 'User', which is either not installed, or is abstract.
posting.Posting.user: (fields.E307) The field posting.Posting.user was declared with a lazy reference to 'posting.user', but app 'posting' doesn't provide model 'user'.
System check identified 2 issues (0 silenced).
login_decorator
에서 INVALIE_ERROR
에 대해 블로그에선 클라이언트가 잘못된 요청을 보냈다는 400 Bad Request로 처리하고 있다. 하지만LogInView
에서 INVALID_ERROR
를 클라이언트가 해당 리소스에 대한 인증이 필요하다는 401 Unauthorized로 처리했기에, 통일감과 의미를 고려하여 status 401로 처리하였다.
서버가 요청을 이해했지만 승인을 거부한 경우에 사용하는 status다. 주로 인증 자격 증명은 있지만, 접근 권한이 불충분한 경우 사용한다.
블로그에선 posting의 userid와 일치하지 않는 id가 삭제 요청하는 경우 _401 Unauthorized를 return하고 있으나 403이 더 적합하다고 판단했다.
posting 수정엔 POST 또는 PUT Method를 사용해도 되나, PUT을 사용하기로 했다. PUT과 POST의 차이점은 크게 두 가지일 것이다.
단순한 게시물 수정이기 때문에 위 성질들이 크게 중요하진 않아보인다. 무엇을 선택하든 무방하기에 써보지 않은 PUT을 사용해보기로 했다.
실제 인스타그램 포스팅 수정 기능에선 이미지를 삭제하는 기능이 있으나, 블로그에서 코드엔 이러한 기능이 구현되지 않았다. 그래서 직접 구현해보았다.
class PostingDetailView(View):
@login_decorator
def put(self, request, posting_id):
try:
# ...(생략)...
old_image_url_list = [image.image_url for image in Image.objects.filter(posting_id=posting_id)]
image_url_list = data.get("image_url", old_image_url_list)
# KEY_ERROR
if image_url_list is None:
return JsonResponse({"message": "KEY_ERROR"}, status=400)
if image_url_list != old_image_url_list:
for image_url in set(old_image_url_list) - set(image_url_list):
Image.objects.get(image_url=image_url, posting_id=posting_id).delete()
# ...(생략)...
MySQL을 공식 홈페이지에 들어가서 직접 설치하였으나, database connector를 설치하는 것에서 ERROR가 발생하였다. 찾아보니 파이썬 mac에선 homebrew를 이용하여 설치하는 것이 깔끔하다고 하여 homebrew를 이용하였다.
터미널을 실행시키고brew install mysql
을 사용하여 설치를 완료...하려 했으나 이번엔 brew에서 ERROR가 났다. 이 글을 참고하여 그대로 따라하니 해결되었다.
brew doctor
brew에서 문제가 생기면 위 명령어를 자주 애용하자!
이 글을 참고하여 초기설정을 하였다.
mysql 서버 Start, Stop 하는 법
터미널에서
mysql.server start
mysql.server stop
당연하지만 장고에서 python manage.py migrate
를 하기위해선 MySQL 서버가 동작하고 있어야한다.
파이썬에 mysql을 연동하긱 위해선 database connector를 설치해야하며, 유명한 것이 pysql과 mysqlclient였다. pysql은 파이썬 기반이며, mysqlclient는 C기반이어서 속도가 빠르다고 한다. mysqlclient를 권장하는 글들이 많아서 일단 mysqlclient를 사용하기로 했다.
프로젝트 가상환경에서 $ pip install mysqlclient
입력하여 설치에 성공했다. MySQL을 공식 홈페이지를 통해 다운 받았을 때는 mysql_config not found
라는 문구를 포함한 ERROR가 발생하여 실패했었다. 때문에 해결방법을 서칭하여 위에서 언급한 단계를 시행해보았고, 설치에 성공할 수 있었다.
이 글 참고하였다.