319

I recently upgrade Django from v1.3.1 to v1.4.

In my old settings.py I have

TEMPLATE_DIRS = (
    os.path.join(os.path.dirname( __file__ ), 'templates').replace('\\', '/'),
    # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
    # Always use forward slashes, even on Windows.
    # Don't forget to use absolute paths, not relative paths.
)

This will point to /Users/hobbes3/Sites/mysite/templates, but because Django v1.4 moved the project folder to the same level as the app folders, my settings.py file is now in /Users/hobbes3/Sites/mysite/mysite/ instead of /Users/hobbes3/Sites/mysite/.

So actually my question is now twofold:

  1. How do I use os.path to look at a directory one level above from __file__. In other words, I want /Users/hobbes3/Sites/mysite/mysite/settings.py to find /Users/hobbes3/Sites/mysite/templates using relative paths.
  2. Should I be keeping the template folder (which has cross-app templates, like admin, registration, etc.) at the project /User/hobbes3/Sites/mysite level or at /User/hobbes3/Sites/mysite/mysite?
hobbes3
  • 28,078
  • 24
  • 87
  • 116
  • Cant you just use `os` to `cd` to `../mysite`? Or whatever command you want – prelic Mar 24 '12 at 23:44
  • @prelic Hmm? I don't understand. I am trying to avoid hardcoding the path, because I use the same `settings.py` in multiple servers. The only difference might be the database credentials. I was reading the [`os.path` documentation](http://docs.python.org/library/os.path.html) but I couldn't find a command that let's you go up one directory. Like `cd ..`. – hobbes3 Mar 24 '12 at 23:46
  • 3
    @hobbes3 You can just `os.path.join( os.path.dirname( __file__ ), '..' )` `..` means _the directory above_ throughout the filesystem, not just when passed to `cd`. – Michael Berkowski Mar 24 '12 at 23:48
  • 3
    @Michael, it is probably better to use `os.path.join( os.path.dirname ( __file__), os.path.pardir)` – mgilson Mar 24 '12 at 23:50

17 Answers17

390
os.path.abspath(os.path.join(os.path.dirname( __file__ ), '..', 'templates'))

As far as where the templates folder should go, I don't know since Django 1.4 just came out and I haven't looked at it yet. You should probably ask another question on SE to solve that issue.

You can also use normpath to clean up the path, rather than abspath. However, in this situation, Django expects an absolute path rather than a relative path.

For cross platform compatability, use os.pardir instead of '..'.

