[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

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

django, bower, grunt로 사이트 구조잡기

상황

재고/창고관리 웹페이지를 만들려고 한다.
전체적으로 djangodocker로 돌아가고, 화면은 6개 정도 되는 크지 않은 admin페이지다.
나는 프론트엔드 담당이다.

생각하기

앞단을 짜는 것엔 두 가지 방법이 있다.
1. django템플릿을 써서 서버사이드 렌더링을 한다.
2. api를 받아와서 ajax로 만든다.

관리자 페이지니 봇이 긁어가야 할 필요도 없고, 이미 api들이 만들어지고 있는 상태라 2번을 사용하기로 했다. 기간이 2주뿐이라 조금이나마 익숙한 ang`ular를 쓰기로 했다(리액트 써보고 싶다 힝).

구조 잡기

현재 디렉토리 구조는

- api
    + migrations
    + serializers
    + test
    + views
    + (등등 django REST Framework기반의 api서버이다.)
- conf
    + development
    + production
    + testing
- etc
    + (docker들어가있음)
- stock
    + (django 메인 앱)
    + __init__.py
    + settings.py
    + urls.py
    + wsgi.py
- web
    + (여기다 웹 프론트를 짜면 된다)
- manage.py 등 장고 관련 파일들

로 되어있다. 나는 web에다 프론트를 짜면 된다.
angularjsone page web을 만들 예정이라 루트(/)로 접속했을 때 web/base.html로 연결되도록 stock/urls.py에 명시해주어야 했다.

static file 사용하기

urls.py

from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    url(r'^$',
        TemplateView.as_view(template_name='base.html'),
        name='main'),
    url(r'^api/', include('api.urls')),
    url(r'^admin/', admin.site.urls),
    url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework'))
]+ static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

base.html을 템플릿뷰로 연결해주었다. 하지만 그냥 url만 적어주면 web폴더 내의 base.html은 템플릿파일로 인식을 못하기에 뒤에 static을 연결해준다.

settings.py

TEMPLATES = [
    {
        ...
        'DIRS': [
            'web'
        ],
        ...
    },
]

...

STATICFILES_DIRS = [
    ("web", os.path.join(BASE_DIR, "web")),
]

settings.py TEMPLATES내부의 DIRS에 폴더명을 적어준다.
그리고 밑의 STATICFILES_DIRSweb이란 이름으로 BASE_DIRweb폴더를 연결해둔 패스를 지정해준다. 그 후로부턴 저 BASE_DIR/web폴더 내부 staticfile을 사용할 때 앞에 ‘web’이란 이름을 적어주면 된다.

web/base.html

{% load staticfiles %}

base.html위에 staticfiles를 로드해주고

<link rel="stylesheet" href="{% static 'web/dist/css/base.css' %}">

불러올 땐, 폴더를 아까 정해준 이름(web)으로 호출하면 된다.

의존성 관리 – Bower

의존성 관리 툴은 bower와 npm을 사용하였다.
최상위 폴더에 bower 설정파일을 만들어준다.

bower.json

{
"name": "sandi",
"dependencies": {
"admin-lte": "latest",
"fastclick": "latest"
}
}

이름과 dependencies만 적어주었다.
admin-lte라는 admin사이트 만드는 곳에 특화된 부트스트랩 템플릿과, 모바일에서 touch evnet를 도와주는 fastclick을 설치하였다.
콘솔에서 bower install하면 이들이 bower_componenets폴더 내에 설치된다.
이도 staticfile로 접근해야하니, settings.py에 한번 더 명시해준다.

settings.py

STATICFILES_DIRS = [
    ("components", os.path.join(BASE_DIR, "bower_components")),
    ("web", os.path.join(BASE_DIR, "web")),
]

components란 이름으로 연결해주었다.

<link rel="stylesheet" href="{% static 'components/admin-lte/bootstrap/css/bootstrap.min.css' %}">

이는 아까와 같이 사용할 수 있다(web대신 componenets라고 명시)

프론트엔드 태스크 자동화 – grunt

태스크 자동화는 grunt로 하였다. 저번 프로젝트는 gulp로 했었는데, admin-lte가 준 grunt파일이 좋아보여 얘로 결정!

web/package.json

{
"name": "sandi",
"version": "0.1.0",
"repository": {
"type": "git",
"url": `https://github.com/haha`
},
"devDependencies": {
"R2": "^1.4.3",
"grunt": "~0.4.5",
"grunt-contrib-clean": "^0.6.0",
"grunt-contrib-csslint": "^0.5.0",
"grunt-contrib-cssmin": "^0.12.2",
"grunt-contrib-jshint": "^0.11.2",
"grunt-contrib-less": "^0.12.0",
"grunt-contrib-uglify": "^0.7.0",
"grunt-contrib-watch": "~0.6.1",
"grunt-cssjanus": "^0.2.4",
"grunt-image": "^1.0.5",
}
}

