티스토리 뷰

반응형

0. 들어가면서

팔로우 기능은 user와 user의 관계다. 현재 내부에 있는 User를 쓰고 있다. 그러나 그 user는 field도 없고 그냥 껍데기 이다. 우리는 User가 상속 박도 있는 AbstractUser를 이용하여 custom user를 만들것이다. 그렇다면 팔로우기능을 구현하기 위해서는 custom user에 대한 개념을 잡고 해야한다. 따라서 custom user에 대해 먼저 학습해보자.

 

 

https://docs.djangoproject.com/en/3.0/topics/auth/customizing/

 

Customizing authentication in Django | Django documentation | Django

The Django Software Foundation deeply values the diversity of our developers, users, and community. We are distraught by the suffering, oppression, and systemic racism the Black community faces every day. We can no longer remain silent. In silence, we are

docs.djangoproject.com

위의 공식 문서의 내용 중에 아래의 내용이 있다.

 

위의 내용을 읽어보면 커스텀 유져는 바로 쓰지 않더라도 꺼내 놓고 시작하는 것을 매우 추천한다고 적혀 있다. 자세한 과정은 블로그 글인 '프로젝트 시작 순서' 부분을 참고 하자.

 

0-1. 내부 user

 

자세한 내용은 아래의 블로그를 참고하고 다시 돌아오자.

https://han-py.tistory.com/144

 

 

내부 유저를 알아보면서 왜 AbstractUser를 상속받아 커스텀 유저를 만드는 지를 알아보자.

 

https://github.com/django/django/blob/master/django/contrib/auth/models.py

 

django/django

The Web framework for perfectionists with deadlines. - django/django

github.com

위의 공식 문서를 들어가서 django>contrib>auth>models.py에서 class User를 보면 아래와 같다.

User가 가진 기능은 거의 없다. 대부분의 속성은 User가 가지고 있는게 아니라 User가 상속 받는 AbstractUser가 다 가지고 있다. 그래서 앞으로 AbstractUser의 상속을 받아 커스텀 유져를 만들것이다. 그리고 swappable= 'AUTH_USER_MODEL' 의 뜻은 AUTH_USER_MODEL이라는 속성에 의해서 무엇인가가 바뀔 수 있다는 것을 의미한다. 아래 코드 부분에서 settings.py에서 디폴트 값을 수정할 예정이다.

 

정리하자면, 내부 User는 껍데기일 뿐 아무 기능이 없다. 그래서 추가적인 필드를 정의하고 싶은데, 장고 내부 setting된 값이라 class를 열어서 재정의가 불가능하다. 그래서 지금부터 내부의 class를 외부로 꺼내올 것이다. 아래의 순서대로 코드를 작성해보자. 우선은 accounts의 models.py로 가서 정의를 하자.

 

 

User 재정의

1. accounts/models.py

from django.conf import settings
from django.contrib.auth.models import AbstractUser

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

AbstractUser를 User에 상속시켜서 User를 재정의한다.

settings.AUTH_USER_MODEL은 user class를 가져오기 위해 적은거다.

followers - 팔로우 당한사람

followings - 팔로우를 한 사람

 

여기서 바로 makemigrations를 하면 ERROR 뜬다. 내부설정 값인 auth_user와 중복되서 기존 것들을 못쓴다. settings.py에서 추가 설정이 필요하다.

 

2. settings.py

AUTH_USER_MODEL = 'accounts.User'

아랫줄에 추가하자. 더 이상 AUTH의 Model에 있는 User를 쓰지 않고, accounts.User를 쓰겠다고 선언 한 것이다.

 

+ 설정 전에 디폴트 값(기본값)은

AUTH_USER_MODEL = 'auth.User'

이다.

 

이제 makemigrations 하면 된다. DB가 있는 경우는 db.sqlite3 파일을 지우고 migrate하자.

 

3. accounts/admin.py

> python manage.py createsuperuser

해서 id를 admin으로 하고 비번을 설정한 후에 서버를 열고 관리자 page에 들어가보면, user가 사라져 있다. 즉, 커스텀을 한 순간부터는 자동으로 되지 않고 직접 커스텀 해줘야한다.

from .models import User
admin.site.register(User)

