카테고리 없음

[Django framework (3) AbstractUser를 활용한 SNS구현] 내일배움캠프 AI 트랙

lovvepearl 2022. 1. 21. 21:54

장고의 데이터베이스를 실행하면

이미 구현되어있는 여러 모델들이

table에 저장되어있는데

그중에서도 auth_user 모델이 있다.

 

이 모델을 살펴보면, 지난번 SNS 구현을 할때

생성했던 my_user 모델과 상당히 비슷한 구조임을

알 수 있다.

장고가 기본으로 제공하는 auth_user 모델
생성한 my_user 모델

 

auth_user 모델을 사용하면

pw 자동 암호화등 제공하는 기능이 많기 때문에

기능을 구현하기 수월해진다는 장점이 있다.

 

하지만, my_user 모델에는 bio와 같이

auth_user에는 구현되어있지 않은 기능도 있기 때문에

my_user 모델 업그레이드 방식을 사용한다.

 

auth_user의 기능을 my_user 에서 사용하기 위해서는

'상속'을 받아야 한다. 

 

my_user 모델이 구현되어있는 models.py에서

아래와 같이 코드를 작성한다. 

 

from django.db import models
from django.contrib.auth.models import AbstractUser # 상속을 위한 인자


# Create your models here.
class UserModel(AbstractUser):  # 인자로 받아주면 auth_user 기능이 상속된다. 
    class Meta:
        db_table = "my_user"

    bio = models.CharField(max_length=256, default='')

AbstractUser를 인자로 받으면 auth_user의 기능이 상속되는데

bio는 상속받은 모델에 없음으로 추가로 작성해준다.

 

auth_user를 상속받아 그대로 사용하는게 아니라

my_user를 업그레이드 하여 사용할 것임으로

메인 app의 settings.py 도 수정해주어야 한다.

 

AUTH_USER_MODEL = 'user.UserModel'

 

기본적인 장고 사용자 모델을 UserModel로 바꿀 것이다

라고 알려주는 코드이다.

 

모델을 변경하면 터미널에서 명령어를 입력하여

장고가 변경된 모델을 알 수 있도록 한다. 

 

python manage.py makemigrations

python manage.py migrate

 

아래와 같이 my_user에 상속받은 auth_user와 동일한

field 가 자동으로 생성되었고, 추가로 bio도 생성된 것을

확인 할 수 있다. 

 

auth_user 모델을 사용하면

기존에 작성하였던 코드를 수정해주어야 한다.

 

01. 회원가입

 

앱의 views.py 에서 get_user_model로 

사용자 정보가 데이터베이스에 존재하는지 확인할 수 있다. 

 

from django.contrib.auth import get_user_model

 

기존 유저를 불러올때 UserModel에서 불러오지 않고

get_user_model을 사용한다. 

 

exist_user = get_user_model().objects.filter(username=username)

 

만약 같은 이름이 없다면 새로운 user로 저장을 해줘야하는데

 

UserModel.objects.create_user(username=username, password=password, bio=bio)

 

create_user를 사용하여 바로 모델에 저장할 수 있다.

저장하려는 정보는 input의 name 속성으로 받아와서

정의된 값들만 가능하다.

 

 

02. 로그인

 

앱의 views.py 에서 auth를 import하고

 

from django.contrib import auth

로그인 시 사용자가 입력한 username과 pw 정보와

일치하는 사용자가 있다면 me라는 변수에 넣어준다. 

 

아래 코드를 작성하면 자동으로 조건에 일치하는 사용자 정보를 찾아준다. 

me = auth.authenticate(request, username=username, password=password)

me의 값이 존재한다면(이미 회원가입한 사용자라면)

사용자의 정보를 받아서 로그인 한다. 

if me is not None: 
    auth.login(request, me)

 

03. 로그인 후 페이지 이동

 

tweet 앱의 views.py

home 함수가 실행되면

사용자가 DB에 존재하면 게시글 페이지로

