Changed to Django1.7. Django 1.6 no longer supported. To change, upgrade django to 1.7+ and then run manage.py migrate, possibly followed by manage.py migrate --fake for objects and players.

This commit is contained in:
Griatch 2014-09-17 10:49:42 +02:00
parent 1fc91f85ea
commit bb36f2cb76
20 changed files with 431 additions and 14 deletions

View file

@ -14,6 +14,7 @@ import sys
import signal
from optparse import OptionParser
from subprocess import Popen
import django
# Set the Python path up so we can get to settings.py from here.
from django.core import management
@ -29,6 +30,9 @@ if not os.path.exists('settings.py'):
import game.manage
sys.exit()
# required since django1.7.
django.setup()
# signal processing
SIG = signal.SIGINT

View file

@ -19,6 +19,7 @@ import sys
from optparse import OptionParser
from subprocess import Popen
import Queue, thread
import django
try:
# check if launched with pypy
@ -45,6 +46,8 @@ if not os.path.exists('settings.py'):
print "No settings.py file found. Run evennia.py to create it."
sys.exit()
django.setup()
# Get the settings
from django.conf import settings

View file

@ -0,0 +1,44 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='ChannelDB',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_key', models.CharField(max_length=255, verbose_name=b'key', db_index=True)),
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
],
options={
'verbose_name': 'Channel',
'verbose_name_plural': 'Channels',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Msg',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_sender_external', models.CharField(help_text=b"identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).", max_length=255, null=True, verbose_name=b'external sender', db_index=True)),
('db_header', models.TextField(null=True, verbose_name=b'header', blank=True)),
('db_message', models.TextField(verbose_name=b'messsage')),
('db_date_sent', models.DateTimeField(auto_now_add=True, verbose_name=b'date sent', db_index=True)),
('db_lock_storage', models.TextField(help_text=b'access locks on this message.', verbose_name=b'locks', blank=True)),
('db_hide_from_channels', models.ManyToManyField(related_name=b'hide_from_channels_set', null=True, to='comms.ChannelDB')),
],
options={
'verbose_name': 'Message',
},
bases=(models.Model,),
),
]

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('objects', '0001_initial'),
('comms', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='msg',
name='db_hide_from_objects',
field=models.ManyToManyField(related_name=b'hide_from_objects_set', null=True, to='objects.ObjectDB'),
preserve_default=True,
),
]

View file

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('objects', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('typeclasses', '0001_initial'),
('comms', '0002_msg_db_hide_from_objects'),
]
operations = [
migrations.AddField(
model_name='msg',
name='db_hide_from_players',
field=models.ManyToManyField(related_name=b'hide_from_players_set', null=True, to=settings.AUTH_USER_MODEL),
preserve_default=True,
),
migrations.AddField(
model_name='msg',
name='db_receivers_channels',
field=models.ManyToManyField(help_text=b'channel recievers', related_name=b'channel_set', null=True, to='comms.ChannelDB'),
preserve_default=True,
),
migrations.AddField(
model_name='msg',
name='db_receivers_objects',
field=models.ManyToManyField(help_text=b'object receivers', related_name=b'receiver_object_set', null=True, to='objects.ObjectDB'),
preserve_default=True,
),
migrations.AddField(
model_name='msg',
name='db_receivers_players',
field=models.ManyToManyField(help_text=b'player receivers', related_name=b'receiver_player_set', null=True, to=settings.AUTH_USER_MODEL),
preserve_default=True,
),
migrations.AddField(
model_name='msg',
name='db_sender_objects',
field=models.ManyToManyField(related_name=b'sender_object_set', null=True, verbose_name=b'sender(object)', to='objects.ObjectDB', db_index=True),
preserve_default=True,
),
migrations.AddField(
model_name='msg',
name='db_sender_players',
field=models.ManyToManyField(related_name=b'sender_player_set', null=True, verbose_name=b'sender(player)', to=settings.AUTH_USER_MODEL, db_index=True),
preserve_default=True,
),
migrations.AddField(
model_name='channeldb',
name='db_attributes',
field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True),
preserve_default=True,
),
migrations.AddField(
model_name='channeldb',
name='db_subscriptions',
field=models.ManyToManyField(related_name=b'subscription_set', null=True, verbose_name=b'subscriptions', to=settings.AUTH_USER_MODEL, db_index=True),
preserve_default=True,
),
migrations.AddField(
model_name='channeldb',
name='db_tags',
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True),
preserve_default=True,
),
]

