Merge lp:~ricardokirkner/django-pgtools/support-django-1.6-to-1.10 into lp:django-pgtools

Proposed by Ricardo Kirkner
Status: Merged
Approved by: Ricardo Kirkner
Approved revision: 20
Merged at revision: 9
Proposed branch: lp:~ricardokirkner/django-pgtools/support-django-1.6-to-1.10
Merge into: lp:django-pgtools
Diff against target: 1099 lines (+471/-280)
17 files modified
.bzrignore (+4/-1)
Makefile (+51/-0)
Makefile.db (+44/-0)
manage.py (+10/-0)
pgtools/dbrole.py (+6/-14)
pgtools/dbuser.py (+5/-9)
pgtools/management/commands/createuser.py (+35/-10)
pgtools/management/commands/deleteuser.py (+20/-3)
pgtools/management/commands/grantuser.py (+19/-12)
pgtools/management/commands/listusers.py (+18/-9)
pgtools/tests.py (+128/-79)
pgtools/utils.py (+64/-10)
requirements.txt (+1/-1)
setup.cfg (+2/-0)
setup.py (+4/-53)
test_project/settings.py (+29/-0)
tox.ini (+31/-79)
To merge this branch: bzr merge lp:~ricardokirkner/django-pgtools/support-django-1.6-to-1.10
Reviewer Review Type Date Requested Status
Fabián Ezequiel Gallina (community) Approve
Review via email: mp+314917@code.launchpad.net

Commit message

support django 1.6 to 1.10

- dropped support for python 2.5 and 2.6
- dropped support for django < 1.6
- improved bootstrapping and testing of project

To post a comment you must log in.
18. By Ricardo Kirkner

swapped distutils for setuptools

19. By Ricardo Kirkner

support building universal wheels

20. By Ricardo Kirkner

better version of get_models that supports django 1.6 to 1.10

Revision history for this message
Fabián Ezequiel Gallina (fgallina) wrote :

