forked from python/pythondotorg
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmodels.py
310 lines (252 loc) · 9.49 KB
/
models.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
import re
from django.urls import reverse
from django.conf import settings
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.template.loader import render_to_string
from django.utils import timezone
from markupfield.fields import MarkupField
from boxes.models import Box
from cms.models import ContentManageable, NameSlugModel
from fastly.utils import purge_url
from pages.models import Page
from .managers import ReleaseManager
DEFAULT_MARKUP_TYPE = getattr(settings, 'DEFAULT_MARKUP_TYPE', 'restructuredtext')
class OS(ContentManageable, NameSlugModel):
""" OS for Python release """
class Meta:
verbose_name = 'Operating System'
verbose_name_plural = 'Operating Systems'
ordering = ('name', )
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('download:download_os_list', kwargs={'os_slug': self.slug})
class Release(ContentManageable, NameSlugModel):
"""
A particular version release. Name field should be version number for
example: 3.3.4 or 2.7.6
"""
PYTHON1 = 1
PYTHON2 = 2
PYTHON3 = 3
PYTHON_VERSION_CHOICES = (
(PYTHON3, 'Python 3.x.x'),
(PYTHON2, 'Python 2.x.x'),
(PYTHON1, 'Python 1.x.x'),
)
version = models.IntegerField(default=PYTHON3, choices=PYTHON_VERSION_CHOICES)
is_latest = models.BooleanField(
verbose_name='Is this the latest release?',
default=False,
db_index=True,
help_text="Set this if this should be considered the latest release "
"for the major version. Previous 'latest' versions will "
"automatically have this flag turned off.",
)
is_published = models.BooleanField(
verbose_name='Is Published?',
default=False,
db_index=True,
help_text="Whether or not this should be considered a released/published version",
)
pre_release = models.BooleanField(
verbose_name='Pre-release',
default=False,
db_index=True,
help_text="Boolean to denote pre-release/beta/RC versions",
)
show_on_download_page = models.BooleanField(
default=True,
db_index=True,
help_text="Whether or not to show this release on the main /downloads/ page",
)
release_date = models.DateTimeField(default=timezone.now)
release_page = models.ForeignKey(
Page,
related_name='release',
blank=True,
null=True,
on_delete=models.CASCADE,
)
release_notes_url = models.URLField('Release Notes URL', blank=True)
content = MarkupField(default_markup_type=DEFAULT_MARKUP_TYPE, default='')
objects = ReleaseManager()
class Meta:
verbose_name = 'Release'
verbose_name_plural = 'Releases'
ordering = ('name', )
get_latest_by = 'release_date'
def __str__(self):
return self.name
def get_absolute_url(self):
if not self.content.raw and self.release_page:
return self.release_page.get_absolute_url()
else:
return reverse('download:download_release_detail', kwargs={'release_slug': self.slug})
def download_file_for_os(self, os_slug):
""" Given an OS slug return the appropriate download file """
try:
file = self.files.get(os__slug=os_slug, download_button=True)
except ReleaseFile.DoesNotExist:
file = None
return file
def files_for_os(self, os_slug):
""" Return all files for this release for a given OS """
files = self.files.filter(os__slug=os_slug).order_by('-name')
return files
def get_version(self):
version = re.match(r'Python\s([\d.]+)', self.name)
if version is not None:
return version.group(1)
return None
def update_supernav():
latest_python3 = Release.objects.latest_python3()
if not latest_python3:
return
python_files = []
for o in OS.objects.all():
data = {
'os': o,
'python3': None,
}
release_file = latest_python3.download_file_for_os(o.slug)
if not release_file:
continue
data['python3'] = release_file
python_files.append(data)
if not python_files:
return
if not all(f['python3'] for f in python_files):
# We have a latest Python release, different OSes, but don't have release
# files for the release, so return early.
return
content = render_to_string('downloads/supernav.html', {
'python_files': python_files,
'last_updated': timezone.now(),
})
box, _ = Box.objects.update_or_create(
label='supernav-python-downloads',
defaults={
'content': content,
'content_markup_type': 'html',
}
)
def update_download_landing_sources_box():
latest_python2 = Release.objects.latest_python2()
latest_python3 = Release.objects.latest_python3()
context = {}
if latest_python2:
latest_python2_source = latest_python2.download_file_for_os('source')
if latest_python2_source:
context['latest_python2_source'] = latest_python2_source
if latest_python3:
latest_python3_source = latest_python3.download_file_for_os('source')
if latest_python3_source:
context['latest_python3_source'] = latest_python3_source
if 'latest_python2_source' not in context or 'latest_python3_source' not in context:
return
source_content = render_to_string('downloads/download-sources-box.html', context)
source_box, _ = Box.objects.update_or_create(
label='download-sources',
defaults={
'content': source_content,
'content_markup_type': 'html',
}
)
def update_homepage_download_box():
latest_python2 = Release.objects.latest_python2()
latest_python3 = Release.objects.latest_python3()
context = {}
if latest_python2:
context['latest_python2'] = latest_python2
if latest_python3:
context['latest_python3'] = latest_python3
if 'latest_python2' not in context or 'latest_python3' not in context:
return
content = render_to_string('downloads/homepage-downloads-box.html', context)
box, _ = Box.objects.update_or_create(
label='homepage-downloads',
defaults={
'content': content,
'content_markup_type': 'html',
}
)
@receiver(post_save, sender=Release)
def promote_latest_release(sender, instance, **kwargs):
""" Promote this release to be the latest if this flag is set """
# Skip in fixtures
if kwargs.get('raw', False):
return
if instance.is_latest:
# Demote all previous instances
Release.objects.filter(
version=instance.version
).exclude(
pk=instance.pk
).update(is_latest=False)
@receiver(post_save, sender=Release)
def purge_fastly_download_pages(sender, instance, **kwargs):
"""
Purge Fastly caches so new Downloads show up more quickly
"""
# Don't purge on fixture loads
if kwargs.get('raw', False):
return
# Only purge on published instances
if instance.is_published:
# Purge our common pages
purge_url('/downloads/')
purge_url('/downloads/latest/python2/')
purge_url('/downloads/latest/python3/')
purge_url('/downloads/mac-osx/')
purge_url('/downloads/source/')
purge_url('/downloads/windows/')
if instance.get_version() is not None:
purge_url('/ftp/python/{}/'.format(instance.get_version()))
# See issue #584 for details
purge_url('/box/supernav-python-downloads/')
purge_url('/box/homepage-downloads/')
purge_url('/box/download-sources/')
# Purge the release page itself
purge_url(instance.get_absolute_url())
@receiver(post_save, sender=Release)
def update_download_supernav_and_boxes(sender, instance, **kwargs):
# Skip in fixtures
if kwargs.get('raw', False):
return
if instance.is_published:
# Supernav only has download buttons for Python 3.
if instance.version == instance.PYTHON3:
update_supernav()
update_download_landing_sources_box()
update_homepage_download_box()
class ReleaseFile(ContentManageable, NameSlugModel):
"""
Individual files in a release. If a specific OS/release combo has multiple
versions for example Windows and MacOS 32 vs 64 bit each file needs to be
added separately
"""
os = models.ForeignKey(
OS,
related_name='releases',
verbose_name='OS',
on_delete=models.CASCADE,
)
release = models.ForeignKey(Release, related_name='files', on_delete=models.CASCADE)
description = models.TextField(blank=True)
is_source = models.BooleanField('Is Source Distribution', default=False)
url = models.URLField('URL', unique=True, db_index=True, help_text="Download URL")
gpg_signature_file = models.URLField(
'GPG SIG URL',
blank=True,
help_text="GPG Signature URL"
)
md5_sum = models.CharField('MD5 Sum', max_length=200, blank=True)
filesize = models.IntegerField(default=0)
download_button = models.BooleanField(default=False, help_text="Use for the supernav download button for this OS")
class Meta:
verbose_name = 'Release File'
verbose_name_plural = 'Release Files'
ordering = ('-release__is_published', 'release__name', 'os__name', 'name')