Skip to content

Commit 3e2ae99

Browse files
Add python version info to admin list view
Now also 2 and 3 compatible
1 parent 97f5cb2 commit 3e2ae99

File tree

12 files changed

+109
-14
lines changed

12 files changed

+109
-14
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ language:
22
python
33

44
python:
5+
2.7
56
3.6
67

78
install:

package_monitor/admin.py

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ def check_pypi(modeladmin, request, queryset):
2727
logger.debug("Ignoring version update '%s' is editable", p.package_name)
2828
else:
2929
p.update_from_pypi()
30+
31+
3032
check_pypi.short_description = "Update selected packages from PyPI"
3133

3234

@@ -76,15 +78,39 @@ class PackageVersionAdmin(admin.ModelAdmin):
7678
actions = (check_pypi,)
7779
change_list_template = 'change_list.html'
7880
list_display = (
79-
'package_name', 'is_editable', '_updateable', 'current_version', 'next_version',
80-
'latest_version', '_licence', 'diff_status', 'checked_pypi_at', 'is_parseable'
81+
'package_name',
82+
'_updateable',
83+
'current_version',
84+
'next_version',
85+
'latest_version',
86+
'supports_py3',
87+
'_licence',
88+
'diff_status',
89+
'checked_pypi_at',
90+
)
91+
list_filter = (
92+
'diff_status',
93+
'is_editable',
94+
'is_parseable',
95+
UpdateAvailableListFilter,
96+
'supports_py3'
8197
)
82-
list_filter = ('diff_status', 'is_editable', 'is_parseable', UpdateAvailableListFilter)
8398
ordering = ["package_name"]
8499
readonly_fields = (
85-
'package_name', 'is_editable', 'current_version', 'next_version',
86-
'latest_version', 'diff_status', 'checked_pypi_at',
87-
'url', 'licence', 'raw', 'available_updates', 'is_parseable'
100+
'package_name',
101+
'is_editable',
102+
'is_parseable',
103+
'current_version',
104+
'next_version',
105+
'latest_version',
106+
'diff_status',
107+
'checked_pypi_at',
108+
'url',
109+
'licence',
110+
'raw',
111+
'available_updates',
112+
'python_support',
113+
'supports_py3',
88114
)
89115

90116
def _licence(self, obj):
@@ -108,4 +134,5 @@ def available_updates(self, obj):
108134
versions = package.all_versions()
109135
return html_list([v for v in versions if v > obj.current_version])
110136

137+
111138
admin.site.register(PackageVersion, PackageVersionAdmin)

package_monitor/compat.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# -*- coding: utf-8 -*-
2+
"""Python 2/3 compatibility imports."""
3+
try:
4+
from unittest import mock
5+
print("Successfully imports mock from unittest")
6+
except ImportError:
7+
print("Trying to import standalone mock")
8+
import mock
9+
print("Successfully imported standalone mock")
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# -*- coding: utf-8 -*-
2+
3+
4+
from django.db import migrations, models
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
('package_monitor', '0005_packageversion_is_parseable'),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name='packageversion',
16+
name='python_support',
17+
field=models.CharField(default='', help_text=b'Python version support as specified in the PyPI classifiers.', max_length=100),
18+
preserve_default=False,
19+
),
20+
migrations.AddField(
21+
model_name='packageversion',
22+
name='supports_py3',
23+
field=models.NullBooleanField(default=None, help_text=b'Does this package support Python3?'),
24+
),
25+
]

