Merge branch 'inline-formsets' of git://github.com/AndrewIngram/django-admin2 into AndrewIngram-inline-formsets

This commit is contained in:
Daniel Greenfeld 2013-05-22 17:47:17 +02:00
commit bb3d4712b9
7 changed files with 123 additions and 15 deletions

View file

@ -11,6 +11,8 @@ from django.conf.urls import patterns, url
from django.contrib.auth import models as auth_app
from django.db.models import get_models, signals
import extra_views
from djadmin2 import apiviews
from djadmin2 import views
@ -112,6 +114,8 @@ class ModelAdmin2(BaseAdmin2):
create_form_class = None
update_form_class = None
inlines = []
# Views
index_view = views.ModelListView
create_view = views.ModelAddFormView
@ -161,6 +165,7 @@ class ModelAdmin2(BaseAdmin2):
def get_create_kwargs(self):
kwargs = self.get_default_view_kwargs()
kwargs.update({
'inlines': self.inlines,
'form_class': self.create_form_class if self.create_form_class else self.form_class,
})
return kwargs
@ -168,6 +173,7 @@ class ModelAdmin2(BaseAdmin2):
def get_update_kwargs(self):
kwargs = self.get_default_view_kwargs()
kwargs.update({
'inlines': self.inlines,
'form_class': self.update_form_class if self.update_form_class else self.form_class,
})
return kwargs
@ -244,6 +250,23 @@ class ModelAdmin2(BaseAdmin2):
return self.get_api_urls(), None, None
class Admin2Inline(extra_views.InlineFormSet):
"""
A simple extension of django-extra-view's InlineFormSet that
adds some useful functionality.
"""
def construct_formset(self):
"""
Overrides construct_formset to attach the model class as
an attribute of the returned formset instance.
"""
formset = super(Admin2Inline, self).construct_formset()
formset.model = self.inline_model
return formset
class ImmutableAdmin(object):
"""
Only __init__ allows setting of attributes

View file

@ -1,5 +1,7 @@
{% extends "admin2/bootstrap/base.html" %}
{% load admin2_tags %}
{% block title %}{{ action }} {{ model }}{% endblock %}
{% block page_title %}{{ action }} {{ model }}{% endblock %}
@ -12,6 +14,36 @@
{% csrf_token %}
{{ form.as_p }}
{% for formset in inlines %}
<h4>{{ formset.model|model_verbose_name_plural|capfirst }}</h4>
{{ formset.management_form }}
<table class="table table-striped table-bordered">
<thead>
<tr>
{% for field in formset|formset_visible_fieldlist %}
<th>{{ field }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for inline_form in formset %}
<tr>
{% for field in inline_form.visible_fields %}
<td>
{% if forloop.first %}
{% for hidden_field in inline_form.hidden_fields %}
{{ hidden_field }}
{% endfor %}
{% endif %}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endfor %}
<button class="btn btn-small" type="submit" name="_addanother">Save and add another</button>
<button class="btn btn-small" type="submit" name="_continue">Save and continue editing</button>
<button class="btn btn-small btn-success" type="submit" name="_save">Save</button>

View file

@ -9,3 +9,27 @@ def admin2_urlname(view, action):
Converts the view and the specified action into a valid namespaced URLConf name.
"""
return 'admin2:%s_%s_%s' % (view.app_label, view.model_name, action)
@register.filter
def model_verbose_name(obj):
"""
Returns the verbose name of a model instance or class.
"""
return obj._meta.verbose_name
@register.filter
def model_verbose_name_plural(obj):
"""
Returns the pluralized verbose name of a model instance or class.
"""
return obj._meta.verbose_name_plural
@register.filter
def formset_visible_fieldlist(formset):
"""
Returns the labels of a formset's visible fields as an array.
"""
return [f.label for f in formset.forms[0].visible_fields()]

View file

