해리의 데브로그

Django 26 - 팔로우 기능, 유저모델(AbstractUser) 상속 (1)

|

팔로우 기능, 유저모델(AbstractUser) 상속 (1)

Django 21 M:N 관계설정 포스팅에서 좋아요 기능을 통해 M:N 관계를 설정하는 코드를 작성해보았음. M:N 관계설정을 이용하여 인스타그램의 팔로우 기능을 구현하고 Django 내장 유저모델을 상속받아보도록 하자.

Django user 모델 확장의 4가지 방법

  1. Proxy Model: 기존의 User 모델을 그대로 사용하되, 일부 동작을 변경하는데만 사용
  2. User Profile: 하나의 새로운 모델을 정의한 후, User 모델과 1:1 관계설정(프로필 모델 참조)
  3. AbstractBaseUser: 완전한 새로운 User 모델을 만들때 사용
  4. AbstractUser: 기존의 User 모델을 사용하되, 추가적인 정보를 더 넣고 싶을 때 사용. 2번 방법은 추가로 클래스를 생성하지만, 이 방법의 경우 추가로 클래스를 생성하지는 않음.

관련 내용 참조

accounts/models.py

  • Django 내장 유저 모델을 상속받아 사용
    • 기존의 User 모델을 사용하면서 추가적인 정보만 넣으므로 4번 방법(AbstractUser) 을 이용
    • 사용을 위해 import
from django.conf import settings
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    followers = models.ManyToMnayField(settings.AUTH_USER_MODEL, related_name='followings')

settings.py

  • accounts/models.py 내 AbstractUser를 상속받은 User 모델을 새로 만들었으므로, 이 모델을 유저모델로 사용하기 위해 settings.py 내 추가 설정
  • 유저모델을 변경하였으므로, DB & migration 파일을 삭제 하여 갈아엎은 후, 마이그레이션 재설정
AUTH_USER_MODEL = 'accounts.User'

현재 상태에서 회원가입을 진행할 시에는 아래와 같이 에러가 발생한다.

AttributeError at /accounts/signup/ Manager isn’t available; ‘auth.User’ has been swapped for ‘accounts.User’)

views.py/signup 함수에서 사용하는 UserceationForm 이 문제인데, 이 폼은 장고에서 기본적으로 제공하는 폼으로, 장고 기본 유저모델을 기반으로 만들어져있음. 그러나 settings.py 에서 커스텀 유저모델을 기본 모델로 설정을 변경하였기 때문에 에러가 발생. 따라서, 우리가 만든 유저모델을 기반으로 사용하기위해서는 해당 폼을 커스터마이징할 필요가 있음.

UserCreationForm 커스터마이징

forms.py

  • UserCreationForm 을 상속받아 CustomUserCreationForm 이라는 커스터마이징된 폼 생성
    • get_user_model() 은 settings.py 에서 설정한 유저모델을 가져옴.
    • fields에는 UserCreationForm.Meta.fields 를 입력
from django.contrib.auth.forms import UserCreationForm

class CustomUserCreationForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):
        models = get_user_model()
        fields = UserCreationForm.Meta.fields

views.py

  • signup 함수 내에서 사용하는 UserCreationFormCustomUserCreationForm 으로 변경
    • 사용을 위해 import
from .forms import CustomUserCreationForm

def signup(request):
    if request.user.is_authenticated:
        return redirect('posts:list')
        
    if request.method == 'POST':
        signup_form = CustomUserCreationForm(request.POST)
        if signup_form.is_valid():
            user = signup_form.save()
            Profile.objects.create(user=user) 
            auth_login(request, user)
            return redirect('posts:list')
    
    else:
        signup_form = CustomUserCreationForm()
    
    return render(request, 'accounts/signup.html', {'signup_form':signup_form})

urls.py

  • 팔로우 기능에 대한 urlpattern 설정
urlpatterns = [
    path('<int:user_id>/follow/', views.follow, name='follow'),
]

Django 25 - 1:1 관계 설정 / 프로필 모델 구현

|

1:1관계 설정 (프로필 모델 구현)

유저모델과 1:1로 대응하는 프로필 모델을 구현해보도록 하자. 하나의 유저는 하나의 프로필만을 가지므로 1:1의 관계를 띄는 것이 당연할 것임. 프로필 모델은 유저 모델을 기반으로 프로필 정보를 입력하는 모델로, 인스타그램의 프로필정보와 같은 역할을 하게끔 코드를 작성할 예정임.