npm 설정파일을 만들어 필요한 grunt파일들을 넣어준다.

web/Gruntfile.js

module.exports = function (grunt) {

  'use strict';

  grunt.initConfig({
    watch: {
      files: ["static/less/*.less", "build/less/skins/*.less", "static/js/app.js"],
      tasks: ["less", "uglify"]
    },
    /* LESS Compile */
    less: {
      development: {
        options: {
          compress: false
        },
        files: {
          "dist/css/base.css": "static/less/base.less",
        }
      },
      production: {
        options: {
          compress: true
        },
        files: {
          "dist/css/base.css": "static/less/base.less",
        }
      }
    },
    /* Javascript Uglify */
    uglify: {
      options: {
        mangle: true,
        preserveComments: 'some'
      },
      my_target: {
        files: {
          'dist/js/app.js': ['static/js/app.js']
        }
      }
    },
    /* Image Compression */
    image: {
      dynamic: {
        files: [{
          expand: true,
          cwd: 'static/img/',
          src: ['**/*.{png,jpg,gif,svg,jpeg}'],
          dest: 'dist/img/'
        }]
      }
    },

    // Validate JS code
    jshint: {
      options: {
        jshintrc: '.jshintrc'
      },
      core: {
        src: 'static/js/app.js'
      }
    },

    csslint: {
      options: {
        csslintrc: 'static/less/.csslintrc'
      },
      dist: [
        'dist/css/base.css',
      ]
    },

    /* Compression 전 이미지 삭제 */
    clean: {
      build: ["static/img/*"]
    }
  });

  grunt.loadNpmTasks('grunt-contrib-less');
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-contrib-uglify');
  grunt.loadNpmTasks('grunt-image');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-contrib-clean');
  grunt.loadNpmTasks('grunt-contrib-csslint');

  grunt.registerTask('default', ['watch']);
};

web/static폴더에 less, js, img를 넣고,
grunt를 돌리면 web/dist폴더 minify되고 컴파일되고 compression된 css, js, img가 들어가도록 해두었다.(+ validation)

gitignore

dist

### Frontend ###
node_modules
bower_components

distribution에서 사용하는 dist폴더와,
npm으로 설치한 node_modulesbower로 설치한 bower_components는 git에서 제외시켜뒀다.
이로서 깨끗한 깃헙이 되었다!!! >.<

돌리자!

다른 개발자들을 위해 README.md에 써준다.

bower install
cd web
npm install
grunt

Django Girls 튜토리얼 – django로 블로그 만들기

http://tutorial.djangogirls.org/ko/index.html 이 튜토리얼 따라하며 정리

웹사이트 동작 방식

  • 편지 보내고 받는거랑 비슷
    • 편지(데이터패킷) 보내면 각기 다른 우체국(라우터)를 통해 전달되어 주소지에 최종도착
    • 주소: IP주소
    • 컴퓨터가 DNS(도메인 주소 시스템)가서 djangogirls.org의 IP주소 뭔지 물어본다.
      • 전화번호부에서 이름 찾고 전화번호 알아내는거랑 비슷
    • 규칙을 지켜야 제대로 배달됨
      • 데이터패킷도 규칙 잘 지켜야 웹사이트 볼 수 있다.
        • HTTP(하이퍼텍스트 전송 프로토콜)라는 프로토콜 사용.
      • 서버에서 요청(편지)를 받으면 다시 웹사이트(답장)으로 돌려줌
  • 그렇다면 장고는?
    • 답장 보낼 때 받는 사람에 따라 각각 다른 답장 보낼 수 있게 해줌.

장고란?

  • 웹사이트 만들때 비슷한 유형 요소들 항상 필요하다(회원가입, 로그인, 관리자 패널, 파일 업로드 등등)
  • 웹서버: Request가 도착했는지 확인해주는 Port
    • 받은 request를 읽고 웹 페이지와 함께 답장을 준다.
    • 그 request 안의 내용을 만들 수 있는 역할을 한다. 장고가!
  • 누군가 서버에 웹사이트를 요청한다!
    • 웹서버에 요청이 오면 장고로 전달
    • 장고는 실제로 어떤 요청이 들어왔는지 확인
      • urlresolver: 웹페이지의 주소를 가져와 무엇을 할지 확인
        • 우편배달부
        • 위에서 아래로 그 패턴 확인해봐서 일치하면 그 요청을 관련 함수(view)에 넘겨줌.
    • view함수
      • 재밌는 일들 처리
      • 특정 정보를 DB에서 찾을 수 있다.
      • 사용자가 수정 요청 “데이터를 바꿔주세요”
        • view함수: 수정할 수 있는 권한 있는지 확인
        • 수정하고 “완료했다!”라고 답장 준다.
        • 장고가 그 답장을 사용자의 웹브라우저에 보내준다.

