2009-07-30 8 views
22

J'ai la configuration admin suivante pour que je puisse ajouter/modifier un utilisateur et leur profil en même temps.Comment est-ce que je requière un inline dans Django Admin?

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) 

Le problème est que je besoin de deux des champs du formulaire en ligne de profil pour être requis lors de l'ajout de l'utilisateur. Le formulaire en ligne ne valide pas sauf si l'entrée est entrée. Y at-il de toute façon pour rendre l'inline nécessaire, de sorte qu'il ne peut pas être laissé vide?

Répondre

29

J'ai pris le conseil de Carl et j'ai fait une bien meilleure implémentation que celle que j'ai mentionnée dans mon commentaire. Voici ma solution:

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

Et le 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) 

Ce fait exactement ce que je veux, il fait le profil en ligne formset validate. Donc, comme il y a des champs obligatoires dans le formulaire de profil, il va valider et échouer si les informations requises ne sont pas entrées dans le formulaire en ligne.

+1

Si vous utilisez 'GenericInlineModelAdmin', remplacez' BaseInlineFormSet' par 'BaseGenericInlineFormSet'. – L42y

+1

Merci! Toutefois, si votre formulaire n'a pas de champs obligatoires, il ne sera toujours pas validé et sauvegardé lorsqu'il est vide. Singe-corrige le formulaire en utilisant 'form.has_changed = lambda: True' pour le sauvegarder même s'il est vide. – bouke

9

Vous pouvez probablement le faire, mais vous devrez vous salir les mains dans le code formset/inline. Tout d'abord, je pense que vous voulez qu'il y ait toujours une forme dans le formset dans ce cas, et jamais plus d'une, vous devez donc définir max_num = 1 et extra = 1 dans votre ProfileInline.

Votre problème principal est celui BaseFormSet._construct_form passes empty_permitted=True pour chaque forme "supplémentaire" (c'est-à-dire vide) dans le formset. Ce paramètre indique au formulaire de contourner la validation s'il est inchangé. Vous avez juste besoin de trouver un moyen de définir empty_permitted = False pour le formulaire. Vous pouvez use your own BaseInlineFormset subclass dans votre ligne, ce qui pourrait aider. En remarquant que _construct_form prend ** kwargs et permet de surcharger les kwargs passés aux instances de formulaire individuelles, vous pouvez surcharger _construct_forms dans votre sous-classe Formset et lui faire passer empty_permitted = False dans chaque appel à _construct_form. L'inconvénient est que vous utilisez des API internes (et vous devrez réécrire _construct_forms).

Alternativement, vous pouvez essayer redéfinissant la méthode get_formset sur votre ProfileInline, et après avoir appelé la get_formset du parent, piquez manuellement le formulaire dans le formset retourné:

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 

Jouez et voyez ce que vous pouvez faire le travail !

+0

Merci pour l'information.J'ai trouvé une solution mais c'est très hack-ish, et je n'en suis pas particulièrement fier. J'ai fini par surcharger add_view pour ModelAdmin et copier tout le code de la vue par défaut et modifier les valeurs de formset. Je vais examiner vos suggestions pour voir si je peux l'appliquer de manière plus propre. Merci pour les pistes! –

7

La façon la plus simple et la plus naturelle de le faire est par 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 

(Inspirée par this Matthew Flanagan's snippet et le commentaire de Mitar ci-dessous, testé pour fonctionner dans Django 1.11 et 2.0).

+2

Parfait! Je l'ai changé un peu, j'utilise 'sinon self.is_valid():' au lieu de passer manuellement par 'self.errors' et d'utiliser' self.model._meta.verbose_name'. – Mitar

16

Maintenant, avec Django 1.7, vous pouvez utiliser le paramètre min_num. Vous n'avez plus besoin de la classe RequiredInlineFormSet.

Voir 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) 
+0

Ceci contrôle uniquement le nombre d'inlines affichées. – spookylukey

+5

En fait, si l'un des champ requis est en ligne, il contrôle également que l'en-ligne lui-même est rempli. – quick

+1

Confirmé de travail :) – Geotob