2010-11-24 4 views
12

누군가가 모델 필드 - F() 표현에 대한 비교를 기반으로 관리자 필터링 방법을 알고 있습니까?장고 관리 필터 F() 표현

의 우리가 모델을 다음했다고 가정합시다 : 이제

class Transport(models.Model): 
    start_area = models.ForeignKey(Area, related_name='starting_transports') 
    finish_area = models.ForeignKey(Area, related_name='finishing_transports') 

, 내가 뭘하고 싶은에 지역이다에서 지역 및 트랜스 - 지역 물체의 필터링을 허용 관리자 필터를 만드는 것입니다 start_area와 finish_area가 같고 trans-area가 다른 것들입니다.

나는 정의 FilterSpec를 작성하여이를 달성하기 위해 시도했지만 두 가지 문제가 있습니다 :

  • FilterSpec가 하나의 필드에 바인딩됩니다.
  • FilterSpec은 F() 표현식을 지원하지 않으며 제외됩니다.

두 번째 문제는 사용자 지정 ChangeList 클래스를 정의하여 해결할 수 있지만 첫 번째 문제를 해결할 방법이 없습니다.

또한 Queryset 메서드를 오버로드하고 필터 자체를 하드 코딩하여 손으로 인쇄하는 변경 목록 템플릿에 추가 컨텍스트를 보내서 ModelAdmin 인스턴스에서 필터를 "에뮬레이트"하려고했습니다. 불행히도, Django는 ModelAdmin 인스턴스에 알려지지 않았기 때문에 (필터 링크에서 사용되는) GET 매개 변수를 가져오고 대신에 약간의 오류를 나타내는 것으로 가정되는? e = 1 만 넣는 것이 문제가되는 것 같습니다.

미리 감사드립니다.

EDIT :이 기능은 다음 장고 출시를 위해 계획되었습니다 (http://code.djangoproject.com/ticket/5833 참조). 그래도 누군가 장고 1.2에서 그것을 달성하는 방법을 알고 있습니까?

+0

'트랜스 지역'이란 무엇입니까? :-) –

+0

Nothing special;) start_area가 finish_area와 다른 databse의 Transport 객체의 모든 인스턴스로 간주됩니다. – xaralis

+1