가상 환경

원하는 디렉토리에서 아래 명령을 치면 myvenv라는 디렉토리 만들어짐.

python3 -m venv myvenv

그리고 그 디렉토리에 우리가 사용할 가상환경이 들어있음.
이렇게 가상환경 실행하면 된다.

source myvenv/bin/activate

장고 설치하기

pip install django==1.8

장고 프로젝트

  • 장고에선 디렉토리나 파일 이름 매우 중요. 이름 변경하거나 옮기면 안됨.
  • 중요한 것들을 찾을 수 있게 특정한 구조를 유지해야 함.

앞에 가상환경이 실행되어있어야 하고, 마지막에 .을 찍는걸 잊지 말자. (.: 현재 디렉토리에 장고를 설치하라고 스크립트에 알려줌)

(myvenv) ~/djangogirls$ django-admin startproject mysite .

django-admin.py는 스크립트로, 디렉토리와 파일을 생성. 스크립트 실행하면 이렇게 새로 만들어진 디렉토리 구조 나온다.

djangogirls
├───manage.py
└───mysite
        settings.py
        urls.py
        wsgi.py
        __init__.py
  • manage.py: 스크립트. 사이트 관리를 도와줌. 이 스크립트로 다른 설치 없이 컴에서 웹서버 시작 가능.
  • settings.py: 웹사이트 설정이 있는 파일
  • urls.py: 앞에서 설명한 urlresolver가 사용하는 패턴 목록을 포함.

설정 변경

  • settings.py에서
    • TIME_ZONE = 'Asia/Seoul'을 고쳐본다!
    • 맨 밑에 STATIC_ROOT = os.path.join(BASE_DIR, 'static')를 추가해본다!
      • 정적파일 경로

데이터베이스 설정하기

많은 DB소프트웨어 중 sqlite3를 이용. settings.py에 설치되어있음. 장고 기본.

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}

콘솔에서 코드 실행해 블로그에 데이터베이스를 생성한다.

python manage.py migrate

웹서버를 돌려보자

python manage.py runserver

Django 모델

  • 장고의 모델: 객체의 특별한 종류.
    • 이 모델을 저장하면 그 내용이 데이터베이스에 저장된다!

어플리케이션 제작

프로젝트 내부에 별도의 어플리케이션 만들자.

python manage.py startapp blog

그럼 blog디렉토리가 생성된다.

djangogirls
├── mysite
|       __init__.py
|       settings.py
|       urls.py
|       wsgi.py
├── manage.py
└── blog
    ├── migrations
    |       __init__.py
    ├── __init__.py
    ├── admin.py
    ├── models.py
    ├── tests.py
    └── views.py
  • mysite/settings.py
    • 어플리케이션 생성하면 장고에게 이거 사용하라고 알려주는 역할.
    • INSTALLED_APPS밑에 blog추가해준다.

블로그 글 모델 만들기

blog/models.py파일에 선언해 모델 만든다.

#다른 파일에 있는 것을 추가
from django.db import models
from django.utils import timezone

#모델을 정의
class Post(models.Model): #models.Model: Post(클래스첫자는 대문자)가 장고 모델(=객체)임을 나타낸다. 이 코드 덕에 장고는 Post가 DB에 저장된다 알게 됨.
    author = models.ForeignKey('auth.User') #다른 모델에 대한 링크
    title = models.CharField(max_length=200) #글자수가 제한된 텍스트
    text = models.TextField() #글자수에 제한 없는 텍스트
    created_date = models.DateTimeField(
            default=timezone.now) #날짜와 시간
    published_date = models.DateTimeField(
            blank=True, null=True)

    def publish(self): #메서드. 이름은 소문자로 시작.
        self.published_date = timezone.now()
        self.save()

    def __str__(self): #얘를 호출하면 Post모델의 title을 얻음
        return self.title

DB에 모델을 위한 테이블 만들기

장고 모델에 우리가 몇가지 변화 줌을 알려준다.

python manage.py makemigrations blog

실제 DB에 모델 추가를 반영

python manage.py migrate blog

Django 관리자

from django.contrib import admin
from .models import Post #앞에서 만든 Post모델을 가져온다.

admin.site.register(Post) #만든 모델을 관리자 페이지에서 볼려면!

서버를 실행하고, http://127.0.0.1:8000/admin/로 들어가면 administration페이지가 나온다. 여기서 superuser를 만들려면 python manage.py createsuperuser로 아이디를 생성해준다.

배포하기

.gitignore란 파일 만들고 아래 추가

*.pyc
__pycache__
myvenv
db.sqlite3
.DS_Store

pythonanywhere에 배포하기: bash에 들어가서 아래 코드를 입력한다.

