Skip to content
GitLab
Menu
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
Mattia Rizzolo
nm.debian.org
Commits
240c6913
Commit
240c6913
authored
Apr 21, 2020
by
Enrico Zini
Browse files
Start using Inconsistency in CheckLDAPConsistency. refs:
#5
parent
bd61f3ca
Changes
4
Show whitespace changes
Inline
Side-by-side
dsa/housekeeping.py
View file @
240c6913
...
@@ -6,6 +6,7 @@ from django.utils.timezone import now
...
@@ -6,6 +6,7 @@ from django.utils.timezone import now
from
backend.housekeeping
import
MakeLink
,
Housekeeper
from
backend.housekeeping
import
MakeLink
,
Housekeeper
from
.
import
udldap
from
.
import
udldap
from
backend
import
const
from
backend
import
const
from
sitechecks.models
import
Inconsistency
import
backend.models
as
bmodels
import
backend.models
as
bmodels
import
process.models
as
pmodels
import
process.models
as
pmodels
import
backend.ops
as
bops
import
backend.ops
as
bops
...
@@ -140,8 +141,10 @@ class CheckLDAPConsistency(hk.Task):
...
@@ -140,8 +141,10 @@ class CheckLDAPConsistency(hk.Task):
fpr
=
entry
.
single
(
"keyFingerPrint"
)
fpr
=
entry
.
single
(
"keyFingerPrint"
)
if
fpr
:
if
fpr
:
log
.
warn
(
"%s: %s has fingerprint %s and gid %s in LDAP, but is not in our db"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
entry
.
uid
,
fpr
,
entry
.
single
(
"gidNumber"
))
self
.
IDENTIFIER
,
f
"
{
entry
.
uid
}
has fingerprint
{
fpr
}
and gid
{
entry
.
single
(
'gidNumber'
)
}
in LDAP,"
" but is not in our db"
)
else
:
else
:
ldap_fields_args
=
{
ldap_fields_args
=
{
"cn"
:
entry
.
single
(
"cn"
),
"cn"
:
entry
.
single
(
"cn"
),
...
@@ -199,9 +202,12 @@ class CheckLDAPConsistency(hk.Task):
...
@@ -199,9 +202,12 @@ class CheckLDAPConsistency(hk.Task):
elif
not
entry
.
is_dd
:
elif
not
entry
.
is_dd
:
# mom040267 has a locked account (see the FD comments on the site)
# mom040267 has a locked account (see the FD comments on the site)
if
person
.
ldap_fields
.
uid
!=
"mom040267"
:
if
person
.
ldap_fields
.
uid
!=
"mom040267"
:
log
.
warn
(
"%s: %s has accountStatus '%s' (comment: %s) but in our db the state is %s"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
entry
.
single
(
"accountStatus"
),
self
.
IDENTIFIER
,
entry
.
single
(
"accountComment"
),
const
.
ALL_STATUS_DESCS
[
person
.
status
])
f
"person has accountStatus '
{
entry
.
single
(
'accountStatus'
)
}
'"
f
" (comment:
{
entry
.
single
(
'accountComment'
)
}
)"
f
" but in our db the state is
{
const
.
ALL_STATUS_DESCS
[
person
.
status
]
}
"
,
person
=
person
)
if
entry
.
is_dd
:
if
entry
.
is_dd
:
# if person.status not in (const.STATUS_REMOVED_DD, const.STATUS_EMERITUS_DD):
# if person.status not in (const.STATUS_REMOVED_DD, const.STATUS_EMERITUS_DD):
...
@@ -210,26 +216,36 @@ class CheckLDAPConsistency(hk.Task):
...
@@ -210,26 +216,36 @@ class CheckLDAPConsistency(hk.Task):
if
person
.
status_changed
>
self
.
email_forwarding_cutoff
:
if
person
.
status_changed
>
self
.
email_forwarding_cutoff
:
if
dsa_status
==
"retiring"
and
person
.
status
!=
const
.
STATUS_EMERITUS_DD
:
if
dsa_status
==
"retiring"
and
person
.
status
!=
const
.
STATUS_EMERITUS_DD
:
log
.
warn
(
"%s: %s has accountStatus '%s' (comment: %s) but in our db the state is %s [retiring]"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
entry
.
single
(
"accountStatus"
),
self
.
IDENTIFIER
,
entry
.
single
(
"accountComment"
),
const
.
ALL_STATUS_DESCS
[
person
.
status
])
f
"person has accountStatus '
{
entry
.
single
(
'accountStatus'
)
}
'"
f
" (comment:
{
entry
.
single
(
'accountComment'
)
}
)"
f
" but in our db the state is
{
const
.
ALL_STATUS_DESCS
[
person
.
status
]
}
[retiring]"
,
person
=
person
)
if
dsa_status
==
"inactive"
and
person
.
status
!=
const
.
STATUS_REMOVED_DD
:
if
dsa_status
==
"inactive"
and
person
.
status
!=
const
.
STATUS_REMOVED_DD
:
log
.
warn
(
"%s: %s has accountStatus '%s' (comment: %s) but in our db the state is %s [inactive]"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
entry
.
single
(
"accountStatus"
),
self
.
IDENTIFIER
,
entry
.
single
(
"accountComment"
),
const
.
ALL_STATUS_DESCS
[
person
.
status
])
f
"person has accountStatus '
{
entry
.
single
(
'accountStatus'
)
}
'"
f
" (comment:
{
entry
.
single
(
'accountComment'
)
}
)"
f
" but in our db the state is
{
const
.
ALL_STATUS_DESCS
[
person
.
status
]
}
[inactive]"
,
person
=
person
)
if
dsa_status
==
"locked"
:
if
dsa_status
==
"locked"
:
parsed
=
entry
.
single
(
"accountStatus"
).
split
()
parsed
=
entry
.
single
(
"accountStatus"
).
split
()
if
len
(
parsed
)
==
1
:
if
len
(
parsed
)
==
1
:
log
.
warn
(
"%s: %s has accountStatus '%s' (comment: %s) locked with no date"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
entry
.
single
(
"accountStatus"
),
self
.
IDENTIFIER
,
entry
.
single
(
"accountComment"
))
f
"person has accountStatus '
{
entry
.
single
(
'accountStatus'
)
}
'"
f
"(comment:
{
entry
.
single
(
'accountComment'
)
}
) locked with no date"
,
person
=
person
)
else
:
else
:
if
dsa_status_date
is
not
None
and
dsa_status_date
<
self
.
email_forwarding_cutoff
.
date
():
if
dsa_status_date
is
not
None
and
dsa_status_date
<
self
.
email_forwarding_cutoff
.
date
():
log
.
warn
(
"%s: %s has accountStatus '%s' (comment: %s) locked for longer than 6 months"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
entry
.
single
(
"accountStatus"
),
self
.
IDENTIFIER
,
entry
.
single
(
"accountComment"
))
f
"person has accountStatus '
{
entry
.
single
(
'accountStatus'
)
}
'"
f
" (comment:
{
entry
.
single
(
'accountComment'
)
}
) locked for longer than 6 months"
,
person
=
person
)
def
run_main
(
self
,
stage
):
def
run_main
(
self
,
stage
):
# Prefetch people and index them by uid
# Prefetch people and index them by uid
...
@@ -253,15 +269,19 @@ class CheckLDAPConsistency(hk.Task):
...
@@ -253,15 +269,19 @@ class CheckLDAPConsistency(hk.Task):
try
:
try
:
dsa_status_date
=
datetime
.
datetime
.
strptime
(
parsed
[
1
],
"%Y-%m-%d"
).
date
()
dsa_status_date
=
datetime
.
datetime
.
strptime
(
parsed
[
1
],
"%Y-%m-%d"
).
date
()
except
ValueError
:
except
ValueError
:
log
.
warn
(
"%s: %s has accountStatus '%s' (comment: %s) with unparsable date"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
entry
.
single
(
"accountStatus"
),
self
.
IDENTIFIER
,
entry
.
single
(
"accountComment"
))
"person has accountStatus '{entry.single('accountStatus')}'"
" (comment: {entry.single('accountComment')}) with unparsable date"
,
person
=
person
)
dsa_status_date
=
None
dsa_status_date
=
None
else
:
else
:
dsa_status_date
=
None
dsa_status_date
=
None
if
dsa_status
not
in
(
"retiring"
,
"inactive"
,
"memorial"
,
"locked"
,
"renamed"
):
if
dsa_status
not
in
(
"retiring"
,
"inactive"
,
"memorial"
,
"locked"
,
"renamed"
):
log
.
warn
(
"%s: %s has unknown accountStatus %s"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
entry
.
single
(
"accountStatus"
))
self
.
IDENTIFIER
,
"person has unknown accountStatus {entry.single('accountStatus')}"
,
person
=
person
)
else
:
else
:
self
.
check_account_status
(
person
,
entry
,
dsa_status
,
dsa_status_date
)
self
.
check_account_status
(
person
,
entry
,
dsa_status
,
dsa_status_date
)
else
:
else
:
...
@@ -269,14 +289,18 @@ class CheckLDAPConsistency(hk.Task):
...
@@ -269,14 +289,18 @@ class CheckLDAPConsistency(hk.Task):
# DDs we don't expect
# DDs we don't expect
if
entry
.
is_dd
:
if
entry
.
is_dd
:
if
person
.
status
not
in
(
const
.
STATUS_DD_U
,
const
.
STATUS_DD_NU
):
if
person
.
status
not
in
(
const
.
STATUS_DD_U
,
const
.
STATUS_DD_NU
):
log
.
warn
(
"%s: %s has supplementaryGid 'Debian', but in our db the state is %s"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
self
.
IDENTIFIER
,
const
.
ALL_STATUS_DESCS
[
person
.
status
])
"person has supplementaryGid 'Debian',"
f
" but in our db the state is
{
const
.
ALL_STATUS_DESCS
[
person
.
status
]
}
"
,
person
=
person
)
else
:
else
:
if
person
.
status
in
(
const
.
STATUS_DD_U
,
const
.
STATUS_DD_NU
):
if
person
.
status
in
(
const
.
STATUS_DD_U
,
const
.
STATUS_DD_NU
):
log
.
warn
(
"%s: %s has no supplementaryGid 'Debian', but in our db the state is %s"
,
Inconsistency
.
objects
.
found
(
self
.
IDENTIFIER
,
self
.
hk
.
link
(
person
),
self
.
IDENTIFIER
,
const
.
ALL_STATUS_DESCS
[
person
.
status
])
"person has no supplementaryGid 'Debian',"
f
" but in our db the state is
{
const
.
ALL_STATUS_DESCS
[
person
.
status
]
}
"
,
person
=
person
)
email
=
entry
.
single
(
"emailForward"
)
email
=
entry
.
single
(
"emailForward"
)
if
email
!=
person
.
ldap_fields
.
email
:
if
email
!=
person
.
ldap_fields
.
email
:
...
...
dsa/tests/test_housekeeping.py
View file @
240c6913
...
@@ -16,6 +16,12 @@ class MockHousekeeper:
...
@@ -16,6 +16,12 @@ class MockHousekeeper:
def
link
(
self
,
person
):
def
link
(
self
,
person
):
return
str
(
person
.
ldap_fields
.
uid
or
person
.
pk
)
return
str
(
person
.
ldap_fields
.
uid
or
person
.
pk
)
def
run
(
self
,
cls
,
stage
:
str
=
"main"
):
task
=
cls
(
self
)
task
.
IDENTIFIER
=
f
"test.
{
cls
.
__name__
}
"
getattr
(
task
,
f
"run_
{
stage
}
"
)(
None
)
return
task
class
MockEntry
:
class
MockEntry
:
def
__init__
(
self
,
uid
,
**
attrs
):
def
__init__
(
self
,
uid
,
**
attrs
):
...
@@ -41,8 +47,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -41,8 +47,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
mn
=
"tmn"
,
sn
=
"tsn"
,
emailForward
=
"test@example.org"
,
accountStatus
=
None
)
mn
=
"tmn"
,
sn
=
"tsn"
,
emailForward
=
"test@example.org"
,
accountStatus
=
None
)
]
]
with
LogCapture
()
as
lc
:
with
LogCapture
()
as
lc
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
lc
.
check
((
"dsa.housekeeping"
,
"WARNING"
,
"None: newdd: created to mirror a removed DD account from LDAP"
))
lc
.
check
((
"dsa.housekeeping"
,
"WARNING"
,
"None: newdd: created to mirror a removed DD account from LDAP"
))
p
=
Person
.
objects
.
get
(
ldap_fields__uid
=
"newdd"
)
p
=
Person
.
objects
.
get
(
ldap_fields__uid
=
"newdd"
)
...
@@ -65,8 +70,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -65,8 +70,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
sn
=
"tsn"
,
emailForward
=
"test@example.org"
,
accountStatus
=
None
)
sn
=
"tsn"
,
emailForward
=
"test@example.org"
,
accountStatus
=
None
)
]
]
with
LogCapture
()
as
lc
:
with
LogCapture
()
as
lc
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
lc
.
check
((
"dsa.housekeeping"
,
"WARNING"
,
lc
.
check
((
"dsa.housekeeping"
,
"WARNING"
,
"None: newguest: created to mirror a removed guest account from LDAP"
))
"None: newguest: created to mirror a removed guest account from LDAP"
))
...
@@ -91,8 +95,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -91,8 +95,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
accountStatus
=
None
)
accountStatus
=
None
)
]
]
with
LogCapture
()
as
lc
:
with
LogCapture
()
as
lc
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
lc
.
check
((
"dsa.housekeeping"
,
"WARNING"
,
lc
.
check
((
"dsa.housekeeping"
,
"WARNING"
,
"None: newdd has fingerprint 66B4DFB68CB24EBBD8650BC4F4B4B0CC797EBFAB and gid 800 in LDAP,"
"None: newdd has fingerprint 66B4DFB68CB24EBBD8650BC4F4B4B0CC797EBFAB and gid 800 in LDAP,"
" but is not in our db"
))
" but is not in our db"
))
...
@@ -107,8 +110,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -107,8 +110,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
mn
=
"tmn"
,
sn
=
"tsn"
,
emailForward
=
"test@example.org"
,
accountStatus
=
None
)
mn
=
"tmn"
,
sn
=
"tsn"
,
emailForward
=
"test@example.org"
,
accountStatus
=
None
)
]
]
with
LogCapture
()
as
lc
:
with
LogCapture
()
as
lc
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
lc
.
check
(
lc
.
check
(
(
"dsa.housekeeping"
,
"INFO"
,
"None: dd_u changing email_ldap from dd_u@example.org to test@example.org"
(
"dsa.housekeeping"
,
"INFO"
,
"None: dd_u changing email_ldap from dd_u@example.org to test@example.org"
" (source: LDAP)"
),
" (source: LDAP)"
),
...
@@ -138,8 +140,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -138,8 +140,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
keyFingerPrint
=
"66B4DFB68CB24EBBD8650BC4F4B4B0CC797EBFAB"
,
accountStatus
=
None
)
keyFingerPrint
=
"66B4DFB68CB24EBBD8650BC4F4B4B0CC797EBFAB"
,
accountStatus
=
None
)
]
]
with
self
.
assertLogs
()
as
log
:
with
self
.
assertLogs
()
as
log
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
self
.
assertEqual
(
log
.
output
,
[
self
.
assertEqual
(
log
.
output
,
[
"WARNING:dsa.housekeeping:"
"WARNING:dsa.housekeeping:"
"None: dm has supplementaryGid 'Debian', but in our db the state is Debian Maintainer"
,
"None: dm has supplementaryGid 'Debian', but in our db the state is Debian Maintainer"
,
...
@@ -157,8 +158,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -157,8 +158,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
accountStatus
=
"inactive 2018-03-20"
),
accountStatus
=
"inactive 2018-03-20"
),
]
]
with
self
.
assertLogs
()
as
log
:
with
self
.
assertLogs
()
as
log
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
self
.
assertEqual
(
log
.
output
,
[
self
.
assertEqual
(
log
.
output
,
[
"WARNING:dsa.housekeeping:"
"WARNING:dsa.housekeeping:"
"None: dd_u has accountStatus 'inactive 2018-03-20' (comment: None)"
"None: dd_u has accountStatus 'inactive 2018-03-20' (comment: None)"
...
@@ -178,8 +178,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -178,8 +178,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
accountStatus
=
"inactive 2018-03-20"
,
accountComment
=
"RT#1234"
),
accountStatus
=
"inactive 2018-03-20"
,
accountComment
=
"RT#1234"
),
]
]
with
self
.
assertLogs
()
as
log
:
with
self
.
assertLogs
()
as
log
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
self
.
assertEqual
(
log
.
output
,
[
self
.
assertEqual
(
log
.
output
,
[
"WARNING:dsa.housekeeping:None: dd_u has accountStatus 'inactive 2018-03-20' "
"WARNING:dsa.housekeeping:None: dd_u has accountStatus 'inactive 2018-03-20' "
'(comment: RT#1234) but in our db the state is Debian Developer, uploading [inactive]'
])
'(comment: RT#1234) but in our db the state is Debian Developer, uploading [inactive]'
])
...
@@ -197,8 +196,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -197,8 +196,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
emailForward
=
"dd_u@example.org"
,
accountStatus
=
"retiring 2018-03-20"
),
emailForward
=
"dd_u@example.org"
,
accountStatus
=
"retiring 2018-03-20"
),
]
]
with
self
.
assertLogs
()
as
log
:
with
self
.
assertLogs
()
as
log
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
self
.
assertEqual
(
log
.
output
,
[
self
.
assertEqual
(
log
.
output
,
[
"WARNING:dsa.housekeeping:None: dd_u has accountStatus 'retiring 2018-03-20' "
"WARNING:dsa.housekeeping:None: dd_u has accountStatus 'retiring 2018-03-20' "
'(comment: None) but in our db the state is Debian Developer, uploading '
'(comment: None) but in our db the state is Debian Developer, uploading '
...
@@ -209,8 +207,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -209,8 +207,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
process
.
approved_time
=
now
()
process
.
approved_time
=
now
()
process
.
save
()
process
.
save
()
with
self
.
assertLogs
()
as
log
:
with
self
.
assertLogs
()
as
log
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
self
.
assertEqual
(
log
.
output
,
[
self
.
assertEqual
(
log
.
output
,
[
"INFO:dsa.housekeeping:None: dd_u closed from dsa: retiring 2018-03-20"
,
"INFO:dsa.housekeeping:None: dd_u closed from dsa: retiring 2018-03-20"
,
])
])
...
@@ -234,8 +231,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -234,8 +231,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
emailForward
=
"test@example.org"
,
accountStatus
=
"retiring 2018-03-20"
),
emailForward
=
"test@example.org"
,
accountStatus
=
"retiring 2018-03-20"
),
]
]
with
self
.
assertLogs
()
as
log
:
with
self
.
assertLogs
()
as
log
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
self
.
assertEqual
(
log
.
output
,
[
self
.
assertEqual
(
log
.
output
,
[
"WARNING:dsa.housekeeping:None: dd_u has accountStatus 'retiring 2018-03-20' "
"WARNING:dsa.housekeeping:None: dd_u has accountStatus 'retiring 2018-03-20' "
'(comment: None) but in our db the state is Debian Developer, uploading '
'(comment: None) but in our db the state is Debian Developer, uploading '
...
@@ -248,8 +244,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
...
@@ -248,8 +244,7 @@ class TestCheckLDAPConsistency(ProcessFixtureMixin, TestCase):
process
.
approved_time
=
now
()
process
.
approved_time
=
now
()
process
.
save
()
process
.
save
()
with
self
.
assertLogs
()
as
log
:
with
self
.
assertLogs
()
as
log
:
task
=
CheckLDAPConsistency
(
self
.
hk
)
self
.
hk
.
run
(
CheckLDAPConsistency
)
task
.
run_main
(
None
)
self
.
assertEqual
(
log
.
output
,
[
self
.
assertEqual
(
log
.
output
,
[
"INFO:dsa.housekeeping:None: dd_u closed from dsa: retiring 2018-03-20"
,
"INFO:dsa.housekeeping:None: dd_u closed from dsa: retiring 2018-03-20"
,
])
])
...
...
sitechecks/housekeeping.py
View file @
240c6913
from
__future__
import
annotations
from
collections
import
defaultdict
from
collections
import
defaultdict
import
logging
import
logging
import
django_housekeeping
as
hk
import
django_housekeeping
as
hk
...
...
sitechecks/models.py
View file @
240c6913
from
__future__
import
annotations
from
django.db
import
models
from
django.db
import
models
from
backend.models
import
Person
from
backend.models
import
Person
from
process.models
import
Process
from
process.models
import
Process
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment