Limit access to files to user

No database migration is made
This commit is contained in:
Edgar P. Burkhart 2022-12-31 17:28:15 +01:00
parent de06312a2a
commit 06704aaa77
Signed by: edpibu
GPG key ID: 9833D3C5A25BD227
7 changed files with 48 additions and 35 deletions

View file

@ -19,7 +19,7 @@ class Migration(migrations.Migration):
name="file", name="file",
field=models.FileField( field=models.FileField(
max_length=128, max_length=128,
upload_to=main.models.invoice_path, upload_to=main.models.get_path,
validators=[django.core.validators.FileExtensionValidator(["pdf"])], validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="Fichier", verbose_name="Fichier",
), ),
@ -40,7 +40,7 @@ class Migration(migrations.Migration):
blank=True, blank=True,
default="", default="",
max_length=256, max_length=256,
upload_to=main.models.snapshot_path, upload_to=main.models.get_path,
validators=[django.core.validators.FileExtensionValidator(["pdf"])], validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="Fichier", verbose_name="Fichier",
), ),

View file

@ -118,7 +118,7 @@ class Migration(migrations.Migration):
name="file", name="file",
field=models.FileField( field=models.FileField(
max_length=128, max_length=128,
upload_to=main.models.invoice_path, upload_to=main.models.get_path,
validators=[django.core.validators.FileExtensionValidator(["pdf"])], validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="File", verbose_name="File",
), ),
@ -174,7 +174,7 @@ class Migration(migrations.Migration):
blank=True, blank=True,
default="", default="",
max_length=256, max_length=256,
upload_to=main.models.snapshot_path, upload_to=main.models.get_path,
validators=[django.core.validators.FileExtensionValidator(["pdf"])], validators=[django.core.validators.FileExtensionValidator(["pdf"])],
verbose_name="File", verbose_name="File",
), ),

View file