git clone https://github.com/milooy/djangogirls-blog-tutorial

tree djangogirls-blog-tutorial라 치면 구조를 확인할 수 있다.

PythonAnyware에서 가상환경 생성

bash콘솔에 이렇게 입력

cd djangogirls-blog-tutorial
virtualenv --python=python3.4 myvenv
source myvenv/bin/activate
pip install django whitenoise

정적 파일 모으기

  • whitenose: 정적 파일로 불리는 것들을 제공하는 프로그램.
    • 정적 파일: HTML, CSS와 같이 정기적인 수정이 없거나, 프로그래밍 코드를 실행하지 않는 파일.
    • 서버에서 정적 파일은 컴퓨터와 다르게 작동하기 때문에 정적 파일들을 제공하기 위해서 백색소음과 같은 프로그램 필요
  • 서버 bash 콘솔에 collectstatic실행.
    • 장고가 서버에 있는 모든 정적 파일들을 모으는 것을 지시.
    • 관리자 페이지를 예쁘게 만들어줌
python manage.py collectstatic

PythonAnywhere에서 DB생성

  • 컴퓨터와 서버는 다른 DB를 사용한다!
    • 그래서 사용자 계정과 글은 서버와 내 컴이랑 다를 수 있다.
python manage.py migrate
python manage.py createsuperuser

web app으로 블로그 배포

pythonAnyWhere > Web > add a new web app

URL이란?

  • URLconf(URL Configuration): 장고에서 URL과 일치하는 뷰를 찾기 위한 패턴들의 집합

mysite > urls.py

from django.conf.urls import include, url
    from django.contrib import admin

    urlpatterns = [
        # Examples:
        # url(r'^$', 'mysite.views.home', name='home'),
        # url(r'^blog/', include('blog.urls')),

        url(r'^admin/', include(admin.site.urls)),
    ]

regex를 사용해 찾아낸다.

^ 문자열이 시작할 떄
$ 문자열이 끝날 때
\d 숫자
+ 바로 앞에 나오는 항목이 계속 나올 때
() 패턴의 부분을 저장할 때
  • 밑에 url(r'', include('blog.urls')),를 추가해 모든 접속 요청을 blog.urls로 전송하고 추가 명령을 찾는다.
  • 파이썬에서 정규 표현식 작성할땐 앞에 r붙인다.

blog.urls

blog/urls.py를 생성하고

from django.conf.urls import url
from . import views

urlpatterns = [
    url(r'^$', views.post_list, name='post_list'),
]
  • 장고의 메소드와 blog어플리케이션에서 사용할 모든 views들을 불러온다.
  • 그리고 post_list란 이름의 view가 ^로 시작해서 $로 끝나는, 즉 문자열이 없는, 즉 http://127.0.0.1:8000/로 들어왔을 때 views.post_list를 보여주라고 한다.
  • 이름짓는 이유: 뷰마다 고유한 이름을 붙여, 식별하기 쉽게 하고 많이 쓴다.

Django 뷰 만들기

뷰: 모델에서 필요한 정보를 받아와서 템플릿에 전달
blog/views.py

def post_list(request):
    return render(request, 'blog/post_list.html', {})

post_list메서드는 요청(request)을 넘겨받아 render메서드를 호출.
render메서드는 요청과 blog/post_list.html템플릿을 받아 리턴된 내용이 브라우저에 보여짐

템플릿

blog/tamplates/blog 디렉토리 밑에 post_list.html만들기
그리고 pythonanywhere다시 가서 git pull python manage.py collectstatic하면 배포된다.

Django ORM

  • 쿼리셋: 전달받은 모델의 객체 목록. DB로부터 데이터를 읽고, 필터를 걸거나 정렬함
python manage.py shell

from blog.models import Post # Post모델을 불러온다
Post.objects.all() # 하면 장고 관리자로 만든 포스트들이 출력된다.

from django.contrib.auth.models import User # User모델을 불러온다.
User.objects.all() # 하면 슈퍼유저로 만들었던 사용자가 나온다
me = User.objects.get(username='jayjin') # 'jayjin'User 인스턴스 받아오기
Post.objects.create(author=me, title='Sample title', text="Test") # 게시물 만들기
Post.objects.all() # 잘 들어갔는지 확인한다

Post.objects.filter(author=me) # author가 me인 게시물들
Post.objects.filter(title__contains='title') # 제목에 title이란 글자가 들어간 글들. 필드이름/연산자/필터를 밑줄 2개 사용해 구분.

from django.utils import timezone
Post.objects.filter(published_date__lte=timezone.now()) # 출판날짜가 과거인 글들
post = Post.objects.get(title="Sample title") # 아까 만든 게시물 인스턴스 얻고
post.publish() # 출판한다