LGTM

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2011-05-30 14:30:10 +0000
3+++ .bzrignore 2017-01-17 15:08:22 +0000
4@@ -1,1 +1,4 @@
5-.tox
6\ No newline at end of file
7+.tox
8+*.pyc
9+
10+env
11
12=== added file 'Makefile'
13--- Makefile 1970-01-01 00:00:00 +0000
14+++ Makefile 2017-01-17 15:08:22 +0000
15@@ -0,0 +1,51 @@
16+.PHONY: help
17+.DEFAULT_GOAL := help
18+
19+DJANGO_SETTINGS_MODULE ?= test_project.settings
20+ENV = $(CURDIR)/env
21+ENV_CREATED = $(shell test -e $(PYTHON) && echo "yes" || echo "no")
22+PIP = $(ENV)/bin/pip
23+PYTHON ?= $(ENV)/bin/python
24+export DJANGO_SETTINGS_MODULE
25+export PYTHONPATH
26+
27+help:
28+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
29+ | cut -d ':' -f 2,3 \
30+ | sort \
31+ | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
32+
33+# pre-process arguments
34+# if the first argument is "special" (manage, test)...
35+ifneq (,$(filter $(firstword $(MAKECMDGOALS)),manage test))
36+# use the rest as sub-arguments
37+ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
38+# ...and turn them into do-nothing targets
39+$(eval $(ARGS):;@:)
40+endif
41+
42+bootstrap: ARGS=-r requirements.txt
43+bootstrap: $(ENV) ## Bootstrap the project
44+ @$(PIP) install $(ARGS)
45+
46+$(ENV):
47+ @virtualenv --clear $(ENV)
48+
49+clean: ## Remove build artifacts
50+ @rm -rf $(WHEELS_DIR)
51+ @find -name '*.pyc' -delete
52+ @find -name '*.~*' -delete
53+
54+clean-env: ## Remove virtualenv
55+ @rm -rf $(ENV)
56+
57+manage: ## Run django management commands
58+ @$(PYTHON) manage.py $(ARGS)
59+
60+test: ## Run unit tests
61+ @$(PYTHON) manage.py test $(ARGS)
62+
63+# database related targets
64+ifeq (yes,$(ENV_CREATED))
65+include Makefile.db
66+endif
67
68=== added file 'Makefile.db'
69--- Makefile.db 1970-01-01 00:00:00 +0000
70+++ Makefile.db 2017-01-17 15:08:22 +0000
71@@ -0,0 +1,44 @@
72+PGNAME = $(shell DJANGO_SETTINGS_MODULE=$(DJANGO_SETTINGS_MODULE) PYTHONPATH=$(PYTHONPATH) $(PYTHON) -c "from django.conf import settings; print settings.DATABASES['default']['NAME']")
73+PGHOST = $(shell DJANGO_SETTINGS_MODULE=$(DJANGO_SETTINGS_MODULE) PYTHONPATH=$(PYTHONPATH) $(PYTHON) -c "from django.conf import settings; print settings.DATABASES['default']['HOST']")
74+PGUSER = $(shell DJANGO_SETTINGS_MODULE=$(DJANGO_SETTINGS_MODULE) PYTHONPATH=$(PYTHONPATH) $(PYTHON) -c "from django.conf import settings; print settings.DATABASES['default']['USER']")
75+DATA_DIR = $(PGHOST)/data
76+LOG_FILE = $(PGHOST)/postgresql.log
77+CONF_FILE = $(PGHOST)/postgresql.conf
78+PGBIN = /usr/lib/postgresql/9.3/bin
79+PGCTL = $(PGBIN)/pg_ctl
80+PGINIT = $(PGBIN)/initdb
81+PGSOCKET = $(PGHOST)/.s.PGSQL.5432
82+
83+# The weird setup-db/pgsocket dependency is so that other rules can depend
84+# on setup-db which is nice, but the behavior is that if the socket exists,
85+# the database won't be started again.
86+setup-db: $(PGSOCKET)
87+
88+$(PGSOCKET):
89+ # If the socket didn't exist, means the db server was stopped.
90+ # In this case it should be safe to blast the directory as developers
91+ # don't usually stop the db server if they care about the (transient,
92+ # anyway) data.
93+ rm -rf $(DATA_DIR)
94+ mkdir -p $(DATA_DIR)
95+ $(PGINIT) -A trust -D $(DATA_DIR)
96+ echo "fsync = off" > $(CONF_FILE)
97+ echo "standard_conforming_strings = off" >> $(CONF_FILE)
98+ echo "escape_string_warning = off" >> $(CONF_FILE)
99+ $(PGCTL) start -w -D $(DATA_DIR) -l $(LOG_FILE) -o "-F -k $(PGHOST) -h ''"
100+ PGHOST=$(PGHOST) createdb $(PGNAME)
101+ PGHOST=$(PGHOST) createuser --superuser --createdb $(PGUSER)
102+
103+start-db: ## Start database server
104+ $(MAKE) setup-db
105+ $(MAKE) manage migrate
106+
107+stop-db: ## Stop database server
108+ PGHOST=$(PGHOST) dropdb $(PGNAME)
109+ $(PGCTL) stop -w -D $(DATA_DIR) -m smart
110+ rm -rf $(PGHOST)
111+
112+reset-db: ## Reset database state
113+ PGHOST=$(PGHOST) dropdb $(PGNAME)
114+ PGHOST=$(PGHOST) createdb $(PGNAME)
115+ $(MAKE) manage migrate
116
117=== added file 'manage.py'
118--- manage.py 1970-01-01 00:00:00 +0000
119+++ manage.py 2017-01-17 15:08:22 +0000
120@@ -0,0 +1,10 @@
121+#!/usr/bin/env python
122+import os
123+import sys
124+
125+if __name__ == "__main__":
126+ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "test_project.settings")
127+
128+ from django.core.management import execute_from_command_line
129+
130+ execute_from_command_line(sys.argv)
131
132=== modified file 'pgtools/dbrole.py'
133--- pgtools/dbrole.py 2011-05-30 14:30:10 +0000
134+++ pgtools/dbrole.py 2017-01-17 15:08:22 +0000
135@@ -24,20 +24,16 @@
136
137 cursor = self.connection.cursor()
138 cursor.execute("CREATE ROLE %s" % rolename)
139- if commit:
140- self.transaction.commit_unless_managed()
141- if not self.exists(rolename):
142- raise CommandError("Role couldn't be created: %s" % rolename)
143+ if commit and not self.exists(rolename):
144+ raise CommandError("Role couldn't be created: %s" % rolename)
145 elif not exists:
146 raise CommandError("Role doesn't exist: %s" % rolename)
147
148 def delete(self, commit=True):
149 cursor = self.connection.cursor()
150 cursor.execute("DROP ROLE %s" % self.rolename)
151- if commit:
152- self.transaction.commit_unless_managed()
153- if self.exists(self.rolename):
154- raise CommandError("Role cannot be deleted: %s" % self.rolename)
155+ if commit and self.exists(self.rolename):
156+ raise CommandError("Role cannot be deleted: %s" % self.rolename)
157
158 @classmethod
159 def exists(cls, rolename, db_connection=None):
160@@ -65,13 +61,9 @@
161 def grant(self, user, commit=True):
162 cursor = self.connection.cursor()
163 cursor.execute("GRANT %s TO %s" % (self.rolename, user.username))
164- if commit:
165- self.transaction.commit_unless_managed()
166
167 def revoke(self, user, commit=True):
168 cursor = self.connection.cursor()
169 cursor.execute("REVOKE %s FROM %s" % (self.rolename, user.username))
170- if commit:
171- self.transaction.commit_unless_managed()
172- if user.has_role(self):
173- raise CommandError("Role cannot be revoked: %s" % self.rolename)
174+ if commit and user.has_role(self):
175+ raise CommandError("Role cannot be revoked: %s" % self.rolename)
176
177=== modified file 'pgtools/dbuser.py'
178--- pgtools/dbuser.py 2011-05-30 14:30:10 +0000
179+++ pgtools/dbuser.py 2017-01-17 15:08:22 +0000
180@@ -27,21 +27,17 @@
181 else:
182 cursor.execute("CREATE USER %s PASSWORD %%s" % username,
183 (password,))
184- if commit:
185- self.transaction.commit_unless_managed()
186- if not self.exists(username):
187- raise CommandError(
188- "User couldn't be created: %s" % username)
189+ if commit and not self.exists(username):
190+ raise CommandError(
191+ "User couldn't be created: %s" % username)
192 elif not exists:
193 raise CommandError("User doesn't exist: %s" % username)
194
195 def delete(self, commit=True):
196 cursor = self.connection.cursor()
197 cursor.execute("DROP USER %s" % self.username)
198- if commit:
199- self.transaction.commit_unless_managed()
200- if self.exists(self.username):
201- raise CommandError("User cannot be deleted: %s" % self.username)
202+ if commit and self.exists(self.username):
203+ raise CommandError("User cannot be deleted: %s" % self.username)
204
205 @classmethod
206 def exists(cls, username, db_connection=None):
207
208=== modified file 'pgtools/management/commands/createuser.py'
209--- pgtools/management/commands/createuser.py 2011-05-30 14:30:10 +0000
210+++ pgtools/management/commands/createuser.py 2017-01-17 15:08:22 +0000
211@@ -11,19 +11,44 @@
212 from pgtools.dbrole import DatabaseRole
213 from pgtools.dbuser import DatabaseUser
214 from pgtools.decorators import graceful_db_errors
215-from pgtools.utils import check_database_engine, parse_username_and_rolename
216+from pgtools.utils import (
217+ DJANGO_VERSION,
218+ check_database_engine,
219+ parse_username_and_rolename,
220+)
221
222
223 class Command(BaseCommand):
224- option_list = BaseCommand.option_list + (
225- make_option('-p', '--password', default=None, dest='password',
226- help='Password of the user to be created.'),
227- make_option('-P', '--no-password', default=False, dest='no_password',
228- action='store_true',
229- help='If given, then no password is set up for the user.'),
230- )
231 help = 'Create a new database user based on an existing role.'
232- args = 'username [rolename]'
233+ OPTIONS = [
234+ (('-p', '--password'),
235+ dict(default=None, dest='password',
236+ help='Password of the user to be created.')),
237+ (('-P', '--no-password'),
238+ dict(default=False, dest='no_password',
239+ help='If given, then no password is set up for the user.')),
240+ ]
241+
242+ def __init__(self, *args, **kwargs):
243+ super(Command, self).__init__(*args, **kwargs)
244+ if DJANGO_VERSION < (1, 8):
245+ self.args = "username [rolename]"
246+ self.option_list = BaseCommand.option_list + tuple(
247+ make_option(*a, **kw) for (a, kw) in self.OPTIONS
248+ )
249+
250+ def add_arguments(self, parser):
251+ # positional arguments
252+ parser.add_argument(
253+ 'username', metavar='username', nargs=1,
254+ help='Username for the new user.')
255+ parser.add_argument(
256+ 'rolename', metavar='rolename', nargs='?',
257+ help='Role to use for creating the new user.')
258+
259+ # named arguments
260+ for (args, kwargs) in self.OPTIONS:
261+ parser.add_argument(*args, **kwargs)
262
263 def _ask_password(self):
264 password = None
265@@ -50,7 +75,7 @@
266 @graceful_db_errors
267 def handle(self, *args, **options):
268 check_database_engine()
269- username, rolename = parse_username_and_rolename(args)
270+ username, rolename = parse_username_and_rolename(*args, **options)
271
272 password = options.get('password')
273 no_password = options.get('no_password')
274
275=== modified file 'pgtools/management/commands/deleteuser.py'
276--- pgtools/management/commands/deleteuser.py 2011-05-30 14:30:10 +0000
277+++ pgtools/management/commands/deleteuser.py 2017-01-17 15:08:22 +0000
278@@ -7,17 +7,34 @@
279 from pgtools.dbrole import DatabaseRole
280 from pgtools.dbuser import DatabaseUser
281 from pgtools.decorators import graceful_db_errors
282-from pgtools.utils import check_database_engine, parse_username_and_rolename
283+from pgtools.utils import (
284+ DJANGO_VERSION,
285+ check_database_engine,
286+ parse_username_and_rolename,
287+)
288
289
290 class Command(BaseCommand):
291 help = 'Delete an existing database user based on given role.'
292- args = 'username [rolename]'
293+
294+ def __init__(self, *args, **kwargs):
295+ super(Command, self).__init__(*args, **kwargs)
296+ if DJANGO_VERSION < (1, 8):
297+ self.args = "username [rolename]"
298+
299+ def add_arguments(self, parser):
300+ # positional arguments
301+ parser.add_argument(
302+ 'username', metavar='username', nargs=1,
303+ help='Username for the new user.')
304+ parser.add_argument(
305+ 'rolename', metavar='rolename', nargs='?',
306+ help='Role to use for creating the new user.')
307
308 @graceful_db_errors
309 def handle(self, *args, **options):
310 check_database_engine()
311- username, rolename = parse_username_and_rolename(args)
312+ username, rolename = parse_username_and_rolename(*args, **options)
313
314 role = DatabaseRole(rolename)
315 user = DatabaseUser(username, create=False)
316
317=== modified file 'pgtools/management/commands/grantuser.py'
318--- pgtools/management/commands/grantuser.py 2013-02-21 21:16:14 +0000
319+++ pgtools/management/commands/grantuser.py 2017-01-17 15:08:22 +0000
320@@ -2,12 +2,16 @@
321 # Copyright 2011 Canonical Ltd. This software is licensed under the
322 # GNU Affero General Public License version 3 (see the file LICENSE).
323
324-from django.core.management.base import BaseCommand,CommandError
325+from django.core.management.base import BaseCommand
326 from django.db import connection
327-from django.db.models.loading import get_models
328
329 from pgtools.decorators import graceful_db_errors
330-from pgtools.utils import check_database_engine
331+from pgtools.utils import (
332+ DJANGO_VERSION,
333+ check_database_engine,
334+ get_models,
335+ parse_username,
336+)
337
338
339 GRANT_SQL = "GRANT ALL ON %s TO %s;"
340@@ -16,20 +20,23 @@
341
342 class Command(BaseCommand):
343 help = 'Grant access to user to all models.'
344- args = 'username'
345+
346+ def __init__(self, *args, **kwargs):
347+ super(Command, self).__init__(*args, **kwargs)
348+ if DJANGO_VERSION < (1, 8):
349+ self.args = "username"
350+
351+ def add_arguments(self, parser):
352+ # positional arguments
353+ parser.add_argument(
354+ 'username', metavar='username', nargs=1,
355+ help='Username for the new user.')
356
357 @graceful_db_errors
358 def handle(self, *args, **options):
359 check_database_engine()
360
361- arg_count = len(args)
362- if arg_count > 1:
363- raise CommandError('Too many arguments provided.')
364- elif arg_count < 1:
365- raise CommandError(
366- 'Please provide a username.')
367-
368- self.username = args[0]
369+ self.username = parse_username(*args, **options)
370 self.cursor = connection.cursor()
371
372 for model in get_models():
373
374=== modified file 'pgtools/management/commands/listusers.py'
375--- pgtools/management/commands/listusers.py 2011-05-30 14:30:10 +0000
376+++ pgtools/management/commands/listusers.py 2017-01-17 15:08:22 +0000
377@@ -2,26 +2,35 @@
378 # Copyright 2011 Canonical Ltd. This software is licensed under the
379 # GNU Affero General Public License version 3 (see the file LICENSE).
380
381-from django.core.management.base import BaseCommand, CommandError
382+from django.core.management.base import BaseCommand
383
384 from pgtools.dbrole import DatabaseRole
385 from pgtools.decorators import graceful_db_errors
386-from pgtools.utils import check_database_engine, get_rolename_from_settings
387+from pgtools.utils import (
388+ DJANGO_VERSION,
389+ check_database_engine,
390+ parse_rolename,
391+)
392
393
394 class Command(BaseCommand):
395 help = 'List database users with access to given role.'
396- args = 'rolename'
397+
398+ def __init__(self, *args, **kwargs):
399+ super(Command, self).__init__(*args, **kwargs)
400+ if DJANGO_VERSION < (1, 8):
401+ self.args = "[rolename]"
402+
403+ def add_arguments(self, parser):
404+ # positional arguments
405+ parser.add_argument(
406+ 'rolename', metavar='rolename', nargs='?',
407+ help='Role to use for creating the new user.')
408
409 @graceful_db_errors
410 def handle(self, *args, **options):
411 check_database_engine()
412- if len(args) < 1:
413- rolename = get_rolename_from_settings()
414- elif len(args) == 1:
415- rolename = args[0]
416- else:
417- raise CommandError('Too many arguments.')
418+ rolename = parse_rolename(*args, **options)
419
420 role = DatabaseRole(rolename)
421 roles = role.get_users()
422
423=== modified file 'pgtools/tests.py'
424--- pgtools/tests.py 2013-07-11 20:09:14 +0000
425+++ pgtools/tests.py 2017-01-17 15:08:22 +0000
426@@ -7,30 +7,32 @@
427 import sys
428
429 from cStringIO import StringIO
430-from contextlib import contextmanager
431-
432-from psycopg2 import ProgrammingError
433-
434-from mock import Mock, patch
435-
436-import django
437+from unittest import skipIf
438+
439 from django.conf import settings
440 from django.core.management import call_command
441 from django.core.management.base import CommandError
442 from django.db import connection, models
443-from django.db.backends import postgresql_psycopg2
444-from django.db.models.loading import get_models
445+from django.db.backends.postgresql_psycopg2.base import (
446+ DatabaseWrapper as PGDatabaseWrapper,
447+)
448 from django.db.utils import load_backend
449-from django.test import TestCase
450-
451+from django.test import TransactionTestCase
452+from django.test.utils import override_settings
453+from mock import Mock, patch
454+from psycopg2 import ProgrammingError
455
456 from pgtools.dbrole import DatabaseRole
457 from pgtools.dbuser import DatabaseUser
458 from pgtools.decorators import graceful_db_errors
459 from pgtools.management.commands import grantuser
460 from pgtools.utils import (
461- check_database_engine, get_rolename_from_settings,
462- parse_username_and_rolename
463+ DJANGO_VERSION,
464+ check_database_engine,
465+ get_rolename_from_settings,
466+ parse_rolename,
467+ parse_username,
468+ parse_username_and_rolename,
469 )
470
471 EMPTY_ROLE_NAME = 'test_empty_role'
472@@ -39,35 +41,7 @@
473 TEST_USER_NAME2 = 'test_user2'
474
475
476-# Original snippet from http://djangosnippets.org/snippets/2156/
477-class SettingDoesNotExist:
478- pass
479-
480-
481-def switch_settings(**kwargs):
482- """Helper method that updates settings and returns old settings."""
483- old_settings = {}
484- for key, new_value in kwargs.items():
485- old_value = getattr(settings, key, SettingDoesNotExist)
486- old_settings[key] = old_value
487-
488- if new_value is SettingDoesNotExist:
489- delattr(settings, key)
490- else:
491- setattr(settings, key, new_value)
492-
493- return old_settings
494-
495-
496-@contextmanager
497-def patch_settings(**kwargs):
498- old_settings = switch_settings(**kwargs)
499- yield
500- switch_settings(**old_settings)
501-# end snippet
502-
503-
504-class CommandTestCase(TestCase):
505+class CommandBaseTestCase(TransactionTestCase):
506
507 def setUp(self):
508 test_settings = {
509@@ -80,7 +54,9 @@
510 'USER': 'postgres',
511 'PASSWORD': '',
512 })
513- self.old_settings = switch_settings(**test_settings)
514+ overrides = self.settings(**test_settings)
515+ overrides.enable()
516+ self.addCleanup(overrides.disable)
517
518 self._refresh_connection()
519 self._capture_stdout_stderr()
520@@ -141,7 +117,6 @@
521 def tearDown(self):
522 self._delete_roles_and_users()
523 self._release_stdout_stderr()
524- switch_settings(**self.old_settings)
525
526 def _delete_roles_and_users(self):
527 # clean up everything
528@@ -170,7 +145,7 @@
529 sys.stderr = sys.__stderr__
530
531
532-class CommandTest(CommandTestCase):
533+class CommandTestCase(CommandBaseTestCase):
534
535 def test_command_createuser_no_args(self):
536 # No argument
537@@ -307,19 +282,17 @@
538 except (CommandError, SystemExit):
539 pass
540 # No argument, but existing role predefined
541- old_PG_BASE_ROLE = getattr(settings, 'PG_BASE_ROLE', None)
542- settings.PG_BASE_ROLE = MASTER_ROLE_NAME
543- call_command('listusers')
544+ with self.settings(PG_BASE_ROLE=MASTER_ROLE_NAME):
545+ call_command('listusers')
546 # No argument, non-existing role predefined
547- settings.PG_BASE_ROLE = 'somefunkynonexistingrole'
548- try:
549- call_command('listusers')
550- self.fail(
551- 'Calling listusers without arguments and non-existing role '
552- 'predefined should fail.')
553- except (CommandError, SystemExit):
554- pass
555- settings.PG_BASE_ROLE = old_PG_BASE_ROLE
556+ with self.settings(PG_BASE_ROLE='somefunkynonexistingrole'):
557+ try:
558+ call_command('listusers')
559+ self.fail(
560+ 'Calling listusers without arguments and non-existing role '
561+ 'predefined should fail.')
562+ except (CommandError, SystemExit):
563+ pass
564 # Too many arguments
565 try:
566 call_command('listusers', 'foo', 'bar')
567@@ -447,9 +420,8 @@
568 "User cannot be deleted: %s" % TEST_USER_NAME1)
569
570 def get_backends(self):
571- django_version = django.VERSION[:2]
572 backends = ['django.db.backends.postgresql_psycopg2']
573- if django_version < (1,4):
574+ if DJANGO_VERSION < (1,4):
575 backends.insert(0, 'postgresql_psycopg2')
576 return backends
577
578@@ -484,7 +456,7 @@
579 @patch('pgtools.utils.settings')
580 def test_utils_check_database_engine_subclass_13_style(self,
581 mock_settings):
582- class CustomDatabaseWrapper(postgresql_psycopg2.base.DatabaseWrapper):
583+ class CustomDatabaseWrapper(PGDatabaseWrapper):
584 pass
585
586 dbconfig = {'default': {'NAME': 'mydatabase',
587@@ -505,11 +477,13 @@
588 # Test with default (nothing set)
589 self.assertRaises(CommandError, get_rolename_from_settings)
590 # Test with the master role
591- old_PG_BASE_ROLE = getattr(settings, 'PG_BASE_ROLE', None)
592- settings.PG_BASE_ROLE = MASTER_ROLE_NAME
593- master_role_name = get_rolename_from_settings()
594- self.assertEqual(master_role_name, MASTER_ROLE_NAME)
595- settings.PG_BASE_ROLE = old_PG_BASE_ROLE
596+ with self.settings(PG_BASE_ROLE=MASTER_ROLE_NAME):
597+ master_role_name = get_rolename_from_settings()
598+ self.assertEqual(master_role_name, MASTER_ROLE_NAME)
599+
600+
601+@skipIf(DJANGO_VERSION >= (1, 8), 'Django 1.8 and later uses argparse')
602+class Django17ParserTestCase(CommandBaseTestCase):
603
604 def test_parse_username_and_rolename_no_args(self):
605 # Test with no args
606@@ -519,31 +493,93 @@
607 def test_parse_username_and_rolename_two_args(self):
608 # Test with two arguments
609 args = ['username', 'rolename']
610- username, rolename = parse_username_and_rolename(args)
611+ username, rolename = parse_username_and_rolename(*args)
612 self.assertEqual(username, 'username')
613 self.assertEqual(rolename, 'rolename')
614
615 def test_parse_username_and_rolename_username_no_role(self):
616 # One argument, no predefined base role
617 args = ['username']
618- self.assertRaises(CommandError, parse_username_and_rolename, args)
619+ self.assertRaises(CommandError, parse_username_and_rolename, *args)
620
621+ @override_settings(PG_BASE_ROLE=MASTER_ROLE_NAME)
622 def test_parse_username_and_rolename_username_with_role(self):
623 # One argument, predefined base role
624- old_PG_BASE_ROLE = getattr(settings, 'PG_BASE_ROLE', None)
625- settings.PG_BASE_ROLE = MASTER_ROLE_NAME
626 args = ['username', 'rolename']
627- username, rolename = parse_username_and_rolename(args)
628+ username, rolename = parse_username_and_rolename(*args)
629 self.assertEqual(username, 'username')
630 self.assertEqual(rolename, 'rolename')
631- settings.PG_BASE_ROLE = old_PG_BASE_ROLE
632
633 def test_parse_username_and_rolename_too_many_args(self):
634 args = ['username', 'rolename', 'foo']
635- self.assertRaises(CommandError, parse_username_and_rolename, args)
636-
637-
638-class GrantUserCommandTestCase(CommandTestCase):
639+ self.assertRaises(CommandError, parse_username_and_rolename, *args)
640+
641+ def test_parse_username_no_args(self):
642+ self.assertRaises(CommandError, parse_username)
643+
644+ def test_parse_username_too_many_args(self):
645+ args = ['username', 'rolename']
646+ self.assertRaises(CommandError, parse_username, *args)
647+
648+ def test_parse_username_from_args(self):
649+ args = ['username']
650+ username = parse_username(*args)
651+ self.assertEqual(username, 'username')
652+
653+ @override_settings(PG_BASE_ROLE=MASTER_ROLE_NAME)
654+ def test_parse_rolename_no_args(self):
655+ rolename = parse_rolename()
656+ self.assertEqual(rolename, MASTER_ROLE_NAME)
657+
658+ def test_parse_rolename_too_many_args(self):
659+ args = ['username', 'rolename']
660+ self.assertRaises(CommandError, parse_rolename, *args)
661+
662+ def test_parse_rolename_from_args(self):
663+ args = ['rolename']
664+ rolename = parse_rolename(*args)
665+ self.assertEqual(rolename, 'rolename')
666+
667+
668+
669+@skipIf(DJANGO_VERSION < (1, 8), 'Django 1.7 and earlier uses optparse')
670+class Django18ParserTestCase(CommandBaseTestCase):
671+
672+ def test_parse_username_and_rolename_from_options(self):
673+ args = ['foo', 'bar']
674+ options = {'username': 'username', 'rolename': 'rolename'}
675+ username, rolename = parse_username_and_rolename(*args, **options)
676+ self.assertEqual(username, 'username')
677+ self.assertEqual(rolename, 'rolename')
678+
679+ @override_settings(PG_BASE_ROLE=MASTER_ROLE_NAME)
680+ def test_parse_username_and_rolename_from_options_with_default_role(self):
681+ args = ['foo', 'bar']
682+ options = {'username': 'username'}
683+ username, rolename = parse_username_and_rolename(*args, **options)
684+ self.assertEqual(username, 'username')
685+ self.assertEqual(rolename, MASTER_ROLE_NAME)
686+
687+ def test_parse_username_from_options(self):
688+ args = ['foo']
689+ options = {'username': 'username'}
690+ username = parse_username(*args, **options)
691+ self.assertEqual(username, 'username')
692+
693+ def test_parse_rolename_from_options(self):
694+ args = ['foo']
695+ options = {'rolename': 'rolename'}
696+ rolename = parse_rolename(*args, **options)
697+ self.assertEqual(rolename, 'rolename')
698+
699+
700+@skipIf(DJANGO_VERSION < (1, 7), "django.apps not available in Django 1.6")
701+class GrantUserCommandTestCase(CommandBaseTestCase):
702+
703+ def get_models(self, app_label):
704+ from django.apps import apps
705+ return apps.get_app_config(app_label).get_models()
706+
707 def test_command_grantuser_no_args(self):
708 try:
709 call_command('grantuser')
710@@ -553,8 +589,7 @@
711
712 @patch('pgtools.management.commands.grantuser.get_models')
713 def test_command_grantuser(self, mock_get_models):
714- from django.contrib.admin import models as admin_models
715- mock_get_models.return_value = get_models(admin_models)
716+ mock_get_models.return_value = self.get_models('admin')
717 command = grantuser.Command
718 with patch.object(command, '_grant_one') as mock_grant_one:
719 # force the cache to reload the apps
720@@ -600,8 +635,7 @@
721 @patch('pgtools.management.commands.grantuser.connection')
722 def test__grant_many_to_many(self, mock_connection, mock_get_models):
723 mock_connection.cursor.return_value.fetchone.return_value = ['foo']
724- from django.contrib.auth import models as auth_models
725- mock_get_models.return_value = get_models(auth_models)
726+ mock_get_models.return_value = self.get_models('auth')
727
728 call_command('grantuser', 'payments')
729
730@@ -612,3 +646,18 @@
731 'GRANT ALL ON foo TO payments;'
732 ]:
733 self.assertTrue(expected in calls)
734+
735+
736+@skipIf(DJANGO_VERSION > (1, 6), "Django 1.7 and later use django.apps for getting models")
737+class Django16GrantUserCommandTestCase(GrantUserCommandTestCase):
738+
739+ def get_models(self, app_label):
740+ import django.contrib.admin.models
741+ import django.contrib.auth.models
742+ from django.db.models.loading import get_models
743+
744+ if app_label == 'admin':
745+ models = django.contrib.admin.models
746+ elif app_label == 'auth':
747+ models = django.contrib.auth.models
748+ return get_models(models)
749
750=== modified file 'pgtools/utils.py'
751--- pgtools/utils.py 2013-07-11 19:14:08 +0000
752+++ pgtools/utils.py 2017-01-17 15:08:22 +0000
753@@ -2,12 +2,16 @@
754 # Copyright 2011 Canonical Ltd. This software is licensed under the
755 # GNU Affero General Public License version 3 (see the file LICENSE).
756
757+import django
758 from django.conf import settings
759 from django.core.management.base import CommandError
760 from django.db import DEFAULT_DB_ALIAS
761 from django.db.utils import load_backend
762
763
764+DJANGO_VERSION = django.VERSION[:2]
765+
766+
767 def check_database_engine(alias=None):
768 if alias is None:
769 alias = DEFAULT_DB_ALIAS
770@@ -29,20 +33,70 @@
771 return rolename
772
773
774-def parse_username_and_rolename(args):
775+def parse_username_and_rolename(*args, **options):
776 username = None
777 rolename = None
778- arg_count = len(args)
779+ if DJANGO_VERSION < (1, 8):
780+ arg_count = len(args)
781
782- if arg_count == 2:
783- username, rolename = args
784+ if arg_count == 2:
785+ username, rolename = args
786+ else:
787+ if arg_count > 2:
788+ raise CommandError('Too many arguments provided.')
789+ elif arg_count < 1:
790+ raise CommandError(
791+ 'Please provide both a username and a role name.')
792+ username = args[0]
793+ rolename = get_rolename_from_settings()
794 else:
795- if arg_count > 2:
796+ username = options.get('username', None)
797+ rolename = options.get('rolename', None)
798+
799+ if isinstance(username, list) and len(username) > 0:
800+ username = username[0]
801+
802+ if rolename is None:
803+ rolename = get_rolename_from_settings()
804+
805+ return (username, rolename)
806+
807+
808+def parse_username(*args, **options):
809+ if DJANGO_VERSION < (1, 8):
810+ arg_count = len(args)
811+ if arg_count > 1:
812 raise CommandError('Too many arguments provided.')
813 elif arg_count < 1:
814- raise CommandError(
815- 'Please provide both a username and a role name.')
816+ raise CommandError('Please provide a username.')
817 username = args[0]
818- rolename = get_rolename_from_settings()
819-
820- return (username, rolename)
821+ else:
822+ username = options['username']
823+ if isinstance(username, list) and len(username) > 0:
824+ username = username[0]
825+ return username
826+
827+
828+def parse_rolename(*args, **options):
829+ if DJANGO_VERSION < (1, 8):
830+ if len(args) < 1:
831+ rolename = get_rolename_from_settings()
832+ elif len(args) == 1:
833+ rolename = args[0]
834+ else:
835+ raise CommandError('Too many arguments.')
836+ else:
837+ rolename = options.get('rolename', None)
838+ if rolename is None:
839+ rolename = get_rolename_from_settings()
840+ return rolename
841+
842+
843+def get_models():
844+ try:
845+ from django.apps import apps
846+ models = apps.get_models()
847+ except ImportError:
848+ from django.db.models import loading
849+ models = loading.get_models()
850+ return models
851
852=== modified file 'requirements.txt'
853--- requirements.txt 2011-05-30 14:30:10 +0000
854+++ requirements.txt 2017-01-17 15:08:22 +0000
855@@ -1,3 +1,3 @@
856 mock
857-psycopg2
858+psycopg2==2.4.5
859 django
860
861=== added file 'setup.cfg'
862--- setup.cfg 1970-01-01 00:00:00 +0000
863+++ setup.cfg 2017-01-17 15:08:22 +0000
864@@ -0,0 +1,2 @@
865+[wheel]
866+universal = 1
867
868=== modified file 'setup.py'
869--- setup.py 2013-07-11 19:14:08 +0000
870+++ setup.py 2017-01-17 15:08:22 +0000
871@@ -1,68 +1,19 @@
872 #!/usr/bin/env python
873 # -*- encoding: utf-8 -*-
874-# Copyright 2011 Canonical Ltd. This software is licensed under the
875+# Copyright 2011-2017 Canonical Ltd. This software is licensed under the
876 # GNU Affero General Public License version 3 (see the file LICENSE).
877
878-from distutils.core import setup, Command
879-
880-
881-class TestCommand(Command):
882- user_options = []
883-
884- def initialize_options(self):
885- pass
886-
887- def finalize_options(self):
888- pass
889-
890- def run(self):
891- """Run the project tests."""
892-
893- import django
894- from django.conf import settings
895- from django.core.management import call_command
896-
897- config = {
898- 'SITE_ID': 1,
899- 'ROOT_URLCONF': '',
900- 'INSTALLED_APPS': [
901- 'django.contrib.admin',
902- 'django.contrib.auth',
903- 'django.contrib.contenttypes',
904- 'django.contrib.sessions',
905- 'pgtools',
906- ],
907- 'DATABASES': {
908- 'default': {
909- 'NAME': 'pgtools',
910- 'ENGINE': 'django.db.backends.postgresql_psycopg2',
911- 'USER': 'postgres',
912- }
913- }
914- }
915-
916- django_version = django.VERSION[:2]
917- if django_version < (1,4):
918- config.update({
919- 'DATABASE_ENGINE': 'django.db.backends.postgresql_psycopg2',
920- 'DATABASE_NAME': 'pgtools',
921- 'DATABASE_USER': 'postgres',
922- })
923-
924- settings.configure(**config)
925-
926- call_command('test', interactive=False)
927+from setuptools import setup
928
929
930 setup(
931 name='django-pgtools',
932- version='0.2',
933+ version='0.3',
934 description="Set of Django management commands for handling various aspects of managing PostgreSQL database.",
935 url='https://launchpad.net/django-pgtools',
936 author='Canonical ISD',
937 author_email='canonical-isd@lists.launchpad.net',
938 packages=['pgtools', 'pgtools/management', 'pgtools/management/commands'],
939 license='AGPLv3',
940- cmdclass = {'test': TestCommand},
941- requires=['django (>=1.2)']
942+ requires=['django (>=1.6)']
943 )
944
945=== added directory 'test_project'
946=== added file 'test_project/__init__.py'
947=== added file 'test_project/settings.py'
948--- test_project/settings.py 1970-01-01 00:00:00 +0000
949+++ test_project/settings.py 2017-01-17 15:08:22 +0000
950@@ -0,0 +1,29 @@
951+DATABASES = {
952+ 'default': {
953+ 'ENGINE': 'django.db.backends.postgresql_psycopg2',
954+ 'NAME': 'pgtools',
955+ 'USER': 'postgres',
956+ 'HOST': '/dev/shm/pg_pgtools',
957+ }
958+}
959+INSTALLED_APPS = (
960+ 'django.contrib.admin',
961+ 'django.contrib.auth',
962+ 'django.contrib.contenttypes',
963+ 'django.contrib.sessions',
964+ 'pgtools',
965+)
966+MIDDLEWARE_CLASSES = (
967+ 'django.contrib.sessions.middleware.SessionMiddleware',
968+ 'django.middleware.common.CommonMiddleware',
969+ 'django.middleware.csrf.CsrfViewMiddleware',
970+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
971+ 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
972+ 'django.middleware.locale.LocaleMiddleware',
973+ 'django.contrib.messages.middleware.MessageMiddleware',
974+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
975+ 'django.middleware.security.SecurityMiddleware',
976+)
977+ROOT_URLCONF = ''
978+SECRET_KEY = 'secret'
979+SITE_ID = 1
980
981=== modified file 'tox.ini'
982--- tox.ini 2013-07-11 19:14:08 +0000
983+++ tox.ini 2017-01-17 15:08:22 +0000
984@@ -1,84 +1,36 @@
985 [tox]
986-envlist =
987- py2.5-django1.2, py2.5-django1.3, py2.5-django1.4, py2.5-django1.5,
988- py2.6-django1.2, py2.6-django1.3, py2.6-django1.4, py2.6-django1.5,
989- py2.7-django1.2, py2.7-django1.3, py2.7-django1.4, py2.7-django1.5,
990-
991+envlist = py27-django1.6,py27-django1.7,py27-django1.8,py27-django1.9,py27-django1.10
992
993 [testenv]
994-commands = python setup.py test
995-
996-# Python 2.5
997-[testenv:py2.5-django1.2]
998-basepython = python2.5
999-deps = django >= 1.2, < 1.3
1000- psycopg2==2.4.1
1001- mock
1002-
1003-[testenv:py2.5-django1.3]
1004-basepython = python2.5
1005-deps = django >= 1.3, < 1.4
1006- psycopg2==2.4.1
1007- mock
1008-
1009-[testenv:py2.5-django1.4]
1010-basepython = python2.5
1011-deps = django >= 1.4, < 1.5
1012- psycopg2==2.4.1
1013- mock
1014-
1015-[testenv:py2.5-django1.5]
1016-basepython = python2.5
1017-deps = django >= 1.5, < 1.6
1018- psycopg2==2.4.1
1019- mock
1020-
1021-# Python 2.6
1022-[testenv:py2.6-django1.2]
1023-basepython = python2.6
1024-deps = django >= 1.2, < 1.3
1025- psycopg2==2.4.1
1026- mock
1027-
1028-[testenv:py2.6-django1.3]
1029-basepython = python2.6
1030-deps = django >= 1.3, < 1.4
1031- psycopg2==2.4.1
1032- mock
1033-
1034-[testenv:py2.6-django1.4]
1035-basepython = python2.6
1036-deps = django >= 1.4, < 1.5
1037- psycopg2==2.4.1
1038- mock
1039-
1040-[testenv:py2.6-django1.5]
1041-basepython = python2.6
1042-deps = django >= 1.5, < 1.6
1043- psycopg2==2.4.1
1044- mock
1045+commands = python manage.py test pgtools
1046+basepython =
1047+ python2.7
1048+deps =
1049+ psycopg2 == 2.4.5
1050+ mock
1051
1052 # Python 2.7
1053-[testenv:py2.7-django1.2]
1054-basepython = python2.7
1055-deps = django >= 1.2, < 1.3
1056- psycopg2==2.4.1
1057- mock
1058-
1059-[testenv:py2.7-django1.3]
1060-basepython = python2.7
1061-deps = django >= 1.3, < 1.4
1062- psycopg2==2.4.1
1063- mock
1064-
1065-[testenv:py2.7-django1.4]
1066-basepython = python2.7
1067-deps = django >= 1.4, < 1.5
1068- psycopg2==2.4.1
1069- mock
1070-
1071-[testenv:py2.7-django1.5]
1072-basepython = python2.7
1073-deps = django >= 1.5, < 1.6
1074- psycopg2==2.4.1
1075- mock
1076+[testenv:py27-django1.6]
1077+deps =
1078+ django == 1.6.11
1079+ {[testenv]deps}
1080+
1081+[testenv:py27-django1.7]
1082+deps =
1083+ django == 1.7.11
1084+ {[testenv]deps}
1085+
1086+[testenv:py27-django1.8]
1087+deps =
1088+ django == 1.8.17
1089+ {[testenv]deps}
1090+
1091+[testenv:py27-django1.9]
1092+deps =
1093+ django == 1.9.12
1094+ {[testenv]deps}
1095+
1096+[testenv:py27-django1.10]
1097+deps =
1098+ django == 1.10.5
1099+ {[testenv]deps}

Subscribers

People subscribed via source and target branches