pcards/autocomplete/widgets.py

394 lines
14 KiB
Python

# coding=utf8
from django import forms
from django.conf import settings
from django.utils.safestring import mark_safe
from django.utils.text import truncate_words
from django.contrib import admin
#from django.contrib.admin.util import unquote, flatten_fieldsets, get_deleted_objects, model_format_dict
#from django import forms, template
#from django.shortcuts import get_object_or_404, render_to_response
from django.db import models
import operator, settings
#from django.contrib.auth.models import Message
from django.http import HttpResponse, HttpResponseNotFound
from django.db.models.query import QuerySet
from django.utils.encoding import smart_str
from django.contrib.admin.widgets import RelatedFieldWidgetWrapper
from django.contrib.admin.options import InlineModelAdmin
from django.utils.translation import ugettext as _
from django.utils.encoding import force_unicode
from django.utils.html import escape
from django.utils.safestring import mark_safe, SafeUnicode
from django.utils.datastructures import MultiValueDict, MergeDict
from django.http import HttpResponseRedirect
from django.utils.functional import update_wrapper
#from django.utils.decorators import method_decorator
#from django.views.decorators.csrf import csrf_protect
#from django.db import models, transaction, router
#csrf_protect_m = method_decorator( csrf_protect )
media_css={ 'all': ( '%s/jquery.autocomplete.css' % settings.MEDIA_URL, ) }
media_js=( '%sadmin/js/jquery.autocomplete.js' % settings.MEDIA_URL, )
class ForeignKeySearchInput( forms.HiddenInput ):
"""
A Widget for displaying ForeignKeys in an autocomplete search input
instead in a <select> box.
"""
class Media:
css=media_css
js=media_js
input_type='hidden' # чтобы не отрисовывать текстовое поле
is_hidden=False # Чтобы отрисовывать заголовок в Inline
def label_for_value( self, value ):
rel_name=self.search_fields[0].split( '__' )[0]
key=self.rel.get_related_field().name
obj=self.rel.to._default_manager.get( **{key: value} )
return getattr( obj, rel_name )
def __init__( self, rel, search_fields, attrs = None ):
self.rel=rel
self.search_fields=search_fields
super( ForeignKeySearchInput, self ).__init__( attrs )
def render( self, name, value, attrs = None ):
if attrs is None:
attrs={}
rendered=super( ForeignKeySearchInput, self ).render( name, value, attrs )
if value:
label=self.label_for_value( value )
else:
label=u''
return SafeUnicode( rendered+u'''
<input type="text" id="lookup_%(name)s" value="%(label)s" size="40" search_fields="%(search_fields)s" app_label="%(app_label)s" model_name="%(model_name)s" autocomplete_mode="ForeignKey" class="to_autocomplete"/>
<a class="deletelink" title="удалить связь" onclick="autocomplete_delete(this)">&nbsp;</a>
'''%{
'search_fields': ','.join( self.search_fields ),
'MEDIA_URL': settings.MEDIA_URL,
'model_name': self.rel.to._meta.module_name,
'app_label': self.rel.to._meta.app_label,
'label': label,
'name': name,
'value': value,
}
)
class ManyToManySearchInput( forms.MultipleHiddenInput ):
"""
A Widget for displaying ForeignKeys in an autocomplete search input
instead in a <select> box.
"""
class Media:
css=media_css
js=media_js
input_type='hidden'
is_hidden=False
def __init__( self, rel, search_fields, attrs = None ):
self.rel=rel
self.search_fields=search_fields
super( ManyToManySearchInput, self ).__init__( attrs )
self.help_text=u""
def value_from_datadict( self, data, files, name ):
if isinstance( data, ( MultiValueDict, MergeDict ) ):
res=data.getlist( name )
else:
res=data.get( name, None )
return res
def render( self, name, value, attrs = None ):
if attrs is None:
attrs={}
if value is None:
value=[]
label=''
selected=''
rel_name=self.search_fields[0].split( '__' )[0]
for id in value:
obj=self.rel.to.objects.get( pk = id )
selected=selected+mark_safe( u"""
<div class="to_delete deletelink" ><input type="hidden" name="%(name)s" value="%(value)s"/>%(label)s</div>"""
)%{
'label': getattr( obj, rel_name ),
'name': name,
'value': obj.id,
}
return mark_safe( u'''
<input type="text" id="lookup_%(name)s" value="" size="40" search_fields="%(search_fields)s" app_label="%(app_label)s" model_name="%(model_name)s" autocomplete_mode="ManyToMany" class="to_autocomplete"/>%(label)s
<div style="float:left; padding-left:105px; width:300px;">
<font style="color:#999999;font-size:10px !important;">%(help_text)s</font>
<div id="box_%(name)s" style="padding-left:20px;cursor:pointer;">
%(selected)s
</div></div>
''' )%{
'search_fields': ','.join( self.search_fields ),
'model_name': self.rel.to._meta.module_name,
'app_label': self.rel.to._meta.app_label,
'label': label,
'name': name,
'value': value,
'selected':selected,
'help_text':self.help_text
}
class AutocompleteModelAdmin( admin.ModelAdmin ):
def __call__( self, request, url ):
if url is None:
pass
elif url=='search':
return self.search( request )
return super( AutocompleteModelAdmin, self ).__call__( request, url )
def get_urls( self ):
from django.conf.urls.defaults import patterns, url
def wrap( view ):
def wrapper( *args, **kwargs ):
return self.admin_site.admin_view( view )( *args, **kwargs )
return update_wrapper( wrapper, view )
info=self.model._meta.app_label, self.model._meta.module_name
urlpatterns=patterns( '',
url( r'^$', wrap( self.changelist_view ), name = '%s_%s_changelist'%info ),
url( r'^add/$', wrap( self.add_view ), name = '%s_%s_add'%info ),
url( r'^(.+)/history/$', wrap( self.history_view ), name = '%s_%s_history'%info ),
url( r'^(.+)/delete/$', wrap( self.delete_view ), name = '%s_%s_delete'%info ),
url( r'^search/$', wrap( self.search_view ), name = '%s_%s_search'%info ),
url( r'^(.+)/$', wrap( self.change_view ), name = '%s_%s_change'%info ),
)
return urlpatterns
def search_view( self, request ):
# Searches in the fields of the given related model and returns the
# result as a simple string to be used by the jQuery Autocomplete plugin
query=request.GET.get( 'q', None )
app_label=request.GET.get( 'app_label', None )
model_name=request.GET.get( 'model_name', None )
search_fields=request.GET.get( 'search_fields', None )
if search_fields and app_label and model_name and query:
def construct_search( field_name ):
# use different lookup methods depending on the notation
if field_name.startswith( '^' ):
return "%s__istartswith"%field_name[1:], field_name[1:]
elif field_name.startswith( '=' ):
return "%s__iexact"%field_name[1:], field_name[1:]
elif field_name.startswith( '@' ):
return "%s__search"%field_name[1:], field_name[1:]
else:
return "%s__icontains"%field_name, field_name
model=models.get_model( app_label, model_name )
q=None
fields=[]
for field_name in search_fields.split( ',' ):
name, name1=construct_search( field_name )
fields.append( name1 )
if q:
q=q|models.Q( **{str( name ):query} )
else:
q=models.Q( **{str( name ):query} )
qs=model.objects.filter( q )
data=''
for f in qs[0:10]:
res=[]
for field in fields:
parts=field.split( '__' )
if len( parts )>1:
s='%s'%getattr( getattr( f, parts[0] ), parts[1] )
else:
s='%s'%getattr( f, parts[0] )
if s and s not in( res ):
res.append( s )
data+=u'%s|%s\n'%( ', '.join( res ), f.pk )
return HttpResponse( data )
rel_name=field_name.split( '__' )[0]
data=''.join( [u'%s|%s\n'%( getattr( f, rel_name ), f.pk ) for f in qs] )
return HttpResponse( data )
return HttpResponseNotFound()
def formfield_for_dbfield( self, db_field, **kwargs ):
# For ForeignKey use a special Autocomplete widget.
if isinstance( db_field, models.ForeignKey ) and db_field.name in self.related_search_fields:
kwargs['widget']=ForeignKeySearchInput( db_field.rel,
self.related_search_fields[db_field.name] )
# extra HTML to the end of the rendered output.
if 'request' in kwargs.keys():
kwargs.pop( 'request' )
formfield=db_field.formfield( **kwargs )
# Don't wrap raw_id fields. Their add function is in the popup window.
if not db_field.name in self.raw_id_fields:
# formfield can be None if it came from a OneToOneField with
# parent_link=True
if formfield is not None:
formfield.widget=AutocompleteWidgetWrapper( formfield.widget, db_field.rel, self.admin_site )
return formfield
# For ManyToManyField use a special Autocomplete widget.
if isinstance( db_field, models.ManyToManyField )and db_field.name in self.related_search_fields:
kwargs['widget']=ManyToManySearchInput( db_field.rel,
self.related_search_fields[db_field.name] )
db_field.help_text=''
# extra HTML to the end of the rendered output.
if 'request' in kwargs.keys():
kwargs.pop( 'request' )
formfield=db_field.formfield( **kwargs )
# Don't wrap raw_id fields. Their add function is in the popup window.
if not db_field.name in self.raw_id_fields:
# formfield can be None if it came from a OneToOneField with
# parent_link=True
if formfield is not None:
formfield.widget=AutocompleteWidgetWrapper( formfield.widget, db_field.rel, self.admin_site )
return formfield
return super( AutocompleteModelAdmin, self ).formfield_for_dbfield( db_field, **kwargs )
class AutocompleteWidgetWrapper( RelatedFieldWidgetWrapper ):
def render( self, name, value, *args, **kwargs ):
rel_to=self.rel.to
related_url='../../../%s/%s/'%( rel_to._meta.app_label, rel_to._meta.object_name.lower() )
self.widget.choices=self.choices
output=[self.widget.render( name, value, *args, **kwargs )]
if rel_to in self.admin_site._registry: # If the related object has an admin interface:
# TODO: "id_" is hard-coded here. This should instead use the correct
# API to determine the ID dynamically.
output.append( u'<a href="%sadd/" class="add-another" id="add_id_%s" onclick="return showAutocompletePopup(this);"> '%\
( related_url, name ) )
output.append( u'<img src="%simg/admin/icon_addlink.gif" width="10" height="10" alt="%s" target="_blank"/></a>'%( settings.ADMIN_MEDIA_PREFIX, _( 'Add Another' ) ) )
return mark_safe( u''.join( output ) )
class AutocompleteInlineModelAdmin( InlineModelAdmin ):
def formfield_for_dbfield( self, db_field, **kwargs ):
# For ForeignKey use a special Autocomplete widget.
if isinstance( db_field, models.ForeignKey ) and db_field.name in self.related_search_fields:
kwargs['widget']=ForeignKeySearchInput( db_field.rel,
self.related_search_fields[db_field.name] )
# extra HTML to the end of the rendered output.
if 'request' in kwargs.keys():
kwargs.pop( 'request' )
formfield=db_field.formfield( **kwargs )
# Don't wrap raw_id fields. Their add function is in the popup window.
if not db_field.name in self.raw_id_fields:
# formfield can be None if it came from a OneToOneField with
if formfield is not None:
formfield.widget=AutocompleteWidgetWrapper( formfield.widget, db_field.rel, self.admin_site )
return formfield
# For ManyToManyField use a special Autocomplete widget.
if isinstance( db_field, models.ManyToManyField )and db_field.name in self.related_search_fields:
kwargs['widget']=ManyToManySearchInput( db_field.rel,
self.related_search_fields[db_field.name] )
db_field.help_text=''
# extra HTML to the end of the rendered output.
if 'request' in kwargs.keys():
kwargs.pop( 'request' )
formfield=db_field.formfield( **kwargs )
# Don't wrap raw_id fields. Their add function is in the popup window.
if not db_field.name in self.raw_id_fields:
# formfield can be None if it came from a OneToOneField with
# parent_link=True
if formfield is not None:
formfield.widget=AutocompleteWidgetWrapper( formfield.widget, db_field.rel, self.admin_site )
return formfield
return super( AutocompleteInlineModelAdmin, self ).formfield_for_dbfield( db_field, **kwargs )
class AutocompleteStackedInline( AutocompleteInlineModelAdmin ):
template='admin/edit_inline/stacked.html'
class AutocompleteTabularInline( AutocompleteInlineModelAdmin ):
template='admin/edit_inline/tabular.html'
class WildModelSearchInput( forms.HiddenInput ):
"""
A Widget for displaying ForeignKeys in an autocomplete search input
instead in a <select> box.
"""
class Media:
css=media_css
js=media_js
input_type='hidden'
is_hidden=False
def label_for_value( self, value ):
model=models.get_model( self.app_label, self.model_name )
try:
return model.objects.get( pk = value )
except:
return None
def __init__( self, app_label, model_name, search_fields, attrs = None ):
self.search_fields=search_fields
self.app_label=app_label
self.model_name=model_name
super( WildModelSearchInput, self ).__init__( attrs )
def render( self, name, value, attrs = None ):
if attrs is None:
attrs={}
rendered=super( WildModelSearchInput, self ).render( name, value, attrs )
if value:
label=self.label_for_value( value )
else:
label=u''
return rendered+mark_safe( u'''
<input type="text" id="lookup_%(name)s" value="%(label)s" size="40" search_fields="%(search_fields)s" app_label="%(app_label)s" model_name="%(model_name)s" autocomplete_mode="ForeignKey" class="to_autocomplete"/>
'''%{
'search_fields': ','.join( self.search_fields ),
'MEDIA_URL': settings.MEDIA_URL,
'model_name': self.model_name,
'app_label': self.app_label,
'label': label,
'name': name,
'value': value,
} )