View file

@ -11,6 +11,8 @@ class HelpEntryForm(forms.ModelForm):
"Defines how to display the help entry"
class Meta:
model = HelpEntry
fields = '__all__'
db_help_category = forms.CharField(label="Help category", initial='General',
help_text="organizes help entries in lists")
db_lock_storage = forms.CharField(label="Locks", initial='view:all()',required=False,

View file

@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('typeclasses', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='HelpEntry',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_key', models.CharField(help_text=b'key to search for', unique=True, max_length=255, verbose_name=b'help key')),
('db_help_category', models.CharField(default=b'General', help_text=b'organizes help entries in lists', max_length=255, verbose_name=b'help category')),
('db_entrytext', models.TextField(help_text=b'the main body of help text', verbose_name=b'help entry', blank=True)),
('db_lock_storage', models.TextField(help_text=b'normally view:all().', verbose_name=b'locks', blank=True)),
('db_staff_only', models.BooleanField(default=False)),
('db_tags', models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True)),
],
options={
'verbose_name': 'Help Entry',
'verbose_name_plural': 'Help Entries',
},
bases=(models.Model,),
),
]

View file

@ -22,6 +22,7 @@ class ObjectCreateForm(forms.ModelForm):
"This form details the look of the fields"
class Meta:
model = ObjectDB
fields = '__all__'
db_key = forms.CharField(label="Name/Key",
widget=forms.TextInput(attrs={'size': '78'}),
help_text="Main identifier, like 'apple', 'strong guy', 'Elizabeth' etc. If creating a Character, check so the name is unique among characters!",)
@ -40,6 +41,8 @@ class ObjectCreateForm(forms.ModelForm):
class ObjectEditForm(ObjectCreateForm):
"Form used for editing. Extends the create one with more fields"
class Meta:
fields = '__all__'
db_lock_storage = forms.CharField(label="Locks",
required=False,
widget=forms.Textarea(attrs={'cols':'100', 'rows':'2'}),

View file

@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('typeclasses', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ObjectDB',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_key', models.CharField(max_length=255, verbose_name=b'key', db_index=True)),
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
('db_sessid', models.CommaSeparatedIntegerField(help_text=b'csv list of session ids of connected Player, if any.', max_length=32, null=True, verbose_name=b'session id')),
('db_cmdset_storage', models.CharField(help_text=b'optional python path to a cmdset class.', max_length=255, null=True, verbose_name=b'cmdset', blank=True)),
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
('db_destination', models.ForeignKey(related_name=b'destinations_set', on_delete=django.db.models.deletion.SET_NULL, blank=True, to='objects.ObjectDB', help_text=b'a destination, used only by exit objects.', null=True, verbose_name=b'destination')),
('db_home', models.ForeignKey(related_name=b'homes_set', on_delete=django.db.models.deletion.SET_NULL, verbose_name=b'home location', blank=True, to='objects.ObjectDB', null=True)),
('db_location', models.ForeignKey(related_name=b'locations_set', on_delete=django.db.models.deletion.SET_NULL, verbose_name=b'game location', blank=True, to='objects.ObjectDB', null=True)),
],
options={
'verbose_name': 'Object',
'verbose_name_plural': 'Objects',
},
bases=(models.Model,),
),
]

View file

@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.db.models.deletion
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('objects', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('typeclasses', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='objectdb',
name='db_player',
field=models.ForeignKey(on_delete=django.db.models.deletion.SET_NULL, verbose_name=b'player', to=settings.AUTH_USER_MODEL, help_text=b'a Player connected to this object, if any.', null=True),
preserve_default=True,
),
migrations.AddField(
model_name='objectdb',
name='db_tags',
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True),
preserve_default=True,
),
]

View file

View file

