Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Ludvig Wadenstein 2013-05-19 10:35:43 +02:00
commit 151bbce7ae
17 changed files with 256 additions and 102 deletions

View file

@ -1,6 +1,5 @@
language: python language: python
python: python:
- "2.6"
- "2.7" - "2.7"
before_install: before_install:
- export PIP_USE_MIRRORS=true - export PIP_USE_MIRRORS=true

View file

@ -15,6 +15,14 @@ Contributing
Yes please! Please read our formal contributing document at: https://github.com/pydanny/django-admin2/blob/master/docs/contributing.rst Yes please! Please read our formal contributing document at: https://github.com/pydanny/django-admin2/blob/master/docs/contributing.rst
Requirements
=============
* Django 1.5+
* Python 2.7+ (Python 3.3+ support is pending)
* django-braces
* django-rest-framework
* Sphinx (for documentation)
Basic Pattern Basic Pattern
============== ==============
@ -23,20 +31,25 @@ Our goal is to make this API work:
.. code-block:: python .. code-block:: python
# myapp/admin2.py # Import your custom models
from .models import Post, Comment
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth.models import User
# Import the Admin2 base class import djadmin2
from admin2.models import Admin2 from djadmin2.models import ModelAdmin2
# Import your custom models
from blog.models import Post
# Instantiate the Admin2 class class UserAdmin2(ModelAdmin2):
# Then attach the admin2 object to your model create_form_class = UserCreationForm
Post.admin2 = Admin2() update_form_class = UserChangeForm
# Register each model with the admin
djadmin2.default.register(Post)
djadmin2.default.register(Comment)
djadmin2.default.register(User, UserAdmin2)
.. note:: You will notice a difference between how and django.contrib.admin and django-admin2 do configuration. The former associates the configuration class with the model object via a registration utility, and the latter does so by adding the configuration class as an attribute of the model object.
Themes Themes
======== ========

View file

@ -1,4 +1,4 @@
__version__ = '0.1.1' __version__ = '0.2.0'
__author__ = 'Daniel Greenfeld' __author__ = 'Daniel Greenfeld'
@ -7,7 +7,7 @@ VERSION = __version__ # synonym
# Default datetime input and output formats # Default datetime input and output formats
ISO_8601 = 'iso-8601' ISO_8601 = 'iso-8601'
from . import core from . import core
default = core.Admin2() default = core.Admin2()

View file

