Merge lp:~stevenk/launchpad/kill-entitlements into lp:launchpad

Proposed by Steve Kowalik
Status: Merged
Approved by: William Grant
Approved revision: no longer in the source branch.
Merged at revision: 14793
Proposed branch: lp:~stevenk/launchpad/kill-entitlements
Merge into: lp:launchpad
Diff against target: 1532 lines (+0/-1430)
12 files modified
database/sampledata/current-dev.sql (+0/-11)
database/sampledata/current.sql (+0/-11)
lib/lp/registry/configure.zcml (+0/-23)
lib/lp/registry/doc/entitlement.txt (+0/-333)
lib/lp/registry/interfaces/entitlement.py (+0/-242)
lib/lp/registry/interfaces/person.py (+0/-2)
lib/lp/registry/model/entitlement.py (+0/-189)
lib/lp/registry/model/person.py (+0/-1)
lib/lp/registry/scripts/entitlement.py (+0/-330)
lib/lp/registry/tests/test_entitlementimport.py (+0/-184)
lib/lp/security.py (+0/-21)
scripts/entitlements-to-lp.py (+0/-83)
To merge this branch: bzr merge lp:~stevenk/launchpad/kill-entitlements
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+92888@code.launchpad.net

Commit message

[r=wgrant][no-qa] Kill the never used entitlements interface/model and supporting scripts.

Description of the change

Kill the never used entitlements interface/model and supporting scripts.

