Commit 825429c1 authored by Tim Graham's avatar Tim Graham
Browse files

Moved foreign_object models.py into a module.

parent 5b5a2794
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
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',
]
+99 −0
Original line number Diff line number Diff line
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)
+0 −89
Original line number Diff line number Diff line
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)
+29 −0
Original line number Diff line number Diff line
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))
+0 −24
Original line number Diff line number Diff line
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))