[Django] 비 SPA 방식으로 장고 Forms/Views를 적극 활용한 인스타그램 St 만들기#2_커스텀 유저 지정 및 회원가입 구현

아직·2022년 7월 10일
0
post-thumbnail

1)

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('accounts.urls')),

커스텀 유저를 만들고 회원가입/로그인 기능을 구현하기 위한 accounts 앱을 만들었다. 앱을 만듦과 동시에 내부 urls.py 파일 생성과 설정을 동시에 해주는 것이 좋다.

앱도 있고 귀속된 urls.py 파일도 있다면 프로젝트의 urls.py에서 두 조건을 이어주는 작업을 통해서, accounts/로 접근할 경우 accounts의 urls.py로 안내해준다.

2)

from django.contrib.auth.models import AbstractUser

# class User(models.Model):
 class User(AbstactUser):
    pass

modles.Model을 상속받을 수도 있지만, AbstactUser라는 "그래도 유저 모델이라면 이 정도는 갖추자."는 추상화된 스탠다드(아이디/암호/이메일/이름 등을 갖춤)를 임포트해서 상속하자.

3)
Input

AUTH_USER_MODEL = "accounts.User"

Output

ValueError: Dependency on app with no migrations: accounts

기본 User 모델에 대한 설정을 바꾸는 것이므로 프로젝트 초반에 진행하는 것이 좋다. 이 과정에서 python manage.py migrate 진행 시 경로와 모델이 정상적으로 생성됐음에도 불구하고 ValueError가 발생하는 것을 확인할 수 있다.

프로젝트 초기에 migrate가 무난하게 진행될 수 있었던 것은 이미 auth 앱에 대한 migration 파일이 생성돼 있었기 때문이다. 그러므로 새로 만든 모델일 경우에는 python manage.py makemigrations accounts(모델이 속한 앱?)를 별도로 실행한 다음 migrate를 진행한다.

db.sqlite3를 삭제하지 않고 진행하는 바람에 admind.0001_initial과 accounts.0001_initial 간에 의존성 문제(InconsistentMigrationHistory 에러)가 발생했다. db.sqlite3 파일을 지울 때는 서버를 종료하고 지우자. 이후 migrate 정상 실행.

db.sqlite3를 삭제했기 때문에 createsuperuser도 재실행

4)

from django.contrib import admin
from .models import User

@admin.register(User)
class UserAdmin(admin.ModelAdmin):

settings.py에 등록한 User 클래스는 user 설정을 위한 기본 모델이고 accounts 앱의 admin.py에서 상속한 ModelAdmin 같은 경우는 admin interface를 사용하기 위한 클래스이다.

"The ModelAdmin class is the representation of a model in the admin interface. Usually, these are stored in a file named admin.py in your application."

User라는 모델은 장식자를 통해서 interface라는 뼈대에 세들어 사는 격이다.

@admin.register(User) 적용하지 않았을 때

@admin.register(User) 적용했을 때

5)

def signup(request):
    if request.method == 'POST':
        form = SignupForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, "회원가입 환영합니다.")
            return redirect("/")
    else:
        form = SignupForm()
        
    return render(request, 'accounts/singup_form.html', {
        'form': form,
    })

User 클래스와 이를 통한 SingupForm 클래스가 정의된 상황에서, POST 요청을 처리하는 흐름을 views.py에 fbv로 정의해줬다. SignupForm을 통해서 아직 요청을 보내지 않은 상황이라면 form instance만 정의된 다음에 signup_form.html 파일이 렌더링될 것임을 예상할 수 있다. 그리고 양식을 채워서 유효한 요청을 보낸다면 이번에는 if로 처리될 것임을 예상할 수 있다.

흐름을 따라가자면 else-> return-> if가 더 적절한 것 같은데, 문법적으로는 if/else 짝을 맞추는 게 올바른 것 같다. 혹은 return render 부분이 else 아래로 들어가는 것도 (기능적으로 문제만 없다면) 괜찮지 않을까?

아니다. else가 is_valid에 걸리게 된다면 GET 요청(POST가 아닌 값)에 대한 처리를 해줄 수 없어서 local variable 'form'을 참조할 수 없는 UnboundLocalError가 발생한다.(접근하면서 GET 요청을 보내고, 생성되지 않은 'form'을 참조하려는 꼴)

