Gitlab CSE Unil
Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
M. Chardon
ADIM
Commits
8a856559
Commit
8a856559
authored
May 18, 2015
by
Julien Furrer
Browse files
Added support for Trusted Third Party authentication and authorization
parent
051d7fc1
Changes
26
Hide whitespace changes
Inline
Side-by-side
.gitignore
View file @
8a856559
...
...
@@ -8,4 +8,3 @@
**/*.old.jpg
screen_session_cmd
adim_project/adim/models/annotables.py
View file @
8a856559
...
...
@@ -122,7 +122,7 @@ class AnObj(models.Model):
ordering
=
[
"-id"
]
def
__unicode__
(
self
):
return
u
"{}"
.
format
(
self
.
name
)
return
"{}"
.
format
(
self
.
name
)
def
save
(
self
,
*
args
,
**
kwargs
):
"""
...
...
adim_project/adim/permissions.py
View file @
8a856559
# coding=utf-8
from
__future__
import
unicode_literals
from
django.conf
import
settings
from
django.core.exceptions
import
PermissionDenied
from
django.http.response
import
HttpResponseRedirect
from
django.shortcuts
import
render
from
django.core.cache
import
cache
import
sys
from
.models
import
AnObjMembership
SHARING_MODE_NONE
=
0
SHARING_MODE_MANUAL
=
1
SHARING_MODE_REGKEY
=
4
SHARING_MODE_AAIRULES
=
8
SHARING_MODE_MOODLELTI
=
16
SHARING_MODE_TTP_MOODLE
=
16
SHARING_MODE_TTP_TOTO
=
24
SHARING_MODE_TTP_MY_TTP
=
32
class
PermissionClass
(
object
):
...
...
@@ -19,6 +26,7 @@ class PermissionClass(object):
and ``check_revocation``methods
"""
has_interactive_registration
=
False
ttp
=
False
@
classmethod
def
get_interactive_registration_response
(
cls
,
request
,
anobj
):
...
...
@@ -158,8 +166,101 @@ class AAIRules(PermissionClass):
pass
class
MoodleLTI
(
PermissionClass
):
pass
class
ATTP
(
PermissionClass
):
has_interactive_registration
=
True
ttp
=
True
ttp_id
=
'moodle'
@
classmethod
def
set_attp_status
(
cls
,
request
,
anobj
,
status
):
key
=
"attp_{ttp_id}{user_id}{uuid}"
.
format
(
ttp_id
=
cls
.
ttp_id
,
user_id
=
request
.
user
.
id
,
uuid
=
anobj
.
uuid
[:
12
]
)
cache
.
set
(
key
,
status
,
settings
.
ATTP
[
'OPTIONS'
][
'CACHE_TIMEOUT'
])
return
status
@
classmethod
def
get_attp_status
(
cls
,
request
,
anobj
):
key
=
"attp_{ttp_id}{user_id}{uuid}"
.
format
(
ttp_id
=
cls
.
ttp_id
,
user_id
=
request
.
user
.
id
,
uuid
=
anobj
.
uuid
[:
12
]
)
status
=
cache
.
get
(
key
)
return
status
@
classmethod
def
clear_attp_status
(
cls
,
request
,
anobj
):
# key = "attp_{ttp_id}{user_id}{uuid}".format(
# ttp_id=cls.ttp_id, user_id=request.user.id,
# uuid=anobj.uuid[:12]
# )
session_key
=
"anobj_{}"
.
format
(
anobj
.
uuid
[:
12
])
try
:
del
(
request
.
session
[
session_key
])
except
KeyError
:
pass
@
classmethod
def
check_registration
(
cls
,
request
,
anobj
):
pass
@
classmethod
def
check_revocation
(
cls
,
request
,
anobj
):
pass
@
classmethod
def
check_permission
(
cls
,
request
,
anobj
):
perm_status
=
cls
.
get_attp_status
(
request
,
anobj
)
if
perm_status
is
None
:
raise
PermissionDenied
()
elif
perm_status
==
'denied'
:
if
not
anobj
.
locked
:
# Revoke only if anobj is not locked
cls
.
_revoke_user
(
request
.
user
,
anobj
)
cls
.
clear_attp_status
(
request
,
anobj
)
raise
PermissionDenied
()
else
:
if
request
.
user
not
in
anobj
.
members
.
all
():
cls
.
_register_user
(
request
.
user
,
anobj
)
# Check ownership
owners
=
anobj
.
owners
.
all
()
if
request
.
user
in
owners
:
if
perm_status
!=
'owner'
and
len
(
owners
)
>
1
:
# is owner, but shouldn't -> remove only if not last one
anobj
.
owners
.
remove
(
request
.
user
)
else
:
if
perm_status
==
'owner'
:
# is not owner, but should -> add
anobj
.
owners
.
add
(
request
.
user
)
# cls.clear_attp_status(request, anobj)
return
True
@
classmethod
def
get_interactive_registration_response
(
cls
,
request
,
anobj
):
if
cls
.
get_attp_status
(
request
,
anobj
)
is
None
:
check_url
=
settings
.
ATTP
.
get
(
cls
.
ttp_id
,
{}).
get
(
'CHECK_URL'
)
return
HttpResponseRedirect
(
check_url
.
format
(
uuid
=
anobj
.
uuid
))
else
:
cls
.
clear_attp_status
(
request
,
anobj
)
raise
PermissionDenied
()
class
MoodleTTP
(
ATTP
):
ttp_id
=
'moodle'
class
TotoTTP
(
ATTP
):
ttp_id
=
'toto'
class
MyTTP
(
ATTP
):
ttp_id
=
'my_ttp'
PERMISSION_CLASSES
=
{
...
...
@@ -167,7 +268,9 @@ PERMISSION_CLASSES = {
SHARING_MODE_MANUAL
:
IsMember
,
SHARING_MODE_REGKEY
:
RegistrationKey
,
SHARING_MODE_AAIRULES
:
AAIRules
,
SHARING_MODE_MOODLELTI
:
MoodleLTI
SHARING_MODE_TTP_MOODLE
:
MoodleTTP
,
SHARING_MODE_TTP_TOTO
:
TotoTTP
,
SHARING_MODE_TTP_MY_TTP
:
MyTTP
}
...
...
@@ -175,6 +278,18 @@ def get_permission_class(sharing_mode):
return
PERMISSION_CLASSES
.
get
(
sharing_mode
)
def
get_ttp_sharing_mode
(
ttp_id
=
''
):
"""
Return the sharing mode value for the given ttp_id
:param ttp_id:
:return:
"""
attr_name
=
'SHARING_MODE_TTP_{}'
.
format
(
ttp_id
.
upper
())
print
attr_name
mode
=
getattr
(
sys
.
modules
[
__name__
],
attr_name
,
0
)
return
mode
def
check_anobj_permission
(
request
,
anobj
):
p
=
get_permission_class
(
anobj
.
sharing_mode
)
if
p
is
not
None
:
...
...
adim_project/adim/urls.py
View file @
8a856559
...
...
@@ -6,7 +6,7 @@ from rest_framework.routers import DefaultRouter
from
rest_framework_nested
import
routers
from
rest_framework
import
urls
as
rest_framework_urls
from
adim.views
import
AnObjViewSet
,
SharedAnObjViewSet
,
AnnotationViewSet
,
SharedAnnotationViewSet
,
UserViewSet
from
adim.views
import
AnObjViewSet
,
UAnObjViewSet
,
SharedAnObjViewSet
,
AnnotationViewSet
,
SharedAnnotationViewSet
,
UserViewSet
# Default router, as wee need the 'root-api' name
...
...
@@ -14,6 +14,7 @@ d_router = DefaultRouter()
router
=
routers
.
SimpleRouter
()
router
.
register
(
r
'anobjs'
,
AnObjViewSet
,
base_name
=
'anobjs'
)
router
.
register
(
r
'uanobjs'
,
UAnObjViewSet
,
base_name
=
'uanobjs'
)
router
.
register
(
r
'shared/anobjs'
,
SharedAnObjViewSet
,
base_name
=
'shared-anobjs'
)
router
.
register
(
r
'annotations'
,
AnnotationViewSet
,
base_name
=
'annotations'
)
router
.
register
(
r
'users'
,
UserViewSet
,
base_name
=
'users'
)
...
...
adim_project/adim/views.py
View file @
8a856559
...
...
@@ -114,6 +114,13 @@ class AnObjViewSet(viewsets.ModelViewSet):
return
Response
({
'publish_mode'
:
membership
.
publish_mode
})
class
UAnObjViewSet
(
AnObjViewSet
):
"""
Accessing AnObj by uuid. extents AnObj
"""
lookup_field
=
'uuid'
class
SharedAnObjViewSet
(
AnObjViewSet
):
"""
ViewSet for shared AnObj, requested by a user
...
...
adim_project/adim_app/forms.py
View file @
8a856559
...
...
@@ -9,3 +9,4 @@ class UploadImageFileForm(forms.Form):
for upload
"""
image_file
=
forms
.
ImageField
(
allow_empty_file
=
False
)
name
=
forms
.
CharField
(
max_length
=
125
,
required
=
False
)
\ No newline at end of file
adim_project/adim_app/static/_src/adim/helper/fileUploader.js
View file @
8a856559
...
...
@@ -66,6 +66,7 @@ function($){
onStart
:
null
,
processstart
:
null
,
autoUpload
:
true
,
sequentialUploads
:
false
,
add
:
null
// showAlert: function(errId) {},
// clearAlert: function()
...
...
@@ -85,7 +86,8 @@ function($){
dropZone
:
params
.
dropZone
,
autoUpload
:
params
.
autoUpload
,
done
:
function
(
e
,
data
)
{
if
(
!
data
.
result
.
error
&&
data
.
result
.
next
)
{
if
(
$fileuploadElem
.
fileupload
(
'
active
'
)
===
1
&&
!
data
.
result
.
error
&&
data
.
result
.
next
)
{
document
.
location
.
assign
(
data
.
result
.
next
);
}
},
...
...
adim_project/adim_app/static/_src/anobj-mgr/views/anobj/properties-panel.js
View file @
8a856559
...
...
@@ -35,7 +35,7 @@ define([
SHARING_MODE_MANUAL
=
1
,
SHARING_MODE_REGKEY
=
4
,
SHARING_MODE_AAIRULES
=
8
,
SHARING_MODE_MOODLE
LTI
=
16
;
SHARING_MODE_MOODLE
TTP
=
16
;
return
Backbone
.
View
.
extend
({
...
...
@@ -200,6 +200,14 @@ define([
// Reset controls state
this
.
$
(
"
input[name=aom-prop-new-member]
"
).
val
(
""
);
this
.
$
(
"
.aom-shm-ctrl-members-remove-but
"
).
attr
(
"
disabled
"
,
"
disabled
"
);
},
'
attp_label
'
:
function
(
$el
,
model
,
opts
)
{
if
(
opts
.
url
&&
opts
.
label
)
{
$el
.
attr
(
"
href
"
,
opts
.
url
).
text
(
opts
.
label
);
}
else
{
$el
.
closest
(
'
.aom-prop-shm
'
).
addClass
(
'
hidden
'
);
}
}
};
...
...
adim_project/adim_app/views.py
View file @
8a856559
...
...
@@ -3,7 +3,11 @@ from __future__ import unicode_literals
import
json
import
os
from
django.contrib.auth.views
import
redirect_to_login
from
django.views.decorators.csrf
import
csrf_exempt
import
ldap
import
logging
from
django.conf
import
settings
from
django.core.exceptions
import
PermissionDenied
from
django.core.urlresolvers
import
reverse
...
...
@@ -11,19 +15,25 @@ from django.core.cache import cache
from
django.db.models
import
Q
from
django.http.response
import
HttpResponse
,
HttpResponseBadRequest
,
HttpResponseRedirect
,
Http404
,
\
HttpResponseForbidden
from
django.shortcuts
import
render
,
get_object_or_404
from
django.shortcuts
import
render
,
get_object_or_404
,
resolve_url
from
django.contrib.auth.decorators
import
login_required
from
django.contrib.auth.models
import
User
from
django.views.decorators.http
import
require_POST
from
django.views.decorators.cache
import
cache_control
from
adim.models
import
AnObj
,
AnObjMembership
from
adim.permissions
import
get_permission_class
,
has_anobj_access
,
SHARING_MODE_NONE
from
adim.permissions
import
get_permission_class
,
has_anobj_access
,
get_ttp_sharing_mode
,
SHARING_MODE_NONE
from
adim_ttp.decorators
import
attp_login
from
adim_utils.decorators
import
clear_function_cache
from
.forms
import
UploadImageFileForm
from
sendfile
import
sendfile
from
.utils
import
add_image_border
,
create_image_thumbnail
logger
=
logging
.
getLogger
(
__name__
)
def
home
(
request
):
"""
Home page
...
...
@@ -127,7 +137,7 @@ def annotate_new(request):
return
render
(
request
,
"adim/annotation_new.html"
,
{})
@
login_required
#
@login_required
def
annotate
(
request
,
anobj_uuid
=
None
):
"""
Annotation page
...
...
@@ -135,9 +145,9 @@ def annotate(request, anobj_uuid=None):
:param anobj_uuid:
:return:
"""
context
=
{
'membership'
:
False
}
# ----- Some preliminary validations
if
anobj_uuid
is
None
or
len
(
anobj_uuid
)
<
8
:
raise
Http404
()
try
:
anobj
=
AnObj
.
objects
.
select_related
(
'owner'
).
get
(
uuid__startswith
=
anobj_uuid
)
...
...
@@ -148,44 +158,61 @@ def annotate(request, anobj_uuid=None):
if
len
(
anobj_uuid
)
<
32
:
return
HttpResponseRedirect
(
reverse
(
'adim_app:annotate'
,
kwargs
=
{
'anobj_uuid'
:
anobj
.
uuid
}))
# ----- Login check. Not using decorator so we can delegate to Trusted Third Party if needed
permission_class
=
get_permission_class
(
anobj
.
sharing_mode
)
if
request
.
user
.
is_anonymous
():
if
permission_class
and
permission_class
.
ttp
:
check_url
=
settings
.
ATTP
.
get
(
permission_class
.
ttp_id
,
{}).
get
(
'CHECK_URL'
)
return
HttpResponseRedirect
(
check_url
.
format
(
uuid
=
anobj
.
uuid
))
else
:
return
redirect_to_login
(
resolve_url
(
'adim_app:annotate'
,
anobj_uuid
=
anobj
.
uuid
))
# ----- Build context
context
=
{
'membership'
:
False
}
# is_owner = request.user == anobj.owner
# is_owner = request.user in anobj.owners.all()
is_owner
=
anobj
.
is_owned
(
request
.
user
.
id
)
context
.
update
({
'is_owner'
:
is_owner
})
# Detailed check for permissions
#
-----
Detailed check for permissions
membership
=
None
if
is_owner
:
if
is_owner
and
not
(
permission_class
and
permission_class
.
ttp
):
# User is owner and anobj is not shared via Trusted Third Party
if
anobj
.
sharing_mode
!=
SHARING_MODE_NONE
:
membership
,
_
=
AnObjMembership
.
objects
.
get_or_create
(
anobj
=
anobj
,
user
=
request
.
user
)
else
:
# User is guest
perm_class
=
get_permission_class
(
anobj
.
sharing_mode
)
if
perm_class
is
None
:
# User is guest or owner and anobj shared via TTP
if
permission_class
is
None
:
# AnObj not shared
raise
Http404
()
# raise PermissionDenied()
elif
not
perm_class
.
has_permission
(
request
,
anobj
):
# AnObj shared but user has no permission
if
perm_class
.
has_interactive_registration
:
elif
not
perm
ission
_class
.
has_permission
(
request
,
anobj
):
# AnObj shared but user has no permission
yet
if
perm
ission
_class
.
has_interactive_registration
:
# Interactive registration exists, call it
return
perm_class
.
get_interactive_registration_response
(
request
,
anobj
)
# No interactive registration
return
permission_class
.
get_interactive_registration_response
(
request
,
anobj
)
# No interactive registration for this sharing model, deny access
raise
PermissionDenied
()
else
:
# AnObj shared, user registred
, go on
# AnObj shared, user
authorized and
regist
e
red
pass
# TTP permission may have changed ownership
if
permission_class
.
ttp
:
clear_function_cache
(
f
=
'adim.models.annotablesis_owned'
,
args
=
(
anobj
,
request
.
user
.
id
))
is_owner
=
anobj
.
is_owned
(
request
.
user
.
id
)
membership
=
AnObjMembership
.
objects
.
get
(
anobj
=
anobj
,
user
=
request
.
user
)
# Interactive registration may post credentials, if so redirect to current view with GET method
if
request
.
method
==
'POST'
:
return
HttpResponseRedirect
(
reverse
(
'adim_app:annotate'
,
kwargs
=
{
'anobj_uuid'
:
anobj_uuid
}))
context
.
update
({
'is_owner'
:
is_owner
,
'membership'
:
membership
,
'anobj'
:
anobj
})
...
...
@@ -210,14 +237,14 @@ def annotate(request, anobj_uuid=None):
)
)
})
return
render
(
request
,
"adim/annotation.html"
,
context
)
@
csrf_exempt
@
attp_login
@
login_required
def
upload_file
(
request
,
anobj_uuid
=
None
):
"""
-- inspired by: https://github.com/miki725/Django-jQuery-File-Uploader-Integration-demo/blob/master/upload/views.py
:param request:
:return:
...
...
@@ -228,6 +255,8 @@ def upload_file(request, anobj_uuid=None):
response_type
=
"application/json"
response_data
=
{}
user
=
request
.
user
# if request.user.is_authenticated() else moodle_meta.get('user')
form
=
UploadImageFileForm
(
request
.
POST
,
request
.
FILES
)
if
form
.
is_valid
():
image_file
=
request
.
FILES
[
'image_file'
]
...
...
@@ -236,24 +265,43 @@ def upload_file(request, anobj_uuid=None):
'error'
:
file_response
.
get
(
'error'
),
'files'
:
[
file_response
],
})
anobj_name
=
form
.
cleaned_data
[
'name'
]
# Create AnObj
if
not
file_response
[
'error'
]:
try
:
anobj
=
_get_anobj
(
request
,
anobj_uuid
=
anobj_uuid
)
anobj
=
None
if
anobj_uuid
:
try
:
anobj
=
_get_anobj
(
request
,
anobj_uuid
=
anobj_uuid
)
except
Http404
:
anobj
=
None
if
anobj
:
anobj
.
image
=
image_file
if
anobj_name
:
anobj
.
name
=
anobj_name
anobj
.
save
()
except
Http404
:
else
:
anobj
=
AnObj
.
objects
.
create
(
owner
=
request
.
user
,
name
=
os
.
path
.
splitext
(
image_file
.
name
)[
0
],
owner
=
user
,
name
=
anobj_name
or
os
.
path
.
splitext
(
image_file
.
name
)[
0
],
image
=
image_file
)
if
hasattr
(
request
,
'attp_message'
):
ttp_id
=
request
.
attp_message
.
get
(
'attp_id'
)
sharing_mode
=
get_ttp_sharing_mode
(
ttp_id
=
ttp_id
)
if
sharing_mode
:
anobj
.
sharing_mode
=
sharing_mode
sharing_opts
=
request
.
attp_message
.
get
(
'opts'
)
if
sharing_opts
:
anobj
.
sharing_opts
=
sharing_opts
anobj
.
save
()
# Create original thumbnail, returned to user who has not yet annotated
create_image_thumbnail
(
anobj
.
image
.
path
)
response_data
[
'next'
]
=
reverse
(
'adim_app:annotate'
,
kwargs
=
{
'anobj_uuid'
:
anobj
.
uuid
})
response_data
[
'uuid'
]
=
anobj
.
uuid
# Needed when using iFrame transport
if
"text/html"
in
request
.
META
[
"HTTP_ACCEPT"
]:
...
...
@@ -261,7 +309,9 @@ def upload_file(request, anobj_uuid=None):
else
:
response_data
[
'error'
]
=
"invalid"
if
request
.
is_ajax
():
print
(
"#"
*
80
,
"
\n
"
,
request
.
META
.
get
(
'HTTP_ACCEPT'
,
''
),
"#"
*
80
)
if
'application/json'
in
request
.
META
.
get
(
'HTTP_ACCEPT'
,
''
):
return
HttpResponse
(
json
.
dumps
(
response_data
),
content_type
=
response_type
)
else
:
return
HttpResponseRedirect
(
response_data
[
'next'
])
...
...
adim_project/adim_project/settings/base.py
View file @
8a856559
...
...
@@ -135,6 +135,8 @@ LOGIN_REDIRECT_URL = "adim.app:annotate-new"
LOGIN_URL
=
"adim.app:home"
SESSION_EXPIRE_AT_BROWSER_CLOSE
=
True
SESSION_COOKIE_NAME
=
"adim_sessid"
# ---------- END AUTHENTICATION
...
...
@@ -147,7 +149,7 @@ SENDFILE_BACKEND = 'sendfile.backends.xsendfile'
# ..... REST FRAMEWORK
LOCAL_APPS
+=
(
'rest_framework'
,)
LOCAL_APPS
+=
(
'rest_framework'
,
'rest_framework.authtoken'
)
REST_FRAMEWORK
=
{
'DEFAULT_AUTHENTICATION_CLASSES'
:
(
'rest_framework.authentication.SessionAuthentication'
,
...
...
@@ -158,6 +160,7 @@ REST_FRAMEWORK = {
]
}
# ..... SHIBAUTH
LOCAL_APPS
+=
(
'shibauth'
,
)
AUTHENTICATION_BACKENDS
=
(
'shibauth.shibbolethbackends.ShibbolethBackend'
,)
+
AUTHENTICATION_BACKENDS
...
...
@@ -168,8 +171,20 @@ except ImportError:
raise
ImproperlyConfigured
(
"Unable to import SHIBAUTH configurations"
)
# ..... CORS HEADERS (https://github.com/ottoyiu/django-cors-headers/)
LOCAL_APPS
+=
(
'corsheaders'
,)
MIDDLEWARE_CLASSES
=
list
(
MIDDLEWARE_CLASSES
)
MIDDLEWARE_CLASSES
.
insert
(
MIDDLEWARE_CLASSES
.
index
(
'django.middleware.common.CommonMiddleware'
),
'corsheaders.middleware.CorsMiddleware'
)
MIDDLEWARE_CLASSES
+=
(
'corsheaders.middleware.CorsMiddleware'
,
)
CORS_ORIGIN_ALLOW_ALL
=
True
CORS_URLS_REGEX
=
r
'^/api/.*$'
# ..... ADIM
LOCAL_APPS
+=
(
'adim'
,
'adim_app'
,)
LOCAL_APPS
+=
(
'adim'
,
'adim_app'
,
'adim_ttp'
)
# Max file size in Mb
ADIM_UPLOAD_MAX_FILESIZE
=
50
...
...
@@ -202,6 +217,21 @@ AAI = {
}
}
ATTP
=
{
'OPTIONS'
:
{
'CACHE_TIMEOUT'
:
20
,
# 30,
},
'moodle'
:
{
'CHECK_URL'
:
"http://localhost/tests/phpupload/gv.php?a={uuid}"
},
'toto'
:
{
'CHECK_URL'
:
"http://localhost/tests/phpupload/toto.php?a={uuid}"
},
'my_ttp'
:
{
'CHECK_URL'
:
"http://my-ttp:8001/ttp/check/{uuid}/"
}
}
# ---------- END LOCAL APPS CONFIGURATION
...
...
@@ -220,3 +250,75 @@ INSTALLED_APPS = DJANGO_APPS + LOCAL_APPS
# ---------- END APPS CONFIGURATION
# ========== LOGGING CONFIGURATION
#
def
add_remote_info
(
record
):
req
=
getattr
(
record
,
'request'
,
None
)
record
.
remote_addr
=
req
.
META
.
get
(
'REMOTE_ADDR'
,
'-'
)
if
req
else
'-'
record
.
forwarded_for
=
req
.
META
.
get
(
'HTTP_X_FORWARDED_FOR'
,
'-'
)
if
req
else
'-'
return
True
LOGGING
=
{
'version'
:
1
,
'disable_existing_loggers'
:
False
,
'filters'
:
{
'require_debug_false'
:
{
'()'
:
'django.utils.log.RequireDebugFalse'
},
'require_debug_true'
:
{
'()'
:
'django.utils.log.RequireDebugTrue'
},
'add_remote_info'
:
{
'()'
:
'django.utils.log.CallbackFilter'
,
'callback'
:
add_remote_info
,
}
},
'formatters'
:
{
'verbose'
:
{
'format'
:
'%(levelname)s %(asctime)s %(name)s.%(funcName)s %(message)s'
,
},
'verbose_with_remote'
:
{
'format'
:
'%(levelname)s %(asctime)s %(name)s.%(funcName)s %(remote_addr)s %(forwarded_for)s %(message)s'
,
},
'simple'
:
{
'format'
:
'%(levelname)s %(message)s'
}
},
'handlers'
:
{
'mail_admins'
:
{
'level'
:
'ERROR'
,
'filters'
:
[
'require_debug_false'
],
'class'
:
'django.utils.log.AdminEmailHandler'
},
'console'
:
{
'filters'
:
[
'require_debug_true'
,
],
'class'
:
'logging.StreamHandler'
,
},
'file'
:
{
'level'
:
'INFO'
,
'class'
:
'logging.FileHandler'
,
'filters'
:
[
'add_remote_info'
,
],
'formatter'
:
'verbose_with_remote'
,
'filename'
:
'{}/log/debug.log'
.
format
(
dirname
(
SITE_ROOT
)),
},
},
'loggers'
:
{
'django.request'
:
{
'handlers'
:
[
'mail_admins'
,
'file'
,
],
'level'
:
'WARNING'
,
'propagate'
:
True
,