##// END OF EJS Templates
Merged with default branch
neko259 -
r1543:9a0b5221 merge decentral
parent child Browse files
Show More
@@ -1,124 +1,127 b''
1 from django.contrib import admin
1 from django.contrib import admin
2 from django.utils.translation import ugettext_lazy as _
2 from django.utils.translation import ugettext_lazy as _
3 from django.core.urlresolvers import reverse
3 from django.core.urlresolvers import reverse
4 from boards.models import Post, Tag, Ban, Thread, Banner, PostImage, KeyPair, GlobalId
4 from boards.models import Post, Tag, Ban, Thread, Banner, PostImage, KeyPair, GlobalId
5
5
6
6
7 @admin.register(Post)
7 @admin.register(Post)
8 class PostAdmin(admin.ModelAdmin):
8 class PostAdmin(admin.ModelAdmin):
9
9
10 list_display = ('id', 'title', 'text', 'poster_ip', 'linked_images')
10 list_display = ('id', 'title', 'text', 'poster_ip', 'linked_images')
11 list_filter = ('pub_time',)
11 list_filter = ('pub_time',)
12 search_fields = ('id', 'title', 'text', 'poster_ip')
12 search_fields = ('id', 'title', 'text', 'poster_ip')
13 exclude = ('referenced_posts', 'refmap')
13 exclude = ('referenced_posts', 'refmap', 'images')
14 readonly_fields = ('poster_ip', 'threads', 'thread', 'linked_images',
14 readonly_fields = ('poster_ip', 'threads', 'thread', 'linked_images',
15 'attachments', 'uid', 'url', 'pub_time', 'opening')
15 'attachments', 'uid', 'url', 'pub_time', 'opening')
16
16
17 def ban_poster(self, request, queryset):
17 def ban_poster(self, request, queryset):
18 bans = 0
18 bans = 0
19 for post in queryset:
19 for post in queryset:
20 poster_ip = post.poster_ip
20 poster_ip = post.poster_ip
21 ban, created = Ban.objects.get_or_create(ip=poster_ip)
21 ban, created = Ban.objects.get_or_create(ip=poster_ip)
22 if created:
22 if created:
23 bans += 1
23 bans += 1
24 self.message_user(request, _('{} posters were banned').format(bans))
24 self.message_user(request, _('{} posters were banned').format(bans))
25
25
26 def ban_with_hiding(self, request, queryset):
26 def ban_with_hiding(self, request, queryset):
27 bans = 0
27 bans = 0
28 hidden = 0
28 hidden = 0
29 for post in queryset:
29 for post in queryset:
30 poster_ip = post.poster_ip
30 poster_ip = post.poster_ip
31 ban, created = Ban.objects.get_or_create(ip=poster_ip)
31 ban, created = Ban.objects.get_or_create(ip=poster_ip)
32 if created:
32 if created:
33 bans += 1
33 bans += 1
34 posts = Post.objects.filter(poster_ip=poster_ip, id__gte=post.id)
34 posts = Post.objects.filter(poster_ip=poster_ip, id__gte=post.id)
35 hidden += posts.count()
35 hidden += posts.count()
36 posts.update(hidden=True)
36 posts.update(hidden=True)
37 self.message_user(request, _('{} posters were banned, {} messages were hidden').format(bans, hidden))
37 self.message_user(request, _('{} posters were banned, {} messages were hidden').format(bans, hidden))
38
38
39 def linked_images(self, obj: Post):
39 def linked_images(self, obj: Post):
40 images = obj.images.all()
40 images = obj.images.all()
41 image_urls = ['<a href="{}">{}</a>'.format(reverse('admin:%s_%s_change' %(image._meta.app_label, image._meta.model_name), args=[image.id]), image.hash) for image in images]
41 image_urls = ['<a href="{}"><img src="{}" /></a>'.format(
42 reverse('admin:%s_%s_change' % (image._meta.app_label,
43 image._meta.model_name),
44 args=[image.id]), image.image.url_200x150) for image in images]
42 return ', '.join(image_urls)
45 return ', '.join(image_urls)
43 linked_images.allow_tags = True
46 linked_images.allow_tags = True
44
47
45
48
46 actions = ['ban_poster', 'ban_with_hiding']
49 actions = ['ban_poster', 'ban_with_hiding']
47
50
48
51
49 @admin.register(Tag)
52 @admin.register(Tag)
50 class TagAdmin(admin.ModelAdmin):
53 class TagAdmin(admin.ModelAdmin):
51
54
52 def thread_count(self, obj: Tag) -> int:
55 def thread_count(self, obj: Tag) -> int:
53 return obj.get_thread_count()
56 return obj.get_thread_count()
54
57
55 def display_children(self, obj: Tag):
58 def display_children(self, obj: Tag):
56 return ', '.join([str(child) for child in obj.get_children().all()])
59 return ', '.join([str(child) for child in obj.get_children().all()])
57
60
58 def save_model(self, request, obj, form, change):
61 def save_model(self, request, obj, form, change):
59 super().save_model(request, obj, form, change)
62 super().save_model(request, obj, form, change)
60 for thread in obj.get_threads().all():
63 for thread in obj.get_threads().all():
61 thread.refresh_tags()
64 thread.refresh_tags()
62 list_display = ('name', 'thread_count', 'display_children')
65 list_display = ('name', 'thread_count', 'display_children')
63 search_fields = ('name',)
66 search_fields = ('name',)
64
67
65
68
66 @admin.register(Thread)
69 @admin.register(Thread)
67 class ThreadAdmin(admin.ModelAdmin):
70 class ThreadAdmin(admin.ModelAdmin):
68
71
69 def title(self, obj: Thread) -> str:
72 def title(self, obj: Thread) -> str:
70 return obj.get_opening_post().get_title()
73 return obj.get_opening_post().get_title()
71
74
72 def reply_count(self, obj: Thread) -> int:
75 def reply_count(self, obj: Thread) -> int:
73 return obj.get_reply_count()
76 return obj.get_reply_count()
74
77
75 def ip(self, obj: Thread):
78 def ip(self, obj: Thread):
76 return obj.get_opening_post().poster_ip
79 return obj.get_opening_post().poster_ip
77
80
78 def display_tags(self, obj: Thread):
81 def display_tags(self, obj: Thread):
79 return ', '.join([str(tag) for tag in obj.get_tags().all()])
82 return ', '.join([str(tag) for tag in obj.get_tags().all()])
80
83
81 def op(self, obj: Thread):
84 def op(self, obj: Thread):
82 return obj.get_opening_post_id()
85 return obj.get_opening_post_id()
83
86
84 # Save parent tags when editing tags
87 # Save parent tags when editing tags
85 def save_related(self, request, form, formsets, change):
88 def save_related(self, request, form, formsets, change):
86 super().save_related(request, form, formsets, change)
89 super().save_related(request, form, formsets, change)
87 form.instance.refresh_tags()
90 form.instance.refresh_tags()
88 list_display = ('id', 'op', 'title', 'reply_count', 'status', 'ip',
91 list_display = ('id', 'op', 'title', 'reply_count', 'status', 'ip',
89 'display_tags')
92 'display_tags')
90 list_filter = ('bump_time', 'status')
93 list_filter = ('bump_time', 'status')
91 search_fields = ('id', 'title')
94 search_fields = ('id', 'title')
92 filter_horizontal = ('tags',)
95 filter_horizontal = ('tags',)
93
96
94
97
95 @admin.register(KeyPair)
98 @admin.register(KeyPair)
96 class KeyPairAdmin(admin.ModelAdmin):
99 class KeyPairAdmin(admin.ModelAdmin):
97 list_display = ('public_key', 'primary')
100 list_display = ('public_key', 'primary')
98 list_filter = ('primary',)
101 list_filter = ('primary',)
99 search_fields = ('public_key',)
102 search_fields = ('public_key',)
100
103
101
104
102 @admin.register(Ban)
105 @admin.register(Ban)
103 class BanAdmin(admin.ModelAdmin):
106 class BanAdmin(admin.ModelAdmin):
104 list_display = ('ip', 'can_read')
107 list_display = ('ip', 'can_read')
105 list_filter = ('can_read',)
108 list_filter = ('can_read',)
106 search_fields = ('ip',)
109 search_fields = ('ip',)
107
110
108
111
109 @admin.register(Banner)
112 @admin.register(Banner)
110 class BannerAdmin(admin.ModelAdmin):
113 class BannerAdmin(admin.ModelAdmin):
111 list_display = ('title', 'text')
114 list_display = ('title', 'text')
112
115
113
116
114 @admin.register(PostImage)
117 @admin.register(PostImage)
115 class PostImageAdmin(admin.ModelAdmin):
118 class PostImageAdmin(admin.ModelAdmin):
116 search_fields = ('alias',)
119 search_fields = ('alias',)
117
120
118
121
119 @admin.register(GlobalId)
122 @admin.register(GlobalId)
120 class GlobalIdAdmin(admin.ModelAdmin):
123 class GlobalIdAdmin(admin.ModelAdmin):
121 def is_linked(self, obj):
124 def is_linked(self, obj):
122 return Post.objects.filter(global_id=obj).exists()
125 return Post.objects.filter(global_id=obj).exists()
123
126
124 list_display = ('__str__', 'is_linked',) No newline at end of file
127 list_display = ('__str__', 'is_linked',)
@@ -1,381 +1,410 b''
1 import uuid
1 import uuid
2
2
3 import re
3 import re
4 from boards import settings
4 from boards import settings
5 from boards.abstracts.tripcode import Tripcode
5 from boards.abstracts.tripcode import Tripcode
6 from boards.models import PostImage, Attachment, KeyPair, GlobalId
6 from boards.models import PostImage, Attachment, KeyPair, GlobalId
7 from boards.models.base import Viewable
7 from boards.models.base import Viewable
8 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
8 from boards.models.post.export import get_exporter, DIFF_TYPE_JSON
9 from boards.models.post.manager import PostManager
9 from boards.models.post.manager import PostManager
10 from boards.utils import datetime_to_epoch
10 from boards.utils import datetime_to_epoch
11 from django.core.exceptions import ObjectDoesNotExist
11 from django.core.exceptions import ObjectDoesNotExist
12 from django.core.urlresolvers import reverse
12 from django.core.urlresolvers import reverse
13 from django.db import models
13 from django.db import models
14 from django.db.models import TextField, QuerySet
14 from django.db.models import TextField, QuerySet
15 from django.template.defaultfilters import truncatewords, striptags
15 from django.template.defaultfilters import truncatewords, striptags
16 from django.template.loader import render_to_string
16 from django.template.loader import render_to_string
17
17
18 CSS_CLS_HIDDEN_POST = 'hidden_post'
18 CSS_CLS_HIDDEN_POST = 'hidden_post'
19 CSS_CLS_DEAD_POST = 'dead_post'
19 CSS_CLS_DEAD_POST = 'dead_post'
20 CSS_CLS_ARCHIVE_POST = 'archive_post'
20 CSS_CLS_ARCHIVE_POST = 'archive_post'
21 CSS_CLS_POST = 'post'
21 CSS_CLS_POST = 'post'
22 CSS_CLS_MONOCHROME = 'monochrome'
22 CSS_CLS_MONOCHROME = 'monochrome'
23
23
24 TITLE_MAX_WORDS = 10
24 TITLE_MAX_WORDS = 10
25
25
26 APP_LABEL_BOARDS = 'boards'
26 APP_LABEL_BOARDS = 'boards'
27
27
28 BAN_REASON_AUTO = 'Auto'
28 BAN_REASON_AUTO = 'Auto'
29
29
30 IMAGE_THUMB_SIZE = (200, 150)
30 IMAGE_THUMB_SIZE = (200, 150)
31
31
32 TITLE_MAX_LENGTH = 200
32 TITLE_MAX_LENGTH = 200
33
33
34 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
34 REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]')
35 REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]')
35 REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]')
36 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
36 REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?')
37 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
37 REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]')
38
38
39 PARAMETER_TRUNCATED = 'truncated'
39 PARAMETER_TRUNCATED = 'truncated'
40 PARAMETER_TAG = 'tag'
40 PARAMETER_TAG = 'tag'
41 PARAMETER_OFFSET = 'offset'
41 PARAMETER_OFFSET = 'offset'
42 PARAMETER_DIFF_TYPE = 'type'
42 PARAMETER_DIFF_TYPE = 'type'
43 PARAMETER_CSS_CLASS = 'css_class'
43 PARAMETER_CSS_CLASS = 'css_class'
44 PARAMETER_THREAD = 'thread'
44 PARAMETER_THREAD = 'thread'
45 PARAMETER_IS_OPENING = 'is_opening'
45 PARAMETER_IS_OPENING = 'is_opening'
46 PARAMETER_POST = 'post'
46 PARAMETER_POST = 'post'
47 PARAMETER_OP_ID = 'opening_post_id'
47 PARAMETER_OP_ID = 'opening_post_id'
48 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
48 PARAMETER_NEED_OPEN_LINK = 'need_open_link'
49 PARAMETER_REPLY_LINK = 'reply_link'
49 PARAMETER_REPLY_LINK = 'reply_link'
50 PARAMETER_NEED_OP_DATA = 'need_op_data'
50 PARAMETER_NEED_OP_DATA = 'need_op_data'
51
51
52 POST_VIEW_PARAMS = (
52 POST_VIEW_PARAMS = (
53 'need_op_data',
53 'need_op_data',
54 'reply_link',
54 'reply_link',
55 'need_open_link',
55 'need_open_link',
56 'truncated',
56 'truncated',
57 'mode_tree',
57 'mode_tree',
58 'perms',
58 'perms',
59 'tree_depth',
59 'tree_depth',
60 )
60 )
61
61
62
62
63 class Post(models.Model, Viewable):
63 class Post(models.Model, Viewable):
64 """A post is a message."""
64 """A post is a message."""
65
65
66 objects = PostManager()
66 objects = PostManager()
67
67
68 class Meta:
68 class Meta:
69 app_label = APP_LABEL_BOARDS
69 app_label = APP_LABEL_BOARDS
70 ordering = ('id',)
70 ordering = ('id',)
71
71
72 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
72 title = models.CharField(max_length=TITLE_MAX_LENGTH, null=True, blank=True)
73 pub_time = models.DateTimeField()
73 pub_time = models.DateTimeField()
74 text = TextField(blank=True, null=True)
74 text = TextField(blank=True, null=True)
75 _text_rendered = TextField(blank=True, null=True, editable=False)
75 _text_rendered = TextField(blank=True, null=True, editable=False)
76
76
77 images = models.ManyToManyField(PostImage, null=True, blank=True,
77 images = models.ManyToManyField(PostImage, null=True, blank=True,
78 related_name='post_images', db_index=True)
78 related_name='post_images', db_index=True)
79 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
79 attachments = models.ManyToManyField(Attachment, null=True, blank=True,
80 related_name='attachment_posts')
80 related_name='attachment_posts')
81
81
82 poster_ip = models.GenericIPAddressField()
82 poster_ip = models.GenericIPAddressField()
83
83
84 # TODO This field can be removed cause UID is used for update now
84 # TODO This field can be removed cause UID is used for update now
85 last_edit_time = models.DateTimeField()
85 last_edit_time = models.DateTimeField()
86
86
87 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
87 referenced_posts = models.ManyToManyField('Post', symmetrical=False,
88 null=True,
88 null=True,
89 blank=True, related_name='refposts',
89 blank=True, related_name='refposts',
90 db_index=True)
90 db_index=True)
91 refmap = models.TextField(null=True, blank=True)
91 refmap = models.TextField(null=True, blank=True)
92 threads = models.ManyToManyField('Thread', db_index=True,
92 threads = models.ManyToManyField('Thread', db_index=True,
93 related_name='multi_replies')
93 related_name='multi_replies')
94 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
94 thread = models.ForeignKey('Thread', db_index=True, related_name='pt+')
95
95
96 url = models.TextField()
96 url = models.TextField()
97 uid = models.TextField(db_index=True)
97 uid = models.TextField(db_index=True)
98
98
99 # Global ID with author key. If the message was downloaded from another
99 # Global ID with author key. If the message was downloaded from another
100 # server, this indicates the server.
100 # server, this indicates the server.
101 global_id = models.OneToOneField(GlobalId, null=True, blank=True,
101 global_id = models.OneToOneField(GlobalId, null=True, blank=True,
102 on_delete=models.CASCADE)
102 on_delete=models.CASCADE)
103
103
104 tripcode = models.CharField(max_length=50, blank=True, default='')
104 tripcode = models.CharField(max_length=50, blank=True, default='')
105 opening = models.BooleanField(db_index=True)
105 opening = models.BooleanField(db_index=True)
106 hidden = models.BooleanField(default=False)
106 hidden = models.BooleanField(default=False)
107
107
108 def __str__(self):
108 def __str__(self):
109 return 'P#{}/{}'.format(self.id, self.get_title())
109 return 'P#{}/{}'.format(self.id, self.get_title())
110
110
111 def get_title(self) -> str:
111 def get_title(self) -> str:
112 return self.title
112 return self.title
113
113
114 def get_title_or_text(self):
114 def get_title_or_text(self):
115 title = self.get_title()
115 title = self.get_title()
116 if not title:
116 if not title:
117 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
117 title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS)
118
118
119 return title
119 return title
120
120
121 def build_refmap(self) -> None:
121 def build_refmap(self) -> None:
122 """
122 """
123 Builds a replies map string from replies list. This is a cache to stop
123 Builds a replies map string from replies list. This is a cache to stop
124 the server from recalculating the map on every post show.
124 the server from recalculating the map on every post show.
125 """
125 """
126
126
127 post_urls = [refpost.get_link_view()
127 post_urls = [refpost.get_link_view()
128 for refpost in self.referenced_posts.all()]
128 for refpost in self.referenced_posts.all()]
129
129
130 self.refmap = ', '.join(post_urls)
130 self.refmap = ', '.join(post_urls)
131
131
132 def is_referenced(self) -> bool:
132 def is_referenced(self) -> bool:
133 return self.refmap and len(self.refmap) > 0
133 return self.refmap and len(self.refmap) > 0
134
134
135 def is_opening(self) -> bool:
135 def is_opening(self) -> bool:
136 """
136 """
137 Checks if this is an opening post or just a reply.
137 Checks if this is an opening post or just a reply.
138 """
138 """
139
139
140 return self.opening
140 return self.opening
141
141
142 def get_absolute_url(self, thread=None):
142 def get_absolute_url(self, thread=None):
143 url = None
143 url = None
144
144
145 if thread is None:
145 if thread is None:
146 thread = self.get_thread()
146 thread = self.get_thread()
147
147
148 # Url is cached only for the "main" thread. When getting url
148 # Url is cached only for the "main" thread. When getting url
149 # for other threads, do it manually.
149 # for other threads, do it manually.
150 if self.url:
150 if self.url:
151 url = self.url
151 url = self.url
152
152
153 if url is None:
153 if url is None:
154 opening = self.is_opening()
154 opening = self.is_opening()
155 opening_id = self.id if opening else thread.get_opening_post_id()
155 opening_id = self.id if opening else thread.get_opening_post_id()
156 url = reverse('thread', kwargs={'post_id': opening_id})
156 url = reverse('thread', kwargs={'post_id': opening_id})
157 if not opening:
157 if not opening:
158 url += '#' + str(self.id)
158 url += '#' + str(self.id)
159
159
160 return url
160 return url
161
161
162 def get_thread(self):
162 def get_thread(self):
163 return self.thread
163 return self.thread
164
164
165 def get_thread_id(self):
165 def get_thread_id(self):
166 return self.thread_id
166 return self.thread_id
167
167
168 def get_threads(self) -> QuerySet:
168 def get_threads(self) -> QuerySet:
169 """
169 """
170 Gets post's thread.
170 Gets post's thread.
171 """
171 """
172
172
173 return self.threads
173 return self.threads
174
174
175 def _get_cache_key(self):
175 def _get_cache_key(self):
176 return [datetime_to_epoch(self.last_edit_time)]
176 return [datetime_to_epoch(self.last_edit_time)]
177
177
178 def get_view(self, *args, **kwargs) -> str:
178 def get_view(self, *args, **kwargs) -> str:
179 """
179 """
180 Renders post's HTML view. Some of the post params can be passed over
180 Renders post's HTML view. Some of the post params can be passed over
181 kwargs for the means of caching (if we view the thread, some params
181 kwargs for the means of caching (if we view the thread, some params
182 are same for every post and don't need to be computed over and over.
182 are same for every post and don't need to be computed over and over.
183 """
183 """
184
184
185 thread = self.get_thread()
185 thread = self.get_thread()
186
186
187 css_classes = [CSS_CLS_POST]
187 css_classes = [CSS_CLS_POST]
188 if thread.is_archived():
188 if thread.is_archived():
189 css_classes.append(CSS_CLS_ARCHIVE_POST)
189 css_classes.append(CSS_CLS_ARCHIVE_POST)
190 elif not thread.can_bump():
190 elif not thread.can_bump():
191 css_classes.append(CSS_CLS_DEAD_POST)
191 css_classes.append(CSS_CLS_DEAD_POST)
192 if self.is_hidden():
192 if self.is_hidden():
193 css_classes.append(CSS_CLS_HIDDEN_POST)
193 css_classes.append(CSS_CLS_HIDDEN_POST)
194 if thread.is_monochrome():
194 if thread.is_monochrome():
195 css_classes.append(CSS_CLS_MONOCHROME)
195 css_classes.append(CSS_CLS_MONOCHROME)
196
196
197 params = dict()
197 params = dict()
198 for param in POST_VIEW_PARAMS:
198 for param in POST_VIEW_PARAMS:
199 if param in kwargs:
199 if param in kwargs:
200 params[param] = kwargs[param]
200 params[param] = kwargs[param]
201
201
202 params.update({
202 params.update({
203 PARAMETER_POST: self,
203 PARAMETER_POST: self,
204 PARAMETER_IS_OPENING: self.is_opening(),
204 PARAMETER_IS_OPENING: self.is_opening(),
205 PARAMETER_THREAD: thread,
205 PARAMETER_THREAD: thread,
206 PARAMETER_CSS_CLASS: ' '.join(css_classes),
206 PARAMETER_CSS_CLASS: ' '.join(css_classes),
207 })
207 })
208
208
209 return render_to_string('boards/post.html', params)
209 return render_to_string('boards/post.html', params)
210
210
211 def get_search_view(self, *args, **kwargs):
211 def get_search_view(self, *args, **kwargs):
212 return self.get_view(need_op_data=True, *args, **kwargs)
212 return self.get_view(need_op_data=True, *args, **kwargs)
213
213
214 def get_first_image(self) -> PostImage:
214 def get_first_image(self) -> PostImage:
215 return self.images.earliest('id')
215 return self.images.earliest('id')
216
216
217 def set_global_id(self, key_pair=None):
217 def set_global_id(self, key_pair=None):
218 """
218 """
219 Sets global id based on the given key pair. If no key pair is given,
219 Sets global id based on the given key pair. If no key pair is given,
220 default one is used.
220 default one is used.
221 """
221 """
222
222
223 if key_pair:
223 if key_pair:
224 key = key_pair
224 key = key_pair
225 else:
225 else:
226 try:
226 try:
227 key = KeyPair.objects.get(primary=True)
227 key = KeyPair.objects.get(primary=True)
228 except KeyPair.DoesNotExist:
228 except KeyPair.DoesNotExist:
229 # Do not update the global id because there is no key defined
229 # Do not update the global id because there is no key defined
230 return
230 return
231 global_id = GlobalId(key_type=key.key_type,
231 global_id = GlobalId(key_type=key.key_type,
232 key=key.public_key,
232 key=key.public_key,
233 local_id=self.id)
233 local_id=self.id)
234 global_id.save()
234 global_id.save()
235
235
236 self.global_id = global_id
236 self.global_id = global_id
237
237
238 self.save(update_fields=['global_id'])
238 self.save(update_fields=['global_id'])
239
239
240 def get_pub_time_str(self):
240 def get_pub_time_str(self):
241 return str(self.pub_time)
241 return str(self.pub_time)
242
242
243 def get_replied_ids(self):
243 def get_replied_ids(self):
244 """
244 """
245 Gets ID list of the posts that this post replies.
245 Gets ID list of the posts that this post replies.
246 """
246 """
247
247
248 raw_text = self.get_raw_text()
248 raw_text = self.get_raw_text()
249
249
250 local_replied = REGEX_REPLY.findall(raw_text)
250 local_replied = REGEX_REPLY.findall(raw_text)
251 global_replied = []
251 global_replied = []
252 for match in REGEX_GLOBAL_REPLY.findall(raw_text):
252 for match in REGEX_GLOBAL_REPLY.findall(raw_text):
253 key_type = match[0]
253 key_type = match[0]
254 key = match[1]
254 key = match[1]
255 local_id = match[2]
255 local_id = match[2]
256
256
257 try:
257 try:
258 global_id = GlobalId.objects.get(key_type=key_type,
258 global_id = GlobalId.objects.get(key_type=key_type,
259 key=key, local_id=local_id)
259 key=key, local_id=local_id)
260 for post in Post.objects.filter(global_id=global_id).only('id'):
260 for post in Post.objects.filter(global_id=global_id).only('id'):
261 global_replied.append(post.id)
261 global_replied.append(post.id)
262 except GlobalId.DoesNotExist:
262 except GlobalId.DoesNotExist:
263 pass
263 pass
264 return local_replied + global_replied
264 return local_replied + global_replied
265
265
266 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
266 def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None,
267 include_last_update=False) -> str:
267 include_last_update=False) -> str:
268 """
268 """
269 Gets post HTML or JSON data that can be rendered on a page or used by
269 Gets post HTML or JSON data that can be rendered on a page or used by
270 API.
270 API.
271 """
271 """
272
272
273 return get_exporter(format_type).export(self, request,
273 return get_exporter(format_type).export(self, request,
274 include_last_update)
274 include_last_update)
275
275
276 def notify_clients(self, recursive=True):
276 def notify_clients(self, recursive=True):
277 """
277 """
278 Sends post HTML data to the thread web socket.
278 Sends post HTML data to the thread web socket.
279 """
279 """
280
280
281 if not settings.get_bool('External', 'WebsocketsEnabled'):
281 if not settings.get_bool('External', 'WebsocketsEnabled'):
282 return
282 return
283
283
284 thread_ids = list()
284 thread_ids = list()
285 for thread in self.get_threads().all():
285 for thread in self.get_threads().all():
286 thread_ids.append(thread.id)
286 thread_ids.append(thread.id)
287
287
288 thread.notify_clients()
288 thread.notify_clients()
289
289
290 if recursive:
290 if recursive:
291 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
291 for reply_number in re.finditer(REGEX_REPLY, self.get_raw_text()):
292 post_id = reply_number.group(1)
292 post_id = reply_number.group(1)
293
293
294 try:
294 try:
295 ref_post = Post.objects.get(id=post_id)
295 ref_post = Post.objects.get(id=post_id)
296
296
297 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
297 if ref_post.get_threads().exclude(id__in=thread_ids).exists():
298 # If post is in this thread, its thread was already notified.
298 # If post is in this thread, its thread was already notified.
299 # Otherwise, notify its thread separately.
299 # Otherwise, notify its thread separately.
300 ref_post.notify_clients(recursive=False)
300 ref_post.notify_clients(recursive=False)
301 except ObjectDoesNotExist:
301 except ObjectDoesNotExist:
302 pass
302 pass
303
303
304 def build_url(self):
304 def build_url(self):
305 self.url = self.get_absolute_url()
305 self.url = self.get_absolute_url()
306 self.save(update_fields=['url'])
306 self.save(update_fields=['url'])
307
307
308 def save(self, force_insert=False, force_update=False, using=None,
308 def save(self, force_insert=False, force_update=False, using=None,
309 update_fields=None):
309 update_fields=None):
310 new_post = self.id is None
310 new_post = self.id is None
311
311
312 self.uid = str(uuid.uuid4())
312 self.uid = str(uuid.uuid4())
313 if update_fields is not None and 'uid' not in update_fields:
313 if update_fields is not None and 'uid' not in update_fields:
314 update_fields += ['uid']
314 update_fields += ['uid']
315
315
316 if not new_post:
316 if not new_post:
317 for thread in self.get_threads().all():
317 for thread in self.get_threads().all():
318 thread.last_edit_time = self.last_edit_time
318 thread.last_edit_time = self.last_edit_time
319
319
320 thread.save(update_fields=['last_edit_time', 'status'])
320 thread.save(update_fields=['last_edit_time', 'status'])
321
321
322 super().save(force_insert, force_update, using, update_fields)
322 super().save(force_insert, force_update, using, update_fields)
323
323
324 if self.url is None:
324 if self.url is None:
325 self.build_url()
325 self.build_url()
326
326
327 def get_text(self) -> str:
327 def get_text(self) -> str:
328 return self._text_rendered
328 return self._text_rendered
329
329
330 def get_raw_text(self) -> str:
330 def get_raw_text(self) -> str:
331 return self.text
331 return self.text
332
332
333 def get_sync_text(self) -> str:
333 def get_sync_text(self) -> str:
334 """
334 """
335 Returns text applicable for sync. It has absolute post reflinks.
335 Returns text applicable for sync. It has absolute post reflinks.
336 """
336 """
337
337
338 replacements = dict()
338 replacements = dict()
339 for post_id in REGEX_REPLY.findall(self.get_raw_text()):
339 for post_id in REGEX_REPLY.findall(self.get_raw_text()):
340 absolute_post_id = str(Post.objects.get(id=post_id).global_id)
340 absolute_post_id = str(Post.objects.get(id=post_id).global_id)
341 replacements[post_id] = absolute_post_id
341 replacements[post_id] = absolute_post_id
342
342
343 text = self.get_raw_text() or ''
343 text = self.get_raw_text() or ''
344 for key in replacements:
344 for key in replacements:
345 text = text.replace('[post]{}[/post]'.format(key),
345 text = text.replace('[post]{}[/post]'.format(key),
346 '[post]{}[/post]'.format(replacements[key]))
346 '[post]{}[/post]'.format(replacements[key]))
347 text = text.replace('\r\n', '\n').replace('\r', '\n')
347 text = text.replace('\r\n', '\n').replace('\r', '\n')
348
348
349 return text
349 return text
350
350
351 def connect_threads(self, opening_posts):
351 def connect_threads(self, opening_posts):
352 for opening_post in opening_posts:
352 for opening_post in opening_posts:
353 threads = opening_post.get_threads().all()
353 threads = opening_post.get_threads().all()
354 for thread in threads:
354 for thread in threads:
355 if thread.can_bump():
355 if thread.can_bump():
356 thread.update_bump_status()
356 thread.update_bump_status()
357
357
358 thread.last_edit_time = self.last_edit_time
358 thread.last_edit_time = self.last_edit_time
359 thread.save(update_fields=['last_edit_time', 'status'])
359 thread.save(update_fields=['last_edit_time', 'status'])
360 self.threads.add(opening_post.get_thread())
360 self.threads.add(opening_post.get_thread())
361
361
362 def get_tripcode(self):
362 def get_tripcode(self):
363 if self.tripcode:
363 if self.tripcode:
364 return Tripcode(self.tripcode)
364 return Tripcode(self.tripcode)
365
365
366 def get_link_view(self):
366 def get_link_view(self):
367 """
367 """
368 Gets view of a reflink to the post.
368 Gets view of a reflink to the post.
369 """
369 """
370 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
370 result = '<a href="{}">&gt;&gt;{}</a>'.format(self.get_absolute_url(),
371 self.id)
371 self.id)
372 if self.is_opening():
372 if self.is_opening():
373 result = '<b>{}</b>'.format(result)
373 result = '<b>{}</b>'.format(result)
374
374
375 return result
375 return result
376
376
377 def is_hidden(self) -> bool:
377 def is_hidden(self) -> bool:
378 return self.hidden
378 return self.hidden
379
379
380 def set_hidden(self, hidden):
380 def set_hidden(self, hidden):
381 self.hidden = hidden
381 self.hidden = hidden
382
383
384 # SIGNALS (Maybe move to other module?)
385 @receiver(post_save, sender=Post)
386 def connect_replies(instance, **kwargs):
387 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
388 post_id = reply_number.group(1)
389
390 try:
391 referenced_post = Post.objects.get(id=post_id)
392
393 referenced_post.referenced_posts.add(instance)
394 referenced_post.last_edit_time = instance.pub_time
395 referenced_post.build_refmap()
396 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
397 except ObjectDoesNotExist:
398 pass
399
400
401 @receiver(post_save, sender=Post)
402 def connect_notifications(instance, **kwargs):
403 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
404 user_name = reply_number.group(1).lower()
405 Notification.objects.get_or_create(name=user_name, post=instance)
406
407
408 @receiver(pre_save, sender=Post)
409 def preparse_text(instance, **kwargs):
410 instance._text_rendered = get_parser().parse(instance.get_raw_text())
@@ -1,87 +1,88 b''
1 import re
1 import re
2 from boards.mdx_neboard import get_parser
2 from boards.mdx_neboard import get_parser
3
3
4 from boards.models import Post, GlobalId
4 from boards.models import Post, GlobalId
5 from boards.models.post import REGEX_NOTIFICATION
5 from boards.models.post import REGEX_NOTIFICATION
6 from boards.models.post import REGEX_REPLY, REGEX_GLOBAL_REPLY
6 from boards.models.post import REGEX_REPLY, REGEX_GLOBAL_REPLY
7 from boards.models.user import Notification
7 from boards.models.user import Notification
8 from django.db.models.signals import post_save, pre_save, pre_delete, \
8 from django.db.models.signals import post_save, pre_save, pre_delete, \
9 post_delete
9 post_delete
10 from django.dispatch import receiver
10 from django.dispatch import receiver
11 from django.utils import timezone
11 from django.utils import timezone
12
12
13
13
14 @receiver(post_save, sender=Post)
14 @receiver(post_save, sender=Post)
15 def connect_replies(instance, **kwargs):
15 def connect_replies(instance, **kwargs):
16 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
16 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
17 post_id = reply_number.group(1)
17 post_id = reply_number.group(1)
18
18
19 try:
19 try:
20 referenced_post = Post.objects.get(id=post_id)
20 referenced_post = Post.objects.get(id=post_id)
21
21
22 referenced_post.referenced_posts.add(instance)
22 if not referenced_post.referenced_posts.filter(id=instance.id).exists():
23 referenced_post.last_edit_time = instance.pub_time
23 referenced_post.referenced_posts.add(instance)
24 referenced_post.build_refmap()
24 referenced_post.last_edit_time = instance.pub_time
25 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
25 referenced_post.build_refmap()
26 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
26 except Post.ObjectDoesNotExist:
27 except Post.ObjectDoesNotExist:
27 pass
28 pass
28
29
29
30
30 @receiver(post_save, sender=Post)
31 @receiver(post_save, sender=Post)
31 def connect_global_replies(instance, **kwargs):
32 def connect_global_replies(instance, **kwargs):
32 for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()):
33 for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()):
33 key_type = reply_number.group(1)
34 key_type = reply_number.group(1)
34 key = reply_number.group(2)
35 key = reply_number.group(2)
35 local_id = reply_number.group(3)
36 local_id = reply_number.group(3)
36
37
37 try:
38 try:
38 global_id = GlobalId.objects.get(key_type=key_type, key=key,
39 global_id = GlobalId.objects.get(key_type=key_type, key=key,
39 local_id=local_id)
40 local_id=local_id)
40 referenced_post = Post.objects.get(global_id=global_id)
41 referenced_post = Post.objects.get(global_id=global_id)
41 referenced_post.referenced_posts.add(instance)
42 referenced_post.referenced_posts.add(instance)
42 referenced_post.last_edit_time = instance.pub_time
43 referenced_post.last_edit_time = instance.pub_time
43 referenced_post.build_refmap()
44 referenced_post.build_refmap()
44 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
45 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
45 except (GlobalId.DoesNotExist, Post.DoesNotExist):
46 except (GlobalId.DoesNotExist, Post.DoesNotExist):
46 pass
47 pass
47
48
48
49
49 @receiver(post_save, sender=Post)
50 @receiver(post_save, sender=Post)
50 def connect_notifications(instance, **kwargs):
51 def connect_notifications(instance, **kwargs):
51 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
52 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
52 user_name = reply_number.group(1).lower()
53 user_name = reply_number.group(1).lower()
53 Notification.objects.get_or_create(name=user_name, post=instance)
54 Notification.objects.get_or_create(name=user_name, post=instance)
54
55
55
56
56 @receiver(pre_save, sender=Post)
57 @receiver(pre_save, sender=Post)
57 def preparse_text(instance, **kwargs):
58 def preparse_text(instance, **kwargs):
58 instance._text_rendered = get_parser().parse(instance.get_raw_text())
59 instance._text_rendered = get_parser().parse(instance.get_raw_text())
59
60
60
61
61 @receiver(pre_delete, sender=Post)
62 @receiver(pre_delete, sender=Post)
62 def delete_images(instance, **kwargs):
63 def delete_images(instance, **kwargs):
63 for image in instance.images.all():
64 for image in instance.images.all():
64 image_refs_count = image.post_images.count()
65 image_refs_count = image.post_images.count()
65 if image_refs_count == 1:
66 if image_refs_count == 1:
66 image.delete()
67 image.delete()
67
68
68
69
69 @receiver(pre_delete, sender=Post)
70 @receiver(pre_delete, sender=Post)
70 def delete_attachments(instance, **kwargs):
71 def delete_attachments(instance, **kwargs):
71 for attachment in instance.attachments.all():
72 for attachment in instance.attachments.all():
72 attachment_refs_count = attachment.attachment_posts.count()
73 attachment_refs_count = attachment.attachment_posts.count()
73 if attachment_refs_count == 1:
74 if attachment_refs_count == 1:
74 attachment.delete()
75 attachment.delete()
75
76
76
77
77 @receiver(post_delete, sender=Post)
78 @receiver(post_delete, sender=Post)
78 def update_thread_on_delete(instance, **kwargs):
79 def update_thread_on_delete(instance, **kwargs):
79 thread = instance.get_thread()
80 thread = instance.get_thread()
80 thread.last_edit_time = timezone.now()
81 thread.last_edit_time = timezone.now()
81 thread.save()
82 thread.save()
82
83
83
84
84 @receiver(post_delete, sender=Post)
85 @receiver(post_delete, sender=Post)
85 def delete_global_id(instance, **kwargs):
86 def delete_global_id(instance, **kwargs):
86 if instance.global_id and instance.global_id.id:
87 if instance.global_id and instance.global_id.id:
87 instance.global_id.delete()
88 instance.global_id.delete()
General Comments 0
You need to be logged in to leave comments. Login now