Let's assume I have the following table class:
class TestTable(tables.Table):
id = tables.Column()
description = tables.Column()
def render_description(self, value):
return mark_safe('''<a href=%s>%s</a>''' % (???, value))
Is it possible to access the value of the column "id" in the render method, so that I can build up a link which leads to the id but shows the text which depends on the 'description'-field?
Thanks in advance!
From a quick glance at the docs for render_FOO it looks like you can just do:
class TestTable(tables.Table):
id = tables.Column()
description = tables.Column()
def render_description(self, value, record):
return mark_safe('''<a href=%s>%s</a>''' % (record.id, value)
Not sure of the exact shape of a row record, so it might be record['id'], the link to the docs should help with exploration...
#Darb Thank you, that option works perfectly. However I was wondering if there is any way to do this using accesors instead of hacking a text column to output html...
In my case I use
# tables.py
from django.core.urlresolvers import reverse
from django.utils.safestring import mark_safe
#...
class FieldTable(tables.Table):
allows__count = tables.LinkColumn(viewname=None, attrs={'td': {'class': 'leftA'}},
verbose_name='No. of Allowed values')
def __init__(self, *args, **kwargs):
super(FieldTable, self).__init__(*args, **kwargs)
def render_allows__count(self, value, record):
if value!=0:
a = reverse(viewname='dict:field_detail',
kwargs=
{'field_slug': record.slug,
'extract_slug': record.extract.slug,
'system_slug': record.extract.system.slug})
return mark_safe('<a href={}>{}</a>'.format(a, value))
However I would like to replace the mark_safe, for something that calls the accessor of allows__count and returns the reverse hyperlink and the value...
Anyway works for know
Related
I'm creating the following custom field based off How to create list field in django
import re
from django.db import models
from django.forms.widgets import TextInput
class ListField(models.TextField):
__metaclass__ = models.SubfieldBase
description = "Stores a python list"
widget = TextInput
def __init__(self, *args, **kwargs):
super(ListField, self).__init__(*args, **kwargs)
def to_python(self, value):
if not value:
return []
return filter(None, re.split(r'\,|\s*', value))
def get_prep_value(self, value):
if value is None:
return value
return ', '.join(value)
def value_to_string(self, obj):
value = self._get_val_from_obj(obj)
return self.get_db_prep_value(value)
from south.modelsinspector import add_introspection_rules
add_introspection_rules([], ["^cflowportal\.utils\.modelutils\.ListField"])
Basically, what I want to achieve is a field where you write something like "1, asd, asdf fdgd", it stores it as such in the database but when retrieved it should return that string as an array and when given an array it should convert it back to a comma-seperated string.
I'm still not sure if what I've written so far works, but I'm having trouble displaying it as an input field and not a textarea even if I've set widget=TextInput.
So, how do I show it in the admin with the same input used by the standard CharField?
How can I customize it so that it displays a comma-separated string when showed on such input, but is given back as a Python List when accessed elsewhere?
Thanks
The following is a method to realize what you want
from django.db import models
class Blog(models.Model):
title = models.CharField(max_length=256)
labels = models.TextField()
def get_labels(self):
return self.content.split('\n')
def set_labels(self,value):
if isinstance(value,list) or isinstance(value,tuple) or isinstance(value,set):
content = '\n'.join(value)
else:
content = value
self.content = content
You can regard labels as a ListField, set value use obj.set_labels(list) function, and get value use obj.get_labels()
It act as a List Field, and admin site will run as a normal TextField.
This is what I did, but a better solution is excepted.
and a better way to do this is using save_model in admin.py:
class BlogAdmin(admin.ModelAdmin):
def save_model(self, request, obj, form, change):
# extra data handling, prevent data convert
obj.save()
I have a model lets say MyModel which has a foreign key to another model say Tag.
class MyModel(models.Model):
id=models.AutoField(primary_key=True)
name=models.CharField(max_length=200)
tag = models.ManyToMany(Tag)
I have approximately 50,000 instances of MyModel and each MyModel can have 100 Tags.
If I use the above model I will get 5,000,000 entries in a table, mymodel_tag but I can use all the features of ORM.
However If I write custom methods and treat above field as an array of integers and write custom code to retrieve/save ids of tags associated with MyModel, I will have just 50K entries but I will have to write custom code for retrieval etc.
a) I want to know the pros and cons of both the approaches!
b) If I have to have to use the custom array approach how can i do it efficiently.
Umm..
tag = models.ManyToManyField(Tag)
?
With a foreign key, MyModel can only be associated with one and only one Tag. I'm honestly not even sure how you were able to give each one 100 Tags without have to duplicate each MyModel 100 times. If you were doing that, no wonder you're not liking the results.
ManyToManyField creates a join table that will consist only of an id (integer) reference to MyModel and an id (integer) reference to Tag. That is the most compact you'll ever get with this type of relationship, and it's the best practice, anyways.
Although I totally agree with what chrisdpratt says but unfortunately, I have been forced to do it otherwise. Here is one way I found of doing that at http://djangosnippets.org/snippets/1200/:
from django.db import models
from django import forms
class MultiSelectFormField(forms.MultipleChoiceField):
widget = forms.CheckboxSelectMultiple
def __init__(self, *args, **kwargs):
self.max_choices = kwargs.pop('max_choices', 0)
super(MultiSelectFormField, self).__init__(*args, **kwargs)
def clean(self, value):
if not value and self.required:
raise forms.ValidationError(self.error_messages['required'])
if value and self.max_choices and len(value) > self.max_choices:
raise forms.ValidationError('You must select a maximum of %s choice%s.'
% (apnumber(self.max_choices), pluralize(self.max_choices)))
return value
class MultiSelectField(models.Field):
__metaclass__ = models.SubfieldBase
def get_internal_type(self):
return "CharField"
def get_choices_default(self):
return self.get_choices(include_blank=False)
def _get_FIELD_display(self, field):
value = getattr(self, field.attname)
choicedict = dict(field.choices)
def formfield(self, **kwargs):
# don't call super, as that overrides default widget if it has choices
defaults = {'required': not self.blank, 'label': capfirst(self.verbose_name),
'help_text': self.help_text, 'choices':self.choices}
if self.has_default():
defaults['initial'] = self.get_default()
defaults.update(kwargs)
return MultiSelectFormField(**defaults)
def get_db_prep_value(self, value):
if isinstance(value, basestring):
return value
elif isinstance(value, list):
return ",".join(value)
def to_python(self, value):
if isinstance(value, list):
return value
return value.split(",")
def contribute_to_class(self, cls, name):
super(MultiSelectField, self).contribute_to_class(cls, name)
if self.choices:
func = lambda self, fieldname = name, choicedict = dict(self.choices):",".join([choicedict.get(value,value) for value in getattr(self,fieldname)])
setattr(cls, 'get_%s_display' % self.name, func)
I need help coming up with an efficient way to do a search query for a set of objects, based on a M2M field. My search form is going to look something like Blue Cross Blue Shield's | eg: this image
Now, let's suppose my model looks like this:
# models.py
class Provider(models.Model)
title = models.CharField(max_length=150)
phone = PhoneNumberField()
services_offered = models.ManyToManyField(ServiceType)
def __unicode__(self):
return self.title
class ServiceCategory(models.Model):
service_category = models.CharField(max_length=30)
def __unicode__(self):
return self.service_category
class Meta(object):
verbose_name_plural = "Service Categories"
class ServiceType(models.Model):
service_type = models.CharField(max_length=30)
service_category = models.ForeignKey(ServiceCategory)
def __unicode__(self):
return u'%s | %s' % (self.service_category, self.service_type
Also, we have to keep in mind that the options that we select are subject to change, since how they display on the form is dynamic (new ServiceCategories and ServiceTypes can be added at anytime). *How should I go about constructing a query for the Provider objects, given that a person using the search form can select multiple Services_Offered?*
This is currently my HIGHLY INEFFICIENT METHOD:
#managers.py
from health.providers.models import *
from django.db.models import Q
class Query:
def __init__(self):
self.provider_objects=Provider.objects.all()
self.provider_object=Provider.objects
self.service_object=ServiceType.objects
self.category_objects=ServiceCategory.objects.all()
def simple_search_Q(self, **kwargs): #matt's learning note: **kwargs passes any dictionary
return self.provider_objects.filter(
Q(services_offered__service_type__icontains=kwargs['service']),
Q(title__icontains=kwargs['title']),
Q(state=kwargs['state']),
).distinct().order_by('title')
====================
#views.py
from django.shortcuts import render_to_response
from health.providers.models import *
from health.search.forms import *
from health.search.managers import Query #location of the query sets
from django.core.paginator import Paginator, InvalidPage, EmptyPage
from django.template import RequestContext
def simple_search(request):
if request.method == 'POST':
SimpleSearch_form = SimpleSearch(request.POST)
if SimpleSearch_form.is_valid():
request.session["provider_list"] = None
kwargs = {'title': request.POST['title'],
'service': request.POST['service'], 'state': request.POST['state'] }
provider_list = Query().simple_search_Q(**kwargs)
return pagination_results(request, provider_list)
else:
SimpleSearch_form = SimpleSearch()
return render_to_response('../templates/index.html', { 'SimpleSearch_form': SimpleSearch_form},
context_instance=RequestContext(request))
How can I make my query:
Obtain Provider objects based on selecting multiple request.POST['service']
More efficient
Thanks for any help in advanced.
Best Regards,
Matt
1: for multiple request.POST['service'], I assume you mean these are CheckBoxes.
I'd make the CheckBox values ID's, not names, and do a PK lookup.
'services_offered__pk__in': request.POST.getlist('service')
That would return all Provider objects that have ALL of the services selected.
PS: You are also using CapitalCase for instances which is very confusing. If you want your code to be readable, I highly recommend some changes to your style (don't use CapitalCase for instances or variables) and make your variables more descriptive.
SimpleSearch_form = SimpleSearch() # what is SimpleSearch?
simplesearch_form = SimpleSearchForm() # now, it's very clear what the class SimpleSearchForm is
# and the form instance is clearly a for instance.
2: making it more efficient? You could get rid of a lot of code and code separation by remove your whole Query class. Also, I don't know why you are using Q objects since you are not doing anything that would require it (like OR or OR + AND).
def simple_search(request):
if request.method == 'POST':
searchform = SimpleSearchForm(request.POST)
if searchform.is_valid():
request.session['provider_list'] = None
post = request.POST
providers = Provider.objects.filter(services_offered__pk__in=post.getlist('services'),
title=post['title'], state=post['state'])
return pagination_results(request, provider_list)
else:
searchform = SimpleSearchForm()
return direct_to_template(request, '../templates/index.html', { 'searchform': searchform})
I keep field imdb_id for models Movie in my db:
class Movie(models.Model):
imdb_id = models.IntegerField('imdb ID', blank=True, null=True, unique=True)
def _get_imdb_url(self):
return self.imdb_id and 'http://www.imdb.com/title/tt%s/' % str(self.imdb_id).zfill(7) or ''
def _set_imdb_url(self, imdb_url):
self.imdb_id = int( re.compile(r'[^\d]').sub('', imdb_url))
imdb_url = property(_get_imdb_url, _set_imdb_url)
And I want to make special widget for displaying external link to imdb.com in the admin form near text input for field 'imdb_id'. I think it may be global widget for any form field with external link, generated by using special mask (in my case, this mask is 'http://www.imdb.com/title/tt%s/'). I know how to write widget, but I don't know how push my mask, defined in my Movie model, to this widget. I don't want to violate DRY principe and define this mask in two different places. And also it will be a good tool for the same purpose with other external links in future.
What do you think about this widget? How it is posssible to realize it? May be someone wrote it before me?
Thanks!
So, I decided not waiting for help and wrote this widget:
import re
from django import forms
class LinkFieldWidget(Widget):
'''
A TextField widget with previewing link, generated from field's value with special url mask
'''
def __init__(self, text, url='%s', *args, **kwargs):
self.url = url
self.text = text
if not re.search('%', self.url):
raise forms.ValidationError, u'Invalid URL mask'
super(LinkFieldWidget, self).__init__(*args, **kwargs)
def render(self, name, value, attrs=None):
output = []
output.append('''<input type="text" name="%s" value="%s" onkeyup="$('a#%s-link').attr('href', '%s'.replace('%s', this.value)).css({'display': (this.value ? 'inline' : 'none')})" />''' % (name, value or '', name, self.url, '%s'))
if self.url and self.text:
output.append('<span>%s</span>' % (value and self.url % value or '#', name, value and 'inline' or 'none', self.text))
return mark_safe(u' '.join(output))
Example using it in admin:
from utils.widgets import LinkFieldWidget
class MovieAdminForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(MovieAdminForm, self).__init__(*args, **kwargs)
self.fields['imdb_id'].widget = LinkFieldWidget(text='imdb', url=self.instance.imdb_link)
class MovieAdmin(admin.ModelAdmin):
form = MovieAdminForm
Model:
class Movie(models.Model):
.....
imdb_link = 'http://www.imdb.com/title/tt%s/'
def _get_imdb_url(self):
return self.imdb_id and self.imdb_link % str(self.imdb_id).zfill(7) or ''
def _set_imdb_url(self, imdb_url):
self.imdb_id = int( re.compile(r'[^\d]').sub('', imdb_url))
imdb_url = property(_get_imdb_url, _set_imdb_url)
I have a model with field:
class Movie(models.Model):
genre = models.CommaSeparatedIntegerField(max_length=100, choices=GENRE_CHOICES, blank=True, default=0)
lang = models.CommaSeparatedIntegerField(max_length=100, choices=LANG_CHOICES, blank=True, default=0)
And I need to get multiple select fields (not checkboxes) from that.
One way, that i found, is to redefine form from ModelAdmin
class MyMovieAdminForm(forms.ModelForm):
genre = forms.MultipleChoiceField(choices=GENRE_CHOICES)
lang = forms.MultipleChoiceField(choices=LANG_CHOICES)
class MovieAdmin(admin.ModelAdmin):
form = MyMovieAdminForm
admin.site.register(Movie, MovieAdmin)
But it need to redeclare 'label' and 'initial' for each field, that isn't good for DRY principle. And I doesn't understand, how can I set current value of object for initial value of each field?
And other way, that I found in manual is formfield-overrides. I use dev version from trunk and I try to use this code, but it didn't change my select fields to multiselect in admin interface:
class MovieAdmin(admin.ModelAdmin):
formfield_overrides = {
models.CommaSeparatedIntegerField: {'widget': forms.SelectMultiple},
}
May be anyone know, what is the best way to define multiple select fields? Thanks!
I don't find any working answer for making models.CommaSeparatedIntegerField as forms.SelectMultiple. So I changed models.CommaSeparatedIntegerField to models.ManyToManyField and form field becomes works very well! It is more suitable in cases, where you need to make queries on this field.
1) Remove choices from models.CommaSeparatedIntegerField in models
2) Create CommaSeparatedCharField as replacement
class CommaSeparatedCharField(forms.CharField):
def to_python(self, value):
if value in validators.EMPTY_VALUES:
return u''
csv = smart_unicode(','.join(value) )
return csv
class CommaSeparatedSelectMultiple(forms.SelectMultiple):
def render(self, *args, **kwargs):
print args, kwargs
args_=list(args)
if type(args_[1])!=list:
args_[1] = args_[1].split(',')
return super(CommaSeparatedSelectMultiple, self).render(*args_, **kwargs)
3) Apply
class MyAdmin(admin.ModelAdmin):
class form(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(forms.ModelForm, self).__init__(*args, **kwargs)
for field_name, choices in [
('minutes', [(i,str(i)) for i in range(60)]),
]:
self.fields[field_name] = CommaSeparatedCharField(
label=self.fields[field_name].label,
initial=self.fields[field_name].initial,
widget=forms.CommaSeparatedSelectMultiple(choices=choices)
)
Work nice on 1.2 trunk
I use the following for somewhat-DRYer form manipulation, and it works with 1.0. It's verbose, but it works.
class MyMovieAdminForm(forms.ModelForm):
## Meta, etc
def __init__(self, *args, **kwargs):
super(MyMovieAdminForm, self).__init__(*args, **kwargs)
self.fields["genre"].widget = forms.SelectMultiple(choices=foo)
self.fields["genre"].initial = self.instance.genre
# Doesn't require redefining label, etc.