Como filtrar as escolhas ForeignKey em um Django ModelForm?

votos
190

Digamos que eu tenha o seguinte no meu models.py:

class Company(models.Model):
   name = ...

class Rate(models.Model):
   company = models.ForeignKey(Company)
   name = ...

class Client(models.Model):
   name = ...
   company = models.ForeignKey(Company)
   base_rate = models.ForeignKey(Rate)

Ou seja, existem múltiplos Companies, cada um tendo uma gama de Ratese Clients. Cada Clientdeve ter uma base Rateque é escolhido a partir de sua mãe Company's Rates, e não outro Company's Rates.

Ao criar um formulário para adicionar um Client, eu gostaria de remover as Companyopções (como que já foi selecionado através de um botão Adicionar Cliente na Companypágina) e limitar as Rateescolhas para que Companybem.

Como faço para ir sobre isso no Django 1.0?

Meu atual forms.pyarquivo é apenas clichê no momento:

from models import *
from django.forms import ModelForm

class ClientForm(ModelForm):
    class Meta:
        model = Client

Eo views.pytambém é fundamental:

from django.shortcuts import render_to_response, get_object_or_404
from models import *
from forms import *

def addclient(request, company_id):
    the_company = get_object_or_404(Company, id=company_id)

    if request.POST:
        form = ClientForm(request.POST)
        if form.is_valid():
            form.save()
            return HttpResponseRedirect(the_company.get_clients_url())
    else:
        form = ClientForm()

    return render_to_response('addclient.html', {'form': form, 'the_company':the_company})

No Django 0,96 eu era capaz de cortar isso em fazendo algo como o seguinte antes de renderizar o template:

manipulator.fields[0].choices = [(r.id,r.name) for r in Rate.objects.filter(company_id=the_company.id)]

ForeignKey.limit_choices_toParece promissor, mas eu não sei como passar em the_company.ide eu não sou claro se isso vai funcionar fora da interface de administração de qualquer maneira.

Obrigado. (Este parece ser um pedido bastante básico, mas se eu deveria reformular algo que eu estou aberto a sugestões.)

Publicado 15/11/2008 em 02:21
fonte usuário
Em outras línguas...                            


7 respostas

votos
205

ForeignKey é representado por django.forms.ModelChoiceField, que é um ChoiceField cujas escolhas são um QuerySet modelo. Consulte a referência para ModelChoiceField .

Então, fornecer um QuerySet para o campo do querysetatributo. Depende de como o formulário é construído. Se você construir uma forma explícita, você terá campos nomeados diretamente.

form.rate.queryset = Rate.objects.filter(company_id=the_company.id)

Se você pegar o objeto ModelForm padrão, form.fields["rate"].queryset = ...

Isso é feito explicitamente na vista. Sem cortar ao redor.

Respondeu 15/11/2008 em 02:39
fonte usuário

votos
116

Além de resposta de S. Lott e como becomingGuru mencionado nos comentários, é possível adicionar os filtros queryset, substituindo o ModelForm.__init__função. (Isso pode facilmente aplicar a formas regulares) pode ajudar com a reutilização e mantém a função de visualização arrumado.

class ClientForm(forms.ModelForm):
    def __init__(self,company,*args,**kwargs):
        super (ClientForm,self ).__init__(*args,**kwargs) # populates the post
        self.fields['rate'].queryset = Rate.objects.filter(company=company)
        self.fields['client'].queryset = Client.objects.filter(company=company)

    class Meta:
        model = Client

def addclient(request, company_id):
        the_company = get_object_or_404(Company, id=company_id)

        if request.POST:
            form = ClientForm(the_company,request.POST)  #<-- Note the extra arg
            if form.is_valid():
                form.save()
                return HttpResponseRedirect(the_company.get_clients_url())
        else:
            form = ClientForm(the_company)

        return render_to_response('addclient.html', 
                                  {'form': form, 'the_company':the_company})

Isto pode ser útil para reutilização dizer se você tem filtros comuns necessárias em muitos modelos (normalmente eu declarar uma classe de forma abstrata). Por exemplo

class UberClientForm(ClientForm):
    class Meta:
        model = UberClient

def view(request):
    ...
    form = UberClientForm(company)
    ...