존재하지 않으면 로그인 화면으로 연결해준다. 

 

tweet 함수가 실행되면

작성한 html 을 출력한다.

from django.shortcuts import render, redirect

def home(request):
    # 사용자가 있는지 확인한다.
    user = request.user.is_authenticated
    if user:
        return redirect('/tweet')
    else:
        return redirect('/sign-in')


def tweet(request):
    if request.method == 'GET':
        return render(request, 'tweet/home.html')

urls.py를 생성하여 path 를 지정해준다.

 

from django.urls import path
from . import views

urlpatterns = [
    path('', views.home, name='home'), 
    path('tweet/', views.tweet, name='tweet')
]

빈라우터면 views.py의 home 함수가 실행되고

tweet 라우터면 tweet 함수가 실행되게 한다.

 

빈라우터로 접속하면 위와 같은 페이지가 뜨는데

장고가 접속할 수 있는 url 을 알려주는 것이다.

 

'/' 와 'tweet/'의 url을 장고가 인지하지 못하고 있는 상황임으로

메인 app의 urls.py에 app의 urls를 연결해주어야한다.

 

path('', include('tweet.urls'))

 

user app의 views.py에서 로그인 함수에

로그인이 정상적으로 되면 빈라우터로 넘어갈 수 있도록 한다.

 

return redirect('/')

 

빈라우터로 넘어가면 tweet의 urls.py에 작성된 path로 연결되고

tweet 앱의 views.py의 home 함수가 실행되면서

사용자가 있는지 확인한 후 게시글 작성 화면을 출력해준다. 

 

'/'에서 views.py home 함수를 통해 사용자 확인 후 '/tweet' 으로 redirect 후 다시 views.py의 tweet 함수가 실행되어 tweet/home.html 이 출력된 모습

 

 

04. 로그인 된 사용자 정보 표시하기

 

장고 template 문법(jinja2)으로 html 을 수정한다. 

내가 정보를 넣고싶은 곳에 {{ DB에서 받아온 정보 }} 형태로 작성하면

아래와 같이 왼쪽 상단 박스에 정보를 표시해줄 수 있다.

 

 

user app의 views.py 에 아래와 같이 user를 정의하였기 때문에

 

user = request.user.is_authenticated

template 문법으로 불러올때도 user 정보 안의 username을

가져오고 싶다면 이렇게 작성해주면 된다.

{{ user.username }}

 

base.html 의 nav 부분을

사용자가 인증이 되지 않았을때는 sign-in, sign-up이 뜨도록하고

인증이 되면 username이 보이도록 구현해보자.

 

<form class="form-inline my-2 my-lg-0">
    {% if not user.is_authenticated %}
    <ul class="navbar-nav mr-auto">
        <li class="nav-item active">
            <a class="nav-link" href="/sign-in"> Sign In <span class="sr-only"></span></a>
        </li>
        <li class="nav-item active">
            <a class="nav-link" href="/sign-up"> Sign Up <span class="sr-only"></span></a>
        </li>
    </ul>
    {% else %}
        {{ user.username }} 님 반갑습니다!
    {% endif %}
</form>

 

만약 사용자가 확인된다면 else 구문이 실행되면서

username이 출력될 것이다.

 

오른쪽 상단 nav에 사용자 이름이 출력된다.

 

 

 

05. 로그인 해야만 접속가능한 페이지 설정

 

게시글 작성 페이지는 로그인한 유저들만 작성가능하도록

views.py의 tweet 함수에 아래 코드를 추가한다.

 

user = request.user.is_authenticated

 

유저가 인증되었다면 html 화면이 뜰 수 있도록 한다.

 

def tweet(request):
    if request.method == 'GET':
        user = request.user.is_authenticated

        if user:
            return render(request, 'tweet/home.html')
        else:
            return redirect('/sign-in')

 

반대로, 로그인한 상태의 유저는 sign-in, sign-up의

페이지가 뜨지 않도록 설정한다.

