Skip to content

Commit e9cfa6b

Browse files
authored
make compatible with heroku hosting. (#1389)
* initial herokuification * create custom_storage for manifested pipeline files Note: - @import lines removed from css files cause it was confusing the manifest mixin - missing image restored from best guess of what should have been there * credit where due! * change elasticsearch providers * fixup elasticsearch * move new media storage to it's own prefix * add migration view for old media files * update generate_pep_pages to consume results of python/peps#898 * fix admin inline for files/images on pages * support local and non-local image storage * make PEP_ARTIFACT_URL configurable via env var for heroku * herokuify: last fiew page/image fixups * Jobs rss feed Markup fields should be rendered
1 parent 5023d3d commit e9cfa6b

34 files changed

+273
-175
lines changed

Procfile

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
release: python manage.py migrate --noinput
2+
web: gunicorn pydotorg.wsgi

base-requirements.txt

+2-3
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@ icalendar==3.8.4
1616
chardet2==2.0.3
1717
# TODO: We may drop 'django-imagekit' completely.
1818
django-imagekit==4.0.2
19-
django-haystack==2.8.1
20-
elasticsearch==1.3.0
21-
pyelasticsearch==0.6.1
19+
git+https://github.com/django-haystack/django-haystack.git@802b0f6f4b3b99314453261876a32bac2bbec94f
20+
elasticsearch>=5,<6
2221
# TODO: 0.14.0 only supports Django 1.8 and 1.11.
2322
django-tastypie==0.14.1
2423

bin/pre_compile

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
npm -g install yuglify

custom_storages.py

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from django.conf import settings
2+
from django.contrib.staticfiles.storage import ManifestFilesMixin, StaticFilesStorage
3+
4+
from pipeline.storage import PipelineMixin
5+
from storages.backends.s3boto3 import S3Boto3Storage
6+
7+
8+
class MediaStorage(S3Boto3Storage):
9+
location = settings.MEDIAFILES_LOCATION
10+
11+
12+
class PipelineManifestStorage(PipelineMixin, ManifestFilesMixin, StaticFilesStorage):
13+
pass

dev-requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
factory-boy==2.9.2
66
Faker==0.8.1
77
tblib==1.3.2
8+
responses==0.10.5
89

910
# Extra stuff required for local dev
1011

jobs/feeds.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,6 @@ def item_description(self, item):
2020
""" Description """
2121
return '\n'.join([
2222
item.display_location,
23-
item.description,
24-
item.requirements,
23+
item.description.rendered,
24+
item.requirements.rendered,
2525
])

package.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"name": "pythondotorg",
3+
"description": "Django App behind python.org"
4+
}

pages/admin.py

+1-14
Original file line numberDiff line numberDiff line change
@@ -12,21 +12,8 @@
1212
class PageAdminImageFileWidget(admin.widgets.AdminFileWidget):
1313

1414
def render(self, name, value, attrs=None):
15-
""" Fix admin rendering """
1615
content = super().render(name, value, attrs=None)
17-
soup = BeautifulSoup(content, 'lxml')
18-
19-
# Show useful link/relationship in admin
20-
a_href = soup.find('a')
21-
if a_href and a_href.attrs['href']:
22-
a_href.attrs['href'] = a_href.attrs['href'].replace(settings.MEDIA_ROOT, settings.MEDIA_URL)
23-
a_href.string = a_href.text.replace(settings.MEDIA_ROOT, settings.MEDIA_URL)
24-
25-
if '//' in a_href.attrs['href']:
26-
a_href.attrs['href'] = a_href.attrs['href'].replace('//', '/')
27-
a_href.string = a_href.text.replace('//', '/')
28-
29-
return mark_safe(soup)
16+
return content
3017

3118

3219
class ImageInlineAdmin(admin.StackedInline):

pages/models.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def purge_fastly_cache(sender, instance, **kwargs):
9090

9191

9292
def page_image_path(instance, filename):
93-
return os.path.join(settings.MEDIA_ROOT, instance.page.path, filename)
93+
return os.path.join(instance.page.path, filename)
9494

9595

9696
class Image(models.Model):

peps/converters.py

+29-40
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import functools
2+
import datetime
23
import re
34
import os
45

@@ -7,47 +8,48 @@
78
from django.conf import settings
89
from django.core.exceptions import ImproperlyConfigured
910
from django.core.files import File
11+
from django.db.models import Max
1012

1113
from pages.models import Page, Image
1214

1315
PEP_TEMPLATE = 'pages/pep-page.html'
1416
pep_url = lambda num: 'dev/peps/pep-{}/'.format(num)
1517

1618

17-
def check_paths(func):
18-
"""Ensure that our PEP_REPO_PATH is setup correctly."""
19-
@functools.wraps(func)
20-
def wrapped(*args, **kwargs):
21-
if not hasattr(settings, 'PEP_REPO_PATH'):
22-
raise ImproperlyConfigured('No PEP_REPO_PATH in settings')
23-
if not os.path.exists(settings.PEP_REPO_PATH):
24-
raise ImproperlyConfigured('Path set as PEP_REPO_PATH does not exist')
25-
return func(*args, **kwargs)
26-
return wrapped
19+
def get_peps_last_updated():
20+
last_update = Page.objects.filter(
21+
path__startswith='dev/peps',
22+
).aggregate(Max('updated')).get('updated__max')
23+
if last_update is None:
24+
return datetime.datetime(
25+
1970, 1, 1, tzinfo=datetime.timezone(
26+
datetime.timedelta(0)
27+
)
28+
)
29+
return last_update
2730

