2020-04-02 18:29:27 +00:00
import os
import tempfile
import zipfile
import shutil
2019-03-19 19:19:23 +00:00
from django . conf . urls import url
2016-08-10 20:23:17 +00:00
from django . contrib import admin
2019-07-09 22:08:28 +00:00
from django . contrib import messages
2019-03-19 19:19:23 +00:00
from django . http import HttpResponseRedirect
from django . urls import reverse
from django . utils . html import format_html
2016-10-04 20:36:08 +00:00
from guardian . admin import GuardedModelAdmin
2023-08-24 19:02:30 +00:00
from django . contrib . auth . admin import UserAdmin as BaseUserAdmin
from django . contrib . auth . models import User
2017-07-21 20:48:01 +00:00
2018-07-30 16:44:16 +00:00
from app . models import PluginDatum
2017-07-21 20:48:01 +00:00
from app . models import Preset
2019-03-19 19:19:23 +00:00
from app . models import Plugin
2023-08-24 19:02:30 +00:00
from app . models import Profile
2020-04-02 18:29:27 +00:00
from app . plugins import get_plugin_by_name , enable_plugin , disable_plugin , delete_plugin , valid_plugin , \
get_plugins_persistent_path , clear_plugins_cache , init_plugins
2023-03-23 17:31:07 +00:00
from . models import Project , Task , Setting , Theme
2017-11-06 22:43:47 +00:00
from django import forms
from codemirror2 . widgets import CodeMirrorEditor
2020-04-02 18:29:27 +00:00
from webodm import settings
from django . core . files . uploadedfile import InMemoryUploadedFile
2020-12-21 19:18:43 +00:00
from django . utils . translation import gettext_lazy as _ , gettext
2016-10-04 20:36:08 +00:00
2023-04-26 22:19:29 +00:00
class ProjectAdmin ( GuardedModelAdmin ) :
list_display = ( ' id ' , ' name ' , ' owner ' , ' created_at ' , ' tasks_count ' , ' tags ' )
list_filter = ( ' owner ' , )
search_fields = ( ' id ' , ' name ' , ' owner__username ' )
admin . site . register ( Project , ProjectAdmin )
2017-07-10 17:32:36 +00:00
2022-08-22 20:12:53 +00:00
2017-07-10 17:32:36 +00:00
class TaskAdmin ( admin . ModelAdmin ) :
def has_add_permission ( self , request ) :
return False
2022-08-22 20:12:53 +00:00
2023-11-06 16:21:10 +00:00
list_display = ( ' id ' , ' name ' , ' project ' , ' processing_node ' , ' created_at ' , ' status ' , ' last_error ' )
2022-08-22 20:12:53 +00:00
list_filter = ( ' status ' , ' project ' , )
2023-11-06 16:21:10 +00:00
search_fields = ( ' id ' , ' name ' , ' project__name ' )
2022-08-22 20:12:53 +00:00
2017-07-10 17:32:36 +00:00
admin . site . register ( Task , TaskAdmin )
2016-10-13 20:28:32 +00:00
2017-07-21 20:48:01 +00:00
admin . site . register ( Preset , admin . ModelAdmin )
2017-11-06 18:45:14 +00:00
class SettingAdmin ( admin . ModelAdmin ) :
2017-11-06 22:43:47 +00:00
2017-11-06 18:45:14 +00:00
def has_add_permission ( self , request ) :
# if there's already an entry, do not allow adding
count = Setting . objects . all ( ) . count ( )
return count == 0
2022-08-22 20:12:53 +00:00
2017-11-06 18:45:14 +00:00
admin . site . register ( Setting , SettingAdmin )
2017-11-06 22:43:47 +00:00
class ThemeModelForm ( forms . ModelForm ) :
2020-12-18 17:32:44 +00:00
css = forms . CharField ( help_text = _ ( " Enter custom CSS " ) ,
label = _ ( " CSS " ) ,
2017-11-06 22:43:47 +00:00
required = False ,
widget = CodeMirrorEditor ( options = { ' mode ' : ' css ' , ' lineNumbers ' : True } ) )
2020-12-18 17:32:44 +00:00
html_before_header = forms . CharField ( help_text = _ ( " HTML that will be displayed above site header " ) ,
label = _ ( " HTML (before header) " ) ,
2017-11-06 22:43:47 +00:00
required = False ,
widget = CodeMirrorEditor ( options = { ' mode ' : ' xml ' , ' lineNumbers ' : True } ) )
2020-12-18 17:32:44 +00:00
html_after_header = forms . CharField ( help_text = _ ( " HTML that will be displayed after site header " ) ,
label = _ ( " HTML (after header) " ) ,
2017-11-06 22:43:47 +00:00
required = False ,
widget = CodeMirrorEditor ( options = { ' mode ' : ' xml ' , ' lineNumbers ' : True } ) )
2020-12-18 21:54:00 +00:00
html_after_body = forms . CharField ( help_text = _ ( " HTML that will be displayed after the body tag " ) ,
2020-12-18 17:32:44 +00:00
label = _ ( " HTML (after body) " ) ,
2017-11-06 22:43:47 +00:00
required = False ,
2022-08-22 20:12:53 +00:00
widget = CodeMirrorEditor ( options = { ' mode ' : ' xml ' , ' lineNumbers ' : True } ) )
html_footer = forms . CharField ( help_text = _ (
" HTML that will be displayed in the footer. You can also use the special tags such as {ORGANIZATION} and {YEAR} . " ) ,
label = _ ( " HTML (footer) " ) ,
required = False ,
widget = CodeMirrorEditor ( options = { ' mode ' : ' xml ' , ' lineNumbers ' : True } ) )
2017-11-06 22:43:47 +00:00
class Meta :
model = Theme
fields = ' __all__ '
class ThemeAdmin ( admin . ModelAdmin ) :
form = ThemeModelForm
2017-11-07 21:48:27 +00:00
2018-07-30 16:44:16 +00:00
admin . site . register ( Theme , ThemeAdmin )
2019-03-19 19:19:23 +00:00
admin . site . register ( PluginDatum , admin . ModelAdmin )
2018-07-30 16:44:16 +00:00
2019-03-19 19:19:23 +00:00
class PluginAdmin ( admin . ModelAdmin ) :
list_display = ( " name " , " description " , " version " , " author " , " enabled " , " plugin_actions " )
2022-08-22 20:12:53 +00:00
readonly_fields = ( " name " , )
2020-03-31 20:32:14 +00:00
change_list_template = " admin/change_list_plugin.html "
2019-03-19 19:19:23 +00:00
def has_add_permission ( self , request ) :
return False
2022-08-22 20:12:53 +00:00
2019-03-19 19:19:23 +00:00
def has_delete_permission ( self , request , obj = None ) :
return False
def description ( self , obj ) :
2019-03-29 17:12:43 +00:00
manifest = get_plugin_by_name ( obj . name , only_active = False , refresh_cache_if_none = True ) . get_manifest ( )
2020-12-21 19:18:43 +00:00
return _ ( manifest . get ( ' description ' , ' ' ) )
description . short_description = _ ( " Description " )
2019-03-19 19:19:23 +00:00
def version ( self , obj ) :
2019-03-29 17:12:43 +00:00
manifest = get_plugin_by_name ( obj . name , only_active = False , refresh_cache_if_none = True ) . get_manifest ( )
2019-03-19 19:19:23 +00:00
return manifest . get ( ' version ' , ' ' )
2020-12-21 19:18:43 +00:00
version . short_description = _ ( " Version " )
2019-03-19 19:19:23 +00:00
def author ( self , obj ) :
2019-03-29 17:12:43 +00:00
manifest = get_plugin_by_name ( obj . name , only_active = False , refresh_cache_if_none = True ) . get_manifest ( )
2019-03-19 19:19:23 +00:00
return manifest . get ( ' author ' , ' ' )
2020-12-21 19:18:43 +00:00
author . short_description = _ ( " Author " )
2019-03-19 19:19:23 +00:00
def get_urls ( self ) :
urls = super ( ) . get_urls ( )
custom_urls = [
url (
r ' ^(?P<plugin_name>.+)/enable/$ ' ,
self . admin_site . admin_view ( self . plugin_enable ) ,
name = ' plugin-enable ' ,
) ,
url (
r ' ^(?P<plugin_name>.+)/disable/$ ' ,
self . admin_site . admin_view ( self . plugin_disable ) ,
name = ' plugin-disable ' ,
) ,
2020-03-31 18:02:19 +00:00
url (
r ' ^(?P<plugin_name>.+)/delete/$ ' ,
self . admin_site . admin_view ( self . plugin_delete ) ,
name = ' plugin-delete ' ,
) ,
2020-03-31 20:32:14 +00:00
url (
r ' ^actions/upload/$ ' ,
self . admin_site . admin_view ( self . plugin_upload ) ,
name = ' plugin-upload ' ,
) ,
2019-03-19 19:19:23 +00:00
]
return custom_urls + urls
def plugin_enable ( self , request , plugin_name , * args , * * kwargs ) :
2019-07-09 22:08:28 +00:00
try :
2021-07-01 19:30:45 +00:00
p = enable_plugin ( plugin_name )
if p . requires_restart ( ) :
2022-08-22 20:12:53 +00:00
messages . warning ( request , _ ( " Restart required. Please restart WebODM to enable %(plugin)s " ) % {
' plugin ' : plugin_name } )
2019-07-09 22:08:28 +00:00
except Exception as e :
2022-08-22 20:12:53 +00:00
messages . warning ( request , _ ( " Cannot enable plugin %(plugin)s : %(message)s " ) % { ' plugin ' : plugin_name ,
' message ' : str ( e ) } )
2019-07-09 22:08:28 +00:00
2019-03-19 19:19:23 +00:00
return HttpResponseRedirect ( reverse ( ' admin:app_plugin_changelist ' ) )
def plugin_disable ( self , request , plugin_name , * args , * * kwargs ) :
2019-07-09 22:08:28 +00:00
try :
2021-07-02 00:11:33 +00:00
p = disable_plugin ( plugin_name )
if p . requires_restart ( ) :
2022-08-22 20:12:53 +00:00
messages . warning ( request , _ ( " Restart required. Please restart WebODM to fully disable %(plugin)s " ) % {
' plugin ' : plugin_name } )
2019-07-09 22:08:28 +00:00
except Exception as e :
2022-08-22 20:12:53 +00:00
messages . warning ( request , _ ( " Cannot disable plugin %(plugin)s : %(message)s " ) % { ' plugin ' : plugin_name ,
' message ' : str ( e ) } )
2019-07-09 22:08:28 +00:00
2019-03-19 19:19:23 +00:00
return HttpResponseRedirect ( reverse ( ' admin:app_plugin_changelist ' ) )
2020-03-31 18:02:19 +00:00
def plugin_delete ( self , request , plugin_name , * args , * * kwargs ) :
try :
delete_plugin ( plugin_name )
except Exception as e :
2022-08-22 20:12:53 +00:00
messages . warning ( request , _ ( " Cannot delete plugin %(plugin)s : %(message)s " ) % { ' plugin ' : plugin_name ,
' message ' : str ( e ) } )
2020-03-31 18:02:19 +00:00
return HttpResponseRedirect ( reverse ( ' admin:app_plugin_changelist ' ) )
2020-03-31 20:32:14 +00:00
def plugin_upload ( self , request , * args , * * kwargs ) :
2020-04-02 18:29:27 +00:00
file = request . FILES . get ( ' file ' )
if file is not None :
# Save to tmp dir
tmp_zip_path = tempfile . mktemp ( ' plugin.zip ' , dir = settings . MEDIA_TMP )
tmp_extract_path = tempfile . mkdtemp ( ' plugin ' , dir = settings . MEDIA_TMP )
try :
with open ( tmp_zip_path , ' wb+ ' ) as fd :
if isinstance ( file , InMemoryUploadedFile ) :
for chunk in file . chunks ( ) :
fd . write ( chunk )
else :
with open ( file . temporary_file_path ( ) , ' rb ' ) as f :
shutil . copyfileobj ( f , fd )
# Extract
with zipfile . ZipFile ( tmp_zip_path , " r " ) as zip_h :
zip_h . extractall ( tmp_extract_path )
# Validate
folders = os . listdir ( tmp_extract_path )
if len ( folders ) != 1 :
raise ValueError ( " The plugin has more than 1 root directory (it should have only one) " )
plugin_name = folders [ 0 ]
plugin_path = os . path . join ( tmp_extract_path , plugin_name )
if not valid_plugin ( plugin_path ) :
2022-08-22 20:12:53 +00:00
raise ValueError (
" This doesn ' t look like a plugin. Are plugin.py and manifest.json in the proper place? " )
2020-04-02 18:29:27 +00:00
if os . path . exists ( get_plugins_persistent_path ( plugin_name ) ) :
2022-08-22 20:12:53 +00:00
raise ValueError (
" A plugin with the name {} already exist. Please remove it before uploading one with the same name. " . format (
plugin_name ) )
2020-04-02 18:29:27 +00:00
# Move
shutil . move ( plugin_path , get_plugins_persistent_path ( ) )
# Initialize
clear_plugins_cache ( )
init_plugins ( )
2021-07-01 19:30:45 +00:00
messages . info ( request , _ ( " Plugin added successfully " ) )
2020-04-02 18:29:27 +00:00
except Exception as e :
2021-07-01 19:30:45 +00:00
messages . warning ( request , _ ( " Cannot load plugin: %(message)s " ) % { ' message ' : str ( e ) } )
2020-04-02 18:29:27 +00:00
if os . path . exists ( tmp_zip_path ) :
os . remove ( tmp_zip_path )
if os . path . exists ( tmp_extract_path ) :
shutil . rmtree ( tmp_extract_path )
else :
2021-07-01 19:30:45 +00:00
messages . error ( request , _ ( " You need to upload a zip file " ) )
2020-04-02 18:29:27 +00:00
2020-03-31 20:32:14 +00:00
return HttpResponseRedirect ( reverse ( ' admin:app_plugin_changelist ' ) )
2019-03-19 19:19:23 +00:00
def plugin_actions ( self , obj ) :
2020-03-31 18:02:19 +00:00
plugin = get_plugin_by_name ( obj . name , only_active = False )
2019-03-19 19:19:23 +00:00
return format_html (
2021-07-01 19:30:45 +00:00
' <a class= " button " href= " {} " {} > {} </a> '
' <a class= " button " href= " {} " {} > {} </a> '
2022-08-22 20:12:53 +00:00
+ (
' <a class= " button " href= " {} " onclick= " return confirm( \' Are you sure you want to delete {} ? \' ) " ><i class= " fa fa-trash " ></i></a> ' if not plugin . is_persistent ( ) else ' ' )
2020-03-31 18:02:19 +00:00
,
2019-03-19 19:19:23 +00:00
reverse ( ' admin:plugin-disable ' , args = [ obj . pk ] ) if obj . enabled else ' # ' ,
' disabled ' if not obj . enabled else ' ' ,
2021-07-01 19:30:45 +00:00
_ ( ' Disable ' ) ,
2019-03-19 19:19:23 +00:00
reverse ( ' admin:plugin-enable ' , args = [ obj . pk ] ) if not obj . enabled else ' # ' ,
' disabled ' if obj . enabled else ' ' ,
2021-07-01 19:30:45 +00:00
_ ( ' Enable ' ) ,
2020-03-31 18:02:19 +00:00
reverse ( ' admin:plugin-delete ' , args = [ obj . pk ] ) ,
obj . name
2019-03-19 19:19:23 +00:00
)
2020-12-21 19:18:43 +00:00
plugin_actions . short_description = _ ( ' Actions ' )
2019-03-19 19:19:23 +00:00
plugin_actions . allow_tags = True
admin . site . register ( Plugin , PluginAdmin )
2023-08-24 19:02:30 +00:00
class ProfileInline ( admin . StackedInline ) :
model = Profile
can_delete = False
class UserAdmin ( BaseUserAdmin ) :
inlines = [ ProfileInline ]
# Re-register UserAdmin
admin . site . unregister ( User )
admin . site . register ( User , UserAdmin )