forivall
  • 9,504
  • 2
  • 33
  • 58
  • 6
    Is it a bad idea to use `..` or something? Why is this answer getting less votes? – hobbes3 Mar 25 '12 at 00:01
  • 2
    I don't know why it's getting less votes, but it's what I've always used. It's even defined in the example for [`normpath`](http://docs.python.org/library/os.path.html#os.path.normpath). Plus, it will traverse symlinks properly. – forivall Mar 25 '12 at 00:06
  • Well I like the simplicity! Why the `abspath` though? I didn't use it before and it still worked in `settings.py`. – hobbes3 Mar 25 '12 at 00:14
  • 3
    Using abspath will just clean it up a bit. If it's not there, the actual string for the path name will be `/Users/hobbes3/Sites/mysite/mysite/../templates`, which is perfectly fine, but just a little more cluttered. It also ensures that Django's reminder to use absolute paths is obeyed. If you're in a different situation that uses a relative path, you should use normpath to simplify your paths instead. – forivall Mar 25 '12 at 01:29
  • 2
    [This question was just asked regarding migrating folder structure for the new version of Django](http://stackoverflow.com/questions/9857364/whats-is-the-best-way-to-migrate-folder-and-files-structure-from-django1-3-to-dj), so you probably should look to that for solving your second issue. – forivall Mar 25 '12 at 02:16
  • 1
    @Zitrax Do you know of any platforms where this would be different, or is it just there for future safety? (I didn't actually know about `os.pardir`, never actually got to the bottom of the `os` docs) – forivall Feb 20 '13 at 19:54
  • 1
    @forivall For example classic mac uses '::' instead of '..'. For more versions see http://en.wikipedia.org/wiki/Path_(computing) – Zitrax Feb 20 '13 at 23:34
  • Use `'__file__'` instead of `__file__` if you are executing this within your python code – Prageeth Jayathissa Oct 29 '19 at 04:04
  • @PrageethJayathissa explain please. – forivall Oct 31 '19 at 19:06
  • Without the apostrophe you can get the error `NameError: name '__file__' is not defined` Using `'__file__'` instead of `__file__` will overcome this issue – Prageeth Jayathissa Nov 01 '19 at 09:23
  • @PrageethJayathissa That error will only occur at the repl. Changing it to a string is the wrong way to solve the issue. This example is specifically targetting the Django config, which runs as an imported module. When running inside a module, `__file__` refers to the current file path. See https://stackoverflow.com/questions/9271464/what-does-the-file-variable-mean-do – forivall Nov 13 '19 at 19:11
149

To get the folder of a file just use:

os.path.dirname(path) 

To get a folder up just use os.path.dirname again

os.path.dirname(os.path.dirname(path))

You might want to check if __file__ is a symlink:

if os.path.islink(__file__): path = os.readlink (__file__)
Michael Ohlrogge
  • 10,559
  • 5
  • 48
  • 76
jassinm
  • 7,323
  • 3
  • 33
  • 42
  • 20
    Is there a way to go up `n` folders without having to call `os.path.dirname` `n` times? – Oriol Nieto Jun 20 '15 at 18:55
  • 17
    @OriolNieto Yes, as of version Python 3.4+ you can use ```pathlib.Path.parents[levels_up-1]```. See [this question](https://stackoverflow.com/q/55516779/11021886) for more solutions – jwalton Apr 04 '19 at 15:22
93

If you are using Python 3.4 or newer, a convenient way to move up multiple directories is pathlib:

from pathlib import Path

full_path = "path/to/directory"
str(Path(full_path).parents[0])  # "path/to"
str(Path(full_path).parents[1])  # "path"
str(Path(full_path).parents[2])  # "."
birnbaum
  • 4,718
  • 28
  • 37
25

You want exactly this:

BASE_DIR = os.path.join( os.path.dirname( __file__ ), '..' )
Alan Viars
  • 3,112
  • 31
  • 14
13

Personally, I'd go for the function approach

def get_parent_dir(directory):
    import os
    return os.path.dirname(directory)

current_dirs_parent = get_parent_dir(os.getcwd())
Lord Sumner
  • 573
  • 5
  • 6
  • 1
    Be careful this answer not works if input contains trailing slash, e.g. `os.path.dirname('/tmp/lala/')` still return `'/tmp/lala'` – 林果皞 Mar 04 '19 at 13:48
  • The trailing slash case can refer this answer: https://stackoverflow.com/a/25669963/1074998 – 林果皞 Mar 04 '19 at 13:58
12

If you prefer a one-liner for getting the parent directory, I'd suggest this:

import os
    
parent_dir = os.path.split(os.getcwd())[0]

os.path.split() method returns a tuple (head, tail) where tail is everything after the final slash. So the first index is the parent of your absolute path.

carlosgg
  • 129
  • 1
  • 4
9

I think the easiest thing to do is just to reuse dirname() So you can call

os.path.dirname(os.path.dirname( __file__ ))

if you file is at /Users/hobbes3/Sites/mysite/templates/method.py

This will return "/Users/hobbes3/Sites/mysite"

7
from os.path import dirname, realpath, join
join(dirname(realpath(dirname(__file__))), 'templates')

Update:

If you happen to "copy" settings.py through symlinking, @forivall's answer is better:

~user/
    project1/  
        mysite/
            settings.py
        templates/
            wrong.html

    project2/
        mysite/
            settings.py -> ~user/project1/settings.py
        templates/
            right.html

The method above will 'see' wrong.html while @forivall's method will see right.html

In the absense of symlinks the two answers are identical.

Antony Hatchkins
  • 31,947
  • 10
  • 111
  • 111
7

This might be useful for other cases where you want to go x folders up. Just run walk_up_folder(path, 6) to go up 6 folders.

def walk_up_folder(path, depth=1):
    _cur_depth = 1        
    while _cur_depth < depth:
        path = os.path.dirname(path)
        _cur_depth += 1
    return path   
5

Go up a level from the work directory

import os
os.path.dirname(os.getcwd())

or from the current directory

import os
os.path.dirname('current path')
Julio CamPlaz
  • 857
  • 8
  • 18
4

To go n folders up... run up(n)

import os

def up(n, nth_dir=os.getcwd()):
    while n != 0:
        nth_dir = os.path.dirname(nth_dir)
        n -= 1
    return nth_dir
Jo3l
  • 66
  • 3
3

I'm surprised handling for an arbitrary number of ".." parent directory tokens in a path string isn't directly handled for by the os library. Here's a quick and dirty function that'll give you an absolute path string from a relative one:

def get_abs_from_relpath(relpath:str) -> str:
    ap = os.path.abspath(__file__).split("/")[:-1]
    sp = relpath.split("/")
    sp_start_index = 0
    for slug in sp:
        if slug == "..":
            ap.pop(-1)
            sp_start_index += 1
        else:
            return "/".join(ap+sp[sp_start_index:])

You can call it with open() like this:

with open(get_abs_from_relpath('../../../somedir/myfile.txt')) as f:
    foo = f.read()
Alan Garcia
  • 131
  • 1
2

For a paranoid like me, I'd prefer this one

TEMPLATE_DIRS = (
    __file__.rsplit('/', 2)[0] + '/templates',
)
serv-inc
  • 35,772
  • 9
  • 166
  • 188
haterh
  • 21
  • 1
2

With using os.path we can go one directory up like that

one_directory_up_path = os.path.dirname('.')

also after finding the directory you want you can join with other file/directory path

other_image_path = os.path.join(one_directory_up_path, 'other.jpg')
abdullahselek
  • 7,893
  • 3
  • 50
  • 40
1

From the current file path you could use:

os.path.join(os.path.dirname(__file__),'..','img','banner.png')
0

Of course: simply use os.chdir(..).

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
The Shwarma
  • 180
  • 1
  • 11
0

Not an answer but a long tangential comment that probably should be made as someone may be led astray...

The syntax os.path.join( os.path.dirname( __file__ ), 'foo.txt') to get a file within the same folder as the python file getting run is not the "advised" solution for packages, instead package data is preferred for a couple of reasons, for example in a zip packaged package or a more complicated filesystem.

pkg_resources.read_text(__package__, 'foo.txt') was the formerly recommended solution, but will be removed at some point and importlib.resources.read_text(__package__, 'foo.txt') is the recommended way —see https://docs.python.org/3/library/importlib.html#module-importlib.resources for the many options. However, this

  • requires include_package_data=True and package_data with a Dict[str, List[str] in the setup.py file
  • requires a MANIFEST.in if pip distributed as sdist (but not a built wheel)
  • will not work for relative imports (i.e. not installed)
  • is wisely and generally ignored for sake of sanity in webapps due to the way they run
Matteo Ferla
  • 2,128
  • 1
  • 16
  • 27