Django: acessando a instância do modelo de dentro ModelAdmin?
-
09-09-2019 - |
Pergunta
Eu tenho um modelo para ordens em um aplicativo de loja virtual, com uma chave primária auto-incremento e uma chave estrangeira para si, uma vez que as encomendas podem ser divididos em várias ordens, mas a relação com a ordem original deve ser mantida.
class Order(models.Model):
ordernumber = models.AutoField(primary_key=True)
parent_order = models.ForeignKey('self', null=True, blank=True, related_name='child_orders')
# .. other fields not relevant here
Eu tenho registrado uma classe OrderAdmin para o site de administração. Para a vista de detalhe, eu incluí parent_order
no atributo fieldsets
. Claro que, por padrão este Listas de todas as ordens em uma caixa de seleção, mas este não é o comportamento desejado. Em vez disso, para as encomendas que não têm uma ordem de pai (ou seja, não têm sido dividida de outra ordem; parent_order
é NULL / None), nenhuma ordem deve ser exibido. Para encomendas que foram divididos, isso só deve apresentar a ordem de mãe solteira.
Há um bastante novo método ModelAdmin disponíveis, formfield_for_foreignkey
, que parece perfeito para isso, já que o queryset podem ser filtrados dentro dela. Imagine que nós estamos olhando para a vista da ordem # 11234, que foi dividido a partir da ordem # 11208 detalhes. O código está abaixo
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'parent_order':
# kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=11234)
return db_field.formfield(**kwargs)
return super(OrderAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
A linha comentou funciona quando executado em um shell Python, retornando um queryset-item único contendo fim # 11208 para 11234 # e todas as outras ordens que podem ter sido dividido com isso.
É claro que não pode codificar o número de ordem lá. Precisamos de uma referência ao campo ordernumber
da instância ordem cuja página de detalhes que estamos olhando. Como esta:
kwargs["queryset"] = Order.objects.filter(child_orders__ordernumber__exact=?????)
Eu encontrei nenhuma maneira de trabalho para substituir ????? com uma referência à instância "atual" Ordem, e eu cavou bem fundo. self
dentro formfield_for_foreignkey
refere-se ao exemplo ModelAdmin, e ao mesmo tempo que tem um atributo model
, que não é o caso do modelo de ordem (que é uma referência ModelBase; self.model () retorna um exemplo, mas a sua NÚMERO DO PEDIDO é Nenhum).
Uma solução poderia ser a de puxar o número de ordem de request.path (/ admin / encomendas / ordem / 11234 /), mas que é realmente feio. Eu realmente gostaria que há uma maneira melhor.
Solução
Eu acho que você pode precisar de abordar isso de uma maneira um pouco diferente - modificando o ModelForm, ao invés da classe admin. Algo parecido com isto:
class OrderForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(OrderForm, self).__init__(*args, **kwargs)
self.fields['parent_order'].queryset = Order.objects.filter(
child_orders__ordernumber__exact=self.instance.pk)
class OrderAdmin(admin.ModelAdmin):
form = OrderForm
Outras dicas
eu modelei minha classe em linha desta forma. É um pouco feio de como ele recebe o formulário pai ID de dados em linha filtro, mas funciona. Ele filtra unidades por empresa do formulário pai.
O conceito original é explicado aqui http://www.stereoplex.com/blog/filtering-dropdown-lists-in-the-django-admin
class CompanyOccupationInline(admin.TabularInline):
model = Occupation
# max_num = 1
extra = 0
can_delete = False
formset = RequiredInlineFormSet
def formfield_for_dbfield(self, field, **kwargs):
if field.name == 'unit':
parent_company = self.get_object(kwargs['request'], Company)
units = Unit.objects.filter(company=parent_company)
return forms.ModelChoiceField(queryset=units)
return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs)
def get_object(self, request, model):
object_id = resolve(request.path).args[0]
try:
object_id = int(object_id)
except ValueError:
return None
return model.objects.get(pk=object_id)
A resposta acima Erwin Julius trabalhou para mim, se eu achei que o nome "get_object" conflitos com a função de Django então nomear o "my_get_object" função.
class CompanyOccupationInline(admin.TabularInline):
model = Occupation
# max_num = 1
extra = 0
can_delete = False
formset = RequiredInlineFormSet
def formfield_for_dbfield(self, field, **kwargs):
if field.name == 'unit':
parent_company = self.my_get_object(kwargs['request'], Company)
units = Unit.objects.filter(company=parent_company)
return forms.ModelChoiceField(queryset=units)
return super(CompanyOccupationInline, self).formfield_for_dbfield(field, **kwargs)
def my_get_object(self, request, model):
object_id = request.META['PATH_INFO'].strip('/').split('/')[-1]
try:
object_id = int(object_id)
except ValueError:
return None
return model.objects.get(pk=object_id)
me disse que não "responder" às respostas dos outros, mas eu não estou autorizado a 'resposta' ainda, e eu tenho procurado isso por um tempo, espero que isso vai ser útil para outras pessoas. Eu também não estou autorizado a upvote ainda ou I totalmente faria!