Add early author search; Load Photo.mimetype on instantiation
This commit is contained in:
		
							parent
							
								
									5038d92b93
								
							
						
					
					
						commit
						564518f4d8
					
				
					 12 changed files with 69 additions and 46 deletions
				
			
		|  | @ -121,7 +121,7 @@ DEFAULT_CONFIGURATION = { | ||||||
| 
 | 
 | ||||||
|     'min_username_length': 2, |     'min_username_length': 2, | ||||||
|     'max_username_length': 24, |     'max_username_length': 24, | ||||||
|     'valid_username_chars': string.ascii_letters + string.digits + '~!@#$%^&*()[]{}:;,.<>/\\-_+=', |     'valid_username_chars': string.ascii_letters + string.digits + '~!@#$%^*()[]{}:;,.<>/\\-_+=', | ||||||
|     'min_password_length': 6, |     'min_password_length': 6, | ||||||
| 
 | 
 | ||||||
|     'id_length': 12, |     'id_length': 12, | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								etiquette.py
									
									
									
									
									
								
							
							
						
						
									
										22
									
								
								etiquette.py
									
									
									
									
									
								
							|  | @ -404,8 +404,8 @@ def get_search_core(): | ||||||
|         limit = 50 |         limit = 50 | ||||||
| 
 | 
 | ||||||
|     # OFFSET |     # OFFSET | ||||||
|     offset = request.args.get('offset', None) |     offset = request.args.get('offset', '') | ||||||
|     if offset: |     if offset.isdigit(): | ||||||
|         offset = int(offset) |         offset = int(offset) | ||||||
|     else: |     else: | ||||||
|         offset = None |         offset = None | ||||||
|  | @ -421,6 +421,15 @@ def get_search_core(): | ||||||
|     tag_mays = [qualname_map.get(tag, tag) for tag in tag_mays if tag != ''] |     tag_mays = [qualname_map.get(tag, tag) for tag in tag_mays if tag != ''] | ||||||
|     tag_forbids = [qualname_map.get(tag, tag) for tag in tag_forbids if tag != ''] |     tag_forbids = [qualname_map.get(tag, tag) for tag in tag_forbids if tag != ''] | ||||||
| 
 | 
 | ||||||
|  |     # AUTHOR | ||||||
|  |     authors = request.args.get('author', None) | ||||||
|  |     if authors: | ||||||
|  |         authors = authors.split(',') | ||||||
|  |         authors = [a.strip() for a in authors] | ||||||
|  |         authors = [P.get_user(username=a) for a in authors] | ||||||
|  |     else: | ||||||
|  |         authors = None | ||||||
|  | 
 | ||||||
|     # ORDERBY |     # ORDERBY | ||||||
|     orderby = request.args.get('orderby', None) |     orderby = request.args.get('orderby', None) | ||||||
|     if orderby: |     if orderby: | ||||||
|  | @ -430,11 +439,11 @@ def get_search_core(): | ||||||
|         orderby = None |         orderby = None | ||||||
| 
 | 
 | ||||||
|     # HAS_TAGS |     # HAS_TAGS | ||||||
|     has_tags = request.args.get('has_tags', '') |     has_tags = request.args.get('has_tags', None) | ||||||
|     if has_tags == '': |     if has_tags: | ||||||
|         has_tags = None |  | ||||||
|     else: |  | ||||||
|         has_tags = helpers.truthystring(has_tags) |         has_tags = helpers.truthystring(has_tags) | ||||||
|  |     else: | ||||||
|  |         has_tags = None | ||||||
| 
 | 
 | ||||||
|     # MINMAXERS |     # MINMAXERS | ||||||
|     area = request.args.get('area', None) |     area = request.args.get('area', None) | ||||||
|  | @ -454,6 +463,7 @@ def get_search_core(): | ||||||
|         'bytes': bytes, |         'bytes': bytes, | ||||||
|         'duration': duration, |         'duration': duration, | ||||||
| 
 | 
 | ||||||
|  |         'authors': authors, | ||||||
|         'created': created, |         'created': created, | ||||||
|         'extension': extension_list, |         'extension': extension_list, | ||||||
|         'extension_not': extension_not_list, |         'extension_not': extension_not_list, | ||||||
|  |  | ||||||
|  | @ -38,7 +38,7 @@ def photo(p, include_albums=True, include_tags=True): | ||||||
|         'has_thumbnail': bool(p.thumbnail), |         'has_thumbnail': bool(p.thumbnail), | ||||||
|         'created': p.created, |         'created': p.created, | ||||||
|         'filename': p.basename, |         'filename': p.basename, | ||||||
|         'mimetype': p.mimetype(), |         'mimetype': p.mimetype, | ||||||
|     } |     } | ||||||
|     if include_albums: |     if include_albums: | ||||||
|         j['albums'] = [album(a, minimal=True) for a in p.albums()] |         j['albums'] = [album(a, minimal=True) for a in p.albums()] | ||||||
|  |  | ||||||
							
								
								
									
										17
									
								
								objects.py
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								objects.py
									
									
									
									
									
								
							|  | @ -300,6 +300,8 @@ class Photo(ObjectBase): | ||||||