The database table will be dropped in a separate branch.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'database/sampledata/current-dev.sql'
2--- database/sampledata/current-dev.sql 2012-01-27 09:29:19 +0000
3+++ database/sampledata/current-dev.sql 2012-02-14 00:28:18 +0000
4@@ -4032,17 +4032,6 @@
5 ALTER TABLE emailaddress ENABLE TRIGGER ALL;
6
7
8-ALTER TABLE entitlement DISABLE TRIGGER ALL;
9-
10-INSERT INTO entitlement (id, person, entitlement_type, quota, amount_used, date_starts, date_expires, registrant, date_created, approved_by, date_approved, state, whiteboard, is_dirty, distribution, product, project) VALUES (1, 17, 10, 100, 0, '2007-06-11 12:00:00', '2008-06-11 12:00:00', NULL, '2007-06-10 12:00:00', NULL, NULL, 30, NULL, true, NULL, NULL, NULL);
11-INSERT INTO entitlement (id, person, entitlement_type, quota, amount_used, date_starts, date_expires, registrant, date_created, approved_by, date_approved, state, whiteboard, is_dirty, distribution, product, project) VALUES (2, 17, 20, 200, 0, '2007-06-11 12:00:00', '2008-06-11 12:00:00', NULL, '2007-06-10 12:00:00', NULL, NULL, 30, NULL, true, NULL, NULL, NULL);
12-INSERT INTO entitlement (id, person, entitlement_type, quota, amount_used, date_starts, date_expires, registrant, date_created, approved_by, date_approved, state, whiteboard, is_dirty, distribution, product, project) VALUES (4, 18, 10, 5, 0, '2007-06-11 12:00:00', '2007-06-11 00:00:00', NULL, '2007-06-11 00:44:19.267601', NULL, NULL, 30, NULL, true, NULL, NULL, NULL);
13-INSERT INTO entitlement (id, person, entitlement_type, quota, amount_used, date_starts, date_expires, registrant, date_created, approved_by, date_approved, state, whiteboard, is_dirty, distribution, product, project) VALUES (5, 18, 10, 3, 0, '2007-06-11 12:00:00', '2007-06-11 00:00:00', NULL, '2007-06-11 01:02:48.538842', NULL, NULL, 30, NULL, true, NULL, NULL, NULL);
14-
15-
16-ALTER TABLE entitlement ENABLE TRIGGER ALL;
17-
18-
19 ALTER TABLE faq DISABLE TRIGGER ALL;
20
21 INSERT INTO faq (id, title, tags, content, product, distribution, owner, date_created, last_updated_by, date_last_updated, fti) VALUES (1, 'Wireless Networking Documentation', 'wifi', 'Installation instructions for many wireless cards can be found at:
22
23=== modified file 'database/sampledata/current.sql'
24--- database/sampledata/current.sql 2012-01-27 09:29:19 +0000
25+++ database/sampledata/current.sql 2012-02-14 00:28:18 +0000
26@@ -3965,17 +3965,6 @@
27 ALTER TABLE emailaddress ENABLE TRIGGER ALL;
28
29
30-ALTER TABLE entitlement DISABLE TRIGGER ALL;
31-
32-INSERT INTO entitlement (id, person, entitlement_type, quota, amount_used, date_starts, date_expires, registrant, date_created, approved_by, date_approved, state, whiteboard, is_dirty, distribution, product, project) VALUES (1, 17, 10, 100, 0, '2007-06-11 12:00:00', '2008-06-11 12:00:00', NULL, '2007-06-10 12:00:00', NULL, NULL, 30, NULL, true, NULL, NULL, NULL);
33-INSERT INTO entitlement (id, person, entitlement_type, quota, amount_used, date_starts, date_expires, registrant, date_created, approved_by, date_approved, state, whiteboard, is_dirty, distribution, product, project) VALUES (2, 17, 20, 200, 0, '2007-06-11 12:00:00', '2008-06-11 12:00:00', NULL, '2007-06-10 12:00:00', NULL, NULL, 30, NULL, true, NULL, NULL, NULL);
34-INSERT INTO entitlement (id, person, entitlement_type, quota, amount_used, date_starts, date_expires, registrant, date_created, approved_by, date_approved, state, whiteboard, is_dirty, distribution, product, project) VALUES (4, 18, 10, 5, 0, '2007-06-11 12:00:00', '2007-06-11 00:00:00', NULL, '2007-06-11 00:44:19.267601', NULL, NULL, 30, NULL, true, NULL, NULL, NULL);
35-INSERT INTO entitlement (id, person, entitlement_type, quota, amount_used, date_starts, date_expires, registrant, date_created, approved_by, date_approved, state, whiteboard, is_dirty, distribution, product, project) VALUES (5, 18, 10, 3, 0, '2007-06-11 12:00:00', '2007-06-11 00:00:00', NULL, '2007-06-11 01:02:48.538842', NULL, NULL, 30, NULL, true, NULL, NULL, NULL);
36-
37-
38-ALTER TABLE entitlement ENABLE TRIGGER ALL;
39-
40-
41 ALTER TABLE faq DISABLE TRIGGER ALL;
42
43 INSERT INTO faq (id, title, tags, content, product, distribution, owner, date_created, last_updated_by, date_last_updated, fti) VALUES (1, 'Wireless Networking Documentation', 'wifi', 'Installation instructions for many wireless cards can be found at:
44
45=== modified file 'lib/lp/registry/configure.zcml'
46--- lib/lp/registry/configure.zcml 2012-02-13 05:50:16 +0000
47+++ lib/lp/registry/configure.zcml 2012-02-14 00:28:18 +0000
48@@ -1710,29 +1710,6 @@
49 permission="launchpad.Edit"
50 interface="lp.registry.interfaces.productrelease.IProductReleaseFileEditRestricted"/>
51 </class>
52- <class
53- class="lp.registry.model.entitlement.Entitlement">
54- <require
55- permission="launchpad.View"
56- interface="lp.registry.interfaces.entitlement.IEntitlement"/>
57- <require
58- permission="launchpad.Admin"
59- set_schema="lp.registry.interfaces.entitlement.IEntitlement"/>
60- </class>
61-
62- <!-- EntitlementSet -->
63-
64- <class
65- class="lp.registry.model.entitlement.EntitlementSet">
66- <allow
67- interface="lp.registry.interfaces.entitlement.IEntitlementSet"/>
68- </class>
69- <securedutility
70- class="lp.registry.model.entitlement.EntitlementSet"
71- provides="lp.registry.interfaces.entitlement.IEntitlementSet">
72- <allow
73- interface="lp.registry.interfaces.entitlement.IEntitlementSet"/>
74- </securedutility>
75
76 <!-- SuiteSourcePackage -->
77 <class
78
79=== removed file 'lib/lp/registry/doc/entitlement.txt'
80--- lib/lp/registry/doc/entitlement.txt 2011-12-30 06:14:56 +0000
81+++ lib/lp/registry/doc/entitlement.txt 1970-01-01 00:00:00 +0000
82@@ -1,333 +0,0 @@
83-= Entitlements =
84-
85-Entitlements are a means of recording features and privileges
86-Launchpad users have been assigned. The business model for these
87-assignments is not important, but it could be through a commercial
88-arrangement or through a grant that applies to everyone.
89-
90- >>> from datetime import datetime
91- >>> import pytz
92- >>> from lp.services.database.sqlbase import flush_database_updates
93- >>> from lp.testing import login
94- >>> from lp.services.webapp.testing import verifyObject
95- >>> from lp.registry.interfaces.entitlement import (
96- ... EntitlementQuota,
97- ... EntitlementState,
98- ... EntitlementType,
99- ... IEntitlement,
100- ... IEntitlementSet,
101- ... )
102- >>> from lp.registry.interfaces.person import IPersonSet
103-
104-The logged in user must be an admin in order to change the quota.
105-
106- >>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
107- >>> login("foo.bar@canonical.com")
108- >>> foobar = getUtility(ILaunchBag).user
109- >>> lp_admins = getUtility(ILaunchpadCelebrities).admin
110- >>> foobar.inTeam(lp_admins)
111- True
112- >>> entitlements = getUtility(IEntitlementSet)
113- >>> entitlements_in_sample_data = entitlements.count()
114-
115-Find a team with an entitlement.
116-
117- >>> personset = getUtility(IPersonSet)
118- >>> ubuntu_team = personset.getByName('ubuntu-team')
119-
120-An entitlement grants privilege to use a restricted feature. The
121-'quota' is the number of instances being granted and the
122-entitlement_type is the feature being enabled. The 'person' is the
123-person or team being granted the right. The state for a granted
124-entitlement is ACTIVE and the expiration and start dates can be specified.
125-
126- >>> UTC = pytz.timezone('UTC')
127- >>> entitlement = getUtility(IEntitlementSet).new(
128- ... quota=5,
129- ... entitlement_type=EntitlementType.PRIVATE_BUGS,
130- ... person=ubuntu_team,
131- ... state=EntitlementState.ACTIVE,
132- ... date_expires=datetime(2038, 6, 11, tzinfo=UTC),
133- ... amount_used=0)
134- >>> verifyObject(IEntitlement, entitlement)
135- True
136-
137-The person can be accessed and shown to be correct.
138-
139- >>> print entitlement.person.name
140- ubuntu-team
141-
142-If the current date is after the start date (if not None) or before
143-the expiration date (if not None) then the in_date_range test will
144-pass.
145-
146- >>> entitlement.in_date_range
147- True
148-
149-== Quota checking ==
150-
151-If the amount used is less than the quota value the exceeded_quota
152-test will be false.
153-
154- >>> entitlement.quota = 100
155- >>> entitlement.amount_used = 0
156- >>> entitlement.exceeded_quota
157- False
158-
159-The quota is not exceeded, the date is in range, and the state is
160-ACTIVE, so the entitlement is valid.
161-
162- >>> entitlement.is_valid
163- True
164-
165-If the amount used is equal to the quota 'exceeded_quota' should still
166-be false and 'is_valid' is still true.
167-
168- >>> entitlement.quota = 100
169- >>> entitlement.amount_used = 100
170- >>> entitlement.exceeded_quota
171- False
172- >>> entitlement.is_valid
173- True
174-
175-However if the amount_used to exceeds the quota, the exceeded quota is
176-true and the entitlement is no longer valid.
177-
178- >>> entitlement.quota = 100
179- >>> entitlement.amount_used = 101
180- >>> entitlement.exceeded_quota
181- True
182- >>> entitlement.is_valid
183- False
184-
185-If the quota is UNLIMITED the exceeded quota test is false even when
186-using a really large value for amount used. The entitlement remains
187-valid.
188-
189-We use math.pow here, since __builtin__.pow is overridden by twisted.conch.
190-
191- >>> import math
192- >>> entitlement.quota = EntitlementQuota.UNLIMITED
193- >>> entitlement.amount_used = math.pow(10,9)
194- >>> entitlement.exceeded_quota
195- False
196- >>> entitlement.is_valid
197- True
198-
199-Calling incrementAmountUsed increases the value by one.
200-
201- >>> entitlement.quota = 10
202- >>> entitlement.amount_used = 9
203- >>> entitlement.incrementAmountUsed()
204- >>> entitlement.amount_used
205- 10
206-
207-Attempting to increment past the quota will cause an error.
208-
209- >>> entitlement.incrementAmountUsed()
210- Traceback (most recent call last):
211- ...
212- EntitlementQuotaExceededError: ...
213-
214-If the entitlement is invalid for any reason, attempting to increment
215-the amount used will cause an error.
216-
217- >>> entitlement.state = EntitlementState.INACTIVE
218- >>> entitlement.quota = 50
219- >>> entitlement.incrementAmountUsed()
220- Traceback (most recent call last):
221- ...
222- EntitlementInvalidError: ...
223-
224-
225-== Date range checking ==
226-
227-The 'in_date_range' attribute can be used to tell whether the
228-entitlement is valid with respect to the start date and expires date.
229-When the current date is out of range the entitlement is considered
230-invalid.
231-
232-When the entitlement does not have a start date or an expiration date
233-then those values are not checked. If no start date is given the
234-expiration date is still used, and vice versa. If both start date and
235-expiration date are None then no date range checking is performed and
236-the entitlement is valid with respect to dates.
237-
238-Remove the start and expiration dates and the date range will be
239-valid.
240-
241- >>> entitlement.state = EntitlementState.ACTIVE
242- >>> entitlement.date_starts = None
243- >>> entitlement.date_expires = None
244- >>> entitlement.in_date_range
245- True
246- >>> entitlement.is_valid
247- True
248-
249- >>> from datetime import datetime, timedelta
250- >>> from pytz import UTC
251- >>> now = datetime.now(UTC)
252- >>> yesterday = now - timedelta(days = 1)
253- >>> tomorrow = now + timedelta(days = 1)
254-
255-If the start date is in the past and the expiration date is not set
256-this condition is considered an open-ended entitlement and we are
257-still in its range.
258-
259- >>> entitlement.date_starts = yesterday
260- >>> entitlement.in_date_range
261- True
262- >>> entitlement.is_valid
263- True
264-
265-If the start date is in the future the date is not in range and the
266-entitlement is invalid.
267-
268- >>> entitlement.date_starts = tomorrow
269- >>> entitlement.in_date_range
270- False
271- >>> entitlement.is_valid
272- False
273-
274-If the expires date is in the past, we are not in its date range and
275-the entitlement is not valid.
276-
277- >>> entitlement.date_starts = None
278- >>> entitlement.date_expires = yesterday
279- >>> entitlement.in_date_range
280- False
281- >>> entitlement.is_valid
282- False
283-
284-If the expiration date is in the future the date range is satisfied
285-and the entitlement is valid.
286-
287- >>> entitlement.date_expires = tomorrow
288- >>> entitlement.in_date_range
289- True
290- >>> entitlement.is_valid
291- True
292-
293-If the start date is in the past and the expires date is in the future
294-the date range is again satisfied and the entitlement is valid.
295-
296- >>> entitlement.date_starts = yesterday
297- >>> entitlement.date_expires = tomorrow
298- >>> entitlement.in_date_range
299- True
300- >>> entitlement.is_valid
301- True
302-
303-
304-== Security ==
305-
306-Viewing the entitlement's attributes is restricted to the
307-entitlement's owner, the entitlement's registrant (or to a member of
308-these teams), and to Launchpad administrators.
309-
310-If logged in as an anonymous user the entitlement values cannot be
311-viewed. A security-wrapped entitlement must be used to exercise the
312-security adapters.
313-
314- >>> entitlement = ubuntu_team.entitlements[0]
315- >>> entitlement.quota = 50
316- >>> login(ANONYMOUS)
317- >>> entitlement.quota
318- Traceback (most recent call last):
319- ...
320- Unauthorized: (<Entitlement..., 'quota', 'launchpad.View')
321-
322-
323- >>> entitlement.quota=10
324- Traceback (most recent call last):
325- ...
326- Unauthorized: (<Entitlement..., 'quota', 'launchpad.Admin')
327-
328-Authenticated Launchpad users who are not the owner or team member are
329-similarly restricted.
330-
331- >>> login('no-priv@canonical.com')
332- >>> entitlement.quota
333- Traceback (most recent call last):
334- ...
335- Unauthorized: (<Entitlement..., 'quota', 'launchpad.View')
336-
337-
338- >>> entitlement.quota=10
339- Traceback (most recent call last):
340- ...
341- Unauthorized: (<Entitlement..., 'quota', 'launchpad.Admin')
342-
343-Add the current user to the team and the user can now access
344-attributes. Since ubuntu-team is restricted, an administrator must be
345-listed as a reviewer for the membership to go through.
346-
347- >>> from lp.registry.interfaces.teammembership import TeamMembershipStatus
348- >>> nopriv = getUtility(ILaunchBag).user
349- >>> mark = personset.getByName('mark')
350- >>> nopriv.join(ubuntu_team)
351-
352- # Login as the team's owner in order to be able to change its memberships.
353- >>> login(ubuntu_team.teamowner.preferredemail.email)
354- >>> ubuntu_team.setMembershipData(nopriv, TeamMembershipStatus.APPROVED, mark)
355- >>> login('no-priv@canonical.com')
356-
357- >>> flush_database_updates()
358- >>> nopriv in ubuntu_team.activemembers
359- True
360- >>> nopriv.inTeam(ubuntu_team)
361- True
362- >>> entitlement.quota
363- 50
364-
365- >>> entitlement.quota=10
366- Traceback (most recent call last):
367- ...
368- Unauthorized: (<Entitlement..., 'quota', 'launchpad.Admin')
369-
370-
371-= EntitlementSet =
372-
373-All of the entitlements can be obtained using IEntitlementSet.
374-
375- >>> login("foo.bar@canonical.com")
376- >>> entitlements = getUtility(IEntitlementSet)
377-
378-One entitlement was created in the previous examples.
379-
380- >>> entitlements.count() - entitlements_in_sample_data
381- 1
382-
383-To look up an entitlement from the set, simply use the 'get' with the id.
384-
385- >>> entitlement2 = entitlements.get(entitlement.id)
386- >>> entitlement2.id == entitlement.id
387- True
388-
389-Get all of the valid entitlements for ubuntu_team.
390-
391- >>> valid_list = getUtility(IEntitlementSet).getValidForPerson(ubuntu_team)
392- >>> len(valid_list)
393- 1
394-
395-A new entitlement for the ubuntu_team can be created, increasing the
396-number of valid entitlements.
397-
398- >>> entitlement = getUtility(IEntitlementSet).new(
399- ... quota=50,
400- ... entitlement_type=EntitlementType.PRIVATE_BRANCHES,
401- ... person=ubuntu_team,
402- ... state=EntitlementState.ACTIVE,
403- ... date_expires=datetime(2038, 6, 11, tzinfo=UTC),
404- ... amount_used=0)
405- >>> valid_list = getUtility(IEntitlementSet).getValidForPerson(ubuntu_team)
406- >>> len(valid_list)
407- 2
408-
409-Setting the original entitlement to INACTIVE will now reduce the
410-number of valid entitlements.
411-
412- >>> entitlement.state = EntitlementState.INACTIVE
413- >>> valid_list = getUtility(IEntitlementSet).getValidForPerson(ubuntu_team)
414- >>> len(valid_list)
415- 1
416
417=== removed file 'lib/lp/registry/interfaces/entitlement.py'
418--- lib/lp/registry/interfaces/entitlement.py 2011-12-24 16:54:44 +0000
419+++ lib/lp/registry/interfaces/entitlement.py 1970-01-01 00:00:00 +0000
420@@ -1,242 +0,0 @@
421-# Copyright 2009 Canonical Ltd. This software is licensed under the
422-# GNU Affero General Public License version 3 (see the file LICENSE).
423-
424-# pylint: disable-msg=E0211,E0213
425-
426-"""Entitlement interfaces."""
427-
428-__metaclass__ = type
429-
430-__all__ = [
431- 'EntitlementInvalidError',
432- 'EntitlementQuota',
433- 'EntitlementQuotaExceededError',
434- 'EntitlementState',
435- 'EntitlementType',
436- 'IEntitlement',
437- 'IEntitlementSet',
438- ]
439-
440-from lazr.enum import (
441- DBEnumeratedType,
442- DBItem,
443- )
444-from zope.interface import (
445- Attribute,
446- Interface,
447- )
448-from zope.schema import (
449- Bool,
450- Choice,
451- Datetime,
452- Int,
453- )
454-
455-from lp import _
456-from lp.services.fields import Whiteboard
457-
458-
459-class EntitlementQuotaExceededError(Exception):
460- """The quota has been exceeded for the entitlement."""
461-
462-
463-class EntitlementInvalidError(Exception):
464- """The entitlement is not valid."""
465-
466-
467-class EntitlementType(DBEnumeratedType):
468- """The set of features supported via entitlements.
469-
470- The listed features may be enabled by the granting of an entitlement.
471- """
472-
473- PRIVATE_BRANCHES = DBItem(10, """
474- Private Branches
475-
476- The ability to create branches which are only visible to the team.
477- """)
478-
479- PRIVATE_BUGS = DBItem(20, """
480- Private Bugs
481-
482- The ability to create private bugs which are only visible to the team.
483- """)
484-
485- PRIVATE_TEAMS = DBItem(30, """
486- Private Teams
487-
488- The ability to create private teams which are only visible to parent
489- teams.
490- """)
491-
492-
493-class EntitlementState(DBEnumeratedType):
494- """States for an entitlement.
495-
496- The entitlement may start life as a REQUEST that is then granted and
497- made ACTIVE. At some point the entitlement may be revoked by marking
498- as INACTIVE.
499- """
500-
501- REQUESTED = DBItem(10, """
502- Entitlement has been requested.
503-
504- The entitlement is inactive in this state.
505- """)
506-
507- ACTIVE = DBItem(20, """
508- The entitlement is active.
509-
510- The entitlement is approved in Launchpad or was imported in the
511- active state.
512- """)
513-
514- INACTIVE = DBItem(30, """
515- The entitlement is inactive.
516-
517- The entitlement has be deactivated.
518- """)
519-
520-
521-class IEntitlement(Interface):
522- """An entitlement the right to use a specific feature in Launchpad.
523-
524- Entitlements can be granted in an unlimited quantity or with a given
525- quota. They have a start date and optionally an expiration date. An
526- entitlement is invalid if it is not active, the quota is exceeded, or if
527- it is expired.
528- """
529-
530- id = Int(
531- title=_("Entitlement id"),
532- required=True,
533- readonly=True)
534- person = Choice(
535- title=_('Person'),
536- required=True,
537- readonly=True,
538- vocabulary='ValidPersonOrTeam',
539- description=_("Person or team to whom the entitlements is assigned."))
540- date_created = Datetime(
541- title=_("Date Created"),
542- description=_("The date on which this entitlement was created."),
543- required=True,
544- readonly=True)
545- date_starts = Datetime(
546- title=_("Date Starts"),
547- description=_("The date on which this entitlement starts."),
548- readonly=False)
549- date_expires = Datetime(
550- title=_("Date Expires"),
551- description=_("The date on which this entitlement expires."),
552- readonly=False)
553- entitlement_type = Choice(
554- title=_("Type of entitlement."),
555- required=True,
556- vocabulary='EntitlementType',
557- description=_("Type of feature for this entitlement."),
558- readonly=True)
559- quota = Int(
560- title=_("Allocated quota."),
561- required=True,
562- description=_(
563- "A quota is the number of a feature allowed by this entitlement, "
564- "for instance 50 private bugs."))
565- amount_used = Int(
566- title=_("Amount used."),
567- description=_(
568- "The amount used is the number of instances of a feature "
569- "the person has used so far."))
570- registrant = Choice(
571- title=_('Registrant'),
572- vocabulary='ValidPersonOrTeam',
573- description=_(
574- "Person who registered the entitlement. "
575- "May be None if created automatically."),
576- readonly=True)
577- approved_by = Choice(
578- title=_('Approved By'),
579- vocabulary='ValidPersonOrTeam',
580- description=_(
581- "Person who approved the entitlement. "
582- "May be None if created automatically."),
583- readonly=True)
584- state = Choice(
585- title=_("State"),
586- required=True,
587- vocabulary='EntitlementState',
588- description = _("Current state of the entitlement."))
589-
590- whiteboard = Whiteboard(title=_('Whiteboard'), required=False,
591- description=_('Notes on the current status of the entitlement.'))
592-
593- is_dirty = Bool(
594- title=_("Dirty?"),
595- description=_(
596- "Is the entitlement 'dirty', i.e. has been written since the "
597- "most recent update to an external system?"))
598-
599- is_valid = Attribute(
600- "Is this entitlement valid?")
601-
602- exceeded_quota = Attribute(
603- "If the quota is not unlimited, is it exceeded?")
604-
605- in_date_range = Attribute(
606- "Has the start date passed but not the expiration date?")
607-
608- def incrementAmountUsed():
609- """Add one to the amount used."""
610-
611-
612-class IEntitlementSet(Interface):
613- """Interface representing a set of Entitlements."""
614-
615- def __getitem__(entitlement_id):
616- """Return the entitlement with the given id.
617-
618- Raise NotFoundError if there is no such entitlement.
619- """
620-
621- def __iter__():
622- """Return an iterator that will go through all entitlements."""
623-
624- def count():
625- """Return the number of entitlements in the database."""
626-
627- def get(entitlement_id, default=None):
628- """Return the entitlement with the given id.
629-
630- Return the default value if there is no such entitlement.
631- """
632-
633- def getForPerson(person):
634- """Return the entitlements for the person or team.
635-
636- Get all entitlements for a person.
637- """
638-
639- def getValidForPerson(person):
640- """Return a list of valid entitlements for the person or team.
641-
642- Get all valid entitlements for a person. None is returned if no valid
643- entitlements are found.
644- """
645-
646- def getDirty():
647- """Return the entitlements that have the dirty bit set.
648-
649- Get all entitlements that are marked as dirty.
650- """
651-
652- def new(person, quota, entitlement_type, state,
653- is_dirty=True, date_created=None, date_expires=None,
654- date_starts=None, amount_used=None, registrant=None,
655- approved_by=None):
656- """Create a new entitlement."""
657-
658-
659-class EntitlementQuota:
660- """This class stores constants for entitlements quotas."""
661-
662- UNLIMITED = 0
663
664=== modified file 'lib/lp/registry/interfaces/person.py'
665--- lib/lp/registry/interfaces/person.py 2012-02-13 23:02:42 +0000
666+++ lib/lp/registry/interfaces/person.py 2012-02-14 00:28:18 +0000
667@@ -1022,8 +1022,6 @@
668 # Really IArchive, see archive.py
669 value_type=Reference(schema=Interface)))
670
671- entitlements = Attribute("List of Entitlements for this person or team.")
672-
673 structural_subscriptions = Attribute(
674 "The structural subscriptions for this person.")
675
676
677=== removed file 'lib/lp/registry/model/entitlement.py'
678--- lib/lp/registry/model/entitlement.py 2011-12-30 06:14:56 +0000
679+++ lib/lp/registry/model/entitlement.py 1970-01-01 00:00:00 +0000
680@@ -1,189 +0,0 @@
681-# Copyright 2009 Canonical Ltd. This software is licensed under the
682-# GNU Affero General Public License version 3 (see the file LICENSE).
683-
684-# pylint: disable-msg=E0611,W0212
685-
686-__metaclass__ = type
687-__all__ = ['Entitlement', 'EntitlementSet']
688-
689-from datetime import datetime
690-
691-import pytz
692-from sqlobject import (
693- BoolCol,
694- ForeignKey,
695- IntCol,
696- SQLObjectNotFound,
697- StringCol,
698- )
699-from zope.interface import implements
700-
701-from lp.app.errors import NotFoundError
702-from lp.registry.interfaces.entitlement import (
703- EntitlementInvalidError,
704- EntitlementQuota,
705- EntitlementQuotaExceededError,
706- EntitlementState,
707- EntitlementType,
708- IEntitlement,
709- IEntitlementSet,
710- )
711-from lp.services.database.constants import DEFAULT
712-from lp.services.database.datetimecol import UtcDateTimeCol
713-from lp.services.database.enumcol import EnumCol
714-from lp.services.database.sqlbase import SQLBase
715-
716-
717-class Entitlement(SQLBase):
718- """A table recording the entitlements for a person or team."""
719-
720- implements(IEntitlement)
721- _table = 'Entitlement'
722- _defaultOrder = ['person', '-date_expires']
723-
724- person = ForeignKey(
725- dbName='person', foreignKey='Person',
726- default=None, notNull=True)
727- date_created = UtcDateTimeCol(notNull=True, default=DEFAULT)
728- date_starts = UtcDateTimeCol(notNull=False, default=None)
729- date_expires = UtcDateTimeCol(notNull=False, default=None)
730-
731- entitlement_type = EnumCol(
732- dbName='entitlement_type',
733- notNull=True,
734- enum=EntitlementType,
735- default=EntitlementType.PRIVATE_BUGS)
736- quota = IntCol(notNull=True)
737- amount_used = IntCol(notNull=True, default=0)
738- registrant = ForeignKey(
739- dbName='registrant', foreignKey='Person',
740- default=None, notNull=False)
741- approved_by = ForeignKey(
742- dbName='approved_by', foreignKey='Person',
743- default=None, notNull=False)
744- state = EnumCol(
745- dbName='state',
746- notNull=True,
747- enum=EntitlementState,
748- default=EntitlementState.INACTIVE)
749- whiteboard = StringCol(notNull=False, default=None)
750- is_dirty = BoolCol(dbName="is_dirty", notNull=True, default=True)
751-
752- @property
753- def exceeded_quota(self):
754- """See IEntitlement."""
755- if self.quota == EntitlementQuota.UNLIMITED:
756- return False
757- else:
758- return self.amount_used > self.quota
759-
760- def _isExpired(self, now=None):
761- if now is None:
762- now = datetime.now(pytz.timezone('UTC'))
763- if self.date_expires is None:
764- return False
765- else:
766- return now > self.date_expires
767-
768- def _hasNotYetStarted(self, now=None):
769- if now is None:
770- now = datetime.now(pytz.timezone('UTC'))
771- if self.date_starts is None:
772- return False
773- else:
774- return now < self.date_starts
775-
776- @property
777- def in_date_range(self):
778- """See IEntitlement."""
779- now = datetime.now(pytz.timezone('UTC'))
780- too_late = self._isExpired(now)
781- too_early = self._hasNotYetStarted(now)
782- just_right = not (too_late or too_early)
783- return just_right
784-
785- @property
786- def is_valid(self):
787- """See IEntitlement."""
788- if self.state != EntitlementState.ACTIVE:
789- return False
790- else:
791- return self.in_date_range and not self.exceeded_quota
792-
793- def incrementAmountUsed(self):
794- """See IEntitlement."""
795- if not self.is_valid:
796- raise EntitlementInvalidError(
797- "This entitlement is invalid and cannot be used.")
798-
799- self.amount_used += 1
800-
801- if self.exceeded_quota:
802- self.amount_used -= 1
803- raise EntitlementQuotaExceededError(
804- "The quota for this entitlement has been exceeded.")
805-
806-
807-class EntitlementSet:
808- """The set of all entitlements."""
809-
810- implements(IEntitlementSet)
811-
812- def __getitem__(self, entitlement_id):
813- """See `IEntitlementSet`."""
814- entitlement = self.get(entitlement_id)
815- if entitlement is None:
816- raise NotFoundError(entitlement_id)
817- return entitlement
818-
819- def __iter__(self):
820- """See `IEntitlementSet`."""
821- return iter(Entitlement.select())
822-
823- def count(self):
824- """See `IEntitlementSet`."""
825- return Entitlement.select().count()
826-
827- def get(self, entitlement_id, default=None):
828- """See `IEntitlementSet`."""
829- try:
830- return Entitlement.get(entitlement_id)
831- except SQLObjectNotFound:
832- return default
833-
834- def getForPerson(self, person):
835- """See `IEntitlementSet`."""
836- return Entitlement.selectBy(person=person)
837-
838- def getValidForPerson(self, person):
839- """See `IEntitlementSet`."""
840- entitlements = self.getForPerson(person)
841- return [entitlement for entitlement in entitlements
842- if entitlement.is_valid]
843-
844- def getDirty(self):
845- """See `IEntitlementSet`."""
846- return Entitlement.selectBy(is_dirty=True)
847-
848- def new(self, person, quota, entitlement_type,
849- state, is_dirty=True, date_created=None, date_starts=None,
850- date_expires=None, amount_used=0, registrant=None,
851- approved_by=None, whiteboard=None):
852- """See `IEntitlementSet`."""
853-
854- if date_created is None:
855- date_created = datetime.now(pytz.timezone('UTC'))
856-
857- return Entitlement(
858- person=person,
859- quota=quota,
860- entitlement_type=entitlement_type,
861- state=state,
862- is_dirty=is_dirty,
863- date_created=date_created,
864- date_starts=date_starts,
865- date_expires=date_expires,
866- amount_used=amount_used,
867- registrant=registrant,
868- approved_by=approved_by,
869- whiteboard=whiteboard)
870
871=== modified file 'lib/lp/registry/model/person.py'
872--- lib/lp/registry/model/person.py 2012-02-13 23:02:42 +0000
873+++ lib/lp/registry/model/person.py 2012-02-14 00:28:18 +0000
874@@ -595,7 +595,6 @@
875 _ircnicknames = SQLMultipleJoin('IrcID', joinColumn='person')
876 jabberids = SQLMultipleJoin('JabberID', joinColumn='person')
877
878- entitlements = SQLMultipleJoin('Entitlement', joinColumn='person')
879 visibility = EnumCol(
880 enum=PersonVisibility,
881 default=PersonVisibility.PUBLIC,
882
883=== removed file 'lib/lp/registry/scripts/entitlement.py'
884--- lib/lp/registry/scripts/entitlement.py 2011-12-19 23:38:16 +0000
885+++ lib/lp/registry/scripts/entitlement.py 1970-01-01 00:00:00 +0000
886@@ -1,330 +0,0 @@
887-# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
888-# GNU Affero General Public License version 3 (see the file LICENSE).
889-
890-"""Import entitlements from Salesforce.
891-
892-Provide a class that allows the writing and reading of entitlement exchange
893-files and a class to create and update entitlements in Launchpad.
894-"""
895-
896-__metaclass__ = type
897-__all__ = [
898- 'EntitlementExchange',
899- 'EntitlementImporter',
900- 'InvalidFormat',
901- 'NoSuchEntitlement',
902- 'UnsupportedVersion',
903- ]
904-
905-import cStringIO
906-import csv
907-import datetime
908-import re
909-import time
910-
911-import pytz
912-from zope.component import getUtility
913-
914-from lp.app.errors import NotFoundError
915-from lp.registry.interfaces.entitlement import (
916- EntitlementState,
917- EntitlementType,
918- IEntitlementSet,
919- )
920-from lp.registry.interfaces.person import IPersonSet
921-from lp.services.unicode_csv import (
922- UnicodeDictReader,
923- UnicodeDictWriter,
924- )
925-
926-
927-COMMENT = '#'
928-COMMA = ','
929-
930-
931-class NoSuchEntitlement(Exception):
932- """Used if a non-existent entitlement is specified."""
933-
934-
935-class UnsupportedVersion(Exception):
936- """Used if the version is not supported."""
937-
938-
939-class InvalidFormat(Exception):
940- """Used if the file format is not as expected."""
941-
942-
943-class RequiredValueMissing(Exception):
944- """Used if a required value was not provided."""
945-
946-
947-class EntitlementExchange:
948- """Define the exchange format for entitlement data.
949-
950- Writers of entitlement data should use the 'writerFactory' method to
951- obtain a writer object. Readers should use the 'readerFactory'. They
952- return a UnicodeDictWriter and UnicodeDictReader respectively.
953-
954- Any changes to the list of fieldnames or their order will require an
955- increment in the version value.
956- """
957-
958- file_header = "%s Entitlement exchange format version" % COMMENT
959- version = 1
960- version_re = re.compile(
961- "^%s (\d+)" % file_header)
962-
963- fieldnames = [
964- 'id', 'ext_id', 'person_name', 'entitlement_type', 'quota',
965- 'amount_used', 'date_starts', 'date_expires', 'date_created',
966- 'registrant_name', 'approved_by_name', 'state', 'whiteboard',
967- ]
968-
969- @staticmethod
970- def _preprocessData(in_file):
971- """Verify the version and remove comments."""
972- version_line = in_file.readline()
973- match = EntitlementExchange.version_re.search(version_line)
974- if not match:
975- raise InvalidFormat(
976- "The first line does not have valid version information.")
977- read_version = int(match.group(1))
978- if EntitlementExchange.version != read_version:
979- raise UnsupportedVersion(
980- "Version %d of the file format is not supported." %
981- read_version)
982- lines= [line for line in in_file.readlines()
983- if not line.lstrip().startswith(COMMENT)]
984- return "".join(lines)
985-
986- @staticmethod
987- def readerFactory(in_file):
988- """Return a properly provisioned reader.
989-
990- Assumes data in the file is UTF-8 encoded.
991- """
992-
993- filedata = EntitlementExchange._preprocessData(in_file)
994- return UnicodeDictReader(cStringIO.StringIO(filedata),
995- EntitlementExchange.fieldnames,
996- skipinitialspace=True,
997- quoting=csv.QUOTE_ALL)
998-
999- @staticmethod
1000- def writerFactory(filedescriptor):
1001- """Return a properly provisioned writer.
1002-
1003- Data in the file will be UTF-8 encoded.
1004- """
1005-
1006- filedescriptor.write(
1007- "%s %d\n" % (EntitlementExchange.file_header,
1008- EntitlementExchange.version))
1009- filedescriptor.write(
1010- "%s %s\n" % (COMMENT,
1011- COMMA.join(EntitlementExchange.fieldnames)))
1012- writer = UnicodeDictWriter(filedescriptor,
1013- EntitlementExchange.fieldnames,
1014- skipinitialspace=True,
1015- quoting=csv.QUOTE_ALL)
1016- return writer
1017-
1018-
1019-class EntitlementImporter:
1020- """Class for writing and updating entitlement data.
1021-
1022- Methods create_entitlements and update_entitlements are called with a list
1023- of dictionaries representing entitlement data.
1024- """
1025- def __init__(self, logger):
1026- self.logger = logger
1027-
1028- def _replacePersonName(self, entitlement, old_key, new_key,
1029- row_no, required=False):
1030- """Replace a person's name with a Person object in the entitlement.
1031-
1032- Raise RequiredValueMissing if the old_key is not found in the
1033- entitlement dictionary and required is True.
1034- Raise NotFoundError if no matching person can be found.
1035- """
1036- person_name = entitlement.get(old_key, '')
1037- del entitlement[old_key]
1038- if person_name == '':
1039- if required:
1040- raise RequiredValueMissing(
1041- "'person_name' not supplied in row %d." % row_no)
1042- else:
1043- return entitlement
1044- person = getUtility(IPersonSet).getByName(person_name)
1045- if person is None:
1046- self.logger.error(
1047- "[E%d] Person '%s' is not found." % (
1048- row_no, person_name))
1049- raise NotFoundError(
1050- "Person '%s' not supplied in row %d." % (
1051- person_name, row_no))
1052- entitlement[new_key] = person
1053- return entitlement
1054-
1055- def _normalizeEntitlement(
1056- self, entitlement, row_no, person_required=True):
1057- """Normalize a dictionary representing an entitlement.
1058-
1059- Convert names of people and teams to database objects and
1060- convert string representations of numerics to the correct type.
1061- Remove any keys in the dictionary that do not correspond to attributes
1062- on an Entitlement.
1063- """
1064- entitlement = self._replacePersonName(
1065- entitlement, 'person_name', 'person', row_no, person_required)
1066- entitlement = self._replacePersonName(
1067- entitlement, 'registrant_name', 'registrant', row_no)
1068- entitlement = self._replacePersonName(
1069- entitlement, 'approved_by_name', 'approved_by', row_no)
1070-
1071- # Remove the 'ext_id' since it is not part of the Launchpad data.
1072- del entitlement['ext_id']
1073-
1074- # Convert numeric data from string to int.
1075- for field in ['id', 'quota', 'entitlement_type', 'state', 'amount_used']:
1076- if entitlement[field]:
1077- entitlement[field] = int(entitlement[field])
1078-
1079- # Convert strings to dates.
1080- for field in ['date_starts', 'date_expires', 'date_created']:
1081- if entitlement[field]:
1082- date_string = entitlement[field]
1083- if len(date_string) == len('YYYY-mm-dd'):
1084- year, month, day, hour, minute, second = time.strptime(
1085- date_string, '%Y-%m-%d')[:6]
1086- elif len(date_string) == len('YYYY-mm-dd HH:MM:SS'):
1087- year, month, day, hour, minute, second = time.strptime(
1088- date_string, '%Y-%m-%d %H:%M:%S')[:6]
1089- else:
1090- raise AssertionError(
1091- 'Unknown date format: %s' % date_string)
1092- entitlement[field] = datetime.datetime(
1093- year, month, day, hour, minute, second,
1094- tzinfo=pytz.timezone('UTC'))
1095-
1096- # Convert the entitlement_type and state to the corresponding
1097- # database objects.
1098- if entitlement['entitlement_type']:
1099- entitlement_type = entitlement['entitlement_type']
1100- entitlement['entitlement_type'] = (
1101- EntitlementType.items.mapping[entitlement_type])
1102-
1103- if entitlement['state']:
1104- state = entitlement['state']
1105- entitlement['state'] = (
1106- EntitlementState.items.mapping[state])
1107-
1108- # Remove the entries from the dictionary that only have placeholder
1109- # data.
1110- for key, value in entitlement.items():
1111- if value == '':
1112- del entitlement[key]
1113- return entitlement
1114-
1115- def _checkRequired(self, entitlement, required, row_no):
1116- """Check to see that all required keys are in the entitlement."""
1117- for key in required:
1118- val = entitlement.get(key, '')
1119- # Test for None or ''. No boolean variable are expected.
1120- if val == '':
1121- self.logger.error(
1122- "[E%d] A required key is missing: %s." % (row_no, key))
1123- return False
1124- return True
1125-
1126- def createEntitlements(self, entitlements):
1127- """Create a new entitlement for each in the list.
1128-
1129- Return a list of sparsely populated dictionaries suitable for writing
1130- as a return CSV file.
1131- """
1132-
1133- required = ['ext_id', 'person_name', 'quota', 'entitlement_type',
1134- 'state']
1135- entitlement_set = getUtility(IEntitlementSet)
1136- new_entitlements = []
1137- for row_no, entitlement in enumerate(entitlements):
1138- if self._checkRequired(entitlement, required, row_no) is False:
1139- continue
1140- ext_id = entitlement.get('ext_id')
1141- try:
1142- normalized_entitlement = self._normalizeEntitlement(
1143- entitlement, row_no)
1144- except NotFoundError:
1145- continue
1146- except RequiredValueMissing:
1147- continue
1148-
1149- new_entitlement = entitlement_set.new(**normalized_entitlement)
1150-
1151- if new_entitlement is not None:
1152- # Add a dictionary with id and ext_id to the list of
1153- # new entitlements.
1154- new_entitlements.append(dict(id=str(new_entitlement.id),
1155- ext_id=ext_id))
1156- return new_entitlements
1157-
1158- def updateEntitlements(self, entitlements):
1159- """Update an existing entitlement.
1160-
1161- The entitlement must already exist. A list of dictionaries with the
1162- ids of the entitlments that were modified is returned.
1163- """
1164-
1165- modified = []
1166- required = ['id']
1167- for row_no, upd_entitlement in enumerate(entitlements):
1168- if not self._checkRequired(upd_entitlement, required, row_no):
1169- continue
1170- # The ext_id must be cached before the data is normalized.
1171- ext_id = upd_entitlement.get('ext_id')
1172-
1173- try:
1174- norm_entitlement = self._normalizeEntitlement(
1175- upd_entitlement, row_no, person_required=False)
1176- except NotFoundError:
1177- continue
1178- except RequiredValueMissing:
1179- continue
1180-
1181- lp_id = norm_entitlement.get('id')
1182- entitlement_set = getUtility(IEntitlementSet)
1183-
1184- existing = entitlement_set.get(lp_id)
1185- if existing is None:
1186- self.logger.error(
1187- "[E%d] Invalid entitlement id: %d" % (row_no,
1188- lp_id))
1189- continue
1190-
1191- succeeded = True
1192- for (key, val) in norm_entitlement.items():
1193- if key == 'id':
1194- pass
1195- elif key == 'person':
1196- self.logger.info(
1197- "[E%d] You may not change the person for the "
1198- "entitlement." % (row_no))
1199- succeeded = False
1200- break
1201- elif key == 'whiteboard':
1202- # Append the whiteboard value rather than replacing it.
1203- existing.whiteboard = "%s\n%s" % (existing.whiteboard,
1204- val)
1205- elif key in ['entitlement_type', 'quota', 'amount_used',
1206- 'date_starts', 'date_expires', 'date_created',
1207- 'registrant', 'approved_by', 'state']:
1208- setattr(existing, key, val)
1209- else:
1210- self.logger.error(
1211- "[E%d] Unrecognized key: %s." % (row_no, key))
1212- succeeded = False
1213- break
1214- if succeeded:
1215- modified.append(dict(id=str(lp_id)))
1216- return modified
1217
1218=== removed file 'lib/lp/registry/tests/test_entitlementimport.py'
1219--- lib/lp/registry/tests/test_entitlementimport.py 2012-01-01 02:58:52 +0000
1220+++ lib/lp/registry/tests/test_entitlementimport.py 1970-01-01 00:00:00 +0000
1221@@ -1,184 +0,0 @@
1222-# Copyright 2009 Canonical Ltd. This software is licensed under the
1223-# GNU Affero General Public License version 3 (see the file LICENSE).
1224-
1225-"""Test EntitlementExchange and EntitlementImporter."""
1226-
1227-__metaclass__ = type
1228-
1229-from cStringIO import StringIO
1230-import logging
1231-import unittest
1232-
1233-from zope.testing.loghandler import Handler
1234-
1235-from lp.registry.scripts.entitlement import (
1236- EntitlementExchange,
1237- EntitlementImporter,
1238- InvalidFormat,
1239- UnsupportedVersion,
1240- )
1241-from lp.testing.layers import LaunchpadZopelessLayer
1242-
1243-
1244-class EntitlementExchangeTestCase(unittest.TestCase):
1245- """Test EntitlementExchange methods."""
1246- layer = LaunchpadZopelessLayer
1247-
1248- def test_preprocessData(self):
1249- """The preprocessor verifies the header and removes comments."""
1250- # Wrong header
1251- data = ("# bad format data\n"
1252- "more data")
1253- in_file = StringIO(data)
1254- self.assertRaises(
1255- InvalidFormat, EntitlementExchange._preprocessData, in_file)
1256-
1257- # Invalid version
1258- data = ("# Entitlement exchange format version 0\n"
1259- "more data")
1260- in_file = StringIO(data)
1261- self.assertRaises(
1262- UnsupportedVersion, EntitlementExchange._preprocessData, in_file)
1263-
1264- # Only one line should remain after processing
1265- data = ("# Entitlement exchange format version 1\n"
1266- "# comment line\n"
1267- " # another comment\n"
1268- "'name', 'date' #valid line")
1269- in_file = StringIO(data)
1270- processed = EntitlementExchange._preprocessData(in_file)
1271- self.assertEqual(len(processed.split('\n')), 1)
1272-
1273-class EntitlementImporterTestCase(unittest.TestCase):
1274- """Test EntitlementImport methods."""
1275- layer = LaunchpadZopelessLayer
1276-
1277- def setUp(self):
1278- """Setup the test environment and retrieve useful instances."""
1279- self.log = logging.getLogger("test_entitlement")
1280- self.log.setLevel(logging.INFO)
1281- self.handler = Handler(self)
1282- self.handler.add(self.log.name)
1283-
1284- def tearDown(self):
1285- """Teardown the test environment."""
1286- self.layer.txn.commit()
1287- self.handler.close()
1288-
1289- def _getImporterAndReader(self, data):
1290- in_file = StringIO(data)
1291- importer = EntitlementImporter(self.log)
1292- return importer, EntitlementExchange.readerFactory(in_file)
1293-
1294- def _testCreate(self, data):
1295- importer, reader = self._getImporterAndReader(data)
1296- return importer.createEntitlements(reader)
1297-
1298- def _testUpdate(self, data):
1299- importer, reader = self._getImporterAndReader(data)
1300- return importer.updateEntitlements(reader)
1301-
1302- def test_manipulateEntitlement(self):
1303- """Test creating and updating entitlements."""
1304-
1305- def test_wrongVersion(self):
1306- """Wrong version."""
1307- data = r"""# Entitlement exchange format version 0"""
1308- in_file = StringIO(data)
1309- importer = EntitlementImporter(self.log)
1310- self.assertRaises(UnsupportedVersion,
1311- EntitlementExchange.readerFactory,
1312- in_file)
1313-
1314- def test_successfulInsert(self):
1315- """Successfully insert an entitlement."""
1316- data = ("# Entitlement exchange format version 1\n"
1317- ",A-100, lifeless, 10, 100, 0, 2007-06-14, , , "
1318- "keybuk, keybuk, 20,")
1319- results = self._testCreate(data)
1320- self.assertEqual(len(results), 1)
1321-
1322- def test_insertUsingNonExistentPerson(self):
1323- """Attempt to insert using a non-existent person."""
1324- data = ("# Entitlement exchange format version 1\n"
1325- ",A-100, persona_non_grata, 10, 100, 0, 2007-06-14, , , "
1326- "keybuk, keybuk, 20,")
1327- results = self._testCreate(data)
1328- self.handler.assertLogsMessage(
1329- "[E0] Person 'persona_non_grata' "
1330- "is not found.", level=logging.ERROR)
1331-
1332- def test_omitQuota(self):
1333- """Omit the quota and get an error."""
1334- data = ("# Entitlement exchange format version 1\n"
1335- ",A-100, lifeless, 10, , 0, 2007-06-14, , , "
1336- "keybuk, keybuk, 20,")
1337- results = self._testCreate(data)
1338- self.handler.assertLogsMessage(
1339- "[E0] A required key is missing: quota.",
1340- level=logging.ERROR)
1341-
1342- def test_omitPerson(self):
1343- """Omit the person and get an error."""
1344- data = ("# Entitlement exchange format version 1\n"
1345- ",A-100, , 10, 100, 0, 2007-06-14, , , "
1346- "keybuk, keybuk, 20,")
1347- results = self._testCreate(data)
1348- self.handler.assertLogsMessage(
1349- "[E0] A required key is missing: person_name.",
1350- level=logging.ERROR)
1351-
1352- def test_omitExtId(self):
1353- """Omit the ext_id and get an error."""
1354- data = ("# Entitlement exchange format version 1\n"
1355- ",, lifeless, 10, 100, 0, 2007-06-14, , , "
1356- "keybuk, keybuk, 20,")
1357- results = self._testCreate(data)
1358- self.handler.assertLogsMessage(
1359- "[E0] A required key is missing: ext_id.",
1360- level=logging.ERROR)
1361-
1362- def test_omitEntitlementType(self):
1363- """Omit the entitlement_type and get an error."""
1364- data = ("# Entitlement exchange format version 1\n"
1365- ",A-100, lifeless, , 100, 0, 2007-06-14, , , "
1366- "keybuk, keybuk, 20,")
1367- results = self._testCreate(data)
1368- self.handler.assertLogsMessage(
1369- "[E0] A required key is missing: entitlement_type.",
1370- level=logging.ERROR)
1371-
1372- def test_omitState(self):
1373- """Omit the state and get an error."""
1374- data = ("# Entitlement exchange format version 1\n"
1375- ",A-100, lifeless, 10, 100, 0, 2007-06-14, , , "
1376- "keybuk, keybuk, ,")
1377- results = self._testCreate(data)
1378- self.handler.assertLogsMessage(
1379- "[E0] A required key is missing: state.",
1380- level=logging.ERROR)
1381-
1382- def test_updateWithInvalidId(self):
1383- """Update using an invalid entitlement id."""
1384- data = ("# Entitlement exchange format version 1\n"
1385- "9999,A-100,kiko, ,1500, , , , , , , , ")
1386- results = self._testUpdate(data)
1387- self.handler.assertLogsMessage(
1388- "[E0] Invalid entitlement id: 9999",
1389- level=logging.ERROR)
1390-
1391- def test_updateChangingPerson(self):
1392- """Changing the person, which isn't allowed."""
1393- data = ("# Entitlement exchange format version 1\n"
1394- ",A-100, lifeless, 10, 100, 0, 2007-06-14, , , "
1395- "keybuk, keybuk, 20,")
1396- results = self._testCreate(data)
1397- self.assertEqual(len(results), 1)
1398- valid_id = results[0].get('id')
1399- data = ("# Entitlement exchange format version 1\n"
1400- " %s, A-100, kiko, , 1500, , , , , , , , " %
1401- valid_id)
1402- results = self._testUpdate(data)
1403- self.handler.assertLogsMessage(
1404- "[E0] You may not change the person for the entitlement.",
1405- level=logging.INFO)
1406
1407=== modified file 'lib/lp/security.py'
1408--- lib/lp/security.py 2012-02-08 05:07:20 +0000
1409+++ lib/lp/security.py 2012-02-14 00:28:18 +0000
1410@@ -103,7 +103,6 @@
1411 IDistroSeriesDifferenceEdit,
1412 )
1413 from lp.registry.interfaces.distroseriesparent import IDistroSeriesParent
1414-from lp.registry.interfaces.entitlement import IEntitlement
1415 from lp.registry.interfaces.gpg import IGPGKey
1416 from lp.registry.interfaces.irc import IIrcID
1417 from lp.registry.interfaces.location import IPersonLocation
1418@@ -2249,26 +2248,6 @@
1419 user.inTeam(self.obj.target_branch.reviewer))
1420
1421
1422-class ViewEntitlement(AuthorizationBase):
1423- """Permissions to view IEntitlement objects.
1424-
1425- Allow the owner of the entitlement, the entitlement registrant,
1426- or any member of the team or any admin to view the entitlement.
1427- """
1428- permission = 'launchpad.View'
1429- usedfor = IEntitlement
1430-
1431- def checkAuthenticated(self, user):
1432- """Is the user able to view an Entitlement attribute?
1433-
1434- Any team member can edit a branch subscription for their team.
1435- Launchpad Admins can also edit any branch subscription.
1436- """
1437- return (user.inTeam(self.obj.person) or
1438- user.inTeam(self.obj.registrant) or
1439- user.in_admin)
1440-
1441-
1442 class AdminDistroSeriesLanguagePacks(
1443 OnlyRosettaExpertsAndAdmins,
1444 EditDistroSeriesByReleaseManagerOrDistroOwnersOrAdmins):
1445
1446=== removed file 'scripts/entitlements-to-lp.py'
1447--- scripts/entitlements-to-lp.py 2012-01-01 03:13:08 +0000
1448+++ scripts/entitlements-to-lp.py 1970-01-01 00:00:00 +0000
1449@@ -1,83 +0,0 @@
1450-#!/usr/bin/python -S
1451-#
1452-# Copyright 2009 Canonical Ltd. This software is licensed under the
1453-# GNU Affero General Public License version 3 (see the file LICENSE).
1454-
1455-# pylint: disable-msg=W0403
1456-import _pythonpath
1457-
1458-import logging
1459-import sys
1460-
1461-from lp.registry.scripts.entitlement import (
1462- EntitlementExchange,
1463- EntitlementImporter,
1464- )
1465-from lp.services.scripts.base import LaunchpadScript
1466-
1467-
1468-class ImportEntitlementsScript(LaunchpadScript):
1469- """Script for to import entitlement data into Launchpad."""
1470-
1471- description = "Create or update entitlements."
1472- usage = ("usage: %s [-c|--create | -u|--update] file_name" %
1473- sys.argv[0])
1474-
1475- loglevel = logging.INFO
1476-
1477- def add_my_options(self):
1478- """See `LaunchpadScript`."""
1479- self.parser.add_option(
1480- '-c', '--create', action='store_const', const='create',
1481- help='Create new entitlements', dest='action')
1482- self.parser.add_option(
1483- '-u', '--update', action='store_const', const='update',
1484- help='Update existing entitlements', dest='action')
1485- self.parser.add_option(
1486- '-f', '--infile', action='store', default='-',
1487- help='Input file name ("-" for stdin)', dest='in_file_name')
1488- self.parser.add_option(
1489- '-o', '--outfile', action='store', default='-',
1490- help='Output file name ("-" for stdout)', dest='out_file_name')
1491-
1492- def main(self):
1493- """See `LaunchpadScript`."""
1494-
1495- action = self.options.action
1496-
1497- if self.options.in_file_name == '-':
1498- in_file = sys.stdin
1499- else:
1500- in_file = open(self.options.in_file_name, "rb")
1501-
1502- if self.options.out_file_name == '-':
1503- out_file = sys.stdout
1504- else:
1505- out_file = open(self.options.out_file_name, "wb")
1506-
1507- # get a reader and writer
1508- reader = EntitlementExchange.readerFactory(in_file)
1509- entitlement_writer = EntitlementImporter(self.logger)
1510- importer = EntitlementImporter(self.logger)
1511- if action == 'create':
1512- out_data = importer.createEntitlements(reader)
1513- elif action == 'update':
1514- out_data = importer.updateEntitlements(reader)
1515- elif action is None:
1516- self.logger.error("No action specified. Use either -c or -u.")
1517- return 1
1518- else:
1519- self.logger.error("Invalid action: %s\n" % action)
1520- return 1
1521-
1522- self.txn.commit()
1523-
1524- if out_data:
1525- writer = EntitlementExchange.writerFactory(out_file)
1526- writer.writerows(out_data)
1527- return 0
1528-
1529-if __name__ == '__main__':
1530- script = ImportEntitlementsScript(
1531- 'lp.services.scripts.entitlements')
1532- script.run()