jul
jul

Reputation: 37474

ValidationError when modifying an object with inline admin

I've got a Position model with a ForeignKey field related to an Emplacement model. In the admin of Emplacement I set an Inline admin for Position.

What I want to achieve is that when the num_position field is modified in a Emplacement instance, the positions related to this instance are deleted and new ones are created.

To achieve this I override the Emplacement model's save() method and check whether num_position has changed.

The problem is that when saving an Emplacement model after modification of its num_position field, I get the following error:

ValidationError: Select a valid choice. That choice is not one of the available choices.

I guess it has to do with the deletion/recreation of the positions, but I can't find why.

What's wrong?

models.py

class Emplacement(models.Model): 

    num_position = models.IntegerField()
    # more fields

    __original_num_position = None

    def __init__(self, *args, **kwargs):
        super(Emplacement, self).__init__(*args, **kwargs)
        self.__original_num_position = self.num_position

    def save(self, *args, **kwargs):

        if self.num_position != self._Emplacement__original_num_position:
            # if num_position has changed, delete existing positions and recreate <num_position> positions
            Position.objects.filter(emplacement=self).delete()            
            for i in range(self.num_position):
                position = Position()
                position.emplacement = self
                position.number = i+1
                position.save()

        super(Emplacement, self).save(*args, **kwargs)

class Position(models.Model):
    emplacement = models.ForeignKey(Emplacement)
    number = models.IntegerField()

admin.py

class PositionInline(admin.TabularInline):
    model = Position

class EmplacementAdmin(admin.ModelAdmin):
    inlines = [PositionInline]

my_site.register(Emplacement, EmplacementAdmin)

EDIT

I also tried with a signal, but got the same error:

@receiver(post_save, sender=Emplacement)
def create_positions(sender, instance, created, **kwargs):
    """Create positions when num_position has changed."""
    if instance.num_position != instance.old_num_position:
        Position.objects.filter(emplacement=instance).delete()
        for i in range(instance.num_position):
            position = Position()
            position.emplacement = instance
            position.numero = i+1            
            position.save()

Upvotes: 0

Views: 444

Answers (1)

tleguijt
tleguijt

Reputation: 81

I encountered exactly the same issue. Finally solved it by not implementing the create/delete logic on the Model-side but in the Forms/FormSets.

So in the 'main' admin class, in your case 'EmplacementAdmin', extended the save_formset method:

def save_formset(self, request, form, formset, change):
    instance = form.instance
    deleted = []
    if instance and instance.__original_num_position != instance.num_position:
        ''' create new Positions if needed '''

        formset.delete_positions = None # pass on a list, QuerySet, whatever

    super(EmplacementAdmin, self).save_formset(request, form, formset, change)

And create a PositionFormSet like so:

class PositionFormSet(BaseInlineFormSet):
    delete_positions = []

    @property
    def deleted_forms(self):
        deleted_forms = []
        try:
            deleted_forms = super(PositionFormSet, self).deleted_forms
        except AttributeError:
            pass
        for form in self.forms:
            if form.instance in self.delete_positions:
                 deleted_forms.append(form)
        self.delete_positions = []
        return deleted_forms

And set this formset in your inlineadmin:

class PositionInline(admin.TabularInline):
    model = Position
    formset = PositionFormSet

Not exactly the way I had in mind, but the trick worked for me. Curious to know if there are some more elegant solutions :-)

Upvotes: 1

Related Questions