Create: views and templates

This commit is contained in:
Stepan Zhukovsky 2023-07-29 13:56:42 +09:00
parent 52a77a4b27
commit 96ae8647e1
9 changed files with 395 additions and 23 deletions

View File

@ -1,6 +1,6 @@
# Generated by Django 4.2 on 2023-07-27 02:04 # Generated by Django 4.2 on 2023-07-28 14:40
import collector.models import collector.utils
from django.conf import settings from django.conf import settings
import django.core.files.storage import django.core.files.storage
from django.db import migrations, models from django.db import migrations, models
@ -22,6 +22,7 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=20)), ('name', models.CharField(max_length=20)),
('pretty_name', models.CharField(max_length=20)),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
@ -41,7 +42,8 @@ class Migration(migrations.Migration):
name='Archive', name='Archive',
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('file', models.FileField(blank=True, null=True, storage=django.core.files.storage.FileSystemStorage(base_url='/archives/', location=pathlib.PurePosixPath('/home/stepan/Documents/Dev/ISPsystem/logs-collector/logs_collector/archives')), upload_to=collector.models.logs_dir_path)), ('file', models.FileField(blank=True, null=True, storage=django.core.files.storage.FileSystemStorage(base_url='/archives/', location=pathlib.PurePosixPath('/home/stepan/Documents/Dev/ISPsystem/logs-collector/logs_collector/archives')), upload_to=collector.utils.logs_dir_path)),
('size', models.CharField(blank=True, max_length=50)),
('sha1', models.CharField(editable=False, max_length=1024)), ('sha1', models.CharField(editable=False, max_length=1024)),
('time_create', models.DateTimeField(auto_now_add=True)), ('time_create', models.DateTimeField(auto_now_add=True)),
('time_update', models.DateTimeField(auto_now=True)), ('time_update', models.DateTimeField(auto_now=True)),

View File

@ -5,7 +5,9 @@ from django.contrib.auth.models import User
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.core.files.storage import FileSystemStorage from django.core.files.storage import FileSystemStorage
from django.db.models import FileField from django.urls import reverse
from .utils import logs_dir_path, get_file_size
# Create a custom storage location, using a value from your settings file # Create a custom storage location, using a value from your settings file
@ -14,13 +16,10 @@ sensitive_upload_storage = FileSystemStorage(
base_url=settings.MEDIA_URL_FOR_SENSITIVE_FILES base_url=settings.MEDIA_URL_FOR_SENSITIVE_FILES
) )
# ... and a file field that will use the custom storage # ... and a file field that will use the custom storage
AuthenticatedFileField = partial(FileField, storage=sensitive_upload_storage) AuthenticatedFileField = partial(
models.FileField,
storage=sensitive_upload_storage
def logs_dir_path(instance, filename): )
# file will be uploaded to
# MEDIA_ROOT_FOR_SENSITIVE_FILES/<ticket>/<filename>
return f'{instance.ticket}/{filename}'
class Archive(models.Model): class Archive(models.Model):
@ -29,6 +28,7 @@ class Archive(models.Model):
blank=True, blank=True,
null=True null=True
) )
size = models.CharField(max_length=50, blank=True, editable=False)
sha1 = models.CharField(max_length=1024, editable=False) sha1 = models.CharField(max_length=1024, editable=False)
time_create = models.DateTimeField(auto_now_add=True) time_create = models.DateTimeField(auto_now_add=True)
time_update = models.DateTimeField(auto_now=True) time_update = models.DateTimeField(auto_now=True)
@ -42,15 +42,32 @@ class Archive(models.Model):
for byte_block in iter(lambda: f.read(4096), b""): for byte_block in iter(lambda: f.read(4096), b""):
sha1.update(byte_block) sha1.update(byte_block)
self.sha1 = sha1.hexdigest() self.sha1 = sha1.hexdigest()
# calculate size and write size field to db
try:
mr = settings.MEDIA_ROOT_FOR_SENSITIVE_FILES
unit = 'gb'
file_path = mr / self.ticket / self.file.replace(" ", "_")
file_size = get_file_size(file_path, unit)
self.size = f"{file_size} {unit.title()}"
except Exception as error:
print(error)
self.size = '?'
# Call the "real" save() method # Call the "real" save() method
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_absolute_url(self):
return reverse('download', kwargs={'path': self.file})
def __str__(self): def __str__(self):
return str(self.file) return str(self.file)
class Platform(models.Model): class Platform(models.Model):
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
pretty_name = models.CharField(max_length=20)
def get_absolute_url(self):
return reverse('platform', kwargs={'platform': self.name})
def __str__(self): def __str__(self):
return self.name return self.name
@ -65,5 +82,11 @@ class Ticket(models.Model):
platform = models.ForeignKey('Platform', on_delete=models.CASCADE) platform = models.ForeignKey('Platform', on_delete=models.CASCADE)
user = models.ForeignKey(User, on_delete=models.CASCADE) user = models.ForeignKey(User, on_delete=models.CASCADE)
def get_absolute_url(self):
return reverse(
'ticket',
kwargs={'platform': self.platform.name, 'ticket': self.number}
)
def __str__(self): def __str__(self):
return str(self.number) return str(self.number)

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-9ndCyUaIbzAi2FUVXJi0CjmCapSmO7SnpJef0486qhLnuZ2cdeRhO02iuK6FUUVM"
crossorigin="anonymous"
/>
<title>Document</title>
</head>
<body>
{% block content %}{% endblock content %}
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous"
></script>
<script>
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]')
const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl))
</script>
</body>
</html>

