Merge lp:~michael.nelson/ubuntu-webcatalog/store-apps-per-distroseries into lp:ubuntu-webcatalog

Proposed by Michael Nelson
Status: Merged
Approved by: Danny Tamez
Approved revision: 19
Merged at revision: 13
Proposed branch: lp:~michael.nelson/ubuntu-webcatalog/store-apps-per-distroseries
Merge into: lp:ubuntu-webcatalog
Diff against target: 324 lines (+150/-13)
8 files modified
src/webcatalog/forms.py (+1/-0)
src/webcatalog/management/commands/import_app_install_data.py (+30/-5)
src/webcatalog/models.py (+15/-3)
src/webcatalog/static/css/webcatalog.css (+29/-0)
src/webcatalog/templates/webcatalog/application_detail.html (+3/-2)
src/webcatalog/tests/factory.py (+15/-2)
src/webcatalog/tests/test_commands.py (+47/-0)
src/webcatalog/tests/test_views.py (+10/-1)
To merge this branch: bzr merge lp:~michael.nelson/ubuntu-webcatalog/store-apps-per-distroseries
Reviewer Review Type Date Requested Status
Danny Tamez (community) Approve
Review via email: mp+60200@code.launchpad.net

Commit message

[r=zematynnad][bug=777869,777454] Store apps per distroseries, update when importing and include install button.

Description of the change

Overview
========

This branch ensures that apps are stored per distroseries (bug 777869), and that apps are updated if they already exist in the distroseries (rather than duplicated).

I also added a very simple apt url to the details page to look like this (bug 777454):
http://people.canonical.com/~michaeln/tmp/scribus_with_apturl.png

I'll create bugs for:
 1) more sensible apt-urls (only display if correct distroseries, otherwise display "Not on this distroseries but yours..", or "only for ubuntu" etc.
 2) Rendering of xpm icons for display on browser pages.

To test:
 $ fab bootstrap
 $ fab test

To post a comment you must log in.
19. By Michael Nelson

Merge trunk.

Revision history for this message
Danny Tamez (zematynnad) wrote :

