##// END OF EJS Templates
Added domain image for linux.org.ru, added ability to make images for third-level domains when it is an org.ru, com.ua, co.uk etc domain
neko259 -
r1724:70ff8482 default
parent child Browse files
Show More
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -1,192 +1,192 b''
1 import re
1 import re
2
2
3 from django.contrib.staticfiles import finders
3 from django.contrib.staticfiles import finders
4 from django.contrib.staticfiles.templatetags.staticfiles import static
4 from django.contrib.staticfiles.templatetags.staticfiles import static
5 from django.core.files.images import get_image_dimensions
5 from django.core.files.images import get_image_dimensions
6 from django.template.defaultfilters import filesizeformat
6 from django.template.defaultfilters import filesizeformat
7
7
8 from boards.utils import get_domain
8
9
9 REGEX_DOMAIN = re.compile(r'(\w+\.)*(\w+\.\w+)')
10
10
11 FILE_STUB_IMAGE = 'images/file.png'
11 FILE_STUB_IMAGE = 'images/file.png'
12 FILE_STUB_URL = 'url'
12 FILE_STUB_URL = 'url'
13
13
14 FILE_TYPES_VIDEO = (
14 FILE_TYPES_VIDEO = (
15 'webm',
15 'webm',
16 'mp4',
16 'mp4',
17 'mpeg',
17 'mpeg',
18 'ogv',
18 'ogv',
19 )
19 )
20 FILE_TYPE_SVG = 'svg'
20 FILE_TYPE_SVG = 'svg'
21 FILE_TYPES_AUDIO = (
21 FILE_TYPES_AUDIO = (
22 'ogg',
22 'ogg',
23 'mp3',
23 'mp3',
24 'opus',
24 'opus',
25 )
25 )
26 FILE_TYPES_IMAGE = (
26 FILE_TYPES_IMAGE = (
27 'jpeg',
27 'jpeg',
28 'jpg',
28 'jpg',
29 'png',
29 'png',
30 'bmp',
30 'bmp',
31 'gif',
31 'gif',
32 )
32 )
33
33
34 PLAIN_FILE_FORMATS = {
34 PLAIN_FILE_FORMATS = {
35 'pdf': 'pdf',
35 'pdf': 'pdf',
36 'djvu': 'djvu',
36 'djvu': 'djvu',
37 'txt': 'txt',
37 'txt': 'txt',
38 'tex': 'tex',
38 'tex': 'tex',
39 'xcf': 'xcf',
39 'xcf': 'xcf',
40 'zip': 'archive',
40 'zip': 'archive',
41 'tar': 'archive',
41 'tar': 'archive',
42 'gz': 'archive',
42 'gz': 'archive',
43 }
43 }
44
44
45 URL_PROTOCOLS = {
45 URL_PROTOCOLS = {
46 'magnet': 'magnet',
46 'magnet': 'magnet',
47 }
47 }
48
48
49 CSS_CLASS_IMAGE = 'image'
49 CSS_CLASS_IMAGE = 'image'
50 CSS_CLASS_THUMB = 'thumb'
50 CSS_CLASS_THUMB = 'thumb'
51
51
52
52
53 def get_viewers():
53 def get_viewers():
54 return AbstractViewer.__subclasses__()
54 return AbstractViewer.__subclasses__()
55
55
56
56
57 def get_static_dimensions(filename):
57 def get_static_dimensions(filename):
58 file_path = finders.find(filename)
58 file_path = finders.find(filename)
59 return get_image_dimensions(file_path)
59 return get_image_dimensions(file_path)
60
60
61
61
62 # TODO Move this to utils
62 # TODO Move this to utils
63 def file_exists(filename):
63 def file_exists(filename):
64 return finders.find(filename) is not None
64 return finders.find(filename) is not None
65
65
66
66
67 class AbstractViewer:
67 class AbstractViewer:
68 def __init__(self, file, file_type, hash, url):
68 def __init__(self, file, file_type, hash, url):
69 self.file = file
69 self.file = file
70 self.file_type = file_type
70 self.file_type = file_type
71 self.hash = hash
71 self.hash = hash
72 self.url = url
72 self.url = url
73
73
74 @staticmethod
74 @staticmethod
75 def supports(file_type):
75 def supports(file_type):
76 return True
76 return True
77
77
78 def get_view(self):
78 def get_view(self):
79 return '<div class="image">'\
79 return '<div class="image">'\
80 '{}'\
80 '{}'\
81 '<div class="image-metadata"><a href="{}" download >{}, {}</a></div>'\
81 '<div class="image-metadata"><a href="{}" download >{}, {}</a></div>'\
82 '</div>'.format(self.get_format_view(), self.file.url,
82 '</div>'.format(self.get_format_view(), self.file.url,
83 self.file_type, filesizeformat(self.file.size))
83 self.file_type, filesizeformat(self.file.size))
84
84
85 def get_format_view(self):
85 def get_format_view(self):
86 if self.file_type in PLAIN_FILE_FORMATS:
86 if self.file_type in PLAIN_FILE_FORMATS:
87 image = 'images/fileformats/{}.png'.format(
87 image = 'images/fileformats/{}.png'.format(
88 PLAIN_FILE_FORMATS[self.file_type])
88 PLAIN_FILE_FORMATS[self.file_type])
89 else:
89 else:
90 image = FILE_STUB_IMAGE
90 image = FILE_STUB_IMAGE
91
91
92 w, h = get_static_dimensions(image)
92 w, h = get_static_dimensions(image)
93
93
94 return '<a href="{}">'\
94 return '<a href="{}">'\
95 '<img class="url-image" src="{}" width="{}" height="{}"/>'\
95 '<img class="url-image" src="{}" width="{}" height="{}"/>'\
96 '</a>'.format(self.file.url, static(image), w, h)
96 '</a>'.format(self.file.url, static(image), w, h)
97
97
98
98
99 class VideoViewer(AbstractViewer):
99 class VideoViewer(AbstractViewer):
100 @staticmethod
100 @staticmethod
101 def supports(file_type):
101 def supports(file_type):
102 return file_type in FILE_TYPES_VIDEO
102 return file_type in FILE_TYPES_VIDEO
103
103
104 def get_format_view(self):
104 def get_format_view(self):
105 return '<video width="200" height="150" controls src="{}"></video>'\
105 return '<video width="200" height="150" controls src="{}"></video>'\
106 .format(self.file.url)
106 .format(self.file.url)
107
107
108
108
109 class AudioViewer(AbstractViewer):
109 class AudioViewer(AbstractViewer):
110 @staticmethod
110 @staticmethod
111 def supports(file_type):
111 def supports(file_type):
112 return file_type in FILE_TYPES_AUDIO
112 return file_type in FILE_TYPES_AUDIO
113
113
114 def get_format_view(self):
114 def get_format_view(self):
115 return '<audio controls src="{}"></audio>'.format(self.file.url)
115 return '<audio controls src="{}"></audio>'.format(self.file.url)
116
116
117
117
118 class SvgViewer(AbstractViewer):
118 class SvgViewer(AbstractViewer):
119 @staticmethod
119 @staticmethod
120 def supports(file_type):
120 def supports(file_type):
121 return file_type == FILE_TYPE_SVG
121 return file_type == FILE_TYPE_SVG
122
122
123 def get_format_view(self):
123 def get_format_view(self):
124 return '<a class="thumb" href="{}">'\
124 return '<a class="thumb" href="{}">'\
125 '<img class="post-image-preview" width="200" height="150" src="{}" />'\
125 '<img class="post-image-preview" width="200" height="150" src="{}" />'\
126 '</a>'.format(self.file.url, self.file.url)
126 '</a>'.format(self.file.url, self.file.url)
127
127
128
128
129 class ImageViewer(AbstractViewer):
129 class ImageViewer(AbstractViewer):
130 @staticmethod
130 @staticmethod
131 def supports(file_type):
131 def supports(file_type):
132 return file_type in FILE_TYPES_IMAGE
132 return file_type in FILE_TYPES_IMAGE
133
133
134 def get_format_view(self):
134 def get_format_view(self):
135 metadata = '{}, {}'.format(self.file.name.split('.')[-1],
135 metadata = '{}, {}'.format(self.file.name.split('.')[-1],
136 filesizeformat(self.file.size))
136 filesizeformat(self.file.size))
137 width, height = get_image_dimensions(self.file.file)
137 width, height = get_image_dimensions(self.file.file)
138 preview_path = self.file.path.replace('.', '.200x150.')
138 preview_path = self.file.path.replace('.', '.200x150.')
139 pre_width, pre_height = get_image_dimensions(preview_path)
139 pre_width, pre_height = get_image_dimensions(preview_path)
140
140
141 split = self.file.url.rsplit('.', 1)
141 split = self.file.url.rsplit('.', 1)
142 w, h = 200, 150
142 w, h = 200, 150
143 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
143 thumb_url = '%s.%sx%s.%s' % (split[0], w, h, split[1])
144
144
145 return '<a class="{}" href="{full}">' \
145 return '<a class="{}" href="{full}">' \
146 '<img class="post-image-preview"' \
146 '<img class="post-image-preview"' \
147 ' src="{}"' \
147 ' src="{}"' \
148 ' alt="{}"' \
148 ' alt="{}"' \
149 ' width="{}"' \
149 ' width="{}"' \
150 ' height="{}"' \
150 ' height="{}"' \
151 ' data-width="{}"' \
151 ' data-width="{}"' \
152 ' data-height="{}" />' \
152 ' data-height="{}" />' \
153 '</a>' \
153 '</a>' \
154 .format(CSS_CLASS_THUMB,
154 .format(CSS_CLASS_THUMB,
155 thumb_url,
155 thumb_url,
156 self.hash,
156 self.hash,
157 str(pre_width),
157 str(pre_width),
158 str(pre_height), str(width), str(height),
158 str(pre_height), str(width), str(height),
159 full=self.file.url, image_meta=metadata)
159 full=self.file.url, image_meta=metadata)
160
160
161
161
162 class UrlViewer(AbstractViewer):
162 class UrlViewer(AbstractViewer):
163 @staticmethod
163 @staticmethod
164 def supports(file_type):
164 def supports(file_type):
165 return file_type is None
165 return file_type is None
166
166
167 def get_view(self):
167 def get_view(self):
168 return '<div class="image">' \
168 return '<div class="image">' \
169 '{}' \
169 '{}' \
170 '</div>'.format(self.get_format_view())
170 '</div>'.format(self.get_format_view())
171
171
172 def get_format_view(self):
172 def get_format_view(self):
173 protocol = self.url.split('://')[0]
173 protocol = self.url.split('://')[0]
174 full_domain = self.url.split('/')[2]
174 full_domain = self.url.split('/')[2]
175 domain = REGEX_DOMAIN.search(full_domain).group(2)
175 domain = get_domain(full_domain)
176
176
177 if protocol in URL_PROTOCOLS:
177 if protocol in URL_PROTOCOLS:
178 url_image_name = URL_PROTOCOLS.get(protocol)
178 url_image_name = URL_PROTOCOLS.get(protocol)
179 else:
179 else:
180 filename = 'images/domains/{}.png'.format(domain)
180 filename = 'images/domains/{}.png'.format(domain)
181 if file_exists(filename):
181 if file_exists(filename):
182 url_image_name = 'domains/' + domain
182 url_image_name = 'domains/' + domain
183 else:
183 else:
184 url_image_name = FILE_STUB_URL
184 url_image_name = FILE_STUB_URL
185
185
186 image_path = 'images/{}.png'.format(url_image_name)
186 image_path = 'images/{}.png'.format(url_image_name)
187 image = static(image_path)
187 image = static(image_path)
188 w, h = get_static_dimensions(image_path)
188 w, h = get_static_dimensions(image_path)
189
189
190 return '<a href="{}">' \
190 return '<a href="{}">' \
191 '<img class="url-image" src="{}" width="{}" height="{}"/>' \
191 '<img class="url-image" src="{}" width="{}" height="{}"/>' \
192 '</a>'.format(self.url, image, w, h)
192 '</a>'.format(self.url, image, w, h)
@@ -1,145 +1,170 b''
1 """
1 """
2 This module contains helper functions and helper classes.
2 This module contains helper functions and helper classes.
3 """
3 """
4 import hashlib
4 import hashlib
5 from boards.abstracts.constants import FILE_DIRECTORY
5 from boards.abstracts.constants import FILE_DIRECTORY
6 from random import random
6 from random import random
7 import time
7 import time
8 import hmac
8 import hmac
9
9
10 from django.core.cache import cache
10 from django.core.cache import cache
11 from django.db.models import Model
11 from django.db.models import Model
12 from django import forms
12 from django import forms
13 from django.template.defaultfilters import filesizeformat
13 from django.template.defaultfilters import filesizeformat
14 from django.utils import timezone
14 from django.utils import timezone
15 from django.utils.translation import ugettext_lazy as _
15 from django.utils.translation import ugettext_lazy as _
16 import magic
16 import magic
17 import os
17 import os
18
18
19 import boards
19 import boards
20 from boards.settings import get_bool
20 from boards.settings import get_bool
21 from neboard import settings
21 from neboard import settings
22
22
23 CACHE_KEY_DELIMITER = '_'
23 CACHE_KEY_DELIMITER = '_'
24
24
25 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
25 HTTP_FORWARDED = 'HTTP_X_FORWARDED_FOR'
26 META_REMOTE_ADDR = 'REMOTE_ADDR'
26 META_REMOTE_ADDR = 'REMOTE_ADDR'
27
27
28 SETTING_MESSAGES = 'Messages'
28 SETTING_MESSAGES = 'Messages'
29 SETTING_ANON_MODE = 'AnonymousMode'
29 SETTING_ANON_MODE = 'AnonymousMode'
30
30
31 ANON_IP = '127.0.0.1'
31 ANON_IP = '127.0.0.1'
32
32
33 FILE_EXTENSION_DELIMITER = '.'
33 FILE_EXTENSION_DELIMITER = '.'
34
34
35 KNOWN_DOMAINS = (
36 'org.ru',
37 )
38
35
39
36 def is_anonymous_mode():
40 def is_anonymous_mode():
37 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
41 return get_bool(SETTING_MESSAGES, SETTING_ANON_MODE)
38
42
39
43
40 def get_client_ip(request):
44 def get_client_ip(request):
41 if is_anonymous_mode():
45 if is_anonymous_mode():
42 ip = ANON_IP
46 ip = ANON_IP
43 else:
47 else:
44 x_forwarded_for = request.META.get(HTTP_FORWARDED)
48 x_forwarded_for = request.META.get(HTTP_FORWARDED)
45 if x_forwarded_for:
49 if x_forwarded_for:
46 ip = x_forwarded_for.split(',')[-1].strip()
50 ip = x_forwarded_for.split(',')[-1].strip()
47 else:
51 else:
48 ip = request.META.get(META_REMOTE_ADDR)
52 ip = request.META.get(META_REMOTE_ADDR)
49 return ip
53 return ip
50
54
51
55
52 # TODO The output format is not epoch because it includes microseconds
56 # TODO The output format is not epoch because it includes microseconds
53 def datetime_to_epoch(datetime):
57 def datetime_to_epoch(datetime):
54 return int(time.mktime(timezone.localtime(
58 return int(time.mktime(timezone.localtime(
55 datetime,timezone.get_current_timezone()).timetuple())
59 datetime,timezone.get_current_timezone()).timetuple())
56 * 1000000 + datetime.microsecond)
60 * 1000000 + datetime.microsecond)
57
61
58
62
59 def get_websocket_token(user_id='', timestamp=''):
63 def get_websocket_token(user_id='', timestamp=''):
60 """
64 """
61 Create token to validate information provided by new connection.
65 Create token to validate information provided by new connection.
62 """
66 """
63
67
64 sign = hmac.new(settings.CENTRIFUGE_PROJECT_SECRET.encode())
68 sign = hmac.new(settings.CENTRIFUGE_PROJECT_SECRET.encode())
65 sign.update(settings.CENTRIFUGE_PROJECT_ID.encode())
69 sign.update(settings.CENTRIFUGE_PROJECT_ID.encode())
66 sign.update(user_id.encode())
70 sign.update(user_id.encode())
67 sign.update(timestamp.encode())
71 sign.update(timestamp.encode())
68 token = sign.hexdigest()
72 token = sign.hexdigest()
69
73
70 return token
74 return token
71
75
72
76
73 # TODO Test this carefully
77 # TODO Test this carefully
74 def cached_result(key_method=None):
78 def cached_result(key_method=None):
75 """
79 """
76 Caches method result in the Django's cache system, persisted by object name,
80 Caches method result in the Django's cache system, persisted by object name,
77 object name, model id if object is a Django model, args and kwargs if any.
81 object name, model id if object is a Django model, args and kwargs if any.
78 """
82 """
79 def _cached_result(function):
83 def _cached_result(function):
80 def inner_func(obj, *args, **kwargs):
84 def inner_func(obj, *args, **kwargs):
81 cache_key_params = [obj.__class__.__name__, function.__name__]
85 cache_key_params = [obj.__class__.__name__, function.__name__]
82
86
83 cache_key_params += args
87 cache_key_params += args
84 for key, value in kwargs:
88 for key, value in kwargs:
85 cache_key_params.append(key + ':' + value)
89 cache_key_params.append(key + ':' + value)
86
90
87 if isinstance(obj, Model):
91 if isinstance(obj, Model):
88 cache_key_params.append(str(obj.id))
92 cache_key_params.append(str(obj.id))
89
93
90 if key_method is not None:
94 if key_method is not None:
91 cache_key_params += [str(arg) for arg in key_method(obj)]
95 cache_key_params += [str(arg) for arg in key_method(obj)]
92
96
93 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
97 cache_key = CACHE_KEY_DELIMITER.join(cache_key_params)
94
98
95 persisted_result = cache.get(cache_key)
99 persisted_result = cache.get(cache_key)
96 if persisted_result is not None:
100 if persisted_result is not None:
97 result = persisted_result
101 result = persisted_result
98 else:
102 else:
99 result = function(obj, *args, **kwargs)
103 result = function(obj, *args, **kwargs)
100 if result is not None:
104 if result is not None:
101 cache.set(cache_key, result)
105 cache.set(cache_key, result)
102
106
103 return result
107 return result
104
108
105 return inner_func
109 return inner_func
106 return _cached_result
110 return _cached_result
107
111
108
112
109 def get_file_hash(file) -> str:
113 def get_file_hash(file) -> str:
110 md5 = hashlib.md5()
114 md5 = hashlib.md5()
111 for chunk in file.chunks():
115 for chunk in file.chunks():
112 md5.update(chunk)
116 md5.update(chunk)
113 return md5.hexdigest()
117 return md5.hexdigest()
114
118
115
119
116 def validate_file_size(size: int):
120 def validate_file_size(size: int):
117 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
121 max_size = boards.settings.get_int('Forms', 'MaxFileSize')
118 if size > max_size:
122 if size > max_size:
119 raise forms.ValidationError(
123 raise forms.ValidationError(
120 _('File must be less than %s but is %s.')
124 _('File must be less than %s but is %s.')
121 % (filesizeformat(max_size), filesizeformat(size)))
125 % (filesizeformat(max_size), filesizeformat(size)))
122
126
123
127
124 def get_extension(filename):
128 def get_extension(filename):
125 return filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
129 return filename.split(FILE_EXTENSION_DELIMITER)[-1:][0]
126
130
127
131
128 def get_upload_filename(model_instance, old_filename):
132 def get_upload_filename(model_instance, old_filename):
129 # TODO Use something other than random number in file name
133 # TODO Use something other than random number in file name
130 extension = get_extension(old_filename)
134 extension = get_extension(old_filename)
131 new_name = '{}{}.{}'.format(
135 new_name = '{}{}.{}'.format(
132 str(int(time.mktime(time.gmtime()))),
136 str(int(time.mktime(time.gmtime()))),
133 str(int(random() * 1000)),
137 str(int(random() * 1000)),
134 extension)
138 extension)
135
139
136 return os.path.join(FILE_DIRECTORY, new_name)
140 return os.path.join(FILE_DIRECTORY, new_name)
137
141
138
142
139 def get_file_mimetype(file) -> str:
143 def get_file_mimetype(file) -> str:
140 file_type = magic.from_buffer(file.chunks().__next__(), mime=True)
144 file_type = magic.from_buffer(file.chunks().__next__(), mime=True)
141 if file_type is None:
145 if file_type is None:
142 file_type = 'application/octet-stream'
146 file_type = 'application/octet-stream'
143 elif type(file_type) == bytes:
147 elif type(file_type) == bytes:
144 file_type = file_type.decode()
148 file_type = file_type.decode()
145 return file_type
149 return file_type
150
151
152 def get_domain(url: str) -> str:
153 """
154 Gets domain from an URL with random number of domain levels.
155 """
156 levels = url.split('.')
157 top = levels[-1]
158 second = levels[-2]
159
160 has_third_level = len(levels) > 2
161 if has_third_level:
162 third = levels[-3]
163
164 if has_third_level and ('{}.{}'.format(second, top) in KNOWN_DOMAINS):
165 result = '{}.{}.{}'.format(third, second, top)
166 else:
167 result = '{}.{}'.format(second, top)
168
169 return result
170
General Comments 0
You need to be logged in to leave comments. Login now