Permalink
Browse files

basics working

  • Loading branch information...
1 parent f26c4f4 commit 559794a0b49c7fb82fd3ff6bd2c3a0caf5f0134b @stefanfoulis committed Aug 31, 2011
View
22 LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2011 Stefan Foulis and contributors.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
View
@@ -0,0 +1,2 @@
+include LICENSE
+include README.rst
@@ -0,0 +1,2 @@
+#-*- coding: utf-8 -*-
+__version__ = (0, 2, 0, 'dev', 0)
@@ -0,0 +1,20 @@
+#-*- coding: utf-8 -*-
+from django.utils.translation import ugettext_lazy as _
+from django.forms.fields import CharField
+from django.core.exceptions import ValidationError
+from phonenumber_field.validators import validate_international_phonenumber
+from phonenumbers.phonenumberutil import NumberParseException
+from phonenumber_field.phonenumber import PhoneNumber, to_python
+
+
+class PhoneNumberField(CharField):
+ default_error_messages = {
+ 'invalid': _(u'Enter a valid phone number ("e.g +411234567").'),
+ }
+ default_validators = [validate_international_phonenumber]
+
+ def to_python(self, value):
+ phone_number = to_python(value)
+ if phone_number and not phone_number.is_valid():
+ raise ValidationError(self.error_messages['invalid'])
+ return phone_number
@@ -0,0 +1,83 @@
+#-*- coding: utf-8 -*-
+from django.core import validators
+from django.db import models
+from django.utils.translation import ugettext_lazy as _
+from phonenumber_field.validators import validate_international_phonenumber
+from phonenumber_field import formfields
+from phonenumber_field.phonenumber import PhoneNumber, to_python
+from phonenumbers.phonenumberutil import NumberParseException
+import phonenumbers
+
+
+class PhoneNumberDescriptor(object):
+ """
+ The descriptor for the phone number attribute on the model instance. Returns a PhoneNumber when accessed so you can
+ do stuff like::
+
+ >>> instance.phone_number.as_international
+
+ Assigns a phone number object on assignment so you can do::
+
+ >>> instance.phone_number = PhoneNumber(...)
+ or
+ >>> instance.phone_number = '+414204242'
+ """
+
+ def __init__(self, field):
+ self.field = field
+
+ def __get__(self, instance=None, owner=None):
+ if instance is None:
+ raise AttributeError(
+ "The '%s' attribute can only be accessed from %s instances."
+ % (self.field.name, owner.__name__))
+ return instance.__dict__[self.field.name]
+
+ def __set__(self, instance, value):
+ instance.__dict__[self.field.name] = to_python(value)
+
+
+class PhoneNumberField(models.Field):
+ attr_class = PhoneNumber
+ descriptor_class = PhoneNumberDescriptor
+ default_validators = [validate_international_phonenumber]
+
+ description = _("Phone number")
+
+ def __init__(self, *args, **kwargs):
+ kwargs['max_length'] = kwargs.get('max_length', 128)
+ super(PhoneNumberField, self).__init__(*args, **kwargs)
+ self.validators.append(validators.MaxLengthValidator(self.max_length))
+
+ def get_internal_type(self):
+ return "CharField"
+
+ def get_prep_value(self, value):
+ "Returns field's value prepared for saving into a database."
+ # Need to convert File objects provided via a form to unicode for database insertion
+ if value is None:
+ return None
+ return value.as_e164
+
+ def contribute_to_class(self, cls, name):
+ super(PhoneNumberField, self).contribute_to_class(cls, name)
+ setattr(cls, self.name, self.descriptor_class(self))
+
+ def formfield(self, **kwargs):
+ defaults = {
+ 'form_class': formfields.PhoneNumberField,
+ }
+ defaults.update(kwargs)
+ return super(PhoneNumberField, self).formfield(**defaults)
+
+try:
+ from south.modelsinspector import add_introspection_rules
+ add_introspection_rules([
+ (
+ [PhoneNumberField],
+ [],
+ {},
+ ),
+ ], ["^phonenumber_field\.modelfields\.PhoneNumberField"])
+except ImportError:
+ pass
@@ -0,0 +1 @@
+#-*- coding: utf-8 -*-
@@ -0,0 +1,76 @@
+#-*- coding: utf-8 -*-
+import phonenumbers
+from django.core import validators
+from phonenumbers.phonenumberutil import NumberParseException
+
+
+class PhoneNumber(phonenumbers.phonenumber.PhoneNumber):
+ """
+ A extended version of phonenumbers.phonenumber.PhoneNumber that provides some neat and more pythonic, easy
+ to access methods. This makes using a PhoneNumber instance much easier, especially in templates and such.
+ """
+ @classmethod
+ def from_string(cls, phone_number):
+ phone_number_obj = cls()
+ phonenumbers.parse(number=phone_number, region=None, keep_raw_input=True, numobj=phone_number_obj)
+ return phone_number_obj
+
+ def __str__(self):
+ if self.is_valid():
+ return self.as_e164
+ return self.raw_input
+
+ def __unicode__(self):
+ return unicode(self.__str__())
+
+ def original_unicode(self):
+ return super(PhoneNumber, self).__unicode__()
+
+ def is_valid(self):
+ """
+ checks wether the number supplied is actually valid
+ """
+ return bool(self.country_code and self.national_number)
+
+ def format_as(self, format):
+ if self.is_valid():
+ return phonenumbers.format_number(self, format)
+ else:
+ return self.raw_input
+
+ @property
+ def as_international(self):
+ return phonenumbers.format_number(self, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
+
+ @property
+ def as_e164(self):
+ return phonenumbers.format_number(self, phonenumbers.PhoneNumberFormat.E164)
+
+ @property
+ def as_national(self):
+ return phonenumbers.format_number(self, phonenumbers.PhoneNumberFormat.NATIONAL)
+
+ @property
+ def as_rfc3966(self):
+ return phonenumbers.format_number(self, phonenumbers.PhoneNumberFormat.RFC3966)
+
+ def __len__(self):
+ return len(self.__unicode__())
+
+
+def to_python(value):
+ if value in validators.EMPTY_VALUES: # None or ''
+ phone_number = None
+ elif value and isinstance(value, basestring):
+ try:
+ phone_number = PhoneNumber.from_string(phone_number=value)
+ except NumberParseException, e:
+ # the string provided is not a valid PhoneNumber.
+ phone_number = PhoneNumber(raw_input=value)
+ elif isinstance(value, phonenumbers.phonenumber.PhoneNumber) and \
+ not isinstance(value, PhoneNumber):
+ phone_number = self.field.attr_class()
+ phone_number.merge_from(value)
+ elif isinstance(value, PhoneNumber):
+ phone_number = value
+ return phone_number
View
@@ -0,0 +1,49 @@
+#-*- coding: utf-8 -*-
+from django.test.testcases import TestCase
+from django.db import models
+from phonenumber_field.modelfields import PhoneNumberField
+from phonenumber_field.phonenumber import PhoneNumber
+import phonenumber_field
+
+
+###############
+# Test Models #
+###############
+
+
+class MandatoryPhoneNumber(models.Model):
+ phone_number = PhoneNumberField()
+
+
+class OptionalPhoneNumber(models.Model):
+ phone_number = PhoneNumberField(blank=True, default='')
+
+
+class NullablePhoneNumber(models.Model):
+ phone_number = PhoneNumberField(blank=True, null=True)
+
+
+##############
+# Test Cases #
+##############
+
+
+class PhoneNumberFieldTestCase(TestCase):
+ test_number_1 = '+414204242'
+
+ def create_fixtures(self):
+ self.mandatory = MandatoryPhoneNumber(phone_number=self.test_number_1)
+ self.mandatory.save()
+
+ def test_can_create_model_with_string(self):
+ obj = MandatoryPhoneNumber.objects.create(phone_number=self.test_number_1)
+ # refetch it
+ obj2 = MandatoryPhoneNumber.objects.get(pk=obj.pk)
+ self.assertEqual(obj.phone_number, obj2.phone_number)
+
+ def test_can_assign_string_phone_number(self):
+ opt_phone = OptionalPhoneNumber()
+ opt_phone.phone_number = self.test_number_1
+
+ self.assertEqual(type(opt_phone.phone_number), PhoneNumber)
+ self.assertEqual(opt_phone.phone_number.as_e164, self.test_number_1)
@@ -0,0 +1,12 @@
+#-*- coding: utf-8 -*-
+from django.core.exceptions import ValidationError
+from django.utils.translation import ugettext_lazy as _
+from phonenumbers import parse
+from phonenumbers.phonenumberutil import NumberParseException
+from phonenumber_field.phonenumber import PhoneNumber, to_python
+
+def validate_international_phonenumber(value):
+ phone_number = to_python(value)
+ if phone_number and not phone_number.is_valid():
+ raise ValidationError(_(u'Enter a valid phone number ("e.g +411234567").'))
+
@@ -0,0 +1 @@
+#-*- coding: utf-8 -*-
View
@@ -0,0 +1,33 @@
+from setuptools import setup, find_packages
+
+setup(
+ name="django-phonenumber-field",
+ version = ":versiontools:phonenumber_field:",
+ url='http://github.com/stefanfoulis/django-phonenumber-field',
+ license='BSD',
+ platforms=['OS Independent'],
+ description="An international phone number field for django models.",
+ setup_requires = [
+ 'versiontools >= 1.4',
+ ],
+ install_requires = [
+ 'phonenumbers',
+ ],
+ long_description=open('README.rst').read(),
+ author='Stefan Foulis',
+ author_email='stefan.foulis@gmail.com',
+ maintainer='Stefan Foulis',
+ maintainer_email='stefan.foulis@gmail.com',
+ packages=find_packages(),
+ include_package_data=True,
+ zip_safe=False,
+ classifiers=[
+ 'Development Status :: 3 - Alpha',
+ 'Framework :: Django',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: BSD License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: WWW/HTTP',
+ ]
+)

0 comments on commit 559794a

Please sign in to comment.