|         self.ratio = row_tuple['ratio'] |         self.ratio = row_tuple['ratio'] | ||||||
|         self.thumbnail = row_tuple['thumbnail'] |         self.thumbnail = row_tuple['thumbnail'] | ||||||
| 
 | 
 | ||||||
|  |         self.mimetype = helpers.get_mimetype(self.real_filepath) | ||||||
|  | 
 | ||||||
|     def __reinit__(self): |     def __reinit__(self): | ||||||
|         ''' |         ''' | ||||||
|         Reload the row from the database and do __init__ with them. |         Reload the row from the database and do __init__ with them. | ||||||
|  | @ -392,8 +394,7 @@ class Photo(ObjectBase): | ||||||
|         hopeful_filepath = self.make_thumbnail_filepath() |         hopeful_filepath = self.make_thumbnail_filepath() | ||||||
|         return_filepath = None |         return_filepath = None | ||||||
| 
 | 
 | ||||||
|         mime = self.mimetype() |         if self.mimetype == 'image': | ||||||
|         if mime == 'image': |  | ||||||
|             self.photodb.log.debug('Thumbnailing %s' % self.real_filepath) |             self.photodb.log.debug('Thumbnailing %s' % self.real_filepath) | ||||||
|             try: |             try: | ||||||
|                 image = PIL.Image.open(self.real_filepath) |                 image = PIL.Image.open(self.real_filepath) | ||||||
|  | @ -413,7 +414,7 @@ class Photo(ObjectBase): | ||||||
|                 image.save(hopeful_filepath, quality=50) |                 image.save(hopeful_filepath, quality=50) | ||||||
|                 return_filepath = hopeful_filepath |                 return_filepath = hopeful_filepath | ||||||
| 
 | 
 | ||||||
|         elif mime == 'video' and constants.ffmpeg: |         elif self.mimetype == 'video' and constants.ffmpeg: | ||||||
|             #print('video') |             #print('video') | ||||||
|             probe = constants.ffmpeg.probe(self.real_filepath) |             probe = constants.ffmpeg.probe(self.real_filepath) | ||||||
|             try: |             try: | ||||||
|  | @ -495,9 +496,6 @@ class Photo(ObjectBase): | ||||||
|         hopeful_filepath = os.path.join(folder, basename) + '.jpg' |         hopeful_filepath = os.path.join(folder, basename) + '.jpg' | ||||||
|         return hopeful_filepath |         return hopeful_filepath | ||||||
| 
 | 
 | ||||||
|     def mimetype(self): |  | ||||||
|         return helpers.get_mimetype(self.real_filepath) |  | ||||||
| 
 |  | ||||||
|     @decorators.time_me |     @decorators.time_me | ||||||
|     def reload_metadata(self, *, commit=True): |     def reload_metadata(self, *, commit=True): | ||||||
|         ''' |         ''' | ||||||
|  | @ -510,8 +508,7 @@ class Photo(ObjectBase): | ||||||
|         self.ratio = None |         self.ratio = None | ||||||
|         self.duration = None |         self.duration = None | ||||||
| 
 | 
 | ||||||