@ -18,6 +18,7 @@ class PlayerDBChangeForm(UserChangeForm):
class Meta:
model = PlayerDB
fields = '__all__'
username = forms.RegexField(
label="Username",
@ -45,6 +46,7 @@ class PlayerDBCreationForm(UserCreationForm):
class Meta:
model = PlayerDB
fields = '__all__'
username = forms.RegexField(
label="Username",
@ -72,6 +74,7 @@ class PlayerForm(forms.ModelForm):
"""
class Meta:
model = PlayerDB
fields = '__all__'
db_key = forms.RegexField(
label="Username",

View file

@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import django.utils.timezone
import django.core.validators
class Migration(migrations.Migration):
dependencies = [
('auth', '0001_initial'),
('typeclasses', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='PlayerDB',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(default=django.utils.timezone.now, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(help_text='Required. 30 characters or fewer. Letters, digits and @/./+/-/_ only.', unique=True, max_length=30, verbose_name='username', validators=[django.core.validators.RegexValidator('^[\\w.@+-]+$', 'Enter a valid username.', 'invalid')])),
('first_name', models.CharField(max_length=30, verbose_name='first name', blank=True)),
('last_name', models.CharField(max_length=30, verbose_name='last name', blank=True)),
('email', models.EmailField(max_length=75, verbose_name='email address', blank=True)),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('db_key', models.CharField(max_length=255, verbose_name=b'key', db_index=True)),
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
('db_is_connected', models.BooleanField(default=False, help_text=b'If player is connected to game or not', verbose_name=b'is_connected')),
('db_cmdset_storage', models.CharField(help_text=b'optional python path to a cmdset class. If creating a Character, this will default to settings.CMDSET_CHARACTER.', max_length=255, null=True, verbose_name=b'cmdset')),
('db_is_bot', models.BooleanField(default=False, help_text=b'Used to identify irc/imc2/rss bots', verbose_name=b'is_bot')),
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
('db_tags', models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True)),
('groups', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Group', blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of his/her group.', verbose_name='groups')),
('user_permissions', models.ManyToManyField(related_query_name='user', related_name='user_set', to='auth.Permission', blank=True, help_text='Specific permissions for this user.', verbose_name='user permissions')),
],
options={
'verbose_name': 'Player',
'verbose_name_plural': 'Players',
},
bases=(models.Model,),
),
]

View file

@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
('objects', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('typeclasses', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='ScriptDB',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_key', models.CharField(max_length=255, verbose_name=b'key', db_index=True)),
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
('db_desc', models.CharField(max_length=255, verbose_name=b'desc', blank=True)),
('db_interval', models.IntegerField(default=-1, help_text=b'how often to repeat script, in seconds. -1 means off.', verbose_name=b'interval')),
('db_start_delay', models.BooleanField(default=False, help_text=b'pause interval seconds before starting.', verbose_name=b'start delay')),
('db_repeats', models.IntegerField(default=0, help_text=b'0 means off.', verbose_name=b'number of repeats')),
('db_persistent', models.BooleanField(default=False, verbose_name=b'survive server reboot')),
('db_is_active', models.BooleanField(default=False, verbose_name=b'script active')),
('db_attributes', models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True)),
('db_obj', models.ForeignKey(blank=True, to='objects.ObjectDB', help_text=b'the object to store this script on, if not a global script.', null=True, verbose_name=b'scripted object')),
('db_player', models.ForeignKey(blank=True, to=settings.AUTH_USER_MODEL, help_text=b'the player to store this script on (should not be set if obj is set)', null=True, verbose_name=b'scripted player')),
('db_tags', models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True)),
],
options={
'verbose_name': 'Script',
},
bases=(models.Model,),
),
]

View file

@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='ServerConfig',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_key', models.CharField(unique=True, max_length=64)),
('db_value', models.TextField(blank=True)),
],
options={
'verbose_name': 'Server Config value',
'verbose_name_plural': 'Server Config values',
},
bases=(models.Model,),
),
]

View file

@ -18,6 +18,10 @@ from src.server.webserver import EvenniaReverseProxyResource
from twisted.application import internet, service
from twisted.internet import protocol, reactor
from twisted.web import server
import django
django.setup()
from django.conf import settings
from src.utils.utils import get_evennia_version, mod_import, make_iter
from src.server.portal.portalsessionhandler import PORTAL_SESSIONS

View file

@ -18,6 +18,8 @@ from twisted.web import server, static
from twisted.application import internet, service
from twisted.internet import reactor, defer
import django
django.setup()
from django.db import connection
from django.conf import settings

View file

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
import src.utils.picklefield
class Migration(migrations.Migration):
dependencies = [
]
operations = [
migrations.CreateModel(
name='Attribute',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_key', models.CharField(max_length=255, verbose_name=b'key', db_index=True)),
('db_value', src.utils.picklefield.PickledObjectField(help_text=b'The data returned when the attribute is accessed. Must be written as a Python literal if editing through the admin interface. Attribute values which are not Python literals cannot be edited through the admin interface.', null=True, verbose_name=b'value')),
('db_strvalue', models.TextField(help_text=b'String-specific storage for quick look-up', null=True, verbose_name=b'strvalue', blank=True)),
('db_category', models.CharField(max_length=128, blank=True, help_text=b'Optional categorization of attribute.', null=True, verbose_name=b'category', db_index=True)),
('db_lock_storage', models.TextField(help_text=b'Lockstrings for this object are stored here.', verbose_name=b'locks', blank=True)),
('db_model', models.CharField(max_length=32, blank=True, help_text=b'Which model of object this attribute is attached to (A natural key like objects.dbobject). You should not change this value unless you know what you are doing.', null=True, verbose_name=b'model', db_index=True)),
('db_attrtype', models.CharField(max_length=16, blank=True, help_text=b'Subclass of Attribute (None or nick)', null=True, verbose_name=b'attrtype', db_index=True)),
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'date_created')),
],
options={
'verbose_name': 'Evennia Attribute',
},
bases=(models.Model,),
),
migrations.CreateModel(
name='Tag',
fields=[
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('db_key', models.CharField(help_text=b'tag identifier', max_length=255, null=True, verbose_name=b'key', db_index=True)),
('db_category', models.CharField(help_text=b'tag category', max_length=64, null=True, verbose_name=b'category', db_index=True)),
('db_data', models.TextField(help_text=b'optional data field with extra information. This is not searched for.', null=True, verbose_name=b'data', blank=True)),
('db_model', models.CharField(help_text=b'database model to Tag', max_length=32, null=True, verbose_name=b'model', db_index=True)),
('db_tagtype', models.CharField(help_text=b'overall type of Tag', max_length=16, null=True, verbose_name=b'tagtype', db_index=True)),
],
options={
'verbose_name': 'Tag',
},
bases=(models.Model,),
),
migrations.AlterUniqueTogether(
name='tag',
unique_together=set([('db_key', 'db_category', 'db_tagtype')]),
),
migrations.AlterIndexTogether(
name='tag',
index_together=set([('db_key', 'db_category', 'db_tagtype')]),
),
]

View file

@ -278,7 +278,7 @@ def to_pickle(data):
return process_item(data)
@transaction.autocommit
#@transaction.autocommit
def from_pickle(data, db_obj=None):
"""
This should be fed a just de-pickled data object. It will be converted back

View file

@ -633,9 +633,8 @@ def check_evennia_dependencies():
nt_python_min = '2.7'
nt_stop_python_min = "2.6"
twisted_min = '11.0'
django_min = '1.5'
django_rec = '1.6'
south_min = '0.8.4'
django_min = '1.7'
django_rec = '1.7'
errstring = ""
no_error = True
@ -667,10 +666,6 @@ def check_evennia_dependencies():
no_error = False
elif django_min <= dversion < django_rec:
errstring += "\n NOTE: Django %s found. This will work, but v%s is recommended for production." % (dversion, django_rec)
elif dversion_main >= '1.7':
errstring += "\n NOTE: Django 1.7+ found. Evennia is not yet tested with this version of django and due " \
"\n to the changes to migrations in 1.7 it is not likely to work yet. Revert to 1.6+ if you have " \
"\n any problems."
elif django_rec < dversion_main:
errstring += "\n NOTE: Django %s found. This is newer than Evennia's recommended version (v%s). It will"
errstring += "\n probably work, but may be new enough not to be fully tested yet. Report any issues." % (dversion, django_rec)
@ -679,12 +674,8 @@ def check_evennia_dependencies():
no_error = False
# South
try:
import south
sversion = south.__version__
if sversion < south_min:
errstring += "\n WARNING: South %s found. Evennia recommends version %s or higher." % (sversion, south_min)
if sversion in ("0.8.2", "0.8.3"):
errstring += "\n ERROR: South %s found. This has known issues. Please upgrade." % sversion
if 'south' in settings.INSTALLED_APPS:
errstring += "\n ERROR: 'south' found in settings.INSTALLED_APPS. South is no longer used. If this was added manually, remove."
no_error = False
except ImportError:
errstring += "\n ERROR: South (django-south) does not seem to be installed."