From 036966da97bb1a4b0d810953dfd13e4ece414796 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Fri, 1 Oct 2021 21:31:24 +0000 Subject: [PATCH] Provide a metadata endpoint to assist rfcdiff.pyht. Commit ready for merge. - Legacy-Id: 19396 --- ietf/doc/tests.py | 122 ++++++++++++++++++++++++++++++++++++- ietf/doc/urls.py | 5 ++ ietf/doc/views_doc.py | 136 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 261 insertions(+), 2 deletions(-) diff --git a/ietf/doc/tests.py b/ietf/doc/tests.py index 0b6f3c4a0..d7dd8e04b 100644 --- a/ietf/doc/tests.py +++ b/ietf/doc/tests.py @@ -48,7 +48,7 @@ from ietf.name.models import SessionStatusName, BallotPositionName, DocTypeName from ietf.person.models import Person from ietf.person.factories import PersonFactory, EmailFactory from ietf.utils.mail import outbox -from ietf.utils.test_utils import login_testing_unauthorized, unicontent +from ietf.utils.test_utils import login_testing_unauthorized, unicontent, reload_db_objects from ietf.utils.test_utils import TestCase from ietf.utils.text import normalize_text @@ -2502,3 +2502,123 @@ class Idnits2SupportTests(TestCase): r = self.client.get(url) self.assertEqual(r.status_code, 200) self.assertContains(r,'Proposed') + +class RfcdiffSupportTests(TestCase): + + def setUp(self): + self.target_view = 'ietf.doc.views_doc.rfcdiff_latest_json' + + def getJson(self, view_args): + url = urlreverse(self.target_view, kwargs=view_args) + r = self.client.get(url) + self.assertEqual(r.status_code, 200) + return r.json() + + def test_draft(self): + draft = IndividualDraftFactory(name='draft-somebody-did-something',rev='00') + for r in range(0,13): + e = NewRevisionDocEventFactory(doc=draft,rev=f'{r:02d}') + draft.rev = f'{r:02d}' + draft.save_with_history([e]) + draft = reload_db_objects(draft) + + received = self.getJson(dict(name=draft.name)) + self.assertEqual(received, dict( + name=draft.name, + rev=draft.rev, + content_url=draft.get_href(), + previous=f'{draft.name}-{(int(draft.rev)-1):02d}' + )) + + received = self.getJson(dict(name=draft.name, rev=draft.rev)) + self.assertEqual(received, dict( + name=draft.name, + rev=draft.rev, + content_url=draft.get_href(), + previous=f'{draft.name}-{(int(draft.rev)-1):02d}' + )) + + received = self.getJson(dict(name=draft.name, rev='10')) + self.assertEqual(received, dict( + name=draft.name, + rev='10', + content_url=draft.history_set.get(rev='10').get_href(), + previous=f'{draft.name}-09' + )) + + received = self.getJson(dict(name=draft.name, rev='00')) + self.assertNotIn('previous', received) + + replaced = IndividualDraftFactory() + RelatedDocument.objects.create(relationship_id='replaces',source=draft,target=replaced.docalias.first()) + received = self.getJson(dict(name=draft.name, rev='00')) + self.assertEqual(received['previous'], f'{replaced.name}-{replaced.rev}') + + + def test_draft_with_broken_history(self): + draft = IndividualDraftFactory(rev='10') + received = self.getJson(dict(name=draft.name,rev='09')) + self.assertEqual(received['rev'],'09') + self.assertEqual(received['previous'], f'{draft.name}-09') + self.assertTrue('warning' in received) + + + def test_draftname_with_numeric_suffix(self): + draft = IndividualDraftFactory(name='draft-someone-did-something-01-02',rev='00') + for r in range(0,4): + e = NewRevisionDocEventFactory(doc=draft,rev=f'{r:02d}') + draft.rev = f'{r:02d}' + draft.save_with_history([e]) + + received = self.getJson(dict(name=draft.name)) + self.assertEqual(received['rev'],'03') + self.assertIn('01-02-03',received['content_url']) + self.assertIn('01-02-02',received['previous']) + + received = self.getJson(dict(name=draft.name,rev='02')) + self.assertEqual(received['rev'],'02') + self.assertIn('01-02-02',received['content_url']) + + def test_rfc(self): + draft = WgDraftFactory() + for r in range(0,2): + e = NewRevisionDocEventFactory(doc=draft,rev=f'{r:02d}') + draft.rev = f'{r:02d}' + draft.save_with_history([e]) + + draft.docalias.create(name='rfc8000') + draft.set_state(State.objects.get(type_id='draft',slug='rfc')) + draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub')) + draft = reload_db_objects(draft) + rfc = draft + + number = rfc.rfc_number() + received = self.getJson(dict(name=number)) + self.assertEqual(received, dict( + content_url=rfc.get_href(), + name=rfc.canonical_name(), + previous=f'{draft.name}-{draft.rev}', + )) + + num_received = received + received = self.getJson(dict(name=rfc.canonical_name())) + self.assertEqual(num_received, received) + + def test_rfc_with_tombstone(self): + draft = WgDraftFactory() + for r in range(0,2): + e = NewRevisionDocEventFactory(doc=draft,rev=f'{r:02d}') + draft.rev = f'{r:02d}' + draft.save_with_history([e]) + + draft.docalias.create(name='rfc3261') # See views_doc.HAS_TOMBSTONE + draft.set_state(State.objects.get(type_id='draft',slug='rfc')) + draft.set_state(State.objects.get(type_id='draft-iesg', slug='pub')) + draft = reload_db_objects(draft) + rfc = draft + + # Some old rfcs had tombstones that shouldn't be used for comparisons + received = self.getJson(dict(name=rfc.canonical_name())) + self.assertTrue(received['previous'].endswith('00')) + + diff --git a/ietf/doc/urls.py b/ietf/doc/urls.py index 1bf8c1752..ceb2ff216 100644 --- a/ietf/doc/urls.py +++ b/ietf/doc/urls.py @@ -71,6 +71,11 @@ urlpatterns = [ url(r'^html/(?P[Rr][Ff][Cc] [0-9]+?)(\.txt|\.html)?/?$', views_doc.document_html), url(r'^idnits2-rfcs-obsoleted/?$', views_doc.idnits2_rfcs_obsoleted), url(r'^idnits2-rfc-status/?$', views_doc.idnits2_rfc_status), +# These two are proof-of-concept of a service that would redirect to the latest version +# url(r'^rfcdiff-latest/%(name)s(?:-%(rev)s)?(\.txt|\.html)?/?$' % settings.URL_REGEXPS, views_doc.rfcdiff_latest), +# url(r'^rfcdiff-latest/(?P[Rr][Ff][Cc] [0-9]+?)(\.txt|\.html)?/?$', views_doc.rfcdiff_latest), + url(r'^rfcdiff-latest-json/%(name)s(?:-%(rev)s)?(\.txt|\.html)?/?$' % settings.URL_REGEXPS, views_doc.rfcdiff_latest_json), + url(r'^rfcdiff-latest-json/(?P[Rr][Ff][Cc] [0-9]+?)(\.txt|\.html)?/?$', views_doc.rfcdiff_latest_json), url(r'^all/?$', views_search.index_all_drafts), url(r'^active/?$', views_search.index_active_drafts), diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 70e0e45cc..5c90cc16d 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -1772,8 +1772,142 @@ def idnits2_state(request, name, rev=None): doc.deststatus="Unknown" return render(request, 'doc/idnits2-state.txt', context={'doc':doc}, content_type='text/plain;charset=utf-8') +def find_doc_for_rfcdiff(name, rev): + if name.startswith('rfc0'): + name = "rfc" + name[3:].lstrip('0') + if name.startswith('review-') and re.search(r'-\d\d\d\d-\d\d$', name): + name = "%s-%s" % (name, rev) + if rev and not name.startswith('charter-') and re.search('[0-9]{1,2}-[0-9]{2}', rev): + name = "%s-%s" % (name, rev[:-3]) + rev = rev[-2:] + if re.match("^[0-9]+$", name): + name = f'rfc{name}' + if re.match("^[Rr][Ff][Cc] [0-9]+$",name): + name = f'rfc{name[4:]}' + + docs = Document.objects.filter(docalias__name=name, type_id='draft') + if rev and not docs.exists(): + # handle some special cases, like draft-ietf-tsvwg-ieee-802-11 + name = '%s-%s' % (name, rev) + rev=None + docs = Document.objects.filter(docalias__name=name, type_id='draft') + condition = 'no such document' + if not docs.exists() or docs.count() > 1: + return (condition, None, None) + doc = docs.get() + if not rev or (rev and doc.rev==rev): + condition = 'current version' + return condition, doc, None + else: + candidate = None + for h in doc.history_set.order_by("-time"): + if rev == h.rev: + candidate = h + break + if candidate: + condition = 'historic version' + return condition, doc, candidate + else: + condition = 'version dochistory not found' + return condition, doc, None + +# This is a proof of concept of a service that would redirect to the current revision +# def rfcdiff_latest(request, name, rev=None): +# condition, doc, history = find_doc_for_rfcdiff(name, rev) +# if not doc: +# raise Http404 +# if history: +# return redirect(history.get_href()) +# else: +# return redirect(doc.get_href()) +HAS_TOMBSTONE = [ + 2821, 2822, 2873, 2919, 2961, 3023, 3029, 3031, 3032, 3033, 3034, 3035, 3036, + 3037, 3038, 3042, 3044, 3050, 3052, 3054, 3055, 3056, 3057, 3059, 3060, 3061, + 3062, 3063, 3064, 3067, 3068, 3069, 3070, 3071, 3072, 3073, 3074, 3075, 3076, + 3077, 3078, 3080, 3081, 3082, 3084, 3085, 3086, 3087, 3088, 3089, 3090, 3094, + 3095, 3096, 3097, 3098, 3101, 3102, 3103, 3104, 3105, 3106, 3107, 3108, 3109, + 3110, 3111, 3112, 3113, 3114, 3115, 3116, 3117, 3118, 3119, 3120, 3121, 3123, + 3124, 3126, 3127, 3128, 3130, 3131, 3132, 3133, 3134, 3135, 3136, 3137, 3138, + 3139, 3140, 3141, 3142, 3143, 3144, 3145, 3147, 3149, 3150, 3151, 3152, 3153, + 3154, 3155, 3156, 3157, 3158, 3159, 3160, 3161, 3162, 3163, 3164, 3165, 3166, + 3167, 3168, 3169, 3170, 3171, 3172, 3173, 3174, 3176, 3179, 3180, 3181, 3182, + 3183, 3184, 3185, 3186, 3187, 3188, 3189, 3190, 3191, 3192, 3193, 3194, 3197, + 3198, 3201, 3202, 3203, 3204, 3205, 3206, 3207, 3208, 3209, 3210, 3211, 3212, + 3213, 3214, 3215, 3216, 3217, 3218, 3220, 3221, 3222, 3224, 3225, 3226, 3227, + 3228, 3229, 3230, 3231, 3232, 3233, 3234, 3235, 3236, 3237, 3238, 3240, 3241, + 3242, 3243, 3244, 3245, 3246, 3247, 3248, 3249, 3250, 3253, 3254, 3255, 3256, + 3257, 3258, 3259, 3260, 3261, 3262, 3263, 3264, 3265, 3266, 3267, 3268, 3269, + 3270, 3271, 3272, 3273, 3274, 3275, 3276, 3278, 3279, 3280, 3281, 3282, 3283, + 3284, 3285, 3286, 3287, 3288, 3289, 3290, 3291, 3292, 3293, 3294, 3295, 3296, + 3297, 3298, 3301, 3302, 3303, 3304, 3305, 3307, 3308, 3309, 3310, 3311, 3312, + 3313, 3315, 3317, 3318, 3319, 3320, 3321, 3322, 3323, 3324, 3325, 3326, 3327, + 3329, 3330, 3331, 3332, 3334, 3335, 3336, 3338, 3340, 3341, 3342, 3343, 3346, + 3348, 3349, 3351, 3352, 3353, 3354, 3355, 3356, 3360, 3361, 3362, 3363, 3364, + 3366, 3367, 3368, 3369, 3370, 3371, 3372, 3374, 3375, 3377, 3378, 3379, 3383, + 3384, 3385, 3386, 3387, 3388, 3389, 3390, 3391, 3394, 3395, 3396, 3397, 3398, + 3401, 3402, 3403, 3404, 3405, 3406, 3407, 3408, 3409, 3410, 3411, 3412, 3413, + 3414, 3415, 3416, 3417, 3418, 3419, 3420, 3421, 3422, 3423, 3424, 3425, 3426, + 3427, 3428, 3429, 3430, 3431, 3433, 3434, 3435, 3436, 3437, 3438, 3439, 3440, + 3441, 3443, 3444, 3445, 3446, 3447, 3448, 3449, 3450, 3451, 3452, 3453, 3454, + 3455, 3458, 3459, 3460, 3461, 3462, 3463, 3464, 3465, 3466, 3467, 3468, 3469, + 3470, 3471, 3472, 3473, 3474, 3475, 3476, 3477, 3480, 3481, 3483, 3485, 3488, + 3494, 3495, 3496, 3497, 3498, 3501, 3502, 3503, 3504, 3505, 3506, 3507, 3508, + 3509, 3511, 3512, 3515, 3516, 3517, 3518, 3520, 3521, 3522, 3523, 3524, 3525, + 3527, 3529, 3530, 3532, 3533, 3534, 3536, 3537, 3538, 3539, 3541, 3543, 3544, + 3545, 3546, 3547, 3548, 3549, 3550, 3551, 3552, 3555, 3556, 3557, 3558, 3559, + 3560, 3562, 3563, 3564, 3565, 3568, 3569, 3570, 3571, 3572, 3573, 3574, 3575, + 3576, 3577, 3578, 3579, 3580, 3581, 3582, 3583, 3584, 3588, 3589, 3590, 3591, + 3592, 3593, 3594, 3595, 3597, 3598, 3601, 3607, 3609, 3610, 3612, 3614, 3615, + 3616, 3625, 3627, 3630, 3635, 3636, 3637, 3638 +] +def rfcdiff_latest_json(request, name, rev=None): + response = dict() + condition, document, history = find_doc_for_rfcdiff(name, rev) - + if condition == 'no such document': + raise Http404 + elif condition in ('historic version', 'current version'): + doc = history if history else document + if not rev and doc.is_rfc(): + response['content_url'] = doc.get_href() + response['name']=doc.canonical_name() + if doc.name != doc.canonical_name(): + prev_rev = doc.rev + if int(doc.rfc_number()) in HAS_TOMBSTONE and prev_rev != '00': + prev_rev = f'{(int(doc.rev)-1):02d}' + response['previous'] = f'{doc.name}-{prev_rev}' + else: + doc.is_rfc = lambda: False + response['content_url'] = doc.get_href() + response['rev'] = doc.rev + response['name'] = doc.name + if doc.rev == '00': + replaces_docs = (history.doc if condition=='historic version' else doc).related_that_doc('replaces') + if replaces_docs: + replaces = replaces_docs[0].document + response['previous'] = f'{replaces.name}-{replaces.rev}' + else: + match = re.search("-(rfc)?([0-9][0-9][0-9]+)bis(-.*)?$", name) + if match and match.group(2): + response['previous'] = f'rfc{match.group(2)}' + else: + response['previous'] = f'{doc.name}-{(int(doc.rev)-1):02d}' + elif condition == 'version dochistory not found': + response['warning'] = 'History for this version not found - these results are speculation' + response['name'] = document.name + response['rev'] = rev + document.rev = rev + document.is_rfc = lambda: False + response['content_url'] = document.get_href() + if int(rev)>0: + response['previous'] = f'{document.name}-{(int(rev)-1):02d}' + else: + match = re.search("-(rfc)?([0-9][0-9][0-9]+)bis(-.*)?$", name) + if match and match.group(2): + response['previous'] = f'rfc{match.group(2)}' + if not response: + raise Http404 + return HttpResponse(json.dumps(response), content_type='application/json')