45 votes

Pré-remplir une ligne FormSet?

Je suis en train de travailler sur la présence d'un formulaire d'inscription pour un groupe. Mon idée est d'avoir une section du formulaire pour entrer les informations d'événement pour un spectacle ou à une répétition. Voici le modèle pour le cas de la table:

class Event(models.Model):
    event_id = models.AutoField(primary_key=True)
    date = models.DateField()
    event_type = models.ForeignKey(EventType)
    description = models.TextField()

Alors j'aimerais avoir une ligne FormSet qui lie les membres de la bande à l'événement et les dossiers s'ils étaient présents, absents ou excusés:

class Attendance(models.Model):
    attendance_id = models.AutoField(primary_key=True)
    event_id = models.ForeignKey(Event)
    member_id = models.ForeignKey(Member)
    attendance_type = models.ForeignKey(AttendanceType)
    comment = models.TextField(blank=True)

Maintenant, ce que je voudrais faire est de pré-remplir cette inline FormSet avec des entrées pour tous les membres actuels et par défaut à être présent (autour de 60 membres). Malheureusement, Django n'autorise pas les valeurs initiales dans ce cas.

Toutes les suggestions?

35voto

James Bennett Points 6318

Donc, vous n'allez pas aimer la réponse, en partie parce que je ne suis pas encore terminé l'écriture du code, et en partie parce que c'est beaucoup de travail.

