diff --git a/pgweb/docs/migrations/0004_auto_20180806_1917.py b/pgweb/docs/migrations/0004_auto_20180806_1917.py new file mode 100644 index 0000000..5d67c29 --- /dev/null +++ b/pgweb/docs/migrations/0004_auto_20180806_1917.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.13 on 2018-08-06 19:17 +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0001_initial'), + ('docs', '0003_docs_alias'), + ] + + operations = [ + migrations.CreateModel( + name='ReleaseNote', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('version', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='core.Version', db_column='version', to_field=b'tree')), + ('minor_version', models.IntegerField()), + ('title', models.CharField(max_length=256)), + ('content', models.TextField()), + ], + ), + migrations.AlterUniqueTogether( + name='releasenote', + unique_together=set([('version', 'minor_version')]), + ), + ] diff --git a/pgweb/docs/models.py b/pgweb/docs/models.py index a2754b6..73f46fb 100644 --- a/pgweb/docs/models.py +++ b/pgweb/docs/models.py @@ -31,3 +31,13 @@ class DocPageAlias(models.Model): class Meta: db_table = 'docsalias' verbose_name_plural='Doc page aliases' + +class ReleaseNote(models.Model): + """Contains content for a release note""" + version = models.ForeignKey(Version, db_column='version', to_field='tree') + minor_version = models.IntegerField() + title = models.CharField(max_length=256) + content = models.TextField() + + class Meta: + unique_together = [('version', 'minor_version')] diff --git a/pgweb/docs/views.py b/pgweb/docs/views.py index 7cc1692..49e3a60 100644 --- a/pgweb/docs/views.py +++ b/pgweb/docs/views.py @@ -14,7 +14,7 @@ from pgweb.util.misc import send_template_mail from pgweb.core.models import Version -from models import DocPage +from models import DocPage, ReleaseNote from forms import DocCommentForm def docpage(request, version, typ, filename): @@ -81,6 +81,21 @@ def docsrootpage(request, version, typ): def redirect_root(request, version): return HttpResponseRedirect("/docs/%s/static/" % version) +def releasenotesarchive(request, *args): + """Have a page available to view release notes""" + # If the version is greater than 10, only use the first two arguments + if int(args[0]) >= 10: + major_version = int(args[0]) + minor_version = int(args[2]) if args[2] else 0 + else: + major_version = "%s.%s" % (args[0], args[2]) + minor_version = int(args[4]) if args[4] else 0 + page = get_object_or_404(ReleaseNote, version=major_version, minor_version=minor_version) + return render_pgweb(request, 'docs', 'docs/release-notes.html', { + 'page': page, + 'title': page.title, + }) + def root(request): versions = Version.objects.filter(Q(supported=True) | Q(testing__gt=0,tree__gt=0)).order_by('-tree') return render_pgweb(request, 'docs', 'docs/index.html', { diff --git a/pgweb/urls.py b/pgweb/urls.py index 64caf1e..5d8748f 100644 --- a/pgweb/urls.py +++ b/pgweb/urls.py @@ -57,6 +57,7 @@ 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-notes/(\d+)(.(\d+)(.(\d+))?)?/$', pgweb.docs.views.releasenotesarchive), url(r'^docs/(current|devel|\d+(?:\.\d)?)/(static|interactive)/(.*).html?$', pgweb.docs.views.docpage), url(r'^docs/(current|devel|\d+(?:\.\d)?)/(static|interactive)/$', pgweb.docs.views.docsrootpage), url(r'^docs/(current|devel|\d+(?:\.\d)?)/$', pgweb.docs.views.redirect_root), diff --git a/templates/docs/release-notes.html b/templates/docs/release-notes.html new file mode 100644 index 0000000..4b9096a --- /dev/null +++ b/templates/docs/release-notes.html @@ -0,0 +1,7 @@ +{%extends "base/page.html"%} +{%block title%}Release Notes - {{page.title}}{%endblock%} +{%block contents%} +
+ {{ page.content|safe }} +
+{% endblock contents %} diff --git a/tools/docs/docload.py b/tools/docs/docload.py index 2965f9c..5a6d178 100755 --- a/tools/docs/docload.py +++ b/tools/docs/docload.py @@ -10,6 +10,7 @@ import tidy from optparse import OptionParser from ConfigParser import ConfigParser +import bs4 import psycopg2 pagecount = 0 @@ -62,9 +63,66 @@ def load_doc_file(filename, f): 't': title, 'c': str(s), }) + # If this is a release note, load the release note. + if filename.startswith('release-'): + load_release_note(filename, str(s)) global pagecount pagecount += 1 +def load_release_note(filename, content): + """Load a release note into the system based on the filename""" + if not quiet: print "--- release note: %s" % filename + # Format the content for display in the release note page + parser = bs4.BeautifulSoup(content, "html.parser") + # Get the version that this release document is referencing + release_version = parser.find(class_="sect1")["id"].split("RELEASE-")[-1].replace('-', '.') + # Extract the versions + if float(release_version.split('.')[0]) >= 10 or float(release_version.split('.')[0]) <= 1: + try: + major_version, minor_version = release_version.split('.') + except ValueError: + major_version, minor_version = release_version, 0 + else: + m = re.search(r'(\d+).(\d+)(.(\d+))?', release_version) + major_version = "%s.%s" % (m.groups()[0], m.groups()[1]) + minor_version = m.groups()[3] if m.groups()[3] else 0 + # Remove extraneous sections of the release notes + for class_name in ['navheader', 'navfooter', 'toc']: + tag = parser.find(class_=class_name) + if tag: + tag.decompose() + # Update the headers to the proper release version, and remove extra references + # from the documentation + release_title = "Release " + release_version + # For really old release of PostgreSQL there is a nested product name + tag = parser.find('h2', class_="title") + if not tag.string: + tag.replace_with('

