Refactor release notes rendering
authorMagnus Hagander <magnus@hagander.net>
Thu, 4 May 2023 10:38:21 +0000 (12:38 +0200)
committerMagnus Hagander <magnus@hagander.net>
Thu, 4 May 2023 10:50:20 +0000 (12:50 +0200)
Move to use core_version for more information, making some purges easier
and more predictable as well. Just create a hardcoded list of versions
prior to what we have real version entries for, it's just a couple that
we need to reference old release notes for (pre-6.3), so it simplifies a
lot.

Fixes (again) the release notes purging to use xkeys as set, which was
accidentally and partially undone in ac618d1b.

Generally makes the code easier to read and fixes a few cornercases.

Templates also much simplified, with the list template accidentally
already committed as part of 19682de8.

Reviewed-by: Jonathan Katz
pgweb/core/models.py
pgweb/core/templatetags/pgfilters.py
pgweb/docs/views.py
pgweb/urls.py
templates/docs/release_notes.html

index 2ef2692b027de4be8f5e9a625c884af1f92dc9aa..98716d7606d4ffb971f96c2501a3fdad1f247f09 100644 (file)
@@ -99,15 +99,12 @@ class Version(models.Model):
         yield '/about/featurematrix/$'
         yield '/versions.rss'
         yield '/versions.json'
-        # the next two purge the cache for the release notes.
-        # note that we need to purge all the release notes for a major version
-        # tree as we currently have a sidebar that links to all of the minor
-        # versions within a tree on an individual release notes page.
-        yield '/docs/release/{:.0f}'.format(self.tree)
-        yield '/docs/release/$'
 
     def purge_xkeys(self):
+        # Purge the documentation index pages as well as all documentation pages for
+        # this version.
         yield 'pgdocs_all'
+        yield 'pgdocs_{}'.format(self.numtree)
 
 
 class Country(models.Model):
index bcdb43a5ba73fc2544b77071a693495af01dbb19..d66630d874ae7a955e47b6023268b687f23277f2 100644 (file)
@@ -4,6 +4,7 @@ from django.utils.safestring import mark_safe
 from django.template.loader import get_template
 from django.conf import settings
 
+from decimal import Decimal
 import os
 from pathlib import Path
 import json
@@ -89,13 +90,29 @@ def tojson(value):
     return json.dumps(value)
 
 
+@register.filter()
+def pg_major_version(major_version):
+    """
+    Turn a major version into a string. This means before 10 it's 2-digit,
+    and after 10 it's 1-digit.
+    """
+    d = Decimal(major_version)
+    if d >= 10 or d <= 1:
+        return '{:f}'.format(d.normalize())
+    else:
+        return str(d)
+
+
 @register.filter()
 def release_notes_pg_minor_version(minor_version, major_version):
     """Formats the minor version number to the appropriate PostgreSQL version.
     This is particularly for very old version of PostgreSQL.
     """
-    if str(major_version) in ['0', '1']:
-        return str(minor_version)[2:4]
+    if str(major_version) in ['0', '1', '1.0']:
+        if minor_version == 0:
+            return '0'
+        else:
+            return '{:02}'.format(minor_version)
     return minor_version
 
 
index e79d8900e4078346ae663aca4f3ae3f0646c92f6..1f2e39859d4d919d06010afcfa85ec2ae1d0e375 100644 (file)
@@ -267,121 +267,127 @@ def manualarchive(request):
     return r
 
 
-def release_notes(request, major_version=None, minor_version=None):
+# Store a list of versions for which we have release notes, but nothing else,
+# so we don't have to add them to core_version.
+# NOTE! Order-sensitive!
+_release_notes_only_versions = [
+    # PostgreSQL 6.2
+    [Decimal('6.2'), 1],
+    [Decimal('6.2'), 0],
+    # PostgreSQL 6.1
+    [Decimal('6.1'), 1],
+    [Decimal('6.1'), 0],
+    # PostgreSQL 6.0
+    [Decimal('6.0'), 0],
+    # PostgresSQL 1
+    [1, 9],
+    [1, 2],
+    [1, 1],
+    [1, 0],
+    # Postgres95
+    [0, 3],
+    [0, 2],
+    [0, 1],
+]
+release_notes_only_versions = [{'major': major, 'minor': minor} for major, minor in _release_notes_only_versions]
+
+
+def release_notes_list(request):
+    """Lists the available release notes"""
+    # We only keep 6.3 and newer in core_version (for legacy reasons)
+    releases = exec_to_dict("SELECT tree AS major, minor FROM core_version INNER JOIN generate_series(0, latestminor) g(minor) ON true WHERE testing=0 AND tree > 6.2 ORDER BY tree DESC, minor DESC")
+
+    r = render_pgweb(request, 'docs', 'docs/release_notes_list.html', {
+        'releases': releases + release_notes_only_versions,
+    })
+    r['xkey'] = 'pgdocs_all'
+    return r
+
+
+def release_notes(request, version):
     """Contains the main archive of release notes."""
