diff --git a/django/core/management/validation.py b/django/core/management/validation.py index 85897ee9e2..9beb47e542 100644 --- a/django/core/management/validation.py +++ b/django/core/management/validation.py @@ -120,8 +120,6 @@ def get_validation_errors(outfile, app=None): if decimalp_ok and mdigits_ok: if decimal_places > max_digits: e.add(opts, invalid_values_msg % f.name) - if isinstance(f, models.FileField) and not f.upload_to: - e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name) if isinstance(f, models.ImageField): try: from django.utils.image import Image diff --git a/docs/faq/usage.txt b/docs/faq/usage.txt index be3839e08f..ecc473762f 100644 --- a/docs/faq/usage.txt +++ b/docs/faq/usage.txt @@ -45,10 +45,9 @@ Using a :class:`~django.db.models.FileField` or an account. #. Add the :class:`~django.db.models.FileField` or - :class:`~django.db.models.ImageField` to your model, making sure to - define the :attr:`~django.db.models.FileField.upload_to` option to tell - Django to which subdirectory of :setting:`MEDIA_ROOT` it should upload - files. + :class:`~django.db.models.ImageField` to your model, defining the + :attr:`~django.db.models.FileField.upload_to` option to specify a + subdirectory of :setting:`MEDIA_ROOT` to use for uploaded files. #. All that will be stored in your database is a path to the file (relative to :setting:`MEDIA_ROOT`). You'll most likely want to use the diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt index bcadf4d27a..0a3abae644 100644 --- a/docs/ref/models/fields.txt +++ b/docs/ref/models/fields.txt @@ -542,7 +542,7 @@ A :class:`CharField` that checks that the value is a valid email address. ``FileField`` ------------- -.. class:: FileField(upload_to=None, [max_length=100, **options]) +.. class:: FileField([upload_to=None, max_length=100, **options]) A file-upload field. @@ -550,10 +550,14 @@ A file-upload field. The ``primary_key`` and ``unique`` arguments are not supported, and will raise a ``TypeError`` if used. -Has one **required** argument: +Has two optional arguments: .. attribute:: FileField.upload_to + .. versionchanged:: 1.7 + + ``upload_to`` was required in older versions of Django. + A local filesystem path that will be appended to your :setting:`MEDIA_ROOT` setting to determine the value of the :attr:`~django.db.models.fields.files.FieldFile.url` attribute. @@ -586,11 +590,9 @@ Has one **required** argument: when determining the final destination path. ====================== =============================================== -Also has one optional argument: - .. attribute:: FileField.storage - Optional. A storage object, which handles the storage and retrieval of your + A storage object, which handles the storage and retrieval of your files. See :doc:`/topics/files` for details on how to provide this object. The default form widget for this field is a :class:`~django.forms.FileInput`. @@ -604,9 +606,9 @@ takes a few steps: :setting:`MEDIA_URL` as the base public URL of that directory. Make sure that this directory is writable by the Web server's user account. -2. Add the :class:`FileField` or :class:`ImageField` to your model, making - sure to define the :attr:`~FileField.upload_to` option to tell Django - to which subdirectory of :setting:`MEDIA_ROOT` it should upload files. +2. Add the :class:`FileField` or :class:`ImageField` to your model, defining + the :attr:`~FileField.upload_to` option to specify a subdirectory of + :setting:`MEDIA_ROOT` to use for uploaded files. 3. All that will be stored in your database is a path to the file (relative to :setting:`MEDIA_ROOT`). You'll most likely want to use the @@ -807,7 +809,7 @@ The default form widget for this field is a :class:`~django.forms.TextInput`. ``ImageField`` -------------- -.. class:: ImageField(upload_to=None, [height_field=None, width_field=None, max_length=100, **options]) +.. class:: ImageField([upload_to=None, height_field=None, width_field=None, max_length=100, **options]) Inherits all attributes and methods from :class:`FileField`, but also validates that the uploaded object is a valid image. diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 0f1a5de073..8fbd82adc6 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -246,6 +246,10 @@ File Uploads the file system permissions of directories created during file upload, like :setting:`FILE_UPLOAD_PERMISSIONS` does for the files themselves. +* The :attr:`FileField.upload_to ` + attribute is now optional. If it is omitted or given ``None`` or an empty + string, a subdirectory won't be used for storing the uploaded files. + Forms ^^^^^ diff --git a/tests/files/models.py b/tests/files/models.py index 4134472748..eea3ddaf09 100644 --- a/tests/files/models.py +++ b/tests/files/models.py @@ -28,3 +28,4 @@ class Storage(models.Model): custom = models.FileField(storage=temp_storage, upload_to=custom_upload_to) random = models.FileField(storage=temp_storage, upload_to=random_upload_to) default = models.FileField(storage=temp_storage, upload_to='tests', default='tests/default.txt') + empty = models.FileField(storage=temp_storage) diff --git a/tests/files/tests.py b/tests/files/tests.py index 36664fa83a..40aa9f6d75 100644 --- a/tests/files/tests.py +++ b/tests/files/tests.py @@ -106,6 +106,12 @@ class FileStorageTests(TestCase): obj4.random.save("random_file", ContentFile("random content")) self.assertTrue(obj4.random.name.endswith("/random_file")) + # upload_to can be empty, meaning it does not use subdirectory. + obj5 = Storage() + obj5.empty.save('django_test.txt', ContentFile('more content')) + self.assertEqual(obj5.empty.name, "./django_test.txt") + self.assertEqual(obj5.empty.read(), b"more content") + def test_file_object(self): # Create sample file temp_storage.save('tests/example.txt', ContentFile('some content')) diff --git a/tests/invalid_models/invalid_models/models.py b/tests/invalid_models/invalid_models/models.py index 1113c0c056..8e04a4d328 100644 --- a/tests/invalid_models/invalid_models/models.py +++ b/tests/invalid_models/invalid_models/models.py @@ -19,7 +19,6 @@ class FieldErrors(models.Model): decimalfield3 = models.DecimalField(max_digits="bad", decimal_places="bad") decimalfield4 = models.DecimalField(max_digits=9, decimal_places=10) decimalfield5 = models.DecimalField(max_digits=10, decimal_places=10) - filefield = models.FileField() choices = models.CharField(max_length=10, choices='bad') choices2 = models.CharField(max_length=10, choices=[(1, 2, 3), (1, 2, 3)]) index = models.CharField(max_length=10, db_index='bad') @@ -424,7 +423,6 @@ invalid_models.fielderrors: "decimalfield2": DecimalFields require a "max_digits invalid_models.fielderrors: "decimalfield3": DecimalFields require a "decimal_places" attribute that is a non-negative integer. invalid_models.fielderrors: "decimalfield3": DecimalFields require a "max_digits" attribute that is a positive integer. invalid_models.fielderrors: "decimalfield4": DecimalFields require a "max_digits" attribute value that is greater than or equal to the value of the "decimal_places" attribute. -invalid_models.fielderrors: "filefield": FileFields require an "upload_to" attribute. invalid_models.fielderrors: "choices": "choices" should be iterable (e.g., a tuple or list). invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples). invalid_models.fielderrors: "choices2": "choices" should be a sequence of two-item iterables (e.g. list of 2 item tuples).