글쎄, 아무도 대답이없는 것처럼 보입니다. 내가 지금까지 생각해 낸 유일한 해결책은 매우 못생긴다 : 전송이 in-area 또는 trans-area 인 경우 정보를 저장하기 위해 저장 될 때 업데이트 될 전송 모델에 다른 필드를 추가한다. (나는 여분의 필드를 싫어한다. ( – xaralis

답변

1

해결 방법에는 FilterSpec을 추가하고 자체 ChangeList를 구현했다고 말한 것입니다. 필터 이름의 유효성을 검사 할 때 모델 필드 이름으로 필터의 이름을 지정해야합니다. 아래에서 동일한 필드에 기본 필터를 사용할 수있는 해킹이 표시됩니다.

표준 FilterSpec 앞에 FilterSpec을 추가하십시오.

아래는 Django 1에서 실행되는 작동 구현물입니다.3

from django.contrib.admin.views.main import * 
from django.contrib import admin 
from django.db.models.fields import Field 
from django.contrib.admin.filterspecs import FilterSpec 
from django.db.models import F 
from models import Transport, Area 
from django.contrib.admin.util import get_fields_from_path 
from django.utils.translation import ugettext as _ 


# Our filter spec 
class InAreaFilterSpec(FilterSpec): 

    def __init__(self, f, request, params, model, model_admin, field_path=None): 
     super(InAreaFilterSpec, self).__init__(
      f, request, params, model, model_admin, field_path=field_path) 
     self.lookup_val = request.GET.get('in_area', None) 

    def title(self): 
     return 'Area' 

    def choices(self, cl): 
     del self.field._in_area 
     yield {'selected': self.lookup_val is None, 
       'query_string': cl.get_query_string({}, ['in_area']), 
       'display': _('All')} 
     for pk_val, val in (('1', 'In Area'), ('0', 'Trans Area')): 
      yield {'selected': self.lookup_val == pk_val, 
        'query_string': cl.get_query_string({'in_area' : pk_val}), 
        'display': val} 

    def filter(self, params, qs): 
     if 'in_area' in params: 
      if params['in_area'] == '1': 
       qs = qs.filter(start_area=F('finish_area')) 
      else: 
       qs = qs.exclude(start_area=F('finish_area')) 
      del params['in_area'] 
     return qs 

def in_area_test(field): 
    # doing this so standard filters can be added with the same name 
    if field.name == 'start_area' and not hasattr(field, '_in_area'): 
     field._in_area = True 
     return True  
    return False 

# we add our special filter before standard ones 
FilterSpec.filter_specs.insert(0, (in_area_test, InAreaFilterSpec)) 


# Defining my own change list for transport 
class TransportChangeList(ChangeList): 

    # Here we are doing our own initialization so the filters 
    # are initialized when we request the data 
    def __init__(self, request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin): 
     #super(TransportChangeList, self).__init__(request, model, list_display, list_display_links, list_filter, date_hierarchy, search_fields, list_select_related, list_per_page, list_editable, model_admin) 
     self.model = model 
     self.opts = model._meta 
     self.lookup_opts = self.opts 
     self.root_query_set = model_admin.queryset(request) 
     self.list_display = list_display 
     self.list_display_links = list_display_links 
     self.list_filter = list_filter 
     self.date_hierarchy = date_hierarchy 
     self.search_fields = search_fields 
     self.list_select_related = list_select_related 
     self.list_per_page = list_per_page 
     self.model_admin = model_admin 

     # Get search parameters from the query string. 
     try: 
      self.page_num = int(request.GET.get(PAGE_VAR, 0)) 
     except ValueError: 
      self.page_num = 0 
     self.show_all = ALL_VAR in request.GET 
     self.is_popup = IS_POPUP_VAR in request.GET 
     self.to_field = request.GET.get(TO_FIELD_VAR) 
     self.params = dict(request.GET.items()) 
     if PAGE_VAR in self.params: 
      del self.params[PAGE_VAR] 
     if TO_FIELD_VAR in self.params: 
      del self.params[TO_FIELD_VAR] 
     if ERROR_FLAG in self.params: 
      del self.params[ERROR_FLAG] 

     if self.is_popup: 
      self.list_editable =() 
     else: 
      self.list_editable = list_editable 
     self.order_field, self.order_type = self.get_ordering() 
     self.query = request.GET.get(SEARCH_VAR, '') 
     self.filter_specs, self.has_filters = self.get_filters(request) 
     self.query_set = self.get_query_set() 
     self.get_results(request) 
     self.title = (self.is_popup and ugettext('Select %s') % force_unicode(self.opts.verbose_name) or ugettext('Select %s to change') % force_unicode(self.opts.verbose_name)) 
     self.pk_attname = self.lookup_opts.pk.attname 


    # To be able to do our own filter, 
    # we need to override this 
    def get_query_set(self): 

     qs = self.root_query_set 
     params = self.params.copy() 

     # now we pass the parameters and the query set 
     # to each filter spec that may change it 
     # The filter MUST delete a parameter that it uses 
     if self.has_filters: 
      for filter_spec in self.filter_specs: 
       if hasattr(filter_spec, 'filter'): 
        qs = filter_spec.filter(params, qs) 

     # Now we call the parent get_query_set() 
     # method to apply subsequent filters 
     sav_qs = self.root_query_set 
     sav_params = self.params 

     self.root_query_set = qs 
     self.params = params 

     qs = super(TransportChangeList, self).get_query_set() 

     self.root_query_set = sav_qs 
     self.params = sav_params 

     return qs 


class TransportAdmin(admin.ModelAdmin): 
    list_filter = ('start_area','start_area') 

    def get_changelist(self, request, **kwargs): 
     """ 
     Overriden from ModelAdmin 
     """ 
     return TransportChangeList 


admin.site.register(Transport, TransportAdmin) 
admin.site.register(Area) 
+0

불행히도,이 너무 불필요한 오히려 하나의 중복 필드를 추가하는 방법을 선택할 것이라고합니다. 다행히 django devs 다음 릴리스에서이 문제를 해결할 것입니다 (http://code.djangoproject.com/ticket/5833). – xaralis

3

그렇지 최선의 방법 *하지만 그것이 불행하게도, FilterSpecs 매우 장고 현재 제한됩니다

class TransportForm(forms.ModelForm): 
    transports = Transport.objects.all() 
    list = [] 
    for t in transports: 
     if t.start_area.pk == t.finish_area.pk: 
      list.append(t.pk) 
    select = forms.ModelChoiceField(queryset=Page.objects.filter(pk__in=list)) 

    class Meta: 
     model = Transport 
+0

예 이 솔루션을 것 같다 – KronnorK

0

을 작동합니다. 단순히 사용자 정의를 염두에두고 만들지 않았습니다.

그러나 고맙게도 많은 사람들이 오랫동안 FilterSpec에 대한 패치 작업을 해왔습니다. 그것은 1.3 이정표를 놓쳤지만, 이제 트렁크에 들어간 것처럼 보입니다. 다음 릴리스에서 충돌해야합니다. 당신이 트렁크에 프로젝트를 실행하려면

#5833 (Custom FilterSpecs)

, 당신은 지금을 이용할 수 있습니다, 또는 당신은 당신의 현재 설치를 패치 할 수 있습니다. 그렇지 않으면, 당신은 기다려야 할 것이지만 적어도 그것은 곧 올 것입니다.