#or even extend the existing custom init
class PITAClient(ClientForm):
    def __init__(company, *args, **args):
        super (PITAClient,self ).__init__(company,*args,**kwargs)
        self.fields['support_staff'].queryset = User.objects.exclude(user='michael')

Outros, que eu só estou reafirmando o material do blog Django dos quais há muitos bons por aí.

Respondeu 07/08/2009 em 14:01
fonte usuário

votos
38

Isto é simples, e trabalha com Django 1.4:

class ClientAdminForm(forms.ModelForm):
    def __init__(self, *args, **kwargs):
        super(ClientAdminForm, self).__init__(*args, **kwargs)
        # access object through self.instance...
        self.fields['base_rate'].queryset = Rate.objects.filter(company=self.instance.company)

class ClientAdmin(admin.ModelAdmin):
    form = ClientAdminForm
    ....

Você não precisa especificar isso em uma classe de formulário, mas pode fazê-lo diretamente no ModelAdmin, como Django já inclui esse método incorporado na ModelAdmin (dos documentos):

ModelAdmin.formfield_for_foreignkey(self, db_field, request, **kwargs)¶
'''The formfield_for_foreignkey method on a ModelAdmin allows you to 
   override the default formfield for a foreign keys field. For example, 
   to return a subset of objects for this foreign key field based on the
   user:'''

class MyModelAdmin(admin.ModelAdmin):
    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "car":
            kwargs["queryset"] = Car.objects.filter(owner=request.user)
        return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

Uma forma ainda mais niftier para fazer esta (por exemplo na criação de uma interface de administração front-end que os utilizadores podem aceder a) é a subclasse do ModelAdmin e em seguida alterar os métodos abaixo. O resultado líquido é uma interface de usuário que apenas mostra-lhes o conteúdo que está relacionado a eles, permitindo-lhe (a super-usuário) para ver tudo.

Eu tenho substituído quatro métodos, os dois primeiros tornam impossível para um usuário para excluir qualquer coisa, e ele também remove os botões Excluir no site administrativo.

A terceira substituição filtra qualquer consulta que contém uma referência ao (no exemplo 'utilizador' ou 'porco-espinho' (apenas como ilustração).

A última substituição filtra qualquer campo ForeignKey no modelo para filtrar as opções disponíveis o mesmo que o queryset básico.

Desta forma, você pode apresentar um fácil de gerenciar site administrativo frontal que permite aos usuários para mexer com seus próprios objetos, e você não tem que se lembrar de digitar os filtros ModelAdmin específicas que falamos acima.

class FrontEndAdmin(models.ModelAdmin):
    def __init__(self, model, admin_site):
        self.model = model
        self.opts = model._meta
        self.admin_site = admin_site
        super(FrontEndAdmin, self).__init__(model, admin_site)

remover 'apagar' botões:

    def get_actions(self, request):
        actions = super(FrontEndAdmin, self).get_actions(request)
        if 'delete_selected' in actions:
            del actions['delete_selected']
        return actions

impede a permissão de exclusão

    def has_delete_permission(self, request, obj=None):
        return False

filtra os objetos que podem ser vistos no site admin:

    def get_queryset(self, request):
        if request.user.is_superuser:
            try:
                qs = self.model.objects.all()
            except AttributeError:
                qs = self.model._default_manager.get_queryset()
            return qs

        else:
            try:
                qs = self.model.objects.all()
            except AttributeError:
                qs = self.model._default_manager.get_queryset()

            if hasattr(self.model, ‘user’):
                return qs.filter(user=request.user)
            if hasattr(self.model, ‘porcupine’):
                return qs.filter(porcupine=request.user.porcupine)
            else:
                return qs

filtros de opções para todos os campos ForeignKey no local de administração:

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if request.employee.is_superuser:
            return super(FrontEndAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)

        else:
            if hasattr(db_field.rel.to, 'user'):
                kwargs["queryset"] = db_field.rel.to.objects.filter(user=request.user)
            if hasattr(db_field.rel.to, 'porcupine'):
                kwargs["queryset"] = db_field.rel.to.objects.filter(porcupine=request.user.porcupine)
            return super(ModelAdminFront, self).formfield_for_foreignkey(db_field, request, **kwargs)
Respondeu 27/03/2013 em 20:18
fonte usuário

votos
15

Para fazer isso com uma visão genérica, como CreateView ...

class AddPhotoToProject(CreateView):
    """
    a view where a user can associate a photo with a project
    """
    model = Connection
    form_class = CreateConnectionForm


    def get_context_data(self, **kwargs):
        context = super(AddPhotoToProject, self).get_context_data(**kwargs)
        context['photo'] = self.kwargs['pk']
        context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user)
        return context
    def form_valid(self, form):
        pobj = Photo.objects.get(pk=self.kwargs['pk'])
        obj = form.save(commit=False)
        obj.photo = pobj
        obj.save()

        return_json = {'success': True}

        if self.request.is_ajax():

            final_response = json.dumps(return_json)
            return HttpResponse(final_response)

        else:

            messages.success(self.request, 'photo was added to project!')
            return HttpResponseRedirect(reverse('MyPhotos'))

