##// END OF EJS Templates
Added support for partial import: when the reply is imported but the original post is not, the reply will be connected after the original post is ready
neko259 -
r1588:b69fb396 default
parent child Browse files
Show More
@@ -1,184 +1,193 b''
1 import logging
1 import logging
2
2
3 from datetime import datetime, timedelta, date
3 from datetime import datetime, timedelta, date
4 from datetime import time as dtime
4 from datetime import time as dtime
5
5
6 from django.db import models, transaction
6 from django.db import models, transaction
7 from django.utils import timezone
7 from django.utils import timezone
8 from django.dispatch import Signal
8
9
9 import boards
10 import boards
10
11
11 from boards.models.user import Ban
12 from boards.models.user import Ban
12 from boards.mdx_neboard import Parser
13 from boards.mdx_neboard import Parser
13 from boards.models import PostImage, Attachment
14 from boards.models import PostImage, Attachment
14 from boards import utils
15 from boards import utils
15
16
16 __author__ = 'neko259'
17 __author__ = 'neko259'
17
18
18 IMAGE_TYPES = (
19 IMAGE_TYPES = (
19 'jpeg',
20 'jpeg',
20 'jpg',
21 'jpg',
21 'png',
22 'png',
22 'bmp',
23 'bmp',
23 'gif',
24 'gif',
24 )
25 )
25
26
26 POSTS_PER_DAY_RANGE = 7
27 POSTS_PER_DAY_RANGE = 7
27 NO_IP = '0.0.0.0'
28 NO_IP = '0.0.0.0'
28
29
29
30
31 post_import_deps = Signal()
32
33
30 class PostManager(models.Manager):
34 class PostManager(models.Manager):
31 @transaction.atomic
35 @transaction.atomic
32 def create_post(self, title: str, text: str, file=None, thread=None,
36 def create_post(self, title: str, text: str, file=None, thread=None,
33 ip=NO_IP, tags: list=None, opening_posts: list=None,
37 ip=NO_IP, tags: list=None, opening_posts: list=None,
34 tripcode='', monochrome=False, images=[]):
38 tripcode='', monochrome=False, images=[]):
35 """
39 """
36 Creates new post
40 Creates new post
37 """
41 """
38
42
39 if thread is not None and thread.is_archived():
43 if thread is not None and thread.is_archived():
40 raise Exception('Cannot post into an archived thread')
44 raise Exception('Cannot post into an archived thread')
41
45
42 if not utils.is_anonymous_mode():
46 if not utils.is_anonymous_mode():
43 is_banned = Ban.objects.filter(ip=ip).exists()
47 is_banned = Ban.objects.filter(ip=ip).exists()
44 else:
48 else:
45 is_banned = False
49 is_banned = False
46
50
47 # TODO Raise specific exception and catch it in the views
51 # TODO Raise specific exception and catch it in the views
48 if is_banned:
52 if is_banned:
49 raise Exception("This user is banned")
53 raise Exception("This user is banned")
50
54
51 if not tags:
55 if not tags:
52 tags = []
56 tags = []
53 if not opening_posts:
57 if not opening_posts:
54 opening_posts = []
58 opening_posts = []
55
59
56 posting_time = timezone.now()
60 posting_time = timezone.now()
57 new_thread = False
61 new_thread = False
58 if not thread:
62 if not thread:
59 thread = boards.models.thread.Thread.objects.create(
63 thread = boards.models.thread.Thread.objects.create(
60 bump_time=posting_time, last_edit_time=posting_time,
64 bump_time=posting_time, last_edit_time=posting_time,
61 monochrome=monochrome)
65 monochrome=monochrome)
62 list(map(thread.tags.add, tags))
66 list(map(thread.tags.add, tags))
63 boards.models.thread.Thread.objects.process_oldest_threads()
67 boards.models.thread.Thread.objects.process_oldest_threads()
64 new_thread = True
68 new_thread = True
65
69
66 pre_text = Parser().preparse(text)
70 pre_text = Parser().preparse(text)
67
71
68 post = self.create(title=title,
72 post = self.create(title=title,
69 text=pre_text,
73 text=pre_text,
70 pub_time=posting_time,
74 pub_time=posting_time,
71 poster_ip=ip,
75 poster_ip=ip,
72 thread=thread,
76 thread=thread,
73 last_edit_time=posting_time,
77 last_edit_time=posting_time,
74 tripcode=tripcode,
78 tripcode=tripcode,
75 opening=new_thread)
79 opening=new_thread)
76 post.threads.add(thread)
80 post.threads.add(thread)
77
81
78 logger = logging.getLogger('boards.post.create')
82 logger = logging.getLogger('boards.post.create')
79
83
80 logger.info('Created post [{}] with text [{}] by {}'.format(post,
84 logger.info('Created post [{}] with text [{}] by {}'.format(post,
81 post.get_text(),post.poster_ip))
85 post.get_text(),post.poster_ip))
82
86
83 if file:
87 if file:
84 self._add_file_to_post(file, post)
88 self._add_file_to_post(file, post)
85 for image in images:
89 for image in images:
86 post.images.add(image)
90 post.images.add(image)
87
91
88 post.connect_threads(opening_posts)
92 post.connect_threads(opening_posts)
89 post.set_global_id()
93 post.set_global_id()
90
94
91 # Thread needs to be bumped only when the post is already created
95 # Thread needs to be bumped only when the post is already created
92 if not new_thread:
96 if not new_thread:
93 thread.last_edit_time = posting_time
97 thread.last_edit_time = posting_time
94 thread.bump()
98 thread.bump()
95 thread.save()
99 thread.save()
96
100
97 return post
101 return post
98
102
99 def delete_posts_by_ip(self, ip):
103 def delete_posts_by_ip(self, ip):
100 """
104 """
101 Deletes all posts of the author with same IP
105 Deletes all posts of the author with same IP
102 """
106 """
103
107
104 posts = self.filter(poster_ip=ip)
108 posts = self.filter(poster_ip=ip)
105 for post in posts:
109 for post in posts:
106 post.delete()
110 post.delete()
107
111
108 @utils.cached_result()
112 @utils.cached_result()
109 def get_posts_per_day(self) -> float:
113 def get_posts_per_day(self) -> float:
110 """
114 """
111 Gets average count of posts per day for the last 7 days
115 Gets average count of posts per day for the last 7 days
112 """
116 """
113
117
114 day_end = date.today()
118 day_end = date.today()
115 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
119 day_start = day_end - timedelta(POSTS_PER_DAY_RANGE)
116
120
117 day_time_start = timezone.make_aware(datetime.combine(
121 day_time_start = timezone.make_aware(datetime.combine(
118 day_start, dtime()), timezone.get_current_timezone())
122 day_start, dtime()), timezone.get_current_timezone())
119 day_time_end = timezone.make_aware(datetime.combine(
123 day_time_end = timezone.make_aware(datetime.combine(
120 day_end, dtime()), timezone.get_current_timezone())
124 day_end, dtime()), timezone.get_current_timezone())
121
125
122 posts_per_period = float(self.filter(
126 posts_per_period = float(self.filter(
123 pub_time__lte=day_time_end,
127 pub_time__lte=day_time_end,
124 pub_time__gte=day_time_start).count())
128 pub_time__gte=day_time_start).count())
125
129
126 ppd = posts_per_period / POSTS_PER_DAY_RANGE
130 ppd = posts_per_period / POSTS_PER_DAY_RANGE
127
131
128 return ppd
132 return ppd
129
133
130 @transaction.atomic
134 @transaction.atomic
131 def import_post(self, title: str, text: str, pub_time: str, global_id,
135 def import_post(self, title: str, text: str, pub_time: str, global_id,
132 opening_post=None, tags=list(), files=list(),
136 opening_post=None, tags=list(), files=list(),
133 tripcode=None, version=1):
137 tripcode=None, version=1):
134 is_opening = opening_post is None
138 is_opening = opening_post is None
135 if is_opening:
139 if is_opening:
136 thread = boards.models.thread.Thread.objects.create(
140 thread = boards.models.thread.Thread.objects.create(
137 bump_time=pub_time, last_edit_time=pub_time)
141 bump_time=pub_time, last_edit_time=pub_time)
138 list(map(thread.tags.add, tags))
142 list(map(thread.tags.add, tags))
139 else:
143 else:
140 thread = opening_post.get_thread()
144 thread = opening_post.get_thread()
141
145
142 post = self.create(title=title,
146 post = self.create(title=title,
143 text=text,
147 text=text,
144 pub_time=pub_time,
148 pub_time=pub_time,
145 poster_ip=NO_IP,
149 poster_ip=NO_IP,
146 last_edit_time=pub_time,
150 last_edit_time=pub_time,
147 global_id=global_id,
151 global_id=global_id,
148 opening=is_opening,
152 opening=is_opening,
149 thread=thread,
153 thread=thread,
150 tripcode=tripcode,
154 tripcode=tripcode,
151 version=version)
155 version=version)
152
156
153 for file in files:
157 for file in files:
154 self._add_file_to_post(file, post)
158 self._add_file_to_post(file, post)
155
159
156 post.threads.add(thread)
160 post.threads.add(thread)
157
161
162 url_to_post = '[post]{}[/post]'.format(str(global_id))
163 replies = self.filter(text__contains=url_to_post)
164 for reply in replies:
165 post_import_deps.send(reply.__class__)
166
158 @transaction.atomic
167 @transaction.atomic
159 def update_post(self, post, title: str, text: str, pub_time: str,
168 def update_post(self, post, title: str, text: str, pub_time: str,
160 tags=list(), files=list(), tripcode=None, version=1):
169 tags=list(), files=list(), tripcode=None, version=1):
161 post.title = title
170 post.title = title
162 post.text = text
171 post.text = text
163 post.pub_time = pub_time
172 post.pub_time = pub_time
164 post.tripcode = tripcode
173 post.tripcode = tripcode
165 post.version = version
174 post.version = version
166 post.save()
175 post.save()
167
176
168 post.clear_cache()
177 post.clear_cache()
169
178
170 post.images.clear()
179 post.images.clear()
171 post.attachments.clear()
180 post.attachments.clear()
172 for file in files:
181 for file in files:
173 self._add_file_to_post(file, post)
182 self._add_file_to_post(file, post)
174
183
175 thread = post.get_thread()
184 thread = post.get_thread()
176 thread.tags.clear()
185 thread.tags.clear()
177 list(map(thread.tags.add, tags))
186 list(map(thread.tags.add, tags))
178
187
179 def _add_file_to_post(self, file, post):
188 def _add_file_to_post(self, file, post):
180 file_type = file.name.split('.')[-1].lower()
189 file_type = file.name.split('.')[-1].lower()
181 if file_type in IMAGE_TYPES:
190 if file_type in IMAGE_TYPES:
182 post.images.add(PostImage.objects.create_with_hash(file))
191 post.images.add(PostImage.objects.create_with_hash(file))
183 else:
192 else:
184 post.attachments.add(Attachment.objects.create_with_hash(file))
193 post.attachments.add(Attachment.objects.create_with_hash(file))
@@ -1,89 +1,92 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, REGEX_REPLY,\
6 from boards.models.post import REGEX_REPLY, REGEX_GLOBAL_REPLY
6 REGEX_GLOBAL_REPLY
7 from boards.models.post.manager import post_import_deps
7 from boards.models.user import Notification
8 from boards.models.user import Notification
8 from django.db.models.signals import post_save, pre_save, pre_delete, \
9 from django.db.models.signals import post_save, pre_save, pre_delete, \
9 post_delete
10 post_delete
10 from django.dispatch import receiver
11 from django.dispatch import receiver
11 from django.utils import timezone
12 from django.utils import timezone
12
13
13
14
14 @receiver(post_save, sender=Post)
15 @receiver(post_save, sender=Post)
15 def connect_replies(instance, **kwargs):
16 def connect_replies(instance, **kwargs):
16 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
17 for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()):
17 post_id = reply_number.group(1)
18 post_id = reply_number.group(1)
18
19
19 try:
20 try:
20 referenced_post = Post.objects.get(id=post_id)
21 referenced_post = Post.objects.get(id=post_id)
21
22
22 if not referenced_post.referenced_posts.filter(
23 if not referenced_post.referenced_posts.filter(
23 id=instance.id).exists():
24 id=instance.id).exists():
24 referenced_post.referenced_posts.add(instance)
25 referenced_post.referenced_posts.add(instance)
25 referenced_post.last_edit_time = instance.pub_time
26 referenced_post.last_edit_time = instance.pub_time
26 referenced_post.build_refmap()
27 referenced_post.build_refmap()
27 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
28 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
28 except Post.DoesNotExist:
29 except Post.DoesNotExist:
29 pass
30 pass
30
31
31
32
32 @receiver(post_save, sender=Post)
33 @receiver(post_save, sender=Post)
34 @receiver(post_import_deps, sender=Post)
33 def connect_global_replies(instance, **kwargs):
35 def connect_global_replies(instance, **kwargs):
34 for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()):
36 for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()):
35 key_type = reply_number.group(1)
37 key_type = reply_number.group(1)
36 key = reply_number.group(2)
38 key = reply_number.group(2)
37 local_id = reply_number.group(3)
39 local_id = reply_number.group(3)
38
40
39 try:
41 try:
40 global_id = GlobalId.objects.get(key_type=key_type, key=key,
42 global_id = GlobalId.objects.get(key_type=key_type, key=key,
41 local_id=local_id)
43 local_id=local_id)
42 referenced_post = Post.objects.get(global_id=global_id)
44 referenced_post = Post.objects.get(global_id=global_id)
43 referenced_post.referenced_posts.add(instance)
45 referenced_post.referenced_posts.add(instance)
44 referenced_post.last_edit_time = instance.pub_time
46 referenced_post.last_edit_time = instance.pub_time
45 referenced_post.build_refmap()
47 referenced_post.build_refmap()
46 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
48 referenced_post.save(update_fields=['refmap', 'last_edit_time'])
47 except (GlobalId.DoesNotExist, Post.DoesNotExist):
49 except (GlobalId.DoesNotExist, Post.DoesNotExist):
48 pass
50 pass
49
51
50
52
51 @receiver(post_save, sender=Post)
53 @receiver(post_save, sender=Post)
52 def connect_notifications(instance, **kwargs):
54 def connect_notifications(instance, **kwargs):
53 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
55 for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()):
54 user_name = reply_number.group(1).lower()
56 user_name = reply_number.group(1).lower()
55 Notification.objects.get_or_create(name=user_name, post=instance)
57 Notification.objects.get_or_create(name=user_name, post=instance)
56
58
57
59
58 @receiver(pre_save, sender=Post)
60 @receiver(pre_save, sender=Post)
59 def preparse_text(instance, **kwargs):
61 @receiver(post_import_deps, sender=Post)
62 def parse_text(instance, **kwargs):
60 instance._text_rendered = get_parser().parse(instance.get_raw_text())
63 instance._text_rendered = get_parser().parse(instance.get_raw_text())
61
64
62
65
63 @receiver(pre_delete, sender=Post)
66 @receiver(pre_delete, sender=Post)
64 def delete_images(instance, **kwargs):
67 def delete_images(instance, **kwargs):
65 for image in instance.images.all():
68 for image in instance.images.all():
66 image_refs_count = image.post_images.count()
69 image_refs_count = image.post_images.count()
67 if image_refs_count == 1:
70 if image_refs_count == 1:
68 image.delete()
71 image.delete()
69
72
70
73
71 @receiver(pre_delete, sender=Post)
74 @receiver(pre_delete, sender=Post)
72 def delete_attachments(instance, **kwargs):
75 def delete_attachments(instance, **kwargs):
73 for attachment in instance.attachments.all():
76 for attachment in instance.attachments.all():
74 attachment_refs_count = attachment.attachment_posts.count()
77 attachment_refs_count = attachment.attachment_posts.count()
75 if attachment_refs_count == 1:
78 if attachment_refs_count == 1:
76 attachment.delete()
79 attachment.delete()
77
80
78
81
79 @receiver(post_delete, sender=Post)
82 @receiver(post_delete, sender=Post)
80 def update_thread_on_delete(instance, **kwargs):
83 def update_thread_on_delete(instance, **kwargs):
81 thread = instance.get_thread()
84 thread = instance.get_thread()
82 thread.last_edit_time = timezone.now()
85 thread.last_edit_time = timezone.now()
83 thread.save()
86 thread.save()
84
87
85
88
86 @receiver(post_delete, sender=Post)
89 @receiver(post_delete, sender=Post)
87 def delete_global_id(instance, **kwargs):
90 def delete_global_id(instance, **kwargs):
88 if instance.global_id and instance.global_id.id:
91 if instance.global_id and instance.global_id.id:
89 instance.global_id.delete()
92 instance.global_id.delete()
General Comments 0
You need to be logged in to leave comments. Login now