##// END OF EJS Templates
Implemented form validation. When the form fails validation, showing the index page.
neko259 -
r29:575870f6 default
parent child Browse files
Show More
@@ -1,8 +1,41 b''
1 from django import forms
1 from django import forms
2
2
3
3
4 class NewThreadForm(forms.Form):
4 class PostForm(forms.Form):
5 title = forms.CharField(max_length=100)
5 MAX_TEXT_LENGTH = 10000
6 text = forms.CharField(widget=forms.Textarea)
6 MAX_IMAGE_SIZE = 8 * 1024 * 1024
7 image = forms.ImageField()
7
8 tags = forms.CharField(max_length=100)
8 title = forms.CharField(max_length=50, required=False)
9 text = forms.CharField(widget=forms.Textarea, required=False)
10 image = forms.ImageField(required=False)
11 tags = forms.CharField(max_length=100, required=False)
12
13 def clean_text(self):
14 text = self.cleaned_data['text']
15 if text:
16 if len(text) > self.MAX_TEXT_LENGTH:
17 raise forms.ValidationError('Too many text')
18 return text
19
20 def clean_image(self):
21 image = self.cleaned_data['image']
22 if image:
23 if image._size > self.MAX_IMAGE_SIZE:
24 raise forms.ValidationError('Too large image: more than ' +
25 str(self.MAX_IMAGE_SIZE) + ' bytes')
26 return image
27
28 def clean(self):
29 cleaned_data = super(PostForm, self).clean()
30
31 text = cleaned_data.get('text')
32 image = cleaned_data.get('image')
33
34 if (not text) and (not image):
35 raise forms.ValidationError('Enter either text or image')
36
37 return cleaned_data
38
39
40 class ThreadForm(PostForm):
41 tags = forms.CharField(max_length=100) No newline at end of file
@@ -1,135 +1,145 b''
1 import os
1 from django.db import models
2 from django.db import models
2 from django.utils import timezone
3 from django.utils import timezone
4 import time
3
5
4 from neboard import settings
6 from neboard import settings
5
7
6 import thumbs
8 import thumbs
7
9
8 NO_PARENT = -1
10 NO_PARENT = -1
9 NO_IP = '0.0.0.0'
11 NO_IP = '0.0.0.0'
10 UNKNOWN_UA = ''
12 UNKNOWN_UA = ''
11
13
12
14
15 def update_image_filename(instance, filename):
16 """Get unique image filename"""
17
18 path = 'images/'
19 new_name = str(int(time.mktime(time.gmtime()))) + '_' + filename
20 return os.path.join(path, new_name)
21
22
13 class PostManager(models.Manager):
23 class PostManager(models.Manager):
14 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
24 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
15 ip=NO_IP, tags=None):
25 ip=NO_IP, tags=None):
16 post = self.create(title=title,
26 post = self.create(title=title,
17 text=text,
27 text=text,
18 pub_time=timezone.now(),
28 pub_time=timezone.now(),
19 parent=parent_id,
29 parent=parent_id,
20 image=image,
30 image=image,
21 poster_ip=ip,
31 poster_ip=ip,
22 poster_user_agent=UNKNOWN_UA,
32 poster_user_agent=UNKNOWN_UA,
23 last_edit_time=timezone.now())
33 last_edit_time=timezone.now())
24
34
25 if tags:
35 if tags:
26 for tag in tags:
36 for tag in tags:
27 post.tags.add(tag)
37 post.tags.add(tag)
28
38
29 if parent_id != NO_PARENT:
39 if parent_id != NO_PARENT:
30 parent = self.get(id=parent_id)
40 parent = self.get(id=parent_id)
31 parent.last_edit_time=timezone.now()
41 parent.last_edit_time=timezone.now()
32 parent.save()
42 parent.save()
33 else:
43 else:
34 self._delete_old_threads()
44 self._delete_old_threads()
35
45
36 return post
46 return post
37
47
38 def delete_post(self, post):
48 def delete_post(self, post):
39 children = self.filter(parent=post.id)
49 children = self.filter(parent=post.id)
40 for child in children:
50 for child in children:
41 self.delete_post(child)
51 self.delete_post(child)
42 post.delete()
52 post.delete()
43
53
44 def delete_posts_by_ip(self, ip):
54 def delete_posts_by_ip(self, ip):
45 posts = self.filter(poster_ip=ip)
55 posts = self.filter(poster_ip=ip)
46 for post in posts:
56 for post in posts:
47 self.delete_post(post)
57 self.delete_post(post)
48
58
49 def get_threads(self, tag=None):
59 def get_threads(self, tag=None):
50 if tag:
60 if tag:
51 threads = self.filter(parent=NO_PARENT, tags=tag)
61 threads = self.filter(parent=NO_PARENT, tags=tag)
52 else:
62 else:
53 threads = self.filter(parent=NO_PARENT)
63 threads = self.filter(parent=NO_PARENT)
54 threads = list(threads.order_by('-last_edit_time'))
64 threads = list(threads.order_by('-last_edit_time'))
55
65
56 return threads
66 return threads
57
67
58 def get_thread(self, opening_post_id):
68 def get_thread(self, opening_post_id):
59 opening_post = self.get(id=opening_post_id)
69 opening_post = self.get(id=opening_post_id)
60 replies = self.filter(parent=opening_post_id)
70 replies = self.filter(parent=opening_post_id)
61
71
62 thread = [opening_post]
72 thread = [opening_post]
63 thread.extend(replies)
73 thread.extend(replies)
64
74
65 return thread
75 return thread
66
76
67 def exists(self, post_id):
77 def exists(self, post_id):
68 posts = self.filter(id=post_id)
78 posts = self.filter(id=post_id)
69
79
70 return len(posts) > 0
80 return len(posts) > 0
71
81
72 def _delete_old_threads(self):
82 def _delete_old_threads(self):
73 """
83 """
74 Preserves maximum thread count. If there are too many threads,
84 Preserves maximum thread count. If there are too many threads,
75 delete the old ones.
85 delete the old ones.
76 """
86 """
77
87
78 # TODO Try to find a better way to get the active thread count.
88 # TODO Try to find a better way to get the active thread count.
79
89
80 # TODO Move old threads to the archive instead of deleting them.
90 # TODO Move old threads to the archive instead of deleting them.
81 # Maybe make some 'old' field in the model to indicate the thread
91 # Maybe make some 'old' field in the model to indicate the thread
82 # must not be shown and be able for replying.
92 # must not be shown and be able for replying.
83
93
84 threads = self.get_threads()
94 threads = self.get_threads()
85 thread_count = len(threads)
95 thread_count = len(threads)
86
96
87 if thread_count > settings.MAX_THREAD_COUNT:
97 if thread_count > settings.MAX_THREAD_COUNT:
88 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
98 num_threads_to_delete = thread_count - settings.MAX_THREAD_COUNT
89 old_threads = threads[-num_threads_to_delete:]
99 old_threads = threads[-num_threads_to_delete:]
90
100
91 for thread in old_threads:
101 for thread in old_threads:
92 self.delete_post(thread)
102 self.delete_post(thread)
93
103
94
104
95 class Tag(models.Model):
105 class Tag(models.Model):
96 """
106 """
97 A tag is a text node assigned to the post. The tag serves as a board
107 A tag is a text node assigned to the post. The tag serves as a board
98 section. There can be multiple tags for each message
108 section. There can be multiple tags for each message
99 """
109 """
100
110
101 name = models.CharField(max_length=100)
111 name = models.CharField(max_length=100)
102 # TODO Connect the tag to its posts to check the number of threads for
112 # TODO Connect the tag to its posts to check the number of threads for
103 # the tag.
113 # the tag.
104
114
105
115
106 class Post(models.Model):
116 class Post(models.Model):
107 """A post is a message."""
117 """A post is a message."""
108
118
109 objects = PostManager()
119 objects = PostManager()
110
120
111 title = models.CharField(max_length=100)
121 title = models.CharField(max_length=50)
112 pub_time = models.DateTimeField()
122 pub_time = models.DateTimeField()
113 text = models.TextField()
123 text = models.TextField()
114 image = thumbs.ImageWithThumbsField(upload_to='images/',
124 image = thumbs.ImageWithThumbsField(upload_to=update_image_filename,
115 blank=True, sizes=((200, 150),))
125 blank=True, sizes=((200, 150),))
116 poster_ip = models.IPAddressField()
126 poster_ip = models.IPAddressField()
117 poster_user_agent = models.TextField()
127 poster_user_agent = models.TextField()
118 parent = models.BigIntegerField()
128 parent = models.BigIntegerField()
119 tags = models.ManyToManyField(Tag)
129 tags = models.ManyToManyField(Tag)
120 last_edit_time = models.DateTimeField()
130 last_edit_time = models.DateTimeField()
121
131
122 def __unicode__(self):
132 def __unicode__(self):
123 return self.title + ' (' + self.text + ')'
133 return self.title + ' (' + self.text + ')'
124
134
125
135
126 class Admin(models.Model):
136 class Admin(models.Model):
127 """
137 """
128 Model for admin users
138 Model for admin users
129 """
139 """
130 name = models.CharField(max_length=100)
140 name = models.CharField(max_length=100)
131 password = models.CharField(max_length=100)
141 password = models.CharField(max_length=100)
132
142
133 def __unicode__(self):
143 def __unicode__(self):
134 return self.name + '/' + '*' * len(self.password)
144 return self.name + '/' + '*' * len(self.password)
135
145
@@ -1,72 +1,71 b''
1 html {
1 html {
2 background: #333;
2 background: #444;
3 color: #ffffff;
3 color: #ffffff;
4 }
4 }
5
5
6 #admin_panel {
6 #admin_panel {
7 background: #FF0000;
7 background: #FF0000;
8 color: #00FF00
8 color: #00FF00
9 }
9 }
10
10
11 .title {
11 .title {
12 font-weight: bold;
12 font-weight: bold;
13 color: #ffcc00;
13 color: #ffcc00;
14 }
14 }
15
15
16 .post-form {
16 .post-form {
17 text-align: left;
17 text-align: left;
18 color: #ffffff;
19 display: table;
18 display: table;
20 border: dashed 1px;
19 border-radius: 5px;
21 padding: 3px;
20 padding: 5px;
22 margin: 5px;
21 margin: 5px;
22 background: #334;
23 }
23 }
24
24
25 .form-row {
25 .form-row {
26 display: table-row;
26 display: table-row;
27 }
27 }
28
28
29 .form-input {
29 .form-input {
30 display: table-cell;
30 display: table-cell;
31 }
31 }
32
32
33 .link {
33 .link {
34 color: #829dba;
34 color: #829dba;
35 }
35 }
36
36
37 .link:hover {
37 .link:hover {
38 color: #ba9f82;
38 color: #ba9f82;
39 }
39 }
40
40
41 .post_id {
41 .post_id {
42 color: #ffffff;
42 color: #ffffff;
43 }
43 }
44
44
45 .block {
45 .block {
46 display: inline-block;
46 display: inline-block;
47 vertical-align: top;
47 vertical-align: top;
48 }
48 }
49
49
50 .tag {
50 .tag {
51 color: #b4cfec;
51 color: #b4cfec;
52 }
52 }
53
53
54 .tag:hover {
54 .tag:hover {
55 color: #d0edb4;
55 color: #d0edb4;
56 }
56 }
57
57
58 .post_id {
58 .post_id {
59 color: #fff380;
59 color: #fff380;
60 }
60 }
61
61
62 .post {
62 .post {
63 background: #222;
63 background: #333;
64 margin: 5px;
64 margin: 5px;
65 padding: 5px;
65 padding: 10px;
66 border: solid 1px;
67 border-radius: 5px;
66 border-radius: 5px;
68 }
67 }
69
68
70 .form-title {
69 .form-title {
71 font-weight: bolder;
70 font-weight: bolder;
72 } No newline at end of file
71 }
@@ -1,125 +1,133 b''
1 from django.template import RequestContext
1 from django.template import RequestContext
2 from boards import forms
2 from boards import forms
3 import boards
3 import boards
4 from boards.forms import NewThreadForm
4 from boards.forms import ThreadForm, PostForm
5 from boards.models import Post, Admin, Tag
5 from boards.models import Post, Admin, Tag
6 from django.shortcuts import render, get_list_or_404, redirect
6 from django.shortcuts import render, get_list_or_404, redirect
7 from django.http import HttpResponseRedirect, Http404
7 from django.http import HttpResponseRedirect, Http404
8
8
9
9
10 def index(request):
10 def index(request):
11 context = RequestContext(request)
11 context = RequestContext(request)
12
12
13 if request.method == 'POST':
13 if request.method == 'POST':
14 return new_post(request)
14 return new_post(request)
15 else:
15 else:
16 threads = Post.objects.get_threads()
16 threads = Post.objects.get_threads()
17
17
18 context['threads'] = None if len(threads) == 0 else threads
18 context['threads'] = None if len(threads) == 0 else threads
19 context['form'] = forms.NewThreadForm()
19 context['form'] = forms.ThreadForm()
20
20
21 return render(request, 'posting_general.html',
21 return render(request, 'posting_general.html',
22 context)
22 context)
23
23
24
24
25 def new_post(request, thread_id=boards.models.NO_PARENT):
25 def new_post(request, thread_id=boards.models.NO_PARENT):
26 """Add a new post (in thread or as a reply)."""
26 """Add a new post (in thread or as a reply)."""
27
27
28 form = NewThreadForm(request.POST, request.FILES)
28 if thread_id == boards.models.NO_PARENT:
29 form = ThreadForm(request.POST, request.FILES)
30 else:
31 form = PostForm(request.POST, request.FILES)
29
32
30 title = request.POST['title']
33 if form.is_valid():
31 text = request.POST['text']
34 data = form.cleaned_data
35 else:
36 return redirect(index)
32
37
33 if 'image' in request.FILES.keys():
38 title = data['title']
34 image = request.FILES['image']
39 text = data['text']
40
41 if 'image' in data.keys():
42 image = data['image']
35 else:
43 else:
36 image = None
44 image = None
37
45
38 ip = request.META['REMOTE_ADDR']
46 ip = request.META['REMOTE_ADDR']
39
47
40 tags = []
48 tags = []
41 if thread_id == boards.models.NO_PARENT:
49 if thread_id == boards.models.NO_PARENT:
42 tag_strings = request.POST['tags']
50 tag_strings = data['tags']
43
51
44 if tag_strings:
52 if tag_strings:
45 tag_strings = tag_strings.split(',')
53 tag_strings = tag_strings.split(',')
46 for tag_name in tag_strings:
54 for tag_name in tag_strings:
47 tag_name = tag_name.strip()
55 tag_name = tag_name.strip()
48 if len(tag_name) > 0:
56 if len(tag_name) > 0:
49 tag, created = Tag.objects.get_or_create(name=tag_name)
57 tag, created = Tag.objects.get_or_create(name=tag_name)
50 tags.append(tag)
58 tags.append(tag)
51
59
52 # TODO Add a possibility to define a link image instead of an image file.
60 # TODO Add a possibility to define a link image instead of an image file.
53 # If a link is given, download the image automatically.
61 # If a link is given, download the image automatically.
54
62
55 post = Post.objects.create_post(title=title, text=text, ip=ip,
63 post = Post.objects.create_post(title=title, text=text, ip=ip,
56 parent_id=thread_id, image=image,
64 parent_id=thread_id, image=image,
57 tags=tags)
65 tags=tags)
58
66
59 thread_to_show = (post.id if thread_id == boards.models.NO_PARENT else
67 thread_to_show = (post.id if thread_id == boards.models.NO_PARENT else
60 thread_id)
68 thread_id)
61 return redirect(thread, post_id=thread_to_show)
69 return redirect(thread, post_id=thread_to_show)
62
70
63
71
64 def tag(request, tag_name):
72 def tag(request, tag_name):
65 """Get all tag threads (posts without a parent)."""
73 """Get all tag threads (posts without a parent)."""
66
74
67 tag = Tag.objects.get(name=tag_name)
75 tag = Tag.objects.get(name=tag_name)
68 threads = Post.objects.get_threads(tag=tag)
76 threads = Post.objects.get_threads(tag=tag)
69
77
70 if request.method == 'POST':
78 if request.method == 'POST':
71 return new_post(request)
79 return new_post(request)
72 else:
80 else:
73 context = RequestContext(request)
81 context = RequestContext(request)
74 context['threads'] = None if len(threads) == 0 else threads
82 context['threads'] = None if len(threads) == 0 else threads
75 context['tag'] = tag_name
83 context['tag'] = tag_name
76
84
77 context['form'] = forms.NewThreadForm(initial={'tags': tag_name})
85 context['form'] = forms.ThreadForm(initial={'tags': tag_name})
78
86
79 return render(request, 'posting_general.html',
87 return render(request, 'posting_general.html',
80 context)
88 context)
81
89
82
90
83 def thread(request, post_id):
91 def thread(request, post_id):
84 """Get all thread posts"""
92 """Get all thread posts"""
85
93
86 if request.method == 'POST':
94 if request.method == 'POST':
87 return new_post(request, post_id)
95 return new_post(request, post_id)
88 else:
96 else:
89 # TODO Show 404 if there is no such thread
97 # TODO Show 404 if there is no such thread
90 posts = Post.objects.get_thread(post_id)
98 posts = Post.objects.get_thread(post_id)
91
99
92 context = RequestContext(request)
100 context = RequestContext(request)
93 context['posts'] = posts
101 context['posts'] = posts
94
102
95 context['form'] = forms.NewThreadForm()
103 context['form'] = forms.PostForm()
96
104
97 return render(request, 'thread.html', context)
105 return render(request, 'thread.html', context)
98
106
99
107
100 def login(request):
108 def login(request):
101 """Log in as admin"""
109 """Log in as admin"""
102
110
103 if 'name' in request.POST and 'password' in request.POST:
111 if 'name' in request.POST and 'password' in request.POST:
104 request.session['admin'] = False
112 request.session['admin'] = False
105
113
106 isAdmin = len(Admin.objects.filter(name=request.POST['name'],
114 isAdmin = len(Admin.objects.filter(name=request.POST['name'],
107 password=request.POST[
115 password=request.POST[
108 'password'])) > 0
116 'password'])) > 0
109
117
110 if isAdmin:
118 if isAdmin:
111 request.session['admin'] = True
119 request.session['admin'] = True
112
120
113 response = HttpResponseRedirect('/')
121 response = HttpResponseRedirect('/')
114
122
115 else:
123 else:
116 response = render(request, 'login.html', {'error': 'Login error'})
124 response = render(request, 'login.html', {'error': 'Login error'})
117 else:
125 else:
118 response = render(request, 'login.html', {})
126 response = render(request, 'login.html', {})
119
127
120 return response
128 return response
121
129
122
130
123 def logout(request):
131 def logout(request):
124 request.session['admin'] = False
132 request.session['admin'] = False
125 return HttpResponseRedirect('/') No newline at end of file
133 return HttpResponseRedirect('/')
General Comments 0
You need to be logged in to leave comments. Login now