252
votes

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?
16
Cant you just use os to cd to ../mysite? Or whatever command you wantprelic
@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 but I couldn't find a command that let's you go up one directory. Like cd ...hobbes3
@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
@Michael, it is probably better to use os.path.join( os.path.dirname ( __file__), os.path.pardir)mgilson

16 Answers

322
votes
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 '..'.

119
votes

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__)
45
votes

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])  # "."
19
votes

You want exactly this:

BASE_DIR = os.path.join( os.path.dirname( __file__ ), '..' )
9
votes

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())
7
votes

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"

6
votes
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.

6
votes

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   
3
votes

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
2
votes

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

TEMPLATE_DIRS = (
    __file__.rsplit('/', 2)[0] + '/templates',
)
2
votes

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.

1
votes

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

1
votes

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')
1
votes

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')
0
votes

From the current file path you could use:

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

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