%s

' % release_title) + else: + tag.string.replace_with(release_title) + for tag in parser.find_all('h3', class_='title'): + if not tag.string: continue + m = re.search(r'(([A-Z0-9]+\.)+).(.*)$', tag.string) + if not m: continue + tag.string.replace_with(m.groups()[-1]) + # Update the URLs to point to the release note archives + for tag in parser.find_all(class_="xref"): + release = tag['href'].split('.')[0].split("release-")[-1] + title = 'Release ' + ".".join(release.split('-')) + tag['href'] = "/docs/release-notes/archive/" + tag['href'] + tag['title'] = title + tag.string.replace_with(title) + # Update the documentation links to point to the current docs. + for tag in parser.find_all(class_="link"): + tag['href'] = "/docs/current/static/" + tag['href'] + # The content is now ready to be loaded into the database + curs.execute(""" + INSERT INTO docs_releasenote (version, minor_version, title, content) + VALUES (%(v)s, %(m)s, %(t)s, %(c)s) + ON CONFLICT (version, minor_version) DO UPDATE + SET title = EXCLUDED.title, content = EXCLUDED.content""", + { 'v': major_version, 'm': minor_version, 't': release_title, 'c': parser.prettify() }) + ## Main execution parser = OptionParser(usage="usage: %prog [options] ") @@ -110,7 +168,7 @@ re_htmlfile = re.compile('[^/]*/doc/src/sgml/html/.*') re_tarfile = re.compile('[^/]*/doc/postgres.tar.gz$') for member in tf: if re_htmlfile.match(member.name): - load_doc_file(os.path.basename(member.name), tf.extractfile(member)) + load_doc_file(filename, tf.extractfile(member)) if re_tarfile.match(member.name): f = tf.extractfile(member) inner_tar = tarfile.open(fileobj=f) @@ -139,4 +197,3 @@ connection.commit() connection.close() if not quiet: print "Done (%i pages)." % pagecount -