added folders

This commit is contained in:
Alexander Karpov 2023-04-24 01:07:11 +03:00
parent 24ef17bde3
commit 83ea171886
14 changed files with 628 additions and 85 deletions

View File

@ -0,0 +1,223 @@
# Generated by Django 4.2 on 2023-04-22 09:07
import akarpov.files.services.files
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
class Migration(migrations.Migration):
replaces = [
("files", "0001_initial"),
("files", "0002_alter_basefile_options_alter_folder_options_and_more"),
("files", "0003_basefile_short_link_folder_short_link"),
("files", "0004_alter_basefile_short_link_alter_folder_short_link"),
("files", "0005_alter_basefile_description_alter_basefile_folder_and_more"),
("files", "0006_alter_basefile_slug"),
("files", "0007_file_alter_folder_parent_delete_basefile_file_folder_and_more"),
("files", "0008_file_completed_on_file_created_on_file_filename_and_more"),
("files", "0009_remove_file_completed_on_remove_file_created_on_and_more"),
("files", "0010_fileintrash"),
("files", "0011_file_file_type_alter_file_description_and_more"),
("files", "0012_alter_file_options_alter_file_file"),
("files", "0013_alter_file_options"),
("files", "0014_alter_fileintrash_file"),
("files", "0015_fileintrash_name"),
("files", "0016_alter_file_file_type_alter_file_name"),
]
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("shortener", "0001_initial"),
]
operations = [
migrations.CreateModel(
name="Folder",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
("name", models.CharField(max_length=100)),
("slug", models.SlugField(blank=True, max_length=20)),
(
"parent",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="files.folder",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="files_folders",
to=settings.AUTH_USER_MODEL,
),
),
(
"short_link",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="shortener.link",
),
),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="FileInTrash",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
(
"file",
models.FileField(
upload_to=akarpov.files.services.files.trash_file_upload
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="trash_files",
to=settings.AUTH_USER_MODEL,
),
),
("name", models.CharField(blank=True, max_length=200)),
],
options={
"abstract": False,
},
),
migrations.CreateModel(
name="File",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("slug", models.SlugField(blank=True, max_length=20, unique=True)),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="modified",
),
),
("name", models.CharField(blank=True, max_length=255, null=True)),
("description", models.TextField(blank=True, null=True)),
("private", models.BooleanField(default=True)),
("preview", models.FileField(blank=True, upload_to="file/previews/")),
(
"file",
models.FileField(
upload_to=akarpov.files.services.files.user_unique_file_upload
),
),
(
"folder",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="files",
to="files.folder",
),
),
(
"short_link",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to="shortener.link",
),
),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
related_name="files",
to=settings.AUTH_USER_MODEL,
),
),
("file_type", models.CharField(blank=True, max_length=255, null=True)),
],
options={
"abstract": False,
"ordering": ["-modified"],
},
),
]

View File

@ -0,0 +1,106 @@
# Generated by Django 4.2 on 2023-04-22 09:13
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
("contenttypes", "0002_remove_content_type_name"),
("files", "0001_squashed_0016_alter_file_file_type_alter_file_name"),
]
operations = [
migrations.AlterModelOptions(
name="folder",
options={"base_manager_name": "objects"},
),
migrations.RenameField(
model_name="file",
old_name="file",
new_name="file_obj",
),
migrations.RemoveField(
model_name="file",
name="folder",
),
migrations.RemoveField(
model_name="file",
name="id",
),
migrations.RemoveField(
model_name="folder",
name="id",
),
migrations.RemoveField(
model_name="folder",
name="parent",
),
migrations.CreateModel(
name="BaseFileItem",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
(
"parent",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="files.basefileitem",
),
),
(
"polymorphic_ctype",
models.ForeignKey(
editable=False,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="polymorphic_%(app_label)s.%(class)s_set+",
to="contenttypes.contenttype",
),
),
],
options={
"abstract": False,
"base_manager_name": "objects",
},
),
migrations.AddField(
model_name="file",
name="basefileitem_ptr",
field=models.OneToOneField(
auto_created=True,
default=1,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="files.basefileitem",
),
preserve_default=False,
),
migrations.AddField(
model_name="folder",
name="basefileitem_ptr",
field=models.OneToOneField(
auto_created=True,
default=2,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="files.basefileitem",
),
preserve_default=False,
),
]

View File

