1 from django
.shortcuts
import render
, get_object_or_404
2 from django
.http
import HttpResponse
, Http404
, HttpResponseRedirect
3 from django
.http
import HttpResponseNotModified
4 from django
.template
import TemplateDoesNotExist
, loader
5 from django
.contrib
.auth
.decorators
import user_passes_test
6 from pgweb
.util
.decorators
import login_required
7 from django
.contrib
import messages
8 from django
.views
.decorators
.csrf
import csrf_exempt
9 from django
.db
.models
import Count
10 from django
.db
import connection
, transaction
11 from django
.utils
.http
import http_date
, parse_http_date
12 from django
.conf
import settings
15 from datetime
import date
, datetime
, timedelta
20 from pgweb
.util
.decorators
import cache
, nocache
21 from pgweb
.util
.contexts
import render_pgweb
, get_nav_menu
, PGWebContextProcessor
22 from pgweb
.util
.helpers
import simple_form
, PgXmlHelper
, HttpServerError
23 from pgweb
.util
.moderation
import get_all_pending_moderations
24 from pgweb
.util
.misc
import get_client_ip
, varnish_purge
, varnish_purge_expr
, varnish_purge_xkey
25 from pgweb
.util
.sitestruct
import get_all_pages_struct
27 # models needed for the pieces on the frontpage
28 from pgweb
.news
.models
import NewsArticle
, NewsTag
29 from pgweb
.events
.models
import Event
30 from pgweb
.quotes
.models
import Quote
31 from .models
import Version
, ImportedRSSItem
33 # models needed for the pieces on the community page
34 from pgweb
.survey
.models
import Survey
36 # models and forms needed for core objects
37 from .models
import Organisation
38 from .forms
import OrganisationForm
, MergeOrgsForm
44 news
= NewsArticle
.objects
.filter(approved
=True)[:5]
46 # get up to seven events to display on the homepage
47 event_base_queryset
= Event
.objects
.select_related('country').filter(
51 # first, see if there are up to two non-badged events within 90 days
52 other_events
= event_base_queryset
.filter(
54 startdate__lte
=today
+ timedelta(days
=90),
55 ).order_by('enddate', 'startdate')[:2]
56 # based on that, get 7 - |other_events| community events to display
57 community_event_queryset
= event_base_queryset
.filter(badged
=True).order_by('enddate', 'startdate')[:(7 - other_events
.count())]
58 # now, return all the events in one unioned array!
59 events
= community_event_queryset
.union(other_events
).order_by('enddate', 'startdate').all()
60 versions
= Version
.objects
.filter(supported
=True)
61 planet
= ImportedRSSItem
.objects
.filter(feed__internalname
="planet").order_by("-posttime")[:9]
63 return render(request
, 'index.html', {
64 'title': 'The world\'s most advanced open source database',
66 'newstags': NewsTag
.objects
.all(),
73 # About page view (contains information about PostgreSQL + random quotes)
77 quotes
= Quote
.objects
.filter(approved
=True).order_by('?').all()[:5]
78 return render_pgweb(request
, 'about', 'core/about.html', {
83 # Community main page (contains surveys and potentially more)
84 def community(request
):
85 s
= Survey
.objects
.filter(current
=True)
90 planet
= ImportedRSSItem
.objects
.filter(feed__internalname
="planet").order_by("-posttime")[:7]
91 return render_pgweb(request
, 'community', 'core/community.html', {
97 # List of supported versions
98 def versions(request
):
99 return render_pgweb(request
, 'support', 'support/versioning.html', {
100 'versions': Version
.objects
.filter(tree__gt
=0).filter(testing
=0),
104 re_staticfilenames
= re
.compile("^[0-9A-Z/_-]+$", re
.IGNORECASE
)
107 # Generic fallback view for static pages
108 def fallback(request
, url
):
109 if url
.find('..') > -1:
110 raise Http404('Page not found.')
112 if not re_staticfilenames
.match(url
):
113 raise Http404('Page not found.')
116 # Maximum length is really per-directory, but we shouldn't have any pages/fallback
117 # urls with anywhere *near* that, so let's just limit it on the whole
118 raise Http404('Page not found.')
121 t
= loader
.get_template('pages/%s.html' % url
)
122 except TemplateDoesNotExist
:
124 t
= loader
.get_template('pages/%s/en.html' % url
)
125 except TemplateDoesNotExist
:
126 raise Http404('Page not found.')
128 # Guestimate the nav section by looking at the URL and taking the first
131 navsect
= url
.split('/', 2)[0]
134 c
= PGWebContextProcessor(request
)
135 c
.update({'navmenu': get_nav_menu(navsect
)})
136 return HttpResponse(t
.render(c
))
139 # Edit-forms for core objects
141 def organisationform(request
, itemid
):
143 get_object_or_404(Organisation
, pk
=itemid
, managers
=request
.user
)
145 return simple_form(Organisation
, itemid
, request
, OrganisationForm
,
146 redirect
='/account/edit/organisations/')
151 return HttpResponse("""User-agent: *
154 Disallow: /docs/devel/
157 Disallow: /message-id/raw/
158 Disallow: /message-id/flat/
160 Sitemap: https://www.postgresql.org/sitemap.xml
161 """, content_type
='text/plain')
164 def _make_sitemap(pagelist
):
165 resp
= HttpResponse(content_type
='text/xml')
166 x
= PgXmlHelper(resp
)
168 x
.startElement('urlset', {'xmlns': 'http://www.sitemaps.org/schemas/sitemap/0.9'})
172 x
.startElement('url', {})
173 x
.add_xml_element('loc', 'https://www.postgresql.org/%s' % urllib
.parse
.quote(p
[0]))
174 if len(p
) > 1 and p
[1]:
175 x
.add_xml_element('priority', str(p
[1]))
176 if len(p
) > 2 and p
[2]:
177 x
.add_xml_element('lastmod', p
[2].isoformat() + "Z")
179 x
.endElement('urlset')
184 # Sitemap (XML format)
186 def sitemap(request
):
187 return _make_sitemap(get_all_pages_struct())
190 # Internal sitemap (only for our own search engine)
191 # Note! Still served up to anybody who wants it, so don't
192 # put anything secret in it...
194 def sitemap_internal(request
):
195 return _make_sitemap(get_all_pages_struct(method
='get_internal_struct'))
198 # dynamic CSS serving, meaning we merge a number of different CSS into a
199 # single one, making sure it turns into a single http response. We do this
200 # dynamically, since the output will be cached.
202 'base': ['media/css/main.css',
203 'media/css/normalize.css', ],
204 'docs': ['media/css/global.css',
205 'media/css/table.css',
206 'media/css/text.css',
207 'media/css/docs.css'],
212 def dynamic_css(request
, css
):
213 if css
not in _dynamic_cssmap
:
214 raise Http404('CSS not found')
215 files
= _dynamic_cssmap
[css
]
216 resp
= HttpResponse(content_type
='text/css')
218 # We honor if-modified-since headers by looking at the most recently
223 stime
= os
.stat(fn
).st_mtime
224 if latestmod
< stime
:
227 # If we somehow referred to a file that didn't exist, or
228 # one that we couldn't access.
229 raise Http404('CSS (sub) not found')
230 if 'HTTP_IF_MODIFIED_SINCE' in request
.META
:
231 # This code is mostly stolen from django :)
232 matches
= re
.match(r
"^([^;]+)(; length=([0-9]+))?$",
233 request
.META
.get('HTTP_IF_MODIFIED_SINCE'),
235 header_mtime
= parse_http_date(matches
.group(1))
236 # We don't do length checking, just the date
237 if int(latestmod
) <= header_mtime
:
238 return HttpResponseNotModified(content_type
='text/css')
239 resp
['Last-Modified'] = http_date(latestmod
)
243 resp
.write("/* %s */\n" % fn
)
251 def csrf_failure(request
, reason
=''):
252 resp
= render(request
, 'errors/csrf_failure.html', {
255 resp
.status_code
= 403 # Forbidden
259 # Basic information about the connection
261 def system_information(request
):
262 return render(request
, 'core/system_information.html', {
263 'server': os
.uname()[1],
264 'cache_server': request
.META
['REMOTE_ADDR'] or None,
265 'client_ip': get_client_ip(request
),
266 'django_version': django
.get_version(),
270 # Sync timestamp for automirror. Keep it around for 30 seconds
271 # Basically just a check that we can access the backend still...
273 def sync_timestamp(request
):
274 s
= datetime
.now().strftime("%Y-%m-%d %H:%M:%S\n")
275 r
= HttpResponse(s
, content_type
='text/plain')
276 r
['Content-Length'] = len(s
)
280 # List of all unapproved objects, for the special admin page
282 @user_passes_test(lambda u
: u
.is_staff
)
283 @user_passes_test(lambda u
: u
.groups
.filter(name
='pgweb moderators').exists())
284 def admin_pending(request
):
285 return render(request
, 'core/admin_pending.html', {
286 'app_list': get_all_pending_moderations(),
290 # Purge objects from varnish, for the admin pages
292 @user_passes_test(lambda u
: u
.is_staff
)
293 @user_passes_test(lambda u
: u
.groups
.filter(name
='varnish purgers').exists())
294 def admin_purge(request
):
295 if request
.method
== 'POST':
296 url
= request
.POST
['url']
297 expr
= request
.POST
['expr']
298 xkey
= request
.POST
['xkey']
299 l
= len([_f
for _f
in [url
, expr
, xkey
] if _f
])
302 return HttpResponseRedirect('.')
304 messages
.error(request
, "Can only specify one of url, expression and xkey!")
305 return HttpResponseRedirect('.')
310 varnish_purge_expr(expr
)
312 varnish_purge_xkey(xkey
)
314 messages
.info(request
, "Purge added.")
315 return HttpResponseRedirect('.')
317 # Fetch list of latest purges
318 curs
= connection
.cursor()
319 curs
.execute("SELECT added, completed, consumer, CASE WHEN mode = 'K' THEN 'XKey' WHEN mode='P' THEN 'URL' ELSE 'Expression' END, expr FROM varnishqueue.queue q LEFT JOIN varnishqueue.consumers c ON c.consumerid=q.consumerid ORDER BY added DESC")
320 latest
= curs
.fetchall()
322 return render(request
, 'core/admin_purge.html', {
323 'latest_purges': latest
,
328 def api_varnish_purge(request
):
329 if not request
.META
['REMOTE_ADDR'] in settings
.VARNISH_PURGERS
:
330 return HttpServerError(request
, "Invalid client address")
331 if request
.method
!= 'POST':
332 return HttpServerError(request
, "Can't use this way")
333 n
= int(request
.POST
['n'])
334 curs
= connection
.cursor()
335 for i
in range(0, n
):
336 if 'p{0}'.format(i
) in request
.POST
:
337 curs
.execute("SELECT varnish_purge_expr(%s)", (request
.POST
['p{0}'.format(i
)], ))
338 if 'x{0}'.format(i
) in request
.POST
:
339 curs
.execute("SELECT varnish_purge_xkey(%s)", (request
.POST
['x{0}'.format(i
)], ))
341 return HttpResponse("Purged %s entries\n" % n
)
344 # Merge two organisations
346 @user_passes_test(lambda u
: u
.is_superuser
)
348 def admin_mergeorg(request
):
349 if request
.method
== 'POST':
350 form
= MergeOrgsForm(data
=request
.POST
)
352 # Ok, try to actually merge organisations, by moving all objects
354 f
= form
.cleaned_data
['merge_from']
355 t
= form
.cleaned_data
['merge_into']
356 for e
in f
.event_set
.all():
359 for n
in f
.newsarticle_set
.all():
362 for p
in f
.product_set
.all():
365 for p
in f
.professionalservice_set
.all():
368 # Now that everything is moved, we can delete the organisation
371 return HttpResponseRedirect("/admin/core/organisation/")
372 # Else fall through to re-render form with errors
374 form
= MergeOrgsForm()
376 return render(request
, 'core/admin_mergeorg.html', {