Domanda

Ho la seguente configurazione dell'amministratore in modo da poter aggiungere / modificare un utente e il suo profilo contemporaneamente.

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)

Il problema è che ho bisogno di due dei campi nel modulo inline Profile per essere richiesti quando si aggiunge l'utente. Il modulo in linea non viene convalidato se non viene immesso un input. Esiste un modo per rendere inline richiesto, in modo che non possa essere lasciato in bianco?

È stato utile?

Soluzione

Ho seguito il consiglio di Carl e ho realizzato un'implementazione molto migliore di quella hack che ho citato nel mio commento alla sua risposta. Ecco la mia soluzione:

Dal mio form.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 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)

Fa esattamente quello che voglio, convalida il formset inline Profile. Pertanto, poiché nel modulo del profilo sono presenti campi obbligatori, verrà convalidato e non riuscirà se le informazioni richieste non vengono immesse nel modulo incorporato.

Altri suggerimenti

Ora con Django 1.7 puoi usare il parametro min_num . Non è più necessaria la classe RequiredInlineFormSet .

Vedi 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)

Probabilmente puoi farlo, ma dovrai sporcarti le mani nel codice formset / inline.

Prima di tutto, penso che tu voglia che ci sia sempre una forma nel formset in questo caso, e mai più di una, quindi ti consigliamo di impostare max_num = 1 e extra = 1 nella tua ProfileInline.

Il tuo problema principale è che BaseFormSet._construct_form passa empty_permitted = Vero per ogni " extra " (ovvero vuoto) nel formset. Questo parametro indica al modulo di ignorare la convalida se è invariato. Devi solo trovare un modo per impostare empty_permitted = False per il modulo.

Puoi utilizzare la tua sottoclasse BaseInlineFormset nella tua in linea, in modo che possa aiutare. Notando che _construct_form accetta ** kwargs e consente di sovrascrivere i kwarg passati alle singole istanze di Form, è possibile sovrascrivere _construct_forms nella sottoclasse Formset e far passare vuoto_permit = Falso in ogni chiamata a _construct_form. Il rovescio della medaglia è che stai facendo affidamento su API interne (e dovresti riscrivere _construct_forms).

In alternativa, puoi provare a sovrascrivere il metodo get_formset su ProfileInline e, dopo aver chiamato get_formset del genitore, frugare manualmente nel modulo all'interno del formset restituito:

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

Gioca e vedi cosa riesci a far funzionare!

Il modo più semplice e naturale per farlo è tramite 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

(Ispirato da questo Matthew Lo snippet di Flanagan e il commento di Mitar sotto, testati per funzionare in Django 1.11 e 2.0).

Devi impostare min_num in linea e validate_min nel formset.

https://docs.djangoproject.com/ it / 1.8 / argomenti / forme / 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
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top