Ce que vous devez faire, comme je l'ai découvert quand je suis tombé sur moi-même, est:

  1. Passer beaucoup de temps à lire à travers le formset et le modèle-formset code pour avoir une idée de comment tout cela fonctionne (pas aidé par le fait que certaines des fonctionnalités de vie sur l'ensemble des classes, et qu'il vit dans l'usine des fonctions qui leur cracher). Vous aurez besoin de ces connaissances dans les étapes ultérieures.
  2. Écrivez votre propre ensemble de la classe qui les sous-classes de BaseInlineFormSet et accepte initial. Le plus difficile c'est ici que vous devez remplacer __init__(), et vous devez vous assurer qu'il appelle à l' BaseFormSet.__init__() plutôt que d'utiliser directement parent ou un grand-parent __init__() (car ceux-ci sont BaseInlineFormSet et BaseModelFormSet, respectivement, et aucun d'eux ne peut traiter des données initiales).
  3. Écrivez votre propre sous-classe de l'admin inline classe (dans mon cas, il a été TabularInline) et de remplacer son get_formset méthode de retourner le résultat d' inlineformset_factory() à l'aide de votre personnalisé formset classe.
  4. Sur la base réelle ModelAdmin sous-classe pour le modèle avec la ligne, remplacer add_view et change_view, et de reproduire la plupart du code, mais avec un changement de taille: construire les données initiales de votre formset aura besoin, et de le passer à votre custom formset (qui sera restitué par votre ModelAdmins' get_formsets() méthode).

J'ai eu un peu productives chats avec Brian et Joseph à propos de l'amélioration pour les futures Django à l'extérieur; à l'heure actuelle, la façon dont le modèle formsets travail faire de ce plus de problèmes que généralement la peine, mais avec un peu d'API de nettoyage, je pense qu'il peut être extrêmement facile.

20voto

Erik Karulf Points 360

J'ai passé pas mal de temps à essayer de trouver une solution que je pourrais ré-utilisation de l'ensemble des sites. James' post contenait la pièce clé de la sagesse de l'extension de la BaseInlineFormSet mais stratégiquement en invoquant des appels à l'encontre BaseFormSet.

La solution ci-dessous est divisée en deux parties: une AdminInline et BaseInlineFormSet.

  1. L' InlineAdmin génère dynamiquement une valeur initiale basée sur l'exposé de l'objet request.
  2. Il utilise nourrissage pour exposer les valeurs initiales à une coutume BaseInlineFormSet par mot-clé arguments passés au constructeur.
  3. L' BaseInlineFormSet constructeur pop les valeurs initiales de la liste des mots clés les arguments et les constructions normalement.
  4. La dernière pièce est d'une substitution de la forme du processus de construction en changeant le nombre total de formes et à l'aide de l' BaseFormSet._construct_form et BaseFormSet._construct_forms méthodes

Voici quelques cas concrets extraits à l'aide de l'OP classes. J'ai testé cette contre Django 1.2.3. Je recommande fortement de garder le formset et admin de la documentation à portée de la main en développement.

admin.py

from django.utils.functional import curry
from django.contrib import admin
from example_app.forms import *
from example_app.models import *

class AttendanceInline(admin.TabularInline):
    model           = Attendance
    formset         = AttendanceFormSet
    extra           = 5

    def get_formset(self, request, obj=None, **kwargs):
        """
        Pre-populating formset using GET params
        """
        initial = []
        if request.method == "GET":
            #
            # Populate initial based on request
            #
            initial.append({
                'foo': 'bar',
            })
        formset = super(AttendanceInline, self).get_formset(request, obj, **kwargs)
        formset.__init__ = curry(formset.__init__, initial=initial)
        return formset

forms.py

from django.forms import formsets
from django.forms.models import BaseInlineFormSet

class BaseAttendanceFormSet(BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        """
        Grabs the curried initial values and stores them into a 'private'
        variable. Note: the use of self.__initial is important, using
        self.initial or self._initial will be erased by a parent class
        """
        self.__initial = kwargs.pop('initial', [])
        super(BaseAttendanceFormSet, self).__init__(*args, **kwargs)

    def total_form_count(self):
        return len(self.__initial) + self.extra

    def _construct_forms(self):
        return formsets.BaseFormSet._construct_forms(self)

    def _construct_form(self, i, **kwargs):
        if self.__initial:
            try:
                kwargs['initial'] = self.__initial[i]
            except IndexError:
                pass
        return formsets.BaseFormSet._construct_form(self, i, **kwargs)

AttendanceFormSet = formsets.formset_factory(AttendanceForm, formset=BaseAttendanceFormSet)

19voto

ruiseal Points 81

Django 1.4 et supérieur prend en charge fournissant des valeurs initiales.

En termes de la question d'origine, la suivante devrait fonctionner:

class AttendanceFormSet(models.BaseInlineFormSet):
    def __init__(self, *args, **kwargs):
        super(AttendanceFormSet, self).__init__(*args, **kwargs)
        # Check that the data doesn't already exist
        if not kwargs['instance'].member_id_set.filter(# some criteria):
            initial = []
            initial.append({}) # Fill in with some data
            self.initial = initial
            # Make enough extra formsets to hold initial forms
            self.extra += len(initial)

Si vous trouvez que les formes sont remplies, mais n'étant pas enregistrer, puis vous pouvez avoir besoin pour personnaliser votre modèle de formulaire. Un moyen facile est de passer d'un tag dans les données initiales et de regarder pour elle dans le formulaire init:

class AttendanceForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(AttendanceForm, self).__init__(*args, **kwargs)
        # If the form was prepopulated from default data (and has the
        # appropriate tag set), then manually set the changed data
        # so later model saving code is activated when calling
        # has_changed().
        initial = kwargs.get('initial')
        if initial:
            self._changed_data = initial.copy()

    class Meta:
        model = Attendance

3voto

Danni Points 121

Je suis tombé sur le même problème.

Vous pouvez le faire à l'aide de JavaScript, faire un simple JS qui fait un appel ajax pour l'ensemble de la bande de cadres, et remplit le formulaire.

Cette solution manque de principe SEC, parce que vous avez besoin d'écrire pour chaque inline formulaire que vous avez.

0voto

daan Points 11

Voici comment j'ai résolu le problème. Il y a un peu de compromis dans la création et la suppression des enregistrements, mais le code est propre...

def manage_event(request, event_id):
    """
    Add a boolean field 'record_saved' (default to False) to the Event model
    Edit an existing Event record or, if the record does not exist:
    - create and save a new Event record
    - create and save Attendance records for each Member
    Clean up any unsaved records each time you're using this view
    """
    # delete any "unsaved" Event records (cascading into Attendance records)
    Event.objects.filter(record_saved=False).delete()
    try:
        my_event = Event.objects.get(pk=int(event_id))
    except Event.DoesNotExist:
        # create a new Event record
        my_event = Event.objects.create()
        # create an Attendance object for each Member with the currect Event id
        for m in Members.objects.get.all():
            Attendance.objects.create(event_id=my_event.id, member_id=m.id)
    AttendanceFormSet = inlineformset_factory(Event, Attendance, 
                                        can_delete=False, 
                                        extra=0, 
                                        form=AttendanceForm)
    if request.method == "POST":
        form = EventForm(request.POST, request.FILES, instance=my_event)
        formset = AttendanceFormSet(request.POST, request.FILES, 
                                        instance=my_event)
        if formset.is_valid() and form.is_valid():
            # set record_saved to True before saving
            e = form.save(commit=False)
            e.record_saved=True
            e.save()
            formset.save()
            return HttpResponseRedirect('/')
    else:
        form = EventForm(instance=my_event)
        formset = OptieFormSet(instance=my_event)
    return render_to_response("edit_event.html", {
                            "form":form, 
                            "formset": formset,
                            }, 
                            context_instance=RequestContext(request))

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X