From c7ddb60c3bbbfaf95b8a434bec66fea261d9d06a Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 19:43:42 +0200 Subject: [PATCH 01/52] Add .idea in PyCharm --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 8e44d11..ab3e8ed 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,6 @@ logfile # test media upload media + +# PyCharm +.idea/ \ No newline at end of file From 599ec2d236c7187b50408b943961fa50fb1b3167 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 19:44:00 +0200 Subject: [PATCH 02/52] Add migrations --- djadmin2/migrations/0001_initial.py | 34 ++++++++++ djadmin2/migrations/__init__.py | 0 example/blog/migrations/0001_initial.py | 84 ++++++++++++++++++++++++ example/blog/migrations/__init__.py | 0 example/files/migrations/0001_initial.py | 36 ++++++++++ example/files/migrations/__init__.py | 0 example/polls/migrations/0001_initial.py | 42 ++++++++++++ example/polls/migrations/__init__.py | 0 8 files changed, 196 insertions(+) create mode 100644 djadmin2/migrations/0001_initial.py create mode 100644 djadmin2/migrations/__init__.py create mode 100644 example/blog/migrations/0001_initial.py create mode 100644 example/blog/migrations/__init__.py create mode 100644 example/files/migrations/0001_initial.py create mode 100644 example/files/migrations/__init__.py create mode 100644 example/polls/migrations/0001_initial.py create mode 100644 example/polls/migrations/__init__.py diff --git a/djadmin2/migrations/0001_initial.py b/djadmin2/migrations/0001_initial.py new file mode 100644 index 0000000..e4b1319 --- /dev/null +++ b/djadmin2/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='LogEntry', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('action_time', models.DateTimeField(verbose_name='action time', auto_now=True)), + ('object_id', models.TextField(blank=True, verbose_name='object id', null=True)), + ('object_repr', models.CharField(max_length=200, verbose_name='object repr')), + ('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')), + ('change_message', models.TextField(verbose_name='change message', blank=True)), + ('content_type', models.ForeignKey(related_name='log_entries', to='contenttypes.ContentType', blank=True, null=True)), + ('user', models.ForeignKey(related_name='log_entries', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ('-action_time',), + 'verbose_name': 'log entry', + 'verbose_name_plural': 'log entries', + }, + ), + ] diff --git a/djadmin2/migrations/__init__.py b/djadmin2/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/blog/migrations/0001_initial.py b/example/blog/migrations/0001_initial.py new file mode 100644 index 0000000..86f8f44 --- /dev/null +++ b/example/blog/migrations/0001_initial.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('body', models.TextField(verbose_name='body')), + ], + options={ + 'verbose_name': 'comment', + 'verbose_name_plural': 'comments', + }, + ), + migrations.CreateModel( + name='Count', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('num', models.PositiveSmallIntegerField()), + ('parent', models.ForeignKey(null=True, to='blog.Count')), + ], + ), + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('date', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='EventGuide', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='blog.Event')), + ], + ), + migrations.CreateModel( + name='Guest', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('event', models.OneToOneField(to='blog.Event')), + ], + options={ + 'verbose_name': 'awesome guest', + }, + ), + migrations.CreateModel( + name='Location', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('event', models.OneToOneField(verbose_name='awesome event', to='blog.Event')), + ], + ), + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('title', models.CharField(max_length=255, verbose_name='title')), + ('body', models.TextField(verbose_name='body')), + ('published', models.BooleanField(verbose_name='published', default=False)), + ('published_date', models.DateField(null=True, blank=True)), + ], + options={ + 'verbose_name': 'post', + 'verbose_name_plural': 'posts', + }, + ), + migrations.AddField( + model_name='comment', + name='post', + field=models.ForeignKey(verbose_name='post', related_name='comments', to='blog.Post'), + ), + ] diff --git a/example/blog/migrations/__init__.py b/example/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/files/migrations/0001_initial.py b/example/files/migrations/0001_initial.py new file mode 100644 index 0000000..02c6f96 --- /dev/null +++ b/example/files/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CaptionedFile', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('caption', models.CharField(max_length=200, verbose_name='caption')), + ('publication', models.FileField(upload_to='media', verbose_name='Uploaded File')), + ], + options={ + 'verbose_name': 'Captioned File', + 'verbose_name_plural': 'Captioned Files', + }, + ), + migrations.CreateModel( + name='UncaptionedFile', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('publication', models.FileField(upload_to='media', verbose_name='Uploaded File')), + ], + options={ + 'verbose_name': 'Uncaptioned File', + 'verbose_name_plural': 'Uncaptioned Files', + }, + ), + ] diff --git a/example/files/migrations/__init__.py b/example/files/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/polls/migrations/0001_initial.py b/example/polls/migrations/0001_initial.py new file mode 100644 index 0000000..634c1ca --- /dev/null +++ b/example/polls/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Choice', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('choice_text', models.CharField(max_length=200, verbose_name='choice text')), + ('votes', models.IntegerField(verbose_name='votes', default=0)), + ], + options={ + 'verbose_name': 'choice', + 'verbose_name_plural': 'choices', + }, + ), + migrations.CreateModel( + name='Poll', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('question', models.CharField(max_length=200, verbose_name='question')), + ('pub_date', models.DateTimeField(verbose_name='date published')), + ], + options={ + 'verbose_name': 'poll', + 'verbose_name_plural': 'polls', + }, + ), + migrations.AddField( + model_name='choice', + name='poll', + field=models.ForeignKey(verbose_name='poll', to='polls.Poll'), + ), + ] diff --git a/example/polls/migrations/__init__.py b/example/polls/migrations/__init__.py new file mode 100644 index 0000000..e69de29 From 4bdaba8932850d3cb7d864a0f78f1a4e53bf09fa Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 20:24:21 +0200 Subject: [PATCH 03/52] Add .cache in gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ab3e8ed..70be80d 100644 --- a/.gitignore +++ b/.gitignore @@ -61,4 +61,6 @@ logfile media # PyCharm -.idea/ \ No newline at end of file +.idea/ + +.cache \ No newline at end of file From 5f9707e467a8502c4051b8cf4b5cf9e23aaf6759 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 20:28:04 +0200 Subject: [PATCH 04/52] Delete migration for tox to work --- djadmin2/migrations/0001_initial.py | 34 ---------- djadmin2/migrations/__init__.py | 0 example/blog/migrations/0001_initial.py | 84 ------------------------ example/blog/migrations/__init__.py | 0 example/files/migrations/0001_initial.py | 36 ---------- example/files/migrations/__init__.py | 0 example/polls/migrations/0001_initial.py | 42 ------------ example/polls/migrations/__init__.py | 0 8 files changed, 196 deletions(-) delete mode 100644 djadmin2/migrations/0001_initial.py delete mode 100644 djadmin2/migrations/__init__.py delete mode 100644 example/blog/migrations/0001_initial.py delete mode 100644 example/blog/migrations/__init__.py delete mode 100644 example/files/migrations/0001_initial.py delete mode 100644 example/files/migrations/__init__.py delete mode 100644 example/polls/migrations/0001_initial.py delete mode 100644 example/polls/migrations/__init__.py diff --git a/djadmin2/migrations/0001_initial.py b/djadmin2/migrations/0001_initial.py deleted file mode 100644 index e4b1319..0000000 --- a/djadmin2/migrations/0001_initial.py +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models -from django.conf import settings - - -class Migration(migrations.Migration): - - dependencies = [ - ('contenttypes', '0002_remove_content_type_name'), - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='LogEntry', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('action_time', models.DateTimeField(verbose_name='action time', auto_now=True)), - ('object_id', models.TextField(blank=True, verbose_name='object id', null=True)), - ('object_repr', models.CharField(max_length=200, verbose_name='object repr')), - ('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')), - ('change_message', models.TextField(verbose_name='change message', blank=True)), - ('content_type', models.ForeignKey(related_name='log_entries', to='contenttypes.ContentType', blank=True, null=True)), - ('user', models.ForeignKey(related_name='log_entries', to=settings.AUTH_USER_MODEL)), - ], - options={ - 'ordering': ('-action_time',), - 'verbose_name': 'log entry', - 'verbose_name_plural': 'log entries', - }, - ), - ] diff --git a/djadmin2/migrations/__init__.py b/djadmin2/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/example/blog/migrations/0001_initial.py b/example/blog/migrations/0001_initial.py deleted file mode 100644 index 86f8f44..0000000 --- a/example/blog/migrations/0001_initial.py +++ /dev/null @@ -1,84 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Comment', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('body', models.TextField(verbose_name='body')), - ], - options={ - 'verbose_name': 'comment', - 'verbose_name_plural': 'comments', - }, - ), - migrations.CreateModel( - name='Count', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('num', models.PositiveSmallIntegerField()), - ('parent', models.ForeignKey(null=True, to='blog.Count')), - ], - ), - migrations.CreateModel( - name='Event', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('date', models.DateTimeField(auto_now_add=True)), - ], - ), - migrations.CreateModel( - name='EventGuide', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='blog.Event')), - ], - ), - migrations.CreateModel( - name='Guest', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('name', models.CharField(max_length=255)), - ('event', models.OneToOneField(to='blog.Event')), - ], - options={ - 'verbose_name': 'awesome guest', - }, - ), - migrations.CreateModel( - name='Location', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('event', models.OneToOneField(verbose_name='awesome event', to='blog.Event')), - ], - ), - migrations.CreateModel( - name='Post', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('title', models.CharField(max_length=255, verbose_name='title')), - ('body', models.TextField(verbose_name='body')), - ('published', models.BooleanField(verbose_name='published', default=False)), - ('published_date', models.DateField(null=True, blank=True)), - ], - options={ - 'verbose_name': 'post', - 'verbose_name_plural': 'posts', - }, - ), - migrations.AddField( - model_name='comment', - name='post', - field=models.ForeignKey(verbose_name='post', related_name='comments', to='blog.Post'), - ), - ] diff --git a/example/blog/migrations/__init__.py b/example/blog/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/example/files/migrations/0001_initial.py b/example/files/migrations/0001_initial.py deleted file mode 100644 index 02c6f96..0000000 --- a/example/files/migrations/0001_initial.py +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='CaptionedFile', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('caption', models.CharField(max_length=200, verbose_name='caption')), - ('publication', models.FileField(upload_to='media', verbose_name='Uploaded File')), - ], - options={ - 'verbose_name': 'Captioned File', - 'verbose_name_plural': 'Captioned Files', - }, - ), - migrations.CreateModel( - name='UncaptionedFile', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('publication', models.FileField(upload_to='media', verbose_name='Uploaded File')), - ], - options={ - 'verbose_name': 'Uncaptioned File', - 'verbose_name_plural': 'Uncaptioned Files', - }, - ), - ] diff --git a/example/files/migrations/__init__.py b/example/files/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/example/polls/migrations/0001_initial.py b/example/polls/migrations/0001_initial.py deleted file mode 100644 index 634c1ca..0000000 --- a/example/polls/migrations/0001_initial.py +++ /dev/null @@ -1,42 +0,0 @@ -# -*- coding: utf-8 -*- -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ] - - operations = [ - migrations.CreateModel( - name='Choice', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('choice_text', models.CharField(max_length=200, verbose_name='choice text')), - ('votes', models.IntegerField(verbose_name='votes', default=0)), - ], - options={ - 'verbose_name': 'choice', - 'verbose_name_plural': 'choices', - }, - ), - migrations.CreateModel( - name='Poll', - fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('question', models.CharField(max_length=200, verbose_name='question')), - ('pub_date', models.DateTimeField(verbose_name='date published')), - ], - options={ - 'verbose_name': 'poll', - 'verbose_name_plural': 'polls', - }, - ), - migrations.AddField( - model_name='choice', - name='poll', - field=models.ForeignKey(verbose_name='poll', to='polls.Poll'), - ), - ] diff --git a/example/polls/migrations/__init__.py b/example/polls/migrations/__init__.py deleted file mode 100644 index e69de29..0000000 From 8189b9cfaa4d8121af29ac8608030ac8115b539f Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 20:28:41 +0200 Subject: [PATCH 05/52] Update django-floppyforms and django-filter --- requirements.txt | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index 208828c..a38238c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ django-extra-views>=0.6.5 django-braces>=1.3.0 djangorestframework<=2.4.4 -django-floppyforms<=1.2 -django-filter<0.12.0 +django-floppyforms>=1.6.2 +django-filter>=0.13.0 django-crispy-forms>=1.3.2 django-debug-toolbar>=0.9.4 pytz==2014.7 diff --git a/setup.py b/setup.py index 8e2bcc7..b03a7f5 100644 --- a/setup.py +++ b/setup.py @@ -131,8 +131,8 @@ setup( 'django-extra-views>=0.6.5', 'django-braces>=1.3.0', 'djangorestframework<=2.4.4', - 'django-floppyforms<=1.2', - 'django-filter<0.12.0', + 'django-floppyforms>=1.6.2', + 'django-filter>=0.13.0', 'django-crispy-forms>=1.3.2', 'pytz==2014.7' ], From 3fbc396fa2a39386e03aae0322e3130b04a9127f Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 21:07:51 +0200 Subject: [PATCH 06/52] Fix slice has been taken issue on _format_days, _format_months and _format_years --- djadmin2/views.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/djadmin2/views.py b/djadmin2/views.py index 9a27989..a881d4a 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -271,38 +271,38 @@ class ModelListView(AdminModel2Mixin, generic.ListView): context["active_day"] = new_date.strftime("%B %d") - context["dates"] = self._format_days(context) + context["dates"] = self._format_days(self.get_queryset()) elif year and month: context["previous_date"] = { "link": "?year=%s" % (year), "text": "‹ %s" % year, } - context["dates"] = self._format_days(context) + context["dates"] = self._format_days(self.get_queryset()) elif year: context["previous_date"] = { "link": "?", "text": ugettext_lazy("‹ All dates"), } - context["dates"] = self._format_months(context) + context["dates"] = self._format_months(self.get_queryset()) else: - context["dates"] = self._format_years(context) + context["dates"] = self._format_years(self.get_queryset()) return context - def _format_years(self, context): - years = self._qs_date_or_datetime(context['object_list'], 'year') + def _format_years(self, queryset): + years = self._qs_date_or_datetime(queryset, 'year') if len(years) == 1: - return self._format_months(context) + return self._format_months(queryset) else: return [ (("?year=%s" % year.strftime("%Y")), year.strftime("%Y")) for year in - self._qs_date_or_datetime(context['object_list'], 'year') + self._qs_date_or_datetime(queryset, 'year') ] - def _format_months(self, context): + def _format_months(self, queryset): return [ ( "?year=%s&month=%s" % ( @@ -310,10 +310,10 @@ class ModelListView(AdminModel2Mixin, generic.ListView): ), date.strftime("%B %Y") ) for date in - self._qs_date_or_datetime(context['object_list'], 'month') + self._qs_date_or_datetime(queryset, 'month') ] - def _format_days(self, context): + def _format_days(self, queryset): return [ ( "?year=%s&month=%s&day=%s" % ( @@ -323,7 +323,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView): ), date.strftime("%B %d") ) for date in - self._qs_date_or_datetime(context['object_list'], 'day') + self._qs_date_or_datetime(queryset, 'day') ] def _qs_date_or_datetime(self, object_list, type): From 9872521f6fc5799d271be26b244020b4a149aaa4 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 21:09:22 +0200 Subject: [PATCH 07/52] Fix collector in BaseListAction who generate bug when trying to delete multiple object from action list --- djadmin2/actions.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/djadmin2/actions.py b/djadmin2/actions.py index 38e21eb..fc90bd7 100644 --- a/djadmin2/actions.py +++ b/djadmin2/actions.py @@ -2,6 +2,7 @@ from __future__ import division, absolute_import, unicode_literals from django.contrib import messages +from django.db import router from django.views.generic import TemplateView from django.utils.encoding import force_text from django.utils.text import capfirst @@ -94,7 +95,9 @@ class BaseListAction(AdminModel2Mixin, TemplateView): return '%s: %s' % (force_text(capfirst(opts.verbose_name)), force_text(obj)) - collector = utils.NestedObjects(using=None) + using = router.db_for_write(self.model) + + collector = utils.NestedObjects(using=using) collector.collect(self.queryset) context.update({ From 77fd9401bbf7b3bdc0496692e341a8ac772c503e Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 21:09:58 +0200 Subject: [PATCH 08/52] Add field_name attribute for build_date_filter --- djadmin2/filters.py | 8 ++++---- djadmin2/views.py | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/djadmin2/filters.py b/djadmin2/filters.py index 7c06c49..fe275c0 100644 --- a/djadmin2/filters.py +++ b/djadmin2/filters.py @@ -100,18 +100,18 @@ def build_list_filter(request, model_admin, queryset): return type(type_str('%sFilterSet' % queryset.model.__name__),(django_filters.FilterSet, ),filterset_dict,)(request.GET, queryset=queryset) -def build_date_filter(request, model_admin, queryset): +def build_date_filter(request, model_admin, queryset, field_name="published_date"): filterset_dict = { "year": NumericDateFilter( - name="published_date", + name=field_name, lookup_type="year", ), "month": NumericDateFilter( - name="published_date", + name=field_name, lookup_type="month", ), "day": NumericDateFilter( - name="published_date", + name=field_name, lookup_type="day", ) } diff --git a/djadmin2/views.py b/djadmin2/views.py index a881d4a..ccafd35 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -186,7 +186,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView): queryset = self.build_list_filter(queryset).qs if self.model_admin.date_hierarchy: - queryset = self.build_date_filter(queryset).qs + queryset = self.build_date_filter(queryset, self.model_admin.date_hierarchy).qs queryset = self._modify_queryset_for_sort(queryset) @@ -233,7 +233,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView): ) return self._list_filter - def build_date_filter(self, queryset=None): + def build_date_filter(self, queryset=None, field_name=None): if not hasattr(self, "_date_filter"): if queryset is None: queryset = self.get_queryset() @@ -241,6 +241,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView): self.request, self.model_admin, queryset, + field_name ) return self._date_filter From 5bd01187d487a17448bb5d8b2b2c32cc8b56ee48 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 21:36:43 +0200 Subject: [PATCH 09/52] Fix nested object test --- example/blog/tests/test_nestedobjects.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/example/blog/tests/test_nestedobjects.py b/example/blog/tests/test_nestedobjects.py index 1501906..46e4ff1 100644 --- a/example/blog/tests/test_nestedobjects.py +++ b/example/blog/tests/test_nestedobjects.py @@ -1,4 +1,4 @@ -from django.db import DEFAULT_DB_ALIAS +from django.db import DEFAULT_DB_ALIAS, router from django.test import TestCase from djadmin2.utils import NestedObjects @@ -66,7 +66,8 @@ class NestedObjectsTests(TestCase): Check that the nested collector doesn't query for DO_NOTHING objects. """ objs = [Event.objects.create()] - n = NestedObjects(using=None) + using = router.db_for_write(Event._meta.model) + n = NestedObjects(using=using) EventGuide.objects.create(event=objs[0]) with self.assertNumQueries(2): # One for Location, one for Guest, and no query for EventGuide From d3d3fbe9913216059220460aa5d22b28d2209cb4 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 22:51:22 +0200 Subject: [PATCH 10/52] Add django 1.8 setup --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b03a7f5..cedbd35 100644 --- a/setup.py +++ b/setup.py @@ -127,7 +127,7 @@ setup( include_package_data=True, #test_suite='runtests.runtests', install_requires=[ - 'django>=1.6.0', + 'django>=1.8.0', 'django-extra-views>=0.6.5', 'django-braces>=1.3.0', 'djangorestframework<=2.4.4', From f5701e5c15a8ba6676a7c1983bbb3063b7989053 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 22:58:56 +0200 Subject: [PATCH 11/52] Update tox and travis configuration with flake8 support --- .travis.yml | 56 +++++----------- requirements_test.txt | 1 + tox.ini | 151 ++++++------------------------------------ 3 files changed, 36 insertions(+), 172 deletions(-) diff --git a/.travis.yml b/.travis.yml index 51897c8..7758e31 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,47 +1,21 @@ sudo: false language: python -python: "2.7" +python: + - "2.7" + - "3.3" + - "3.4" + - "3.5" env: - matrix: - - TOX_ENV=py27-dj1.6.x - - TOX_ENV=py27-dj1.7.x - - TOX_ENV=py27-dj1.8.x - - TOX_ENV=py27-dj1.9.x - - TOX_ENV=py33-dj1.6.x - - TOX_ENV=py33-dj1.7.x - - TOX_ENV=py33-dj1.8.x - - TOX_ENV=py33-dj1.9.x - - TOX_ENV=py34-dj1.6.x - - TOX_ENV=py34-dj1.7.x - - TOX_ENV=py34-dj1.8.x - - TOX_ENV=py34-dj1.9.x - - TOX_ENV=pypy-dj1.6.x - - TOX_ENV=pypy-dj1.7.x - - TOX_ENV=pypy-dj1.8.x - - TOX_ENV=pypy-dj1.9.x - - TOX_ENV=pypy3-dj1.6.x - - TOX_ENV=pypy3-dj1.8.x - - TOX_ENV=pypy3-dj1.9.x + - DJANGO=1.8 + - DJANGO=1.9 + - DJANGO=master +matrix: + exclude: + - python: "3.3" + env: DJANGO=1.9 + - python: "3.3" + env: DJANGO=master install: - pip install tox script: - - tox -e $TOX_ENV -# for now commented. We have to figure which version use for coverage -# and coveralls -#after_success: -# - coverage report -# - pip install --quiet python-coveralls -# - coveralls -matrix: - allow_failures: - - env: TOX_ENV=py27-dj1.8.x - - env: TOX_ENV=py27-dj1.9.x - - env: TOX_ENV=py33-dj1.8.x - - env: TOX_ENV=py33-dj1.9.x - - env: TOX_ENV=py34-dj1.8.x - - env: TOX_ENV=py34-dj1.9.x - - env: TOX_ENV=pypy-dj1.8.x - - env: TOX_ENV=pypy-dj1.9.x - - env: TOX_ENV=pypy3-dj1.8.x - - env: TOX_ENV=pypy3-dj1.9.x - - env: TOX_ENV=pypy3-dj1.9.x + - tox -e py${TRAVIS_PYTHON_VERSION//[.]/}-$DJANGO \ No newline at end of file diff --git a/requirements_test.txt b/requirements_test.txt index 52a3f23..bacdb50 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,3 +1,4 @@ -rrequirements.txt +flake8==2.5.0 pytest pytest-django \ No newline at end of file diff --git a/tox.ini b/tox.ini index 57771d5..f707b5e 100644 --- a/tox.ini +++ b/tox.ini @@ -1,138 +1,27 @@ +[flake8] +ignore = E265,E501 +max-line-length = 100 +max-complexity = 10 +exclude = migrations/*,docs/* + [tox] -envlist = py27-dj1.6.x, py33-dj1.6.x, py34-dj1.6.x, pypy-dj1.6.x, - pypy3-dj1.6.x, - py27-dj1.7.x, py33-dj1.7.x, py34-dj1.7.x, pypy-dj1.7.x, - pypy3-dj1.7.x, - py27-dj1.8.x, py33-dj1.8.x, py34-dj1.8.x, pypy-dj1.8.x, - pypy3-dj1.8.x, - py27-dj1.9.x, py33-dj1.9.x, py34-dj1.9.x, pypy-dj1.9.x, - pypy3-dj1.9.x, -skipsdist = True +envlist = + py27-{1.8,1.9,master}, + py33-{1.8}, + py34-{1.8,1.9,master}, + py35-{1.8,1.9,master} [testenv] -commands = py.test [] -deps = -rrequirements_test.txt +commands = + flake8 djadmin2 + py.test [] +deps = + -rrequirements_test.txt + 1.8: Django==1.8 + 1.9: Django>=1.9,<1.10 + master: https://github.com/django/django/tarball/master +usedevelop = True setenv= DJANGO_SETTINGS_MODULE = example.settings PYTHONPATH = {toxinidir}/example:{toxinidir} -[testenv:py27-dj1.6.x] -basepython=python2.7 -deps = - Django>=1.6,<1.7 - coverage - {[testenv]deps} - -[testenv:py27-dj1.7.x] -basepython=python2.7 -deps = - Django>=1.7,<1.8 - {[testenv]deps} - -[testenv:py33-dj1.6.x] -basepython=python3.3 -deps = - Django>=1.6,<1.7 - {[testenv]deps} - -[testenv:py34-dj1.6.x] -basepython=python3.4 -deps = - Django>=1.6,<1.7 - {[testenv]deps} - -[testenv:py33-dj1.7.x] -basepython=python3.3 -deps = - Django>=1.7,<1.8 - {[testenv]deps} - -[testenv:py34-dj1.7.x] -basepython=python3.4 -deps = - Django>=1.7,<1.8 - {[testenv]deps} - -[testenv:pypy-dj1.6.x] -basepython=pypy -deps = - Django>=1.6,<1.7 - {[testenv]deps} - -[testenv:pypy3-dj1.6.x] -basepython=pypy3 -deps = - Django>=1.6,<1.7 - {[testenv]deps} - -[testenv:pypy-dj1.7.x] -basepython=pypy -deps = - Django>=1.7,<1.8 - {[testenv]deps} - -[testenv:pypy3-dj1.7.x] -basepython=pypy3 -deps = - Django>=1.7,<1.8 - {[testenv]deps} - -[testenv:py27-dj1.8.x] -basepython=python2.7 -deps = - Django>=1.8,<1.9 - {[testenv]deps} - -[testenv:py34-dj1.8.x] -basepython=python3.4 -deps = - Django>=1.8,<1.9 - {[testenv]deps} - -[testenv:py33-dj1.8.x] -basepython=python2.7 -deps = - Django>=1.8,<1.9 - {[testenv]deps} - -[testenv:pypy-dj1.8.x] -basepython=pypy -deps = - Django>=1.8,<1.9 - {[testenv]deps} - -[testenv:pypy3-dj1.8.x] -basepython=pypy3 -deps = - Django>=1.8,<1.9 - {[testenv]deps} - -[testenv:py27-dj1.9.x] -basepython=python2.7 -deps = - Django>=1.9,<1.9.999 - {[testenv]deps} - -[testenv:py34-dj1.9.x] -basepython=python3.4 -deps = - Django>=1.9,<1.9.999 - {[testenv]deps} - -[testenv:py33-dj1.9.x] -basepython=python2.7 -deps = - Django>=1.9,<1.9.999 - {[testenv]deps} - -[testenv:pypy-dj1.9.x] -basepython=pypy -deps = - Django>=1.9,<1.9.999 - {[testenv]deps} - -[testenv:pypy3-dj1.9.x] -basepython=pypy3 -deps = - Django>=1.9,<1.9.999 - {[testenv]deps} From f3ff443373bacaa1dc9f1f19521b207fe781298c Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 22:59:15 +0200 Subject: [PATCH 12/52] Fix flake8 errors --- .eggs/README.txt | 6 ++++++ djadmin2/__init__.py | 6 ++---- djadmin2/actions.py | 1 - djadmin2/core.py | 1 - djadmin2/filters.py | 6 +++--- djadmin2/forms.py | 17 ++++++++++------- djadmin2/permissions.py | 5 ++--- djadmin2/tests/test_actions.py | 14 +++++++------- djadmin2/tests/test_admin2tags.py | 2 +- djadmin2/tests/test_renderers.py | 2 +- djadmin2/tests/test_types.py | 1 - djadmin2/tests/test_utils.py | 6 ++---- djadmin2/tests/test_views.py | 1 - djadmin2/utils.py | 10 ++++------ djadmin2/views.py | 4 +++- 15 files changed, 41 insertions(+), 41 deletions(-) create mode 100644 .eggs/README.txt diff --git a/.eggs/README.txt b/.eggs/README.txt new file mode 100644 index 0000000..5d01668 --- /dev/null +++ b/.eggs/README.txt @@ -0,0 +1,6 @@ +This directory contains eggs that were downloaded by setuptools to build, test, and run plug-ins. + +This directory caches those eggs to prevent repeated downloads. + +However, it is safe to delete this directory. + diff --git a/djadmin2/__init__.py b/djadmin2/__init__.py index d7ea63c..dfc150f 100644 --- a/djadmin2/__init__.py +++ b/djadmin2/__init__.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, unicode_literals +from . import core +from . import types __version__ = '0.6.1' @@ -10,10 +12,6 @@ VERSION = __version__ # synonym # Default datetime input and output formats ISO_8601 = 'iso-8601' -from . import core -from . import types - - default = core.Admin2() ModelAdmin2 = types.ModelAdmin2 Admin2TabularInline = types.Admin2TabularInline diff --git a/djadmin2/actions.py b/djadmin2/actions.py index fc90bd7..ffab8a4 100644 --- a/djadmin2/actions.py +++ b/djadmin2/actions.py @@ -169,7 +169,6 @@ class DeleteSelectedAction(BaseListAction): # objects, so render a template asking for their confirmation. return self.get(request) - def process_queryset(self): # The user has confirmed that they want to delete the objects. self.get_queryset().delete() diff --git a/djadmin2/core.py b/djadmin2/core.py index fcd8e3d..105671a 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -208,4 +208,3 @@ class Admin2(object): def urls(self): # We set the application and instance namespace here return self.get_urls(), self.name, self.name - diff --git a/djadmin2/filters.py b/djadmin2/filters.py index fe275c0..a2c894f 100644 --- a/djadmin2/filters.py +++ b/djadmin2/filters.py @@ -6,9 +6,9 @@ import collections from itertools import chain from django import forms -from django.forms.util import flatatt +from django.forms.utils import flatatt from django.utils.html import format_html -from django.utils.encoding import force_text, force_bytes +from django.utils.encoding import force_text from django.utils.safestring import mark_safe from django.forms import widgets as django_widgets from django.utils import six @@ -97,7 +97,7 @@ def build_list_filter(request, model_admin, queryset): 'fields': fields, }, ) - return type(type_str('%sFilterSet' % queryset.model.__name__),(django_filters.FilterSet, ),filterset_dict,)(request.GET, queryset=queryset) + return type(type_str('%sFilterSet' % queryset.model.__name__), (django_filters.FilterSet, ), filterset_dict,)(request.GET, queryset=queryset) def build_date_filter(request, model_admin, queryset, field_name="published_date"): diff --git a/djadmin2/forms.py b/djadmin2/forms.py index 7721f97..7688cd2 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -166,9 +166,7 @@ _django_to_floppyforms_widget = { django.forms.extras.widgets.SelectDateWidget: _create_widget( floppyforms.widgets.SelectDateWidget, - init_arguments= - ('years',) - if django.VERSION >= (1, 7) else ('years', 'required')), + init_arguments=('years',) if django.VERSION >= (1, 7) else ('years', 'required')), } _django_field_to_floppyform_widget = { @@ -272,8 +270,10 @@ def modelform_factory(model, form=django.forms.models.ModelForm, fields=None, # Translators : %(username)s will be replaced by the username_field name # (default : username, but could be email, or something else) -ERROR_MESSAGE = ugettext_lazy("Please enter the correct %(username)s and password " - "for a staff account. Note that both fields may be case-sensitive.") +ERROR_MESSAGE = ugettext_lazy( + "Please enter the correct %(username)s and password " + "for a staff account. Note that both fields may be case-sensitive." +) class AdminAuthenticationForm(AuthenticationForm): @@ -285,8 +285,11 @@ class AdminAuthenticationForm(AuthenticationForm): error_messages = { 'required': ugettext_lazy("Please log in again, because your session has expired."), } - this_is_the_login_form = django.forms.BooleanField(widget=floppyforms.HiddenInput, - initial=1, error_messages=error_messages) + this_is_the_login_form = django.forms.BooleanField( + widget=floppyforms.HiddenInput, + initial=1, + error_messages=error_messages + ) def clean(self): username = self.cleaned_data.get('username') diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index 08acbd2..665e248 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -82,13 +82,13 @@ def model_permission(permission): assert model_class, ( 'Cannot apply model permissions on a view that does not ' 'have a `.model` or `.queryset` property.') - + try: # django 1.8+ model_name = model_class._meta.model_name except AttributeError: model_name = model_class._meta.module_name - + permission_name = permission.format( app_label=model_class._meta.app_label, model_name=model_name) @@ -363,7 +363,6 @@ class TemplatePermissionChecker(object): else: return self._view.has_permission(self._obj) - def __str__(self): if self._view is None: return '' diff --git a/djadmin2/tests/test_actions.py b/djadmin2/tests/test_actions.py index ff11118..f4bd6c4 100644 --- a/djadmin2/tests/test_actions.py +++ b/djadmin2/tests/test_actions.py @@ -26,24 +26,24 @@ class ActionTest(TestCase): self.admin2.registry[Thing].list_actions.extend([ TestAction, test_function, - ]) + ]) self.assertEquals( get_description( self.admin2.registry[Thing].list_actions[0] - ), + ), 'Delete selected items' - ) + ) self.assertEquals( get_description( self.admin2.registry[Thing].list_actions[1] - ), + ), 'Test Action Class' - ) + ) self.assertEquals( get_description( self.admin2.registry[Thing].list_actions[2] - ), + ), 'Test function' - ) + ) self.admin2.registry[Thing].list_actions.remove(TestAction) self.admin2.registry[Thing].list_actions.remove(test_function) diff --git a/djadmin2/tests/test_admin2tags.py b/djadmin2/tests/test_admin2tags.py index 0d9bf5b..61c5042 100644 --- a/djadmin2/tests/test_admin2tags.py +++ b/djadmin2/tests/test_admin2tags.py @@ -89,7 +89,7 @@ class TagsTests(TestCase): self.assertEquals( admin2_tags.formset_visible_fieldlist(formset), [u'Visible 1', u'Visible 2'] - ) + ) def test_verbose_name_for(self): app_verbose_names = { diff --git a/djadmin2/tests/test_renderers.py b/djadmin2/tests/test_renderers.py index 76c3ef6..a2921e3 100644 --- a/djadmin2/tests/test_renderers.py +++ b/djadmin2/tests/test_renderers.py @@ -109,7 +109,7 @@ class NumberRendererTest(TestCase): self.assertEqual('42.5', out) def testEndlessFloat(self): - out = self.renderer(1.0/3, None) + out = self.renderer(1.0 / 3, None) if six.PY2: self.assertEqual('0.333333333333', out) else: diff --git a/djadmin2/tests/test_types.py b/djadmin2/tests/test_types.py index 088f083..ed0a92d 100644 --- a/djadmin2/tests/test_types.py +++ b/djadmin2/tests/test_types.py @@ -1,6 +1,5 @@ from django.db import models from django.test import TestCase -from django.views.generic import View from .. import views from ..types import ModelAdmin2, immutable_admin_factory diff --git a/djadmin2/tests/test_utils.py b/djadmin2/tests/test_utils.py index 6c69d3e..c53946d 100644 --- a/djadmin2/tests/test_utils.py +++ b/djadmin2/tests/test_utils.py @@ -40,7 +40,7 @@ class UtilsTest(TestCase): self.assertEquals( UtilsTestModel._meta, utils.model_options(UtilsTestModel) - ) + ) UtilsTestModel._meta.verbose_name = "Utils Test Model" UtilsTestModel._meta.verbose_name_plural = "Utils Test Models" @@ -55,7 +55,7 @@ class UtilsTest(TestCase): self.assertEquals( self.instance._meta, utils.model_options(self.instance) - ) + ) self.instance._meta.verbose_name = "Utils Test Model" self.instance._meta.verbose_name_plural = "Utils Test Models" @@ -166,8 +166,6 @@ class UtilsTest(TestCase): "str" ) - - def test_get_attr(self): class Klass(object): attr = "value" diff --git a/djadmin2/tests/test_views.py b/djadmin2/tests/test_views.py index 8609e71..1e25e76 100644 --- a/djadmin2/tests/test_views.py +++ b/djadmin2/tests/test_views.py @@ -1,5 +1,4 @@ from django.test import TestCase -from django.views.generic import View from .. import views diff --git a/djadmin2/utils.py b/djadmin2/utils.py index df7a91e..f59e29e 100644 --- a/djadmin2/utils.py +++ b/djadmin2/utils.py @@ -10,6 +10,7 @@ from django.db.models.fields.related import ForeignObjectRel from django.utils import six from django.utils.encoding import force_bytes, force_text + def lookup_needs_distinct(opts, lookup_path): """ Returns True if 'distinct()' should be used to query the given lookup path. @@ -91,10 +92,8 @@ def get_attr(obj, attr): and the __str__ attribute. """ if attr == '__str__': - if six.PY2: - value = unicode(obj) - else: - value = str(obj) + from builtins import str as text + value = text(obj) else: attribute = getattr(obj, attr) value = attribute() if callable(attribute) else attribute @@ -109,7 +108,6 @@ class NestedObjects(Collector): https://github.com/django/django/blob/1.8c1/django/contrib/admin/utils.py#L160-L221 """ - def __init__(self, *args, **kwargs): super(NestedObjects, self).__init__(*args, **kwargs) self.edges = {} # {from_instance: [to_instances]} @@ -199,4 +197,4 @@ def type_str(text): if six.PY2: return force_bytes(text) else: - return force_text(text) \ No newline at end of file + return force_text(text) diff --git a/djadmin2/views.py b/djadmin2/views.py index ccafd35..3f3867c 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -30,6 +30,7 @@ from .viewmixins import Admin2Mixin, AdminModel2Mixin, Admin2ModelFormMixin from .filters import build_list_filter, build_date_filter from .models import LogEntry + class AdminView(object): def __init__(self, url, view, name=None): @@ -462,7 +463,7 @@ class ModelDeleteView(AdminModel2Mixin, generic.DeleteView): opts = utils.model_options(obj) return '%s: %s' % (force_text(capfirst(opts.verbose_name)), force_text(obj)) - + using = router.db_for_write(self.get_object()._meta.model) collector = utils.NestedObjects(using=using) collector.collect([self.get_object()]) @@ -542,6 +543,7 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView): from django.contrib.auth import get_user_model return get_user_model()._default_manager.all() + class PasswordChangeDoneView(Admin2Mixin, generic.TemplateView): default_template_name = 'auth/password_change_done.html' From 5849d80dcb1644caa5542efeda271638e3adf549 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 23:26:48 +0200 Subject: [PATCH 13/52] Update django rest framework --- djadmin2/admin2.py | 6 +++--- djadmin2/apiviews.py | 6 +++--- djadmin2/forms.py | 9 ++++----- djadmin2/types.py | 6 +++--- djadmin2/utils.py | 38 +++++++++++++++++++++++++------------- requirements.txt | 2 +- setup.py | 2 +- 7 files changed, 40 insertions(+), 29 deletions(-) diff --git a/djadmin2/admin2.py b/djadmin2/admin2.py index c229716..1575af8 100644 --- a/djadmin2/admin2.py +++ b/djadmin2/admin2.py @@ -13,7 +13,7 @@ from djadmin2.apiviews import Admin2APISerializer class GroupSerializer(Admin2APISerializer): - permissions = PrimaryKeyRelatedField(many=True) + permissions = PrimaryKeyRelatedField(many=True, read_only=True) class Meta: model = Group @@ -24,11 +24,11 @@ class GroupAdmin2(djadmin2.ModelAdmin2): class UserSerializer(Admin2APISerializer): - user_permissions = PrimaryKeyRelatedField(many=True) + user_permissions = PrimaryKeyRelatedField(many=True, read_only=True) class Meta: model = User - exclude = ('passwords',) + exclude = ('password',) class UserAdmin2(djadmin2.ModelAdmin2): diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index 39e7e32..de4b424 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -17,7 +17,7 @@ API_VERSION = '0.1' class Admin2APISerializer(serializers.HyperlinkedModelSerializer): _default_view_name = 'admin2:%(app_label)s_%(model_name)s_api_detail' - pk = fields.Field(source='pk') + pk = fields.Field() __unicode__ = fields.Field(source='__str__') @@ -91,8 +91,8 @@ class IndexAPIView(Admin2APIMixin, APIView): class ListCreateAPIView(Admin2APIMixin, generics.ListCreateAPIView): - pass + model = None class RetrieveUpdateDestroyAPIView(Admin2APIMixin, generics.RetrieveUpdateDestroyAPIView): - pass + model = None diff --git a/djadmin2/forms.py b/djadmin2/forms.py index 7688cd2..5e3fdf1 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -199,11 +199,10 @@ def allow_floppify_widget_for_field(field): # replaces the default TextInput with a NumberInput, if localization is # turned off. That applies for Django 1.6 upwards. # See the relevant source code in django: - # https://github.com/django/django/blob/1.6/django/forms/fields.py#L225 - if django.VERSION >= (1, 6): - if isinstance(field, django.forms.IntegerField) and not field.localize: - if field.widget.__class__ is django.forms.NumberInput: - return True + # https://github.com/django/django/blob/1.9.6/django/forms/fields.py#L261-264 + if isinstance(field, django.forms.IntegerField) and not field.localize: + if field.widget.__class__ is django.forms.NumberInput: + return True # We can check if the widget was replaced by comparing the class of the # specified widget with the default widget that is specified on the field diff --git a/djadmin2/types.py b/djadmin2/types.py index 4b36250..181976b 100644 --- a/djadmin2/types.py +++ b/djadmin2/types.py @@ -201,9 +201,9 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)): def get_api_list_kwargs(self): kwargs = self.get_default_api_view_kwargs() - kwargs.update({ - 'paginate_by': self.list_per_page, - }) + # kwargs.update({ + # 'paginate_by': self.list_per_page, + # }) return kwargs def get_api_detail_kwargs(self): diff --git a/djadmin2/utils.py b/djadmin2/utils.py index f59e29e..9c03f34 100644 --- a/djadmin2/utils.py +++ b/djadmin2/utils.py @@ -7,6 +7,7 @@ from django.db.models import ProtectedError from django.db.models import ManyToManyRel from django.db.models.deletion import Collector from django.db.models.fields.related import ForeignObjectRel +from django.db.models.sql.constants import QUERY_TERMS from django.utils import six from django.utils.encoding import force_bytes, force_text @@ -18,13 +19,24 @@ def lookup_needs_distinct(opts, lookup_path): This is adopted from the Django core. django-admin2 mandates that code doesn't depend on imports from django.contrib.admin. - https://github.com/django/django/blob/1.5.1/django/contrib/admin/util.py#L20 + https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L22 """ - field_name = lookup_path.split('__', 1)[0] - field = opts.get_field_by_name(field_name)[0] - condition1 = hasattr(field, 'rel') and isinstance(field.rel, ManyToManyRel) - condition2 = isinstance(field, ForeignObjectRel) and not field.field.unique - return condition1 or condition2 + + lookup_fields = lookup_path.split('__') + # Remove the last item of the lookup path if it is a query term + if lookup_fields[-1] in QUERY_TERMS: + lookup_fields = lookup_fields[:-1] + # Now go through the fields (following all relations) and look for an m2m + for field_name in lookup_fields: + field = opts.get_field(field_name) + if hasattr(field, 'get_path_info'): + # This field is a relation, update opts to follow the relation + path_info = field.get_path_info() + opts = path_info[-1].to_opts + if any(path.m2m for path in path_info): + # This field is a m2m relation so we know we need to call distinct + return True + return False def model_options(model): @@ -105,14 +117,14 @@ class NestedObjects(Collector): This is adopted from the Django core. django-admin2 mandates that code doesn't depend on imports from django.contrib.admin. - https://github.com/django/django/blob/1.8c1/django/contrib/admin/utils.py#L160-L221 + https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L171-L231 """ def __init__(self, *args, **kwargs): super(NestedObjects, self).__init__(*args, **kwargs) self.edges = {} # {from_instance: [to_instances]} self.protected = set() - self.model_count = defaultdict(int) + self.model_objs = defaultdict(set) def add_edge(self, source, target): self.edges.setdefault(source, []).append(target) @@ -127,10 +139,10 @@ class NestedObjects(Collector): self.add_edge(getattr(obj, related_name), obj) else: self.add_edge(None, obj) - self.model_count[obj._meta.verbose_name_plural] += 1 + self.model_objs[obj._meta.model].add(obj) try: return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) - except ProtectedError as e: + except models.ProtectedError as e: self.protected.update(e.protected_objects) def related_objects(self, related, objs): @@ -155,7 +167,6 @@ class NestedObjects(Collector): def nested(self, format_callback=None): """ Return the graph as a nested list. - """ seen = set() roots = [] @@ -171,6 +182,7 @@ class NestedObjects(Collector): return False + def quote(s): """ Ensure that primary key values do not confuse the admin URLs by escaping @@ -181,14 +193,14 @@ def quote(s): This is adopted from the Django core. django-admin2 mandates that code doesn't depend on imports from django.contrib.admin. - https://github.com/django/django/blob/1.5.1/django/contrib/admin/util.py#L48-L62 + https://github.com/django/django/blob/1.9.6/django/contrib/admin/utils.py#L66-L73 """ if not isinstance(s, six.string_types): return s res = list(s) for i in range(len(res)): c = res[i] - if c in """:/_#?;@&=+$,"<>%\\""": + if c in """:/_#?;@&=+$,"[]<>%\n\\""": res[i] = '_%02X' % ord(c) return ''.join(res) diff --git a/requirements.txt b/requirements.txt index a38238c..b92160e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ django-extra-views>=0.6.5 django-braces>=1.3.0 -djangorestframework<=2.4.4 +djangorestframework<=3.3.3 django-floppyforms>=1.6.2 django-filter>=0.13.0 django-crispy-forms>=1.3.2 diff --git a/setup.py b/setup.py index cedbd35..e237257 100644 --- a/setup.py +++ b/setup.py @@ -130,7 +130,7 @@ setup( 'django>=1.8.0', 'django-extra-views>=0.6.5', 'django-braces>=1.3.0', - 'djangorestframework<=2.4.4', + 'djangorestframework<=3.3.3', 'django-floppyforms>=1.6.2', 'django-filter>=0.13.0', 'django-crispy-forms>=1.3.2', From c532393b97a7257978cb60d55b45673a1bbdc769 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 23:28:30 +0200 Subject: [PATCH 14/52] Fix flake8 errors --- djadmin2/utils.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/djadmin2/utils.py b/djadmin2/utils.py index 9c03f34..a831864 100644 --- a/djadmin2/utils.py +++ b/djadmin2/utils.py @@ -3,10 +3,7 @@ from __future__ import division, absolute_import, unicode_literals from collections import defaultdict -from django.db.models import ProtectedError -from django.db.models import ManyToManyRel -from django.db.models.deletion import Collector -from django.db.models.fields.related import ForeignObjectRel +from django.db.models.deletion import Collector, ProtectedError from django.db.models.sql.constants import QUERY_TERMS from django.utils import six from django.utils.encoding import force_bytes, force_text @@ -142,7 +139,7 @@ class NestedObjects(Collector): self.model_objs[obj._meta.model].add(obj) try: return super(NestedObjects, self).collect(objs, source_attr=source_attr, **kwargs) - except models.ProtectedError as e: + except ProtectedError as e: self.protected.update(e.protected_objects) def related_objects(self, related, objs): @@ -182,7 +179,6 @@ class NestedObjects(Collector): return False - def quote(s): """ Ensure that primary key values do not confuse the admin URLs by escaping From 3bf33946c62c260eb4934f2437956709166373a0 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 23:29:25 +0200 Subject: [PATCH 15/52] Update tox.init --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index f707b5e..d1c275a 100644 --- a/tox.ini +++ b/tox.ini @@ -9,7 +9,7 @@ envlist = py27-{1.8,1.9,master}, py33-{1.8}, py34-{1.8,1.9,master}, - py35-{1.8,1.9,master} + py35-{1.8,1.9,master}, [testenv] commands = @@ -17,7 +17,7 @@ commands = py.test [] deps = -rrequirements_test.txt - 1.8: Django==1.8 + 1.8: Django>=1.8,<1.9 1.9: Django>=1.9,<1.10 master: https://github.com/django/django/tarball/master usedevelop = True From e7a40e1806ef9146c65b26bd17ac6dc1fe5b2cfd Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 23:57:53 +0200 Subject: [PATCH 16/52] Update url patterns --- djadmin2/core.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/djadmin2/core.py b/djadmin2/core.py index 105671a..5adafdf 100644 --- a/djadmin2/core.py +++ b/djadmin2/core.py @@ -5,12 +5,11 @@ Issue #99. """ from __future__ import division, absolute_import, unicode_literals -from django.conf.urls import patterns, include, url -from django.conf import settings -from django.core.exceptions import ImproperlyConfigured - from importlib import import_module +from django.conf import settings +from django.conf.urls import include, url +from django.core.exceptions import ImproperlyConfigured from . import apiviews from . import types @@ -160,8 +159,7 @@ class Admin2(object): } def get_urls(self): - urlpatterns = patterns( - '', + urlpatterns = [ url(regex=r'^$', view=self.index_view.as_view(**self.get_index_kwargs()), name='dashboard' @@ -188,11 +186,10 @@ class Admin2(object): **self.get_api_index_kwargs()), name='api_index' ), - ) + ] for model, model_admin in self.registry.items(): model_options = utils.model_options(model) - urlpatterns += patterns( - '', + urlpatterns += [ url('^{}/{}/'.format( model_options.app_label, model_options.object_name.lower()), @@ -201,7 +198,7 @@ class Admin2(object): model_options.app_label, model_options.object_name.lower()), include(model_admin.api_urls)), - ) + ] return urlpatterns @property From 54f710055a340f18de619ad683a724147a338894 Mon Sep 17 00:00:00 2001 From: arthur Date: Sat, 7 May 2016 23:59:55 +0200 Subject: [PATCH 17/52] Add migrations but not use them in test for now --- djadmin2/migrations/0001_initial.py | 34 ++++++++++ djadmin2/migrations/__init__.py | 0 example/blog/migrations/0001_initial.py | 84 ++++++++++++++++++++++++ example/blog/migrations/__init__.py | 0 example/files/migrations/0001_initial.py | 36 ++++++++++ example/files/migrations/__init__.py | 0 example/polls/migrations/0001_initial.py | 42 ++++++++++++ example/polls/migrations/__init__.py | 0 tox.ini | 2 +- 9 files changed, 197 insertions(+), 1 deletion(-) create mode 100644 djadmin2/migrations/0001_initial.py create mode 100644 djadmin2/migrations/__init__.py create mode 100644 example/blog/migrations/0001_initial.py create mode 100644 example/blog/migrations/__init__.py create mode 100644 example/files/migrations/0001_initial.py create mode 100644 example/files/migrations/__init__.py create mode 100644 example/polls/migrations/0001_initial.py create mode 100644 example/polls/migrations/__init__.py diff --git a/djadmin2/migrations/0001_initial.py b/djadmin2/migrations/0001_initial.py new file mode 100644 index 0000000..bd03761 --- /dev/null +++ b/djadmin2/migrations/0001_initial.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +from django.conf import settings + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('contenttypes', '0002_remove_content_type_name'), + ] + + operations = [ + migrations.CreateModel( + name='LogEntry', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('action_time', models.DateTimeField(auto_now=True, verbose_name='action time')), + ('object_id', models.TextField(blank=True, null=True, verbose_name='object id')), + ('object_repr', models.CharField(max_length=200, verbose_name='object repr')), + ('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')), + ('change_message', models.TextField(blank=True, verbose_name='change message')), + ('content_type', models.ForeignKey(null=True, related_name='log_entries', to='contenttypes.ContentType', blank=True)), + ('user', models.ForeignKey(related_name='log_entries', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'verbose_name_plural': 'log entries', + 'ordering': ('-action_time',), + 'verbose_name': 'log entry', + }, + ), + ] diff --git a/djadmin2/migrations/__init__.py b/djadmin2/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/blog/migrations/0001_initial.py b/example/blog/migrations/0001_initial.py new file mode 100644 index 0000000..6c0aa59 --- /dev/null +++ b/example/blog/migrations/0001_initial.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Comment', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('body', models.TextField(verbose_name='body')), + ], + options={ + 'verbose_name_plural': 'comments', + 'verbose_name': 'comment', + }, + ), + migrations.CreateModel( + name='Count', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('num', models.PositiveSmallIntegerField()), + ('parent', models.ForeignKey(to='blog.Count', null=True)), + ], + ), + migrations.CreateModel( + name='Event', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('date', models.DateTimeField(auto_now_add=True)), + ], + ), + migrations.CreateModel( + name='EventGuide', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='blog.Event')), + ], + ), + migrations.CreateModel( + name='Guest', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('name', models.CharField(max_length=255)), + ('event', models.OneToOneField(to='blog.Event')), + ], + options={ + 'verbose_name': 'awesome guest', + }, + ), + migrations.CreateModel( + name='Location', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('event', models.OneToOneField(to='blog.Event', verbose_name='awesome event')), + ], + ), + migrations.CreateModel( + name='Post', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('title', models.CharField(max_length=255, verbose_name='title')), + ('body', models.TextField(verbose_name='body')), + ('published', models.BooleanField(default=False, verbose_name='published')), + ('published_date', models.DateField(blank=True, null=True)), + ], + options={ + 'verbose_name_plural': 'posts', + 'verbose_name': 'post', + }, + ), + migrations.AddField( + model_name='comment', + name='post', + field=models.ForeignKey(to='blog.Post', verbose_name='post', related_name='comments'), + ), + ] diff --git a/example/blog/migrations/__init__.py b/example/blog/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/files/migrations/0001_initial.py b/example/files/migrations/0001_initial.py new file mode 100644 index 0000000..f397eb9 --- /dev/null +++ b/example/files/migrations/0001_initial.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='CaptionedFile', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('caption', models.CharField(max_length=200, verbose_name='caption')), + ('publication', models.FileField(upload_to='media', verbose_name='Uploaded File')), + ], + options={ + 'verbose_name_plural': 'Captioned Files', + 'verbose_name': 'Captioned File', + }, + ), + migrations.CreateModel( + name='UncaptionedFile', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('publication', models.FileField(upload_to='media', verbose_name='Uploaded File')), + ], + options={ + 'verbose_name_plural': 'Uncaptioned Files', + 'verbose_name': 'Uncaptioned File', + }, + ), + ] diff --git a/example/files/migrations/__init__.py b/example/files/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/example/polls/migrations/0001_initial.py b/example/polls/migrations/0001_initial.py new file mode 100644 index 0000000..536c8a9 --- /dev/null +++ b/example/polls/migrations/0001_initial.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Choice', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('choice_text', models.CharField(max_length=200, verbose_name='choice text')), + ('votes', models.IntegerField(default=0, verbose_name='votes')), + ], + options={ + 'verbose_name_plural': 'choices', + 'verbose_name': 'choice', + }, + ), + migrations.CreateModel( + name='Poll', + fields=[ + ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('question', models.CharField(max_length=200, verbose_name='question')), + ('pub_date', models.DateTimeField(verbose_name='date published')), + ], + options={ + 'verbose_name_plural': 'polls', + 'verbose_name': 'poll', + }, + ), + migrations.AddField( + model_name='choice', + name='poll', + field=models.ForeignKey(to='polls.Poll', verbose_name='poll'), + ), + ] diff --git a/example/polls/migrations/__init__.py b/example/polls/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tox.ini b/tox.ini index d1c275a..c8c6e87 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ envlist = [testenv] commands = flake8 djadmin2 - py.test [] + py.test --nomigrations [] deps = -rrequirements_test.txt 1.8: Django>=1.8,<1.9 From cb91aabcf4860fb3dd27919392fec253cc6dcd2b Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 8 May 2016 00:12:53 +0200 Subject: [PATCH 18/52] Update permission.py --- djadmin2/permissions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index 665e248..2f070d8 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -22,7 +22,7 @@ import re from django.contrib.auth import models as auth_models from django.contrib.contenttypes import models as contenttypes_models -from django.db.models import get_models +from django.apps import apps from django.utils import six from . import utils @@ -381,7 +381,7 @@ def create_view_permissions(app, created_models, verbosity, **kwargs): """ # Is there any reason for doing this import here? - app_models = get_models(app) + app_models = apps.get_models(app) # This will hold the permissions we're looking for as # (content_type, (codename, name)) From 92e7d52c476f2ced4c24b5774efba632dc5733b5 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 8 May 2016 00:18:16 +0200 Subject: [PATCH 19/52] rename ctype to c_type (flake8 on python2.7) --- djadmin2/permissions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index 2f070d8..312f658 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -407,8 +407,8 @@ def create_view_permissions(app, created_models, verbosity, **kwargs): perms = [ auth_models.Permission(codename=codename, name=name, content_type=ctype) - for ctype, (codename, name) in searched_perms - if (ctype.pk, codename) not in all_perms + for c_type, (codename, name) in searched_perms + if (c_type.pk, codename) not in all_perms ] auth_models.Permission.objects.bulk_create(perms) if verbosity >= 2: From f223b8542249866aef03d671af84a36058c36728 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 8 May 2016 00:25:12 +0200 Subject: [PATCH 20/52] Rename AdminModel2Mixin to Admin2ModelMixin --- djadmin2/actions.py | 6 +++--- djadmin2/viewmixins.py | 4 ++-- djadmin2/views.py | 14 +++++++------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/djadmin2/actions.py b/djadmin2/actions.py index ffab8a4..6718b81 100644 --- a/djadmin2/actions.py +++ b/djadmin2/actions.py @@ -10,7 +10,7 @@ from django.utils.translation import ugettext_lazy, ungettext, pgettext_lazy from django.utils.translation import ugettext as _ from . import permissions, utils -from .viewmixins import AdminModel2Mixin +from .viewmixins import Admin2ModelMixin def get_description(action): @@ -22,7 +22,7 @@ def get_description(action): return capfirst(action.__name__.replace('_', ' ')) -class BaseListAction(AdminModel2Mixin, TemplateView): +class BaseListAction(Admin2ModelMixin, TemplateView): permission_classes = (permissions.IsStaffPermission,) @@ -55,7 +55,7 @@ class BaseListAction(AdminModel2Mixin, TemplateView): super(BaseListAction, self).__init__(*args, **kwargs) def get_queryset(self): - """ Replaced `get_queryset` from `AdminModel2Mixin`""" + """ Replaced `get_queryset` from `Admin2ModelMixin`""" return self.queryset def description(self): diff --git a/djadmin2/viewmixins.py b/djadmin2/viewmixins.py index 09ef357..cf7888e 100644 --- a/djadmin2/viewmixins.py +++ b/djadmin2/viewmixins.py @@ -116,11 +116,11 @@ class Admin2Mixin(PermissionMixin): return super(Admin2Mixin, self).dispatch(request, *args, **kwargs) -class AdminModel2Mixin(Admin2Mixin): +class Admin2ModelMixin(Admin2Mixin): model_admin = None def get_context_data(self, **kwargs): - context = super(AdminModel2Mixin, self).get_context_data(**kwargs) + context = super(Admin2ModelMixin, self).get_context_data(**kwargs) model = self.get_model() model_meta = model_options(model) app_verbose_names = self.model_admin.admin.app_verbose_names diff --git a/djadmin2/views.py b/djadmin2/views.py index 3f3867c..7bc3292 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -26,7 +26,7 @@ import extra_views from . import permissions, utils from .forms import AdminAuthenticationForm -from .viewmixins import Admin2Mixin, AdminModel2Mixin, Admin2ModelFormMixin +from .viewmixins import Admin2Mixin, Admin2ModelMixin, Admin2ModelFormMixin from .filters import build_list_filter, build_date_filter from .models import LogEntry @@ -102,7 +102,7 @@ class AppIndexView(Admin2Mixin, generic.TemplateView): return data -class ModelListView(AdminModel2Mixin, generic.ListView): +class ModelListView(Admin2ModelMixin, generic.ListView): """Context Variables :is_paginated: If the page is paginated (page has a next button) @@ -347,7 +347,7 @@ class ModelListView(AdminModel2Mixin, generic.ListView): return self.model_admin.search_fields -class ModelDetailView(AdminModel2Mixin, generic.DetailView): +class ModelDetailView(Admin2ModelMixin, generic.DetailView): """Context Variables :model: Type of object you are editing @@ -365,7 +365,7 @@ class ModelDetailView(AdminModel2Mixin, generic.DetailView): permissions.ModelViewPermission) -class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin, +class ModelEditFormView(Admin2ModelMixin, Admin2ModelFormMixin, extra_views.UpdateWithInlinesView): """Context Variables @@ -401,7 +401,7 @@ class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin, return response -class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin, +class ModelAddFormView(Admin2ModelMixin, Admin2ModelFormMixin, extra_views.CreateWithInlinesView): """Context Variables @@ -437,7 +437,7 @@ class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin, return response -class ModelDeleteView(AdminModel2Mixin, generic.DeleteView): +class ModelDeleteView(Admin2ModelMixin, generic.DeleteView): """Context Variables :model: Type of object you are editing @@ -481,7 +481,7 @@ class ModelDeleteView(AdminModel2Mixin, generic.DeleteView): return super(ModelDeleteView, self).delete(request, *args, **kwargs) -class ModelHistoryView(AdminModel2Mixin, generic.ListView): +class ModelHistoryView(Admin2ModelMixin, generic.ListView): """Context Variables :model: Type of object you are editing From 754f5dab562fa62c2726a9b022dc55a29e1cddc6 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 8 May 2016 00:54:04 +0200 Subject: [PATCH 21/52] Replace post_sync signal with post_migrate --- djadmin2/models.py | 2 +- djadmin2/permissions.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/djadmin2/models.py b/djadmin2/models.py index f39e086..9a4eb60 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -103,6 +103,6 @@ class LogEntry(models.Model): # setup signal handlers here, since ``models.py`` will be imported by django # for sure if ``djadmin2`` is listed in the ``INSTALLED_APPS``. -signals.post_syncdb.connect( +signals.post_migrate.connect( permissions.create_view_permissions, dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions") diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index 312f658..5384cc7 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -369,7 +369,7 @@ class TemplatePermissionChecker(object): return force_text(bool(self)) -def create_view_permissions(app, created_models, verbosity, **kwargs): +def create_view_permissions(app_config, created_models=None, verbosity=2, **kwargs): """ Create 'view' permissions for all models. @@ -381,7 +381,7 @@ def create_view_permissions(app, created_models, verbosity, **kwargs): """ # Is there any reason for doing this import here? - app_models = apps.get_models(app) + app_models = apps.get_models(app_config) # This will hold the permissions we're looking for as # (content_type, (codename, name)) From 23f7f9479acb43c4496c1f87474b6c67eed1df74 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 8 May 2016 01:29:17 +0200 Subject: [PATCH 22/52] create a djadmin2_site in djadmin2/site.py to make it work in django 1.9 --- djadmin2/__init__.py | 14 +- djadmin2/admin2.py | 15 +- djadmin2/forms.py | 4 +- djadmin2/models.py | 2 +- djadmin2/site.py | 3 + djadmin2/tests/test_auth_admin.py | 6 +- djadmin2/tests/test_core.py | 4 +- example/blog/admin2.py | 15 +- example/blog/templates/base.html | 10 +- example/blog/tests/test_filters.py | 8 +- example/blog/tests/test_permissions.py | 19 ++- example/db.sqlite3 | Bin 0 -> 67584 bytes example/example/settings.py | 220 ++++++++++--------------- example/example/urls.py | 15 +- example/files/admin2.py | 5 +- example/files/models.py | 8 +- example/polls/admin2.py | 10 +- requirements.txt | 2 +- tox.ini | 9 +- 19 files changed, 160 insertions(+), 209 deletions(-) create mode 100644 djadmin2/site.py create mode 100644 example/db.sqlite3 diff --git a/djadmin2/__init__.py b/djadmin2/__init__.py index dfc150f..651dd0b 100644 --- a/djadmin2/__init__.py +++ b/djadmin2/__init__.py @@ -1,7 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, unicode_literals -from . import core -from . import types + __version__ = '0.6.1' @@ -11,14 +10,3 @@ VERSION = __version__ # synonym # Default datetime input and output formats ISO_8601 = 'iso-8601' - -default = core.Admin2() -ModelAdmin2 = types.ModelAdmin2 -Admin2TabularInline = types.Admin2TabularInline -Admin2StackedInline = types.Admin2StackedInline - -# Utility to make migration between versions easier -sites = default -ModelAdmin = ModelAdmin2 -AdminInline = Admin2TabularInline -Admin2Inline = Admin2TabularInline diff --git a/djadmin2/admin2.py b/djadmin2/admin2.py index 1575af8..f1089f0 100644 --- a/djadmin2/admin2.py +++ b/djadmin2/admin2.py @@ -7,9 +7,10 @@ from django.contrib.sites.models import Site from rest_framework.relations import PrimaryKeyRelatedField -import djadmin2 from djadmin2.forms import UserCreationForm, UserChangeForm from djadmin2.apiviews import Admin2APISerializer +from djadmin2.site import djadmin2_site +from djadmin2.types import ModelAdmin2 class GroupSerializer(Admin2APISerializer): @@ -19,7 +20,7 @@ class GroupSerializer(Admin2APISerializer): model = Group -class GroupAdmin2(djadmin2.ModelAdmin2): +class GroupAdmin2(ModelAdmin2): api_serializer_class = GroupSerializer @@ -31,7 +32,7 @@ class UserSerializer(Admin2APISerializer): exclude = ('password',) -class UserAdmin2(djadmin2.ModelAdmin2): +class UserAdmin2(ModelAdmin2): create_form_class = UserCreationForm update_form_class = UserChangeForm search_fields = ('username', 'groups__name', 'first_name', 'last_name', @@ -43,15 +44,15 @@ class UserAdmin2(djadmin2.ModelAdmin2): # Register each model with the admin -djadmin2.default.register(User, UserAdmin2) -djadmin2.default.register(Group, GroupAdmin2) +djadmin2_site.register(User, UserAdmin2) +djadmin2_site.register(Group, GroupAdmin2) # Register the sites app if it's been activated in INSTALLED_APPS if "django.contrib.sites" in settings.INSTALLED_APPS: - class SiteAdmin2(djadmin2.ModelAdmin2): + class SiteAdmin2(ModelAdmin2): list_display = ('domain', 'name') search_fields = ('domain', 'name') - djadmin2.default.register(Site, SiteAdmin2) + djadmin2_site.register(Site, SiteAdmin2) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index 5e3fdf1..c5e5646 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -182,8 +182,8 @@ _django_field_to_floppyform_widget = { _create_widget(floppyforms.widgets.URLInput), django.forms.fields.SlugField: _create_widget(floppyforms.widgets.SlugInput), - django.forms.fields.IPAddressField: - _create_widget(floppyforms.widgets.IPAddressInput), + #django.forms.fields.IPAddressField: + # _create_widget(floppyforms.widgets.IPAddressInput), django.forms.fields.SplitDateTimeField: _create_splitdatetimewidget(floppyforms.widgets.SplitDateTimeWidget), } diff --git a/djadmin2/models.py b/djadmin2/models.py index 9a4eb60..8c8b81e 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -7,8 +7,8 @@ from django.contrib.contenttypes.models import ContentType from django.db import models from django.db.models import signals from django.utils.encoding import force_text -from django.utils.encoding import smart_text from django.utils.encoding import python_2_unicode_compatible +from django.utils.encoding import smart_text from django.utils.translation import ugettext, ugettext_lazy as _ from . import permissions diff --git a/djadmin2/site.py b/djadmin2/site.py new file mode 100644 index 0000000..111cf76 --- /dev/null +++ b/djadmin2/site.py @@ -0,0 +1,3 @@ +from . import core + +djadmin2_site = core.Admin2() diff --git a/djadmin2/tests/test_auth_admin.py b/djadmin2/tests/test_auth_admin.py index 5afdb1c..88ae67d 100644 --- a/djadmin2/tests/test_auth_admin.py +++ b/djadmin2/tests/test_auth_admin.py @@ -5,7 +5,7 @@ from django.test.client import RequestFactory import floppyforms -import djadmin2 +from djadmin2.site import djadmin2_site from ..admin2 import UserAdmin2 @@ -27,7 +27,7 @@ class UserAdminTest(TestCase): request = self.factory.get(reverse('admin2:auth_user_create')) request.user = self.user - model_admin = UserAdmin2(User, djadmin2.default) + model_admin = UserAdmin2(User, djadmin2_site) view = model_admin.create_view.view.as_view( **model_admin.get_create_kwargs()) response = view(request) @@ -48,7 +48,7 @@ class UserAdminTest(TestCase): request = self.factory.get( reverse('admin2:auth_user_update', args=(self.user.pk,))) request.user = self.user - model_admin = UserAdmin2(User, djadmin2.default) + model_admin = UserAdmin2(User, djadmin2_site) view = model_admin.update_view.view.as_view( **model_admin.get_update_kwargs()) response = view(request, pk=self.user.pk) diff --git a/djadmin2/tests/test_core.py b/djadmin2/tests/test_core.py index fe091cd..f92fb69 100644 --- a/djadmin2/tests/test_core.py +++ b/djadmin2/tests/test_core.py @@ -4,7 +4,7 @@ from django.test import TestCase from django.contrib.auth.models import Group, User from django.contrib.sites.models import Site -import djadmin2 +from djadmin2.site import djadmin2_site from ..types import ModelAdmin2 from ..core import Admin2 @@ -71,4 +71,4 @@ class Admin2Test(TestCase): def test_default_entries(self): expected_default_models = (User, Group, Site) for model in expected_default_models: - self.assertTrue(isinstance(djadmin2.default.registry[model], ModelAdmin2)) + self.assertTrue(isinstance(djadmin2_site.registry[model], ModelAdmin2)) diff --git a/example/blog/admin2.py b/example/blog/admin2.py index f71ae01..d63a0ab 100644 --- a/example/blog/admin2.py +++ b/example/blog/admin2.py @@ -3,21 +3,22 @@ from __future__ import division, absolute_import, unicode_literals from django.utils.translation import ugettext_lazy -import djadmin2 from djadmin2 import renderers from djadmin2.actions import DeleteSelectedAction # Import your custom models +from djadmin2.site import djadmin2_site +from djadmin2.types import Admin2TabularInline, ModelAdmin2 from .actions import (CustomPublishAction, PublishAllItemsAction, unpublish_items, unpublish_all_items) from .models import Post, Comment -class CommentInline(djadmin2.Admin2TabularInline): +class CommentInline(Admin2TabularInline): model = Comment -class PostAdmin(djadmin2.ModelAdmin2): +class PostAdmin(ModelAdmin2): list_actions = [ DeleteSelectedAction, CustomPublishAction, PublishAllItemsAction, unpublish_items, @@ -34,7 +35,7 @@ class PostAdmin(djadmin2.ModelAdmin2): ordering = ["-published_date", "title",] -class CommentAdmin(djadmin2.ModelAdmin2): +class CommentAdmin(ModelAdmin2): search_fields = ('body', '=post__title') list_filter = ['post', ] actions_on_top = True @@ -42,12 +43,12 @@ class CommentAdmin(djadmin2.ModelAdmin2): actions_selection_counter = False # Register the blog app with a verbose name -djadmin2.default.register_app_verbose_name( +djadmin2_site.register_app_verbose_name( 'blog', ugettext_lazy('My Blog') ) # Register each model with the admin -djadmin2.default.register(Post, PostAdmin) -djadmin2.default.register(Comment, CommentAdmin) +djadmin2_site.register(Post, PostAdmin) +djadmin2_site.register(Comment, CommentAdmin) diff --git a/example/blog/templates/base.html b/example/blog/templates/base.html index 31d2fdf..77743b1 100644 --- a/example/blog/templates/base.html +++ b/example/blog/templates/base.html @@ -1,4 +1,4 @@ -{% load i18n static %} +{% load i18n staticfiles %} @@ -7,8 +7,8 @@ {% block css %} - - + + {% endblock css %} @@ -19,8 +19,8 @@ {% block javascript %} - - + + {% endblock javascript %} diff --git a/example/blog/tests/test_filters.py b/example/blog/tests/test_filters.py index f3a8986..660ecc8 100644 --- a/example/blog/tests/test_filters.py +++ b/example/blog/tests/test_filters.py @@ -5,9 +5,9 @@ from django.test import TestCase from django.test.client import RequestFactory from django.core.urlresolvers import reverse +from djadmin2.types import ModelAdmin2 from ..models import Post -import djadmin2 import djadmin2.filters as djadmin2_filters import django_filters @@ -18,11 +18,11 @@ class ListFilterBuilderTest(TestCase): self.rf = RequestFactory() def test_filter_building(self): - class PostAdminSimple(djadmin2.ModelAdmin2): + class PostAdminSimple(ModelAdmin2): list_filter = ['published', ] - class PostAdminWithFilterInstances(djadmin2.ModelAdmin2): + class PostAdminWithFilterInstances(ModelAdmin2): list_filter = [ django_filters.BooleanFilter(name='published'), ] @@ -33,7 +33,7 @@ class ListFilterBuilderTest(TestCase): fields = ['published'] - class PostAdminWithFilterSetInst(djadmin2.ModelAdmin2): + class PostAdminWithFilterSetInst(ModelAdmin2): list_filter = FS Post.objects.create(title="post_1_title", body="body") diff --git a/example/blog/tests/test_permissions.py b/example/blog/tests/test_permissions.py index 11c1f7a..242a00d 100644 --- a/example/blog/tests/test_permissions.py +++ b/example/blog/tests/test_permissions.py @@ -4,12 +4,13 @@ from django.template import Template, Context from django.test import TestCase from django.test.client import RequestFactory -import djadmin2 -from djadmin2 import ModelAdmin2 from djadmin2.permissions import TemplatePermissionChecker from blog.models import Post +from djadmin2.site import djadmin2_site +from djadmin2.types import ModelAdmin2 + class TemplatePermissionTest(TestCase): def setUp(self): @@ -26,7 +27,7 @@ class TemplatePermissionTest(TestCase): return template.render(context) def test_permission_wrapper(self): - model_admin = ModelAdmin2(Post, djadmin2.default) + model_admin = ModelAdmin2(Post, djadmin2_site) request = self.factory.get(reverse('admin2:blog_post_index')) request.user = self.user permissions = TemplatePermissionChecker(request, model_admin) @@ -61,7 +62,7 @@ class TemplatePermissionTest(TestCase): codename='add_post') self.user.user_permissions.add(post_add_permission) - model_admin = ModelAdmin2(Post, djadmin2.default) + model_admin = ModelAdmin2(Post, djadmin2_site) request = self.factory.get(reverse('admin2:blog_post_index')) request.user = self.user permissions = TemplatePermissionChecker(request, model_admin) @@ -89,8 +90,8 @@ class TemplatePermissionTest(TestCase): self.assertEqual(result, '') def test_admin_binding(self): - user_admin = djadmin2.default.get_admin_by_name('auth_user') - post_admin = djadmin2.default.get_admin_by_name('blog_post') + user_admin = djadmin2_site.get_admin_by_name('auth_user') + post_admin = djadmin2_site.get_admin_by_name('blog_post') request = self.factory.get(reverse('admin2:auth_user_index')) request.user = self.user permissions = TemplatePermissionChecker(request, user_admin) @@ -155,8 +156,8 @@ class TemplatePermissionTest(TestCase): self.assertEqual(result, '') def test_view_binding(self): - user_admin = djadmin2.default.get_admin_by_name('auth_user') - post_admin = djadmin2.default.get_admin_by_name('blog_post') + user_admin = djadmin2_site.get_admin_by_name('auth_user') + post_admin = djadmin2_site.get_admin_by_name('blog_post') request = self.factory.get(reverse('admin2:auth_user_index')) request.user = self.user permissions = TemplatePermissionChecker(request, user_admin) @@ -235,7 +236,7 @@ class TemplatePermissionTest(TestCase): self.assertEqual(result, '1True2False34True') def test_object_level_permission(self): - model_admin = ModelAdmin2(Post, djadmin2.default) + model_admin = ModelAdmin2(Post, djadmin2_site) request = self.factory.get(reverse('admin2:blog_post_index')) request.user = self.user permissions = TemplatePermissionChecker(request, model_admin) diff --git a/example/db.sqlite3 b/example/db.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..186c5e801745251cfb283137403b1028c57eeb47 GIT binary patch literal 67584 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 literal 0 HcmV?d00001 diff --git a/example/example/settings.py b/example/example/settings.py index 7768d08..50a4748 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -1,117 +1,20 @@ # Django settings for example project. import os -BASE_DIR = os.path.dirname(os.path.dirname(__file__)) +# Build paths inside the project like this: os.path.join(BASE_DIR, ...) +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DEBUG = True -TEMPLATE_DEBUG = DEBUG -ADMINS = ( - # ('Your Name', 'your_email@example.com'), -) +# Quick-start development settings - unsuitable for production +# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/ -MANAGERS = ADMINS - -DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': 'example.db', - } -} - -# Hosts/domain names that are valid for this site; required if DEBUG is False -# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts -ALLOWED_HOSTS = [] - -# Local time zone for this installation. Choices can be found here: -# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name -# although not all choices may be available on all operating systems. -# In a Windows environment this must be set to your system time zone. -TIME_ZONE = 'America/Chicago' - -# Language code for this installation. All choices can be found here: -# http://www.i18nguy.com/unicode/language-identifiers.html -LANGUAGE_CODE = 'en-us' - -SITE_ID = 1 - -# If you set this to False, Django will make some optimizations so as not -# to load the internationalization machinery. -USE_I18N = True - -# If you set this to False, Django will not format dates, numbers and -# calendars according to the current locale. -USE_L10N = True - -# If you set this to False, Django will not use timezone-aware datetimes. -USE_TZ = True - -# Absolute filesystem path to the directory that will hold user-uploaded files. -# Example: "/var/www/example.com/media/" -MEDIA_ROOT = '' - -# URL that handles the media served from MEDIA_ROOT. Make sure to use a -# trailing slash. -# Examples: "http://example.com/media/", "http://media.example.com/" -MEDIA_URL = '' - -# Absolute path to the directory static files should be collected to. -# Don't put anything in this directory yourself; store your static files -# in apps' "static/" subdirectories and in STATICFILES_DIRS. -# Example: "/var/www/example.com/static/" -STATIC_ROOT = '' - -# URL prefix for static files. -# Example: "http://example.com/static/", "http://static.example.com/" -STATIC_URL = '/static/' - -# Additional locations of static files -STATICFILES_DIRS = ( - # Put strings here, like "/home/html/static" or "C:/www/django/static". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - os.path.join(BASE_DIR, "static"), -) - -# List of finder classes that know how to find static files in -# various locations. -STATICFILES_FINDERS = ( - 'django.contrib.staticfiles.finders.FileSystemFinder', - 'django.contrib.staticfiles.finders.AppDirectoriesFinder', -# 'django.contrib.staticfiles.finders.DefaultStorageFinder', -) - -# Make this unique, and don't share it with anybody. +# SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = '*ymubzn8p_s7vrm%jsqvr6$qnea_5mcp(ao0z-yh1q0gro!0g1' -# List of callables that know how to import templates from various sources. -TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.Loader', - 'django.template.loaders.app_directories.Loader', -# 'django.template.loaders.eggs.Loader', -) +# SECURITY WARNING: don't run with debug turned on in production! +DEBUG = True -MIDDLEWARE_CLASSES = ( - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - # Uncomment the next line for simple clickjacking protection: - # 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) - -ROOT_URLCONF = 'example.urls' - -# Python dotted path to the WSGI application used by Django's runserver. -WSGI_APPLICATION = 'example.wsgi.application' - -TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. -) +ALLOWED_HOSTS = [] INSTALLED_APPS = ( 'django.contrib.auth', @@ -133,34 +36,91 @@ INSTALLED_APPS = ( 'polls' ) -# A sample logging configuration. The only tangible logging -# performed by this configuration is to send an email to -# the site admins on every HTTP 500 error when DEBUG=False. -# See http://docs.djangoproject.com/en/dev/topics/logging for -# more details on how to customize your logging configuration. -LOGGING = { - 'version': 1, - 'disable_existing_loggers': False, - 'filters': { - 'require_debug_false': { - '()': 'django.utils.log.RequireDebugFalse' - } - }, - 'handlers': { - 'mail_admins': { - 'level': 'ERROR', - 'filters': ['require_debug_false'], - 'class': 'django.utils.log.AdminEmailHandler' - } - }, - 'loggers': { - 'django.request': { - 'handlers': ['mail_admins'], - 'level': 'ERROR', - 'propagate': True, +MIDDLEWARE_CLASSES = ( + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.middleware.csrf.CsrfViewMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.messages.middleware.MessageMiddleware', + # Uncomment the next line for simple clickjacking protection: + # 'django.middleware.clickjacking.XFrameOptionsMiddleware', +) + +ROOT_URLCONF = 'example.urls' + +TEMPLATES = [ + { + 'BACKEND': 'django.template.backends.django.DjangoTemplates', + 'DIRS': [], + 'APP_DIRS': True, + 'OPTIONS': { + 'context_processors': [ + 'django.template.context_processors.debug', + 'django.template.context_processors.request', + 'django.contrib.auth.context_processors.auth', + 'django.contrib.messages.context_processors.messages', + ], }, + }, +] + +WSGI_APPLICATION = 'example.wsgi.application' + +# Database +# https://docs.djangoproject.com/en/1.9/ref/settings/#databases + +DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } } +# Password validation +# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators +AUTH_PASSWORD_VALIDATORS = [ + { + 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + }, + { + 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + }, +] + + +# Internationalization +# https://docs.djangoproject.com/en/1.9/topics/i18n/ + +LANGUAGE_CODE = 'en-us' + +TIME_ZONE = 'UTC' + +USE_I18N = True + +USE_L10N = True + +USE_TZ = True + +SITE_ID = 1 + +# Static files (CSS, JavaScript, Images) +# https://docs.djangoproject.com/en/1.9/howto/static-files/ + +STATIC_URL = '/static/' + +# Additional locations of static files +STATICFILES_DIRS = ( + # Put strings here, like "/home/html/static" or "C:/www/django/static". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + os.path.join(BASE_DIR, "static"), +) ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" \ No newline at end of file diff --git a/example/example/urls.py b/example/example/urls.py index ad18380..ee17edd 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -1,18 +1,15 @@ -from django.conf.urls import patterns, include, url +from django.conf.urls import include, url from django.contrib import admin - +from djadmin2.site import djadmin2_site from blog.views import BlogListView, BlogDetailView admin.autodiscover() +djadmin2_site.autodiscover() -import djadmin2 - -djadmin2.default.autodiscover() - -urlpatterns = patterns('', - url(r'^admin2/', include(djadmin2.default.urls)), +urlpatterns = [ + url(r'^admin2/', include(djadmin2_site.urls)), url(r'^admin/', include(admin.site.urls)), url(r'^blog/', BlogListView.as_view(template_name="blog/blog_list.html"), name='blog_list'), url(r'^blog/detail(?P\d+)/$', BlogDetailView.as_view(template_name="blog/blog_detail.html"), name='blog_detail'), url(r'^$', BlogListView.as_view(template_name="blog/home.html"), name='home'), -) +] diff --git a/example/files/admin2.py b/example/files/admin2.py index 1e6016e..c3dfe6e 100644 --- a/example/files/admin2.py +++ b/example/files/admin2.py @@ -2,9 +2,10 @@ from __future__ import division, absolute_import, unicode_literals import djadmin2 +from djadmin2.site import djadmin2_site from .models import CaptionedFile, UncaptionedFile -djadmin2.default.register(CaptionedFile) -djadmin2.default.register(UncaptionedFile) +djadmin2_site.register(CaptionedFile) +djadmin2_site.register(UncaptionedFile) diff --git a/example/files/models.py b/example/files/models.py index b373326..09158c8 100644 --- a/example/files/models.py +++ b/example/files/models.py @@ -9,8 +9,7 @@ from django.utils.translation import ugettext_lazy as _ @python_2_unicode_compatible class CaptionedFile(models.Model): caption = models.CharField(max_length=200, verbose_name=_('caption')) - publication = models.FileField( - upload_to='media', verbose_name=_('Uploaded File')) + publication = models.FileField(upload_to='media', verbose_name=_('Uploaded File')) def __str__(self): return self.caption @@ -22,11 +21,10 @@ class CaptionedFile(models.Model): @python_2_unicode_compatible class UncaptionedFile(models.Model): - publication = models.FileField( - upload_to='media', verbose_name=_('Uploaded File')) + publication = models.FileField(upload_to='media', verbose_name=_('Uploaded File')) def __str__(self): - return self.publication + return self.publication.name class Meta: verbose_name = _('Uncaptioned File') diff --git a/example/polls/admin2.py b/example/polls/admin2.py index 5ff19ea..ab94abb 100644 --- a/example/polls/admin2.py +++ b/example/polls/admin2.py @@ -1,17 +1,17 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, unicode_literals -import djadmin2 - +from djadmin2.site import djadmin2_site +from djadmin2.types import Admin2TabularInline, ModelAdmin2 from .models import Poll, Choice -class ChoiceInline(djadmin2.Admin2TabularInline): +class ChoiceInline(Admin2TabularInline): model = Choice extra = 3 -class PollAdmin(djadmin2.ModelAdmin2): +class PollAdmin(ModelAdmin2): fieldsets = [ (None, {'fields': ['question']}), ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}), @@ -23,4 +23,4 @@ class PollAdmin(djadmin2.ModelAdmin2): date_hierarchy = 'pub_date' -djadmin2.default.register(Poll, PollAdmin) +djadmin2_site.register(Poll, PollAdmin) diff --git a/requirements.txt b/requirements.txt index b92160e..1ee9877 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,4 @@ django-floppyforms>=1.6.2 django-filter>=0.13.0 django-crispy-forms>=1.3.2 django-debug-toolbar>=0.9.4 -pytz==2014.7 +pytz==2016.4 diff --git a/tox.ini b/tox.ini index c8c6e87..7b3c82c 100644 --- a/tox.ini +++ b/tox.ini @@ -6,10 +6,11 @@ exclude = migrations/*,docs/* [tox] envlist = - py27-{1.8,1.9,master}, - py33-{1.8}, - py34-{1.8,1.9,master}, - py35-{1.8,1.9,master}, + #py27-{1.8,1.9,master}, + #py33-{1.8}, + #py34-{1.8,1.9,master}, + #py35-{1.8,1.9,master}, + py35-{1.8}, [testenv] commands = From babe7a70c7e3b4d2f5683f8d0492b90071a6e6d6 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 8 May 2016 01:31:16 +0200 Subject: [PATCH 23/52] Optimise imports --- djadmin2/actions.py | 4 ++-- djadmin2/admin2.py | 3 +-- djadmin2/apiviews.py | 1 - djadmin2/filters.py | 18 ++++++++---------- djadmin2/forms.py | 11 +++++------ djadmin2/permissions.py | 4 ++-- djadmin2/renderers.py | 2 +- djadmin2/types.py | 13 ++++++------- djadmin2/views.py | 10 ++++------ example/blog/actions.py | 4 ++-- example/blog/migrations/0001_initial.py | 2 +- example/blog/tests/test_apiviews.py | 7 ++++--- example/blog/tests/test_filters.py | 7 +++---- example/blog/tests/test_modelforms.py | 3 +-- example/blog/tests/test_nestedobjects.py | 3 +-- example/blog/tests/test_permissions.py | 4 +--- example/blog/tests/test_views.py | 1 + example/example/urls.py | 3 ++- example/files/admin2.py | 2 -- example/files/tests/test_models.py | 10 +++------- example/files/tests/test_views.py | 5 ++--- example/polls/models.py | 2 +- example/polls/tests/test_models.py | 3 +-- 23 files changed, 52 insertions(+), 70 deletions(-) diff --git a/djadmin2/actions.py b/djadmin2/actions.py index 6718b81..9638726 100644 --- a/djadmin2/actions.py +++ b/djadmin2/actions.py @@ -3,11 +3,11 @@ from __future__ import division, absolute_import, unicode_literals from django.contrib import messages from django.db import router -from django.views.generic import TemplateView from django.utils.encoding import force_text from django.utils.text import capfirst -from django.utils.translation import ugettext_lazy, ungettext, pgettext_lazy from django.utils.translation import ugettext as _ +from django.utils.translation import ugettext_lazy, ungettext, pgettext_lazy +from django.views.generic import TemplateView from . import permissions, utils from .viewmixins import Admin2ModelMixin diff --git a/djadmin2/admin2.py b/djadmin2/admin2.py index f1089f0..5cfece8 100644 --- a/djadmin2/admin2.py +++ b/djadmin2/admin2.py @@ -4,11 +4,10 @@ from __future__ import division, absolute_import, unicode_literals from django.conf import settings from django.contrib.auth.models import Group, User from django.contrib.sites.models import Site - from rest_framework.relations import PrimaryKeyRelatedField -from djadmin2.forms import UserCreationForm, UserChangeForm from djadmin2.apiviews import Admin2APISerializer +from djadmin2.forms import UserCreationForm, UserChangeForm from djadmin2.site import djadmin2_site from djadmin2.types import ModelAdmin2 diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index de4b424..da2a02c 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -2,7 +2,6 @@ from __future__ import division, absolute_import, unicode_literals from django.utils.encoding import force_str - from rest_framework import fields, generics, serializers from rest_framework.response import Response from rest_framework.reverse import reverse diff --git a/djadmin2/filters.py b/djadmin2/filters.py index a2c894f..557b2f7 100644 --- a/djadmin2/filters.py +++ b/djadmin2/filters.py @@ -2,19 +2,17 @@ from __future__ import division, absolute_import, unicode_literals import collections - from itertools import chain -from django import forms -from django.forms.utils import flatatt -from django.utils.html import format_html -from django.utils.encoding import force_text -from django.utils.safestring import mark_safe -from django.forms import widgets as django_widgets -from django.utils import six -from django.utils.translation import ugettext_lazy - import django_filters +from django import forms +from django.forms import widgets as django_widgets +from django.forms.utils import flatatt +from django.utils import six +from django.utils.encoding import force_text +from django.utils.html import format_html +from django.utils.safestring import mark_safe +from django.utils.translation import ugettext_lazy from .utils import type_str diff --git a/djadmin2/forms.py b/djadmin2/forms.py index c5e5646..528e6d7 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -3,17 +3,16 @@ from __future__ import division, absolute_import, unicode_literals from copy import deepcopy +import django +import django.forms +import django.forms.extras.widgets +import django.forms.models +import floppyforms from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm -import django -import django.forms -import django.forms.models -import django.forms.extras.widgets from django.utils.translation import ugettext_lazy -import floppyforms - _WIDGET_COMMON_ATTRIBUTES = ( 'is_hidden', diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index 5384cc7..359aab4 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -20,13 +20,13 @@ from __future__ import division, absolute_import, unicode_literals import logging import re +from django.apps import apps from django.contrib.auth import models as auth_models from django.contrib.contenttypes import models as contenttypes_models -from django.apps import apps from django.utils import six +from django.utils.encoding import python_2_unicode_compatible, force_text from . import utils -from django.utils.encoding import python_2_unicode_compatible, force_text logger = logging.getLogger('djadmin2') diff --git a/djadmin2/renderers.py b/djadmin2/renderers.py index fbfc9b6..614528f 100644 --- a/djadmin2/renderers.py +++ b/djadmin2/renderers.py @@ -9,9 +9,9 @@ import os.path from datetime import date, time, datetime from django.db import models +from django.template.loader import render_to_string from django.utils import formats, timezone from django.utils.encoding import force_text -from django.template.loader import render_to_string from djadmin2 import settings diff --git a/djadmin2/types.py b/djadmin2/types.py index 181976b..14976c8 100644 --- a/djadmin2/types.py +++ b/djadmin2/types.py @@ -1,22 +1,21 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, unicode_literals -from collections import namedtuple import logging import os import sys - -from django.core.urlresolvers import reverse -from django.conf.urls import patterns, url -from django.utils.six import with_metaclass +from collections import namedtuple import extra_views +from django.conf.urls import patterns, url +from django.core.urlresolvers import reverse +from django.utils.six import with_metaclass +from . import actions from . import apiviews from . import settings -from . import views -from . import actions from . import utils +from . import views from .forms import modelform_factory diff --git a/djadmin2/views.py b/djadmin2/views.py index 7bc3292..bd6eb4a 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -2,10 +2,10 @@ from __future__ import division, absolute_import, unicode_literals import operator +from datetime import datetime from functools import reduce -from datetime import datetime - +import extra_views from django.conf import settings from django.contrib.auth.forms import (PasswordChangeForm, AdminPasswordChangeForm) @@ -21,14 +21,12 @@ from django.utils.encoding import force_text from django.utils.text import capfirst from django.utils.translation import ugettext_lazy from django.views import generic -import extra_views - from . import permissions, utils -from .forms import AdminAuthenticationForm -from .viewmixins import Admin2Mixin, Admin2ModelMixin, Admin2ModelFormMixin from .filters import build_list_filter, build_date_filter +from .forms import AdminAuthenticationForm from .models import LogEntry +from .viewmixins import Admin2Mixin, Admin2ModelMixin, Admin2ModelFormMixin class AdminView(object): diff --git a/example/blog/actions.py b/example/blog/actions.py index f090e1a..fcb6e5d 100644 --- a/example/blog/actions.py +++ b/example/blog/actions.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, unicode_literals -from django.utils.translation import ugettext_lazy, pgettext_lazy from django.contrib import messages +from django.utils.translation import ugettext_lazy, pgettext_lazy -from djadmin2.actions import BaseListAction from djadmin2 import permissions +from djadmin2.actions import BaseListAction diff --git a/example/blog/migrations/0001_initial.py b/example/blog/migrations/0001_initial.py index 6c0aa59..73b42b7 100644 --- a/example/blog/migrations/0001_initial.py +++ b/example/blog/migrations/0001_initial.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -from django.db import migrations, models import django.db.models.deletion +from django.db import migrations, models class Migration(migrations.Migration): diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index aca0e8d..6504eaa 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -1,16 +1,17 @@ from __future__ import unicode_literals + +import json + from django.contrib.auth.models import AnonymousUser, User from django.core.exceptions import PermissionDenied from django.core.urlresolvers import reverse from django.test import TestCase from django.test.client import RequestFactory from django.utils.encoding import force_text -import json - +from djadmin2 import ModelAdmin2 from djadmin2 import apiviews from djadmin2 import default -from djadmin2 import ModelAdmin2 from ..models import Post diff --git a/example/blog/tests/test_filters.py b/example/blog/tests/test_filters.py index 660ecc8..c189f08 100644 --- a/example/blog/tests/test_filters.py +++ b/example/blog/tests/test_filters.py @@ -1,16 +1,15 @@ # -*- coding: utf-8 -*- # vim:fenc=utf-8 +import django_filters +from django.core.urlresolvers import reverse from django.test import TestCase from django.test.client import RequestFactory -from django.core.urlresolvers import reverse +import djadmin2.filters as djadmin2_filters from djadmin2.types import ModelAdmin2 from ..models import Post -import djadmin2.filters as djadmin2_filters - -import django_filters class ListFilterBuilderTest(TestCase): diff --git a/example/blog/tests/test_modelforms.py b/example/blog/tests/test_modelforms.py index 7d63b11..c7c8e42 100644 --- a/example/blog/tests/test_modelforms.py +++ b/example/blog/tests/test_modelforms.py @@ -1,10 +1,9 @@ from __future__ import unicode_literals +import floppyforms from django import forms from django.test import TestCase -import floppyforms - from djadmin2.forms import floppify_widget, floppify_form, modelform_factory from ..models import Post diff --git a/example/blog/tests/test_nestedobjects.py b/example/blog/tests/test_nestedobjects.py index 46e4ff1..49ab64f 100644 --- a/example/blog/tests/test_nestedobjects.py +++ b/example/blog/tests/test_nestedobjects.py @@ -2,8 +2,7 @@ from django.db import DEFAULT_DB_ALIAS, router from django.test import TestCase from djadmin2.utils import NestedObjects - -from ..models import Count, Event, EventGuide, Guest, Location +from ..models import Count, Event, EventGuide class NestedObjectsTests(TestCase): diff --git a/example/blog/tests/test_permissions.py b/example/blog/tests/test_permissions.py index 242a00d..d92c1a2 100644 --- a/example/blog/tests/test_permissions.py +++ b/example/blog/tests/test_permissions.py @@ -1,3 +1,4 @@ +from blog.models import Post from django.contrib.auth.models import User, Permission from django.core.urlresolvers import reverse from django.template import Template, Context @@ -5,9 +6,6 @@ from django.test import TestCase from django.test.client import RequestFactory from djadmin2.permissions import TemplatePermissionChecker - -from blog.models import Post - from djadmin2.site import djadmin2_site from djadmin2.types import ModelAdmin2 diff --git a/example/blog/tests/test_views.py b/example/blog/tests/test_views.py index 3301908..539884f 100644 --- a/example/blog/tests/test_views.py +++ b/example/blog/tests/test_views.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals + from datetime import datetime from django.contrib.auth import get_user_model diff --git a/example/example/urls.py b/example/example/urls.py index ee17edd..fe6d458 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -1,7 +1,8 @@ +from blog.views import BlogListView, BlogDetailView from django.conf.urls import include, url from django.contrib import admin + from djadmin2.site import djadmin2_site -from blog.views import BlogListView, BlogDetailView admin.autodiscover() djadmin2_site.autodiscover() diff --git a/example/files/admin2.py b/example/files/admin2.py index c3dfe6e..1a029e9 100644 --- a/example/files/admin2.py +++ b/example/files/admin2.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- from __future__ import division, absolute_import, unicode_literals -import djadmin2 from djadmin2.site import djadmin2_site - from .models import CaptionedFile, UncaptionedFile diff --git a/example/files/tests/test_models.py b/example/files/tests/test_models.py index 4ccc0c5..8fb34dd 100644 --- a/example/files/tests/test_models.py +++ b/example/files/tests/test_models.py @@ -1,12 +1,8 @@ -from django.test import TestCase -from django.utils import timezone - -from files.models import CaptionedFile -from files.models import UncaptionedFile - - from os import path +from django.test import TestCase +from files.models import CaptionedFile + fixture_dir = path.join(path.abspath(path.dirname(__file__)), 'fixtures') diff --git a/example/files/tests/test_views.py b/example/files/tests/test_views.py index d36a456..6751c6a 100644 --- a/example/files/tests/test_views.py +++ b/example/files/tests/test_views.py @@ -1,13 +1,12 @@ +from os import path + from django.contrib.auth import get_user_model from django.core.urlresolvers import reverse from django.test import TestCase, Client -from django.utils import timezone from django.utils.encoding import force_text from ..models import CaptionedFile -from os import path - fixture_dir = path.join(path.abspath(path.dirname(__file__)), 'fixtures') fixture_file = path.join(fixture_dir, 'pubtest.txt') diff --git a/example/polls/models.py b/example/polls/models.py index 44cc4c2..422849e 100644 --- a/example/polls/models.py +++ b/example/polls/models.py @@ -3,9 +3,9 @@ from __future__ import division, absolute_import, unicode_literals import datetime -from django.utils.encoding import python_2_unicode_compatible from django.db import models from django.utils import timezone +from django.utils.encoding import python_2_unicode_compatible from django.utils.translation import ugettext_lazy as _ diff --git a/example/polls/tests/test_models.py b/example/polls/tests/test_models.py index e0487d6..a33fcf2 100644 --- a/example/polls/tests/test_models.py +++ b/example/polls/tests/test_models.py @@ -1,8 +1,7 @@ from django.test import TestCase from django.utils import timezone - -from polls.models import Poll from polls.models import Choice +from polls.models import Poll class PollTestCase(TestCase): From 41133b2b9cea54dad11dfafb631cf44816559af6 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 8 May 2016 01:38:44 +0200 Subject: [PATCH 24/52] Fix imports --- example/blog/tests/test_apiviews.py | 10 +++++----- example/blog/tests/test_filters.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 6504eaa..37ba562 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -9,9 +9,9 @@ from django.test import TestCase from django.test.client import RequestFactory from django.utils.encoding import force_text -from djadmin2 import ModelAdmin2 from djadmin2 import apiviews -from djadmin2 import default +from djadmin2.site import djadmin2_site +from djadmin2.types import ModelAdmin2 from ..models import Post @@ -25,21 +25,21 @@ class APITestCase(TestCase): self.user.save() def get_model_admin(self, model): - return ModelAdmin2(model, default) + return ModelAdmin2(model, djadmin2_site) class IndexAPIViewTest(APITestCase): def test_response_ok(self): request = self.factory.get(reverse('admin2:api_index')) request.user = self.user - view = apiviews.IndexAPIView.as_view(**default.get_api_index_kwargs()) + view = apiviews.IndexAPIView.as_view(**djadmin2_site.get_api_index_kwargs()) response = view(request) self.assertEqual(response.status_code, 200) def test_view_permission(self): request = self.factory.get(reverse('admin2:api_index')) request.user = AnonymousUser() - view = apiviews.IndexAPIView.as_view(**default.get_api_index_kwargs()) + view = apiviews.IndexAPIView.as_view(**djadmin2_site.get_api_index_kwargs()) self.assertRaises(PermissionDenied, view, request) diff --git a/example/blog/tests/test_filters.py b/example/blog/tests/test_filters.py index c189f08..8f3d3bd 100644 --- a/example/blog/tests/test_filters.py +++ b/example/blog/tests/test_filters.py @@ -6,7 +6,7 @@ from django.core.urlresolvers import reverse from django.test import TestCase from django.test.client import RequestFactory -import djadmin2.filters as djadmin2_filters +from djadmin2 import filters as djadmin2_filters from djadmin2.types import ModelAdmin2 from ..models import Post From e7df67e150780d156508f527bee0b68c72f668c4 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 8 May 2016 02:22:49 +0200 Subject: [PATCH 25/52] Update create_view_permission and move signal to AppConfig --- djadmin2/apps.py | 14 +++++++++ djadmin2/models.py | 9 ------ djadmin2/permissions.py | 63 +++++++++++++++++++++++++++-------------- 3 files changed, 56 insertions(+), 30 deletions(-) create mode 100644 djadmin2/apps.py diff --git a/djadmin2/apps.py b/djadmin2/apps.py new file mode 100644 index 0000000..16eab43 --- /dev/null +++ b/djadmin2/apps.py @@ -0,0 +1,14 @@ +from django.apps import AppConfig +from django.db.models.signals import post_migrate +from django.utils.translation import ugettext_lazy as _ + +from djadmin2.permissions import create_view_permissions + + +class Djadmin2Config(AppConfig): + name = 'djadmin2' + verbose_name = _("Django Admin2") + + def ready(self): + post_migrate.connect(create_view_permissions, + dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions") diff --git a/djadmin2/models.py b/djadmin2/models.py index 8c8b81e..0cb781b 100644 --- a/djadmin2/models.py +++ b/djadmin2/models.py @@ -5,13 +5,11 @@ from __future__ import division, absolute_import, unicode_literals from django.conf import settings from django.contrib.contenttypes.models import ContentType from django.db import models -from django.db.models import signals from django.utils.encoding import force_text from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import smart_text from django.utils.translation import ugettext, ugettext_lazy as _ -from . import permissions from .utils import quote @@ -99,10 +97,3 @@ class LogEntry(models.Model): quote(self.object_id) ) return None - -# setup signal handlers here, since ``models.py`` will be imported by django -# for sure if ``djadmin2`` is listed in the ``INSTALLED_APPS``. - -signals.post_migrate.connect( - permissions.create_view_permissions, - dispatch_uid="django-admin2.djadmin2.permissions.create_view_permissions") diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index 359aab4..7624a6c 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -20,15 +20,13 @@ from __future__ import division, absolute_import, unicode_literals import logging import re +from django.db.utils import DEFAULT_DB_ALIAS from django.apps import apps -from django.contrib.auth import models as auth_models -from django.contrib.contenttypes import models as contenttypes_models +from django.core.exceptions import ValidationError +from django.db import router from django.utils import six from django.utils.encoding import python_2_unicode_compatible, force_text -from . import utils - - logger = logging.getLogger('djadmin2') @@ -369,7 +367,7 @@ class TemplatePermissionChecker(object): return force_text(bool(self)) -def create_view_permissions(app_config, created_models=None, verbosity=2, **kwargs): +def create_view_permissions(app_config, verbosity=2, interactive=True, using=DEFAULT_DB_ALIAS, **kwargs): # noqa """ Create 'view' permissions for all models. @@ -377,40 +375,63 @@ def create_view_permissions(app_config, created_models=None, verbosity=2, **kwar Since we want to support read-only views, we need to add our own permission. - Copied from ``django.contrib.auth.management.create_permissions``. - """ - # Is there any reason for doing this import here? + Copied from ``https://github.com/django/django/blob/1.9.6/django/contrib/auth/management/__init__.py#L60``. - app_models = apps.get_models(app_config) + """ + if not app_config.models_module: + return + + try: + Permission = apps.get_model('auth', 'Permission') + except LookupError: + return + + if not router.allow_migrate_model(using, Permission): + return + + from django.contrib.contenttypes.models import ContentType # This will hold the permissions we're looking for as # (content_type, (codename, name)) searched_perms = list() # The codenames and ctypes that should exist. ctypes = set() - for klass in app_models: - ctype = contenttypes_models.ContentType.objects.get_for_model(klass) + for klass in app_config.get_models(): + # Force looking up the content types in the current database + # before creating foreign keys to them. + ctype = ContentType.objects.db_manager(using).get_for_model(klass) ctypes.add(ctype) - - opts = utils.model_options(klass) - perm = ('view_%s' % opts.object_name.lower(), u'Can view %s' % opts.verbose_name_raw) + perm = ('view_%s' % klass.object_name.lower(), u'Can view %s' % klass._meta.verbose_name_raw) searched_perms.append((ctype, perm)) # Find all the Permissions that have a content_type for a model we're # looking for. We don't need to check for codenames since we already have # a list of the ones we're going to create. - all_perms = set(auth_models.Permission.objects.filter( + all_perms = set(Permission.objects.using(using).filter( content_type__in=ctypes, ).values_list( "content_type", "codename" )) perms = [ - auth_models.Permission(codename=codename, name=name, content_type=ctype) - for c_type, (codename, name) in searched_perms - if (c_type.pk, codename) not in all_perms + Permission(codename=codename, name=name, content_type=ct) + for ct, (codename, name) in searched_perms + if (ct.pk, codename) not in all_perms ] - auth_models.Permission.objects.bulk_create(perms) + # Validate the permissions before bulk_creation to avoid cryptic + # database error when the verbose_name is longer than 50 characters + permission_name_max_length = Permission._meta.get_field('name').max_length + verbose_name_max_length = permission_name_max_length - 11 # len('Can change ') prefix + for perm in perms: + if len(perm.name) > permission_name_max_length: + raise ValidationError( + "The verbose_name of %s.%s is longer than %s characters" % ( + perm.content_type.app_label, + perm.content_type.model, + verbose_name_max_length, + ) + ) + Permission.objects.using(using).bulk_create(perms) if verbosity >= 2: for perm in perms: - logger.info("Adding permission '%s'" % perm) + print("Adding permission '%s'" % perm) From 0b923103f38495ea6a723180feeb94b0865719bc Mon Sep 17 00:00:00 2001 From: arthur Date: Thu, 19 May 2016 17:05:21 +0200 Subject: [PATCH 26/52] Replace IPAddressField with GenericIPAddressField --- djadmin2/forms.py | 4 ++-- example/blog/tests/test_modelforms.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index 528e6d7..a128326 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -181,8 +181,8 @@ _django_field_to_floppyform_widget = { _create_widget(floppyforms.widgets.URLInput), django.forms.fields.SlugField: _create_widget(floppyforms.widgets.SlugInput), - #django.forms.fields.IPAddressField: - # _create_widget(floppyforms.widgets.IPAddressInput), + django.forms.fields.GenericIPAddressField: + _create_widget(floppyforms.widgets.IPAddressInput), django.forms.fields.SplitDateTimeField: _create_splitdatetimewidget(floppyforms.widgets.SplitDateTimeWidget), } diff --git a/example/blog/tests/test_modelforms.py b/example/blog/tests/test_modelforms.py index c7c8e42..7b66e98 100644 --- a/example/blog/tests/test_modelforms.py +++ b/example/blog/tests/test_modelforms.py @@ -490,7 +490,7 @@ class FieldWidgetTest(TestCase): def test_ipaddress_field(self): class MyForm(forms.ModelForm): - ipaddress = forms.IPAddressField() + ipaddress = forms.GenericIPAddressField() form_class = modelform_factory(model=Post, form=MyForm, exclude=[]) widget = form_class().fields['ipaddress'].widget From 830bffcf113c86e0f995a0ec0a48a341370ba977 Mon Sep 17 00:00:00 2001 From: arthur Date: Thu, 19 May 2016 18:16:01 +0200 Subject: [PATCH 27/52] Using TextField widget for GenericIPAddressField according to django-floppyforms documentation http://django-floppyforms.readthedocs.io/en/latest/widgets.html --- djadmin2/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index a128326..ef1783e 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -182,7 +182,7 @@ _django_field_to_floppyform_widget = { django.forms.fields.SlugField: _create_widget(floppyforms.widgets.SlugInput), django.forms.fields.GenericIPAddressField: - _create_widget(floppyforms.widgets.IPAddressInput), + _create_widget(floppyforms.widgets.TextInput), django.forms.fields.SplitDateTimeField: _create_splitdatetimewidget(floppyforms.widgets.SplitDateTimeWidget), } From d9a6da6e52f5fc55763f001e86e8d79fdbd656b0 Mon Sep 17 00:00:00 2001 From: arthur Date: Thu, 19 May 2016 18:27:41 +0200 Subject: [PATCH 28/52] Try fix for some blog test --- example/blog/tests/test_modelforms.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/example/blog/tests/test_modelforms.py b/example/blog/tests/test_modelforms.py index 7b66e98..92616ba 100644 --- a/example/blog/tests/test_modelforms.py +++ b/example/blog/tests/test_modelforms.py @@ -102,7 +102,6 @@ class GetFloppyformWidgetTest(TestCase): floppyforms.widgets.HiddenInput) widget = forms.widgets.HiddenInput() - widget.is_hidden = False self.assertExpectWidget( widget, floppyforms.widgets.HiddenInput, @@ -488,13 +487,13 @@ class FieldWidgetTest(TestCase): self.assertTrue(isinstance(widget, floppyforms.widgets.SlugInput)) self.assertEqual(widget.input_type, 'text') - def test_ipaddress_field(self): + def test_genericipaddress_field(self): class MyForm(forms.ModelForm): ipaddress = forms.GenericIPAddressField() form_class = modelform_factory(model=Post, form=MyForm, exclude=[]) widget = form_class().fields['ipaddress'].widget - self.assertTrue(isinstance(widget, floppyforms.widgets.IPAddressInput)) + self.assertTrue(isinstance(widget, floppyforms.widgets.TextInput)) self.assertEqual(widget.input_type, 'text') def test_splitdatetime_field(self): From 5511b4dc8d739d35c160c69761d697a1b6afc720 Mon Sep 17 00:00:00 2001 From: arthur Date: Thu, 19 May 2016 18:50:56 +0200 Subject: [PATCH 29/52] Fix password link in user --- djadmin2/forms.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index ef1783e..a65a0a5 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -11,7 +11,8 @@ import floppyforms from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.forms import UserCreationForm, UserChangeForm -from django.utils.translation import ugettext_lazy +from django.core.urlresolvers import reverse_lazy +from django.utils.translation import ugettext_lazy as _ _WIDGET_COMMON_ATTRIBUTES = ( @@ -268,7 +269,7 @@ def modelform_factory(model, form=django.forms.models.ModelForm, fields=None, # Translators : %(username)s will be replaced by the username_field name # (default : username, but could be email, or something else) -ERROR_MESSAGE = ugettext_lazy( +ERROR_MESSAGE = _( "Please enter the correct %(username)s and password " "for a staff account. Note that both fields may be case-sensitive." ) @@ -281,7 +282,7 @@ class AdminAuthenticationForm(AuthenticationForm): """ error_messages = { - 'required': ugettext_lazy("Please log in again, because your session has expired."), + 'required': _("Please log in again, because your session has expired."), } this_is_the_login_form = django.forms.BooleanField( widget=floppyforms.HiddenInput, @@ -307,5 +308,17 @@ class AdminAuthenticationForm(AuthenticationForm): return self.cleaned_data +class Admin2UserChangeForm(UserChangeForm): + + def __init__(self, *args, **kwargs): + super(Admin2UserChangeForm, self).__init__(*args, **kwargs) + print(self.fields['password'].help_text) + self.fields['password'].help_text = _("Raw passwords are not stored, so there is no way to see " + "this user's password, but you can change the password " + "using this form." % self.get_update_password_url()) + + def get_update_password_url(self): + return reverse_lazy('admin2:password_change', args=[self.instance.pk]) + UserCreationForm = floppify_form(UserCreationForm) -UserChangeForm = floppify_form(UserChangeForm) +UserChangeForm = floppify_form(Admin2UserChangeForm) From a373875c0d0ef64dab21de37ccc85b92dcb3fb4e Mon Sep 17 00:00:00 2001 From: arthur Date: Thu, 19 May 2016 19:21:56 +0200 Subject: [PATCH 30/52] Set return of get_update password_url to default value in UserChangeFrom when no instance --- djadmin2/forms.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index a65a0a5..0cb23d2 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -313,12 +313,12 @@ class Admin2UserChangeForm(UserChangeForm): def __init__(self, *args, **kwargs): super(Admin2UserChangeForm, self).__init__(*args, **kwargs) print(self.fields['password'].help_text) - self.fields['password'].help_text = _("Raw passwords are not stored, so there is no way to see " - "this user's password, but you can change the password " - "using this form." % self.get_update_password_url()) + self.fields['password'].help_text = _("Raw passwords are not stored, so there is no way to see this user's password, but you can change the password using this form." % self.get_update_password_url()) def get_update_password_url(self): - return reverse_lazy('admin2:password_change', args=[self.instance.pk]) - + if self.instance and self.instance.pk: + return reverse_lazy('admin2:password_change', args=[self.instance.pk]) + return 'password/' + UserCreationForm = floppify_form(UserCreationForm) UserChangeForm = floppify_form(Admin2UserChangeForm) From f0a5ac67e48c6ceb21b4f48505ac96e6e57934a4 Mon Sep 17 00:00:00 2001 From: arthur Date: Thu, 19 May 2016 19:27:02 +0200 Subject: [PATCH 31/52] Fix flake8 --- djadmin2/forms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/djadmin2/forms.py b/djadmin2/forms.py index 0cb23d2..ab7c24d 100644 --- a/djadmin2/forms.py +++ b/djadmin2/forms.py @@ -319,6 +319,6 @@ class Admin2UserChangeForm(UserChangeForm): if self.instance and self.instance.pk: return reverse_lazy('admin2:password_change', args=[self.instance.pk]) return 'password/' - + UserCreationForm = floppify_form(UserCreationForm) UserChangeForm = floppify_form(Admin2UserChangeForm) From eafe2ac7c1a0deda2f73afbf2471daaaeecd80d1 Mon Sep 17 00:00:00 2001 From: arthur Date: Thu, 19 May 2016 20:06:53 +0200 Subject: [PATCH 32/52] Update DRF 3 --- djadmin2/apiviews.py | 26 ++++++++++++++++++++++---- djadmin2/types.py | 7 ++++--- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index da2a02c..e76c678 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -16,11 +16,29 @@ API_VERSION = '0.1' class Admin2APISerializer(serializers.HyperlinkedModelSerializer): _default_view_name = 'admin2:%(app_label)s_%(model_name)s_api_detail' - pk = fields.Field() - __unicode__ = fields.Field(source='__str__') + pk = fields.ReadOnlyField() + __unicode__ = fields.ReadOnlyField(source='__str__') + def get_extra_kwargs(self): + extra_kwargs = super(Admin2APISerializer, self).get_extra_kwargs() + extra_kwargs.update({ + 'url': {'view_name': self._get_default_view_name(self.Meta.model)} + }) + return extra_kwargs + + def _get_default_view_name(self, model): + """ + Return the view name to use if 'view_name' is not specified in 'Meta' + """ + model_meta = model._meta + format_kwargs = { + 'app_label': model_meta.app_label, + 'model_name': model_meta.object_name.lower() + } + return self._default_view_name % format_kwargs class Admin2APIMixin(Admin2Mixin): + model = None raise_exception = True def get_serializer_class(self): @@ -90,8 +108,8 @@ class IndexAPIView(Admin2APIMixin, APIView): class ListCreateAPIView(Admin2APIMixin, generics.ListCreateAPIView): - model = None + pass class RetrieveUpdateDestroyAPIView(Admin2APIMixin, generics.RetrieveUpdateDestroyAPIView): - model = None + pass diff --git a/djadmin2/types.py b/djadmin2/types.py index 14976c8..18744f9 100644 --- a/djadmin2/types.py +++ b/djadmin2/types.py @@ -200,9 +200,10 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)): def get_api_list_kwargs(self): kwargs = self.get_default_api_view_kwargs() - # kwargs.update({ - # 'paginate_by': self.list_per_page, - # }) + kwargs.update({ + 'queryset': self.model.objects.all(), + # 'paginate_by': self.list_per_page, + }) return kwargs def get_api_detail_kwargs(self): From fdd938b1ac5433781e7a36046e5bc6320b64868b Mon Sep 17 00:00:00 2001 From: arthur Date: Thu, 19 May 2016 20:07:45 +0200 Subject: [PATCH 33/52] Flake8 in apiviews --- djadmin2/apiviews.py | 1 + 1 file changed, 1 insertion(+) diff --git a/djadmin2/apiviews.py b/djadmin2/apiviews.py index e76c678..e126f5f 100644 --- a/djadmin2/apiviews.py +++ b/djadmin2/apiviews.py @@ -37,6 +37,7 @@ class Admin2APISerializer(serializers.HyperlinkedModelSerializer): } return self._default_view_name % format_kwargs + class Admin2APIMixin(Admin2Mixin): model = None raise_exception = True From fdc5563cba47dfc53c279e63ff8ee79921d27cb2 Mon Sep 17 00:00:00 2001 From: arthur Date: Fri, 20 May 2016 09:19:41 +0200 Subject: [PATCH 34/52] Fix permissions error --- djadmin2/__init__.py | 2 ++ djadmin2/permissions.py | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/djadmin2/__init__.py b/djadmin2/__init__.py index 651dd0b..9e4414b 100644 --- a/djadmin2/__init__.py +++ b/djadmin2/__init__.py @@ -10,3 +10,5 @@ VERSION = __version__ # synonym # Default datetime input and output formats ISO_8601 = 'iso-8601' + +default_app_config = "djadmin2.apps.Djadmin2Config" diff --git a/djadmin2/permissions.py b/djadmin2/permissions.py index 7624a6c..8bbee29 100644 --- a/djadmin2/permissions.py +++ b/djadmin2/permissions.py @@ -20,6 +20,7 @@ from __future__ import division, absolute_import, unicode_literals import logging import re +from django.contrib.auth import get_permission_codename from django.db.utils import DEFAULT_DB_ALIAS from django.apps import apps from django.core.exceptions import ValidationError @@ -400,8 +401,9 @@ def create_view_permissions(app_config, verbosity=2, interactive=True, using=DEF # Force looking up the content types in the current database # before creating foreign keys to them. ctype = ContentType.objects.db_manager(using).get_for_model(klass) + ctypes.add(ctype) - perm = ('view_%s' % klass.object_name.lower(), u'Can view %s' % klass._meta.verbose_name_raw) + perm = (get_permission_codename('view', klass._meta), 'Can view %s' % (klass._meta.verbose_name_raw)) searched_perms.append((ctype, perm)) # Find all the Permissions that have a content_type for a model we're From 00522b6c9e3e23ed2e6792c5397f41918ce032b2 Mon Sep 17 00:00:00 2001 From: arthur Date: Fri, 20 May 2016 09:20:01 +0200 Subject: [PATCH 35/52] Fix test_apiviews (remove a space) --- example/blog/tests/test_apiviews.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/blog/tests/test_apiviews.py b/example/blog/tests/test_apiviews.py index 37ba562..0d4586d 100644 --- a/example/blog/tests/test_apiviews.py +++ b/example/blog/tests/test_apiviews.py @@ -72,7 +72,7 @@ class ListCreateAPIViewTest(APITestCase): response.render() self.assertEqual(response.status_code, 200) - self.assertIn('"__unicode__": "Foo"', force_text(response.content)) + self.assertIn('"__unicode__":"Foo"', force_text(response.content)) def test_pagination(self): request = self.factory.get(reverse('admin2:blog_post_api_list')) From 37be02f1c647a2f7f17508836347c460e0288555 Mon Sep 17 00:00:00 2001 From: arthur Date: Fri, 20 May 2016 10:30:07 +0200 Subject: [PATCH 36/52] New example settings generated from django 1.9 Serve media for better manual testing --- djadmin2/tests/models.py | 0 example/example/settings.py | 37 +++++++++++++++++++++++++++++-------- example/example/urls.py | 7 ++++++- example/example/wsgi.py | 28 ++++++---------------------- example/files/models.py | 4 ++-- 5 files changed, 43 insertions(+), 33 deletions(-) create mode 100644 djadmin2/tests/models.py diff --git a/djadmin2/tests/models.py b/djadmin2/tests/models.py new file mode 100644 index 0000000..e69de29 diff --git a/example/example/settings.py b/example/example/settings.py index 50a4748..cfedad2 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -1,4 +1,15 @@ -# Django settings for example project. +""" +Django settings for example project. + +Generated by 'django-admin startproject' using Django 1.9.6. + +For more information on this file, see +https://docs.djangoproject.com/en/1.9/topics/settings/ + +For the full list of settings and their values, see +https://docs.djangoproject.com/en/1.9/ref/settings/ +""" + import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -16,14 +27,17 @@ DEBUG = True ALLOWED_HOSTS = [] -INSTALLED_APPS = ( + +# Application definition + +INSTALLED_APPS = [ + 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.sites', 'django.contrib.messages', 'django.contrib.staticfiles', - 'django.contrib.admin', # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'floppyforms', @@ -34,18 +48,19 @@ INSTALLED_APPS = ( 'blog', 'files', 'polls' -) +] -MIDDLEWARE_CLASSES = ( +MIDDLEWARE_CLASSES = [ + 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', - # Uncomment the next line for simple clickjacking protection: - # 'django.middleware.clickjacking.XFrameOptionsMiddleware', -) + 'django.middleware.clickjacking.XFrameOptionsMiddleware', +] ROOT_URLCONF = 'example.urls' @@ -67,6 +82,7 @@ TEMPLATES = [ WSGI_APPLICATION = 'example.wsgi.application' + # Database # https://docs.djangoproject.com/en/1.9/ref/settings/#databases @@ -77,8 +93,10 @@ DATABASES = { } } + # Password validation # https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators + AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', @@ -123,4 +141,7 @@ STATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"), ) +MEDIA_ROOT = os.path.join(BASE_DIR) +MEDIA_URL = "/media/" + ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" \ No newline at end of file diff --git a/example/example/urls.py b/example/example/urls.py index fe6d458..89f0c48 100644 --- a/example/example/urls.py +++ b/example/example/urls.py @@ -1,5 +1,9 @@ +from __future__ import unicode_literals + from blog.views import BlogListView, BlogDetailView +from django.conf import settings from django.conf.urls import include, url +from django.conf.urls.static import static from django.contrib import admin from djadmin2.site import djadmin2_site @@ -13,4 +17,5 @@ urlpatterns = [ url(r'^blog/', BlogListView.as_view(template_name="blog/blog_list.html"), name='blog_list'), url(r'^blog/detail(?P\d+)/$', BlogDetailView.as_view(template_name="blog/blog_detail.html"), name='blog_detail'), url(r'^$', BlogListView.as_view(template_name="blog/home.html"), name='home'), -] +] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + diff --git a/example/example/wsgi.py b/example/example/wsgi.py index 9f3e21b..fd6d782 100644 --- a/example/example/wsgi.py +++ b/example/example/wsgi.py @@ -1,32 +1,16 @@ """ WSGI config for example project. -This module contains the WSGI application used by Django's development server -and any production WSGI deployments. It should expose a module-level variable -named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover -this application via the ``WSGI_APPLICATION`` setting. - -Usually you will have the standard Django WSGI application here, but it also -might make sense to replace the whole Django WSGI application with a custom one -that later delegates to the Django one. For example, you could introduce WSGI -middleware here, or combine a Django application with an application of another -framework. +It exposes the WSGI callable as a module-level variable named ``application``. +For more information on this file, see +https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/ """ + import os -# We defer to a DJANGO_SETTINGS_MODULE already in the environment. This breaks -# if running multiple sites in the same mod_wsgi process. To fix this, use -# mod_wsgi daemon mode with each site in its own daemon process, or use -# os.environ["DJANGO_SETTINGS_MODULE"] = "example.settings" +from django.core.wsgi import get_wsgi_application + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "example.settings") -# This application object is used by any WSGI server configured to use this -# file. This includes Django's development server, if the WSGI_APPLICATION -# setting points here. -from django.core.wsgi import get_wsgi_application application = get_wsgi_application() - -# Apply WSGI middleware here. -# from helloworld.wsgi import HelloWorldApplication -# application = HelloWorldApplication(application) diff --git a/example/files/models.py b/example/files/models.py index 09158c8..966c611 100644 --- a/example/files/models.py +++ b/example/files/models.py @@ -9,7 +9,7 @@ from django.utils.translation import ugettext_lazy as _ @python_2_unicode_compatible class CaptionedFile(models.Model): caption = models.CharField(max_length=200, verbose_name=_('caption')) - publication = models.FileField(upload_to='media', verbose_name=_('Uploaded File')) + publication = models.FileField(upload_to='captioned-files', verbose_name=_('Uploaded File')) def __str__(self): return self.caption @@ -21,7 +21,7 @@ class CaptionedFile(models.Model): @python_2_unicode_compatible class UncaptionedFile(models.Model): - publication = models.FileField(upload_to='media', verbose_name=_('Uploaded File')) + publication = models.FileField(upload_to='uncaptioned-files', verbose_name=_('Uploaded File')) def __str__(self): return self.publication.name From 829840f411f73cb18d58f5c0c2a20e06a5b7d991 Mon Sep 17 00:00:00 2001 From: arthur Date: Fri, 20 May 2016 10:54:42 +0200 Subject: [PATCH 37/52] Fix model migrations --- djadmin2/migrations/0001_initial.py | 14 ++--- djadmin2/tests/migrations/0001_initial.py | 62 ++++++++++++++++++++++ djadmin2/tests/migrations/__init__.py | 0 djadmin2/tests/models.py | 50 +++++++++++++++++ djadmin2/tests/test_actions.py | 6 +-- djadmin2/tests/test_admin2tags.py | 17 +----- djadmin2/tests/test_core.py | 13 ++--- djadmin2/tests/test_renderers.py | 8 +-- djadmin2/tests/test_types.py | 6 +-- djadmin2/tests/test_utils.py | 20 +------ example/blog/migrations/0001_initial.py | 28 +++++----- example/db.sqlite3 | Bin 67584 -> 77824 bytes example/example/settings.py | 3 +- example/files/migrations/0001_initial.py | 12 ++--- example/polls/migrations/0001_initial.py | 12 ++--- tox.ini | 11 ++-- 16 files changed, 162 insertions(+), 100 deletions(-) create mode 100644 djadmin2/tests/migrations/0001_initial.py create mode 100644 djadmin2/tests/migrations/__init__.py diff --git a/djadmin2/migrations/0001_initial.py b/djadmin2/migrations/0001_initial.py index bd03761..3931cd3 100644 --- a/djadmin2/migrations/0001_initial.py +++ b/djadmin2/migrations/0001_initial.py @@ -16,19 +16,19 @@ class Migration(migrations.Migration): migrations.CreateModel( name='LogEntry', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('action_time', models.DateTimeField(auto_now=True, verbose_name='action time')), - ('object_id', models.TextField(blank=True, null=True, verbose_name='object id')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('action_time', models.DateTimeField(verbose_name='action time', auto_now=True)), + ('object_id', models.TextField(verbose_name='object id', null=True, blank=True)), ('object_repr', models.CharField(max_length=200, verbose_name='object repr')), ('action_flag', models.PositiveSmallIntegerField(verbose_name='action flag')), - ('change_message', models.TextField(blank=True, verbose_name='change message')), - ('content_type', models.ForeignKey(null=True, related_name='log_entries', to='contenttypes.ContentType', blank=True)), + ('change_message', models.TextField(verbose_name='change message', blank=True)), + ('content_type', models.ForeignKey(related_name='log_entries', null=True, blank=True, to='contenttypes.ContentType')), ('user', models.ForeignKey(related_name='log_entries', to=settings.AUTH_USER_MODEL)), ], options={ - 'verbose_name_plural': 'log entries', - 'ordering': ('-action_time',), 'verbose_name': 'log entry', + 'ordering': ('-action_time',), + 'verbose_name_plural': 'log entries', }, ), ] diff --git a/djadmin2/tests/migrations/0001_initial.py b/djadmin2/tests/migrations/0001_initial.py new file mode 100644 index 0000000..700cd16 --- /dev/null +++ b/djadmin2/tests/migrations/0001_initial.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='BigThing', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ], + ), + migrations.CreateModel( + name='RendererTestModel', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('decimal', models.DecimalField(max_digits=10, decimal_places=5)), + ], + ), + migrations.CreateModel( + name='SmallThing', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ], + ), + migrations.CreateModel( + name='TagsTestsModel', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('field1', models.CharField(max_length=23)), + ('field2', models.CharField(max_length=42, verbose_name='second field')), + ], + options={ + 'verbose_name': 'Tags Test Model', + 'verbose_name_plural': 'Tags Test Models', + }, + ), + migrations.CreateModel( + name='Thing', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ], + ), + migrations.CreateModel( + name='UtilsTestModel', + fields=[ + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('field1', models.CharField(max_length=23)), + ('field2', models.CharField(max_length=42, verbose_name='second field')), + ], + options={ + 'verbose_name': 'Utils Test Model', + 'verbose_name_plural': 'Utils Test Models', + }, + ), + ] diff --git a/djadmin2/tests/migrations/__init__.py b/djadmin2/tests/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/djadmin2/tests/models.py b/djadmin2/tests/models.py index e69de29..22ffe33 100644 --- a/djadmin2/tests/models.py +++ b/djadmin2/tests/models.py @@ -0,0 +1,50 @@ +from django.db import models + + +class Thing(models.Model): + pass + + +class SmallThing(models.Model): + pass + + +class BigThing(models.Model): + pass + + +class TagsTestsModel(models.Model): + + field1 = models.CharField(max_length=23) + field2 = models.CharField('second field', max_length=42) + + def was_published_recently(self): + return True + was_published_recently.boolean = True + was_published_recently.short_description = 'Published recently?' + + class Meta: + verbose_name = "Tags Test Model" + verbose_name_plural = "Tags Test Models" + + +class RendererTestModel(models.Model): + decimal = models.DecimalField(decimal_places=5, max_digits=10) + + +class UtilsTestModel(models.Model): + + field1 = models.CharField(max_length=23) + field2 = models.CharField('second field', max_length=42) + + def simple_method(self): + return 42 + + def was_published_recently(self): + return True + was_published_recently.boolean = True + was_published_recently.short_description = 'Published recently?' + + class Meta: + verbose_name = "Utils Test Model" + verbose_name_plural = "Utils Test Models" diff --git a/djadmin2/tests/test_actions.py b/djadmin2/tests/test_actions.py index f4bd6c4..7a48e55 100644 --- a/djadmin2/tests/test_actions.py +++ b/djadmin2/tests/test_actions.py @@ -1,12 +1,8 @@ -from django.db import models from django.test import TestCase from ..core import Admin2 from ..actions import get_description - - -class Thing(models.Model): - pass +from .models import Thing class TestAction(object): diff --git a/djadmin2/tests/test_admin2tags.py b/djadmin2/tests/test_admin2tags.py index 61c5042..6c3dcb9 100644 --- a/djadmin2/tests/test_admin2tags.py +++ b/djadmin2/tests/test_admin2tags.py @@ -1,25 +1,10 @@ -from django.db import models from django import forms from django.forms.formsets import formset_factory from django.test import TestCase from ..templatetags import admin2_tags from ..views import IndexView - - -class TagsTestsModel(models.Model): - - field1 = models.CharField(max_length=23) - field2 = models.CharField('second field', max_length=42) - - def was_published_recently(self): - return True - was_published_recently.boolean = True - was_published_recently.short_description = 'Published recently?' - - class Meta: - verbose_name = "Tags Test Model" - verbose_name_plural = "Tags Test Models" +from .models import TagsTestsModel class TagsTestForm(forms.Form): diff --git a/djadmin2/tests/test_core.py b/djadmin2/tests/test_core.py index f92fb69..3a61ec0 100644 --- a/djadmin2/tests/test_core.py +++ b/djadmin2/tests/test_core.py @@ -1,17 +1,12 @@ -from django.db import models -from django.core.exceptions import ImproperlyConfigured -from django.test import TestCase from django.contrib.auth.models import Group, User from django.contrib.sites.models import Site +from django.core.exceptions import ImproperlyConfigured +from django.test import TestCase from djadmin2.site import djadmin2_site -from ..types import ModelAdmin2 +from .models import SmallThing from ..core import Admin2 - - -class SmallThing(models.Model): - pass - +from ..types import ModelAdmin2 APP_LABEL, APP_VERBOSE_NAME = 'app_one_label', 'App One Verbose Name' diff --git a/djadmin2/tests/test_renderers.py b/djadmin2/tests/test_renderers.py index a2921e3..964e695 100644 --- a/djadmin2/tests/test_renderers.py +++ b/djadmin2/tests/test_renderers.py @@ -5,15 +5,11 @@ import datetime as dt from decimal import Decimal from django.test import TestCase -from django.db import models -from django.utils.translation import activate from django.utils import six +from django.utils.translation import activate from .. import renderers - - -class RendererTestModel(models.Model): - decimal = models.DecimalField(decimal_places=5, max_digits=10) +from .models import RendererTestModel class BooleanRendererTest(TestCase): diff --git a/djadmin2/tests/test_types.py b/djadmin2/tests/test_types.py index ed0a92d..a3f7d40 100644 --- a/djadmin2/tests/test_types.py +++ b/djadmin2/tests/test_types.py @@ -1,9 +1,9 @@ -from django.db import models from django.test import TestCase from .. import views from ..types import ModelAdmin2, immutable_admin_factory from ..core import Admin2 +from .models import BigThing class ModelAdmin(object): @@ -39,10 +39,6 @@ class ImmutableAdminFactoryTests(TestCase): self.immutable_admin.d -class BigThing(models.Model): - pass - - class ModelAdminTest(TestCase): def setUp(self): diff --git a/djadmin2/tests/test_utils.py b/djadmin2/tests/test_utils.py index c53946d..39767ec 100644 --- a/djadmin2/tests/test_utils.py +++ b/djadmin2/tests/test_utils.py @@ -1,27 +1,9 @@ -from django.db import models from django.test import TestCase from django.utils import six from .. import utils from ..views import IndexView - - -class UtilsTestModel(models.Model): - - field1 = models.CharField(max_length=23) - field2 = models.CharField('second field', max_length=42) - - def simple_method(self): - return 42 - - def was_published_recently(self): - return True - was_published_recently.boolean = True - was_published_recently.short_description = 'Published recently?' - - class Meta: - verbose_name = "Utils Test Model" - verbose_name_plural = "Utils Test Models" +from .models import UtilsTestModel class UtilsTest(TestCase): diff --git a/example/blog/migrations/0001_initial.py b/example/blog/migrations/0001_initial.py index 73b42b7..6761aea 100644 --- a/example/blog/migrations/0001_initial.py +++ b/example/blog/migrations/0001_initial.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals -import django.db.models.deletion from django.db import migrations, models +import django.db.models.deletion class Migration(migrations.Migration): @@ -14,18 +14,18 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Comment', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('body', models.TextField(verbose_name='body')), ], options={ - 'verbose_name_plural': 'comments', 'verbose_name': 'comment', + 'verbose_name_plural': 'comments', }, ), migrations.CreateModel( name='Count', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('num', models.PositiveSmallIntegerField()), ('parent', models.ForeignKey(to='blog.Count', null=True)), ], @@ -33,21 +33,21 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Event', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('date', models.DateTimeField(auto_now_add=True)), ], ), migrations.CreateModel( name='EventGuide', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('event', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='blog.Event')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('event', models.ForeignKey(to='blog.Event', on_delete=django.db.models.deletion.DO_NOTHING)), ], ), migrations.CreateModel( name='Guest', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('name', models.CharField(max_length=255)), ('event', models.OneToOneField(to='blog.Event')), ], @@ -58,27 +58,27 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Location', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('event', models.OneToOneField(to='blog.Event', verbose_name='awesome event')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('event', models.OneToOneField(verbose_name='awesome event', to='blog.Event')), ], ), migrations.CreateModel( name='Post', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('title', models.CharField(max_length=255, verbose_name='title')), ('body', models.TextField(verbose_name='body')), - ('published', models.BooleanField(default=False, verbose_name='published')), + ('published', models.BooleanField(verbose_name='published', default=False)), ('published_date', models.DateField(blank=True, null=True)), ], options={ - 'verbose_name_plural': 'posts', 'verbose_name': 'post', + 'verbose_name_plural': 'posts', }, ), migrations.AddField( model_name='comment', name='post', - field=models.ForeignKey(to='blog.Post', verbose_name='post', related_name='comments'), + field=models.ForeignKey(related_name='comments', verbose_name='post', to='blog.Post'), ), ] diff --git a/example/db.sqlite3 b/example/db.sqlite3 index 186c5e801745251cfb283137403b1028c57eeb47..396feb1cdb5f88f5dc1528ab7f789b2d713f70aa 100644 GIT binary patch literal 77824 zcmeHw3v^t^b=aGk2Q2=7U;z>!766yPatSULi^T#YK@ub(ilRtL{ODszUM+zoxYpuh zEFg(ZYM+H9r)f?aJI- z?8=9DWiT3LkCUCsg@x?9*RqSVSzl6k?DG+aupu0RoRQ+=ED(&v*m}n8yM^5RN@f)- zDk#+)1oz0bTBbwaR=b4u;B>D*#&HJ8bcj>N}@N0P&%BcYLrBV&_ClH-R*QlqIvYNU~J6rkmAo+BBL zC&v;E6-G~Zx{#&zwRxto@x)|&q@IddUdrdUkRv{p7#pdh95-_LEhsjc8c9whYDqC@ z9&-!1#nDae9~~K=7>m_Z813Qd>-nYmO)~)hcxt*DGMt;nsDcdR=@n?}xs|Nddt&EV zNCRcG)aS{=lc~wn=)|ci5OLlTkxXautJ#(GS^+>lQ&>%dF1f|@;#$5;y`hYW@$t!% z0g!Rlk}>9%fr7Asrk9pi3uUz!PbJ4kPFI423zme0TS9grlgp;*~R(Qn`MP0 zlVkD3{S_eOQA^0E65{H9NRdq>jU^NDvHQ&mBYbvR4MOBb^dk6oVd+`hy)-zbQD|A6 z5~I-AMg+ASY6Z8iQ*h?U*yKb!#=(lPWyNixCV6;bax9S=Pccw7>{s^o8B>XPe6(JC z-UtYY|JOuXJTE>bJ}iD*{Ob0CUFXss&>rx3AZUsND>HTJYON4a=9>x4HP(o56%9%0 z*_%tb*=%DqAaE#v-h5#J=E8MVqPL2&VW%qo$S@mf#Xz+x0SC=?E@D=R&Wxi8wCc3j z-1F)hAk49k8se|TUyARG{|)o<-xdE;d_w%D_zm$D@$bZ!#OKAQ#V?6}CVobI5c-B6 z5k>JsVofZGoR}3)iATl5;=GsjX0dpC){o;5!LEg)sOe!j32C_d5taPVh0pKT7Zsg4YnXUq#q<1!3!Dge?yvY<>t~ z(Ab5`8{Rmsn61xK83Ju9ARw|Va*uA>IA}uQG#)TBM9qb1P>D&Mp$=4hel%9vy7H2cd_$-{A!*oXnPzH(O!MC)VMtdKQXW3_AFq>b? z&1LJ`02f5u{2DwZHnl>s2b1}wS$e##Z?PCCQ+>0QoLyR5Ty1QELXNPA(PJ&vY{G~K|)fxt;#fddaUO_Pz{Vzw1mi6=wrD8K@WN7);HtEUhiKa7gxH(s}r4W~e_shgeGB_}|65 z4e_RUMSM$q9!Bb4fpK|}ufwSPHSr7L27iPf9S+a-eHzsn3pS-JQR2F>80qdp3cLSXzicACKmqe%3|f5Tog7?XsO_=kSZ z%H)mzx)w9oBQsg?U)yE|`y|SV|C&ZK7?eng|LS(Lrw1qHv<_s2UKP31j{iWL*|V4V z;=i)n3=c3@%okk0E67wU{!Ma>9sh!S?Tvrl4Ek4l;vdI<&#Jy|_}T;71G~lp_56cI zrE%I2`}x3D#6#U zPt*j0lauVlS}MGJ?(*0=@2*RErfqG#NHxpuBwn7mZ|3sM#nUraU~kYiRh1Hfojqsv zmEM{FCxau!-ZZo|DUlqBB&sWeXZyBL4{`^NMAa6Xssh2jK306j=WoZDnvMLrEnljg zDz&{j;o4mdNh>1|dnfK-J8?Jf|0%EfcL!nXFrUl9M)+K2HM^Qy$l5#Fk$N67xba^N zRt*6EXYiloKe_X|zY~p>!7IbJZTz&KMXlcIeq5NA^|ppUaCn%#@PrcxE;@ySE8Scs zC|u-ig$Cu{S|@?5PWRAncMGMjI&9WK*`G*S4bDupx2c?y}q=R zx5ERdi1qeaiPAm`_-^D@V5f9rPGBKLZp%UYoQ@?a?g_B>3m} zJUe6jq7h=h=Qg8gv0&7vcY=r;cEcjYeQkl@`AJrsaa52yRL)j;Aw7{uC9cO)uDxyd`v#q5 z)Y({uOQ{kjRLvIx1^vYV>1P%c+?H*vdB7Q z6%IJ+xvRnw>aW5*WmGDc&!voujDB*|xYAr1oS)u&8<7M!?)7Zc`e|+J(@lZk^fY_1 z-n-E#zg%=laV+!8XjL|fgyGlzy ztJ&vQ>F`2W#8K##>@s`=V7>MZ=Zkq>dso757`XHbs8Pob=3C?~&l9-~`^y#{CH zj1&)d27*_nS@D{00n@*l=UoIQs-LCD+mP(SO4bZ*S^niszeH5ix2jeg3QC2~ZKklb z48E}*-CI`JlC>3u$q0-B*Z7YIG3r0<0qucZV zd_4w9xNGIbcIO35XDO7{G@WF>cLCi`g;mtDZ%W?osvh?`^XBhK-hOd<7H;kFp3UOF z`o~N4ov~yeqvO|!*3XsbNJOj|El(uzJ=n?tyqm``ruy#W@|=O0-n(*XPVweGc|Fi` z?#&RlEoHp-cXR=hb1{;=x8bjfJGo-1XzX>r(eB^KvEOL-6Z*Uh@qV(MZ{sZ}d(j=L z-7K2@aLQhk6^ETY0w-AP4J}`oST{1cU#l;Z%e4jIP;sNeU8D5wx*q>23N_Fk*p(j8 z@xLppT^p@EKpxQV|LTUIJ+ON{pvV8+Tld;>?ExJBD~vV{xBLtkud<&t+Kj1U>JTmK zt%qqb2=8l2I~TV2l40+%o{y^D;VgaQ^LER6RMzI}c}`Kzo1$kA(rNbV^7d!qplf2B zUyWt-C6;Zey12Hm$;-9N8Mt3YzQSu?L+*cV*3r(k1U}oZjLMVwANAFOWB2|twvh9- zWec7ZUFJ39NPMU=`1Xzf?|5NwFhECu_c#l~xcX(k<{I#7ZsWJ5_~u7||NTAUdo(%b zd29Rc;EZorfi8apIy6J~`@gy~)E>|t*kvB50DDdG23q}~cuV|1d{4Z=zXSJu{ycx4 zKgo~qPWC2ym3^GO#ICXd;|In!jE@*E8c!Q%97pZxsxVkjIFOl}OXGEBr!$Ko_#MLE z{hdGy4_7{$%RZM@mzyCS(;<~U5Con`ymBs^&%$K|E-s0&`8p(@`d-DwCovXZs2%tQ zqLri%eAEKp*w`w`WV8)9hEo-!mD&@*8{Tcr6fT6wSSv8ck5(x4eC$d^PlgtmLFwts zpmcR-h%`fnXtDwo_Hm;h-K^bBz}h=#lIA$1+A3pWUn5ZaB7&%N`i@Pe)KCK!l;A{n zc7|*w2NkLZ&Vi_@jJ8Tr)?zYLCpnar7KbtqlY44`qbF>lSr!@X!QcHgKno8GCEwOn z6+r3!YRRKYuz6GwlfjI2jk>&>*_z-;eZIyWuQX>xS~m=sGCoz>2Px5R7$!z zm5MfJKLgHi3>73ch}Z|70C4U;0QbcJ%ye;pTzn0naUSme{+f7Myd)O*n{c!D10uy= z7ek^$nEb2!)BMA5L-!)|31|5z+|S*_{+7MLzRkV@cW=MUew5u}H`o<+oE=~d##_c8 z7{6is2jgdr_ZZjrnk>cYQHhd^7Ke#er;PUAwRlL-uXT1klXu2Pb$n(mcYCCtL^qavzMo`ve6A_625 zyW;LbatcTe>c^66bbl#n0v#+lXN~9%7cCsBL-Q5efcbIIL zmGru8RGnq|*H%eTVqHBWTqMk*%->%&lfh2c*wkdQKIZBS8!4N=_iG?M-8~)LF$|tE zRk_$*)sbK9o~)Mq;_19-lv-Mak{1|YDT4gWN14@3Ad>GV!<&hTo^2ljbJ*Qfuq2X?0ibo}qmde>HK51*@sqrQ4MXCYyYHLgsnI|%9A@ihoz-l0W4l`YURZz!p+nwvVK-@8OT*Wf=5Cc9 zXn{AwZ*hMCS?xvn;sOj6eob6Msa@sPB^S_O?M*bE|n+khjnGNP2IB+`zQ8 z>-k*aCSLaFzX@{LBBh;`emYSkGC5KiyfC=Y>AYlJerYyChn2)V$%Dm-SRgn!$ktC|9?;|e-S?@M zs6DV#JfO$_ol;X;Pwj!b?*Tpj-+iBIiP{4@#REG2cS=oZJ+%k!z6a|05Pbi0%wV5m zQ^qU&3j8#TFKw-yVqFUEUyQM0(mKq4ZXvfgYQKPHA7TP$rFbcEwMC+iF)5+jIqkz# zy!mk_MZFH@9O%EC3}sOrc(0S9$j5P|mkYc#mjwi~F}P92^e>V=?wVL0e=c zm0N-sQOc=HI7{f;+Beg8D_o_`UC;dt{_m_J`um@)b#-xFD(HtFXi)v^z6;0+-%nUd&0Z$>n3e$%~zI?#`|ks-_7{$#eH^Z zHCyN}`4k6ENy73s|13yqiBRbni4+eVlOKf)xXeWkhc{KuRF{wveL}Z8A?@AxT?!m) zvayx$v5)IBAG~4MzVSCw96bU%B`LNp7wB)~^4UUqZE-fUj54!xh$xFpdW|T{v#pAw zPm|;aMvh6IL;oYi3zPDOf@$)xEW3=#9@JPlg*_bGQddcERQ2DzycK)Q3AuR|-eB|0 zy3V&`J7iQDwc+NOmGd^6XLzOo?IG#s|2yyPv;^&eo$3J{|2wtjwBFhSckTiG{D0>@ z)DpA@cB%*T_`g$YPV22baOWP-@qgz&)DpA@cB%(3{wvrshWID^H~CNVJ@E5;#t9>} zi9MAw^7h(d>`Wkd^e8K?(W)%0tgjYI&-vW|eZspsy8+j>R%qw4v$=%~tkA0;&f@p; zrvt%AgcYMMg{|{{r3O~&cscbf;@j5>b_q@ef&&9=U0$9H>$;2cOLSnm>*tiWP#5Lx zmk3)Lb4v@EoNJrFv!)Jo@99#>5$7(7$PZ6L&4Q!)to3QqRvzt}p35f3Ze+$sE&U|= zSjozCxjgo=w2O$wWuITptz_k{Mdawo%HXBQMn{r)i%rM9>7&K*6I99TgH*|0syB6{ zyuYPAsFgZpKK)D<4wAFZVjFWg%Dqto9<})4jI~VZ$#ZTSLwF3EcV}k#xcd_pxqGBG zwUQH~*~x4&<3^5q{p}{DEq>{3Hwmr6v|j(;q?6NR?SXsU1A6`MUaw(opZ37U9? zv2Qd*d*EL806zaS@%sk+U;k+jXb;?j9@xW7V_+crd}d)epFIp`Vz_?l_5XXY!nH}- z16%Zfj{hw>O0#JX+=Cv_@qZ6isy0b`V2d8W_^&iB7~*~W8~iHV|?fw<-zdsO6 zCfRzzwYk2wn#&h(XMJG_t|*mT>|Wd%?;dP9mThf*-^gY2b8%4HwWFV>%jr#&x36xbc>jH+zVqI0tcuRelRFFK&r@Y}@UXmn zWi01x(|7Ng2?U3SSy5gICbf3%NA{;rmb1WNfBV`;^h+s=tJ!(Dq5a||bq3&u-;zf3 z_`luGPUq7e*!dpN@xSxyP+Ooquw4)6_}{MQbUy8Yo$mpR|1RTW4rJ!$=ymqf znZ*$N4i$2%+5S$Tg@-Gj&1IiUt9PT3j_Hs}9|!_ZBwh*21KHKAn@eJBz7EMZdlTNA z_wY%K#TRM^zJX{Z=_B9dwpEhJXd7@0rz%J*b&vt(nZkt-8EXaR_|Xcbo{wFr z=*iF`GblZM8I-Q>43TEY5KUH~!ai>Fqnow630QjvP0}1saj{j##J)zL_C*9y=U0~2 zmTfYnh8nP-1Sk4(c4Z+~D8P|FHj{%2)dS~1)Ko@WB`Iq$8LE>U%1VnvnTN?ewZPF6 zHqk7LjP~H~{u-c#hlP@F>#7Q%bbqzvQ6<A&pujS01}uaUhXO#2rMN1^ zF%Kz3@gV}FZ7}bMua(RAf9xl0Gxn(;X z5Fx@03E+w*m7;DwrKZEliBl=*=2R-$oc#Xqgz*~Pi+N_GWT zd1!&k z-c>f2!dGD(Ep!!?y?|?%$*9bxlr7DsH1%bR9fWN0qjjidX&zM3mmv~?4AEq*QqTJ2 z$fX#dLbT8r8Ir6@B{!>5#%3KD1lDk@7S(X`p#nD3@Bv_oP1dNQon<&z;e?dM4~HeY zD&EVk6mYQ*?g#crq6Ss)@}d$h#{L1+b-3E9pqshHd6x!qjkRC$sIujXtP`k|?(PGg z-oa`t&}P80C=m_;bueDFNoYoTfi0S>Qp&hPNur!+itLlDN+&m~@`fFF13kbRj#Z&* zZa&n`X6o(+rryB-X^6uqIsp?jwubfsbs!qBDofFoQpg_KBRP~lmIS2>Ch`5>{p^$> z{#1NR{2I&wUKTHj1^%XZN<1J^{BotVBcn6 zVV`6#!@j{Sc7t7Er`RxSH~z->LzpN0C*$83KV|&TUX!I*xe)C%S)7%Lii52t8)UU) zv(-?O?Iw#de?&wiR#|894z-vp%slOUkQi&E8{xaaJk|eL&}1=I+SU(9yyY~TKip=r z2=lex{Y@t8W7Q;@q$a3%PtzTcC?$}%`WsEw%bbSUM@D;_Vu-Z%x4T}6Rl=;MvyY1K zw5A>^!f89*%_bXQ?$)wbVk|?UIqYdLSr4;ka$O|VGkNPIC-~-SL5Z{k$w^iRNq5h} z+B-~WA0@M{HfjlF=2xwfpv1anMlIw6|9q*L40fhQO;k0m$xtI@^Ur=7NKf}nr(R-I zxj0>^Bfr=)nOgFTXYx`bQ7RA3KB`F{XKGSK?W%M#5|DV6)tNa|l2?5bjEWYsdXQ0b zv+CY#GpnP_*F6bBDw!51Z!)XH)aWb>#{WKc+2B>|&)B!vm)R%S&#@n2i|n%aeevt! z-;19R%i;<=73>q7{}KNt|1$p+|0VuG{vMv=kMR3=f`?d^U1lfPVb;Z%@m=HV#%GOR zFkUjAaZeNAcbCZ$EQtM`@2ttk_LwZmI+OvP;~5{7BxQz&^ay#a1Lb+}*Qk=)$wSV~ zSyzd>U6tYBFG##9z`;LAzHdkA4)Q!Ts5TNkk4JD`p-OL4QVI0{^>=kjpm)5F?z}FK z3~aWlW(lqmo0cf6N)UCD8hSHT1*T6BOB4A?UBPyV{Mlp+T3CQ-?{SH=MG%{?zM}=(Wqo0=BUIdL5Mj*0o#aDFgZz$uYp8(H=ie{z8aJ;FKtxPlvpn< zPIcLU1$YS~-6or4^;DEK_p~RaseLA!VjE7DW0I^2EXjR%!?84*A0`Xys90~S8<#Co zS?Hx5!ufyDNE_l+@gea}aa^?WH~44xd--`D@SGu@7G(+rrfXVA z>HgD_N0n&vsNyKyeF}Jb2V1Z#n*j@w5~fLlr_sAgk+8S+_;LvC73EOdH6@j$T37{oTlnXLs!CQk>ge%X6EwcB+LMP__lX0%-AknzZ9x9=PZWsE-t852|GCMdS^d|48YLYCx- zvNIQ#ieAeSkH}0)!4hYvs`X|Xz6P0MlclpAOEoWhnfp0L@N;T;2ph=*G*!M6>i?k4z-L*Pp?t6&n%|y3* zO`=n({}Rx{!=9;wt&f^YbYBFX-a%(RU^8GZG<*T5vB}bz9&)L0bD2NgWr_y915r#w?bmj)i!9y}*`l@8=?SMeU+{r3ZJZ>kN4 zE}Id{_HYfI1uhtNRrFZ3<5Ivv;ru_uo;JjviPvGi{~7VG#QViR5I4jl;Pt18RwXM7te_E zBFz6E|6M@oKkb3P?;Z%v!9X_^|#%#IA9Js9j;Bs9&!J5pbo~o-LGqy zlwIxqjO0?0B$3D^5%3S*Y_soa;OdLGx@csSj89Cry*iH1$jqXC_G}ZJI|XlQzy{9M^fYdDKmvPLs~` z-@Vt~5g;k*F%q>fy}JGP|M&m*yW73H{rC1icjk#yLG~=YQ~aO!4g4SY+xQjyHT)I)rQ~c{ zUbjmQcPNBzgbE}(znsi2<`Q$cY(dTz5{0#uawOa6*|CxHW1jOPC!ZMebd?e6^7MA4 z=DR#8sAo}L^-N8l_e@=Q;t9`_XD22{&dzvFkIi^SE}Wm9n1Z?{$EMC7^>k&E8M(`I zHMu&slw9o%`29Xz4oZ_ND~WV+R!&!D$>ioYDZVf@apuAp7-rN2)haSRpLj#~=-e<8 zQrUU=TK?%YoSQ_lSjf?QqD&JLiLCrgB2cNs4w%1AfG;g#itu{zCJ>gkhw!k&?mp93 z$!IWtDX}82W>WclDwjSqgi}g>V!keWe-Xa1Rcm^(2d3CbhVlEI^C$s%Zv6G}WtJnMV1Ju1mOH zjB73!iI^@+_M!zSL&K2tMJgv4_clKowfw6sCutcRpwFC;mXhZ{r*5Al}!~=$#l>k80z;& z`u$N)Fm%j6bj%+Z@cSd7SU6N?6WT#Srq|aNLjHkxJU$eT`CU}aN-mw=hD10JjQVS- z#6l{)m0_SG76=6Wp&Du!Tpsh8R5rMIYd|O(jz(jH)i$BUJ2IQjEp90=6b#2BBTf*Q z7$Ln11cnl;aIL3SWo`5%gJ(VgCBcmdg3eGl5DuKIf+|jHRYVfWbU|KC6!Vazllejd zY)NGk*Xt0TN?NGy{RzrxbLCz#o z=|m=ZEs>V9i-o1KmH1;rq2OaSsN|wnNl>kX8GfiCo7WVJ$AUxA$0VEJ9UoC|LKH@V zBH)|JUDe^GH>U)tw46<`*ibAK^<#SF(5qmpa~d1+hobR85nAEZTCqtV@DIcyfnYoq zMPRJgYV78Mp-?Ciap7Nwd-HLOzkmz)-|=7J_e16rcoM(m?sa*ti6d}dAka{ay@=j2 z5_3yAb1pR}+v;j@r&><9aEl8MB5n95qbNMhuC8IXghPaEea3CZ9~2s$di_viy%T$@ z$gKcI^GpV$0Vb)o3U@f^LlhZU>uPYfQ>~2LG+RVPC#l?c;vjeYW*hE9mWQu+H5A0f zzYy@x@K5mT@Lc{H{wDrc{LlDv_%rw={38Bq{2}}&_yaI5{5F0Uemi~+|1!P~V?`D( z;aU7<{1ko?KaNk~hj9!C@lkvjdvF`xgX?e=7R8^5Z-_qE_(ky_#7~JI z7e6L`1XS`LN8lzAuwjIn<1~%YG)mH@A(}>L8YXFDh^9fB21wfAr|BR~2S{4qPt&6` z?IUU35t{mF+DlSb4^0o#)JxLZLp1HCX%|UrI%(>mX$MKG57M-qrUyvs+)vXsnzoX( zY9CGAG~G*5M+;5&&=mSALiQ$_Hqx|#q_%pR*3r~OQmK}vH8iazDR$Ddilz>digudX zXevqAi7-h-jGa#M{NF78z5qS{Yxtk=hw=OHJMhnA8DGSY;wbLLd$B|OFY&A5C&Ui} z8~pDsmQHe4m9N4rb+MS5mtA#0a}(NPQO*|{T##)i*>rA>zT~@VH3ll=s?oA@xnj0Z zUk%y)gkQZ+*E)gFLI^9ld|^+OB(%3{4+21>&jF1Nu&|6^*U-v7lWXgGayF#12a|0A3d;E(?}0vv%mg+OT!atWm#XRFKQ zaymywR%WlvF9Z|$rDQNNbcjq2`wxv5&V}Z(D`UB_Co_jq$+MX!pUOWu8l4!I&s<)< zFdA9A`1mu$*|o7$@v+SGnUynzwbaFAWN>CUxe7P@)eSR{f#5*E9|%V`PW=WV1Ho7% z9tya`-xTb^sDKZ_+qr-~EBppzMjh_qVRU1dx;wV?&7H2XyA$zBej76SB-sJkyR`FN&FEJ&9+>9vwS7HZ?kS4t#@pQ&lZK z@a!2wZDM;B7y?Euy3_nw27QBtdR9C`v zM_96+s@2{Y<6ZSAt@J?;JaG%|#0TF0x7us<4ub12osz*3KA$Yeg;Yk?J=#8(=n+KW zGXio#LjDuPcZu)1Q-k20tM^2`-F>cq(~F<_v*=Z?4L=N}X?;(f!`)0AsWlSM; zz*IB(b9YAvdam1yqz++S1?+YN+DbB?eBE3|RU0=|XRDtJmOa{>!BWK$06=QLUpH9Pr|=qRi014cUGa1CMWJpkI!M;>qv=MQTH}?rj;U)v=Xc_!nQIdm6KzPVTuV(h8gD??e4{qt=)*U0s~%?qt;rsXMLo> z;T{=5&$`TxM&-*zrWT`R);gNiHmcu{S7zLiZ<1=;kwe!o{&uVy(d+;pq`6|BkGcP| z-JTzF1ULc@00R8|{{cA3yonrv3JCD~e+6JTnIrH3Ai(ec55P(0P2>nvK!Du;B_Su^ z3*rZa|AhlV_;Cf++d(c>wJCE^0Xi3@?ps2?fh7B*B*KB{LSQy9XD#S3&qY~_bvb1& z%34e&Z`6+@mHnE?%-D^dR=VK-IYeiVtTNX8LGw(JwRlJKLuAc1?1)Fc$SA)wJvX~7 z&lTttP(i*{pqm%MB#ug6l~>>+0BzYjd_rbg_O4dGkWMakdGeWLIt|+(dWzY6Y7y3g zvrF7zqr>c?oXEfiYRN^gL{X*gl?dvr&>0^Yki8Vy4-8aPSOFCn@+p)y4Q0q{`!TY0 z=yPzIq^~rv&*45df=U-G6PVW7Jo6+lEw!?=n=d3?*@ihoy)NtYrnN?5re#*G)azCZ zp4iG@Z5q6?9S62;uvXW045p1By@1^R5q?8}Ke7=c|8WF30=pgoCvJ5r-wgBT|6PBw zc>_5D6%pY5zaliefFrQ$5#arQ*PmkEK#o8~1bF|i2n{db2<&}OZBTOB$2 zigs&3r+FgYTCB@ec0uWr?r_?rlGFve?De5izu_Y=I*abmvUC$IA``o;zKj&(0x(L6 zqk=u7bnQC7|I=Q`6OODvy`f9I5)2N+-zEp>e_lf z&roHo6dgZ8ce7ttqCZm(8lc5EsxMtYMwr68LQ2 z-ly!$f6;OljCLO?(-sQej@m+YiY~JZ*%#=wyKim_@Vz&BdK`2M@OEQjn9P3Z%Um61 z%XRvW4BvVS@OyWQ?_eZsTD5%#_V|Vw=<-{j!(~X<|MLAm`F(&R@L(cfgJz4c_#b{J z@lWv^co?^e{|E6t|3-X=_!jY~SdCsmA4Bg)Z$+0;1XT;)5I!b6FU$*%nel3CwF#)* z>rBqiCrHS%(PY*G#~yO*+XrQRk*aw)EkiH@E&dr{QAi#jd1y>TpIc#7KS^O(6_UZ) zy%$)$gH=Q$%SW^t^|jFY9NSU7M`7KL>Sn6iv8ifL6EJo6*;Umfs2kH_lJzty9I8f* zLv1L@wlx4pyVp(>X~m>{6N8q@mYE*qmg|!v7VJg)_$b&VCfU4RepsHsDeAOV(7m!({8F$!qD%ck(C z8gw321RtZFmpgjrQ-5WNVPeXunb z^hbF$lhUsaq8s||2|pwcw~CM={2HXf2*m#WApTwaHi+-N1aZ9|!9BPRB6xorB7=QW z{7doo#J7uA#h()=#i-aRHbJzoAE1}fr_qPe3+Qs2g_7mj{~t~T4CAKfD%AF3zy7{tCN33*YScGgHR z*CPr^m8bC?ef1LRKy^fv=6hQt)Q4QElos=`N1>{M8uc)(&P8N3l9!TdS<4S8B-ZF| zsrDglUdm`4ep?i(MSKqxHw~)I z)I4?wZBi(zC>aMEsloaX*+8MNjEeONQIk@JGYIkt_^{)hYoCMysLc3Qs}!j6%*a$j zTVfrrs%g#a(BxDoY6WC`siJ1uz*+ABE+k#;`yZjLEg z6(qeMy5A$pDN}j1Q?`%K|C@#;p5X{QPzdn;|3IB?-dK)+2?5go?ZT3P{o+r>M?@Ry zhs+fd%WcV&P6i!ruNSS48?)JJtaNqun?9i)HO~vPMLU`rp3Tj#ZQRg;Jm;EcmT2aE7>8Cr2_~L%Md$Om}?!2^h+N(|?V9FjT#ReVjo*uM5Xh5RznmB!BpwSq2 zbZY54U-`4BF2-he6`{6J5&dk21~!YAOaf&jn&-xe5oB^-hK5&?eyzb{W4 zH<}}GTM*#)|Jwp1uY@CTUn0Q!|9yGdxX~Pe+k${g?1k@t9um-tXju5Hcn%H);gj2| zx>Os5_=|(66wx;GpU&B%D47hz<8hWaXtseZ()ktjY!T6{xetB65LUKu=`b`JD-jdn za40&rkes9927RE6UZYf(VpJQYL_5t9zn5=01bg;$qLRx{PnQTLhGykZG#(48h8MGi zx-v@5yo^3tKpU!myF}uM_HEODd*jHE!#y~Np2O5dw&CP%Aox||)F7N2%84mAy*Ct( zt#FIh&OJ|GI#GhJS)z-@OgzE#nAq1PB5Wb|Op? z5o4#5JpbE-rv$tv{xSM78iT{P-8xRC^f3L}=f-|I1-z0=r}K%qrCe%GW{+d$iC?y8 zM{~Z4h6IUU%X~LuiHmzRSCI2v8$QK>U6P3Y@*kA2Uy>+Rl2oL@{gH zjG-(sVs1oPo*kVy`f1W!k~|lj& z&m6a#BXH{okoSKZ{+2rbhrbJl{KpaC2<#pN$ZQ|B(Nv--o&OW*{GX`!|K20uZ{Qd4 zdv;Hwc}q9~_ca2YSQ0uJx1sJ^414_kWAv|6G_eGAmY~kkfh3__uSI7F=nQoJ4}VF3 z+5aElf5R`sA^&j%I0E-C0uI=fxV{RFVh3zJ3Mll}%X_^KvA-kQ@rav?oJ; zXv3T*Q>e@66P`>Vnc$VLt)y0EWj@?j8am-{`+R7H zWlYioWrXBes=5v{3s*>jfE<5ucViD&0^pX2N5?>PFkN-FV_aOr7 z$B}LFS=-3cTKxK|e<>b~Mg7IqVm6pgU0(^M0{P`@SFdM_{+Y~#e{%ftm8tWSfy<|+ z(o^TpE>E30vo?8ZF+4p!5t$yJxi)?NWO{NrotnBnd2KQ{6Pj9HiB6>BOY@h`<}QVv zx_`7$6H1{X@qBk%3?=8i@qS{2#(E3-HH(9088NE<&IiPdc|66_x(~ihy6) zMGfao;RtX9R0P`bi2h;1#``~g{>QHh@W+1~0gk{fM4%0v0Nvg4wPa=`Ef2t+7|bE* z|E&aa2VUx57Nlb7xJ*r?gkN*d89GNg!Ot?BWMjwy=S6sO#7hNc@zplgNl&aB~1&rnSN;&>K_21!T84bf z;dw0V{gi!U*g?iBy5){%v6z~d`;RK@s!7JyJd8aIdru#*`$Co=g_=Q%`qOpy~Ar4*m8ZiR|96W~0e>HV8UG!R|EUBh zRxWBD|1)3U@jpo*f{73w^_z&?raS3Pf zoA7BI!(QA35rAJ4zYGz8KP3K+ctgA*o)cqYyNJ&dFr6y zdrc1=_h5Ku^8D`EtW*JRnQ1wI<4rbV8v=ENAFmSYc y^1%H^s0fxHuNe!hf`I@ChK9?H<(gquk^cwZyBHz> diff --git a/example/example/settings.py b/example/example/settings.py index cfedad2..bafc798 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -44,6 +44,7 @@ INSTALLED_APPS = [ 'rest_framework', 'crispy_forms', 'djadmin2', + 'djadmin2.tests', 'djadmin2.themes.djadmin2theme_default', 'blog', 'files', @@ -141,7 +142,7 @@ STATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"), ) -MEDIA_ROOT = os.path.join(BASE_DIR) +MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = "/media/" ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" \ No newline at end of file diff --git a/example/files/migrations/0001_initial.py b/example/files/migrations/0001_initial.py index f397eb9..fb58015 100644 --- a/example/files/migrations/0001_initial.py +++ b/example/files/migrations/0001_initial.py @@ -13,24 +13,24 @@ class Migration(migrations.Migration): migrations.CreateModel( name='CaptionedFile', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('caption', models.CharField(max_length=200, verbose_name='caption')), - ('publication', models.FileField(upload_to='media', verbose_name='Uploaded File')), + ('publication', models.FileField(verbose_name='Uploaded File', upload_to='captioned-files')), ], options={ - 'verbose_name_plural': 'Captioned Files', 'verbose_name': 'Captioned File', + 'verbose_name_plural': 'Captioned Files', }, ), migrations.CreateModel( name='UncaptionedFile', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), - ('publication', models.FileField(upload_to='media', verbose_name='Uploaded File')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), + ('publication', models.FileField(verbose_name='Uploaded File', upload_to='uncaptioned-files')), ], options={ - 'verbose_name_plural': 'Uncaptioned Files', 'verbose_name': 'Uncaptioned File', + 'verbose_name_plural': 'Uncaptioned Files', }, ), ] diff --git a/example/polls/migrations/0001_initial.py b/example/polls/migrations/0001_initial.py index 536c8a9..21980fd 100644 --- a/example/polls/migrations/0001_initial.py +++ b/example/polls/migrations/0001_initial.py @@ -13,30 +13,30 @@ class Migration(migrations.Migration): migrations.CreateModel( name='Choice', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('choice_text', models.CharField(max_length=200, verbose_name='choice text')), - ('votes', models.IntegerField(default=0, verbose_name='votes')), + ('votes', models.IntegerField(verbose_name='votes', default=0)), ], options={ - 'verbose_name_plural': 'choices', 'verbose_name': 'choice', + 'verbose_name_plural': 'choices', }, ), migrations.CreateModel( name='Poll', fields=[ - ('id', models.AutoField(auto_created=True, serialize=False, primary_key=True, verbose_name='ID')), + ('id', models.AutoField(serialize=False, verbose_name='ID', auto_created=True, primary_key=True)), ('question', models.CharField(max_length=200, verbose_name='question')), ('pub_date', models.DateTimeField(verbose_name='date published')), ], options={ - 'verbose_name_plural': 'polls', 'verbose_name': 'poll', + 'verbose_name_plural': 'polls', }, ), migrations.AddField( model_name='choice', name='poll', - field=models.ForeignKey(to='polls.Poll', verbose_name='poll'), + field=models.ForeignKey(verbose_name='poll', to='polls.Poll'), ), ] diff --git a/tox.ini b/tox.ini index 7b3c82c..d1c275a 100644 --- a/tox.ini +++ b/tox.ini @@ -6,16 +6,15 @@ exclude = migrations/*,docs/* [tox] envlist = - #py27-{1.8,1.9,master}, - #py33-{1.8}, - #py34-{1.8,1.9,master}, - #py35-{1.8,1.9,master}, - py35-{1.8}, + py27-{1.8,1.9,master}, + py33-{1.8}, + py34-{1.8,1.9,master}, + py35-{1.8,1.9,master}, [testenv] commands = flake8 djadmin2 - py.test --nomigrations [] + py.test [] deps = -rrequirements_test.txt 1.8: Django>=1.8,<1.9 From e74593d4b4fd65ed744d16a88174505a68aab825 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 10:20:17 +0200 Subject: [PATCH 38/52] Use DRF PageNumberPagination to finish fixing DRF tests --- example/example/settings.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/example/example/settings.py b/example/example/settings.py index bafc798..746ad4d 100644 --- a/example/example/settings.py +++ b/example/example/settings.py @@ -145,4 +145,9 @@ STATICFILES_DIRS = ( MEDIA_ROOT = os.path.join(BASE_DIR, 'media') MEDIA_URL = "/media/" -ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" \ No newline at end of file +ADMIN2_THEME_DIRECTORY = "djadmin2theme_default" + +REST_FRAMEWORK = { + 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination', + 'PAGE_SIZE': 10 +} From 39d02f4190f287e816a2fffce9a0c4240f25fd1f Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 10:49:09 +0200 Subject: [PATCH 39/52] Fix no logout on update password for current user --- djadmin2/views.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/djadmin2/views.py b/djadmin2/views.py index bd6eb4a..2240f2a 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -7,6 +7,7 @@ from functools import reduce import extra_views from django.conf import settings +from django.contrib.auth import update_session_auth_hash from django.contrib.auth.forms import (PasswordChangeForm, AdminPasswordChangeForm) from django.contrib.auth.views import (logout as auth_logout, @@ -541,6 +542,11 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView): from django.contrib.auth import get_user_model return get_user_model()._default_manager.all() + def form_valid(self, form): + self.object = form.save() + if self.request.user == self.object: + update_session_auth_hash(self.request, self.object) + return HttpResponseRedirect(self.get_success_url()) class PasswordChangeDoneView(Admin2Mixin, generic.TemplateView): From 702b3cd611aeb2115d89a4372faa45ace8772d08 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 10:52:43 +0200 Subject: [PATCH 40/52] Fix flake8 --- djadmin2/views.py | 1 + 1 file changed, 1 insertion(+) diff --git a/djadmin2/views.py b/djadmin2/views.py index 2240f2a..99c5489 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -548,6 +548,7 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView): update_session_auth_hash(self.request, self.object) return HttpResponseRedirect(self.get_success_url()) + class PasswordChangeDoneView(Admin2Mixin, generic.TemplateView): default_template_name = 'auth/password_change_done.html' From 23ea5d6065e49a7533041f5b1dcf71fa3e2de9ab Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 11:10:33 +0200 Subject: [PATCH 41/52] Fix update password view --- djadmin2/views.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/djadmin2/views.py b/djadmin2/views.py index 99c5489..abc43fd 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -544,8 +544,7 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView): def form_valid(self, form): self.object = form.save() - if self.request.user == self.object: - update_session_auth_hash(self.request, self.object) + update_session_auth_hash(self.request, form.user) return HttpResponseRedirect(self.get_success_url()) From 986d7e7fea66fa00f721ecbc821fd4fcf66e4ae3 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 11:28:10 +0200 Subject: [PATCH 42/52] Replace DATE_FORMAT with %Y-%m-%d in floppyforms tests because it has been hardcoded since 1.3 because it is required by the W3C HTML5 specification. https://github.com/gregmuellegger/django-floppyforms/commit/53d1d8a8d7f00c14bdc6249afd9242ba1748fe3b --- example/blog/tests/test_modelforms.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/example/blog/tests/test_modelforms.py b/example/blog/tests/test_modelforms.py index 92616ba..34cb204 100644 --- a/example/blog/tests/test_modelforms.py +++ b/example/blog/tests/test_modelforms.py @@ -160,7 +160,7 @@ class GetFloppyformWidgetTest(TestCase): forms.DateInput(), floppyforms.DateInput) - widget = forms.widgets.DateInput(format='DATE_FORMAT') + widget = forms.widgets.DateInput(format='%Y-%m-%d') self.assertExpectWidget( widget, floppyforms.widgets.DateInput, @@ -309,13 +309,13 @@ class GetFloppyformWidgetTest(TestCase): floppyforms.widgets.SplitDateTimeWidget) widget = forms.widgets.SplitDateTimeWidget( - date_format='DATE_FORMAT', time_format='TIME_FORMAT') + date_format='%Y-%m-%d', time_format='TIME_FORMAT') new_widget = floppify_widget(widget) self.assertTrue(isinstance( new_widget.widgets[0], floppyforms.widgets.DateInput)) self.assertTrue(isinstance( new_widget.widgets[1], floppyforms.widgets.TimeInput)) - self.assertEqual(new_widget.widgets[0].format, 'DATE_FORMAT') + self.assertEqual(new_widget.widgets[0].format, '%Y-%m-%d') self.assertEqual(new_widget.widgets[1].format, 'TIME_FORMAT') def test_splithiddendatetime_widget(self): @@ -325,13 +325,13 @@ class GetFloppyformWidgetTest(TestCase): floppyforms.widgets.SplitHiddenDateTimeWidget) widget = forms.widgets.SplitHiddenDateTimeWidget( - date_format='DATE_FORMAT', time_format='TIME_FORMAT') + date_format='%Y-%m-%d', time_format='TIME_FORMAT') new_widget = floppify_widget(widget) self.assertTrue(isinstance( new_widget.widgets[0], floppyforms.widgets.DateInput)) self.assertTrue(isinstance( new_widget.widgets[1], floppyforms.widgets.TimeInput)) - self.assertEqual(new_widget.widgets[0].format, 'DATE_FORMAT') + self.assertEqual(new_widget.widgets[0].format, '%Y-%m-%d') self.assertEqual(new_widget.widgets[0].is_hidden, True) self.assertEqual(new_widget.widgets[1].format, 'TIME_FORMAT') self.assertEqual(new_widget.widgets[1].is_hidden, True) From 49523a3ae8f34fc39110c6c79c32be4234673270 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 11:45:08 +0200 Subject: [PATCH 43/52] Update session hach only if user is logged user --- djadmin2/views.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/djadmin2/views.py b/djadmin2/views.py index abc43fd..321909c 100644 --- a/djadmin2/views.py +++ b/djadmin2/views.py @@ -544,7 +544,8 @@ class PasswordChangeView(Admin2Mixin, generic.UpdateView): def form_valid(self, form): self.object = form.save() - update_session_auth_hash(self.request, form.user) + if self.request.user == self.get_object(): + update_session_auth_hash(self.request, form.user) return HttpResponseRedirect(self.get_success_url()) From e9db0de13736f2c8d7e1b26629088c8ee286607f Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 14:23:51 +0200 Subject: [PATCH 44/52] New way to invalidate the users permission cache in test by loading user from database --- example/blog/tests/test_permissions.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/example/blog/tests/test_permissions.py b/example/blog/tests/test_permissions.py index d92c1a2..8034e26 100644 --- a/example/blog/tests/test_permissions.py +++ b/example/blog/tests/test_permissions.py @@ -1,6 +1,7 @@ from blog.models import Post from django.contrib.auth.models import User, Permission from django.core.urlresolvers import reverse +from django.shortcuts import get_object_or_404 from django.template import Template, Context from django.test import TestCase from django.test.client import RequestFactory @@ -47,8 +48,8 @@ class TemplatePermissionTest(TestCase): codename='add_post') self.user.user_permissions.add(post_add_permission) # invalidate the users permission cache - if hasattr(self.user, '_perm_cache'): - del self.user._perm_cache + self.user = get_object_or_404(User, pk=self.user.id) + request.user = self.user result = self.render('{{ permissions.has_add_permission }}', context) self.assertEqual(result, 'True') @@ -120,10 +121,11 @@ class TemplatePermissionTest(TestCase): content_type__app_label='blog', content_type__model='post', codename='add_post') + self.user.user_permissions.add(post_add_permission) # invalidate the users permission cache - if hasattr(self.user, '_perm_cache'): - del self.user._perm_cache + self.user = get_object_or_404(User, pk=self.user.id) + request.user = self.user result = self.render( '{% load admin2_tags %}' @@ -202,8 +204,8 @@ class TemplatePermissionTest(TestCase): self.user.user_permissions.add(user_change_permission) # invalidate the users permission cache - if hasattr(self.user, '_perm_cache'): - del self.user._perm_cache + self.user = get_object_or_404(User, pk=self.user.id) + request.user = self.user result = self.render( '{% load admin2_tags %}' @@ -263,8 +265,8 @@ class TemplatePermissionTest(TestCase): codename='add_post') self.user.user_permissions.add(post_add_permission) # invalidate the users permission cache - if hasattr(self.user, '_perm_cache'): - del self.user._perm_cache + self.user = get_object_or_404(User, pk=self.user.id) + request.user = self.user # object level permission are not supported by default. So this will # return ``False``. From 3b09d8b02d48d6a577b9b90ed02acf0df66c5736 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 14:32:38 +0200 Subject: [PATCH 45/52] Fix lasts tests --- example/blog/tests/test_views.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/example/blog/tests/test_views.py b/example/blog/tests/test_views.py index 539884f..b154bdc 100644 --- a/example/blog/tests/test_views.py +++ b/example/blog/tests/test_views.py @@ -516,18 +516,19 @@ class TestAuthViews(TestCase): def test_change_password_for_myself(self): self.client.login(username=self.user.username, password='password') + request = self.client.post(reverse('admin2:password_change', kwargs={'pk': self.user.pk}), {'old_password': 'password', - 'new_password1': 'user', - 'new_password2': 'user'}) + 'new_password1': 'new_password', + 'new_password2': 'new_password'}) self.assertRedirects(request, reverse('admin2:password_change_done')) self.client.logout() self.assertFalse(self.client.login(username=self.user.username, password='password')) self.assertTrue(self.client.login(username=self.user.username, - password='user')) + password='new_password')) def test_change_password(self): self.client.login(username=self.user.username, @@ -539,8 +540,7 @@ class TestAuthViews(TestCase): request = self.client.post(reverse('admin2:password_change', kwargs={'pk': new_user.pk}), - {'old_password': 'new_user', - 'password1': 'new_user_password', + {'password1': 'new_user_password', 'password2': 'new_user_password'}) self.assertRedirects(request, reverse('admin2:password_change_done')) self.client.logout() From 23c256d6a013545f829a237196144d9726224399 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 14:38:41 +0200 Subject: [PATCH 46/52] Remove import from django.conf.urls.patterns --- djadmin2/types.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/djadmin2/types.py b/djadmin2/types.py index 18744f9..22d522c 100644 --- a/djadmin2/types.py +++ b/djadmin2/types.py @@ -7,7 +7,7 @@ import sys from collections import namedtuple import extra_views -from django.conf.urls import patterns, url +from django.conf.urls import url from django.core.urlresolvers import reverse from django.utils.six import with_metaclass @@ -236,11 +236,10 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)): name=self.get_prefixed_view_name(admin_view.name) ) ) - return patterns('', *pattern_list) + return pattern_list def get_api_urls(self): - return patterns( - '', + return [ url( regex=r'^$', view=self.api_list_view.as_view(**self.get_api_list_kwargs()), @@ -252,7 +251,7 @@ class ModelAdmin2(with_metaclass(ModelAdminBase2)): **self.get_api_detail_kwargs()), name=self.get_prefixed_view_name('api_detail'), ), - ) + ] @property def urls(self): From 4bcc02f735eb9f04c53f3bbcfc899104e32cfb34 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 14:43:35 +0200 Subject: [PATCH 47/52] Add future in requirements to fix python2 compatibility --- requirements.txt | 3 ++- setup.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1ee9877..14427e8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,9 @@ django-extra-views>=0.6.5 django-braces>=1.3.0 -djangorestframework<=3.3.3 +djangorestframework>=3.3.3 django-floppyforms>=1.6.2 django-filter>=0.13.0 django-crispy-forms>=1.3.2 django-debug-toolbar>=0.9.4 +future>=0.15.2 pytz==2016.4 diff --git a/setup.py b/setup.py index e237257..8294491 100644 --- a/setup.py +++ b/setup.py @@ -130,11 +130,12 @@ setup( 'django>=1.8.0', 'django-extra-views>=0.6.5', 'django-braces>=1.3.0', - 'djangorestframework<=3.3.3', + 'djangorestframework>=3.3.3', 'django-floppyforms>=1.6.2', 'django-filter>=0.13.0', 'django-crispy-forms>=1.3.2', - 'pytz==2014.7' + 'pytz==2014.7', + 'future>=0.15.2', ], extras_require={ 'testing': ['pytest', 'pytest-django', 'pytest-ipdb'], From 7d2f5b1251abcb20fee40ef7268f88e1ea315e08 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 14:52:05 +0200 Subject: [PATCH 48/52] Test travis config to allow failures for django master --- .travis.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.travis.yml b/.travis.yml index 7758e31..49235bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,6 +15,13 @@ matrix: env: DJANGO=1.9 - python: "3.3" env: DJANGO=master + allow_failures: + - python: "2.7" + env: DJANGO=master + - python: "3.4" + env: DJANGO=master + - python: "3.5" + env: DJANGO=master install: - pip install tox script: From fb05f25084d6c3fcde36455f4cee2601ca7b35d6 Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 15:06:02 +0200 Subject: [PATCH 49/52] Update docs --- docs/installation.rst | 6 +++--- docs/ref/themes.rst | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 4d39e3b..e8edc43 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -33,17 +33,17 @@ Add djadmin2 urls to your URLconf: .. code-block:: python # urls.py - from django.conf.urls import patterns, include + from django.conf.urls import include import djadmin2 djadmin2.default.autodiscover() - urlpatterns = patterns( + urlpatterns = [ ... url(r'^admin2/', include(djadmin2.default.urls)), - ) + ] Development Installation ========================= diff --git a/docs/ref/themes.rst b/docs/ref/themes.rst index 26eca22..ded7997 100644 --- a/docs/ref/themes.rst +++ b/docs/ref/themes.rst @@ -8,7 +8,7 @@ How To Create a Theme A Django Admin 2 theme is merely a packaged Django app. Here are the necessary steps to create a theme called '*dandy*': -1. Make sure you have Django 1.5 or higher installed. +1. Make sure you have Django 1.8 or higher installed. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: python From 73b95eaa701802d72957bceeb26c094f9f1eb56e Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 15:06:31 +0200 Subject: [PATCH 50/52] Add myself in contributors --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index af669c3..afc7c06 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -52,6 +52,7 @@ Developers * marangonico * Kamil Gałuszka (@galuszkak / galuszkak@gmail.com) * Germano Gabbianelli (@tyrion) +* Arthur (@arthur-wsw / arthur@wallstreetweb.net) Translators ----------- From 336bb3753d573c716cf91ab29ca346a126351b7e Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 15:07:08 +0200 Subject: [PATCH 51/52] Update flake8 requirements --- requirements_test.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_test.txt b/requirements_test.txt index bacdb50..38bcee7 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -1,4 +1,4 @@ -rrequirements.txt -flake8==2.5.0 +flake8==2.5.4 pytest pytest-django \ No newline at end of file From 38198c2d9f785e8ac512d0fa4e7b288766f2489e Mon Sep 17 00:00:00 2001 From: arthur Date: Sun, 22 May 2016 15:15:13 +0200 Subject: [PATCH 52/52] Update History --- HISTORY.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index af185ae..794b0f7 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -1,6 +1,20 @@ History ========= +0.6.2 (?) + +* Fix Django 1.8 issues and add 1.9 compatibility +* Update all dependancies (DRF, floppyforms, filters, ...) +* Regenerate example project to make it django 1.9 compatible +* Update tox and travis and add flake8 +* Rename AdminModel2Mixin to Admin2ModelMixin +* Add migrations +* Replace IPAddressField with GenericIPAddressField +* Fix password link in user admin +* Fix user logout on password change +* Fix tests + + 0.6.1 (2014-02-26) * Fix empty form display