[Django] Admin Customizing하기

Admin Form 커스터마이즈하기

class QuestionAdmin(admin.ModelAdmin):
    fields = ['pub_date', 'question_text'] # 필드 순서 조정

admin.site.register(Question, QuestionAdmin) # 두 번째 인자로 위에 만든 model admin class를 넘긴다.
class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('날짜 정보', {'fields': ['pub_date']}),
    ]

admin.site.register(Question, QuestionAdmin)

이렇게 하면
– 헤더 없이: question_text 필드 보여짐
– ‘날짜 정보’헤더: pub_date 필드 보여짐

처럼 admin을 커스터마이즈 할 수 있다.

Related objects 추가하기

Question마다 related model인 Choices들이 있는데, admin page에선 안 보이니까 추가해준다.

from .models import Choice, Question


class ChoiceInline(admin.StackedInline): # admin.TabularInline로 하면 더 컴팩트하게 보여준다
    model = Choice
    extra = 3 # 3세트의 Choice 필드를 보여준다


class QuestionAdmin(admin.ModelAdmin):
    fieldsets = [
        (None,               {'fields': ['question_text']}),
        ('날짜 정보', {'fields': ['pub_date'], 'classes': ['collapse']}),
    ]
    inlines = [ChoiceInline] # Choice오브젝트가 Question 어드민 페이지에서 edit될것이다

admin.site.register(Question, QuestionAdmin)

Admin List 커스터마이즈하기

기본적으로, 장고는 각 오브젝트의 str()를 보여준다.

class QuestionAdmin(admin.ModelAdmin):
    # ...
    list_display = ('question_text', 'pub_date') # 리스트에 이 필드들 추가
    list_filter = ['pub_date'] # 리스트에 필터 추가
    search_fields = ['question_text'] # 검색 필터 추가

Refer

https://docs.djangoproject.com/en/1.9/intro/tutorial07/