Looks good

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'src/webcatalog/forms.py'
--- src/webcatalog/forms.py 2011-04-13 09:28:55 +0000
+++ src/webcatalog/forms.py 2011-05-06 15:06:19 +0000
@@ -53,6 +53,7 @@
5353
54 class Meta:54 class Meta:
55 model = Application55 model = Application
56 exclude = ('distroseries',)
5657
57 @classmethod58 @classmethod
58 def get_form_from_desktop_data(cls, str_data):59 def get_form_from_desktop_data(cls, str_data):
5960
=== modified file 'src/webcatalog/management/commands/import_app_install_data.py'
--- src/webcatalog/management/commands/import_app_install_data.py 2011-04-15 09:18:10 +0000
+++ src/webcatalog/management/commands/import_app_install_data.py 2011-05-06 15:06:19 +0000
@@ -33,9 +33,14 @@
33import apt33import apt
34from apt_inst import DebFile34from apt_inst import DebFile
35from django.core.files.images import ImageFile35from django.core.files.images import ImageFile
36from django.forms.models import construct_instance
36from django.core.management.base import LabelCommand37from django.core.management.base import LabelCommand
3738
38from webcatalog.forms import ApplicationForm39from webcatalog.forms import ApplicationForm
40from webcatalog.models import (
41 Application,
42 DistroSeries,
43 )
3944
40__metaclass__ = type45__metaclass__ = type
41__all__ = [46__all__ = [
@@ -55,13 +60,20 @@
55 )60 )
56 verbosity = 061 verbosity = 0
5762
58 def handle_label(self, distroseries, **options):63 def handle_label(self, distroseries_name, **options):
59 self.verbosity = int(options['verbosity'])64 self.verbosity = int(options['verbosity'])
60 data_dir = tempfile.mkdtemp()65 data_dir = tempfile.mkdtemp()
66 distroseries, created = DistroSeries.objects.get_or_create(
67 code_name=distroseries_name)
68 if created and self.verbosity > 0:
69 self.stdout.write(
70 "Created a DistroSeries record with codename '{0}'.".format(
71 distroseries_name))
72
61 # Download app install data when requested.73 # Download app install data when requested.
62 if options['local_app_install_deb'] == '':74 if options['local_app_install_deb'] == '':
63 deb_location = self.get_latest_app_data_for_series(75 deb_location = self.get_latest_app_data_for_series(
64 distroseries, working_dir=data_dir)76 distroseries_name, working_dir=data_dir)
65 else:77 else:
66 deb_location = options['local_app_install_deb']78 deb_location = options['local_app_install_deb']
6779
@@ -74,7 +86,7 @@
74 matcher = data_dir + '/usr/share/app-install/desktop/*.desktop'86 matcher = data_dir + '/usr/share/app-install/desktop/*.desktop'
75 icon_dir = data_dir + '/usr/share/app-install/icons/'87 icon_dir = data_dir + '/usr/share/app-install/icons/'
76 for desktop_file in iglob(matcher):88 for desktop_file in iglob(matcher):
77 self.process_desktop_file(desktop_file, icon_dir)89 self.process_desktop_file(desktop_file, icon_dir, distroseries)
7890
79 shutil.rmtree(data_dir)91 shutil.rmtree(data_dir)
8092
@@ -121,13 +133,26 @@
121 shutil.rmtree(tmp_sources_dir)133 shutil.rmtree(tmp_sources_dir)
122 return uri134 return uri
123135
124 def process_desktop_file(self, desktop_file_path, icon_dir):136 def process_desktop_file(self, desktop_file_path, icon_dir, distroseries):
125 with open(desktop_file_path, 'r') as desktop_file:137 with open(desktop_file_path, 'r') as desktop_file:
126 data = desktop_file.read()138 data = desktop_file.read()
127 form = ApplicationForm.get_form_from_desktop_data(data)139 form = ApplicationForm.get_form_from_desktop_data(data)
128140
129 if form.is_valid():141 if form.is_valid():
130 app = form.save()142 # Check if the app already exists in our DB.
143 existing_apps = Application.objects.filter(
144 package_name=form.cleaned_data['package_name'],
145 distroseries=distroseries)
146 if existing_apps.count() > 0:
147 # Force the form to update the existing application.
148 # Simply setting the forms.instance doesn't work as the
149 # form constructs its instance during is_valid, and
150 # doesn't reconstruct it to save.
151 app = construct_instance(form, existing_apps[0])
152 else:
153 app = form.save(commit=False)
154 app.distroseries = distroseries
155 app.save()
131 app.update_departments()156 app.update_departments()
132 self.add_icon_to_app(app, icon_dir)157 self.add_icon_to_app(app, icon_dir)
133 if self.verbosity > 0:158 if self.verbosity > 0:
134159
=== modified file 'src/webcatalog/models.py'
--- src/webcatalog/models.py 2011-04-19 18:46:21 +0000
+++ src/webcatalog/models.py 2011-05-06 15:06:19 +0000
@@ -32,15 +32,27 @@
32__metaclass__ = type32__metaclass__ = type
33__all__ = [33__all__ = [
34 'Application',34 'Application',
35 'DistroSeries',
35 ]36 ]
3637
3738
39class DistroSeries(models.Model):
40 """An Ubuntu distribution series."""
41 code_name = models.CharField(max_length=20, unique=True, db_index=True)
42 version = models.CharField(max_length=10, blank=True)
43
44 def __unicode__(self):
45 return "Ubuntu %s (%s)" % (self.code_name.capitalize(), self.version)
46
47
38class Application(models.Model):48class Application(models.Model):
39 # We'll most likely need one of these per lang/series (as each lang49 # We'll most likely need one of these per lang/series (as each lang
40 # will potentially have a different name) and names/descriptions can50 # will potentially have a different name) and names/descriptions can
41 # change for a package for different series. Regarding the language,51 # change for a package for different series.
42 # if we have the gettext data available, we might be able to do this52 # We may need to have one application item per language, depending
43 # at runtime instead.53 # whether we have geettext data available that we could use at
54 # runtime (much preferred)
55 distroseries = models.ForeignKey(DistroSeries)
4456
45 # The following fields are extracted from app-install-data.57 # The following fields are extracted from app-install-data.
46 package_name = models.CharField(max_length=100)58 package_name = models.CharField(max_length=100)
4759
=== modified file 'src/webcatalog/static/css/webcatalog.css'
--- src/webcatalog/static/css/webcatalog.css 2011-04-20 19:41:44 +0000
+++ src/webcatalog/static/css/webcatalog.css 2011-05-06 15:06:19 +0000
@@ -108,6 +108,35 @@
108 width: 300px;108 width: 300px;
109}109}
110110
111.awesome, .awesome:visited {
112 background-color: #222222;
113 border-bottom: 1px solid rgba(0, 0, 0, 0.25);
114 border-radius: 5px 5px 5px 5px;
115 box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
116 cursor: pointer;
117 display: inline-block;
118 font-size: 20px;
119 font-weight: bold;
120 line-height: 1;
121 margin: 5px;
122 padding: 5px;
123 position: relative;
124 text-decoration: none;
125 text-shadow: 0 -1px 1px rgba(0, 0, 0, 0.25);
126}
127.awesome:hover {
128 background-color: #D45500;
129 color: #FFFFFF;
130 text-decoration: none;
131}
132.awesome:active {
133 top: 1px;
134}
135.awesome, .awesome:visited {
136 background-color: #FF5C00;
137 color: #FFFFFF;
138}
139
111.dept-icon {140.dept-icon {
112 float:left;141 float:left;
113 width:56px;142 width:56px;
114143
=== modified file 'src/webcatalog/templates/webcatalog/application_detail.html'
--- src/webcatalog/templates/webcatalog/application_detail.html 2011-04-08 12:25:25 +0000
+++ src/webcatalog/templates/webcatalog/application_detail.html 2011-05-06 15:06:19 +0000
@@ -9,7 +9,7 @@
9 {% if application.icon %}9 {% if application.icon %}
10 <img src="{{ application.icon.url }}"/>10 <img src="{{ application.icon.url }}"/>
11 {% else %}11 {% else %}
12 <img src="{{ STATIC_URL }}images/noicon_32.png"/>12 <img src="{{ STATIC_URL }}images/noicon_32.png"/>
13 {% endif %}13 {% endif %}
1414
15 <h2>{{ application.name }}</h2>15 <h2>{{ application.name }}</h2>
@@ -23,7 +23,8 @@
23 {% endcomment %}23 {% endcomment %}
24 <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" />24 <img src="http://screenshots.ubuntu.com/thumbnail-with-version/{{ application.package_name }}/ignored" />
25 </div>25 </div>
26 {{ application.description }}26 <p>{{ application.description }}</p>
27<a href="apt://{{ application.package_name }}" class="awesome">Download {{ application.name }}</a>
27 </div>28 </div>
28 <div class="license">29 <div class="license">
29 <table>30 <table>
3031
=== modified file 'src/webcatalog/tests/factory.py'
--- src/webcatalog/tests/factory.py 2011-04-14 00:19:29 +0000
+++ src/webcatalog/tests/factory.py 2011-05-06 15:06:19 +0000
@@ -30,6 +30,7 @@
30from webcatalog.models import (30from webcatalog.models import (
31 Application,31 Application,
32 Department,32 Department,
33 DistroSeries,
33 )34 )
3435
35__metaclass__ = type36__metaclass__ = type
@@ -74,7 +75,8 @@
74 user.save()75 user.save()
7576
76 def make_application(self, package_name=None, name=None,77 def make_application(self, package_name=None, name=None,
77 comment=None, description=None, icon_name='', icon=None):78 comment=None, description=None, icon_name='', icon=None,
79 distroseries=None):
78 if name is None:80 if name is None:
79 name = self.get_unique_string(prefix='Readable Name')81 name = self.get_unique_string(prefix='Readable Name')
80 if package_name is None:82 if package_name is None:
@@ -83,15 +85,26 @@
83 comment = self.get_unique_string(prefix='comment')85 comment = self.get_unique_string(prefix='comment')
84 if description is None:86 if description is None:
85 description = self.get_unique_string(prefix='description')87 description = self.get_unique_string(prefix='description')
88 if distroseries is None:
89 distroseries = self.make_distroseries()
8690
87 return Application.objects.create(91 return Application.objects.create(
88 package_name=package_name, name=name, comment=comment,92 package_name=package_name, name=name, comment=comment,
89 description=description, popcon=999, icon=icon,93 description=description, popcon=999, icon=icon,
90 icon_name=icon_name)94 icon_name=icon_name, distroseries=distroseries)
9195
92 def make_department(self, name, parent=None):96 def make_department(self, name, parent=None):
93 return Department.objects.create(name=name, parent=parent)97 return Department.objects.create(name=name, parent=parent)
9498
99 def make_distroseries(self, code_name=None, version=None):
100 if code_name is None:
101 code_name = self.get_unique_string(prefix='series-')
102 if version is None:
103 version = "1.{0}".format(self.get_unique_integer)
104
105 return DistroSeries.objects.create(
106 code_name=code_name, version=version)
107
95 def get_test_path(self, file_name):108 def get_test_path(self, file_name):
96 return os.path.join(109 return os.path.join(
97 os.path.dirname(__file__), 'test_data', file_name)110 os.path.dirname(__file__), 'test_data', file_name)
98111
=== modified file 'src/webcatalog/tests/test_commands.py'
--- src/webcatalog/tests/test_commands.py 2011-04-15 09:14:36 +0000
+++ src/webcatalog/tests/test_commands.py 2011-05-06 15:06:19 +0000
@@ -97,6 +97,53 @@
97 'Graphic Page Layout and Publication (Stable)',97 'Graphic Page Layout and Publication (Stable)',
98 scribus.comment)98 scribus.comment)
9999
100 def test_applications_updated(self):
101 # If applications already exist for the same series, it is
102 # updated.
103 app = self.factory.make_application(package_name='scribus',
104 comment='old comment',
105 distroseries=self.factory.make_distroseries(code_name='natty'))
106
107 call_command(
108 'import_app_install_data', 'natty',
109 local_app_install_deb=self.factory.get_test_path(
110 'app-install-data-test_all.deb'),
111 verbosity=0)
112
113 app_reloaded = Application.objects.get(id=app.id)
114 self.assertEqual(
115 'Graphic Page Layout and Publication (Stable)',
116 app_reloaded.comment)
117
118 def test_same_app_different_distroseries(self):
119 # Importing the same app (package_name) as an existing app, but
120 # for a different distroseries creates a new record.
121 app = self.factory.make_application(package_name='scribus',
122 comment='old comment',
123 distroseries=self.factory.make_distroseries(code_name='natty'))
124
125 call_command(
126 'import_app_install_data', 'oneric',
127 local_app_install_deb=self.factory.get_test_path(
128 'app-install-data-test_all.deb'),
129 verbosity=0)
130
131 app_reloaded = Application.objects.get(id=app.id)
132 self.assertEqual('old comment', app_reloaded.comment)
133 self.assertEqual(
134 2, Application.objects.filter(package_name='scribus').count())
135
136 def test_distroseries_added_to_app(self):
137 call_command(
138 'import_app_install_data', 'natty',
139 local_app_install_deb=self.factory.get_test_path(
140 'app-install-data-test_all.deb'),
141 verbosity=0)
142
143 self.assertEqual(2, Application.objects.count())
144 scribus = Application.objects.get(package_name='scribus')
145 self.assertEqual('natty', scribus.distroseries.code_name)
146
100 def test_icon_added_to_model(self):147 def test_icon_added_to_model(self):
101 call_command(148 call_command(
102 'import_app_install_data', 'natty',149 'import_app_install_data', 'natty',
103150
=== modified file 'src/webcatalog/tests/test_views.py'
--- src/webcatalog/tests/test_views.py 2011-04-13 15:48:31 +0000
+++ src/webcatalog/tests/test_views.py 2011-05-06 15:06:19 +0000
@@ -68,6 +68,15 @@
68 response, '<img src="http://screenshots.ubuntu.com/'68 response, '<img src="http://screenshots.ubuntu.com/'
69 'thumbnail-with-version/pkgfoo/ignored"')69 'thumbnail-with-version/pkgfoo/ignored"')
7070
71 def test_link_to_apt_package(self):
72 self.factory.make_application(package_name='pkgfoo')
73 url = reverse('wc-package-detail', args=['pkgfoo'])
74
75 response = self.client.get(url)
76
77 self.assertContains( response, '<a href="apt://pkgfoo"')
78
79
71class SearchTestCase(TestCaseWithFactory):80class SearchTestCase(TestCaseWithFactory):
72 def test_no_search_retrieves_no_apps(self):81 def test_no_search_retrieves_no_apps(self):
73 # Ensure there's some data in the DB82 # Ensure there's some data in the DB
@@ -170,4 +179,4 @@
170179
171 response = self.client.get(reverse('wc-department', args=[dept.id]))180 response = self.client.get(reverse('wc-department', args=[dept.id]))
172181
173 self.assertNotContains(response, 'Subsections')
174\ No newline at end of file182\ No newline at end of file
183 self.assertNotContains(response, 'Subsections')

Subscribers

People subscribed via source and target branches