Tests for magic system are failing under Python 3.3
Currently, this line (https://github.com/brian-team/brian2/blob/master/brian2/tests/test_network.py#L286):

```
assert_raises(MagicError, lambda: run(1*ms, level=3))
```

fails to raise the expected `MagicError` when tested with Python 3.3 -- it's working as expected under Python 2.x

diff --git a/.travis.yml b/.travis.yml
index 860cfc02..dbfa28ae 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,7 +2,6 @@ language: python
 python:
   - "2.6"
   - "2.7"
-  - "3.2"
   - "3.3"
 # install build dependencies
 before_install:
diff --git a/brian2/core/core_preferences.py b/brian2/core/core_preferences.py
index 6e34c223..8de4bbe0 100644
--- a/brian2/core/core_preferences.py
+++ b/brian2/core/core_preferences.py
@@ -16,25 +16,5 @@ def dtype_repr(dtype):
         Default dtype for all arrays of scalars (state variables, weights, etc.).'
         ''',
         representor=dtype_repr,
-        ),
-    delete_log_on_exit=BrianPreference(
-        default=True,
-        docs=    '''
-        Whether to delete the log and script file on exit.
-        
-        If set to ``True`` (the default), log files (and the copy of the main
-        script) will be deleted after the brian process has exited, unless an
-        uncaught exception occured. If set to ``False``, all log files will be kept.
-        ''',
-        ),
-    weave_compiler=BrianPreference(
-        default='gcc',
-        docs='''
-        The compiler name to use for ``scipy.weave``.
-        
-        On Linux platforms, gcc is usually available. On Windows, it can be made
-        available as part of the mingw and cygwin packages, and also included in
-        some Python distributions such as EPD and Python(x,y).
-        ''',
-        ),
+        )
     )
diff --git a/brian2/core/preferences.py b/brian2/core/preferences.py
index 8c572f40..7331a649 100644
--- a/brian2/core/preferences.py
+++ b/brian2/core/preferences.py
@@ -2,6 +2,7 @@
 Brian global preferences are stored as attributes of a `BrianGlobalPreferences`
 object ``brian_prefs``.
 '''
+import itertools
 import re
 import os
 from collections import MutableMapping
@@ -11,6 +12,61 @@
 
 __all__ = ['PreferenceError', 'BrianPreference', 'brian_prefs']
 
+def parse_preference_name(name):
+    '''
+    Split a preference name into a base and end name.
+    
+    Parameters
+    ----------
+    name : str
+        The full name of the preference.
+    
+    Returns
+    -------        
+    basename : str
+        The first part of the name up to the final ``.``.
+    endname : str
+        The last part of the name from the final ``.`` onwards.
+    
+    Examples
+    --------
+    >>> parse_preference_name('core.weave_compiler')
+    ('core', 'weave_compiler')
+    >>> parse_preference_name('codegen.cpp.compiler')
+    ('codegen.cpp', 'compiler')
+    '''
+    # parse the name
+    parts = name.split('.')
+    basename = '.'.join(parts[:-1])
+    endname = parts[-1]
+    return basename, endname
+
+
+def check_preference_name(name):
+    '''
+    Make sure that a preference name is valid. This currently checks that the
+    name does not contain illegal characters and does not clash with method
+    names such as "keys" or "items".
+    
+    Parameters
+    ----------
+    name : str
+        The name to check.
+    
+    Raises
+    ------
+    PreferenceError
+        In case the name is invalid.
+    '''
+    if not re.match("[A-Za-z][_a-zA-Z0-9]*$", name):
+        raise PreferenceError(('Illegal preference name "%s": A preference '
+                               'name can only start with a letter and only '
+                               'contain letters, digits or underscore.' % name))
+    if name in dir(MutableMapping) or name in brian_prefs.__dict__:
+        raise PreferenceError(('Illegal preference name "%s": This is also the '
+                              'name of a method.') % name)
+
+
 class PreferenceError(Exception):
     '''
     Exception relating to the Brian preferences system.
@@ -67,35 +123,6 @@ def __init__(self, default, docs, validator=None, representor=repr):
         self.default = default
         self.docs = docs
 
-def parse_preference_name(name):
-    '''
-    Split a preference name into a base and end name.
-    
-    Parameters
-    ----------
-    name : str
-        The full name of the preference.
-    
-    Returns
-    -------        
-    basename : str
-        The first part of the name up to the final ``.``.
-    endname : str
-        The last part of the name from the final ``.`` onwards.
-    
-    Examples
-    --------
-    >>> parse_preference_name('core.weave_compiler')
-    ('core', 'weave_compiler')
-    >>> parse_preference_name('codegen.cpp.compiler')
-    ('codegen.cpp', 'compiler')
-    '''
-    # parse the name
-    parts = name.split('.')
-    basename = '.'.join(parts[:-1])
-    endname = parts[-1]
-    return basename, endname
-
 
 class BrianGlobalPreferences(MutableMapping):
     '''
@@ -103,9 +130,11 @@ class BrianGlobalPreferences(MutableMapping):
     
     Used for getting/setting/validating/registering preference values.
     All preferences must be registered via `register_preferences`. To get or
-    set a preference, use the dictionary-like interface, e.g.::
+    set a preference, you can either use a dictionary-based or an
+    attribute-based interface::
     
         brian_prefs['core.default_scalar_dtype'] = float32
+        brian_prefs.core.default_scalar_dtype = float32
         
     Preferences can be read from files, see `load_preferences` and
     `read_preference_file`. Note that `load_preferences` is called
@@ -125,6 +154,9 @@ def __init__(self):
             ''') in self.eval_namespace
 
     def __getitem__(self, item):
+        if item in self.pref_register:
+            # This asks for a category, not a single preference
+            return BrianGlobalPreferencesView(item, self)
         return self.prefs[item]
 
     def __len__(self):
@@ -139,8 +171,8 @@ def __contains__(self, item):
     def __setitem__(self, name, value):
         basename, endname = parse_preference_name(name)
         if basename not in self.pref_register:
-            self.prefs_unvalidated[name] = value
-            return
+            raise PreferenceError("Preference category " + basename +
+                                  " is unregistered. Spelling error?")
         prefdefs, _ = self.pref_register[basename]
         if endname in prefdefs:
             # do validation
@@ -158,12 +190,83 @@ def __setitem__(self, name, value):
     def __delitem__(self, item):
         raise PreferenceError("Preferences cannot be deleted.")
 
+    def __getattr__(self, name):
+        if name in self.__dict__:
+            return MutableMapping.__getattr__(self, name)
+        
+        # This function might get called from BrianGlobalPreferencesView with
+        # a prefixed name -- therefore the name can contain dots!
+        if name in self.pref_register:
+            # This asks for a category, not a single preference
+            return BrianGlobalPreferencesView(name, self)
+        
+        basename, _ = parse_preference_name(name)
+        if len(basename) and basename not in self.pref_register:
+            raise AssertionError(('__getattr__ received basename %s which is '
+                                 'unregistered. This should never happen!') %
+                                 basename)
+        
+        return self[name]
+
+    def __setattr__(self, name, value):  
+        # Do not allow to set a category name to something else
+        if 'pref_register' in self.__dict__ and name in self.pref_register:
+            raise PreferenceError('Cannot set a preference category.')
+        else:
+            MutableMapping.__setattr__(self, name, value)      
+
+    def __delattr__(self, name):
+        if 'pref_register' in self.__dict__ and name in self.pref_register:
+            raise PreferenceError('Cannot delete a preference category.')
+        else:
+            MutableMapping.__setattr__(self, name, value)
+
+    toplevel_categories = property(fget=lambda self: [category for category in
+                                                      self.pref_register
+                                                      if not '.' in category],
+                                   doc='The toplevel preference categories')    
+
+    def _get_docstring(self):
+        '''
+        Document the toplevel categories, used as a docstring for the object.
+        '''
+        s =  'Preference categories:\n\n'
+        for category in self.toplevel_categories:
+            s += '** %s **\n' % category
+            _, category_doc = self.pref_register[category]
+            s += '    ' + category_doc + '\n\n'
+        
+        return s
+
+    def __dir__(self):
+        res = dir(type(self)) + self.__dict__.keys()
+        categories = self.toplevel_categories
+        res.extend(categories)
+        return res        
+
     def eval_pref(self, value):
         '''
         Evaluate a string preference in the units namespace
         '''
         return eval(value, self.eval_namespace)
 
+    def _set_preference(self, name, value):
+        '''
+        Try to set the preference and allow for unregistered base names. This
+        method is used internally when reading preferences from the file
+        because the preferences are potentially defined in packages that are
+        not imported yet. Unvalidated preferences are safed and will be
+        validated as soon as the category is registered. `Network.run` will
+        also check for unvalidated preferences.
+        '''
+        basename, _ = parse_preference_name(name)
+        if basename not in self.pref_register:
+            self.prefs_unvalidated[name] = value
+        else:
+            # go via the standard __setitem__ method
+            self[name] = value
+        
+
     def _backup(self):
         '''
         Store a backup copy of the preferences to restore with `_restore`.
@@ -304,7 +407,7 @@ def read_preference_file(self, file):
                 extname = m.group(1).strip()
                 value = m.group(2).strip()
                 keyname = '.'.join(bases + extname.split('.'))
-                self[keyname] = self.eval_pref(value)
+                self._set_preference(keyname, self.eval_pref(value))
                 continue
             # Otherwise raise a parsing error
             raise PreferenceError("Parsing error in preference file " + filename)
@@ -365,10 +468,34 @@ def register_preferences(self, prefbasename, prefbasedoc, **prefs):
         '''
         if prefbasename in self.pref_register:
             raise PreferenceError("Base name " + prefbasename + " already registered.")
+        # Check that the new category does not clash with a preference name of
+        # the parent category. For example, if a category "a" with the
+        # preference "b" is already registered, do not allow to register a
+        # preference category "a.b"
+        basename, category_name = parse_preference_name(prefbasename)
+        if len(basename) and basename in self.pref_register:
+            parent_preferences, _ = self.pref_register[basename]
+            if category_name in parent_preferences:
+                raise PreferenceError(('Cannot register category "%s", '
+                                       'parent category "%s" already has a '
+                                       'preference named "%s".') %
+                                      (prefbasename, basename, category_name))
+
         self.pref_register[prefbasename] = (prefs, prefbasedoc)
         for k, v in prefs.items():
-            self.prefs_unvalidated[prefbasename + '.' + k] = v.default
+            fullname = prefbasename + '.' + k
+            # The converse of the above check: Check that a preference name
+            # does not clash with a category 
+            if fullname in self.pref_register:
+                raise PreferenceError(('Cannot register "%s" as a preference, '
+                                       'it is already registered as a '
+                                       'preference category.') % fullname)
+            check_preference_name(k)
+            self.prefs_unvalidated[fullname] = v.default
         self.do_validation()
+        
+        # Update the docstring (a new toplevel category might have been added)
+        self.__doc__ = self._get_docstring()
 
     def do_validation(self):
         '''
@@ -393,5 +520,92 @@ def check_all_validated(self):
                         "import." % ', '.join(self.prefs_unvalidated.keys()),
                         once=True)
 