[Django Models 뜯어보기 #1] Models

Django Models

필드(Fields)

from django.db import models

class Musician(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    instrument = models.CharField(max_length=100)

class Album(models.Model):
    artist = models.ForeignKey(Musician, on_delete=models.CASCADE)
    name = models.CharField(max_length=100)
    release_date = models.DateField()
    num_stars = models.IntegerField()

class Person(models.Model):
    SHIRT_SIZES = (
        ('S', 'Small'),
        ('M', 'Medium'),
        ('L', 'Large'),
    )
    name = models.CharField(max_length=60)
    shirt_size = models.CharField(max_length=1, choices=SHIRT_SIZES)    
  • null: True면, DB의 컬럼에 NULL을 할당할 수 있게 된다. (DB의 NOT NULL)
  • blank: True면, 필드값 입력하지 않아도 된다. 디비의 제약과 관련 없음. 유효성 검증(validation)과 연관.

Verbose field names

ForeignKey, ManyToManyField, OneToOneField빼고 모든 필드는 첫 인자로(옵션) Verbose name을 받는다. 따로 지정하지 않으면 필드명으로 대신한다(언더스코어를 스페이스로 바꾸어서)

first_name = models.CharField("성", max_length=30)

ForeignKey, ManyToManyField, OneToOneField는 첫 인자가 모델 클래스이므로, verbose_name 키워드 인자를 줄 수 있다.

dogs = models.ManyToManyField(Dog, verbose_name='개 리스트')

관계(Relationships)

  1. Many-to-one
class Kind(models.Model):
    # 개 종류

class Dog(models.Model):
    kind = models.ForeignKey(Kind, on_delete=models.CASCADE) # 개마다 종류가 있음

  1. Many-to-many
class Food(models.Model):
    # 음식
    # 음식에 dogs가 있을 수도 있겠지만, Dog에 foods가 있는 것이 더 명확하므로 그렇게 한다.

class Dog(models.Model):
    foods = models.ManyToManyField(Food) # 개마다 먹을 수 있는 여러가지 음식. 복수 형태.

좀 더 복잡한 M2M관계를 표현하기 위해서는 별도의 모델을 만든다. (중간모델/intermediate model)

class Person(models.Model): #인간
    name = models.CharField

class Group(models.Model): #뮤직 그룹
    name = models.CharField
    # 그룹엔 멤버십으로 연결된 멤버들이 있다.
    members = models.ManyToManyField(Person, through='Membership') #멤버스는 멤버십을 통해 생긴다.

class Membership(models.Model): #뮤지컬 멤버십
    person = models.ForeignKey(Person, on_delete=models.CASCADE)
    group = models.ForeignKey(Group, on_delete=models.CASCADE)
    date_joined = models.DateField()
    invite_reason = models.CharField(max_length=64)
>>> ringo = Person.objects.create(name="Ringo Starr")
>>> paul = Person.objects.create(name="Paul McCartney")
>>> beatles = Group.objects.create(name="The Beatles")

# 멤버십에 person, group 넣어서 만들면 그 그룹에 person이 들어가서 생긴다.
>>> m1 = Membership(person=ringo, group=beatles, 
...     date_joined=date(1962, 8, 16),
...     invite_reason="Needed a new drummer.")
>>> m1.save()
>>> beatles.members.all()
[<Person: Ringo Starr>]
>>> ringo.group_set.all()
[<Group: The Beatles>]

>>> m2 = Membership.objects.create(person=paul, group=beatles,
...     date_joined=date(1960, 8, 1),
...     invite_reason="Wanted to form a band.")
>>> beatles.members.all()
[<Person: Ringo Starr>, <Person: Paul McCartney>]

# 아래 3개처럼 추가는 안된다.
>>> beatles.members.add(john)
>>> beatles.members.create(name="George Harrison")
>>> beatles.members = [john, paul, ringo, george]

# intermediate model로 만들었어도, 쿼리 아래처럼 평범하게 날릴 수 있다.
>>> Group.objects.filter(members__name__startswith='Paul')
[<Group: The Beatles>]

# intermediate model 만들때 attribute로도 쿼리 날릴 수 있다.
>>> Person.objects.filter(
...     group__name='The Beatles',
...     membership__date_joined__gt=date(1961,1,1))
  • 중간 모델을 직접 만들 때는, 관계를 가지는 두 모델에 대한 ForeignKey필드를 선언하고 추가적인 필드를 선언하면 된다.
  1. One-to-one
    다른 모델을 확장하여 새로운 모델을 만드는 경우 유용.
    가게라는 데이터베이스가 이미 있었는데, 맛집 데이터베이스를 추가적으로 만들게 되었다. 이 때 가게를 extend받아서 확장시킬 수 있다.

Meta 옵션

모델클래스 내부에 메타데이터를 추가할 수 있다.

class Ox(models.Model):
    horn_length = models.IntegerField()

    class Meta:
        ordering = ["horn_length"] # 순서 정의
        verbose_name_plural = "oxen"

Model Attributes – objects

모델에서 가장 중요한 attribute은 Manager다. 모델 클래스 선언 기반하여 실제 디비에 대한 쿼리 인터페이스를 제공하며, 디비 레코드를 모델 객체로 인스턴스화 하는데 사용된다.
커스텀 Manager를 만들지 않으면, 기본은 ‘objects’라는 이름으로 잡힌다.

class Person(models.Model):
    people = models.Manager()

위와 같이 써주면 Person.objects는 AttributeError를 반환하지만,
Person.people.all()은 모든 Person오브젝트들을 반환한다.

모델에 메서드 추가하기

class Person(models.Model)
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    birth_date = models.DateField()

    def baby_boomer_status(self):
        "사람의 베이비 부머 status를 보여준다."
        import datetime
        if self.birth_date < datetime.date(1945, 8, 1):
            return "Pre-boomer"
        elif self.birth_date < datetime.date(1965, 1, 1):
            return "Baby boomer"
        else:
            return "Post-boomer"

    def _get_full_name(self):
        "사람의 풀네임을 반환한다."
        return '%s %s' % (self.first_name, self.last_name)
    full_name = property(_get_full_name)
  • __unicode__(): 해당 클래스의 유니코드 표현. (python2)
  • __str__(): python3에서 위와 같다. utf-8
  • get_absolute_url(): 오브젝트의 URL. 어드민이나 오브젝트에 url이 필요할 때 쓰인다.

미리 정의된 메서드 오버라이드

save()delete()를 오버라이드 할 경우가 많다.

class Blog(models.Model):
    name = models.CharField(max_length=100)

    def save(self, *args, **kwargs):
        do_something()
        super(Blog, self).save(*args, **kwargs) # 원래 save() method 부르기
        do_something_else()

superclass method를 불러서 원래 기능을 호출한다.

모델 상속(Inheritance)

Abstract base classes

공통적 정보를 다수의 모델에 넣고 싶을 때 유용.

class CommonInfo(models.Model):
    name = models.CharField(max_length=100)
    age = models.PositiveIntegerField()

    class Meta:
        abstract = True #abstract = True로 해준다.

class Student(CommonInfo): #여기에 상속
    home_group = models.CharField(max_length=5)

related_name이 겹치지 않도록 unique한 reverse_name을 적어준다.

from django.db import models

class Base(models.Model):
    m2m = models.ManyToManyField(OtherModel, related_name="%(app_label)s_%(class)s_related")

    class Meta:
        abstract = True

class ChildA(Base): # related_name: common_childa_related
    pass

class ChildB(Base): # related_name: common_childb_related
    pass

Multi-table inheritance

공통 부분의 데이터는 부모모델에 저장, 자식 모델의 데이터는 자식모델 테이블에 저장.

refer

https://docs.djangoproject.com/es/1.9/topics/db/models/
http://nukggul.tistory.com/17

[Django] redirect시에 데이터 넘기고 싶어요(feat. HTTP)

상황

페이스북 로그인을 하고, 페이스북 연결 해제버튼을 눌렀을 때 비밀번호가 설정이 되어있지 않다면 비밀번호 설정 페이지로 리다이렉트 시키고 싶다.
이로 인해서 들어온 비밀번호 설정 페이지에서는 그냥 들어왔을 때와는 다르게 “페이스북 연결을 해제하려면 비밀번호를 설정해주셔야 합니다”라는 안내멘트를 보여주고 싶은데, 이를 위해 리다이렉트 시켰을 때 데이터를 넘기고 싶다.

첫 시도

Django docs에서 redirect섹션을 보고,

def my_view(request):
    ...
    return redirect('some-view-name', foo='bar')

이렇게 뒤에 foo=’bar’로 넘기면 되겠다 생각했다. (허접이 스키밍을 했을때의 폐해) (여기서 뒤에 붙는 인자는 kwargs이다.)
하지만 인자는 넘어가지 않았고, ‘django pass data with redirect’ 키워드로 구글링을 해보았다.
스택오버플로우링크의 답변을 보았는데,

redirect is merely a wrapper around HttpResponseRedirect that automatically calls reverse for you to create the URL to redirect to. As a result, the parameters you pass to it, aren’t arbitrary, they must be same you would pass to reverse and, specifically, only those required to create the URL.

Many people seem to have troubles understanding that data can’t just be arbitrarily passed to a view. HTTP is a stateless protocol: each request exists on it’s own, as if user had never been to any other page of the site. The concept of a session was created to provide a sense of “state” to a cohesive unit such as a site. With sessions, data is stored in some form of persistent storage and a “key” to look up that data is given to the client (typically the user’s browser). On the next page load, the client sends the key back to the server, and the server uses it to look up the data to give the appearance of state.

As a result, if you need data from one view available in another, you need to add it to the session, do your redirect, and look up the data in the session from the next view.

내가 찾던 답변같은데 이해하기 쉽지 않았다.
대략 HTTP의 stateless한 특성으로 reverse로는 데이터를 넘길 수 없어, 다른 방안을 찾으라는 것 같아 또 이것 저것 읽어본 결과,
1. Session을 사용하여 넘기기
2. GET으로 뒤에 Parameter달아 넘기기
3. django messages 사용하기

정도의 방법이 있는데, 지금 상황에서 어떤 방법을 써야 좀 더 세련될지 조언을 구하고자 P모님께 질문을 하였다. 결론은 역시 나의 HTTP에 대한 이해부족에서 나온 삽질이었다는 것이다.

HTTP의 Stateless성

P모님: HTTP는 왜 상태가 없죠?
나: (???)

한 초밥집이 있다. 이 초밥집은
1. 손님이 30분간 밥을 먹을동안 주방장이 계속 붙어서 손님에게 초밥을 내어줌
2. 테이크아웃
두가지 방식으로 운영될 수 있다.
1은 서로가 서로의 상태를 바로바로 알 수 있지만 한 손님이 먹는동안 주방장이 다른 손님을 응대하지 못한다는 단점이 있다.
2는 서로의 상태를 유지하고 있지 않지만, 한 주방장이 여러 손님을 순차적으로 응대할 수 있다는 장점이 있다.

보통 채팅이나 게임같은 실시간성 서비스가 1의 형태, 웹은 2의 형태를 취한다.

만약 2 방식에서 주방장이 손님의 단골메뉴를 기억해야 한다 해보자.
주방장이 손님별로 단골메뉴를 적어두거나, 손님이 각자 단골메뉴 쿠폰을 들고있다가 주방장에게 주어 기억하는 방법이 있을것이다. session을 사용하는것이 여기에 속한다. 이 방식의 특징은 서버의 디비와 사용자의 브라우저에 데이터를 저장한다는 것이다. 굳이 세션에 저장할 필요 없는 데이터를 저장하면 불필요하게 디비를 한 번 더 다녀오는 비용이 들게 된다. 그리고 django의 messages도 세션을 사용해서 구현한 것이니 위의 3가지 방법 중 1은 3을 포함하게 된다.
그리고 2번, GET으로 뒤에 파라미터를 달아 넘기는 것은, 아예 처음부터 다른 주소로 들어가는 것이다. 지금 상황처럼 이 url자체로 사람들이 많이 들어올 걱정을 안 해도 된다면 충분히 GET으로 데이터를 넘겨도 되겠다.

참고 – 쿠키와 세션 개념

render, redirect, reverse의 차이

그렇다면 왜 redirect에서 데이터를 인자로 넘기는게 안되는 것일까? 이는 HTTP의 response때문이다.
HTTP의 requestPOST혹은 GET등으로 데이터를 뭍혀서 보낸다.
그리고 온 response엔 200, 404 등 많은 종류가 있다. 여기서 200 ok같은 경우는 헤더영역에 200 등이 써있고, 그 밑에 html문서가 오던지, 혹은 요청한 데이터들이 좌라락 오던지 한다. 하지만 300번대인 redirect는, 밑에 redirect될 주소만 띡 하고 온다. 데이터를 뭍힐 곳이 없는것이다. (참고: HTTP 상태 코드)
(참고: 301은 permanent한 이동, 302는 임시 이동)

그리고 redirect, reverse의 관계는 아래와 같다.

  • redirect(‘welcome’) : 원래 redirect('/some/url/')처럼 url을 주소로 써주는데, url name을 쓰면 redirect 내부에서 자동으로 reverse를 호출하여 이름을 매칭해서 보내준다.
  • reverse(‘welcome’): url name welcome을 찾아서 보내준다.
  • redirect(reverse(‘welcome’)): 즉 이렇게 안쓰고 그냥 redirect만 써도 된다.

render는 템플릿과 컨텍스트를 합쳐서 HttpResponse 오브젝트를 리턴한다. 여기서 합친다는 말은, 예를 들어 컨텍스트에서 {"foo": "bar"}를 넘겼다고 하면, html template에서 {{ foo }}를 찾아서 bar로 치환해 섞어서 HttpResponse로 리턴해준다는 말이다.

그래서 HttpResponseRedirect를 리턴하는 redirect()는 합칠 컨텍스트를 못 받는 것이다.
참고: [Django] HttpResponse VS HttpResponseRedirect

결론

HTTP 부들부들

[Django] HttpResponse VS HttpResponseRedirect

의문

redirect
reverse
redirect(reverse(‘password-set’))
return HttpResponseRedirect(reverse(‘news-year-archive’, args=(year,)))
render
render_to_response
차이가 뭐지

redirect(‘welcome’)
reverse(‘welcome’)
redirect(reverse(‘welcome’)) 차이?

HttpResponseRedirect랑 HttpResponse 차이는 뭐지

Django shortcut functions

render()

render(request, template_name, context=None, context_instance=_context_instance_undefined, content_type=None, status=None, current_app=_current_app_undefined, dirs=_dirs_undefined, using=None)

템플릿과 컨텍스트를 합쳐서 HttpResponse 오브젝트를 리턴함.

from django.shortcuts import render

def my_view1(request):
    # View code here...
    return render(request, 'myapp/index.html', {&quot;foo&quot;: &quot;bar&quot;},
        content_type=&quot;application/xhtml+xml&quot;)


def my_view2(request):
    # View code here...
    t = loader.get_template('myapp/index.html')
    c = {'foo': 'bar'}
    return HttpResponse(t.render(c, request),
        content_type=&quot;application/xhtml+xml&quot;)

(my_view1과 my_view2는 동일한 코드)

render_to_response()

render_to_response(template_name, context=None, context_instance=_context_instance_undefined, content_type=None, status=None, dirs=_dirs_undefined, using=None)

별로 권장하지 않음. render()이랑 비슷한데 response에서 request가 불가능한 차이가 있다.

return render_to_response('my_template.html',
                          my_context,
                          context_instance=RequestContext(request))

redirect()

redirect(to, permanent=False, *args, **kwargs)

패스된 인자를 가지고 HttpResponseRedirect를 리턴한다

def my_view(request):
    ...
    # return redirect('/some/url/') # 요렇게 하드코드 URL로 써도 됨.
    return redirect('some-view-name', foo='bar')

get_object_or_404()

get_object_or_404(klass, *args, **kwargs)

준 모델을 get()해오지만, 모델이 DoesNotExist익셉션 되면 Http404를 raise한다.

from django.shortcuts import get_object_or_404

def my_view(request):
    my_object = get_object_or_404(MyModel, pk=1)

django.core.urlresolvers utility functions

reverse()

reverse(viewname, urlconf=None, args=None, kwargs=None, current_app=None)

urls.py에서 만든 url name 사용할 수 있음

reverse('news-archive')

def myview(request):
    # url이 argument를 받는 경우
    return HttpResponseRedirect(reverse('arch-summary', args=[1945])) 


# kwargs를 쓰는 방법. '/admin/auth/'로 간다.
reverse('admin:app_list', kwargs={'app_label': 'auth'})

HttpRespose VS HttpResponseRedirect

링크

HttpResponse

response = HttpResponse("Here's the text of the Web page.")
– HTTP코드가 200(ok)이고 생성자로 전달된 컨텐츠를 포함한 HttpResponse오브젝트를 만든다.
– 보통 작은 response에서만 쓴다. (ajax로 받은 데이터나, 작은 number 등)

HttpResponseRedirect

HttpResponseRedirect("http://example.com/")
– HTTP코드가 302(Found/Moved temporarily)인 HttpResponse오브젝트를 만든다.
– 다른 페이지로 redirect할때만 써야한다(e.g. 폼 POST전송 성공 이후)

결론

여전히 헷갈린다.

refer

http://makerj.tistory.com/220

[Django] User 모델 email을 기본으로 하기& 썸네일 추가하기

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

처음에 이유를 모르게 계속 안 되어서 스트레스받았던 부분이다.
하고 나니까 되게 속시원하네!!