accounts/models.py

  • user모델과 1:1관계를 형성해야하므로 OneToOneField 사용
  • 그외 별명 & 자기소개 & 프로필 사진에 대한 필드를 추가
    • 이미지 필드를 사용하기 위해 ProcessedImageField & ResizeToFill import
  • 프로필 필드의 경우, 값이 반드시 있어야 하는게 아니라 비어있어 괜찮다는 옵션 적용: blank = True
  • 작성 후 마이그레이션 재 설정
from imagekit.models import ProcesssedImageField
from imagekit.processors import ResizeToFill
form django.conf import settings

class Profile(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    nickname = models.CharField(max_length=40, blank =True)
    introduction = models.TextField(blank=True)
    image = ProcessedImageField(
    		blank = True,
        	upload_to = 'profile/images',
        	processors = [ResizeToFill(300, 300)],
        	format = 'JPEG',
        	options = {'quality':90},
    		)

views.py (프로필 생성)

유저와 프로필은 1:1관계이므로 유저가 존재할 경우 프로필도 반드시 존재해야함. 따라서 유저가 회원가입하면 프로필도 바로 생성되게 하자.

  • signup (회원가입) 함수에 유저가 생성되는 코드 바로 아래에 프로필을 생성하는 코드 작성
  • profile 모델 import
  • 기존에 회원가입이 이미 완료된 유저들의 경우 프로필을 생성 할 수 없으므로 DB를 밀어서 초기화 진행
    • DB 초기화 경우, db.sqlite3 파일과 migrations폴더 내 0001, 00002 등으로 시작되는 파일을 삭제하면 됨.
from .models import Profile

def signup(request):
    if request.user.is_authenticated:
        return redirect('posts:list')
        
    if request.method == 'POST':
        signup_form = UserCreationForm(request.POST)
        if signup_form.is_valid():
            user = signup_form.save()
            Profile.objects.create(user=user) #프로필 생성
            auth_login(request, user)
            return redirect('posts:list')
    
    else:
        signup_form = UserCreationForm()
    
    return render(request, 'accounts/signup.html', {'signup_form': signup_form})

프로필 수정

기본 MTV 패턴 구현

유저가 존재하는 이상 프로필은 삭제할 필요가 없으므로 delete 로직은 불필요함. signup 함수를 통해서 create로직은 이미 구현되어있으므로 update 로직에 대한 코드만 작성하면 됨.

#views.py
def profile_update(request):
    return render(request, 'accounts/profile_update.html')


#urls.py
urlpatterns = [
    path('profile/', views.profile_update, name='profile_update'),
]
{% extends 'base.html' %}
{% block container %}

<h1>Profile Update</h1>

{% endblock %}

forms.py

  • 프로필 모델 폼 생성 (프로필 모델 import)
from .models import Profile

class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['nickname', 'introduction', 'image',]

views.py

  • forms.py 에서 새로 생성한 프로필 모델폼 import
  • 요청을 보내는(현재 로그인된) 유저의 프로필(request.user.profile) 을 profile 이라는 변수에 저장
  • HTTP methods(GET/POST)에 따라, 프로필 폼을 넘기거나, 사용자가 html 문서에서 새로 입력한 프로필 정보를 받아와 내용을 업데이트 & DB에 저장하게 코드를 작성
    • POST방식의 경우, 이미지 파일이 있으므로 request.FILES 를 인자로 넘겨줄 것.
from .forms import ProfileForm

def profile_update(request):
    profile = request.user.profile
    if request.method == 'POST':
        profile_form = ProfileForm(request.POST, request.FILES, instance=profile)
        if profile_form.is_valid():
            profile_form.save()
        return redirect('accounts:people', request.user.username)
    else:
	    profile_form = ProfileForm(instance=profile)
    return render(request, 'accounts/profile_update.html', {
        						'profile_form':profile_form
    							})

profile_update.html

  • 프로필 폼을 부트스트랩 양식으로 나타냄
  • 이미지 파일이 있으므로, enctype 속성값 적용
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block container %}

<h1>Profile Update</h1>

<form method='POST' enctype="multipart/form-data">
    {% csrf_token %}
    {% bootstrap_form profile_form %}
    <input type="submit" value="Submit"/>
</form>

{% endblock %}