Post.objects.order_by('created_date') # created_date필드로 정렬
Post.objects.order_by('-created_date') # 내림차순

Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date') # 체이닝도 가능

exit() #종료

템플릿의 동적 데이터

  • views: 모델과 템플릿을 연결한다
from django.shortcuts import render
from django.utils import timezone
from .models import Post # .은 현재 디렉토리/어플리케이션을 의미. Post를 불러온다.

def post_list(request):
    posts = Post.objects.filter(published_date__lte=timezone.now()).order_by('published_date')
    return render(request, 'blog/post_list.html', {'posts':posts})
  • Django template tags: 파이썬을 HTML로 바꿔줌
  • blog/templates/blog/post_list.html에서 {{posts}}하면 객체들 목록 나온다.
{% for post in posts %}
    <div>
        published: {{ post.published_date }}
        <h1><a href="">{{ post.title }}</a></h1>
        {{ post.text|linebreaks }}
    </div>
{% endfor %}

하면 for loop 돈다.

CSS

  • blog/static/css/blog.css를 만든다.
  • blog/templates/blog/post_list.html에서 가장 위에 {% load staticfiles %}를 추가한다. 그럼 정적 파일이 실행됨!
  • 사이에 “를 추가한다.
  • 그럼 css가 먹는다! (안되면 서버 껏다 켜라)

템플릿 확장

  • blog/templates/blog/base.html만들기
{% load staticfiles %}
<html>
<head>
    <title>Django Girls blog</title>
    ...
    <link rel="stylesheet" href="{% static 'css/blog.css' %}">
</head>
<body>
    ...
    {% block content %}
    {% endblock %}
    ...
</body>
</html>
  • base.html을 확장한 이 블럭에 HTML을 추가할 수 있게 해준다.
{% extends 'blog/base.html' %}

{% block content %}
    {% for post in posts %}
        <div class="post">
            <div class="date">
                {{ post.published_date }}
            </div>
            <h1><a href="">{{ post.title }}</a></h1>
            <p>{{ post.text|linebreaks }}</p>
        </div>
    {% endfor %}
{% endblock content %}

blog/base.html를 확장한거고, content에 그걸 껴넣는다.

프로그램 어플리케이션 확장하기

post_list.html

<h1><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h1>

를 추가한다. post목록에 있는 제목에서 post내용 페이지로 가는 링크.
post_detail뷰의 경로: blog.views.post_detail. 여기서 views는 views.py이다.

urls.py

url(r'^post/(?P<pk>[0-9]+)/$', views.post_detail, name='post_detail'),

을 추가한다. post/로 시작하고,
(?P[0-9]+): 장고가 우리가 여기 넣은 모든것을 pk변수에 넣어 뷰로 전송하겠다는 뜻.
http://127.0.0.1:8000/post/1234567890/이 완벽히 매칭되겠지.
http://127.0.0.1:8000/post/5/를 입력하면, post_detail인 view를 찾고있다고 생각하고 pk가 5인 view로 정보를 보낸다.
pk: 프라이머리 키 라는 뜻으로 장고 프로젝트에서 자주 사용하는 변수이름.

404 추가

blog/views.pyfrom django.shortcuts import render, get_object_or_404추가한다.
밑에는

def post_detail(request, pk): # request, 그리고 urls에 지정한것과 동일한 이름인 pk를 넣어준다.
    post = get_object_or_404(Post, pk=pk) # pk가 없는 포스트라면 404페이지로 넘겨준다.
    return render(request, 'blog/post_detail.html', {'post': post})

을 추가한다.

post상세페이지 템플릿 만들기

blog/templates/blog/post_detail.html생성하고

{% extends 'blog/base.html' %}

{% block content %}
    <div class="post">
        {% if post.published_date %} <!-- published date 있다면 보여주기 -->
            <div class="date">
                {{ post.published_date }}
            </div>
        {% endif %}
        <h1>{{ post.title }}</h1>
        <p>{{ post.text|linebreaks }}</p>
    </div>
{% endblock %}

Django 폼

from django import forms # forms 모델을 import
from .models import Post # Post모델 임포트

class PostForm(forms.ModelForm): # 우리가 만들 폼의 이름, 그리고 이 폼은 `ModelForm`이란걸 알려주면 장고가 이것저것 해준다.
    class Meta:
        model = Post # 이 폼을 만들기 위해 어떤 모델이 쓰여야 하는지
        fields = ('title', 'text') # 필드를 넣는다.

base.html에 이걸 추가한다.

<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>

blog/urls.py에 이걸 추가한다.

url(r'^post/new/$', views.post_new, name='post_new'),

blog/views.py에 추가

from .forms import PostForm
...
def post_new(request):
    form = PostForm()
    return render(request, 'blog/post_edit.html', {'form': form});

templates/blog/post_edit.html을 추가하고 아래를 적어준다.