sign-in 그리고 sign-up 과 연결된 함수에

아래 코드를 작성해준다. 

user = request.user.is_authenticated
if user:
    return redirect('/')
Else:
 	return render(request, ‘user/signin.html’)

 

 

06. 로그아웃

 

로그인 해야지만 로그아웃 할 수 있도록 구현해야한다.

로그인 되어야지만 접근 가능한 함수라고 명시해주어야 하는데

함수 윗부분에 @login_required 코드를 추가해주면 해결된다.

 

from django.contrib.auth.decorators import login_required


@login_required
def logout(request):
    auth.logout(request)
    return redirect('/')

 

views.py를 구현했다면 urls.py에서 path 를 연결해주어야 한다.

 

path('logout/', views.logout, name='logout')

 

html에서 로그아웃에 해당하는 버튼에 url을 연결해준다.

 

<form class="form-inline my-2 my-lg-0">
    {% if not user.is_authenticated %}
    <ul class="navbar-nav mr-auto">
        <li class="nav-item active">
            <a class="nav-link" href="/sign-in"> Sign In <span class="sr-only"></span></a>
        </li>
        <li class="nav-item active">
            <a class="nav-link" href="/sign-up"> Sign Up <span class="sr-only"></span></a>
        </li>
    </ul>
    {% else %}
        <ul class="navbar-nav mr-auto">
        <li class="nav-item disabled">
            <span class="nav-link">{{ user.username }} 님 반갑습니다!</span>
        </li>
        <li class="nav-item active">
            <a class="nav-link" href="/logout"> 로그아웃</a>
        </li>
    </ul>
    {% endif %}
</form>

 

사용자 인증이 되지 않았을때는 sign-in, sign-up 버튼이 뜨고

인증이 되었다면 username 님 반갑습니다! 라는 문구와

로그아웃 버튼이 뜰 것이다. 

 

여기서 username 부분은 버튼이 아님으로 disabled 처리해주어

클릭반응이 없도록 하고 로그아웃버튼에는 href 로 링크를 걸어준다.

 

 

08. 게시글 작성

 

views.py에서 tweet 함수의 POST 방식을 추가해준다.

(현재는 html을 보여주는 상태임으로 GET 방식만 적용된 상태)

 

user는 현재 로그인한 사용자의 정보로 저장되고,

게시글(content)는 input 태그에 작성한 내용을 받아서 저장된다. 

from .models import TweetModel

elif request.method == 'POST':
    user = request.user
    my_tweet = TweetModel()
    my_tweet.author = user
    my_tweet.content = request.POST.get('my-content')
    my_tweet.save()
    return redirect('/tweet')

 

urls.py에서 GET 방식으로 지정된 path가 이미 존재함으로

path를 공유한다. 

 

정보를 받아오는 html 태그에 

action=“/tweet/”(url path) method=“post” 로 지정하고

암호화코드 {% csrf_token %} 를 form 태그 안에 작성해준다. 

 

게시글을 작성하면 html 상에서는 보이지 않지만

DB의 tweet - content에서 확인할 수 있다. 

 

 

09. 게시글 보여주기

 

views.py Tweet 함수의 GET 방식에서

tweetModel의 모든 정보를 불러오고

.order_by('-created_at') 생성일의 역순으로 배열한다.

(나중에 생성된 것이 제일 위로 올라옴)

 

def tweet(request):
    if request.method == 'GET':
        user = request.user.is_authenticated

        if user:
            all_tweet = TweetModel.objects.all().order_by('-created_at')
            return render(request, 'tweet/home.html', {'tweet': all_tweet})
        else:
            return redirect('/sign-in')

'tweet' 이라는 이름으로 넘겨준 모든 게시글 all_tweet을 html에서 받는다. 

 