people.html

  • 프로필에 대한 정보를 people.html에 표시
    • 조건문을 사용하여 프로필 이미지가 있는 경우, 프로필 이미지를 보여줌
    • bootstrap - gridsystem을 사용하여 적절히 영역 분배
  • 프로필 수정 링크를 a태그로 연결
{% extends 'base.html' %}
{% load bootstrap 4 %}
{% block container %}

<div class="container">
    <div class="row">
        <div class="col-3">
            <h1> 
            {% if people.profile.image %}
            <img src="{{ people.profile.image.url }}" width= 70, 
                 alt="{{ people.profile.image}}">
            {% endif %}
            {{ people.username }}
            </h1>
        </div>
        <div class="col-9">
            <div>
                <strong>{{ people.profile.nickname }}</strong>
            </div>
            <div>
                {{ people.profile.introduction }}
            </div>
        </div>
    </div>
    
    {% if user == people %}
    <div>
        <a href="{% url 'accounts:profile_update' %}">프로필 수정</a>

_post.html

  • 리스트 페이지의 각 게시글에 작성자의 프로필 이미지가 보이도록 코드 작성
<div class="card" style="width: 18rem;">
  
  <div class="card-header">
    {% if post.user.profile.image %}
    <img src="{{ post.user.profile.image.url }}" width="25" alt="">
    {% endif %}

Django 24 - 유저 정보 수정 & 삭제

|

유저 정보 수정 & 삭제

유저 정보 수정하기

기본 MTV 패턴 구현

  • User에 대한 정보를 수정하므로 accounts 앱 내에서 MTV 패턴을 구현
  • Django 내장 모델폼인 UserChangeForm 사용을 위해 import
  • posts/views.py/update함수와 마찬가지로, 유저정보에 대한 수정이므로 instance 값에 request.user 을 입력하여, 유저 정보가 담긴 모델폼을 변수에 저장한 후 템플릿 변수로 뿌림.
# views.py
from django.contrib.auth.forms import UserChangeForm

def update(request):
    user_change_form = UserChangeForm(instance = request.user)
    return render(request, 'accounts/update.html', {'user_change_form':user_change_form})

# urls.py
urlpatterns = [
    path('update/', views.update, name='update'),
]
  • update 함수로 넘겨진 user_change_form 을 bootstrap 양식에 맞춰서 html에 보여줌
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block container %}

<h1>User Edit</h1>

<form method="POST">
    {% csrf_token %}
    
    {% bootstrap_form user_change_form %}
    <input type="submit" value="Submit"/>
</form>

{% endblock %}

forms.py

장고 내장 모델폼인 UserChangeForm에는 사용자가 업데이트할 필요가 없는 정보들도 함께 많이 담겨 있음. 따라서 많은 데이터 중, Username, Firs tname, Last Name, Email Address에 대한 정보만 수정하게끔 코드를 다듬어보자.

  • accounts/forms.py 파일 생성
    • UserChangeForm을 상속한 CustomUserChangeForm을 만들어 필요한 필드만 가져옴
    • UserChangeForm 사용을 위해 import
  • 모델폼에서 연결해주는 모델의 경우 User() 이라고 설정해도 되나, global settiings 오버라이딩을 통해서 인증 User 모델을 다른 모델로 변경할 수도 있음. 따라서 settings.py에서 설정된 User 모델을 갖고오는 get_user_model() 을 이용하여 사용하자.
    • get_user_model() 사용을 위해 import
from django.contrib.auth.forms import UserChangeForm
from django.contrib.auth import get_user_model

class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = get_user_model()
        fields = ['username', 'email', 'first_name','last_name']

views.py 재 수정

  • views.py 내 유저정보수정하는 모델폼을 forms.py에서 새로 정의한 CustomChangeForm 으로 변경
    • 사용을 위해 CustomUserChangeForm import 해올 것
  • POST 방식인 경우, 수정한 내용으로 업데이트를 하고, GET 방식인 경우, 기존의 데이터를 instance에 담은 채, 폼을 html으로 넘김
  • 로그인이 된 경우에만 해당 함수가 실행될 수 있도록 데코레이터 설정
from .forms import CustomUserChangeForm
from django.contrib.auth.decorators import login_required

