mirror of
https://github.com/Xevion/simple-viewer.git
synced 2025-12-06 01:16:24 -06:00
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
|||||||
# Repository specific
|
# Repository specific
|
||||||
.idea/**
|
.idea/**
|
||||||
|
viewer/static/thumbnails/**
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
|
|||||||
@@ -1,42 +1,28 @@
|
|||||||
import os
|
|
||||||
from typing import List, Tuple
|
|
||||||
|
|
||||||
|
|
||||||
def extra_listdir(path: str) -> List[Tuple[str, str]]:
|
|
||||||
"""
|
"""
|
||||||
Helper function used for identifying file media type for every file in a given directory, extending os.listdir
|
helpers.py
|
||||||
|
|
||||||
:param path: The path to the directory.
|
Contains helper functions used as refactored shortcuts or in order to separate code for readability.
|
||||||
:return: A list of tuples, each containing two strings, the file or directory name, and the media type.
|
|
||||||
"""
|
"""
|
||||||
files = []
|
import cv2
|
||||||
for file in os.listdir(path):
|
from PIL import Image
|
||||||
mediatype = get_all_mediatype(file, path)
|
|
||||||
if mediatype == 'folder':
|
|
||||||
files.append((file, mediatype, os.path.join(path, file)))
|
|
||||||
else:
|
|
||||||
files.append((file, mediatype))
|
|
||||||
return files
|
|
||||||
|
|
||||||
|
|
||||||
def get_all_mediatype(head: str, tail: str) -> str:
|
def generate_thumbnail(path: str, output_path: str) -> None:
|
||||||
"""
|
"""
|
||||||
A extra media type function supporting directories on top of files.
|
Helper function which completes the process of generating thumbnails for both pictures and videos.
|
||||||
|
|
||||||
:param head: The head of the path, usually the directory name or filename at the very end.
|
:param path: The absolute path to the file.
|
||||||
:param tail: The rest of the path, everything that comes before the head.
|
:param output_path: The absolute path to the intended output thumbnail file.
|
||||||
:return: A media type in string form.
|
|
||||||
"""
|
"""
|
||||||
if os.path.isfile(os.path.join(tail, head)):
|
vidcap = cv2.VideoCapture(path)
|
||||||
return get_file_mediatype(head)
|
success, image = vidcap.read()
|
||||||
return "folder"
|
if success:
|
||||||
|
img = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
|
||||||
|
im_pil = Image.fromarray(img)
|
||||||
|
|
||||||
|
# Resize, crop, thumbnail
|
||||||
|
im_pil.thumbnail((300, 300))
|
||||||
|
# im_pil.crop((0, 0, 200, 66))
|
||||||
|
# im_pil.resize((200, 66))
|
||||||
|
|
||||||
def get_file_mediatype(mimetype: str) -> str:
|
im_pil.save(output_path)
|
||||||
"""Simple media type categorization based on the given mimetype"""
|
|
||||||
if mimetype is not None:
|
|
||||||
if mimetype.startswith('image'):
|
|
||||||
return 'image'
|
|
||||||
elif mimetype.startswith('video'):
|
|
||||||
return 'video'
|
|
||||||
return 'file'
|
|
||||||
|
|||||||
18
viewer/migrations/0003_auto_20201031_2007.py
Normal file
18
viewer/migrations/0003_auto_20201031_2007.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-01 01:07
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('viewer', '0002_auto_20201031_0526'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='serveddirectory',
|
||||||
|
name='regex_pattern',
|
||||||
|
field=models.CharField(default='', max_length=100, verbose_name='RegEx Matching Pattern'),
|
||||||
|
),
|
||||||
|
]
|
||||||
24
viewer/migrations/0004_file.py
Normal file
24
viewer/migrations/0004_file.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-01 01:36
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('viewer', '0003_auto_20201031_2007'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='File',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('path', models.CharField(max_length=300, verbose_name='Full Filepath')),
|
||||||
|
('filename', models.CharField(max_length=160, verbose_name='Filename')),
|
||||||
|
('mediatype', models.CharField(max_length=30, verbose_name='Mediatype')),
|
||||||
|
('directory', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='files', to='viewer.serveddirectory')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-01 01:46
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
import jsonfield.fields
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('viewer', '0004_file'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='serveddirectory',
|
||||||
|
name='known_subdirectories',
|
||||||
|
field=jsonfield.fields.JSONField(default=[], verbose_name='Tracked Subdirectories JSON'),
|
||||||
|
),
|
||||||
|
]
|
||||||
18
viewer/migrations/0006_file_thumbnail.py
Normal file
18
viewer/migrations/0006_file_thumbnail.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 3.1.2 on 2020-11-01 03:11
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('viewer', '0005_serveddirectory_known_subdirectories'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='file',
|
||||||
|
name='thumbnail',
|
||||||
|
field=models.CharField(default=None, max_length=160, null=True, verbose_name='Thumbnail Filename'),
|
||||||
|
),
|
||||||
|
]
|
||||||
115
viewer/models.py
115
viewer/models.py
@@ -1,6 +1,16 @@
|
|||||||
|
import mimetypes
|
||||||
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import jsonfield
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
from django.urls import reverse
|
||||||
|
from easy_thumbnails.alias import aliases
|
||||||
|
|
||||||
|
from viewer import helpers
|
||||||
|
|
||||||
|
if not aliases.get('small'):
|
||||||
|
aliases.set('small', {'size': (150, 80), 'crop': True})
|
||||||
|
|
||||||
|
|
||||||
class ServedDirectory(models.Model):
|
class ServedDirectory(models.Model):
|
||||||
@@ -19,6 +29,111 @@ class ServedDirectory(models.Model):
|
|||||||
regex_pattern = models.CharField('RegEx Matching Pattern', max_length=100, default='')
|
regex_pattern = models.CharField('RegEx Matching Pattern', max_length=100, default='')
|
||||||
regex = models.BooleanField('Directory RegEx Option', default=False)
|
regex = models.BooleanField('Directory RegEx Option', default=False)
|
||||||
match_filename = models.BooleanField('RegEx Matches Against Filename', default=True)
|
match_filename = models.BooleanField('RegEx Matches Against Filename', default=True)
|
||||||
|
known_subdirectories = jsonfield.JSONField('Tracked Subdirectories JSON', default=[])
|
||||||
|
|
||||||
|
def refresh(self):
|
||||||
|
"""Refresh the directory listing to see if any new files have appeared and add them to the list."""
|
||||||
|
# TODO: Implement separate recursive file matching implementation
|
||||||
|
# TODO: Implement RegEx filtering step
|
||||||
|
directories = []
|
||||||
|
files = os.listdir(self.path)
|
||||||
|
for i, file in enumerate(files):
|
||||||
|
print(f'{i} / {len(files)}')
|
||||||
|
file_path = os.path.join(self.path, file)
|
||||||
|
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
# Check if the file has been entered before
|
||||||
|
entry: File
|
||||||
|
entry = self.files.filter(filename__exact=file).first()
|
||||||
|
if entry is None:
|
||||||
|
# create the file entry
|
||||||
|
entry = File.create(full_path=file_path, parent=self)
|
||||||
|
entry.save()
|
||||||
|
else:
|
||||||
|
if entry.thumbnail is None:
|
||||||
|
entry.generate_thumbnail()
|
||||||
|
else:
|
||||||
|
# directory found, remember it
|
||||||
|
directories.append(file_path)
|
||||||
|
|
||||||
|
# Dump subdirectories found
|
||||||
|
self.known_subdirectories = directories
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.path
|
return self.path
|
||||||
|
|
||||||
|
|
||||||
|
class File(models.Model):
|
||||||
|
path = models.CharField('Full Filepath', max_length=300)
|
||||||
|
filename = models.CharField('Filename', max_length=160)
|
||||||
|
mediatype = models.CharField('Mediatype', max_length=30)
|
||||||
|
directory = models.ForeignKey(ServedDirectory, on_delete=models.CASCADE, related_name='files')
|
||||||
|
thumbnail = models.CharField('Thumbnail Filename', max_length=160, null=True, default=None)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, full_path: str, parent: ServedDirectory) -> 'File':
|
||||||
|
"""Simple shortcut for creating a File database entry with just the path."""
|
||||||
|
return File(
|
||||||
|
path=full_path,
|
||||||
|
filename=os.path.basename(full_path),
|
||||||
|
mediatype=File.get_mediatype(full_path),
|
||||||
|
directory=parent
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_url(self, directory: ServedDirectory) -> str:
|
||||||
|
"""Retrieve the direct URL for a given file."""
|
||||||
|
return reverse('file', args=(directory.id, self.filename))
|
||||||
|
|
||||||
|
def delete_thumbnail(self) -> None:
|
||||||
|
if self.thumbnail:
|
||||||
|
try:
|
||||||
|
os.remove(os.path.join(self.thumbs_dir, self.thumbnail))
|
||||||
|
self.thumbnail = None
|
||||||
|
self.save()
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
@property
|
||||||
|
def thumbnail_url(self):
|
||||||
|
if self.thumbnail:
|
||||||
|
return f'/thumbnails/{self.thumbnail}'
|
||||||
|
return ''
|
||||||
|
|
||||||
|
@property
|
||||||
|
def thumbs_dir(self):
|
||||||
|
return os.path.join(os.path.dirname(os.path.abspath(__file__)), 'static', 'thumbnails')
|
||||||
|
|
||||||
|
def generate_thumbnail(self) -> None:
|
||||||
|
# TODO: Add django-background-task scheduling
|
||||||
|
|
||||||
|
self.delete_thumbnail()
|
||||||
|
|
||||||
|
thumb_file = f'{uuid.uuid4()}.jpeg'
|
||||||
|
self.thumbnail = thumb_file
|
||||||
|
|
||||||
|
# Generate thumbnail
|
||||||
|
try:
|
||||||
|
helpers.generate_thumbnail(self.path, os.path.join(self.thumbs_dir, self.thumbnail))
|
||||||
|
except Exception:
|
||||||
|
print(f'Could not thumbnail: {self.filename}')
|
||||||
|
self.delete_thumbnail()
|
||||||
|
|
||||||
|
self.save()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_mediatype(path) -> str:
|
||||||
|
"""Simple media type categorization based on the given path."""
|
||||||
|
if os.path.exists(path):
|
||||||
|
if os.path.isdir(path):
|
||||||
|
return 'folder'
|
||||||
|
mimetype = mimetypes.guess_type(path)[0]
|
||||||
|
if mimetype is not None:
|
||||||
|
if mimetype.startswith('image'):
|
||||||
|
return 'image'
|
||||||
|
elif mimetype.startswith('video'):
|
||||||
|
return 'video'
|
||||||
|
return 'file'
|
||||||
|
return 'unknown'
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.filename
|
||||||
|
|||||||
@@ -1,5 +1,59 @@
|
|||||||
{% extends 'base.html' %}
|
{% extends 'base.html' %}
|
||||||
|
{% block head %}
|
||||||
|
{{ block.super }}
|
||||||
|
<style>
|
||||||
|
.media {
|
||||||
|
width: 100%;
|
||||||
|
{#height: 3em;#}
|
||||||
|
}
|
||||||
|
|
||||||
|
.media .media-filename {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-color: #3273dc;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock head %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
<p class="card-header-title">
|
||||||
|
{{ directory.path }}
|
||||||
|
<span class="pl-1" style="font-weight: 400; font-style: italic; font-size: 70%;">
|
||||||
|
{{ files|length }} files
|
||||||
|
</span>
|
||||||
|
<span class="icon align-self">
|
||||||
|
<a href="{% url 'refresh' directory.id %}">
|
||||||
|
<i class="fas fa-sync"></i>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="content">
|
||||||
|
{% for directory in directories %}
|
||||||
|
<div>
|
||||||
|
<span class="icon">
|
||||||
|
<i class="fas fa-folder"></i>
|
||||||
|
</span>
|
||||||
|
{{ directory }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endfor %}
|
||||||
|
{% for file in files %}
|
||||||
|
<div class="media">
|
||||||
|
{% load static %}
|
||||||
|
<img class="px-2" loading="lazy" src="{% static "/thumbnails/"|add:file.thumbnail %}">
|
||||||
|
<span class="media-filename">
|
||||||
|
<a href="{% url 'file' directory.id file.filename %}">
|
||||||
|
{{ file.filename }}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<div class="panel-heading">
|
<div class="panel-heading">
|
||||||
{{ directory.path }}
|
{{ directory.path }}
|
||||||
@@ -15,16 +69,15 @@
|
|||||||
{% for file in files %}
|
{% for file in files %}
|
||||||
<div class="panel-block">
|
<div class="panel-block">
|
||||||
<span class="panel-icon pr-4">
|
<span class="panel-icon pr-4">
|
||||||
<i class="fas fa-{{ file.1 }} fa-lg" aria-hidden="true"></i>
|
{# <i class="fas fa-{{ file }} fa-lg" aria-hidden="true"></i>#}
|
||||||
</span>
|
</span>
|
||||||
{% if file.1 == 'folder' %}
|
{% if file.1 == 'folder' %}
|
||||||
<a href="{% url 'add' %}?path={{ file.2 }}">
|
<a href="{% url 'add' %}?path={{ file.fullpath }}">
|
||||||
{{ file.0 }}
|
{{ file.filename }}
|
||||||
</a>
|
</a>
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'file' directory.id file.0 %}">
|
<a href="{% url 'file' directory.id file.filename %}">
|
||||||
{{ file.0 }}
|
{{ file.filename }}
|
||||||
{{ file.0 }}
|
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
@@ -33,6 +86,5 @@
|
|||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"
|
||||||
integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg=="
|
integrity="sha512-bLT0Qm9VnAYZDflyKcBaQ2gg0hSYNQrJ8RilYldYQ1FxQYoCLtUjuuRuZo+fjqhx/qtq/1itJ0C2ejDxltZVFg=="
|
||||||
crossorigin="anonymous"></script>
|
crossorigin="anonymous"></script>
|
||||||
{% load static %}
|
|
||||||
<script src="{% static "hover.js" %}"></script>
|
<script src="{% static "hover.js" %}"></script>
|
||||||
{% endblock content %}
|
{% endblock content %}
|
||||||
|
|||||||
@@ -7,5 +7,8 @@ urlpatterns = [
|
|||||||
path('add/', views.add, name='add'),
|
path('add/', views.add, name='add'),
|
||||||
path('add/submit', views.submit_new, name='add_submit'),
|
path('add/submit', views.submit_new, name='add_submit'),
|
||||||
path('<uuid:directory_id>/', views.browse, name='browse'),
|
path('<uuid:directory_id>/', views.browse, name='browse'),
|
||||||
path('<uuid:directory_id>/<str:file>/', views.file, name='file')
|
path('<uuid:directory_id>/refresh', views.refresh, name='refresh'),
|
||||||
|
path('<uuid:directory_id>/<str:file>/', views.file, name='file'),
|
||||||
|
path('<uuid:directory_id>/<str:file>/generate', views.generate_thumb, name='generate_thumb'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ from django.http import FileResponse, HttpResponseRedirect
|
|||||||
from django.shortcuts import render, get_object_or_404
|
from django.shortcuts import render, get_object_or_404
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
from viewer.helpers import extra_listdir
|
from viewer.models import ServedDirectory, File
|
||||||
from viewer.models import ServedDirectory
|
|
||||||
|
|
||||||
|
|
||||||
def index(request):
|
def index(request):
|
||||||
@@ -17,29 +16,29 @@ def index(request):
|
|||||||
|
|
||||||
|
|
||||||
def browse(request, directory_id):
|
def browse(request, directory_id):
|
||||||
dir = get_object_or_404(ServedDirectory, id=directory_id)
|
directory = get_object_or_404(ServedDirectory, id=directory_id)
|
||||||
|
|
||||||
if os.path.isdir(dir.path):
|
if os.path.isdir(directory.path):
|
||||||
context = {
|
context = {
|
||||||
'title': f'Browse - {os.path.dirname(dir.path)}',
|
'title': f'Browse - {os.path.dirname(directory.path)}',
|
||||||
'files': extra_listdir(dir.path),
|
'files': directory.files.all(),
|
||||||
'directory': dir
|
'directory': directory
|
||||||
}
|
}
|
||||||
return render(request, 'browse.html', context)
|
return render(request, 'browse.html', context)
|
||||||
else:
|
else:
|
||||||
context = {
|
context = {
|
||||||
'title': 'Invalid Directory',
|
'title': 'Invalid Directory',
|
||||||
'message': 'The path this server directory points to {}.'.format(
|
'message': 'The path this server directory points to {}.'.format(
|
||||||
'exists, but is not a directory' if os.path.exists(dir.path) else 'does not exist'
|
'exists, but is not a directory' if os.path.exists(directory.path) else 'does not exist'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return render(request, 'message.html', context, status=500)
|
return render(request, 'message.html', context, status=500)
|
||||||
|
|
||||||
|
|
||||||
def file(request, directory_id, file):
|
def file(request, directory_id, file):
|
||||||
dir = get_object_or_404(ServedDirectory, id=directory_id)
|
directory = get_object_or_404(ServedDirectory, id=directory_id)
|
||||||
if os.path.isdir(dir.path):
|
if os.path.isdir(directory.path):
|
||||||
path = os.path.join(dir.path, file)
|
path = os.path.join(directory.path, file)
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
return FileResponse(open(path, 'rb'))
|
return FileResponse(open(path, 'rb'))
|
||||||
else:
|
else:
|
||||||
@@ -51,7 +50,7 @@ def file(request, directory_id, file):
|
|||||||
context = {
|
context = {
|
||||||
'title': 'Invalid Directory',
|
'title': 'Invalid Directory',
|
||||||
'message': 'The path this server directory points to {}.'.format(
|
'message': 'The path this server directory points to {}.'.format(
|
||||||
'exists, but is not a directory' if os.path.exists(dir.path) else 'does not exist'
|
'exists, but is not a directory' if os.path.exists(directory.path) else 'does not exist'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return render(request, 'message.html', context, status=500)
|
return render(request, 'message.html', context, status=500)
|
||||||
@@ -64,6 +63,13 @@ def add(request):
|
|||||||
return render(request, 'add.html', context)
|
return render(request, 'add.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
def refresh(request, directory_id):
|
||||||
|
"""A simple API view for refreshing a directory. May schedule new thumbnail generation."""
|
||||||
|
directory = get_object_or_404(ServedDirectory, id=directory_id)
|
||||||
|
directory.refresh()
|
||||||
|
return HttpResponseRedirect(reverse('browse', args=(directory.id,)))
|
||||||
|
|
||||||
|
|
||||||
def submit_new(request):
|
def submit_new(request):
|
||||||
try:
|
try:
|
||||||
s = ServedDirectory(
|
s = ServedDirectory(
|
||||||
@@ -87,3 +93,11 @@ def submit_new(request):
|
|||||||
'message': 'The directory you specified was not a valid directory, either it doesn\'t '
|
'message': 'The directory you specified was not a valid directory, either it doesn\'t '
|
||||||
'exist or it isn\'t a directory.'})
|
'exist or it isn\'t a directory.'})
|
||||||
return HttpResponseRedirect(reverse('browse', args=(s.id,)))
|
return HttpResponseRedirect(reverse('browse', args=(s.id,)))
|
||||||
|
|
||||||
|
|
||||||
|
def generate_thumb(request, directory_id, file: str):
|
||||||
|
"""View for regenerating a thumbnail for a specific file."""
|
||||||
|
directory = get_object_or_404(ServedDirectory, id=directory_id)
|
||||||
|
file = directory.files.filter(filename=file).first()
|
||||||
|
file.generate_thumbnail()
|
||||||
|
return HttpResponseRedirect(reverse('browse', args=(directory.id,)))
|
||||||
|
|||||||
Reference in New Issue
Block a user