+    def __repr__(self):
+        description = '<{classname} with top-level categories: {categories}>'
+        categories = ', '.join(['"%s"' % category for category
+                                in self.toplevel_categories])
+        return description.format(classname=self.__class__.__name__,
+                                  categories=categories)
+
+
+class BrianGlobalPreferencesView(MutableMapping):
+    '''
+    A class allowing for accessing preferences in a subcategory. It forwards
+    requests to `BrianGlobalPreferences` and provides documentation and
+    autocompletion support for all preferences in the given category. This
+    object is used to allow accessing preferences via attributes of the
+    `brian_prefs` object.
+    
+    Parameters
+    ----------
+    basename : str
+        The name of the preference category. Has to correspond to a key in
+        `BrianGlobalPreferences.pref_register`.
+    all_prefs : `BrianGlobalPreferences`
+        A reference to the main object storing the preferences.
+    '''
+    
+    def __init__(self, basename, all_prefs):
+        self._basename = basename
+        self._all_prefs = all_prefs
+        self._subcategories = [key for key in all_prefs.pref_register.iterkeys()
+                              if key.startswith(basename + '.')]
+        self._preferences = all_prefs.pref_register[basename][0].keys()
+        self.__doc__ = all_prefs.get_documentation(basename=basename,
+                                                   link_targets=False)
+
+    _sub_preferences = property(lambda self: [pref[len(self._basename+'.'):] for pref in self._all_prefs
+                                              if pref.startswith(self._basename+'.')],
+                                doc='All preferences in this category and its subcategories')
+
+    def __getitem__(self, item):
+        return self._all_prefs[self._basename + '.' + item]
+
+    def __setitem__(self, item, value):
+        self._all_prefs[self._basename + '.' + item] = value
+
+    def __delitem__(self, item):
+        raise PreferenceError("Preferences cannot be deleted.")
+    
+    def __len__(self):
+        return len(self._sub_preferences)
+    
+    def __iter__(self):
+        return iter(self._sub_preferences)
+
+    def __contains__(self, item):
+        return item in self._sub_preferences
+
+    def __getattr__(self, name):
+        return getattr(self._all_prefs, self._basename + '.' + name)
+
+    def __setattr__(self, name, value):
+        # Names starting with an underscore are not preferences but normal
+        # instance attributes
+        if name.startswith('_'):
+            MutableMapping.__setattr__(self, name, value)
+        else:
+            self._all_prefs[self._basename + '.' + name] = value
+
+    def __delattr__(self, name):
+        # Names starting with an underscore are not preferences but normal
+        # instance attributes
+        if name.startswith('_'):
+            MutableMapping.__delattr__(self, name)
+        else:
+            del self._all_prefs[self._basename + '.' + name]
+
+    def __dir__(self):
+        res = dir(type(self)) + self.__dict__.keys()
+        res.extend(self._preferences)
+        res.extend([category[len(self._basename+'.'):]
+                    for category in self._subcategories])
+        return res
+    
+    def __repr__(self):
+        description = '<{classname} for preference category "{category}">'
+        return description.format(classname=self.__class__.__name__,
+                                  category=self._basename)
+
 # : Object storing Brian's preferences
 brian_prefs = BrianGlobalPreferences()
