mirror of
https://github.com/jazzband/django-admin2.git
synced 2026-03-17 06:30:25 +00:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
151bbce7ae
17 changed files with 256 additions and 102 deletions
|
|
@ -1,6 +1,5 @@
|
|||
language: python
|
||||
python:
|
||||
- "2.6"
|
||||
- "2.7"
|
||||
before_install:
|
||||
- export PIP_USE_MIRRORS=true
|
||||
|
|
|
|||
33
README.rst
33
README.rst
|
|
@ -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
|
||||
|
||||
Requirements
|
||||
=============
|
||||
|
||||
* Django 1.5+
|
||||
* Python 2.7+ (Python 3.3+ support is pending)
|
||||
* django-braces
|
||||
* django-rest-framework
|
||||
* Sphinx (for documentation)
|
||||
|
||||
Basic Pattern
|
||||
==============
|
||||
|
|
@ -23,20 +31,25 @@ Our goal is to make this API work:
|
|||
|
||||
.. 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
|
||||
from admin2.models import Admin2
|
||||
import djadmin2
|
||||
from djadmin2.models import ModelAdmin2
|
||||
|
||||
# Import your custom models
|
||||
from blog.models import Post
|
||||
|
||||
# Instantiate the Admin2 class
|
||||
# Then attach the admin2 object to your model
|
||||
Post.admin2 = Admin2()
|
||||
class UserAdmin2(ModelAdmin2):
|
||||
create_form_class = UserCreationForm
|
||||
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
|
||||
========
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
__version__ = '0.1.1'
|
||||
__version__ = '0.2.0'
|
||||
|
||||
__author__ = 'Daniel Greenfeld'
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ VERSION = __version__ # synonym
|
|||
# Default datetime input and output formats
|
||||
ISO_8601 = 'iso-8601'
|
||||
|
||||
from . import core
|
||||
from . import core
|
||||
|
||||
|
||||
default = core.Admin2()
|
||||
|
|
|
|||
|
|
@ -9,14 +9,29 @@ from . import views
|
|||
|
||||
|
||||
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
|
||||
|
||||
def __init__(self, name='admin2', app_name='admin2'):
|
||||
def __init__(self, name='admin2'):
|
||||
self.registry = {}
|
||||
self.name = name
|
||||
self.app_name = app_name
|
||||
|
||||
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:
|
||||
raise ImproperlyConfigured
|
||||
if not modeladmin:
|
||||
|
|
@ -24,13 +39,21 @@ class Admin2(object):
|
|||
self.registry[model] = modeladmin(model, **kwargs)
|
||||
|
||||
def deregister(self, model):
|
||||
"""
|
||||
Deregisters the given model.
|
||||
|
||||
If the model is not already registered, this will raise ImproperlyConfigured.
|
||||
"""
|
||||
try:
|
||||
del self.registry[model]
|
||||
except KeyError:
|
||||
raise ImproperlyConfigured
|
||||
|
||||
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]:
|
||||
try:
|
||||
import_module("%s.admin2" % app_name)
|
||||
|
|
@ -46,11 +69,11 @@ class Admin2(object):
|
|||
|
||||
def get_urls(self):
|
||||
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():
|
||||
app_label = model._meta.app_label
|
||||
model_name = model._meta.object_name.lower()
|
||||
model_name = model._meta.object_name.lower()
|
||||
|
||||
urlpatterns += patterns('',
|
||||
url('^{}/{}/'.format(app_label, model_name),
|
||||
|
|
@ -60,4 +83,4 @@ class Admin2(object):
|
|||
|
||||
@property
|
||||
def urls(self):
|
||||
return self.get_urls(), self.app_name, self.name
|
||||
return self.get_urls(), self.name, self.name
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
"""
|
||||
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.db.models import get_models, signals
|
||||
|
||||
|
|
@ -16,7 +19,6 @@ except ImportError:
|
|||
from django import forms
|
||||
|
||||
|
||||
|
||||
class BaseAdmin2(object):
|
||||
|
||||
search_fields = []
|
||||
|
|
@ -46,13 +48,11 @@ class BaseAdmin2(object):
|
|||
readonly_fields = ()
|
||||
ordering = None
|
||||
|
||||
|
||||
def __init__(self, model):
|
||||
super(BaseAdmin2, self).__init__()
|
||||
|
||||
self.model = model
|
||||
|
||||
|
||||
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.
|
||||
Type can be one of view, add, change, delete.
|
||||
|
|
@ -110,6 +110,8 @@ class ModelAdmin2(BaseAdmin2):
|
|||
|
||||
def __init__(self, model, **kwargs):
|
||||
self.model = model
|
||||
self.app_label = model._meta.app_label
|
||||
self.model_name = model._meta.object_name.lower()
|
||||
|
||||
if self.verbose_name is None:
|
||||
self.verbose_name = self.model._meta.verbose_name
|
||||
|
|
@ -118,10 +120,15 @@ class ModelAdmin2(BaseAdmin2):
|
|||
|
||||
def get_default_view_kwargs(self):
|
||||
return {
|
||||
'app_label': self.app_label,
|
||||
'model': self.model,
|
||||
'model_name': self.model_name,
|
||||
'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):
|
||||
return self.get_default_view_kwargs()
|
||||
|
||||
|
|
@ -145,32 +152,35 @@ class ModelAdmin2(BaseAdmin2):
|
|||
def get_delete_kwargs(self):
|
||||
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):
|
||||
return patterns('',
|
||||
url(
|
||||
regex=r'^$',
|
||||
view=self.index_view.as_view(**self.get_index_kwargs()),
|
||||
name='index'
|
||||
name=self.get_prefixed_view_name('index')
|
||||
),
|
||||
url(
|
||||
regex=r'^create/$',
|
||||
view=self.create_view.as_view(**self.get_create_kwargs()),
|
||||
name='create'
|
||||
name=self.get_prefixed_view_name('create')
|
||||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>[0-9]+)/$',
|
||||
view=self.detail_view.as_view(**self.get_detail_kwargs()),
|
||||
name='detail'
|
||||
name=self.get_prefixed_view_name('detail')
|
||||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>[0-9]+)/update/$',
|
||||
view=self.update_view.as_view(**self.get_update_kwargs()),
|
||||
name='update'
|
||||
name=self.get_prefixed_view_name('update')
|
||||
),
|
||||
url(
|
||||
regex=r'^(?P<pk>[0-9]+)/delete/$',
|
||||
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
|
||||
return self.get_urls(), None, None
|
||||
|
||||
|
||||
|
||||
def create_permissions(app, created_models, verbosity, **kwargs):
|
||||
def create_extra_permissions(app, created_models, verbosity, **kwargs):
|
||||
"""
|
||||
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
|
||||
|
|
@ -190,9 +198,6 @@ def create_permissions(app, created_models, verbosity, **kwargs):
|
|||
"""
|
||||
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)
|
||||
|
||||
# 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)
|
||||
|
||||
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))
|
||||
|
||||
# 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
|
||||
# a list of the ones we're going to create.
|
||||
all_perms = set(auth_app.Permission.objects.filter(
|
||||
|
|
@ -217,16 +222,16 @@ def create_permissions(app, created_models, verbosity, **kwargs):
|
|||
"content_type", "codename"
|
||||
))
|
||||
|
||||
objs = [
|
||||
perms = [
|
||||
auth_app.Permission(codename=codename, name=name, content_type=ctype)
|
||||
for ctype, (codename, name) in searched_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:
|
||||
for obj in objs:
|
||||
print "Adding permission '%s'" % obj
|
||||
for perm in perms:
|
||||
print "Adding permission '%s'" % perm
|
||||
|
||||
|
||||
signals.post_syncdb.connect(create_permissions,
|
||||
dispatch_uid = "django-admin2.djadmin2.models.create_permissions")
|
||||
signals.post_syncdb.connect(create_extra_permissions,
|
||||
dispatch_uid = "django-admin2.djadmin2.models.create_extra_permissions")
|
||||
|
|
|
|||
|
|
@ -1,19 +1,33 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>django-admin2</title>
|
||||
<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>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
|
||||
<script src="http://code.jquery.com/jquery.js"></script>
|
||||
<script src="{{ STATIC_URL }}themes/bootstrap/js/bootstrap.min.js"></script>
|
||||
{% block extrajs %}{% endblock %}
|
||||
<title>django-admin2</title>
|
||||
<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="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>
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<h1>Index</h1>
|
||||
<table>
|
||||
{% 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 %}
|
||||
</table>
|
||||
{% endblock content %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
{% extends "admin2/bootstrap/base.html" %}
|
||||
{% load admin2_urls %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
|
|
@ -6,9 +7,9 @@
|
|||
<h3>Select {{ model }} to change</h3>
|
||||
</div>
|
||||
<div class="span2">
|
||||
{% if has_add_permission %}
|
||||
<a class="btn" href="./create/">Add {{ model|title }} <i class=" icon-plus-sign"></i></a>
|
||||
{% endif %}
|
||||
{# if has_add_permission #}
|
||||
<a href="{% url view|admin2_urlname:'create' %}">add</a>
|
||||
{# endif #}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -16,6 +17,14 @@
|
|||
|
||||
<div class="row">
|
||||
<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">
|
||||
<thead>
|
||||
<th><input type="checkbox"></th>
|
||||
|
|
@ -25,17 +34,19 @@
|
|||
{% for obj in object_list %}
|
||||
<td><input type="checkbox"></td>
|
||||
<td>
|
||||
{{ obj }} <a href="./{{ obj.pk }}/">detail</a>
|
||||
{% if has_edit_permission %}
|
||||
<a href="./{{ obj.pk }}/update/">edit</a>
|
||||
{% endif %}
|
||||
{% if has_delete_permission %}
|
||||
<a href="./{{ obj.pk }}/delete/">delete</a>
|
||||
{% endif %}
|
||||
{{ obj }} <a href="{% url view|admin2_urlname:'detail' pk=obj.pk %}">detail</a>
|
||||
{# if has_edit_permission #}
|
||||
<a href="{% url view|admin2_urlname:'update' pk=obj.pk %}">edit</a>
|
||||
{# endif #}
|
||||
{# if has_delete_permission #}
|
||||
<a href="{% url view|admin2_urlname:'delete' pk=obj.pk %}">delete</a>
|
||||
{# endif #}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{ object_list|length }} {{ model }}{{ object_list|pluralize }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
8
djadmin2/templatetags/admin2_urls.py
Normal file
8
djadmin2/templatetags/admin2_urls.py
Normal 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)
|
||||
|
|
@ -6,9 +6,11 @@ from django.core.exceptions import ImproperlyConfigured
|
|||
from ..models import ModelAdmin2
|
||||
from ..core import Admin2
|
||||
|
||||
|
||||
class Thing(models.Model):
|
||||
pass
|
||||
|
||||
|
||||
class Admin2Test(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.admin2 = Admin2()
|
||||
|
|
@ -28,7 +30,7 @@ class Admin2Test(unittest.TestCase):
|
|||
|
||||
def test_deregister_error(self):
|
||||
self.assertRaises(ImproperlyConfigured, self.admin2.deregister, Thing)
|
||||
|
||||
|
||||
def test_get_urls(self):
|
||||
self.admin2.register(Thing)
|
||||
self.assertEquals(2, len(self.admin2.get_urls()))
|
||||
|
|
|
|||
|
|
@ -1,22 +1,36 @@
|
|||
import os
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.views import redirect_to_login
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.forms.models import modelform_factory
|
||||
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")
|
||||
|
||||
|
||||
class Admin2Mixin(object):
|
||||
modeladmin = None
|
||||
model_name = None
|
||||
app_label = None
|
||||
|
||||
def get_template_names(self):
|
||||
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):
|
||||
modeladmin = None
|
||||
|
|
@ -70,7 +84,9 @@ class IndexView(Admin2Mixin, generic.TemplateView):
|
|||
})
|
||||
return data
|
||||
|
||||
class ModelListView(AdminModel2Mixin, generic.ListView):
|
||||
|
||||
class ModelListView(Admin2Mixin, generic.ListView):
|
||||
|
||||
default_template_name = "model_list.html"
|
||||
permission_type = 'view'
|
||||
|
||||
|
|
@ -80,6 +96,10 @@ class ModelListView(AdminModel2Mixin, generic.ListView):
|
|||
context['model_pluralized'] = self.get_model()._meta.verbose_name_plural
|
||||
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):
|
||||
default_template_name = "model_detail.html"
|
||||
|
|
@ -99,8 +119,12 @@ class ModelAddFormView(AdminModel2Mixin, generic.CreateView):
|
|||
default_template_name = "model_add_form.html"
|
||||
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):
|
||||
success_url = "../../"
|
||||
default_template_name = "model_delete.html"
|
||||
default_template_name = "model_confirm_delete.html"
|
||||
permission_type = 'delete'
|
||||
|
|
|
|||
|
|
@ -1,10 +1,30 @@
|
|||
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
|
||||
: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
|
||||
==============
|
||||
|
||||
|
|
@ -44,6 +64,7 @@ Content
|
|||
design
|
||||
meta
|
||||
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
|
|
|
|||
|
|
@ -17,4 +17,4 @@ class UserAdmin2(ModelAdmin2):
|
|||
# Register each model with the admin
|
||||
djadmin2.default.register(Post)
|
||||
djadmin2.default.register(Comment)
|
||||
djadmin2.default.register(User, UserAdmin2)
|
||||
djadmin2.default.register(User, UserAdmin2)
|
||||
|
|
|
|||
|
|
@ -14,4 +14,4 @@ class Comment(models.Model):
|
|||
body = models.TextField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.body
|
||||
return self.body
|
||||
|
|
|
|||
|
|
@ -1,36 +1,70 @@
|
|||
from django.utils import unittest
|
||||
from django.test.client import RequestFactory
|
||||
from django.contrib.auth import get_user_model
|
||||
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):
|
||||
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):
|
||||
def test_response_ok(self):
|
||||
request = self.factory.get('/admin/blog/post/')
|
||||
response = views.IndexView.as_view()(request)
|
||||
class AdminIndexTest(BaseIntegrationTest):
|
||||
def test_view_ok(self):
|
||||
response = self.client.get(reverse("admin2:dashboard"))
|
||||
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)
|
||||
|
||||
|
||||
class ModelListViewTest(ViewTest):
|
||||
pass
|
||||
def test_create_post(self):
|
||||
response = self.client.post(reverse("admin2:blog_post_create"),
|
||||
{"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):
|
||||
pass
|
||||
|
||||
|
||||
class ModelEditFormViewTest(ViewTest):
|
||||
pass
|
||||
|
||||
|
||||
class ModelAddFormViewTest(ViewTest):
|
||||
pass
|
||||
|
||||
|
||||
class ModelDeleteViewTest(ViewTest):
|
||||
pass
|
||||
class PostDeleteViewTest(BaseIntegrationTest):
|
||||
def test_view_ok(self):
|
||||
post = Post.objects.create(title="a_post_title", body="body")
|
||||
response = self.client.get(reverse("admin2:blog_post_delete",
|
||||
args=(post.pk, )))
|
||||
self.assertContains(response, post.title)
|
||||
|
||||
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())
|
||||
|
|
|
|||
Loading…
Reference in a new issue