2831

29-
@check_paths
30-
def convert_pep0():
32+
def convert_pep0(artifact_path):
3133
"""
3234
Take existing generated pep-0000.html and convert to something suitable
3335
for a Python.org Page returns the core body HTML necessary only
3436
"""
35-
pep0_path = os.path.join(settings.PEP_REPO_PATH, 'pep-0000.html')
37+
pep0_path = os.path.join(artifact_path, 'pep-0000.html')
3638
pep0_content = open(pep0_path).read()
3739
data = convert_pep_page(0, pep0_content)
3840
if data is None:
3941
return
4042
return data['content']
4143

4244

43-
def get_pep0_page(commit=True):
45+
def get_pep0_page(artifact_path, commit=True):
4446
"""
4547
Using convert_pep0 above, create a CMS ready pep0 page and return it
4648
4749
pep0 is used as the directory index, but it's also an actual pep, so we
4850
return both Page objects.
4951
"""
50-
pep0_content = convert_pep0()
52+
pep0_content = convert_pep0(artifact_path)
5153
if pep0_content is None:
5254
return None, None
5355
pep0_page, _ = Page.objects.get_or_create(path='dev/peps/')
@@ -88,7 +90,6 @@ def fix_headers(soup, data):
8890
return soup, data
8991

9092

91-
@check_paths
9293
def convert_pep_page(pep_number, content):
9394
"""
9495
Handle different formats that pep2html.py outputs
@@ -163,12 +164,12 @@ def convert_pep_page(pep_number, content):
163164
return data
164165

165166

166-
def get_pep_page(pep_number, commit=True):
167+
def get_pep_page(artifact_path, pep_number, commit=True):
167168
"""
168169
Given a pep_number retrieve original PEP source text, rst, or html.
169170
Get or create the associated Page and return it
170171
"""
171-
pep_path = os.path.join(settings.PEP_REPO_PATH, 'pep-{}.html'.format(pep_number))
172+
pep_path = os.path.join(artifact_path, 'pep-{}.html'.format(pep_number))
172173
if not os.path.exists(pep_path):
173174
print("PEP Path '{}' does not exist, skipping".format(pep_path))
174175
return
@@ -177,7 +178,7 @@ def get_pep_page(pep_number, commit=True):
177178
if pep_content is None:
178179
return None
179180
pep_rst_source = os.path.join(
180-
settings.PEP_REPO_PATH, 'pep-{}.rst'.format(pep_number),
181+
artifact_path, 'pep-{}.rst'.format(pep_number),
181182
)
182183
pep_ext = '.rst' if os.path.exists(pep_rst_source) else '.txt'
183184
source_link = 'https://github.com/python/peps/blob/master/pep-{}{}'.format(
@@ -198,8 +199,8 @@ def get_pep_page(pep_number, commit=True):
198199
return pep_page
199200

200201

201-
def add_pep_image(pep_number, path):
202-
image_path = os.path.join(settings.PEP_REPO_PATH, path)
202+
def add_pep_image(artifact_path, pep_number, path):
203+
image_path = os.path.join(artifact_path, path)
203204
if not os.path.exists(image_path):
204205
print("Image Path '{}' does not exist, skipping".format(image_path))
205206
return
@@ -213,26 +214,15 @@ def add_pep_image(pep_number, path):
213214
# Find existing images, we have to loop here as we can't use the ORM
214215
# to query against image__path
215216
existing_images = Image.objects.filter(page=page)
216-
MISSING = False
217-
FOUND = False
218217

218+
FOUND = False
219219
for image in existing_images:
220-
image_root_path = os.path.join(settings.MEDIA_ROOT, page.path, path)
221-
222-
if image.image.path.endswith(path):
220+
if image.image.name.endswith(path):
223221
FOUND = True
224-
# File is missing on disk, recreate
225-
if not os.path.exists(image_root_path):
226-
MISSING = image
227-
228222
break
229223

230-
if not FOUND or MISSING:
231-
image = None
232-
if MISSING:
233-
image = MISSING
234-
else:
235-
image = Image(page=page)
224+
if not FOUND:
225+
image = Image(page=page)
236226

237227
with open(image_path, 'rb') as image_obj:
238228
image.image.save(path, File(image_obj))
@@ -243,17 +233,16 @@ def add_pep_image(pep_number, path):
243233
soup = BeautifulSoup(page.content.raw, 'lxml')
244234
for img_tag in soup.findAll('img'):
245235
if img_tag['src'] == path:
246-
img_tag['src'] = os.path.join(settings.MEDIA_URL, page.path, path)
236+
img_tag['src'] = image.image.url
247237

248238
page.content.raw = str(soup)
249239
page.save()
250240

251241
return image
252242

253243

254-
@check_paths
255-
def get_peps_rss():
256-
rss_feed = os.path.join(settings.PEP_REPO_PATH, 'peps.rss')
244+
def get_peps_rss(artifact_path):
245+
rss_feed = os.path.join(artifact_path, 'peps.rss')
257246
if not os.path.exists(rss_feed):
258247
return
259248

0 commit comments

Comments
 (0)