{% for tw in tweet %}
    <div class="col-md-12 mb-2">
        <div class="card">
            <div class="card-body">
                <div class="media">
                    <div class="media-body">
                        <h5 class="mt-0">{{ tw.content }}</h5>
                    </div>
                    <div style="text-align: right">
                        <span style="font-size: small">{{ tw.author.username }}-{{ tw.created_at|timesince }} 전</span>
                    </div>
                </div>
            </div>
        </div>
    </div>
{% endfor %}

 

반복문으로 작성된 게시물을 하나씩 꺼내서 보여준다.

 

왜 tw.author.username 으로 유저를 명시할까?

author은 foreignkey로 UserModel의 전체 정보를 불러오는 역할을 하고,

전체 정보중에서 username만 뽑아서 오는 방식이다.

 

시간에 | timesince를 붙여주면 현재로부터 얼마 전이라고 표시된다.

 

 

10. 게시글 삭제

 

게시글은 고유의 ID값이 부여되기 때문에,

원하는 게시글만 삭제하려면 ID값으로 게시글을

추적해야 한다.

 

views.py에 delete_tweet 함수를 작성하고

로그인 시에만 접근가능한 함수로 표시한다.

 

from django.contrib.auth.decorators import login_required


@login_required
def delete_tweet(request, id):
    my_tweet = TweetModel.objects.get(id=id)
    my_tweet.delete()
    return redirect('/tweet')

 

id값으로 작성한 댓글을 찾으려면

id를 html 에서 받아올 수 있도록 urls.py 에서 path를

설정해야 한다.

 

path('tweet/delete/<int:id>', views.delete_tweet, name='delete-tweet'),

 

path의 url에서 id 값을 int화 하여 숫자로 views.py 에 넘겨준다.

 

{% if tw.author == user %}
    <div style="text-align: right">
        <a href="/tweet/delete/{{ tw.id }}">
            <span class="badge rounded-pill bg-danger">삭제</span>
        </a>
    </div>
{% endif %}

{% for tw in tweet %} 반복문 안에 위의 코드를 작성하고

tw.id를 url에 함께 넘겨준다. 

 

Tw.author이 현재 로그인한 user와 같다면,

삭제 버튼을 보여주고 /tweet/delete/{{ tw.id }} url을 타고 id 가 전달되고

url path 연결된 views.py delete_tweet 함수가 실행된다.

 

 

11. 게시글의 댓글 기능(댓글 작성, 보여주기, 삭제하기)

 

게시글 작성기능과 전체적인 구현방식은 같다. 

 

댓글을 저장할 새로운 모델을 생성한다.

class TweetComment(models.Model):
	class Meta: db_table = "comment"
    
        tweet = models.ForeignKey(TweetModel, on_delete=models.CASCADE)
        author = models.ForeignKey(UserModel, on_delete=models.CASCADE)
        comment = models.CharField(max_length=256)
        created_at = models.DateTimeField(auto_now_add=True)
        updated_at = models.DateTimeField(auto_now=True)

 

새롭게 받아올 정보는 comment 이고, 나머지는 foreignkey로

기존의 모델에서 정보를 받아온다. 

 

새모델을 생성하면 makemigrations 와 migrate 명령을 실행시켜준다.

 

11-1) 댓글 작성

@login_required
def write_comment(request, id):
    if request.method == 'POST':
        comment = request.POST.get("comment", "")
        current_tweet = TweetModel.objects.get(id=id)

        tweet_comment = TweetComment()
        tweet_comment.comment = comment
        tweet_comment.author = request.user
        tweet_comment.tweet = current_tweet
        tweet_comment.save()

        return redirect('/tweet/'+str(id))

input에 작성한 정보를 DB로 전달해야하기 때문에 POST 방식을 사용한다. 

 

comment 는 html의 input name 속성으로 받아오고

html에서 넘겨준 tweet(게시글)의 id 값과 함께 저장된다. 

 

path('tweet/comment/<int:id>', views.write_comment, name='write-comment'),

urls.py에서 path 를 연결해주고 id 값을 전달한다.

 

