mirror of
https://github.com/django/django.git
synced 2025-10-31 09:41:08 +00:00
Refs #33476 -- Reformatted code with Black.
This commit is contained in:
committed by
Mariusz Felisiak
parent
f68fa8b45d
commit
9c19aff7c7
File diff suppressed because it is too large
Load Diff
@@ -6,24 +6,26 @@ from django.contrib.auth import get_permission_codename
|
||||
from django.contrib.auth.forms import AuthenticationForm
|
||||
from django.core.exceptions import ValidationError
|
||||
|
||||
from . import admin as base_admin, models
|
||||
from . import admin as base_admin
|
||||
from . import models
|
||||
|
||||
PERMISSION_NAME = 'admin_views.%s' % get_permission_codename('change', models.Article._meta)
|
||||
PERMISSION_NAME = "admin_views.%s" % get_permission_codename(
|
||||
"change", models.Article._meta
|
||||
)
|
||||
|
||||
|
||||
class PermissionAdminAuthenticationForm(AuthenticationForm):
|
||||
def confirm_login_allowed(self, user):
|
||||
if not user.is_active or not (user.is_staff or user.has_perm(PERMISSION_NAME)):
|
||||
raise ValidationError('permission denied')
|
||||
raise ValidationError("permission denied")
|
||||
|
||||
|
||||
class HasPermissionAdmin(admin.AdminSite):
|
||||
login_form = PermissionAdminAuthenticationForm
|
||||
|
||||
def has_permission(self, request):
|
||||
return (
|
||||
request.user.is_active and
|
||||
(request.user.is_staff or request.user.has_perm(PERMISSION_NAME))
|
||||
return request.user.is_active and (
|
||||
request.user.is_staff or request.user.has_perm(PERMISSION_NAME)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -7,32 +7,33 @@ from django.contrib.auth.models import User
|
||||
from django.http import HttpResponse
|
||||
from django.urls import path
|
||||
|
||||
from . import admin as base_admin, forms, models
|
||||
from . import admin as base_admin
|
||||
from . import forms, models
|
||||
|
||||
|
||||
class Admin2(admin.AdminSite):
|
||||
app_index_template = 'custom_admin/app_index.html'
|
||||
app_index_template = "custom_admin/app_index.html"
|
||||
login_form = forms.CustomAdminAuthenticationForm
|
||||
login_template = 'custom_admin/login.html'
|
||||
logout_template = 'custom_admin/logout.html'
|
||||
index_template = ['custom_admin/index.html'] # a list, to test fix for #18697
|
||||
password_change_template = 'custom_admin/password_change_form.html'
|
||||
password_change_done_template = 'custom_admin/password_change_done.html'
|
||||
login_template = "custom_admin/login.html"
|
||||
logout_template = "custom_admin/logout.html"
|
||||
index_template = ["custom_admin/index.html"] # a list, to test fix for #18697
|
||||
password_change_template = "custom_admin/password_change_form.html"
|
||||
password_change_done_template = "custom_admin/password_change_done.html"
|
||||
|
||||
# A custom index view.
|
||||
def index(self, request, extra_context=None):
|
||||
return super().index(request, {'foo': '*bar*'})
|
||||
return super().index(request, {"foo": "*bar*"})
|
||||
|
||||
def get_urls(self):
|
||||
return [
|
||||
path('my_view/', self.admin_view(self.my_view), name='my_view'),
|
||||
path("my_view/", self.admin_view(self.my_view), name="my_view"),
|
||||
] + super().get_urls()
|
||||
|
||||
def my_view(self, request):
|
||||
return HttpResponse("Django is a magical pony!")
|
||||
|
||||
def password_change(self, request, extra_context=None):
|
||||
return super().password_change(request, {'spam': 'eggs'})
|
||||
return super().password_change(request, {"spam": "eggs"})
|
||||
|
||||
|
||||
class UserLimitedAdmin(UserAdmin):
|
||||
@@ -43,19 +44,23 @@ class UserLimitedAdmin(UserAdmin):
|
||||
|
||||
|
||||
class CustomPwdTemplateUserAdmin(UserAdmin):
|
||||
change_user_password_template = ['admin/auth/user/change_password.html'] # a list, to test fix for #18697
|
||||
change_user_password_template = [
|
||||
"admin/auth/user/change_password.html"
|
||||
] # a list, to test fix for #18697
|
||||
|
||||
|
||||
class BookAdmin(admin.ModelAdmin):
|
||||
def get_deleted_objects(self, objs, request):
|
||||
return ['a deletable object'], {'books': 1}, set(), []
|
||||
return ["a deletable object"], {"books": 1}, set(), []
|
||||
|
||||
|
||||
site = Admin2(name="admin2")
|
||||
|
||||
site.register(models.Article, base_admin.ArticleAdmin)
|
||||
site.register(models.Book, BookAdmin)
|
||||
site.register(models.Section, inlines=[base_admin.ArticleInline], search_fields=['name'])
|
||||
site.register(
|
||||
models.Section, inlines=[base_admin.ArticleInline], search_fields=["name"]
|
||||
)
|
||||
site.register(models.Thing, base_admin.ThingAdmin)
|
||||
site.register(models.Fabric, base_admin.FabricAdmin)
|
||||
site.register(models.ChapterXtra1, base_admin.ChapterXtra1Admin)
|
||||
@@ -63,5 +68,5 @@ site.register(User, UserLimitedAdmin)
|
||||
site.register(models.UndeletableObject, base_admin.UndeletableObjectAdmin)
|
||||
site.register(models.Simple, base_admin.AttributeErrorRaisingAdmin)
|
||||
|
||||
simple_site = Admin2(name='admin4')
|
||||
simple_site = Admin2(name="admin4")
|
||||
simple_site.register(User, CustomPwdTemplateUserAdmin)
|
||||
|
||||
@@ -4,17 +4,16 @@ from django.core.exceptions import ValidationError
|
||||
|
||||
|
||||
class CustomAdminAuthenticationForm(AdminAuthenticationForm):
|
||||
|
||||
class Media:
|
||||
css = {'all': ('path/to/media.css',)}
|
||||
css = {"all": ("path/to/media.css",)}
|
||||
|
||||
def clean_username(self):
|
||||
username = self.cleaned_data.get('username')
|
||||
if username == 'customform':
|
||||
raise ValidationError('custom form error')
|
||||
username = self.cleaned_data.get("username")
|
||||
if username == "customform":
|
||||
raise ValidationError("custom form error")
|
||||
return username
|
||||
|
||||
|
||||
class MediaActionForm(ActionForm):
|
||||
class Media:
|
||||
js = ['path/to/media.js']
|
||||
js = ["path/to/media.js"]
|
||||
|
||||
@@ -4,9 +4,7 @@ import uuid
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.contenttypes.fields import (
|
||||
GenericForeignKey, GenericRelation,
|
||||
)
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.files.storage import FileSystemStorage
|
||||
@@ -18,6 +16,7 @@ class Section(models.Model):
|
||||
A simple section that links to articles, to test linking to related items
|
||||
in admin views.
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
def __str__(self):
|
||||
@@ -35,26 +34,31 @@ class Article(models.Model):
|
||||
"""
|
||||
A simple article to test admin views. Test backwards compatibility.
|
||||
"""
|
||||
|
||||
title = models.CharField(max_length=100)
|
||||
content = models.TextField()
|
||||
date = models.DateTimeField()
|
||||
section = models.ForeignKey(Section, models.CASCADE, null=True, blank=True)
|
||||
another_section = models.ForeignKey(Section, models.CASCADE, null=True, blank=True, related_name='+')
|
||||
sub_section = models.ForeignKey(Section, models.SET_NULL, null=True, blank=True, related_name='+')
|
||||
another_section = models.ForeignKey(
|
||||
Section, models.CASCADE, null=True, blank=True, related_name="+"
|
||||
)
|
||||
sub_section = models.ForeignKey(
|
||||
Section, models.SET_NULL, null=True, blank=True, related_name="+"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
@admin.display(ordering='date', description='')
|
||||
@admin.display(ordering="date", description="")
|
||||
def model_year(self):
|
||||
return self.date.year
|
||||
|
||||
@admin.display(ordering='-date', description='')
|
||||
@admin.display(ordering="-date", description="")
|
||||
def model_year_reversed(self):
|
||||
return self.date.year
|
||||
|
||||
@property
|
||||
@admin.display(ordering='date')
|
||||
@admin.display(ordering="date")
|
||||
def model_property_year(self):
|
||||
return self.date.year
|
||||
|
||||
@@ -67,14 +71,15 @@ class Book(models.Model):
|
||||
"""
|
||||
A simple book that has chapters.
|
||||
"""
|
||||
name = models.CharField(max_length=100, verbose_name='¿Name?')
|
||||
|
||||
name = models.CharField(max_length=100, verbose_name="¿Name?")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class Promo(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name='¿Name?')
|
||||
name = models.CharField(max_length=100, verbose_name="¿Name?")
|
||||
book = models.ForeignKey(Book, models.CASCADE)
|
||||
author = models.ForeignKey(User, models.SET_NULL, blank=True, null=True)
|
||||
|
||||
@@ -83,33 +88,33 @@ class Promo(models.Model):
|
||||
|
||||
|
||||
class Chapter(models.Model):
|
||||
title = models.CharField(max_length=100, verbose_name='¿Title?')
|
||||
title = models.CharField(max_length=100, verbose_name="¿Title?")
|
||||
content = models.TextField()
|
||||
book = models.ForeignKey(Book, models.CASCADE)
|
||||
|
||||
class Meta:
|
||||
# Use a utf-8 bytestring to ensure it works (see #11710)
|
||||
verbose_name = '¿Chapter?'
|
||||
verbose_name = "¿Chapter?"
|
||||
|
||||
def __str__(self):
|
||||
return self.title
|
||||
|
||||
|
||||
class ChapterXtra1(models.Model):
|
||||
chap = models.OneToOneField(Chapter, models.CASCADE, verbose_name='¿Chap?')
|
||||
xtra = models.CharField(max_length=100, verbose_name='¿Xtra?')
|
||||
chap = models.OneToOneField(Chapter, models.CASCADE, verbose_name="¿Chap?")
|
||||
xtra = models.CharField(max_length=100, verbose_name="¿Xtra?")
|
||||
guest_author = models.ForeignKey(User, models.SET_NULL, blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
return '¿Xtra1: %s' % self.xtra
|
||||
return "¿Xtra1: %s" % self.xtra
|
||||
|
||||
|
||||
class ChapterXtra2(models.Model):
|
||||
chap = models.OneToOneField(Chapter, models.CASCADE, verbose_name='¿Chap?')
|
||||
xtra = models.CharField(max_length=100, verbose_name='¿Xtra?')
|
||||
chap = models.OneToOneField(Chapter, models.CASCADE, verbose_name="¿Chap?")
|
||||
xtra = models.CharField(max_length=100, verbose_name="¿Xtra?")
|
||||
|
||||
def __str__(self):
|
||||
return '¿Xtra2: %s' % self.xtra
|
||||
return "¿Xtra2: %s" % self.xtra
|
||||
|
||||
|
||||
class RowLevelChangePermissionModel(models.Model):
|
||||
@@ -128,7 +133,7 @@ class ModelWithStringPrimaryKey(models.Model):
|
||||
return self.string_pk
|
||||
|
||||
def get_absolute_url(self):
|
||||
return '/dummy/%s/' % self.string_pk
|
||||
return "/dummy/%s/" % self.string_pk
|
||||
|
||||
|
||||
class Color(models.Model):
|
||||
@@ -147,7 +152,7 @@ class Color2(Color):
|
||||
|
||||
class Thing(models.Model):
|
||||
title = models.CharField(max_length=20)
|
||||
color = models.ForeignKey(Color, models.CASCADE, limit_choices_to={'warm': True})
|
||||
color = models.ForeignKey(Color, models.CASCADE, limit_choices_to={"warm": True})
|
||||
pub_date = models.DateField(blank=True, null=True)
|
||||
|
||||
def __str__(self):
|
||||
@@ -178,22 +183,22 @@ class Sketch(models.Model):
|
||||
Inquisition,
|
||||
models.CASCADE,
|
||||
limit_choices_to={
|
||||
'leader__name': 'Palin',
|
||||
'leader__age': 27,
|
||||
'expected': False,
|
||||
"leader__name": "Palin",
|
||||
"leader__age": 27,
|
||||
"expected": False,
|
||||
},
|
||||
)
|
||||
defendant0 = models.ForeignKey(
|
||||
Actor,
|
||||
models.CASCADE,
|
||||
limit_choices_to={'title__isnull': False},
|
||||
related_name='as_defendant0',
|
||||
limit_choices_to={"title__isnull": False},
|
||||
related_name="as_defendant0",
|
||||
)
|
||||
defendant1 = models.ForeignKey(
|
||||
Actor,
|
||||
models.CASCADE,
|
||||
limit_choices_to={'title__isnull': True},
|
||||
related_name='as_defendant1',
|
||||
limit_choices_to={"title__isnull": True},
|
||||
related_name="as_defendant1",
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
@@ -224,7 +229,9 @@ class StumpJoke(models.Model):
|
||||
limit_choices_to=today_callable_dict,
|
||||
related_name="+",
|
||||
)
|
||||
has_fooled_today = models.ManyToManyField(Character, limit_choices_to=today_callable_q, related_name="+")
|
||||
has_fooled_today = models.ManyToManyField(
|
||||
Character, limit_choices_to=today_callable_q, related_name="+"
|
||||
)
|
||||
|
||||
def __str__(self):
|
||||
return self.variation
|
||||
@@ -232,11 +239,14 @@ class StumpJoke(models.Model):
|
||||
|
||||
class Fabric(models.Model):
|
||||
NG_CHOICES = (
|
||||
('Textured', (
|
||||
('x', 'Horizontal'),
|
||||
('y', 'Vertical'),
|
||||
)),
|
||||
('plain', 'Smooth'),
|
||||
(
|
||||
"Textured",
|
||||
(
|
||||
("x", "Horizontal"),
|
||||
("y", "Vertical"),
|
||||
),
|
||||
),
|
||||
("plain", "Smooth"),
|
||||
)
|
||||
surface = models.CharField(max_length=20, choices=NG_CHOICES)
|
||||
|
||||
@@ -260,6 +270,7 @@ class Persona(models.Model):
|
||||
A simple persona associated with accounts, to test inlining of related
|
||||
accounts which inherit from a common accounts class.
|
||||
"""
|
||||
|
||||
name = models.CharField(blank=False, max_length=80)
|
||||
|
||||
def __str__(self):
|
||||
@@ -271,9 +282,10 @@ class Account(models.Model):
|
||||
A simple, generic account encapsulating the information shared by all
|
||||
types of accounts.
|
||||
"""
|
||||
|
||||
username = models.CharField(blank=False, max_length=80)
|
||||
persona = models.ForeignKey(Persona, models.CASCADE, related_name="accounts")
|
||||
servicename = 'generic service'
|
||||
servicename = "generic service"
|
||||
|
||||
def __str__(self):
|
||||
return "%s: %s" % (self.servicename, self.username)
|
||||
@@ -281,12 +293,14 @@ class Account(models.Model):
|
||||
|
||||
class FooAccount(Account):
|
||||
"""A service-specific account of type Foo."""
|
||||
servicename = 'foo'
|
||||
|
||||
servicename = "foo"
|
||||
|
||||
|
||||
class BarAccount(Account):
|
||||
"""A service-specific account of type Bar."""
|
||||
servicename = 'bar'
|
||||
|
||||
servicename = "bar"
|
||||
|
||||
|
||||
class Subscriber(models.Model):
|
||||
@@ -313,11 +327,13 @@ class Podcast(Media):
|
||||
release_date = models.DateField()
|
||||
|
||||
class Meta:
|
||||
ordering = ('release_date',) # overridden in PodcastAdmin
|
||||
ordering = ("release_date",) # overridden in PodcastAdmin
|
||||
|
||||
|
||||
class Vodcast(Media):
|
||||
media = models.OneToOneField(Media, models.CASCADE, primary_key=True, parent_link=True)
|
||||
media = models.OneToOneField(
|
||||
Media, models.CASCADE, primary_key=True, parent_link=True
|
||||
)
|
||||
released = models.BooleanField(default=False)
|
||||
|
||||
|
||||
@@ -325,8 +341,8 @@ class Parent(models.Model):
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
def clean(self):
|
||||
if self.name == '_invalid':
|
||||
raise ValidationError('invalid')
|
||||
if self.name == "_invalid":
|
||||
raise ValidationError("invalid")
|
||||
|
||||
|
||||
class Child(models.Model):
|
||||
@@ -334,19 +350,20 @@ class Child(models.Model):
|
||||
name = models.CharField(max_length=30, blank=True)
|
||||
|
||||
def clean(self):
|
||||
if self.name == '_invalid':
|
||||
raise ValidationError('invalid')
|
||||
if self.name == "_invalid":
|
||||
raise ValidationError("invalid")
|
||||
|
||||
|
||||
class PKChild(models.Model):
|
||||
"""
|
||||
Used to check autocomplete to_field resolution when ForeignKey is PK.
|
||||
"""
|
||||
|
||||
parent = models.ForeignKey(Parent, models.CASCADE, primary_key=True)
|
||||
name = models.CharField(max_length=128)
|
||||
|
||||
class Meta:
|
||||
ordering = ['parent']
|
||||
ordering = ["parent"]
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -370,7 +387,7 @@ class Gallery(models.Model):
|
||||
|
||||
class Picture(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
image = models.FileField(storage=temp_storage, upload_to='test_upload')
|
||||
image = models.FileField(storage=temp_storage, upload_to="test_upload")
|
||||
gallery = models.ForeignKey(Gallery, models.CASCADE, related_name="pictures")
|
||||
|
||||
|
||||
@@ -384,7 +401,7 @@ class Language(models.Model):
|
||||
return self.iso
|
||||
|
||||
class Meta:
|
||||
ordering = ('iso',)
|
||||
ordering = ("iso",)
|
||||
|
||||
|
||||
# a base class for Recommender and Recommendation
|
||||
@@ -446,10 +463,10 @@ class Category(models.Model):
|
||||
order = models.PositiveIntegerField()
|
||||
|
||||
class Meta:
|
||||
ordering = ('order',)
|
||||
ordering = ("order",)
|
||||
|
||||
def __str__(self):
|
||||
return '%s:o%s' % (self.id, self.order)
|
||||
return "%s:o%s" % (self.id, self.order)
|
||||
|
||||
|
||||
def link_posted_default():
|
||||
@@ -476,12 +493,16 @@ class PrePopulatedSubPost(models.Model):
|
||||
|
||||
|
||||
class Post(models.Model):
|
||||
title = models.CharField(max_length=100, help_text='Some help text for the title (with Unicode ŠĐĆŽćžšđ)')
|
||||
content = models.TextField(help_text='Some help text for the content (with Unicode ŠĐĆŽćžšđ)')
|
||||
title = models.CharField(
|
||||
max_length=100, help_text="Some help text for the title (with Unicode ŠĐĆŽćžšđ)"
|
||||
)
|
||||
content = models.TextField(
|
||||
help_text="Some help text for the content (with Unicode ŠĐĆŽćžšđ)"
|
||||
)
|
||||
readonly_content = models.TextField()
|
||||
posted = models.DateField(
|
||||
default=datetime.date.today,
|
||||
help_text='Some help text for the date (with Unicode ŠĐĆŽćžšđ)',
|
||||
help_text="Some help text for the date (with Unicode ŠĐĆŽćžšđ)",
|
||||
)
|
||||
public = models.BooleanField(null=True, blank=True)
|
||||
|
||||
@@ -519,7 +540,7 @@ class FunkyTag(models.Model):
|
||||
name = models.CharField(max_length=25)
|
||||
content_type = models.ForeignKey(ContentType, models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
content_object = GenericForeignKey("content_type", "object_id")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -527,8 +548,8 @@ class FunkyTag(models.Model):
|
||||
|
||||
class Plot(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
team_leader = models.ForeignKey(Villain, models.CASCADE, related_name='lead_plots')
|
||||
contact = models.ForeignKey(Villain, models.CASCADE, related_name='contact_plots')
|
||||
team_leader = models.ForeignKey(Villain, models.CASCADE, related_name="lead_plots")
|
||||
contact = models.ForeignKey(Villain, models.CASCADE, related_name="contact_plots")
|
||||
tags = GenericRelation(FunkyTag)
|
||||
|
||||
def __str__(self):
|
||||
@@ -549,7 +570,8 @@ class PlotProxy(Plot):
|
||||
|
||||
|
||||
class SecretHideout(models.Model):
|
||||
""" Secret! Not registered with the admin! """
|
||||
"""Secret! Not registered with the admin!"""
|
||||
|
||||
location = models.CharField(max_length=100)
|
||||
villain = models.ForeignKey(Villain, models.CASCADE)
|
||||
|
||||
@@ -558,7 +580,8 @@ class SecretHideout(models.Model):
|
||||
|
||||
|
||||
class SuperSecretHideout(models.Model):
|
||||
""" Secret! Not registered with the admin! """
|
||||
"""Secret! Not registered with the admin!"""
|
||||
|
||||
location = models.CharField(max_length=100)
|
||||
supervillain = models.ForeignKey(SuperVillain, models.CASCADE)
|
||||
|
||||
@@ -568,7 +591,7 @@ class SuperSecretHideout(models.Model):
|
||||
|
||||
class Bookmark(models.Model):
|
||||
name = models.CharField(max_length=60)
|
||||
tag = GenericRelation(FunkyTag, related_query_name='bookmark')
|
||||
tag = GenericRelation(FunkyTag, related_query_name="bookmark")
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -576,7 +599,7 @@ class Bookmark(models.Model):
|
||||
|
||||
class CyclicOne(models.Model):
|
||||
name = models.CharField(max_length=25)
|
||||
two = models.ForeignKey('CyclicTwo', models.CASCADE)
|
||||
two = models.ForeignKey("CyclicTwo", models.CASCADE)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
@@ -599,7 +622,7 @@ class Topping(models.Model):
|
||||
|
||||
class Pizza(models.Model):
|
||||
name = models.CharField(max_length=20)
|
||||
toppings = models.ManyToManyField('Topping', related_name='pizzas')
|
||||
toppings = models.ManyToManyField("Topping", related_name="pizzas")
|
||||
|
||||
|
||||
# Pizza's ModelAdmin has readonly_fields = ['toppings'].
|
||||
@@ -634,7 +657,7 @@ class Employee(Person):
|
||||
code = models.CharField(max_length=20)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
ordering = ["name"]
|
||||
|
||||
|
||||
class WorkHour(models.Model):
|
||||
@@ -646,6 +669,7 @@ class Manager(Employee):
|
||||
"""
|
||||
A multi-layer MTI child.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@@ -658,7 +682,7 @@ class Question(models.Model):
|
||||
question = models.CharField(max_length=20)
|
||||
posted = models.DateField(default=datetime.date.today)
|
||||
expires = models.DateTimeField(null=True, blank=True)
|
||||
related_questions = models.ManyToManyField('self')
|
||||
related_questions = models.ManyToManyField("self")
|
||||
uuid = models.UUIDField(default=uuid.uuid4, unique=True)
|
||||
|
||||
def __str__(self):
|
||||
@@ -668,12 +692,15 @@ class Question(models.Model):
|
||||
class Answer(models.Model):
|
||||
question = models.ForeignKey(Question, models.PROTECT)
|
||||
question_with_to_field = models.ForeignKey(
|
||||
Question, models.SET_NULL,
|
||||
blank=True, null=True, to_field='uuid',
|
||||
related_name='uuid_answers',
|
||||
limit_choices_to=~models.Q(question__istartswith='not'),
|
||||
Question,
|
||||
models.SET_NULL,
|
||||
blank=True,
|
||||
null=True,
|
||||
to_field="uuid",
|
||||
related_name="uuid_answers",
|
||||
limit_choices_to=~models.Q(question__istartswith="not"),
|
||||
)
|
||||
related_answers = models.ManyToManyField('self')
|
||||
related_answers = models.ManyToManyField("self")
|
||||
answer = models.CharField(max_length=20)
|
||||
|
||||
def __str__(self):
|
||||
@@ -692,17 +719,19 @@ class Reservation(models.Model):
|
||||
|
||||
class FoodDelivery(models.Model):
|
||||
DRIVER_CHOICES = (
|
||||
('bill', 'Bill G'),
|
||||
('steve', 'Steve J'),
|
||||
("bill", "Bill G"),
|
||||
("steve", "Steve J"),
|
||||
)
|
||||
RESTAURANT_CHOICES = (
|
||||
('indian', 'A Taste of India'),
|
||||
('thai', 'Thai Pography'),
|
||||
('pizza', 'Pizza Mama'),
|
||||
("indian", "A Taste of India"),
|
||||
("thai", "Thai Pography"),
|
||||
("pizza", "Pizza Mama"),
|
||||
)
|
||||
reference = models.CharField(max_length=100)
|
||||
driver = models.CharField(max_length=100, choices=DRIVER_CHOICES, blank=True)
|
||||
restaurant = models.CharField(max_length=100, choices=RESTAURANT_CHOICES, blank=True)
|
||||
restaurant = models.CharField(
|
||||
max_length=100, choices=RESTAURANT_CHOICES, blank=True
|
||||
)
|
||||
|
||||
class Meta:
|
||||
unique_together = (("driver", "restaurant"),)
|
||||
@@ -761,6 +790,7 @@ class PrePopulatedPostLargeSlug(models.Model):
|
||||
be localized in prepopulated_fields_js.html or it might end up breaking
|
||||
the JavaScript (ie, using THOUSAND_SEPARATOR ends up with maxLength=1,000)
|
||||
"""
|
||||
|
||||
title = models.CharField(max_length=100)
|
||||
published = models.BooleanField(default=False)
|
||||
# `db_index=False` because MySQL cannot index large CharField (#21196).
|
||||
@@ -776,7 +806,7 @@ class AdminOrderedModelMethod(models.Model):
|
||||
order = models.IntegerField()
|
||||
stuff = models.CharField(max_length=200)
|
||||
|
||||
@admin.display(ordering='order')
|
||||
@admin.display(ordering="order")
|
||||
def some_order(self):
|
||||
return self.order
|
||||
|
||||
@@ -803,8 +833,8 @@ class MainPrepopulated(models.Model):
|
||||
pubdate = models.DateField()
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=(('option one', 'Option One'),
|
||||
('option two', 'Option Two')))
|
||||
choices=(("option one", "Option One"), ("option two", "Option Two")),
|
||||
)
|
||||
slug1 = models.SlugField(blank=True)
|
||||
slug2 = models.SlugField(blank=True)
|
||||
slug3 = models.SlugField(blank=True, allow_unicode=True)
|
||||
@@ -813,13 +843,13 @@ class MainPrepopulated(models.Model):
|
||||
class RelatedPrepopulated(models.Model):
|
||||
parent = models.ForeignKey(MainPrepopulated, models.CASCADE)
|
||||
name = models.CharField(max_length=75)
|
||||
fk = models.ForeignKey('self', models.CASCADE, blank=True, null=True)
|
||||
m2m = models.ManyToManyField('self', blank=True)
|
||||
fk = models.ForeignKey("self", models.CASCADE, blank=True, null=True)
|
||||
m2m = models.ManyToManyField("self", blank=True)
|
||||
pubdate = models.DateField()
|
||||
status = models.CharField(
|
||||
max_length=20,
|
||||
choices=(('option one', 'Option One'),
|
||||
('option two', 'Option Two')))
|
||||
choices=(("option one", "Option One"), ("option two", "Option Two")),
|
||||
)
|
||||
slug1 = models.SlugField(max_length=50)
|
||||
slug2 = models.SlugField(max_length=60)
|
||||
|
||||
@@ -829,6 +859,7 @@ class UnorderedObject(models.Model):
|
||||
Model without any defined `Meta.ordering`.
|
||||
Refs #16819.
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
bool = models.BooleanField(default=True)
|
||||
|
||||
@@ -838,6 +869,7 @@ class UndeletableObject(models.Model):
|
||||
Model whose show_delete in admin change_view has been disabled
|
||||
Refs #10057.
|
||||
"""
|
||||
|
||||
name = models.CharField(max_length=255)
|
||||
|
||||
|
||||
@@ -862,8 +894,9 @@ class Simple(models.Model):
|
||||
|
||||
class Choice(models.Model):
|
||||
choice = models.IntegerField(
|
||||
blank=True, null=True,
|
||||
choices=((1, 'Yes'), (0, 'No'), (None, 'No opinion')),
|
||||
blank=True,
|
||||
null=True,
|
||||
choices=((1, "Yes"), (0, "No"), (None, "No opinion")),
|
||||
)
|
||||
|
||||
|
||||
@@ -873,6 +906,7 @@ class ParentWithDependentChildren(models.Model):
|
||||
Model where the validation of child foreign-key relationships depends
|
||||
on validation of the parent
|
||||
"""
|
||||
|
||||
some_required_info = models.PositiveIntegerField()
|
||||
family_name = models.CharField(max_length=255, blank=False)
|
||||
|
||||
@@ -883,6 +917,7 @@ class DependentChild(models.Model):
|
||||
Model that depends on validation of the parent class for one of its
|
||||
fields to validate during clean
|
||||
"""
|
||||
|
||||
parent = models.ForeignKey(ParentWithDependentChildren, models.CASCADE)
|
||||
family_name = models.CharField(max_length=255)
|
||||
|
||||
@@ -901,27 +936,27 @@ class FilteredManager(models.Model):
|
||||
|
||||
|
||||
class EmptyModelVisible(models.Model):
|
||||
""" See ticket #11277. """
|
||||
"""See ticket #11277."""
|
||||
|
||||
|
||||
class EmptyModelHidden(models.Model):
|
||||
""" See ticket #11277. """
|
||||
"""See ticket #11277."""
|
||||
|
||||
|
||||
class EmptyModelMixin(models.Model):
|
||||
""" See ticket #11277. """
|
||||
"""See ticket #11277."""
|
||||
|
||||
|
||||
class State(models.Model):
|
||||
name = models.CharField(max_length=100, verbose_name='State verbose_name')
|
||||
name = models.CharField(max_length=100, verbose_name="State verbose_name")
|
||||
|
||||
|
||||
class City(models.Model):
|
||||
state = models.ForeignKey(State, models.CASCADE)
|
||||
name = models.CharField(max_length=100, verbose_name='City verbose_name')
|
||||
name = models.CharField(max_length=100, verbose_name="City verbose_name")
|
||||
|
||||
def get_absolute_url(self):
|
||||
return '/dummy/%s/' % self.pk
|
||||
return "/dummy/%s/" % self.pk
|
||||
|
||||
|
||||
class Restaurant(models.Model):
|
||||
@@ -929,7 +964,7 @@ class Restaurant(models.Model):
|
||||
name = models.CharField(max_length=100)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return '/dummy/%s/' % self.pk
|
||||
return "/dummy/%s/" % self.pk
|
||||
|
||||
|
||||
class Worker(models.Model):
|
||||
@@ -947,8 +982,8 @@ class ParentWithFK(models.Model):
|
||||
fk = models.ForeignKey(
|
||||
ReferencedByParent,
|
||||
models.CASCADE,
|
||||
to_field='name',
|
||||
related_name='hidden+',
|
||||
to_field="name",
|
||||
related_name="hidden+",
|
||||
)
|
||||
|
||||
|
||||
@@ -970,8 +1005,8 @@ class InlineReference(models.Model):
|
||||
fk = models.ForeignKey(
|
||||
ReferencedByInline,
|
||||
models.CASCADE,
|
||||
to_field='name',
|
||||
related_name='hidden+',
|
||||
to_field="name",
|
||||
related_name="hidden+",
|
||||
)
|
||||
|
||||
|
||||
@@ -981,12 +1016,12 @@ class Recipe(models.Model):
|
||||
|
||||
class Ingredient(models.Model):
|
||||
iname = models.CharField(max_length=20, unique=True)
|
||||
recipes = models.ManyToManyField(Recipe, through='RecipeIngredient')
|
||||
recipes = models.ManyToManyField(Recipe, through="RecipeIngredient")
|
||||
|
||||
|
||||
class RecipeIngredient(models.Model):
|
||||
ingredient = models.ForeignKey(Ingredient, models.CASCADE, to_field='iname')
|
||||
recipe = models.ForeignKey(Recipe, models.CASCADE, to_field='rname')
|
||||
ingredient = models.ForeignKey(Ingredient, models.CASCADE, to_field="iname")
|
||||
recipe = models.ForeignKey(Recipe, models.CASCADE, to_field="rname")
|
||||
|
||||
|
||||
# Model for #23839
|
||||
@@ -1008,7 +1043,7 @@ class ImplicitlyGeneratedPK(models.Model):
|
||||
class ReferencedByGenRel(models.Model):
|
||||
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
|
||||
object_id = models.PositiveIntegerField()
|
||||
content_object = GenericForeignKey('content_type', 'object_id')
|
||||
content_object = GenericForeignKey("content_type", "object_id")
|
||||
|
||||
|
||||
class GenRelReference(models.Model):
|
||||
@@ -1024,7 +1059,9 @@ class ParentWithUUIDPK(models.Model):
|
||||
|
||||
|
||||
class RelatedWithUUIDPKModel(models.Model):
|
||||
parent = models.ForeignKey(ParentWithUUIDPK, on_delete=models.SET_NULL, null=True, blank=True)
|
||||
parent = models.ForeignKey(
|
||||
ParentWithUUIDPK, on_delete=models.SET_NULL, null=True, blank=True
|
||||
)
|
||||
|
||||
|
||||
class Author(models.Model):
|
||||
@@ -1038,6 +1075,7 @@ class Authorship(models.Model):
|
||||
|
||||
class UserProxy(User):
|
||||
"""Proxy a model with a different app_label."""
|
||||
|
||||
class Meta:
|
||||
proxy = True
|
||||
|
||||
@@ -1054,5 +1092,9 @@ class Héllo(models.Model):
|
||||
|
||||
class Box(models.Model):
|
||||
title = models.CharField(max_length=100)
|
||||
next_box = models.ForeignKey("self", null=True, on_delete=models.SET_NULL, blank=True)
|
||||
next_box = models.ForeignKey("self", null=True, on_delete=models.SET_NULL, blank=True)
|
||||
next_box = models.ForeignKey(
|
||||
"self", null=True, on_delete=models.SET_NULL, blank=True
|
||||
)
|
||||
next_box = models.ForeignKey(
|
||||
"self", null=True, on_delete=models.SET_NULL, blank=True
|
||||
)
|
||||
|
||||
@@ -12,19 +12,29 @@ from django.urls import reverse
|
||||
from .admin import SubscriberAdmin
|
||||
from .forms import MediaActionForm
|
||||
from .models import (
|
||||
Actor, Answer, Book, ExternalSubscriber, Question, Subscriber,
|
||||
Actor,
|
||||
Answer,
|
||||
Book,
|
||||
ExternalSubscriber,
|
||||
Question,
|
||||
Subscriber,
|
||||
UnchangeableObject,
|
||||
)
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_views.urls')
|
||||
@override_settings(ROOT_URLCONF="admin_views.urls")
|
||||
class AdminActionsTest(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(username='super', password='secret', email='super@example.com')
|
||||
cls.s1 = ExternalSubscriber.objects.create(name='John Doe', email='john@example.org')
|
||||
cls.s2 = Subscriber.objects.create(name='Max Mustermann', email='max@example.org')
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
username="super", password="secret", email="super@example.com"
|
||||
)
|
||||
cls.s1 = ExternalSubscriber.objects.create(
|
||||
name="John Doe", email="john@example.org"
|
||||
)
|
||||
cls.s2 = Subscriber.objects.create(
|
||||
name="Max Mustermann", email="max@example.org"
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.client.force_login(self.superuser)
|
||||
@@ -33,44 +43,56 @@ class AdminActionsTest(TestCase):
|
||||
"""A custom action defined in a ModelAdmin method."""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk],
|
||||
'action': 'mail_admin',
|
||||
'index': 0,
|
||||
"action": "mail_admin",
|
||||
"index": 0,
|
||||
}
|
||||
self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
|
||||
self.client.post(
|
||||
reverse("admin:admin_views_subscriber_changelist"), action_data
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, 'Greetings from a ModelAdmin action')
|
||||
self.assertEqual(mail.outbox[0].subject, "Greetings from a ModelAdmin action")
|
||||
|
||||
def test_model_admin_default_delete_action(self):
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
|
||||
'action': 'delete_selected',
|
||||
'index': 0,
|
||||
"action": "delete_selected",
|
||||
"index": 0,
|
||||
}
|
||||
delete_confirmation_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
|
||||
'action': 'delete_selected',
|
||||
'post': 'yes',
|
||||
"action": "delete_selected",
|
||||
"post": "yes",
|
||||
}
|
||||
confirmation = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
|
||||
confirmation = self.client.post(
|
||||
reverse("admin:admin_views_subscriber_changelist"), action_data
|
||||
)
|
||||
self.assertIsInstance(confirmation, TemplateResponse)
|
||||
self.assertContains(confirmation, 'Are you sure you want to delete the selected subscribers?')
|
||||
self.assertContains(confirmation, '<h2>Summary</h2>')
|
||||
self.assertContains(confirmation, '<li>Subscribers: 2</li>')
|
||||
self.assertContains(confirmation, '<li>External subscribers: 1</li>')
|
||||
self.assertContains(
|
||||
confirmation, "Are you sure you want to delete the selected subscribers?"
|
||||
)
|
||||
self.assertContains(confirmation, "<h2>Summary</h2>")
|
||||
self.assertContains(confirmation, "<li>Subscribers: 2</li>")
|
||||
self.assertContains(confirmation, "<li>External subscribers: 1</li>")
|
||||
self.assertContains(confirmation, ACTION_CHECKBOX_NAME, count=2)
|
||||
self.client.post(reverse('admin:admin_views_subscriber_changelist'), delete_confirmation_data)
|
||||
self.client.post(
|
||||
reverse("admin:admin_views_subscriber_changelist"), delete_confirmation_data
|
||||
)
|
||||
self.assertEqual(Subscriber.objects.count(), 0)
|
||||
|
||||
def test_default_delete_action_nonexistent_pk(self):
|
||||
self.assertFalse(Subscriber.objects.filter(id=9998).exists())
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: ['9998'],
|
||||
'action': 'delete_selected',
|
||||
'index': 0,
|
||||
ACTION_CHECKBOX_NAME: ["9998"],
|
||||
"action": "delete_selected",
|
||||
"index": 0,
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
|
||||
self.assertContains(response, 'Are you sure you want to delete the selected subscribers?')
|
||||
self.assertContains(response, '<ul></ul>', html=True)
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_subscriber_changelist"), action_data
|
||||
)
|
||||
self.assertContains(
|
||||
response, "Are you sure you want to delete the selected subscribers?"
|
||||
)
|
||||
self.assertContains(response, "<ul></ul>", html=True)
|
||||
|
||||
@override_settings(USE_THOUSAND_SEPARATOR=True, NUMBER_GROUPING=3)
|
||||
def test_non_localized_pk(self):
|
||||
@@ -81,11 +103,13 @@ class AdminActionsTest(TestCase):
|
||||
s = ExternalSubscriber.objects.create(id=9999)
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [s.pk, self.s2.pk],
|
||||
'action': 'delete_selected',
|
||||
'index': 0,
|
||||
"action": "delete_selected",
|
||||
"index": 0,
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), action_data)
|
||||
self.assertTemplateUsed(response, 'admin/delete_selected_confirmation.html')
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_subscriber_changelist"), action_data
|
||||
)
|
||||
self.assertTemplateUsed(response, "admin/delete_selected_confirmation.html")
|
||||
self.assertContains(response, 'value="9999"') # Instead of 9,999
|
||||
self.assertContains(response, 'value="%s"' % self.s2.pk)
|
||||
|
||||
@@ -94,33 +118,43 @@ class AdminActionsTest(TestCase):
|
||||
The default delete action where some related objects are protected
|
||||
from deletion.
|
||||
"""
|
||||
q1 = Question.objects.create(question='Why?')
|
||||
a1 = Answer.objects.create(question=q1, answer='Because.')
|
||||
a2 = Answer.objects.create(question=q1, answer='Yes.')
|
||||
q2 = Question.objects.create(question='Wherefore?')
|
||||
q1 = Question.objects.create(question="Why?")
|
||||
a1 = Answer.objects.create(question=q1, answer="Because.")
|
||||
a2 = Answer.objects.create(question=q1, answer="Yes.")
|
||||
q2 = Question.objects.create(question="Wherefore?")
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [q1.pk, q2.pk],
|
||||
'action': 'delete_selected',
|
||||
'index': 0,
|
||||
"action": "delete_selected",
|
||||
"index": 0,
|
||||
}
|
||||
delete_confirmation_data = action_data.copy()
|
||||
delete_confirmation_data['post'] = 'yes'
|
||||
response = self.client.post(reverse('admin:admin_views_question_changelist'), action_data)
|
||||
self.assertContains(response, 'would require deleting the following protected related objects')
|
||||
delete_confirmation_data["post"] = "yes"
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_question_changelist"), action_data
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<li>Answer: <a href="%s">Because.</a></li>' % reverse('admin:admin_views_answer_change', args=(a1.pk,)),
|
||||
html=True
|
||||
response, "would require deleting the following protected related objects"
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<li>Answer: <a href="%s">Yes.</a></li>' % reverse('admin:admin_views_answer_change', args=(a2.pk,)),
|
||||
html=True
|
||||
'<li>Answer: <a href="%s">Because.</a></li>'
|
||||
% reverse("admin:admin_views_answer_change", args=(a1.pk,)),
|
||||
html=True,
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
'<li>Answer: <a href="%s">Yes.</a></li>'
|
||||
% reverse("admin:admin_views_answer_change", args=(a2.pk,)),
|
||||
html=True,
|
||||
)
|
||||
# A POST request to delete protected objects displays the page which
|
||||
# says the deletion is prohibited.
|
||||
response = self.client.post(reverse('admin:admin_views_question_changelist'), delete_confirmation_data)
|
||||
self.assertContains(response, 'would require deleting the following protected related objects')
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_question_changelist"), delete_confirmation_data
|
||||
)
|
||||
self.assertContains(
|
||||
response, "would require deleting the following protected related objects"
|
||||
)
|
||||
self.assertEqual(Question.objects.count(), 2)
|
||||
|
||||
def test_model_admin_default_delete_action_no_change_url(self):
|
||||
@@ -131,58 +165,68 @@ class AdminActionsTest(TestCase):
|
||||
obj = UnchangeableObject.objects.create()
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: obj.pk,
|
||||
'action': 'delete_selected',
|
||||
'index': '0',
|
||||
"action": "delete_selected",
|
||||
"index": "0",
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_unchangeableobject_changelist'), action_data)
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_unchangeableobject_changelist"), action_data
|
||||
)
|
||||
# No 500 caused by NoReverseMatch. The page doesn't display a link to
|
||||
# the nonexistent change page.
|
||||
self.assertContains(response, '<li>Unchangeable object: %s</li>' % obj, 1, html=True)
|
||||
self.assertContains(
|
||||
response, "<li>Unchangeable object: %s</li>" % obj, 1, html=True
|
||||
)
|
||||
|
||||
def test_delete_queryset_hook(self):
|
||||
delete_confirmation_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
|
||||
'action': 'delete_selected',
|
||||
'post': 'yes',
|
||||
'index': 0,
|
||||
"action": "delete_selected",
|
||||
"post": "yes",
|
||||
"index": 0,
|
||||
}
|
||||
SubscriberAdmin.overridden = False
|
||||
self.client.post(reverse('admin:admin_views_subscriber_changelist'), delete_confirmation_data)
|
||||
self.client.post(
|
||||
reverse("admin:admin_views_subscriber_changelist"), delete_confirmation_data
|
||||
)
|
||||
# SubscriberAdmin.delete_queryset() sets overridden to True.
|
||||
self.assertIs(SubscriberAdmin.overridden, True)
|
||||
self.assertEqual(Subscriber.objects.all().count(), 0)
|
||||
|
||||
def test_delete_selected_uses_get_deleted_objects(self):
|
||||
"""The delete_selected action uses ModelAdmin.get_deleted_objects()."""
|
||||
book = Book.objects.create(name='Test Book')
|
||||
book = Book.objects.create(name="Test Book")
|
||||
data = {
|
||||
ACTION_CHECKBOX_NAME: [book.pk],
|
||||
'action': 'delete_selected',
|
||||
'index': 0,
|
||||
"action": "delete_selected",
|
||||
"index": 0,
|
||||
}
|
||||
response = self.client.post(reverse('admin2:admin_views_book_changelist'), data)
|
||||
response = self.client.post(reverse("admin2:admin_views_book_changelist"), data)
|
||||
# BookAdmin.get_deleted_objects() returns custom text.
|
||||
self.assertContains(response, 'a deletable object')
|
||||
self.assertContains(response, "a deletable object")
|
||||
|
||||
def test_custom_function_mail_action(self):
|
||||
"""A custom action may be defined in a function."""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk],
|
||||
'action': 'external_mail',
|
||||
'index': 0,
|
||||
"action": "external_mail",
|
||||
"index": 0,
|
||||
}
|
||||
self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
|
||||
self.client.post(
|
||||
reverse("admin:admin_views_externalsubscriber_changelist"), action_data
|
||||
)
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action')
|
||||
self.assertEqual(mail.outbox[0].subject, "Greetings from a function action")
|
||||
|
||||
def test_custom_function_action_with_redirect(self):
|
||||
"""Another custom action defined in a function."""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk],
|
||||
'action': 'redirect_to',
|
||||
'index': 0,
|
||||
"action": "redirect_to",
|
||||
"index": 0,
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_externalsubscriber_changelist"), action_data
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_default_redirect(self):
|
||||
@@ -192,10 +236,10 @@ class AdminActionsTest(TestCase):
|
||||
"""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk],
|
||||
'action': 'external_mail',
|
||||
'index': 0,
|
||||
"action": "external_mail",
|
||||
"index": 0,
|
||||
}
|
||||
url = reverse('admin:admin_views_externalsubscriber_changelist') + '?o=1'
|
||||
url = reverse("admin:admin_views_externalsubscriber_changelist") + "?o=1"
|
||||
response = self.client.post(url, action_data)
|
||||
self.assertRedirects(response, url)
|
||||
|
||||
@@ -203,29 +247,37 @@ class AdminActionsTest(TestCase):
|
||||
"""A custom action may return a StreamingHttpResponse."""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk],
|
||||
'action': 'download',
|
||||
'index': 0,
|
||||
"action": "download",
|
||||
"index": 0,
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
|
||||
content = b''.join(response.streaming_content)
|
||||
self.assertEqual(content, b'This is the content of the file')
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_externalsubscriber_changelist"), action_data
|
||||
)
|
||||
content = b"".join(response.streaming_content)
|
||||
self.assertEqual(content, b"This is the content of the file")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_custom_function_action_no_perm_response(self):
|
||||
"""A custom action may returns an HttpResponse with a 403 code."""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk],
|
||||
'action': 'no_perm',
|
||||
'index': 0,
|
||||
"action": "no_perm",
|
||||
"index": 0,
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_externalsubscriber_changelist"), action_data
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
self.assertEqual(response.content, b'No permission to perform this action')
|
||||
self.assertEqual(response.content, b"No permission to perform this action")
|
||||
|
||||
def test_actions_ordering(self):
|
||||
"""Actions are ordered as expected."""
|
||||
response = self.client.get(reverse('admin:admin_views_externalsubscriber_changelist'))
|
||||
self.assertContains(response, '''<label>Action: <select name="action" required>
|
||||
response = self.client.get(
|
||||
reverse("admin:admin_views_externalsubscriber_changelist")
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
"""<label>Action: <select name="action" required>
|
||||
<option value="" selected>---------</option>
|
||||
<option value="delete_selected">Delete selected external
|
||||
subscribers</option>
|
||||
@@ -234,15 +286,20 @@ subscribers</option>
|
||||
action)</option>
|
||||
<option value="download">Download subscription</option>
|
||||
<option value="no_perm">No permission to run</option>
|
||||
</select>''', html=True)
|
||||
</select>""",
|
||||
html=True,
|
||||
)
|
||||
|
||||
def test_model_without_action(self):
|
||||
"""A ModelAdmin might not have any actions."""
|
||||
response = self.client.get(reverse('admin:admin_views_oldsubscriber_changelist'))
|
||||
self.assertIsNone(response.context['action_form'])
|
||||
response = self.client.get(
|
||||
reverse("admin:admin_views_oldsubscriber_changelist")
|
||||
)
|
||||
self.assertIsNone(response.context["action_form"])
|
||||
self.assertNotContains(
|
||||
response, '<input type="checkbox" class="action-select"',
|
||||
msg_prefix='Found an unexpected action toggle checkboxbox in response'
|
||||
response,
|
||||
'<input type="checkbox" class="action-select"',
|
||||
msg_prefix="Found an unexpected action toggle checkboxbox in response",
|
||||
)
|
||||
self.assertNotContains(response, '<input type="checkbox" class="action-select"')
|
||||
|
||||
@@ -250,18 +307,21 @@ action)</option>
|
||||
"""
|
||||
A ModelAdmin without any actions still has jQuery included on the page.
|
||||
"""
|
||||
response = self.client.get(reverse('admin:admin_views_oldsubscriber_changelist'))
|
||||
self.assertIsNone(response.context['action_form'])
|
||||
response = self.client.get(
|
||||
reverse("admin:admin_views_oldsubscriber_changelist")
|
||||
)
|
||||
self.assertIsNone(response.context["action_form"])
|
||||
self.assertContains(
|
||||
response, 'jquery.min.js',
|
||||
msg_prefix='jQuery missing from admin pages for model with no admin actions'
|
||||
response,
|
||||
"jquery.min.js",
|
||||
msg_prefix="jQuery missing from admin pages for model with no admin actions",
|
||||
)
|
||||
|
||||
def test_action_column_class(self):
|
||||
"""The checkbox column class is present in the response."""
|
||||
response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
|
||||
self.assertIsNotNone(response.context['action_form'])
|
||||
self.assertContains(response, 'action-checkbox-column')
|
||||
response = self.client.get(reverse("admin:admin_views_subscriber_changelist"))
|
||||
self.assertIsNotNone(response.context["action_form"])
|
||||
self.assertContains(response, "action-checkbox-column")
|
||||
|
||||
def test_multiple_actions_form(self):
|
||||
"""
|
||||
@@ -270,24 +330,26 @@ action)</option>
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk],
|
||||
# Two different actions selected on the two forms...
|
||||
'action': ['external_mail', 'delete_selected'],
|
||||
"action": ["external_mail", "delete_selected"],
|
||||
# ...but "go" was clicked on the top form.
|
||||
'index': 0
|
||||
"index": 0,
|
||||
}
|
||||
self.client.post(reverse('admin:admin_views_externalsubscriber_changelist'), action_data)
|
||||
self.client.post(
|
||||
reverse("admin:admin_views_externalsubscriber_changelist"), action_data
|
||||
)
|
||||
# The action sends mail rather than deletes.
|
||||
self.assertEqual(len(mail.outbox), 1)
|
||||
self.assertEqual(mail.outbox[0].subject, 'Greetings from a function action')
|
||||
self.assertEqual(mail.outbox[0].subject, "Greetings from a function action")
|
||||
|
||||
def test_media_from_actions_form(self):
|
||||
"""
|
||||
The action form's media is included in the changelist view's media.
|
||||
"""
|
||||
response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
|
||||
response = self.client.get(reverse("admin:admin_views_subscriber_changelist"))
|
||||
media_path = MediaActionForm.Media.js[0]
|
||||
self.assertIsInstance(response.context['action_form'], MediaActionForm)
|
||||
self.assertIn('media', response.context)
|
||||
self.assertIn(media_path, response.context['media']._js)
|
||||
self.assertIsInstance(response.context["action_form"], MediaActionForm)
|
||||
self.assertIn("media", response.context)
|
||||
self.assertIn(media_path, response.context["media"]._js)
|
||||
self.assertContains(response, media_path)
|
||||
|
||||
def test_user_message_on_none_selected(self):
|
||||
@@ -296,14 +358,14 @@ action)</option>
|
||||
"""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [],
|
||||
'action': 'delete_selected',
|
||||
'index': 0,
|
||||
"action": "delete_selected",
|
||||
"index": 0,
|
||||
}
|
||||
url = reverse('admin:admin_views_subscriber_changelist')
|
||||
url = reverse("admin:admin_views_subscriber_changelist")
|
||||
response = self.client.post(url, action_data)
|
||||
self.assertRedirects(response, url, fetch_redirect_response=False)
|
||||
response = self.client.get(response.url)
|
||||
msg = 'Items must be selected in order to perform actions on them. No items have been changed.'
|
||||
msg = "Items must be selected in order to perform actions on them. No items have been changed."
|
||||
self.assertContains(response, msg)
|
||||
self.assertEqual(Subscriber.objects.count(), 2)
|
||||
|
||||
@@ -313,28 +375,28 @@ action)</option>
|
||||
"""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
|
||||
'action': '',
|
||||
'index': 0,
|
||||
"action": "",
|
||||
"index": 0,
|
||||
}
|
||||
url = reverse('admin:admin_views_subscriber_changelist')
|
||||
url = reverse("admin:admin_views_subscriber_changelist")
|
||||
response = self.client.post(url, action_data)
|
||||
self.assertRedirects(response, url, fetch_redirect_response=False)
|
||||
response = self.client.get(response.url)
|
||||
self.assertContains(response, 'No action selected.')
|
||||
self.assertContains(response, "No action selected.")
|
||||
self.assertEqual(Subscriber.objects.count(), 2)
|
||||
|
||||
def test_selection_counter(self):
|
||||
"""The selection counter is there."""
|
||||
response = self.client.get(reverse('admin:admin_views_subscriber_changelist'))
|
||||
self.assertContains(response, '0 of 2 selected')
|
||||
response = self.client.get(reverse("admin:admin_views_subscriber_changelist"))
|
||||
self.assertContains(response, "0 of 2 selected")
|
||||
|
||||
def test_popup_actions(self):
|
||||
""" Actions aren't shown in popups."""
|
||||
changelist_url = reverse('admin:admin_views_subscriber_changelist')
|
||||
"""Actions aren't shown in popups."""
|
||||
changelist_url = reverse("admin:admin_views_subscriber_changelist")
|
||||
response = self.client.get(changelist_url)
|
||||
self.assertIsNotNone(response.context['action_form'])
|
||||
response = self.client.get(changelist_url + '?%s' % IS_POPUP_VAR)
|
||||
self.assertIsNone(response.context['action_form'])
|
||||
self.assertIsNotNone(response.context["action_form"])
|
||||
response = self.client.get(changelist_url + "?%s" % IS_POPUP_VAR)
|
||||
self.assertIsNone(response.context["action_form"])
|
||||
|
||||
def test_popup_template_response_on_add(self):
|
||||
"""
|
||||
@@ -342,78 +404,90 @@ action)</option>
|
||||
easy customization.
|
||||
"""
|
||||
response = self.client.post(
|
||||
reverse('admin:admin_views_actor_add') + '?%s=1' % IS_POPUP_VAR,
|
||||
{'name': 'Troy McClure', 'age': '55', IS_POPUP_VAR: '1'}
|
||||
reverse("admin:admin_views_actor_add") + "?%s=1" % IS_POPUP_VAR,
|
||||
{"name": "Troy McClure", "age": "55", IS_POPUP_VAR: "1"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.template_name, [
|
||||
'admin/admin_views/actor/popup_response.html',
|
||||
'admin/admin_views/popup_response.html',
|
||||
'admin/popup_response.html',
|
||||
])
|
||||
self.assertTemplateUsed(response, 'admin/popup_response.html')
|
||||
self.assertEqual(
|
||||
response.template_name,
|
||||
[
|
||||
"admin/admin_views/actor/popup_response.html",
|
||||
"admin/admin_views/popup_response.html",
|
||||
"admin/popup_response.html",
|
||||
],
|
||||
)
|
||||
self.assertTemplateUsed(response, "admin/popup_response.html")
|
||||
|
||||
def test_popup_template_response_on_change(self):
|
||||
instance = Actor.objects.create(name='David Tennant', age=45)
|
||||
instance = Actor.objects.create(name="David Tennant", age=45)
|
||||
response = self.client.post(
|
||||
reverse('admin:admin_views_actor_change', args=(instance.pk,)) + '?%s=1' % IS_POPUP_VAR,
|
||||
{'name': 'David Tennant', 'age': '46', IS_POPUP_VAR: '1'}
|
||||
reverse("admin:admin_views_actor_change", args=(instance.pk,))
|
||||
+ "?%s=1" % IS_POPUP_VAR,
|
||||
{"name": "David Tennant", "age": "46", IS_POPUP_VAR: "1"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.template_name, [
|
||||
'admin/admin_views/actor/popup_response.html',
|
||||
'admin/admin_views/popup_response.html',
|
||||
'admin/popup_response.html',
|
||||
])
|
||||
self.assertTemplateUsed(response, 'admin/popup_response.html')
|
||||
self.assertEqual(
|
||||
response.template_name,
|
||||
[
|
||||
"admin/admin_views/actor/popup_response.html",
|
||||
"admin/admin_views/popup_response.html",
|
||||
"admin/popup_response.html",
|
||||
],
|
||||
)
|
||||
self.assertTemplateUsed(response, "admin/popup_response.html")
|
||||
|
||||
def test_popup_template_response_on_delete(self):
|
||||
instance = Actor.objects.create(name='David Tennant', age=45)
|
||||
instance = Actor.objects.create(name="David Tennant", age=45)
|
||||
response = self.client.post(
|
||||
reverse('admin:admin_views_actor_delete', args=(instance.pk,)) + '?%s=1' % IS_POPUP_VAR,
|
||||
{IS_POPUP_VAR: '1'}
|
||||
reverse("admin:admin_views_actor_delete", args=(instance.pk,))
|
||||
+ "?%s=1" % IS_POPUP_VAR,
|
||||
{IS_POPUP_VAR: "1"},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertEqual(response.template_name, [
|
||||
'admin/admin_views/actor/popup_response.html',
|
||||
'admin/admin_views/popup_response.html',
|
||||
'admin/popup_response.html',
|
||||
])
|
||||
self.assertTemplateUsed(response, 'admin/popup_response.html')
|
||||
self.assertEqual(
|
||||
response.template_name,
|
||||
[
|
||||
"admin/admin_views/actor/popup_response.html",
|
||||
"admin/admin_views/popup_response.html",
|
||||
"admin/popup_response.html",
|
||||
],
|
||||
)
|
||||
self.assertTemplateUsed(response, "admin/popup_response.html")
|
||||
|
||||
def test_popup_template_escaping(self):
|
||||
popup_response_data = json.dumps({
|
||||
'new_value': 'new_value\\',
|
||||
'obj': 'obj\\',
|
||||
'value': 'value\\',
|
||||
})
|
||||
popup_response_data = json.dumps(
|
||||
{
|
||||
"new_value": "new_value\\",
|
||||
"obj": "obj\\",
|
||||
"value": "value\\",
|
||||
}
|
||||
)
|
||||
context = {
|
||||
'popup_response_data': popup_response_data,
|
||||
"popup_response_data": popup_response_data,
|
||||
}
|
||||
output = render_to_string('admin/popup_response.html', context)
|
||||
self.assertIn(
|
||||
r'"value\\"', output
|
||||
)
|
||||
self.assertIn(
|
||||
r'"new_value\\"', output
|
||||
)
|
||||
self.assertIn(
|
||||
r'"obj\\"', output
|
||||
)
|
||||
output = render_to_string("admin/popup_response.html", context)
|
||||
self.assertIn(r""value\\"", output)
|
||||
self.assertIn(r""new_value\\"", output)
|
||||
self.assertIn(r""obj\\"", output)
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_views.urls')
|
||||
@override_settings(ROOT_URLCONF="admin_views.urls")
|
||||
class AdminActionsPermissionTests(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.s1 = ExternalSubscriber.objects.create(name='John Doe', email='john@example.org')
|
||||
cls.s2 = Subscriber.objects.create(name='Max Mustermann', email='max@example.org')
|
||||
cls.s1 = ExternalSubscriber.objects.create(
|
||||
name="John Doe", email="john@example.org"
|
||||
)
|
||||
cls.s2 = Subscriber.objects.create(
|
||||
name="Max Mustermann", email="max@example.org"
|
||||
)
|
||||
cls.user = User.objects.create_user(
|
||||
username='user', password='secret', email='user@example.com',
|
||||
username="user",
|
||||
password="secret",
|
||||
email="user@example.com",
|
||||
is_staff=True,
|
||||
)
|
||||
permission = Permission.objects.get(codename='change_subscriber')
|
||||
permission = Permission.objects.get(codename="change_subscriber")
|
||||
cls.user.user_permissions.add(permission)
|
||||
|
||||
def setUp(self):
|
||||
@@ -426,25 +500,27 @@ class AdminActionsPermissionTests(TestCase):
|
||||
"""
|
||||
action_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk],
|
||||
'action': 'delete_selected',
|
||||
"action": "delete_selected",
|
||||
}
|
||||
url = reverse('admin:admin_views_subscriber_changelist')
|
||||
url = reverse("admin:admin_views_subscriber_changelist")
|
||||
response = self.client.post(url, action_data)
|
||||
self.assertRedirects(response, url, fetch_redirect_response=False)
|
||||
response = self.client.get(response.url)
|
||||
self.assertContains(response, 'No action selected.')
|
||||
self.assertContains(response, "No action selected.")
|
||||
|
||||
def test_model_admin_no_delete_permission_externalsubscriber(self):
|
||||
"""
|
||||
Permission is denied if the user doesn't have delete permission for a
|
||||
related model (ExternalSubscriber).
|
||||
"""
|
||||
permission = Permission.objects.get(codename='delete_subscriber')
|
||||
permission = Permission.objects.get(codename="delete_subscriber")
|
||||
self.user.user_permissions.add(permission)
|
||||
delete_confirmation_data = {
|
||||
ACTION_CHECKBOX_NAME: [self.s1.pk, self.s2.pk],
|
||||
'action': 'delete_selected',
|
||||
'post': 'yes',
|
||||
"action": "delete_selected",
|
||||
"post": "yes",
|
||||
}
|
||||
response = self.client.post(reverse('admin:admin_views_subscriber_changelist'), delete_confirmation_data)
|
||||
response = self.client.post(
|
||||
reverse("admin:admin_views_subscriber_changelist"), delete_confirmation_data
|
||||
)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
@@ -12,71 +12,76 @@ site.register(User)
|
||||
site.register(Article)
|
||||
|
||||
urlpatterns = [
|
||||
path('test_admin/admin/', site.urls),
|
||||
path("test_admin/admin/", site.urls),
|
||||
]
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_views.test_adminsite')
|
||||
@override_settings(ROOT_URLCONF="admin_views.test_adminsite")
|
||||
class SiteEachContextTest(TestCase):
|
||||
"""
|
||||
Check each_context contains the documented variables and that available_apps context
|
||||
variable structure is the expected one.
|
||||
"""
|
||||
|
||||
request_factory = RequestFactory()
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.u1 = User.objects.create_superuser(username='super', password='secret', email='super@example.com')
|
||||
cls.u1 = User.objects.create_superuser(
|
||||
username="super", password="secret", email="super@example.com"
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
request = self.request_factory.get(reverse('test_adminsite:index'))
|
||||
request = self.request_factory.get(reverse("test_adminsite:index"))
|
||||
request.user = self.u1
|
||||
self.ctx = site.each_context(request)
|
||||
|
||||
def test_each_context(self):
|
||||
ctx = self.ctx
|
||||
self.assertEqual(ctx['site_header'], 'Django administration')
|
||||
self.assertEqual(ctx['site_title'], 'Django site admin')
|
||||
self.assertEqual(ctx['site_url'], '/')
|
||||
self.assertIs(ctx['has_permission'], True)
|
||||
self.assertEqual(ctx["site_header"], "Django administration")
|
||||
self.assertEqual(ctx["site_title"], "Django site admin")
|
||||
self.assertEqual(ctx["site_url"], "/")
|
||||
self.assertIs(ctx["has_permission"], True)
|
||||
|
||||
def test_each_context_site_url_with_script_name(self):
|
||||
request = self.request_factory.get(reverse('test_adminsite:index'), SCRIPT_NAME='/my-script-name/')
|
||||
request = self.request_factory.get(
|
||||
reverse("test_adminsite:index"), SCRIPT_NAME="/my-script-name/"
|
||||
)
|
||||
request.user = self.u1
|
||||
self.assertEqual(site.each_context(request)['site_url'], '/my-script-name/')
|
||||
self.assertEqual(site.each_context(request)["site_url"], "/my-script-name/")
|
||||
|
||||
def test_available_apps(self):
|
||||
ctx = self.ctx
|
||||
apps = ctx['available_apps']
|
||||
apps = ctx["available_apps"]
|
||||
# we have registered two models from two different apps
|
||||
self.assertEqual(len(apps), 2)
|
||||
|
||||
# admin_views.Article
|
||||
admin_views = apps[0]
|
||||
self.assertEqual(admin_views['app_label'], 'admin_views')
|
||||
self.assertEqual(len(admin_views['models']), 1)
|
||||
article = admin_views['models'][0]
|
||||
self.assertEqual(article['object_name'], 'Article')
|
||||
self.assertEqual(article['model'], Article)
|
||||
self.assertEqual(admin_views["app_label"], "admin_views")
|
||||
self.assertEqual(len(admin_views["models"]), 1)
|
||||
article = admin_views["models"][0]
|
||||
self.assertEqual(article["object_name"], "Article")
|
||||
self.assertEqual(article["model"], Article)
|
||||
|
||||
# auth.User
|
||||
auth = apps[1]
|
||||
self.assertEqual(auth['app_label'], 'auth')
|
||||
self.assertEqual(len(auth['models']), 1)
|
||||
user = auth['models'][0]
|
||||
self.assertEqual(user['object_name'], 'User')
|
||||
self.assertEqual(user['model'], User)
|
||||
self.assertEqual(auth["app_label"], "auth")
|
||||
self.assertEqual(len(auth["models"]), 1)
|
||||
user = auth["models"][0]
|
||||
self.assertEqual(user["object_name"], "User")
|
||||
self.assertEqual(user["model"], User)
|
||||
|
||||
self.assertEqual(auth['app_url'], '/test_admin/admin/auth/')
|
||||
self.assertIs(auth['has_module_perms'], True)
|
||||
self.assertEqual(auth["app_url"], "/test_admin/admin/auth/")
|
||||
self.assertIs(auth["has_module_perms"], True)
|
||||
|
||||
self.assertIn('perms', user)
|
||||
self.assertIs(user['perms']['add'], True)
|
||||
self.assertIs(user['perms']['change'], True)
|
||||
self.assertIs(user['perms']['delete'], True)
|
||||
self.assertEqual(user['admin_url'], '/test_admin/admin/auth/user/')
|
||||
self.assertEqual(user['add_url'], '/test_admin/admin/auth/user/add/')
|
||||
self.assertEqual(user['name'], 'Users')
|
||||
self.assertIn("perms", user)
|
||||
self.assertIs(user["perms"]["add"], True)
|
||||
self.assertIs(user["perms"]["change"], True)
|
||||
self.assertIs(user["perms"]["delete"], True)
|
||||
self.assertEqual(user["admin_url"], "/test_admin/admin/auth/user/")
|
||||
self.assertEqual(user["add_url"], "/test_admin/admin/auth/user/add/")
|
||||
self.assertEqual(user["name"], "Users")
|
||||
|
||||
|
||||
class SiteActionsTests(SimpleTestCase):
|
||||
@@ -86,11 +91,12 @@ class SiteActionsTests(SimpleTestCase):
|
||||
def test_add_action(self):
|
||||
def test_action():
|
||||
pass
|
||||
|
||||
self.site.add_action(test_action)
|
||||
self.assertEqual(self.site.get_action('test_action'), test_action)
|
||||
self.assertEqual(self.site.get_action("test_action"), test_action)
|
||||
|
||||
def test_disable_action(self):
|
||||
action_name = 'delete_selected'
|
||||
action_name = "delete_selected"
|
||||
self.assertEqual(self.site._actions[action_name], delete_selected)
|
||||
self.site.disable_action(action_name)
|
||||
with self.assertRaises(KeyError):
|
||||
@@ -98,7 +104,7 @@ class SiteActionsTests(SimpleTestCase):
|
||||
|
||||
def test_get_action(self):
|
||||
"""AdminSite.get_action() returns an action even if it's disabled."""
|
||||
action_name = 'delete_selected'
|
||||
action_name = "delete_selected"
|
||||
self.assertEqual(self.site.get_action(action_name), delete_selected)
|
||||
self.site.disable_action(action_name)
|
||||
self.assertEqual(self.site.get_action(action_name), delete_selected)
|
||||
|
||||
@@ -14,8 +14,18 @@ from django.urls import reverse, reverse_lazy
|
||||
|
||||
from .admin import AnswerAdmin, QuestionAdmin
|
||||
from .models import (
|
||||
Answer, Author, Authorship, Bonus, Book, Employee, Manager, Parent,
|
||||
PKChild, Question, Toy, WorkHour,
|
||||
Answer,
|
||||
Author,
|
||||
Authorship,
|
||||
Bonus,
|
||||
Book,
|
||||
Employee,
|
||||
Manager,
|
||||
Parent,
|
||||
PKChild,
|
||||
Question,
|
||||
Toy,
|
||||
WorkHour,
|
||||
)
|
||||
from .tests import AdminViewBasicTestCase
|
||||
|
||||
@@ -23,30 +33,30 @@ PAGINATOR_SIZE = AutocompleteJsonView.paginate_by
|
||||
|
||||
|
||||
class AuthorAdmin(admin.ModelAdmin):
|
||||
ordering = ['id']
|
||||
search_fields = ['id']
|
||||
ordering = ["id"]
|
||||
search_fields = ["id"]
|
||||
|
||||
|
||||
class AuthorshipInline(admin.TabularInline):
|
||||
model = Authorship
|
||||
autocomplete_fields = ['author']
|
||||
autocomplete_fields = ["author"]
|
||||
|
||||
|
||||
class BookAdmin(admin.ModelAdmin):
|
||||
inlines = [AuthorshipInline]
|
||||
|
||||
|
||||
site = admin.AdminSite(name='autocomplete_admin')
|
||||
site = admin.AdminSite(name="autocomplete_admin")
|
||||
site.register(Question, QuestionAdmin)
|
||||
site.register(Answer, AnswerAdmin)
|
||||
site.register(Author, AuthorAdmin)
|
||||
site.register(Book, BookAdmin)
|
||||
site.register(Employee, search_fields=['name'])
|
||||
site.register(WorkHour, autocomplete_fields=['employee'])
|
||||
site.register(Manager, search_fields=['name'])
|
||||
site.register(Bonus, autocomplete_fields=['recipient'])
|
||||
site.register(PKChild, search_fields=['name'])
|
||||
site.register(Toy, autocomplete_fields=['child'])
|
||||
site.register(Employee, search_fields=["name"])
|
||||
site.register(WorkHour, autocomplete_fields=["employee"])
|
||||
site.register(Manager, search_fields=["name"])
|
||||
site.register(Bonus, autocomplete_fields=["recipient"])
|
||||
site.register(PKChild, search_fields=["name"])
|
||||
site.register(Toy, autocomplete_fields=["child"])
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -63,70 +73,87 @@ def model_admin(model, model_admin, admin_site=site):
|
||||
|
||||
|
||||
class AutocompleteJsonViewTests(AdminViewBasicTestCase):
|
||||
as_view_args = {'admin_site': site}
|
||||
as_view_args = {"admin_site": site}
|
||||
opts = {
|
||||
'app_label': Answer._meta.app_label,
|
||||
'model_name': Answer._meta.model_name,
|
||||
'field_name': 'question'
|
||||
"app_label": Answer._meta.app_label,
|
||||
"model_name": Answer._meta.model_name,
|
||||
"field_name": "question",
|
||||
}
|
||||
factory = RequestFactory()
|
||||
url = reverse_lazy('autocomplete_admin:autocomplete')
|
||||
url = reverse_lazy("autocomplete_admin:autocomplete")
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.user = User.objects.create_user(
|
||||
username='user', password='secret',
|
||||
email='user@example.com', is_staff=True,
|
||||
username="user",
|
||||
password="secret",
|
||||
email="user@example.com",
|
||||
is_staff=True,
|
||||
)
|
||||
super().setUpTestData()
|
||||
|
||||
def test_success(self):
|
||||
q = Question.objects.create(question='Is this a question?')
|
||||
request = self.factory.get(self.url, {'term': 'is', **self.opts})
|
||||
q = Question.objects.create(question="Is this a question?")
|
||||
request = self.factory.get(self.url, {"term": "is", **self.opts})
|
||||
request.user = self.superuser
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [{'id': str(q.pk), 'text': q.question}],
|
||||
'pagination': {'more': False},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [{"id": str(q.pk), "text": q.question}],
|
||||
"pagination": {"more": False},
|
||||
},
|
||||
)
|
||||
|
||||
def test_custom_to_field(self):
|
||||
q = Question.objects.create(question='Is this a question?')
|
||||
request = self.factory.get(self.url, {'term': 'is', **self.opts, 'field_name': 'question_with_to_field'})
|
||||
q = Question.objects.create(question="Is this a question?")
|
||||
request = self.factory.get(
|
||||
self.url,
|
||||
{"term": "is", **self.opts, "field_name": "question_with_to_field"},
|
||||
)
|
||||
request.user = self.superuser
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [{'id': str(q.uuid), 'text': q.question}],
|
||||
'pagination': {'more': False},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [{"id": str(q.uuid), "text": q.question}],
|
||||
"pagination": {"more": False},
|
||||
},
|
||||
)
|
||||
|
||||
def test_custom_to_field_permission_denied(self):
|
||||
Question.objects.create(question='Is this a question?')
|
||||
request = self.factory.get(self.url, {'term': 'is', **self.opts, 'field_name': 'question_with_to_field'})
|
||||
Question.objects.create(question="Is this a question?")
|
||||
request = self.factory.get(
|
||||
self.url,
|
||||
{"term": "is", **self.opts, "field_name": "question_with_to_field"},
|
||||
)
|
||||
request.user = self.user
|
||||
with self.assertRaises(PermissionDenied):
|
||||
AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
|
||||
def test_custom_to_field_custom_pk(self):
|
||||
q = Question.objects.create(question='Is this a question?')
|
||||
q = Question.objects.create(question="Is this a question?")
|
||||
opts = {
|
||||
'app_label': Question._meta.app_label,
|
||||
'model_name': Question._meta.model_name,
|
||||
'field_name': 'related_questions',
|
||||
"app_label": Question._meta.app_label,
|
||||
"model_name": Question._meta.model_name,
|
||||
"field_name": "related_questions",
|
||||
}
|
||||
request = self.factory.get(self.url, {'term': 'is', **opts})
|
||||
request = self.factory.get(self.url, {"term": "is", **opts})
|
||||
request.user = self.superuser
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [{'id': str(q.big_id), 'text': q.question}],
|
||||
'pagination': {'more': False},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [{"id": str(q.big_id), "text": q.question}],
|
||||
"pagination": {"more": False},
|
||||
},
|
||||
)
|
||||
|
||||
def test_to_field_resolution_with_mti(self):
|
||||
"""
|
||||
@@ -134,59 +161,75 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase):
|
||||
MTI. Tests for single and multi-level cases.
|
||||
"""
|
||||
tests = [
|
||||
(Employee, WorkHour, 'employee'),
|
||||
(Manager, Bonus, 'recipient'),
|
||||
(Employee, WorkHour, "employee"),
|
||||
(Manager, Bonus, "recipient"),
|
||||
]
|
||||
for Target, Remote, related_name in tests:
|
||||
with self.subTest(target_model=Target, remote_model=Remote, related_name=related_name):
|
||||
o = Target.objects.create(name="Frida Kahlo", gender=2, code="painter", alive=False)
|
||||
with self.subTest(
|
||||
target_model=Target, remote_model=Remote, related_name=related_name
|
||||
):
|
||||
o = Target.objects.create(
|
||||
name="Frida Kahlo", gender=2, code="painter", alive=False
|
||||
)
|
||||
opts = {
|
||||
'app_label': Remote._meta.app_label,
|
||||
'model_name': Remote._meta.model_name,
|
||||
'field_name': related_name,
|
||||
"app_label": Remote._meta.app_label,
|
||||
"model_name": Remote._meta.model_name,
|
||||
"field_name": related_name,
|
||||
}
|
||||
request = self.factory.get(self.url, {'term': 'frida', **opts})
|
||||
request = self.factory.get(self.url, {"term": "frida", **opts})
|
||||
request.user = self.superuser
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [{'id': str(o.pk), 'text': o.name}],
|
||||
'pagination': {'more': False},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [{"id": str(o.pk), "text": o.name}],
|
||||
"pagination": {"more": False},
|
||||
},
|
||||
)
|
||||
|
||||
def test_to_field_resolution_with_fk_pk(self):
|
||||
p = Parent.objects.create(name="Bertie")
|
||||
c = PKChild.objects.create(parent=p, name="Anna")
|
||||
opts = {
|
||||
'app_label': Toy._meta.app_label,
|
||||
'model_name': Toy._meta.model_name,
|
||||
'field_name': 'child',
|
||||
"app_label": Toy._meta.app_label,
|
||||
"model_name": Toy._meta.model_name,
|
||||
"field_name": "child",
|
||||
}
|
||||
request = self.factory.get(self.url, {'term': 'anna', **opts})
|
||||
request = self.factory.get(self.url, {"term": "anna", **opts})
|
||||
request.user = self.superuser
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [{'id': str(c.pk), 'text': c.name}],
|
||||
'pagination': {'more': False},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [{"id": str(c.pk), "text": c.name}],
|
||||
"pagination": {"more": False},
|
||||
},
|
||||
)
|
||||
|
||||
def test_field_does_not_exist(self):
|
||||
request = self.factory.get(self.url, {'term': 'is', **self.opts, 'field_name': 'does_not_exist'})
|
||||
request = self.factory.get(
|
||||
self.url, {"term": "is", **self.opts, "field_name": "does_not_exist"}
|
||||
)
|
||||
request.user = self.superuser
|
||||
with self.assertRaises(PermissionDenied):
|
||||
AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
|
||||
def test_field_no_related_field(self):
|
||||
request = self.factory.get(self.url, {'term': 'is', **self.opts, 'field_name': 'answer'})
|
||||
request = self.factory.get(
|
||||
self.url, {"term": "is", **self.opts, "field_name": "answer"}
|
||||
)
|
||||
request.user = self.superuser
|
||||
with self.assertRaises(PermissionDenied):
|
||||
AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
|
||||
def test_field_does_not_allowed(self):
|
||||
request = self.factory.get(self.url, {'term': 'is', **self.opts, 'field_name': 'related_questions'})
|
||||
request = self.factory.get(
|
||||
self.url, {"term": "is", **self.opts, "field_name": "related_questions"}
|
||||
)
|
||||
request.user = self.superuser
|
||||
with self.assertRaises(PermissionDenied):
|
||||
AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
@@ -194,23 +237,29 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase):
|
||||
def test_limit_choices_to(self):
|
||||
# Answer.question_with_to_field defines limit_choices_to to "those not
|
||||
# starting with 'not'".
|
||||
q = Question.objects.create(question='Is this a question?')
|
||||
Question.objects.create(question='Not a question.')
|
||||
request = self.factory.get(self.url, {'term': 'is', **self.opts, 'field_name': 'question_with_to_field'})
|
||||
q = Question.objects.create(question="Is this a question?")
|
||||
Question.objects.create(question="Not a question.")
|
||||
request = self.factory.get(
|
||||
self.url,
|
||||
{"term": "is", **self.opts, "field_name": "question_with_to_field"},
|
||||
)
|
||||
request.user = self.superuser
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [{'id': str(q.uuid), 'text': q.question}],
|
||||
'pagination': {'more': False},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [{"id": str(q.uuid), "text": q.question}],
|
||||
"pagination": {"more": False},
|
||||
},
|
||||
)
|
||||
|
||||
def test_must_be_logged_in(self):
|
||||
response = self.client.get(self.url, {'term': '', **self.opts})
|
||||
response = self.client.get(self.url, {"term": "", **self.opts})
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.client.logout()
|
||||
response = self.client.get(self.url, {'term': '', **self.opts})
|
||||
response = self.client.get(self.url, {"term": "", **self.opts})
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
def test_has_view_or_change_permission_required(self):
|
||||
@@ -218,16 +267,16 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase):
|
||||
Users require the change permission for the related model to the
|
||||
autocomplete view for it.
|
||||
"""
|
||||
request = self.factory.get(self.url, {'term': 'is', **self.opts})
|
||||
request = self.factory.get(self.url, {"term": "is", **self.opts})
|
||||
request.user = self.user
|
||||
with self.assertRaises(PermissionDenied):
|
||||
AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
for permission in ('view', 'change'):
|
||||
for permission in ("view", "change"):
|
||||
with self.subTest(permission=permission):
|
||||
self.user.user_permissions.clear()
|
||||
p = Permission.objects.get(
|
||||
content_type=ContentType.objects.get_for_model(Question),
|
||||
codename='%s_question' % permission,
|
||||
codename="%s_question" % permission,
|
||||
)
|
||||
self.user.user_permissions.add(p)
|
||||
request.user = User.objects.get(pk=self.user.pk)
|
||||
@@ -239,106 +288,134 @@ class AutocompleteJsonViewTests(AdminViewBasicTestCase):
|
||||
Searching across model relations use QuerySet.distinct() to avoid
|
||||
duplicates.
|
||||
"""
|
||||
q1 = Question.objects.create(question='question 1')
|
||||
q2 = Question.objects.create(question='question 2')
|
||||
q1 = Question.objects.create(question="question 1")
|
||||
q2 = Question.objects.create(question="question 2")
|
||||
q2.related_questions.add(q1)
|
||||
q3 = Question.objects.create(question='question 3')
|
||||
q3 = Question.objects.create(question="question 3")
|
||||
q3.related_questions.add(q1)
|
||||
request = self.factory.get(self.url, {'term': 'question', **self.opts})
|
||||
request = self.factory.get(self.url, {"term": "question", **self.opts})
|
||||
request.user = self.superuser
|
||||
|
||||
class DistinctQuestionAdmin(QuestionAdmin):
|
||||
search_fields = ['related_questions__question', 'question']
|
||||
search_fields = ["related_questions__question", "question"]
|
||||
|
||||
with model_admin(Question, DistinctQuestionAdmin):
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(len(data['results']), 3)
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(len(data["results"]), 3)
|
||||
|
||||
def test_missing_search_fields(self):
|
||||
class EmptySearchAdmin(QuestionAdmin):
|
||||
search_fields = []
|
||||
|
||||
with model_admin(Question, EmptySearchAdmin):
|
||||
msg = 'EmptySearchAdmin must have search_fields for the autocomplete_view.'
|
||||
msg = "EmptySearchAdmin must have search_fields for the autocomplete_view."
|
||||
with self.assertRaisesMessage(Http404, msg):
|
||||
site.autocomplete_view(self.factory.get(self.url, {'term': '', **self.opts}))
|
||||
site.autocomplete_view(
|
||||
self.factory.get(self.url, {"term": "", **self.opts})
|
||||
)
|
||||
|
||||
def test_get_paginator(self):
|
||||
"""Search results are paginated."""
|
||||
class PKOrderingQuestionAdmin(QuestionAdmin):
|
||||
ordering = ['pk']
|
||||
|
||||
Question.objects.bulk_create(Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10))
|
||||
class PKOrderingQuestionAdmin(QuestionAdmin):
|
||||
ordering = ["pk"]
|
||||
|
||||
Question.objects.bulk_create(
|
||||
Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10)
|
||||
)
|
||||
# The first page of results.
|
||||
request = self.factory.get(self.url, {'term': '', **self.opts})
|
||||
request = self.factory.get(self.url, {"term": "", **self.opts})
|
||||
request.user = self.superuser
|
||||
with model_admin(Question, PKOrderingQuestionAdmin):
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [{'id': str(q.pk), 'text': q.question} for q in Question.objects.all()[:PAGINATOR_SIZE]],
|
||||
'pagination': {'more': True},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [
|
||||
{"id": str(q.pk), "text": q.question}
|
||||
for q in Question.objects.all()[:PAGINATOR_SIZE]
|
||||
],
|
||||
"pagination": {"more": True},
|
||||
},
|
||||
)
|
||||
# The second page of results.
|
||||
request = self.factory.get(self.url, {'term': '', 'page': '2', **self.opts})
|
||||
request = self.factory.get(self.url, {"term": "", "page": "2", **self.opts})
|
||||
request.user = self.superuser
|
||||
with model_admin(Question, PKOrderingQuestionAdmin):
|
||||
response = AutocompleteJsonView.as_view(**self.as_view_args)(request)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [{'id': str(q.pk), 'text': q.question} for q in Question.objects.all()[PAGINATOR_SIZE:]],
|
||||
'pagination': {'more': False},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [
|
||||
{"id": str(q.pk), "text": q.question}
|
||||
for q in Question.objects.all()[PAGINATOR_SIZE:]
|
||||
],
|
||||
"pagination": {"more": False},
|
||||
},
|
||||
)
|
||||
|
||||
def test_serialize_result(self):
|
||||
class AutocompleteJsonSerializeResultView(AutocompleteJsonView):
|
||||
def serialize_result(self, obj, to_field_name):
|
||||
return {
|
||||
**super().serialize_result(obj, to_field_name),
|
||||
'posted': str(obj.posted),
|
||||
"posted": str(obj.posted),
|
||||
}
|
||||
|
||||
Question.objects.create(question='Question 1', posted=datetime.date(2021, 8, 9))
|
||||
Question.objects.create(question='Question 2', posted=datetime.date(2021, 8, 7))
|
||||
request = self.factory.get(self.url, {'term': 'question', **self.opts})
|
||||
Question.objects.create(question="Question 1", posted=datetime.date(2021, 8, 9))
|
||||
Question.objects.create(question="Question 2", posted=datetime.date(2021, 8, 7))
|
||||
request = self.factory.get(self.url, {"term": "question", **self.opts})
|
||||
request.user = self.superuser
|
||||
response = AutocompleteJsonSerializeResultView.as_view(**self.as_view_args)(request)
|
||||
response = AutocompleteJsonSerializeResultView.as_view(**self.as_view_args)(
|
||||
request
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
data = json.loads(response.content.decode('utf-8'))
|
||||
self.assertEqual(data, {
|
||||
'results': [
|
||||
{'id': str(q.pk), 'text': q.question, 'posted': str(q.posted)}
|
||||
for q in Question.objects.order_by('-posted')
|
||||
],
|
||||
'pagination': {'more': False},
|
||||
})
|
||||
data = json.loads(response.content.decode("utf-8"))
|
||||
self.assertEqual(
|
||||
data,
|
||||
{
|
||||
"results": [
|
||||
{"id": str(q.pk), "text": q.question, "posted": str(q.posted)}
|
||||
for q in Question.objects.order_by("-posted")
|
||||
],
|
||||
"pagination": {"more": False},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_views.urls')
|
||||
@override_settings(ROOT_URLCONF="admin_views.urls")
|
||||
class SeleniumTests(AdminSeleniumTestCase):
|
||||
available_apps = ['admin_views'] + AdminSeleniumTestCase.available_apps
|
||||
available_apps = ["admin_views"] + AdminSeleniumTestCase.available_apps
|
||||
|
||||
def setUp(self):
|
||||
self.superuser = User.objects.create_superuser(
|
||||
username='super', password='secret', email='super@example.com',
|
||||
username="super",
|
||||
password="secret",
|
||||
email="super@example.com",
|
||||
)
|
||||
self.admin_login(
|
||||
username="super",
|
||||
password="secret",
|
||||
login_url=reverse("autocomplete_admin:index"),
|
||||
)
|
||||
self.admin_login(username='super', password='secret', login_url=reverse('autocomplete_admin:index'))
|
||||
|
||||
@contextmanager
|
||||
def select2_ajax_wait(self, timeout=10):
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions as ec
|
||||
|
||||
yield
|
||||
with self.disable_implicit_wait():
|
||||
try:
|
||||
loading_element = self.selenium.find_element(
|
||||
By.CSS_SELECTOR,
|
||||
'li.select2-results__option.loading-results'
|
||||
By.CSS_SELECTOR, "li.select2-results__option.loading-results"
|
||||
)
|
||||
except NoSuchElementException:
|
||||
pass
|
||||
@@ -349,109 +426,146 @@ class SeleniumTests(AdminSeleniumTestCase):
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.support.ui import Select
|
||||
self.selenium.get(self.live_server_url + reverse('autocomplete_admin:admin_views_answer_add'))
|
||||
elem = self.selenium.find_element(By.CSS_SELECTOR, '.select2-selection')
|
||||
|
||||
self.selenium.get(
|
||||
self.live_server_url + reverse("autocomplete_admin:admin_views_answer_add")
|
||||
)
|
||||
elem = self.selenium.find_element(By.CSS_SELECTOR, ".select2-selection")
|
||||
elem.click() # Open the autocomplete dropdown.
|
||||
results = self.selenium.find_element(By.CSS_SELECTOR, '.select2-results')
|
||||
results = self.selenium.find_element(By.CSS_SELECTOR, ".select2-results")
|
||||
self.assertTrue(results.is_displayed())
|
||||
option = self.selenium.find_element(By.CSS_SELECTOR, '.select2-results__option')
|
||||
self.assertEqual(option.text, 'No results found')
|
||||
option = self.selenium.find_element(By.CSS_SELECTOR, ".select2-results__option")
|
||||
self.assertEqual(option.text, "No results found")
|
||||
elem.click() # Close the autocomplete dropdown.
|
||||
q1 = Question.objects.create(question='Who am I?')
|
||||
Question.objects.bulk_create(Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10))
|
||||
q1 = Question.objects.create(question="Who am I?")
|
||||
Question.objects.bulk_create(
|
||||
Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10)
|
||||
)
|
||||
elem.click() # Reopen the dropdown now that some objects exist.
|
||||
result_container = self.selenium.find_element(By.CSS_SELECTOR, '.select2-results')
|
||||
result_container = self.selenium.find_element(
|
||||
By.CSS_SELECTOR, ".select2-results"
|
||||
)
|
||||
self.assertTrue(result_container.is_displayed())
|
||||
# PAGINATOR_SIZE results and "Loading more results".
|
||||
self.assertCountSeleniumElements('.select2-results__option', PAGINATOR_SIZE + 1, root_element=result_container)
|
||||
search = self.selenium.find_element(By.CSS_SELECTOR, '.select2-search__field')
|
||||
self.assertCountSeleniumElements(
|
||||
".select2-results__option",
|
||||
PAGINATOR_SIZE + 1,
|
||||
root_element=result_container,
|
||||
)
|
||||
search = self.selenium.find_element(By.CSS_SELECTOR, ".select2-search__field")
|
||||
# Load next page of results by scrolling to the bottom of the list.
|
||||
with self.select2_ajax_wait():
|
||||
for _ in range(PAGINATOR_SIZE + 1):
|
||||
search.send_keys(Keys.ARROW_DOWN)
|
||||
# All objects are now loaded.
|
||||
self.assertCountSeleniumElements(
|
||||
'.select2-results__option',
|
||||
".select2-results__option",
|
||||
PAGINATOR_SIZE + 11,
|
||||
root_element=result_container,
|
||||
)
|
||||
# Limit the results with the search field.
|
||||
with self.select2_ajax_wait():
|
||||
search.send_keys('Who')
|
||||
search.send_keys("Who")
|
||||
# Ajax request is delayed.
|
||||
self.assertTrue(result_container.is_displayed())
|
||||
self.assertCountSeleniumElements(
|
||||
'.select2-results__option',
|
||||
".select2-results__option",
|
||||
PAGINATOR_SIZE + 12,
|
||||
root_element=result_container,
|
||||
)
|
||||
self.assertTrue(result_container.is_displayed())
|
||||
self.assertCountSeleniumElements('.select2-results__option', 1, root_element=result_container)
|
||||
self.assertCountSeleniumElements(
|
||||
".select2-results__option", 1, root_element=result_container
|
||||
)
|
||||
# Select the result.
|
||||
search.send_keys(Keys.RETURN)
|
||||
select = Select(self.selenium.find_element(By.ID, 'id_question'))
|
||||
self.assertEqual(select.first_selected_option.get_attribute('value'), str(q1.pk))
|
||||
select = Select(self.selenium.find_element(By.ID, "id_question"))
|
||||
self.assertEqual(
|
||||
select.first_selected_option.get_attribute("value"), str(q1.pk)
|
||||
)
|
||||
|
||||
def test_select_multiple(self):
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.support.ui import Select
|
||||
self.selenium.get(self.live_server_url + reverse('autocomplete_admin:admin_views_question_add'))
|
||||
elem = self.selenium.find_element(By.CSS_SELECTOR, '.select2-selection')
|
||||
|
||||
self.selenium.get(
|
||||
self.live_server_url
|
||||
+ reverse("autocomplete_admin:admin_views_question_add")
|
||||
)
|
||||
elem = self.selenium.find_element(By.CSS_SELECTOR, ".select2-selection")
|
||||
elem.click() # Open the autocomplete dropdown.
|
||||
results = self.selenium.find_element(By.CSS_SELECTOR, '.select2-results')
|
||||
results = self.selenium.find_element(By.CSS_SELECTOR, ".select2-results")
|
||||
self.assertTrue(results.is_displayed())
|
||||
option = self.selenium.find_element(By.CSS_SELECTOR, '.select2-results__option')
|
||||
self.assertEqual(option.text, 'No results found')
|
||||
option = self.selenium.find_element(By.CSS_SELECTOR, ".select2-results__option")
|
||||
self.assertEqual(option.text, "No results found")
|
||||
elem.click() # Close the autocomplete dropdown.
|
||||
Question.objects.create(question='Who am I?')
|
||||
Question.objects.bulk_create(Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10))
|
||||
Question.objects.create(question="Who am I?")
|
||||
Question.objects.bulk_create(
|
||||
Question(question=str(i)) for i in range(PAGINATOR_SIZE + 10)
|
||||
)
|
||||
elem.click() # Reopen the dropdown now that some objects exist.
|
||||
result_container = self.selenium.find_element(By.CSS_SELECTOR, '.select2-results')
|
||||
result_container = self.selenium.find_element(
|
||||
By.CSS_SELECTOR, ".select2-results"
|
||||
)
|
||||
self.assertTrue(result_container.is_displayed())
|
||||
self.assertCountSeleniumElements('.select2-results__option', PAGINATOR_SIZE + 1, root_element=result_container)
|
||||
search = self.selenium.find_element(By.CSS_SELECTOR, '.select2-search__field')
|
||||
self.assertCountSeleniumElements(
|
||||
".select2-results__option",
|
||||
PAGINATOR_SIZE + 1,
|
||||
root_element=result_container,
|
||||
)
|
||||
search = self.selenium.find_element(By.CSS_SELECTOR, ".select2-search__field")
|
||||
# Load next page of results by scrolling to the bottom of the list.
|
||||
with self.select2_ajax_wait():
|
||||
for _ in range(PAGINATOR_SIZE + 1):
|
||||
search.send_keys(Keys.ARROW_DOWN)
|
||||
self.assertCountSeleniumElements('.select2-results__option', 31, root_element=result_container)
|
||||
self.assertCountSeleniumElements(
|
||||
".select2-results__option", 31, root_element=result_container
|
||||
)
|
||||
# Limit the results with the search field.
|
||||
with self.select2_ajax_wait():
|
||||
search.send_keys('Who')
|
||||
search.send_keys("Who")
|
||||
# Ajax request is delayed.
|
||||
self.assertTrue(result_container.is_displayed())
|
||||
self.assertCountSeleniumElements('.select2-results__option', 32, root_element=result_container)
|
||||
self.assertCountSeleniumElements(
|
||||
".select2-results__option", 32, root_element=result_container
|
||||
)
|
||||
self.assertTrue(result_container.is_displayed())
|
||||
|
||||
self.assertCountSeleniumElements('.select2-results__option', 1, root_element=result_container)
|
||||
self.assertCountSeleniumElements(
|
||||
".select2-results__option", 1, root_element=result_container
|
||||
)
|
||||
# Select the result.
|
||||
search.send_keys(Keys.RETURN)
|
||||
# Reopen the dropdown and add the first result to the selection.
|
||||
elem.click()
|
||||
search.send_keys(Keys.ARROW_DOWN)
|
||||
search.send_keys(Keys.RETURN)
|
||||
select = Select(self.selenium.find_element(By.ID, 'id_related_questions'))
|
||||
select = Select(self.selenium.find_element(By.ID, "id_related_questions"))
|
||||
self.assertEqual(len(select.all_selected_options), 2)
|
||||
|
||||
def test_inline_add_another_widgets(self):
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
def assertNoResults(row):
|
||||
elem = row.find_element(By.CSS_SELECTOR, '.select2-selection')
|
||||
elem = row.find_element(By.CSS_SELECTOR, ".select2-selection")
|
||||
elem.click() # Open the autocomplete dropdown.
|
||||
results = self.selenium.find_element(By.CSS_SELECTOR, '.select2-results')
|
||||
results = self.selenium.find_element(By.CSS_SELECTOR, ".select2-results")
|
||||
self.assertTrue(results.is_displayed())
|
||||
option = self.selenium.find_element(By.CSS_SELECTOR, '.select2-results__option')
|
||||
self.assertEqual(option.text, 'No results found')
|
||||
option = self.selenium.find_element(
|
||||
By.CSS_SELECTOR, ".select2-results__option"
|
||||
)
|
||||
self.assertEqual(option.text, "No results found")
|
||||
|
||||
# Autocomplete works in rows present when the page loads.
|
||||
self.selenium.get(self.live_server_url + reverse('autocomplete_admin:admin_views_book_add'))
|
||||
rows = self.selenium.find_elements(By.CSS_SELECTOR, '.dynamic-authorship_set')
|
||||
self.selenium.get(
|
||||
self.live_server_url + reverse("autocomplete_admin:admin_views_book_add")
|
||||
)
|
||||
rows = self.selenium.find_elements(By.CSS_SELECTOR, ".dynamic-authorship_set")
|
||||
self.assertEqual(len(rows), 3)
|
||||
assertNoResults(rows[0])
|
||||
# Autocomplete works in rows added using the "Add another" button.
|
||||
self.selenium.find_element(By.LINK_TEXT, 'Add another Authorship').click()
|
||||
rows = self.selenium.find_elements(By.CSS_SELECTOR, '.dynamic-authorship_set')
|
||||
self.selenium.find_element(By.LINK_TEXT, "Add another Authorship").click()
|
||||
rows = self.selenium.find_elements(By.CSS_SELECTOR, ".dynamic-authorship_set")
|
||||
self.assertEqual(len(rows), 4)
|
||||
assertNoResults(rows[-1])
|
||||
|
||||
@@ -8,28 +8,35 @@ from .admin import ArticleForm
|
||||
|
||||
# To verify that the login form rejects inactive users, use an authentication
|
||||
# backend that allows them.
|
||||
@override_settings(AUTHENTICATION_BACKENDS=['django.contrib.auth.backends.AllowAllUsersModelBackend'])
|
||||
@override_settings(
|
||||
AUTHENTICATION_BACKENDS=["django.contrib.auth.backends.AllowAllUsersModelBackend"]
|
||||
)
|
||||
class AdminAuthenticationFormTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
User.objects.create_user(username='inactive', password='password', is_active=False)
|
||||
User.objects.create_user(
|
||||
username="inactive", password="password", is_active=False
|
||||
)
|
||||
|
||||
def test_inactive_user(self):
|
||||
data = {
|
||||
'username': 'inactive',
|
||||
'password': 'password',
|
||||
"username": "inactive",
|
||||
"password": "password",
|
||||
}
|
||||
form = AdminAuthenticationForm(None, data)
|
||||
self.assertEqual(form.non_field_errors(), ['This account is inactive.'])
|
||||
self.assertEqual(form.non_field_errors(), ["This account is inactive."])
|
||||
|
||||
|
||||
class AdminFormTests(SimpleTestCase):
|
||||
def test_repr(self):
|
||||
fieldsets = (
|
||||
('My fields', {
|
||||
'classes': ['collapse'],
|
||||
'fields': ('url', 'title', 'content', 'sites'),
|
||||
}),
|
||||
(
|
||||
"My fields",
|
||||
{
|
||||
"classes": ["collapse"],
|
||||
"fields": ("url", "title", "content", "sites"),
|
||||
},
|
||||
),
|
||||
)
|
||||
form = ArticleForm()
|
||||
admin_form = AdminForm(form, fieldsets, {})
|
||||
|
||||
@@ -9,13 +9,14 @@ from django.urls import reverse
|
||||
from .models import City, State
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_views.urls')
|
||||
@override_settings(ROOT_URLCONF="admin_views.urls")
|
||||
class AdminHistoryViewTests(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
username='super', password='secret', email='super@example.com',
|
||||
username="super",
|
||||
password="secret",
|
||||
email="super@example.com",
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
@@ -26,35 +27,39 @@ class AdminHistoryViewTests(TestCase):
|
||||
Admin's model history change messages use form labels instead of
|
||||
field names.
|
||||
"""
|
||||
state = State.objects.create(name='My State Name')
|
||||
city = City.objects.create(name='My City Name', state=state)
|
||||
state = State.objects.create(name="My State Name")
|
||||
city = City.objects.create(name="My City Name", state=state)
|
||||
change_dict = {
|
||||
'name': 'My State Name 2',
|
||||
'nolabel_form_field': True,
|
||||
'city_set-0-name': 'My City name 2',
|
||||
'city_set-0-id': city.pk,
|
||||
'city_set-TOTAL_FORMS': '3',
|
||||
'city_set-INITIAL_FORMS': '1',
|
||||
'city_set-MAX_NUM_FORMS': '0',
|
||||
"name": "My State Name 2",
|
||||
"nolabel_form_field": True,
|
||||
"city_set-0-name": "My City name 2",
|
||||
"city_set-0-id": city.pk,
|
||||
"city_set-TOTAL_FORMS": "3",
|
||||
"city_set-INITIAL_FORMS": "1",
|
||||
"city_set-MAX_NUM_FORMS": "0",
|
||||
}
|
||||
state_change_url = reverse('admin:admin_views_state_change', args=(state.pk,))
|
||||
state_change_url = reverse("admin:admin_views_state_change", args=(state.pk,))
|
||||
self.client.post(state_change_url, change_dict)
|
||||
logentry = LogEntry.objects.filter(content_type__model__iexact='state').latest('id')
|
||||
logentry = LogEntry.objects.filter(content_type__model__iexact="state").latest(
|
||||
"id"
|
||||
)
|
||||
self.assertEqual(
|
||||
logentry.get_change_message(),
|
||||
'Changed State name (from form’s Meta.labels), '
|
||||
'nolabel_form_field and not_a_form_field. '
|
||||
'Changed City verbose_name for city “%s”.' % city
|
||||
"Changed State name (from form’s Meta.labels), "
|
||||
"nolabel_form_field and not_a_form_field. "
|
||||
"Changed City verbose_name for city “%s”." % city,
|
||||
)
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_views.urls')
|
||||
@override_settings(ROOT_URLCONF="admin_views.urls")
|
||||
class SeleniumTests(AdminSeleniumTestCase):
|
||||
available_apps = ['admin_views'] + AdminSeleniumTestCase.available_apps
|
||||
available_apps = ["admin_views"] + AdminSeleniumTestCase.available_apps
|
||||
|
||||
def setUp(self):
|
||||
self.superuser = User.objects.create_superuser(
|
||||
username='super', password='secret', email='super@example.com',
|
||||
username="super",
|
||||
password="secret",
|
||||
email="super@example.com",
|
||||
)
|
||||
content_type_pk = ContentType.objects.get_for_model(User).pk
|
||||
for i in range(1, 1101):
|
||||
@@ -64,34 +69,38 @@ class SeleniumTests(AdminSeleniumTestCase):
|
||||
self.superuser.pk,
|
||||
repr(self.superuser),
|
||||
CHANGE,
|
||||
change_message=f'Changed something {i}',
|
||||
change_message=f"Changed something {i}",
|
||||
)
|
||||
self.admin_login(
|
||||
username='super', password='secret', login_url=reverse('admin:index'),
|
||||
username="super",
|
||||
password="secret",
|
||||
login_url=reverse("admin:index"),
|
||||
)
|
||||
|
||||
def test_pagination(self):
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
user_history_url = reverse('admin:auth_user_history', args=(self.superuser.pk,))
|
||||
user_history_url = reverse("admin:auth_user_history", args=(self.superuser.pk,))
|
||||
self.selenium.get(self.live_server_url + user_history_url)
|
||||
|
||||
paginator = self.selenium.find_element(By.CSS_SELECTOR, '.paginator')
|
||||
paginator = self.selenium.find_element(By.CSS_SELECTOR, ".paginator")
|
||||
self.assertTrue(paginator.is_displayed())
|
||||
self.assertIn('%s entries' % LogEntry.objects.count(), paginator.text)
|
||||
self.assertIn("%s entries" % LogEntry.objects.count(), paginator.text)
|
||||
self.assertIn(str(Paginator.ELLIPSIS), paginator.text)
|
||||
# The current page.
|
||||
current_page_link = self.selenium.find_element(By.CSS_SELECTOR, 'span.this-page')
|
||||
self.assertEqual(current_page_link.text, '1')
|
||||
current_page_link = self.selenium.find_element(
|
||||
By.CSS_SELECTOR, "span.this-page"
|
||||
)
|
||||
self.assertEqual(current_page_link.text, "1")
|
||||
# The last page.
|
||||
last_page_link = self.selenium.find_element(By.CSS_SELECTOR, '.end')
|
||||
self.assertTrue(last_page_link.text, '20')
|
||||
last_page_link = self.selenium.find_element(By.CSS_SELECTOR, ".end")
|
||||
self.assertTrue(last_page_link.text, "20")
|
||||
# Select the second page.
|
||||
pages = paginator.find_elements(By.TAG_NAME, 'a')
|
||||
pages = paginator.find_elements(By.TAG_NAME, "a")
|
||||
second_page_link = pages[0]
|
||||
self.assertEqual(second_page_link.text, '2')
|
||||
self.assertEqual(second_page_link.text, "2")
|
||||
second_page_link.click()
|
||||
self.assertIn('?p=2', self.selenium.current_url)
|
||||
rows = self.selenium.find_elements(By.CSS_SELECTOR, '#change-history tbody tr')
|
||||
self.assertIn('Changed something 101', rows[0].text)
|
||||
self.assertIn('Changed something 200', rows[-1].text)
|
||||
self.assertIn("?p=2", self.selenium.current_url)
|
||||
rows = self.selenium.find_elements(By.CSS_SELECTOR, "#change-history tbody tr")
|
||||
self.assertIn("Changed something 101", rows[0].text)
|
||||
self.assertIn("Changed something 200", rows[-1].text)
|
||||
|
||||
@@ -17,17 +17,17 @@ class Router:
|
||||
db_for_write = db_for_read
|
||||
|
||||
|
||||
site = admin.AdminSite(name='test_adminsite')
|
||||
site = admin.AdminSite(name="test_adminsite")
|
||||
site.register(Book)
|
||||
|
||||
urlpatterns = [
|
||||
path('admin/', site.urls),
|
||||
path("admin/", site.urls),
|
||||
]
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF=__name__, DATABASE_ROUTERS=['%s.Router' % __name__])
|
||||
@override_settings(ROOT_URLCONF=__name__, DATABASE_ROUTERS=["%s.Router" % __name__])
|
||||
class MultiDatabaseTests(TestCase):
|
||||
databases = {'default', 'other'}
|
||||
databases = {"default", "other"}
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
@@ -36,44 +36,52 @@ class MultiDatabaseTests(TestCase):
|
||||
for db in cls.databases:
|
||||
Router.target_db = db
|
||||
cls.superusers[db] = User.objects.create_superuser(
|
||||
username='admin', password='something', email='test@test.org',
|
||||
username="admin",
|
||||
password="something",
|
||||
email="test@test.org",
|
||||
)
|
||||
b = Book(name='Test Book')
|
||||
b = Book(name="Test Book")
|
||||
b.save(using=db)
|
||||
cls.test_book_ids[db] = b.id
|
||||
|
||||
@mock.patch('django.contrib.admin.options.transaction')
|
||||
@mock.patch("django.contrib.admin.options.transaction")
|
||||
def test_add_view(self, mock):
|
||||
for db in self.databases:
|
||||
with self.subTest(db=db):
|
||||
Router.target_db = db
|
||||
self.client.force_login(self.superusers[db])
|
||||
self.client.post(
|
||||
reverse('test_adminsite:admin_views_book_add'),
|
||||
{'name': 'Foobar: 5th edition'},
|
||||
reverse("test_adminsite:admin_views_book_add"),
|
||||
{"name": "Foobar: 5th edition"},
|
||||
)
|
||||
mock.atomic.assert_called_with(using=db)
|
||||
|
||||
@mock.patch('django.contrib.admin.options.transaction')
|
||||
@mock.patch("django.contrib.admin.options.transaction")
|
||||
def test_change_view(self, mock):
|
||||
for db in self.databases:
|
||||
with self.subTest(db=db):
|
||||
Router.target_db = db
|
||||
self.client.force_login(self.superusers[db])
|
||||
self.client.post(
|
||||
reverse('test_adminsite:admin_views_book_change', args=[self.test_book_ids[db]]),
|
||||
{'name': 'Test Book 2: Test more'},
|
||||
reverse(
|
||||
"test_adminsite:admin_views_book_change",
|
||||
args=[self.test_book_ids[db]],
|
||||
),
|
||||
{"name": "Test Book 2: Test more"},
|
||||
)
|
||||
mock.atomic.assert_called_with(using=db)
|
||||
|
||||
@mock.patch('django.contrib.admin.options.transaction')
|
||||
@mock.patch("django.contrib.admin.options.transaction")
|
||||
def test_delete_view(self, mock):
|
||||
for db in self.databases:
|
||||
with self.subTest(db=db):
|
||||
Router.target_db = db
|
||||
self.client.force_login(self.superusers[db])
|
||||
self.client.post(
|
||||
reverse('test_adminsite:admin_views_book_delete', args=[self.test_book_ids[db]]),
|
||||
{'post': 'yes'},
|
||||
reverse(
|
||||
"test_adminsite:admin_views_book_delete",
|
||||
args=[self.test_book_ids[db]],
|
||||
),
|
||||
{"post": "yes"},
|
||||
)
|
||||
mock.atomic.assert_called_with(using=db)
|
||||
|
||||
@@ -15,165 +15,204 @@ class AdminSiteWithoutSidebar(admin.AdminSite):
|
||||
enable_nav_sidebar = False
|
||||
|
||||
|
||||
site_with_sidebar = AdminSiteWithSidebar(name='test_with_sidebar')
|
||||
site_without_sidebar = AdminSiteWithoutSidebar(name='test_without_sidebar')
|
||||
site_with_sidebar = AdminSiteWithSidebar(name="test_with_sidebar")
|
||||
site_without_sidebar = AdminSiteWithoutSidebar(name="test_without_sidebar")
|
||||
|
||||
site_with_sidebar.register(User)
|
||||
site_with_sidebar.register(Héllo)
|
||||
|
||||
urlpatterns = [
|
||||
path('test_sidebar/admin/', site_with_sidebar.urls),
|
||||
path('test_wihout_sidebar/admin/', site_without_sidebar.urls),
|
||||
path("test_sidebar/admin/", site_with_sidebar.urls),
|
||||
path("test_wihout_sidebar/admin/", site_without_sidebar.urls),
|
||||
]
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_views.test_nav_sidebar')
|
||||
@override_settings(ROOT_URLCONF="admin_views.test_nav_sidebar")
|
||||
class AdminSidebarTests(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
username='super',
|
||||
password='secret',
|
||||
email='super@example.com',
|
||||
username="super",
|
||||
password="secret",
|
||||
email="super@example.com",
|
||||
)
|
||||
|
||||
def setUp(self):
|
||||
self.client.force_login(self.superuser)
|
||||
|
||||
def test_sidebar_not_on_index(self):
|
||||
response = self.client.get(reverse('test_with_sidebar:index'))
|
||||
response = self.client.get(reverse("test_with_sidebar:index"))
|
||||
self.assertContains(response, '<div class="main" id="main">')
|
||||
self.assertNotContains(response, '<nav class="sticky" id="nav-sidebar">')
|
||||
|
||||
def test_sidebar_disabled(self):
|
||||
response = self.client.get(reverse('test_without_sidebar:index'))
|
||||
response = self.client.get(reverse("test_without_sidebar:index"))
|
||||
self.assertNotContains(response, '<nav class="sticky" id="nav-sidebar">')
|
||||
|
||||
def test_sidebar_unauthenticated(self):
|
||||
self.client.logout()
|
||||
response = self.client.get(reverse('test_with_sidebar:login'))
|
||||
response = self.client.get(reverse("test_with_sidebar:login"))
|
||||
self.assertNotContains(response, '<nav class="sticky" id="nav-sidebar">')
|
||||
|
||||
def test_sidebar_aria_current_page(self):
|
||||
url = reverse('test_with_sidebar:auth_user_changelist')
|
||||
url = reverse("test_with_sidebar:auth_user_changelist")
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, '<nav class="sticky" id="nav-sidebar">')
|
||||
self.assertContains(response, '<a href="%s" aria-current="page">Users</a>' % url)
|
||||
self.assertContains(
|
||||
response, '<a href="%s" aria-current="page">Users</a>' % url
|
||||
)
|
||||
|
||||
@override_settings(
|
||||
TEMPLATES=[{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
],
|
||||
},
|
||||
}]
|
||||
TEMPLATES=[
|
||||
{
|
||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||
"DIRS": [],
|
||||
"APP_DIRS": True,
|
||||
"OPTIONS": {
|
||||
"context_processors": [
|
||||
"django.contrib.auth.context_processors.auth",
|
||||
"django.contrib.messages.context_processors.messages",
|
||||
],
|
||||
},
|
||||
}
|
||||
]
|
||||
)
|
||||
def test_sidebar_aria_current_page_missing_without_request_context_processor(self):
|
||||
url = reverse('test_with_sidebar:auth_user_changelist')
|
||||
url = reverse("test_with_sidebar:auth_user_changelist")
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, '<nav class="sticky" id="nav-sidebar">')
|
||||
# Does not include aria-current attribute.
|
||||
self.assertContains(response, '<a href="%s">Users</a>' % url)
|
||||
self.assertNotContains(response, 'aria-current')
|
||||
self.assertNotContains(response, "aria-current")
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_included_app_list_template_context_fully_set(self):
|
||||
# All context variables should be set when rendering the sidebar.
|
||||
url = reverse('test_with_sidebar:auth_user_changelist')
|
||||
with self.assertNoLogs('django.template', 'DEBUG'):
|
||||
url = reverse("test_with_sidebar:auth_user_changelist")
|
||||
with self.assertNoLogs("django.template", "DEBUG"):
|
||||
self.client.get(url)
|
||||
|
||||
def test_sidebar_model_name_non_ascii(self):
|
||||
url = reverse('test_with_sidebar:admin_views_héllo_changelist')
|
||||
url = reverse("test_with_sidebar:admin_views_héllo_changelist")
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, '<div class="app-admin_views module current-app">')
|
||||
self.assertContains(
|
||||
response, '<div class="app-admin_views module current-app">'
|
||||
)
|
||||
self.assertContains(response, '<tr class="model-héllo current-model">')
|
||||
self.assertContains(
|
||||
response,
|
||||
'<th scope="row">'
|
||||
'<a href="/test_sidebar/admin/admin_views/h%C3%A9llo/" aria-current="page">'
|
||||
'Héllos</a></th>'
|
||||
"Héllos</a></th>",
|
||||
)
|
||||
|
||||
|
||||
@override_settings(ROOT_URLCONF='admin_views.test_nav_sidebar')
|
||||
@override_settings(ROOT_URLCONF="admin_views.test_nav_sidebar")
|
||||
class SeleniumTests(AdminSeleniumTestCase):
|
||||
available_apps = ['admin_views'] + AdminSeleniumTestCase.available_apps
|
||||
available_apps = ["admin_views"] + AdminSeleniumTestCase.available_apps
|
||||
|
||||
def setUp(self):
|
||||
self.superuser = User.objects.create_superuser(
|
||||
username='super',
|
||||
password='secret',
|
||||
email='super@example.com',
|
||||
username="super",
|
||||
password="secret",
|
||||
email="super@example.com",
|
||||
)
|
||||
self.admin_login(
|
||||
username="super",
|
||||
password="secret",
|
||||
login_url=reverse("test_with_sidebar:index"),
|
||||
)
|
||||
self.selenium.execute_script(
|
||||
"localStorage.removeItem('django.admin.navSidebarIsOpen')"
|
||||
)
|
||||
self.admin_login(username='super', password='secret', login_url=reverse('test_with_sidebar:index'))
|
||||
self.selenium.execute_script("localStorage.removeItem('django.admin.navSidebarIsOpen')")
|
||||
|
||||
def test_sidebar_starts_open(self):
|
||||
from selenium.webdriver.common.by import By
|
||||
self.selenium.get(self.live_server_url + reverse('test_with_sidebar:auth_user_changelist'))
|
||||
main_element = self.selenium.find_element(By.CSS_SELECTOR, '#main')
|
||||
self.assertIn('shifted', main_element.get_attribute('class').split())
|
||||
|
||||
self.selenium.get(
|
||||
self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
|
||||
)
|
||||
main_element = self.selenium.find_element(By.CSS_SELECTOR, "#main")
|
||||
self.assertIn("shifted", main_element.get_attribute("class").split())
|
||||
|
||||
def test_sidebar_can_be_closed(self):
|
||||
from selenium.webdriver.common.by import By
|
||||
self.selenium.get(self.live_server_url + reverse('test_with_sidebar:auth_user_changelist'))
|
||||
toggle_button = self.selenium.find_element(By.CSS_SELECTOR, '#toggle-nav-sidebar')
|
||||
self.assertEqual(toggle_button.tag_name, 'button')
|
||||
self.assertEqual(toggle_button.get_attribute('aria-label'), 'Toggle navigation')
|
||||
for link in self.selenium.find_elements(By.CSS_SELECTOR, '#nav-sidebar a'):
|
||||
self.assertEqual(link.get_attribute('tabIndex'), '0')
|
||||
|
||||
self.selenium.get(
|
||||
self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
|
||||
)
|
||||
toggle_button = self.selenium.find_element(
|
||||
By.CSS_SELECTOR, "#toggle-nav-sidebar"
|
||||
)
|
||||
self.assertEqual(toggle_button.tag_name, "button")
|
||||
self.assertEqual(toggle_button.get_attribute("aria-label"), "Toggle navigation")
|
||||
for link in self.selenium.find_elements(By.CSS_SELECTOR, "#nav-sidebar a"):
|
||||
self.assertEqual(link.get_attribute("tabIndex"), "0")
|
||||
toggle_button.click()
|
||||
# Hidden sidebar is not reachable via keyboard navigation.
|
||||
for link in self.selenium.find_elements(By.CSS_SELECTOR, '#nav-sidebar a'):
|
||||
self.assertEqual(link.get_attribute('tabIndex'), '-1')
|
||||
main_element = self.selenium.find_element(By.CSS_SELECTOR, '#main')
|
||||
self.assertNotIn('shifted', main_element.get_attribute('class').split())
|
||||
for link in self.selenium.find_elements(By.CSS_SELECTOR, "#nav-sidebar a"):
|
||||
self.assertEqual(link.get_attribute("tabIndex"), "-1")
|
||||
main_element = self.selenium.find_element(By.CSS_SELECTOR, "#main")
|
||||
self.assertNotIn("shifted", main_element.get_attribute("class").split())
|
||||
|
||||
def test_sidebar_state_persists(self):
|
||||
from selenium.webdriver.common.by import By
|
||||
self.selenium.get(self.live_server_url + reverse('test_with_sidebar:auth_user_changelist'))
|
||||
self.assertIsNone(self.selenium.execute_script("return localStorage.getItem('django.admin.navSidebarIsOpen')"))
|
||||
toggle_button = self.selenium.find_element(By.CSS_SELECTOR, '#toggle-nav-sidebar')
|
||||
toggle_button.click()
|
||||
self.assertEqual(
|
||||
self.selenium.execute_script("return localStorage.getItem('django.admin.navSidebarIsOpen')"),
|
||||
'false',
|
||||
)
|
||||
self.selenium.get(self.live_server_url + reverse('test_with_sidebar:auth_user_changelist'))
|
||||
main_element = self.selenium.find_element(By.CSS_SELECTOR, '#main')
|
||||
self.assertNotIn('shifted', main_element.get_attribute('class').split())
|
||||
|
||||
toggle_button = self.selenium.find_element(By.CSS_SELECTOR, '#toggle-nav-sidebar')
|
||||
# Hidden sidebar is not reachable via keyboard navigation.
|
||||
for link in self.selenium.find_elements(By.CSS_SELECTOR, '#nav-sidebar a'):
|
||||
self.assertEqual(link.get_attribute('tabIndex'), '-1')
|
||||
toggle_button.click()
|
||||
for link in self.selenium.find_elements(By.CSS_SELECTOR, '#nav-sidebar a'):
|
||||
self.assertEqual(link.get_attribute('tabIndex'), '0')
|
||||
self.assertEqual(
|
||||
self.selenium.execute_script("return localStorage.getItem('django.admin.navSidebarIsOpen')"),
|
||||
'true',
|
||||
self.selenium.get(
|
||||
self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
|
||||
)
|
||||
self.selenium.get(self.live_server_url + reverse('test_with_sidebar:auth_user_changelist'))
|
||||
main_element = self.selenium.find_element(By.CSS_SELECTOR, '#main')
|
||||
self.assertIn('shifted', main_element.get_attribute('class').split())
|
||||
self.assertIsNone(
|
||||
self.selenium.execute_script(
|
||||
"return localStorage.getItem('django.admin.navSidebarIsOpen')"
|
||||
)
|
||||
)
|
||||
toggle_button = self.selenium.find_element(
|
||||
By.CSS_SELECTOR, "#toggle-nav-sidebar"
|
||||
)
|
||||
toggle_button.click()
|
||||
self.assertEqual(
|
||||
self.selenium.execute_script(
|
||||
"return localStorage.getItem('django.admin.navSidebarIsOpen')"
|
||||
),
|
||||
"false",
|
||||
)
|
||||
self.selenium.get(
|
||||
self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
|
||||
)
|
||||
main_element = self.selenium.find_element(By.CSS_SELECTOR, "#main")
|
||||
self.assertNotIn("shifted", main_element.get_attribute("class").split())
|
||||
|
||||
toggle_button = self.selenium.find_element(
|
||||
By.CSS_SELECTOR, "#toggle-nav-sidebar"
|
||||
)
|
||||
# Hidden sidebar is not reachable via keyboard navigation.
|
||||
for link in self.selenium.find_elements(By.CSS_SELECTOR, "#nav-sidebar a"):
|
||||
self.assertEqual(link.get_attribute("tabIndex"), "-1")
|
||||
toggle_button.click()
|
||||
for link in self.selenium.find_elements(By.CSS_SELECTOR, "#nav-sidebar a"):
|
||||
self.assertEqual(link.get_attribute("tabIndex"), "0")
|
||||
self.assertEqual(
|
||||
self.selenium.execute_script(
|
||||
"return localStorage.getItem('django.admin.navSidebarIsOpen')"
|
||||
),
|
||||
"true",
|
||||
)
|
||||
self.selenium.get(
|
||||
self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
|
||||
)
|
||||
main_element = self.selenium.find_element(By.CSS_SELECTOR, "#main")
|
||||
self.assertIn("shifted", main_element.get_attribute("class").split())
|
||||
|
||||
def test_sidebar_filter_persists(self):
|
||||
from selenium.webdriver.common.by import By
|
||||
|
||||
self.selenium.get(
|
||||
self.live_server_url +
|
||||
reverse('test_with_sidebar:auth_user_changelist')
|
||||
self.live_server_url + reverse("test_with_sidebar:auth_user_changelist")
|
||||
)
|
||||
filter_value_script = (
|
||||
"return sessionStorage.getItem('django.admin.navSidebarFilterValue')"
|
||||
)
|
||||
self.assertIsNone(self.selenium.execute_script(filter_value_script))
|
||||
filter_input = self.selenium.find_element(By.CSS_SELECTOR, '#nav-filter')
|
||||
filter_input.send_keys('users')
|
||||
self.assertEqual(self.selenium.execute_script(filter_value_script), 'users')
|
||||
filter_input = self.selenium.find_element(By.CSS_SELECTOR, "#nav-filter")
|
||||
filter_input.send_keys("users")
|
||||
self.assertEqual(self.selenium.execute_script(filter_value_script), "users")
|
||||
|
||||
@@ -20,24 +20,28 @@ class AdminTemplateTagsTest(AdminViewBasicTestCase):
|
||||
"""
|
||||
submit_row template tag should pass whole context.
|
||||
"""
|
||||
request = self.request_factory.get(reverse('admin:auth_user_change', args=[self.superuser.pk]))
|
||||
request = self.request_factory.get(
|
||||
reverse("admin:auth_user_change", args=[self.superuser.pk])
|
||||
)
|
||||
request.user = self.superuser
|
||||
admin = UserAdmin(User, site)
|
||||
extra_context = {'extra': True}
|
||||
response = admin.change_view(request, str(self.superuser.pk), extra_context=extra_context)
|
||||
extra_context = {"extra": True}
|
||||
response = admin.change_view(
|
||||
request, str(self.superuser.pk), extra_context=extra_context
|
||||
)
|
||||
template_context = submit_row(response.context_data)
|
||||
self.assertIs(template_context['extra'], True)
|
||||
self.assertIs(template_context['show_save'], True)
|
||||
self.assertIs(template_context["extra"], True)
|
||||
self.assertIs(template_context["show_save"], True)
|
||||
|
||||
def test_override_show_save_and_add_another(self):
|
||||
request = self.request_factory.get(
|
||||
reverse('admin:auth_user_change', args=[self.superuser.pk]),
|
||||
reverse("admin:auth_user_change", args=[self.superuser.pk]),
|
||||
)
|
||||
request.user = self.superuser
|
||||
admin = UserAdmin(User, site)
|
||||
for extra_context, expected_flag in (
|
||||
({}, True), # Default.
|
||||
({'show_save_and_add_another': False}, False),
|
||||
({"show_save_and_add_another": False}, False),
|
||||
):
|
||||
with self.subTest(show_save_and_add_another=expected_flag):
|
||||
response = admin.change_view(
|
||||
@@ -46,7 +50,9 @@ class AdminTemplateTagsTest(AdminViewBasicTestCase):
|
||||
extra_context=extra_context,
|
||||
)
|
||||
template_context = submit_row(response.context_data)
|
||||
self.assertIs(template_context['show_save_and_add_another'], expected_flag)
|
||||
self.assertIs(
|
||||
template_context["show_save_and_add_another"], expected_flag
|
||||
)
|
||||
|
||||
def test_override_change_form_template_tags(self):
|
||||
"""
|
||||
@@ -54,37 +60,43 @@ class AdminTemplateTagsTest(AdminViewBasicTestCase):
|
||||
admin/app_label/model/template.html.
|
||||
"""
|
||||
article = Article.objects.all()[0]
|
||||
request = self.request_factory.get(reverse('admin:admin_views_article_change', args=[article.pk]))
|
||||
request = self.request_factory.get(
|
||||
reverse("admin:admin_views_article_change", args=[article.pk])
|
||||
)
|
||||
request.user = self.superuser
|
||||
admin = ArticleAdmin(Article, site)
|
||||
extra_context = {'show_publish': True, 'extra': True}
|
||||
response = admin.change_view(request, str(article.pk), extra_context=extra_context)
|
||||
extra_context = {"show_publish": True, "extra": True}
|
||||
response = admin.change_view(
|
||||
request, str(article.pk), extra_context=extra_context
|
||||
)
|
||||
response.render()
|
||||
self.assertIs(response.context_data['show_publish'], True)
|
||||
self.assertIs(response.context_data['extra'], True)
|
||||
self.assertIs(response.context_data["show_publish"], True)
|
||||
self.assertIs(response.context_data["extra"], True)
|
||||
self.assertContains(response, 'name="_save"')
|
||||
self.assertContains(response, 'name="_publish"')
|
||||
self.assertContains(response, 'override-change_form_object_tools')
|
||||
self.assertContains(response, 'override-prepopulated_fields_js')
|
||||
self.assertContains(response, "override-change_form_object_tools")
|
||||
self.assertContains(response, "override-prepopulated_fields_js")
|
||||
|
||||
def test_override_change_list_template_tags(self):
|
||||
"""
|
||||
admin_list template tags follow the standard search pattern
|
||||
admin/app_label/model/template.html.
|
||||
"""
|
||||
request = self.request_factory.get(reverse('admin:admin_views_article_changelist'))
|
||||
request = self.request_factory.get(
|
||||
reverse("admin:admin_views_article_changelist")
|
||||
)
|
||||
request.user = self.superuser
|
||||
admin = ArticleAdmin(Article, site)
|
||||
admin.date_hierarchy = 'date'
|
||||
admin.search_fields = ('title', 'content')
|
||||
admin.date_hierarchy = "date"
|
||||
admin.search_fields = ("title", "content")
|
||||
response = admin.changelist_view(request)
|
||||
response.render()
|
||||
self.assertContains(response, 'override-actions')
|
||||
self.assertContains(response, 'override-change_list_object_tools')
|
||||
self.assertContains(response, 'override-change_list_results')
|
||||
self.assertContains(response, 'override-date_hierarchy')
|
||||
self.assertContains(response, 'override-pagination')
|
||||
self.assertContains(response, 'override-search_form')
|
||||
self.assertContains(response, "override-actions")
|
||||
self.assertContains(response, "override-change_list_object_tools")
|
||||
self.assertContains(response, "override-change_list_results")
|
||||
self.assertContains(response, "override-date_hierarchy")
|
||||
self.assertContains(response, "override-pagination")
|
||||
self.assertContains(response, "override-search_form")
|
||||
|
||||
|
||||
class DateHierarchyTests(TestCase):
|
||||
@@ -92,11 +104,13 @@ class DateHierarchyTests(TestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.superuser = User.objects.create_superuser(username='super', password='secret', email='super@example.com')
|
||||
cls.superuser = User.objects.create_superuser(
|
||||
username="super", password="secret", email="super@example.com"
|
||||
)
|
||||
|
||||
def test_choice_links(self):
|
||||
modeladmin = ModelAdmin(Question, site)
|
||||
modeladmin.date_hierarchy = 'posted'
|
||||
modeladmin.date_hierarchy = "posted"
|
||||
|
||||
posted_dates = (
|
||||
datetime.date(2017, 10, 1),
|
||||
@@ -106,67 +120,82 @@ class DateHierarchyTests(TestCase):
|
||||
datetime.date(2017, 12, 31),
|
||||
datetime.date(2018, 2, 1),
|
||||
)
|
||||
Question.objects.bulk_create(Question(question='q', posted=posted) for posted in posted_dates)
|
||||
Question.objects.bulk_create(
|
||||
Question(question="q", posted=posted) for posted in posted_dates
|
||||
)
|
||||
|
||||
tests = (
|
||||
({}, [['year=2017'], ['year=2018']]),
|
||||
({'year': 2016}, []),
|
||||
({'year': 2017}, [['month=10', 'year=2017'], ['month=12', 'year=2017']]),
|
||||
({'year': 2017, 'month': 9}, []),
|
||||
({'year': 2017, 'month': 12}, [['day=15', 'month=12', 'year=2017'], ['day=31', 'month=12', 'year=2017']]),
|
||||
({}, [["year=2017"], ["year=2018"]]),
|
||||
({"year": 2016}, []),
|
||||
({"year": 2017}, [["month=10", "year=2017"], ["month=12", "year=2017"]]),
|
||||
({"year": 2017, "month": 9}, []),
|
||||
(
|
||||
{"year": 2017, "month": 12},
|
||||
[
|
||||
["day=15", "month=12", "year=2017"],
|
||||
["day=31", "month=12", "year=2017"],
|
||||
],
|
||||
),
|
||||
)
|
||||
for query, expected_choices in tests:
|
||||
with self.subTest(query=query):
|
||||
query = {'posted__%s' % q: val for q, val in query.items()}
|
||||
request = self.factory.get('/', query)
|
||||
query = {"posted__%s" % q: val for q, val in query.items()}
|
||||
request = self.factory.get("/", query)
|
||||
request.user = self.superuser
|
||||
changelist = modeladmin.get_changelist_instance(request)
|
||||
spec = date_hierarchy(changelist)
|
||||
choices = [choice['link'] for choice in spec['choices']]
|
||||
choices = [choice["link"] for choice in spec["choices"]]
|
||||
expected_choices = [
|
||||
'&'.join('posted__%s' % c for c in choice) for choice in expected_choices
|
||||
"&".join("posted__%s" % c for c in choice)
|
||||
for choice in expected_choices
|
||||
]
|
||||
expected_choices = [
|
||||
("?" + choice) if choice else "" for choice in expected_choices
|
||||
]
|
||||
expected_choices = [('?' + choice) if choice else '' for choice in expected_choices]
|
||||
self.assertEqual(choices, expected_choices)
|
||||
|
||||
def test_choice_links_datetime(self):
|
||||
modeladmin = ModelAdmin(Question, site)
|
||||
modeladmin.date_hierarchy = 'expires'
|
||||
Question.objects.bulk_create([
|
||||
Question(question='q1', expires=datetime.datetime(2017, 10, 1)),
|
||||
Question(question='q2', expires=datetime.datetime(2017, 10, 1)),
|
||||
Question(question='q3', expires=datetime.datetime(2017, 12, 15)),
|
||||
Question(question='q4', expires=datetime.datetime(2017, 12, 15)),
|
||||
Question(question='q5', expires=datetime.datetime(2017, 12, 31)),
|
||||
Question(question='q6', expires=datetime.datetime(2018, 2, 1)),
|
||||
])
|
||||
modeladmin.date_hierarchy = "expires"
|
||||
Question.objects.bulk_create(
|
||||
[
|
||||
Question(question="q1", expires=datetime.datetime(2017, 10, 1)),
|
||||
Question(question="q2", expires=datetime.datetime(2017, 10, 1)),
|
||||
Question(question="q3", expires=datetime.datetime(2017, 12, 15)),
|
||||
Question(question="q4", expires=datetime.datetime(2017, 12, 15)),
|
||||
Question(question="q5", expires=datetime.datetime(2017, 12, 31)),
|
||||
Question(question="q6", expires=datetime.datetime(2018, 2, 1)),
|
||||
]
|
||||
)
|
||||
tests = [
|
||||
({}, [['year=2017'], ['year=2018']]),
|
||||
({'year': 2016}, []),
|
||||
({}, [["year=2017"], ["year=2018"]]),
|
||||
({"year": 2016}, []),
|
||||
(
|
||||
{'year': 2017}, [
|
||||
['month=10', 'year=2017'],
|
||||
['month=12', 'year=2017'],
|
||||
{"year": 2017},
|
||||
[
|
||||
["month=10", "year=2017"],
|
||||
["month=12", "year=2017"],
|
||||
],
|
||||
),
|
||||
({'year': 2017, 'month': 9}, []),
|
||||
({"year": 2017, "month": 9}, []),
|
||||
(
|
||||
{'year': 2017, 'month': 12}, [
|
||||
['day=15', 'month=12', 'year=2017'],
|
||||
['day=31', 'month=12', 'year=2017'],
|
||||
{"year": 2017, "month": 12},
|
||||
[
|
||||
["day=15", "month=12", "year=2017"],
|
||||
["day=31", "month=12", "year=2017"],
|
||||
],
|
||||
),
|
||||
]
|
||||
for query, expected_choices in tests:
|
||||
with self.subTest(query=query):
|
||||
query = {'expires__%s' % q: val for q, val in query.items()}
|
||||
request = self.factory.get('/', query)
|
||||
query = {"expires__%s" % q: val for q, val in query.items()}
|
||||
request = self.factory.get("/", query)
|
||||
request.user = self.superuser
|
||||
changelist = modeladmin.get_changelist_instance(request)
|
||||
spec = date_hierarchy(changelist)
|
||||
choices = [choice['link'] for choice in spec['choices']]
|
||||
choices = [choice["link"] for choice in spec["choices"]]
|
||||
expected_choices = [
|
||||
'?' + '&'.join('expires__%s' % c for c in choice)
|
||||
"?" + "&".join("expires__%s" % c for c in choice)
|
||||
for choice in expected_choices
|
||||
]
|
||||
self.assertEqual(choices, expected_choices)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,23 +10,31 @@ def non_admin_view(request):
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path('test_admin/admin/doc/', include('django.contrib.admindocs.urls')),
|
||||
path('test_admin/admin/secure-view/', views.secure_view, name='secure_view'),
|
||||
path('test_admin/admin/secure-view2/', views.secure_view2, name='secure_view2'),
|
||||
path('test_admin/admin/', admin.site.urls),
|
||||
path('test_admin/admin2/', customadmin.site.urls),
|
||||
path('test_admin/admin3/', (admin.site.get_urls(), 'admin', 'admin3'), {'form_url': 'pony'}),
|
||||
path('test_admin/admin4/', customadmin.simple_site.urls),
|
||||
path('test_admin/admin5/', admin.site2.urls),
|
||||
path('test_admin/admin6/', admin.site6.urls),
|
||||
path('test_admin/admin7/', admin.site7.urls),
|
||||
path("test_admin/admin/doc/", include("django.contrib.admindocs.urls")),
|
||||
path("test_admin/admin/secure-view/", views.secure_view, name="secure_view"),
|
||||
path("test_admin/admin/secure-view2/", views.secure_view2, name="secure_view2"),
|
||||
path("test_admin/admin/", admin.site.urls),
|
||||
path("test_admin/admin2/", customadmin.site.urls),
|
||||
path(
|
||||
"test_admin/admin3/",
|
||||
(admin.site.get_urls(), "admin", "admin3"),
|
||||
{"form_url": "pony"},
|
||||
),
|
||||
path("test_admin/admin4/", customadmin.simple_site.urls),
|
||||
path("test_admin/admin5/", admin.site2.urls),
|
||||
path("test_admin/admin6/", admin.site6.urls),
|
||||
path("test_admin/admin7/", admin.site7.urls),
|
||||
# All admin views accept `extra_context` to allow adding it like this:
|
||||
path('test_admin/admin8/', (admin.site.get_urls(), 'admin', 'admin-extra-context'), {'extra_context': {}}),
|
||||
path('test_admin/admin9/', admin.site9.urls),
|
||||
path('test_admin/admin10/', admin.site10.urls),
|
||||
path('test_admin/has_permission_admin/', custom_has_permission_admin.site.urls),
|
||||
path('test_admin/autocomplete_admin/', autocomplete_site.urls),
|
||||
path(
|
||||
"test_admin/admin8/",
|
||||
(admin.site.get_urls(), "admin", "admin-extra-context"),
|
||||
{"extra_context": {}},
|
||||
),
|
||||
path("test_admin/admin9/", admin.site9.urls),
|
||||
path("test_admin/admin10/", admin.site10.urls),
|
||||
path("test_admin/has_permission_admin/", custom_has_permission_admin.site.urls),
|
||||
path("test_admin/autocomplete_admin/", autocomplete_site.urls),
|
||||
# Shares the admin URL prefix.
|
||||
path('test_admin/admin/non_admin_view/', non_admin_view, name='non_admin'),
|
||||
path('test_admin/admin10/non_admin_view/', non_admin_view, name='non_admin10'),
|
||||
path("test_admin/admin/non_admin_view/", non_admin_view, name="non_admin"),
|
||||
path("test_admin/admin10/non_admin_view/", non_admin_view, name="non_admin10"),
|
||||
]
|
||||
|
||||
@@ -7,6 +7,6 @@ def secure_view(request):
|
||||
return HttpResponse(str(request.POST))
|
||||
|
||||
|
||||
@staff_member_required(redirect_field_name='myfield')
|
||||
@staff_member_required(redirect_field_name="myfield")
|
||||
def secure_view2(request):
|
||||
return HttpResponse(str(request.POST))
|
||||
|
||||
Reference in New Issue
Block a user