{% extends 'blog/base.html' %}

{% block content %}
    <h1>New post</h1>
    <form method="POST" class="post-form">{% csrf_token %} <!-- 폼 보안을 위함 -->
        {{ form.as_p }} <!-- 폼을 보여준다 -->
        <button type="submit" class="save btn btn-default">Save</button>
    </form>
{% endblock %}

blog/views.py에서 지금은 폼 제출시에도 동일한 뷰가 나온다. 조건문으로 분기

def post_new(request):
    if request.method == "POST": # 폼에 입력된 데이터를 view페이지로 가지고 올 때. request는.POST 우리가 입력했던 데이터들 갖고있음.
        form = PostForm(request.POST)
        if form.is_valid(): # 폼에 들어있는 값들이 올바른지 확인. 잘못된 값이 있다면 저장 ㄴㄴ
            post = form.save(commit=False) # 폼을 저장하는 작업.commit=False라고 해서 넘겨진 데이터를 바로 POST모델에 저장하지 말라. 작성자 추가해야하니까.
            post.author = request.user # 작성자를 추가.
            post.published_date = timezone.now()
            post.save() # 작성자까지 추가되고 저장.
            return redirect('blog.views.post_detail', pk=post.pk)
    else: # 처음 페이지에 접속했을때. 폼이 비어있어야 한다.
        form = PostForm()


    return render(request, 'blog/post_edit.html', {'form': form});

블로그 글 작성하고 post_detail로 자동으로 가기 위해

from django.shortcuts import redirect # 리다이렉트를 임포트해오고
...
return redirect('blog.views.post_detail', pk=post.pk) # pk첨부해서 post_detail로 리다이렉트

폼 수정하기

post_detail.html에 추가한다.

<a class="btn btn-default" href="{% url 'post_edit' pk=post.pk %}"><span class="glyphicon glyphicon-pencil"></span></a>

urls.py에도 추가한다

 url(r'^post/(?P<pk>[0-9]+)/edit/$', views.post_edit, name='post_edit'),

views.py에도 추가한다.

def post_edit(request, pk): #url에서 pk를 받아서 처리
    post = get_object_or_404(Post, pk=pk) #수정하고자 하는 글의 Post모델 인스턴스를 가져온다. 원하는 글은 pk를 이용해 찾는다.
    if request.method == "POST":
        form = PostForm(request.POST, instance=post)

        if form.is_valid():
            post = form.save(commit=False)
            post.author = request.user
            post.published_date = timezone.now()
            post.save()
            return redirect('blog.views.post_detail', pk=post.pk)
    else:
        form = PostForm(instance=post)
    return render(request, 'blog/post_edit.html', {'form': form});

보안

base.html에서 관리자로 로그인한 유저들만 링크 보이게 함.

<a href="{% url 'post_new' %}" class="top-menu"><span class="glyphicon glyphicon-plus"></span></a>

Refer

간단한 블로그를 Django 이해하기
Django, 저는 이렇게 씁니다
쉽게 씌여진 장고
Django로 웹사이트 만들고 런칭하기
django girls tutorial

2015 PYCON KOREA 컨퍼런스 노트

http://www.pycon.kr/2015/

강의 들으면서 인상깊은 것들 적기

Sphinx autodoc (타카유키 시미즈카와)

  • docstring
    • API DOCS
  • sphinx
    • documentation generator
    • python version is ipt
    • import your code docstrings
    • translation into other languages

Character Encoding in Python (강대성)

  • Encoding
    • 전달하고자 하는 내용을 부호화
  • Character Encoding
    • 저장/통신 하기 위해선 2진수
  • Unicode
    • 전세계 문자/기호를 codepoint에 매칭
    • 한글, 타이문자 등등
    • 많이 쓰이는 이모티콘 등도 정의(똥같은거)
    • Unicode != UTF-8
  • UTF-8
    • 모든 Unicode codepoint를 다룰 수 있다.
    • Unicode를 Encoding했을 때 NULL 포함 X
    • ASCII Text는 UTF-8 될 수 있음.
    • 일부 바이트 유실되어도 다음시작 byte알 수 있다(복구가능)
    • Web Encoding중 84%가 사용!
  • Unicode Sandwich
  • python2
    • default: ASCII
  • python3
    • default: UTF8
  • Python은 파일의 인코딩을 알지 못함
  • 일본어 디코딩
    • 주고 받은 인코딩을 정확히 파악!!
  • 인코딩 해결법
    • encode, decode이렇게 저렇게 하다 잘 되면 써요 ==> 망하는 지름길
  • 인코딩 파악 위한 순서
    • 문서 또는 서로의 약속 확인
    • 전송받은 데이터 열어서 확인
    • 테스트(반드시!!)
  • 인코딩 파악에 도움되는 것
    • chardet 2.3.0
    • n퍼센트 확률로 이 인코딩이다 하고 알려줌
  • 테스트
    • 전체 프로그램 돌리면 오래 걸릴 수 있으니 부분을 떼어서 검사
  • 파일 IO를 위한 팁
    • 파일을 열 때 codecs쓰면 간단해짐
    • python3에선 내장함수에서 가능
  • 결론 TIP
      1. Unicode Sandwich
      • python에선 항상 Unicode
      1. 인코딩 파악하기
      • 문서보고, 확인하고 테스트.
      • 주어야하는 인코딩도 명확히

