Search code examples
pythondjangodjango-settings

Django - SuspiciousFileOperation from image add on local development environment


I've been working on a personal photo website built on Django for the last several months and successfully deployed it with AWS. Til now I've done all my development on my Windows 10 desktop. I recently got a new Windows 10 laptop and have gotten stuck setting up a dev environment on it. I cloned my Github repo to my laptop and successfully gotten the site to run locally. However when I go to the admin page to upload a photo and every time I get a SuspiciousFileOperation exception. The joined path is located outside of the base path component. I've also gotten this error when running collectstatic. The settings are identical to my desktop setup, unless I'm overlooking an issue staring right at me.

I think it might have something to do with the way I set up the virtualenv or how my MEDIA variables are set up, though they are the same as what works on my desktop. I've read through the Django documentation but haven't found anything definitive. Or maybe someone could explain a bit more about setting up virtualenv? Does Django/Python need certain permissions I missed setting up on my laptop to move files around?

Below is the full error and the relevant code snippets.

Stack Trace:

    Environment:


    Request Method: POST
    Request URL: http://127.0.0.1:8000/admin/PortfolioApp/image/add/

    Django Version: 2.1.4
    Python Version: 3.7.1
    Installed Applications:
    ['django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'PortfolioApp.apps.PortfolioappConfig',
     'storages']
    Installed Middleware:
    ['django.middleware.security.SecurityMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware']



    Traceback:

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\core\handlers\exception.py" in inner
      34.             response = get_response(request)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\core\handlers\base.py" in _get_response
      126.                 response = self.process_exception_by_middleware(e, request)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\core\handlers\base.py" in _get_response
      124.                 response = wrapped_callback(request, *callback_args, **callback_kwargs)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\contrib\admin\options.py" in wrapper
      604.                 return self.admin_site.admin_view(view)(*args, **kwargs)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\utils\decorators.py" in _wrapped_view
      142.                     response = view_func(request, *args, **kwargs)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\views\decorators\cache.py" in _wrapped_view_func
      44.         response = view_func(request, *args, **kwargs)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\contrib\admin\sites.py" in inner
      223.             return view(request, *args, **kwargs)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\contrib\admin\options.py" in add_view
      1637.         return self.changeform_view(request, None, form_url, extra_context)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\utils\decorators.py" in _wrapper
      45.         return bound_method(*args, **kwargs)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\utils\decorators.py" in _wrapped_view
      142.                     response = view_func(request, *args, **kwargs)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\contrib\admin\options.py" in changeform_view
      1525.             return self._changeform_view(request, object_id, form_url, extra_context)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\contrib\admin\options.py" in _changeform_view
      1564.                 self.save_model(request, new_object, form, not add)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\contrib\admin\options.py" in save_model
      1091.         obj.save()

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\base.py" in save
      718.                        force_update=force_update, update_fields=update_fields)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\base.py" in save_base
      748.             updated = self._save_table(raw, cls, force_insert, force_update, using, update_fields)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\base.py" in _save_table
      831.             result = self._do_insert(cls._base_manager, using, fields, update_pk, raw)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\base.py" in _do_insert
      869.                                using=using, raw=raw)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\manager.py" in manager_method
      82.                 return getattr(self.get_queryset(), name)(*args, **kwargs)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\query.py" in _insert
      1136.         return query.get_compiler(using=using).execute_sql(return_id)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\sql\compiler.py" in execute_sql
      1288.             for sql, params in self.as_sql():

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\sql\compiler.py" in as_sql
      1241.                 for obj in self.query.objs

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\sql\compiler.py" in <listcomp>
      1241.                 for obj in self.query.objs

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\sql\compiler.py" in <listcomp>
      1240.                 [self.prepare_value(field, self.pre_save_val(field, obj)) for field in fields]

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\sql\compiler.py" in pre_save_val
      1192.         return field.pre_save(obj, add=True)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\fields\files.py" in pre_save
      288.             file.save(file.name, file.file, save=False)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\db\models\fields\files.py" in save
      87.         self.name = self.storage.save(name, content, max_length=self.field.max_length)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\core\files\storage.py" in save
      48.         name = self.get_available_name(name, max_length=max_length)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\core\files\storage.py" in get_available_name
      72.         while self.exists(name) or (max_length and len(name) > max_length):

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\core\files\storage.py" in exists
      308.         return os.path.exists(self.path(name))

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\core\files\storage.py" in path
      321.         return safe_join(self.location, name)

    File "C:\Users\Bryan\Envs\dj\lib\site-packages\django\utils\_os.py" in safe_join
      49.             'component ({})'.format(final_path, base_path))

    Exception Type: SuspiciousFileOperation at /admin/PortfolioApp/image/add/
    Exception Value: The joined path (C:\Users\Bryan\Documents\GitHub\Portfolio\media\pictures\VeselkaNYC-BJM.jpg) is located outside of the base path component (C:\Users\Bryan\Documents\GitHub\Portfolio\media\)

models.py

    class Image(models.Model):
        ...
        picture = models.ImageField(upload_to='pictures/', height_field='height', width_field='width', null=True)
        ...

settings.py

    ...
    STATIC_URL = '/static/'
    STATIC_ROOT = 'static/'

    MEDIA_URL = '/media/'
    MEDIA_ROOT = 'media/'
    ...

urls.py

    ...
    if settings.DEBUG:
        # Use static() to add url mapping to serve static files during development (only)
        urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
        urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

I've included what I think are the relevant code snippets but please ask if you need more to help me out. Any help is much appreciated.

EDIT: Thanks to Giancarlo's suggestions, redefined my STATIC_ROOT and MEDIA_ROOT as follows:

    STATIC_ROOT = os.join.path(BASE_DIR, "static")
    MEDIA_ROOT = os.join.path(BASE_DIR, "media")

I think the settings on my desktop work because of a .pyc cache file containing the correct settings that were saved during the development process. That's the best I can come up with.


Solution

  • Change your STATIC_ROOT and MEDIA_ROOT to

    STATIC_ROOT = os.path.join(BASE_DIR, "static/")
    MEDIA_ROOT = os.path.join(BASE_DIR, "media/")
    

    Because maybe Windows is searching at C:\media or C:\static

    Edit: I think BASE_DIR is pre-defined in settings.py, but here is if it is missing

    BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
    

    And to be sure, add a trailing slash to the upload_to

    upload_to='pictures/'