@login_required
def update(request):
    if request.method == 'POST':
        user_change_from = CustomUserChangeForm(request.POST, instance=request.user)
        if user_change_form.is_valid():
            user_change_form.save()
            return redirect('accounts:people', request.user.username)
    
    else:
	    user_change_form = CustomUserChangeForm(instance = request.user)
	return render(request, 'accounts/update.html', {
        						'user_change_form':user_change_form
    								})

people.html

  • 유저정보를 수정하는 링크 추가
  • 현재 로그인된 userpeople과 동일한 경우에만 유저 정보 수정 링크가 보이도록 조건문 적용
{% extends 'base.html' %}
{% block container %}

<div class="container">
    <h1> {{ people.username }}</h1>
    {% if user == people %}
    <div>
    <a href="{% url 'accounts:update' %}">계정정보수정</a>
    </div>
    {% endif %}
    <div class="row">
        {% for post in people.post_set.all %}
        <div class="col-4">
            <img src= "{{ post.image_set.first.file.url }}" class="img-fluid"/>
        </div>
        {% endfor %}
    </div>
</div>

{% endblock %}

유저 정보 삭제하기 (회원 탈퇴)

기본 MTV 패턴 구현

  • POST 방식인 경우, DB에서 유저 정보를 삭제하고, GET 방식인 경우, 삭제 페이지로 넘김
  • 로그인이 된 경우에만 해당 함수가 실행 될 수 있또록 데코레이터 설정
# views.py
from django.contrib.auth.decorators import login_reqiured

@login_required
def delete(request):
    if request.method == 'POST':
        request.user.delete()
        return redirect('posts:list')
    return render(request, 'accounts/delete.html')


# urls.py
urlpatterns = [
    path('delete', views.delete, name='delete'),
]
  • delete.html
{% extends 'base.html' %}
{% block container %}

<h1>User delete</h1>
<form method="POST">
    {% csrf_token %}
    <p>정말로 탈퇴하시겠습니까?</p>
    <input type="submit" value="탈퇴"/>
</form>
{% endblock %}
  • update.html
    • 유저정보 삭제 링크를 update.html에 표기
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block container %}

<h1>User Edit</h1>

<form method="POST">
    {% csrf_token %}
    
    {% bootstrap_form user_change_form %}
    <input type="submit" value="Submit"/>
</form>

<a href="{% url 'accounts:delete' %}" class="btn btn-danger">회원탈퇴</a>

{% endblock %}

비밀번호 변경하기

accounts/update.html에서 사용하는 UserChangeForm 으로는 비밀번호 수정이 불가능함. 비밀번호는 암호화가 되어 DB에 저장되므로, 단순히 User edit form에서 알아볼 수 있는 문자로는 수정이 불가능하며 암호화 과정이 필요함. 따라서 비밀번호 변경을 위한 별도의 폼을 사용해야한다.

views.py & urls.py

  • 비밀번호 수정을 위한 Django 내장 폼인 PasswordChangeForm 을 사용 & import
  • 앞서 반복했던 방식과 마찬가지로 POST 방식인 경우, 수정된 비밀번호로 변경시키고, GET 방식인 경우에는 비밀번호 변경 폼을 password.html에 뿌려줌
    • 해당 폼의 경우 들어오는 인자의 순서가 조금 다름.
    • 첫번째 인자: request.user (유저에 대한 정보)
    • 두번째 인자: request.POST (비밀번호에 대한 정보)
    • 혹은 키워드 인자명을 함께 써줘도 됨.
# views.py
from django.contrib.auth.forms import PasswordChangeForm

@login_required
def password(request):
    if request.method == 'POST':
        password_change_form = PasswordChangeForm(request.user, request.POST)
        
        # 키워드인자명을 함께 써줘도 가능
        # password_change_form = PasswordChangeForm(user=request.user, data=request.POST)
        if password_change_form.is_valid():
            password_cchange_form.save()
            return redirect('accounts:people', request.user.username)
    
    else:
        password_change_form = PasswordChangeForm(request.user)
    return render(request, 'accounts/password.html',{
        						'password_change_form':password_change_form
    										})'


# urls.py
urlpatterns = [
    path('password/', views.password, name='password'),]           

password.html & update.html

  • password.html
{% extends 'base.html' %}
{% block container %}

<h1>User password change</h1>
<form method="POST">
    {% csrf_token %}
    {{ password_change_form }}
    <input type="submit" value="submit"/>