diff --git a/brian2/tests/test_preferences.py b/brian2/tests/test_preferences.py
index b2d24221..bfcd854c 100644
--- a/brian2/tests/test_preferences.py
+++ b/brian2/tests/test_preferences.py
@@ -4,7 +4,7 @@
 from brian2 import restore_initial_state, volt, amp
 from brian2.core.preferences import (DefaultValidator, BrianPreference,
                                      BrianGlobalPreferences, PreferenceError,
-                                     )
+                                     BrianGlobalPreferencesView)
 
 from numpy.testing import assert_equal, assert_raises
 from nose import with_setup
@@ -33,14 +33,53 @@ def test_brianpreference():
     assert pref.default==1./3
     assert pref.representor(pref.default)==repr(1./3)
 
+@with_setup(teardown=restore_initial_state)
+def test_preference_name_checking():
+    '''
+    Test that you cannot set illegal preference names.
+    '''
+    gp = BrianGlobalPreferences()
     
+    # Name that starts with an underscore
+    assert_raises(PreferenceError, lambda: gp.register_preferences('dummy', 'dummy doc',
+                                                                   _notalegalname=BrianPreference(True, 'some preference')
+                                                                   ))
+
+    # Name that clashes with a method name
+    assert_raises(PreferenceError, lambda: gp.register_preferences('dummy', 'dummy doc',
+                                                                   update=BrianPreference(True, 'some preference')
+                                                                   ))
+                   
+    gp.register_preferences('a', 'dummy doc',
+                            b=BrianPreference(True, 'some preference'))
+    
+    #Trying to register a subcategory that would shadow a preference
+    assert_raises(PreferenceError, lambda: gp.register_preferences('a.b', 'dummy doc',
+                                                                   name=BrianPreference(True, 'some preference')
+                                                                   ))
+
+
+    gp.register_preferences('b.c', 'dummy doc',
+                            name=BrianPreference(True, 'some preference'))
+        
+    #Trying to register a preference that clashes with an existing category
+    assert_raises(PreferenceError, lambda: gp.register_preferences('b', 'dummy doc',
+                                                                   c=BrianPreference(True, 'some preference')
+                                                                   ))
+
+
 @with_setup(teardown=restore_initial_state)
 def test_brianglobalpreferences():
     # test that pre-setting a nonexistent preference in a subsequently
     # existing base name raises an error at the correct point
     gp = BrianGlobalPreferences()
