##// END OF EJS Templates
Speed up getting tag's random image
neko259 -
r1264:50888044 default
parent child Browse files
Show More
@@ -1,125 +1,123 b''
1 1 import hashlib
2 2 import os
3 3 from random import random
4 4 import time
5 5
6 6 from django.db import models
7 7 from django.template.defaultfilters import filesizeformat
8 8
9 9 from boards import thumbs
10 10 import boards
11 11 from boards.models.base import Viewable
12 12
13 13 __author__ = 'neko259'
14 14
15 15
16 16 IMAGE_THUMB_SIZE = (200, 150)
17 17 IMAGES_DIRECTORY = 'images/'
18 18 FILE_EXTENSION_DELIMITER = '.'
19 19 HASH_LENGTH = 36
20 20
21 21 CSS_CLASS_IMAGE = 'image'
22 22 CSS_CLASS_THUMB = 'thumb'
23 23
24 24
25 25 class PostImageManager(models.Manager):
26 26 def create_with_hash(self, image):
27 27 image_hash = self.get_hash(image)
28 28 existing = self.filter(hash=image_hash)
29 29 if len(existing) > 0:
30 30 post_image = existing[0]
31 31 else:
32 32 post_image = PostImage.objects.create(image=image)
33 33
34 34 return post_image
35 35
36 36 def get_hash(self, image):
37 37 """
38 38 Gets hash of an image.
39 39 """
40 40 md5 = hashlib.md5()
41 41 for chunk in image.chunks():
42 42 md5.update(chunk)
43 43 return md5.hexdigest()
44 44
45 45 def get_random_images(self, count, include_archived=False, tags=None):
46 46 images = self.filter(post_images__thread__archived=include_archived)
47 47 if tags is not None:
48 48 images = images.filter(post_images__threads__tags__in=tags)
49 49 return images.order_by('?')[:count]
50 50
51 51
52 52 class PostImage(models.Model, Viewable):
53 53 objects = PostImageManager()
54 54
55 55 class Meta:
56 56 app_label = 'boards'
57 57 ordering = ('id',)
58 58
59 59 def _update_image_filename(self, filename):
60 60 """
61 61 Gets unique image filename
62 62 """
63 63
64 64 path = IMAGES_DIRECTORY
65 65
66 66 # TODO Use something other than random number in file name
67 67 new_name = '{}{}.{}'.format(
68 68 str(int(time.mktime(time.gmtime()))),
69 69 str(int(random() * 1000)),
70 70 filename.split(FILE_EXTENSION_DELIMITER)[-1:][0])
71 71
72 72 return os.path.join(path, new_name)
73 73
74 74 width = models.IntegerField(default=0)
75 75 height = models.IntegerField(default=0)
76 76
77 77 pre_width = models.IntegerField(default=0)
78 78 pre_height = models.IntegerField(default=0)
79 79
80 80 image = thumbs.ImageWithThumbsField(upload_to=_update_image_filename,
81 81 blank=True, sizes=(IMAGE_THUMB_SIZE,),
82 82 width_field='width',
83 83 height_field='height',
84 84 preview_width_field='pre_width',
85 85 preview_height_field='pre_height')
86 86 hash = models.CharField(max_length=HASH_LENGTH)
87 87
88 88 def save(self, *args, **kwargs):
89 89 """
90 90 Saves the model and computes the image hash for deduplication purposes.
91 91 """
92 92
93 93 if not self.pk and self.image:
94 94 self.hash = PostImage.objects.get_hash(self.image)
95 95 super(PostImage, self).save(*args, **kwargs)
96 96
97 97 def __str__(self):
98 98 return self.image.url
99 99
100 100 def get_view(self):
101 101 metadata = '{}, {}'.format(self.image.name.split('.')[-1],
102 102 filesizeformat(self.image.size))
103 103 return '<div class="{}">' \
104 104 '<a class="{}" href="{full}">' \
105 105 '<img class="post-image-preview"' \
106 106 ' src="{}"' \
107 107 ' alt="{}"' \
108 108 ' width="{}"' \
109 109 ' height="{}"' \
110 110 ' data-width="{}"' \
111 111 ' data-height="{}" />' \
112 112 '</a>' \
113 113 '<div class="image-metadata">{image_meta}</div>' \
114 114 '</div>'\
115 115 .format(CSS_CLASS_IMAGE, CSS_CLASS_THUMB,
116 116 self.image.url_200x150,
117 117 str(self.hash), str(self.pre_width),
118 118 str(self.pre_height), str(self.width), str(self.height),
119 119 full=self.image.url, image_meta=metadata)
120 120
121 def get_random_associated_post(self, tags=None):
121 def get_random_associated_post(self):
122 122 posts = boards.models.Post.objects.filter(images__in=[self])
123 if tags is not None:
124 posts = posts.filter(threads__tags__in=tags)
125 123 return posts.order_by('?').first()
@@ -1,92 +1,98 b''
1 1 from django.template.loader import render_to_string
2 2 from django.db import models
3 3 from django.db.models import Count
4 4 from django.core.urlresolvers import reverse
5 5
6 6 from boards.models.base import Viewable
7 7 from boards.utils import cached_result
8
8 import boards
9 9
10 10 __author__ = 'neko259'
11 11
12 12
13 13 class TagManager(models.Manager):
14 14
15 15 def get_not_empty_tags(self):
16 16 """
17 17 Gets tags that have non-archived threads.
18 18 """
19 19
20 20 return self.annotate(num_threads=Count('thread')).filter(num_threads__gt=0)\
21 21 .order_by('-required', 'name')
22 22
23 23 def get_tag_url_list(self, tags: list) -> str:
24 24 """
25 25 Gets a comma-separated list of tag links.
26 26 """
27 27
28 28 return ', '.join([tag.get_view() for tag in tags])
29 29
30 30
31 31 class Tag(models.Model, Viewable):
32 32 """
33 33 A tag is a text node assigned to the thread. The tag serves as a board
34 34 section. There can be multiple tags for each thread
35 35 """
36 36
37 37 objects = TagManager()
38 38
39 39 class Meta:
40 40 app_label = 'boards'
41 41 ordering = ('name',)
42 42
43 43 name = models.CharField(max_length=100, db_index=True, unique=True)
44 44 required = models.BooleanField(default=False, db_index=True)
45 45 description = models.TextField(blank=True)
46 46
47 47 def __str__(self):
48 48 return self.name
49 49
50 50 def is_empty(self) -> bool:
51 51 """
52 52 Checks if the tag has some threads.
53 53 """
54 54
55 55 return self.get_thread_count() == 0
56 56
57 57 def get_thread_count(self, archived=None) -> int:
58 58 threads = self.get_threads()
59 59 if archived is not None:
60 60 threads = threads.filter(archived=archived)
61 61 return threads.count()
62 62
63 63 def get_active_thread_count(self) -> int:
64 64 return self.get_thread_count(archived=False)
65 65
66 66 def get_absolute_url(self):
67 67 return reverse('tag', kwargs={'tag_name': self.name})
68 68
69 69 def get_threads(self):
70 70 return self.thread_set.order_by('-bump_time')
71 71
72 72 def is_required(self):
73 73 return self.required
74 74
75 75 def get_view(self):
76 76 link = '<a class="tag" href="{}">{}</a>'.format(
77 77 self.get_absolute_url(), self.name)
78 78 if self.is_required():
79 79 link = '<b>{}</b>'.format(link)
80 80 return link
81 81
82 82 def get_search_view(self, *args, **kwargs):
83 83 return render_to_string('boards/tag.html', {
84 84 'tag': self,
85 85 })
86 86
87 87 @cached_result()
88 88 def get_post_count(self):
89 89 return self.get_threads().aggregate(num_posts=Count('post'))['num_posts']
90 90
91 91 def get_description(self):
92 92 return self.description
93
94 def get_random_image_post(self):
95 return boards.models.Post.objects.annotate(images_count=Count(
96 'images')).filter(images_count__gt=0, threads__tags__in=[self])\
97 .order_by('?').first()
98
@@ -1,179 +1,181 b''
1 1 {% extends "boards/base.html" %}
2 2
3 3 {% load i18n %}
4 4 {% load board %}
5 5 {% load static %}
6 6 {% load tz %}
7 7
8 8 {% block head %}
9 9 <meta name="robots" content="noindex">
10 10
11 11 {% if tag %}
12 12 <title>{{ tag.name }} - {{ site_name }}</title>
13 13 {% else %}
14 14 <title>{{ site_name }}</title>
15 15 {% endif %}
16 16
17 17 {% if prev_page_link %}
18 18 <link rel="prev" href="{{ prev_page_link }}" />
19 19 {% endif %}
20 20 {% if next_page_link %}
21 21 <link rel="next" href="{{ next_page_link }}" />
22 22 {% endif %}
23 23
24 24 {% endblock %}
25 25
26 26 {% block content %}
27 27
28 28 {% get_current_language as LANGUAGE_CODE %}
29 29 {% get_current_timezone as TIME_ZONE %}
30 30
31 31 {% for banner in banners %}
32 32 <div class="post">
33 33 <div class="title">{{ banner.title }}</div>
34 34 <div>{{ banner.text }}</div>
35 35 <div>{% trans 'Related message' %}: <a href="{{ banner.post.get_absolute_url }}">>>{{ banner.post.id }}</a></div>
36 36 </div>
37 37 {% endfor %}
38 38
39 39 {% if tag %}
40 40 <div class="tag_info">
41 41 <div class="tag-image">
42 <a href="{{ random_image_post.get_absolute_url }}"><img
43 src="{{ random_image.image.url_200x150 }}"
44 width="{{ random_image.pre_width }}"
45 height="{{ random_image.pre_height }}"/></a>
42 {% with image=random_image_post.images.first %}
43 <a href="{{ random_image_post.get_absolute_url }}"><img
44 src="{{ image.image.url_200x150 }}"
45 width="{{ image.pre_width }}"
46 height="{{ image.pre_height }}"/></a>
47 {% endwith %}
46 48 </div>
47 49 <div class="tag-text-data">
48 50 <h2>
49 51 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
50 52 {% if is_favorite %}
51 53 <button name="method" value="unsubscribe" class="fav">β˜…</button>
52 54 {% else %}
53 55 <button name="method" value="subscribe" class="not_fav">β˜…</button>
54 56 {% endif %}
55 57 </form>
56 58 <form action="{% url 'tag' tag.name %}" method="post" class="post-button-form">
57 59 {% if is_hidden %}
58 60 <button name="method" value="unhide" class="fav">H</button>
59 61 {% else %}
60 62 <button name="method" value="hide" class="not_fav">H</button>
61 63 {% endif %}
62 64 </form>
63 65 {% autoescape off %}
64 66 {{ tag.get_view }}
65 67 {% endautoescape %}
66 68 {% if moderator %}
67 69 <span class="moderator_info">| <a href="{% url 'admin:boards_tag_change' tag.id %}">{% trans 'Edit tag' %}</a></span>
68 70 {% endif %}
69 71 </h2>
70 72 {% if tag.get_description %}
71 73 {% autoescape off %}
72 74 <p>{{ tag.get_description }}</p>
73 75 {% endautoescape %}
74 76 {% endif %}
75 77 <p>{% blocktrans with active_thread_count=tag.get_active_thread_count thread_count=tag.get_thread_count post_count=tag.get_post_count %}This tag has {{ active_thread_count}}/{{ thread_count }} threads and {{ post_count }} posts.{% endblocktrans %}</p>
76 78 </div>
77 79 </div>
78 80 {% endif %}
79 81
80 82 {% if threads %}
81 83 {% if prev_page_link %}
82 84 <div class="page_link">
83 85 <a href="{{ prev_page_link }}">{% trans "Previous page" %}</a>
84 86 </div>
85 87 {% endif %}
86 88
87 89 {% for thread in threads %}
88 90 <div class="thread">
89 91 {% post_view thread.get_opening_post moderator=moderator is_opening=True thread=thread truncated=True need_open_link=True %}
90 92 {% if not thread.archived %}
91 93 {% with last_replies=thread.get_last_replies %}
92 94 {% if last_replies %}
93 95 {% with skipped_replies_count=thread.get_skipped_replies_count %}
94 96 {% if skipped_replies_count %}
95 97 <div class="skipped_replies">
96 98 <a href="{% url 'thread' thread.get_opening_post_id %}">
97 99 {% blocktrans with count=skipped_replies_count %}Skipped {{ count }} replies. Open thread to see all replies.{% endblocktrans %}
98 100 </a>
99 101 </div>
100 102 {% endif %}
101 103 {% endwith %}
102 104 <div class="last-replies">
103 105 {% for post in last_replies %}
104 106 {% post_view post is_opening=False moderator=moderator truncated=True %}
105 107 {% endfor %}
106 108 </div>
107 109 {% endif %}
108 110 {% endwith %}
109 111 {% endif %}
110 112 </div>
111 113 {% endfor %}
112 114
113 115 {% if next_page_link %}
114 116 <div class="page_link">
115 117 <a href="{{ next_page_link }}">{% trans "Next page" %}</a>
116 118 </div>
117 119 {% endif %}
118 120 {% else %}
119 121 <div class="post">
120 122 {% trans 'No threads exist. Create the first one!' %}</div>
121 123 {% endif %}
122 124
123 125 <div class="post-form-w">
124 126 <script src="{% static 'js/panel.js' %}"></script>
125 127 <div class="post-form">
126 128 <div class="form-title">{% trans "Create new thread" %}</div>
127 129 <div class="swappable-form-full">
128 130 <form enctype="multipart/form-data" method="post" id="form">{% csrf_token %}
129 131 {{ form.as_div }}
130 132 <div class="form-submit">
131 133 <input type="submit" value="{% trans "Post" %}"/>
132 134 </div>
133 135 </form>
134 136 </div>
135 137 <div>
136 138 {% trans 'Tags must be delimited by spaces. Text or image is required.' %}
137 139 </div>
138 140 <div><button id="preview-button">{% trans 'Preview' %}</button></div>
139 141 <div id="preview-text"></div>
140 142 <div><a href="{% url "staticpage" name="help" %}">{% trans 'Text syntax' %}</a></div>
141 143 <div><a href="{% url "tags" "required" %}">{% trans 'Tags' %}</a></div>
142 144 </div>
143 145 </div>
144 146
145 147 <script src="{% static 'js/form.js' %}"></script>
146 148 <script src="{% static 'js/thread_create.js' %}"></script>
147 149
148 150 {% endblock %}
149 151
150 152 {% block metapanel %}
151 153
152 154 <span class="metapanel">
153 155 <b><a href="{% url "authors" %}">{{ site_name }}</a> {{ version }}</b>
154 156 {% trans "Pages:" %}
155 157 [
156 158 {% with dividers=paginator.get_dividers %}
157 159 {% for page in paginator.get_divided_range %}
158 160 {% if page in dividers %}
159 161 …,
160 162 {% endif %}
161 163 <a
162 164 {% ifequal page current_page.number %}
163 165 class="current_page"
164 166 {% endifequal %}
165 167 href="
166 168 {% if tag %}
167 169 {% url "tag" tag_name=tag.name %}?page={{ page }}
168 170 {% else %}
169 171 {% url "index" %}?page={{ page }}
170 172 {% endif %}
171 173 ">{{ page }}</a>
172 174 {% if not forloop.last %},{% endif %}
173 175 {% endfor %}
174 176 {% endwith %}
175 177 ]
176 178 [<a href="rss/">RSS</a>]
177 179 </span>
178 180
179 181 {% endblock %}
@@ -1,126 +1,123 b''
1 1 from django.shortcuts import get_object_or_404, redirect
2 2 from django.core.urlresolvers import reverse
3 3
4 4 from boards.abstracts.settingsmanager import get_settings_manager, \
5 5 SETTING_FAVORITE_TAGS, SETTING_HIDDEN_TAGS
6 6 from boards.models import Tag, PostImage
7 7 from boards.views.all_threads import AllThreadsView, DEFAULT_PAGE
8 8 from boards.views.mixins import DispatcherMixin
9 9 from boards.forms import ThreadForm, PlainErrorList
10 10
11 11 PARAM_HIDDEN_TAGS = 'hidden_tags'
12 12 PARAM_TAG = 'tag'
13 13 PARAM_IS_FAVORITE = 'is_favorite'
14 14 PARAM_IS_HIDDEN = 'is_hidden'
15 PARAM_RANDOM_IMAGE = 'random_image'
16 15 PARAM_RANDOM_IMAGE_POST = 'random_image_post'
17 16
18 17 __author__ = 'neko259'
19 18
20 19
21 20 class TagView(AllThreadsView, DispatcherMixin):
22 21
23 22 tag_name = None
24 23
25 24 def get_threads(self):
26 25 tag = get_object_or_404(Tag, name=self.tag_name)
27 26
28 27 hidden_tags = self.settings_manager.get_hidden_tags()
29 28
30 29 try:
31 30 hidden_tags.remove(tag)
32 31 except ValueError:
33 32 pass
34 33
35 34 return tag.get_threads().exclude(
36 35 tags__in=hidden_tags)
37 36
38 37 def get_context_data(self, **kwargs):
39 38 params = super(TagView, self).get_context_data(**kwargs)
40 39
41 40 settings_manager = get_settings_manager(kwargs['request'])
42 41
43 42 tag = get_object_or_404(Tag, name=self.tag_name)
44 43 params[PARAM_TAG] = tag
45 44
46 45 fav_tag_names = settings_manager.get_setting(SETTING_FAVORITE_TAGS)
47 46 hidden_tag_names = settings_manager.get_setting(SETTING_HIDDEN_TAGS)
48 47
49 48 params[PARAM_IS_FAVORITE] = fav_tag_names is not None and tag.name in fav_tag_names
50 49 params[PARAM_IS_HIDDEN] = hidden_tag_names is not None and tag.name in hidden_tag_names
51 50
52 image = PostImage.objects.get_random_images(1, tags=[tag])[0]
53 params[PARAM_RANDOM_IMAGE] = image
54 params[PARAM_RANDOM_IMAGE_POST] = image.get_random_associated_post(tags=[tag])
51 params[PARAM_RANDOM_IMAGE_POST] = tag.get_random_image_post()
55 52
56 53 return params
57 54
58 55 def get_previous_page_link(self, current_page):
59 56 return reverse('tag', kwargs={
60 57 'tag_name': self.tag_name,
61 58 }) + '?page=' + str(current_page.previous_page_number())
62 59
63 60 def get_next_page_link(self, current_page):
64 61 return reverse('tag', kwargs={
65 62 'tag_name': self.tag_name,
66 63 }) + '?page=' + str(current_page.next_page_number())
67 64
68 65 def get(self, request, tag_name, form=None):
69 66 self.tag_name = tag_name
70 67
71 68 return super(TagView, self).get(request, form)
72 69
73 70
74 71 def post(self, request, tag_name):
75 72 self.tag_name = tag_name
76 73
77 74 if 'method' in request.POST:
78 75 self.dispatch_method(request)
79 76 form = None
80 77
81 78 return redirect('tag', tag_name)
82 79 else:
83 80 form = ThreadForm(request.POST, request.FILES,
84 81 error_class=PlainErrorList)
85 82 form.session = request.session
86 83
87 84 if form.is_valid():
88 85 return self.create_thread(request, form)
89 86 if form.need_to_ban:
90 87 # Ban user because he is suspected to be a bot
91 88 self._ban_current_user(request)
92 89
93 90 return self.get(request, tag_name, page, form)
94 91
95 92 def subscribe(self, request):
96 93 tag = get_object_or_404(Tag, name=self.tag_name)
97 94
98 95 settings_manager = get_settings_manager(request)
99 96 settings_manager.add_fav_tag(tag)
100 97
101 98 def unsubscribe(self, request):
102 99 tag = get_object_or_404(Tag, name=self.tag_name)
103 100
104 101 settings_manager = get_settings_manager(request)
105 102 settings_manager.del_fav_tag(tag)
106 103
107 104 def hide(self, request):
108 105 """
109 106 Adds tag to user's hidden tags. Threads with this tag will not be
110 107 shown.
111 108 """
112 109
113 110 tag = get_object_or_404(Tag, name=self.tag_name)
114 111
115 112 settings_manager = get_settings_manager(request)
116 113 settings_manager.add_hidden_tag(tag)
117 114
118 115 def unhide(self, request):
119 116 """
120 117 Removed tag from user's hidden tags.
121 118 """
122 119
123 120 tag = get_object_or_404(Tag, name=self.tag_name)
124 121
125 122 settings_manager = get_settings_manager(request)
126 123 settings_manager.del_hidden_tag(tag)
General Comments 0
You need to be logged in to leave comments. Login now