</form>
{% endblock %}
  • update.html
    • 비밀번호 변경 링크를 update.html에 표기
{% extends 'base.html' %}
{% load bootstrap4 %}
{% block container %}

<h1>User Edit</h1>

<form method="POST">
    {% csrf_token %}
    
    {% bootstrap_form user_change_form %}
    <input type="submit" value="Submit"/>
</form>

<a href="{% url 'accounts:delete' %}" class="btn btn-danger">회원탈퇴</a>
<a href="{% url 'accounts:password' %}">비밀밀호 변경</a>

{% endblock %}

views.py (2)

세션에는 비밀번호를 포함하여 유저의 정보가 담겨있음. 비밀번호가 바뀔경우, 기존의 세션에 담긴 유저의 비밀번호와 일치하지 않게 되고, 세션이 만료되어 로그인이 풀리게 됨. 따라서 비밀번호를 변경하고도 로그인을 유지하게 하기 위해서는 추가적인 코드 작성이 필요함.

  • update_session_auth_hash(request, user)
    • 세션에 있는 로그인 정보 해쉬를 업데이트할 때 사용하는 메서드
    • 새로 설정된 비밀번호의 정보는 user 에 담겨있음.
    • reqeuest 인자를 통해 session에 정보를 업데이트
    • 사용을 위해 import 해올 것
    • 공식문서 참조
from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth import update_session_auth_hash

@login_required
def password(request):
    if request.method == 'POST':
        password_change_form = PasswordChangeForm(request.user, request.POST)

        if password_change_form.is_valid():
            # 추가된 부분
            user = password_cchange_form.save()
            update_session_auth_hash(request, user)
            # 끝 
            return redirect('accounts:people', request.user.username)
    
    else:
        password_change_form = PasswordChangeForm(request.user)
    return render(request, 'accounts/password.html',{
        						'password_change_form':password_change_form
    										})'

2019년 6월 4주차 TIL

|

2019-06-17

  • 어제 공부한 이미지 폼셋에 이어, 여러장의 사진을 Carousel을 이용하여 자동으로 넘어가는 코드 복습을 진행하였음.
  • forloop.counter 은 반복문의 횟수를 카운터할 때 쓰는 장고템플릿 문법
  • Carousel을 사용할 때, Carousel의 id값을 적용해야하는데, 고유의값을 사용하기 위해 post_id를 활용하였음. 이때, id값에는 숫자만 입력할 경우에는 작동하지 않으므로 문자열을 섞어서 사용해야함.
  • 깃허브 블로그 Liquid tag error를 피하기 위해 raw & endraw 를 장고 & 진자템플릿 문법이 사용되는데마다 쓰고있다. 블로그에서는 raw & endraw 부분이 자동으로 생략되어 raw한 코드들만 보이긴한데, README 파일에서 코드블럭 내용을 보면 raw &endraw 부분때문에 매우지저분해보인다. raw &endraw를 삽입하는 1차원적인 방법말고 근본적인 방법이 없는지 찾아봐야겠다.

2019-06-19

  • 사용자 CRUD 중 수정과 삭제에 해당하는 U,D에 대해 공부하였음.
  • UserChangeForm에는 많은 정보가 담겨있는데, 필요한 필드에 대해서만 사용자 정보를 수정하기 위해 UserchangeForm을 상속받아 CustomUserChangeForm 을 새로 생성하였음.
  • 비밀번호 변경에는 PasswordChangeForm 사용. 다른 폼과 다르게 request.user, request.POST 순으로 인자를 받는데, 혼란이 올 수 있으니, 키워드 인자명을 함께 써주는게 나은 듯 하다.
password_change_form = PasswordChangeForm(request.user, request.POST)
#키워드인자명 함께 사용
password_change_form = PasswordChangeForm(user=request.user, data=request.POST)
  • update_session_auth_hash 는 비밀번호 수정 후에도 로그인을 유지하게 하는 메서드. 세션에 있는 로그인 정보 해쉬를 업데이트 할때 사용함.

2019-06-20

  • Django에서 유저모델과 1:1로 대응하는 프로필 모델을 구현해보았음.
  • 1:1 관계설정에서는 OneToOneField 를 사용함. 말그대로 프로필 처럼, 기존의 유저모델은 보존하면서, 유저모델의 기반의 또다른 하나의 모델을 사용할 때 유용할 듯 하다.
  • 전반적인 코드 작성은 기본적인 MTV 패턴을 구현하는 방식을 사용함.