-    gp['a.b'] = 5
-    gp['a.c'] = 5
+    
+    # This shouldn't work, in user code only registered preferences can be set
+    assert_raises(PreferenceError, lambda: gp.__setitem__('a.b', 5))
+    
+    # This uses the method that is used when reading preferences from a file
+    gp._set_preference('a.b', 5)
+    gp._set_preference('a.c', 5)
     assert_raises(PreferenceError, gp.register_preferences, 'a', 'docs for a',
                   b=BrianPreference(5, 'docs for b'))
     # test that post-setting a nonexistent preference in an existing base
@@ -51,7 +90,7 @@ def test_brianglobalpreferences():
     assert_raises(PreferenceError, gp.__setitem__, 'a.c', 5)
     # Test pre and post-setting some correct names but valid and invalid values
     gp = BrianGlobalPreferences()
-    gp['a.b'] = 5
+    gp._set_preference('a.b', 5)
     gp.register_preferences('a', 'docs for a',
         b=BrianPreference(5, 'docs for b'),
         c=BrianPreference(1*volt, 'docs for c'),
@@ -120,12 +159,117 @@ def test_brianglobalpreferences():
     # check that load_preferences works, but nothing about its values
     gp = BrianGlobalPreferences()
     gp.load_preferences()
+
+@with_setup(teardown=restore_initial_state)
+def test_preference_name_access():
+    '''
+    Test various ways of accessing preferences
+    '''
+    
+    gp = BrianGlobalPreferences()
+    
+    gp.register_preferences('main', 'main category',
+                            name=BrianPreference(True, 'some preference'))    
+    gp.register_preferences('main.sub', 'subcategory',
+                            name2=BrianPreference(True, 'some preference'))
+    
+    # Keyword based access
+    assert gp['main.name']
+    assert gp['main.sub.name2']
+    gp['main.name'] = False
+    gp['main.sub.name2'] = False
+    
+    # Attribute based access
+    assert not gp.main.name  # we set it to False above
+    assert not gp.main.sub.name2
+    gp.main.name = True
+    gp.main.sub.name2 = True
+    
+    # Mixed access
+    assert gp.main['name']
+    assert gp['main'].name
+    assert gp.main['sub'].name2
+    assert gp['main'].sub['name2']
+    
+    # Accessing categories
+    assert isinstance(gp['main'], BrianGlobalPreferencesView)
+    assert isinstance(gp['main.sub'], BrianGlobalPreferencesView)
+    assert isinstance(gp.main, BrianGlobalPreferencesView)
+    assert isinstance(gp.main.sub, BrianGlobalPreferencesView)
+    
+    # Setting categories shouldn't work
+    assert_raises(PreferenceError, lambda: gp.__setitem__('main', None))
+    assert_raises(PreferenceError, lambda: gp.__setattr__('main', None))
+    assert_raises(PreferenceError, lambda: gp.main.__setitem__('sub', None))
+    assert_raises(PreferenceError, lambda: gp.main.__setattr__('sub', None))
+    
+    # Neither should deleting categories or preferences
+    assert_raises(PreferenceError, lambda: gp.__delitem__('main'))
+    assert_raises(PreferenceError, lambda: gp.__delattr__('main'))
+    assert_raises(PreferenceError, lambda: gp.main.__delitem__('name'))
+    assert_raises(PreferenceError, lambda: gp.main.__delattr__('name'))
+    assert_raises(PreferenceError, lambda: gp.main.__delitem__('sub'))
+    assert_raises(PreferenceError, lambda: gp.main.__delattr__('sub'))
+    
+    #Errors for accessing non-existing preferences
+    assert_raises(KeyError, lambda: gp['main.doesnotexist'])
+    assert_raises(KeyError, lambda: gp['nonexisting.name'])
+    assert_raises(KeyError, lambda: gp.main.doesnotexist)
+    assert_raises(KeyError, lambda: gp.nonexisting.name)
+
+    # Check dictionary functionality
+    for name, value in gp.iteritems():
+        assert gp[name] == value
+    
+    for name, value in gp.main.iteritems():
+        assert gp.main[name] == value
+    
+    assert len(gp) == 2  # two preferences in total
+    assert len(gp['main']) == 2  # both preferences are in the main category
+    assert len(gp['main.sub']) == 1  # one preference in main.sub
+    
+    assert 'main.name' in gp
+    assert 'name' in gp['main']
+    assert 'name2' in gp['main.sub']
+    assert not 'name' in gp['main.sub']
+    
+    gp['main.name'] = True
+    gp.update({'main.name': False})
+    assert not gp['main.name']
+    
+    gp.main.update({'name': True})
+    assert gp['main.name']
+    
+    # Class based functionality
+    assert 'main' in dir(gp)
+    assert 'sub' in dir(gp.main)
+    assert 'name' in dir(gp.main)
+
+    # Check that the fiddling with getattr and setattr did not destroy the
+    # access to standard attributes
+    assert len(gp.prefs)
+    assert gp.main._basename == 'main'
+
+
+@with_setup(teardown=restore_initial_state)
+def test_str_repr():
+    # Just test whether str and repr do not throw an error and return something
+    gp = BrianGlobalPreferences()    
+    gp.register_preferences('main', 'main category',
+                            name=BrianPreference(True, 'some preference'))
     
+    assert len(str(gp))
+    assert len(repr(gp))
+    assert len(str(gp.main))
+    assert len(repr(gp.main))
 
+   
 if __name__=='__main__':
     for t in [test_defaultvalidator,
               test_brianpreference,
               test_brianglobalpreferences,
+              test_preference_name_checking,
+              test_preference_name_access
               ]:
         t()
         restore_initial_state()
diff --git a/brian2/utils/logger.py b/brian2/utils/logger.py
index 8fcab6ff..f13302ff 100644
--- a/brian2/utils/logger.py
+++ b/brian2/utils/logger.py
@@ -1,5 +1,9 @@
 '''
 Brian's logging module.
+
+Preferences
+--------------------
+.. document_brian_prefs:: logging
 '''
 import atexit
 import logging
@@ -15,12 +19,72 @@
 import sympy
 
 import brian2
-from brian2.core.preferences import brian_prefs
+from brian2.core.preferences import brian_prefs, BrianPreference
 
 from .environment import running_from_ipython
 
 __all__ = ['get_logger', 'BrianLogger']
 
+#===============================================================================
+# Logging preferences
+#===============================================================================
+
+def log_level_validator(log_level):
+    log_levels = ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG')
+    return log_level.upper() in log_levels
+
+
+brian_prefs.register_preferences('logging', 'Logging system preferences',
+    delete_log_on_exit=BrianPreference(
+        default=True,
+        docs=    '''
+        Whether to delete the log and script file on exit.
+        
+        If set to ``True`` (the default), log files (and the copy of the main
+        script) will be deleted after the brian process has exited, unless an
+        uncaught exception occured. If set to ``False``, all log files will be kept.
+        ''',
+        ),
+    file_log_level=BrianPreference(
+        default='DEBUG',
+        docs='''
+        What log level to use for the log written to the log file.
+        
+        In case file logging is activated (see `logging.file_log`), which log
+        level should be used for logging. Has to be one of CRITICAL, ERROR,
+        WARNING, INFO or DEBUG.
+        ''',
+        validator=log_level_validator),
+    console_log_level=BrianPreference(
+        default='WARNING',
+        docs='''
+        What log level to use for the log written to the console.
+        
+        Has to be one of CRITICAL, ERROR, WARNING, INFO or DEBUG.
+        ''',
+        validator=log_level_validator),
+    file_log=BrianPreference(
+        default=True,
+        docs= '''
+        Whether to log to a file or not.
+        
+        If set to ``True`` (the default), logging information will be written
+        to a file. The log level can be set via the `logging.file_log_level`
+        preference.
+        '''),
+    save_script=BrianPreference(
+        default=True,
+        docs= '''
+        Whether to save a copy of the script that is run.
+        
+        If set to ``True`` (the default), a copy of the currently run script
+        is saved to a temporary location. It is deleted after a successful
+        run (unless `logging.delete_log_on_exit` is ``False``) but is kept after
+        an uncaught exception occured. This can be helpful for debugging,
+        in particular when several simulations are running in parallel.
+        ''')
+    )
+
 #===============================================================================
 # Initial setup
 #===============================================================================
@@ -34,43 +98,45 @@ def _encode(text):
 logger.setLevel(logging.DEBUG)
 
 # Log to a file
-try:
-    # Temporary filename used for logging
-    TMP_LOG = tempfile.NamedTemporaryFile(prefix='brian_debug_',
-                                          suffix='.log', delete=False)
-    TMP_LOG = TMP_LOG.name
-    FILE_HANDLER = logging.FileHandler(TMP_LOG, mode='wt')
-    FILE_HANDLER.setLevel(logging.DEBUG)
-    FILE_HANDLER.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(name)s: %(message)s'))
-    logger.addHandler(FILE_HANDLER)
-except IOError as ex:
-    warn('Could not create log file: %s' % ex)
-    TMP_LOG = None
+TMP_LOG = None
+if brian_prefs['logging.file_log']:    
+    try:
+        # Temporary filename used for logging
+        TMP_LOG = tempfile.NamedTemporaryFile(prefix='brian_debug_',
+                                              suffix='.log', delete=False)
+        TMP_LOG = TMP_LOG.name
+        FILE_HANDLER = logging.FileHandler(TMP_LOG, mode='wt')
+        FILE_HANDLER.setLevel(brian_prefs['logging.file_log_level'].upper())
+        FILE_HANDLER.setFormatter(logging.Formatter('%(asctime)s %(levelname)-8s %(name)s: %(message)s'))
+        logger.addHandler(FILE_HANDLER)
+    except IOError as ex:
+        warn('Could not create log file: %s' % ex)
 
 # Save a copy of the script
 TMP_SCRIPT = None
-if len(sys.argv[0]) and not running_from_ipython():
-    try:
-        tmp_file = tempfile.NamedTemporaryFile(prefix='brian_script_',
-                                               suffix='.py',
-                                               delete=False)
-        with tmp_file:
-            # Timestamp
-            tmp_file.write(_encode(u'# %s\n' % time.asctime()))
-            # Command line arguments
-            tmp_file.write(_encode(u'# Run as: %s\n\n' % (' '.join(sys.argv))))
-            # The actual script file
-            # TODO: We are copying the script file as it is, this might clash
-            # with the encoding we used for the comments added above
-            with open(os.path.abspath(sys.argv[0]), 'rb') as script_file:
-                shutil.copyfileobj(script_file, tmp_file)    
-            TMP_SCRIPT = tmp_file.name
-    except IOError as ex:
-        warn('Could not copy script file to temp directory: %s' % ex)
+if brian_prefs['logging.save_script']:
+    if len(sys.argv[0]) and not running_from_ipython():
+        try:
+            tmp_file = tempfile.NamedTemporaryFile(prefix='brian_script_',
+                                                   suffix='.py',
+                                                   delete=False)
+            with tmp_file:
+                # Timestamp
+                tmp_file.write(_encode(u'# %s\n' % time.asctime()))
+                # Command line arguments
+                tmp_file.write(_encode(u'# Run as: %s\n\n' % (' '.join(sys.argv))))
+                # The actual script file
+                # TODO: We are copying the script file as it is, this might clash
+                # with the encoding we used for the comments added above
+                with open(os.path.abspath(sys.argv[0]), 'rb') as script_file:
+                    shutil.copyfileobj(script_file, tmp_file)    
+                TMP_SCRIPT = tmp_file.name
+        except IOError as ex:
+            warn('Could not copy script file to temp directory: %s' % ex)
 
 # create console handler with a higher log level
 CONSOLE_HANDLER = logging.StreamHandler()
-CONSOLE_HANDLER.setLevel(logging.WARN)
+CONSOLE_HANDLER.setLevel(brian_prefs['logging.console_log_level'])
 CONSOLE_HANDLER.setFormatter(logging.Formatter('%(levelname)-8s %(name)s: %(message)s'))
 
 # add the handler to the logger
@@ -123,7 +189,7 @@ def clean_up_logging():
     occured.
     '''
     logging.shutdown()
-    if not BrianLogger.exception_occured and brian_prefs['core.delete_log_on_exit']:
+    if not BrianLogger.exception_occured and brian_prefs['logging.delete_log_on_exit']:
         if not TMP_LOG is None:
             try:
                 os.remove(TMP_LOG)
diff --git a/docs_sphinx/developer/preferences.rst b/docs_sphinx/developer/preferences.rst
index 19569eb9..770771f2 100644
--- a/docs_sphinx/developer/preferences.rst
+++ b/docs_sphinx/developer/preferences.rst
@@ -1,6 +1,8 @@
 Preferences system
 ==================
 
+.. currentmodule:: brian2.core.preferences
+
 Each preference looks like ``codegen.c.compiler``, i.e. dotted names. Each
 preference has to be registered and validated. The idea is that registering
 all preferences ensures that misspellings of a preference value by a user
@@ -13,7 +15,27 @@ An additional requirement is that the preferences system allows for extension
 modules to define their own preferences, including extending the existing
 core brian preferences. For example, an extension might want to define
 ``extension.*`` but it might also want to define a new language for
-codegen, e.g. ``codegen.lisp.*``.
+codegen, e.g. ``codegen.lisp.*``. However, extensions cannot add preferences
+to an existing category.
+
+Accessing and setting preferences
+---------------------------------
+Preferences can be accessed and set either keyword-based or attribute-based.
+To set/get the value for the preference example mentioned before, the following
+are equivalent::
+
+    brian_prefs['codegen.c.compiler'] = 'gcc'
+    brian_prefs.codegen.c.compiler = 'gcc'
+    
+    if brian_prefs['codegen.c.compiler'] == 'gcc':
+        ...
+    if brian_prefs.codegen.c.compiler == 'gcc':
+        ...
+
+Using the attribute-based form can be particulary useful for interactive
+work, e.g. in ipython, as it offers autocompletion and documentation.
+In ipython, ``brian_prefs.codegen.c?`` would display a docstring with all
+the preferences available in the ``codegen.c`` category.
 
 Preference files
 ----------------
@@ -30,50 +52,46 @@ if one is missing):
 Registration
 ------------
 
-Registration of preferences is performed by a call to `register_preferences`,
-e.g.::
+Registration of preferences is performed by a call to
+`BrianGlobalPreferences.register_preferences`, e.g.::
 
     register_preferences(
         'codegen.c',
-        {'compiler': BrianPreference(
+        'Code generation preferences for the C language',
+        'compiler'= BrianPreference(
             validator=is_compiler,
             docs='...',
             default='gcc'),
          ...
-        })
+        )
         
 The first argument ``'codegen.c'`` is the base name, and every preference of
-the form ``codegen.c.*`` has to be registered by this function (although you
-can also register, e.g. ``codegen.c.somethingelse.*`` separately). In other
-words, by calling `register_preferences`, a module takes ownership of all
-the preferences with one particular base name.
-
-The second argument is a dictionary of name/definition pairs in a fairly
-obvious way. Alternatively, we could make it look like::
-
-    register_preferences(
-        'codegen.c',
-        compiler=BrianPreference(
-            validator=is_compiler,
-            docs='...',
-            default='gcc'),
-        ...
-        )
+the form ``codegen.c.*`` has to be registered by this function (preferences in subcategories
+such as ``codegen.c.somethingelse.*`` have to be specified separately). In other
+words, by calling `~BrianGlobalPreferences.register_preferences`,
+a module takes ownership of all the preferences with one particular base name. The second argument
+is a descriptive text explaining what this category is about. The preferences themselves are
+provided as keyword arguments, each set to a `BrianPreference` object.
 
 Validation functions
 --------------------
 
-A validation function takes a string as an argument, and returns a value (e.g.
-int or string). If the string is invalid, it raises a
-`PreferenceValidationError` exception.
+A validation function takes a value for the preference and returns ``True`` (if the value is a valid
+value) or ``False``. If no validation function is specified, a default validator is used that
+compares the value against the default value: Both should belong to the same class (e.g. int or
+str) and, in the case of a `Quantity` have the same unit.
 
 Validation
 ----------
 
 Setting the value of a preference with a registered base name instantly triggers
-validation. For base names that are not yet registered, validation occurs when
+validation. Trying to set an unregistered preference using keyword or attribute access raises an
+error. The only exception from this rule is when the preferences are read from configuration files
+(see below). Since this happens before the user has the chance to import extensions that potentially
+define new preferences, this uses a special function (`_set_preference`). In this case,for base
+names that are not yet registered, validation occurs when
 the base name is registered. If, at the time that `Network.run` is called, there
-are unregistered preferences set, a `PreferenceValidationError` is raised.
+are unregistered preferences set, a `PreferenceError` is raised.
 
 File format
 -----------
@@ -94,4 +112,4 @@ Built-in preferences
 Brian itself defines the following preferences:
 
 .. document_brian_prefs::
-   :nolinks:
\ No newline at end of file
+   :nolinks:
diff --git a/setup.py b/setup.py
index 48d977c2..c8018ecb 100644
--- a/setup.py
+++ b/setup.py
@@ -3,6 +3,7 @@
 Preliminary Brian2 setup script
 '''
 import sys
+import os
 
 from distutils.core import setup
 try:
@@ -10,26 +11,24 @@
 except ImportError:
     from distutils.command.build_py import build_py
 
-from distutils.command.install_data import install_data
-
-class install_preferences(install_data):
-    def run(self):        
-        # Make sure we load the brian2 packages from the installation
-        # directory, not from the source directory (important for Python 3)
-        sys.path.insert(0, self.install_purelib)
+class generate_preferences(build_py):
+    def run(self):
+        # Make sure we first run the build (including running 2to3 for Python3)
+        # and then import from the build directory
+        build_py.run(self)
+        sys.path.insert(0, self.build_lib)
         from brian2.core.preferences import brian_prefs
         
+        # We generate the file directly in the build directory
         try:
-            with open('./brian2/default_preferences', 'wt') as f:
+            with open(os.path.join(self.build_lib,
+                                   'brian2', 'default_preferences'), 'wt') as f:
                 defaults = brian_prefs.defaults_as_file
                 f.write(defaults)
         except IOError as ex:
             raise IOError(('Could not write the default preferences to a '
                            'file: %s' % str(ex)))
-        
-        # Now, run the original install_data command which copies the
-        # generated file to the appropriate place
-        install_data.run(self)
+
 
 setup(name='Brian2',
       version='2.0dev',
@@ -47,11 +46,9 @@ def run(self):
                 'brian2.tests',
                 'brian2.units',
                 'brian2.utils'],
-      package_data={'brian2': ['default_preferences']}, 
       requires=['numpy(>=1.4.1)',
                 'scipy(>=0.7.0)',
                 'sympy(>=0.7.1)'
                 ],
-      cmdclass = {'build_py': build_py,
-                  'install_data': install_preferences}                
+      cmdclass = {'build_py': generate_preferences}                
      )
['docs_sphinx/developer/preferences.rst', 'brian2/tests/test_preferences.py', 'brian2/utils/logger.py', 'setup.py', 'brian2/core/preferences.py', 'brian2/core/core_preferences.py', '.travis.yml']