|         mime = self.mimetype() |         if self.mimetype == 'image': | ||||||
|         if mime == 'image': |  | ||||||
|             try: |             try: | ||||||
|                 image = PIL.Image.open(self.real_filepath) |                 image = PIL.Image.open(self.real_filepath) | ||||||
|             except (OSError, ValueError): |             except (OSError, ValueError): | ||||||
|  | @ -521,7 +518,7 @@ class Photo(ObjectBase): | ||||||
|                 image.close() |                 image.close() | ||||||
|                 self.photodb.log.debug('Loaded image data for {photo:r}'.format(photo=self)) |                 self.photodb.log.debug('Loaded image data for {photo:r}'.format(photo=self)) | ||||||
| 
 | 
 | ||||||
|         elif mime == 'video' and constants.ffmpeg: |         elif self.mimetype == 'video' and constants.ffmpeg: | ||||||
|             try: |             try: | ||||||
|                 probe = constants.ffmpeg.probe(self.real_filepath) |                 probe = constants.ffmpeg.probe(self.real_filepath) | ||||||
|                 if probe and probe.video: |                 if probe and probe.video: | ||||||
|  | @ -531,7 +528,7 @@ class Photo(ObjectBase): | ||||||
|             except: |             except: | ||||||
|                 traceback.print_exc() |                 traceback.print_exc() | ||||||
| 
 | 
 | ||||||
|         elif mime == 'audio': |         elif self.mimetype == 'audio': | ||||||
|             try: |             try: | ||||||
|                 probe = constants.ffmpeg.probe(self.real_filepath) |                 probe = constants.ffmpeg.probe(self.real_filepath) | ||||||
|                 if probe and probe.audio: |                 if probe and probe.audio: | ||||||
|  |  | ||||||
|  | @ -452,6 +452,8 @@ class PDBPhotoMixin: | ||||||
|         elif author is not None: |         elif author is not None: | ||||||
|             # Just to confirm |             # Just to confirm | ||||||
|             author_id = self.get_user(id=author).id |             author_id = self.get_user(id=author).id | ||||||
|  |         else: | ||||||
|  |             author_id = None | ||||||
| 
 | 
 | ||||||
|         extension = os.path.splitext(filename)[1] |         extension = os.path.splitext(filename)[1] | ||||||
|         extension = extension.replace('.', '') |         extension = extension.replace('.', '') | ||||||
|  | @ -524,6 +526,7 @@ class PDBPhotoMixin: | ||||||
|             bytes=None, |             bytes=None, | ||||||
|             duration=None, |             duration=None, | ||||||
| 
 | 
 | ||||||
|  |             authors=None, | ||||||
|             created=None, |             created=None, | ||||||
|             extension=None, |             extension=None, | ||||||
|             extension_not=None, |             extension_not=None, | ||||||
|  | @ -541,11 +544,14 @@ class PDBPhotoMixin: | ||||||
|             orderby=None |             orderby=None | ||||||
|         ): |         ): | ||||||
|         ''' |         ''' | ||||||
|         PHOTO PROPERTISE |         PHOTO PROPERTIES | ||||||
|         area, width, height, ratio, bytes, duration: |         area, width, height, ratio, bytes, duration: | ||||||
|             A hyphen_range string representing min and max. Or just a number for lower bound. |             A hyphen_range string representing min and max. Or just a number for lower bound. | ||||||
| 
 | 
 | ||||||
|         TAGS AND FILTERS |         TAGS AND FILTERS | ||||||
|  |         authors: | ||||||
|  |             A list of User object or users IDs. | ||||||
|  | 
 | ||||||
|         created: |         created: | ||||||
|             A hyphen_range string respresenting min and max. Or just a number for lower bound. |             A hyphen_range string respresenting min and max. Or just a number for lower bound. | ||||||
| 
 | 
 | ||||||
|  | @ -615,6 +621,11 @@ class PDBPhotoMixin: | ||||||
|         extension_not = helpers._normalize_extensions(extension_not) |         extension_not = helpers._normalize_extensions(extension_not) | ||||||
|         mimetype = helpers._normalize_extensions(mimetype) |         mimetype = helpers._normalize_extensions(mimetype) | ||||||
| 
 | 
 | ||||||
