2020-07-28 03:26:45 +00:00
__package__ = ' archivebox.core '
from io import StringIO
2021-02-16 20:47:49 +00:00
from pathlib import Path
2020-07-28 03:26:45 +00:00
from contextlib import redirect_stdout
2021-04-10 08:19:30 +00:00
from datetime import datetime , timezone
2020-07-28 03:26:45 +00:00
2019-04-02 20:36:41 +00:00
from django . contrib import admin
2024-01-19 10:44:59 +00:00
from django . db . models import Count
2020-07-28 03:26:45 +00:00
from django . urls import path
2020-06-30 09:56:17 +00:00
from django . utils . html import format_html
2020-07-28 09:59:54 +00:00
from django . utils . safestring import mark_safe
2020-07-28 03:56:35 +00:00
from django . shortcuts import render , redirect
2020-07-28 03:26:45 +00:00
from django . contrib . auth import get_user_model
2020-10-12 18:47:03 +00:00
from django import forms
2024-05-06 18:06:42 +00:00
2024-05-06 15:11:01 +00:00
from signal_webhooks . apps import DjangoSignalWebhooksConfig
2024-05-06 14:13:54 +00:00
from signal_webhooks . admin import WebhookAdmin , WebhookModel
2024-05-06 18:06:42 +00:00
2021-01-21 00:02:31 +00:00
from . . util import htmldecode , urldecode , ansi_to_html
2021-01-20 23:44:28 +00:00
2021-02-16 11:13:27 +00:00
from core . models import Snapshot , ArchiveResult , Tag
2021-03-01 03:54:04 +00:00
from core . forms import AddLinkForm
2020-07-28 03:26:45 +00:00
2020-11-17 23:42:57 +00:00
from core . mixins import SearchResultsAdminMixin
2024-04-25 10:56:22 +00:00
from api . models import APIToken
2020-11-17 23:42:57 +00:00
2020-11-28 07:12:27 +00:00
from index . html import snapshot_icons
2020-07-28 10:50:03 +00:00
from logging_util import printable_filesize
from main import add , remove
from extractors import archive_links
2024-01-09 04:55:30 +00:00
from config import (
OUTPUT_DIR ,
SNAPSHOTS_PER_PAGE ,
VERSION ,
VERSIONS_AVAILABLE ,
CAN_UPGRADE
)
GLOBAL_CONTEXT = { ' VERSION ' : VERSION , ' VERSIONS_AVAILABLE ' : VERSIONS_AVAILABLE , ' CAN_UPGRADE ' : CAN_UPGRADE }
2020-06-30 09:56:17 +00:00
2021-01-30 10:35:17 +00:00
# Admin URLs
# /admin/
# /admin/login/
# /admin/core/
# /admin/core/snapshot/
# /admin/core/snapshot/:uuid/
# /admin/core/tag/
# /admin/core/tag/:uuid/
2020-06-30 09:56:17 +00:00
# TODO: https://stackoverflow.com/questions/40760880/add-custom-button-to-django-admin-panel
2019-04-27 21:26:24 +00:00
2019-04-22 17:20:19 +00:00
2024-03-26 00:46:01 +00:00
class ArchiveBoxAdmin ( admin . AdminSite ) :
site_header = ' ArchiveBox '
index_title = ' Links '
site_title = ' Index '
namespace = ' admin '
def get_urls ( self ) :
return [
path ( ' core/snapshot/add/ ' , self . add_view , name = ' Add ' ) ,
] + super ( ) . get_urls ( )
def add_view ( self , request ) :
if not request . user . is_authenticated :
return redirect ( f ' /admin/login/?next= { request . path } ' )
request . current_app = self . name
context = {
* * self . each_context ( request ) ,
' title ' : ' Add URLs ' ,
}
if request . method == ' GET ' :
context [ ' form ' ] = AddLinkForm ( )
elif request . method == ' POST ' :
form = AddLinkForm ( request . POST )
if form . is_valid ( ) :
url = form . cleaned_data [ " url " ]
print ( f ' [+] Adding URL: { url } ' )
depth = 0 if form . cleaned_data [ " depth " ] == " 0 " else 1
input_kwargs = {
" urls " : url ,
" depth " : depth ,
" update_all " : False ,
" out_dir " : OUTPUT_DIR ,
}
add_stdout = StringIO ( )
with redirect_stdout ( add_stdout ) :
add ( * * input_kwargs )
print ( add_stdout . getvalue ( ) )
context . update ( {
" stdout " : ansi_to_html ( add_stdout . getvalue ( ) . strip ( ) ) ,
" form " : AddLinkForm ( )
} )
else :
context [ " form " ] = form
return render ( template_name = ' add.html ' , request = request , context = context )
2024-05-06 15:11:01 +00:00
# monkey patch django-signals-webhooks to change how it shows up in Admin UI
DjangoSignalWebhooksConfig . verbose_name = ' API '
WebhookModel . _meta . get_field ( ' name ' ) . help_text = ' Give your webhook a descriptive name (e.g. Notify ACME Slack channel of any new ArchiveResults). '
WebhookModel . _meta . get_field ( ' signal ' ) . help_text = ' The type of event the webhook should fire for (e.g. Create, Update, Delete). '
WebhookModel . _meta . get_field ( ' ref ' ) . help_text = ' Dot import notation of the model the webhook should fire for (e.g. core.models.Snapshot or core.models.ArchiveResult). '
WebhookModel . _meta . get_field ( ' endpoint ' ) . help_text = ' External URL to POST the webhook notification to (e.g. https://someapp.example.com/webhook/some-webhook-receiver). '
WebhookModel . _meta . app_label = ' api '
2024-03-26 00:46:01 +00:00
archivebox_admin = ArchiveBoxAdmin ( )
archivebox_admin . register ( get_user_model ( ) )
2024-04-25 10:56:22 +00:00
archivebox_admin . register ( APIToken )
2024-05-06 14:13:54 +00:00
archivebox_admin . register ( WebhookModel , WebhookAdmin )
2024-03-26 00:46:01 +00:00
archivebox_admin . disable_action ( ' delete_selected ' )
2024-05-06 18:06:42 +00:00
# patch admin with methods to add data views
from admin_data_views . admin import get_app_list , admin_data_index_view , get_admin_data_urls , get_urls
archivebox_admin . get_app_list = get_app_list . __get__ ( archivebox_admin , ArchiveBoxAdmin )
archivebox_admin . admin_data_index_view = admin_data_index_view . __get__ ( archivebox_admin , ArchiveBoxAdmin )
archivebox_admin . get_admin_data_urls = get_admin_data_urls . __get__ ( archivebox_admin , ArchiveBoxAdmin )
archivebox_admin . get_urls = get_urls ( archivebox_admin . get_urls ) . __get__ ( archivebox_admin , ArchiveBoxAdmin )
2021-02-16 20:47:49 +00:00
class ArchiveResultInline ( admin . TabularInline ) :
model = ArchiveResult
2021-02-18 13:04:14 +00:00
class TagInline ( admin . TabularInline ) :
model = Snapshot . tags . through
2021-02-18 07:32:58 +00:00
from django . contrib . admin . helpers import ActionForm
2021-04-10 08:11:32 +00:00
from django . contrib . admin . widgets import AutocompleteSelectMultiple
2024-01-04 03:00:07 +00:00
class AutocompleteTags :
model = Tag
search_fields = [ ' name ' ]
name = ' tags '
2024-03-26 00:46:01 +00:00
remote_field = TagInline
2021-04-10 08:11:32 +00:00
2024-01-04 03:00:07 +00:00
class AutocompleteTagsAdminStub :
name = ' admin '
2021-02-18 07:32:58 +00:00
class SnapshotActionForm ( ActionForm ) :
2021-04-10 08:11:32 +00:00
tags = forms . ModelMultipleChoiceField (
queryset = Tag . objects . all ( ) ,
required = False ,
2024-01-04 03:00:07 +00:00
widget = AutocompleteSelectMultiple (
AutocompleteTags ( ) ,
AutocompleteTagsAdminStub ( ) ,
) ,
2021-04-10 08:11:32 +00:00
)
# TODO: allow selecting actions for specific extractors? is this useful?
# EXTRACTOR_CHOICES = [
# (name, name.title())
# for name, _, _ in get_default_archive_methods()
# ]
# extractor = forms.ChoiceField(
# choices=EXTRACTOR_CHOICES,
# required=False,
# widget=forms.MultileChoiceField(attrs={'class': "form-control"})
# )
2021-02-18 07:32:58 +00:00
2020-10-12 18:47:03 +00:00
2024-03-26 00:46:01 +00:00
@admin.register ( Snapshot , site = archivebox_admin )
2020-11-17 23:42:57 +00:00
class SnapshotAdmin ( SearchResultsAdminMixin , admin . ModelAdmin ) :
2021-04-10 08:12:30 +00:00
list_display = ( ' added ' , ' title_str ' , ' files ' , ' size ' , ' url_str ' )
sort_fields = ( ' title_str ' , ' url_str ' , ' added ' , ' files ' )
readonly_fields = ( ' info ' , ' bookmarked ' , ' added ' , ' updated ' )
2021-04-01 18:00:02 +00:00
search_fields = ( ' id ' , ' url ' , ' timestamp ' , ' title ' , ' tags__name ' )
2021-02-18 13:04:14 +00:00
fields = ( ' timestamp ' , ' url ' , ' title ' , ' tags ' , * readonly_fields )
2021-04-06 06:07:22 +00:00
list_filter = ( ' added ' , ' updated ' , ' tags ' , ' archiveresult__status ' )
2020-06-30 09:56:17 +00:00
ordering = [ ' -added ' ]
2021-04-10 08:11:32 +00:00
actions = [ ' add_tags ' , ' remove_tags ' , ' update_titles ' , ' update_snapshots ' , ' resnapshot_snapshot ' , ' overwrite_snapshots ' , ' delete_snapshots ' ]
2021-02-18 13:04:14 +00:00
autocomplete_fields = [ ' tags ' ]
2021-02-16 20:47:49 +00:00
inlines = [ ArchiveResultInline ]
2021-02-18 13:04:14 +00:00
list_per_page = SNAPSHOTS_PER_PAGE
2020-06-30 09:56:17 +00:00
2021-02-18 07:32:58 +00:00
action_form = SnapshotActionForm
2024-01-09 04:55:30 +00:00
def changelist_view ( self , request , extra_context = None ) :
extra_context = extra_context or { }
return super ( ) . changelist_view ( request , extra_context | GLOBAL_CONTEXT )
2020-12-12 04:03:46 +00:00
def get_urls ( self ) :
urls = super ( ) . get_urls ( )
custom_urls = [
2021-04-10 08:12:30 +00:00
path ( ' grid/ ' , self . admin_site . admin_view ( self . grid_view ) , name = ' grid ' )
2020-12-12 04:03:46 +00:00
]
return custom_urls + urls
2020-09-21 16:50:26 +00:00
def get_queryset ( self , request ) :
2021-02-18 07:32:58 +00:00
self . request = request
2024-01-19 10:44:59 +00:00
return super ( ) . get_queryset ( request ) . prefetch_related ( ' tags ' ) . annotate ( archiveresult_count = Count ( ' archiveresult ' ) )
2020-09-21 16:50:26 +00:00
def tag_list ( self , obj ) :
2020-09-24 20:11:17 +00:00
return ' , ' . join ( obj . tags . values_list ( ' name ' , flat = True ) )
2020-09-21 16:50:26 +00:00
2021-02-18 07:32:58 +00:00
# TODO: figure out a different way to do this, you cant nest forms so this doenst work
# def action(self, obj):
# # csrfmiddlewaretoken: Wa8UcQ4fD3FJibzxqHN3IYrrjLo4VguWynmbzzcPYoebfVUnDovon7GEMYFRgsh0
# # action: update_snapshots
# # select_across: 0
# # _selected_action: 76d29b26-2a88-439e-877c-a7cca1b72bb3
# return format_html(
# '''
# <form action="/admin/core/snapshot/" method="post" onsubmit="e => e.stopPropagation()">
# <input type="hidden" name="csrfmiddlewaretoken" value="{}">
# <input type="hidden" name="_selected_action" value="{}">
# <button name="update_snapshots">Check</button>
# <button name="update_titles">Pull title + favicon</button>
# <button name="update_snapshots">Update</button>
# <button name="overwrite_snapshots">Re-Archive (overwrite)</button>
# <button name="delete_snapshots">Permanently delete</button>
# </form>
# ''',
# csrf.get_token(self.request),
# obj.id,
# )
2021-04-10 08:12:30 +00:00
def info ( self , obj ) :
2020-06-30 09:56:17 +00:00
return format_html (
2021-04-10 08:12:30 +00:00
'''
UUID : < code style = " font-size: 10px; user-select: all " > { } < / code > & nbsp ; & nbsp ;
Timestamp : < code style = " font-size: 10px; user-select: all " > { } < / code > & nbsp ; & nbsp ;
URL Hash : < code style = " font-size: 10px; user-select: all " > { } < / code > < br / >
Archived : { } ( { } files { } ) & nbsp ; & nbsp ;
Favicon : < img src = " {} " style = " height: 20px " / > & nbsp ; & nbsp ;
Status code : { } & nbsp ; & nbsp ;
Server : { } & nbsp ; & nbsp ;
Content type : { } & nbsp ; & nbsp ;
Extension : { } & nbsp ; & nbsp ;
< br / > < br / >
< a href = " /archive/ {} " > View Snapshot index ➡ ️ < / a > & nbsp ; & nbsp ;
< a href = " /admin/core/snapshot/?id__exact= {} " > View actions ⚙ ️ < / a >
''' ,
2021-02-18 13:04:14 +00:00
obj . id ,
obj . timestamp ,
2021-04-10 08:12:30 +00:00
obj . url_hash ,
' ✅ ' if obj . is_archived else ' ❌ ' ,
obj . num_outputs ,
self . size ( obj ) ,
f ' /archive/ { obj . timestamp } /favicon.ico ' ,
obj . status_code or ' ? ' ,
obj . headers and obj . headers . get ( ' Server ' ) or ' ? ' ,
obj . headers and obj . headers . get ( ' Content-Type ' ) or ' ? ' ,
obj . extension or ' ? ' ,
obj . timestamp ,
2021-02-18 13:04:14 +00:00
obj . id ,
2020-06-30 09:56:17 +00:00
)
2024-03-26 00:46:01 +00:00
@admin.display (
description = ' Title ' ,
ordering = ' title ' ,
)
2020-06-30 09:56:17 +00:00
def title_str ( self , obj ) :
canon = obj . as_link ( ) . canonical_outputs ( )
2020-07-28 09:59:54 +00:00
tags = ' ' . join (
2021-04-01 07:31:05 +00:00
format_html ( ' <a href= " /admin/core/snapshot/?tags__id__exact= {} " ><span class= " tag " > {} </span></a> ' , tag . id , tag )
2020-09-21 16:50:26 +00:00
for tag in obj . tags . all ( )
2020-10-31 11:55:27 +00:00
if str ( tag ) . strip ( )
2020-10-07 15:15:56 +00:00
)
2020-06-30 09:56:17 +00:00
return format_html (
' <a href= " / {} " > '
2020-07-28 03:26:45 +00:00
' <img src= " / {} / {} " class= " favicon " onerror= " this.remove() " > '
2020-06-30 09:56:17 +00:00
' </a> '
2020-08-18 13:17:21 +00:00
' <a href= " / {} /index.html " > '
2020-07-28 03:26:45 +00:00
' <b class= " status- {} " > {} </b> '
' </a> ' ,
2020-06-30 09:56:17 +00:00
obj . archive_path ,
2020-07-27 23:29:57 +00:00
obj . archive_path , canon [ ' favicon_path ' ] ,
2020-08-18 13:17:21 +00:00
obj . archive_path ,
2020-07-28 03:26:45 +00:00
' fetched ' if obj . latest_title or obj . title else ' pending ' ,
2020-07-28 09:59:54 +00:00
urldecode ( htmldecode ( obj . latest_title or obj . title or ' ' ) ) [ : 128 ] or ' Pending... '
2020-10-31 11:55:27 +00:00
) + mark_safe ( f ' <span class= " tags " > { tags } </span> ' )
2020-06-30 09:56:17 +00:00
2024-03-26 00:46:01 +00:00
@admin.display (
description = ' Files Saved ' ,
ordering = ' archiveresult_count ' ,
)
2020-06-30 09:56:17 +00:00
def files ( self , obj ) :
2020-11-28 07:12:27 +00:00
return snapshot_icons ( obj )
2020-07-27 23:29:57 +00:00
2021-04-10 08:12:30 +00:00
2024-03-26 00:46:01 +00:00
@admin.display (
ordering = ' archiveresult_count '
)
2020-07-27 23:29:57 +00:00
def size ( self , obj ) :
2021-02-16 20:51:17 +00:00
archive_size = ( Path ( obj . link_dir ) / ' index.html ' ) . exists ( ) and obj . archive_size
2020-10-31 11:56:19 +00:00
if archive_size :
size_txt = printable_filesize ( archive_size )
if archive_size > 52428800 :
size_txt = mark_safe ( f ' <b> { size_txt } </b> ' )
else :
2020-10-31 23:32:43 +00:00
size_txt = mark_safe ( ' <span style= " opacity: 0.3 " >...</span> ' )
2020-07-27 23:29:57 +00:00
return format_html (
' <a href= " / {} " title= " View all files " > {} </a> ' ,
2020-06-30 09:56:17 +00:00
obj . archive_path ,
2020-10-31 11:56:19 +00:00
size_txt ,
2020-06-30 09:56:17 +00:00
)
2021-04-10 08:12:30 +00:00
2024-03-26 00:46:01 +00:00
@admin.display (
description = ' Original URL ' ,
ordering = ' url ' ,
)
2020-06-30 09:56:17 +00:00
def url_str ( self , obj ) :
return format_html (
2021-04-10 08:12:30 +00:00
' <a href= " {} " ><code style= " user-select: all; " > {} </code></a> ' ,
obj . url ,
2020-06-30 09:56:17 +00:00
obj . url ,
)
2019-04-22 17:20:19 +00:00
2021-04-10 08:12:30 +00:00
def grid_view ( self , request , extra_context = None ) :
2020-12-12 04:03:46 +00:00
# cl = self.get_changelist_instance(request)
# Save before monkey patching to restore for changelist list view
saved_change_list_template = self . change_list_template
saved_list_per_page = self . list_per_page
saved_list_max_show_all = self . list_max_show_all
# Monkey patch here plus core_tags.py
2021-01-30 10:35:07 +00:00
self . change_list_template = ' private_index_grid.html '
2021-04-10 08:12:30 +00:00
self . list_per_page = SNAPSHOTS_PER_PAGE
2020-12-12 04:03:46 +00:00
self . list_max_show_all = self . list_per_page
# Call monkey patched view
2021-04-10 08:12:30 +00:00
rendered_response = self . changelist_view ( request , extra_context = extra_context )
2020-12-12 04:03:46 +00:00
# Restore values
2021-03-27 09:12:54 +00:00
self . change_list_template = saved_change_list_template
2020-12-12 04:03:46 +00:00
self . list_per_page = saved_list_per_page
self . list_max_show_all = saved_list_max_show_all
return rendered_response
2021-02-18 07:32:58 +00:00
2021-04-10 08:12:30 +00:00
# for debugging, uncomment this to print all requests:
# def changelist_view(self, request, extra_context=None):
# print('[*] Got request', request.method, request.POST)
# return super().changelist_view(request, extra_context=None)
2021-02-18 07:32:58 +00:00
2024-03-26 00:46:01 +00:00
@admin.action (
description = " Pull "
)
2021-02-18 09:29:24 +00:00
def update_snapshots ( self , request , queryset ) :
2021-02-18 07:32:58 +00:00
archive_links ( [
snapshot . as_link ( )
for snapshot in queryset
] , out_dir = OUTPUT_DIR )
2024-03-26 00:46:01 +00:00
@admin.action (
description = " ⬇️ Title "
)
2021-02-18 09:29:24 +00:00
def update_titles ( self , request , queryset ) :
2021-02-18 07:32:58 +00:00
archive_links ( [
snapshot . as_link ( )
for snapshot in queryset
] , overwrite = True , methods = ( ' title ' , ' favicon ' ) , out_dir = OUTPUT_DIR )
2021-04-10 08:12:30 +00:00
2024-03-26 00:46:01 +00:00
@admin.action (
description = " Re-Snapshot "
)
2021-04-10 08:12:30 +00:00
def resnapshot_snapshot ( self , request , queryset ) :
for snapshot in queryset :
timestamp = datetime . now ( timezone . utc ) . isoformat ( ' T ' , ' seconds ' )
new_url = snapshot . url . split ( ' # ' ) [ 0 ] + f ' # { timestamp } '
add ( new_url , tag = snapshot . tags_str ( ) )
2021-02-18 07:32:58 +00:00
2024-03-26 00:46:01 +00:00
@admin.action (
description = " Reset "
)
2021-02-18 09:29:24 +00:00
def overwrite_snapshots ( self , request , queryset ) :
2021-02-18 07:32:58 +00:00
archive_links ( [
snapshot . as_link ( )
for snapshot in queryset
] , overwrite = True , out_dir = OUTPUT_DIR )
2024-03-26 00:46:01 +00:00
@admin.action (
description = " Delete "
)
2021-02-18 09:29:24 +00:00
def delete_snapshots ( self , request , queryset ) :
2021-02-18 07:32:58 +00:00
remove ( snapshots = queryset , yes = True , delete = True , out_dir = OUTPUT_DIR )
2024-03-26 00:46:01 +00:00
@admin.action (
description = " + "
)
2021-04-10 08:11:32 +00:00
def add_tags ( self , request , queryset ) :
tags = request . POST . getlist ( ' tags ' )
print ( ' [+] Adding tags ' , tags , ' to Snapshots ' , queryset )
2021-03-27 09:27:02 +00:00
for obj in queryset :
2021-04-10 08:11:32 +00:00
obj . tags . add ( * tags )
2021-02-18 07:32:58 +00:00
2024-03-26 00:46:01 +00:00
@admin.action (
description = " – "
)
2021-04-10 08:11:32 +00:00
def remove_tags ( self , request , queryset ) :
tags = request . POST . getlist ( ' tags ' )
print ( ' [-] Removing tags ' , tags , ' to Snapshots ' , queryset )
2021-02-18 07:32:58 +00:00
for obj in queryset :
2021-04-10 08:11:32 +00:00
obj . tags . remove ( * tags )
2021-02-18 07:32:58 +00:00
2020-12-12 04:03:46 +00:00
2019-04-22 17:20:19 +00:00
2019-04-23 01:40:42 +00:00
2021-02-18 07:32:58 +00:00
2024-03-26 00:46:01 +00:00
@admin.register ( Tag , site = archivebox_admin )
2020-10-31 11:55:27 +00:00
class TagAdmin ( admin . ModelAdmin ) :
2021-02-18 07:32:58 +00:00
list_display = ( ' slug ' , ' name ' , ' num_snapshots ' , ' snapshots ' , ' id ' )
2020-10-31 11:55:27 +00:00
sort_fields = ( ' id ' , ' name ' , ' slug ' )
2021-02-18 07:32:58 +00:00
readonly_fields = ( ' id ' , ' num_snapshots ' , ' snapshots ' )
2020-10-31 11:55:27 +00:00
search_fields = ( ' id ' , ' name ' , ' slug ' )
fields = ( * readonly_fields , ' name ' , ' slug ' )
2021-02-18 07:32:58 +00:00
actions = [ ' delete_selected ' ]
2021-02-18 13:04:14 +00:00
ordering = [ ' -id ' ]
2021-02-18 07:32:58 +00:00
def num_snapshots ( self , obj ) :
return format_html (
' <a href= " /admin/core/snapshot/?tags__id__exact= {} " > {} total</a> ' ,
obj . id ,
obj . snapshot_set . count ( ) ,
)
def snapshots ( self , obj ) :
total_count = obj . snapshot_set . count ( )
return mark_safe ( ' <br/> ' . join (
format_html (
' {} <code><a href= " /admin/core/snapshot/ {} /change " ><b>[ {} ]</b></a> {} </code> ' ,
2021-03-01 05:40:41 +00:00
snap . updated . strftime ( ' % Y- % m- %d % H: % M ' ) if snap . updated else ' pending... ' ,
2021-02-18 07:32:58 +00:00
snap . id ,
snap . timestamp ,
snap . url ,
)
for snap in obj . snapshot_set . order_by ( ' -updated ' ) [ : 10 ]
) + ( f ' <br/><a href= " /admin/core/snapshot/?tags__id__exact= { obj . id } " >and { total_count - 10 } more...<a> ' if obj . snapshot_set . count ( ) > 10 else ' ' ) )
2020-07-28 03:26:45 +00:00
2024-03-26 00:46:01 +00:00
@admin.register ( ArchiveResult , site = archivebox_admin )
2021-02-16 11:13:27 +00:00
class ArchiveResultAdmin ( admin . ModelAdmin ) :
2021-04-01 05:39:34 +00:00
list_display = ( ' id ' , ' start_ts ' , ' extractor ' , ' snapshot_str ' , ' tags_str ' , ' cmd_str ' , ' status ' , ' output_str ' )
2021-02-16 11:13:27 +00:00
sort_fields = ( ' start_ts ' , ' extractor ' , ' status ' )
2021-04-01 05:39:34 +00:00
readonly_fields = ( ' id ' , ' uuid ' , ' snapshot_str ' , ' tags_str ' )
2021-02-16 20:47:49 +00:00
search_fields = ( ' id ' , ' uuid ' , ' snapshot__url ' , ' extractor ' , ' output ' , ' cmd_version ' , ' cmd ' , ' snapshot__timestamp ' )
2021-04-01 05:39:34 +00:00
fields = ( * readonly_fields , ' snapshot ' , ' extractor ' , ' status ' , ' start_ts ' , ' end_ts ' , ' output ' , ' pwd ' , ' cmd ' , ' cmd_version ' )
2021-02-18 13:04:14 +00:00
autocomplete_fields = [ ' snapshot ' ]
2021-02-16 20:47:49 +00:00
list_filter = ( ' status ' , ' extractor ' , ' start_ts ' , ' cmd_version ' )
ordering = [ ' -start_ts ' ]
list_per_page = SNAPSHOTS_PER_PAGE
2024-03-26 00:46:01 +00:00
@admin.display (
description = ' snapshot '
)
2021-02-16 20:47:49 +00:00
def snapshot_str ( self , obj ) :
return format_html (
' <a href= " /archive/ {} /index.html " ><b><code>[ {} ]</code></b></a><br/> '
' <small> {} </small> ' ,
obj . snapshot . timestamp ,
obj . snapshot . timestamp ,
obj . snapshot . url [ : 128 ] ,
)
2024-03-26 00:46:01 +00:00
@admin.display (
description = ' tags '
)
2021-04-01 05:39:34 +00:00
def tags_str ( self , obj ) :
return obj . snapshot . tags_str ( )
2021-02-16 20:47:49 +00:00
def cmd_str ( self , obj ) :
return format_html (
' <pre> {} </pre> ' ,
' ' . join ( obj . cmd ) if isinstance ( obj . cmd , list ) else str ( obj . cmd ) ,
)
def output_str ( self , obj ) :
return format_html (
2021-02-18 07:32:58 +00:00
' <a href= " /archive/ {} / {} " class= " output-link " >↗️</a><pre> {} </pre> ' ,
2021-02-16 20:47:49 +00:00
obj . snapshot . timestamp ,
obj . output if ( obj . status == ' succeeded ' ) and obj . extractor not in ( ' title ' , ' archive_org ' ) else ' index.html ' ,
obj . output ,
)