a parte mais importante do que ...

    context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user)

, Ler o meu post aqui

Respondeu 15/04/2012 em 04:53
fonte usuário

votos
4

Se você não tiver criado o formulário e quiser alterar o queryset você pode fazer:

formmodel.base_fields['myfield'].queryset = MyModel.objects.filter(...)

Isso é muito útil quando você estiver usando views genéricas!

Respondeu 08/08/2011 em 23:11
fonte usuário

votos
2

Então, eu realmente tentei entender isso, mas parece que Django ainda não faz isso muito simples. Eu não sou tão idiota, mas eu simplesmente não consigo ver qualquer (um pouco) solução simples.

Acho que é geralmente muito feio ter que substituir os pontos de vista do administrador para esse tipo de coisa, e cada exemplo eu nunca encontrar totalmente aplica-se aos pontos de vista do administrador.

Esta é uma circunstância comum com os modelos que eu faço que eu achar que é terrível que não há nenhuma solução óbvia para isso ...

Eu tenho essas classes:

# models.py
class Company(models.Model):
    # ...
class Contract(models.Model):
    company = models.ForeignKey(Company)
    locations = models.ManyToManyField('Location')
class Location(models.Model):
    company = models.ForeignKey(Company)

Isso cria um problema ao configurar o Administrador de Empresa, porque tem inlines para ambos contrato e local, e as opções de M2M do Contrato de Localização não são devidamente filtrados de acordo com a Companhia que você está editando no momento.

Em suma, eu iria precisar de alguma opção de administrador para fazer algo como isto:

# admin.py
class LocationInline(admin.TabularInline):
    model = Location
class ContractInline(admin.TabularInline):
    model = Contract
class CompanyAdmin(admin.ModelAdmin):
    inlines = (ContractInline, LocationInline)
    inline_filter = dict(Location__company='self')

Em última análise, eu não me importaria se o processo de filtragem foi colocado no CompanyAdmin base, ou se ele foi colocado no ContractInline. (Colocá-lo na linha faz mais sentido, mas torna-se difícil para fazer referência ao contrato base como 'self'.)

Existe alguém lá fora que sabe de algo tão simples como este atalho mal necessário? Voltar quando eu fiz admins PHP para este tipo de coisa, este foi considerado funcionalidade básica! Na verdade, sempre foi automática, e teve que ser desativado se você realmente não quer!

Respondeu 23/10/2009 em 01:45
fonte usuário

votos
0

Uma maneira mais pública é chamando get_form em aulas de administrador. Ele também funciona para campos não-banco de dados também. Por exemplo, aqui eu tenho um campo chamado '_terminal_list' no formulário que pode ser usado em casos especiais para a escolha de vários itens de terminais de get_list (request), em seguida, filtrando com base em request.user:

class ChangeKeyValueForm(forms.ModelForm):  
    _terminal_list = forms.ModelMultipleChoiceField( 
queryset=Terminal.objects.all() )

    class Meta:
        model = ChangeKeyValue
        fields = ['_terminal_list', 'param_path', 'param_value', 'scheduled_time',  ] 

class ChangeKeyValueAdmin(admin.ModelAdmin):
    form = ChangeKeyValueForm
    list_display = ('terminal','task_list', 'plugin','last_update_time')
    list_per_page =16

    def get_form(self, request, obj = None, **kwargs):
        form = super(ChangeKeyValueAdmin, self).get_form(request, **kwargs)
        qs, filterargs = Terminal.get_list(request)
        form.base_fields['_terminal_list'].queryset = qs
        return form
Respondeu 17/08/2016 em 05:42
fonte usuário

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more