diff --git a/ietf/doc/models.py b/ietf/doc/models.py index a9834f5e3..472367db9 100644 --- a/ietf/doc/models.py +++ b/ietf/doc/models.py @@ -235,51 +235,66 @@ class Document(DocumentInfo): return "charter-ietf-%s" % self.chartered_group.acronym return name + def canonical_docalias(self): + return self.docalias_set.get(name=self.name) + def display_name(self): name = self.canonical_name() if name.startswith('rfc'): name = name.upper() return name - def related_that(self, relationship): - """Return the documents that are source of relationship targeting self.""" + def relations_that(self, relationship): + """Return the related-document objects that describe a given relationship targeting self.""" if isinstance(relationship, str): relationship = [ relationship ] if isinstance(relationship, tuple): relationship = list(relationship) if not isinstance(relationship, list): raise TypeError("Expected a string, tuple or list, received %s" % type(relationship)) - return Document.objects.filter(relateddocument__target__document=self, relateddocument__relationship__in=relationship) + return RelatedDocument.objects.filter(target__document=self, relationship__in=relationship).select_related('source') - def related_that_doc(self, relationship): - """Return the doc aliases that are target of relationship originating from self.""" + def all_relations_that(self, relationship, related=None): + if not related: + related = [] + rels = self.relations_that(relationship) + for r in rels: + if not r in related: + related += [ r ] + related = r.source.all_relations_that(relationship, related) + return related + + def relations_that_doc(self, relationship): + """Return the related-document objects that describe a given relationship from self to other documents.""" if isinstance(relationship, str): relationship = [ relationship ] if isinstance(relationship, tuple): relationship = list(relationship) if not isinstance(relationship, list): raise TypeError("Expected a string, tuple or list, received %s" % type(relationship)) - return DocAlias.objects.filter(relateddocument__source=self, relateddocument__relationship__in=relationship) + return RelatedDocument.objects.filter(source=self, relationship__in=relationship).select_related('target__document') + + def all_relations_that_doc(self, relationship, related=None): + if not related: + related = [] + rels = self.relations_that_doc(relationship) + for r in rels: + if not r in related: + related += [ r ] + related = r.target.document.all_relations_that_doc(relationship, related) + return related + + def related_that(self, relationship): + return list(set([x.source.docalias_set.get(name=x.source.name) for x in self.relations_that(relationship)])) def all_related_that(self, relationship, related=None): - if related is None: - related = [] - rel = self.related_that(relationship) - for doc in rel: - if not doc in related: - related += [ doc ] - related = doc.document.all_related_that(relationship, related) - return related + return list(set([x.source.docalias_set.get(name=x.source.name) for x in self.all_relations_that(relationship)])) + + def related_that_doc(self, relationship): + return list(set([x.target for x in self.relations_that_doc(relationship)])) def all_related_that_doc(self, relationship, related=None): - if related is None: - related = [] - rel = self.related_that_doc(relationship) - for alias in rel: - if not alias in related: - related += [ alias ] - related = alias.document.all_related_that_doc(relationship, related) - return related + return list(set([x.target for x in self.all_relations_that_doc(relationship)])) def telechat_date(self, e=None): if not e: @@ -352,7 +367,7 @@ class Document(DocumentInfo): elif state.slug == "repl": rs = self.related_that("replaces") if rs: - return mark_safe("Replaced by " + ", ".join("%s" % (urlreverse('doc_view', kwargs=dict(name=name)), name) for name in rs)) + return mark_safe("Replaced by " + ", ".join("%s" % (urlreverse('doc_view', kwargs=dict(name=alias.document)), alias.document) for alias in rs)) else: return "Replaced" elif state.slug == "active": @@ -383,6 +398,12 @@ class Document(DocumentInfo): from ietf.ipr.models import IprDocAlias return IprDocAlias.objects.filter(doc_alias__document=self) + def related_ipr(self): + """Returns the IPR disclosures against this document and those documents this + document directly or indirectly obsoletes or replaces + """ + from ietf.ipr.models import IprDocAlias + return IprDocAlias.objects.filter(doc_alias__in=list(self.docalias_set.all())+self.all_related_that_doc(['obs','replaces'])) class RelatedDocHistory(models.Model): diff --git a/ietf/doc/views_doc.py b/ietf/doc/views_doc.py index 2b8460ca4..0291f0412 100644 --- a/ietf/doc/views_doc.py +++ b/ietf/doc/views_doc.py @@ -49,6 +49,7 @@ from ietf.utils.history import find_history_active_at from ietf.ietfauth.utils import * from ietf.doc.views_status_change import RELATION_SLUGS as status_change_relationships from ietf.wgcharter.utils import historic_milestones_for_charter +from ietf.ipr.models import IprDocAlias def render_document_top(request, doc, tab, name): tabs = [] @@ -92,7 +93,7 @@ def document_main(request, name, rev=None): group = doc.group if doc.type_id == 'conflrev': - conflictdoc = doc.related_that_doc('conflrev').get().document + conflictdoc = doc.related_that_doc('conflrev')[0].document revisions = [] for h in doc.history_set.order_by("time", "id"): @@ -278,11 +279,11 @@ def document_main(request, name, rev=None): search_archive = urllib.quote(search_archive, safe="~") # conflict reviews - conflict_reviews = [d.name for d in doc.related_that("conflrev")] + conflict_reviews = [d.document.name for d in doc.related_that("conflrev")] status_change_docs = doc.related_that(status_change_relationships) - status_changes = [ rel for rel in status_change_docs if rel.get_state_slug() in ('appr-sent','appr-pend')] - proposed_status_changes = [ rel for rel in status_change_docs if rel.get_state_slug() in ('needshep','adrev','iesgeval','defer','appr-pr')] + status_changes = [ rel.document for rel in status_change_docs if rel.document.get_state_slug() in ('appr-sent','appr-pend')] + proposed_status_changes = [ rel.document for rel in status_change_docs if rel.document.get_state_slug() in ('needshep','adrev','iesgeval','defer','appr-pr')] # remaining actions actions = [] @@ -345,9 +346,9 @@ def document_main(request, name, rev=None): replaces=[d.name for d in doc.related_that_doc("replaces")], replaced_by=[d.name for d in doc.related_that("replaces")], updates=[prettify_std_name(d.name) for d in doc.related_that_doc("updates")], - updated_by=[prettify_std_name(d.canonical_name()) for d in doc.related_that("updates")], + updated_by=[prettify_std_name(d.document.canonical_name()) for d in doc.related_that("updates")], obsoletes=[prettify_std_name(d.name) for d in doc.related_that_doc("obs")], - obsoleted_by=[prettify_std_name(d.canonical_name()) for d in doc.related_that("obs")], + obsoleted_by=[prettify_std_name(d.document.canonical_name()) for d in doc.related_that("obs")], conflict_reviews=conflict_reviews, status_changes=status_changes, proposed_status_changes=proposed_status_changes, diff --git a/ietf/idrfc/mails.py b/ietf/idrfc/mails.py index 8def9ca78..70fc7d22b 100644 --- a/ietf/idrfc/mails.py +++ b/ietf/idrfc/mails.py @@ -147,7 +147,7 @@ def generate_last_call_announcementREDESIGN(request, doc): doc.filled_title = textwrap.fill(doc.title, width=70, subsequent_indent=" " * 3) - iprs, _ = iprs_from_docs(related_docs(DocAlias.objects.get(name=doc.canonical_name()),[])) + iprs, _ = iprs_from_docs(related_docs(DocAlias.objects.get(name=doc.canonical_name()))) if iprs: ipr_links = [ urlreverse("ietf.ipr.views.show", kwargs=dict(ipr_id=i.ipr_id)) for i in iprs] ipr_links = [ settings.IDTRACKER_BASE_URL+url if not url.startswith("http") else url for url in ipr_links ] diff --git a/ietf/idrfc/views_search.py b/ietf/idrfc/views_search.py index 322710256..21ab15a99 100644 --- a/ietf/idrfc/views_search.py +++ b/ietf/idrfc/views_search.py @@ -44,6 +44,7 @@ from ietf.doc.models import * from ietf.person.models import * from ietf.group.models import * from ietf.ipr.models import IprDocAlias +from ietf.ipr.search import related_docs from ietf.idindex.index import active_drafts_index_by_group class SearchForm(forms.Form): @@ -135,9 +136,32 @@ def fill_in_search_attributes(docs): for d in docs: d.iprs = [] +# Consider reworking the following block using all_relations_that_doc? That might simplify the final assembly +# down to the code at "if a not in docs_dict"... + + rel_docs = [] + rel_id_camefrom = {} + for d in docs: + if isinstance(d,DocAlias): + d = d.document + rel_this_doc = d.all_related_that_doc(['replaces','obs']) + for rel in rel_this_doc: + rel_id_camefrom.setdefault(rel.document.pk,[]).append(d.pk) + rel_docs += [x.document for x in rel_this_doc] + ipr_docaliases = IprDocAlias.objects.filter(doc_alias__document__in=doc_ids).select_related('doc_alias') for a in ipr_docaliases: docs_dict[a.doc_alias.document_id].iprs.append(a) + + rel_docs_dict = dict((d.pk, d) for d in rel_docs) + rel_doc_ids = rel_docs_dict.keys() + + rel_ipr_docaliases = IprDocAlias.objects.filter(doc_alias__document__in=rel_doc_ids).select_related('doc_alias') + for a in rel_ipr_docaliases: + if a.doc_alias.document_id in rel_id_camefrom: + for k in rel_id_camefrom[a.doc_alias.document_id]: + if a not in docs_dict[k].iprs: + docs_dict[k].iprs.append(a) # telechat date, can't do this with above query as we need to get TelechatDocEvents out seen = set() diff --git a/ietf/ipr/related.py b/ietf/ipr/related.py index 11304b25c..27b762fc7 100644 --- a/ietf/ipr/related.py +++ b/ietf/ipr/related.py @@ -1,137 +1,15 @@ # Copyright The IETF Trust 2007, All Rights Reserved -from django.conf import settings -from django.db.models import Q -from ietf.idtracker.models import InternetDraft, Rfc - -inverse = { - 'updates': 'is_updated_by', - 'is_updated_by': 'updates', - 'obsoletes': 'is_obsoleted_by', - 'is_obsoleted_by': 'obsoletes', - 'replaces': 'is_replaced_by', - 'is_replaced_by': 'replaces', - 'is_rfc_of': 'is_draft_of', - 'is_draft_of': 'is_rfc_of', - } - -display_relation = { - 'updates': 'that updated', - 'is_updated_by': 'that was updated by', - 'obsoletes': 'that obsoleted', - 'is_obsoleted_by': 'that was obsoleted by', - 'replaces': 'that replaced', - 'is_replaced_by': 'that was replaced by', - 'is_rfc_of': 'which came from', - 'is_draft_of': 'that was published as', - } - -def set_related(obj, rel, target): - #print obj, rel, target - # remember only the first relationship we find. - if not hasattr(obj, "related"): - obj.related = target - obj.relation = display_relation[rel] - return obj - -def set_relation(first, rel, second): - set_related(first, rel, second) - set_related(second, inverse[rel], first) - -def related_docs(doc, found = []): - """Get a list of document related to the given document. - """ - #print "\nrelated_docs(%s, %s)" % (doc, found) - found.append(doc) - if isinstance(doc, Rfc): - try: - item = InternetDraft.objects.get(rfc_number=doc.rfc_number) - if not item in found: - set_relation(doc, 'is_rfc_of', item) - found = related_docs(item, found) - except InternetDraft.DoesNotExist: - pass - for entry in doc.updated_or_obsoleted_by.all(): - item = entry.rfc - if not item in found: - action = inverse[entry.action.lower()] - set_relation(doc, action, item) - found = related_docs(item, found) - for entry in doc.updates_or_obsoletes.all(): - item = entry.rfc_acted_on - if not item in found: - action = entry.action.lower() - set_relation(doc, action, item) - found = related_docs(item, found) - if isinstance(doc, InternetDraft): - if doc.replaced_by_id: - item = doc.replaced_by - if not item in found: - set_relation(doc, 'is_replaced_by', item) - found = related_docs(item, found) - for item in doc.replaces_set.all(): - if not item in found: - set_relation(doc, 'replaces', item) - found = related_docs(item, found) - if doc.rfc_number: - item = Rfc.objects.get(rfc_number=doc.rfc_number) - if not item in found: - set_relation(doc, 'is_draft_of', item) - found = related_docs(item, found) - return found - -def related_docsREDESIGN(alias, _): - """Get related document aliases to given alias through depth-first search.""" - from ietf.doc.models import RelatedDocument - from ietf.doc.proxy import DraftLikeDocAlias - - mapping = dict( - updates='that updated', - obs='that obsoleted', - replaces='that replaced', - ) - inverse_mapping = dict( - updates='that was updated by', - obs='that was obsoleted by', - replaces='that was replaced by', - ) +def related_docs(alias): + results = list(alias.document.docalias_set.all()) - res = [ alias ] - remaining = [ alias ] - while remaining: - a = remaining.pop() - related = RelatedDocument.objects.filter(relationship__in=mapping.keys()).filter(Q(source=a.document) | Q(target=a)) - for r in related: - if r.source == a.document: - found = DraftLikeDocAlias.objects.filter(pk=r.target_id) - inverse = True - else: - found = DraftLikeDocAlias.objects.filter(document=r.source) - inverse = False + rels = alias.document.all_relations_that_doc(['replaces','obs']) - for x in found: - if not x in res: - x.related = a - x.relation = (inverse_mapping if inverse else mapping)[r.relationship_id] - res.append(x) - remaining.append(x) - - # there's one more source of relatedness, a draft can have been published - aliases = DraftLikeDocAlias.objects.filter(document=a.document).exclude(pk__in=[x.pk for x in res]) - for oa in aliases: - rel = None - if a.name.startswith("rfc") and oa.name.startswith("draft"): - rel = "that was published as" - elif a.name.startswith("draft") and oa.name.startswith("rfc"): - rel = "which came from" - - if rel: - oa.related = a - oa.relation = rel - res.append(oa) - remaining.append(oa) - - return res - -if settings.USE_DB_REDESIGN_PROXY_CLASSES: - related_docs = related_docsREDESIGN + for rel in rels: + rel_aliases = list(rel.target.document.docalias_set.all()) + + for x in rel_aliases: + x.related = rel + x.relation = rel.relationship.revname + results += rel_aliases + return list(set(results)) diff --git a/ietf/ipr/search.py b/ietf/ipr/search.py index fc71f7de9..9ea73a09c 100644 --- a/ietf/ipr/search.py +++ b/ietf/ipr/search.py @@ -3,15 +3,15 @@ import codecs import re import os.path -import django.utils.html +from django.http import HttpResponseRedirect, Http404 from django.shortcuts import render_to_response as render from django.template import RequestContext from django.conf import settings -from django.http import Http404 -from ietf.idtracker.models import IETFWG, InternetDraft, Rfc -from ietf.ipr.models import IprRfc, IprDraft, IprDetail +from ietf.ipr.models import IprDraft, IprDetail from ietf.ipr.related import related_docs from ietf.utils import log, normalize_draftname +from ietf.group.models import Group +from ietf.doc.models import DocAlias def mark_last_doc(iprs): @@ -24,16 +24,9 @@ def mark_last_doc(iprs): def iprs_from_docs(docs): iprs = [] for doc in docs: - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.ipr.models import IprDocAlias - disclosures = [ x.ipr for x in IprDocAlias.objects.filter(doc_alias=doc, ipr__status__in=[1,3]) ] - - elif isinstance(doc, InternetDraft): - disclosures = [ item.ipr for item in IprDraft.objects.filter(document=doc, ipr__status__in=[1,3]) ] - elif isinstance(doc, Rfc): - disclosures = [ item.ipr for item in IprRfc.objects.filter(document=doc, ipr__status__in=[1,3]) ] - else: - raise ValueError("Doc type is neither draft nor rfc: %s" % doc) + from ietf.ipr.models import IprDocAlias + disclosures = [ x.ipr for x in IprDocAlias.objects.filter(doc_alias=doc, ipr__status__in=[1,3]) ] + doc.iprs = None if disclosures: doc.iprs = disclosures iprs += disclosures @@ -54,11 +47,7 @@ def patent_file_search(url, q): return False def search(request, type="", q="", id=""): - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.group.models import Group - wgs = Group.objects.filter(type="wg").exclude(acronym="2000").select_related().order_by("acronym") - else: - wgs = IETFWG.objects.filter(group_type__group_type_id=1).exclude(group_acronym__acronym='2000').select_related().order_by('acronym.acronym') + wgs = Group.objects.filter(type="wg").select_related().order_by("acronym") args = request.REQUEST.items() if args: for key, value in args: @@ -78,40 +67,22 @@ def search(request, type="", q="", id=""): if type == "document_search": if q: q = normalize_draftname(q) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.doc.proxy import DraftLikeDocAlias - start = DraftLikeDocAlias.objects.filter(name__contains=q, name__startswith="draft") - else: - start = InternetDraft.objects.filter(filename__contains=q) + start = DocAlias.objects.filter(name__contains=q, name__startswith="draft") if id: - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.doc.proxy import DraftLikeDocAlias - start = DraftLikeDocAlias.objects.filter(name=id) - else: - try: - id = int(id,10) - except: - id = -1 - start = InternetDraft.objects.filter(id_document_tag=id) + start = DocAlias.objects.filter(name=id) if type == "rfc_search": if q: try: q = int(q, 10) except: q = -1 - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.doc.proxy import DraftLikeDocAlias - start = DraftLikeDocAlias.objects.filter(name__contains=q, name__startswith="rfc") - else: - start = Rfc.objects.filter(rfc_number=q) + start = DocAlias.objects.filter(name__contains=q, name__startswith="rfc") if start.count() == 1: first = start[0] doc = str(first) - # get all related drafts, then search for IPRs on all - - docs = related_docs(first, []) - #docs = get_doclist.get_doclist(first) + docs = related_docs(first) iprs, docs = iprs_from_docs(docs) + iprs.sort(key=lambda x:(x.submitted_date,x.ipr_id)) return render("ipr/search_doc_result.html", {"q": q, "first": first, "iprs": iprs, "docs": docs, "doc": doc }, context_instance=RequestContext(request) ) elif start.count(): @@ -162,23 +133,16 @@ def search(request, type="", q="", id=""): # Search by wg acronym # Document list with IPRs elif type == "wg_search": - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.doc.proxy import DraftLikeDocAlias - try: - docs = list(DraftLikeDocAlias.objects.filter(document__group__acronym=q)) - docs += list(DraftLikeDocAlias.objects.filter(document__relateddocument__target__in=docs, document__relateddocument__relationship="replaces")) - except: - docs = [] - else: - try: - docs = list(InternetDraft.objects.filter(group__acronym=q)) - except: - docs = [] - docs += [ draft.replaced_by for draft in docs if draft.replaced_by_id ] - docs += list(Rfc.objects.filter(group_acronym=q)) + docs = list(DocAlias.objects.filter(document__group__acronym=q)) + related = [] + for doc in docs: + doc.product_of_this_wg = True + related += related_docs(doc) - docs = [ doc for doc in docs if doc.ipr.count() ] - iprs, docs = iprs_from_docs(docs) + iprs, docs = iprs_from_docs(list(set(docs+related))) + docs = [ doc for doc in docs if doc.iprs ] + docs = sorted(docs,key=lambda x: max([ipr.submitted_date for ipr in x.iprs]),reverse=True) + count = len(iprs) return render("ipr/search_wg_result.html", {"q": q, "docs": docs, "count": count }, context_instance=RequestContext(request) ) @@ -186,21 +150,15 @@ def search(request, type="", q="", id=""): # Search by rfc and id title # Document list with IPRs elif type == "title_search": - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - from ietf.doc.proxy import DraftLikeDocAlias - try: - docs = list(DraftLikeDocAlias.objects.filter(document__title__icontains=q)) - except: - docs = [] - else: - try: - docs = list(InternetDraft.objects.filter(title__icontains=q)) - except: - docs = [] - docs += list(Rfc.objects.filter(title__icontains=q)) + docs = list(DocAlias.objects.filter(document__title__icontains=q)) + related = [] + for doc in docs: + related += related_docs(doc) + + iprs, docs = iprs_from_docs(list(set(docs+related))) + docs = [ doc for doc in docs if doc.iprs ] + docs = sorted(docs,key=lambda x: max([ipr.submitted_date for ipr in x.iprs]),reverse=True) - docs = [ doc for doc in docs if doc.ipr.count() ] - iprs, docs = iprs_from_docs(docs) count = len(iprs) return render("ipr/search_doctitle_result.html", {"q": q, "docs": docs, "count": count }, context_instance=RequestContext(request) ) @@ -220,9 +178,6 @@ def search(request, type="", q="", id=""): else: raise Http404("Unexpected search type in IPR query: %s" % type) - return django.http.HttpResponseRedirect(request.path) + return HttpResponseRedirect(request.path) - if settings.USE_DB_REDESIGN_PROXY_CLASSES: - for wg in wgs: - wg.group_acronym = wg # proxy group_acronym for select box return render("ipr/search.html", {"wgs": wgs}, context_instance=RequestContext(request)) diff --git a/ietf/ipr/views.py b/ietf/ipr/views.py index ef33f391c..4cca6d8f1 100644 --- a/ietf/ipr/views.py +++ b/ietf/ipr/views.py @@ -131,14 +131,6 @@ def show(request, ipr_id=None, removed=None): context_instance=RequestContext(request)) - -def form(request): - wgs = IETFWG.objects.filter(group_type__group_type_id=1).exclude(group_acronym__acronym='2000').select_related().order_by('acronym.acronym') - log("Search form") - return render("ipr/search.html", {"wgs": wgs}, context_instance=RequestContext(request)) - - - # ---- Helper functions ------------------------------------------------------ def get_section_list(ipr): diff --git a/ietf/templates/doc/document_draft.html b/ietf/templates/doc/document_draft.html index 45f21e99c..6a39e1390 100644 --- a/ietf/templates/doc/document_draft.html +++ b/ietf/templates/doc/document_draft.html @@ -222,7 +222,7 @@