Update syntax for relative imports
[pgweb/local.git] / pgweb / downloads / views.py
blob3554e2b7bd94fd8856ed128671a26411ebab4e55
1 from django.shortcuts import render, get_object_or_404
2 from django.http import HttpResponse, Http404, HttpResponseRedirect
3 from pgweb.util.decorators import login_required
4 from django.views.decorators.csrf import csrf_exempt
5 from django.conf import settings
7 import os
8 import cPickle as pickle
9 import json
11 from pgweb.util.decorators import nocache
12 from pgweb.util.contexts import render_pgweb
13 from pgweb.util.helpers import simple_form, PgXmlHelper, HttpServerError
14 from pgweb.util.misc import varnish_purge, version_sort
16 from pgweb.core.models import Version
17 from .models import Category, Product, StackBuilderApp
18 from .forms import ProductForm
21 #######
22 # FTP browser
23 #######
24 def ftpbrowser(request, subpath):
25 if subpath:
26 # An actual path has been selected. Fancy!
28 if subpath.find('..') > -1:
29 # Just claim it doesn't exist if the user tries to do this
30 # type of bad thing
31 raise Http404
32 subpath = subpath.strip('/')
33 else:
34 subpath = ""
36 # Pickle up the list of things we need
37 try:
38 f = open(settings.FTP_PICKLE, "rb")
39 allnodes = pickle.load(f)
40 f.close()
41 except Exception as e:
42 return HttpServerError(request, "Failed to load ftp site information: %s" % e)
44 # An incoming subpath may either be canonical, or have one or more elements
45 # present that are actually symlinks. For each element of the path, test to
46 # see if it is present in the pickle. If not, look for a symlink entry with
47 # and if present, replace the original entry with the symlink target.
48 canonpath = ''
49 if subpath != '':
50 parent = ''
51 for d in subpath.split('/'):
52 # Check if allnodes contains a node matching the path
53 if d in allnodes[parent]:
54 if allnodes[parent][d]['t'] == 'd':
55 canonpath = os.path.join(canonpath, d)
56 elif allnodes[parent][d]['t'] == 'l':
57 canonpath = os.path.join(canonpath, allnodes[parent][d]['d']).strip('/')
58 else:
59 # There's a matching node, but it's not a link or a directory
60 raise Http404
62 parent = canonpath
63 else:
64 # There's no matching node
65 raise Http404
67 # If we wound up with a canonical path that doesn't match the original request,
68 # redirect the user
69 canonpath = canonpath.strip('/')
70 if subpath != canonpath:
71 return HttpResponseRedirect('/ftp/' + canonpath)
73 node = allnodes[subpath]
74 del allnodes
76 # Add all directories
77 directories = [{'link': k, 'url': k, 'type': 'd'} for k, v in node.items() if v['t'] == 'd']
78 # Add all symlinks (only directories supported)
79 directories.extend([{'link': k, 'url': v['d'], 'type': 'l'} for k, v in node.items() if v['t'] == 'l'])
81 # A ittle early sorting wouldn't go amiss, so .. ends up at the top
82 directories.sort(key=version_sort, reverse=True)
84 # Add a link to the parent directory
85 if subpath:
86 directories.insert(0, {'link': '[Parent Directory]', 'url': '..'})
88 # Fetch files
89 files = [{'name': k, 'mtime': v['d'], 'size': v['s']} for k, v in node.items() if v['t'] == 'f']
91 breadcrumbs = []
92 if subpath:
93 breadroot = ""
94 for pathpiece in subpath.split('/'):
95 if not pathpiece:
96 # Trailing slash will give out an empty pathpiece
97 continue
98 if breadroot:
99 breadroot = "%s/%s" % (breadroot, pathpiece)
100 else:
101 breadroot = pathpiece
102 breadcrumbs.append({'name': pathpiece, 'path': breadroot})
104 # Check if there are any "content files" we should render directly on the webpage
105 file_readme = ('README' in node and node['README']['t'] == 'f') and node['README']['c'] or None
106 file_message = ('.message' in node and node['.message']['t'] == 'f') and node['.message']['c'] or None
107 file_maintainer = ('CURRENT_MAINTAINER' in node and node['CURRENT_MAINTAINER']['t'] == 'f') and node['CURRENT_MAINTAINER']['c'] or None
109 del node
111 return render_pgweb(request, 'download', 'downloads/ftpbrowser.html', {
112 'basepath': subpath.rstrip('/'),
113 'directories': directories,
114 'files': sorted(files),
115 'breadcrumbs': breadcrumbs,
116 'readme': file_readme,
117 'messagefile': file_message,
118 'maintainer': file_maintainer,
122 # Accept an upload of the ftpsite pickle. This is fairly resource consuming,
123 # and not very optimized, but that's why we limit it so that only the ftp
124 # server(s) can post it.
125 # There is no concurrency check - the ftp site better not send more than one
126 # file in parallel.
127 @csrf_exempt
128 def uploadftp(request):
129 if request.method != 'PUT':
130 return HttpServerError(request, "Invalid method")
131 if not request.META['REMOTE_ADDR'] in settings.FTP_MASTERS:
132 return HttpServerError(request, "Invalid client address")
133 # We have the data in request.body. Attempt to load it as
134 # a pickle to make sure it's properly formatted
135 pickle.loads(request.body)
137 # Next, check if it's the same as the current file
138 f = open(settings.FTP_PICKLE, "rb")
139 x = f.read()
140 f.close()
141 if x == request.body:
142 # Don't rewrite the file or purge any data if nothing changed
143 return HttpResponse("NOT CHANGED", content_type="text/plain")
145 # File has changed - let's write it!
146 f = open("%s.new" % settings.FTP_PICKLE, "wb")
147 f.write(request.body)
148 f.close()
149 os.rename("%s.new" % settings.FTP_PICKLE, settings.FTP_PICKLE)
151 # Purge it out of varnish so we start responding right away
152 varnish_purge("/ftp")
154 # Finally, indicate to the client that we're happy
155 return HttpResponse("OK", content_type="text/plain")
158 @csrf_exempt
159 def uploadyum(request):
160 if request.method != 'PUT':
161 return HttpServerError(request, "Invalid method")
162 if not request.META['REMOTE_ADDR'] in settings.FTP_MASTERS:
163 return HttpServerError(request, "Invalid client address")
164 # We have the data in request.body. Attempt to load it as
165 # json to ensure correct format.
166 json.loads(request.body)
168 # Next, check if it's the same as the current file
169 if os.path.isfile(settings.YUM_JSON):
170 with open(settings.YUM_JSON, "r") as f:
171 if f.read() == request.body:
172 # Don't rewrite the file or purge any data if nothing changed
173 return HttpResponse("NOT CHANGED", content_type="text/plain")
175 # File has changed - let's write it!
176 with open("%s.new" % settings.YUM_JSON, "w") as f:
177 f.write(request.body)
179 os.rename("%s.new" % settings.YUM_JSON, settings.YUM_JSON)
181 # Purge it out of varnish so we start responding right away
182 varnish_purge("/download/js/yum.js")
184 # Finally, indicate to the client that we're happy
185 return HttpResponse("OK", content_type="text/plain")
188 @nocache
189 def mirrorselect(request, path):
190 # Old access to mirrors will just redirect to the main ftp site.
191 # We don't really need it anymore, but the cost of keeping it is
192 # very low...
193 return HttpResponseRedirect("https://ftp.postgresql.org/pub/%s" % path)
196 # Render javascript for yum downloads
197 def yum_js(request):
198 with open(settings.YUM_JSON) as f:
199 jsonstr = f.read()
200 return render(request, 'downloads/js/yum.js', {
201 'json': jsonstr,
202 'supported_versions': ','.join([str(v.numtree) for v in Version.objects.filter(supported=True)]),
203 }, content_type='application/json')
206 #######
207 # Product catalogue
208 #######
209 def categorylist(request):
210 categories = Category.objects.all()
211 return render_pgweb(request, 'download', 'downloads/categorylist.html', {
212 'categories': categories,
216 def productlist(request, catid, junk=None):
217 category = get_object_or_404(Category, pk=catid)
218 products = Product.objects.select_related('org', 'licencetype').filter(category=category, approved=True)
219 return render_pgweb(request, 'download', 'downloads/productlist.html', {
220 'category': category,
221 'products': products,
222 'productcount': len(products),
226 @login_required
227 def productform(request, itemid):
228 return simple_form(Product, itemid, request, ProductForm,
229 redirect='/account/edit/products/')
232 #######
233 # Stackbuilder
234 #######
235 def applications_v2_xml(request):
236 all_apps = StackBuilderApp.objects.select_related().filter(active=True)
238 resp = HttpResponse(content_type='text/xml')
239 x = PgXmlHelper(resp, skipempty=True)
240 x.startDocument()
241 x.startElement('applications', {})
242 for a in all_apps:
243 x.startElement('application', {})
244 x.add_xml_element('id', a.textid)
245 x.add_xml_element('platform', a.platform)
246 x.add_xml_element('secondaryplatform', a.secondaryplatform)
247 x.add_xml_element('version', a.version)
248 x.add_xml_element('name', a.name)
249 x.add_xml_element('description', a.description)
250 x.add_xml_element('category', a.category)
251 x.add_xml_element('pgversion', a.pgversion)
252 x.add_xml_element('edbversion', a.edbversion)
253 x.add_xml_element('format', a.format)
254 x.add_xml_element('installoptions', a.installoptions)
255 x.add_xml_element('upgradeoptions', a.upgradeoptions)
256 x.add_xml_element('checksum', a.checksum)
257 x.add_xml_element('mirrorpath', a.mirrorpath)
258 x.add_xml_element('alturl', a.alturl)
259 x.add_xml_element('versionkey', a.versionkey)
260 x.add_xml_element('manifesturl', a.manifesturl)
261 for dep in a.txtdependencies.split(','):
262 x.add_xml_element('dependency', dep)
263 x.endElement('application')
264 x.endElement('applications')
265 x.endDocument()
266 return resp