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