2019-06-21

  • 유저모델을 상속받아서 M:N 관계를 이용하여 팔로우 기능을 구현하는 챕터를 다시 공부하였음. Django에서 기본 유저 모델을 상속받아 확장하는 방법이 다양한데, 기본적으로 아래와 같이 4가지가 있고, 미세하게 다르다는 것을 알게 되었다. 팔로우 기능의 경우, 기본 유저모델을 사용하면서, follow에 대한 필드만을 추가하므로 AbstractUser 모델을 상속받아 모델을 작성하였음.
    • Proxy Model: 기존의 User 모델을 그대로 사용하되, 일부 동작을 변경하는데만 사용
    • User Profile: 하나의 새로운 모델을 정의한 후, User 모델과 1:1 관계설정(프로필 모델 참조)
    • AbstractBaseUser: 완전한 새로운 User 모델을 만들때 사용
    • AbstractUser: 기존의 User 모델을 사용하되, 추가적인 정보를 더 넣고 싶을 때 사용. 2번 방법은 추가로 클래스를 생성하지만, 이 방법의 경우 추가로 클래스를 생성하지는 않음.
  • 유저모델을 변경했을 경우, 커스터마이징된 유저모델을 기본유저모델로 사용한다는 코드를 settings.py에 추가해야함. => AUTH_USER_MODEL = ‘어플리케이션명.모델명’ 예) accounts.User
  • UserCreationForm 은 장고의 기본 User 모델을 사용하기 때문에, 에러를 피하기 위해 커스터마이징 필요

2019-06-22

  • 어제에 이어서 팔로우 기능에 대한 MTV 패턴을 복습하였음.
  • 좋아요 기능과 마찬가지로 M:N 관계의 경우, 리스트의 형태로 저장되기때문에, 이를 활용하여 같은 기능에 대하여 좋아요/좋아요 취소를 분기문을 통해 나타낼 수 있음.
  • values_list 는 쿼리셋을 튜플의 형태로 변경할 떄 사용하는 메소드임. 하나의 필드만 넣을 경우, flat 인자를 넣을 수 있으며 flat=True는 결과값을 single values로 변경시킴.
  • itertools.chain() 은 여러개의 쿼리셋을 하나의 리스트로 합칠때 사용하는 메서드. 이때 인자는 iterable가능한 데이터 타입이 들어가야함. 따라서 iterable하지 않을 경우, 타입 변경을 통해 iterable 하게 바꿔줘야함!

2019-06-23

  • 장고 복습의 마지막 챕터인 Django-RestFramework 를 이용한 백엔드 API 서버를 구현에 대해 공부를 하고 코드를 작성해보았다.
  • DRF(Django RestFramework): RESTful API 서버 구축을 쉽게 도와주는 라이브러리
  • Django에서 사용하는 복잡한 데이터를 클라이언트 측에서 사용하기 위해서는 JSON이나 XML으로 데이터의 형태(DRF에서는 string이라고 지칭함)로 변환을 해야하는데, serializer가 이 기능을 함.
  • @api_view는 어떠한 HTTP Methods를 허용할건지를 설정할 때 사용하는 데코레이터
  • 이 프로젝트에서는 Artist와 Music 모델이 1:N 관계로 이루어져있는데, serializer에서 Artist 모델을 토대로 Artist에 대한 모든 Music을 갖고올 때, 필드명을 music_set으로 설정하는 코드를 다시 살펴보았다. 1에서 N으로 접근할때, artist.music_set.all() 처럼 사용하기 위해 필드명을 강제로 지정하는 듯 한데, 반드시 이 조건 지켜야하는지 궁금하다. 내일 다시 장고 프로젝트를 새로만들어볼 때 테스트를 해봐야겠다.

Django 23 - 이미지 다루기 2 (Carousel)

|

이미지 다루기 2 (인스타그램)

Django17 미디어 파일 관리 및 업로드 구현 포스팅에서 이미지(미디어)파일을 어떻게 업로드하여 관리하고, 재가공하는지에 대해 정리해보았다. 추가로, 하나의 게시글(Post)에 여러 사진을 올리고, 또한 그사진들을 횡으로 보여지도록, 마치 인스타그램 처럼 기능하게끔 내용을 확장시켜보도록 하자.

사진 슬라이드 넘기기(Carousel 적용)