한국어 & NLTK & Gensim (박은정)

단어/문서를 컴퓨터가 이해할 수 있게 표현하는 방법

  • 어떻게 하면 구조가 없는 데이터인 텍스트의 의미를 컴퓨터가 잘 이해할 수 있을까?
  • 단어를 표현하는 가장 쉬운 방법: 이진 표현법
    • 어떤 단어를 벡터화 시킬 수 있다.
    • 근데 이진 표현법 사용 => 단어간 유사도 정의 불가
      • 호텔&모텔 / 호텔&고양이 얼마나 비슷한지 전혀 모름
  • BOW(bag of words)
    • 단어가 문서에 존재한다/안한다(term existance)
    • 단어가 문서에 n번 존재한다(term frequency)
    • 근데 공간의 차원이 너무 커서 문서간 유사도 지표의 효과 떨어짐
  • 워드넷 같은 텍소노미
    • 모든 용어를 포함하려 하지만, 전문 도메인 용어들은 많이 빠짐
  • 단어의 주변을 보면 그 단어를 안다
    • == 단어의 의미는 해당 단어의 문맥(context)이 담고 있다.
    • co-occurence: 두 단어가 정해진 구간 내에서 동시에 등장
        1. Term-document matrix : 한 문서에 같이 등장하면 비슷한 단어
        1. Term-term matrix : 단어가 문맥 내에 같이 등장하면 비슷한 단어
  • word2vec
    • 단어에 대한
  • doc2vec
    • 문서/문단 벡터를 마치 단어인 양 학습시키자!
    • 차원 축소
  • 한국어 영화 리뷰 토이데이터

Python and Test (배권한)

  • 테스트 이점
    • 불안감 해소
    • 테스트 통한 점진적 개선 가능
    • 손으로 하는 수고 덜어준다.
  • 어떻게 해야하나
    • TDD추천
    • 느리지만(직접 해보니 5배쯤 느림…ㅠㅠ) 많이 배우고 좋은 프로그램을 짤 수 있다
    • 하지만 만능은 아님.
      • 리팩토링을 수반
    • 하지만 TDD로 개발을 진행할 힘을 얻고, 리팩토링으로 구조 개선
      • 좋은 구조는 또 다른 분야라 생각 ㅎㅎ
  • 언제 리팩토링 해야하나
    • Three strikes and you refactor
    • 세 번 이상 같은 것을 하면 안댕!!!
    • egoless programming을 해야 한다.
      • 나의 코드는 의미가 없으며(!=실력없음) 목적을 위하여 언제든 지울 수 있음(==나는 계속 성장할 수 있다).
  • 다시, 어떻게 해야하나
    • pyramid
      • unit test해야한다
      • 가급적 테스트는 30초 이내에 끝나야 함(그래야 피드백을 빨리 받고 다른 일을 할 수 있음)
      • 그 여러개의 unit test를 합쳐 integration test
    • Functional test
      • 위에서 아래로
  • Obey the test goat 라는 사이트
    • 클린코드를 위한 테스트 주도 개발 <<책 봐라
    • 가급적이면 unit test를 많이 짜는 습관
  • 테스트 비용
    • 테스트도 결국 알고리즘 처럼 비용의 세계
    • 인간 확인 비용 < 테스트 짜는 비용
      • 이면 굿. 반대면 인간이 확인해도 됨.
    • 테스트 커버리지가 100%일 필요는 없음(70%만 넘으면 될듯.)
  • 테스트 하기 쉬운 코드 짜려면?
    • 테스트를 먼저 짠다.
    • 기능을 많이 나눔
    • 코드리뷰를 거친다
    • 의도를 잘 나타내야함
    • 가독성
  • CI를 통해서 협업
    • 깔끔한 환경에서 할 수 있다.
    • 테스트를 돌림
    • 형식을 검사
    • import order등을 검사
  • more…
    • py.test를 쓰세요. 이게 좋앙.
    • 다른 분들의 코드를 많이 보세요(e.g. 장고쪽에 좋은 테스트 많다)
    • 더 고민해서 TC를 짜자

