diff --git a/zentst/0024/db.sqlite3 b/zentst/0024/db.sqlite3
new file mode 100644
index 00000000..4237f413
Binary files /dev/null and b/zentst/0024/db.sqlite3 differ
diff --git a/zentst/0024/manage.py b/zentst/0024/manage.py
new file mode 100644
index 00000000..5c6e784b
--- /dev/null
+++ b/zentst/0024/manage.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+import os
+import sys
+
+if __name__ == "__main__":
+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "website.settings")
+
+ from django.core.management import execute_from_command_line
+
+ execute_from_command_line(sys.argv)
diff --git a/zentst/0024/todo/__init__.py b/zentst/0024/todo/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/zentst/0024/todo/admin.py b/zentst/0024/todo/admin.py
new file mode 100644
index 00000000..fe2d8ac5
--- /dev/null
+++ b/zentst/0024/todo/admin.py
@@ -0,0 +1,16 @@
+from django.contrib import admin
+from todo.models import Todolist
+
+# Register your models here.
+
+class TodolistAdmin(admin.ModelAdmin):
+ fieldsets = [
+ (None, {'fields':['list_name']}),
+ ('user imformation', {'fields':['user'],}),
+ ('date imformation',{'fields':['added_date','last_edited_date'],}),
+ ('todo context',{'fields':['content'],}),
+ ]
+ list_display = ('subject', 'user', 'added_date', 'last_edited_date')
+ search_fields = ['subject']
+
+admin.site.register(Todolist, TodolistAdmin)
\ No newline at end of file
diff --git a/zentst/0024/todo/migrations/0001_initial.py b/zentst/0024/todo/migrations/0001_initial.py
new file mode 100644
index 00000000..96932b36
--- /dev/null
+++ b/zentst/0024/todo/migrations/0001_initial.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Todolist',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('list_name', models.CharField(max_length=200)),
+ ('list_text', models.CharField(max_length=500)),
+ ('added_date', models.DateTimeField(verbose_name=b'published date')),
+ ('last_edited_date', models.DateTimeField(verbose_name=b'last edited date')),
+ ('user', models.ForeignKey(to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/zentst/0024/todo/migrations/0002_auto_20150508_1936.py b/zentst/0024/todo/migrations/0002_auto_20150508_1936.py
new file mode 100644
index 00000000..7dc6308f
--- /dev/null
+++ b/zentst/0024/todo/migrations/0002_auto_20150508_1936.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('todo', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='todolist',
+ name='added_date',
+ field=models.DateTimeField(verbose_name=b'added date'),
+ ),
+ ]
diff --git a/zentst/0024/todo/migrations/0003_auto_20150512_0215.py b/zentst/0024/todo/migrations/0003_auto_20150512_0215.py
new file mode 100644
index 00000000..3e68d322
--- /dev/null
+++ b/zentst/0024/todo/migrations/0003_auto_20150512_0215.py
@@ -0,0 +1,24 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('todo', '0002_auto_20150508_1936'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='todolist',
+ old_name='list_text',
+ new_name='content',
+ ),
+ migrations.RenameField(
+ model_name='todolist',
+ old_name='list_name',
+ new_name='subject',
+ ),
+ ]
diff --git a/zentst/0024/todo/migrations/__init__.py b/zentst/0024/todo/migrations/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/zentst/0024/todo/models.py b/zentst/0024/todo/models.py
new file mode 100644
index 00000000..247c6026
--- /dev/null
+++ b/zentst/0024/todo/models.py
@@ -0,0 +1,28 @@
+from django.db import models
+from django.contrib.auth import hashers
+from django.contrib.auth.models import User
+
+# Create your models here.
+
+'''
+class User(models.User):
+ user_name = models.CharField(max_length = 20)
+ user_password = models.CharField(max_length = 200)
+ joined_date = models.DateTimeField('registed date')
+
+ def __unicode__(self):
+ return self.user_name
+'''
+
+
+class Todolist(models.Model):
+ user = models.ForeignKey(User)
+ subject = models.CharField(max_length = 200)
+ content = models.CharField(max_length = 500)
+ added_date = models.DateTimeField('added date')
+ last_edited_date = models.DateTimeField('last edited date')
+
+ def __unicode__(self):
+ return self.list_name
+
+
\ No newline at end of file
diff --git a/zentst/0024/todo/templates/todo/detail.html b/zentst/0024/todo/templates/todo/detail.html
new file mode 100644
index 00000000..1f72eb50
--- /dev/null
+++ b/zentst/0024/todo/templates/todo/detail.html
@@ -0,0 +1,17 @@
+
+
+ {% if error_message %}
+ {{error_message}}
+
+ {% else %}
+
+ {% endif %}
+
+
\ No newline at end of file
diff --git a/zentst/0024/todo/templates/todo/index.html b/zentst/0024/todo/templates/todo/index.html
new file mode 100644
index 00000000..71703c04
--- /dev/null
+++ b/zentst/0024/todo/templates/todo/index.html
@@ -0,0 +1,20 @@
+
+
+ welcome {{user}} logout
+ {% if redirect_message %}
+ {{redirect_message}}
+ {% endif %}
+
+ add
+
+
\ No newline at end of file
diff --git a/zentst/0024/todo/templates/todo/login.html b/zentst/0024/todo/templates/todo/login.html
new file mode 100644
index 00000000..a274fea7
--- /dev/null
+++ b/zentst/0024/todo/templates/todo/login.html
@@ -0,0 +1,12 @@
+
+
+ Login
+ {% if error_message %}
{{error_message}}
{% endif %}
+
+
+
\ No newline at end of file
diff --git a/zentst/0024/todo/templates/todo/register.html b/zentst/0024/todo/templates/todo/register.html
new file mode 100644
index 00000000..3c1f3e07
--- /dev/null
+++ b/zentst/0024/todo/templates/todo/register.html
@@ -0,0 +1,12 @@
+
+
+ User Register
+ {% if error_message %}{{error_message}}
{% endif %}
+
+
+
\ No newline at end of file
diff --git a/zentst/0024/todo/tests.py b/zentst/0024/todo/tests.py
new file mode 100644
index 00000000..8c4533b8
--- /dev/null
+++ b/zentst/0024/todo/tests.py
@@ -0,0 +1,218 @@
+from django.test import TestCase
+from django.contrib.auth.models import User
+from todo.models import Todolist
+from django.core.urlresolvers import reverse
+from django.utils import timezone
+
+# Create your tests here.
+
+
+def CreateNewUser(username, password, email):
+ user = User()
+ user.username = username
+ user.set_password(password)
+ user.email = email
+ user.save()
+ return user
+
+def CreateNewList(user, subject, content):
+ return user.todolist_set.create(subject = subject, content = content, added_date = timezone.now(), last_edited_date = timezone.now())
+
+
+
+
+
+
+
+class UserLoginTest(TestCase):
+ def test_user_login_with_correct_inpurt(self):
+ user = CreateNewUser('test001', 'test001', 'test001@test.com')
+ response = self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ self.assertEqual(response.status_code, 302)
+ redirect_response = self.client.get(response.url)
+ self.assertContains(redirect_response, 'test001')
+
+ def test_user_login_with_incorrect_username(self):
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ response = self.client.post('/todo/userlogin/', {'username':'test002', 'password':'test001'})
+ self.assertContains(response, 'username is not exist or password incorrect', status_code = 200)
+ response2 = self.client.post('/todo/userlogin/', {'username':'', 'password':'test001'})
+ self.assertContains(response2, 'This field is required', status_code = 200)
+
+ def test_user_login_with_incorrect_password(self):
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ response = self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test002'})
+ self.assertContains(response, 'username is not exist or password incorrect', status_code = 200)
+ response2 = self.client.post('/todo/userlogin/', {'username':'test001', 'password':''})
+ self.assertContains(response2, 'This field is required', status_code = 200)
+
+
+
+ def test_user_login_while_already_login(self):
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ response = self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ self.assertEqual(response.status_code, 302)
+ redirect_response = self.client.get(response.url)
+ self.assertContains(redirect_response, 'test001')
+
+
+class UserRegisterTest(TestCase):
+
+ def test_user_register_with_correct_input(self):
+ username = 'test001'
+ password = 'test001'
+ repeatpassword = 'test001'
+ email = 'zentst@test.com'
+
+ response = self.client.post('/todo/register/',{'username':username, 'password':password, 'repeatpassword':repeatpassword, 'email':email})
+ self.assertEqual(response.status_code, 302)
+ redirect_response = self.client.get(response.url)
+ self.assertContains(redirect_response, 'test001')
+ self.assertContains(redirect_response, 'regist succeed')
+
+ def test_user_register_with_two_diffrent_password(self):
+ username = 'test001'
+ password = 'test001'
+ repeatpassword = 'test002'
+ email = 'zentst@test.com'
+
+ response = self.client.post('/todo/register/',{'username':username, 'password':password, 'repeatpassword':repeatpassword, 'email':email})
+
+ self.assertContains(response, 'repeat password is not the same', status_code = 200)
+
+ def test_user_register_with_empty_form(self):
+ username = 'test001'
+ password = 'test001'
+ repeatpassword = 'test001'
+ email = 'zentst@test.com'
+ response = self.client.post('/todo/register/',{'username':'', 'password':password, 'repeatpassword':repeatpassword, 'email':email})
+ self.assertContains(response, 'This field is required', status_code = 200)
+
+
+ def test_user_register_with_invalid_email_format(self):
+ #this test may not working as design, nothing useless
+ username = 'test001'
+ password = 'test001'
+ repeatpassword = 'test001'
+ email = 'zentst1test.com'
+ response = self.client.post('/todo/register/',{'username':'', 'password':password, 'repeatpassword':repeatpassword, 'email':email})
+ self.assertContains(response, 'This field is required', status_code = 200)
+
+ def test_user_register_with_exist_user(self):
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ username = 'test001'
+ password = 'test001'
+ repeatpassword = 'test001'
+ email = 'zentst@test.com'
+ response = self.client.post('/todo/register/',{'username':username, 'password':password, 'repeatpassword':repeatpassword, 'email':email})
+ self.assertContains(response, 'username is exist', status_code = 200)
+
+class AddTodoListTest(TestCase):
+
+ def test_add_todo_withow_login(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ response = self.client.post('/todo/add/',{'subject':subject, 'content':content})
+ self.assertEqual(response.status_code, 302)
+ response = self.client.get(response.url)
+ self.assertContains(response, 'login', status_code = 200)
+
+ def test_add_todo_after_login(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ response = self.client.post('/todo/add/', {'subject':subject, 'content':content})
+ self.assertEqual(response.status_code, 302)
+ redirect_response = self.client.get(response.url)
+ self.assertContains(redirect_response, 'news', status_code = 200)
+ self.assertContains(redirect_response, 'add succeed')
+
+class DetailEditViewTest(TestCase):
+ def test_datail_view_withou_login(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ user = CreateNewUser('test001', 'test001', 'test001@test.com')
+ CreateNewList(user, subject, content)
+ response = self.client.get('/todo/1/')
+ self.assertEqual(response.status_code, 302)
+ redirect_response = self.client.get(response.url)
+ self.assertContains(redirect_response, 'login', status_code = 200)
+
+ def test_detail_display(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ self.client.post('/todo/add/', {'subject':subject, 'content':content})
+ response = self.client.get('/todo/1/')
+ self.assertContains(response, 'is a goodnew', status_code = 200)
+
+ def test_detail_display_with_invalid_id(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ user = CreateNewUser('test001', 'test001', 'test001@test.com')
+ CreateNewList(user, subject, content)
+ self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ response = self.client.get('/todo/2/')
+ self.assertContains(response, 'no permit.This list does not exist', status_code = 200)
+
+ def test_detail_display_with_other_user_id(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ user = CreateNewUser('test001', 'test001', 'test001@test.com')
+ CreateNewList(user, subject, content)
+ user = CreateNewUser('test002', 'test002', 'test002@test.com')
+ self.client.post('/todo/userlogin/', {'username':'test002', 'password':'test002'})
+ response = self.client.get('/todo/1/')
+ self.assertContains(response, 'no permit.This is not your list', status_code = 200)
+
+
+
+ def test_Edit_view(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ self.client.post('/todo/add/', {'subject':subject, 'content':content})
+ response = self.client.post('/todo/1/edit/', {'subject':'badnew', 'content':'it is badnew'})
+ self.assertEqual(response.status_code, 302)
+ redirect_response = self.client.get(response.url)
+ self.assertContains(redirect_response, 'edit succeed', status_code = 200)
+ self.assertEqual(Todolist.objects.get(pk=1).content, 'it is badnew')
+
+class DeleteViewTest(TestCase):
+
+ def test_delete(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ self.client.post('/todo/add/', {'subject':subject, 'content':content})
+ response = self.client.post('/todo/1/delete/')
+ self.assertEqual(response.status_code,302)
+ response = self.client.get(response.url)
+ self.assertContains(response, 'delete succded', status_code=200)
+
+ def test_delete_with_invalid_list(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ response = self.client.post('/todo/1/delete/')
+ self.assertEqual(response.status_code,302)
+ response = self.client.get(response.url)
+ self.assertContains(response, 'delete fail.list not exist', status_code=200)
+
+ def test_delete_with_incorrect_user(self):
+ subject = 'news'
+ content = 'is a goodnew'
+ CreateNewUser('test001', 'test001', 'test001@test.com')
+ user = CreateNewUser('test002', 'test002', 'test002@test.com')
+ CreateNewList(user, subject, content)
+ self.client.post('/todo/userlogin/', {'username':'test001', 'password':'test001'})
+ response = self.client.post('/todo/1/delete/')
+ self.assertEqual(response.status_code,302)
+ response = self.client.get(response.url)
+ self.assertContains(response, 'delete fail.this is not your list', status_code=200)
\ No newline at end of file
diff --git a/zentst/0024/todo/urls.py b/zentst/0024/todo/urls.py
new file mode 100644
index 00000000..87037b85
--- /dev/null
+++ b/zentst/0024/todo/urls.py
@@ -0,0 +1,16 @@
+from django.conf.urls import include, url
+from . import views
+
+urlpatterns = [
+ # Examples:
+ # url(r'^$', 'website.views.home', name='home'),
+ # url(r'^blog/', include('blog.urls')),
+ url(r'^$', views.index, name = 'index'),
+ url(r'^add/$', views.add, name = 'add'),
+ url(r'^register/$', views.register, name = 'register'),
+ url(r'^userlogin/$', views.userlogin, name = 'login'),
+ url(r'^userlogout/$', views.userlogout, name = 'logout'),
+ url(r'^(?P\d+)/$',views.detail, name = 'detail'),
+ url(r'^(?P\d+)/edit/$', views.edit, name = 'edit'),
+ url(r'^(?P\d+)/delete/$', views.delete, name = 'delete'),
+]
diff --git a/zentst/0024/todo/views.py b/zentst/0024/todo/views.py
new file mode 100644
index 00000000..4e373d9d
--- /dev/null
+++ b/zentst/0024/todo/views.py
@@ -0,0 +1,179 @@
+from django.shortcuts import render
+from django import forms
+from django.utils import timezone
+from django.contrib.auth.models import User
+from django.contrib.auth.decorators import login_required
+from django.contrib.auth import login, logout, authenticate
+from django.http import HttpResponseRedirect, HttpResponse
+from django.core.urlresolvers import reverse
+from .models import Todolist
+
+# Create your views here.
+
+class RegisterForm(forms.Form):
+ username = forms.CharField(label='username')
+ password = forms.CharField(label='password', widget = forms.PasswordInput)
+ repeatpassword = forms.CharField(label='repeat password', widget = forms.PasswordInput)
+ email = forms.EmailField()
+
+class LoginForm(forms.Form):
+ username = forms.CharField(label='username')
+ password = forms.CharField(label='password', widget = forms.PasswordInput)
+
+class ListForm(forms.Form):
+ subject = forms.CharField(label='title')
+ content = forms.CharField(label='content', widget = forms.Textarea)
+
+@login_required(login_url = 'todo:login')
+def index(request):
+ todolist = request.user.todolist_set.all()
+ #catch HttpResonseRedirect message
+ try:
+ redirect_message = request.session['redirect_message']
+ except Exception:
+ context = {'todolist':todolist}
+ else:
+ context = {'todolist':todolist, 'redirect_message':redirect_message}
+ return render(request, 'todo/index.html', context)
+
+@login_required(login_url = 'todo:login')
+def add(request):
+ if request.method == 'POST':
+ subject = request.POST['subject']
+ content = request.POST['content']
+ #check input is valid
+ addform = ListForm({'subject':subject, 'content':content,})
+ if not addform.is_valid():
+ return render(request, 'todo/add_edit.html',{'form':addform})
+ add_date = timezone.now()
+ #create new todo quest
+ request.user.todolist_set.create(
+ subject = subject,
+ content = content,
+ added_date = add_date,
+ last_edited_date = add_date
+ )
+ request.session['redirect_message'] = 'add succeed'
+ return HttpResponseRedirect(reverse('todo:index'))
+ else:
+ addform = ListForm()
+ return render(request, 'todo/add_edit.html', {'form':addform})
+
+
+def register(request):
+ #user logined, redirect to index
+ if request.user.is_authenticated():
+ return HttpResponseRedirect(reverse('todo:index'))
+ # get post data
+ elif request.method == 'POST':
+ username = request.POST['username']
+ password = request.POST['password']
+ repeatpassword = request.POST['repeatpassword']
+ email = request.POST['email']
+ # checking post data
+ registerform = RegisterForm({'username':username, 'password':password, 'repeatpassword':repeatpassword, 'email':email})
+ if not registerform.is_valid():
+ return render(request, 'todo/register.html', {'form':registerform})
+
+ if password != repeatpassword:
+ return render(request, 'todo/register.html',{
+ 'form':registerform,
+ 'error_message':'repeat password is not the same'
+ })
+ # check username exist
+ if len(User.objects.filter(username = username)) > 0:
+ return render(request, 'todo/register.html',{
+ 'form':registerform,
+ 'error_message':'username is exist'
+ })
+ # create new user
+ new_user = User()
+ new_user.username = username
+ new_user.set_password(password)
+ new_user.email = email
+ new_user.save()
+ #check new user is valid
+ new = authenticate(username = username, password = password)
+ if new is not None:
+ login(request, new)
+ request.session['redirect_message'] = 'regist succeed'
+ return HttpResponseRedirect(reverse('todo:index'))
+ else:
+ #in not post request, create a new form for input
+ registerform = RegisterForm()
+ return render(request, 'todo/register.html',{'form':registerform})
+
+def userlogout(request):
+ if request.user.is_authenticated():
+ logout(request)
+ return HttpResponseRedirect(reverse('todo:login'))
+
+def userlogin(request):
+ #if already login,redirect to index
+ if request.user.is_authenticated():
+ return HttpResponseRedirect(reverse('todo:index'))
+ if request.method == 'POST':
+ username = request.POST['username']
+ password = request.POST['password']
+ #check form input correction
+ loginform = LoginForm({'username':username, 'password':password})
+ if not loginform.is_valid():
+ return render(request, 'todo/login.html', {'form':loginform})
+ else:
+ #check user valid
+ user = authenticate(username = username, password = password)
+ if user is not None:
+ login(request, user)
+ return HttpResponseRedirect(reverse('todo:index'))
+ else:
+ return render(request, 'todo/login.html', {'form':loginform, 'error_message':'username is not exist or password incorrect'})
+ else:
+ #display loginform
+ loginform = LoginForm()
+ return render(request, 'todo/login.html',{'form':loginform})
+
+
+@login_required(login_url = 'todo:login')
+def detail(request, list_id):
+ #check this list exist and belond to user ,if not, return error
+ try:
+ request_list = Todolist.objects.get(pk = list_id)
+ except Todolist.DoesNotExist:
+ return render(request, 'todo/detail.html', {'error_message':'no permit.This list does not exist'})
+ if request_list.user != request.user:
+ return render(request, 'todo/detail.html', {'error_message':'no permit.This is not your list'})
+ #display list
+ listform = ListForm({'subject':request_list.subject, 'content':request_list.content})
+ return render(request, 'todo/detail.html',{'todolist':request_list, 'form':listform})
+
+@login_required(login_url = 'todo:login')
+def edit(request, list_id):
+ request_list = Todolist.objects.get(pk = list_id)
+ subject = request.POST['subject']
+ content = request.POST['content']
+ listform = ListForm({'subject':subject, 'content':content})
+ if not listform.is_valid():
+ return render(request, 'todo/detail.html',{'form':listform, 'todolist':request_list})
+ #set todolist new values
+ request_list.subject = subject
+ request_list.content = content
+ request_list.last_edited_date = timezone.now()
+ request_list.save()
+ request.session['redirect_message'] = 'edit succeed'
+ return HttpResponseRedirect(reverse('todo:index'))
+
+@login_required(login_url = 'todo:login')
+def delete(request, list_id):
+ try:
+ request_list = Todolist.objects.get(pk = list_id)
+ except Todolist.DoesNotExist:
+ request.session['redirect_message'] = 'delete fail.list not exist'
+ return HttpResponseRedirect('/todo/')
+ if request_list.user == request.user:
+ request_list.delete()
+ request.session['redirect_message'] = 'delete succded'
+ return HttpResponseRedirect('/todo/')
+ else:
+ request.session['redirect_message'] = 'delete fail.this is not your list'
+ return HttpResponseRedirect('/todo/')
+
\ No newline at end of file
diff --git a/zentst/0024/website/__init__.py b/zentst/0024/website/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/zentst/0024/website/settings.py b/zentst/0024/website/settings.py
new file mode 100644
index 00000000..5e83d3b1
--- /dev/null
+++ b/zentst/0024/website/settings.py
@@ -0,0 +1,103 @@
+"""
+Django settings for website project.
+
+Generated by 'django-admin startproject' using Django 1.8.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.8/topics/settings/
+
+For the full list of settings and their values, see
+https://docs.djangoproject.com/en/1.8/ref/settings/
+"""
+
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
+import os
+
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+
+
+# Quick-start development settings - unsuitable for production
+# See https://docs.djangoproject.com/en/1.8/howto/deployment/checklist/
+
+# SECURITY WARNING: keep the secret key used in production secret!
+SECRET_KEY = 'gm07je_4_nt51r1(x)s-j*1kyo4on(c5(luq$^o7uv)icbpv(g'
+
+# SECURITY WARNING: don't run with debug turned on in production!
+DEBUG = True
+
+ALLOWED_HOSTS = []
+
+
+# Application definition
+
+INSTALLED_APPS = (
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'todo',
+)
+
+MIDDLEWARE_CLASSES = (
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+ 'django.middleware.security.SecurityMiddleware',
+)
+
+ROOT_URLCONF = 'website.urls'
+
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [os.path.join(BASE_DIR, 'templates')],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+WSGI_APPLICATION = 'website.wsgi.application'
+
+
+# Database
+# https://docs.djangoproject.com/en/1.8/ref/settings/#databases
+
+DATABASES = {
+ 'default': {
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ }
+}
+
+
+# Internationalization
+# https://docs.djangoproject.com/en/1.8/topics/i18n/
+
+LANGUAGE_CODE = 'en-us'
+
+TIME_ZONE = 'GMT+8'
+
+USE_I18N = True
+
+USE_L10N = True
+
+USE_TZ = True
+
+
+# Static files (CSS, JavaScript, Images)
+# https://docs.djangoproject.com/en/1.8/howto/static-files/
+
+STATIC_URL = '/static/'
diff --git a/zentst/0024/website/urls.py b/zentst/0024/website/urls.py
new file mode 100644
index 00000000..f89238f2
--- /dev/null
+++ b/zentst/0024/website/urls.py
@@ -0,0 +1,11 @@
+from django.conf.urls import include, url
+from django.contrib import admin
+
+urlpatterns = [
+ # Examples:
+ # url(r'^$', 'website.views.home', name='home'),
+ # url(r'^blog/', include('blog.urls')),
+
+ url(r'^admin/', include(admin.site.urls)),
+ url(r'^todo/', include('todo.urls', namespace = 'todo')),
+]
diff --git a/zentst/0024/website/wsgi.py b/zentst/0024/website/wsgi.py
new file mode 100644
index 00000000..af920e84
--- /dev/null
+++ b/zentst/0024/website/wsgi.py
@@ -0,0 +1,16 @@
+"""
+WSGI config for website project.
+
+It exposes the WSGI callable as a module-level variable named ``application``.
+
+For more information on this file, see
+https://docs.djangoproject.com/en/1.8/howto/deployment/wsgi/
+"""
+
+import os
+
+from django.core.wsgi import get_wsgi_application
+
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "website.settings")
+
+application = get_wsgi_application()