views:

581

answers:

3

I have the following admin setup so that I can add/edit a user and their profile at the same time.

class ProfileInline(admin.StackedInline):
    """
    Allows profile to be added when creating user
    """
    model = Profile


class UserProfileAdmin(admin.ModelAdmin):
    """
    Options for the admin interface
    """
    inlines = [ProfileInline]
    list_display = ['edit_obj', 'name', 'username', 'email', 'is_active',
        'last_login', 'delete_obj']
    list_display_links = ['username']
    list_filter = ['is_active']
    fieldsets = (
        (None, {
            'fields': ('first_name', 'last_name', 'email', 'username',
                'is_active', 'is_superuser')}),
        )
    ordering = ['last_name', 'first_name']
    search_fields = ['first_name', 'last_name']

admin.site.register(User, UserProfileAdmin)

The problem is I need two of the fields in the Profile inline form to be required when adding the user. The inline form doesn't validate unless input is entered. Is there anyway to make the inline required, so that it can't be left blank?

+1  A: 

You can probably do this, but you'll have to get your hands dirty in the formset/inline code.

First of all, I think you want there to be always one form in the formset in this case, and never more than one, so you'll want to set max_num=1 and extra=1 in your ProfileInline.

Your core problem is that BaseFormSet._construct_form passes empty_permitted=True to each "extra" (i.e. empty) form in the formset. This parameter tells the form to bypass validation if its unchanged. You just need to find a way to set empty_permitted=False for the form.

You can use your own BaseInlineFormset subclass in your inline, so that might help. Noticing that _construct_form takes **kwargs and allows that to override the kwargs passed to the individual Form instances, you could override _construct_forms in your Formset subclass and have it pass empty_permitted=False in every call to _construct_form. The downside there is that you're relying on internal APIs (and you'd have to rewrite _construct_forms).

Alternatively, you could try overriding the get_formset method on your ProfileInline, and after calling the parent's get_formset, manually poke at the form inside the returned formset:

def get_formset(self, request, obj=None, **kwargs):
    formset = super(ProfileInline, self).get_formset(request, obj, **kwargs)
    formset.forms[0].empty_permitted = False
    return formset

Play around and see what you can make work!

Carl Meyer
Thanks for the information. I did come up with a solution but it is very hack-ish, and I'm not particularly proud of it. I ended up overrideing the add_view for the ModelAdmin and copying all of the code from the default view and modifying the formset values. I'm going to take a look at your suggestions to see if I can implement it in a cleaner way. Thanks for the leads!
AJ
+2  A: 

I took Carl's advice and made a much better implementation then the hack-ish one I mentioned in my comment to his answer. Here is my solution:

From my forms.py:

from django.forms.models import BaseInlineFormSet


class RequiredInlineFormSet(BaseInlineFormSet):
    """
    Generates an inline formset that is required
    """

    def _construct_form(self, i, **kwargs):
        """
        Override the method to change the form attribute empty_permitted
        """
        form = super(RequiredInlineFormSet, self)._construct_form(i, **kwargs)
        form.empty_permitted = False
        return form

And the admin.py

class ProfileInline(admin.StackedInline):
    """
    Allows profile to be added when creating user
    """
    model = Profile
    extra = 1
    max_num = 1
    formset = RequiredInlineFormSet


class UserProfileAdmin(CRUDAdmin):
    """
    Options for the admin interface
    """
    inlines = [ProfileInline]
    list_display = ['edit_obj', 'name', 'username', 'email', 'is_active',
        'last_login', 'delete_obj']
    list_display_links = ['username']
    list_filter = ['is_active']
    fieldsets = (
        (None, {
            'fields': ('first_name', 'last_name', 'email', 'username',
                'is_active', 'is_superuser')}),
        (('Groups'), {'fields': ('groups', )}),
    )
    ordering = ['last_name', 'first_name']
    search_fields = ['first_name', 'last_name']


admin.site.register(User, UserProfileAdmin)

This does exactly what I want, it makes the Profile inline formset validate. So since there are required fields in the profile form it will validate and fail if the required information isn't entered on the inline form.

AJ
A: 

The easiest and most natural way to do that is via fomset clean().

See http://code.google.com/p/wadofstuff/source/browse/trunk/python/forms/wadofstuff/django/forms/forms.py .

mrts