mirror of
				https://github.com/Alexander-D-Karpov/akarpov
				synced 2025-11-04 03:27:24 +03:00 
			
		
		
		
	added folder creation, folder meta, folder upload, tables
This commit is contained in:
		
							parent
							
								
									83ea171886
								
							
						
					
					
						commit
						6d65e771d7
					
				
							
								
								
									
										14
									
								
								akarpov/files/filters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								akarpov/files/filters.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,14 @@
 | 
			
		|||
import django_filters
 | 
			
		||||
 | 
			
		||||
from .models import File
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileFilter(django_filters.FilterSet):
 | 
			
		||||
    modified = django_filters.DateFromToRangeFilter(
 | 
			
		||||
        label="Dates",
 | 
			
		||||
        widget=django_filters.widgets.RangeWidget(attrs={"type": "date"}),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = File
 | 
			
		||||
        fields = ["modified", "private", "parent"]
 | 
			
		||||
| 
						 | 
				
			
			@ -1,9 +1,15 @@
 | 
			
		|||
from django import forms
 | 
			
		||||
 | 
			
		||||
from akarpov.files.models import File
 | 
			
		||||
from akarpov.files.models import File, Folder
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = File
 | 
			
		||||
        fields = ["name", "private", "description"]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FolderForm(forms.ModelForm):
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = Folder
 | 
			
		||||
        fields = ["name", "private"]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,13 +39,13 @@ class Meta:
 | 
			
		|||
    def is_file(self):
 | 
			
		||||
        return type(self) is File
 | 
			
		||||
 | 
			
		||||
    def get_folder_chain(self):
 | 
			
		||||
        folders = [self]
 | 
			
		||||
    def get_top_folders(self):
 | 
			
		||||
        folders = []
 | 
			
		||||
        obj = self
 | 
			
		||||
        while obj.parent:
 | 
			
		||||
            folders.append(obj.parent)
 | 
			
		||||
            obj = obj.parent
 | 
			
		||||
        return folders
 | 
			
		||||
        return folders[::-1]
 | 
			
		||||
 | 
			
		||||
    def save(self, *args, **kwargs):
 | 
			
		||||
        update_fields = kwargs.get("update_fields", None)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,7 @@
 | 
			
		|||
from django.core.files.base import File
 | 
			
		||||
from django.db.models.signals import post_delete, post_save
 | 
			
		||||
from django.dispatch import receiver
 | 
			
		||||
from django.utils.timezone import now
 | 
			
		||||
 | 
			
		||||
from akarpov.files.models import File as FileModel
 | 
			
		||||
from akarpov.files.models import FileInTrash
 | 
			
		||||
| 
						 | 
				
			
			@ -10,8 +11,13 @@
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
@receiver(post_save, sender=FileModel)
 | 
			
		||||
def post_on_create(sender, instance: FileModel, created, **kwargs):
 | 
			
		||||
def file_on_create(sender, instance: FileModel, created, **kwargs):
 | 
			
		||||
    if created:
 | 
			
		||||
        for folder in instance.get_top_folders():
 | 
			
		||||
            folder.modified = now()
 | 
			
		||||
            folder.size += instance.file.size
 | 
			
		||||
            folder.amount += 1
 | 
			
		||||
            folder.save()
 | 
			
		||||
        process_file.apply_async(
 | 
			
		||||
            kwargs={
 | 
			
		||||
                "pk": instance.pk,
 | 
			
		||||
| 
						 | 
				
			
			@ -23,6 +29,12 @@ def post_on_create(sender, instance: FileModel, created, **kwargs):
 | 
			
		|||
@receiver(post_delete, sender=FileModel)
 | 
			
		||||
def move_file_to_trash(sender, instance, **kwargs):
 | 
			
		||||
    if instance.file:
 | 
			
		||||
        for folder in instance.get_top_folders():
 | 
			
		||||
            folder.modified = now()
 | 
			
		||||
            folder.size -= instance.file.size
 | 
			
		||||
            folder.amount -= 1
 | 
			
		||||
            folder.save()
 | 
			
		||||
 | 
			
		||||
        name = instance.file.name.split("/")[-1]
 | 
			
		||||
        trash = FileInTrash(user=instance.user, name=name)
 | 
			
		||||
        trash.file = File(instance.file, name=name)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										30
									
								
								akarpov/files/tables.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								akarpov/files/tables.py
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
import django_tables2 as tables
 | 
			
		||||
 | 
			
		||||
from akarpov.files.models import File
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileTable(tables.Table):
 | 
			
		||||
    name = tables.columns.Column("name", linkify=True)
 | 
			
		||||
    time = tables.columns.DateTimeColumn(
 | 
			
		||||
        format="H:m:s", accessor="modified", verbose_name="Time"
 | 
			
		||||
    )
 | 
			
		||||
    folder = tables.columns.Column(
 | 
			
		||||
        linkify=True, accessor="parent", verbose_name="Folder"
 | 
			
		||||
    )
 | 
			
		||||
    file = tables.columns.FileColumn(
 | 
			
		||||
        linkify=True, accessor="file_obj", verbose_name="File"
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    class Meta:
 | 
			
		||||
        model = File
 | 
			
		||||
        template_name = "django_tables2/bootstrap5.html"
 | 
			
		||||
        fields = (
 | 
			
		||||
            "name",
 | 
			
		||||
            "created",
 | 
			
		||||
            "modified",
 | 
			
		||||
            "time",
 | 
			
		||||
            "folder",
 | 
			
		||||
            "private",
 | 
			
		||||
            "file",
 | 
			
		||||
            "file_type",
 | 
			
		||||
        )
 | 
			
		||||
| 
						 | 
				
			
			@ -5,14 +5,17 @@
 | 
			
		|||
    MyChunkedUploadView,
 | 
			
		||||
    TopFolderView,
 | 
			
		||||
    delete_file_view,
 | 
			
		||||
    file_table,
 | 
			
		||||
    file_update,
 | 
			
		||||
    files_view,
 | 
			
		||||
    folder_create,
 | 
			
		||||
    folder_view,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
app_name = "files"
 | 
			
		||||
urlpatterns = [
 | 
			
		||||
    path("", TopFolderView.as_view(), name="main"),
 | 
			
		||||
    path("table/", file_table, name="table"),
 | 
			
		||||
    path(
 | 
			
		||||
        "api/chunked_upload_complete/",
 | 
			
		||||
        MyChunkedUploadCompleteView.as_view(),
 | 
			
		||||
| 
						 | 
				
			
			@ -26,6 +29,8 @@
 | 
			
		|||
    path(
 | 
			
		||||
        "api/chunked_upload/", MyChunkedUploadView.as_view(), name="api_chunked_upload"
 | 
			
		||||
    ),
 | 
			
		||||
    path("api/folder/create/", folder_create, name="folder_create"),
 | 
			
		||||
    path("api/folder/create/<str:slug>", folder_create, name="sub_folder_create"),
 | 
			
		||||
    path("<str:slug>", files_view, name="view"),
 | 
			
		||||
    path("<str:slug>/update", file_update, name="update"),
 | 
			
		||||
    path("<str:slug>/delete", delete_file_view, name="delete"),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,9 +6,16 @@
 | 
			
		|||
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
 | 
			
		||||
from django.views.generic import (
 | 
			
		||||
    CreateView,
 | 
			
		||||
    DetailView,
 | 
			
		||||
    ListView,
 | 
			
		||||
    RedirectView,
 | 
			
		||||
    UpdateView,
 | 
			
		||||
)
 | 
			
		||||
from django_filters.views import FilterView
 | 
			
		||||
from django_tables2 import SingleTableView
 | 
			
		||||
from django_tables2.export import ExportMixin
 | 
			
		||||
 | 
			
		||||
from akarpov.contrib.chunked_upload.exceptions import ChunkedUploadError
 | 
			
		||||
from akarpov.contrib.chunked_upload.models import ChunkedUpload
 | 
			
		||||
| 
						 | 
				
			
			@ -16,22 +23,29 @@
 | 
			
		|||
    ChunkedUploadCompleteView,
 | 
			
		||||
    ChunkedUploadView,
 | 
			
		||||
)
 | 
			
		||||
from akarpov.files.forms import FileForm
 | 
			
		||||
from akarpov.files.filters import FileFilter
 | 
			
		||||
from akarpov.files.forms import FileForm, FolderForm
 | 
			
		||||
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
 | 
			
		||||
from akarpov.files.tables import FileTable
 | 
			
		||||
 | 
			
		||||
logger = structlog.get_logger(__name__)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class TopFolderView(LoginRequiredMixin, ListView):
 | 
			
		||||
    template_name = "files/list.html"
 | 
			
		||||
    paginate_by = 19
 | 
			
		||||
    paginate_by = 18
 | 
			
		||||
    model = BaseFileItem
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        context["folder_slug"] = None
 | 
			
		||||
        context["folder_form"] = FolderForm()
 | 
			
		||||
        context["is_folder_owner"] = True
 | 
			
		||||
 | 
			
		||||
        # folder path
 | 
			
		||||
        context["folders"] = []
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -41,17 +55,28 @@ def get_queryset(self):
 | 
			
		|||
class FileFolderView(ListView):
 | 
			
		||||
    template_name = "files/folder.html"
 | 
			
		||||
    model = BaseFileItem
 | 
			
		||||
    paginate_by = 39
 | 
			
		||||
    paginate_by = 38
 | 
			
		||||
    slug_field = "slug"
 | 
			
		||||
 | 
			
		||||
    def __init__(self, **kwargs):
 | 
			
		||||
        super().__init__(**kwargs)
 | 
			
		||||
        self.object = None
 | 
			
		||||
 | 
			
		||||
    def get_paginate_by(self, queryset):
 | 
			
		||||
        if self.request.user == self.get_object().user:
 | 
			
		||||
            # return 38 items for owner to fit file and folder forms
 | 
			
		||||
            return 38
 | 
			
		||||
        return 40
 | 
			
		||||
 | 
			
		||||
    def get_context_data(self, **kwargs):
 | 
			
		||||
        folder = self.get_object()
 | 
			
		||||
        context = super().get_context_data(**kwargs)
 | 
			
		||||
        context["folder_slug"] = folder.slug
 | 
			
		||||
        context["folder_form"] = FolderForm()
 | 
			
		||||
        context["is_folder_owner"] = self.request.user == self.get_object().user
 | 
			
		||||
 | 
			
		||||
        # folder path
 | 
			
		||||
        context["folders"] = folder.get_top_folders() + [folder]
 | 
			
		||||
        return context
 | 
			
		||||
 | 
			
		||||
    def get_object(self, *args):
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +95,7 @@ class FileUpdateView(LoginRequiredMixin, UpdateView):
 | 
			
		|||
    model = File
 | 
			
		||||
    form_class = FileForm
 | 
			
		||||
 | 
			
		||||
    def get_object(self):
 | 
			
		||||
    def get_object(self, *args):
 | 
			
		||||
        file = get_object_or_404(File, slug=self.kwargs["slug"])
 | 
			
		||||
        if file.user != self.request.user:
 | 
			
		||||
            raise PermissionDenied
 | 
			
		||||
| 
						 | 
				
			
			@ -82,6 +107,27 @@ def get_object(self):
 | 
			
		|||
file_update = FileUpdateView.as_view()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FolderCreateView(LoginRequiredMixin, CreateView):
 | 
			
		||||
    model = Folder
 | 
			
		||||
    form_class = FolderForm
 | 
			
		||||
 | 
			
		||||
    def form_valid(self, form):
 | 
			
		||||
        folder = None
 | 
			
		||||
        if "slug" in self.kwargs and self.kwargs["slug"]:
 | 
			
		||||
            folder = get_object_or_404(Folder, slug=self.kwargs["slug"])
 | 
			
		||||
        form.instance.user = self.request.user
 | 
			
		||||
        form.instance.parent = folder
 | 
			
		||||
        super().form_valid(form)
 | 
			
		||||
        if folder:
 | 
			
		||||
            return HttpResponseRedirect(
 | 
			
		||||
                reverse("files:folder", kwargs={"slug": folder.slug})
 | 
			
		||||
            )
 | 
			
		||||
        return HttpResponseRedirect(reverse("files:main"))
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
folder_create = FolderCreateView.as_view()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileView(DetailView):
 | 
			
		||||
    template_name = "files/view.html"
 | 
			
		||||
    model = File
 | 
			
		||||
| 
						 | 
				
			
			@ -161,10 +207,6 @@ def get_redirect_url(self, *args, **kwargs):
 | 
			
		|||
folder_view = FileFolderView.as_view()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ChunkedUploadDemo(LoginRequiredMixin, TemplateView):
 | 
			
		||||
    template_name = "files/upload.html"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MyChunkedUploadView(ChunkedUploadView):
 | 
			
		||||
    model = ChunkedUpload
 | 
			
		||||
    field_name = "the_file"
 | 
			
		||||
| 
						 | 
				
			
			@ -214,11 +256,6 @@ def on_completion(self, uploaded_file, request):
 | 
			
		|||
                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 = {
 | 
			
		||||
| 
						 | 
				
			
			@ -235,3 +272,17 @@ def on_completion(self, uploaded_file, request):
 | 
			
		|||
 | 
			
		||||
    def get_response_data(self, chunked_upload, request):
 | 
			
		||||
        return self.message
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileTableView(LoginRequiredMixin, FilterView, ExportMixin, SingleTableView):
 | 
			
		||||
    model = File
 | 
			
		||||
    table_class = FileTable
 | 
			
		||||
    filterset_class = FileFilter
 | 
			
		||||
    template_name = "files/tables.html"
 | 
			
		||||
    paginate_by = 200
 | 
			
		||||
 | 
			
		||||
    def get_queryset(self, **kwargs):
 | 
			
		||||
        return File.objects.filter(user=self.request.user)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
file_table = FileTableView.as_view()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										
											BIN
										
									
								
								akarpov/static/images/files/folder.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								akarpov/static/images/files/folder.jpg
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 3.8 KiB  | 
| 
						 | 
				
			
			@ -1 +1,8 @@
 | 
			
		|||
{% extends 'files/list.html' %}
 | 
			
		||||
 | 
			
		||||
{% block meta %}
 | 
			
		||||
  <meta property="og:type" content="website">
 | 
			
		||||
  <meta property="og:title" content="{{ folder.name }}">
 | 
			
		||||
  <meta property="og:url" content="{{ folder.get_absolute_url }}">
 | 
			
		||||
  <meta property="og:description" content="folder on akarpov.ru">
 | 
			
		||||
{% endblock %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,5 @@
 | 
			
		|||
{% extends "base.html" %}
 | 
			
		||||
{% load static %}
 | 
			
		||||
{% load crispy_forms_tags %}
 | 
			
		||||
{% load static crispy_forms_tags %}
 | 
			
		||||
 | 
			
		||||
{% block title %}editing file on akarpov{% endblock %}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
{% extends 'base.html' %}
 | 
			
		||||
{% load humanize static %}
 | 
			
		||||
{% load humanize static crispy_forms_tags %}
 | 
			
		||||
 | 
			
		||||
{% block javascript %}
 | 
			
		||||
  <script src="{% static 'js/jquery.min.js' %}"></script>
 | 
			
		||||
| 
						 | 
				
			
			@ -33,12 +33,30 @@
 | 
			
		|||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
{% for folder in folders %}
 | 
			
		||||
  {{ 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="ms-3 row">
 | 
			
		||||
  {% if is_folder_owner %}
 | 
			
		||||
    {% if folder_slug %}
 | 
			
		||||
    <nav aria-label="breadcrumb">
 | 
			
		||||
      <ol class="breadcrumb">
 | 
			
		||||
        <li class="breadcrumb-item active" aria-current="page"><a href="{% url 'files:main' %}">home</a></li>
 | 
			
		||||
        {% for f in folders %}
 | 
			
		||||
        <li class="breadcrumb-item"><a href="{% url 'files:folder' slug=f.slug %}">{{ f.name }}</a></li>
 | 
			
		||||
        {% endfor %}
 | 
			
		||||
      </ol>
 | 
			
		||||
    </nav>
 | 
			
		||||
    {% else %}
 | 
			
		||||
    <nav aria-label="breadcrumb">
 | 
			
		||||
      <ol class="breadcrumb">
 | 
			
		||||
        <li class="breadcrumb-item active" aria-current="page"><a href="{% url 'files:main' %}">home</a></li>
 | 
			
		||||
      </ol>
 | 
			
		||||
    </nav>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
  {% endif %}
 | 
			
		||||
  <div class="d-flex justify-content-end me-5">
 | 
			
		||||
    <a class="me-5" href="{% url 'files:table' %}">View in table view</a>
 | 
			
		||||
  </div>
 | 
			
		||||
  {% if request.user.is_authenticated and is_folder_owner %}
 | 
			
		||||
  <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 %}
 | 
			
		||||
        <fieldset class="upload_dropZone text-center mb-3 p-4">
 | 
			
		||||
| 
						 | 
				
			
			@ -64,10 +82,26 @@
 | 
			
		|||
        </div>
 | 
			
		||||
        <div id="messages"></div>
 | 
			
		||||
      </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endif %}
 | 
			
		||||
{% for file in basefileitem_list %}
 | 
			
		||||
  </div>
 | 
			
		||||
  <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">
 | 
			
		||||
    <h5>Add folder</h5>
 | 
			
		||||
    <form class="pt-2" method="POST" id="designer-form" action="{% if folder_slug %}{% url 'files:sub_folder_create' slug=folder_slug %}{% else %}{% url 'files:folder_create' %}{% endif %}">
 | 
			
		||||
      {% csrf_token %}
 | 
			
		||||
      {% for field in folder_form %}
 | 
			
		||||
          {{ field|as_crispy_field }}
 | 
			
		||||
      {% endfor %}
 | 
			
		||||
      <div class="mt-4 flex justify-end space-x-4">
 | 
			
		||||
          <button class="btn btn-success" type="submit" id="submit">
 | 
			
		||||
              Create
 | 
			
		||||
          </button>
 | 
			
		||||
      </div>
 | 
			
		||||
  </form>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  {% 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 justify-content-center">
 | 
			
		||||
        {% if file.is_file %}
 | 
			
		||||
        <div class="card-body d-flex flex-column">
 | 
			
		||||
          <h5 class="card-title">{{ file.name }}</h5>
 | 
			
		||||
| 
						 | 
				
			
			@ -84,7 +118,7 @@
 | 
			
		|||
          <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>
 | 
			
		||||
            <img src="{% static 'images/files/folder.jpg' %}" class="img-fluid" alt="">
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <a href="{% url 'files:folder' file.slug %}" class="stretched-link"></a>
 | 
			
		||||
| 
						 | 
				
			
			@ -93,7 +127,7 @@
 | 
			
		|||
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </div>
 | 
			
		||||
{% endfor %}
 | 
			
		||||
  {% endfor %}
 | 
			
		||||
</div>
 | 
			
		||||
{% if page_obj.has_other_pages %}
 | 
			
		||||
<div class="btn-group" role="group" aria-label="Item pagination">
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										35
									
								
								akarpov/templates/files/tables.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								akarpov/templates/files/tables.html
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
{% extends "base.html" %}
 | 
			
		||||
{% load django_tables2 crispy_forms_tags %}
 | 
			
		||||
 | 
			
		||||
{% block css %}
 | 
			
		||||
  <style>
 | 
			
		||||
  .pagination {
 | 
			
		||||
    gap: 20px;
 | 
			
		||||
  }
 | 
			
		||||
  </style>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
  <div class="justify-content-end d-flex">
 | 
			
		||||
    <a href="{% url 'files:main' %}">View in folder view</a>
 | 
			
		||||
  </div>
 | 
			
		||||
  <form class="mb-4 p-2" >
 | 
			
		||||
  <div class="row d-flex">
 | 
			
		||||
  {% for field in filter.form %}
 | 
			
		||||
    <div class="col col-md-4 col-sm-5 justify-content-center align-self-center">{{ field | as_crispy_field }}</div>
 | 
			
		||||
  {% endfor %}
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="row">
 | 
			
		||||
  <div class="col col-auto justify-content-center align-self-center">
 | 
			
		||||
    <button class="btn btn-success mt-3">Apply filters</button>
 | 
			
		||||
  </div>
 | 
			
		||||
  <div class="col col-auto justify-content-center align-self-center">
 | 
			
		||||
    <a style="text-decoration: none" href="{% export_url "xlsx" %}" class="btn btn-warning mt-3">Export into Excel</a>
 | 
			
		||||
  </div>
 | 
			
		||||
  </div>
 | 
			
		||||
  </form>
 | 
			
		||||
  <div class="table-responsive">
 | 
			
		||||
    {% render_table table %}
 | 
			
		||||
  </div>
 | 
			
		||||
{% endblock %}
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +14,17 @@
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
{% block content %}
 | 
			
		||||
{% if has_perm %}
 | 
			
		||||
<nav class="ms-3" aria-label="breadcrumb">
 | 
			
		||||
  <ol class="breadcrumb">
 | 
			
		||||
    <li class="breadcrumb-item active" aria-current="page"><a href="{% url 'files:main' %}">home</a></li>
 | 
			
		||||
    {% for f in file.get_top_folders %}
 | 
			
		||||
    <li class="breadcrumb-item"><a href="{% url 'files:folder' slug=f.slug %}">{{ f.name }}</a></li>
 | 
			
		||||
    {% endfor %}
 | 
			
		||||
    <li class="breadcrumb-item active" aria-current="page">{{ file.name }}</li>
 | 
			
		||||
  </ol>
 | 
			
		||||
</nav>
 | 
			
		||||
{% endif %}
 | 
			
		||||
<div class="row m-2">
 | 
			
		||||
  <h1 class="fs-1 text-break mb-4">{{ file.name }}
 | 
			
		||||
  {% if has_perm %}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -115,6 +115,8 @@
 | 
			
		|||
    "akarpov.contrib.chunked_upload",
 | 
			
		||||
    "active_link",
 | 
			
		||||
    "robots",
 | 
			
		||||
    "django_filters",
 | 
			
		||||
    "django_tables2",
 | 
			
		||||
    # django-cms
 | 
			
		||||
    "cms",
 | 
			
		||||
    "menus",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										57
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										57
									
								
								poetry.lock
									
									
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -1519,6 +1519,21 @@ files = [
 | 
			
		|||
[package.dependencies]
 | 
			
		||||
jsonfield = ">=3.0.0"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "django-filter"
 | 
			
		||||
version = "23.1"
 | 
			
		||||
description = "Django-filter is a reusable Django application for allowing users to filter querysets dynamically."
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.7"
 | 
			
		||||
files = [
 | 
			
		||||
    {file = "django-filter-23.1.tar.gz", hash = "sha256:dee5dcf2cea4d7f767e271b6d01f767fce7500676d5e5dc58dac8154000b87df"},
 | 
			
		||||
    {file = "django_filter-23.1-py3-none-any.whl", hash = "sha256:e3c52ad83c32fb5882125105efb5fea2a1d6a85e7dc64b04ef52edbf14451b6c"},
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[package.dependencies]
 | 
			
		||||
Django = ">=3.2"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "django-formtools"
 | 
			
		||||
version = "2.4"
 | 
			
		||||
| 
						 | 
				
			
			@ -1757,6 +1772,24 @@ files = [
 | 
			
		|||
django = "*"
 | 
			
		||||
typing-extensions = "*"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "django-tables2"
 | 
			
		||||
version = "2.5.3"
 | 
			
		||||
description = "Table/data-grid framework for Django"
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = "*"
 | 
			
		||||
files = [
 | 
			
		||||
    {file = "django-tables2-2.5.3.tar.gz", hash = "sha256:f6c1623aac188d29aae9cf6b4de3211c96c525e49890654bec3359c181600eb9"},
 | 
			
		||||
    {file = "django_tables2-2.5.3-py2.py3-none-any.whl", hash = "sha256:e336fdf8899a8fab110550a40cad956064bd4054818e0b972c1893b3e2542168"},
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[package.dependencies]
 | 
			
		||||
Django = ">=3.2"
 | 
			
		||||
 | 
			
		||||
[package.extras]
 | 
			
		||||
tablib = ["tablib"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "django-timezone-field"
 | 
			
		||||
version = "5.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -4780,6 +4813,28 @@ docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-
 | 
			
		|||
tests = ["coverage[toml]", "freezegun (>=0.2.8)", "pretend", "pytest (>=6.0)", "pytest-asyncio (>=0.17)", "simplejson"]
 | 
			
		||||
typing = ["mypy", "rich", "twisted"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "tablib"
 | 
			
		||||
version = "3.4.0"
 | 
			
		||||
description = "Format agnostic tabular data library (XLS, JSON, YAML, CSV, etc.)"
 | 
			
		||||
category = "main"
 | 
			
		||||
optional = false
 | 
			
		||||
python-versions = ">=3.7"
 | 
			
		||||
files = [
 | 
			
		||||
    {file = "tablib-3.4.0-py3-none-any.whl", hash = "sha256:0f9c45141195c472202f30d82c0c035bf7e0dd7e4da2257815e506acff4ab364"},
 | 
			
		||||
    {file = "tablib-3.4.0.tar.gz", hash = "sha256:77ea97faf6f92a7e198c05bd0c690f3cba57b83ea45a636b72f967cb6fe6f160"},
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[package.extras]
 | 
			
		||||
all = ["markuppy", "odfpy", "openpyxl (>=2.6.0)", "pandas", "pyyaml", "tabulate", "xlrd", "xlwt"]
 | 
			
		||||
cli = ["tabulate"]
 | 
			
		||||
html = ["markuppy"]
 | 
			
		||||
ods = ["odfpy"]
 | 
			
		||||
pandas = ["pandas"]
 | 
			
		||||
xls = ["xlrd", "xlwt"]
 | 
			
		||||
xlsx = ["openpyxl (>=2.6.0)"]
 | 
			
		||||
yaml = ["pyyaml"]
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "termcolor"
 | 
			
		||||
version = "2.2.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -5642,4 +5697,4 @@ testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"]
 | 
			
		|||
[metadata]
 | 
			
		||||
lock-version = "2.0"
 | 
			
		||||
python-versions = "^3.11"
 | 
			
		||||
content-hash = "667d1226a682d79bea11fdee05d079b1b494ac325167ca069f79f4d18d1a7d04"
 | 
			
		||||
content-hash = "c065bd831f46b58518895fa101821a580bebe3f7792348ca31d6a4fb4aab2b82"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -88,6 +88,9 @@ cairosvg = "^2.7.0"
 | 
			
		|||
textract = "^1.6.5"
 | 
			
		||||
spotipy = "2.16.1"
 | 
			
		||||
django-robots = "^5.0"
 | 
			
		||||
django-tables2 = "^2.5.3"
 | 
			
		||||
django-filter = "^23.1"
 | 
			
		||||
tablib = "^3.4.0"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[build-system]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user