Pergunta

Eu tenho a seguinte configuração de administrador para que eu possa adicionar / editar um usuário e seu perfil ao mesmo tempo.

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)

O problema é que eu preciso dois dos campos do formulário de perfil linha a ser necessária ao adicionar o usuário. O formulário em linha não valida a menos que a entrada é inserido. Existe uma maneira de fazer a linha necessário, de modo que não pode estar em branco esquerdo?

Foi útil?

Solução

Eu levei o conselho de Carl e fez uma implementação muito melhor, então o truque-ish mencionei no meu comentário à sua resposta. Aqui está a minha solução:

De minha 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

E o 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(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')}),
        (('Groups'), {'fields': ('groups', )}),
    )
    ordering = ['last_name', 'first_name']
    search_fields = ['first_name', 'last_name']


admin.site.register(User, UserProfileAdmin)

Este é exatamente o que eu quero, ele faz a validação perfil em linha formset. Então, uma vez que não são necessários campos do formulário de perfil que irá validar e falhar se a informação requerida não for introduzido no formulário em linha.

Outras dicas

Agora, com Django 1.7 você pode usar min_num parâmetro. Você não precisa mais de classe RequiredInlineFormSet.

https://docs.djangoproject.com /en/1.8/ref/contrib/admin/#django.contrib.admin.InlineModelAdmin.min_num

class ProfileInline(admin.StackedInline):
    """
    Allows profile to be added when creating user
    """
    model = Profile
    extra = 1
    max_num = 1
    min_num = 1 # new in Django 1.7


class UserProfileAdmin(admin.ModelAdmin):
    """
    Options for the admin interface
    """
    inlines = [ProfileInline]
    ...


admin.site.register(User, UserProfileAdmin)

Você provavelmente pode fazer isso, mas você vai ter que sujar as mãos no código formset / embutido.

Em primeiro lugar, eu acho que você quer que haja sempre um formulário no formset neste caso, e nunca mais do que um, então você vai querer definir MAX_NUM = 1 e adicional = 1 em seu ProfileInline.

O problema central é que BaseFormSet._construct_form passes empty_permitted = True para cada forma "extra" (isto é, vazios) no formset. Este parâmetro informa o formulário para validação de bypass se o seu inalterada. Você só precisa encontrar uma maneira de conjunto empty_permitted = False para o formulário.

Você pode usar seu próprio BaseInlineFormset subclasse em sua em linha, de modo que ajuda poder. Notando que _construct_form leva kwargs ** e permite que para substituir as kwargs passados ??para as instâncias forma individual, você poderia substituir _construct_forms em seu Formset subclasse e tê-lo passar empty_permitted = False em cada chamada para _construct_form. A desvantagem não é que você está confiando em APIs internas (e você teria que reescrever _construct_forms).

Como alternativa, você pode tentar substituir o método get_formset em seu ProfileInline, e depois de chamar get_formset do pai, manualmente cutucar a forma dentro do formset devolvidos:

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

Jogar ao redor e ver o que você pode fazer o trabalho!

A maneira mais fácil e mais natural de fazer isso é através fomset clean():

class RequireOneFormSet(forms.models.BaseInlineFormSet):
    def clean(self):
        super().clean()
        if not self.is_valid():
            return
        if not self.forms or not self.forms[0].cleaned_data:
            raise ValidationError('At least one {} required'
                                  .format(self.model._meta.verbose_name))

class ProfileInline(admin.StackedInline):
    model = Profile
    formset =  RequireOneFormSet

(Inspirado por esta Matthew Flanagan do trecho e comentário de Mitar abaixo, testado para trabalhar em Django 1.11 e 2.0).

Você precisa definir MIN_NUM na linha e validate_min no formset.

https://docs.djangoproject.com/ en / 1.8 / temas / formas / formsets / # validate-min

class SomeInline(admin.TabularInline):
    ...
    min_num = 1

    def get_formset(self, request, obj=None, **kwargs):
        formset = super().get_formset(request, obj=None, **kwargs)
        formset.validate_min = True
        return formset
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top