@ -9,7 +9,9 @@ from django.views import generic
from braces.views import AccessMixin
from templatetags.admin2_tags import admin2_urlname
import extra_views
from .templatetags.admin2_tags import admin2_urlname
ADMIN2_THEME_DIRECTORY = getattr(settings, "ADMIN2_THEME_DIRECTORY", "admin2/bootstrap")
@ -121,7 +123,7 @@ class ModelDetailView(AdminModel2Mixin, generic.DetailView):
permission_type = 'view'
class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin, generic.UpdateView):
class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin, extra_views.UpdateWithInlinesView):
form_class = None
default_template_name = "model_update_form.html"
permission_type = 'change'
@ -133,7 +135,7 @@ class ModelEditFormView(AdminModel2Mixin, Admin2ModelFormMixin, generic.UpdateVi
return context
class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin, generic.CreateView):
class ModelAddFormView(AdminModel2Mixin, Admin2ModelFormMixin, extra_views.CreateWithInlinesView):
form_class = None
default_template_name = "model_update_form.html"
permission_type = 'add'

View file

@ -5,9 +5,8 @@ from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from django.contrib.auth.models import Group, User
from rest_framework.relations import PrimaryKeyRelatedField
import djadmin2
from djadmin2.models import ModelAdmin2
from djadmin2.models import ModelAdmin2, Admin2Inline
from djadmin2.apiviews import Admin2APISerializer
@ -30,6 +29,14 @@ class UserSerializer(Admin2APISerializer):
exclude = ('passwords',)
class CommentInline(Admin2Inline):
model = Comment
class PostAdmin(ModelAdmin2):
inlines = [CommentInline]
class UserAdmin2(ModelAdmin2):
create_form_class = UserCreationForm
update_form_class = UserChangeForm
@ -38,7 +45,7 @@ class UserAdmin2(ModelAdmin2):
# Register each model with the admin
djadmin2.default.register(Post)
djadmin2.default.register(Post, PostAdmin)
djadmin2.default.register(Comment)
djadmin2.default.register(User, UserAdmin2)
djadmin2.default.register(Group, GroupAdmin2)

View file

@ -2,7 +2,7 @@ from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test import TestCase, Client
from ..models import Post
from ..models import Post, Comment
class BaseIntegrationTest(TestCase):
@ -45,12 +45,21 @@ class PostCreateViewTest(BaseIntegrationTest):
self.assertEqual(response.status_code, 200)
def test_create_post(self):
post_data = {
"comment_set-TOTAL_FORMS": u'2',
"comment_set-INITIAL_FORMS": u'0',
"comment_set-MAX_NUM_FORMS": u'',
"comment_set-0-body": u'Comment Body',
"title": "a_post_title",
"body": "a_post_body",
}
response = self.client.post(reverse("admin2:blog_post_create"),
{"title": "a_post_title",
"body": "a_post_body"},
post_data,
follow=True)
self.assertTrue(Post.objects.filter(title="a_post_title").exists())
post = Post.objects.get(title="a_post_title")
comment = Comment.objects.get(body="Comment Body")
self.assertRedirects(response, reverse("admin2:blog_post_index"))
def test_save_and_add_another_redirects_to_create(self):
@ -58,9 +67,14 @@ class PostCreateViewTest(BaseIntegrationTest):
Tests that choosing 'Save and add another' from the model create
page redirects the user to the model create page.
"""
post_data = {"title": "a_post_title",
"body": "a_post_body",
"_addanother": ""}
post_data = {
"comment_set-TOTAL_FORMS": u'2',
"comment_set-INITIAL_FORMS": u'0',
"comment_set-MAX_NUM_FORMS": u'',
"title": "a_post_title",
"body": "a_post_body",
"_addanother": ""
}
self.client.login(username='admin', password='password')
response = self.client.post(reverse("admin2:blog_post_create"),
post_data)
@ -72,9 +86,14 @@ class PostCreateViewTest(BaseIntegrationTest):
Tests that choosing "Save and continue editing" redirects
the user to the model update form.
"""
post_data = {"title": "Unique",
"body": "a_post_body",
"_continue": ""}
post_data = {
"comment_set-TOTAL_FORMS": u'2',
"comment_set-INITIAL_FORMS": u'0',
"comment_set-MAX_NUM_FORMS": u'',
"title": "Unique",
"body": "a_post_body",
"_continue": ""
}
response = self.client.post(reverse("admin2:blog_post_create"),
post_data)
post = Post.objects.get(title="Unique")

View file

@ -90,6 +90,7 @@ setup(
install_requires=[
'django>=1.5.0',
'django-braces==1.0.0',
'django-extra-views==0.6.2',
'djangorestframework==2.3.3'
],
zip_safe=False,