<form class="input-group mb-3" action="/tweet/comment/{{ tweet.id }}" method="post">

    {% csrf_token %}

    <input type="text" class="form-control" id='comment' name='comment' placeholder="댓글을 작성 해 주세요"/>

    <button class="btn btn-outline-secondary" type="submit">작성</button>

 

버튼을 클릭하면 /tweet/comment/{{ tweet.id }} url 로 이동하게되고

url path를 따라가면 views.py의 write_comment 함수가 실행되면서

댓글이 저장됨을 있다.

 

 

11-2) 댓글 보여주기

 

@login_required
def detail_tweet(request, id):
    if request.method == 'GET':
        my_tweet = TweetModel.objects.get(id=id)
        tweet_comment = TweetComment.objects.filter(tweet_id=id).order_by('-created_at')
        return render(request, 'tweet/tweet_detail.html', {'tweet': my_tweet, 'comment': tweet_comment})

 

댓글 보여주기는 GET 방식이고

tweet/home.html(게시글)의 ‘보기’ 버튼에 링크가 걸려있기 때문에

게시글(tweet)의 id를 받아오게 된다.

tweet_id와 html을 통해 받아온 id가 같은 댓글만 filter하여 DB에서 꺼내온다.

 

불러온 게시글과 댓글 정보는 tweet, comment라는 이름으로 html 에 전달되고

Html에서 template 언어로 작성하면 화면에 보여진다.

 

path('tweet/<int:id>', views.detail_tweet, name='detail-tweet'),

 

게시글 작성버튼을 클릭하면 redirect('/tweet/'+str(id)) 로

이동하면서 위의 path를 통해 detail_tweet 함수가 실행된다.

 

<div class="media">
    <div class="media-bod">
        <h5 class="mt-0">{{ tweet.content }}</h5>
    </div>
    <div style="text-align: right">
        <span style="font-size: small">{{ tweet.author.username }}-{{ tweet.created_at|timesince }} 전</span>
    </div>
</div>

{% for cm in comment %}
    <div class="row">
        <div class="col-md-12">
            <div class="media">
                <div class="media-body">
                    <h5 class="mt-0"> {{ cm.comment }} </h5>
                    <span> {{ cm.author.username }} </span>
                    <span> - {{ cm.created_at | timesince }} 전</span>
                </div>
            </div>
        </div>
    </div>
    <hr>
{% endfor %}

 

views.py를 통해 전달된 tweet과 comment는

위와 같이 template 문법으로 보여줄 수 있다. 

 

 

11-3) 댓글 삭제

 

@login_required
def delete_comment(request, id):
    tweet_comment = TweetComment.objects.get(id=id)
    current_tweet = tweet_comment.tweet.id
    tweet_comment.delete()
    return redirect('/tweet/'+str(current_tweet))

 

comment의 id 값을 html 에서 전달받고 일치하는 댓글을 delete()한다. 

tweet_comment가 저장될때 tweet(게시글)의 id도 함께 저장되기 때문에

 

삭제 후, redirect('/tweet/'+str(current_tweet)) 하여

댓글이 작성되어있었던 게시글의 url로 연결해준다. 

 

path('tweet/comment/delete/<int:id>', views.delete_comment, name='delete-comment'),

 

urls.py의 path 와 html의 링크를 연결해준다. 

 

{% if cm.author == user %}
    <div style="float: right">
        <a href="/tweet/comment/delete/{{ cm.id }}">
            <span class="badge bg-danger">삭제</span>
        </a>
    </div>
{% endif %}

 

댓글 작성자가 로그인한 user와 일치하면 삭제 버튼이 보이도록하고

{% for cm in comment %} 반복문 안에 html 이 작성되어

comment의 id 값을 url로 받아 views.py에 전달한다.

 

 

여기까지 AbstractUser의 상속을 통해

장고의 auth_user 모델을 활용한 SNS 구현을 해보았다.

 

Django는 Flask 와 다르게 정말 많은

기능이 내재되어있다는 것을 실감할 수 있었던

작업이었다💛