라고 admin.py에 직접 정의를 해야 admin에 User가 뜬다.

 

admin.site.register(User)

어드민 싸이트등록해죠! User

 

하지만 커스텀 하는 순간 오류가 뜬다.

 

4. Arrtribute Error (에러의 원인)

error내용) 'auth.User' has been swapped for 'accounts.User'

아래의 내용들을 보면서 오류를 이해해 보자

 

위의 공식문서에서 django>contrib>auth>forms.py 에 들어가 보면,

class UserCreationForm(forms.ModelForm)
	~
    ~
    class Meta:
    	model = User
        fields = ('~~)

이런식으로 적혀 있다. 하나씩 풀어보면 UserCreationForm은 우리가 지금 쓰고 있는 거고, medel의 User은 장고 내부에서 상속을 받는것이다. 앞에서 말했듯 여기서의 User는 껍데기 밖에 없고 속성이 없는 User이다.

UserCreationForm에서와 같이 모델폼은 항상 모델과 필드, 2가지를 정의해 줘야한다. 지금까지 모델 정의나 다른 어떤 것을 쓰는 경우에는 get_user_model()이라는 함수를 호출해서 user 클래스를 가져왔다. 모델을 정의 할 떄 우리가 항상 settings.AUTH_USER_MODEL을 썼던 이유가 바로 여기에 있다.

커스텀 유저를 하는 순간 모든 코드를 바꿔줘야 한다. 그런데 지금은 장고 내부의 코드만 바꾸면 된다. 왜냐하면 articles의 models.py를 보면 전부 settings.AUTH_USER_MODEL로 설정해 놨기 때문이다. views의 def detail만 봐도 'User=get_user_model()'이라는 method를 쓰고 있기 떄문에 바꿀 필요 없다. 그렇다면 우리는 장고내부의 모델폼을 바꾸기 위해 우리가 썼었던 UserCreationForm을 커스텀 시키는 작업을 form.py에서 진행해 보자.

 

5. accounts/forms.py

forms.py는 원래 없는 파일이니 없다면 생성해 줘야한다.  나머지는 변경할 필요가 없지만 UserCreationForm은 모델 폼이기 때문에 다시 정의를 해주자.

from django.contrib.auth import get_user_model
from django.contrib.auth.forms import UserChangeForm, UserCreationForm

# 그대로 활용하지 못하는 경우는 항상 상속받아서 custom!!!!
class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = get_user_model()
        fields = ['username', 'first_name', 'last_name', 'email']

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = get_user_model()
        fields = ['username', 'email']

 

UserCreationForm은 내부 설정이라 고칠 수 없으니 받아서 등록된 모델을 바꿔준다. 그리고 views에서 UserCreationForm을 지우고 CustomUserCreationForm으로 바꿔주자.

 

model 부분에 get_user_model()이 들어가는 이유는 이 부분에 문자열 말고 클래스를 넣어줘야하기 떄문에 get_user_model이라는 함수를 호출해 준다.

 

+ Q. get_user_model()과 settings.AUTH_USER_MODEL은 같은 것일까?

shell_plus로 들어가자.

> python manage.py shell_plus

> from django.contrib.auth import get_user_model

> from django.conf import settings

> get_user_model()

< accounts.models.User           - User 클래스

> settings.AUTH_USER_MODEL

< 'accounts.User'                   - 문자열

 

즉, method나 어떤 것을 쓰기 위해서는 문자열이 아닌 class를 가지고 와야한다.

 

+ 그렇다면 login form은 왜 커스텀으로 forms.py에 상속을 안해도 될까? 왜 Error 없이 logon이 잘될까?

그 이유는 Authentication Form은 forms나 form을 상속 받기 때문에 모델폼이 아니기 때문이다. 따라서 모델 설정을 추가적으로 할 필요가 없고, get안에 user로 meta 설정도 없다.

 

user 재정의를 했으니 팔로우 구현을 해보자

 

follow 구현

1. 코드 계획

1.url

특정 유저를 팔로우하는 거니 accounts 앱에 만들어야겠다. 그리고 특정 사람을 follow하므로 variable routing이 필요할 듯하다. ex) accounts/1/follow/

2. view

팔로우를 하면 remove(). 팔로우 안하면 add(). (좋아요와 로직은 비슷하다.)