@ -0,0 +1,49 @@
# Generated by Django 4.2 on 2023-04-22 09:21
from django.db import migrations
import django.utils.timezone
import model_utils.fields
class Migration(migrations.Migration):
dependencies = [
("files", "0017_alter_folder_options_rename_file_file_file_obj_and_more"),
]
operations = [
migrations.RemoveField(
model_name="file",
name="created",
),
migrations.RemoveField(
model_name="file",
name="modified",
),
migrations.RemoveField(
model_name="folder",
name="created",
),
migrations.RemoveField(
model_name="folder",
name="modified",
),
migrations.AddField(
model_name="basefileitem",
name="created",
field=model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="created",
),
),
migrations.AddField(
model_name="basefileitem",
name="modified",
field=model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now,
editable=False,
verbose_name="updated",
),
),
]

View File

@ -0,0 +1,54 @@
# Generated by Django 4.2 on 2023-04-22 09:24
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("files", "0018_remove_file_created_remove_file_modified_and_more"),
]
operations = [
migrations.AlterModelOptions(
name="basefileitem",
options={"ordering": ["-modified"]},
),
migrations.AlterModelOptions(
name="file",
options={"base_manager_name": "objects"},
),
migrations.RemoveField(
model_name="file",
name="user",
),
migrations.RemoveField(
model_name="folder",
name="user",
),
migrations.AddField(
model_name="basefileitem",
name="user",
field=models.ForeignKey(
default=1,
on_delete=django.db.models.deletion.CASCADE,
related_name="files",
to=settings.AUTH_USER_MODEL,
),
preserve_default=False,
),
migrations.AlterField(
model_name="basefileitem",
name="parent",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.CASCADE,
related_name="children",
to="files.folder",
),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.2 on 2023-04-22 09:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("files", "0019_alter_basefileitem_options_alter_file_options_and_more"),
]
operations = [
migrations.AddField(
model_name="folder",
name="size",
field=models.IntegerField(default=0),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 4.2 on 2023-04-22 09:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("files", "0020_folder_size"),
]
operations = [
migrations.AddField(
model_name="folder",
name="amount",
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name="folder",
name="private",
field=models.BooleanField(default=True),
),
]

View File

@ -7,28 +7,60 @@
CharField,
FileField,
ForeignKey,
IntegerField,
SlugField,
TextField,
)
from django.urls import reverse
from model_utils.fields import AutoCreatedField, AutoLastModifiedField
from model_utils.models import TimeStampedModel
from polymorphic.models import PolymorphicModel
from akarpov.files.services.files import trash_file_upload, user_unique_file_upload
from akarpov.tools.shortener.models import ShortLink
class File(TimeStampedModel, ShortLink):
class BaseFileItem(PolymorphicModel):
parent = ForeignKey(
to="files.Folder",
null=True,
blank=True,
on_delete=CASCADE,
related_name="children",
)
user = ForeignKey("users.User", related_name="files", on_delete=CASCADE)
created = AutoCreatedField("created")
modified = AutoLastModifiedField("updated")
class Meta:
ordering = ["-modified"]
@property
def is_file(self):
return type(self) is File
def get_folder_chain(self):
folders = [self]
obj = self
while obj.parent:
folders.append(obj.parent)
obj = obj.parent
return folders
def save(self, *args, **kwargs):
update_fields = kwargs.get("update_fields", None)
if update_fields:
kwargs["update_fields"] = set(update_fields).union({"modified"})
super().save(*args, **kwargs)
class File(BaseFileItem, TimeStampedModel, ShortLink):
"""model to store user's files"""
private = BooleanField(default=True)
user = ForeignKey("users.User", related_name="files", on_delete=CASCADE)
folder = ForeignKey(
"files.Folder", related_name="files", blank=True, null=True, on_delete=CASCADE
)
preview = FileField(blank=True, upload_to="file/previews/")
file = FileField(blank=False, upload_to=user_unique_file_upload)
file_obj = FileField(blank=False, upload_to=user_unique_file_upload)
# meta
name = CharField(max_length=255, null=True, blank=True)
@ -39,6 +71,10 @@ class File(TimeStampedModel, ShortLink):
def file_name(self):
return self.file.path.split("/")[-1]
@property
def file(self):
return self.file_obj
@property
def file_image_url(self):
if self.preview:
@ -61,27 +97,24 @@ def get_absolute_url(self):
def __str__(self):
return f"file: {self.name}"
class Meta:
ordering = ["-modified"]
class Folder(BaseFileItem, ShortLink):
name = CharField(max_length=100)
slug = SlugField(max_length=20, blank=True)
private = BooleanField(default=True)
# meta
size = IntegerField(default=0)
amount = IntegerField(default=0)
def get_absolute_url(self):
return reverse("files:folder", kwargs={"slug": self.slug})
def __str__(self):
return f"folder: {self.name}"
class FileInTrash(TimeStampedModel):
name = CharField(max_length=200, blank=True)
user = ForeignKey("users.User", related_name="trash_files", on_delete=CASCADE)
file = FileField(blank=False, upload_to=trash_file_upload)
class Folder(TimeStampedModel, ShortLink):
name = CharField(max_length=100)
slug = SlugField(max_length=20, blank=True)
user = ForeignKey("users.User", related_name="files_folders", on_delete=CASCADE)
parent = ForeignKey(
"self", null=True, blank=True, related_name="children", on_delete=CASCADE
)
def get_absolute_url(self):
return reverse("files:folder", kwargs={"slug": self.slug})
def __str__(self):
return f"file: {self.name}"