package_monitor/models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ class PackageVersion(models.Model):
4848
null=True, blank=True,
4949
help_text="Next available version available from PyPI."
5050
)
51+
python_support = models.CharField(
52+
max_length=100,
53+
help_text="Python version support as specified in the PyPI classifiers."
54+
)
55+
supports_py3 = models.NullBooleanField(
56+
default=None,
57+
help_text="Does this package support Python3?"
58+
)
5159
licence = models.CharField(
5260
max_length=100,
5361
blank=True,
@@ -118,6 +126,8 @@ def update_from_pypi(self):
118126
self.latest_version = package.latest_version()
119127
self.next_version = package.next_version(self.current_version)
120128
self.diff_status = pypi.version_diff(self.current_version, self.latest_version)
129+
self.python_support = package.python_support()
130+
self.supports_py3 = package.supports_py3()
121131
self.checked_pypi_at = tz_now()
122132
self.save()
123133
return self

package_monitor/pypi.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,17 @@ def parse_version(version_string):
2727
"""Parse a string into a PackageVersion."""
2828
try:
2929
return Version.coerce(version_string)
30-
except:
30+
except Exception:
3131
return None
3232

3333

34+
def parse_python(classifiers):
35+
"""Parse out the versions of python supported a/c classifiers."""
36+
prefix = 'Programming Language :: Python ::'
37+
python_classifiers = [c.split('::')[2].strip() for c in classifiers if c.startswith(prefix)]
38+
return ', '.join([c for c in python_classifiers if parse_version(c)])
39+
40+
3441
def version_diff(version1, version2):
3542
"""Return string representing the diff between package versions.
3643
@@ -86,6 +93,9 @@ def info(self):
8693
def licence(self):
8794
return self.info().get('license') or '(unspecified)'
8895

96+
def classifiers(self):
97+
return self.info().get('classifiers', [])
98+
8999
def latest_version(self):
90100
return parse_version(self.info().get('version'))
91101

@@ -99,3 +109,10 @@ def next_version(self, current_version):
99109
return min([v for v in self.all_versions() if v > current_version])
100110
except ValueError:
101111
return None
112+
113+
def python_support(self):
114+
return parse_python(self.classifiers())
115+
116+
def supports_py3(self):
117+
versions = self.python_support().split(', ')
118+
return len([v for v in versions if v != '' and v[0] == '3']) > 0

package_monitor/tests/test_models.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# -*- coding: utf-8 -*-
22
from django.test import TestCase
33

4-
from unittest.mock import patch
54
from requirements import requirement
65
from semantic_version import Version
76

87
from .. import models
8+
from ..compat import mock
99
from ..tests import mock_get
1010

1111

@@ -81,7 +81,7 @@ def test_init_unparseable(self):
8181
self.assertEqual(v.is_editable, False)
8282
self.assertEqual(v.url, 'http://pypi.python.org/pypi/foo/json')
8383

84-
@patch('requests.get', mock_get)
84+
@mock.patch('requests.get', mock_get)
8585
def test_update_from_pypi(self):
8686
"""Test the update_from_pypi method."""
8787
# editable packages return None
@@ -93,7 +93,7 @@ def test_update_from_pypi(self):
9393
self.assertEqual(v.latest_version, Version('1.9.1'))
9494
self.assertEqual(v.diff_status, 'major')
9595

96-
@patch('requests.get', mock_get)
96+
@mock.patch('requests.get', mock_get)
9797
def test_update_from_pypi_unparseable(self):
9898
"""Test the update_from_pypi method for unparseable requirements."""
9999
# editable packages return None

package_monitor/tests/test_pypi.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
"""Tests for the pypi module."""
33
import json
44
from os import path
5-
from unittest import mock
65

76
from django.core.cache import cache
87
from django.test import TestCase
98

109
from semantic_version import Version
1110

1211
from .. import pypi
12+
from ..compat import mock
1313

1414

1515
def mock_get(packge_url):

package_monitor/views.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ def reload(request):
1010
"""Reload local requirements file."""
1111
refresh_packages.clean()
1212
refresh_packages.local()
13+
refresh_packages.remote()
1314
url = request.META.get('HTTP_REFERER')
1415
if url:
1516
return HttpResponseRedirect(url)

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
django==1.11
1+
django>=1.8
22
requests>=2.0
33
requirements_parser==0.1.0
44
semantic_version>=2.5

0 commit comments

Comments
 (0)