Create + Update with custom widget - django

I have created a custom widget that holds "SelecTimeWidget" & "SelectDateWidget"
It works fine when i'm creating a new event but when i turn it into a (UpdateView)
I get an error
global name 'to_current_timezone' is not defined
I don't know how to go about this to allow the widget to be used in the creation and edit of an event.
class EventUpdateView(UpdateView):
form_class = CreateEvent
model = Event
class EventCreateView(CreateView):
form_class = CreateEvent
model = Event
def form_valid(self, form):
Event = form.save(commit=False)
Event.created_by = self.request.user
Event.save()
return HttpResponseRedirect('/calendar/')
class CreateEvent(forms.ModelForm):
class Meta:
model = Event
fields = ['title', 'start', 'end', 'description', 'category']
widgets = {
'start': SelectDateTimeWidget(date_format= '%m/%m/%Y'),
'end': SelectDateTimeWidget(date_format= '%d/%m/%Y')
}
The Widget, obviously SelectTimeWidget is also a custom one but the error is leading me to "value = to_current_timezone(value)" this line within the code below
class SelectDateTimeWidget(forms.MultiWidget):
supports_microseconds = False
def __init__(self, attrs=None, date_format=None, time_format=None):
widgets = (SelectDateWidget(empty_label=( "Year", "Month", "Day")),
SelectTimeWidget(use_seconds=False))
super(SelectDateTimeWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
value = to_current_timezone(value)
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
def subwidgets(self, name, value, attrs=None):
if self.is_localized:
for widget in self.widgets:
widget.is_localized = self.is_localized
# value is a list of values, each corresponding to a widget
# in self.widgets.
if not isinstance(value, list):
value = self.decompress(value)
output = []
final_attrs = self.build_attrs(attrs)
id_ = final_attrs.get('id')
for i, widget in enumerate(self.widgets):
try:
widget_value = value[i]
except IndexError:
widget_value = None
if id_:
final_attrs = dict(final_attrs, id='%s_%s' % (id_, i))
output.append(widget.render(name + '_%s' % i, widget_value, final_attrs))
return output
Does Anyone know a way around this?

"from django.forms.util import to_current_timezone"
I did not realise To_current_timezone was an import, once i found this it worked :)
Love answering my own question after some time worrying about it

Related

How to populate a bespoke Django form MultiWidget

