##// END OF EJS Templates
Fixed thread creation
neko259 -
r1862:b58ef726 default
parent child Browse files
Show More
@@ -0,0 +1,38 b''
1 # -*- coding: utf-8 -*-
2 # Generated by Django 1.10.5 on 2017-02-24 21:10
3 from __future__ import unicode_literals
4
5 import boards.models.base
6 from django.db import migrations, models
7 import django.db.models.deletion
8
9
10 class Migration(migrations.Migration):
11
12 dependencies = [
13 ('boards', '0057_tag_aliases'),
14 ]
15
16 operations = [
17 migrations.CreateModel(
18 name='TagAlias',
19 fields=[
20 ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
21 ('name', models.CharField(db_index=True, max_length=100, unique=True)),
22 ('locale', models.CharField(db_index=True, max_length=10)),
23 ],
24 options={
25 'ordering': ('name',),
26 },
27 bases=(models.Model, boards.models.base.Viewable),
28 ),
29 migrations.RemoveField(
30 model_name='tag',
31 name='aliases',
32 ),
33 migrations.AddField(
34 model_name='tagalias',
35 name='parent',
36 field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='aliases', to='boards.Tag'),
37 ),
38 ]
@@ -1,169 +1,175 b''
1 1 from boards.models.attachment import FILE_TYPES_IMAGE
2 2 from django.contrib import admin
3 3 from django.utils.translation import ugettext_lazy as _
4 4 from django.core.urlresolvers import reverse
5 from boards.models import Post, Tag, Ban, Thread, Banner, Attachment, KeyPair, GlobalId
5 from boards.models import Post, Tag, Ban, Thread, Banner, Attachment, \
6 KeyPair, GlobalId, TagAlias
6 7
7 8
8 9 @admin.register(Post)
9 10 class PostAdmin(admin.ModelAdmin):
10 11
11 12 list_display = ('id', 'title', 'text', 'poster_ip', 'linked_images',
12 13 'foreign', 'tags')
13 14 list_filter = ('pub_time',)
14 15 search_fields = ('id', 'title', 'text', 'poster_ip')
15 16 exclude = ('referenced_posts', 'refmap', 'images', 'global_id')
16 17 readonly_fields = ('poster_ip', 'thread', 'linked_images',
17 18 'attachments', 'uid', 'url', 'pub_time', 'opening', 'linked_global_id',
18 19 'version', 'foreign', 'tags')
19 20
20 21 def ban_poster(self, request, queryset):
21 22 bans = 0
22 23 for post in queryset:
23 24 poster_ip = post.poster_ip
24 25 ban, created = Ban.objects.get_or_create(ip=poster_ip)
25 26 if created:
26 27 bans += 1
27 28 self.message_user(request, _('{} posters were banned').format(bans))
28 29
29 30 def ban_latter_with_delete(self, request, queryset):
30 31 bans = 0
31 32 hidden = 0
32 33 for post in queryset:
33 34 poster_ip = post.poster_ip
34 35 ban, created = Ban.objects.get_or_create(ip=poster_ip)
35 36 if created:
36 37 bans += 1
37 38 posts = Post.objects.filter(poster_ip=poster_ip, id__gte=post.id)
38 39 hidden += posts.count()
39 40 posts.delete()
40 41 self.message_user(request, _('{} posters were banned, {} messages were removed.').format(bans, hidden))
41 42 ban_latter_with_delete.short_description = _('Ban user and delete posts starting from this one and later')
42 43
43 44 def linked_images(self, obj: Post):
44 45 images = obj.attachments.filter(mimetype__in=FILE_TYPES_IMAGE)
45 46 image_urls = ['<a href="{}"><img src="{}" /></a>'.format(
46 47 reverse('admin:%s_%s_change' % (image._meta.app_label,
47 48 image._meta.model_name),
48 49 args=[image.id]), image.get_thumb_url()) for image in images]
49 50 return ', '.join(image_urls)
50 51 linked_images.allow_tags = True
51 52
52 53 def linked_global_id(self, obj: Post):
53 54 global_id = obj.global_id
54 55 if global_id is not None:
55 56 return '<a href="{}">{}</a>'.format(
56 57 reverse('admin:%s_%s_change' % (global_id._meta.app_label,
57 58 global_id._meta.model_name),
58 59 args=[global_id.id]), str(global_id))
59 60 linked_global_id.allow_tags = True
60 61
61 62 def tags(self, obj: Post):
62 63 return ', '.join([tag.name for tag in obj.get_tags()])
63 64
64 65 def save_model(self, request, obj, form, change):
65 66 obj.increment_version()
66 67 obj.save()
67 68 obj.clear_cache()
68 69
69 70 def foreign(self, obj: Post):
70 71 return obj is not None and obj.global_id is not None and\
71 72 not obj.global_id.is_local()
72 73
73 74 actions = ['ban_poster', 'ban_latter_with_delete']
74 75
75 76
76 77 @admin.register(Tag)
77 78 class TagAdmin(admin.ModelAdmin):
78 79
79 80 def thread_count(self, obj: Tag) -> int:
80 81 return obj.get_thread_count()
81 82
82 83 def display_children(self, obj: Tag):
83 84 return ', '.join([str(child) for child in obj.get_children().all()])
84 85
85 86 def save_model(self, request, obj, form, change):
86 87 super().save_model(request, obj, form, change)
87 88 for thread in obj.get_threads().all():
88 89 thread.refresh_tags()
89 90 list_display = ('name', 'thread_count', 'display_children')
90 91 search_fields = ('name',)
91 92
92 93
94 @admin.register(TagAlias)
95 class TagAliasAdmin(admin.ModelAdmin):
96 list_display = ('locale', 'name', 'parent')
97
98
93 99 @admin.register(Thread)
94 100 class ThreadAdmin(admin.ModelAdmin):
95 101
96 102 def title(self, obj: Thread) -> str:
97 103 return obj.get_opening_post().get_title()
98 104
99 105 def reply_count(self, obj: Thread) -> int:
100 106 return obj.get_reply_count()
101 107
102 108 def ip(self, obj: Thread):
103 109 return obj.get_opening_post().poster_ip
104 110
105 111 def display_tags(self, obj: Thread):
106 112 return ', '.join([str(tag) for tag in obj.get_tags().all()])
107 113
108 114 def op(self, obj: Thread):
109 115 return obj.get_opening_post_id()
110 116
111 117 # Save parent tags when editing tags
112 118 def save_related(self, request, form, formsets, change):
113 119 super().save_related(request, form, formsets, change)
114 120 form.instance.refresh_tags()
115 121
116 122 def save_model(self, request, obj, form, change):
117 123 op = obj.get_opening_post()
118 124 op.increment_version()
119 125 op.save(update_fields=['version'])
120 126 obj.save()
121 127 op.clear_cache()
122 128
123 129 list_display = ('id', 'op', 'title', 'reply_count', 'status', 'ip',
124 130 'display_tags')
125 131 list_filter = ('bump_time', 'status')
126 132 search_fields = ('id', 'title')
127 133 filter_horizontal = ('tags',)
128 134
129 135
130 136 @admin.register(KeyPair)
131 137 class KeyPairAdmin(admin.ModelAdmin):
132 138 list_display = ('public_key', 'primary')
133 139 list_filter = ('primary',)
134 140 search_fields = ('public_key',)
135 141
136 142
137 143 @admin.register(Ban)
138 144 class BanAdmin(admin.ModelAdmin):
139 145 list_display = ('ip', 'can_read')
140 146 list_filter = ('can_read',)
141 147 search_fields = ('ip',)
142 148
143 149
144 150 @admin.register(Banner)
145 151 class BannerAdmin(admin.ModelAdmin):
146 152 list_display = ('title', 'text')
147 153
148 154
149 155 @admin.register(Attachment)
150 156 class AttachmentAdmin(admin.ModelAdmin):
151 157 list_display = ('__str__', 'mimetype', 'file', 'url', 'alias')
152 158 search_fields = ('alias',)
153 159
154 160 def delete_alias(self, request, queryset):
155 161 for attachment in queryset:
156 162 attachment.alias = None
157 163 attachment.save(update_fields=['alias'])
158 164 self.message_user(request, _('Aliases removed'))
159 165
160 166 actions = ['delete_alias']
161 167
162 168
163 169 @admin.register(GlobalId)
164 170 class GlobalIdAdmin(admin.ModelAdmin):
165 171 def is_linked(self, obj):
166 172 return Post.objects.filter(global_id=obj).exists()
167 173
168 174 list_display = ('__str__', 'is_linked',)
169 175 readonly_fields = ('content',)
@@ -1,13 +1,14 b''
1 1 STATUS_ACTIVE = 'active'
2 2 STATUS_BUMPLIMIT = 'bumplimit'
3 3 STATUS_ARCHIVE = 'archived'
4 4
5 5
6 6 from boards.models.sync_key import KeyPair
7 7 from boards.models.signature import GlobalId, Signature
8 8 from boards.models.attachment import Attachment
9 9 from boards.models.thread import Thread
10 10 from boards.models.post import Post
11 from boards.models.tag import TagAlias
11 12 from boards.models.tag import Tag
12 13 from boards.models.user import Ban
13 14 from boards.models.banner import Banner
@@ -1,162 +1,177 b''
1 1 import hashlib
2 2 import re
3 3
4 4 from boards.models.attachment import FILE_TYPES_IMAGE
5 5 from django.template.loader import render_to_string
6 6 from django.db import models
7 7 from django.db.models import Count
8 8 from django.core.urlresolvers import reverse
9 9 from django.utils.translation import get_language
10 10
11 11 from boards.models import Attachment
12 12 from boards.models.base import Viewable
13 13 from boards.models.thread import STATUS_ACTIVE, STATUS_BUMPLIMIT, STATUS_ARCHIVE
14 14 from boards.utils import cached_result
15 15 import boards
16 16
17 17 __author__ = 'neko259'
18 18
19 19
20 20 RELATED_TAGS_COUNT = 5
21 21
22 REGEX_TAG_ALIAS = r'{}:(\w+),'
22
23 class TagAlias(models.Model, Viewable):
24 class Meta:
25 app_label = 'boards'
26 ordering = ('name',)
27
28 name = models.CharField(max_length=100, db_index=True, unique=True)
29 locale = models.CharField(max_length=10, db_index=True)
30
31 parent = models.ForeignKey('Tag', null=True, blank=True,
32 related_name='aliases')
23 33
24 34
25 35 class TagManager(models.Manager):
26 36 def get_not_empty_tags(self):
27 37 """
28 38 Gets tags that have non-archived threads.
29 39 """
30 40
31 41 return self.annotate(num_threads=Count('thread_tags')).filter(num_threads__gt=0)\
32 42 .order_by('name')
33 43
34 44 def get_tag_url_list(self, tags: list) -> str:
35 45 """
36 46 Gets a comma-separated list of tag links.
37 47 """
38 48
39 49 return ', '.join([tag.get_view() for tag in tags])
40 50
41 51 def get_by_alias(self, alias):
42 return self.filter(aliases__contains=":{},".format(alias)).first()
52 tag = None
53 try:
54 tag = TagAlias.objects.get(name=alias).parent
55 except TagAlias.DoesNotExist:
56 pass
57 return tag
43 58
44 59
45 60 class Tag(models.Model, Viewable):
46 61 """
47 62 A tag is a text node assigned to the thread. The tag serves as a board
48 63 section. There can be multiple tags for each thread
49 64 """
50 65
51 66 objects = TagManager()
52 67
53 68 class Meta:
54 69 app_label = 'boards'
55 70 ordering = ('name',)
56 71
57 72 name = models.CharField(max_length=100, db_index=True, unique=True)
58 73 required = models.BooleanField(default=False, db_index=True)
59 74 description = models.TextField(blank=True)
60 75
61 76 parent = models.ForeignKey('Tag', null=True, blank=True,
62 77 related_name='children')
63 aliases = models.TextField(blank=True)
64 78
65 79 def __str__(self):
66 80 return self.name
67 81
68 82 def is_empty(self) -> bool:
69 83 """
70 84 Checks if the tag has some threads.
71 85 """
72 86
73 87 return self.get_thread_count() == 0
74 88
75 89 def get_thread_count(self, status=None) -> int:
76 90 threads = self.get_threads()
77 91 if status is not None:
78 92 threads = threads.filter(status=status)
79 93 return threads.count()
80 94
81 95 def get_active_thread_count(self) -> int:
82 96 return self.get_thread_count(status=STATUS_ACTIVE)
83 97
84 98 def get_bumplimit_thread_count(self) -> int:
85 99 return self.get_thread_count(status=STATUS_BUMPLIMIT)
86 100
87 101 def get_archived_thread_count(self) -> int:
88 102 return self.get_thread_count(status=STATUS_ARCHIVE)
89 103
90 104 def get_absolute_url(self):
91 105 return reverse('tag', kwargs={'tag_name': self.name})
92 106
93 107 def get_threads(self):
94 108 return self.thread_tags.order_by('-bump_time')
95 109
96 110 def is_required(self):
97 111 return self.required
98 112
99 113 def get_view(self):
100 114 locale = get_language()
101 115
102 localized_tag_name = ''
103 if self.aliases:
104 match = re.search(REGEX_TAG_ALIAS.format(locale), self.aliases)
105 if match:
106 localized_tag_name = match.group(1)
116 try:
117 localized_tag_name = self.aliases.get(locale=locale).name
118 except TagAlias.DoesNotExist:
119 localized_tag_name = ''
107 120
108 121 name = '{} ({})'.format(self.name, localized_tag_name) if localized_tag_name else self.name
109 122 link = '<a class="tag" href="{}">{}</a>'.format(
110 123 self.get_absolute_url(), name)
111 124 if self.is_required():
112 125 link = '<b>{}</b>'.format(link)
113 126 return link
114 127
115 128 @cached_result()
116 129 def get_post_count(self):
117 130 return self.get_threads().aggregate(num_posts=Count('replies'))['num_posts']
118 131
119 132 def get_description(self):
120 133 return self.description
121 134
122 135 def get_random_image_post(self, status=[STATUS_ACTIVE, STATUS_BUMPLIMIT]):
123 136 posts = boards.models.Post.objects.filter(attachments__mimetype__in=FILE_TYPES_IMAGE)\
124 137 .annotate(images_count=Count(
125 138 'attachments')).filter(images_count__gt=0, thread__tags__in=[self])
126 139 if status is not None:
127 140 posts = posts.filter(thread__status__in=status)
128 141 return posts.order_by('?').first()
129 142
130 143 def get_first_letter(self):
131 144 return self.name and self.name[0] or ''
132 145
133 146 def get_related_tags(self):
134 147 return set(Tag.objects.filter(thread_tags__in=self.get_threads()).exclude(
135 148 id=self.id).order_by('?')[:RELATED_TAGS_COUNT])
136 149
137 150 @cached_result()
138 151 def get_color(self):
139 152 """
140 153 Gets color hashed from the tag name.
141 154 """
142 155 return hashlib.md5(self.name.encode()).hexdigest()[:6]
143 156
144 157 def get_parent(self):
145 158 return self.parent
146 159
147 160 def get_all_parents(self):
148 161 parents = list()
149 162 parent = self.get_parent()
150 163 if parent and parent not in parents:
151 164 parents.insert(0, parent)
152 165 parents = parent.get_all_parents() + parents
153 166
154 167 return parents
155 168
156 169 def get_children(self):
157 170 return self.children
158 171
159 172 def get_images(self):
160 173 return Attachment.objects.filter(
161 174 attachment_posts__thread__tags__in=[self]).filter(
162 175 mimetype__in=FILE_TYPES_IMAGE).order_by('-attachment_posts__pub_time')
176
177
General Comments 0
You need to be logged in to leave comments. Login now