@ -9,14 +9,29 @@ from . import views
class Admin2(object): class Admin2(object):
"""
The base Admin2 object.
It keeps a registry of all registered Models and collects the urls of their
related ModelAdmin2 instances.
It also provides an index view that serves as an entry point to the admin site.
"""
index_view = views.IndexView index_view = views.IndexView
def __init__(self, name='admin2', app_name='admin2'): def __init__(self, name='admin2'):
self.registry = {} self.registry = {}
self.name = name self.name = name
self.app_name = app_name
def register(self, model, modeladmin=None, **kwargs): def register(self, model, modeladmin=None, **kwargs):
"""
Registers the given model with the given admin class.
If no modeladmin is passed, it will use ModelAdmin2. If keyword
arguments are given they will be passed to the admin class on
instantiation.
If a model is already registered, this will raise ImproperlyConfigured.
"""
if model in self.registry: if model in self.registry:
raise ImproperlyConfigured raise ImproperlyConfigured
if not modeladmin: if not modeladmin:
@ -24,13 +39,21 @@ class Admin2(object):
self.registry[model] = modeladmin(model, **kwargs) self.registry[model] = modeladmin(model, **kwargs)
def deregister(self, model): def deregister(self, model):
"""
Deregisters the given model.
If the model is not already registered, this will raise ImproperlyConfigured.
"""
try: try:
del self.registry[model] del self.registry[model]
except KeyError: except KeyError:
raise ImproperlyConfigured raise ImproperlyConfigured
def autodiscover(self): def autodiscover(self):
apps = [] """
Autodiscovers all admin2.py modules for apps in INSTALLED_APPS by
trying to import them.
"""
for app_name in [x for x in settings.INSTALLED_APPS]: for app_name in [x for x in settings.INSTALLED_APPS]:
try: try:
import_module("%s.admin2" % app_name) import_module("%s.admin2" % app_name)
@ -46,11 +69,11 @@ class Admin2(object):
def get_urls(self): def get_urls(self):
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', self.index_view.as_view(**self.get_index_kwargs()), name='index'), url(r'^$', self.index_view.as_view(**self.get_index_kwargs()), name='dashboard'),
) )
for model, modeladmin in self.registry.iteritems(): for model, modeladmin in self.registry.iteritems():
app_label = model._meta.app_label app_label = model._meta.app_label
model_name = model._meta.object_name.lower() model_name = model._meta.object_name.lower()
urlpatterns += patterns('', urlpatterns += patterns('',
url('^{}/{}/'.format(app_label, model_name), url('^{}/{}/'.format(app_label, model_name),
@ -60,4 +83,4 @@ class Admin2(object):
@property @property
def urls(self): def urls(self):
return self.get_urls(), self.app_name, self.name return self.get_urls(), self.name, self.name

View file

@ -4,7 +4,10 @@ For wont of a better name, this module is called 'models'. It's role is
synonymous with the django.contrib.admin.sites model. synonymous with the django.contrib.admin.sites model.
""" """
from django.conf.urls import patterns, include, url
from django.core.urlresolvers import reverse
from django.conf.urls import patterns, url
from django.contrib.auth import models as auth_app from django.contrib.auth import models as auth_app
from django.db.models import get_models, signals from django.db.models import get_models, signals
@ -16,7 +19,6 @@ except ImportError:
from django import forms from django import forms
class BaseAdmin2(object): class BaseAdmin2(object):
search_fields = [] search_fields = []
@ -46,13 +48,11 @@ class BaseAdmin2(object):
readonly_fields = () readonly_fields = ()
ordering = None ordering = None
def __init__(self, model): def __init__(self, model):
super(BaseAdmin2, self).__init__() super(BaseAdmin2, self).__init__()
self.model = model self.model = model
def _user_has_permission(self, user, permission_type, obj=None): def _user_has_permission(self, user, permission_type, obj=None):
""" Generic method for checking whether the user has permission of specified type for the model. """ Generic method for checking whether the user has permission of specified type for the model.
Type can be one of view, add, change, delete. Type can be one of view, add, change, delete.
@ -110,6 +110,8 @@ class ModelAdmin2(BaseAdmin2):
def __init__(self, model, **kwargs): def __init__(self, model, **kwargs):
self.model = model self.model = model
self.app_label = model._meta.app_label
self.model_name = model._meta.object_name.lower()
if self.verbose_name is None: if self.verbose_name is None:
self.verbose_name = self.model._meta.verbose_name self.verbose_name = self.model._meta.verbose_name
@ -118,10 +120,15 @@ class ModelAdmin2(BaseAdmin2):
def get_default_view_kwargs(self): def get_default_view_kwargs(self):
return { return {
'app_label': self.app_label,
'model': self.model, 'model': self.model,
'model_name': self.model_name,
'modeladmin': self, 'modeladmin': self,
} }
def get_prefixed_view_name(self, view_name):
return '{}_{}_{}'.format(self.app_label, self.model_name, view_name)
def get_index_kwargs(self): def get_index_kwargs(self):
return self.get_default_view_kwargs() return self.get_default_view_kwargs()
@ -145,32 +152,35 @@ class ModelAdmin2(BaseAdmin2):
def get_delete_kwargs(self): def get_delete_kwargs(self):
return self.get_default_view_kwargs() return self.get_default_view_kwargs()
def get_index_url(self):
return reverse('admin2:{}'.format(self.get_prefixed_view_name('index')))
def get_urls(self): def get_urls(self):
return patterns('', return patterns('',
url( url(
regex=r'^$', regex=r'^$',
view=self.index_view.as_view(**self.get_index_kwargs()), view=self.index_view.as_view(**self.get_index_kwargs()),
name='index' name=self.get_prefixed_view_name('index')
), ),
url( url(
regex=r'^create/$', regex=r'^create/$',
view=self.create_view.as_view(**self.get_create_kwargs()), view=self.create_view.as_view(**self.get_create_kwargs()),
name='create' name=self.get_prefixed_view_name('create')
), ),
url( url(
regex=r'^(?P<pk>[0-9]+)/$', regex=r'^(?P<pk>[0-9]+)/$',
view=self.detail_view.as_view(**self.get_detail_kwargs()), view=self.detail_view.as_view(**self.get_detail_kwargs()),
name='detail' name=self.get_prefixed_view_name('detail')
), ),
url( url(
regex=r'^(?P<pk>[0-9]+)/update/$', regex=r'^(?P<pk>[0-9]+)/update/$',
view=self.update_view.as_view(**self.get_update_kwargs()), view=self.update_view.as_view(**self.get_update_kwargs()),
name='update' name=self.get_prefixed_view_name('update')
), ),
url( url(
regex=r'^(?P<pk>[0-9]+)/delete/$', regex=r'^(?P<pk>[0-9]+)/delete/$',
view=self.delete_view.as_view(**self.get_delete_kwargs()), view=self.delete_view.as_view(**self.get_delete_kwargs()),
name='delete' name=self.get_prefixed_view_name('delete')
), ),
) )
@ -179,9 +189,7 @@ class ModelAdmin2(BaseAdmin2):
# We set the application and instance namespace here # We set the application and instance namespace here
return self.get_urls(), None, None return self.get_urls(), None, None
def create_extra_permissions(app, created_models, verbosity, **kwargs):
def create_permissions(app, created_models, verbosity, **kwargs):
""" """
Creates 'view' permissions for all models. Creates 'view' permissions for all models.
django.contrib.auth only creates add, change and delete permissions. Since we also support read-only views, we need django.contrib.auth only creates add, change and delete permissions. Since we also support read-only views, we need
@ -190,9 +198,6 @@ def create_permissions(app, created_models, verbosity, **kwargs):
""" """
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
def _get_permission_codename(action, opts):
return u'%s_%s' % (action, opts.object_name.lower())
app_models = get_models(app) app_models = get_models(app)
# This will hold the permissions we're looking for as # This will hold the permissions we're looking for as
@ -205,10 +210,10 @@ def create_permissions(app, created_models, verbosity, **kwargs):
ctypes.add(ctype) ctypes.add(ctype)
opts = klass._meta opts = klass._meta
perm = (_get_permission_codename('view', opts), u'Can view %s' % opts.verbose_name_raw) perm = ('view_%s' % opts.object_name.lower(), u'Can view %s' % opts.verbose_name_raw)
searched_perms.append((ctype, perm)) searched_perms.append((ctype, perm))
# Find all the Permissions that have a context_type for a model we're # Find all the Permissions that have a content_type for a model we're
# looking for. We don't need to check for codenames since we already have # looking for. We don't need to check for codenames since we already have
# a list of the ones we're going to create. # a list of the ones we're going to create.
all_perms = set(auth_app.Permission.objects.filter( all_perms = set(auth_app.Permission.objects.filter(
@ -217,16 +222,16 @@ def create_permissions(app, created_models, verbosity, **kwargs):
"content_type", "codename" "content_type", "codename"
)) ))
objs = [ perms = [
auth_app.Permission(codename=codename, name=name, content_type=ctype) auth_app.Permission(codename=codename, name=name, content_type=ctype)
for ctype, (codename, name) in searched_perms for ctype, (codename, name) in searched_perms
if (ctype.pk, codename) not in all_perms if (ctype.pk, codename) not in all_perms
] ]
auth_app.Permission.objects.bulk_create(objs) auth_app.Permission.objects.bulk_create(perms)
if verbosity >= 2: if verbosity >= 2:
for obj in objs: for perm in perms:
print "Adding permission '%s'" % obj print "Adding permission '%s'" % perm
signals.post_syncdb.connect(create_permissions, signals.post_syncdb.connect(create_extra_permissions,
dispatch_uid = "django-admin2.djadmin2.models.create_permissions") dispatch_uid = "django-admin2.djadmin2.models.create_extra_permissions")

View file

@ -1,19 +1,33 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>django-admin2</title> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Bootstrap -->
<link href="{{ STATIC_URL }}themes/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
</head>
<body>
<div class="container-fluid">
{% block content %}{% endblock %}
</div>
<script src="http://code.jquery.com/jquery.js"></script> <title>django-admin2</title>
<script src="{{ STATIC_URL }}themes/bootstrap/js/bootstrap.min.js"></script> <meta name="viewport" content="width=device-width, initial-scale=1.0">
{% block extrajs %}{% endblock %} <!-- Bootstrap -->
<link href="{{ STATIC_URL }}themes/bootstrap/css/bootstrap.min.css" rel="stylesheet" media="screen">
</head>
<body>
<div class="navbar navbar-inverse navbar-static-top">
<div class="navbar-inner">
<div class="container-fluid">
<a class="brand" href="{% url 'admin2:dashboard' %}">Django-Admin2</a>
</body> <ul class="nav pull-right">
<li><a href="TODO">Log out</a></li>
</ul>
</div>
</div>
</div>
<div class="container-fluid">
{% block content %}{% endblock %}
</div>
<script src="http://code.jquery.com/jquery.js"></script>
<script src="{{ STATIC_URL }}themes/bootstrap/js/bootstrap.min.js"></script>
{% block extrajs %}{% endblock %}
</body>
</html> </html>

View file

@ -4,7 +4,7 @@
<h1>Index</h1> <h1>Index</h1>
<table> <table>
{% for modeladmin in registry.values %} {% for modeladmin in registry.values %}
<tr><td><a href="">{{ modeladmin.verbose_name_plural }}</a></td></tr> <tr><td><a href="{{ modeladmin.get_index_url }}">{{ modeladmin.verbose_name_plural }}</a></td></tr>
{% endfor %} {% endfor %}
</table> </table>
{% endblock content %} {% endblock content %}

View file

@ -1,4 +1,5 @@
{% extends "admin2/bootstrap/base.html" %} {% extends "admin2/bootstrap/base.html" %}
{% load admin2_urls %}
{% block content %} {% block content %}
<div class="row"> <div class="row">
@ -6,9 +7,9 @@
<h3>Select {{ model }} to change</h3> <h3>Select {{ model }} to change</h3>
</div> </div>
<div class="span2"> <div class="span2">
{% if has_add_permission %} {# if has_add_permission #}
<a class="btn" href="./create/">Add {{ model|title }} <i class=" icon-plus-sign"></i></a> <a href="{% url view|admin2_urlname:'create' %}">add</a>
{% endif %} {# endif #}
</div> </div>
</div> </div>
@ -16,6 +17,14 @@
<div class="row"> <div class="row">
<div class="span12"> <div class="span12">
Action:
<select>
<option>----------</option>
<option>Delete selected {{ model }}{{ object_list|pluralize }}</option>
</select>
<a class="btn btn-mini" href="TODO">Go</a>
TODO of {{ object_list|length }} selected
<table class="table table-bordered table-striped"> <table class="table table-bordered table-striped">
<thead> <thead>
<th><input type="checkbox"></th> <th><input type="checkbox"></th>
@ -25,17 +34,19 @@
{% for obj in object_list %} {% for obj in object_list %}
<td><input type="checkbox"></td> <td><input type="checkbox"></td>
<td> <td>
{{ obj }} <a href="./{{ obj.pk }}/">detail</a> {{ obj }} <a href="{% url view|admin2_urlname:'detail' pk=obj.pk %}">detail</a>
{% if has_edit_permission %} {# if has_edit_permission #}
<a href="./{{ obj.pk }}/update/">edit</a> <a href="{% url view|admin2_urlname:'update' pk=obj.pk %}">edit</a>
{% endif %} {# endif #}
{% if has_delete_permission %} {# if has_delete_permission #}
<a href="./{{ obj.pk }}/delete/">delete</a> <a href="{% url view|admin2_urlname:'delete' pk=obj.pk %}">delete</a>
{% endif %} {# endif #}
</td> </td>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
{{ object_list|length }} {{ model }}{{ object_list|pluralize }}
</div> </div>
</div> </div>

View file

@ -0,0 +1,8 @@
from django import template
register = template.Library()
@register.filter
def admin2_urlname(value, arg):
return 'admin2:%s_%s_%s' % (value.app_label, value.model_name, arg)

View file

@ -6,9 +6,11 @@ from django.core.exceptions import ImproperlyConfigured
from ..models import ModelAdmin2 from ..models import ModelAdmin2
from ..core import Admin2 from ..core import Admin2
class Thing(models.Model): class Thing(models.Model):
pass pass
class Admin2Test(unittest.TestCase): class Admin2Test(unittest.TestCase):
def setUp(self): def setUp(self):
self.admin2 = Admin2() self.admin2 = Admin2()
@ -28,7 +30,7 @@ class Admin2Test(unittest.TestCase):
def test_deregister_error(self): def test_deregister_error(self):
self.assertRaises(ImproperlyConfigured, self.admin2.deregister, Thing) self.assertRaises(ImproperlyConfigured, self.admin2.deregister, Thing)
def test_get_urls(self): def test_get_urls(self):
self.admin2.register(Thing) self.admin2.register(Thing)
self.assertEquals(2, len(self.admin2.get_urls())) self.assertEquals(2, len(self.admin2.get_urls()))

View file

@ -1,22 +1,36 @@
import os import os
from django.core.urlresolvers import reverse
from django.conf import settings from django.conf import settings
from django.contrib.auth.views import redirect_to_login from django.contrib.auth.views import redirect_to_login
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.forms.models import modelform_factory from django.forms.models import modelform_factory
from django.views import generic from django.views import generic
from django.db import models
from braces.views import LoginRequiredMixin, StaffuserRequiredMixin, AccessMixin
from braces.views import AccessMixin
ADMIN2_THEME_DIRECTORY = getattr(settings, "ADMIN2_THEME_DIRECTORY", "admin2/bootstrap") ADMIN2_THEME_DIRECTORY = getattr(settings, "ADMIN2_THEME_DIRECTORY", "admin2/bootstrap")
class Admin2Mixin(object): class Admin2Mixin(object):
modeladmin = None
model_name = None
app_label = None
def get_template_names(self): def get_template_names(self):
return [os.path.join(ADMIN2_THEME_DIRECTORY, self.default_template_name)] return [os.path.join(ADMIN2_THEME_DIRECTORY, self.default_template_name)]
def get_model(self):
return self.model
def get_queryset(self):
return self.get_model()._default_manager.all()
def get_form_class(self):
if self.form_class is not None:
return self.form_class
return modelform_factory(self.get_model())
class AdminModel2Mixin(Admin2Mixin, AccessMixin): class AdminModel2Mixin(Admin2Mixin, AccessMixin):
modeladmin = None modeladmin = None
@ -70,7 +84,9 @@ class IndexView(Admin2Mixin, generic.TemplateView):
}) })
return data return data
class ModelListView(AdminModel2Mixin, generic.ListView):
class ModelListView(Admin2Mixin, generic.ListView):
default_template_name = "model_list.html" default_template_name = "model_list.html"
permission_type = 'view' permission_type = 'view'
@ -80,6 +96,10 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
context['model_pluralized'] = self.get_model()._meta.verbose_name_plural context['model_pluralized'] = self.get_model()._meta.verbose_name_plural
return context return context
def get_success_url(self):
view_name = 'admin2:{}_{}_detail'.format(self.app_label, self.model_name)
return reverse(view_name, kwargs={'pk': self.object.pk})
class ModelDetailView(AdminModel2Mixin, generic.DetailView): class ModelDetailView(AdminModel2Mixin, generic.DetailView):
default_template_name = "model_detail.html" default_template_name = "model_detail.html"
@ -99,8 +119,12 @@ class ModelAddFormView(AdminModel2Mixin, generic.CreateView):
default_template_name = "model_add_form.html" default_template_name = "model_add_form.html"
permission_type = 'add' permission_type = 'add'
def get_success_url(self):
view_name = 'admin2:{}_{}_detail'.format(self.app_label, self.model_name)
return reverse(view_name, kwargs={'pk': self.object.pk})
class ModelDeleteView(AdminModel2Mixin, generic.DeleteView): class ModelDeleteView(AdminModel2Mixin, generic.DeleteView):
success_url = "../../" success_url = "../../"
default_template_name = "model_delete.html" default_template_name = "model_confirm_delete.html"
permission_type = 'delete' permission_type = 'delete'

View file

@ -1,10 +1,30 @@
Welcome to django-admin2's documentation! Welcome to django-admin2's documentation!
========================================= =========================================
**django-admin2** aims to replace django's builtin admin that lives in **django-admin2** aims to replace django's built-in admin that lives in
``django.contrib.admin``. Come and help us, have a look at the ``django.contrib.admin``. Come and help us, have a look at the
:doc:`contributing` page and see our `GitHub`_ page. :doc:`contributing` page and see our `GitHub`_ page.
This project is intentionally backwards-incompatible with ``django.contrib.admin``.
Features
==========
* Easy-to-extend API that follows similar patterns to ``django.contrib.admin``.
* Built-in RESTFUL API powered by ``django-rest-framework``
* Default theme built on Twitter Bootstrap
* Easy to implement theme system.
Requirements
=============
* Django 1.5+
* Python 2.7+ (Python 3.3+ support is pending)
* django-braces
* django-rest-framework
* Sphinx (for documentation)
Basic API Basic API
============== ==============
@ -44,6 +64,7 @@ Content
design design
meta meta
Indices and tables Indices and tables
================== ==================

View file

@ -17,4 +17,4 @@ class UserAdmin2(ModelAdmin2):
# Register each model with the admin # Register each model with the admin
djadmin2.default.register(Post) djadmin2.default.register(Post)
djadmin2.default.register(Comment) djadmin2.default.register(Comment)
djadmin2.default.register(User, UserAdmin2) djadmin2.default.register(User, UserAdmin2)

View file

@ -14,4 +14,4 @@ class Comment(models.Model):
body = models.TextField() body = models.TextField()
def __unicode__(self): def __unicode__(self):
return self.body return self.body

View file

@ -1,36 +1,70 @@
from django.utils import unittest from django.contrib.auth import get_user_model
from django.test.client import RequestFactory from django.core.urlresolvers import reverse
from django.test import TestCase, Client
from djadmin2 import views from .models import Post
class ViewTest(unittest.TestCase):
class BaseIntegrationTest(TestCase):
"""
Base TestCase for integration tests.
"""
def setUp(self): def setUp(self):
self.factory = RequestFactory() self.client = Client()
self.user = get_user_model()(username='user', is_staff=True,
is_superuser=True)
self.user.set_password("password")
self.user.save()
self.client.login(username='user', password='password')
class IndexViewTest(ViewTest): class AdminIndexTest(BaseIntegrationTest):
def test_response_ok(self): def test_view_ok(self):
request = self.factory.get('/admin/blog/post/') response = self.client.get(reverse("admin2:dashboard"))
response = views.IndexView.as_view()(request) self.assertContains(response, reverse("admin2:blog_post_index"))
class PostListTest(BaseIntegrationTest):
def test_view_ok(self):
post = Post.objects.create(title="a_post_title", body="body")
response = self.client.get(reverse("admin2:blog_post_index"))
self.assertContains(response, post.title)
class PostDetailViewTest(BaseIntegrationTest):
def test_view_ok(self):
post = Post.objects.create(title="a_post_title", body="body")
response = self.client.get(reverse("admin2:blog_post_detail",
args=(post.pk, )))
self.assertContains(response, post.title)
class PostCreateViewTest(BaseIntegrationTest):
def test_view_ok(self):
response = self.client.get(reverse("admin2:blog_post_create"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_create_post(self):
class ModelListViewTest(ViewTest): response = self.client.post(reverse("admin2:blog_post_create"),
pass {"title": "a_post_title",
"body": "a_post_body"},
follow=True)
self.assertTrue(Post.objects.filter(title="a_post_title").exists())
post = Post.objects.get(title="a_post_title")
self.assertRedirects(response, reverse("admin2:blog_post_detail",
args=(post.pk, )))
class ModelDetailViewTest(ViewTest): class PostDeleteViewTest(BaseIntegrationTest):
pass def test_view_ok(self):
post = Post.objects.create(title="a_post_title", body="body")
response = self.client.get(reverse("admin2:blog_post_delete",
class ModelEditFormViewTest(ViewTest): args=(post.pk, )))
pass self.assertContains(response, post.title)
class ModelAddFormViewTest(ViewTest):
pass
class ModelDeleteViewTest(ViewTest):
pass
def test_delete_post(self):
post = Post.objects.create(title="a_post_title", body="body")
response = self.client.post(reverse("admin2:blog_post_delete",
args=(post.pk, )))
self.assertRedirects(response, reverse("admin2:blog_post_index"))
self.assertFalse(Post.objects.filter(pk=post.pk).exists())