I have created a bespoke form widget which saves an address as a list.
class AddressWidget(MultiWidget):
def __init__(self, base_widget, attrs=None):
widgets = (
forms.TextInput(attrs={'placeholder': 'Address', 'class': 'form-control'}),
forms.TextInput(attrs={'placeholder': 'Address Line 2', 'class': 'form-control'}),
forms.TextInput(attrs={'placeholder': 'City', 'class': 'form-control'}),
forms.TextInput(attrs={'placeholder': 'State', 'class': 'form-control'}),
forms.TextInput(attrs={'placeholder': 'Postcode', 'class': 'form-control'}),
)
super().__init__(widgets, attrs)
def decompress(self, value):
if value:
return (value.address1, value.address2, value.city, value.state, value.postcode)
return (None, None, None, None, None)
Saving the form works as I want, but when re-entering the form in order to change values, it doesn't get prepopulated, although all other regular fields do.
How do I get it to populate the field?
It is used in the form as:
class ResponseForm(forms.ModelForm)
address = AddressField()
...
class Meta:
model = SomeModel
fields = ('address',)
class AddressField(MultiValueField):
"""
Custom field to take user inputs of Address
"""
widget = AddressWidget(base_widget=TextInput)
def __init__(self, *, attrs=None, **kwargs):
fields = (
CharField(label=_('Address Line 1'), max_length=25),
CharField(label=_('Address Line 2'), max_length=25),
CharField(label=_('City'), max_length=25),
CharField(label=_('State'), max_length=25),
CharField(label=_('Country'), max_length=25)
)
super().__init__(fields, required=False)
def clean(self, value, initial=None):
value = super().clean(value)
return value
def compress(self, value_list):
if value_list:
return value_list
return [[],[],[]]
Within the model it is defined as:
class SomeModel(models.Model):
address = models.TextField()
...
A typical value entered might be:
123 Some Street
Example Area
This Town
MYP 0ST
It is saved to the database table like this in a text field.
EDIT
I think my problem is the ResponseForm. I am using an app and response form is initialising the widgets.
class ResponseForm(forms.ModelForm):
FIELDS = {
Question.TINY_TEXT: TinyCharField,
Question.EXTRA_SHORT_TEXT: ExtraShortCharField,
Question.SHORT_TEXT: ShortCharField,
Question.TEXT: forms.CharField,
Question.EXTRA_LONG_TEXT: forms.CharField,
Question.SELECT_MULTIPLE: forms.MultipleChoiceField,
Question.INTEGER: forms.IntegerField,
Question.FLOAT: forms.FloatField,
Question.DATE: forms.DateField,
Question.TIME: forms.TimeField,
Question.CHECK_BOXES: forms.MultipleChoiceField,
Question.ADDRESS: forms.CharField, #AddressField,
Question.DISCLAIMER: forms.BooleanField,
Question.HEIGHT: HeightFormField,
Question.WEIGHT: WeightFormField,
Question.RANGE: IntegerRangeField,
Question.VOLUME: VolumeFormField,
}
WIDGETS = {
Question.TINY_TEXT: forms.TextInput(),
Question.EXTRA_SHORT_TEXT: forms.TextInput(),
Question.SHORT_TEXT: forms.TextInput(),
Question.TEXT: forms.Textarea(attrs={'maxlength':250}),
Question.ADDRESS: forms.Textarea(attrs={'maxlength':250}),
Question.EXTRA_LONG_TEXT: forms.Textarea(attrs={'maxlength':750}),
Question.RADIO: forms.RadioSelect,
Question.SELECT: forms.Select,
Question.SELECT_IMAGE: ImageSelectWidget,
Question.SELECT_MULTIPLE: forms.SelectMultiple,
Question.CHECK_BOXES: forms.CheckboxSelectMultiple,
Question.DATE: DatePickerInput,
Question.DISCLAIMER: forms.CheckboxInput(attrs={'required': True}),
}
class Meta:
model = Response
fields = ()
def __init__(self, *args, **kwargs):
"""Expects a survey object to be passed in initially"""
self.survey = kwargs.pop("survey")
self.user = kwargs.pop("user")
try:
self.step = int(kwargs.pop("step"))
except KeyError:
self.step = None
super().__init__(*args, **kwargs)
self.uuid = uuid.uuid4().hex
self.categories = self.survey.non_empty_categories()
self.qs_with_no_cat = self.survey.questions.filter(category__isnull=True).order_by("order", "id")
if self.survey.display_method == Survey.BY_CATEGORY:
self.steps_count = len(self.categories) + (1 if self.qs_with_no_cat else 0)
else:
self.steps_count = len(self.survey.questions.all())
# will contain prefetched data to avoid multiple db calls
self.response = False
self.answers = False
self.add_questions(kwargs.get("data"))
self._get_preexisting_response()
if not self.survey.editable_answers and self.response is not None:
for name in self.fields.keys():
self.fields[name].widget.attrs["disabled"] = True
def add_questions(self, data):
'''
add a field for each survey question, corresponding to the question
type as appropriate.
'''
if self.survey.display_method == Survey.BY_CATEGORY and self.step is not None:
if self.step == len(self.categories):
qs_for_step = self.survey.questions.filter(category__isnull=True).order_by("order", "id")
else:
qs_for_step = self.survey.questions.filter(category=self.categories[self.step])
for question in qs_for_step:
self.add_question(question, data)
else:
for i, question in enumerate(self.survey.questions.all()):
not_to_keep = i != self.step and self.step is not None
if self.survey.display_method == Survey.BY_QUESTION and not_to_keep:
continue
self.add_question(question, data)
def current_categories(self):
if self.survey.display_method == Survey.BY_CATEGORY:
if self.step is not None and self.step < len(self.categories):
return [self.categories[self.step]]
return [Category(name="No category", description="No cat desc")]
else:
extras = []
if self.qs_with_no_cat:
extras = [Category(name="No category", description="No cat desc")]
return self.categories + extras
def _get_preexisting_response(self):
"""Recover a pre-existing response in database.
The user must be logged. Will store the response retrieved in an attribute
to avoid multiple db calls.
:rtype: Response or None"""
if self.response:
return self.response
if not self.user.is_authenticated:
self.response = None
else:
try:
self.response = Response.objects.prefetch_related("user", "survey").get(
user=self.user, survey=self.survey
)
except Response.DoesNotExist:
LOGGER.debug("No saved response for '%s' for user %s", self.survey, self.user)
self.response = None
return self.response
def _get_preexisting_answers(self):
"""Recover pre-existing answers in database.
The user must be logged. A Response containing the Answer must exists.
Will create an attribute containing the answers retrieved to avoid multiple
db calls.
:rtype: dict of Answer or None"""
if self.answers:
return self.answers
response = self._get_preexisting_response()
if response is None:
self.answers = None
try:
answers = Answer.objects.filter(response=response).prefetch_related("question")
self.answers = {answer.question.id: answer for answer in answers.all()}
except Answer.DoesNotExist:
self.answers = None
return self.answers
def _get_preexisting_answer(self, question):
"""Recover a pre-existing answer in database.
The user must be logged. A Response containing the Answer must exists.
:param Question question: The question we want to recover in the
response.
:rtype: Answer or None"""
answers = self._get_preexisting_answers()
return answers.get(question.id, None)
def get_question_initial(self, question, data):
"""Get the initial value that we should use in the Form
:param Question question: The question
:param dict data: Value from a POST request.
:rtype: String or None"""
initial = None
answer = self._get_preexisting_answer(question)
if answer:
# Initialize the field with values from the database if any
if question.type in [Question.SELECT_MULTIPLE]:
initial = []
if answer.body == "[]":
pass
elif "[" in answer.body and "]" in answer.body:
initial = []
unformated_choices = answer.body[1:-1].strip()
for unformated_choice in unformated_choices.split(settings.CHOICES_SEPARATOR):
choice = unformated_choice.split("'")[1]
initial.append(slugify(choice))
else:
# Only one element
initial.append(slugify(answer.body))
elif question.type == Question.DATE:
initial = datetime.datetime.strptime(answer.body, "%Y-%m-%d").date()
else:
initial = answer.body
if data:
# Initialize the field field from a POST request, if any.
# Replace values from the database
initial = data.get("question_%d" % question.pk)
return initial
def get_question_widget(self, question):
"""Return the widget we should use for a question.
:param Question question: The question
:rtype: django.forms.widget or None"""
try:
return self.WIDGETS[question.type]
except KeyError:
return None
#staticmethod
def get_question_choices(question):
"""Return the choices we should use for a question.
:param Question question: The question
:rtype: List of String or None"""
qchoices = None
if question.type not in [Question.TEXT, Question.SHORT_TEXT, Question.INTEGER, Question.FLOAT, Question.DATE]:
qchoices = question.get_choices()
# add an empty option at the top so that the user has to explicitly
# select one of the options
if question.type in [Question.SELECT, Question.SELECT_IMAGE]:
qchoices = tuple([("", "-------------")]) + qchoices
return qchoices
def get_question_field(self, question, **kwargs):
"""Return the field we should use in our form.
:param Question question: The question
:param **kwargs: A dict of parameter properly initialized in
add_question.
:rtype: django.forms.fields"""
# logging.debug("Args passed to field %s", kwargs)
try:
return self.FIELDS[question.type](**kwargs)
except KeyError:
return forms.ChoiceField(**kwargs)
def add_question(self, question, data):
"""Add a question to the form.
:param Question question: The question to add.
:param dict data: The pre-existing values from a post request."""
kwargs = {"label": question.text, "required": question.required}
initial = self.get_question_initial(question, data)
if initial:
kwargs["initial"] = initial
choices = self.get_question_choices(question)
if choices:
kwargs["choices"] = choices
widget = self.get_question_widget(question)
if widget:
kwargs["widget"] = widget
field = self.get_question_field(question, **kwargs)
field.widget.attrs["category"] = question.category.name if question.category else ""
if question.type == Question.DATE:
field.widget.attrs["class"] = "date"
# logging.debug("Field for %s : %s", question, field.__dict__)
self.fields["question_%d" % question.pk] = field
def has_next_step(self):
if not self.survey.is_all_in_one_page():
if self.step < self.steps_count - 1:
return True
return False
def next_step_url(self):
if self.has_next_step():
context = {"id": self.survey.id, "step": self.step + 1}
return reverse("survey:survey-detail-step", kwargs=context)
def current_step_url(self):
return reverse("survey-detail-step", kwargs={"id": self.survey.id, "step": self.step})
def save(self, commit=True):
"""Save the response object"""
# Recover an existing response from the database if any
# There is only one response by logged user.
response = self._get_preexisting_response()
if not self.survey.editable_answers and response is not None:
return None
if response is None:
response = super().save(commit=False)
response.survey = self.survey
response.interview_uuid = self.uuid
if self.user.is_authenticated:
response.user = self.user
response.save()
# response "raw" data as dict (for signal)
data = {"survey_id": response.survey.id, "interview_uuid": response.interview_uuid, "responses": []}
# create an answer object for each question and associate it with this
# response.
for field_name, field_value in list(self.cleaned_data.items()):
if field_name.startswith("question_"):
# warning: this way of extracting the id is very fragile and
# entirely dependent on the way the question_id is encoded in
# the field name in the __init__ method of this form class.
q_id = int(field_name.split("_")[1])
question = Question.objects.get(pk=q_id)
answer = self._get_preexisting_answer(question)
if answer is None:
answer = Answer(question=question)
if question.type == Question.SELECT_IMAGE:
value, img_src = field_value.split(":", 1)
# TODO Handling of SELECT IMAGE
LOGGER.debug("Question.SELECT_IMAGE not implemented, please use : %s and %s", value, img_src)
answer.body = field_value
data["responses"].append((answer.question.id, answer.body))
LOGGER.debug("Creating answer for question %d of type %s : %s", q_id, answer.question.type, field_value)
answer.response = response
answer.save()
survey_completed.send(sender=Response, instance=response, data=data)
return response
Since address is stored as models.TextField() and retrieved as Python str in the model:
In MultiWidget decompress, load from str:
class AddressWidget(MultiWidget):
...
def decompress(self, value):
if value:
return json.loads(value)
return (None, None, None, None, None)
In MultiValueField compress, dump to str:
class AddressField(MultiValueField):
...
def compress(self, value_list):
if value_list:
return json.dumps(value_list)
return ''

django form.is_valid returns false after adding fields dynamically to the form

I have a form like this,
class UniqueUrlForm(forms.ModelForm):
cc_number = cc_form.CardNumberField(label='Card Number')
cc_expiry = cc_form.CardExpiryField(label='Expiration Date')
cc_code = cc_form.SecurityCodeField(label='CVV/CVC')
class Meta:
model = Transactions
fields = ['customer_name', 'customer_phone', 'customer_email', 'total_amount', 'cc_number', 'cc_expiry',
'cc_code']
def __init__(self, *args, **kwargs):
super().__init__()
store_id = kwargs.get("store_id", "1")
payment_page = get_object_or_404(
PaymentPageDisplayDetails.objects.filter(store_id=store_id).values("payment_fields_visible"))
with urllib.request.urlopen(payment_page['payment_fields_visible']) as url:
display_fields = json.loads(url.read().decode())
for field_name in display_fields:
self.fields[field_name] = forms.CharField(required=False)
and a view like this,
def getpaymentpage(request, store_identifier):
uniqueurl_form = UniqueUrlForm(request.POST or None, request.FILES or None, {"store_id": 1})
if uniqueurl_form.is_valid():
trx_details = {
"amount": uniqueurl_form.cleaned_data['amount'],
"customer_email": uniqueurl_form.cleaned_data['customer_email'],
"customer_phone": uniqueurl_form.cleaned_data['customer_phone'],
"cc_number": uniqueurl_form.cleaned_data['cc_number'],
"cc_name": uniqueurl_form.cleaned_data['customer_name'],
"cc_month": uniqueurl_form.cleaned_data['cc_month'],
"cc_year": uniqueurl_form.cleaned_data['cc_year'],
"cvv": uniqueurl_form.cleaned_data['cvv'],
}
return HttpResponse(trx_details)
context = {
'form': {
uniqueurl_form,
},
"page": store_display,
}
return render(request, 'unique_url.html', context)
I have tried print(uniqueurl_form.errors) it always returns empty and uniqueurl_form.is_valid() as false.
Is it because I'm adding dynamic fields to the form.
I have referred the following,
dynamically add field to a form
What am I doing wrong here?
Thank you for your suggestions.
weirdly it started working when i made following changes,
class UniqueUrlForm(forms.ModelForm):
cc_number = cc_form.CardNumberField(label='Card Number')
cc_expiry = cc_form.CardExpiryField(label='Expiration Date')
cc_code = cc_form.SecurityCodeField(label='CVV/CVC')
class Meta:
model = Transactions
fields = ['customer_name', 'customer_phone', 'customer_email', 'total_amount', 'cc_number', 'cc_expiry',
'cc_code']
def __init__(self, *args, **kwargs):
store_id = kwargs.get("store_id", "1")
super(UniqueUrlForm, self).__init__(*args, **kwargs)
payment_page = get_object_or_404(
PaymentPageDisplayDetails.objects.filter(store_id=store_id).values("payment_fields_visible"))
with urllib.request.urlopen(payment_page['payment_fields_visible']) as url:
display_fields = json.loads(url.read().decode())
for field_name in display_fields:
self.fields[field_name] = forms.CharField()
my guess is because I did not specify class name in my .super() event though it was appending the fields it was not sure what validations to put on those fields.

Overriding render of RadioSelect in django forms gives unexpected results

I want to override the render for RadioSelect in django. (I have done a similar thing for checkboxes and I want both to look the same). The general workflow for this would be to write a custom renderer, and then change the render in the ChoiceInput, BUT when I copy the existing code and run it the html output is not safe and the escaped html string is shown. This is not making any sense as I didn't do any changes to the class yet, other than change the name:
In my widgets.py:
class ButtonRadioFieldRenderer(ChoiceFieldRenderer):
choice_input_class = OtherRadioChoiceInput
class OtherRadioChoiceInput(OtherChoiceInput):
input_type = 'radio'
def __init__(self, *args, **kwargs):
super(OtherRadioChoiceInput, self).__init__(*args, **kwargs)
self.value = force_text(self.value)
#html_safe
#python_2_unicode_compatible
class OtherChoiceInput(SubWidget):
"""
An object used by ChoiceFieldRenderer that represents a single
<input type='$input_type'>.
"""
input_type = None # Subclasses must define this
def __init__(self, name, value, attrs, choice, index):
self.name = name
self.value = value
self.attrs = attrs
self.choice_value = force_text(choice[0])
self.choice_label = force_text(choice[1])
self.index = index
if 'id' in self.attrs:
self.attrs['id'] += "_%d" % self.index
def __str__(self):
return self.render()
def render(self, name=None, value=None, attrs=None, choices=()):
if self.id_for_label:
label_for = format_html(' for="{}"', self.id_for_label)
else:
label_for = ''
attrs = dict(self.attrs, **attrs) if attrs else self.attrs
return format_html(
'<label{}>{} {}</label>', label_for, self.tag(attrs), self.choice_label
)
def is_checked(self):
return self.value == self.choice_value
def tag(self, attrs=None):
attrs = attrs or self.attrs
final_attrs = dict(attrs, type=self.input_type, name=self.name, value=self.choice_value)
if self.is_checked():
final_attrs['checked'] = 'checked'
return format_html('<input{} />', flatatt(final_attrs))
#property
def id_for_label(self):
return self.attrs.get('id', '')
In my forms.py:
DELIMITER_CHOICES = [
('space', ugettext_lazy("space")),
('underscore', "_"),
]
class SingleDelimiterForm(forms.Form):
delimiter = forms.ChoiceField(initial=0, widget=forms.RadioSelect(renderer=ButtonRadioFieldRenderer), choices=DELIMITER_CHOICES)
The only changes I did was to put "Other" and "Button" in front of already existing classes, and the code doesn't run anymore. If I change OtherChoiceInput to ChoiceInput the code is working. (in the end I only want to add a class to the label...)
I needed unicode strings for the code to work, so from __future__ import unicode_literals fixed the issue. I still found this error very confusing.

Serializing django-mptt trees in Tastypie

How do I serialize django-mptt trees in Tastypie?
I want to use django-mptt's cache_tree_children(). I've tried applying in different Tastypie hooks, but it throws an error.
Without the cache_tree_children method you could probably have your children serialized by simply hooking up a ToManyField with full=True pointing at the children property:
class MenuResource(ModelResource):
children = fields.ToManyField('self', 'children', null=True, full=True)
parent = fields.ToOneField('self', 'parent', null=True)
class Meta:
queryset = Menu.objects.all()
To implement the cache_tree_children function you could write your own ToManyField subclass that overrides the standard dehydrate function. Please note, that I only tested this solution very superficially:
def dehydrate(self, bundle):
if not bundle.obj or not bundle.obj.pk:
if not self.null:
raise ApiFieldError("The model '%r' does not have a primary key and can not be used in a ToMany context." % bundle.obj)
return []
the_m2ms = None
previous_obj = bundle.obj
attr = self.attribute
if isinstance(self.attribute, basestring):
attrs = self.attribute.split('__')
the_m2ms = bundle.obj
for attr in attrs:
previous_obj = the_m2ms
try:
the_m2ms = getattr(the_m2ms, attr, None)
except ObjectDoesNotExist:
the_m2ms = None
if not the_m2ms:
break
elif callable(self.attribute):
the_m2ms = self.attribute(bundle)
if not the_m2ms:
if not self.null:
raise ApiFieldError("The model '%r' has an empty attribute '%s' and doesn't allow a null value." % (previous_obj, attr))
return []
self.m2m_resources = []
m2m_dehydrated = []
# There goes your ``cache_tree_children``
for m2m in cache_tree_children(the_m2ms.all()):
m2m_resource = self.get_related_resource(m2m)
m2m_bundle = Bundle(obj=m2m, request=bundle.request)
self.m2m_resources.append(m2m_resource)
m2m_dehydrated.append(self.dehydrate_related(m2m_bundle, m2m_resource))
return m2m_dehydrated
One of the main advantages of this method is that you don't have to care about detail / list view constraints / differences no more. You even can parameterize this aspect of your resource further down until you got some kind of default behavior that fits your needs. Field-based, that is. Which I think is cool.
This is how I solved it:
class MenuResource(ModelResource):
parent = fields.ForeignKey('self', 'parent', null=True)
class Meta:
serializer = PrettyJSONSerializer()
queryset = Menu.objects.all().select_related('parent')
include_resource_uri = False
fields = ['name']
def get_child_data(self, obj):
data = {
'id': obj.id,
'name': obj.name,
}
if not obj.is_leaf_node():
data['children'] = [self.get_child_data(child) \
for child in obj.get_children()]
return data
def get_list(self, request, **kwargs):
base_bundle = self.build_bundle(request=request)
objects = self.obj_get_list(bundle=base_bundle,
**self.remove_api_resource_names(kwargs))
sorted_objects = self.apply_sorting(objects, options=request.GET)
paginator = self._meta.paginator_class(
request.GET, sorted_objects,
resource_uri=self.get_resource_uri(), limit=self._meta.limit,
max_limit=self._meta.max_limit,
collection_name=self._meta.collection_name
)
to_be_serialized = paginator.page()
from mptt.templatetags.mptt_tags import cache_tree_children
objects = cache_tree_children(objects)
bundles = []
for obj in objects:
data = self.get_child_data(obj)
bundle = self.build_bundle(data=data, obj=obj, request=request)
bundles.append(self.full_dehydrate(bundle))
to_be_serialized[self._meta.collection_name] = bundles
to_be_serialized = self.alter_list_data_to_serialize(request,
to_be_serialized)
return self.create_response(request, to_be_serialized)
If you're not using pagination you can just take that part out. That's what I did.

