[Django Models 뜯어보기 #2] Making Queries
앞으로 예시들에 사용할 모델.
글 하나에 블로그 여러 개 중 하나가 연결되어있고, author는 m2m.
class Blog(models.Model): # 블로그 name = models.CharField(max_length=100) tagline = models.TextField() def __str__(self): return self.name class Author(models.Model): # 작가 name = models.CharField(max_length=50) email = models.EmailField() def __str__(self): return self.name class Entry(models.Model): # 글 blog = models.ForeignKey(Blog) headline = models.CharField(max_length=255) body_text = models.TextField() pub_date = models.DateField() mod_date = models.DateField() authors = models.ManyToManyField(Author) n_comments = models.IntegerField() n_pingbacks = models.IntegerField() rating = models.IntegerField() def __str__(self): return self.headline
오브젝트 만들기
b = Blog(name='Beatles Blog', tagline='All the latest Beatles news.') # 모델 인스턴스화 b.save() # 디비에 저장. SQL의 INSERT를 호출한다. 이거 하기 전까지는 디비를 건들지 않는다.
create()는 create하고 save까지 한다.
오브젝트 바뀐거 저장
>>> b5.name = 'New name' >>> b5.save()
save()하면 UPDATE SQL문을 날린다.
foreignkey 필드 저장
entry = Entry.objects.get(pk=1) yoolmoo_blog = Blog.objects.get(name='유르무르 블로그') entry.blog = yoolmoo_blog entry.save
ManyToManyField 저장
jay = Author.objects.create(name='Jay') entry.authors.add(jay) josh = Author.objects.create(name='Josh') yoolmoo = Author.objects.create(name='Yoolmoo') entry.authors.add(josh, yoolmoo)
add로 추가한다.
오브젝트 가져오기 (Retrieving objects)
오브젝트를 retrieve하면, Manager를 통해 Queryset을 만든다. Filters는 쿼리를 좁힌다. 모든 모델들은 하나 이상의 Manager를 가진다. Manager는 기본으로 objects라는 이름.
Queryset은 디비에서 나온 오브젝트들의 컬렉션이다.
– QuerySet == SELECT
– filter == WHERE, LIMIT
>>> Blog.objects <django.db.models.manager.Manager object at ...> >>> b = Blog(name='Foo', tagline='Bar') >>> b.objects Traceback: ... AttributeError: "Manager isn't accessible via Blog instances."
Managers는 모델 클래스에서만 접근 가능한다.
모든 오브젝트 가져오기
all_entries = Entry.objects.all()
Manager의 all()메서드를 쓴다. 디비 모든 오브젝트의 쿼리셋을 반환한다.
Filter로 오브젝트 걸러서 가져오기
Entry.objects.filter(pub_date__year=2006) # all() 안써도 된다. Entry.objects.all().filter(pub_date__year=2006) >>> Entry.objects.filter( ... headline__startswith='What' ... ).exclude( ... pub_date__gte=datetime.date.today() ... ).filter( ... pub_date__gte=datetime(2005, 1, 30) ... )
Queryset이 evaluated되기 전까진 장고는 쿼리를 돌리지 않는다.
get()으로 싱글 오브젝트 가져오기
filter()는 싱글 오브젝트를 반환하더라도 쿼리셋으로 반환한다.
하나 오브젝트를 가져오려면 get()을써라.
one_entry = Engry.objects.get(pk=1)
get 안에 filter처럼 쓰면 된다.
쿼리 반환값이 없을 때 get()은 DoesNotExist exception을 뱉는다. 반환값이 1 이상일때는 MultipleObjectsReturned.
주석(annotate)
Queryset의 각 오브젝트마다 annotate하기.
>>> from django.db.models import Count
>>> q = Blog.objects.annotate(Count('entry'))
# 첫 번째 블로그 이름
>>> q[0].name
'율무네 블로그'
# 첫 번째 블로그의 엔트리 숫자
>>> q[0].entry__count
10
>>> q = Blog.objects.annotate(number_of_entries=Count('entry'))
# 첫 번째 블로그의 엔트리 숫자. 지정한 이름으로 가져오기
>>> q[0].number_of_entries
42
쿼리셋 제한하기
파이썬 Array-slicing 문법을 사용해서 쿼리셋의 서브셋 가져오기.
SQL의 LIMIT, OFFSET과 비슷하다.
Entry.objects.all()[:5] # 앞에서부터 5개 오브젝트
Entry.objects.all()[5:10] # 6번부터 10번까지
Entry.objects.all()[-1] # 에러난다
Entry.objects.all()[:10:2] # 10번째까지 모든 오브젝트들의 2번째 오브젝트
Entry.objects.order_by('headline')[0]
Field lookups
field이름__lookuptype=value이렇게 쓰면 된다.
Entry.objects.filter(pub_date__lte='2006-01-01') Entry.objects.filter(blog_id=4) # foreignkey일땐 _id 서픽스를 붙여서 primary key를 찾는다. Entry.objects.get(headline__exact="개발자 생활백서") # headline이 '개발자 생활백서'인 것을 찾는다. Blog.objects.get(id__exact=14) # Explicit form Blog.objects.get(id=14) # __exact is implied. 위랑 같다. Blog.objects.get(name__iexact="beatles blog") # 대소문자 구분 X Entry.objects.get(headline__contains='Lennon') # 포함
Span relationships의 Lookups
JOIN을 자동으로 해준다.
Entry.objects.filter(blog__name='율무 블로그') # 블로그모델의 name필드가 `율무 블로그`
Reverse relationship도 참조 가능. 모델 이름을 소문자로 쓴다.
Blog.objects.filter(entry__headline__contains='율무') # 거꾸로 참조 # 두 조건 모두 만족 Blog.objects.filter(entry__authors__isnull=False, entry__authors__name__isnull=True)
Spanning multi-valued relationships
M2M필드나 reverse foreignkey를 필터링할때, 두 가지 종류의 필터가 있다.
– Blog / Entry (one-to-many)
+ 엔트리 제목이 Lennon이면서 2008년에 발행된 블로그는 1개
+ 엔트리 제목이 Lennon이면서 2008년에 발행된 블로그를 찾고싶다.
Blog.objects.filter(entry__headline__contains='Lennon', entry__pub_date__year=2008) # 엔트리 제목이 Lennon포함하면서 엔트리 발행일이 2008 Blog.objects.filter(entry__headline__contains='Lennon').filter(entry__pub_date__year=2008) # 엔트리 제목이 Lennon포함하는 블로그를 찾고, 거기서 엔트리 발행일이 2008인 블로그를 찾는다. 위의 쿼리셋과 다를 수 있다.
모델의 필드를 참조할 수 있는 필터
특정 모델 필드랑 다른 필드 값 비교할 때도 쓸 수 있다.
>>> from django.db.models import F
>>> Entry.objects.filter(n_comments__gt=F('n_pingbacks')) # 커멘트가 핑백보다 수가 많은 Entry 가져오기
>>> Entry.objects.filter(rating__lt=F('n_comments') + F('n_pingbacks')) # 레이팅이 커멘트+핑백보다 적은 엔트리 가져오기
>>> Entry.objects.filter(authors__name=F('blog__name')) # 관계를 span하기 위해선 __를 쓴다.
>>> Entry.objects.filter(mod_date__gt=F('pub_date') + timedelta(days=3)) # 발행일보다 3일 후 날짜보다 큰 수정일을 가진 엔트리를 가져와라
pk lookup shortcut
primary key를 좀 더 편하게 참조할 수 있게 pk라고 shortcut을 만듦
>>> Blog.objects.get(id__exact=14) # Explicit form >>> Blog.objects.get(id=14) # __exact is implied >>> Blog.objects.get(pk=14) # pk implies id__exact # pk가 1이나 4나 7인것 >>> Blog.objects.filter(pk__in=[1,4,7]) # pk가 14보다 큰 것 >>> Blog.objects.filter(pk__gt=14) >>> Entry.objects.filter(blog__pk=3) # entry의 blog의 pk
쿼리셋 캐싱하기
>>> queryset = Entry.objects.all() # 재활용할거 할당해두기 >>> print([p.headline for p in queryset]) >>> print([p.pub_date for p in queryset]) >>> [entry for entry in queryset] # 데이터베이스 쿼리하기 >>> print queryset[5] # 캐시 쓰기 >>> print queryset[5] # 캐시 쓰기
Q 오브젝트로 복잡한 lookups 만들기
filter, exclude, get등에 쓸 수 있다.
from django.db.models import Q Q(question__startswith='What') Poll.objects.get( Q(question__startswith='Who'), Q(pub_date=date(2005, 5, 2)) | Q(pub_date=date(2005, 5, 6)) # OR연산 )
오브젝트 삭제
e.delete()
다중 오브젝트 한번에 업데이트 하기
# pub_date가 2007인 모든 엔트리 헤드라인 업데이트하기 Entry.objects.filter(pub_date__year=2007).update(headline='Everything is the same') # 모든 엔트리 업데이트 >>> Entry.objects.all().update(blog=b)
update 메서드는 SQL문으로 바뀌어 바로 적용된다.
Related objects
다대일(One-to-many)
Forward
Foreignkey로 연결했으면 그냥 .으로 호출 가능
>>> e = Entry.objects.get(id=2) >>> e.blog = some_blog >>> e.save() #이거 해야 저장됨 >>> e = Entry.objects.get(id=2) >>> print(e.blog) # e에서 블로그를 가져올 때 DB를 건드린다. >>> print(e.blog) # 캐시 써서 디비를 건드리지 않는다. >>> e = Entry.objects.select_related().get(id=2) #select_related() >>> print(e.blog) # 캐시 써서 디비를 건드리지 않는다. >>> print(e.blog) # 캐시 써서 디비를 건드리지 않는다.
Backward
모델이 ForeignKey를 갖고 있으면,
>>> b = Blog.objects.get(id=1) >>> b.entry_set.all() # 거꾸로 연결되어있던 Entry를 소문자로 바꾸고, 뒤에 _set을 붙여주면 블로그랑 연결된 모든 entry의 쿼리셋 가져온다. >>> b.entry_set.filter(headline__contains='Lennon') >>> b.entry_set.count()
related_name파라미터를 ForeignKey만들때 쓰면 FOO_set을 오버라이드 할 수 있다.
# related_name 지정해두기 blog = ForeignKey(Blog, on_delete=models.CASCADE, related_name='entries') # related_name으로 불러오기 >>> b = Blog.objects.get(id=1) >>> b.entries.all()
다대다(Many-to-many)
e = Entry.objects.get(id=3) e.authors.all() # Returns all Author objects for this Entry. e.authors.count() e.authors.filter(name__contains='John') a = Author.objects.get(id=5) a.entry_set.all() # Returns all Entry objects for this Author.
Refer
https://docs.djangoproject.com/en/1.9/topics/db/queries/
