Moved foreign_object models.py into a module.

This commit is contained in:
Tim Graham 2015-08-12 11:10:52 -04:00
parent 5b5a27942b
commit 825429c1f7
5 changed files with 137 additions and 113 deletions

View File

@ -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',
]

View File

@ -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)

View File

@ -1,10 +1,7 @@
import datetime import datetime
from django.db import models 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.encoding import python_2_unicode_compatible
from django.utils.translation import get_language
@python_2_unicode_compatible @python_2_unicode_compatible
@ -112,89 +109,3 @@ class Friendship(models.Model):
related_name='to_friend', related_name='to_friend',
on_delete=models.CASCADE, 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)

View File

@ -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))

View File

@ -1,7 +1,6 @@
import datetime import datetime
from operator import attrgetter from operator import attrgetter
from django import forms
from django.core.exceptions import FieldError from django.core.exceptions import FieldError
from django.test import TestCase, skipUnlessDBFeature from django.test import TestCase, skipUnlessDBFeature
from django.utils import translation from django.utils import translation
@ -392,26 +391,3 @@ class MultiColumnFKTests(TestCase):
""" See: https://code.djangoproject.com/ticket/21566 """ """ See: https://code.djangoproject.com/ticket/21566 """
objs = [Person(name="abcd_%s" % i, person_country=self.usa) for i in range(0, 5)] objs = [Person(name="abcd_%s" % i, person_country=self.usa) for i in range(0, 5)]
Person.objects.bulk_create(objs, 10) 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))