-    # this query gets a list of a unique set of release notes for each version of
-    # PostgreSQL. From PostgreSQL 9.4+, release notes are only present for their
-    # specific version of PostgreSQL, so all legacy release notes are present in
-    # 9.3 and older
-    # First the query identifies all of the release note files that have been loaded
-    # into the docs. We will limit our lookup to release notes from 9.3 on up,
-    # given 9.3 has all the release notes for PostgreSQL 9.3 and older
-    # From there, it parses the version the release notes are for
-    # from the file name, and breaks it up into "major" and "minor" version from
-    # our understanding of how PostgreSQL version numbering is handled, which is
-    # in 3 camps: 1 and older, 6.0 - 9.6, 10 - current
-    # It is then put into a unique set
-    # Lastly, we determine the next/previous versions (lead/lag) so we are able
-    # to easily page between the different versions in the unique release note view
-    # We only include the content if we are doing an actual lookup on an exact
-    # major/minor release pair, to limit how much data we load into memory
-    sql = """
-    SELECT
-        {content}
-        file, major, minor,
-        lag(minor) OVER (PARTITION BY major ORDER BY minor) AS previous,
-        lead(minor) OVER (PARTITION BY major ORDER BY minor) AS next
-    FROM (
-        SELECT DISTINCT ON (file, major, minor)
-            {content}
-            file,
-            CASE
-                WHEN v[1]::int >= 10 THEN v[1]::numeric
-                WHEN v[1]::int <= 1 THEN v[1]::int
-                ELSE array_to_string(v[1:2], '.')::numeric END AS major,
-            COALESCE(
-                CASE
-                    WHEN v[1]::int >= 10 THEN v[2]
-                    WHEN v[1]::int <= 1 THEN '.' || v[2]
-                    ELSE v[3]
-                END::numeric, 0
-            ) AS minor
-        FROM (
-            SELECT
-                {content}
-                file,
-                string_to_array(regexp_replace(file, 'release-(.*)\\.htm.*', '\\1'), '-') AS v
-            FROM docs
-            WHERE file ~ '^release-\\d+' AND version >= 9.3
-        ) r
-    ) rr
-    """
-    params = []
-    # if the major + minor version are provided, then we want to narrow down
-    # the results to all the release notes for the minor version, as we need the
-    # list of the entire set in order to generate the nice side bar in the release
-    # notes
-    # otherwise ensure the release notes are returned in order
-    if major_version is not None and minor_version is not None:
-        # a quick check to see if major is one of 6 - 9 as a whole number. If
-        # it is, this may be because someone is trying to up a major version
-        # directly from the URL, e.g. "9.1", even through officially the release
-        # number was "9.1.0".
-        # anyway, we shouldn't 404, but instead transpose from "9.1" to "9.1.0".
-        # if it's not an actual PostgreSQL release (e.g. "9.9"), then it will
-        # 404 at a later step.
-        if major_version in ['6', '7', '8', '9']:
-            major_version = "{}.{}".format(major_version, minor_version)
-            minor_version = '0'
-        # at this point, include the content
-        sql = sql.format(content="content,")
-        # restrict to the major version, order from latest to earliest minor
-        sql = """{}
-        WHERE rr.major = %s
-        ORDER BY rr.minor DESC""".format(sql)
-        params += [major_version]
-    else:
-        sql = sql.format(content="")
-        sql += """
-        ORDER BY rr.major DESC, rr.minor DESC;
-        """
-    # run the query, loading a list of dict that contain all of the release
-    # notes that are filtered out by the query
-    release_notes = exec_to_dict(sql, params)
-    # determine which set of data to pass to the template:
-    # if both major/minor versions are present, we will load the release notes
-    # if neither are present, we load the list of all of the release notes to list out
-    if major_version is not None and minor_version is not None:
-        # first, see if any release notes were returned; if not, raise a 404
-        if not release_notes:
-            raise Http404()
-        # next, see if we can find the specific release notes we are looking for
-        # format what the "minor" version should look like
-        try:
-            minor = Decimal('0.{}'.format(minor_version) if major_version in ['0', '1'] else minor_version)
-        except TypeError:
-            raise Http404()
-        try:
-            release_note = [r for r in release_notes if r['minor'] == minor][0]
-        except IndexError:
-            raise Http404()
-        # of course, if nothing is found, return a 404
-        if not release_note:
-            raise Http404()
-        context = {
-            'major_version': major_version,
-            'minor_version': minor_version,
-            'release_note': release_note,
-            'release_notes': release_notes
-        }
+
+    version_pieces = version.split('.')  # Gives 1, 2 or 3 pieces due to regexp
+    if len(version_pieces) == 3:
+        # This is always major.major.minor
+        major_version = Decimal('.'.join(version_pieces[0:2]))
+        minor_version = Decimal(version_pieces[2])
+        if major_version >= 10:
+            # There is no three-digit version for 10+, so redirect back
+            return HttpResponseRedirect('/docs/release/{}/'.format(major_version))
+        if minor_version > 0:
+            version_file = 'release-{}-{}.html'.format(str(major_version).replace('.', '-'), minor_version)
+        else:
+            version_file = 'release-{}-{}.html'.format(str(major_version).replace('.', '-'))
+    elif len(version_pieces) == 2:
+        # This can be either a full version (10.3) *or* it can be
+        # a major version without minor (9.5).
+        if int(version_pieces[0]) >= 10 or int(version_pieces[0]) <= 1:
+            major_version = Decimal(version_pieces[0])
+            minor_version = Decimal(version_pieces[1])
+            if major_version > 1:
+                if minor_version == 0:
+                    version_file = 'release-{}.html'.format(major_version)
+                else:
+                    version_file = 'release-{}-{}.html'.format(major_version, minor_version)
+            elif major_version in (0, 1):
+                if minor_version == 0:
+                    version_file = 'release-{}-0.html'.format(major_version)
+                else:
+                    version_file = 'release-{}-{:02}.html'.format(major_version, minor_version)
+        else:
+            # Major version without a minor we redirect to the .0 minor
+            return HttpResponseRedirect('/docs/release/{}.{}.0/'.format(major_version, minor_version))
     else:
-        context = {'release_notes': release_notes}
+        # Single digit major version, so redirect to a point-zero version of it
+        if version_pieces[0] == '0':
+            # Postgres95 did not have a .0 version :O
+            return HttpResponseRedirect('/docs/release/0.1/')
+        else:
+            return HttpResponseRedirect('/docs/release/{}.0/'.format(Decimal(version_pieces[0])))
 
-    r = render_pgweb(request, 'docs', 'docs/release_notes.html', context)
-    if major_version:
-        r['xkey'] = 'pgdocs_{}'.format(major_version)
+    # If we have an exact match for our major version, get that one. If not, get the release
+    # notes from the highest available version.
+    release_notes = exec_to_dict("SELECT content FROM docs WHERE file=%(filename)s ORDER BY version=%(major_version)s DESC, version DESC LIMIT 1", {
+        'filename': version_file,
+        'major_version': major_version,
+    })
+    try:
+        release_note = release_notes[0]
+    except IndexError:
+        # Must version this one, as this minor version can show up later and in that case we
+        # need it to render once purged.
+        return _versioned_404("Minor version release notes not found", major_version)
+
+    # We only keep 6.3 and newer in core_version (for legacy reasons)
+    if major_version > 6.2:
+        available_minor_versions = exec_to_dict("SELECT minor FROM generate_series(0, (SELECT latestminor FROM core_version WHERE tree=%(major_version)s)) g(minor) ORDER BY minor DESC", {
+            'major_version': major_version,
+        })
+        previous_minor = minor_version - 1 if minor_version > 0 else None
+        next_minor = minor_version + 1 if minor_version < available_minor_versions[0]['minor'] else None
     else:
-        r['xkey'] = 'pgdocs_all'
+        available_minor_versions = [v for v in release_notes_only_versions if v['major'] == major_version]
+        # Ugh, there are gaps, so we have to do it the ugly way
+        previous_minor = None
+        next_minor = None
+        for i, v in enumerate(available_minor_versions):
+            if v['minor'] == minor_version:
+                if i > 0:
+                    next_minor = available_minor_versions[i - 1]['minor']
+                if v != available_minor_versions[-1]:
+                    previous_minor = available_minor_versions[i + 1]['minor']
+                break
+
+    r = render_pgweb(request, 'docs', 'docs/release_notes.html', {
+        'major_version': major_version,
+        'minor_version': minor_version,
+        'release_note': release_note,
+        'available_minor_versions': available_minor_versions,
+        'previous_minor_release': previous_minor,
+        'next_minor_release': next_minor,
+    })
+    r['xkey'] = 'pgdocs_{}'.format(major_version)
     return r
 
 
