Скажем, у меня есть следующее в 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)
т.е. существует несколько Companies
, каждый из которых имеет диапазон Rates
и Clients
. Каждый Client
должен иметь базу Rate
, которая выбирается из нее parent Company Rates
, а не другая Company Rates
.
При создании формы для добавления Client
я хотел бы удалить выбор Company
(так как это уже было выбрано с помощью кнопки "Добавить клиент" на странице Company
) и ограничить Rate
выбор для этого Company
.
Как мне это сделать в Django 1.0?
Мой текущий файл forms.py
сейчас только шаблонный:
from models import *
from django.forms import ModelForm
class ClientForm(ModelForm):
class Meta:
model = Client
И views.py
также является основным:
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})
В Django 0.96 мне удалось взломать это, сделав что-то вроде следующего, прежде чем отображать шаблон:
manipulator.fields[0].choices = [(r.id,r.name) for r in Rate.objects.filter(company_id=the_company.id)]
ForeignKey.limit_choices_to
кажется многообещающим, но я не знаю, как пройти в the_company.id
, и я не понимаю, будет ли это работать вне интерфейса администратора в любом случае.
Спасибо. (Это похоже на довольно простой запрос, но если я должен перепроектировать что-то, то я открыт для предложений.)
ForeignKey представлен django.forms.ModelChoiceField, который является ChoiceField, выбор которого является модель QuerySet. См. Ссылку ModelChoiceField.
Итак, укажите QuerySet в поле queryset
. Зависит от того, как ваша форма построена. Если вы создадите явную форму, у вас будут поля с именами напрямую.
form.rate.queryset = Rate.objects.filter(company_id=the_company.id)
Если вы берете объект ModelForm по умолчанию, form.fields["rate"].queryset = ...
Это делается явно в представлении. Нет взлома.
В дополнение к ответу S.Lott и, как упоминалось в комментариях к Guru, его можно добавить фильтры выбора запроса, переопределив функцию ModelForm.__init__
. (Это может быть легко применимо к обычным формам), это может помочь в повторном использовании и сохраняет функцию обзора в порядке.
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})
Это может быть полезно для повторного использования, если у вас есть общие фильтры, необходимые для многих моделей (обычно я объявляю абстрактный класс формы). Например.
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')
Кроме этого, я просто переписываю материал блога Django, из которого есть много хороших.
Это просто и работает с 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
....
Вам не нужно указывать это в классе формы, но можете делать это непосредственно в ModelAdmin, поскольку Django уже включает этот встроенный метод в ModelAdmin (из документов):
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)
Даже более удобный способ сделать это (например, при создании интерфейсного интерфейса администратора, к которому пользователи могут обращаться) - подклассифицировать ModelAdmin, а затем изменить методы ниже. Конечным результатом является пользовательский интерфейс, который ТОЛЬКО показывает им связанный с ними контент, позволяя вам (суперпользователю) видеть все.
Я переопределил четыре метода, первые два не позволяют пользователю удалять что-либо, а также удаляют кнопки удаления с сайта администратора.
Третье переопределение фильтрует любой запрос, содержащий ссылку (в примере "пользователь" или "дикобраз" (как иллюстрация).
Последнее переопределение фильтрует любое поле foreignkey в модели для фильтрации доступных вариантов, аналогичных базовому набору запросов.
Таким образом, вы можете представить простой для управления фронт-админ-сайт, который позволяет пользователям общаться со своими собственными объектами, и вам не нужно помнить, чтобы вводить конкретные фильтры ModelAdmin, о которых мы говорили выше.
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)
удалите кнопки "удалить":
def get_actions(self, request):
actions = super(FrontEndAdmin, self).get_actions(request)
if 'delete_selected' in actions:
del actions['delete_selected']
return actions
предотвращает удаление разрешения
def has_delete_permission(self, request, obj=None):
return False
фильтрует объекты, которые можно просмотреть на сайте администратора:
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
выбор фильтров для всех полей foreignkey на сайте администратора:
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)
Чтобы сделать это с общим представлением, например 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'))
самая важная часть этого...
context['form'].fields['project'].queryset = Project.objects.for_user(self.request.user)
Если вы не создали форму и хотите изменить набор запросов, вы можете сделать:
formmodel.base_fields['myfield'].queryset = MyModel.objects.filter(...)
Это очень полезно, если вы используете общие представления!
Итак, я действительно пытался это понять, но кажется, что Django все еще не делает этого очень простым. Я не настолько глупый, но я просто не вижу никакого (несколько) простого решения.
Я считаю, что в целом довольно уродливо переопределять виды Admin для такого рода вещей, и каждый пример, который я нахожу, никогда полностью не применяется к представлениям администратора.
Это такое распространенное обстоятельство с моделями, которые я делаю, что я нахожу это ужасным, что нет очевидного решения этого...
У меня есть следующие классы:
# 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)
Это создает проблему при настройке администратора для компании, поскольку он имеет встроенные параметры для Контракта и местоположения, а параметры контракта m2m для местоположения не фильтруются надлежащим образом в соответствии с компанией, которую вы сейчас редактируете.
Короче говоря, мне понадобится опция администратора, чтобы сделать что-то вроде этого:
# 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')
В конечном счете мне было бы безразлично, если бы процесс фильтрации был размещен на базе CompanyAdmin или если он был помещен в ContractInline. (Размещение его на встроенной линии имеет больше смысла, но это затрудняет ссылку базового контракта как "я".)
Есть ли там кто-нибудь, кто знает что-то столь же прямое, как этот крайне необходимый ярлык? Назад, когда я сделал администраторов PHP для такого рода вещей, это считалось базовой функциональностью! Фактически, он всегда был автоматическим и должен был быть отключен, если вы действительно этого не хотели!
Более общедоступным способом является вызов get_form в классах Admin. Он также работает и для полей без базы данных. Например, здесь у меня есть поле "_terminal_list" в форме, которое может использоваться в особых случаях для выбора нескольких элементов терминала из get_list (запрос), а затем для фильтрации на основе 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