Show More
@@ -1,4 +1,5 b'' | |||||
1 | import re |
|
1 | import re | |
2 |
|
2 | |||
3 | FILE_DIRECTORY = 'files/' |
|
3 | FILE_DIRECTORY = 'files/' | |
4 | REGEX_TAGS = re.compile(r'^[\w\s\d\']+$', re.UNICODE) |
|
4 | REGEX_TAGS = re.compile(r'^[\w\s\d\']+$', re.UNICODE) | |
|
5 | REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]') No newline at end of file |
@@ -1,360 +1,360 b'' | |||||
1 | import uuid |
|
1 | import uuid | |
2 |
|
2 | |||
3 | import hashlib |
|
3 | import hashlib | |
4 | import re |
|
4 | import re | |
5 | from django.db import models |
|
5 | from django.db import models | |
6 | from django.db.models import TextField |
|
6 | from django.db.models import TextField | |
7 | from django.template.defaultfilters import truncatewords, striptags |
|
7 | from django.template.defaultfilters import truncatewords, striptags | |
8 | from django.template.loader import render_to_string |
|
8 | from django.template.loader import render_to_string | |
9 | from django.urls import reverse |
|
9 | from django.urls import reverse | |
10 |
|
10 | |||
|
11 | from boards.abstracts.constants import REGEX_REPLY | |||
11 | from boards.abstracts.tripcode import Tripcode |
|
12 | from boards.abstracts.tripcode import Tripcode | |
12 | from boards.models import Attachment, KeyPair, GlobalId |
|
13 | from boards.models import Attachment, KeyPair, GlobalId | |
13 | from boards.models.attachment import FILE_TYPES_IMAGE |
|
14 | from boards.models.attachment import FILE_TYPES_IMAGE | |
14 | from boards.models.base import Viewable |
|
15 | from boards.models.base import Viewable | |
15 | from boards.models.post.export import get_exporter, DIFF_TYPE_JSON |
|
16 | from boards.models.post.export import get_exporter, DIFF_TYPE_JSON | |
16 | from boards.models.post.manager import PostManager, NO_IP |
|
17 | from boards.models.post.manager import PostManager, NO_IP | |
17 | from boards.utils import datetime_to_epoch |
|
18 | from boards.utils import datetime_to_epoch | |
18 |
|
19 | |||
19 | CSS_CLS_HIDDEN_POST = 'hidden_post' |
|
20 | CSS_CLS_HIDDEN_POST = 'hidden_post' | |
20 | CSS_CLS_DEAD_POST = 'dead_post' |
|
21 | CSS_CLS_DEAD_POST = 'dead_post' | |
21 | CSS_CLS_ARCHIVE_POST = 'archive_post' |
|
22 | CSS_CLS_ARCHIVE_POST = 'archive_post' | |
22 | CSS_CLS_POST = 'post' |
|
23 | CSS_CLS_POST = 'post' | |
23 | CSS_CLS_MONOCHROME = 'monochrome' |
|
24 | CSS_CLS_MONOCHROME = 'monochrome' | |
24 |
|
25 | |||
25 | TITLE_MAX_WORDS = 10 |
|
26 | TITLE_MAX_WORDS = 10 | |
26 |
|
27 | |||
27 | APP_LABEL_BOARDS = 'boards' |
|
28 | APP_LABEL_BOARDS = 'boards' | |
28 |
|
29 | |||
29 | BAN_REASON_AUTO = 'Auto' |
|
30 | BAN_REASON_AUTO = 'Auto' | |
30 |
|
31 | |||
31 | TITLE_MAX_LENGTH = 200 |
|
32 | TITLE_MAX_LENGTH = 200 | |
32 |
|
33 | |||
33 | REGEX_REPLY = re.compile(r'\[post\](\d+)\[/post\]') |
|
|||
34 | REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]') |
|
34 | REGEX_GLOBAL_REPLY = re.compile(r'\[post\](\w+)::([^:]+)::(\d+)\[/post\]') | |
35 | REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?') |
|
35 | REGEX_URL = re.compile(r'https?\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?') | |
36 | REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]') |
|
36 | REGEX_NOTIFICATION = re.compile(r'\[user\](\w+)\[/user\]') | |
37 |
|
37 | |||
38 | PARAMETER_TRUNCATED = 'truncated' |
|
38 | PARAMETER_TRUNCATED = 'truncated' | |
39 | PARAMETER_TAG = 'tag' |
|
39 | PARAMETER_TAG = 'tag' | |
40 | PARAMETER_OFFSET = 'offset' |
|
40 | PARAMETER_OFFSET = 'offset' | |
41 | PARAMETER_DIFF_TYPE = 'type' |
|
41 | PARAMETER_DIFF_TYPE = 'type' | |
42 | PARAMETER_CSS_CLASS = 'css_class' |
|
42 | PARAMETER_CSS_CLASS = 'css_class' | |
43 | PARAMETER_THREAD = 'thread' |
|
43 | PARAMETER_THREAD = 'thread' | |
44 | PARAMETER_IS_OPENING = 'is_opening' |
|
44 | PARAMETER_IS_OPENING = 'is_opening' | |
45 | PARAMETER_POST = 'post' |
|
45 | PARAMETER_POST = 'post' | |
46 | PARAMETER_OP_ID = 'opening_post_id' |
|
46 | PARAMETER_OP_ID = 'opening_post_id' | |
47 | PARAMETER_NEED_OPEN_LINK = 'need_open_link' |
|
47 | PARAMETER_NEED_OPEN_LINK = 'need_open_link' | |
48 | PARAMETER_REPLY_LINK = 'reply_link' |
|
48 | PARAMETER_REPLY_LINK = 'reply_link' | |
49 | PARAMETER_NEED_OP_DATA = 'need_op_data' |
|
49 | PARAMETER_NEED_OP_DATA = 'need_op_data' | |
50 |
|
50 | |||
51 | POST_VIEW_PARAMS = ( |
|
51 | POST_VIEW_PARAMS = ( | |
52 | 'need_op_data', |
|
52 | 'need_op_data', | |
53 | 'reply_link', |
|
53 | 'reply_link', | |
54 | 'need_open_link', |
|
54 | 'need_open_link', | |
55 | 'truncated', |
|
55 | 'truncated', | |
56 | 'mode_tree', |
|
56 | 'mode_tree', | |
57 | 'perms', |
|
57 | 'perms', | |
58 | 'tree_depth', |
|
58 | 'tree_depth', | |
59 | ) |
|
59 | ) | |
60 |
|
60 | |||
61 |
|
61 | |||
62 | class Post(models.Model, Viewable): |
|
62 | class Post(models.Model, Viewable): | |
63 | """A post is a message.""" |
|
63 | """A post is a message.""" | |
64 |
|
64 | |||
65 | objects = PostManager() |
|
65 | objects = PostManager() | |
66 |
|
66 | |||
67 | class Meta: |
|
67 | class Meta: | |
68 | app_label = APP_LABEL_BOARDS |
|
68 | app_label = APP_LABEL_BOARDS | |
69 | ordering = ('id',) |
|
69 | ordering = ('id',) | |
70 |
|
70 | |||
71 | title = models.CharField(max_length=TITLE_MAX_LENGTH, blank=True, default='') |
|
71 | title = models.CharField(max_length=TITLE_MAX_LENGTH, blank=True, default='') | |
72 | pub_time = models.DateTimeField(db_index=True) |
|
72 | pub_time = models.DateTimeField(db_index=True) | |
73 | text = TextField(blank=True, default='') |
|
73 | text = TextField(blank=True, default='') | |
74 | _text_rendered = TextField(blank=True, null=True, editable=False) |
|
74 | _text_rendered = TextField(blank=True, null=True, editable=False) | |
75 |
|
75 | |||
76 | attachments = models.ManyToManyField(Attachment, null=True, blank=True, |
|
76 | attachments = models.ManyToManyField(Attachment, null=True, blank=True, | |
77 | related_name='attachment_posts') |
|
77 | related_name='attachment_posts') | |
78 |
|
78 | |||
79 | poster_ip = models.GenericIPAddressField() |
|
79 | poster_ip = models.GenericIPAddressField() | |
80 |
|
80 | |||
81 | # Used for cache and threads updating |
|
81 | # Used for cache and threads updating | |
82 | last_edit_time = models.DateTimeField() |
|
82 | last_edit_time = models.DateTimeField() | |
83 |
|
83 | |||
84 | referenced_posts = models.ManyToManyField('Post', symmetrical=False, |
|
84 | referenced_posts = models.ManyToManyField('Post', symmetrical=False, | |
85 | null=True, |
|
85 | null=True, | |
86 | blank=True, related_name='refposts', |
|
86 | blank=True, related_name='refposts', | |
87 | db_index=True) |
|
87 | db_index=True) | |
88 | refmap = models.TextField(null=True, blank=True) |
|
88 | refmap = models.TextField(null=True, blank=True) | |
89 | thread = models.ForeignKey('Thread', on_delete=models.CASCADE, |
|
89 | thread = models.ForeignKey('Thread', on_delete=models.CASCADE, | |
90 | db_index=True, related_name='replies') |
|
90 | db_index=True, related_name='replies') | |
91 |
|
91 | |||
92 | url = models.TextField() |
|
92 | url = models.TextField() | |
93 | uid = models.TextField() |
|
93 | uid = models.TextField() | |
94 |
|
94 | |||
95 | # Global ID with author key. If the message was downloaded from another |
|
95 | # Global ID with author key. If the message was downloaded from another | |
96 | # server, this indicates the server. |
|
96 | # server, this indicates the server. | |
97 | global_id = models.OneToOneField(GlobalId, null=True, blank=True, |
|
97 | global_id = models.OneToOneField(GlobalId, null=True, blank=True, | |
98 | on_delete=models.CASCADE) |
|
98 | on_delete=models.CASCADE) | |
99 |
|
99 | |||
100 | tripcode = models.CharField(max_length=50, blank=True, default='') |
|
100 | tripcode = models.CharField(max_length=50, blank=True, default='') | |
101 | opening = models.BooleanField(db_index=True) |
|
101 | opening = models.BooleanField(db_index=True) | |
102 | hidden = models.BooleanField(default=False) |
|
102 | hidden = models.BooleanField(default=False) | |
103 |
|
103 | |||
104 | def __str__(self): |
|
104 | def __str__(self): | |
105 | return 'P#{}/{}'.format(self.id, self.get_title()) |
|
105 | return 'P#{}/{}'.format(self.id, self.get_title()) | |
106 |
|
106 | |||
107 | def get_title(self) -> str: |
|
107 | def get_title(self) -> str: | |
108 | return self.title |
|
108 | return self.title | |
109 |
|
109 | |||
110 | def get_title_or_text(self): |
|
110 | def get_title_or_text(self): | |
111 | title = self.get_title() |
|
111 | title = self.get_title() | |
112 | if not title: |
|
112 | if not title: | |
113 | title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS) |
|
113 | title = truncatewords(striptags(self.get_text()), TITLE_MAX_WORDS) | |
114 |
|
114 | |||
115 | return title |
|
115 | return title | |
116 |
|
116 | |||
117 | def build_refmap(self, excluded_ids=None) -> None: |
|
117 | def build_refmap(self, excluded_ids=None) -> None: | |
118 | """ |
|
118 | """ | |
119 | Builds a replies map string from replies list. This is a cache to stop |
|
119 | Builds a replies map string from replies list. This is a cache to stop | |
120 | the server from recalculating the map on every post show. |
|
120 | the server from recalculating the map on every post show. | |
121 | """ |
|
121 | """ | |
122 |
|
122 | |||
123 | replies = self.referenced_posts |
|
123 | replies = self.referenced_posts | |
124 | if excluded_ids is not None: |
|
124 | if excluded_ids is not None: | |
125 | replies = replies.exclude(id__in=excluded_ids) |
|
125 | replies = replies.exclude(id__in=excluded_ids) | |
126 | else: |
|
126 | else: | |
127 | replies = replies.all() |
|
127 | replies = replies.all() | |
128 |
|
128 | |||
129 | post_urls = [refpost.get_link_view() for refpost in replies] |
|
129 | post_urls = [refpost.get_link_view() for refpost in replies] | |
130 |
|
130 | |||
131 | self.refmap = ', '.join(post_urls) |
|
131 | self.refmap = ', '.join(post_urls) | |
132 |
|
132 | |||
133 | def is_referenced(self) -> bool: |
|
133 | def is_referenced(self) -> bool: | |
134 | return self.refmap and len(self.refmap) > 0 |
|
134 | return self.refmap and len(self.refmap) > 0 | |
135 |
|
135 | |||
136 | def is_opening(self) -> bool: |
|
136 | def is_opening(self) -> bool: | |
137 | """ |
|
137 | """ | |
138 | Checks if this is an opening post or just a reply. |
|
138 | Checks if this is an opening post or just a reply. | |
139 | """ |
|
139 | """ | |
140 |
|
140 | |||
141 | return self.opening |
|
141 | return self.opening | |
142 |
|
142 | |||
143 | def get_absolute_url(self, thread=None): |
|
143 | def get_absolute_url(self, thread=None): | |
144 | # Url is cached only for the "main" thread. When getting url |
|
144 | # Url is cached only for the "main" thread. When getting url | |
145 | # for other threads, do it manually. |
|
145 | # for other threads, do it manually. | |
146 | return self.url |
|
146 | return self.url | |
147 |
|
147 | |||
148 | def get_thread(self): |
|
148 | def get_thread(self): | |
149 | return self.thread |
|
149 | return self.thread | |
150 |
|
150 | |||
151 | def get_thread_id(self): |
|
151 | def get_thread_id(self): | |
152 | return self.thread_id |
|
152 | return self.thread_id | |
153 |
|
153 | |||
154 | def _get_cache_key(self): |
|
154 | def _get_cache_key(self): | |
155 | return [datetime_to_epoch(self.last_edit_time)] |
|
155 | return [datetime_to_epoch(self.last_edit_time)] | |
156 |
|
156 | |||
157 | def get_view_params(self, *args, **kwargs): |
|
157 | def get_view_params(self, *args, **kwargs): | |
158 | """ |
|
158 | """ | |
159 | Gets the parameters required for viewing the post based on the arguments |
|
159 | Gets the parameters required for viewing the post based on the arguments | |
160 | given and the post itself. |
|
160 | given and the post itself. | |
161 | """ |
|
161 | """ | |
162 | thread = kwargs.get('thread') or self.get_thread() |
|
162 | thread = kwargs.get('thread') or self.get_thread() | |
163 |
|
163 | |||
164 | css_classes = [CSS_CLS_POST] |
|
164 | css_classes = [CSS_CLS_POST] | |
165 | if thread.is_archived(): |
|
165 | if thread.is_archived(): | |
166 | css_classes.append(CSS_CLS_ARCHIVE_POST) |
|
166 | css_classes.append(CSS_CLS_ARCHIVE_POST) | |
167 | elif not thread.can_bump(): |
|
167 | elif not thread.can_bump(): | |
168 | css_classes.append(CSS_CLS_DEAD_POST) |
|
168 | css_classes.append(CSS_CLS_DEAD_POST) | |
169 | if self.is_hidden(): |
|
169 | if self.is_hidden(): | |
170 | css_classes.append(CSS_CLS_HIDDEN_POST) |
|
170 | css_classes.append(CSS_CLS_HIDDEN_POST) | |
171 | if thread.is_monochrome(): |
|
171 | if thread.is_monochrome(): | |
172 | css_classes.append(CSS_CLS_MONOCHROME) |
|
172 | css_classes.append(CSS_CLS_MONOCHROME) | |
173 |
|
173 | |||
174 | params = dict() |
|
174 | params = dict() | |
175 | for param in POST_VIEW_PARAMS: |
|
175 | for param in POST_VIEW_PARAMS: | |
176 | if param in kwargs: |
|
176 | if param in kwargs: | |
177 | params[param] = kwargs[param] |
|
177 | params[param] = kwargs[param] | |
178 |
|
178 | |||
179 | params.update({ |
|
179 | params.update({ | |
180 | PARAMETER_POST: self, |
|
180 | PARAMETER_POST: self, | |
181 | PARAMETER_IS_OPENING: self.is_opening(), |
|
181 | PARAMETER_IS_OPENING: self.is_opening(), | |
182 | PARAMETER_THREAD: thread, |
|
182 | PARAMETER_THREAD: thread, | |
183 | PARAMETER_CSS_CLASS: ' '.join(css_classes), |
|
183 | PARAMETER_CSS_CLASS: ' '.join(css_classes), | |
184 | }) |
|
184 | }) | |
185 |
|
185 | |||
186 | return params |
|
186 | return params | |
187 |
|
187 | |||
188 | def get_view(self, *args, **kwargs) -> str: |
|
188 | def get_view(self, *args, **kwargs) -> str: | |
189 | """ |
|
189 | """ | |
190 | Renders post's HTML view. Some of the post params can be passed over |
|
190 | Renders post's HTML view. Some of the post params can be passed over | |
191 | kwargs for the means of caching (if we view the thread, some params |
|
191 | kwargs for the means of caching (if we view the thread, some params | |
192 | are same for every post and don't need to be computed over and over. |
|
192 | are same for every post and don't need to be computed over and over. | |
193 | """ |
|
193 | """ | |
194 | params = self.get_view_params(*args, **kwargs) |
|
194 | params = self.get_view_params(*args, **kwargs) | |
195 |
|
195 | |||
196 | return render_to_string('boards/post.html', params) |
|
196 | return render_to_string('boards/post.html', params) | |
197 |
|
197 | |||
198 | def get_images(self) -> Attachment: |
|
198 | def get_images(self) -> Attachment: | |
199 | return self.attachments.filter(mimetype__in=FILE_TYPES_IMAGE) |
|
199 | return self.attachments.filter(mimetype__in=FILE_TYPES_IMAGE) | |
200 |
|
200 | |||
201 | def get_first_image(self) -> Attachment: |
|
201 | def get_first_image(self) -> Attachment: | |
202 | try: |
|
202 | try: | |
203 | return self.get_images().earliest('-id') |
|
203 | return self.get_images().earliest('-id') | |
204 | except Attachment.DoesNotExist: |
|
204 | except Attachment.DoesNotExist: | |
205 | return None |
|
205 | return None | |
206 |
|
206 | |||
207 | def set_global_id(self, key_pair=None): |
|
207 | def set_global_id(self, key_pair=None): | |
208 | """ |
|
208 | """ | |
209 | Sets global id based on the given key pair. If no key pair is given, |
|
209 | Sets global id based on the given key pair. If no key pair is given, | |
210 | default one is used. |
|
210 | default one is used. | |
211 | """ |
|
211 | """ | |
212 |
|
212 | |||
213 | if key_pair: |
|
213 | if key_pair: | |
214 | key = key_pair |
|
214 | key = key_pair | |
215 | else: |
|
215 | else: | |
216 | try: |
|
216 | try: | |
217 | key = KeyPair.objects.get(primary=True) |
|
217 | key = KeyPair.objects.get(primary=True) | |
218 | except KeyPair.DoesNotExist: |
|
218 | except KeyPair.DoesNotExist: | |
219 | # Do not update the global id because there is no key defined |
|
219 | # Do not update the global id because there is no key defined | |
220 | return |
|
220 | return | |
221 | global_id = GlobalId(key_type=key.key_type, |
|
221 | global_id = GlobalId(key_type=key.key_type, | |
222 | key=key.public_key, |
|
222 | key=key.public_key, | |
223 | local_id=self.id) |
|
223 | local_id=self.id) | |
224 | global_id.save() |
|
224 | global_id.save() | |
225 |
|
225 | |||
226 | self.global_id = global_id |
|
226 | self.global_id = global_id | |
227 |
|
227 | |||
228 | self.save(update_fields=['global_id']) |
|
228 | self.save(update_fields=['global_id']) | |
229 |
|
229 | |||
230 | def get_pub_time_str(self): |
|
230 | def get_pub_time_str(self): | |
231 | return str(self.pub_time) |
|
231 | return str(self.pub_time) | |
232 |
|
232 | |||
233 | def get_replied_ids(self): |
|
233 | def get_replied_ids(self): | |
234 | """ |
|
234 | """ | |
235 | Gets ID list of the posts that this post replies. |
|
235 | Gets ID list of the posts that this post replies. | |
236 | """ |
|
236 | """ | |
237 |
|
237 | |||
238 | raw_text = self.get_raw_text() |
|
238 | raw_text = self.get_raw_text() | |
239 |
|
239 | |||
240 | local_replied = REGEX_REPLY.findall(raw_text) |
|
240 | local_replied = REGEX_REPLY.findall(raw_text) | |
241 | global_replied = [] |
|
241 | global_replied = [] | |
242 | for match in REGEX_GLOBAL_REPLY.findall(raw_text): |
|
242 | for match in REGEX_GLOBAL_REPLY.findall(raw_text): | |
243 | key_type = match[0] |
|
243 | key_type = match[0] | |
244 | key = match[1] |
|
244 | key = match[1] | |
245 | local_id = match[2] |
|
245 | local_id = match[2] | |
246 |
|
246 | |||
247 | try: |
|
247 | try: | |
248 | global_id = GlobalId.objects.get(key_type=key_type, |
|
248 | global_id = GlobalId.objects.get(key_type=key_type, | |
249 | key=key, local_id=local_id) |
|
249 | key=key, local_id=local_id) | |
250 | for post in Post.objects.filter(global_id=global_id).only('id'): |
|
250 | for post in Post.objects.filter(global_id=global_id).only('id'): | |
251 | global_replied.append(post.id) |
|
251 | global_replied.append(post.id) | |
252 | except GlobalId.DoesNotExist: |
|
252 | except GlobalId.DoesNotExist: | |
253 | pass |
|
253 | pass | |
254 | return local_replied + global_replied |
|
254 | return local_replied + global_replied | |
255 |
|
255 | |||
256 | def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None, |
|
256 | def get_post_data(self, format_type=DIFF_TYPE_JSON, request=None, | |
257 | include_last_update=False) -> str: |
|
257 | include_last_update=False) -> str: | |
258 | """ |
|
258 | """ | |
259 | Gets post HTML or JSON data that can be rendered on a page or used by |
|
259 | Gets post HTML or JSON data that can be rendered on a page or used by | |
260 | API. |
|
260 | API. | |
261 | """ |
|
261 | """ | |
262 |
|
262 | |||
263 | return get_exporter(format_type).export(self, request, |
|
263 | return get_exporter(format_type).export(self, request, | |
264 | include_last_update) |
|
264 | include_last_update) | |
265 |
|
265 | |||
266 | def _build_url(self): |
|
266 | def _build_url(self): | |
267 | opening = self.is_opening() |
|
267 | opening = self.is_opening() | |
268 | opening_id = self.id if opening else self.get_thread().get_opening_post_id() |
|
268 | opening_id = self.id if opening else self.get_thread().get_opening_post_id() | |
269 | url = reverse('thread', kwargs={'post_id': opening_id}) |
|
269 | url = reverse('thread', kwargs={'post_id': opening_id}) | |
270 | if not opening: |
|
270 | if not opening: | |
271 | url += '#' + str(self.id) |
|
271 | url += '#' + str(self.id) | |
272 |
|
272 | |||
273 | return url |
|
273 | return url | |
274 |
|
274 | |||
275 | def save(self, force_insert=False, force_update=False, using=None, |
|
275 | def save(self, force_insert=False, force_update=False, using=None, | |
276 | update_fields=None): |
|
276 | update_fields=None): | |
277 | new_post = self.id is None |
|
277 | new_post = self.id is None | |
278 |
|
278 | |||
279 | self.uid = str(uuid.uuid4()) |
|
279 | self.uid = str(uuid.uuid4()) | |
280 | if update_fields is not None and 'uid' not in update_fields: |
|
280 | if update_fields is not None and 'uid' not in update_fields: | |
281 | update_fields += ['uid'] |
|
281 | update_fields += ['uid'] | |
282 |
|
282 | |||
283 | if not new_post: |
|
283 | if not new_post: | |
284 | thread = self.get_thread() |
|
284 | thread = self.get_thread() | |
285 | if thread: |
|
285 | if thread: | |
286 | thread.last_edit_time = self.last_edit_time |
|
286 | thread.last_edit_time = self.last_edit_time | |
287 | thread.save(update_fields=['last_edit_time', 'status']) |
|
287 | thread.save(update_fields=['last_edit_time', 'status']) | |
288 |
|
288 | |||
289 | super().save(force_insert, force_update, using, update_fields) |
|
289 | super().save(force_insert, force_update, using, update_fields) | |
290 |
|
290 | |||
291 | if new_post: |
|
291 | if new_post: | |
292 | self.url = self._build_url() |
|
292 | self.url = self._build_url() | |
293 | super().save(update_fields=['url']) |
|
293 | super().save(update_fields=['url']) | |
294 |
|
294 | |||
295 | def get_text(self) -> str: |
|
295 | def get_text(self) -> str: | |
296 | return self._text_rendered |
|
296 | return self._text_rendered | |
297 |
|
297 | |||
298 | def get_raw_text(self) -> str: |
|
298 | def get_raw_text(self) -> str: | |
299 | return self.text |
|
299 | return self.text | |
300 |
|
300 | |||
301 | def get_sync_text(self) -> str: |
|
301 | def get_sync_text(self) -> str: | |
302 | """ |
|
302 | """ | |
303 | Returns text applicable for sync. It has absolute post reflinks. |
|
303 | Returns text applicable for sync. It has absolute post reflinks. | |
304 | """ |
|
304 | """ | |
305 |
|
305 | |||
306 | replacements = dict() |
|
306 | replacements = dict() | |
307 | for post_id in REGEX_REPLY.findall(self.get_raw_text()): |
|
307 | for post_id in REGEX_REPLY.findall(self.get_raw_text()): | |
308 | try: |
|
308 | try: | |
309 | absolute_post_id = str(Post.objects.get(id=post_id).global_id) |
|
309 | absolute_post_id = str(Post.objects.get(id=post_id).global_id) | |
310 | replacements[post_id] = absolute_post_id |
|
310 | replacements[post_id] = absolute_post_id | |
311 | except Post.DoesNotExist: |
|
311 | except Post.DoesNotExist: | |
312 | pass |
|
312 | pass | |
313 |
|
313 | |||
314 | text = self.get_raw_text() or '' |
|
314 | text = self.get_raw_text() or '' | |
315 | for key in replacements: |
|
315 | for key in replacements: | |
316 | text = text.replace('[post]{}[/post]'.format(key), |
|
316 | text = text.replace('[post]{}[/post]'.format(key), | |
317 | '[post]{}[/post]'.format(replacements[key])) |
|
317 | '[post]{}[/post]'.format(replacements[key])) | |
318 | text = text.replace('\r\n', '\n').replace('\r', '\n') |
|
318 | text = text.replace('\r\n', '\n').replace('\r', '\n') | |
319 |
|
319 | |||
320 | return text |
|
320 | return text | |
321 |
|
321 | |||
322 | def get_tripcode(self): |
|
322 | def get_tripcode(self): | |
323 | if self.tripcode: |
|
323 | if self.tripcode: | |
324 | return Tripcode(self.tripcode) |
|
324 | return Tripcode(self.tripcode) | |
325 |
|
325 | |||
326 | def get_link_view(self): |
|
326 | def get_link_view(self): | |
327 | """ |
|
327 | """ | |
328 | Gets view of a reflink to the post. |
|
328 | Gets view of a reflink to the post. | |
329 | """ |
|
329 | """ | |
330 | result = '<a href="{}">>>{}</a>'.format(self.get_absolute_url(), |
|
330 | result = '<a href="{}">>>{}</a>'.format(self.get_absolute_url(), | |
331 | self.id) |
|
331 | self.id) | |
332 | if self.is_opening(): |
|
332 | if self.is_opening(): | |
333 | result = '<b>{}</b>'.format(result) |
|
333 | result = '<b>{}</b>'.format(result) | |
334 |
|
334 | |||
335 | return result |
|
335 | return result | |
336 |
|
336 | |||
337 | def is_hidden(self) -> bool: |
|
337 | def is_hidden(self) -> bool: | |
338 | return self.hidden |
|
338 | return self.hidden | |
339 |
|
339 | |||
340 | def set_hidden(self, hidden): |
|
340 | def set_hidden(self, hidden): | |
341 | self.hidden = hidden |
|
341 | self.hidden = hidden | |
342 |
|
342 | |||
343 | def clear_cache(self): |
|
343 | def clear_cache(self): | |
344 | """ |
|
344 | """ | |
345 | Clears sync data (content cache, signatures etc). |
|
345 | Clears sync data (content cache, signatures etc). | |
346 | """ |
|
346 | """ | |
347 | global_id = self.global_id |
|
347 | global_id = self.global_id | |
348 | if global_id is not None and global_id.is_local()\ |
|
348 | if global_id is not None and global_id.is_local()\ | |
349 | and global_id.content is not None: |
|
349 | and global_id.content is not None: | |
350 | global_id.clear_cache() |
|
350 | global_id.clear_cache() | |
351 |
|
351 | |||
352 | def get_tags(self): |
|
352 | def get_tags(self): | |
353 | return self.get_thread().get_tags() |
|
353 | return self.get_thread().get_tags() | |
354 |
|
354 | |||
355 | def get_ip_color(self): |
|
355 | def get_ip_color(self): | |
356 | return hashlib.md5(self.poster_ip.encode()).hexdigest()[:6] |
|
356 | return hashlib.md5(self.poster_ip.encode()).hexdigest()[:6] | |
357 |
|
357 | |||
358 | def has_ip(self): |
|
358 | def has_ip(self): | |
359 | return self.poster_ip != NO_IP |
|
359 | return self.poster_ip != NO_IP | |
360 |
|
360 |
@@ -1,228 +1,291 b'' | |||||
1 | import logging |
|
1 | import logging | |
|
2 | import re | |||
2 | from datetime import datetime, timedelta, date |
|
3 | from datetime import datetime, timedelta, date | |
3 | from datetime import time as dtime |
|
4 | from datetime import time as dtime | |
4 |
|
5 | |||
5 | from django.core.exceptions import PermissionDenied |
|
6 | from django.core.exceptions import PermissionDenied | |
6 | from django.db import models, transaction |
|
7 | from django.db import models, transaction | |
7 | from django.dispatch import Signal |
|
8 | from django.dispatch import Signal | |
|
9 | from django.shortcuts import redirect | |||
8 | from django.utils import timezone |
|
10 | from django.utils import timezone | |
9 |
|
11 | |||
10 | import boards |
|
12 | import boards | |
11 | from boards import utils |
|
13 | from boards import utils | |
12 | from boards.abstracts.exceptions import ArchiveException |
|
14 | from boards.abstracts.exceptions import ArchiveException | |
13 | from boards.abstracts.constants import REGEX_TAGS |
|
15 | from boards.abstracts.constants import REGEX_TAGS, REGEX_REPLY | |
14 | from boards.mdx_neboard import Parser |
|
16 | from boards.mdx_neboard import Parser | |
15 | from boards.models import Attachment |
|
17 | from boards.models import Attachment | |
16 | from boards.models.attachment import StickerPack, AttachmentSticker |
|
18 | from boards.models.attachment import StickerPack, AttachmentSticker | |
17 | from boards.models.user import Ban |
|
19 | from boards.models.user import Ban | |
18 |
|
20 | |||
19 | __author__ = 'neko259' |
|
21 | __author__ = 'neko259' | |
20 |
|
22 | |||
21 | POSTS_PER_DAY_RANGE = 7 |
|
23 | POSTS_PER_DAY_RANGE = 7 | |
22 | NO_IP = '0.0.0.0' |
|
24 | NO_IP = '0.0.0.0' | |
23 |
|
25 | |||
24 |
|
26 | |||
25 | post_import_deps = Signal() |
|
27 | post_import_deps = Signal() | |
26 |
|
28 | |||
|
29 | FORM_TEXT = 'text' | |||
|
30 | FORM_TAGS = 'tags' | |||
|
31 | ||||
|
32 | REFLINK_PREFIX = '>>' | |||
|
33 | ||||
27 |
|
34 | |||
28 | class PostManager(models.Manager): |
|
35 | class PostManager(models.Manager): | |
29 | @transaction.atomic |
|
36 | @transaction.atomic | |
30 | def create_post(self, title: str, text: str, files=[], thread=None, |
|
37 | def create_post(self, title: str, text: str, files=[], thread=None, | |
31 | ip=NO_IP, tags: list=None, |
|
38 | ip=NO_IP, tags: list=None, | |
32 | tripcode='', monochrome=False, images=[], |
|
39 | tripcode='', monochrome=False, images=[], | |
33 | file_urls=[], stickerpack=False): |
|
40 | file_urls=[], stickerpack=False): | |
34 | """ |
|
41 | """ | |
35 | Creates new post |
|
42 | Creates new post | |
36 | """ |
|
43 | """ | |
37 |
|
44 | |||
38 | if thread is not None and thread.is_archived(): |
|
45 | if thread is not None and thread.is_archived(): | |
39 | raise ArchiveException('Cannot post into an archived thread') |
|
46 | raise ArchiveException('Cannot post into an archived thread') | |
40 |
|
47 | |||
41 | if not utils.is_anonymous_mode(): |
|
48 | if not utils.is_anonymous_mode(): | |
42 | is_banned = Ban.objects.filter(ip=ip).exists() |
|
49 | is_banned = Ban.objects.filter(ip=ip).exists() | |
43 | else: |
|
50 | else: | |
44 | is_banned = False |
|
51 | is_banned = False | |
45 |
|
52 | |||
46 | if is_banned: |
|
53 | if is_banned: | |
47 | raise PermissionDenied() |
|
54 | raise PermissionDenied() | |
48 |
|
55 | |||
49 | if not tags: |
|
56 | if not tags: | |
50 | tags = [] |
|
57 | tags = [] | |
51 |
|
58 | |||
52 | posting_time = timezone.now() |
|
59 | posting_time = timezone.now() | |
53 | new_thread = False |
|
60 | new_thread = False | |
54 | if not thread: |
|
61 | if not thread: | |
55 | thread = boards.models.thread.Thread.objects.create( |
|
62 | thread = boards.models.thread.Thread.objects.create( | |
56 | bump_time=posting_time, last_edit_time=posting_time, |
|
63 | bump_time=posting_time, last_edit_time=posting_time, | |
57 | monochrome=monochrome, stickerpack=stickerpack) |
|
64 | monochrome=monochrome, stickerpack=stickerpack) | |
58 | list(map(thread.tags.add, tags)) |
|
65 | list(map(thread.tags.add, tags)) | |
59 | new_thread = True |
|
66 | new_thread = True | |
60 |
|
67 | |||
61 | pre_text = Parser().preparse(text) |
|
68 | pre_text = Parser().preparse(text) | |
62 |
|
69 | |||
63 | post = self.create(title=title, |
|
70 | post = self.create(title=title, | |
64 | text=pre_text, |
|
71 | text=pre_text, | |
65 | pub_time=posting_time, |
|
72 | pub_time=posting_time, | |
66 | poster_ip=ip, |
|
73 | poster_ip=ip, | |
67 | thread=thread, |
|
74 | thread=thread, | |
68 | last_edit_time=posting_time, |
|
75 | last_edit_time=posting_time, | |
69 | tripcode=tripcode, |
|
76 | tripcode=tripcode, | |
70 | opening=new_thread) |
|
77 | opening=new_thread) | |
71 |
|
78 | |||
72 | logger = logging.getLogger('boards.post.create') |
|
79 | logger = logging.getLogger('boards.post.create') | |
73 |
|
80 | |||
74 | logger.info('Created post [{}] with text [{}] by {}'.format(post, |
|
81 | logger.info('Created post [{}] with text [{}] by {}'.format(post, | |
75 | post.get_text(),post.poster_ip)) |
|
82 | post.get_text(),post.poster_ip)) | |
76 |
|
83 | |||
77 | for file in files: |
|
84 | for file in files: | |
78 | self._add_file_to_post(file, post) |
|
85 | self._add_file_to_post(file, post) | |
79 | for image in images: |
|
86 | for image in images: | |
80 | post.attachments.add(image) |
|
87 | post.attachments.add(image) | |
81 | for file_url in file_urls: |
|
88 | for file_url in file_urls: | |
82 | post.attachments.add(Attachment.objects.create_from_url(file_url)) |
|
89 | post.attachments.add(Attachment.objects.create_from_url(file_url)) | |
83 |
|
90 | |||
84 | post.set_global_id() |
|
91 | post.set_global_id() | |
85 |
|
92 | |||
86 | # Thread needs to be bumped only when the post is already created |
|
93 | # Thread needs to be bumped only when the post is already created | |
87 | if not new_thread: |
|
94 | if not new_thread: | |
88 | thread.last_edit_time = posting_time |
|
95 | thread.last_edit_time = posting_time | |
89 | thread.bump() |
|
96 | thread.bump() | |
90 | thread.save() |
|
97 | thread.save() | |
91 |
|
98 | |||
92 | self._create_stickers(post) |
|
99 | self._create_stickers(post) | |
93 |
|
100 | |||
94 | return post |
|
101 | return post | |
95 |
|
102 | |||
96 | def delete_posts_by_ip(self, ip): |
|
103 | def delete_posts_by_ip(self, ip): | |
97 | """ |
|
104 | """ | |
98 | Deletes all posts of the author with same IP |
|
105 | Deletes all posts of the author with same IP | |
99 | """ |
|
106 | """ | |
100 |
|
107 | |||
101 | posts = self.filter(poster_ip=ip) |
|
108 | posts = self.filter(poster_ip=ip) | |
102 | for post in posts: |
|
109 | for post in posts: | |
103 | post.delete() |
|
110 | post.delete() | |
104 |
|
111 | |||
105 | @utils.cached_result() |
|
112 | @utils.cached_result() | |
106 | def get_posts_per_day(self) -> float: |
|
113 | def get_posts_per_day(self) -> float: | |
107 | """ |
|
114 | """ | |
108 | 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 | |
109 | """ |
|
116 | """ | |
110 |
|
117 | |||
111 | day_end = date.today() |
|
118 | day_end = date.today() | |
112 | day_start = day_end - timedelta(POSTS_PER_DAY_RANGE) |
|
119 | day_start = day_end - timedelta(POSTS_PER_DAY_RANGE) | |
113 |
|
120 | |||
114 | day_time_start = timezone.make_aware(datetime.combine( |
|
121 | day_time_start = timezone.make_aware(datetime.combine( | |
115 | day_start, dtime()), timezone.get_current_timezone()) |
|
122 | day_start, dtime()), timezone.get_current_timezone()) | |
116 | day_time_end = timezone.make_aware(datetime.combine( |
|
123 | day_time_end = timezone.make_aware(datetime.combine( | |
117 | day_end, dtime()), timezone.get_current_timezone()) |
|
124 | day_end, dtime()), timezone.get_current_timezone()) | |
118 |
|
125 | |||
119 | posts_per_period = float(self.filter( |
|
126 | posts_per_period = float(self.filter( | |
120 | pub_time__lte=day_time_end, |
|
127 | pub_time__lte=day_time_end, | |
121 | pub_time__gte=day_time_start).count()) |
|
128 | pub_time__gte=day_time_start).count()) | |
122 |
|
129 | |||
123 | ppd = posts_per_period / POSTS_PER_DAY_RANGE |
|
130 | ppd = posts_per_period / POSTS_PER_DAY_RANGE | |
124 |
|
131 | |||
125 | return ppd |
|
132 | return ppd | |
126 |
|
133 | |||
127 | def get_post_per_days(self, days) -> int: |
|
134 | def get_post_per_days(self, days) -> int: | |
128 | day_end = date.today() + timedelta(1) |
|
135 | day_end = date.today() + timedelta(1) | |
129 | day_start = day_end - timedelta(days) |
|
136 | day_start = day_end - timedelta(days) | |
130 |
|
137 | |||
131 | day_time_start = timezone.make_aware(datetime.combine( |
|
138 | day_time_start = timezone.make_aware(datetime.combine( | |
132 | day_start, dtime()), timezone.get_current_timezone()) |
|
139 | day_start, dtime()), timezone.get_current_timezone()) | |
133 | day_time_end = timezone.make_aware(datetime.combine( |
|
140 | day_time_end = timezone.make_aware(datetime.combine( | |
134 | day_end, dtime()), timezone.get_current_timezone()) |
|
141 | day_end, dtime()), timezone.get_current_timezone()) | |
135 |
|
142 | |||
136 | return self.filter( |
|
143 | return self.filter( | |
137 | pub_time__lte=day_time_end, |
|
144 | pub_time__lte=day_time_end, | |
138 | pub_time__gte=day_time_start).count() |
|
145 | pub_time__gte=day_time_start).count() | |
139 |
|
146 | |||
140 | @transaction.atomic |
|
147 | @transaction.atomic | |
141 | def import_post(self, title: str, text: str, pub_time: str, global_id, |
|
148 | def import_post(self, title: str, text: str, pub_time: str, global_id, | |
142 | opening_post=None, tags=list(), files=list(), |
|
149 | opening_post=None, tags=list(), files=list(), | |
143 | file_urls=list(), tripcode=None, last_edit_time=None): |
|
150 | file_urls=list(), tripcode=None, last_edit_time=None): | |
144 | is_opening = opening_post is None |
|
151 | is_opening = opening_post is None | |
145 | if is_opening: |
|
152 | if is_opening: | |
146 | thread = boards.models.thread.Thread.objects.create( |
|
153 | thread = boards.models.thread.Thread.objects.create( | |
147 | bump_time=pub_time, last_edit_time=pub_time) |
|
154 | bump_time=pub_time, last_edit_time=pub_time) | |
148 | list(map(thread.tags.add, tags)) |
|
155 | list(map(thread.tags.add, tags)) | |
149 | else: |
|
156 | else: | |
150 | thread = opening_post.get_thread() |
|
157 | thread = opening_post.get_thread() | |
151 |
|
158 | |||
152 | post = self.create(title=title, |
|
159 | post = self.create(title=title, | |
153 | text=text, |
|
160 | text=text, | |
154 | pub_time=pub_time, |
|
161 | pub_time=pub_time, | |
155 | poster_ip=NO_IP, |
|
162 | poster_ip=NO_IP, | |
156 | last_edit_time=last_edit_time or pub_time, |
|
163 | last_edit_time=last_edit_time or pub_time, | |
157 | global_id=global_id, |
|
164 | global_id=global_id, | |
158 | opening=is_opening, |
|
165 | opening=is_opening, | |
159 | thread=thread, |
|
166 | thread=thread, | |
160 | tripcode=tripcode) |
|
167 | tripcode=tripcode) | |
161 |
|
168 | |||
162 | for file in files: |
|
169 | for file in files: | |
163 | self._add_file_to_post(file, post) |
|
170 | self._add_file_to_post(file, post) | |
164 | for file_url in file_urls: |
|
171 | for file_url in file_urls: | |
165 | post.attachments.add(Attachment.objects.create_from_url(file_url)) |
|
172 | post.attachments.add(Attachment.objects.create_from_url(file_url)) | |
166 |
|
173 | |||
167 | url_to_post = '[post]{}[/post]'.format(str(global_id)) |
|
174 | url_to_post = '[post]{}[/post]'.format(str(global_id)) | |
168 | replies = self.filter(text__contains=url_to_post) |
|
175 | replies = self.filter(text__contains=url_to_post) | |
169 | for reply in replies: |
|
176 | for reply in replies: | |
170 | post_import_deps.send(reply) |
|
177 | post_import_deps.send(reply) | |
171 |
|
178 | |||
172 | @transaction.atomic |
|
179 | @transaction.atomic | |
173 | def update_post(self, post, title: str, text: str, pub_time: str, |
|
180 | def update_post(self, post, title: str, text: str, pub_time: str, | |
174 | tags=list(), files=list(), file_urls=list(), tripcode=None): |
|
181 | tags=list(), files=list(), file_urls=list(), tripcode=None): | |
175 | post.title = title |
|
182 | post.title = title | |
176 | post.text = text |
|
183 | post.text = text | |
177 | post.pub_time = pub_time |
|
184 | post.pub_time = pub_time | |
178 | post.tripcode = tripcode |
|
185 | post.tripcode = tripcode | |
179 | post.save() |
|
186 | post.save() | |
180 |
|
187 | |||
181 | post.clear_cache() |
|
188 | post.clear_cache() | |
182 |
|
189 | |||
183 | post.attachments.clear() |
|
190 | post.attachments.clear() | |
184 | for file in files: |
|
191 | for file in files: | |
185 | self._add_file_to_post(file, post) |
|
192 | self._add_file_to_post(file, post) | |
186 | for file_url in file_urls: |
|
193 | for file_url in file_urls: | |
187 | post.attachments.add(Attachment.objects.create_from_url(file_url)) |
|
194 | post.attachments.add(Attachment.objects.create_from_url(file_url)) | |
188 |
|
195 | |||
189 | thread = post.get_thread() |
|
196 | thread = post.get_thread() | |
190 | thread.tags.clear() |
|
197 | thread.tags.clear() | |
191 | list(map(thread.tags.add, tags)) |
|
198 | list(map(thread.tags.add, tags)) | |
192 |
|
199 | |||
|
200 | def create_from_form(self, request, form, opening_post, html_response=True): | |||
|
201 | ip = utils.get_client_ip(request) | |||
|
202 | ||||
|
203 | data = form.cleaned_data | |||
|
204 | ||||
|
205 | title = form.get_title() | |||
|
206 | text = data[FORM_TEXT] | |||
|
207 | files = form.get_files() | |||
|
208 | file_urls = form.get_file_urls() | |||
|
209 | images = form.get_images() | |||
|
210 | ||||
|
211 | text = self._remove_invalid_links(text) | |||
|
212 | ||||
|
213 | if opening_post: | |||
|
214 | post_thread = opening_post.get_thread() | |||
|
215 | monochrome = False | |||
|
216 | stickerpack = False | |||
|
217 | tags = [] | |||
|
218 | else: | |||
|
219 | tags = data[FORM_TAGS] | |||
|
220 | monochrome = form.is_monochrome() | |||
|
221 | stickerpack = form.is_stickerpack() | |||
|
222 | post_thread = None | |||
|
223 | ||||
|
224 | post = self.create_post(title=title, text=text, files=files, | |||
|
225 | thread=post_thread, ip=ip, | |||
|
226 | tripcode=form.get_tripcode(), | |||
|
227 | images=images, file_urls=file_urls, | |||
|
228 | monochrome=monochrome, | |||
|
229 | stickerpack=stickerpack, tags=tags) | |||
|
230 | ||||
|
231 | if form.is_subscribe(): | |||
|
232 | from boards.abstracts.settingsmanager import get_settings_manager | |||
|
233 | settings_manager = get_settings_manager(request) | |||
|
234 | settings_manager.add_or_read_fav_thread( | |||
|
235 | post_thread.get_opening_post()) | |||
|
236 | ||||
|
237 | if html_response: | |||
|
238 | return redirect(post.get_absolute_url()) | |||
|
239 | else: | |||
|
240 | return post | |||
|
241 | ||||
193 | def _add_file_to_post(self, file, post): |
|
242 | def _add_file_to_post(self, file, post): | |
194 | post.attachments.add(Attachment.objects.create_with_hash(file)) |
|
243 | post.attachments.add(Attachment.objects.create_with_hash(file)) | |
195 |
|
244 | |||
196 | def _create_stickers(self, post): |
|
245 | def _create_stickers(self, post): | |
197 | thread = post.get_thread() |
|
246 | thread = post.get_thread() | |
198 | stickerpack_thread = thread.is_stickerpack() |
|
247 | stickerpack_thread = thread.is_stickerpack() | |
199 | if stickerpack_thread: |
|
248 | if stickerpack_thread: | |
200 | logger = logging.getLogger('boards.stickers') |
|
249 | logger = logging.getLogger('boards.stickers') | |
201 | if not post.is_opening(): |
|
250 | if not post.is_opening(): | |
202 | has_title = len(post.title) > 0 |
|
251 | has_title = len(post.title) > 0 | |
203 | has_one_attachment = post.attachments.count() == 1 |
|
252 | has_one_attachment = post.attachments.count() == 1 | |
204 | opening_post = thread.get_opening_post() |
|
253 | opening_post = thread.get_opening_post() | |
205 | valid_name = REGEX_TAGS.match(post.title) |
|
254 | valid_name = REGEX_TAGS.match(post.title) | |
206 | if has_title and has_one_attachment and valid_name: |
|
255 | if has_title and has_one_attachment and valid_name: | |
207 | existing_sticker = AttachmentSticker.objects.filter( |
|
256 | existing_sticker = AttachmentSticker.objects.filter( | |
208 | name=post.get_title()).first() |
|
257 | name=post.get_title()).first() | |
209 | attachment = post.attachments.first() |
|
258 | attachment = post.attachments.first() | |
210 | if existing_sticker: |
|
259 | if existing_sticker: | |
211 | existing_sticker.attachment = attachment |
|
260 | existing_sticker.attachment = attachment | |
212 | existing_sticker.save() |
|
261 | existing_sticker.save() | |
213 | logger.info('Updated sticker {} with new attachment'.format(existing_sticker)) |
|
262 | logger.info('Updated sticker {} with new attachment'.format(existing_sticker)) | |
214 | else: |
|
263 | else: | |
215 | try: |
|
264 | try: | |
216 | stickerpack = StickerPack.objects.get( |
|
265 | stickerpack = StickerPack.objects.get( | |
217 | name=opening_post.get_title(), tripcode=post.tripcode) |
|
266 | name=opening_post.get_title(), tripcode=post.tripcode) | |
218 | sticker = AttachmentSticker.objects.create( |
|
267 | sticker = AttachmentSticker.objects.create( | |
219 | stickerpack=stickerpack, name=post.get_title(), |
|
268 | stickerpack=stickerpack, name=post.get_title(), | |
220 | attachment=attachment) |
|
269 | attachment=attachment) | |
221 | logger.info('Created sticker {}'.format(sticker)) |
|
270 | logger.info('Created sticker {}'.format(sticker)) | |
222 | except StickerPack.DoesNotExist: |
|
271 | except StickerPack.DoesNotExist: | |
223 | pass |
|
272 | pass | |
224 | else: |
|
273 | else: | |
225 | stickerpack, created = StickerPack.objects.get_or_create( |
|
274 | stickerpack, created = StickerPack.objects.get_or_create( | |
226 | name=post.get_title(), tripcode=post.tripcode) |
|
275 | name=post.get_title(), tripcode=post.tripcode) | |
227 | if created: |
|
276 | if created: | |
228 | logger.info('Created stickerpack {}'.format(stickerpack)) |
|
277 | logger.info('Created stickerpack {}'.format(stickerpack)) | |
|
278 | ||||
|
279 | def _remove_invalid_links(self, text): | |||
|
280 | """ | |||
|
281 | Replace invalid links in posts so that they won't be parsed. | |||
|
282 | Invalid links are links to non-existent posts | |||
|
283 | """ | |||
|
284 | ||||
|
285 | for reply_number in re.finditer(REGEX_REPLY, text): | |||
|
286 | post_id = reply_number.group(1) | |||
|
287 | post = self.filter(id=post_id) | |||
|
288 | if not post.exists(): | |||
|
289 | text = text.replace(REFLINK_PREFIX + post_id, post_id) | |||
|
290 | ||||
|
291 | return text |
@@ -1,143 +1,143 b'' | |||||
1 | import re |
|
1 | import re | |
2 | import os |
|
2 | import os | |
3 | import logging |
|
3 | import logging | |
4 |
|
4 | |||
5 | from django.db.models.signals import post_save, pre_save, pre_delete, \ |
|
5 | from django.db.models.signals import post_save, pre_save, pre_delete, \ | |
6 | post_delete |
|
6 | post_delete | |
7 | from django.dispatch import receiver |
|
7 | from django.dispatch import receiver | |
8 | from django.utils import timezone |
|
8 | from django.utils import timezone | |
9 |
|
9 | |||
10 | from boards import thumbs |
|
10 | from boards import thumbs | |
11 | from boards.mdx_neboard import get_parser |
|
11 | from boards.mdx_neboard import get_parser | |
12 |
|
12 | |||
13 | from boards.models import Post, GlobalId, Attachment, Thread |
|
13 | from boards.models import Post, GlobalId, Attachment, Thread | |
14 | from boards.models.attachment import StickerPack, AttachmentSticker |
|
14 | from boards.models.attachment import StickerPack, AttachmentSticker | |
15 | from boards.models.attachment.viewers import FILE_TYPES_IMAGE |
|
15 | from boards.models.attachment.viewers import FILE_TYPES_IMAGE | |
16 |
from boards.models.post import REGEX_NOTIFICATION, REGEX_REPLY |
|
16 | from boards.models.post import REGEX_NOTIFICATION, REGEX_GLOBAL_REPLY | |
17 | REGEX_GLOBAL_REPLY |
|
17 | from boards.abstracts.constants import REGEX_REPLY | |
18 | from boards.models.post.manager import post_import_deps |
|
18 | from boards.models.post.manager import post_import_deps | |
19 | from boards.models.user import Notification |
|
19 | from boards.models.user import Notification | |
20 | from neboard.settings import MEDIA_ROOT |
|
20 | from neboard.settings import MEDIA_ROOT | |
21 |
|
21 | |||
22 |
|
22 | |||
23 | THUMB_SIZES = ((200, 150),) |
|
23 | THUMB_SIZES = ((200, 150),) | |
24 |
|
24 | |||
25 |
|
25 | |||
26 | @receiver(post_save, sender=Post) |
|
26 | @receiver(post_save, sender=Post) | |
27 | def connect_replies(instance, **kwargs): |
|
27 | def connect_replies(instance, **kwargs): | |
28 | if not kwargs['update_fields']: |
|
28 | if not kwargs['update_fields']: | |
29 | for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()): |
|
29 | for reply_number in re.finditer(REGEX_REPLY, instance.get_raw_text()): | |
30 | post_id = reply_number.group(1) |
|
30 | post_id = reply_number.group(1) | |
31 |
|
31 | |||
32 | try: |
|
32 | try: | |
33 | referenced_post = Post.objects.get(id=post_id) |
|
33 | referenced_post = Post.objects.get(id=post_id) | |
34 |
|
34 | |||
35 | if not referenced_post.referenced_posts.filter( |
|
35 | if not referenced_post.referenced_posts.filter( | |
36 | id=instance.id).exists(): |
|
36 | id=instance.id).exists(): | |
37 | referenced_post.referenced_posts.add(instance) |
|
37 | referenced_post.referenced_posts.add(instance) | |
38 | referenced_post.last_edit_time = instance.pub_time |
|
38 | referenced_post.last_edit_time = instance.pub_time | |
39 | referenced_post.build_refmap() |
|
39 | referenced_post.build_refmap() | |
40 | referenced_post.save(update_fields=['refmap', 'last_edit_time']) |
|
40 | referenced_post.save(update_fields=['refmap', 'last_edit_time']) | |
41 | except Post.DoesNotExist: |
|
41 | except Post.DoesNotExist: | |
42 | pass |
|
42 | pass | |
43 |
|
43 | |||
44 |
|
44 | |||
45 | @receiver(post_save, sender=Post) |
|
45 | @receiver(post_save, sender=Post) | |
46 | @receiver(post_import_deps, sender=Post) |
|
46 | @receiver(post_import_deps, sender=Post) | |
47 | def connect_global_replies(instance, **kwargs): |
|
47 | def connect_global_replies(instance, **kwargs): | |
48 | if not kwargs['update_fields']: |
|
48 | if not kwargs['update_fields']: | |
49 | for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()): |
|
49 | for reply_number in re.finditer(REGEX_GLOBAL_REPLY, instance.get_raw_text()): | |
50 | key_type = reply_number.group(1) |
|
50 | key_type = reply_number.group(1) | |
51 | key = reply_number.group(2) |
|
51 | key = reply_number.group(2) | |
52 | local_id = reply_number.group(3) |
|
52 | local_id = reply_number.group(3) | |
53 |
|
53 | |||
54 | try: |
|
54 | try: | |
55 | global_id = GlobalId.objects.get(key_type=key_type, key=key, |
|
55 | global_id = GlobalId.objects.get(key_type=key_type, key=key, | |
56 | local_id=local_id) |
|
56 | local_id=local_id) | |
57 | referenced_post = Post.objects.get(global_id=global_id) |
|
57 | referenced_post = Post.objects.get(global_id=global_id) | |
58 | referenced_post.referenced_posts.add(instance) |
|
58 | referenced_post.referenced_posts.add(instance) | |
59 | referenced_post.last_edit_time = instance.pub_time |
|
59 | referenced_post.last_edit_time = instance.pub_time | |
60 | referenced_post.build_refmap() |
|
60 | referenced_post.build_refmap() | |
61 | referenced_post.save(update_fields=['refmap', 'last_edit_time']) |
|
61 | referenced_post.save(update_fields=['refmap', 'last_edit_time']) | |
62 | except (GlobalId.DoesNotExist, Post.DoesNotExist): |
|
62 | except (GlobalId.DoesNotExist, Post.DoesNotExist): | |
63 | pass |
|
63 | pass | |
64 |
|
64 | |||
65 |
|
65 | |||
66 | @receiver(post_save, sender=Post) |
|
66 | @receiver(post_save, sender=Post) | |
67 | def connect_notifications(instance, **kwargs): |
|
67 | def connect_notifications(instance, **kwargs): | |
68 | if not kwargs['update_fields']: |
|
68 | if not kwargs['update_fields']: | |
69 | for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()): |
|
69 | for reply_number in re.finditer(REGEX_NOTIFICATION, instance.get_raw_text()): | |
70 | user_name = reply_number.group(1).lower() |
|
70 | user_name = reply_number.group(1).lower() | |
71 | Notification.objects.get_or_create(name=user_name, post=instance) |
|
71 | Notification.objects.get_or_create(name=user_name, post=instance) | |
72 |
|
72 | |||
73 |
|
73 | |||
74 | @receiver(pre_save, sender=Post) |
|
74 | @receiver(pre_save, sender=Post) | |
75 | @receiver(post_import_deps, sender=Post) |
|
75 | @receiver(post_import_deps, sender=Post) | |
76 | def parse_text(instance, **kwargs): |
|
76 | def parse_text(instance, **kwargs): | |
77 | instance._text_rendered = get_parser().parse(instance.get_raw_text()) |
|
77 | instance._text_rendered = get_parser().parse(instance.get_raw_text()) | |
78 |
|
78 | |||
79 |
|
79 | |||
80 | @receiver(pre_delete, sender=Post) |
|
80 | @receiver(pre_delete, sender=Post) | |
81 | def delete_attachments(instance, **kwargs): |
|
81 | def delete_attachments(instance, **kwargs): | |
82 | for attachment in instance.attachments.all(): |
|
82 | for attachment in instance.attachments.all(): | |
83 | attachment_refs_count = attachment.attachment_posts.count() |
|
83 | attachment_refs_count = attachment.attachment_posts.count() | |
84 | if attachment_refs_count == 1: |
|
84 | if attachment_refs_count == 1: | |
85 | attachment.delete() |
|
85 | attachment.delete() | |
86 |
|
86 | |||
87 |
|
87 | |||
88 | @receiver(post_delete, sender=Post) |
|
88 | @receiver(post_delete, sender=Post) | |
89 | def update_thread_on_delete(instance, **kwargs): |
|
89 | def update_thread_on_delete(instance, **kwargs): | |
90 | thread = instance.get_thread() |
|
90 | thread = instance.get_thread() | |
91 | thread.last_edit_time = timezone.now() |
|
91 | thread.last_edit_time = timezone.now() | |
92 | thread.save() |
|
92 | thread.save() | |
93 |
|
93 | |||
94 |
|
94 | |||
95 | @receiver(post_delete, sender=Post) |
|
95 | @receiver(post_delete, sender=Post) | |
96 | def delete_global_id(instance, **kwargs): |
|
96 | def delete_global_id(instance, **kwargs): | |
97 | if instance.global_id and instance.global_id.id: |
|
97 | if instance.global_id and instance.global_id.id: | |
98 | instance.global_id.delete() |
|
98 | instance.global_id.delete() | |
99 |
|
99 | |||
100 |
|
100 | |||
101 | @receiver(post_save, sender=Attachment) |
|
101 | @receiver(post_save, sender=Attachment) | |
102 | def generate_thumb(instance, **kwargs): |
|
102 | def generate_thumb(instance, **kwargs): | |
103 | if instance.mimetype in FILE_TYPES_IMAGE: |
|
103 | if instance.mimetype in FILE_TYPES_IMAGE: | |
104 | for size in THUMB_SIZES: |
|
104 | for size in THUMB_SIZES: | |
105 | (w, h) = size |
|
105 | (w, h) = size | |
106 | split = instance.file.name.rsplit('.', 1) |
|
106 | split = instance.file.name.rsplit('.', 1) | |
107 | thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1]) |
|
107 | thumb_name = '%s.%sx%s.%s' % (split[0], w, h, split[1]) | |
108 |
|
108 | |||
109 | if not instance.file.storage.exists(thumb_name): |
|
109 | if not instance.file.storage.exists(thumb_name): | |
110 | # you can use another thumbnailing function if you like |
|
110 | # you can use another thumbnailing function if you like | |
111 | thumb_content = thumbs.generate_thumb(instance.file, size, split[1]) |
|
111 | thumb_content = thumbs.generate_thumb(instance.file, size, split[1]) | |
112 |
|
112 | |||
113 | thumb_name_ = instance.file.storage.save(thumb_name, thumb_content) |
|
113 | thumb_name_ = instance.file.storage.save(thumb_name, thumb_content) | |
114 |
|
114 | |||
115 | if not thumb_name == thumb_name_: |
|
115 | if not thumb_name == thumb_name_: | |
116 | raise ValueError( |
|
116 | raise ValueError( | |
117 | 'There is already a file named %s' % thumb_name_) |
|
117 | 'There is already a file named %s' % thumb_name_) | |
118 |
|
118 | |||
119 |
|
119 | |||
120 | @receiver(pre_delete, sender=Post) |
|
120 | @receiver(pre_delete, sender=Post) | |
121 | def rebuild_refmap(instance, **kwargs): |
|
121 | def rebuild_refmap(instance, **kwargs): | |
122 | for referenced_post in instance.refposts.all(): |
|
122 | for referenced_post in instance.refposts.all(): | |
123 | referenced_post.build_refmap(excluded_ids=[instance.id]) |
|
123 | referenced_post.build_refmap(excluded_ids=[instance.id]) | |
124 | referenced_post.save(update_fields=['refmap']) |
|
124 | referenced_post.save(update_fields=['refmap']) | |
125 |
|
125 | |||
126 |
|
126 | |||
127 | @receiver(post_delete, sender=Attachment) |
|
127 | @receiver(post_delete, sender=Attachment) | |
128 | def delete_file(instance, **kwargs): |
|
128 | def delete_file(instance, **kwargs): | |
129 | if instance.is_internal(): |
|
129 | if instance.is_internal(): | |
130 | file = MEDIA_ROOT + instance.file.name |
|
130 | file = MEDIA_ROOT + instance.file.name | |
131 | try: |
|
131 | try: | |
132 | os.remove(file) |
|
132 | os.remove(file) | |
133 | except FileNotFoundError: |
|
133 | except FileNotFoundError: | |
134 | pass |
|
134 | pass | |
135 | if instance.mimetype in FILE_TYPES_IMAGE: |
|
135 | if instance.mimetype in FILE_TYPES_IMAGE: | |
136 | for size in THUMB_SIZES: |
|
136 | for size in THUMB_SIZES: | |
137 | file_name_parts = instance.file.name.split('.') |
|
137 | file_name_parts = instance.file.name.split('.') | |
138 | thumb_file = MEDIA_ROOT + '{}.{}x{}.{}'.format(file_name_parts[0], size[0], size[1], file_name_parts[1]) |
|
138 | thumb_file = MEDIA_ROOT + '{}.{}x{}.{}'.format(file_name_parts[0], size[0], size[1], file_name_parts[1]) | |
139 | try: |
|
139 | try: | |
140 | os.remove(thumb_file) |
|
140 | os.remove(thumb_file) | |
141 | except FileNotFoundError: |
|
141 | except FileNotFoundError: | |
142 | pass |
|
142 | pass | |
143 |
|
143 |
@@ -1,90 +1,90 b'' | |||||
1 | from django.conf.urls import url |
|
1 | from django.conf.urls import url | |
2 | from django.urls import path |
|
2 | from django.urls import path | |
3 | from django.views.i18n import JavaScriptCatalog |
|
3 | from django.views.i18n import JavaScriptCatalog | |
4 |
|
4 | |||
5 | from boards import views |
|
5 | from boards import views | |
6 | from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed |
|
6 | from boards.rss import AllThreadsFeed, TagThreadsFeed, ThreadPostsFeed | |
7 | from boards.views import api, tag_threads, all_threads, settings, feed, stickers |
|
7 | from boards.views import api, tag_threads, all_threads, settings, feed, stickers, thread, banned | |
8 | from boards.views.authors import AuthorsView |
|
8 | from boards.views.authors import AuthorsView | |
9 | from boards.views.landing import LandingView |
|
9 | from boards.views.landing import LandingView | |
10 | from boards.views.notifications import NotificationView |
|
10 | from boards.views.notifications import NotificationView | |
11 | from boards.views.preview import PostPreviewView |
|
11 | from boards.views.preview import PostPreviewView | |
12 | from boards.views.random import RandomImageView |
|
12 | from boards.views.random import RandomImageView | |
13 | from boards.views.search import BoardSearchView |
|
13 | from boards.views.search import BoardSearchView | |
14 | from boards.views.static import StaticPageView |
|
14 | from boards.views.static import StaticPageView | |
15 | from boards.views.sync import get_post_sync_data, response_get, response_list |
|
15 | from boards.views.sync import get_post_sync_data, response_get, response_list | |
16 | from boards.views.tag_gallery import TagGalleryView |
|
16 | from boards.views.tag_gallery import TagGalleryView | |
17 | from boards.views.utils import UtilsView |
|
17 | from boards.views.utils import UtilsView | |
18 |
|
18 | |||
19 |
|
19 | |||
20 | urlpatterns = [ |
|
20 | urlpatterns = [ | |
21 | # /boards/ |
|
21 | # /boards/ | |
22 | path('all/', all_threads.AllThreadsView.as_view(), name='index'), |
|
22 | path('all/', all_threads.AllThreadsView.as_view(), name='index'), | |
23 |
|
23 | |||
24 | # /boards/tag/tag_name/ |
|
24 | # /boards/tag/tag_name/ | |
25 | url(r'^tag/(?P<tag_name>[\w\d\']+)/$', tag_threads.TagView.as_view(), |
|
25 | url(r'^tag/(?P<tag_name>[\w\d\']+)/$', tag_threads.TagView.as_view(), | |
26 | name='tag'), |
|
26 | name='tag'), | |
27 | url(r'^tag/(?P<tag_name>[\w\d\']+)/gallery/$', TagGalleryView.as_view(), name='tag_gallery'), |
|
27 | url(r'^tag/(?P<tag_name>[\w\d\']+)/gallery/$', TagGalleryView.as_view(), name='tag_gallery'), | |
28 |
|
28 | |||
29 | # /boards/thread/ |
|
29 | # /boards/thread/ | |
30 | path('thread/<int:post_id>/', views.thread.NormalThreadView.as_view(), |
|
30 | path('thread/<int:post_id>/', views.thread.NormalThreadView.as_view(), | |
31 | name='thread'), |
|
31 | name='thread'), | |
32 | path('thread/<int:post_id>/mode/gallery/', views.thread.GalleryThreadView.as_view(), |
|
32 | path('thread/<int:post_id>/mode/gallery/', views.thread.GalleryThreadView.as_view(), | |
33 | name='thread_gallery'), |
|
33 | name='thread_gallery'), | |
34 | path('thread/<int:post_id>/mode/tree/', views.thread.TreeThreadView.as_view(), |
|
34 | path('thread/<int:post_id>/mode/tree/', views.thread.TreeThreadView.as_view(), | |
35 | name='thread_tree'), |
|
35 | name='thread_tree'), | |
36 | # /feed/ |
|
36 | # /feed/ | |
37 | path('feed/', views.feed.FeedView.as_view(), name='feed'), |
|
37 | path('feed/', views.feed.FeedView.as_view(), name='feed'), | |
38 |
|
38 | |||
39 | path('settings/', settings.SettingsView.as_view(), name='settings'), |
|
39 | path('settings/', settings.SettingsView.as_view(), name='settings'), | |
40 | path('stickers/', stickers.AliasesView.as_view(), name='stickers'), |
|
40 | path('stickers/', stickers.AliasesView.as_view(), name='stickers'), | |
41 | path('stickers/<str:category>/', stickers.AliasesView.as_view(), name='stickers'), |
|
41 | path('stickers/<str:category>/', stickers.AliasesView.as_view(), name='stickers'), | |
42 | path('authors/', AuthorsView.as_view(), name='authors'), |
|
42 | path('authors/', AuthorsView.as_view(), name='authors'), | |
43 |
|
43 | |||
44 | path('banned/', views.banned.BannedView.as_view(), name='banned'), |
|
44 | path('banned/', views.banned.BannedView.as_view(), name='banned'), | |
45 | path('staticpage/<str:name>/', StaticPageView.as_view(), name='staticpage'), |
|
45 | path('staticpage/<str:name>/', StaticPageView.as_view(), name='staticpage'), | |
46 |
|
46 | |||
47 | path('random/', RandomImageView.as_view(), name='random'), |
|
47 | path('random/', RandomImageView.as_view(), name='random'), | |
48 | path('search/', BoardSearchView.as_view(), name='search'), |
|
48 | path('search/', BoardSearchView.as_view(), name='search'), | |
49 | path('', LandingView.as_view(), name='landing'), |
|
49 | path('', LandingView.as_view(), name='landing'), | |
50 | path('utils', UtilsView.as_view(), name='utils'), |
|
50 | path('utils', UtilsView.as_view(), name='utils'), | |
51 |
|
51 | |||
52 | # RSS feeds |
|
52 | # RSS feeds | |
53 | path('rss/', AllThreadsFeed()), |
|
53 | path('rss/', AllThreadsFeed()), | |
54 | path('all/rss/', AllThreadsFeed()), |
|
54 | path('all/rss/', AllThreadsFeed()), | |
55 | url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()), |
|
55 | url(r'^tag/(?P<tag_name>\w+)/rss/$', TagThreadsFeed()), | |
56 | path('thread/<int:post_id>/rss/', ThreadPostsFeed()), |
|
56 | path('thread/<int:post_id>/rss/', ThreadPostsFeed()), | |
57 |
|
57 | |||
58 | # i18n |
|
58 | # i18n | |
59 | path('jsi18n/', JavaScriptCatalog.as_view(packages=['boards']), name='js_info_dict'), |
|
59 | path('jsi18n/', JavaScriptCatalog.as_view(packages=['boards']), name='js_info_dict'), | |
60 |
|
60 | |||
61 | # API |
|
61 | # API | |
62 | url(r'^api/post/(?P<post_id>\d+)/$', api.get_post, name="get_post"), |
|
62 | url(r'^api/post/(?P<post_id>\d+)/$', api.get_post, name="get_post"), | |
63 | url(r'^api/diff_thread/$', api.api_get_threaddiff, name="get_thread_diff"), |
|
63 | url(r'^api/diff_thread/$', api.api_get_threaddiff, name="get_thread_diff"), | |
64 | url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads, |
|
64 | url(r'^api/threads/(?P<count>\w+)/$', api.api_get_threads, | |
65 | name='get_threads'), |
|
65 | name='get_threads'), | |
66 | url(r'^api/tags/$', api.api_get_tags, name='get_tags'), |
|
66 | url(r'^api/tags/$', api.api_get_tags, name='get_tags'), | |
67 | url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts, |
|
67 | url(r'^api/thread/(?P<opening_post_id>\w+)/$', api.api_get_thread_posts, | |
68 | name='get_thread'), |
|
68 | name='get_thread'), | |
69 | url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post, |
|
69 | url(r'^api/add_post/(?P<opening_post_id>\w+)/$', api.api_add_post, | |
70 | name='add_post'), |
|
70 | name='add_post'), | |
71 | url(r'^api/notifications/(?P<username>\w+)/$', api.api_get_notifications, |
|
71 | url(r'^api/notifications/(?P<username>\w+)/$', api.api_get_notifications, | |
72 | name='api_notifications'), |
|
72 | name='api_notifications'), | |
73 | url(r'^api/preview/$', api.api_get_preview, name='preview'), |
|
73 | url(r'^api/preview/$', api.api_get_preview, name='preview'), | |
74 | url(r'^api/new_posts/$', api.api_get_new_posts, name='new_posts'), |
|
74 | url(r'^api/new_posts/$', api.api_get_new_posts, name='new_posts'), | |
75 | url(r'^api/stickers/$', api.api_get_stickers, name='get_stickers'), |
|
75 | url(r'^api/stickers/$', api.api_get_stickers, name='get_stickers'), | |
76 |
|
76 | |||
77 | # Sync protocol API |
|
77 | # Sync protocol API | |
78 | url(r'^api/sync/list/$', response_list, name='api_sync_list'), |
|
78 | url(r'^api/sync/list/$', response_list, name='api_sync_list'), | |
79 | url(r'^api/sync/get/$', response_get, name='api_sync_get'), |
|
79 | url(r'^api/sync/get/$', response_get, name='api_sync_get'), | |
80 |
|
80 | |||
81 | # Notifications |
|
81 | # Notifications | |
82 | path('notifications/<str:username>/', NotificationView.as_view(), name='notifications'), |
|
82 | path('notifications/<str:username>/', NotificationView.as_view(), name='notifications'), | |
83 | path('notifications/', NotificationView.as_view(), name='notifications'), |
|
83 | path('notifications/', NotificationView.as_view(), name='notifications'), | |
84 |
|
84 | |||
85 | # Post preview |
|
85 | # Post preview | |
86 | path('preview/', PostPreviewView.as_view(), name='preview'), |
|
86 | path('preview/', PostPreviewView.as_view(), name='preview'), | |
87 | path('post_xml/<int:post_id>', get_post_sync_data, |
|
87 | path('post_xml/<int:post_id>', get_post_sync_data, | |
88 | name='post_sync_data'), |
|
88 | name='post_sync_data'), | |
89 | ] |
|
89 | ] | |
90 |
|
90 |
@@ -1,178 +1,132 b'' | |||||
1 | from django.core.paginator import EmptyPage |
|
1 | from django.core.paginator import EmptyPage | |
2 | from django.db import transaction |
|
|||
3 | from django.http import Http404 |
|
2 | from django.http import Http404 | |
4 | from django.shortcuts import render, redirect |
|
3 | from django.shortcuts import render, redirect | |
5 | from django.urls import reverse |
|
4 | from django.urls import reverse | |
6 | from django.utils.decorators import method_decorator |
|
5 | from django.utils.decorators import method_decorator | |
7 | from django.views.decorators.csrf import csrf_protect |
|
6 | from django.views.decorators.csrf import csrf_protect | |
8 |
|
7 | |||
9 |
from boards import |
|
8 | from boards import settings | |
10 | from boards.abstracts.paginator import get_paginator |
|
9 | from boards.abstracts.paginator import get_paginator | |
11 | from boards.abstracts.settingsmanager import get_settings_manager, \ |
|
10 | from boards.abstracts.settingsmanager import get_settings_manager, \ | |
12 | SETTING_ONLY_FAVORITES |
|
11 | SETTING_ONLY_FAVORITES | |
13 | from boards.forms import ThreadForm, PlainErrorList |
|
12 | from boards.forms import ThreadForm, PlainErrorList | |
14 |
from boards.models import Post, Thread |
|
13 | from boards.models import Post, Thread | |
15 | from boards.views.banned import BannedView |
|
|||
16 | from boards.views.base import BaseBoardView, CONTEXT_FORM |
|
14 | from boards.views.base import BaseBoardView, CONTEXT_FORM | |
17 | from boards.views.mixins import FileUploadMixin, PaginatedMixin, \ |
|
15 | from boards.views.mixins import FileUploadMixin, PaginatedMixin, \ | |
18 | DispatcherMixin, PARAMETER_METHOD |
|
16 | DispatcherMixin, PARAMETER_METHOD | |
19 | from boards.views.posting_mixin import PostMixin |
|
|||
20 |
|
17 | |||
21 | FORM_TAGS = 'tags' |
|
18 | FORM_TAGS = 'tags' | |
22 | FORM_TEXT = 'text' |
|
19 | FORM_TEXT = 'text' | |
23 | FORM_TITLE = 'title' |
|
20 | FORM_TITLE = 'title' | |
24 | FORM_IMAGE = 'image' |
|
21 | FORM_IMAGE = 'image' | |
25 | FORM_THREADS = 'threads' |
|
22 | FORM_THREADS = 'threads' | |
26 |
|
23 | |||
27 | TAG_DELIMITER = ' ' |
|
24 | TAG_DELIMITER = ' ' | |
28 |
|
25 | |||
29 | PARAMETER_CURRENT_PAGE = 'current_page' |
|
26 | PARAMETER_CURRENT_PAGE = 'current_page' | |
30 | PARAMETER_PAGINATOR = 'paginator' |
|
27 | PARAMETER_PAGINATOR = 'paginator' | |
31 | PARAMETER_THREADS = 'threads' |
|
28 | PARAMETER_THREADS = 'threads' | |
32 | PARAMETER_ADDITIONAL = 'additional_params' |
|
29 | PARAMETER_ADDITIONAL = 'additional_params' | |
33 | PARAMETER_MAX_FILE_SIZE = 'max_file_size' |
|
30 | PARAMETER_MAX_FILE_SIZE = 'max_file_size' | |
34 | PARAMETER_RSS_URL = 'rss_url' |
|
31 | PARAMETER_RSS_URL = 'rss_url' | |
35 | PARAMETER_MAX_FILES = 'max_files' |
|
32 | PARAMETER_MAX_FILES = 'max_files' | |
36 |
|
33 | |||
37 | TEMPLATE = 'boards/all_threads.html' |
|
34 | TEMPLATE = 'boards/all_threads.html' | |
38 | DEFAULT_PAGE = 1 |
|
35 | DEFAULT_PAGE = 1 | |
39 |
|
36 | |||
40 | FORM_TAGS = 'tags' |
|
|||
41 |
|
37 | |||
42 |
|
38 | class AllThreadsView(FileUploadMixin, BaseBoardView, PaginatedMixin, | ||
43 | class AllThreadsView(PostMixin, FileUploadMixin, BaseBoardView, PaginatedMixin, DispatcherMixin): |
|
39 | DispatcherMixin): | |
44 |
|
40 | |||
45 | tag_name = '' |
|
41 | tag_name = '' | |
46 |
|
42 | |||
47 | def __init__(self): |
|
43 | def __init__(self): | |
48 | self.settings_manager = None |
|
44 | self.settings_manager = None | |
49 | super(AllThreadsView, self).__init__() |
|
45 | super(AllThreadsView, self).__init__() | |
50 |
|
46 | |||
51 | @method_decorator(csrf_protect) |
|
47 | @method_decorator(csrf_protect) | |
52 | def get(self, request, form: ThreadForm=None): |
|
48 | def get(self, request, form: ThreadForm=None): | |
53 | page = request.GET.get('page', DEFAULT_PAGE) |
|
49 | page = request.GET.get('page', DEFAULT_PAGE) | |
54 |
|
50 | |||
55 | params = self.get_context_data(request=request) |
|
51 | params = self.get_context_data(request=request) | |
56 |
|
52 | |||
57 | if not form: |
|
53 | if not form: | |
58 | form = ThreadForm(error_class=PlainErrorList, |
|
54 | form = ThreadForm(error_class=PlainErrorList, | |
59 | initial={FORM_TAGS: self.tag_name}) |
|
55 | initial={FORM_TAGS: self.tag_name}) | |
60 |
|
56 | |||
61 | self.settings_manager = get_settings_manager(request) |
|
57 | self.settings_manager = get_settings_manager(request) | |
62 |
|
58 | |||
63 | threads = self.get_threads() |
|
59 | threads = self.get_threads() | |
64 |
|
60 | |||
65 | order = request.GET.get('order', 'bump') |
|
61 | order = request.GET.get('order', 'bump') | |
66 | if order == 'bump': |
|
62 | if order == 'bump': | |
67 | threads = threads.order_by('-bump_time') |
|
63 | threads = threads.order_by('-bump_time') | |
68 | else: |
|
64 | else: | |
69 | threads = threads.filter(replies__opening=True)\ |
|
65 | threads = threads.filter(replies__opening=True)\ | |
70 | .order_by('-replies__pub_time') |
|
66 | .order_by('-replies__pub_time') | |
71 | filter = request.GET.get('filter') |
|
67 | filter = request.GET.get('filter') | |
72 | threads = threads.distinct() |
|
68 | threads = threads.distinct() | |
73 |
|
69 | |||
74 | paginator = get_paginator(threads, |
|
70 | paginator = get_paginator(threads, | |
75 | settings.get_int('View', 'ThreadsPerPage')) |
|
71 | settings.get_int('View', 'ThreadsPerPage')) | |
76 | paginator.current_page = int(page) |
|
72 | paginator.current_page = int(page) | |
77 |
|
73 | |||
78 | try: |
|
74 | try: | |
79 | threads = paginator.page(page).object_list |
|
75 | threads = paginator.page(page).object_list | |
80 | except EmptyPage: |
|
76 | except EmptyPage: | |
81 | raise Http404() |
|
77 | raise Http404() | |
82 |
|
78 | |||
83 | params[PARAMETER_THREADS] = threads |
|
79 | params[PARAMETER_THREADS] = threads | |
84 | params[CONTEXT_FORM] = form |
|
80 | params[CONTEXT_FORM] = form | |
85 | params[PARAMETER_MAX_FILE_SIZE] = self.get_max_upload_size() |
|
81 | params[PARAMETER_MAX_FILE_SIZE] = self.get_max_upload_size() | |
86 | params[PARAMETER_RSS_URL] = self.get_rss_url() |
|
82 | params[PARAMETER_RSS_URL] = self.get_rss_url() | |
87 | params[PARAMETER_MAX_FILES] = settings.get_int('Forms', 'MaxFileCount') |
|
83 | params[PARAMETER_MAX_FILES] = settings.get_int('Forms', 'MaxFileCount') | |
88 |
|
84 | |||
89 | paginator.set_url(self.get_reverse_url(), request.GET.dict()) |
|
85 | paginator.set_url(self.get_reverse_url(), request.GET.dict()) | |
90 | params.update(self.get_page_context(paginator, page)) |
|
86 | params.update(self.get_page_context(paginator, page)) | |
91 |
|
87 | |||
92 | return render(request, TEMPLATE, params) |
|
88 | return render(request, TEMPLATE, params) | |
93 |
|
89 | |||
94 | @method_decorator(csrf_protect) |
|
90 | @method_decorator(csrf_protect) | |
95 | def post(self, request): |
|
91 | def post(self, request): | |
96 | if PARAMETER_METHOD in request.POST: |
|
92 | if PARAMETER_METHOD in request.POST: | |
97 | self.dispatch_method(request) |
|
93 | self.dispatch_method(request) | |
98 |
|
94 | |||
99 | return redirect('index') # FIXME Different for different modes |
|
95 | return redirect('index') # FIXME Different for different modes | |
100 |
|
96 | |||
101 | form = ThreadForm(request.POST, request.FILES, |
|
97 | form = ThreadForm(request.POST, request.FILES, | |
102 | error_class=PlainErrorList) |
|
98 | error_class=PlainErrorList) | |
103 | form.session = request.session |
|
99 | form.session = request.session | |
104 |
|
100 | |||
105 | if form.is_valid(): |
|
101 | if form.is_valid(): | |
106 |
return |
|
102 | return Post.objects.create_from_form(request, form, None) | |
107 | if form.need_to_ban: |
|
103 | if form.need_to_ban: | |
108 | # Ban user because he is suspected to be a bot |
|
104 | # Ban user because he is suspected to be a bot | |
109 | self._ban_current_user(request) |
|
105 | self._ban_current_user(request) | |
110 |
|
106 | |||
111 | return self.get(request, form) |
|
107 | return self.get(request, form) | |
112 |
|
108 | |||
113 | def get_reverse_url(self): |
|
109 | def get_reverse_url(self): | |
114 | return reverse('index') |
|
110 | return reverse('index') | |
115 |
|
111 | |||
116 | @transaction.atomic |
|
|||
117 | def create_thread(self, request, form: ThreadForm, html_response=True): |
|
|||
118 | """ |
|
|||
119 | Creates a new thread with an opening post. |
|
|||
120 | """ |
|
|||
121 |
|
||||
122 | ip = utils.get_client_ip(request) |
|
|||
123 | is_banned = Ban.objects.filter(ip=ip).exists() |
|
|||
124 |
|
||||
125 | if is_banned: |
|
|||
126 | if html_response: |
|
|||
127 | return redirect(BannedView().as_view()) |
|
|||
128 | else: |
|
|||
129 | return |
|
|||
130 |
|
||||
131 | data = form.cleaned_data |
|
|||
132 |
|
||||
133 | title = form.get_title() |
|
|||
134 | text = data[FORM_TEXT] |
|
|||
135 | files = form.get_files() |
|
|||
136 | file_urls = form.get_file_urls() |
|
|||
137 | images = form.get_images() |
|
|||
138 |
|
||||
139 | text = self._remove_invalid_links(text) |
|
|||
140 |
|
||||
141 | tags = data[FORM_TAGS] |
|
|||
142 | monochrome = form.is_monochrome() |
|
|||
143 | stickerpack = form.is_stickerpack() |
|
|||
144 |
|
||||
145 | post = Post.objects.create_post(title=title, text=text, files=files, |
|
|||
146 | ip=ip, tags=tags, |
|
|||
147 | tripcode=form.get_tripcode(), |
|
|||
148 | monochrome=monochrome, images=images, |
|
|||
149 | file_urls=file_urls, stickerpack=stickerpack) |
|
|||
150 |
|
||||
151 | if form.is_subscribe(): |
|
|||
152 | settings_manager = get_settings_manager(request) |
|
|||
153 | settings_manager.add_or_read_fav_thread(post) |
|
|||
154 |
|
||||
155 | if html_response: |
|
|||
156 | return redirect(post.get_absolute_url()) |
|
|||
157 |
|
||||
158 | def get_threads(self): |
|
112 | def get_threads(self): | |
159 | """ |
|
113 | """ | |
160 | Gets list of threads that will be shown on a page. |
|
114 | Gets list of threads that will be shown on a page. | |
161 | """ |
|
115 | """ | |
162 |
|
116 | |||
163 | threads = Thread.objects\ |
|
117 | threads = Thread.objects\ | |
164 | .exclude(tags__in=self.settings_manager.get_hidden_tags()) |
|
118 | .exclude(tags__in=self.settings_manager.get_hidden_tags()) | |
165 | if self.settings_manager.get_setting(SETTING_ONLY_FAVORITES): |
|
119 | if self.settings_manager.get_setting(SETTING_ONLY_FAVORITES): | |
166 | fav_tags = self.settings_manager.get_fav_tags() |
|
120 | fav_tags = self.settings_manager.get_fav_tags() | |
167 | if len(fav_tags) > 0: |
|
121 | if len(fav_tags) > 0: | |
168 | threads = threads.filter(tags__in=fav_tags) |
|
122 | threads = threads.filter(tags__in=fav_tags) | |
169 |
|
123 | |||
170 | return threads |
|
124 | return threads | |
171 |
|
125 | |||
172 | def get_rss_url(self): |
|
126 | def get_rss_url(self): | |
173 | return self.get_reverse_url() + 'rss/' |
|
127 | return self.get_reverse_url() + 'rss/' | |
174 |
|
128 | |||
175 | def toggle_fav(self, request): |
|
129 | def toggle_fav(self, request): | |
176 | settings_manager = get_settings_manager(request) |
|
130 | settings_manager = get_settings_manager(request) | |
177 | settings_manager.set_setting(SETTING_ONLY_FAVORITES, |
|
131 | settings_manager.set_setting(SETTING_ONLY_FAVORITES, | |
178 | not settings_manager.get_setting(SETTING_ONLY_FAVORITES, False)) |
|
132 | not settings_manager.get_setting(SETTING_ONLY_FAVORITES, False)) |
@@ -1,322 +1,321 b'' | |||||
1 | import json |
|
1 | import json | |
2 | import logging |
|
2 | import logging | |
3 |
|
3 | |||
4 | from django.core import serializers |
|
4 | from django.core import serializers | |
5 | from django.db import transaction |
|
5 | from django.db import transaction | |
6 | from django.db.models import Q |
|
6 | from django.db.models import Q | |
7 | from django.http import HttpResponse, HttpResponseBadRequest |
|
7 | from django.http import HttpResponse, HttpResponseBadRequest | |
8 | from django.shortcuts import get_object_or_404 |
|
8 | from django.shortcuts import get_object_or_404 | |
9 | from django.views.decorators.csrf import csrf_protect |
|
9 | from django.views.decorators.csrf import csrf_protect | |
10 |
|
10 | |||
11 | from boards.abstracts.settingsmanager import get_settings_manager |
|
11 | from boards.abstracts.settingsmanager import get_settings_manager | |
12 | from boards.forms import PostForm, PlainErrorList |
|
12 | from boards.forms import PostForm, PlainErrorList | |
13 | from boards.mdx_neboard import Parser |
|
13 | from boards.mdx_neboard import Parser | |
14 | from boards.models import Post, Thread, Tag, TagAlias |
|
14 | from boards.models import Post, Thread, Tag, TagAlias | |
15 | from boards.models.attachment import AttachmentSticker |
|
15 | from boards.models.attachment import AttachmentSticker | |
16 | from boards.models.thread import STATUS_ARCHIVE |
|
16 | from boards.models.thread import STATUS_ARCHIVE | |
17 | from boards.models.user import Notification |
|
17 | from boards.models.user import Notification | |
18 | from boards.utils import datetime_to_epoch |
|
18 | from boards.utils import datetime_to_epoch | |
19 | from boards.views.thread import ThreadView |
|
|||
20 |
|
19 | |||
21 | __author__ = 'neko259' |
|
20 | __author__ = 'neko259' | |
22 |
|
21 | |||
23 | PARAMETER_TRUNCATED = 'truncated' |
|
22 | PARAMETER_TRUNCATED = 'truncated' | |
24 | PARAMETER_TAG = 'tag' |
|
23 | PARAMETER_TAG = 'tag' | |
25 | PARAMETER_OFFSET = 'offset' |
|
24 | PARAMETER_OFFSET = 'offset' | |
26 | PARAMETER_DIFF_TYPE = 'type' |
|
25 | PARAMETER_DIFF_TYPE = 'type' | |
27 | PARAMETER_POST = 'post' |
|
26 | PARAMETER_POST = 'post' | |
28 | PARAMETER_UPDATED = 'updated' |
|
27 | PARAMETER_UPDATED = 'updated' | |
29 | PARAMETER_LAST_UPDATE = 'last_update' |
|
28 | PARAMETER_LAST_UPDATE = 'last_update' | |
30 | PARAMETER_THREAD = 'thread' |
|
29 | PARAMETER_THREAD = 'thread' | |
31 | PARAMETER_UIDS = 'uids' |
|
30 | PARAMETER_UIDS = 'uids' | |
32 | PARAMETER_SUBSCRIBED = 'subscribed' |
|
31 | PARAMETER_SUBSCRIBED = 'subscribed' | |
33 |
|
32 | |||
34 | DIFF_TYPE_HTML = 'html' |
|
33 | DIFF_TYPE_HTML = 'html' | |
35 | DIFF_TYPE_JSON = 'json' |
|
34 | DIFF_TYPE_JSON = 'json' | |
36 |
|
35 | |||
37 | STATUS_OK = 'ok' |
|
36 | STATUS_OK = 'ok' | |
38 | STATUS_ERROR = 'error' |
|
37 | STATUS_ERROR = 'error' | |
39 |
|
38 | |||
40 | logger = logging.getLogger(__name__) |
|
39 | logger = logging.getLogger(__name__) | |
41 |
|
40 | |||
42 |
|
41 | |||
43 | @transaction.atomic |
|
42 | @transaction.atomic | |
44 | def api_get_threaddiff(request): |
|
43 | def api_get_threaddiff(request): | |
45 | """ |
|
44 | """ | |
46 | Gets posts that were changed or added since time |
|
45 | Gets posts that were changed or added since time | |
47 | """ |
|
46 | """ | |
48 |
|
47 | |||
49 | thread_id = request.POST.get(PARAMETER_THREAD) |
|
48 | thread_id = request.POST.get(PARAMETER_THREAD) | |
50 | uids_str = request.POST.get(PARAMETER_UIDS) |
|
49 | uids_str = request.POST.get(PARAMETER_UIDS) | |
51 |
|
50 | |||
52 | if not thread_id or not uids_str: |
|
51 | if not thread_id or not uids_str: | |
53 | return HttpResponse(content='Invalid request.') |
|
52 | return HttpResponse(content='Invalid request.') | |
54 |
|
53 | |||
55 | uids = uids_str.strip().split(' ') |
|
54 | uids = uids_str.strip().split(' ') | |
56 |
|
55 | |||
57 | opening_post = get_object_or_404(Post, id=thread_id) |
|
56 | opening_post = get_object_or_404(Post, id=thread_id) | |
58 | thread = opening_post.get_thread() |
|
57 | thread = opening_post.get_thread() | |
59 |
|
58 | |||
60 | json_data = { |
|
59 | json_data = { | |
61 | PARAMETER_UPDATED: [], |
|
60 | PARAMETER_UPDATED: [], | |
62 | PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already? |
|
61 | PARAMETER_LAST_UPDATE: None, # TODO Maybe this can be removed already? | |
63 | } |
|
62 | } | |
64 | posts = Post.objects.filter(thread=thread).exclude(uid__in=uids) |
|
63 | posts = Post.objects.filter(thread=thread).exclude(uid__in=uids) | |
65 |
|
64 | |||
66 | diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML) |
|
65 | diff_type = request.GET.get(PARAMETER_DIFF_TYPE, DIFF_TYPE_HTML) | |
67 |
|
66 | |||
68 | for post in posts: |
|
67 | for post in posts: | |
69 | json_data[PARAMETER_UPDATED].append(post.get_post_data( |
|
68 | json_data[PARAMETER_UPDATED].append(post.get_post_data( | |
70 | format_type=diff_type, request=request)) |
|
69 | format_type=diff_type, request=request)) | |
71 | json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time) |
|
70 | json_data[PARAMETER_LAST_UPDATE] = str(thread.last_edit_time) | |
72 |
|
71 | |||
73 | settings_manager = get_settings_manager(request) |
|
72 | settings_manager = get_settings_manager(request) | |
74 | json_data[PARAMETER_SUBSCRIBED] = str(settings_manager.thread_is_fav(opening_post)) |
|
73 | json_data[PARAMETER_SUBSCRIBED] = str(settings_manager.thread_is_fav(opening_post)) | |
75 |
|
74 | |||
76 | # If the tag is favorite, update the counter |
|
75 | # If the tag is favorite, update the counter | |
77 | settings_manager = get_settings_manager(request) |
|
76 | settings_manager = get_settings_manager(request) | |
78 | favorite = settings_manager.thread_is_fav(opening_post) |
|
77 | favorite = settings_manager.thread_is_fav(opening_post) | |
79 | if favorite: |
|
78 | if favorite: | |
80 | settings_manager.add_or_read_fav_thread(opening_post) |
|
79 | settings_manager.add_or_read_fav_thread(opening_post) | |
81 |
|
80 | |||
82 | return HttpResponse(content=json.dumps(json_data)) |
|
81 | return HttpResponse(content=json.dumps(json_data)) | |
83 |
|
82 | |||
84 |
|
83 | |||
85 | @csrf_protect |
|
84 | @csrf_protect | |
86 | def api_add_post(request, opening_post_id): |
|
85 | def api_add_post(request, opening_post_id): | |
87 | """ |
|
86 | """ | |
88 | Adds a post and return the JSON response for it |
|
87 | Adds a post and return the JSON response for it | |
89 | """ |
|
88 | """ | |
90 |
|
89 | |||
91 | # TODO Allow thread creation here too, without specifying opening post |
|
90 | # TODO Allow thread creation here too, without specifying opening post | |
92 | opening_post = get_object_or_404(Post, id=opening_post_id) |
|
91 | opening_post = get_object_or_404(Post, id=opening_post_id) | |
93 |
|
92 | |||
94 | status = STATUS_OK |
|
93 | status = STATUS_OK | |
95 | errors = [] |
|
94 | errors = [] | |
96 |
|
95 | |||
97 | post = None |
|
96 | post = None | |
98 | if request.method == 'POST': |
|
97 | if request.method == 'POST': | |
99 | form = PostForm(request.POST, request.FILES, error_class=PlainErrorList) |
|
98 | form = PostForm(request.POST, request.FILES, error_class=PlainErrorList) | |
100 | form.session = request.session |
|
99 | form.session = request.session | |
101 |
|
100 | |||
102 | if form.need_to_ban: |
|
101 | if form.need_to_ban: | |
103 | # Ban user because he is suspected to be a bot |
|
102 | # Ban user because he is suspected to be a bot | |
104 | # _ban_current_user(request) |
|
103 | # _ban_current_user(request) | |
105 | status = STATUS_ERROR |
|
104 | status = STATUS_ERROR | |
106 | if form.is_valid(): |
|
105 | if form.is_valid(): | |
107 |
post = |
|
106 | post = Post.objects.create_from_form(request, form, opening_post, | |
108 | html_response=False) |
|
107 | html_response=False) | |
109 | if not post: |
|
108 | if not post: | |
110 | status = STATUS_ERROR |
|
109 | status = STATUS_ERROR | |
111 | else: |
|
110 | else: | |
112 | logger.info('Added post #%d via api.' % post.id) |
|
111 | logger.info('Added post #%d via api.' % post.id) | |
113 | else: |
|
112 | else: | |
114 | status = STATUS_ERROR |
|
113 | status = STATUS_ERROR | |
115 | errors = form.as_json_errors() |
|
114 | errors = form.as_json_errors() | |
116 | else: |
|
115 | else: | |
117 | status = STATUS_ERROR |
|
116 | status = STATUS_ERROR | |
118 |
|
117 | |||
119 | response = { |
|
118 | response = { | |
120 | 'status': status, |
|
119 | 'status': status, | |
121 | 'errors': errors, |
|
120 | 'errors': errors, | |
122 | } |
|
121 | } | |
123 |
|
122 | |||
124 | if post: |
|
123 | if post: | |
125 | response['post_id'] = post.id |
|
124 | response['post_id'] = post.id | |
126 |
|
125 | |||
127 | return HttpResponse(content=json.dumps(response)) |
|
126 | return HttpResponse(content=json.dumps(response)) | |
128 |
|
127 | |||
129 |
|
128 | |||
130 | def get_post(request, post_id): |
|
129 | def get_post(request, post_id): | |
131 | """ |
|
130 | """ | |
132 | Gets the html of a post. Used for popups. Post can be truncated if used |
|
131 | Gets the html of a post. Used for popups. Post can be truncated if used | |
133 | in threads list with 'truncated' get parameter. |
|
132 | in threads list with 'truncated' get parameter. | |
134 | """ |
|
133 | """ | |
135 |
|
134 | |||
136 | post = get_object_or_404(Post, id=post_id) |
|
135 | post = get_object_or_404(Post, id=post_id) | |
137 | truncated = PARAMETER_TRUNCATED in request.GET |
|
136 | truncated = PARAMETER_TRUNCATED in request.GET | |
138 |
|
137 | |||
139 | return HttpResponse(content=post.get_view(truncated=truncated, need_op_data=True)) |
|
138 | return HttpResponse(content=post.get_view(truncated=truncated, need_op_data=True)) | |
140 |
|
139 | |||
141 |
|
140 | |||
142 | def api_get_threads(request, count): |
|
141 | def api_get_threads(request, count): | |
143 | """ |
|
142 | """ | |
144 | Gets the JSON thread opening posts list. |
|
143 | Gets the JSON thread opening posts list. | |
145 | Parameters that can be used for filtering: |
|
144 | Parameters that can be used for filtering: | |
146 | tag, offset (from which thread to get results) |
|
145 | tag, offset (from which thread to get results) | |
147 | """ |
|
146 | """ | |
148 |
|
147 | |||
149 | if PARAMETER_TAG in request.GET: |
|
148 | if PARAMETER_TAG in request.GET: | |
150 | tag_name = request.GET[PARAMETER_TAG] |
|
149 | tag_name = request.GET[PARAMETER_TAG] | |
151 | if tag_name is not None: |
|
150 | if tag_name is not None: | |
152 | tag = get_object_or_404(Tag, name=tag_name) |
|
151 | tag = get_object_or_404(Tag, name=tag_name) | |
153 | threads = tag.get_threads().exclude(status=STATUS_ARCHIVE) |
|
152 | threads = tag.get_threads().exclude(status=STATUS_ARCHIVE) | |
154 | else: |
|
153 | else: | |
155 | threads = Thread.objects.exclude(status=STATUS_ARCHIVE) |
|
154 | threads = Thread.objects.exclude(status=STATUS_ARCHIVE) | |
156 |
|
155 | |||
157 | if PARAMETER_OFFSET in request.GET: |
|
156 | if PARAMETER_OFFSET in request.GET: | |
158 | offset = request.GET[PARAMETER_OFFSET] |
|
157 | offset = request.GET[PARAMETER_OFFSET] | |
159 | offset = int(offset) if offset is not None else 0 |
|
158 | offset = int(offset) if offset is not None else 0 | |
160 | else: |
|
159 | else: | |
161 | offset = 0 |
|
160 | offset = 0 | |
162 |
|
161 | |||
163 | threads = threads.order_by('-bump_time') |
|
162 | threads = threads.order_by('-bump_time') | |
164 | threads = threads[offset:offset + int(count)] |
|
163 | threads = threads[offset:offset + int(count)] | |
165 |
|
164 | |||
166 | opening_posts = [] |
|
165 | opening_posts = [] | |
167 | for thread in threads: |
|
166 | for thread in threads: | |
168 | opening_post = thread.get_opening_post() |
|
167 | opening_post = thread.get_opening_post() | |
169 |
|
168 | |||
170 | # TODO Add tags, replies and images count |
|
169 | # TODO Add tags, replies and images count | |
171 | post_data = opening_post.get_post_data(include_last_update=True) |
|
170 | post_data = opening_post.get_post_data(include_last_update=True) | |
172 | post_data['status'] = thread.get_status() |
|
171 | post_data['status'] = thread.get_status() | |
173 |
|
172 | |||
174 | opening_posts.append(post_data) |
|
173 | opening_posts.append(post_data) | |
175 |
|
174 | |||
176 | return HttpResponse(content=json.dumps(opening_posts)) |
|
175 | return HttpResponse(content=json.dumps(opening_posts)) | |
177 |
|
176 | |||
178 |
|
177 | |||
179 | # TODO Test this |
|
178 | # TODO Test this | |
180 | def api_get_tags(request): |
|
179 | def api_get_tags(request): | |
181 | """ |
|
180 | """ | |
182 | Gets all tags or user tags. |
|
181 | Gets all tags or user tags. | |
183 | """ |
|
182 | """ | |
184 |
|
183 | |||
185 | # TODO Get favorite tags for the given user ID |
|
184 | # TODO Get favorite tags for the given user ID | |
186 |
|
185 | |||
187 | tags = TagAlias.objects.all() |
|
186 | tags = TagAlias.objects.all() | |
188 |
|
187 | |||
189 | term = request.GET.get('term') |
|
188 | term = request.GET.get('term') | |
190 | if term is not None: |
|
189 | if term is not None: | |
191 | tags = tags.filter(name__contains=term) |
|
190 | tags = tags.filter(name__contains=term) | |
192 |
|
191 | |||
193 | tag_names = [tag.name for tag in tags] |
|
192 | tag_names = [tag.name for tag in tags] | |
194 |
|
193 | |||
195 | return HttpResponse(content=json.dumps(tag_names)) |
|
194 | return HttpResponse(content=json.dumps(tag_names)) | |
196 |
|
195 | |||
197 |
|
196 | |||
198 | def api_get_stickers(request): |
|
197 | def api_get_stickers(request): | |
199 | term = request.GET.get('term') |
|
198 | term = request.GET.get('term') | |
200 | if not term: |
|
199 | if not term: | |
201 | return HttpResponseBadRequest() |
|
200 | return HttpResponseBadRequest() | |
202 |
|
201 | |||
203 | global_stickers = AttachmentSticker.objects.filter(Q(name__icontains=term) | Q(stickerpack__name__icontains=term)) |
|
202 | global_stickers = AttachmentSticker.objects.filter(Q(name__icontains=term) | Q(stickerpack__name__icontains=term)) | |
204 | local_stickers = [sticker for sticker in get_settings_manager(request).get_stickers() if term in sticker.name] |
|
203 | local_stickers = [sticker for sticker in get_settings_manager(request).get_stickers() if term in sticker.name] | |
205 | stickers = list(global_stickers) + local_stickers |
|
204 | stickers = list(global_stickers) + local_stickers | |
206 |
|
205 | |||
207 | image_dict = [{'thumb': sticker.attachment.get_thumb_url(), |
|
206 | image_dict = [{'thumb': sticker.attachment.get_thumb_url(), | |
208 | 'alias': str(sticker)} |
|
207 | 'alias': str(sticker)} | |
209 | for sticker in stickers] |
|
208 | for sticker in stickers] | |
210 |
|
209 | |||
211 | return HttpResponse(content=json.dumps(image_dict)) |
|
210 | return HttpResponse(content=json.dumps(image_dict)) | |
212 |
|
211 | |||
213 |
|
212 | |||
214 | # TODO The result can be cached by the thread last update time |
|
213 | # TODO The result can be cached by the thread last update time | |
215 | # TODO Test this |
|
214 | # TODO Test this | |
216 | def api_get_thread_posts(request, opening_post_id): |
|
215 | def api_get_thread_posts(request, opening_post_id): | |
217 | """ |
|
216 | """ | |
218 | Gets the JSON array of thread posts |
|
217 | Gets the JSON array of thread posts | |
219 | """ |
|
218 | """ | |
220 |
|
219 | |||
221 | opening_post = get_object_or_404(Post, id=opening_post_id) |
|
220 | opening_post = get_object_or_404(Post, id=opening_post_id) | |
222 | thread = opening_post.get_thread() |
|
221 | thread = opening_post.get_thread() | |
223 | posts = thread.get_replies() |
|
222 | posts = thread.get_replies() | |
224 |
|
223 | |||
225 | json_data = { |
|
224 | json_data = { | |
226 | 'posts': [], |
|
225 | 'posts': [], | |
227 | 'last_update': None, |
|
226 | 'last_update': None, | |
228 | } |
|
227 | } | |
229 | json_post_list = [] |
|
228 | json_post_list = [] | |
230 |
|
229 | |||
231 | for post in posts: |
|
230 | for post in posts: | |
232 | json_post_list.append(post.get_post_data()) |
|
231 | json_post_list.append(post.get_post_data()) | |
233 | json_data['last_update'] = datetime_to_epoch(thread.last_edit_time) |
|
232 | json_data['last_update'] = datetime_to_epoch(thread.last_edit_time) | |
234 | json_data['posts'] = json_post_list |
|
233 | json_data['posts'] = json_post_list | |
235 |
|
234 | |||
236 | return HttpResponse(content=json.dumps(json_data)) |
|
235 | return HttpResponse(content=json.dumps(json_data)) | |
237 |
|
236 | |||
238 |
|
237 | |||
239 | def api_get_notifications(request, username): |
|
238 | def api_get_notifications(request, username): | |
240 | last_notification_id_str = request.GET.get('last', None) |
|
239 | last_notification_id_str = request.GET.get('last', None) | |
241 | last_id = int(last_notification_id_str) if last_notification_id_str is not None else None |
|
240 | last_id = int(last_notification_id_str) if last_notification_id_str is not None else None | |
242 |
|
241 | |||
243 | posts = Notification.objects.get_notification_posts(usernames=[username], |
|
242 | posts = Notification.objects.get_notification_posts(usernames=[username], | |
244 | last=last_id) |
|
243 | last=last_id) | |
245 |
|
244 | |||
246 | json_post_list = [] |
|
245 | json_post_list = [] | |
247 | for post in posts: |
|
246 | for post in posts: | |
248 | json_post_list.append(post.get_post_data()) |
|
247 | json_post_list.append(post.get_post_data()) | |
249 | return HttpResponse(content=json.dumps(json_post_list)) |
|
248 | return HttpResponse(content=json.dumps(json_post_list)) | |
250 |
|
249 | |||
251 |
|
250 | |||
252 | def api_get_post(request, post_id): |
|
251 | def api_get_post(request, post_id): | |
253 | """ |
|
252 | """ | |
254 | Gets the JSON of a post. This can be |
|
253 | Gets the JSON of a post. This can be | |
255 | used as and API for external clients. |
|
254 | used as and API for external clients. | |
256 | """ |
|
255 | """ | |
257 |
|
256 | |||
258 | post = get_object_or_404(Post, id=post_id) |
|
257 | post = get_object_or_404(Post, id=post_id) | |
259 |
|
258 | |||
260 | json = serializers.serialize("json", [post], fields=( |
|
259 | json = serializers.serialize("json", [post], fields=( | |
261 | "pub_time", "_text_rendered", "title", "text", "image", |
|
260 | "pub_time", "_text_rendered", "title", "text", "image", | |
262 | "image_width", "image_height", "replies", "tags" |
|
261 | "image_width", "image_height", "replies", "tags" | |
263 | )) |
|
262 | )) | |
264 |
|
263 | |||
265 | return HttpResponse(content=json) |
|
264 | return HttpResponse(content=json) | |
266 |
|
265 | |||
267 |
|
266 | |||
268 | def api_get_preview(request): |
|
267 | def api_get_preview(request): | |
269 | raw_text = request.POST['raw_text'] |
|
268 | raw_text = request.POST['raw_text'] | |
270 |
|
269 | |||
271 | parser = Parser() |
|
270 | parser = Parser() | |
272 | return HttpResponse(content=parser.parse(parser.preparse(raw_text))) |
|
271 | return HttpResponse(content=parser.parse(parser.preparse(raw_text))) | |
273 |
|
272 | |||
274 |
|
273 | |||
275 | def api_get_new_posts(request): |
|
274 | def api_get_new_posts(request): | |
276 | """ |
|
275 | """ | |
277 | Gets favorite threads and unread posts count. |
|
276 | Gets favorite threads and unread posts count. | |
278 | """ |
|
277 | """ | |
279 | posts = list() |
|
278 | posts = list() | |
280 |
|
279 | |||
281 | include_posts = 'include_posts' in request.GET |
|
280 | include_posts = 'include_posts' in request.GET | |
282 |
|
281 | |||
283 | settings_manager = get_settings_manager(request) |
|
282 | settings_manager = get_settings_manager(request) | |
284 | fav_threads = settings_manager.get_fav_threads() |
|
283 | fav_threads = settings_manager.get_fav_threads() | |
285 | fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\ |
|
284 | fav_thread_ops = Post.objects.filter(id__in=fav_threads.keys())\ | |
286 | .order_by('-pub_time').prefetch_related('thread') |
|
285 | .order_by('-pub_time').prefetch_related('thread') | |
287 |
|
286 | |||
288 | ops = [{'op': op, 'last_id': fav_threads[str(op.id)]} for op in fav_thread_ops] |
|
287 | ops = [{'op': op, 'last_id': fav_threads[str(op.id)]} for op in fav_thread_ops] | |
289 | if include_posts: |
|
288 | if include_posts: | |
290 | new_post_threads = Thread.objects.get_new_posts(ops) |
|
289 | new_post_threads = Thread.objects.get_new_posts(ops) | |
291 | if new_post_threads: |
|
290 | if new_post_threads: | |
292 | thread_ids = {thread.id: thread for thread in new_post_threads} |
|
291 | thread_ids = {thread.id: thread for thread in new_post_threads} | |
293 | else: |
|
292 | else: | |
294 | thread_ids = dict() |
|
293 | thread_ids = dict() | |
295 |
|
294 | |||
296 | for op in fav_thread_ops: |
|
295 | for op in fav_thread_ops: | |
297 | fav_thread_dict = dict() |
|
296 | fav_thread_dict = dict() | |
298 |
|
297 | |||
299 | op_thread = op.get_thread() |
|
298 | op_thread = op.get_thread() | |
300 | if op_thread.id in thread_ids: |
|
299 | if op_thread.id in thread_ids: | |
301 | thread = thread_ids[op_thread.id] |
|
300 | thread = thread_ids[op_thread.id] | |
302 | new_post_count = thread.new_post_count |
|
301 | new_post_count = thread.new_post_count | |
303 | fav_thread_dict['newest_post_link'] = thread.get_replies()\ |
|
302 | fav_thread_dict['newest_post_link'] = thread.get_replies()\ | |
304 | .filter(id__gt=fav_threads[str(op.id)])\ |
|
303 | .filter(id__gt=fav_threads[str(op.id)])\ | |
305 | .first().get_absolute_url(thread=thread) |
|
304 | .first().get_absolute_url(thread=thread) | |
306 | else: |
|
305 | else: | |
307 | new_post_count = 0 |
|
306 | new_post_count = 0 | |
308 | fav_thread_dict['new_post_count'] = new_post_count |
|
307 | fav_thread_dict['new_post_count'] = new_post_count | |
309 |
|
308 | |||
310 | fav_thread_dict['id'] = op.id |
|
309 | fav_thread_dict['id'] = op.id | |
311 |
|
310 | |||
312 | fav_thread_dict['post_url'] = op.get_link_view() |
|
311 | fav_thread_dict['post_url'] = op.get_link_view() | |
313 | fav_thread_dict['title'] = op.title |
|
312 | fav_thread_dict['title'] = op.title | |
314 |
|
313 | |||
315 | posts.append(fav_thread_dict) |
|
314 | posts.append(fav_thread_dict) | |
316 | else: |
|
315 | else: | |
317 | fav_thread_dict = dict() |
|
316 | fav_thread_dict = dict() | |
318 | fav_thread_dict['new_post_count'] = \ |
|
317 | fav_thread_dict['new_post_count'] = \ | |
319 | Thread.objects.get_new_post_count(ops) |
|
318 | Thread.objects.get_new_post_count(ops) | |
320 | posts.append(fav_thread_dict) |
|
319 | posts.append(fav_thread_dict) | |
321 |
|
320 | |||
322 | return HttpResponse(content=json.dumps(posts)) |
|
321 | return HttpResponse(content=json.dumps(posts)) |
@@ -1,131 +1,130 b'' | |||||
1 | from django.urls import reverse |
|
1 | from django.urls import reverse | |
2 | from django.shortcuts import render |
|
2 | from django.shortcuts import render | |
3 |
|
3 | |||
4 | from boards import settings |
|
4 | from boards import settings | |
5 | from boards.abstracts.paginator import get_paginator |
|
5 | from boards.abstracts.paginator import get_paginator | |
6 | from boards.abstracts.settingsmanager import get_settings_manager |
|
6 | from boards.abstracts.settingsmanager import get_settings_manager | |
7 | from boards.models import Post |
|
7 | from boards.models import Post | |
8 | from boards.views.base import BaseBoardView |
|
8 | from boards.views.base import BaseBoardView | |
9 | from boards.views.posting_mixin import PostMixin |
|
|||
10 | from boards.views.mixins import PaginatedMixin |
|
9 | from boards.views.mixins import PaginatedMixin | |
11 |
|
10 | |||
12 | POSTS_PER_PAGE = settings.get_int('View', 'PostsPerPage') |
|
11 | POSTS_PER_PAGE = settings.get_int('View', 'PostsPerPage') | |
13 |
|
12 | |||
14 | PARAMETER_POSTS = 'posts' |
|
13 | PARAMETER_POSTS = 'posts' | |
15 | PARAMETER_QUERIES = 'queries' |
|
14 | PARAMETER_QUERIES = 'queries' | |
16 |
|
15 | |||
17 | TEMPLATE = 'boards/feed.html' |
|
16 | TEMPLATE = 'boards/feed.html' | |
18 | DEFAULT_PAGE = 1 |
|
17 | DEFAULT_PAGE = 1 | |
19 |
|
18 | |||
20 |
|
19 | |||
21 | class FeedFilter: |
|
20 | class FeedFilter: | |
22 | @staticmethod |
|
21 | @staticmethod | |
23 | def get_filtered_posts(request, posts): |
|
22 | def get_filtered_posts(request, posts): | |
24 | return posts |
|
23 | return posts | |
25 |
|
24 | |||
26 | @staticmethod |
|
25 | @staticmethod | |
27 | def get_query(request): |
|
26 | def get_query(request): | |
28 | return None |
|
27 | return None | |
29 |
|
28 | |||
30 |
|
29 | |||
31 | class TripcodeFilter(FeedFilter): |
|
30 | class TripcodeFilter(FeedFilter): | |
32 | @staticmethod |
|
31 | @staticmethod | |
33 | def get_filtered_posts(request, posts): |
|
32 | def get_filtered_posts(request, posts): | |
34 | filtered_posts = posts |
|
33 | filtered_posts = posts | |
35 | tripcode = request.GET.get('tripcode', None) |
|
34 | tripcode = request.GET.get('tripcode', None) | |
36 | if tripcode: |
|
35 | if tripcode: | |
37 | filtered_posts = filtered_posts.filter(tripcode=tripcode) |
|
36 | filtered_posts = filtered_posts.filter(tripcode=tripcode) | |
38 | return filtered_posts |
|
37 | return filtered_posts | |
39 |
|
38 | |||
40 | @staticmethod |
|
39 | @staticmethod | |
41 | def get_query(request): |
|
40 | def get_query(request): | |
42 | tripcode = request.GET.get('tripcode', None) |
|
41 | tripcode = request.GET.get('tripcode', None) | |
43 | if tripcode: |
|
42 | if tripcode: | |
44 | return 'Tripcode: {}'.format(tripcode) |
|
43 | return 'Tripcode: {}'.format(tripcode) | |
45 |
|
44 | |||
46 |
|
45 | |||
47 | class FavoritesFilter(FeedFilter): |
|
46 | class FavoritesFilter(FeedFilter): | |
48 | @staticmethod |
|
47 | @staticmethod | |
49 | def get_filtered_posts(request, posts): |
|
48 | def get_filtered_posts(request, posts): | |
50 | filtered_posts = posts |
|
49 | filtered_posts = posts | |
51 |
|
50 | |||
52 | favorites = 'favorites' in request.GET |
|
51 | favorites = 'favorites' in request.GET | |
53 | if favorites: |
|
52 | if favorites: | |
54 | settings_manager = get_settings_manager(request) |
|
53 | settings_manager = get_settings_manager(request) | |
55 | fav_thread_ops = Post.objects.filter(id__in=settings_manager.get_fav_threads().keys()) |
|
54 | fav_thread_ops = Post.objects.filter(id__in=settings_manager.get_fav_threads().keys()) | |
56 | fav_threads = [op.get_thread() for op in fav_thread_ops] |
|
55 | fav_threads = [op.get_thread() for op in fav_thread_ops] | |
57 | filtered_posts = filtered_posts.filter(thread__in=fav_threads) |
|
56 | filtered_posts = filtered_posts.filter(thread__in=fav_threads) | |
58 | return filtered_posts |
|
57 | return filtered_posts | |
59 |
|
58 | |||
60 |
|
59 | |||
61 | class IpFilter(FeedFilter): |
|
60 | class IpFilter(FeedFilter): | |
62 | @staticmethod |
|
61 | @staticmethod | |
63 | def get_filtered_posts(request, posts): |
|
62 | def get_filtered_posts(request, posts): | |
64 | filtered_posts = posts |
|
63 | filtered_posts = posts | |
65 |
|
64 | |||
66 | ip = request.GET.get('ip', None) |
|
65 | ip = request.GET.get('ip', None) | |
67 | if ip and request.user.has_perm('post_delete'): |
|
66 | if ip and request.user.has_perm('post_delete'): | |
68 | filtered_posts = filtered_posts.filter(poster_ip=ip) |
|
67 | filtered_posts = filtered_posts.filter(poster_ip=ip) | |
69 | return filtered_posts |
|
68 | return filtered_posts | |
70 |
|
69 | |||
71 | @staticmethod |
|
70 | @staticmethod | |
72 | def get_query(request): |
|
71 | def get_query(request): | |
73 | ip = request.GET.get('ip', None) |
|
72 | ip = request.GET.get('ip', None) | |
74 | if ip: |
|
73 | if ip: | |
75 | return 'IP: {}'.format(ip) |
|
74 | return 'IP: {}'.format(ip) | |
76 |
|
75 | |||
77 |
|
76 | |||
78 | class ImageFilter(FeedFilter): |
|
77 | class ImageFilter(FeedFilter): | |
79 | @staticmethod |
|
78 | @staticmethod | |
80 | def get_filtered_posts(request, posts): |
|
79 | def get_filtered_posts(request, posts): | |
81 | filtered_posts = posts |
|
80 | filtered_posts = posts | |
82 |
|
81 | |||
83 | image = request.GET.get('image', None) |
|
82 | image = request.GET.get('image', None) | |
84 | if image: |
|
83 | if image: | |
85 | filtered_posts = filtered_posts.filter(attachments__file=image) |
|
84 | filtered_posts = filtered_posts.filter(attachments__file=image) | |
86 | return filtered_posts |
|
85 | return filtered_posts | |
87 |
|
86 | |||
88 | @staticmethod |
|
87 | @staticmethod | |
89 | def get_query(request): |
|
88 | def get_query(request): | |
90 | image = request.GET.get('image', None) |
|
89 | image = request.GET.get('image', None) | |
91 | if image: |
|
90 | if image: | |
92 | return 'File: {}'.format(image) |
|
91 | return 'File: {}'.format(image) | |
93 |
|
92 | |||
94 |
|
93 | |||
95 |
class FeedView( |
|
94 | class FeedView(PaginatedMixin, BaseBoardView): | |
96 | filters = ( |
|
95 | filters = ( | |
97 | TripcodeFilter, |
|
96 | TripcodeFilter, | |
98 | FavoritesFilter, |
|
97 | FavoritesFilter, | |
99 | IpFilter, |
|
98 | IpFilter, | |
100 | ImageFilter, |
|
99 | ImageFilter, | |
101 | ) |
|
100 | ) | |
102 |
|
101 | |||
103 | def get(self, request): |
|
102 | def get(self, request): | |
104 | page = request.GET.get('page', DEFAULT_PAGE) |
|
103 | page = request.GET.get('page', DEFAULT_PAGE) | |
105 |
|
104 | |||
106 | params = self.get_context_data(request=request) |
|
105 | params = self.get_context_data(request=request) | |
107 |
|
106 | |||
108 | settings_manager = get_settings_manager(request) |
|
107 | settings_manager = get_settings_manager(request) | |
109 |
|
108 | |||
110 | posts = Post.objects.exclude( |
|
109 | posts = Post.objects.exclude( | |
111 | thread__tags__in=settings_manager.get_hidden_tags()).order_by( |
|
110 | thread__tags__in=settings_manager.get_hidden_tags()).order_by( | |
112 | '-pub_time').prefetch_related('attachments', 'thread') |
|
111 | '-pub_time').prefetch_related('attachments', 'thread') | |
113 | queries = [] |
|
112 | queries = [] | |
114 | for filter in self.filters: |
|
113 | for filter in self.filters: | |
115 | posts = filter.get_filtered_posts(request, posts) |
|
114 | posts = filter.get_filtered_posts(request, posts) | |
116 | query = filter.get_query(request) |
|
115 | query = filter.get_query(request) | |
117 | if query: |
|
116 | if query: | |
118 | queries.append(query) |
|
117 | queries.append(query) | |
119 | params[PARAMETER_QUERIES] = queries |
|
118 | params[PARAMETER_QUERIES] = queries | |
120 |
|
119 | |||
121 | paginator = get_paginator(posts, POSTS_PER_PAGE) |
|
120 | paginator = get_paginator(posts, POSTS_PER_PAGE) | |
122 | paginator.current_page = int(page) |
|
121 | paginator.current_page = int(page) | |
123 |
|
122 | |||
124 | params[PARAMETER_POSTS] = paginator.page(page).object_list |
|
123 | params[PARAMETER_POSTS] = paginator.page(page).object_list | |
125 |
|
124 | |||
126 | paginator.set_url(reverse('feed'), request.GET.dict()) |
|
125 | paginator.set_url(reverse('feed'), request.GET.dict()) | |
127 |
|
126 | |||
128 | params.update(self.get_page_context(paginator, page)) |
|
127 | params.update(self.get_page_context(paginator, page)) | |
129 |
|
128 | |||
130 | return render(request, TEMPLATE, params) |
|
129 | return render(request, TEMPLATE, params) | |
131 |
|
130 |
@@ -1,61 +1,60 b'' | |||||
1 | import boards |
|
1 | import boards | |
2 |
|
2 | |||
3 |
|
||||
4 | PARAM_NEXT = 'next' |
|
3 | PARAM_NEXT = 'next' | |
5 | PARAMETER_METHOD = 'method' |
|
4 | PARAMETER_METHOD = 'method' | |
6 |
|
5 | |||
7 | PARAMETER_CURRENT_PAGE = 'current_page' |
|
6 | PARAMETER_CURRENT_PAGE = 'current_page' | |
8 | PARAMETER_PAGINATOR = 'paginator' |
|
7 | PARAMETER_PAGINATOR = 'paginator' | |
9 |
|
8 | |||
10 | PARAMETER_PREV_LINK = 'prev_page_link' |
|
9 | PARAMETER_PREV_LINK = 'prev_page_link' | |
11 | PARAMETER_NEXT_LINK = 'next_page_link' |
|
10 | PARAMETER_NEXT_LINK = 'next_page_link' | |
12 |
|
11 | |||
|
12 | ||||
13 | class DispatcherMixin: |
|
13 | class DispatcherMixin: | |
14 | """ |
|
14 | """ | |
15 | This class contains a dispather method that can run a method specified by |
|
15 | This class contains a dispather method that can run a method specified by | |
16 | 'method' request parameter. |
|
16 | 'method' request parameter. | |
17 | """ |
|
17 | """ | |
18 |
|
18 | |||
19 | def __init__(self): |
|
19 | def __init__(self): | |
20 | self.user = None |
|
20 | self.user = None | |
21 |
|
21 | |||
22 | def dispatch_method(self, *args, **kwargs): |
|
22 | def dispatch_method(self, *args, **kwargs): | |
23 | request = args[0] |
|
23 | request = args[0] | |
24 |
|
24 | |||
25 | self.user = request.user |
|
25 | self.user = request.user | |
26 |
|
26 | |||
27 | method_name = None |
|
27 | method_name = None | |
28 | if PARAMETER_METHOD in request.GET: |
|
28 | if PARAMETER_METHOD in request.GET: | |
29 | method_name = request.GET[PARAMETER_METHOD] |
|
29 | method_name = request.GET[PARAMETER_METHOD] | |
30 | elif PARAMETER_METHOD in request.POST: |
|
30 | elif PARAMETER_METHOD in request.POST: | |
31 | method_name = request.POST[PARAMETER_METHOD] |
|
31 | method_name = request.POST[PARAMETER_METHOD] | |
32 |
|
32 | |||
33 | if method_name: |
|
33 | if method_name: | |
34 | return getattr(self, method_name)(*args, **kwargs) |
|
34 | return getattr(self, method_name)(*args, **kwargs) | |
35 |
|
35 | |||
36 |
|
36 | |||
37 | class FileUploadMixin: |
|
37 | class FileUploadMixin: | |
38 | def get_max_upload_size(self): |
|
38 | def get_max_upload_size(self): | |
39 | return boards.settings.get_int('Forms', 'MaxFileSize') |
|
39 | return boards.settings.get_int('Forms', 'MaxFileSize') | |
40 |
|
40 | |||
41 |
|
41 | |||
42 | class PaginatedMixin: |
|
42 | class PaginatedMixin: | |
43 | def get_page_context(self, paginator, page): |
|
43 | def get_page_context(self, paginator, page): | |
44 | """ |
|
44 | """ | |
45 | Get pagination context variables |
|
45 | Get pagination context variables | |
46 | """ |
|
46 | """ | |
47 |
|
47 | |||
48 | params = {} |
|
48 | params = {} | |
49 |
|
49 | |||
50 | params[PARAMETER_PAGINATOR] = paginator |
|
50 | params[PARAMETER_PAGINATOR] = paginator | |
51 | current_page = paginator.page(int(page)) |
|
51 | current_page = paginator.page(int(page)) | |
52 | params[PARAMETER_CURRENT_PAGE] = current_page |
|
52 | params[PARAMETER_CURRENT_PAGE] = current_page | |
53 | if current_page.has_previous(): |
|
53 | if current_page.has_previous(): | |
54 | params[PARAMETER_PREV_LINK] = paginator.get_page_url( |
|
54 | params[PARAMETER_PREV_LINK] = paginator.get_page_url( | |
55 | current_page.previous_page_number()) |
|
55 | current_page.previous_page_number()) | |
56 | if current_page.has_next(): |
|
56 | if current_page.has_next(): | |
57 | params[PARAMETER_NEXT_LINK] = paginator.get_page_url( |
|
57 | params[PARAMETER_NEXT_LINK] = paginator.get_page_url( | |
58 | current_page.next_page_number()) |
|
58 | current_page.next_page_number()) | |
59 |
|
59 | |||
60 | return params |
|
60 | return params | |
61 |
|
@@ -1,124 +1,124 b'' | |||||
1 | from django.shortcuts import get_object_or_404, redirect |
|
1 | from django.shortcuts import get_object_or_404, redirect | |
2 | from django.urls import reverse |
|
2 | from django.urls import reverse | |
3 |
|
3 | |||
4 | from boards.abstracts.settingsmanager import get_settings_manager, \ |
|
4 | from boards.abstracts.settingsmanager import get_settings_manager, \ | |
5 | SETTING_FAVORITE_TAGS, SETTING_HIDDEN_TAGS |
|
5 | SETTING_FAVORITE_TAGS, SETTING_HIDDEN_TAGS | |
6 | from boards.models import Tag, TagAlias |
|
6 | from boards.models import Tag, TagAlias, Post | |
7 | from boards.views.all_threads import AllThreadsView |
|
7 | from boards.views.all_threads import AllThreadsView | |
8 | from boards.views.mixins import DispatcherMixin, PARAMETER_METHOD |
|
8 | from boards.views.mixins import DispatcherMixin, PARAMETER_METHOD | |
9 | from boards.forms import ThreadForm, PlainErrorList |
|
9 | from boards.forms import ThreadForm, PlainErrorList | |
10 |
|
10 | |||
11 | PARAM_HIDDEN_TAGS = 'hidden_tags' |
|
11 | PARAM_HIDDEN_TAGS = 'hidden_tags' | |
12 | PARAM_TAG = 'tag' |
|
12 | PARAM_TAG = 'tag' | |
13 | PARAM_IS_FAVORITE = 'is_favorite' |
|
13 | PARAM_IS_FAVORITE = 'is_favorite' | |
14 | PARAM_IS_HIDDEN = 'is_hidden' |
|
14 | PARAM_IS_HIDDEN = 'is_hidden' | |
15 | PARAM_RANDOM_IMAGE_POST = 'random_image_post' |
|
15 | PARAM_RANDOM_IMAGE_POST = 'random_image_post' | |
16 | PARAM_RELATED_TAGS = 'related_tags' |
|
16 | PARAM_RELATED_TAGS = 'related_tags' | |
17 |
|
17 | |||
18 |
|
18 | |||
19 | __author__ = 'neko259' |
|
19 | __author__ = 'neko259' | |
20 |
|
20 | |||
21 |
|
21 | |||
22 | class TagView(AllThreadsView, DispatcherMixin): |
|
22 | class TagView(AllThreadsView, DispatcherMixin): | |
23 |
|
23 | |||
24 | tag_name = None |
|
24 | tag_name = None | |
25 |
|
25 | |||
26 | def get_threads(self): |
|
26 | def get_threads(self): | |
27 | tag_alias = get_object_or_404(TagAlias, name=self.tag_name) |
|
27 | tag_alias = get_object_or_404(TagAlias, name=self.tag_name) | |
28 | tag = tag_alias.parent |
|
28 | tag = tag_alias.parent | |
29 |
|
29 | |||
30 | hidden_tags = self.settings_manager.get_hidden_tags() |
|
30 | hidden_tags = self.settings_manager.get_hidden_tags() | |
31 |
|
31 | |||
32 | try: |
|
32 | try: | |
33 | hidden_tags.remove(tag) |
|
33 | hidden_tags.remove(tag) | |
34 | except ValueError: |
|
34 | except ValueError: | |
35 | pass |
|
35 | pass | |
36 |
|
36 | |||
37 | return tag.get_threads().exclude( |
|
37 | return tag.get_threads().exclude( | |
38 | tags__in=hidden_tags) |
|
38 | tags__in=hidden_tags) | |
39 |
|
39 | |||
40 | def get_context_data(self, **kwargs): |
|
40 | def get_context_data(self, **kwargs): | |
41 | params = super(TagView, self).get_context_data(**kwargs) |
|
41 | params = super(TagView, self).get_context_data(**kwargs) | |
42 |
|
42 | |||
43 | settings_manager = get_settings_manager(kwargs['request']) |
|
43 | settings_manager = get_settings_manager(kwargs['request']) | |
44 |
|
44 | |||
45 | tag_alias = get_object_or_404(TagAlias, name=self.tag_name) |
|
45 | tag_alias = get_object_or_404(TagAlias, name=self.tag_name) | |
46 | tag = tag_alias.parent |
|
46 | tag = tag_alias.parent | |
47 | params[PARAM_TAG] = tag |
|
47 | params[PARAM_TAG] = tag | |
48 |
|
48 | |||
49 | fav_tag_names = settings_manager.get_setting(SETTING_FAVORITE_TAGS) |
|
49 | fav_tag_names = settings_manager.get_setting(SETTING_FAVORITE_TAGS) | |
50 | hidden_tag_names = settings_manager.get_setting(SETTING_HIDDEN_TAGS) |
|
50 | hidden_tag_names = settings_manager.get_setting(SETTING_HIDDEN_TAGS) | |
51 |
|
51 | |||
52 | params[PARAM_IS_FAVORITE] = fav_tag_names is not None and tag.get_name() in fav_tag_names |
|
52 | params[PARAM_IS_FAVORITE] = fav_tag_names is not None and tag.get_name() in fav_tag_names | |
53 | params[PARAM_IS_HIDDEN] = hidden_tag_names is not None and tag.get_name() in hidden_tag_names |
|
53 | params[PARAM_IS_HIDDEN] = hidden_tag_names is not None and tag.get_name() in hidden_tag_names | |
54 |
|
54 | |||
55 | params[PARAM_RANDOM_IMAGE_POST] = tag.get_random_image_post() |
|
55 | params[PARAM_RANDOM_IMAGE_POST] = tag.get_random_image_post() | |
56 | params[PARAM_RELATED_TAGS] = tag.get_related_tags() |
|
56 | params[PARAM_RELATED_TAGS] = tag.get_related_tags() | |
57 |
|
57 | |||
58 | return params |
|
58 | return params | |
59 |
|
59 | |||
60 | def get_reverse_url(self): |
|
60 | def get_reverse_url(self): | |
61 | return reverse('tag', kwargs={'tag_name': self.tag_name}) |
|
61 | return reverse('tag', kwargs={'tag_name': self.tag_name}) | |
62 |
|
62 | |||
63 | def get(self, request, tag_name, form=None): |
|
63 | def get(self, request, tag_name, form=None): | |
64 | self.tag_name = tag_name |
|
64 | self.tag_name = tag_name | |
65 |
|
65 | |||
66 | return super(TagView, self).get(request, form) |
|
66 | return super(TagView, self).get(request, form) | |
67 |
|
67 | |||
68 |
|
68 | |||
69 | def post(self, request, tag_name): |
|
69 | def post(self, request, tag_name): | |
70 | self.tag_name = tag_name |
|
70 | self.tag_name = tag_name | |
71 |
|
71 | |||
72 | if PARAMETER_METHOD in request.POST: |
|
72 | if PARAMETER_METHOD in request.POST: | |
73 | self.dispatch_method(request) |
|
73 | self.dispatch_method(request) | |
74 |
|
74 | |||
75 | return redirect('tag', tag_name) |
|
75 | return redirect('tag', tag_name) | |
76 | else: |
|
76 | else: | |
77 | form = ThreadForm(request.POST, request.FILES, |
|
77 | form = ThreadForm(request.POST, request.FILES, | |
78 | error_class=PlainErrorList) |
|
78 | error_class=PlainErrorList) | |
79 | form.session = request.session |
|
79 | form.session = request.session | |
80 |
|
80 | |||
81 | if form.is_valid(): |
|
81 | if form.is_valid(): | |
82 |
return |
|
82 | return Post.objects.create_from_form(request, form, opening_post=None) | |
83 | if form.need_to_ban: |
|
83 | if form.need_to_ban: | |
84 | # Ban user because he is suspected to be a bot |
|
84 | # Ban user because he is suspected to be a bot | |
85 | self._ban_current_user(request) |
|
85 | self._ban_current_user(request) | |
86 |
|
86 | |||
87 | return self.get(request, tag_name, form) |
|
87 | return self.get(request, tag_name, form) | |
88 |
|
88 | |||
89 | def subscribe(self, request): |
|
89 | def subscribe(self, request): | |
90 | alias = get_object_or_404(TagAlias, name=self.tag_name) |
|
90 | alias = get_object_or_404(TagAlias, name=self.tag_name) | |
91 | tag = alias.parent |
|
91 | tag = alias.parent | |
92 |
|
92 | |||
93 | settings_manager = get_settings_manager(request) |
|
93 | settings_manager = get_settings_manager(request) | |
94 | settings_manager.add_fav_tag(tag) |
|
94 | settings_manager.add_fav_tag(tag) | |
95 |
|
95 | |||
96 | def unsubscribe(self, request): |
|
96 | def unsubscribe(self, request): | |
97 | alias = get_object_or_404(TagAlias, name=self.tag_name) |
|
97 | alias = get_object_or_404(TagAlias, name=self.tag_name) | |
98 | tag = alias.parent |
|
98 | tag = alias.parent | |
99 |
|
99 | |||
100 | settings_manager = get_settings_manager(request) |
|
100 | settings_manager = get_settings_manager(request) | |
101 | settings_manager.del_fav_tag(tag) |
|
101 | settings_manager.del_fav_tag(tag) | |
102 |
|
102 | |||
103 | def hide(self, request): |
|
103 | def hide(self, request): | |
104 | """ |
|
104 | """ | |
105 | Adds tag to user's hidden tags. Threads with this tag will not be |
|
105 | Adds tag to user's hidden tags. Threads with this tag will not be | |
106 | shown. |
|
106 | shown. | |
107 | """ |
|
107 | """ | |
108 |
|
108 | |||
109 | alias = get_object_or_404(TagAlias, name=self.tag_name) |
|
109 | alias = get_object_or_404(TagAlias, name=self.tag_name) | |
110 | tag = alias.parent |
|
110 | tag = alias.parent | |
111 |
|
111 | |||
112 | settings_manager = get_settings_manager(request) |
|
112 | settings_manager = get_settings_manager(request) | |
113 | settings_manager.add_hidden_tag(tag) |
|
113 | settings_manager.add_hidden_tag(tag) | |
114 |
|
114 | |||
115 | def unhide(self, request): |
|
115 | def unhide(self, request): | |
116 | """ |
|
116 | """ | |
117 | Removed tag from user's hidden tags. |
|
117 | Removed tag from user's hidden tags. | |
118 | """ |
|
118 | """ | |
119 |
|
119 | |||
120 | alias = get_object_or_404(TagAlias, name=self.tag_name) |
|
120 | alias = get_object_or_404(TagAlias, name=self.tag_name) | |
121 | tag = alias.parent |
|
121 | tag = alias.parent | |
122 |
|
122 | |||
123 | settings_manager = get_settings_manager(request) |
|
123 | settings_manager = get_settings_manager(request) | |
124 | settings_manager.del_hidden_tag(tag) |
|
124 | settings_manager.del_hidden_tag(tag) |
@@ -1,159 +1,116 b'' | |||||
1 | from django.core.exceptions import ObjectDoesNotExist |
|
1 | from django.core.exceptions import ObjectDoesNotExist | |
2 | from django.http import Http404 |
|
2 | from django.http import Http404 | |
3 | from django.shortcuts import get_object_or_404, render, redirect |
|
3 | from django.shortcuts import get_object_or_404, render, redirect | |
4 | from django.urls import reverse |
|
4 | from django.urls import reverse | |
5 | from django.utils.decorators import method_decorator |
|
5 | from django.utils.decorators import method_decorator | |
6 | from django.views.decorators.csrf import csrf_protect |
|
6 | from django.views.decorators.csrf import csrf_protect | |
7 | from django.views.generic.edit import FormMixin |
|
7 | from django.views.generic.edit import FormMixin | |
8 |
|
8 | |||
9 | from boards import utils |
|
|||
10 | from boards.abstracts.settingsmanager import get_settings_manager |
|
9 | from boards.abstracts.settingsmanager import get_settings_manager | |
11 | from boards.forms import PostForm, PlainErrorList |
|
10 | from boards.forms import PostForm, PlainErrorList | |
12 | from boards.models import Post |
|
11 | from boards.models import Post | |
13 | from boards.views.base import BaseBoardView, CONTEXT_FORM |
|
12 | from boards.views.base import BaseBoardView, CONTEXT_FORM | |
14 | from boards.views.mixins import DispatcherMixin, PARAMETER_METHOD |
|
13 | from boards.views.mixins import DispatcherMixin, PARAMETER_METHOD | |
15 | from boards.views.posting_mixin import PostMixin |
|
|||
16 |
|
14 | |||
17 | REQ_POST_ID = 'post_id' |
|
15 | REQ_POST_ID = 'post_id' | |
18 |
|
16 | |||
19 | CONTEXT_LASTUPDATE = "last_update" |
|
17 | CONTEXT_LASTUPDATE = "last_update" | |
20 | CONTEXT_THREAD = 'thread' |
|
18 | CONTEXT_THREAD = 'thread' | |
21 | CONTEXT_MODE = 'mode' |
|
19 | CONTEXT_MODE = 'mode' | |
22 | CONTEXT_OP = 'opening_post' |
|
20 | CONTEXT_OP = 'opening_post' | |
23 | CONTEXT_FAVORITE = 'is_favorite' |
|
21 | CONTEXT_FAVORITE = 'is_favorite' | |
24 | CONTEXT_RSS_URL = 'rss_url' |
|
22 | CONTEXT_RSS_URL = 'rss_url' | |
25 |
|
23 | |||
26 | FORM_TITLE = 'title' |
|
|||
27 | FORM_TEXT = 'text' |
|
|||
28 | FORM_IMAGE = 'image' |
|
|||
29 | FORM_THREADS = 'threads' |
|
|||
30 |
|
24 | |||
31 |
|
25 | class ThreadView(BaseBoardView, FormMixin, DispatcherMixin): | ||
32 | class ThreadView(BaseBoardView, PostMixin, FormMixin, DispatcherMixin): |
|
|||
33 |
|
26 | |||
34 | @method_decorator(csrf_protect) |
|
27 | @method_decorator(csrf_protect) | |
35 | def get(self, request, post_id, form: PostForm=None): |
|
28 | def get(self, request, post_id, form: PostForm=None): | |
36 | try: |
|
29 | try: | |
37 | opening_post = Post.objects.get(id=post_id) |
|
30 | opening_post = Post.objects.get(id=post_id) | |
38 | except ObjectDoesNotExist: |
|
31 | except ObjectDoesNotExist: | |
39 | raise Http404 |
|
32 | raise Http404 | |
40 |
|
33 | |||
41 | # If the tag is favorite, update the counter |
|
34 | # If the tag is favorite, update the counter | |
42 | settings_manager = get_settings_manager(request) |
|
35 | settings_manager = get_settings_manager(request) | |
43 | favorite = settings_manager.thread_is_fav(opening_post) |
|
36 | favorite = settings_manager.thread_is_fav(opening_post) | |
44 | if favorite: |
|
37 | if favorite: | |
45 | settings_manager.add_or_read_fav_thread(opening_post) |
|
38 | settings_manager.add_or_read_fav_thread(opening_post) | |
46 |
|
39 | |||
47 | # If this is not OP, don't show it as it is |
|
40 | # If this is not OP, don't show it as it is | |
48 | if not opening_post.is_opening(): |
|
41 | if not opening_post.is_opening(): | |
49 | return redirect('{}#{}'.format(opening_post.get_thread().get_opening_post() |
|
42 | return redirect('{}#{}'.format(opening_post.get_thread().get_opening_post() | |
50 | .get_absolute_url(), opening_post.id)) |
|
43 | .get_absolute_url(), opening_post.id)) | |
51 |
|
44 | |||
52 | if not form: |
|
45 | if not form: | |
53 | form = PostForm(error_class=PlainErrorList) |
|
46 | form = PostForm(error_class=PlainErrorList) | |
54 |
|
47 | |||
55 | thread_to_show = opening_post.get_thread() |
|
48 | thread_to_show = opening_post.get_thread() | |
56 |
|
49 | |||
57 | params = dict() |
|
50 | params = dict() | |
58 |
|
51 | |||
59 | params[CONTEXT_FORM] = form |
|
52 | params[CONTEXT_FORM] = form | |
60 | params[CONTEXT_LASTUPDATE] = str(thread_to_show.last_edit_time) |
|
53 | params[CONTEXT_LASTUPDATE] = str(thread_to_show.last_edit_time) | |
61 | params[CONTEXT_THREAD] = thread_to_show |
|
54 | params[CONTEXT_THREAD] = thread_to_show | |
62 | params[CONTEXT_MODE] = self.get_mode() |
|
55 | params[CONTEXT_MODE] = self.get_mode() | |
63 | params[CONTEXT_OP] = opening_post |
|
56 | params[CONTEXT_OP] = opening_post | |
64 | params[CONTEXT_FAVORITE] = favorite |
|
57 | params[CONTEXT_FAVORITE] = favorite | |
65 | params[CONTEXT_RSS_URL] = self.get_rss_url(post_id) |
|
58 | params[CONTEXT_RSS_URL] = self.get_rss_url(post_id) | |
66 |
|
59 | |||
67 | params.update(self.get_data(thread_to_show)) |
|
60 | params.update(self.get_data(thread_to_show)) | |
68 |
|
61 | |||
69 | return render(request, self.get_template(), params) |
|
62 | return render(request, self.get_template(), params) | |
70 |
|
63 | |||
71 | @method_decorator(csrf_protect) |
|
64 | @method_decorator(csrf_protect) | |
72 | def post(self, request, post_id): |
|
65 | def post(self, request, post_id): | |
73 | opening_post = get_object_or_404(Post, id=post_id) |
|
66 | opening_post = get_object_or_404(Post, id=post_id) | |
74 |
|
67 | |||
75 | # If this is not OP, don't show it as it is |
|
68 | # If this is not OP, don't show it as it is | |
76 | if not opening_post.is_opening(): |
|
69 | if not opening_post.is_opening(): | |
77 | raise Http404 |
|
70 | raise Http404 | |
78 |
|
71 | |||
79 | if PARAMETER_METHOD in request.POST: |
|
72 | if PARAMETER_METHOD in request.POST: | |
80 | self.dispatch_method(request, opening_post) |
|
73 | self.dispatch_method(request, opening_post) | |
81 |
|
74 | |||
82 | return redirect('thread', post_id) # FIXME Different for different modes |
|
75 | return redirect('thread', post_id) # FIXME Different for different modes | |
83 |
|
76 | |||
84 | if not opening_post.get_thread().is_archived(): |
|
77 | if not opening_post.get_thread().is_archived(): | |
85 | form = PostForm(request.POST, request.FILES, |
|
78 | form = PostForm(request.POST, request.FILES, | |
86 | error_class=PlainErrorList) |
|
79 | error_class=PlainErrorList) | |
87 | form.session = request.session |
|
80 | form.session = request.session | |
88 |
|
81 | |||
89 | if form.is_valid(): |
|
82 | if form.is_valid(): | |
90 |
return |
|
83 | return Post.objects.create_from_form(request, form, opening_post) | |
91 | if form.need_to_ban: |
|
84 | if form.need_to_ban: | |
92 | # Ban user because he is suspected to be a bot |
|
85 | # Ban user because he is suspected to be a bot | |
93 | self._ban_current_user(request) |
|
86 | self._ban_current_user(request) | |
94 |
|
87 | |||
95 | return self.get(request, post_id, form) |
|
88 | return self.get(request, post_id, form) | |
96 |
|
89 | |||
97 | def new_post(self, request, form: PostForm, opening_post: Post=None, |
|
|||
98 | html_response=True): |
|
|||
99 | """ |
|
|||
100 | Adds a new post (in thread or as a reply). |
|
|||
101 | """ |
|
|||
102 |
|
||||
103 | ip = utils.get_client_ip(request) |
|
|||
104 |
|
||||
105 | data = form.cleaned_data |
|
|||
106 |
|
||||
107 | title = form.get_title() |
|
|||
108 | text = data[FORM_TEXT] |
|
|||
109 | files = form.get_files() |
|
|||
110 | file_urls = form.get_file_urls() |
|
|||
111 | images = form.get_images() |
|
|||
112 |
|
||||
113 | text = self._remove_invalid_links(text) |
|
|||
114 |
|
||||
115 | post_thread = opening_post.get_thread() |
|
|||
116 |
|
||||
117 | post = Post.objects.create_post(title=title, text=text, files=files, |
|
|||
118 | thread=post_thread, ip=ip, |
|
|||
119 | tripcode=form.get_tripcode(), |
|
|||
120 | images=images, file_urls=file_urls) |
|
|||
121 |
|
||||
122 | if form.is_subscribe(): |
|
|||
123 | settings_manager = get_settings_manager(request) |
|
|||
124 | settings_manager.add_or_read_fav_thread( |
|
|||
125 | post_thread.get_opening_post()) |
|
|||
126 |
|
||||
127 | if html_response: |
|
|||
128 | if opening_post: |
|
|||
129 | return redirect(post.get_absolute_url()) |
|
|||
130 | else: |
|
|||
131 | return post |
|
|||
132 |
|
||||
133 | def get_data(self, thread) -> dict: |
|
90 | def get_data(self, thread) -> dict: | |
134 | """ |
|
91 | """ | |
135 | Returns context params for the view. |
|
92 | Returns context params for the view. | |
136 | """ |
|
93 | """ | |
137 |
|
94 | |||
138 | return dict() |
|
95 | return dict() | |
139 |
|
96 | |||
140 | def get_template(self) -> str: |
|
97 | def get_template(self) -> str: | |
141 | """ |
|
98 | """ | |
142 | Gets template to show the thread mode on. |
|
99 | Gets template to show the thread mode on. | |
143 | """ |
|
100 | """ | |
144 |
|
101 | |||
145 | pass |
|
102 | pass | |
146 |
|
103 | |||
147 | def get_mode(self) -> str: |
|
104 | def get_mode(self) -> str: | |
148 | pass |
|
105 | pass | |
149 |
|
106 | |||
150 | def subscribe(self, request, opening_post): |
|
107 | def subscribe(self, request, opening_post): | |
151 | settings_manager = get_settings_manager(request) |
|
108 | settings_manager = get_settings_manager(request) | |
152 | settings_manager.add_or_read_fav_thread(opening_post) |
|
109 | settings_manager.add_or_read_fav_thread(opening_post) | |
153 |
|
110 | |||
154 | def unsubscribe(self, request, opening_post): |
|
111 | def unsubscribe(self, request, opening_post): | |
155 | settings_manager = get_settings_manager(request) |
|
112 | settings_manager = get_settings_manager(request) | |
156 | settings_manager.del_fav_thread(opening_post) |
|
113 | settings_manager.del_fav_thread(opening_post) | |
157 |
|
114 | |||
158 | def get_rss_url(self, opening_id): |
|
115 | def get_rss_url(self, opening_id): | |
159 | return reverse('thread', kwargs={'post_id': opening_id}) + 'rss/' |
|
116 | return reverse('thread', kwargs={'post_id': opening_id}) + 'rss/' |
1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now