한 유저가 여러개의 포스트를 작성했을 때 포스트라는 여러 레코드가 유저라는 한 레코드에 연결된다
위 관계를 가지는 작성자 필드를 추가할 것임
models.py
from django.contrib.auth.models import User
class Post(models.Model):
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.pk} {self.title} :: {self.author}"
외래키 필드를 추가한다. 이 때 on_delete 속성은 만약 author가 삭제되었을 경우, 해당 포스트를 삭제할 것이냐고 묻는 것이다.
cascade는 삭제하라는 뜻이며 6가지의 속성을 설정할 수 있다.
참고
이 때 migrations을 진행하면 다음과 같은 텍스트가 뜨는데, default값을 정해달라는 뜻이다.
1번은 migration 할테니, default를 지금 정해달라.
2번은 migration를 취소하고 default를 정하라
You are trying to add a non-nullable field 'author' to post without a default; we can't do that (the database needs
something to populate existing rows).
Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit, and let me add a default in models.py
Select an option:
여기서는 1을 누른다.
Please enter the default value now, as valid Python
The datetime and django.utils.timezone modules are available, so you can do e.g. timezone.now
Type 'exit' to exit this prompt
>>> 1
다음과 같이 on_delete 속성을 SET_NULL로 설정할 수도 있다. 이 때 반드시 null=True를 같이 설정해줘야 한다.
이 때 포스트를 작성한 적 있는 유저를 삭제하면 해당 포스트의 이름은 None으로 남게 된다.
포스트 목록, 포스트 상세 페이지에 작성자 추가하기
tests.py 에서는 매번 테스트를 진행할 때 마다 DB가 초기화되었다고 가정하고 실행된다. 이 때 setUp 함수에서 이러한 데이터를 미리 초기화해줄 수 있다.
tests.py
from django.contrib.auth.models import User
class TestView(TestCase):
def setUp(self):
self.client = Client()
self.user_trump = User.object.create_user(
username='trump',
password='somepassword'
)
self.user_obama = User.object.create_user(
username='obama',
password='somepassword'
)
Category 만들기
models.py
class Category(models.Model):
name = models.CharField(max_length=50, unique=True)
slug = models.SlugField(max_length=200, unique=True, allow_unicode=True)
def __str__(self):
return self.name
class Meta:
verbose_name_plural = 'Categories'
unique 속성은 동일한 name을 가진 카테고리가 없도록 설정하는 것
Meta 클래스를 추가하고 verbose_name_plural 변수를 추가하면 Categorys 항목의 이름을 바꿀 수 있다
마이그리에션 작업 할 것
admin.py
from django.contrib import admin
from .models import Post, Category
admin.site.register(Post)
class CategoryAdmin(admin.ModelAdmin):
prepopulated_fields = {'slug': ('name',)}
admin.site.register(Category, CategoryAdmin)
관리자 계정에서 카테고리 페이지를 볼 수 있도록 추가한다.
이 때 CategoryAdmin(admin.ModelAdmin) 클래스를 추가하고 admin.site.register 에 이 클래스를 인자로 받으면 category name이 입력될 때 slug도 동일하게 같이 입력되게 된다.
blank = True vs null = True
blank = True : 데이터 폼을 작성할 때 필수적이어야 되는지에 대한 선택지
null = True : 데이터베이스에 마이그레이트 될 때 해당 필드가 필수적이어야 되는지에 대한 선택지
author를 설정하고, author가 탈퇴해서 해당 게시물의 author가 None 이 더라도 null = True로 되어있으면 상관없다.
django shell로 다대일구조 연결 확인
기존 쉘은 다음과 같다
python manage.py shell
Type "help", "copyright", "credits" or "license" for more information.
(InteractiveConsole)
>>> from blog.models import Post, Category
>>> Post.objects.count()
5
>>> Category.objects.count()
4
>>> for p in Post.objects.all():
... print(p)
...
1 new post :: bini
2 second post :: mini
3 third post :: mini
4 Meteor :: mini
5 여름이 오면 서핑을 하고싶어요 :: bini
>>> for c in Category.objects.all():
... print(c)
...
programming
culture&art
game
entertainment
무언가 칙칙하게 느껴질 수 있다. 이 때 쓸 수 있는 것이 있다. 일단
pip install django_extensions, ipython 을 한다. 그리고 settings.py 의 INSTALLED_APPS 에 django_extensions 를 추가해준다.
그러면 다음과 같이 바뀐 모습을 볼 수 있다.
일단 색이 추가되어서 가독성이 좋아졌다.
그리고 따로 import를 하지 않아도 된다.
그 외에도 이런것들이 된다.
In [5]: category_programming = Category.objects.get(slug='programming')
In [6]: category_programming
Out[6]: <Category: programming>
In [7]: category_programming = Category.objects.get(name__startswith='c')
In [8]: category_programming
Out[8]: <Category: culture&art>
In [9]: for p in category_programming.post_set.all():
...: print(p)
...:
5 여름이 오면 서핑을 하고싶어요 :: bini
포스트 목록 페이지 수정하기 1
일단 지금까지 작성한 tests.py 를 정리해보자
첫번째, 각 테스트마다 포스트를 만들었다. 네비게이션도 한번에 관리하기 위해서 함수로 만들었는데 포스트도 동일하게 관리할 수 있지 않을까? 이 때 카테고리까지 고려해서 관리해보자
따라서 SetUp을 다음과 같이 수정한다.
tests.py
class TestView(TestCase):
def setUp(self):
self.client = Client()
self.user_trump = User.objects.create_user(
username='trump',
password='somepassword'
)
self.user_obama = User.objects.create_user(
username='obama',
password='somepassword'
)
self.category_programming = Category.objects.create(
name='programming',
slug='programming'
)
self.category_music = Category.objects.create(
name='music',
slug='music'
)
self.post_001 = Post.objects.create(
title='첫번째 포스트 입니다.',
content='Hello, World. We are the World.',
author=self.user_trump,
category=self.category_programming,
)
self.post_002 = Post.objects.create(
title='두번째 포스트 입니다.',
content='안녕 여러분, 나도 여러분의 일부야.',
author=self.user_obama,
category=self.category_music,
)
self.post_003 = Post.objects.create(
title='세번째 포스트 입니다.',
content='카테고리 없어.',
author=self.user_obama,
)
그리고, 이후에 나오는 post에 관한 코드들을 self.post로 바꾸어준다.
이제, 포스트가 3개로 고정되었다. 따라서 test를 할 때 post가 있을때와 없을 때로 나누어준다.