탐색적으로 큰 데이터 분석하기 (장혜식)

  • EHR(Electronic Health Records)
    • 병원 데이터 – 엄청나게 많은 데이터 쌓임
  • 탐색적 데이터 분석
    • 재미있는 것을 찾아야 함
      • 코드 추가/수정이 매우 간편
    • 언제 어떤 데이터가 추가될 지 모른다.
    • 코드는 빨리 만들어서 (거의) 한 번만 쓴다.
    • 그렇다고 재사용이 아예 없는 것도 아니다.
      • 프레임워크가 유연하고 성숙해야 함
    • 데이터가 작지는 않다.
  • Jupiter Notebook
    • 인생이 주피터를 쓰기 전과 후로 나뉜다(ㅎㅎ)
      • 판다스도! 두개 꼭 써봐요
  • Snakemake
    • make의 좋은점 / 안좋은점
    • 파이썬 코드를 거의 그대로 쓸 수 있다.
    • 의존성이 없는 작업은 병렬로 실행됨
    • 이미 있는 새 파일은 무시하고 지나감
    • File-driven Programming
      • 보일러판이 필요 없는 프로그램 내장형 병렬화 이벤트 루프
  • 텍스트 파일
    • tsv(탭으로 구분 텍스트), csv(쉼표로 구분 텍스트)
    • 큰 텍스트 파일을 (엄청쉽게) 병렬처리 하려면?
      • 압축을 안 한다?
      • 파일을 쪼갠다?
      • tabix를 쓴다!
        • bgzf
          • 압축을 여러 개로 쪼개서 압축한다.
          • 앞뒤로 돌아다니면서 탐색 가능!!
          • 100% 하위호환
        • 한계점
          • 초기 인덱싱이 병렬화 X. (오래걸림)
          • 반드시 2레벨 인덱스로 정렬되어 있어야 함
          • 레벨 1 인덱스는 병렬 안됨(맞나?)
  • Julia
    • 파이썬과 친한 언어 ㅎㅎ

약속 (하재승)

  • ㅎㅎㅎ좋으당!
  • 프로그래밍은 다 거짓말

도도와 파이썬 (김재석)

  • 스포카가 도도포인트를 만들며 있었던 기술적 의사결정 공유
  • Do things that don't scale
    • 다만 첫번째 고객의 취향에 완전히 맞춤!!
  • 백오피스를 가볍게 두고 고객의 니즈에 더 노력을 쏟음
    • 더 팔릴만한 것 만들기!
    • 이유: 스포카에서의 경험
    • 장점: 장기적으로 효율 좋음. 단, 성공했을 때!!
    • 분석은 엑셀로 <AWS 도쿄로 옮김
  • 빠르고, 점진적으로
  • 나쁜 선택
    • 매장이 1000개 가까이 늘어나니 서비스에 대한 요구사항 늘어남
    • 기존 서비스가 커져서 추가가 고통스러웠음(넣으면 서버 터짐;;)
    • 종종 ‘그 때 제대로 해놨면 지금 이고생 안하는데 ㅠㅠ’후회
    • 하지만 그건 이미 그 코드를 계속 만질 수 있는 미래 시점에서의 평가일 뿐
  • 파이썬은 스타트업에게 좋은가?
    • Duct tape
    • 스타텁은 단지 ‘작은’회사가 아님.
    • 스타텁의 가장 중요한 키워드는 ‘고속성장’
    • 시어머니봇
      • 코드리뷰를 사람끼리 하면 사실 맘이 상할 때가 있다
      • 봇은 잔인하게 엄격함
      • 봇한테 화가 날 순 있어도 미워하진 않더라
      • 오픈소스로 풀었어요~

R이 판치는 세상에 Python데이터 분석가로 사는 법 (하용호)

  • 10분만에 듣는 머신러닝
    • 작대기의 자유도에 따라 성능이 결정
    • 회귀
    • XOR Problem
    • Decision Tree
    • Support Vector Machine
    • Random Forest
      • 놀랍게도 성능이 좋음
  • 빅데이터
    • 말 오글거려
  • GGPLOT2
    • 애증. 안예뻐
  • seaborn
    • 그림 그릴 때 좀더 예쁨
  • 파이썬 인터렉티브 노트북
    • 내가 했던 과정이 노트북처럼 주르륵 나온다.
    • 이런 방식이 아니고, 결과만 pdf로 주거나 하면 협업시 데이터를 조금만 고치거나 할 때 힘듦.
    • 그게 아니고, 이렇게 만든 과정을 팀 협업할 때 보면 매우 도움.
  • mrjob
    • 느리지만 사실 우리가 개발하는 속도가 더 느림
    • 사실 코드를 한번 짜고 한번만 쓸 때가 많다.
  • luigi
    • snakemake랑 비슷(근데 이게 더 좋아 by 용호)
  • Spark
    • 하둡보다 선호!(by 용호)

django (매생이)

  • 스타트업의 마감시간 == 남은 돈