Skip to content

Commit a24e2b9

Browse files
committed
Add factory.django.FileField (See FactoryBoy#52)
1 parent c490870 commit a24e2b9

File tree

8 files changed

+215
-1
lines changed

8 files changed

+215
-1
lines changed

MANIFEST.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ include docs/Makefile
33
recursive-include docs *.py *.rst
44
include docs/_static/.keep_dir
55
prune docs/_build
6-
recursive-include tests *.py
6+
recursive-include tests *.py *.data

Makefile

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ default:
1313

1414
clean:
1515
find . -type f -name '*.pyc' -delete
16+
find . -type f -path '*/__pycache__/*' -delete
17+
find . -type d -empty -delete
18+
@rm -rf tmp_test/
1619

1720

1821
test:

factory/django.py

+66
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,21 @@
2121
# THE SOFTWARE.
2222

2323

24+
from __future__ import absolute_import
2425
from __future__ import unicode_literals
2526

27+
import os
2628

2729
"""factory_boy extensions for use with the Django framework."""
2830

31+
try:
32+
from django.core import files as django_files
33+
except ImportError as e: # pragma: no cover
34+
django_files = None
35+
import_failure = e
2936

3037
from . import base
38+
from . import declarations
3139

3240

3341
class DjangoModelFactory(base.Factory):
@@ -100,3 +108,61 @@ def _after_postgeneration(cls, obj, create, results=None):
100108
obj.save()
101109

102110

111+
class FileField(declarations.PostGenerationDeclaration):
112+
"""Helper to fill in django.db.models.FileField from a Factory."""
113+
114+
def __init__(self, *args, **kwargs):
115+
if django_files is None: # pragma: no cover
116+
raise import_failure
117+
super(FileField, self).__init__(*args, **kwargs)
118+
119+
def _make_content(self, extraction_context):
120+
path = ''
121+
params = extraction_context.extra
122+
123+
if params.get('from_path') and params.get('from_file'):
124+
raise ValueError(
125+
"At most one argument from 'from_file' and 'from_path' should "
126+
"be non-empty when calling factory.django.FileField."
127+
)
128+
129+
if extraction_context.did_extract:
130+
# Should be a django.core.files.File
131+
content = extraction_context.value
132+
path = content.name
133+
134+
elif params.get('from_path'):
135+
path = params['from_path']
136+
f = open(path, 'rb')
137+
content = django_files.File(f, name=path)
138+
139+
elif params.get('from_file'):
140+
f = params['from_file']
141+
content = django_files.File(f)
142+
path = content.name
143+
144+
else:
145+
data = params.get('data', '')
146+
content = django_files.base.ContentFile(data)
147+
148+
if path:
149+
default_filename = os.path.basename(path)
150+
else:
151+
default_filename = 'example.dat'
152+
153+
filename = params.get('filename', default_filename)
154+
return filename, content
155+
156+
def call(self, obj, create, extraction_context):
157+
"""Fill in the field."""
158+
if extraction_context.did_extract and extraction_context.value is None:
159+
# User passed an empty value, don't fill
160+
return
161+
162+
filename, content = self._make_content(extraction_context)
163+
field_file = getattr(obj, extraction_context.for_field)
164+
try:
165+
field_file.save(filename, content, save=create)
166+
finally:
167+
content.file.close()
168+
return field_file

tests/djapp/models.py

+8
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@
2222

2323
"""Helpers for testing django apps."""
2424

25+
import os.path
2526

27+
from django.conf import settings
2628
from django.db import models
2729

2830
class StandardModel(models.Model):
@@ -34,3 +36,9 @@ class NonIntegerPk(models.Model):
3436
bar = models.CharField(max_length=20, blank=True)
3537

3638

39+
WITHFILE_UPLOAD_TO = 'django'
40+
WITHFILE_UPLOAD_DIR = os.path.join(settings.MEDIA_ROOT, WITHFILE_UPLOAD_TO)
41+
42+
class WithFile(models.Model):
43+
afile = models.FileField(upload_to=WITHFILE_UPLOAD_TO)
44+

tests/djapp/settings.py

+10
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@
2020
# THE SOFTWARE.
2121
"""Settings for factory_boy/Django tests."""
2222

23+
import os
24+
25+
FACTORY_ROOT = os.path.join(
26+
os.path.abspath(os.path.dirname(__file__)), # /path/to/fboy/tests/djapp/
27+
os.pardir, # /path/to/fboy/tests/
28+
os.pardir, # /path/to/fboy
29+
)
30+
31+
MEDIA_ROOT = os.path.join(FACTORY_ROOT, 'tmp_test')
32+
2333
DATABASES = {
2434
'default': {
2535
'ENGINE': 'django.db.backends.sqlite3',

tests/test_django.py

+100
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@
3333

3434

3535
from .compat import is_python2, unittest
36+
from . import testdata
3637
from . import tools
3738

3839

3940
if django is not None:
4041
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.djapp.settings')
4142

4243
from django import test as django_test
44+
from django.db import models as django_models
4345
from django.test import simple as django_test_simple
4446
from django.test import utils as django_test_utils
4547
from .djapp import models
@@ -91,6 +93,12 @@ class NonIntegerPkFactory(factory.django.DjangoModelFactory):
9193
bar = ''
9294

9395

96+
class WithFileFactory(factory.django.DjangoModelFactory):
97+
FACTORY_FOR = models.WithFile
98+
99+
afile = factory.django.FileField()
100+
101+
94102
@unittest.skipIf(django is None, "Django not installed.")
95103
class DjangoPkSequenceTestCase(django_test.TestCase):
96104
def setUp(self):
@@ -163,3 +171,95 @@ def test_force_pk(self):
163171
nonint2 = NonIntegerPkFactory.create()
164172
self.assertEqual('foo1', nonint2.foo)
165173
self.assertEqual('foo1', nonint2.pk)
174+
175+
176+
@unittest.skipIf(django is None, "Django not installed.")
177+
class DjangoFileFieldTestCase(unittest.TestCase):
178+
179+
def tearDown(self):
180+
super(DjangoFileFieldTestCase, self).tearDown()
181+
for path in os.listdir(models.WITHFILE_UPLOAD_DIR):
182+
# Remove temporary files written during tests.
183+
os.unlink(os.path.join(models.WITHFILE_UPLOAD_DIR, path))
184+
185+
def test_default_build(self):
186+
o = WithFileFactory.build()
187+
self.assertIsNone(o.pk)
188+
self.assertEqual('', o.afile.read())
189+
self.assertEqual('django/example.dat', o.afile.name)
190+
191+
def test_default_create(self):
192+
o = WithFileFactory.create()
193+
self.assertIsNotNone(o.pk)
194+
self.assertEqual('', o.afile.read())
195+
self.assertEqual('django/example.dat', o.afile.name)
196+
197+
def test_with_content(self):
198+
o = WithFileFactory.build(afile__data='foo')
199+
self.assertIsNone(o.pk)
200+
self.assertEqual('foo', o.afile.read())
201+
self.assertEqual('django/example.dat', o.afile.name)
202+
203+
def test_with_file(self):
204+
with open(testdata.TESTFILE_PATH, 'rb') as f:
205+
o = WithFileFactory.build(afile__from_file=f)
206+
self.assertIsNone(o.pk)
207+
self.assertEqual('example_data\n', o.afile.read())
208+
self.assertEqual('django/example.data', o.afile.name)
209+
210+
def test_with_path(self):
211+
o = WithFileFactory.build(afile__from_path=testdata.TESTFILE_PATH)
212+
self.assertIsNone(o.pk)
213+
self.assertEqual('example_data\n', o.afile.read())
214+
self.assertEqual('django/example.data', o.afile.name)
215+
216+
def test_with_file_empty_path(self):
217+
with open(testdata.TESTFILE_PATH, 'rb') as f:
218+
o = WithFileFactory.build(
219+
afile__from_file=f,
220+
afile__from_path=''
221+
)
222+
self.assertIsNone(o.pk)
223+
self.assertEqual('example_data\n', o.afile.read())
224+
self.assertEqual('django/example.data', o.afile.name)
225+
226+
def test_with_path_empty_file(self):
227+
o = WithFileFactory.build(
228+
afile__from_path=testdata.TESTFILE_PATH,
229+
afile__from_file=None,
230+
)
231+
self.assertIsNone(o.pk)
232+
self.assertEqual('example_data\n', o.afile.read())
233+
self.assertEqual('django/example.data', o.afile.name)
234+
235+
def test_error_both_file_and_path(self):
236+
self.assertRaises(ValueError, WithFileFactory.build,
237+
afile__from_file='fakefile',
238+
afile__from_path=testdata.TESTFILE_PATH,
239+
)
240+
241+
def test_override_filename_with_path(self):
242+
o = WithFileFactory.build(
243+
afile__from_path=testdata.TESTFILE_PATH,
244+
afile__filename='example.foo',
245+
)
246+
self.assertIsNone(o.pk)
247+
self.assertEqual('example_data\n', o.afile.read())
248+
self.assertEqual('django/example.foo', o.afile.name)
249+
250+
def test_existing_file(self):
251+
o1 = WithFileFactory.build(afile__from_path=testdata.TESTFILE_PATH)
252+
253+
o2 = WithFileFactory.build(afile=o1.afile)
254+
self.assertIsNone(o2.pk)
255+
self.assertEqual('example_data\n', o2.afile.read())
256+
self.assertEqual('django/example_1.data', o2.afile.name)
257+
258+
def test_no_file(self):
259+
o = WithFileFactory.build(afile=None)
260+
self.assertIsNone(o.pk)
261+
self.assertFalse(o.afile)
262+
263+
264+
if __name__ == '__main__': # pragma: no cover
265+
unittest.main()

tests/testdata/__init__.py

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# -*- coding: utf-8 -*-
2+
# Copyright (c) 2011-2013 Raphaël Barrois
3+
#
4+
# Permission is hereby granted, free of charge, to any person obtaining a copy
5+
# of this software and associated documentation files (the "Software"), to deal
6+
# in the Software without restriction, including without limitation the rights
7+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8+
# copies of the Software, and to permit persons to whom the Software is
9+
# furnished to do so, subject to the following conditions:
10+
#
11+
# The above copyright notice and this permission notice shall be included in
12+
# all copies or substantial portions of the Software.
13+
#
14+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20+
# THE SOFTWARE.
21+
22+
23+
import os.path
24+
25+
TESTDATA_ROOT = os.path.abspath(os.path.dirname(__file__))
26+
TESTFILE_PATH = os.path.join(TESTDATA_ROOT, 'example.data')

tests/testdata/example.data

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
example_data

0 commit comments

Comments
 (0)