mirror of
https://github.com/jlengrand/bugsink.git
synced 2026-03-10 08:01:17 +00:00
Files: downloadable from admin
for convenience
This commit is contained in:
@@ -55,6 +55,7 @@ urlpatterns = [
|
|||||||
path('teams/', include('teams.urls')),
|
path('teams/', include('teams.urls')),
|
||||||
path('events/', include('events.urls')),
|
path('events/', include('events.urls')),
|
||||||
path('issues/', include('issues.urls')),
|
path('issues/', include('issues.urls')),
|
||||||
|
path('files/', include('files.urls')),
|
||||||
|
|
||||||
path('admin/', admin.site.urls),
|
path('admin/', admin.site.urls),
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.html import format_html
|
||||||
|
|
||||||
from .models import Chunk, File, FileMetadata
|
from .models import Chunk, File, FileMetadata
|
||||||
|
|
||||||
|
|
||||||
@@ -11,9 +14,15 @@ class ChunkAdmin(admin.ModelAdmin):
|
|||||||
|
|
||||||
@admin.register(File)
|
@admin.register(File)
|
||||||
class FileAdmin(admin.ModelAdmin):
|
class FileAdmin(admin.ModelAdmin):
|
||||||
list_display = ('filename', 'checksum', 'size')
|
list_display = ('filename', 'checksum', 'size', 'download_link')
|
||||||
search_fields = ('checksum',)
|
search_fields = ('checksum',)
|
||||||
readonly_fields = ('data',)
|
readonly_fields = ('data', 'download_link')
|
||||||
|
|
||||||
|
def download_link(self, obj):
|
||||||
|
return format_html(
|
||||||
|
'<a href="{}">{}</a>',
|
||||||
|
reverse("download_file", args=(obj.checksum,)), str(obj.filename),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(FileMetadata)
|
@admin.register(FileMetadata)
|
||||||
|
|||||||
27
files/urls.py
Normal file
27
files/urls.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from django.urls import register_converter
|
||||||
|
|
||||||
|
from .views import download_file
|
||||||
|
|
||||||
|
|
||||||
|
def regex_converter(passed_regex):
|
||||||
|
# copy/pasta w/ issues/urls.py
|
||||||
|
|
||||||
|
class RegexConverter:
|
||||||
|
regex = passed_regex
|
||||||
|
|
||||||
|
def to_python(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
def to_url(self, value):
|
||||||
|
return value
|
||||||
|
|
||||||
|
return RegexConverter
|
||||||
|
|
||||||
|
|
||||||
|
register_converter(regex_converter("[0-9a-f]{40}"), "sha1")
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path('downloads/<sha1:checksum>/', download_file, name='download_file'),
|
||||||
|
]
|
||||||
@@ -3,9 +3,11 @@ import json
|
|||||||
from hashlib import sha1
|
from hashlib import sha1
|
||||||
from gzip import GzipFile
|
from gzip import GzipFile
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from os.path import basename
|
||||||
|
|
||||||
from django.http import JsonResponse, HttpResponse
|
from django.http import JsonResponse, HttpResponse
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
|
from django.contrib.auth.decorators import user_passes_test
|
||||||
|
|
||||||
from sentry.assemble import ChunkFileState
|
from sentry.assemble import ChunkFileState
|
||||||
|
|
||||||
@@ -154,7 +156,7 @@ def assemble_artifact_bundle(bundle_checksum, chunk_checksums):
|
|||||||
|
|
||||||
checksum = sha1(file_data).hexdigest()
|
checksum = sha1(file_data).hexdigest()
|
||||||
|
|
||||||
filename = manifest_entry.get("url", filename)[:255]
|
filename = basename(manifest_entry.get("url", filename))[:255]
|
||||||
|
|
||||||
file, _ = File.objects.get_or_create(
|
file, _ = File.objects.get_or_create(
|
||||||
checksum=checksum,
|
checksum=checksum,
|
||||||
@@ -226,3 +228,11 @@ def artifact_bundle_assemble(request, organization_slug):
|
|||||||
# NOTE: as it stands, we process the bundle inline, so arguably we could return "OK" here too; "CREATED" is what
|
# NOTE: as it stands, we process the bundle inline, so arguably we could return "OK" here too; "CREATED" is what
|
||||||
# sentry returns though, so for faithful mimicking it's the safest bet.
|
# sentry returns though, so for faithful mimicking it's the safest bet.
|
||||||
return JsonResponse({"state": ChunkFileState.CREATED, "missingChunks": []})
|
return JsonResponse({"state": ChunkFileState.CREATED, "missingChunks": []})
|
||||||
|
|
||||||
|
|
||||||
|
@user_passes_test(lambda u: u.is_superuser)
|
||||||
|
def download_file(request, checksum):
|
||||||
|
file = File.objects.get(checksum=checksum)
|
||||||
|
response = HttpResponse(file.data, content_type="application/octet-stream")
|
||||||
|
response["Content-Disposition"] = f"attachment; filename={file.filename}"
|
||||||
|
return response
|
||||||
|
|||||||
Reference in New Issue
Block a user