Ricardo B.
Ricardo B.

Reputation: 351

unique_together validation fails if one of the fields is excluded from form

I have a model Menu:

class Menu(models.Model):
    loja = models.ForeignKey(Loja, related_name='menus')
    nome = models.CharField(max_length=30)
    ordenacao = models.IntegerField(blank=True, null=True)

    class Meta:
        ordering = ('ordenacao',)
        #prevent equally named menus within a store(loja)
        unique_together = ('loja', 'nome')

    def __unicode__(self):
        return self.nome 

The Menu form:

class MenuForm(ModelForm):
    class Meta:
        model = Menu
        exclude =('loja', 'ordenacao',)

Add menu view:

def addmenu(request, s_loja):
    loja = get_object_or_404(Loja, slug=s_loja)
    if not loja.activo:
        return render_to_response('painelcontrolo/loja_detail.html', {
            'notificacoes': ['Loja está definida como inactivo.', \
                             'Alterações não permitidas']})
    if request.method == 'POST':
        menu = Menu(loja=loja)
        form = MenuForm(request.POST, instance=menu)
        if form.is_valid():
            menu_existe = Menu.objects.\
                          filter(nome=form.cleaned_data['nome']).\
                          filter(loja=loja)
            if menu_existe:
                return render_to_response(\
                    'painelcontrolo/loja_detail.html', {
                        'notificacoes': ['Já existe um menu com esse nome']
                        })
            form.save()
            return render_to_response(\
                    'painelcontrolo/loja_detail.html', {
                        'notificacoes': ['Menu adicionado']
                        })
    else:
        form = MenuForm()
    return render_to_response('form_as_p.html', {
        'form': form
    })

The error:(no longer occurs with the added validation on the addmenu view)

Request Method:     POST
Request URL:    http://127.0.0.1:8000/painel/bispos/addmenu/
Exception Type:     IntegrityError
Exception Value:    columns loja_id, nome are not unique

The question: The form is valid, but the model isn't if nome+loja_id already exist on the db. Do I need to add this validation somewhere else? And where?

edit: I wrote a validation on the view and it passes a notification to the template and this is fine, but not perfect. I would like to re-display the form with the user input to give an opportunity to fix what's wrong without losing that information. Is there a way to do that?

Upvotes: 1

Views: 943

Answers (3)

Ricardo B.
Ricardo B.

Reputation: 351

Ok, this is the best I could come up with. It gives the error on the form and seems to work fine.

@login_required
def addmenu(request, s_loja):
    loja = get_object_or_404(Loja, slug=s_loja)
    if not loja.activo:
        return render_to_response('painelcontrolo/loja_detail.html', {
            'notificacoes': ['Loja está definida como inactivo.', \
                             'Alterações não permitidas']})
    if request.method == 'POST':
        menu = Menu(loja=loja)
        form = MenuForm(request.POST, instance=menu)
        if form.is_valid():
            menu_existe = Menu.objects.\
                          filter(nome=form.cleaned_data['nome']).\
                          filter(loja=loja)
            if not menu_existe:
                form.save()
                return render_to_response('painelcontrolo/loja_detail.html', {
                        'notificacoes': ['Menu adicionado']
                        })
            else:
                form._errors['nome'] = ErrorList(\
                    ['Um menu com esse nome já existe'])
    else:
        form = MenuForm()
    return render_to_response('form_as_p.html', {
        'form': form,
    })

Upvotes: 1

cethegeek
cethegeek

Reputation: 6394

I would just leave the loja in the form but make it a hidden field.

class MenuForm(ModelForm):
    loja = models.ModelChoiceField(Loja.objects.all(), widget=forms.HiddenInput)

    class Meta:
        model = Menu
        exclude =('ordenacao',)

You probably would have to change your view to call your getloja() regardless if the request is a post or get. You never explain HOW getloja() decides what is the proper instance...

@login_required
def addmenu(request, s_loja):
    if request.method == 'POST':
        form = MenuForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect('/painel/profile/')
    else:
        loja = getloja(request, s_loja) #simply retrieves the correct loja instance
        menu = Menu(loja=loja)
        form = MenuForm(instance=menu)
    return render_to_response('form_as_p.html', {
           'form': form,})

Upvotes: 2

Dan Ostrowski
Dan Ostrowski

Reputation: 101

I'm not really sure how the form would know about what unique values exist in the database, it just validates the type of fields required to populate the model, as far as I know.

In other words "form.is_valid()" is not going to save to the database, which is the only way you can see if the save works.

You will have to catch this on your own, I'm pretty sure, but "unique_together" is already doing it's job.

Upvotes: 0

Related Questions