66
77import datetime , os , shutil , glob , re , itertools
88
9- from ietf .idtracker .models import InternetDraft , IDDates , IDStatus , IDState , DocumentComment , IDAuthor , WGChair
109from ietf .utils .mail import send_mail , send_mail_subj
11- from ietf .idrfc .utils import log_state_changed , add_document_comment
12- from ietf .doc .models import Document , DocEvent , save_document_in_history , State
13- from ietf .name .models import DocTagName
10+ from ietf .idrfc .utils import log_state_changed
11+ from ietf .doc .models import Document , DocEvent , State , save_document_in_history , IESG_SUBSTATE_TAGS
1412from ietf .person .models import Person , Email
1513from ietf .meeting .models import Meeting
1614
17- def in_id_expire_freeze (when = None ):
18- if when == None :
19- when = datetime .datetime .now ()
20-
21- if settings .USE_DB_REDESIGN_PROXY_CLASSES :
22- d = Meeting .get_second_cut_off ()
23- else :
24- d = IDDates .objects .get (id = IDDates .SECOND_CUT_OFF ).date
25- # for some reason, the old Perl code started at 9 am
26- second_cut_off = datetime .datetime .combine (d , datetime .time (9 , 0 ))
27-
28- if settings .USE_DB_REDESIGN_PROXY_CLASSES :
29- d = Meeting .get_ietf_monday ()
30- else :
31- d = IDDates .objects .get (id = IDDates .IETF_MONDAY ).date
32- ietf_monday = datetime .datetime .combine (d , datetime .time (0 , 0 ))
33-
34- return second_cut_off <= when < ietf_monday
35-
36- def expirable_documents ():
15+ def expirable_draft (draft ):
16+ """Return whether draft is in an expirable state or not. This is
17+ the single draft version of the logic in expirable_drafts. These
18+ two functions need to be kept in sync."""
19+ return (draft .expires and draft .get_state_slug () == "active"
20+ and draft .get_state_slug ("draft-iesg" ) in (None , "watching" , "dead" )
21+ and draft .get_state_slug ("draft-stream-%s" % draft .stream_id ) not in ("rfc-edit" , "pub" )
22+ and not draft .tags .filter (slug = "rfc-rev" ))
23+
24+ def expirable_drafts ():
25+ """Return a queryset with expirable drafts."""
3726 # the general rule is that each active draft is expirable, unless
3827 # it's in a state where we shouldn't touch it
39-
40- d = Document .objects .filter (states__type = "draft" , states__slug = "active" ).exclude (tags = "rfc-rev" )
28+ d = Document .objects .filter (states__type = "draft" , states__slug = "active" ).exclude (expires = None )
4129
4230 nonexpirable_states = []
4331 # all IESG states except AD Watching and Dead block expiry
4432 nonexpirable_states += list (State .objects .filter (type = "draft-iesg" ).exclude (slug__in = ("watching" , "dead" )))
45- # Sent to RFC Editor and RFC Published block expiry (the latter
33+ # sent to RFC Editor and RFC Published block expiry (the latter
4634 # shouldn't be possible for an active draft, though)
4735 nonexpirable_states += list (State .objects .filter (type__in = ("draft-stream-iab" , "draft-stream-irtf" , "draft-stream-ise" ), slug__in = ("rfc-edit" , "pub" )))
4836
49- return d .exclude (states__in = nonexpirable_states ). distinct ( )
37+ d = d .exclude (states__in = nonexpirable_states )
5038
51- def get_soon_to_expire_ids (days ):
52- start_date = datetime .date .today () - datetime .timedelta (InternetDraft .DAYS_TO_EXPIRE - 1 )
53- end_date = start_date + datetime .timedelta (days - 1 )
54-
55- for d in InternetDraft .objects .filter (revision_date__gte = start_date ,revision_date__lte = end_date ,status__status = 'Active' ):
56- if d .can_expire ():
57- yield d
39+ # under review by the RFC Editor blocks expiry
40+ d = d .exclude (tags = "rfc-rev" )
41+
42+ return d .distinct ()
5843
59- def get_soon_to_expire_idsREDESIGN ( days ):
44+ def get_soon_to_expire_drafts ( days_of_warning ):
6045 start_date = datetime .date .today () - datetime .timedelta (1 )
61- end_date = start_date + datetime .timedelta (days - 1 )
46+ end_date = start_date + datetime .timedelta (days_of_warning )
47+
48+ return expirable_drafts ().filter (expires__gte = start_date , expires__lt = end_date )
49+
50+ def get_expired_drafts ():
51+ return expirable_drafts ().filter (expires__lt = datetime .date .today () + datetime .timedelta (1 ))
52+
53+ def in_draft_expire_freeze (when = None ):
54+ if when == None :
55+ when = datetime .datetime .now ()
56+
57+ d = Meeting .get_second_cut_off ()
58+ # for some reason, the old Perl code started at 9 am
59+ second_cut_off = datetime .datetime .combine (d , datetime .time (9 , 0 ))
6260
63- for d in expirable_documents ():
64- if d .expires and start_date <= d .expires .date () <= end_date :
65- yield d
66-
67- def get_expired_ids ():
68- cut_off = datetime .date .today () - datetime .timedelta (days = InternetDraft .DAYS_TO_EXPIRE )
69-
70- return InternetDraft .objects .filter (
71- revision_date__lte = cut_off ,
72- status__status = "Active" ,
73- review_by_rfc_editor = 0 ).filter (
74- Q (idinternal = None ) | Q (idinternal__cur_state__document_state_id__gte = 42 ))
75-
76- def get_expired_idsREDESIGN ():
77- today = datetime .date .today ()
78-
79- for d in expirable_documents ():
80- if d .expires and d .expires .date () <= today :
81- yield d
82-
83- def send_expire_warning_for_id (doc ):
84- expiration = doc .expiration ()
85- # Todo:
86- #second_cutoff = IDDates.objects.get(date_id=2)
87- #ietf_monday = IDDates.objects.get(date_id=3)
88- #freeze_delta = ietf_monday - second_cutoff
89- # # The I-D expiration job doesn't run while submissions are frozen.
90- # if ietf_monday > expiration > second_cutoff:
91- # expiration += freeze_delta
61+ d = Meeting .get_ietf_monday ()
62+ ietf_monday = datetime .datetime .combine (d , datetime .time (0 , 0 ))
9263
93- authors = doc .authors .all ()
94- to_addrs = [author .email () for author in authors if author .email ()]
95- cc_addrs = None
96- if doc .group .acronym != 'none' :
97- cc_addrs = [chair .person .email () for chair in WGChair .objects .filter (group_acronym = doc .group )]
98-
99- if to_addrs or cc_addrs :
100- send_mail_subj (None , to_addrs , None , 'notify_expirations/subject.txt' , 'notify_expirations/body.txt' ,
101- {
102- 'draft' :doc ,
103- 'expiration' :expiration ,
104- },
105- cc_addrs )
106-
107- def send_expire_warning_for_idREDESIGN (doc ):
64+ return second_cut_off <= when < ietf_monday
65+
66+ def send_expire_warning_for_draft (doc ):
10867 if doc .get_state_slug ("draft-iesg" ) == "dead" :
10968 return # don't warn about dead documents
11069
@@ -130,23 +89,7 @@ def send_expire_warning_for_idREDESIGN(doc):
13089 ),
13190 cc = cc )
13291
133- def send_expire_notice_for_id (doc ):
134- doc .dunn_sent_date = datetime .date .today ()
135- doc .save ()
136-
137- if not doc .idinternal :
138- return
139-
140- request = None
141- to = u"%s <%s>" % doc .idinternal .job_owner .person .email ()
142- send_mail (request , to ,
143- "I-D Expiring System <ietf-secretariat-reply@ietf.org>" ,
144- u"I-D was expired %s" % doc .file_tag (),
145- "idrfc/id_expired_email.txt" ,
146- dict (doc = doc ,
147- state = doc .idstate ()))
148-
149- def send_expire_notice_for_idREDESIGN (doc ):
92+ def send_expire_notice_for_draft (doc ):
15093 if not doc .ad or doc .get_state_slug ("draft-iesg" ) == "dead" :
15194 return
15295
@@ -163,42 +106,6 @@ def send_expire_notice_for_idREDESIGN(doc):
163106 state = state ,
164107 ))
165108
166- def expire_id (doc ):
167- def move_file (f ):
168- src = os .path .join (settings .INTERNET_DRAFT_PATH , f )
169- dst = os .path .join (settings .INTERNET_DRAFT_ARCHIVE_DIR , f )
170-
171- if os .path .exists (src ):
172- shutil .move (src , dst )
173-
174- move_file ("%s-%s.txt" % (doc .filename , doc .revision_display ()))
175- move_file ("%s-%s.txt.p7s" % (doc .filename , doc .revision_display ()))
176- move_file ("%s-%s.ps" % (doc .filename , doc .revision_display ()))
177- move_file ("%s-%s.pdf" % (doc .filename , doc .revision_display ()))
178-
179- new_revision = "%02d" % (int (doc .revision ) + 1 )
180-
181- new_file = open (os .path .join (settings .INTERNET_DRAFT_PATH , "%s-%s.txt" % (doc .filename , new_revision )), 'w' )
182- txt = render_to_string ("idrfc/expire_text.txt" ,
183- dict (doc = doc ,
184- authors = [a .person .email () for a in doc .authors .all ()],
185- expire_days = InternetDraft .DAYS_TO_EXPIRE ))
186- new_file .write (txt )
187- new_file .close ()
188-
189- doc .revision = new_revision
190- doc .expiration_date = datetime .date .today ()
191- doc .last_modified_date = datetime .date .today ()
192- doc .status = IDStatus .objects .get (status = "Expired" )
193- doc .save ()
194-
195- if doc .idinternal :
196- if doc .idinternal .cur_state_id != IDState .DEAD :
197- doc .idinternal .change_state (IDState .objects .get (document_state_id = IDState .DEAD ), None )
198- log_state_changed (None , doc , "system" )
199-
200- add_document_comment (None , doc , "Document is expired by system" )
201-
202109def move_draft_files_to_archive (doc , rev ):
203110 def move_file (f ):
204111 src = os .path .join (settings .INTERNET_DRAFT_PATH , f )
@@ -211,7 +118,7 @@ def move_file(f):
211118 for t in file_types :
212119 move_file ("%s-%s.%s" % (doc .name , rev , t ))
213120
214- def expire_idREDESIGN (doc ):
121+ def expire_draft (doc ):
215122 # clean up files
216123 move_draft_files_to_archive (doc , doc .rev )
217124
@@ -222,7 +129,7 @@ def expire_idREDESIGN(doc):
222129 if doc .latest_event (type = 'started_iesg_process' ):
223130 dead_state = State .objects .get (type = "draft-iesg" , slug = "dead" )
224131 prev = doc .get_state ("draft-iesg" )
225- prev_tag = doc .tags .filter (slug__in = ( 'point' , 'ad-f-up' , 'need-rev' , 'extpty' ) )
132+ prev_tag = doc .tags .filter (slug__in = IESG_SUBSTATE_TAGS )
226133 prev_tag = prev_tag [0 ] if prev_tag else None
227134 if prev != dead_state :
228135 doc .set_state (dead_state )
@@ -239,67 +146,7 @@ def expire_idREDESIGN(doc):
239146 doc .time = datetime .datetime .now ()
240147 doc .save ()
241148
242- def clean_up_id_files ():
243- """Move unidentified and old files out of the Internet Draft directory."""
244- cut_off = datetime .date .today () - datetime .timedelta (days = InternetDraft .DAYS_TO_EXPIRE )
245-
246- pattern = os .path .join (settings .INTERNET_DRAFT_PATH , "draft-*.*" )
247- files = []
248- filename_re = re .compile ('^(.*)-(\d\d)$' )
249-
250- def splitext (fn ):
251- """
252- Split the pathname path into a pair (root, ext) such that root + ext
253- == path, and ext is empty or begins with a period and contains all
254- periods in the last path component.
255-
256- This differs from os.path.splitext in the number of periods in the ext
257- parts when the final path component containt more than one period.
258- """
259- s = fn .rfind ("/" )
260- if s == - 1 :
261- s = 0
262- i = fn [s :].find ("." )
263- if i == - 1 :
264- return fn , ''
265- else :
266- return fn [:s + i ], fn [s + i :]
267-
268- for path in glob .glob (pattern ):
269- basename = os .path .basename (path )
270- stem , ext = splitext (basename )
271- match = filename_re .search (stem )
272- if not match :
273- filename , revision = ("UNKNOWN" , "00" )
274- else :
275- filename , revision = match .groups ()
276-
277- def move_file_to (subdir ):
278- shutil .move (path ,
279- os .path .join (settings .INTERNET_DRAFT_ARCHIVE_DIR , subdir , basename ))
280-
281- try :
282- doc = InternetDraft .objects .get (filename = filename , revision = revision )
283-
284- if doc .status_id == 3 : # RFC
285- if ext != ".txt" :
286- move_file_to ("unknown_ids" )
287- elif doc .status_id in (2 , 4 , 5 , 6 ) and doc .expiration_date and doc .expiration_date < cut_off :
288- # Expired, Withdrawn by Auth, Replaced, Withdrawn by IETF,
289- # and expired more than DAYS_TO_EXPIRE ago
290- if os .path .getsize (path ) < 1500 :
291- move_file_to ("deleted_tombstones" )
292- # revert version after having deleted tombstone
293- doc .revision = "%02d" % (int (revision ) - 1 )
294- doc .expired_tombstone = True
295- doc .save ()
296- else :
297- move_file_to ("expired_without_tombstone" )
298-
299- except InternetDraft .DoesNotExist :
300- move_file_to ("unknown_ids" )
301-
302- def clean_up_id_filesREDESIGN ():
149+ def clean_up_draft_files ():
303150 """Move unidentified and old files out of the Internet Draft directory."""
304151 cut_off = datetime .date .today ()
305152
@@ -314,7 +161,7 @@ def splitext(fn):
314161 periods in the last path component.
315162
316163 This differs from os.path.splitext in the number of periods in the ext
317- parts when the final path component containt more than one period.
164+ parts when the final path component contains more than one period.
318165 """
319166 s = fn .rfind ("/" )
320167 if s == - 1 :
@@ -357,11 +204,3 @@ def move_file_to(subdir):
357204
358205 except Document .DoesNotExist :
359206 move_file_to ("unknown_ids" )
360-
361- if settings .USE_DB_REDESIGN_PROXY_CLASSES :
362- get_soon_to_expire_ids = get_soon_to_expire_idsREDESIGN
363- get_expired_ids = get_expired_idsREDESIGN
364- send_expire_warning_for_id = send_expire_warning_for_idREDESIGN
365- send_expire_notice_for_id = send_expire_notice_for_idREDESIGN
366- expire_id = expire_idREDESIGN
367- clean_up_id_files = clean_up_id_filesREDESIGN
0 commit comments