Show More
@@ -0,0 +1,27 b'' | |||||
|
1 | # -*- coding: utf-8 -*- | |||
|
2 | # Generated by Django 1.11 on 2017-11-21 11:29 | |||
|
3 | from __future__ import unicode_literals | |||
|
4 | ||||
|
5 | from django.db import migrations, models | |||
|
6 | import django.db.models.deletion | |||
|
7 | ||||
|
8 | ||||
|
9 | class Migration(migrations.Migration): | |||
|
10 | ||||
|
11 | dependencies = [ | |||
|
12 | ('boards', '0066_auto_20171025_1148'), | |||
|
13 | ] | |||
|
14 | ||||
|
15 | operations = [ | |||
|
16 | migrations.CreateModel( | |||
|
17 | name='ThreadSource', | |||
|
18 | fields=[ | |||
|
19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), | |||
|
20 | ('name', models.TextField()), | |||
|
21 | ('timestamp', models.DateTimeField()), | |||
|
22 | ('source', models.TextField()), | |||
|
23 | ('source_type', models.CharField(choices=[('RSS', 'RSS')], max_length=100)), | |||
|
24 | ('thread', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='boards.Thread')), | |||
|
25 | ], | |||
|
26 | ), | |||
|
27 | ] |
@@ -0,0 +1,66 b'' | |||||
|
1 | import feedparser | |||
|
2 | import logging | |||
|
3 | ||||
|
4 | from time import mktime | |||
|
5 | from datetime import datetime | |||
|
6 | ||||
|
7 | from django.db import models, transaction | |||
|
8 | from django.utils.dateparse import parse_datetime | |||
|
9 | from django.utils.timezone import utc | |||
|
10 | from django.utils import timezone | |||
|
11 | from boards.models import Post | |||
|
12 | from boards.models.post import TITLE_MAX_LENGTH | |||
|
13 | ||||
|
14 | ||||
|
15 | SOURCE_TYPE_MAX_LENGTH = 100 | |||
|
16 | SOURCE_TYPE_RSS = 'RSS' | |||
|
17 | TYPE_CHOICES = ( | |||
|
18 | (SOURCE_TYPE_RSS, SOURCE_TYPE_RSS), | |||
|
19 | ) | |||
|
20 | ||||
|
21 | ||||
|
22 | class ThreadSource(models.Model): | |||
|
23 | class Meta: | |||
|
24 | app_label = 'boards' | |||
|
25 | ||||
|
26 | name = models.TextField() | |||
|
27 | thread = models.ForeignKey('Thread') | |||
|
28 | timestamp = models.DateTimeField() | |||
|
29 | source = models.TextField() | |||
|
30 | source_type = models.CharField(max_length=SOURCE_TYPE_MAX_LENGTH, | |||
|
31 | choices=TYPE_CHOICES) | |||
|
32 | ||||
|
33 | def __str__(self): | |||
|
34 | return self.name | |||
|
35 | ||||
|
36 | @transaction.atomic | |||
|
37 | def fetch_latest_posts(self): | |||
|
38 | """Creates new posts with the info fetched since the timestamp.""" | |||
|
39 | logger = logging.getLogger('boards.source') | |||
|
40 | ||||
|
41 | if self.thread.is_archived(): | |||
|
42 | logger.error('The thread {} is archived, please try another one'.format(self.thread)) | |||
|
43 | else: | |||
|
44 | start_timestamp = timezone.localtime(self.timestamp) | |||
|
45 | last_timestamp = start_timestamp | |||
|
46 | if self.thread.is_bumplimit(): | |||
|
47 | logger.warn('The thread {} has reached its bumplimit, please create a new one'.format(self.thread)) | |||
|
48 | if self.source_type == SOURCE_TYPE_RSS: | |||
|
49 | feed = feedparser.parse(self.source) | |||
|
50 | items = sorted(feed.entries, key=lambda entry: entry.published_parsed) | |||
|
51 | for item in items: | |||
|
52 | title = item.title[:TITLE_MAX_LENGTH] | |||
|
53 | timestamp = datetime.fromtimestamp(mktime(item.published_parsed), tz=utc) | |||
|
54 | if not timestamp: | |||
|
55 | logger.error('Invalid timestamp {} for {}'.format(item.published, title)) | |||
|
56 | else: | |||
|
57 | if timestamp > last_timestamp: | |||
|
58 | last_timestamp = timestamp | |||
|
59 | ||||
|
60 | if timestamp > start_timestamp: | |||
|
61 | Post.objects.create_post(title=title, text=item.description, thread=self.thread, file_urls=[item.link]) | |||
|
62 | logger.info('Fetched item {} from {} into thread {}'.format( | |||
|
63 | title, self.name, self.thread)) | |||
|
64 | self.timestamp = last_timestamp | |||
|
65 | self.save(update_fields=['timestamp']) | |||
|
66 |
@@ -6,6 +6,7 b' from django.utils.translation import uge' | |||||
6 | from django.core.urlresolvers import reverse |
|
6 | from django.core.urlresolvers import reverse | |
7 | from boards.models import Post, Tag, Ban, Thread, Banner, Attachment, \ |
|
7 | from boards.models import Post, Tag, Ban, Thread, Banner, Attachment, \ | |
8 | KeyPair, GlobalId, TagAlias |
|
8 | KeyPair, GlobalId, TagAlias | |
|
9 | from boards.models.source import ThreadSource | |||
9 |
|
10 | |||
10 |
|
11 | |||
11 | @admin.register(Post) |
|
12 | @admin.register(Post) | |
@@ -179,3 +180,9 b' class GlobalIdAdmin(admin.ModelAdmin):' | |||||
179 |
|
180 | |||
180 | list_display = ('__str__', 'is_linked',) |
|
181 | list_display = ('__str__', 'is_linked',) | |
181 | readonly_fields = ('content',) |
|
182 | readonly_fields = ('content',) | |
|
183 | ||||
|
184 | ||||
|
185 | @admin.register(ThreadSource) | |||
|
186 | class ThreadSourceAdmin(admin.ModelAdmin): | |||
|
187 | search_fields = ('name', 'source') | |||
|
188 |
@@ -47,3 +47,4 b' MaxItems = 20' | |||||
47 |
|
47 | |||
48 | [External] |
|
48 | [External] | |
49 | ImageSearchHost= |
|
49 | ImageSearchHost= | |
|
50 | SourceFetcherTripcode= |
@@ -233,7 +233,7 b' class Thread(models.Model):' | |||||
233 | return self.get_opening_post().pub_time |
|
233 | return self.get_opening_post().pub_time | |
234 |
|
234 | |||
235 | def __str__(self): |
|
235 | def __str__(self): | |
236 | return 'T#{}'.format(self.id) |
|
236 | return 'T#{}/{}'.format(self.id, self.get_opening_post()) | |
237 |
|
237 | |||
238 | def get_tag_url_list(self) -> list: |
|
238 | def get_tag_url_list(self) -> list: | |
239 | return boards.models.Tag.objects.get_tag_url_list(self.get_tags().all()) |
|
239 | return boards.models.Tag.objects.get_tag_url_list(self.get_tags().all()) | |
@@ -262,6 +262,9 b' class Thread(models.Model):' | |||||
262 | def is_archived(self): |
|
262 | def is_archived(self): | |
263 | return self.get_status() == STATUS_ARCHIVE |
|
263 | return self.get_status() == STATUS_ARCHIVE | |
264 |
|
264 | |||
|
265 | def is_bumplimit(self): | |||
|
266 | return self.get_status() == STATUS_BUMPLIMIT | |||
|
267 | ||||
265 | def get_status(self): |
|
268 | def get_status(self): | |
266 | return self.status |
|
269 | return self.status | |
267 |
|
270 |
@@ -1,9 +1,13 b'' | |||||
1 | import configparser |
|
1 | import configparser | |
2 |
|
2 | |||
3 |
|
3 | |||
|
4 | CONFIG_DEFAULT_SETTINGS = 'boards/config/default_settings.ini' | |||
|
5 | CONFIG_SETTINGS = 'boards/config/settings.ini' | |||
|
6 | ||||
|
7 | ||||
4 | config = configparser.ConfigParser() |
|
8 | config = configparser.ConfigParser() | |
5 | config.read('boards/config/default_settings.ini') |
|
9 | config.read(CONFIG_DEFAULT_SETTINGS) | |
6 | config.read('boards/config/settings.ini') |
|
10 | config.read(CONFIG_SETTINGS) | |
7 |
|
11 | |||
8 |
|
12 | |||
9 | def get(section, name): |
|
13 | def get(section, name): | |
@@ -22,3 +26,4 b' def get_list_dict(section, name):' | |||||
22 | str_dict = get(section, name) |
|
26 | str_dict = get(section, name) | |
23 | return [item.split(':') for item in str_dict.split(',')] |
|
27 | return [item.split(':') for item in str_dict.split(',')] | |
24 |
|
28 | |||
|
29 |
General Comments 0
You need to be logged in to leave comments.
Login now