index f349f0a147fabd1e6654e645f4824eec3259aaea..722b5cf2b337c4ad7c2c29f3fa8c64e370c925c9 100644 (file)
@@ -58,8 +58,8 @@ urlpatterns = [
     url(r'^docs/$', pgweb.docs.views.root),
     url(r'^docs/manuals/$', pgweb.docs.views.manuals),
     url(r'^docs/manuals/archive/$', pgweb.docs.views.manualarchive),
-    url(r'^docs/release/$', pgweb.docs.views.release_notes),
-    url(r'^docs/release/((?P<major_version>(\d+\.\d+)|\d+)\.(?P<minor_version>\d+))/$', pgweb.docs.views.release_notes),
+    url(r'^docs/release/$', pgweb.docs.views.release_notes_list),
+    url(r'^docs/release/(\d+(?:\.\d+){0,2})/$', pgweb.docs.views.release_notes),
     # Legacy URLs for accessing the docs page; provides a permanent redirect
     url(r'^docs/(current|devel|\d+(?:\.\d)?)/(static|interactive)/(([^/]+).html?)?$', pgweb.docs.views.docspermanentredirect),
     url(r'^docs/(current|devel|\d+(?:\.\d)?)/([^/]+).html?$', pgweb.docs.views.docpage),
index 292cb9f474855f11bfbf5d072195a06767dfb0bf..003cd3cbeb9d9c75ded8cbbdb05f18a6ab18033e 100644 (file)
 {% regroup release_notes by major as release_note_groups %}
 
 <div id="release-notes" class="row">
-  {% if major_version is not None and minor_version is not None %}
     <div class="col-md-10">
       <section>
         <h1>Release Notes <i class="far fa-file-alt"></i></h1>
         <h2>
-          {% if major_version == '0' %}Postgres95{% else %}PostgreSQL{% endif %}
-          {{ major_version }}.{{ release_note.minor|release_notes_pg_minor_version:major_version }}
+          {% if major_version == 0 %}Postgres95{% else %}PostgreSQL{% endif %}
+          {{ major_version }}.{{ minor_version|release_notes_pg_minor_version:major_version }}
         </h2>
       </section>
       <div id="docContent">
@@ -27,8 +26,8 @@
       </div>
       <div class="row">
         <div class="col-md-3">
-          {% if release_note.previous is not None %}
-            <a href="/docs/release/{{ major_version }}.{{ release_note.previous|release_notes_pg_minor_version:major_version }}/">
+         {%if previous_minor_release is not None %}
+            <a href="/docs/release/{{ major_version }}.{{ previous_minor_release|release_notes_pg_minor_version:major_version }}/">
               Previous
             </a>
           {% endif %}
@@ -41,8 +40,8 @@
           </p>
         </div>
         <div class="col-md-3 text-right">
-          {% if release_note.next is not None %}
-            <a href="/docs/release/{{ major_version }}.{{ release_note.next|release_notes_pg_minor_version:major_version }}/">
+          {% if next_minor_release is not None %}
+            <a href="/docs/release/{{ major_version }}.{{ next_minor_release|release_notes_pg_minor_version:major_version }}/">
               Next
             </a>
           {% endif %}
@@ -54,9 +53,9 @@
         <h2>Versions</h2>
       </section>
       <ul>
-        {% for r in release_notes %}
+        {% for r in available_minor_versions %}
           <li>
-            {% if r.minor == release_note.minor %}
+            {% if r.minor == minor_version %}
               <strong>
                 {{ major_version }}.{{ r.minor|release_notes_pg_minor_version:major_version }}
               </strong>
         {% endfor %}
       </ul>
     </div>
-  {% else %}
-    <div class="col-md-9">
-      <section>
-        <h1>Release Notes <i class="far fa-file-alt"></i></h1>
-      </section>
-      <p>Below is the complete archive of release notes for every version of PostgreSQL.</p>
-      <ul class="release-notes-list fa-ul">
-        {% for release_note_group in release_note_groups %}
-          {% with major_version=release_note_group.grouper %}
-            <li>
-              <a class="collapsed" href="#release{{ major_version|cut:"." }}" data-toggle="collapse" role="button" aria-expanded="false" aria-controls="release{{ major_version|cut:"." }}">
-                <span class="fa-li right"><i class="fas fa-angle-right"></i></span>
-                <span class="fa-li down"><i class="fas fa-angle-down"></i></span>
-                {% if major_version == 0 %}Postgres95{% else %}PostgreSQL {{ major_version }}{% endif %}
-              </a>
-              <ul class="collapse release-notes-list" id="release{{ major_version|cut:"." }}">
-                {% for release_note in release_note_group.list %}
-                  <li>
-                    <a href="/docs/release/{{ major_version }}.{{ release_note.minor|release_notes_pg_minor_version:major_version }}/">
-                      {{ major_version }}.{{ release_note.minor|release_notes_pg_minor_version:major_version }}
-                    </a>
-                  </li>
-                {% endfor %}
-              </ul>
-            </li>
-          {% endwith %}
-        {% endfor %}
-      </ul>
-    </div>
-  {% endif %}
 </div>
 
 {% endblock %}