View File

@ -65,18 +65,18 @@ def view(file: File) -> (str, str):
def meta(file: File):
descr = ""
description = ""
i = 0
with file.file.open("r") as f:
lines = f.readlines()
for line in lines:
if i == 0:
descr += line + "\n"
description += line + "\n"
else:
descr += line + " "
description += line + " "
i += 1
if i > 20:
descr += "..."
description += "..."
break
url = file.get_absolute_url()
section = ""
@ -88,7 +88,7 @@ def meta(file: File):
<meta property="og:title" content="{file.name}">
<meta property="og:url" content="{url}">
<meta property="og:image" content="">
<meta property="og:description" content="{html.escape(descr)}">
<meta property="og:description" content="{html.escape(description)}">
<meta property="article:author" content="{file.user.username}">
<meta property="article:section" content="{section}">
<meta property="article:published_time" content="{file.created}">

View File

@ -47,36 +47,3 @@ def view(file: File) -> (str, str):
"""
return static, content
def meta(file: File):
descr = ""
i = 0
with file.file.open("r") as f:
lines = f.readlines()
for line in lines:
if i == 0:
descr += line + "\n"
else:
descr += line + " "
i += 1
if i > 20:
descr += "..."
break
url = file.get_absolute_url()
section = ""
if file.file_type:
section = file.file_type.split("/")[0]
meat_f = f"""
<meta property="og:type" content="article">
<meta property="og:title" content="{file.name}">
<meta property="og:url" content="{url}">
<meta property="og:image" content="">
<meta property="og:description" content="{html.escape(descr)}">
<meta property="article:author" content="{file.user.username}">
<meta property="article:section" content="{section}">
<meta property="article:published_time" content="{file.created}">
<meta property="article:modified_time" content="{file.modified}">
"""
return meat_f

View File

@ -1,7 +1,6 @@
from django.urls import path
from akarpov.files.views import (
ChunkedUploadDemo,
MyChunkedUploadCompleteView,
MyChunkedUploadView,
TopFolderView,
@ -14,12 +13,16 @@
app_name = "files"
urlpatterns = [
path("", TopFolderView.as_view(), name="main"),
path("upload", ChunkedUploadDemo.as_view(), name="chunked_upload"),
path(
"api/chunked_upload_complete/",
MyChunkedUploadCompleteView.as_view(),
name="api_chunked_upload_complete",
),
path(
"api/chunked_upload_complete/<str:slug>",
MyChunkedUploadCompleteView.as_view(),
name="api_chunked_upload_complete_folder",
),
path(
"api/chunked_upload/", MyChunkedUploadView.as_view(), name="api_chunked_upload"
),

View File

@ -6,6 +6,7 @@
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.timezone import now
from django.views.generic import DetailView, ListView, RedirectView, UpdateView
from django.views.generic.base import TemplateView
@ -16,7 +17,7 @@
ChunkedUploadView,
)
from akarpov.files.forms import FileForm
from akarpov.files.models import File, Folder
from akarpov.files.models import BaseFileItem, File, Folder
from akarpov.files.previews import extensions, meta, meta_extensions, previews
from akarpov.files.services.preview import get_base_meta
@ -26,17 +27,43 @@
class TopFolderView(LoginRequiredMixin, ListView):
template_name = "files/list.html"
paginate_by = 19
model = File
def get_queryset(self):
return File.objects.filter(user=self.request.user, folder__isnull=True)
model = BaseFileItem
def get_context_data(self, **kwargs):
contex = super().get_context_data(**kwargs)
contex["folders"] = Folder.objects.filter(
user=self.request.user, parent__isnull=True
)
return contex
context = super().get_context_data(**kwargs)
context["folder_slug"] = None
return context
def get_queryset(self):
return BaseFileItem.objects.filter(user=self.request.user, parent__isnull=True)
class FileFolderView(ListView):
template_name = "files/folder.html"
model = BaseFileItem
paginate_by = 39
slug_field = "slug"
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.object = None
def get_context_data(self, **kwargs):
folder = self.get_object()
context = super().get_context_data(**kwargs)
context["folder_slug"] = folder.slug
return context
def get_object(self, *args):
obj = get_object_or_404(Folder, slug=self.kwargs["slug"])
if self.object:
return self.object
self.object = obj
return obj
def get_queryset(self):
folder = self.get_object()
return BaseFileItem.objects.filter(parent=folder)
class FileUpdateView(LoginRequiredMixin, UpdateView):
@ -131,12 +158,6 @@ def get_redirect_url(self, *args, **kwargs):
delete_file_view = DeleteFileView.as_view()
class FileFolderView(DetailView):
template_name = "files/folder.html"
model = Folder
slug_field = "slug"
folder_view = FileFolderView.as_view()
@ -169,17 +190,42 @@ def check_permissions(self, request):
)
def on_completion(self, uploaded_file, request):
if uploaded_file.size <= request.user.left_file_upload:
folder = None
prepared = True
if "slug" in self.kwargs and self.kwargs["slug"]:
try:
folder = Folder.objects.get(slug=self.kwargs["slug"])
if folder.user != self.request.user:
self.message = {
"message": "You can't upload to this folder",
"status": False,
}
prepared = False
except Folder.DoesNotExist:
self.message = {
"message": "Folder doesn't exist",
"status": False,
}
prepared = False
if prepared and uploaded_file.size <= request.user.left_file_upload:
f = File.objects.create(
user=request.user, file=uploaded_file, name=uploaded_file.name
user=request.user,
file_obj=uploaded_file,
name=uploaded_file.name,
parent=folder,
)
if folder:
folder.modified = now()
folder.size += uploaded_file.size
folder.amount += 1
folder.save()
request.user.left_file_upload -= uploaded_file.size
request.user.save()
self.message = {
"message": f"File {f.file.name.split('/')[-1]} successfully uploaded",
"status": True,
}
else:
elif prepared:
self.message = {
"message": "File is too large, please increase disk space",
"status": False,

View File

@ -0,0 +1 @@
{% extends 'files/list.html' %}

View File

@ -37,6 +37,7 @@
{{ folder.name }}
{% endfor %}
<div class="row justify-content-center">
{% if request.user.is_authenticated %}
<div class="col-lg-2 col-xxl-2 col-md-4 col-sm-6 col-xs-12 mb-3 m-3 d-flex align-items-stretch card">
<div class="card-body d-flex flex-column justify-content-center align-items-center">
{% csrf_token %}
@ -64,8 +65,10 @@
<div id="messages"></div>
</div>
</div>
{% for file in file_list %}
{% endif %}
{% for file in basefileitem_list %}
<div class="col-lg-2 col-xxl-2 col-md-4 col-sm-6 col-xs-12 mb-3 m-3 d-flex align-items-stretch card">
{% if file.is_file %}
<div class="card-body d-flex flex-column">
<h5 class="card-title">{{ file.name }}</h5>
<p class="card-text mb-4"><small class="text-muted">{{ file.file_size | filesizeformat }}</small></p>
@ -76,6 +79,19 @@
<a href="{% url 'files:view' file.slug %}" class="stretched-link"></a>
<p class="card-text mb-4 mt-2 ms-3"><small class="text-muted">{{ file.modified | naturaltime }}</small></p>
</div>
{% else %}
<div class="card-body d-flex flex-column">
<h5 class="card-title">{{ file.name }}</h5>
<p class="card-text mb-4"><small class="text-muted">{{ file.size | filesizeformat }}, {{ file.amount }} {% if file.amount == 1 %} item {% else %} items {% endif %}</small></p>
<div class="align-self-stretch align-items-center justify-content-center d-flex flex-column fill-height controlsdiv">
<i class="bi bi-folder"></i>
</div>
<a href="{% url 'files:folder' file.slug %}" class="stretched-link"></a>
<p class="card-text mb-4 mt-2 ms-3"><small class="text-muted">{{ file.modified | naturaltime }}</small></p>
</div>
{% endif %}
</div>
{% endfor %}
</div>
@ -207,7 +223,11 @@
sel.text(progress + "%");
$.ajax({
type: "POST",
{% if folder_slug %}
url: "{% url 'files:api_chunked_upload_complete_folder' slug=folder_slug %}",
{% else %}
url: "{% url 'files:api_chunked_upload_complete' %}",
{% endif %}
data: {
csrfmiddlewaretoken: csrf,
upload_id: data.result.upload_id,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB