def test_valid_db_id(self): """ Make sure ID values are properly stored and serialized. """ reg = UrlTypeRegistry() urltype = reg.register(PageModel) page = PageModel.objects.create(slug='foo') v = AnyUrlValue(urltype.prefix, page.id, reg) # Database state self.assertTrue(page.id) self.assertEqual(urltype.prefix, 'any_urlfield.pagemodel') # app_label.modelname self.assertEqual(v.type_prefix, urltype.prefix) self.assertEqual(v.type_value, page.id) self.assertEqual(v.to_db_value(), 'any_urlfield.pagemodel://1') # Frontend self.assertEqual( unicode(v), "/foo/") # fetches model and returns get_absolute_url() # Programmer API's self.assertIs(v.get_model(), PageModel) self.assertEqual(v.get_object(), page) self.assertTrue(v.exists())
def test_pickle_registry(self): reg = UrlTypeRegistry() urltype = reg.register(PageModel) page = PageModel.objects.create(slug='foo') # See if custom registries can be pickled v1 = AnyUrlValue(urltype.prefix, page.id, reg) out = StringIO() pickle.dump(v1, out) # Unpickle. out.seek(0) v2 = pickle.load(out) self.assertEqual(v1, v2) # Note that __eq__ is overridden for AnyUrlValue!
def test_registry(self): """ Test the basic registry setup. """ reg = UrlTypeRegistry() self.assertIsNotNone(reg['http']) self.assertIsNotNone(reg['https'])
def test_from_dbvalue_ftps(self): reg = UrlTypeRegistry() v = AnyUrlValue.from_db_value("ftps://www.example.com/", reg) self.assertEqual(v.type_prefix, 'http') # http is the constant for external URL types self.assertEqual(v.type_value, "ftps://www.example.com/") self.assertEqual(unicode(v), "ftps://www.example.com/")
def test_from_dbvalue(self): reg = UrlTypeRegistry() v = AnyUrlValue.from_db_value("http://www.example.com/", reg) self.assertEqual(v.type_prefix, 'http') self.assertEqual(v.type_value, "http://www.example.com/") self.assertEqual(unicode(v), "http://www.example.com/")
def test_invalid_db_id(self): reg = UrlTypeRegistry() urltype = reg.register(PageModel) v = AnyUrlValue(urltype.prefix, 999999, reg) # Database state self.assertEqual(v.type_value, 999999) self.assertEqual(v.to_db_value(), 'any_urlfield.pagemodel://999999') # Frontend self.assertEqual(unicode(v), "#DoesNotExist") # Avoids frontend errors # Programmer API's self.assertIs(v.get_model(), PageModel) self.assertRaises(PageModel.DoesNotExist, lambda: v.get_object()) self.assertFalse(v.exists())
def test_render_widget(self): """ See if widget rendering is consistent between Django versions """ reg = UrlTypeRegistry() reg.register(PageModel) class ExampleForm(forms.Form): field = any_urlfield.forms.AnyUrlField(url_type_registry=reg) def _normalize_html(html): # Avoid some differences in Django versions html = html.replace(' checked="checked"', '') html = html.replace(' checked', '') html = html.replace(' selected="selected"', ' selected') html = html.replace(' required', '') return html html = Template('{{ form.field }}').render(Context({'form': ExampleForm()})) self.assertHTMLEqual(_normalize_html(html), _normalize_html(""" <div class="any-urlfield-wrapper related-widget-wrapper"> <ul class="any_urlfield-url_type radiolist inline" id="id_field_0"> <li> <label for="id_field_0_0"> <input type="radio" name="field_0" value="http" class="any_urlfield-url_type radiolist inline" id="id_field_0_0"/> External URL</label> </li> <li> <label for="id_field_0_1"> <input type="radio" name="field_0" value="any_urlfield.pagemodel" class="any_urlfield-url_type radiolist inline" id="id_field_0_1"/> page model</label> </li> </ul> <p class="any_urlfield-url-http" style="clear:left"> <input type="text" name="field_1" class="vTextField" id="id_field_1"/> </p> <p class="any_urlfield-url-any_urlfieldpagemodel" style="clear:left"> <select name="field_2" id="id_field_2"> <option value="" selected>---------</option> </select> </p> </div> """))
def test_invalid_db_id(self): reg = UrlTypeRegistry() urltype = reg.register(PageModel) v = AnyUrlValue(urltype.prefix, 999999, reg) # Database state self.assertEqual(v.type_value, 999999) self.assertEqual(v.to_db_value(), 'any_urlfield.pagemodel://999999') # Frontend from any_urlfield.models.values import logger logger.warning("NOTE: The following statement will cause a log to output") self.assertEqual(unicode(v), "#DoesNotExist") # Avoids frontend errors # Programmer API's self.assertIs(v.get_model(), PageModel) self.assertRaises(PageModel.DoesNotExist, lambda: v.get_object()) self.assertFalse(v.exists())
def test_from_db_value_ftps(self): """ Make sure other URLs are properly serialized. """ reg = UrlTypeRegistry() v = AnyUrlValue.from_db_value("ftps://www.example.com/", reg) self.assertEqual(v.type_prefix, 'http') # http is the constant for external URL types self.assertEqual(v.type_value, "ftps://www.example.com/") self.assertEqual(unicode(v), "ftps://www.example.com/")
def test_from_db_value_mailto(self): """ Test constructing the value from ID. """ reg = UrlTypeRegistry() v = AnyUrlValue.from_db_value("mailto://[email protected]", reg) self.assertEqual(v.type_prefix, 'http') # http is the constant for external URL types self.assertEqual(v.type_value, "mailto://[email protected]") self.assertEqual(unicode(v), "mailto://[email protected]")
def test_pickle_registry(self): """ Test pickle when the ``AnyUrlValue`` has a custom registry. """ reg = UrlTypeRegistry() urltype = reg.register(PageModel) page = PageModel.objects.create(slug='foo') # See if custom registries can be pickled v1 = AnyUrlValue(urltype.prefix, page.id, reg) out = StringIO() pickle.dump(v1, out) # Unpickle. out.seek(0) v2 = pickle.load(out) self.assertEqual(v1, v2) # Note that __eq__ is overridden for AnyUrlValue! # See that the object still works properly self.assertEqual(v2.get_object(), page) self.assertEqual(str(v2), '/foo/')
def test_from_db_value_id(self): reg = UrlTypeRegistry() urltype = reg.register(PageModel) page = PageModel.objects.create(slug='foo') v = AnyUrlValue(urltype.prefix, page.id, reg) # Database state self.assertTrue(page.id) self.assertEqual(urltype.prefix, 'any_urlfield.pagemodel') # app_label.modelname self.assertEqual(v.type_prefix, urltype.prefix) self.assertEqual(v.type_value, page.id) self.assertEqual(v.to_db_value(), 'any_urlfield.pagemodel://1') # Frontend self.assertEqual(unicode(v), "/foo/") # fetches model and returns get_absolute_url() # Programmer API's self.assertIs(v.get_model(), PageModel) self.assertEqual(v.get_object(), page) self.assertTrue(v.exists())
def test_form_clean(self): """ Basic test of form validation. """ reg = UrlTypeRegistry() reg.register(PageModel) class ExampleForm(forms.Form): url = any_urlfield.forms.AnyUrlField(url_type_registry=reg) # Test 1: basic URL form = ExampleForm(data={ 'url_0': 'http', 'url_1': 'http://examle.org/', }) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['url'], AnyUrlValue.from_db_value('http://examle.org/')) # Test 2: ID field x = PageModel.objects.create(slug='foo') form = ExampleForm(data={ 'url_0': 'any_urlfield.pagemodel', 'url_1': '', 'url_2': str(x.pk), }) self.assertTrue(form.is_valid()) self.assertEqual(form.cleaned_data['url'].to_db_value(), 'any_urlfield.pagemodel://{}'.format(x.pk)) self.assertEqual(form.cleaned_data['url'].get_object(), x) expected = AnyUrlValue.from_db_value('any_urlfield.pagemodel://{}'.format(x.pk), url_type_registry=reg) self.assertEqual(form.cleaned_data['url'], expected) # Test 3: invalid ID x = PageModel.objects.create(slug='foo') form = ExampleForm(data={ 'url_0': 'any_urlfield.pagemodel', 'url_1': '', 'url_2': '-1', }) self.assertFalse(form.is_valid())
def test_resolve_values(self): """ Make sure ID values are properly stored and serialized. """ reg = UrlTypeRegistry() urltype = reg.register(PageModel) page = PageModel.objects.create(slug='foo') valid = AnyUrlValue(urltype.prefix, page.id, reg) invalid = AnyUrlValue(urltype.prefix, 999999, reg) with self.assertNumQueries(1): AnyUrlValue.resolve_values([valid, invalid]) self.assertTrue(valid._resolved_objects) self.assertTrue(invalid._resolved_objects) with self.assertNumQueries(0): self.assertEqual(valid.get_object(), page) self.assertTrue(valid.exists()) self.assertRaises(PageModel.DoesNotExist, lambda: invalid.get_object()) self.assertFalse(invalid.exists())
def test_has_changed_empty_form(self): """ Test that empty placeholder forms are not considered filled in """ reg = UrlTypeRegistry() reg.register(PageModel) class ExampleForm(forms.Form): url = any_urlfield.forms.AnyUrlField(url_type_registry=reg) if django.VERSION >= (1, 10): empty_kwargs = dict(empty_permitted=True, use_required_attribute=False) else: empty_kwargs = dict(empty_permitted=True) form = ExampleForm(**empty_kwargs) data = get_input_values(form.as_p()) assert form.initial == {} assert data == {'url_0': 'http', 'url_1': ''} # Submit the values unchanged form = ExampleForm(data=data, **empty_kwargs) self.assertFalse(form.has_changed(), "form marked as changed!")
class ICEkitURLField(AnyUrlField): # start with an empty UrlTypeRegistry to undo Fluent_Page's # inappropriate-for-us restriction to only-published Pages. _static_registry = UrlTypeRegistry() @classmethod def register_model_once(cls, ModelClass, **kwargs): """ Tweaked version of `AnyUrlField.register_model` that only registers the given model after checking that it is not already registered. """ if cls._static_registry.get_for_model(ModelClass) is None: logger.warn("Model is already registered with {0}: '{1}'" .format(cls, ModelClass)) else: cls.register_model.register(ModelClass, **kwargs)
def test_registry(self): reg = UrlTypeRegistry() self.assertIsNotNone(reg['http']) self.assertIsNotNone(reg['https'])
class AnyUrlField(six.with_metaclass(models.SubfieldBase, models.CharField)): """ A CharField that can either refer to a CMS page ID, or external URL. .. figure:: /images/anyurlfield1.* :width: 363px :height: 74px :alt: AnyUrlField, with external URL input. .. figure:: /images/anyurlfield2.* :width: 290px :height: 76px :alt: AnyUrlField, with internal page input. By default, the ``AnyUrlField`` only supports linking to external pages. To add support for your own models (e.g. an ``Article`` model), include the following code in :file:`models.py`: .. code-block:: python from any_urlfield.models import AnyUrlField AnyUrlField.register_model(Article) Now, the ``AnyUrlField`` offers users a dropdown field to directly select an article. By default, it uses a :class:`django.forms.ModelChoiceField` field with a :class:`django.forms.Select` widget to render the field. This can be customized using the ``form_field`` and ``widget`` parameters: .. code-block:: python from any_urlfield.models import AnyUrlField from any_urlfield.forms import SimpleRawIdWidget AnyUrlField.register_model(Article, widget=SimpleRawIdWidget(Article)) Now, the ``Article`` model will be displayed as raw input field with a browse button. """ _static_registry = UrlTypeRegistry( ) # Also accessed by AnyUrlValue as internal field. def __init__(self, *args, **kwargs): if 'max_length' not in kwargs: kwargs['max_length'] = 300 super(AnyUrlField, self).__init__(*args, **kwargs) @classmethod def register_model(cls, ModelClass, form_field=None, widget=None, title=None, prefix=None): """ Register a model to use in the URL field. This function needs to be called once for every model that should be selectable in the URL field. :param ModelClass: The model to register. :param form_field: The form field class used to render the field. This can be a lambda for lazy evaluation. :param widget: The widget class, can be used instead of the form field. :param title: The title of the model, by default it uses the models ``verbose_name``. :param prefix: A custom prefix for the model in the serialized database format. By default it uses "appname.modelname". """ cls._static_registry.register(ModelClass, form_field, widget, title, prefix) def formfield(self, **kwargs): # Associate formfield. # Import locally to avoid circular references. from any_urlfield.forms.fields import AnyUrlField as AnyUrlFormField kwargs['form_class'] = AnyUrlFormField kwargs['url_type_registry'] = self._static_registry if 'widget' in kwargs: del kwargs['widget'] return super(AnyUrlField, self).formfield(**kwargs) def to_python(self, value): if isinstance(value, AnyUrlValue): return value # Convert the string value if value is None: return None return AnyUrlValue.from_db_value(value, self._static_registry) def get_prep_value(self, value): if isinstance(value, six.string_types): # Happens with south migration return value elif value is None: return None if self.null else '' else: # Convert back to string return value.to_db_value() def value_to_string(self, obj): # For dumpdata value = self._get_val_from_obj(obj) return self.get_prep_value(value) def validate(self, value, model_instance): # Final validation of the field, before storing in the DB. super(AnyUrlField, self).validate(value, model_instance) if value: if value.type_prefix == 'http': validate_url = URLValidator() validate_url(value.type_value) elif value.type_value: if not value.exists(): raise ValidationError( self.error_messages['invalid_choice'] % value.type_value)
class ICEkitURLField(AnyUrlField): # start with an empty UrlTypeRegistry to undo Fluent_Page's # inappropriate-for-us restriction to only-published Pages. _static_registry = UrlTypeRegistry()
class AnyUrlField(models.CharField): """ A CharField that can either refer to a CMS page ID, or external URL. .. figure:: /images/anyurlfield1.* :width: 363px :height: 74px :alt: AnyUrlField, with external URL input. .. figure:: /images/anyurlfield2.* :width: 290px :height: 76px :alt: AnyUrlField, with internal page input. By default, the ``AnyUrlField`` only supports linking to external pages. To add support for your own models (e.g. an ``Article`` model), include the following code in :file:`models.py`: .. code-block:: python from any_urlfield.models import AnyUrlField AnyUrlField.register_model(Article) Now, the ``AnyUrlField`` offers users a dropdown field to directly select an article. By default, it uses a :class:`django.forms.ModelChoiceField` field with a :class:`django.forms.Select` widget to render the field. This can be customized using the ``form_field`` and ``widget`` parameters: .. code-block:: python from any_urlfield.models import AnyUrlField from any_urlfield.forms import SimpleRawIdWidget AnyUrlField.register_model(Article, widget=SimpleRawIdWidget(Article)) Now, the ``Article`` model will be displayed as raw input field with a browse button. """ _static_registry = UrlTypeRegistry( ) # Also accessed by AnyUrlValue as internal field. def __init__(self, *args, **kwargs): if 'max_length' not in kwargs: kwargs['max_length'] = 300 super(AnyUrlField, self).__init__(*args, **kwargs) @classmethod def register_model(cls, ModelClass, form_field=None, widget=None, title=None, prefix=None): """ Register a model to use in the URL field. This function needs to be called once for every model that should be selectable in the URL field. :param ModelClass: The model to register. :param form_field: The form field class used to render the field. This can be a lambda for lazy evaluation. :param widget: The widget class, can be used instead of the form field. :param title: The title of the model, by default it uses the models ``verbose_name``. :param prefix: A custom prefix for the model in the serialized database format. By default it uses "appname.modelname". """ cls._static_registry.register(ModelClass, form_field, widget, title, prefix) def formfield(self, **kwargs): # Associate formfield. # Import locally to avoid circular references. from any_urlfield.forms.fields import AnyUrlField as AnyUrlFormField kwargs['form_class'] = AnyUrlFormField kwargs['url_type_registry'] = self._static_registry if 'widget' in kwargs: del kwargs['widget'] return super(AnyUrlField, self).formfield(**kwargs) def from_db_value(self, value, expression, connection, context=None): # This method is used to cast DB values to python values. # The call to to_python() is not used anymore. if value is None: return None return AnyUrlValue.from_db_value(value, self._static_registry) def to_python(self, value): if isinstance(value, AnyUrlValue): return value # Convert the string value if value is None: return None return AnyUrlValue.from_db_value(value, self._static_registry) def get_prep_value(self, value): if isinstance(value, six.string_types): # Happens with south migration return value elif value is None: return None if self.null else '' else: # Convert back to string return value.to_db_value() def pre_save(self, model_instance, add): # Make sure that the SQL compiler in doesn't get an AnyUrlValue, # but a regular 'str' object it can write to the database. value = super(AnyUrlField, self).pre_save(model_instance, add) if not value: return None else: return value.to_db_value() def value_to_string(self, obj): # For dumpdata and serialization value = self.value_from_object(obj) return self.get_prep_value(value) def validate(self, value, model_instance): # Final validation of the field, before storing in the DB. super(AnyUrlField, self).validate(value, model_instance) if value: if value.type_prefix == 'http': validate_url = ExtendedURLValidator() validate_url(value.type_value) elif value.type_value: if not value.exists(): raise ValidationError( self.error_messages['invalid_choice'] % value.type_value) @classmethod def resolve_objects(cls, objects, skip_cached_urls=False): """ Make sure all AnyUrlValue objects from a set of objects is resolved in bulk. This avoids making a query per item. :param objects: A list or queryset of models. :param skip_cached_urls: Whether to avoid prefetching data that has it's URL cached. """ # Allow the queryset or list to consist of multiple models. # This supports querysets from django-polymorphic too. queryset = list(objects) any_url_values = [] for obj in queryset: model = obj.__class__ for field in _any_url_fields_by_model[model]: any_url_value = getattr(obj, field) if any_url_value and any_url_value.url_type.has_id_value: any_url_values.append(any_url_value) AnyUrlValue.resolve_values(any_url_values, skip_cached_urls=skip_cached_urls)