how to work with modelform and multiwidget

Newbie to all this! i'm working on displaying phone field displayed as (xxx)xxx-xxxx on front end.below is my code. My question is 1. all fields are mandatory, for some reason,phone is not behaving as expected.Even if it is left blank its not complaining and 2.how can i test this widget's functionality
class USPhoneNumberWidget(forms.MultiWidget):
def __init__(self,attrs=None):
widgets = (forms.TextInput(attrs={'size':'3','maxlength':'3'}),forms.TextInput(attrs={'size':'3','maxlength':'3'}),forms.TextInput(attrs={'size':'3','maxlength':'4'}))
super(USPhoneNumberWidget,self).__init__(widgets,attrs=attrs)
def decompress(self, value):
if value:
val = value.split('-')
return [val[0],val[1],val[2]]
return [None,None,None]
def compress(self, data_list):
if data_list[0] and data_list[1] and data_list[2]:
ph1 = self.check_value(data_list[0])
ph2 = self.check_value(data_list[1])
ph3 = self.check_value(data_list[2])
return '%s''%s''%s' %(ph1,ph2,ph3)
else:
return None
def check_value(self,val):
try:
if val.isdigit():
return val
except:
raise forms.ValidationError('This Field has to be a number!')
def clean(self, value):
try:
value = re.sub('(\(|\)|\s+)','',smart_unicode(value))
m = phone_digits_re.search(value)
if m:
return u'%s%s%s' % (m.group(1),m.group(2),m.group(3))
except:
raise ValidationError('Phone Number is required.')
def value_from_datadict(self,data,files,name):
val_list = [widget.value_from_datadict(data,files,name+'_%s' %i) for i,widget in enumerate(self.widgets)]
try:
return val_list
except ValueError:
return ''
def format_output(self,rendered_widgets):
return '('+rendered_widgets[0]+')'+rendered_widgets[1]+'-'+rendered_widgets[2]
class CustomerForm(ModelForm):
phone = forms.CharField(required=True,widget=USPhoneNumberWidget())
class Meta:
model = Customer
fields = ('fname','lname','address1','address2','city','state','zipcode','phone')
In models blank and null are not true.
Any input it highly appreciated.Thanks
Here is the phone field:
phone = forms.CharField(label = 'Phone',widget=USPhoneNumberWidget()
class USPhoneNumberWidget(forms.MultiWidget):
"""
A widget that splits phone number into areacode/next3/last4 with textinput.
"""
def __init__(self,attrs=None):
widgets = (forms.TextInput(attrs={'size':'3','maxlength':'3'}),forms.TextInput(attrs={'size':'3','maxlength':'3'}),forms.TextInput(attrs={'size':'4','maxlength':'4'}))
super(USPhoneNumberWidget,self).__init__(widgets,attrs=attrs)
def decompress(self, value):
if value:
val = value
return val[:3],val[3:6],val[6:]
return None,None,None
def compress(self, data_list):
if data_list[0] and data_list[1] and data_list[2]:
return '%s''%s''%s' %(data_list[0],data_list[1],data_list[2])
else:
return None
def value_from_datadict(self,data,files,name):
val_list = [widget.value_from_datadict(data,files,name+'_%s' %i) for i,widget in enumerate(self.widgets)]
if val_list:
return '%s''%s''%s' %(val_list[0],val_list[1],val_list[2])
def format_output(self,rendered_widgets):
return '( '+rendered_widgets[0]+' )'+rendered_widgets[1]+' - '+rendered_widgets[2]
But depending on how you store the phone# in db 'return' line is to be changed. here I'm accepting it as (xxx)-xxx-xxxx format.In compress it receives ph_0(areacode),ph_1(next 3),ph_2(last4) in that order.but I'm storing it as xxxxxxxxxx.
Firebug helped me understand better about what return values should be. I'll update the answer when i come to know how testing could be done.