ref. 1 에서 제공하는 library 는 아래 2가지 case 를 위해 사용할 수 있다.
- 2-phase 등록: 가입하고, email 확인 하는 절차후에 완료
- 1-phase 등록: 가입하는 즉시, login 되고 완료
django-registration 설치
pip install django-registration
설정 : 2-phase 등록, HMAC activation workflow
동작원리
INSTALLED_APPS, migrate
django-registration 이 djagno.contrib.auth 를 사용하기 때문에, setting 의 INSTALLED_APPS 에 django.contrib.auth 가 있어야 하고(보통 default 로 되어 있다.), manage.py migrate 을 통해 db 를 생성해 놔야 한다.URL 등록
from django.conf.urls import include, url urlpatterns = [ # Other URL patterns ... url(r'^accounts/', include('registration.backends.hmac.urls')), # More URL patterns ... ]
- accounts/register : form 에서 submit 을 눌러서 user 를 등록할 때 사용. form 의 action 에 쓰이는 url 이라고 보면 된다.
- accounts/login: user 가 activated 되고 나서 login 할 때 사용
template
django-registration 에서 사용하는 template 을 만든다.- django-registration/registration/tests/templates/registration : template list
- django-registration/views.py
- django-registration/backends/hmac/views.py/views.py
에 보면, 필요한 template 들을 확인할 수 있다.
template folder 안에 registration folder 를 하나 만들어서 넣으면 된다. 아래 url 에서 예제를 볼 수 있다.
노란색 부분은 단순한 template view 로 되어 있다.(hmac/urls.py 참고) 그래서 html 이외의 사항을 수정하고 싶다면, 이 view 를 수정하는 것이 아니라, get_success_url() 부분에서 다른 url 을 호출하는 방식을 써야 한다.
- registration/registration_form.html : {{form}}
- registration/registration_complete.html
- registration/activate.html: {{activation_key}}
- registration/activation_complete.html
- registration/activation_email_subject.txt: {{activation_key}} / {{expiration_days}} / {{site}}
- registration/activation_email.txt: {{activation_key}} / {{expiration_days}} / {{site}}
- registration/registration_closed.html
관련 settings
- ACCOUNT_ACTIVATION_DAYS : activation 유효 시간, 7은 7일을 뜻한다.
- REGISTRATION_OPEN : 새로운 account 의 등록이 가능한지 여부. default 는 True 이다.
- REGISTRATION_SALT : HMAC 함수처리할 때 쓰이는 salt string 을 설정한다.
# settings.py # #### django-registraiton CONFIGURATION ACCOUNT_ACTIVATION_DAYS=7 REGISTRATION_OPEN=True REGISTRATION_SALT='mysalt' # #### END django-registraiton CONFIGURATION
dummy smtp server
아래처럼 command 를 입력하면 smtp 서버를 사용할 수 있다. 다만 auth 와 TLS 등은 지원하지 않으니, 끄고 사용하면 된다.
실제로 email 를 배달해 주지는 않고 smtp 로 보내는 내용을 화면에 뿌려준다.
C:\>c:\Python27\python.exe -m smtpd -n -c DebuggingServer localhost:465
smtp.gmail.com 설정
- https://support.google.com/mail/answer/78775?hl=en > Still can't send mail?
- smtp.gmail.com 에서
- port 456(with SSL)
- prot 587(with TLS)
- 둘다 안되면 port 25(with SSL)
실제 적용
get_url_success 관련
django-registration 에서 register 나 activate 를 하고 난 후에 특정 url 로 redirect 시키게 되는데, 그 url 을 받아오는 부분이 get_url_success 이다. 그래서 register 와 activate 이외의 view 들은 view 를 수정하기 보다는 get_url_success 로 다른 view 를 호출하도록 하면 된다.물론 template 만 수정하는 경우라면 굳이 그러지 않아도 된다.
account/register, ajax 사용
처음에 form 을 하나 가진 view 를 만들자. 여기서는 registration_form.html 을 사용하지 않는 방법을 사용한다. 기존의 html 이 있어서 그것에 맞춰서 작업할 것이다.필자가 가지고 있는 기존의 view(acc/login 이라하자.) 는 url 을 호출하면 id/pw 입력창과 submit 버튼이 있는 page 이다. 여기서 submit 버튼을 누르면 ajax 로 email 과 password 가 acc/register url 로 전달이 되고, 이것을 UserRegistrationForm 의 형태로 받아서 작업을 했다.
django-registration 을 적용하면, 기존의 view(acc/login) 에서 submit 을 하면 위에서 이야기한 django-registration 의 "account/register/" 를 호출하게 만들어야 된다.
아래 예제에서는 이 "account/register" 가 "acc/register-email" 로 변경되었다. 왜냐하면, django-registration 에서 제공하는 RegistrationView 를 extends 해서 수정해야 했기 때문이다.
그리고 ajax 로 호출을 하다 보니 redirect 는 동작하지 않는다. 그래서 account/complete 를 사용할 수 없었다. 그래서 form_valid() 를 수정해서, JsonResponse() 를 return 하게 수정하였다.
간단하게 흐름을 적어보면 아래와 같다.
- /register-user/ : email 과 password 입력하는 창이 있는 page를 보여준다.
- --> submit button 클릭
- --> /acc/register-email
- --> user 등록 성공(서버)
- --> email 전송 (서버)
- --> success/error JsonResponse 전송(서버)
이렇게 하면, 일단 verification email(확인 메일) 까지 전송된 것이다.
여기서, 실제 자신이 직접 작업해야 하는 code 는 아래 2개 이다.
- class RegistrationView2
- class UserRegistrationForm
code
# - url(r'^acc/', include('accblacksmith.urls', namespace='acc')), urlpatterns = [ ... url(r'^hmac/', include('registration.backends.hmac.urls', namespace='hmac')), url(r'^register-email/$', RegistrationView2.as_view(), name='register_email'), url(r'^activate/(?P[-:\w]+)/$', ActivationView.as_view(), name='register_activate'), ]
# client javascript
@$regForm.find('button').on 'click', (e)->
url = '/acc/register-email/'
param = that.$regForm.serialize()
mine.utils.ajax # 여기서 csrf 를 포함시킨다.
url: url,
type: 'POST'
data: param
success: (res) ->
console.log(res)
error: (xhr, status, err) ->
console.log('error occurred')
return
return
# server from registration.backends.hmac.views import RegistrationView as HmacRegView class RegistrationView2(HmacRegView): """ This uses the django-registration module """ form_class = UserRegistrationForm def get_success_url(self, user): return ('acc:hmac:registration_complete', (), {}) # 원래 여기서 get_success_url 을 사용하지만, json response 를 사용하다 보니 # get_success_url 은 사용할 필요가 없어졌다. def form_valid(self, form): try: new_user = self.register(form) except Exception as e: return JsonResponse(err({'message':e.message}), status=400) # success_url may be a simple string, or a tuple providing the # full argument set for redirect(). Attempting to unpack it # tells us which one it is. return JsonResponse(success({ 'message' : "registration has been successfully." })) def create_inactive_user(self, form): """ Invoked by : self.register() Create the inactive user account and send an email containing activation instructions. """ # create User database record # @see self.form_class.saveWithActive new_user = form.saveWithActive(commit=True, active=False) # set AccountInfo # To add additional information from registration form, you MUST add a code here. accinfo = AccountInfo() accinfo.user = new_user accinfo.phone_no = form.data['phone'] accinfo.save() self.send_activation_email(new_user) return new_user def send_activation_email(self, user): """ Send the activation email. The activation key is simply the username, signed using TimestampSigner. """ activation_key = self.get_activation_key(user) context = self.get_email_context(activation_key) context.update({ # add server port info 'server_port': self.request.META['SERVER_PORT'] }) subject = render_to_string(self.email_subject_template, context) # Force subject to a single line to avoid header-injection # issues. subject = ''.join(subject.splitlines()) message = render_to_string(self.email_body_template, context) kwargs = { "subject": subject, "body": message, "from_email": settings.EMAIL_HOST_USER, "to": [user.email], "reply_to": [settings.DEFAULT_FROM_EMAIL], } email = EmailMultiAlternatives(**kwargs) email.send() from django.contrib.auth.forms import UserCreationForm from django.contrib.auth.models import User class UserRegistrationForm(UserCreationForm): class Meta: model = User fields = ('email', 'password1', 'password2') def save(self, commit=True): return self.saveWithActive(commit=True, active=True) def saveWithActive(self, commit=True, active=True): user = super(UserRegistrationForm, self).save(commit=False) user.username = user.email user.is_active = active # to verify the email before make it active if commit: user.save() return user
# registration.views.py class RegistrationView(FormView): ... def form_valid(self, form): new_user = self.register(form) success_url = self.get_success_url(new_user) # success_url may be a simple string, or a tuple providing the # full argument set for redirect(). Attempting to unpack it # tells us which one it is. try: to, args, kwargs = success_url return redirect(to, *args, **kwargs) except ValueError: return redirect(success_url) class FormView(TemplateResponseMixin, BaseFormView): ... class BaseFormView(FormMixin, ProcessFormView): ... class ProcessFormView(View): def get(self, request, *args, **kwargs): return self.render_to_response(self.get_context_data()) def post(self, request, *args, **kwargs): form = self.get_form() if form.is_valid(): return self.form_valid(form) # RegistrationView.form_valid() else: return self.form_invalid(form) ...
# django.views.generic.edit.py class FormMixin(six.with_metaclass(FormMixinBase, ContextMixin)): ... def get_form_class(self): return self.form_class def get_form(self, form_class=None): if form_class is None: form_class = self.get_form_class() return form_class(**self.get_form_kwargs()) # form_class : UserRegistrationForm
account/activate
activation_email.txt 설정
account/register 에서 validation mail 을 보낼때 activation_email.txt 의 내용이 body 로 가게 되는데, 이 때 아래처럼 activation code 를 만들 수 있다.To activate your account, please visit the following page: {% if request.is_secure %}https{% else %}http{% endif %}://{{ site.domain }}:9080{% url 'acc:register_activate' activation_key %} This page will expire in {{ expiration_days }} day{{ expiration_days|pluralize }}.
- http://localhost:9080/acc/activate/3rcbVHhchIEho3fEgl28-/
get_success_url() 수정
그런데 여기서는 registration_activation_complete 의 namespace 가 달라서 get_success_url() 을 수정해야 했다. 그래서 ActivationView 를 상속받아서 새로운 ActivationView 를 만들었다.# - url(r'^acc/', include('accblacksmith.urls', namespace='acc')), urlpatterns = [ ... url(r'^activate/(?P[-:\w]+)/$', ActivationView.as_view(), name='register_activate'), ] from registration.backends.hmac.views import ActivationView as HmacActView class ActivationView(HmacActView): def get_success_url(self, user): return ('acc:hmac:registration_activation_complete', (), {})
이제 user 가 email 에서 URL 을 click 하면, user 의 계정이 activate 되고, user 에게 activation complete 화면을 보여주게 된다.
기타 참고 자료
django 의 PasswordResetTokenGenerator 를 응용해서 email confirm 를 만들어 보자.
관련 view 를 살펴보면 아래와 같은 주석이 있다.
- django/views.py at master · django/django · GitHub
- # - password_reset sends the mail
- # - password_reset_done shows a success message for the above
- # - password_reset_confirm checks the link the user clicked and prompts for a new password
- # - password_reset_complete shows a success message for the above
email 발송방법
email 발송은 아래를 참고하자.- django/forms.py 의 class PasswordResetForm 의 send_mail()
- Sending email | Django documentation | Django
django 의 smtplib.SMTP() 가 socket._GLOBAL_DEFAULT_TIMEOUT 을 사용하는데, 이 값이 default 가 설정되어 있지 않아서 smtplib.SMTP() 는 무한정 기다린다.
댓글 없음:
댓글 쓰기