|  |         if authors is not None: | ||||||
|  |             if isinstance(authors, str): | ||||||
|  |                 authors = {authors, } | ||||||
|  |             authors = set(a.id if isinstance(a, objects.User) else a for a in authors) | ||||||
|  | 
 | ||||||
|         if filename is not None: |         if filename is not None: | ||||||
|             if not isinstance(filename, str): |             if not isinstance(filename, str): | ||||||
|                 filename = ' '.join(filename) |                 filename = ' '.join(filename) | ||||||
|  | @ -669,10 +680,13 @@ class PDBPhotoMixin: | ||||||
|                 #print('Failed extension_not') |                 #print('Failed extension_not') | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|             if mimetype and photo.mimetype() not in mimetype: |             if mimetype and photo.mimetype not in mimetype: | ||||||
|                 #print('Failed mimetype') |                 #print('Failed mimetype') | ||||||
|                 continue |                 continue | ||||||
| 
 | 
 | ||||||
|  |             if authors and photo.author_id not in authors: | ||||||
|  |                 continue | ||||||
|  | 
 | ||||||
|             if filename and not _helper_filenamefilter(subject=photo.basename, terms=filename): |             if filename and not _helper_filenamefilter(subject=photo.basename, terms=filename): | ||||||
|                 #print('Failed filename') |                 #print('Failed filename') | ||||||
|                 continue |                 continue | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							|  | @ -154,6 +154,10 @@ li | ||||||
|     bottom: 0; |     bottom: 0; | ||||||
|     right: 0; |     right: 0; | ||||||
| } | } | ||||||
|  | .photo_card_grid_filename | ||||||
|  | { | ||||||
|  |     word-break:break-word; | ||||||
|  | } | ||||||
| .photo_card_grid_tags | .photo_card_grid_tags | ||||||
| { | { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|  | @ -195,8 +199,3 @@ li | ||||||
| { | { | ||||||
|     background-color: #faa; |     background-color: #faa; | ||||||
| } | } | ||||||
| 
 |  | ||||||
| @font-face { |  | ||||||
|     font-family: 'Highlander'; |  | ||||||
|     src: url('/static/Highlander_Normal.ttf'); |  | ||||||
| } |  | ||||||
|  | @ -47,12 +47,12 @@ p | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|     </ul> |     </ul> | ||||||
|     {% endif %} |     {% endif %} | ||||||
|  |     {% set photos = album.photos() %} | ||||||
|     <span> |     <span> | ||||||
|         Download: |         Download: | ||||||
|         <a href="/album/{{album.id}}.zip?recursive=no">These files</a> |         {% if photos %}<a href="/album/{{album.id}}.zip?recursive=no">These files</a>{% endif %} | ||||||
|         {% if sub_albums %} | <a href="/album/{{album.id}}.zip?recursive=yes">Include children</a>{% endif %} |         {% if sub_albums %}<a href="/album/{{album.id}}.zip?recursive=yes">Include children</a>{% endif %} | ||||||
|     </span> |     </span> | ||||||
|     {% set photos = album.photos() %} |  | ||||||
|     {% if photos %} |     {% if photos %} | ||||||
|     <h3>Photos</h3> |     <h3>Photos</h3> | ||||||
|     <ul> |     <ul> | ||||||
|  |  | ||||||
|  | @ -8,7 +8,6 @@ | ||||||
|     <script src="/static/common.js"></script> |     <script src="/static/common.js"></script> | ||||||
|     {% set filename = photo.id + "." + photo.extension %} |     {% set filename = photo.id + "." + photo.extension %} | ||||||
|     {% set link = "/file/" + filename %} |     {% set link = "/file/" + filename %} | ||||||
|     {% set mimetype=photo.mimetype() %} |  | ||||||
| 
 | 
 | ||||||
| <style> | <style> | ||||||
| #content_body | #content_body | ||||||
|  | @ -21,6 +20,7 @@ | ||||||
| #left | #left | ||||||
| { | { | ||||||
|     display: flex; |     display: flex; | ||||||
|  |     padding: 8px; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|     justify-content: flex-start; |     justify-content: flex-start; | ||||||
|     background-color: rgba(0, 0, 0, 0.1); |     background-color: rgba(0, 0, 0, 0.1); | ||||||
|  | @ -117,10 +117,10 @@ | ||||||
|         {% if photo.width %} |         {% if photo.width %} | ||||||
|             <li>Dimensions: {{photo.width}}x{{photo.height}} px</li> |             <li>Dimensions: {{photo.width}}x{{photo.height}} px</li> | ||||||
|             <li>Aspect ratio: {{photo.ratio}}</li> |             <li>Aspect ratio: {{photo.ratio}}</li> | ||||||
|             <li>Size: {{photo.bytestring()}}</li> |  | ||||||
|         {% endif %} |         {% endif %} | ||||||
|  |         <li>Size: {{photo.bytestring()}}</li> | ||||||
|         {% if photo.duration %} |         {% if photo.duration %} | ||||||
|             <li>Duration: {{photo.duration_str}}</li> |             <li>Duration: {{photo.duration_string()}}</li> | ||||||
|         {% endif %} |         {% endif %} | ||||||
|             <li><a href="/file/{{photo.id}}.{{photo.extension}}?download=1">Download as {{photo.id}}.{{photo.extension}}</a></li> |             <li><a href="/file/{{photo.id}}.{{photo.extension}}?download=1">Download as {{photo.id}}.{{photo.extension}}</a></li> | ||||||
|             <li><a href="/file/{{photo.id}}.{{photo.extension}}?download=1&original_filename=1">Download as "{{photo.basename}}"</a></li> |             <li><a href="/file/{{photo.id}}.{{photo.extension}}?download=1&original_filename=1">Download as "{{photo.basename}}"</a></li> | ||||||
|  | @ -144,12 +144,12 @@ | ||||||
| <div id="right"> | <div id="right"> | ||||||
|     <!-- THE PHOTO ITSELF --> |     <!-- THE PHOTO ITSELF --> | ||||||
|     <div class="photo_viewer"> |     <div class="photo_viewer"> | ||||||
|         {% if mimetype == "image" %} |         {% if photo.mimetype == "image" %} | ||||||
|         <div id="photo_img_holder"><img id="photo_img" src="{{link}}" onclick="toggle_hoverzoom()" onload="this.style.opacity=0.99"></div> |         <div id="photo_img_holder"><img id="photo_img" src="{{link}}" onclick="toggle_hoverzoom()" onload="this.style.opacity=0.99"></div> | ||||||
|         <!-- <img src="{{link}}"> --> |         <!-- <img src="{{link}}"> --> | ||||||
|         {% elif mimetype == "video" %} |         {% elif photo.mimetype == "video" %} | ||||||
|         <video src="{{link}}" controls preload=none {%if photo.thumbnail%}poster="/thumbnail/{{photo.id}}.jpg"{%endif%}></video> |         <video src="{{link}}" controls preload=none {%if photo.thumbnail%}poster="/thumbnail/{{photo.id}}.jpg"{%endif%}></video> | ||||||
|         {% elif mimetype == "audio" %} |         {% elif photo.mimetype == "audio" %} | ||||||
|         <audio src="{{link}}" controls></audio> |         <audio src="{{link}}" controls></audio> | ||||||
|         {% else %} |         {% else %} | ||||||
|         <a href="{{link}}">View {{filename}}</a> |         <a href="{{link}}">View {{filename}}</a> | ||||||
|  |  | ||||||
|  | @ -37,7 +37,7 @@ | ||||||
|         </a> |         </a> | ||||||
|     </div> |     </div> | ||||||
|     <div class="photo_card_grid_info"> |     <div class="photo_card_grid_info"> | ||||||
|         <a target="_blank" href="/photo/{{photo.id}}" style="word-break:break-all">{{photo.basename}}</a> |         <a target="_blank" class="photo_card_grid_filename" href="/photo/{{photo.id}}">{{photo.basename}}</a> | ||||||
|         <span class="photo_card_grid_file_metadata"> |         <span class="photo_card_grid_file_metadata"> | ||||||
|         {% if photo.width %} |         {% if photo.width %} | ||||||
|             {{photo.width}}x{{photo.height}}, |             {{photo.width}}x{{photo.height}}, | ||||||
|  |  | ||||||
|  | @ -33,7 +33,7 @@ form | ||||||
| #left, #right | #left, #right | ||||||
| { | { | ||||||
|     /* Keep the prev-next buttons from scraping the floor */ |     /* Keep the prev-next buttons from scraping the floor */ | ||||||
|     padding-bottom: 30px; |     /*padding-bottom: 30px;*/ | ||||||
| } | } | ||||||
| #left | #left | ||||||
| { | { | ||||||
|  | @ -44,26 +44,27 @@ form | ||||||
| #right | #right | ||||||
| { | { | ||||||
|     flex: 1; |     flex: 1; | ||||||
|  |     padding: 8px; | ||||||
|     width: auto; |     width: auto; | ||||||
| } | } | ||||||
| .prev_next_holder | .prev_next_holder | ||||||
| { | { | ||||||
|     /* Makes div match size of content */ |     display: flex; | ||||||
|     overflow: auto; |     flex-direction: row; | ||||||
| } | } | ||||||
| .prev_page | .prev_page | ||||||
| { | { | ||||||
|     float: left; |     margin-right: 10px; | ||||||
| } | } | ||||||
| .next_page | .next_page | ||||||
| { | { | ||||||
|     float: right; |     margin-left: 10px; | ||||||
| } | } | ||||||
| .prev_page, .next_page | .prev_page, .next_page | ||||||
| { | { | ||||||
|     display: inline-block; |     flex: 1; | ||||||
|     width: 48%; |     display: flex; | ||||||
|     height: auto; |     justify-content: center; | ||||||
|     background-color: #ffffd4; |     background-color: #ffffd4; | ||||||
|     font-size: 20; |     font-size: 20; | ||||||
|     border: 1px solid black; |     border: 1px solid black; | ||||||
|  | @ -79,7 +80,6 @@ form | ||||||
| {% macro prev_next_buttons() %} | {% macro prev_next_buttons() %} | ||||||
| {% if prev_page_url or next_page_url %} | {% if prev_page_url or next_page_url %} | ||||||
| <div class="prev_next_holder"> | <div class="prev_next_holder"> | ||||||
|     <center> |  | ||||||
|     {% if prev_page_url %} |     {% if prev_page_url %} | ||||||
|     <a class="prev_page" href="{{prev_page_url}}">Previous</a> |     <a class="prev_page" href="{{prev_page_url}}">Previous</a> | ||||||
|     {% else %} |     {% else %} | ||||||
|  | @ -90,7 +90,6 @@ form | ||||||
|     {% else %} |     {% else %} | ||||||
|     <a class="next_page"><br></a> |     <a class="next_page"><br></a> | ||||||
|     {% endif %} |     {% endif %} | ||||||
|     </center> |  | ||||||
| </div> | </div> | ||||||
| {% endif %} | {% endif %} | ||||||
| {% endmacro %} | {% endmacro %} | ||||||
|  | @ -238,11 +237,14 @@ form | ||||||
|         {% endif %} |         {% endif %} | ||||||
|     </div> |     </div> | ||||||
|     <div id="right"> |     <div id="right"> | ||||||
|         <p>You got {{photos|length}} items</p><br> |         <p>You got {{photos|length}} items</p> | ||||||
|         {{prev_next_buttons()}} |         {{prev_next_buttons()}} | ||||||
|  |         <div id="search_results_holder"> | ||||||
|         {% for photo in photos %} |         {% for photo in photos %} | ||||||
|         {{photo_card.create_photo_card(photo, view=search_kwargs["view"])}} |         {{photo_card.create_photo_card(photo, view=search_kwargs["view"])}} | ||||||
|         {% endfor %} |         {% endfor %} | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|         {{prev_next_buttons()}} |         {{prev_next_buttons()}} | ||||||
|     </div> |     </div> | ||||||
|     </div> |     </div> | ||||||
|  |  | ||||||
|  | @ -29,6 +29,7 @@ body | ||||||
|     right: 8px; |     right: 8px; | ||||||
|     bottom: 8px; |     bottom: 8px; | ||||||
|     top: 30px; |     top: 30px; | ||||||
|  |     padding: 8px; | ||||||
|     display: flex; |     display: flex; | ||||||
|     flex-direction: column; |     flex-direction: column; | ||||||
|     justify-content: center; |     justify-content: center; | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue