Skip to content

Latest commit

 

History

History
181 lines (162 loc) · 11.8 KB

20210513.md

File metadata and controls

181 lines (162 loc) · 11.8 KB

Django

인상 깊은 조언

  • 모델을 잘 작성하는 것도 중요하지만 실제로 모델을 우리가 짤 일은 많지 않으니 이미 작성된 모델을 잘 이해하고 ORM을 통해 가져오는 걸 잘 하는 것이 중요.
  • 관계를 활용하여 편하게 하려고 RDB를 짜는 거니까, 관계를 잘 이해하자.
  • 모델을 짤 때는 신중하고 견고하게 짜야 한다. 잘못 짠 모델에 Data가 들어가면 돌이킬 수 없는 상황이 되므로.

지난 시간 복습

  • 관계형으로 모델을 만들고 이에 기반하여 입력된 Relational DB에서 ORM(Object-Relational Mapping)으로 구현된 메서드를 사용하여 데이터를 데려오는 것을 해보았다.
  • 1:N 모델을 통해 하나의 카테고리에 여러 개의 글, 하나의 반에 여러 명의 학생 등의 관계를 구현

1:N 예제 실습

좋아요 기능

  • 각 유저가 무한대로 좋아요를 누를 수 있는 것도 방법이지만, 일반적인 경우 한 사람은 한 번만 좋아요를 누르거나 취소할 수 있다.
  • Like라는 모델을 만들어 좋아요를 누를 때 생성되게 한다.
  • 댓글 하나에 여러 명이 좋아요를 누를 수 있으므로, 댓글 1, 좋아요 N의 관계로 foreign key 설정한다.
  • 한 사람이 여러 개의 댓글에 좋아요를 누를 수 있으므로 Student 1, Like N의 관계로 foreign key를 설정한다.
  • N 쪽에서 ForeignKey 필드를 가지고 있으므로 Like에 필드를 정의한다.
class Like(models.Model):
    comment = models.ForeignKey(Comment, on_delete=models.CASCADE, related_name='likes')
    liked_by = models.ForeignKey(Student, on_delete=models.CASCADE, related_name='likes')
    created_at = models.TextField(default=time.time())
  • 이 경우 '어떤 댓글'과 '누가'에 대한 데이터가 좋아요 누를 때마다 인스턴스가 생겨서 1줄씩 쌓인다
  • 한 댓글에 여러 명의 '누가'를 담는 N:N 관계로 나중에 구현해볼 수 있다.

쇼핑몰 유저 등급관리

  • 이름과 등급, 구매한 물품을 갖는 고객 정보 테이블과, 상품 정보 테이블, 할인율과 연관된 고객 등급 테이블을 만들어 서로 관계를 설정해보자.
  • 한 명의 고객은 한 개의 등급을 갖는 반면, 한 등급은 여러 고객을 가질 수 있으므로 등급 1, 고객 N의 1:N 관계
  • 아직 N:N 관계를 배우지 않았으니, 한 사람은 한 개의 물건만 살 수 있다고 하자. 그러면 한 상품은 여러개 팔릴 수 있지만 한 명의 고객은 한 상품만 살 수 있으므로 상품 1, 고객 N의 1:N 관계
class Level(models.Model):
    level = models.CharField(max_length=2)
    discount_rate = models.IntegerField(default=0)

class Product(models.Model):
    name = models.CharField(max_length=16)
    price = models.IntegerField()
    created_at = models.TextField(default=time.time())
    is_deleted = models.BooleanField(default=False)

class Customer(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='customer')
    level = models.ForeignKey(Level, on_delete=models.SET_NULL, related_name='customers', null=True, blank=True)
    shopped_product = models.ForeignKey(Product, on_delete=models.SET_NULL, related_name='purchased_by', null=True, blank=True)

Field 옵션

Choices

  • 등급은 쇼핑몰 정책에서 한 번 정의되고 나면 자주 바뀌지는 않는다. 필드를 정의할 때 choices라는 옵션을 통해 이미 정해진 내용 안에서만 고를 수 있게 할 수 있다.
    • choices에 할당할 수 있는 것은 두 개의 요소를 가진 이터러블을 담은 sequence 객체 또는 여러 값을 인스턴스 변수로 가진 클래스 객체이다.
    • choices가 필드에 주어지면 기본 form 위젯은 select box 형태를 띤다.
    • 등급코드와 등급이름을 문자열로 갖는 튜플로 이루어진 배열 등으로 choices를 넣어준다고 하자
      • DB에는 등급코드가 저장되며 해당 데이터 안의 인스턴스 변수처럼 참조할 수 있다.
      • 튜플의 두번째 요소인 등급이름은 해당 필드의 값으로 저장되며, 그룹객체로도 저장할 수 있다.
class Level(models.Model):
    LEVEL_CHOICES = [
	('GR', 'Green'),
	('SV', 'Silver'),
	('GD', 'Gold'),
	('PT', 'Platinum)
    ]
    level = models.CharField(choices=LEVEL_CHOICES, default='GR', max_length=2)
    discount_rate = models.IntegerField(default=0)

Validators

  • 필드에 들어갈 값이 내가 정해둔 로직(함수)을 통화해야 insert되어 실수를 예방하게끔 한다.
def validate_discount_rate(value):
    if value > 1 or value < 0:
        return 에러에러에러

class CustomerLevel(models.Model):
    name = models.CharField(max_length=32)
    discount_rate = models.FloatField(validators=[validate_discount_rate])
  • 옵션의 값으로는 위와 같이 함수 이름이 담긴 배열을 넣어준다. 여러 개일 수도 있고 한개여도 배열에 넣어서 넘겨야 한다.
  • 이는 이 필드에 입력되는 값이 해당 함수를 에러 없이 통과하는 경우에만 데이터가 생성되게 하는 것으로, 정수만 받거나 존재하는 범위 내의 값만 받는 등으로 쓸 수 있다.
  • 장고에서 기본적으로 제공하는 validator 사용하는게 더 편하다.

Default로 특정 모델의 DB값을 넣기

  • 쇼핑몰 유저가 생성될 때, 특정 등급 등 객체를 ForeignKey로 할 때 dafault값을 갖게끔 하려면 두 가지 방법이 있다.
  1. 테이블 생성 시점을 달리하여 Customer 테이블이 생성되기 전에 CustomerLevel 테이블을 먼저 생성하고 데이터를 만들어줘야만 한다.
  • default 등 옵션 안에서는 연산이 안되니 아래와 같이 함수로 빼서 넣어줘야한다. 이 때 함수는 호출을 해도 되고 이름만 넣어줘도 된다.
class Customer(models.Model):
    def initial_date():
	return Level.objects.get(name='Green')

    level = models.ForeignKey(Level, default=initial_date(), on_delete=models.PROTECT, related_name='customers')
  1. 지금은 같은 models.py에 있지만, 앱이 다를 경우 $ python manage.py makemigrations {app name}으로 특정 앱의 모델만 먼저 migrate할 수 있다.
  • default 값의 대상이 될 모델을 migrate한 후 JSON 파일로 만든 데이터를 $ python manage.py loaddata {file name}한다. 그 다음 default를 가지고 생성될 모델을 migrate해준다.

미용실 예약 관리

  • 고객 테이블, 예약자와 예약제품 등의 정보를 담은 예약 건 테이블, 상품 테이블로 만들 수 있다.
  • 예약 건은 고객 한 명이 여러 예약을 할 수 있으므로 1:N 으로 고객을 바라보는 ForeignKey 필드의 변수를 갖는다.
  • 한 상품이 여러 예약의 대상이 될 수 있으므로 상품과 예약도 1:N의 관계를 가지며, 예약건 하나는 상품 하나를 바라보는 ForeignKey 필드의 변수를 갖는다.
class Customer(models.Model):
    user = models.OneToOneField(User, on_delete=models.CASCADE, related_name='customer')
    name = models.CharField(max_length=16)

class Product(models.Model):
    name = models.CharField(max_length=32)
    price = models.IntegerField()

class Reservation(models.Model):
    customer = models.ForeignKey(Customer, on_delete=models.SET_NULL, related_name='reservations', null=True, blank=True)
    product = models.ForeignKey(Product, on_delete=models.SET_NULL, related_name='reservations', null=True, blank=True)
    appointment = models.DateTimeField()
    is_paid = models.BooleanField(default=False)
    is_completed = models.BooleanField(default=False)
    is_cancelled = models.BooleanField(default=False)
  • 예약은 완료되었는지, 취소되었는지, 결제는 되었는지 등을 BooleanField를 갖게 했는데, 단순 완료/미완 상태에 대해서 하나하나씩 필드를 따로 관리하기보다는 choices를 활용해서 status로 관리하는 것도 방법이다.
  • 그러나 결제의 경우는 나누어 관리할 필요가 있으므로(카드결제를 한 예약건을 따로 관리한다거나 등) Payment라는 모델을 따로 만들어서 method와 status를 바라보는 1:N 관계로 구현하는 것이 좋다.

N:N 관계 구현

학생 조회 페이지에서 팔로잉/팔로워 기능 구현하기

  • 템플릿의 student_detail에서 팔로잉을 할 수 있도록 form과 button 태그를 만들어준다.
  • action의 경로에 {% url 'follow' student.pk %}를 해주고, csrf_token과 method는 POST로 해주는 것을 잊지 말 것
  • urls.py에서 follow 경로를 만들어주는데, 누구를 팔로우하는지 알아야 하니까 student_pk도 endpoint에 넣어준다.
  • views.py에서 함수를 정의한다. follow 버튼은 무조건 POST로 오니까 method를 확인하지 않아도 되며, return은 redirect로 student_detail 페이지로 보내준다.
  • models.py에 Relationship이라는 모델을 다음과 같이 만들어주면서, 한 명의 학생에게 하나의 relationship 객체가 붙게 한다.
class Relationship(models.Model):
    student = models.OneToOneField(Student, on_delete=models.CASCADE, related_name='relationship')
    followers = models.ManyToManyField(Student, related_name='following')
  • 이렇게 하면 한 학생을 팔로우하는 다른 학생 객체들은 relationship 객체의 followers에 이터러블로 담기는 모델이 생성된다. makemigrationsmigrate를 해준다.
  • views.py에서 회원가입시 생성된 학생 객체가 relationship을 가지도록, signup 함수에 relationship을 생성하며 생성된 학생 객체를 student에 값으로 넣어준다.
  • follow 함수에서 들어온 student_pk를 통해 relationship 객체를 취득한다.
    • relationship = Relationship.objects.filter(student__pk=student_pk)
  • 내가 팔로우 중일 때 버튼을 누르면 relationship 객체의 followers 목록에서 내가 사라지고, 팔로잉을 하고 있지 않으면 그 목록에 추가되게끔 조건식을 만든다.
def follow(request, student_pk):
    relationship = Relationship.objects.filter(student__pk=student_pk)
    if request.user.student in relationship.followers.all():
	relationship.followers.remove(request.user.student)
    else:
	relationship.followers.add(request.user.student)
    return redirect('student_detail', student_pk)
  • 이 때 followers 목록이 담는 것이 user 객체인지 student 객체인지 잘 확인할 것
    • student 객체를 담도록 해둔 필드인데 request.user를 담으면 대혼란 초래
  • 팔로우를 누르면 바뀌게끔 하려면, 템플릿에서 if문으로 작업할 수도 있긴 하지만 views에서 처리해주는 게 바람직하다.
    • student_detail에서 위의 조건식과 동일하게 내가 follow 중이라면 is_followed라는 변수가 True값을 가지게 한 후 context에 담아서 렌더링하고, 템플릿에는 is_followed 값에 따라 다른 버튼을 마크업한다.
def student_detail(request, student_pk):
    target_student = get_object_or_404(Student, pk = student_pk)
    is_followed = False
    followers = Relationship.objects.filter(owner__pk=student_pk).first().followers.all()
    if request.user.student in followers:
        is_followed = True
    context = {
        'student': target_student,
        'is_followed': is_followed
    }
    return render(request, 'student_detail.html', context)
<form action="{% url 'follow' student.pk %}" method='POST'>
    {% csrf_token %}
    {% if is_followed %}
    <button type='submit'>팔로우 취소</button>
    {% else %}
    <button type='submit'>팔로우</button>
    {% endif %}
</form>

느낀 점

  • 다양한 예제에서 관계형 테이블을 사용해본 만큼 실제 생활에서도 엄청난 사용양상이 있겠다는 짐작이 된다.
  • 장고는 정말 많은 기능을 구현해놨구나...