_post.html

인스타그램처럼 여러장의 사진을 하나의 템플릿안에서 슬라이드 처럼 횡으로 넘길 수 있게 코드를 구현해보자. 이를 위해서 부트스트랩의 carousel을 적절히 활용하면 됨.

  • 첫번째 사진만 active 속성을 부여하기 위해 아래의 장고템플릿 사용
  • ` <div class="carousel-item {% if forloop.counter == 1 %} active {% endif %}">`
    • 반복문(forloop)의 횟수(counter)가 1일경우, 즉 첫번째 사진인 경우 active 속성이 적용됨.
  • 어떤 사진을 보여주고 있고, previous/Next 에 이전/다음 사진이 가는지에 대한 기준을 설정해줘야함. 이러한 부분을 id 속성에 적용시킴.
    • id는 중복되면 안되는 고유의 값이 들어가야하므로 post_id를 활용하여 고유값 부여
    • 이전/다음 사진으로 이동하는 기능을 하는 하단의 a 태그의 href에는 #를 꼭 붙여 줄 것.
 <a class="carousel-control-prev" href="#post_{{ post.id }}" 
  • id값에 {{ post_id }}만 입력할 경우(숫자만 들어간 경우)는 작동하지 않으므로 유의할 것.
<div class="card" style="width: 18rem;">
  <div class="card-header">
    <span> <a href="{% url 'accounts:people' post.user.username %}">
        {{ post.user.username }} </a></span>
  </div> 
  <div id="post_{{ post.id }}" class="carousel slide" data-ride="carousel">
    <div class="carousel-inner">
      {% for image in post.image_set.all %}
      <div class="carousel-item {% if forloop.counter == 1 %} active {% endif %}">
        <img src="{{ image.file.url }}" class="card-img-top" 
             alt="{{ image.file.image }}">
      </div>
      {% endfor %}
    </div>
    <a class="carousel-control-prev" href="#post_{{ post.id }}" 
       role="button" data-slide="prev">
      <span class="carousel-control-prev-icon" aria-hidden="true"></span>
      <span class="sr-only">Previous</span>
    </a>
    <a class="carousel-control-next" href="#post_{{ post.id }}" 
       role="button" data-slide="next">
      <span class="carousel-control-next-icon" aria-hidden="true"></span>
      <span class="sr-only">Next</span>
    </a>
  </div>

accounts/people.html

  • people 페이지에 들어갈 경우, 여러 사진 중, 가장 최근의 첫번째 사진만 뜨게 코드 작성
    • post.image_set.first.file.url 이용(.first)
<div class="container">
    <h1> {{ people.username }}</h1>
    <div class="row">
        {% for post in people.post_set.all %}
        <div class="col-4">
            <img src= "{{ post.image_set.first.file.url }}" class="img-fluid"/>
        </div>
        {% endfor %}
    </div>

Views.py - Update 함수 수정

ImageFormSet 관련 코드를 Create 함수에 넣어서 수정한 것과 마찬가지로, update 함수도 이미지폼셋 관련 코드를 추가로 삽입하고 다듬어보도록 하자.

  • Get 방식인 경우
    • ImageFormSet 을 불러온 후, instance에 부모클래스를 넣음.
    • 이후, 선언한 변수를 템플릿 변수로 넘김
  • Post 방식인 경우
    • 기본적인 CRUD의 Update와 동일한 방식으로 코드 구현
    • PostForm의 두번째 인자에 있던 request.FILESImageFormSet 의 인자로 이동되었으므로 삭제
    • 유효성 검증 후 DB에 저장
    • Update 로직 구현 시에는, 이미 post 가 생성되어있으므로 transaction 코드를 필요 없음.
@login_required
def update(request, post_id):
    post = get_object_or_404(Post, id=post_id)
    if post.user != request.user:
        return redirect('posts:list')
    
    if request.method == 'POST':
        post_form = PostForm(request.POST, instance=post)
        image_formset = ImageFormSet(request.POST, request.FILES, instance=post)
        if post_form.is_valid() and image_formset.is_valid():
            post_form.save()
            image_formset.save()
            return redirect('posts:list')
    else:
        post_form = PostForm(instance=post)
        image_formset = ImageFormSet(instance=post)
    return render(request, 'posts/form.html', {
                                                'post_form': post_form,
                                                'image_formset' : image_formset,
                                                })