본문 바로가기
개발일지/Django

Django - AttributeError at /accounts/profile/edit/ (해결)

by 다니엘의 개발 이야기 2023. 1. 18.
320x100

#1 개요

 

와.... 진짜 인프런의 이진석님의 강의를 듣기를 너무 잘했다.

알려주시는게 입문적 수준이 아니라서 처음엔 무슨말하는지를 모르겠어서 안들었었는데

책 2권, 강의 2개정도 띄고 들으니깐 정말 중요한 강의이고, 주니어로써 성장하는데에도 중요한 강의중 하나라고 생각한다.

 

아무튼 각설하고

AttributeError at /accounts/profile/edit/
'User' object has no attribute 'profile'

/Users/daniel_choi/Desktop/total_projects/new_instagram/accounts/views.py, line 39, in profile_edit
# FBV
@login_required
def profile_edit(request):
    try:
        # 현재 이 코드는 Profile.objects.get(user=request.user)와 같은 의미다.
        profile = request.user.profile …
    except Profile.DoesNotExist:
        profile = None
    if request.method == 'POST':
        form = ProfileForm(request.POST, request.FILES, instance=profile)
        if form.is_valid():

라는 에러가 떴다.

        profile = request.user.profile

이 부분이 문제가 된 부분이라고 표시가 되었고, 어떻게 해결할지를 몰랐다.

 

경험상 AttributeError를 구글링으로 해결하기란 정말 어렵다. 거의 불가능에 가깝다고 봐도 무방했다.

때문에 다른 에러같은 경우는 직접 힌트를 근거로 찾아간다던지, 구글링으로 가급적이면 해결해보려고 했는데

AttributeError는 무언가 질문할곳이 필요하긴 하다.


#2 에러 당시 나의 코드

 

#forms.py

from django import forms
from .models import Profile


class ProfileForm(forms.ModelForm):
    class Meta:
        model = Profile
        fields = ['address', 'zipcode']
#models.py

from django.conf import settings
from django.db import models

# Create your models here.


class Profile(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL,
                             on_delete=models.CASCADE)
    address = models.CharField(max_length=100)
    zipcode = models.CharField(max_length=6)  # validators = []
#views.py

from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic import TemplateView, UpdateView

from .forms import ProfileForm
from .models import Profile
# Create your views here.

# FBV 방식
# @login_required
# def profile(request):
#     return render(request, 'accounts/profile.html')

# CBV 방식


class ProfileView(LoginRequiredMixin, TemplateView):
    template_name = 'accounts/profile.html'


profile = ProfileView.as_view()

# CBV
# class ProfileUpdateView(LoginRequiredMixin, UpdateView):
#     model = Profile
#     form_class = ProfileForm


# profile_edit = ProfileUpdateView.as_view()


# FBV
@login_required
def profile_edit(request):

    try:
        # 현재 이 코드는 Profile.objects.get(user=request.user)와 같은 의미다.
        profile = request.user.profile
    except Profile.DoesNotExist:
        profile = None

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

#3 위의 에러에 대한 답변

 

안녕하세요.

결론부터 말씀드리자면, Profile 모델에서 User 모델에 대한 외래키를 models.OneToOneField(1:1 관계)가 아니라, models.ForeignKey (1:N 관계) 로 잡으셨기 때문입니다.

models.OneToOneField로 관계를 지정한 것은
하나의 User 모델 인스턴스는 단 하나의 Profile 모델 인스턴스 만을 가지도록 설계했기 때문입니다.

Profile 모델 측에 User 모델과 1:1 관계로 설정하면,
User 인스턴스에서는 .profile 속성이 자동으로 부여됩니다. Profile 모델명을 소문자로 변경한 이름입니다.
그럼 user.profile 로 접근하면, Profile.objects.get(user=user) 를 수행한 것과 동일한 동작을 합니다. user 인스턴스 입장에서 관련 Profile 을 조회할 때 훨씬 편리하고 간결하게 조회할 수 있죠.

그런데, models.ForeignKey로 설정하시게 되면
디폴트로 User 인스턴스에 .profile_set 속성이 자동으로 부여됩니다.
이때에는 .profile_set 속성이 있을 뿐 .profile 속성은 없는 거죠. 그래서 말씀하신 AttributeError가 발생하는 것입니다.

화이팅입니다. :-)

#4 결론 및 분석

 

와.. 진짜 질문하면 답변을 한두번 해주다가 말거나 되도않되는 구글링 답변으로 일관하는 그런 것과는 질적으로 다르다.

문제에 대한 해답 뿐 아니라. 왜 그런지도 알려주셨고

아무튼 답변의 내용을 근거로 해서 기존의 User 모델과 Profile 모델에 대한 ForeignKey 관계를 OneToOneField관계로 변경하니깐 잘 작동 되었다.

 

사실 원칙적으로 접근하면 ForeignKey도, OneToOneField 접근도 모두 옳다고 생각한다.

그도 그럴것이 해당 프로필이 해당 유저가 사라지면 같이 사라지는게 정상이라고 생각하고

하나의 유저에는 하나의 프로필이 부여되는게 맞다고 생각이 들기 때문이다.

하지만 두가지 부분때문에 OneToOneField를 쓰는게 맞았다.

 

첫번째. 나는 views.py에서

 

profile = request.user.profile

라고 정의했다.

그런데 강사님의 말씀대로 하자면

ForeignKey의 경우에

profile = request.user.profile_set

이 되기때문에 일단 첫번째로는 안되고

 

두번째.

실질적으로 내가

profile = request.user.profile_set

으로 값을 바꿔준다고 한들, 그 안에 있는 내용물이

내가 생각하는 profile의 그것과 profile_set의 그것이

다를수 있다는 점은 뭔가 확인은 없이 개인적으로 유추해봤다.

 

다음에는 인자를 ForeignKey로할경우 <model_name>_set

이 될수있다는 점을 인지하고 사용하되,

내용물이 다르면 그냥 OneToOneField로 가든지, 더 적합한 클래스를 사용하여 해결하자.

300x250