##// END OF EJS Templates
Added image duplicate check
neko259 -
r527:5cd89122 default
parent child Browse files
Show More
@@ -0,0 +1,86 b''
1 # -*- coding: utf-8 -*-
2 from south.utils import datetime_utils as datetime
3 from south.db import db
4 from south.v2 import SchemaMigration
5 from django.db import models
6
7
8 class Migration(SchemaMigration):
9
10 def forwards(self, orm):
11 # Adding field 'Post.image_hash'
12 db.add_column(u'boards_post', 'image_hash',
13 self.gf('django.db.models.fields.CharField')(default='', max_length=36),
14 keep_default=False)
15
16
17 def backwards(self, orm):
18 # Deleting field 'Post.image_hash'
19 db.delete_column(u'boards_post', 'image_hash')
20
21
22 models = {
23 'boards.ban': {
24 'Meta': {'object_name': 'Ban'},
25 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
26 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
27 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
28 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
29 },
30 'boards.post': {
31 'Meta': {'object_name': 'Post'},
32 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
33 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
34 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
35 'image_hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
36 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
37 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
38 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
39 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
40 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
41 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
42 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
43 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
44 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
45 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
46 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
47 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Post']", 'null': 'True'}),
48 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
49 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
50 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
51 },
52 'boards.setting': {
53 'Meta': {'object_name': 'Setting'},
54 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
55 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
56 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
57 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
58 },
59 'boards.tag': {
60 'Meta': {'object_name': 'Tag'},
61 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
62 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
63 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
64 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
65 },
66 'boards.thread': {
67 'Meta': {'object_name': 'Thread'},
68 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
69 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
70 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
71 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
72 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
73 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
74 },
75 'boards.user': {
76 'Meta': {'object_name': 'User'},
77 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
78 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
79 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
80 'rank': ('django.db.models.fields.IntegerField', [], {}),
81 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
82 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
83 }
84 }
85
86 complete_apps = ['boards'] No newline at end of file
@@ -0,0 +1,91 b''
1 # -*- coding: utf-8 -*-
2 import hashlib
3
4 from south.utils import datetime_utils as datetime
5 from south.db import db
6 from south.v2 import DataMigration
7 from django.db import models
8
9 class Migration(DataMigration):
10
11 def forwards(self, orm):
12 # Note: Don't use "from appname.models import ModelName".
13 # Use orm.ModelName to refer to models in this application,
14 # and orm['appname.ModelName'] for models in other applications.
15 for post in orm.Post.objects.filter(image_width__gt=0):
16 md5 = hashlib.md5()
17 for chunk in post.image.chunks():
18 md5.update(chunk)
19 image_hash = md5.hexdigest()
20 post.image_hash = image_hash
21 post.save()
22
23 def backwards(self, orm):
24 "Write your backwards methods here."
25
26 models = {
27 'boards.ban': {
28 'Meta': {'object_name': 'Ban'},
29 'can_read': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
30 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
31 'ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
32 'reason': ('django.db.models.fields.CharField', [], {'default': "'Auto'", 'max_length': '200'})
33 },
34 'boards.post': {
35 'Meta': {'object_name': 'Post'},
36 '_text_rendered': ('django.db.models.fields.TextField', [], {}),
37 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
38 'image': ('boards.thumbs.ImageWithThumbsField', [], {'max_length': '100', 'blank': 'True'}),
39 'image_hash': ('django.db.models.fields.CharField', [], {'max_length': '36'}),
40 'image_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
41 'image_pre_height': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
42 'image_pre_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
43 'image_width': ('django.db.models.fields.IntegerField', [], {'default': '0'}),
44 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
45 'poster_ip': ('django.db.models.fields.GenericIPAddressField', [], {'max_length': '39'}),
46 'poster_user_agent': ('django.db.models.fields.TextField', [], {}),
47 'pub_time': ('django.db.models.fields.DateTimeField', [], {}),
48 'referenced_posts': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'rfp+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
49 'text': ('markupfield.fields.MarkupField', [], {'rendered_field': 'True'}),
50 'text_markup_type': ('django.db.models.fields.CharField', [], {'default': "'markdown'", 'max_length': '30'}),
51 'thread': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Post']", 'null': 'True'}),
52 'thread_new': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.Thread']", 'null': 'True'}),
53 'title': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
54 'user': ('django.db.models.fields.related.ForeignKey', [], {'default': 'None', 'to': "orm['boards.User']", 'null': 'True'})
55 },
56 'boards.setting': {
57 'Meta': {'object_name': 'Setting'},
58 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
59 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}),
60 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.User']"}),
61 'value': ('django.db.models.fields.CharField', [], {'max_length': '50'})
62 },
63 'boards.tag': {
64 'Meta': {'object_name': 'Tag'},
65 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
66 'linked': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
67 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
68 'threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tag+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Thread']"})
69 },
70 'boards.thread': {
71 'Meta': {'object_name': 'Thread'},
72 'archived': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
73 'bump_time': ('django.db.models.fields.DateTimeField', [], {}),
74 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
75 'last_edit_time': ('django.db.models.fields.DateTimeField', [], {}),
76 'replies': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'tre+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
77 'tags': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['boards.Tag']", 'symmetrical': 'False'})
78 },
79 'boards.user': {
80 'Meta': {'object_name': 'User'},
81 'fav_tags': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'to': "orm['boards.Tag']", 'null': 'True', 'blank': 'True'}),
82 'fav_threads': ('django.db.models.fields.related.ManyToManyField', [], {'blank': 'True', 'related_name': "'+'", 'null': 'True', 'symmetrical': 'False', 'to': "orm['boards.Post']"}),
83 u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
84 'rank': ('django.db.models.fields.IntegerField', [], {}),
85 'registration_time': ('django.db.models.fields.DateTimeField', [], {}),
86 'user_id': ('django.db.models.fields.CharField', [], {'max_length': '50'})
87 }
88 }
89
90 complete_apps = ['boards']
91 symmetrical = True
@@ -1,12 +1,15 b''
1 import re
1 import re
2 import time
3 import hashlib
4
2 from captcha.fields import CaptchaField
5 from captcha.fields import CaptchaField
3 from django import forms
6 from django import forms
4 from django.forms.util import ErrorList
7 from django.forms.util import ErrorList
5 from django.utils.translation import ugettext_lazy as _
8 from django.utils.translation import ugettext_lazy as _
6 import time
9
7 from boards.mdx_neboard import formatters
10 from boards.mdx_neboard import formatters
8 from boards.models.post import TITLE_MAX_LENGTH
11 from boards.models.post import TITLE_MAX_LENGTH
9 from boards.models import User
12 from boards.models import User, Post
10 from neboard import settings
13 from neboard import settings
11 from boards import utils
14 from boards import utils
12 import boards.settings as board_settings
15 import boards.settings as board_settings
@@ -19,6 +22,11 b" TEXT_PLACEHOLDER = _('''Type message her"
19 this. 2 new lines are required to start new paragraph.''')
22 this. 2 new lines are required to start new paragraph.''')
20 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
23 TAGS_PLACEHOLDER = _('tag1 several_words_tag')
21
24
25 ERROR_IMAGE_DUPLICATE = _('Such image was already posted')
26
27 LABEL_TITLE = _('Title')
28 LABEL_TEXT = _('Text')
29
22
30
23 class FormatPanel(forms.Textarea):
31 class FormatPanel(forms.Textarea):
24 def render(self, name, value, attrs=None):
32 def render(self, name, value, attrs=None):
@@ -74,10 +82,10 b' class NeboardForm(forms.Form):'
74 class PostForm(NeboardForm):
82 class PostForm(NeboardForm):
75
83
76 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
84 title = forms.CharField(max_length=TITLE_MAX_LENGTH, required=False,
77 label=_('Title'))
85 label=LABEL_TITLE)
78 text = forms.CharField(
86 text = forms.CharField(
79 widget=FormatPanel(attrs={ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER}),
87 widget=FormatPanel(attrs={ATTRIBUTE_PLACEHOLDER: TEXT_PLACEHOLDER}),
80 required=False, label=_('Text'))
88 required=False, label=LABEL_TEXT)
81 image = forms.ImageField(required=False, label=_('Image'))
89 image = forms.ImageField(required=False, label=_('Image'))
82
90
83 # This field is for spam prevention only
91 # This field is for spam prevention only
@@ -114,6 +122,14 b' class PostForm(NeboardForm):'
114 raise forms.ValidationError(
122 raise forms.ValidationError(
115 _('Image must be less than %s bytes')
123 _('Image must be less than %s bytes')
116 % str(board_settings.MAX_IMAGE_SIZE))
124 % str(board_settings.MAX_IMAGE_SIZE))
125
126 md5 = hashlib.md5()
127 for chunk in image.chunks():
128 md5.update(chunk)
129 image_hash = md5.hexdigest()
130 if Post.objects.filter(image_hash=image_hash).exists():
131 raise forms.ValidationError(ERROR_IMAGE_DUPLICATE)
132
117 return image
133 return image
118
134
119 def clean(self):
135 def clean(self):
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -7,7 +7,7 b' msgid ""'
7 msgstr ""
7 msgstr ""
8 "Project-Id-Version: PACKAGE VERSION\n"
8 "Project-Id-Version: PACKAGE VERSION\n"
9 "Report-Msgid-Bugs-To: \n"
9 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2014-01-13 10:53+0200\n"
10 "POT-Creation-Date: 2014-01-15 10:46+0200\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
11 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
12 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
13 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -34,7 +34,7 b' msgstr "\xd1\x80\xd0\xb0\xd0\xb7\xd1\x80\xd0\xb0\xd0\xb1\xd0\xbe\xd1\x82\xd1\x87\xd0\xb8\xd0\xba javascript"'
34 msgid "designer"
34 msgid "designer"
35 msgstr "дизайнер"
35 msgstr "дизайнер"
36
36
37 #: forms.py:18
37 #: forms.py:21
38 msgid ""
38 msgid ""
39 "Type message here. You can reply to message >>123 like\n"
39 "Type message here. You can reply to message >>123 like\n"
40 " this. 2 new lines are required to start new paragraph."
40 " this. 2 new lines are required to start new paragraph."
@@ -42,75 +42,79 b' msgstr ""'
42 "Введите сообщение здесь. Вы можете ответить на сообщение >>123 вот так. 2 "
42 "Введите сообщение здесь. Вы можете ответить на сообщение >>123 вот так. 2 "
43 "переноса строки обязательны для создания нового абзаца."
43 "переноса строки обязательны для создания нового абзаца."
44
44
45 #: forms.py:20
45 #: forms.py:23
46 msgid "tag1 several_words_tag"
46 msgid "tag1 several_words_tag"
47 msgstr "тег1 тег_из_нескольких_слов"
47 msgstr "тег1 тег_из_нескольких_слов"
48
48
49 #: forms.py:77
49 #: forms.py:25
50 msgid "Such image was already posted"
51 msgstr "Такое изображение уже было загружено"
52
53 #: forms.py:27
50 msgid "Title"
54 msgid "Title"
51 msgstr "Заголовок"
55 msgstr "Заголовок"
52
56
53 #: forms.py:80
57 #: forms.py:28
54 msgid "Text"
58 msgid "Text"
55 msgstr "Текст"
59 msgstr "Текст"
56
60
57 #: forms.py:81
61 #: forms.py:89
58 msgid "Image"
62 msgid "Image"
59 msgstr "Изображение"
63 msgstr "Изображение"
60
64
61 #: forms.py:84
65 #: forms.py:92
62 msgid "e-mail"
66 msgid "e-mail"
63 msgstr ""
67 msgstr ""
64
68
65 #: forms.py:95
69 #: forms.py:103
66 #, python-format
70 #, python-format
67 msgid "Title must have less than %s characters"
71 msgid "Title must have less than %s characters"
68 msgstr "Заголовок должен иметь меньше %s символов"
72 msgstr "Заголовок должен иметь меньше %s символов"
69
73
70 #: forms.py:104
74 #: forms.py:112
71 #, python-format
75 #, python-format
72 msgid "Text must have less than %s characters"
76 msgid "Text must have less than %s characters"
73 msgstr "Текст должен быть короче %s символов"
77 msgstr "Текст должен быть короче %s символов"
74
78
75 #: forms.py:115
79 #: forms.py:123
76 #, python-format
80 #, python-format
77 msgid "Image must be less than %s bytes"
81 msgid "Image must be less than %s bytes"
78 msgstr "Изображение должно быть менее %s байт"
82 msgstr "Изображение должно быть менее %s байт"
79
83
80 #: forms.py:142
84 #: forms.py:158
81 msgid "Either text or image must be entered."
85 msgid "Either text or image must be entered."
82 msgstr "Текст или картинка должны быть введены."
86 msgstr "Текст или картинка должны быть введены."
83
87
84 #: forms.py:155
88 #: forms.py:171
85 #, python-format
89 #, python-format
86 msgid "Wait %s seconds after last posting"
90 msgid "Wait %s seconds after last posting"
87 msgstr "Подождите %s секунд после последнего постинга"
91 msgstr "Подождите %s секунд после последнего постинга"
88
92
89 #: forms.py:171 templates/boards/tags.html:6 templates/boards/rss/post.html:10
93 #: forms.py:187 templates/boards/tags.html:6 templates/boards/rss/post.html:10
90 msgid "Tags"
94 msgid "Tags"
91 msgstr "Теги"
95 msgstr "Теги"
92
96
93 #: forms.py:179
97 #: forms.py:195
94 msgid "Inappropriate characters in tags."
98 msgid "Inappropriate characters in tags."
95 msgstr "Недопустимые символы в тегах."
99 msgstr "Недопустимые символы в тегах."
96
100
97 #: forms.py:207 forms.py:228
101 #: forms.py:223 forms.py:244
98 msgid "Captcha validation failed"
102 msgid "Captcha validation failed"
99 msgstr "Проверка капчи провалена"
103 msgstr "Проверка капчи провалена"
100
104
101 #: forms.py:234
105 #: forms.py:250
102 msgid "Theme"
106 msgid "Theme"
103 msgstr "Тема"
107 msgstr "Тема"
104
108
105 #: forms.py:239
109 #: forms.py:255
106 msgid "Enable moderation panel"
110 msgid "Enable moderation panel"
107 msgstr "Включить панель модерации"
111 msgstr "Включить панель модерации"
108
112
109 #: forms.py:254
113 #: forms.py:270
110 msgid "No such user found"
114 msgid "No such user found"
111 msgstr "Данный пользователь не найден"
115 msgstr "Данный пользователь не найден"
112
116
113 #: forms.py:268
117 #: forms.py:284
114 #, python-format
118 #, python-format
115 msgid "Wait %s minutes after last login"
119 msgid "Wait %s minutes after last login"
116 msgstr "Подождите %s минут после последнего входа"
120 msgstr "Подождите %s минут после последнего входа"
@@ -5,6 +5,8 b' from random import random'
5 import time
5 import time
6 import math
6 import math
7 import re
7 import re
8 import hashlib
9
8 from django.core.cache import cache
10 from django.core.cache import cache
9 from django.core.paginator import Paginator
11 from django.core.paginator import Paginator
10
12
@@ -232,6 +234,7 b' class Post(models.Model):'
232 height_field='image_height',
234 height_field='image_height',
233 preview_width_field='image_pre_width',
235 preview_width_field='image_pre_width',
234 preview_height_field='image_pre_height')
236 preview_height_field='image_pre_height')
237 image_hash = models.CharField(max_length=36)
235
238
236 poster_ip = models.GenericIPAddressField()
239 poster_ip = models.GenericIPAddressField()
237 poster_user_agent = models.TextField()
240 poster_user_agent = models.TextField()
@@ -265,6 +268,18 b' class Post(models.Model):'
265 def is_opening(self):
268 def is_opening(self):
266 return self.thread_new.get_replies()[0] == self
269 return self.thread_new.get_replies()[0] == self
267
270
271 def save(self, *args, **kwargs):
272 """
273 Save the model and compute the image hash
274 """
275
276 if not self.pk and self.image:
277 md5 = hashlib.md5()
278 for chunk in self.image.chunks():
279 md5.update(chunk)
280 self.image_hash = md5.hexdigest()
281 super(Post, self).save(*args, **kwargs)
282
268
283
269 class Thread(models.Model):
284 class Thread(models.Model):
270
285
@@ -160,6 +160,9 b' class ImageWithThumbsField(ImageField):'
160
160
161 """
161 """
162
162
163 preview_width_field = None
164 preview_height_field = None
165
163 def __init__(self, verbose_name=None, name=None, width_field=None,
166 def __init__(self, verbose_name=None, name=None, width_field=None,
164 height_field=None, sizes=None,
167 height_field=None, sizes=None,
165 preview_width_field=None, preview_height_field=None,
168 preview_width_field=None, preview_height_field=None,
General Comments 0
You need to be logged in to leave comments. Login now