from django.contrib import admin
from django import forms
+from django.template.defaultfilters import slugify
from pgweb.util.admin import PgwebAdmin
from pgweb.core.models import OrganisationEmail
class NewsArticleAdmin(PgwebAdmin):
- list_display = ('title', 'org', 'date', 'modstate', )
+ list_display = ('title', 'org', 'date', 'modstate', 'posturl')
list_filter = ('modstate', )
filter_horizontal = ('tags', )
search_fields = ('content', 'title', )
exclude = ('modstate', 'firstmoderator', )
form = NewsArticleAdminForm
+ def posturl(self, obj):
+ return '/about/news/{}-{}/'.format(slugify(obj.title), obj.id)
+
class NewsTagAdmin(PgwebAdmin):
list_display = ('urlname', 'name', 'description')
from django.contrib.syndication.views import Feed
+from django.template.defaultfilters import slugify
from pgweb.util.moderation import ModerationState
from .models import NewsArticle
return NewsArticle.objects.filter(modstate=ModerationState.APPROVED)[:10]
def item_link(self, obj):
- return "https://www.postgresql.org/about/news/%s/" % obj.id
+ return "https://www.postgresql.org/about/news/{}-{}/".format(slugify(obj.title), obj.id)
def item_pubdate(self, obj):
return datetime.combine(obj.date, time.min)
from django.core.management.base import BaseCommand, CommandError
from django.db import connection
+from django.template.defaultfilters import slugify
from django.conf import settings
from datetime import datetime, timedelta
for a in articles:
# We hardcode 30 chars for the URL shortener. And then 10 to cover the intro and spacing.
- statusstr = "News: {0} {1}/about/news/{2}/".format(a.title[:140 - 40], settings.SITE_ROOT, a.id)
+ statusstr = "News: {0} {1}/about/news/{2}-{3}/".format(a.title[:140 - 40], settings.SITE_ROOT, slugify(a.title), a.id)
r = tw.post('https://api.twitter.com/1.1/statuses/update.json', data={
'status': statusstr,
})
def purge_urls(self):
yield '/about/news/%s/' % self.pk
+ yield '/about/news/.*-%s/' % self.pk
yield '/about/newsarchive/'
yield '/news.rss'
yield '/news/.*.rss'
+from django.template.defaultfilters import slugify
+
from datetime import date, timedelta
from .models import NewsArticle
yearsold = (now - n.date).days / 365
if yearsold > 4:
yearsold = 4
- yield ('about/news/%s/' % n.id,
+ yield ('about/news/{}-{}/'.format(slugify(n.title), n.id),
0.5 - (yearsold / 10.0))
from django.shortcuts import get_object_or_404
-from django.http import HttpResponse, Http404
+from django.http import HttpResponse, Http404, HttpResponsePermanentRedirect
+from django.template.defaultfilters import slugify
from pgweb.util.contexts import render_pgweb
from pgweb.util.moderation import ModerationState
})
-def item(request, itemid, throwaway=None):
+def item(request, itemid, slug=None):
news = get_object_or_404(NewsArticle, pk=itemid)
if news.modstate != ModerationState.APPROVED:
raise Http404
+ if slug != slugify(news.title):
+ return HttpResponsePermanentRedirect('/about/news/{}-{}/'.format(slugify(news.title), news.id))
return render_pgweb(request, 'about', 'news/item.html', {
'obj': news,
'newstags': NewsTag.objects.all(),
url(r'^about/$', pgweb.core.views.about),
url(r'^about/newsarchive/([^/]+/)?$', pgweb.news.views.archive),
- url(r'^about/news/(\d+)(-.*)?/$', pgweb.news.views.item),
+ url(r'^about/news/(?P<itemid>\d+)(?P<slug>-.*)?/$', pgweb.news.views.item),
+ url(r'^about/news/(?P<slug>[^/]+)-(?P<itemid>\d+)/$', pgweb.news.views.item),
url(r'^about/news/taglist.json/$', pgweb.news.views.taglist_json),
url(r'^about/events/$', pgweb.events.views.main),
url(r'^about/eventarchive/$', pgweb.events.views.archive),
<ul class="divided">
{% for n in news %}
<li>
- <h3><a href="/about/news/{{ n.id }}/">{{ n.title }}</a></h3>
+ <h3><a href="/about/news/{{ n.title|slugify }}-{{ n.id }}/">{{ n.title }}</a></h3>
<ul class="meta">
<li><i class="far fa-clock"></i> {{ n.displaydate }} by {{ n.org.name }}</li>
</ul>
</p>
{%for obj in news %}
-<h2 class="news"><a href="/about/news/{{obj.id}}/">{{obj.title}}</a></h2>
+<h2 class="news"><a href="/about/news/{{obj.title|slugify}}-{{obj.id}}/">{{obj.title}}</a></h2>
<div class="newsdate">Posted on <strong>{{obj.displaydate}}</strong>{% if obj.org.name != '_migrated' %} by {{ obj.org.name }}{% endif %}</div>
{{obj.content|markdown:"safe"|striptags|truncatewords:20}}
-<p><a href="/about/news/{{obj.id}}/">Read more...</a></p>
+<p><a href="/about/news/{{obj.title|slugify}}-{{obj.id}}/">Read more...</a></p>
{%endfor%}
<p><a href="/account/news/new/">Submit news</a></p>
{%endblock%}
<tr>
<td>
{%if p.cve%}<nobr>{%if p.cve_visible%}<a href="{{p.cvelink}}">CVE-{{p.cve}}</a>{%else%}CVE-{{p.cve}}{%endif%}</nobr><br/>{%endif%}
- {%if p.newspost%}<a href="/about/news/{{p.newspost.id}}/">Announcement</a><br/>{%endif%}
+ {%if p.newspost%}<a href="/about/news/{{p.newspost.title|slugify}}-{{p.newspost.id}}/">Announcement</a><br/>{%endif%}
</td>
<td>{{p.affected|join:", "}}</td>
<td>{{p.fixed|join:", "}}</td>