View File

@ -0,0 +1,58 @@
<header>
<nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container">
<a class="navbar-brand" href="{% url 'index' %}">Logs Collector</a>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent"
aria-expanded="false"
aria-label="Переключатель навигации"
>
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav ml-auto mb-2 mb-lg-0">
<li class="nav-item dropdown">
<a
class="nav-link dropdown-toggle"
href="#"
role="button"
data-bs-toggle="dropdown"
aria-expanded="false"
>Tickets</a>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="{% url 'index' %}">All</a>
</li>
{% for platform in platforms %}
<li>
<a
class="dropdown-item"
href="{{ platform.get_absolute_url }}"
>{{ platform.pretty_name}}</a>
</li>
{% endfor %}
<li><hr class="dropdown-divider" /></li>
<li><a class="dropdown-item" href="#">Create ticket</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled">Отключенная</a>
</li>
</ul>
<form class="d-flex" role="search">
<input
class="form-control me-2"
type="search"
placeholder="Поиск"
aria-label="Поиск"
/>
<button class="btn btn-outline-success" type="submit">Поиск</button>
</form>
</div>
</div>
</nav>
</header>

View File

@ -0,0 +1,95 @@
{% extends 'collector/base.html' %}
{% block content %}
{% include 'collector/navigation.html' %}
<main>
<section>
<div class="container mt-3">
<div class="row">
<div class="list-group mb-2">
<div class="list-group-item list-group-item-action disable" aria-current="true">
<div class="d-flex w-100 justify-content-between mb-2">
<h5 class="mb-1">Ticket: {{ ticket.number }}</h5>
<small>{{ ticket.time_create }}</small>
</div>
<div class="col-xl-6 mb-2">
<h6 class="mb-1">Platform: {{ ticket.platform.pretty_name }}</h6>
<h6 class="mb-1">Owner: {{ ticket.user.username }}</h6>
</div>
<div class="col-xl-6 mt-1 mb-2">
{% if ticket.note %}
<small><b>Note:</b></small>
<hr>
<p>{{ ticket.note }}</p>
<hr>
{% endif %}
</div>
<!-- Logs -->
{% if ticket.archive_set.all %}
<small><b>Logs:</b></small>
<ul class="list-group mb-2 mt-2">
{% for archive in ticket.archive_set.all %}
<li class="list-group-item list-group-item-action">
<p style="word-wrap: break-word" ><b>File:</b> {{ archive.file }}</p>
<small>
<b>SHA1:</b>
<span style="word-wrap: break-word">{{ archive.sha1 }}</span>
</small>
<br>
<small>
<b>Size:</b>
<span style="word-wrap: break-word">{{ archive.size }}</span>
</small>
<div class="row">
<div class="col" >
<a
class="btn btn-outline-success btn-sm mt-2"
href="{{ archive.get_absolute_url }}"
>GET</a>
<button
class="btn btn-outline-danger btn-sm ms-2 mt-2"
button type="button" data-bs-toggle="modal" data-bs-target="#{{ archive.id }}"
>DEL</button>
</div>
</div>
</li>
{% endfor %}
</ul>
{% endif %}
<!-- Card buttons -->
<div class="d-flex w-100 justify-content-between">
<a
href="{{ ticket.get_absolute_url }}"
class="btn btn-outline-warning mb-1 mt-1"
>Edit</a>
<a
href="{{ ticket.get_absolute_url }}"
class="btn btn-outline-danger mb-1 mt-1"
>Delete</a>
</div>
</div>
</div>
</div>
<!-- Modal -->
{% for archive in ticket.archive_set.all %}
<div class="modal fade" id="{{ archive.id }}" tabindex="-1" aria-labelledby="{{ archive.id }}_Label" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="{{ archive.id }}_Label">Delete this file?</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<p style="word-wrap: break-word">{{ archive.file }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger">Delete</button>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</section>
</main>
{% endblock content %}

View File

@ -0,0 +1,97 @@
{% extends 'collector/base.html' %}
{% block content %}
{% include 'collector/navigation.html' %}
<!-- Modal -->
<main>
<section>
<div class="container mt-3">
{% for ticket in tickets %}
<div class="row">
<!-- Ticket -->
<div class="list-group mb-2">
<div class="list-group-item list-group-item-action disable" aria-current="true">
<div class="d-flex w-100 justify-content-between mb-2">
<h5 class="mb-1">Ticket: {{ ticket.number }}</h5>
<small>{{ ticket.time_create }}</small>
</div>
<div class="col-xl-6 mb-2">
<!-- Info -->
<h6 class="mb-1">Platform: {{ ticket.platform.pretty_name }}</h6>
<h6 class="mb-1">Owner: {{ ticket.user.username }}</h6>
</div>
<div class="col-xl-6 mt-1 mb-2">
<div class="accordion" id="#archive_{{ ticket.number }}">
{% if ticket.note %}
<div class="accordion-item">
<h2 class="accordion-header">
<button
class="accordion-button collapsed"
type="button" data-bs-toggle="collapse"
data-bs-target="#collapse_{{ ticket.number}}_note"
aria-expanded="false"
aria-controls="collapse_{{ ticket.number }}"
>Note</button>
</h2>
<div id="collapse_{{ ticket.number }}_note"
class="accordion-collapse collapse"
data-bs-parent="#archive_{{ ticket.number }}_note"
>
<div class="accordion-body">
<p class="mb-1">{{ ticket.note }}</p>
</div>
</div>
</div>
{% endif %}
{% if ticket.archive_set.all %}
<!-- Logs -->
<div class="accordion-item">
<h3 class="accordion-header">
<button
class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
data-bs-target="#collapse_{{ ticket.number }}"
aria-expanded="true" aria-controls="collapse_{{ ticket.number }}"
>Logs</button>
</h3>
<div
id="collapse_{{ ticket.number }}"
class="accordion-collapse collapse"
data-bs-parent="#archive_{{ ticket.number }}"
>
<div class="accordion-body">
<ul class="list-group mb-2">
{% for archive in ticket.archive_set.all %}
<li class="list-group-item list-group-item-action">
<a
href="{{ archive.get_absolute_url }}"
data-bs-toggle="tooltip"
data-bs-placement="top"
data-bs-title="Size: {{ archive.size }}"
style="word-wrap: break-word"
>{{ archive.file }}</a>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<div class="d-flex w-100 justify-content-between">
<a
href="{{ ticket.get_absolute_url }}"
class="btn btn-outline-primary mb-1 mt-1"
>Open</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</section>
</main>
{% endblock content %}

View File

@ -1,9 +1,28 @@
from django.urls import path from django.urls import path
from . import views from . import views
urlpatterns = [ urlpatterns = [
path('', views.index, name='index',), path(
path('test/<str:path>/', views.test_page, name='test_page'), '',
path('archives/<ticket>/<file>', views.download, name="download") views.ListAllTickets.as_view(),
name='index'
),
path(
'tickets/',
views.ListAllTickets.as_view(),
name='index'
),
path(
'tickets/<slug:platform>/',
views.ListPlatformTickets.as_view(),
name='platform'
),
path(
'tickets/<slug:platform>/<int:ticket>/',
views.DetailTicket.as_view(),
name='ticket'
),
path('archives/<path:path>', views.download, name="download")
] ]

View File

@ -0,0 +1,18 @@
import os
def logs_dir_path(instance, filename):
# file will be uploaded to
# MEDIA_ROOT_FOR_SENSITIVE_FILES/<ticket>/<filename>
return f'{instance.ticket}/{filename}'
def get_file_size(file_path, unit='bytes'):
file_size = os.path.getsize(file_path)
exponents_map = {'bytes': 0, 'kb': 1, 'mb': 2, 'gb': 3}
if unit not in exponents_map:
raise ValueError("Must select from \
['bytes', 'kb', 'mb', 'gb']")
else:
size = file_size / 1024 ** exponents_map[unit]
return round(size, 3)

View File

@ -1,14 +1,14 @@
# from django.shortcuts import render # from django.shortcuts import render
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.http import FileResponse, HttpResponse, Http404 from django.http import FileResponse, Http404
from .models import Archive from django.views import generic
from .models import Archive, Ticket, Platform
# Create your views here.
# handles the url "/archives/{PATH}"". # handles the url "/archives/{PATH}"".
@login_required @login_required
def download(request, ticket, file): def download(request, path):
path = f'{ticket}/{file}'
try: try:
file = Archive.objects.get(file=path) file = Archive.objects.get(file=path)
except Archive.DoesNotExist: except Archive.DoesNotExist:
@ -17,9 +17,43 @@ def download(request, ticket, file):
return FileResponse(file.file) return FileResponse(file.file)
def index(request): class ListAllTickets(generic.ListView):
return HttpResponse('<h1>Index Page</h1>') model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['platforms'] = Platform.objects.all()
return context
def test_page(request, path): class ListPlatformTickets(generic.ListView):
return HttpResponse(f'<h1>{path} Page</h1>') model = Ticket
template_name = 'collector/tickets.html'
context_object_name = 'tickets'
allow_empty = False
def get_queryset(self):
return Ticket.objects.filter(
platform__name=self.kwargs.get('platform')
)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['platforms'] = Platform.objects.all()
return context
class DetailTicket(generic.DetailView):
model = Ticket
template_name = 'collector/ticket.html'
context_object_name = 'ticket'
def get_object(self, queryset=None):
return Ticket.objects.get(number=self.kwargs.get('ticket'))
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['platforms'] = Platform.objects.all()
return context