3. templates

user의 datail로 redirect 해주자

 

2. 코드 구현

1. accounts/urls.py

path('<int:pk>/follow/', views.follow, name='follow'),

 

2. accounts/views.py

def follow(request, pk):
    User = get_user_model()
    # 팔로우 당하는 사람
    user = get_object_or_404(User, pk=pk)
    if user != request.user:
        # 팔로우를 요청한 사람 => request.user
        # 팔로우가 되어 있다면,
        if user.followers.filter(pk=request.user.pk).exists():
            # 삭제
            user.followers.remove(request.user)
        else:
            # 추가
            user.followers.add(request.user)
    return redirect('accounts:detail', user.pk)

 

User = get_user_model()

User class를 사용하려면 get_user_model()을 써야한다는 것을 기억하자.

 

user = get_object_or_404(Userpk=pk)

좋아요를 할 때 Article을 가져온 것 처럼 여기서는 User를 가져온 것이다.

 

위에서 user는 팔로우를 당한 사람이고, request.user은 팔로우를 하는 사람이다. followers는 models.py에 정의한 것이다.

 

3. accounts/detail.html에 추가

{% if request.user == user %}
    <a href="{% url 'accounts:update' %}">회원 수정</a>
    <form action="{% url 'accounts:delete' %}" method="POST">
        {% csrf_token %}
        <button class="btn btn-secondary">회원 탈퇴</button>
    </form>
{% endif %}
{% if request.user != user %}
    <hr>
    {% with user_followers=user.followers.all %}
        {% if request.user in user_followers %}
            <a href="{% url 'accounts:follow' user.pk %}">팔로우 취소</a>
        {% else %}
            <a href="{% url 'accounts:follow' user.pk %}">팔로우</a>
        {% endif %}
    {% endwith%}
{% endif %}
<p> {{ user.followers.all|length }}명이 팔로우하고 있습니다.</p>
<p> {{ user.followings.count }}명을 내가 팔로우하고 있습니다.</p>


<hr>
<h3>작성한 글 목록</h3>
{% for article in user.article_set.all %}
    <a href="{% url 'articles:detail' article.pk %}">
        <p>{{ article.title }}</p>
    </a>
{% endfor %}
<h3>좋아요한 글 목록</h3>
{% for article in user.like_articles.all %}
    <a href="{% url 'articles:detail' article.pk %}">
        <p>{{ article.title }}</p>
    </a>
{% endfor %}

 

위의 코드에는 with를 추가하여 쿼리를 줄였다. 아래의 코드는 좀더 깔끔하게 스타일링을 한 것이다.

 

{% with user_followers=user.followers.all %}
    {% if request.user == user %}
        <a href="{% url 'accounts:update' %}">회원 수정</a>
        <form action="{% url 'accounts:delete' %}" method="POST">
            {% csrf_token %}
            <button class="btn btn-secondary">회원 탈퇴</button>
        </form>
    {% else %}
        <hr>
            {% if request.user in user_followers %}
                <a href="{% url 'accounts:follow' user.pk %}">팔로우 취소</a>
            {% else %}
                <a href="{% url 'accounts:follow' user.pk %}">팔로우</a>
            {% endif %}
    {% endif %}
    <p> {{ user_followers|length }}명이 팔로우하고 있습니다.</p>
    <p> {{ user.followings.count }}명을 내가 팔로우하고 있습니다.</p>
{% endwith %}


<hr>
<h3>작성한 글 목록</h3>
{% for article in user.article_set.all %}
    <a href="{% url 'articles:detail' article.pk %}">
        <p>{{ article.title }}</p>
    </a>
{% endfor %}
<h3>좋아요한 글 목록</h3>
{% for article in user.like_articles.all %}
    <a href="{% url 'articles:detail' article.pk %}">
        <p>{{ article.title }}</p>
    </a>
{% endfor %}

 

 

+ articles/index.html 에서 프로필로 넘어 오는 url 만들기

<a href="{% url 'accounts:detail' article.user.pk %}">{{ article.user.username }}

 

 

+ templates/_nav.html 에서 프로필로 넘어오는 url 만들기

<a href="{% url 'accounts:detail' request.user.id %}">{{ request.user.username }}님</a>
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함