6)

<form action="" method="POST" enctype="multipart/form-data"> <!--업로드할 파일이 있을 경우에 enctype 사용-->
    {% csrf_token %}
    <table>   
    {{ form }}
    </table>
    <input type="submit" />
</form>

signup 함수에서 정의한 instance form은 {{ }} 사이에 들어간다.

"폼은 HTML에서 적어도 한 개 이상의 type="submit"인 input 요소를 포함하는 ^form^...^/form^ 태그 사이의 요소들의 집합으로 정의된다."

"action: 폼이 제출(submit)될 때 처리가 필요한 데이타를 전달받는 곳의 자원/URL 주소. 설정이 안되면 (혹은 빈 문자열로 설정되면), 폼은 현재 페이지 URL로 다시 제출된다."

7)

class SignupForm(forms.ModelForm):
    class Meta:
        model = User
        fields = ['username', 'password']   

class SignupForm(UserCreationForm):
    pass

User 모델과 연결된 model-form을 작성하면서 가져온 passwords field로 값을 저장할 경우 해싱을 거치지 않는다. 반면 UserCreationForm을 뜯어보면 username field만 가져오고 password 부분은 customizing함을 알 수 있다.

8)
Input

class SignupForm(UserCreationForm):
    class Meta(UserCreationForm.Meta):

Output

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

UserCreationForm 자체도 장고의 auth 앱의 User를 model로 하는 model-form이기 때문에 우리가 accounts 앱에서 설정한 User model을 Meta 한 번 더 지정해줘야 한다.

9)

super().__init__(*args, **kwargs)

부모 클래스의 attribute를 끌어쓰는 super().__init__() 기능은 인자의 텔레포트 기능을 일부 담고 있다. 부모 클래스에서 정의된 인자는 넘어가서 처리될 것이고, 그렇지 않은 인자는 자식 클래스에서 정의된 코딩을 따른다.

args, **kwargs은 부모 클래스를 위한 준비 동작같은 것인데, 항에 해당하는 것이 아니라 형식에 해당하는 것이다. 예를 들면 args는 (1,2)를, *kwargs는 (a=1, b=2)를 넘겨주기 위한 명령어이다.

10)

{% load bootstrap4 %}
...
{% bootstrap_form form %}
+
{% include "_form.html" %}

코딩 색상에서 확인 가능하지만, 보라색인 load와 include는 장고 기능으로 보이고 파란색인 bootstrap_form은 html 혹은 django-bootstrap4 라이브러리 기능으로 보인다.

form instance와 _form.html을 가져오는 형식의 차이에 주목하자. 보다 구어스러운 "'form이라고 정의된 것'에 bootstrap_form을 적용해줘"와 "include할래, "_form.html"을." 괜히 집게발이 달린 것이 아닌가보다.

11)

{{ submit_label|default:"Submit" }}

default: 다음에 빈칸 후 문자열 입력할 경우 TemplateSyntaxError: "-requires 2 arguments, 1 provided." 발생

12)

# @login_required
# def root(request):
#     return render(request, "root.html")

urlpatterns = [
    path('admin/', admin.site.urls),
    path('accounts/', include('accounts.urls')),
    path('', login_required(TemplateView.as_view(template_name='root.html')), name='root'),
]

views.py에서 redirect("/") 부분을 redirect("root")로 바꿔주고 urls.py로 오는 root 요청을 view 해줄 때, root 함수로 구현하는 대신에 path 내부로 넣어 주었다.

13)

def signup(request):
    if request.method == 'POST':
        form = SignupForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, "회원가입 환영합니다.")
            next_url = request.GET.get('next', '/')
            return redirect(next_url)

로그인이 되어있지 않다면 @login_requied 조건 때문에 유효한 signup 이후에 (사진처럼) root 페이지로 갔을 때 해당 url로 넘어간다.

해당 페이지의 url에서 next가 있는 점에 주목해서 해당 주소가 없을 경우 '/'로 이동하는 옵션도 signup 함수에 정의해준다.

결과적으로 로그인이 안돼있는 상황에서 signup을 정상적으로 수행한다면, root 페이지로 이동할 것이고 다시 로그인이 안돼있으므로 로그인 페이지로 보내질 것이다.

0개의 댓글