##// END OF EJS Templates
Added localization (not fully working). Added tags posting. Changed the design a bit.
neko259 -
r24:bb515fee default
parent child Browse files
Show More
@@ -0,0 +1,38 b''
1 # SOME DESCRIPTIVE TITLE.
2 # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3 # This file is distributed under the same license as the PACKAGE package.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5 #
6 #, fuzzy
7 msgid ""
8 msgstr ""
9 "Project-Id-Version: PACKAGE VERSION\n"
10 "Report-Msgid-Bugs-To: \n"
11 "POT-Creation-Date: 2013-04-05 20:55+0300\n"
12 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14 "Language-Team: LANGUAGE <LL@li.org>\n"
15 "Language: \n"
16 "MIME-Version: 1.0\n"
17 "Content-Type: text/plain; charset=UTF-8\n"
18 "Content-Transfer-Encoding: 8bit\n"
19 "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
20 "%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
21
22 msgid "Title"
23 msgstr "Π—Π°Π³ΠΎΠ»ΠΎΠ²ΠΎΠΊ"
24
25 msgid "Text"
26 msgstr "ВСкст"
27
28 msgid "Image"
29 msgstr "ΠšΠ°Ρ€Ρ‚ΠΈΠ½ΠΊΠ°"
30
31 msgid "Post"
32 msgstr "ΠžΡ‚ΠΏΡ€Π°Π²ΠΈΡ‚ΡŒ"
33
34 msgid "Tags"
35 msgstr "Π’Π΅Π³ΠΈ"
36
37 msgid "View"
38 msgstr "ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€" No newline at end of file
@@ -1,8 +1,8 b''
1 1 from django import forms
2 2
3 3
4 4 class NewThreadForm(forms.Form):
5 title = forms.CharField(max_length = 100)
6 text = forms.CharField(widget = forms.Textarea)
5 title = forms.CharField(max_length=100)
6 text = forms.CharField(widget=forms.Textarea)
7 7 image = forms.ImageField()
8 8 tags = forms.CharField(max_length=100)
@@ -1,97 +1,101 b''
1 1 from django.db import models
2 2 from django.utils import timezone
3 3
4 4 from neboard import settings
5 5
6 6 import thumbs
7 7
8 8 NO_PARENT = -1
9 9 NO_IP = '0.0.0.0'
10 10 UNKNOWN_UA = ''
11 11
12 12
13 13 class PostManager(models.Manager):
14 14 def create_post(self, title, text, image=None, parent_id=NO_PARENT,
15 ip=NO_IP):
15 ip=NO_IP, tags=None):
16 16 post = self.create(title=title,
17 17 text=text,
18 18 pub_time=timezone.now(),
19 19 parent=parent_id,
20 20 image=image,
21 21 poster_ip=ip,
22 22 poster_user_agent=UNKNOWN_UA)
23 23
24 if tags:
25 for tag in tags:
26 post.tags.add(tag)
27
24 28 return post
25 29
26 30 def delete_post(self, post):
27 31 children = self.filter(parent=post.id)
28 32 for child in children:
29 33 self.delete_post(child)
30 34 post.delete()
31 35
32 36 def delete_posts_by_ip(self, ip):
33 37 posts = self.filter(poster_ip=ip)
34 38 for post in posts:
35 39 self.delete_post(post)
36 40
37 41 def get_threads(self, tag=None):
38 42 if tag is None:
39 43 threads = self.filter(parent=NO_PARENT)
40 44 else:
41 45 threads = self.filter(parent=NO_PARENT, tag=tag)
42 46
43 47 return threads
44 48
45 49 def get_thread(self, opening_post_id):
46 50 opening_post = self.get(id=opening_post_id)
47 51 replies = self.filter(parent=opening_post_id)
48 52
49 53 thread = [opening_post]
50 54 thread.extend(replies)
51 55
52 56 return thread
53 57
54 58 def exists(self, post_id):
55 59 posts = self.filter(id=post_id)
56 60
57 61 return len(posts) > 0
58 62
59 63
60 64 class Tag(models.Model):
61 65 """
62 66 A tag is a text node assigned to the post. The tag serves as a board
63 67 section. There can be multiple tags for each message
64 68 """
65 69
66 70 name = models.CharField(max_length=100)
67 71
68 72
69 73 class Post(models.Model):
70 74 """A post is a message."""
71 75
72 76 objects = PostManager()
73 77
74 78 title = models.CharField(max_length=100)
75 79 pub_time = models.DateTimeField()
76 80 text = models.TextField()
77 81 image = thumbs.ImageWithThumbsField(upload_to='images/',
78 82 blank=True, sizes=((200, 150),))
79 83 poster_ip = models.IPAddressField()
80 84 poster_user_agent = models.TextField()
81 85 parent = models.BigIntegerField()
82 86 tags = models.ManyToManyField(Tag)
83 87
84 88 def __unicode__(self):
85 89 return self.title + ' (' + self.text + ')'
86 90
87 91
88 92 class Admin(models.Model):
89 93 """
90 94 Model for admin users
91 95 """
92 96 name = models.CharField(max_length=100)
93 97 password = models.CharField(max_length=100)
94 98
95 99 def __unicode__(self):
96 100 return self.name + '/' + '*' * len(self.password)
97 101
@@ -1,39 +1,53 b''
1 1 html {
2 2 background: #333;
3 color: #ffffff;
3 4 }
4 5
5 6 #admin_panel {
6 7 background: #FF0000;
7 8 color: #00FF00
8 9 }
9 10
10 11 .title {
11 12 font-weight: bold;
12 13 color: #ffcc00;
13 14 }
14 15
15 .text {
16 color: #ffffff;
17 }
18
19 16 .post-form {
20 17 text-align: left;
21 18 color: #ffffff;
19 display: table;
20 }
21
22 .form-row {
23 display: table-row;
24 }
25
26 .form-input {
27 display: table-cell;
22 28 }
23 29
24 30 .link {
25 31 color: #33bb33;
26 32 }
27 33
28 34 .link:hover {
29 35 color: #00ff00;
30 36 }
31 37
32 38 .post_id {
33 39 color: #ffffff;
34 40 }
35 41
36 42 .block {
37 43 display: inline-block;
38 44 vertical-align: top;
45 }
46
47 .tag {
48 color: #bb7766;
49 }
50
51 .tag:hover {
52 color: #dd8888;
39 53 } No newline at end of file
@@ -1,97 +1,102 b''
1 1 from django.utils.unittest import TestCase
2 2 from django.test.client import Client
3 3
4 4 import boards
5 5
6 from boards.models import Post, Admin
6 from boards.models import Post, Admin, Tag
7 7
8 8
9 9 class BoardTests(TestCase):
10 10 def _create_post(self):
11 11 return Post.objects.create_post(title='title',
12 12 text='text')
13 13
14 14 def test_post_add(self):
15 15 post = self._create_post()
16 16
17 17 self.assertIsNotNone(post)
18 18 self.assertEqual(boards.models.NO_PARENT, post.parent)
19 19
20 20 def test_delete_post(self):
21 21 post = self._create_post()
22 22 post_id = post.id
23 23
24 24 Post.objects.delete_post(post)
25 25
26 26 self.assertFalse(Post.objects.exists(post_id))
27 27
28 28 def test_delete_posts_by_ip(self):
29 29 post = self._create_post()
30 30 post_id = post.id
31 31
32 32 Post.objects.delete_posts_by_ip('0.0.0.0')
33 33
34 34 self.assertFalse(Post.objects.exists(post_id))
35 35
36 36 # Authentication tests
37 37
38 38 def _create_test_user(self):
39 39 admin = Admin(name='test_username12313584353165',
40 40 password='test_userpassword135135512')
41 41
42 42 admin.save()
43 43 return admin
44 44
45 45 def test_admin_login(self):
46 46 client = Client()
47 47
48 48 self.assertFalse('admin' in client.session)
49 49
50 50 admin = self._create_test_user()
51 51
52 52 response = client.post('/login',
53 53 {'name': admin.name, 'password': admin.password})
54 54
55 55 # it means that login passed and user are redirected to another page
56 56 self.assertEqual(302, response.status_code)
57 57
58 58 self.assertTrue('admin' in client.session)
59 59 self.assertTrue(client.session['admin'])
60 60
61 61 admin.delete()
62 62
63 63 wrong_name = 'sd2f1s3d21fs3d21f'
64 64 wrong_password = 'sd2f1s3d21fs3d21fsdfsd'
65 65
66 66 client.post('/login', {'name': wrong_name, 'password': wrong_password})
67 67 self.assertFalse(client.session['admin'])
68 68
69 69 def test_admin_logout(self):
70 70 client = Client()
71 71
72 72 self.assertFalse('admin' in client.session)
73 73
74 74 admin = self._create_test_user()
75 75
76 76 client.post('/login',
77 77 {'name': admin.name, 'password': admin.password})
78 78
79 79 self.assertTrue(client.session['admin'])
80 80
81 81 client.get('/logout')
82 82
83 83 self.assertFalse(client.session['admin'])
84 84
85 85 admin.delete()
86 86
87 87 def test_get_thread(self):
88 88 opening_post = self._create_post()
89 89 op_id = opening_post.id
90 90
91 91 for i in range(0, 2):
92 92 Post.objects.create_post('title', 'text',
93 93 parent_id=op_id)
94 94
95 95 thread = Post.objects.get_thread(op_id)
96 96
97 97 self.assertEqual(3, len(thread))
98
99 def test_create_post_with_tag(self):
100 tag = Tag.objects.create(name='test_tag')
101 post = Post.objects.create_post(title='title', text='text', tags=[tag])
102 self.assertIsNotNone(post) No newline at end of file
@@ -1,21 +1,21 b''
1 1 from django.conf.urls import patterns, url
2 2 from boards import views
3 3
4 4 urlpatterns = patterns('',
5 5
6 6 # /boards/
7 7 url(r'^$', views.index, name = 'index'),
8 8
9 9 # login page
10 10 url(r'^login$', views.login, name='login'),
11 11 # logout page
12 12 url(r'^logout$', views.logout, name='logout'),
13 13
14 14 # /boards/tag/
15 url(r'^tag/(?P<tag>\w+)/$', views.tag, name = 'tag'),
15 url(r'^tag/(?P<tag_name>\w+)/$', views.tag, name = 'tag'),
16 16 # /boards/post_id/
17 url(r'^thread/(?P<id>\w+)/$', views.thread, name = 'thread'),
17 url(r'^thread/(?P<post_id>\w+)/$', views.thread, name = 'thread'),
18 18 # /boards/tag/post/
19 19 url(r'^post.html$', views.new_post,
20 20 name='post'),
21 21 )
@@ -1,107 +1,125 b''
1 1 from django.template import RequestContext
2 2 from boards import forms
3 3 import boards
4 4 from boards.forms import NewThreadForm
5 from boards.models import Post, Admin
5 from boards.models import Post, Admin, Tag
6 6 from django.shortcuts import render, get_list_or_404, redirect
7 7 from django.http import HttpResponseRedirect, Http404
8 8
9 9
10 10 def index(request):
11 11 context = RequestContext(request)
12 12
13 13 if request.method == 'POST':
14 14 return new_post(request)
15 15 else:
16 16 threads = Post.objects.get_threads()
17 17
18 18 context['threads'] = None if len(threads) == 0 else threads
19 19 context['form'] = forms.NewThreadForm()
20 20
21 21 return render(request, 'posting_general.html',
22 22 context)
23 23
24 24
25 25 def new_post(request, thread_id=boards.models.NO_PARENT):
26 26 """Add a new post (in thread or as a reply)."""
27 27
28 28 form = NewThreadForm(request.POST, request.FILES)
29 29
30 30 title = request.POST['title']
31 31 text = request.POST['text']
32 32
33 33 if 'image' in request.FILES.keys():
34 34 image = request.FILES['image']
35 35 else:
36 36 image = None
37 37
38 38 ip = request.META['REMOTE_ADDR']
39 39
40 # TODO Get tags list, download image (if link is given)
40 tags = []
41 if thread_id == boards.models.NO_PARENT:
42 tag_strings = request.POST['tags']
43
44 if tag_strings:
45 tag_strings = tag_strings.split(',')
46 for tag_name in tag_strings:
47 tag_name = tag_name.strip()
48 if len(tag_name) > 0:
49 tag, created = Tag.objects.get_or_create(name=tag_name)
50 tags.append(tag)
51
52 # TODO Add a possibility to define a link image instead of an image file.
53 # If a link is given, download the image automatically.
41 54
42 55 post = Post.objects.create_post(title=title, text=text, ip=ip,
43 parent_id=thread_id, image=image)
56 parent_id=thread_id, image=image,
57 tags=tags)
44 58
45 59 thread_to_show = (post.id if thread_id == boards.models.NO_PARENT else
46 60 thread_id)
47 return redirect(thread, id=thread_to_show)
61 return redirect(thread, post_id=thread_to_show)
48 62
49 63
50 def tag(request):
64 def tag(request, tag_name):
51 65 """Get all tag threads (posts without a parent)."""
52 66
53 tag_name = request.GET['tag']
54
55 threads = get_list_or_404(Post, tag=tag_name)
67 tag = Tag.objects.get(name=tag_name)
68 threads = get_list_or_404(Post, tags=tag)
56 69
57 context = RequestContext(request)
58 context['threads'] = None if len(threads) == 0 else threads
59 context['tag'] = tag_name
70 if request.method == 'POST':
71 return new_post(request)
72 else:
73 context = RequestContext(request)
74 context['threads'] = None if len(threads) == 0 else threads
75 context['tag'] = tag_name
60 76
61 return render(request, 'posting_general.html',
62 context)
77 context['form'] = forms.NewThreadForm(initial={'tags': tag_name})
78
79 return render(request, 'posting_general.html',
80 context)
63 81
64 82
65 def thread(request, id):
83 def thread(request, post_id):
66 84 """Get all thread posts"""
67 85
68 86 if request.method == 'POST':
69 return new_post(request, id)
87 return new_post(request, post_id)
70 88 else:
71 89 # TODO Show 404 if there is no such thread
72 posts = Post.objects.get_thread(id)
90 posts = Post.objects.get_thread(post_id)
73 91
74 92 context = RequestContext(request)
75 93 context['posts'] = posts
76 94
77 95 context['form'] = forms.NewThreadForm()
78 96
79 97 return render(request, 'thread.html', context)
80 98
81 99
82 100 def login(request):
83 101 """Log in as admin"""
84 102
85 103 if 'name' in request.POST and 'password' in request.POST:
86 104 request.session['admin'] = False
87 105
88 106 isAdmin = len(Admin.objects.filter(name=request.POST['name'],
89 107 password=request.POST[
90 108 'password'])) > 0
91 109
92 110 if isAdmin:
93 111 request.session['admin'] = True
94 112
95 113 response = HttpResponseRedirect('/')
96 114
97 115 else:
98 116 response = render(request, 'login.html', {'error': 'Login error'})
99 117 else:
100 118 response = render(request, 'login.html', {})
101 119
102 120 return response
103 121
104 122
105 123 def logout(request):
106 124 request.session['admin'] = False
107 125 return HttpResponseRedirect('/') No newline at end of file
@@ -1,34 +1,61 b''
1 1 {% extends "base.html" %}
2 2
3 {% load i18n %}
4
3 5 {% block content %}
4 6
5 7 {% if threads %}
6 8 {% for thread in threads %}
7 9 {% if thread.image %}
8 10 <div class="block">
9 11 <a href="{{ thread.image.url }}"><img
10 12 src="{{ thread.image.url_200x150 }}" />
11 13 </a>
12 14 </div>
13 15 {% endif %}
14 16 <div class="block">
15 17 <span class="title">{{ thread.title }}</span>
16 18 <a class="link" href="/thread/{{ thread.id }}/">
17 [View]</a><br />
18 <span class="text">{{ thread.text }}</span><br />
19 [{% trans "View" %}]</a><br />
20 {{ thread.text }}<br />
21 {% if thread.tags %}
22 <span class="tags">{% trans 'Tags' %}:
23 {% for tag in thread.tags.all %}
24 <a class="tag" href="/tag/{{ tag.name }}">
25 {{ tag.name }}</a>
26 {% endfor %}
27 </span>
28 {% endif %}
19 29 </div>
20 30 <hr />
21 31 {% endfor %}
22 32 {% else %}
23 <span class="text">No threads found.</span>
33 No threads found.
24 34 <hr />
25 35 {% endif %}
26 36
27 37 <div class="post-form">
28 <form enctype="multipart/form-data" method="post">{% csrf_token %}
29 {{ form.as_p }}
30 <input type="submit" value="Post!" />
31 </form>
38 <div class="post-form">
39 <form enctype="multipart/form-data" method="post">{% csrf_token %}
40 <div class="form-row">
41 <div class="form-input">{% trans 'Title' %}</div>
42 <div class="form-input">{{ form.title }}</div>
43 </div>
44 <div class="form-row">
45 <div class="form-input">{% trans 'Text' %}</div>
46 <div class="form-input">{{ form.text }}</div>
47 </div>
48 <div class="form-row">
49 <div class="form-input">{% trans 'Image' %}</div>
50 <div class="form-input">{{ form.image }}</div>
51 </div>
52 <div class="form-row">
53 <div class="form-input">{% trans 'Tags' %}</div>
54 <div class="form-input">{{ form.tags }}</div>
55 </div>
56 <input type="submit" value="{% trans 'Post' %}" />
57 </form>
58 </div>
32 59 </div>
33 60
34 61 {% endblock %} No newline at end of file
@@ -1,33 +1,54 b''
1 1 {% extends "base.html" %}
2 2
3 {% load i18n %}
4
3 5 {% block content %}
4 6
5 7 {% if posts %}
6 8 {% for post in posts %}
7 9 {% if post.image %}
8 10 <div class="block">
9 11 <a href="{{ post.image.url }}"><img
10 12 src="{{ post.image.url_200x150 }}" />
11 13 </a>
12 14 </div>
13 15 {% endif %}
14 16 <div class="block">
15 17 <span class="title">{{ post.title }}</span>
16 18 <span class="post_id">(#{{ post.id }})</span><br />
17 <span class="text">{{ post.text }}</span><br />
19 {{ post.text }}<br />
20 {% if post.tags %}
21 <span class="tags">{% trans 'Tags' %}:
22 {% for tag in post.tags.all %}
23 <a class="tag" href="/tag/{{ tag.name }}">
24 {{ tag.name }}</a>
25 {% endfor %}
26 </span>
27 {% endif %}
18 28 </div>
19 29 <hr />
20 30 {% endfor %}
21 31 {% else %}
22 32 No threads found.
23 33 <hr />
24 34 {% endif %}
25 35
26 36 <div class="post-form">
27 37 <form enctype="multipart/form-data" method="post">{% csrf_token %}
28 {{ form.as_p }}
29 <input type="submit" value="Post!" />
38 <div class="form-row">
39 <div class="form-input">{% trans 'Title' %}</div>
40 <div class="form-input">{{ form.title }}</div>
41 </div>
42 <div class="form-row">
43 <div class="form-input">{% trans 'Text' %}</div>
44 <div class="form-input">{{ form.text }}</div>
45 </div>
46 <div class="form-row">
47 <div class="form-input">{% trans 'Image' %}</div>
48 <div class="form-input">{{ form.image }}</div>
49 </div>
50 <input type="submit" value="{% trans 'Post' %}" />
30 51 </form>
31 52 </div>
32 53
33 54 {% endblock %} No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now