@ -6,6 +6,7 @@ from django.conf import settings
from django.core.validators import FileExtensionValidator, validate_slug from django.core.validators import FileExtensionValidator, validate_slug
from django.db import models, transaction from django.db import models, transaction
from django.urls import reverse from django.urls import reverse
from django.utils.text import format_lazy
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
@ -30,6 +31,15 @@ class CustomModel(UserModel):
abstract = True abstract = True
def get_path(instance, filename):
return pathlib.Path(
"user",
str(instance.user.get_username()),
instance._meta.model_name,
str(instance.pk),
).with_suffix(".pdf")
class Account(CustomModel): class Account(CustomModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name")) name = models.CharField(max_length=64, default=_("Account"), verbose_name=_("Name"))
@ -104,10 +114,6 @@ class Category(CustomModel):
verbose_name_plural = _("Categories") verbose_name_plural = _("Categories")
def snapshot_path(instance, filename):
return pathlib.Path("snapshots", str(instance.id)).with_suffix(".pdf")
class Snapshot(AccountModel): class Snapshot(AccountModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
date = models.DateField(default=datetime.date.today, verbose_name=_("End date")) date = models.DateField(default=datetime.date.today, verbose_name=_("End date"))
@ -135,7 +141,7 @@ class Snapshot(AccountModel):
editable=False, editable=False,
) )
file = models.FileField( file = models.FileField(
upload_to=snapshot_path, upload_to=get_path,
validators=[FileExtensionValidator(["pdf"])], validators=[FileExtensionValidator(["pdf"])],
verbose_name=_("File"), verbose_name=_("File"),
max_length=256, max_length=256,
@ -258,17 +264,13 @@ class Transaction(CustomModel):
verbose_name_plural = _("Transactions") verbose_name_plural = _("Transactions")
def invoice_path(instance, filename):
return pathlib.Path("invoices", str(instance.id)).with_suffix(".pdf")
class Invoice(CustomModel): class Invoice(CustomModel):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField( name = models.CharField(
max_length=256, default=_("Invoice"), verbose_name=_("Name") max_length=256, default=_("Invoice"), verbose_name=_("Name")
) )
file = models.FileField( file = models.FileField(
upload_to=invoice_path, upload_to=get_path,
validators=[FileExtensionValidator(["pdf"])], validators=[FileExtensionValidator(["pdf"])],
verbose_name=_("File"), verbose_name=_("File"),
max_length=128, max_length=128,
@ -277,13 +279,19 @@ class Invoice(CustomModel):
Transaction, on_delete=models.CASCADE, editable=False Transaction, on_delete=models.CASCADE, editable=False
) )
def save(self):
if Invoice.objects.filter(id=self.id).exists():
_prever = Invoice.objects.get(id=self.id)
if _prever.file and _prever.file != self.file:
pathlib.Path(_prever.file.path).unlink(missing_ok=True)
def __str__(self): def __str__(self):
if hasattr(self, "transaction"): if hasattr(self, "transaction"):
return f"{self.name} {self.transaction.name}" return str(format_lazy("{} {}", self.name, self.transaction.name))
return self.name return str(self.name)
def delete(self, *args, **kwargs): def delete(self, *args, **kwargs):
self.file.delete() self.file.delete(missing_ok=True)
super().delete(*args, **kwargs) super().delete(*args, **kwargs)
def get_absolute_url(self): def get_absolute_url(self):

View file

@ -4,6 +4,7 @@ from . import views
urlpatterns = [ urlpatterns = [
path("", views.IndexView.as_view(), name="index"), path("", views.IndexView.as_view(), name="index"),
path("media/user/<username>/<path:path>", views.MediaView.as_view(), name="media"),
path("login", views.LoginView.as_view(), name="login"), path("login", views.LoginView.as_view(), name="login"),
path("logout", views.LogoutView.as_view(), name="logout"), path("logout", views.LogoutView.as_view(), name="logout"),
path("transactions", views.TransactionListView.as_view(), name="transactions"), path("transactions", views.TransactionListView.as_view(), name="transactions"),

View file

@ -1,3 +1,4 @@
from django.conf import settings
from django.contrib.auth import views as auth_views from django.contrib.auth import views as auth_views
from django.contrib.auth.mixins import LoginRequiredMixin from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.postgres.search import ( from django.contrib.postgres.search import (
@ -6,9 +7,12 @@ from django.contrib.postgres.search import (
SearchVector, SearchVector,
TrigramSimilarity, TrigramSimilarity,
) )
from django.core.exceptions import PermissionDenied
from django.db import models from django.db import models
from django.http import HttpResponse
from django.shortcuts import get_object_or_404, redirect from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse_lazy from django.urls import reverse_lazy
from django.views import View
from django.views.generic import ( from django.views.generic import (
CreateView, CreateView,
DeleteView, DeleteView,
@ -16,7 +20,7 @@ from django.views.generic import (
TemplateView, TemplateView,
UpdateView, UpdateView,
) )
from django.views.generic.edit import ProcessFormView from django.views.static import serve
from .forms import AccountForm, CategoryForm, InvoiceForm, SnapshotForm, TransactionForm from .forms import AccountForm, CategoryForm, InvoiceForm, SnapshotForm, TransactionForm
from .models import Account, Category, Invoice, Snapshot, Transaction from .models import Account, Category, Invoice, Snapshot, Transaction
@ -334,3 +338,19 @@ class SearchView(TransactionListView):
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
return super().get_context_data(**kwargs) | {"search": self.kwargs["search"]} return super().get_context_data(**kwargs) | {"search": self.kwargs["search"]}
class MediaView(View):
def get(self, request, *args, **kwargs):
_username = kwargs.get("username")
_path = kwargs.get("path")
if request.user.get_username() != _username:
raise PermissionDenied
if settings.DEBUG:
return serve(request, f"user/{_username}/{_path}", settings.MEDIA_ROOT)
_res = HttpResponse()
_res["Content-Type"] = ""
_res["X-Accel-Redirect"] = f"/internal/media/user/{_username}/{_path}"
return _res

View file

@ -17,9 +17,7 @@ from django.conf.urls.i18n import i18n_patterns
from django.contrib import admin from django.contrib import admin
from django.urls import include, path from django.urls import include, path
from . import views urlpatterns = i18n_patterns(
urlpatterns = [path("media/<path:path>", views.media, name="media"),] + i18n_patterns(
path("", include("main.urls")), path("", include("main.urls")),
path("plot/", include("plot.urls")), path("plot/", include("plot.urls")),
path("admin/", admin.site.urls), path("admin/", admin.site.urls),

View file

@ -1,14 +0,0 @@
from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.views.static import serve
@login_required
def media(request, path):
if settings.DEBUG:
return serve(request, path, settings.MEDIA_ROOT)
_res = HttpResponse()
_res["Content-Type"] = ""
_res["X-Accel-Redirect"] = "/internal/media/" + path
return _res