Situation
장고로 개인 프로젝트를 개발하는 중이다.
장고 유저 모델을 사용해 회원관리를 할 예정인데, 기본 User는 username
을 Id로 사용한다. 나는 email
을 ID로 사용하고 싶고, 회원마다 아바타도 추가하고 싶다.
Solution
accounts/models.py
# coding: utf-8 from django.db import models from django.contrib.auth.models import ( BaseUserManager, AbstractBaseUser, PermissionsMixin) class MyUserManager(BaseUserManager): def create_user(self, email, nickname, password=None): if not email: raise ValueError('Users must have an email address') user = self.model( email=MyUserManager.normalize_email(email), nickname=nickname, ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, nickname, password): u = self.create_user(email=email, nickname=nickname, password=password, ) u.is_admin = True u.save(using=self._db) return u class MyUser(AbstractBaseUser, PermissionsMixin): email = models.EmailField( verbose_name='email', max_length=255, unique=True, ) nickname = models.CharField( u'닉네임', max_length=10, blank=False, unique=True, default='') avatar = models.ImageField( null=True, blank=True, upload_to='image/avatar/', ) is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) objects = MyUserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['nickname'] def get_full_name(self): # The user is identified by their email address return self.email def get_short_name(self): # The user is identified by their email address return self.email def __str__(self): return self.email def has_perm(self, perm, obj=None): "Does the user have a specific permission?" # Simplest possible answer: Yes, always return True def has_module_perms(self, app_label): "Does the user have permissions to view the app `app_label`?" # Simplest possible answer: Yes, always return True @property def is_staff(self): "Is the user a member of staff?" # Simplest possible answer: All admins are staff return self.is_admin
django 공식 사이트 auth부분에서 최하단 full-example을 참고해 모델을 짠다.
AbstractBaseUser
를 상속받아 새로운 유저 모델을 만들고, USERNAME_FIELD = 'email'
로 이메일을 ID로 사용한다 명시해준다.
settings.py
AUTH_USER_MODEL = 'accounts.MyUser'
settings.py
에 유저 모델을 방금 만든걸로 사용한다 명시한다.
다른앱/models.py
from django.conf import settings class Meetup(models.Model): author = models.ForeignKey(settings.AUTH_USER_MODEL) title = models.CharField(max_length=200)
다른 앱에서는 그 세팅값으로 호출하면 된다.
accounts/forms.py
from django import forms from django.contrib.auth.forms import UserCreationForm, AuthenticationForm from django.core.files.images import get_image_dimensions from .models import MyUser class SignupForm(UserCreationForm): email = forms.EmailField(required=True, widget=forms.EmailInput( attrs={ 'class': 'form-control', 'placeholder': 'Email', 'required': 'True', } )) nickname = forms.RegexField(label="Nickname", max_length=30, regex=r'^[\w.@+-]+$', help_text="Required. 30 characters or fewer. Letters, digits and " "@/./+/-/_ only.", error_messages={ 'invalid': "This value may contain only letters, numbers and " "@/./+/-/_ characters."}, widget=forms.TextInput(attrs={ 'class': 'form-control', 'placeholder': 'Nickname', 'required': 'true', })) password1 = forms.CharField( label='Password', widget=forms.PasswordInput( attrs={ 'class': 'form-control', 'placeholder': 'Password', 'required': 'True', } ) ) password2 = forms.CharField( label="Password confirmation", widget=forms.PasswordInput( attrs={ 'class': 'form-control', 'placeholder': 'Password confirmation', 'required': 'True', } ), help_text="Enter the same password as above, for verification." ) class Meta: # SignupForm에 대한 기술서 model = MyUser fields = ("email", "nickname", "avatar", "password1", "password2",) # 작성한 필드만큼 화면에 보여짐 def clean_avatar(self): avatar = self.cleaned_data['avatar'] try: w, h = get_image_dimensions(avatar) #validate dimensions max_width = max_height = 500 if w > max_width or h > max_height: raise forms.ValidationError( u'Please use an image that is ' '%s x %s pixels or smaller.' % (max_width, max_height)) #validate content type main, sub = avatar.content_type.split('/') if not (main == 'image' and sub in ['jpeg', 'pjpeg', 'gif', 'png']): raise forms.ValidationError(u'Please use a JPEG, ' 'GIF or PNG image.') #validate file size if len(avatar) > (20 * 1024): raise forms.ValidationError( u'Avatar file size may not exceed 20k.') except AttributeError: """ Handles case when we are updating the user profile and do not supply a new avatar """ pass return avatar class LoginForm(AuthenticationForm): email = forms.CharField( max_length=30, widget=forms.TextInput( attrs={ 'class': 'form-control', 'placeholder': 'nickname', 'required': 'True', } ) ) password = forms.CharField( widget=forms.PasswordInput( attrs={ 'class': 'form-control', 'placeholder': 'Password', 'required': 'True', } ) )
필요하다면 form들을 명시해준다.
여기서 avatar = forms.ImageField()
처럼 avatar는 따로 명시하지 않아도 된다. 이따가 clean_avatar
로 추가할것이다. (얘때매 한참 삽질.)
아래 두 개의 링크를 참고해 코딩했다.
http://stackoverflow.com/questions/6396442/add-image-avatar-to-users-in-django
https://coderwall.com/p/bz0sng/simple-django-image-upload-to-model-imagefield
signup.html
<form id="signup" class="form-horizontal" method="post" action="{% url 'signup' %}" enctype="multipart/form-data"> {% csrf_token %} <!-- Username input--> <div class="row control-group"> <div class="form-group col-xs-12 floating-label-form-group controls"> {{ signupform.username.label_tag }} {{ signupform.username }} <span class="field-error"> {{ signupform.username.errors|striptags }} </span> </div> </div> <!-- Email input--> <div class="row control-group"> <div class="form-group col-xs-12 floating-label-form-group controls"> {{ signupform.email.label_tag }} {{ signupform.email }} <span class="field-error"> {{ signupform.email.errors|striptags }} </span> </div> </div> {{ signupform.nickname.label_tag }} {{ signupform.nickname }} {{ signupform.nickname.errors|striptags }} {{ signupform.avatar.label_tag }} {{ signupform.avatar }} {{ signupform.avatar.errors|striptags }} <!-- Password1 input--> <div class="row control-group"> <div class="form-group col-xs-12 floating-label-form-group controls"> {{ signupform.password1.label_tag }} {{ signupform.password1 }} <span class="field-error"> {{ signupform.password1.errors|striptags }} </span> </div> </div> <!-- Password2 input--> <div class="row control-group"> <div class="form-group col-xs-12 floating-label-form-group controls"> {{ signupform.password2.label_tag }} {{ signupform.password2 }} <span class="field-error"> {{ signupform.password2.errors|striptags }} </span> </div> </div> <br> <!-- Button --> <div class="row"> <div class="form-group col-xs-12"> Sign Up <hr> Or <a href="{% url 'login' %}"><u>log in</u></a> if you have an account. </div> </div> </form>
enctype="multipart/form-data"
적어주고, 아까 작성한 모델대로 폼을 넣어준다.
accounts/views.py
from django.shortcuts import render from django.http import HttpResponseRedirect from django.core.urlresolvers import reverse from .forms import SignupForm def signup(request): signupform = SignupForm() if request.method == "POST": signupform = SignupForm(request.POST, request.FILES) if signupform.is_valid(): user = signupform.save(commit=False) user.email = signupform.cleaned_data['email'] user.avatar = signupform.clean_avatar() user.save() return HttpResponseRedirect( reverse("signup_ok") ) return render(request, "signup.html", { "signupform": signupform, })
user save하기 전에 email이랑 avatar는 clean한 데이터로 넣어주고 save한다.
파일 업로드를 위해 SignupForm(request.POST, request.FILES)
에 request.FILES도 추가해줬다.
Conclusion
처음에 이유를 모르게 계속 안 되어서 스트레스받았던 부분이다.
하고 나니까 되게 속시원하네!!