diff --git a/tests/foreign_object/models/__init__.py b/tests/foreign_object/models/__init__.py new file mode 100644 index 0000000000..c7063ff387 --- /dev/null +++ b/tests/foreign_object/models/__init__.py @@ -0,0 +1,9 @@ +from .article import ( + Article, ArticleIdea, ArticleTag, ArticleTranslation, NewsArticle, +) +from .person import Country, Friendship, Group, Membership, Person + +__all__ = [ + 'Article', 'ArticleIdea', 'ArticleTag', 'ArticleTranslation', 'Country', + 'Friendship', 'Group', 'Membership', 'NewsArticle', 'Person', +] diff --git a/tests/foreign_object/models/article.py b/tests/foreign_object/models/article.py new file mode 100644 index 0000000000..f9e6f1fca4 --- /dev/null +++ b/tests/foreign_object/models/article.py @@ -0,0 +1,99 @@ +from django.db import models +from django.db.models.fields.related import \ + ReverseSingleRelatedObjectDescriptor +from django.utils.encoding import python_2_unicode_compatible +from django.utils.translation import get_language + + +class ArticleTranslationDescriptor(ReverseSingleRelatedObjectDescriptor): + """ + The set of articletranslation should not set any local fields. + """ + def __set__(self, instance, value): + if instance is None: + raise AttributeError("%s must be accessed via instance" % self.field.name) + setattr(instance, self.cache_name, value) + if value is not None and not self.field.remote_field.multiple: + setattr(value, self.field.related.get_cache_name(), instance) + + +class ColConstraint(object): + # Anything with as_sql() method works in get_extra_restriction(). + def __init__(self, alias, col, value): + self.alias, self.col, self.value = alias, col, value + + def as_sql(self, compiler, connection): + qn = compiler.quote_name_unless_alias + return '%s.%s = %%s' % (qn(self.alias), qn(self.col)), [self.value] + + +class ActiveTranslationField(models.ForeignObject): + """ + This field will allow querying and fetching the currently active translation + for Article from ArticleTranslation. + """ + requires_unique_target = False + + def get_extra_restriction(self, where_class, alias, related_alias): + return ColConstraint(alias, 'lang', get_language()) + + def get_extra_descriptor_filter(self, instance): + return {'lang': get_language()} + + def contribute_to_class(self, cls, name): + super(ActiveTranslationField, self).contribute_to_class(cls, name) + setattr(cls, self.name, ArticleTranslationDescriptor(self)) + + +@python_2_unicode_compatible +class Article(models.Model): + active_translation = ActiveTranslationField( + 'ArticleTranslation', + from_fields=['id'], + to_fields=['article'], + related_name='+', + on_delete=models.CASCADE, + null=True, + ) + pub_date = models.DateField() + + def __str__(self): + try: + return self.active_translation.title + except ArticleTranslation.DoesNotExist: + return '[No translation found]' + + +class NewsArticle(Article): + pass + + +class ArticleTranslation(models.Model): + article = models.ForeignKey(Article, models.CASCADE) + lang = models.CharField(max_length=2) + title = models.CharField(max_length=100) + body = models.TextField() + abstract = models.CharField(max_length=400, null=True) + + class Meta: + unique_together = ('article', 'lang') + ordering = ('active_translation__title',) + + +class ArticleTag(models.Model): + article = models.ForeignKey( + Article, + models.CASCADE, + related_name='tags', + related_query_name='tag', + ) + name = models.CharField(max_length=255) + + +class ArticleIdea(models.Model): + articles = models.ManyToManyField( + Article, + related_name='ideas', + related_query_name='idea_things', + ) + name = models.CharField(max_length=255) diff --git a/tests/foreign_object/models.py b/tests/foreign_object/models/person.py similarity index 51% rename from tests/foreign_object/models.py rename to tests/foreign_object/models/person.py index 21f8d2439a..eeb3da832e 100644 --- a/tests/foreign_object/models.py +++ b/tests/foreign_object/models/person.py @@ -1,10 +1,7 @@ import datetime from django.db import models -from django.db.models.fields.related import \ - ReverseSingleRelatedObjectDescriptor from django.utils.encoding import python_2_unicode_compatible -from django.utils.translation import get_language @python_2_unicode_compatible @@ -112,89 +109,3 @@ class Friendship(models.Model): related_name='to_friend', on_delete=models.CASCADE, ) - - -class ArticleTranslationDescriptor(ReverseSingleRelatedObjectDescriptor): - """ - The set of articletranslation should not set any local fields. - """ - def __set__(self, instance, value): - if instance is None: - raise AttributeError("%s must be accessed via instance" % self.field.name) - setattr(instance, self.cache_name, value) - if value is not None and not self.field.remote_field.multiple: - setattr(value, self.field.related.get_cache_name(), instance) - - -class ColConstraint(object): - # Anything with as_sql() method works in get_extra_restriction(). - def __init__(self, alias, col, value): - self.alias, self.col, self.value = alias, col, value - - def as_sql(self, compiler, connection): - qn = compiler.quote_name_unless_alias - return '%s.%s = %%s' % (qn(self.alias), qn(self.col)), [self.value] - - -class ActiveTranslationField(models.ForeignObject): - """ - This field will allow querying and fetching the currently active translation - for Article from ArticleTranslation. - """ - requires_unique_target = False - - def get_extra_restriction(self, where_class, alias, related_alias): - return ColConstraint(alias, 'lang', get_language()) - - def get_extra_descriptor_filter(self, instance): - return {'lang': get_language()} - - def contribute_to_class(self, cls, name): - super(ActiveTranslationField, self).contribute_to_class(cls, name) - setattr(cls, self.name, ArticleTranslationDescriptor(self)) - - -@python_2_unicode_compatible -class Article(models.Model): - active_translation = ActiveTranslationField( - 'ArticleTranslation', - from_fields=['id'], - to_fields=['article'], - related_name='+', - on_delete=models.CASCADE, - null=True, - ) - pub_date = models.DateField() - - def __str__(self): - try: - return self.active_translation.title - except ArticleTranslation.DoesNotExist: - return '[No translation found]' - - -class NewsArticle(Article): - pass - - -class ArticleTranslation(models.Model): - article = models.ForeignKey(Article, models.CASCADE) - lang = models.CharField(max_length=2) - title = models.CharField(max_length=100) - body = models.TextField() - abstract = models.CharField(max_length=400, null=True) - - class Meta: - unique_together = ('article', 'lang') - ordering = ('active_translation__title',) - - -class ArticleTag(models.Model): - article = models.ForeignKey(Article, models.CASCADE, related_name="tags", related_query_name="tag") - name = models.CharField(max_length=255) - - -class ArticleIdea(models.Model): - articles = models.ManyToManyField(Article, related_name="ideas", - related_query_name="idea_things") - name = models.CharField(max_length=255) diff --git a/tests/foreign_object/test_forms.py b/tests/foreign_object/test_forms.py new file mode 100644 index 0000000000..32a8fc3876 --- /dev/null +++ b/tests/foreign_object/test_forms.py @@ -0,0 +1,29 @@ +import datetime + +from django import forms +from django.test import TestCase + +from .models import Article + + +class FormsTests(TestCase): + # ForeignObjects should not have any form fields, currently the user needs + # to manually deal with the foreignobject relation. + class ArticleForm(forms.ModelForm): + class Meta: + model = Article + fields = '__all__' + + def test_foreign_object_form(self): + # A very crude test checking that the non-concrete fields do not get form fields. + form = FormsTests.ArticleForm() + self.assertIn('id_pub_date', form.as_table()) + self.assertNotIn('active_translation', form.as_table()) + form = FormsTests.ArticleForm(data={'pub_date': str(datetime.date.today())}) + self.assertTrue(form.is_valid()) + a = form.save() + self.assertEqual(a.pub_date, datetime.date.today()) + form = FormsTests.ArticleForm(instance=a, data={'pub_date': '2013-01-01'}) + a2 = form.save() + self.assertEqual(a.pk, a2.pk) + self.assertEqual(a2.pub_date, datetime.date(2013, 1, 1)) diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py index edb72ced8e..06e2551e4f 100644 --- a/tests/foreign_object/tests.py +++ b/tests/foreign_object/tests.py @@ -1,7 +1,6 @@ import datetime from operator import attrgetter -from django import forms from django.core.exceptions import FieldError from django.test import TestCase, skipUnlessDBFeature from django.utils import translation @@ -392,26 +391,3 @@ class MultiColumnFKTests(TestCase): """ See: https://code.djangoproject.com/ticket/21566 """ objs = [Person(name="abcd_%s" % i, person_country=self.usa) for i in range(0, 5)] Person.objects.bulk_create(objs, 10) - - -class FormsTests(TestCase): - # ForeignObjects should not have any form fields, currently the user needs - # to manually deal with the foreignobject relation. - class ArticleForm(forms.ModelForm): - class Meta: - model = Article - fields = '__all__' - - def test_foreign_object_form(self): - # A very crude test checking that the non-concrete fields do not get form fields. - form = FormsTests.ArticleForm() - self.assertIn('id_pub_date', form.as_table()) - self.assertNotIn('active_translation', form.as_table()) - form = FormsTests.ArticleForm(data={'pub_date': str(datetime.date.today())}) - self.assertTrue(form.is_valid()) - a = form.save() - self.assertEqual(a.pub_date, datetime.date.today()) - form = FormsTests.ArticleForm(instance=a, data={'pub_date': '2013-01-01'}) - a2 = form.save() - self.assertEqual(a.pk, a2.pk) - self.assertEqual(a2.pub_date, datetime.date(2013, 1, 1))