Made all unit tests pass
This commit is contained in:
parent
844b04adbb
commit
aa48593a40
10 changed files with 148 additions and 110 deletions
|
|
@ -258,12 +258,12 @@ def prototype_from_object(obj):
|
||||||
aliases = obj.aliases.get(return_list=True)
|
aliases = obj.aliases.get(return_list=True)
|
||||||
if aliases:
|
if aliases:
|
||||||
prot['aliases'] = aliases
|
prot['aliases'] = aliases
|
||||||
tags = [(tag.db_key, tag.db_category, tag.db_data)
|
tags = sorted([(tag.db_key, tag.db_category, tag.db_data)
|
||||||
for tag in obj.tags.all(return_objs=True)]
|
for tag in obj.tags.all(return_objs=True)])
|
||||||
if tags:
|
if tags:
|
||||||
prot['tags'] = tags
|
prot['tags'] = tags
|
||||||
attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all()))
|
attrs = sorted([(attr.key, attr.value, attr.category, ';'.join(attr.locks.all()))
|
||||||
for attr in obj.attributes.all()]
|
for attr in obj.attributes.all()])
|
||||||
if attrs:
|
if attrs:
|
||||||
prot['attrs'] = attrs
|
prot['attrs'] = attrs
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -122,9 +122,9 @@ class TestUtils(EvenniaTest):
|
||||||
|
|
||||||
self.assertEqual(obj_prototype,
|
self.assertEqual(obj_prototype,
|
||||||
{'aliases': ['foo'],
|
{'aliases': ['foo'],
|
||||||
'attrs': [('oldtest', 'to_keep', None, ''),
|
'attrs': [('desc', 'changed desc', None, ''),
|
||||||
('test', 'testval', None, ''),
|
('oldtest', 'to_keep', None, ''),
|
||||||
('desc', 'changed desc', None, '')],
|
('test', 'testval', None, '')],
|
||||||
'key': 'Obj',
|
'key': 'Obj',
|
||||||
'home': '#1',
|
'home': '#1',
|
||||||
'location': '#1',
|
'location': '#1',
|
||||||
|
|
@ -213,9 +213,9 @@ class TestUtils(EvenniaTest):
|
||||||
self.assertEqual(count, 1)
|
self.assertEqual(count, 1)
|
||||||
|
|
||||||
new_prot = spawner.prototype_from_object(self.obj1)
|
new_prot = spawner.prototype_from_object(self.obj1)
|
||||||
self.assertEqual({'attrs': [('oldtest', 'to_keep', None, ''),
|
self.assertEqual({'attrs': [('fooattr', 'fooattrval', None, ''),
|
||||||
('fooattr', 'fooattrval', None, ''),
|
|
||||||
('new', 'new_val', None, ''),
|
('new', 'new_val', None, ''),
|
||||||
|
('oldtest', 'to_keep', None, ''),
|
||||||
('test', 'testval_changed', None, '')],
|
('test', 'testval_changed', None, '')],
|
||||||
'home': Something,
|
'home': Something,
|
||||||
'key': 'Obj',
|
'key': 'Obj',
|
||||||
|
|
|
||||||
|
|
@ -1401,7 +1401,7 @@ def create_superuser():
|
||||||
"""
|
"""
|
||||||
print(
|
print(
|
||||||
"\nCreate a superuser below. The superuser is Account #1, the 'owner' "
|
"\nCreate a superuser below. The superuser is Account #1, the 'owner' "
|
||||||
"account of the server.\n")
|
"account of the server. Email is optional and can be empty.\n")
|
||||||
django.core.management.call_command("createsuperuser", interactive=True)
|
django.core.management.call_command("createsuperuser", interactive=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -114,20 +114,20 @@ class TestTelnet(TwistedTestCase):
|
||||||
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'], {0: DEFAULT_WIDTH})
|
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'], {0: DEFAULT_WIDTH})
|
||||||
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'], {0: DEFAULT_HEIGHT})
|
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'], {0: DEFAULT_HEIGHT})
|
||||||
self.proto.dataReceived(IAC + WILL + NAWS)
|
self.proto.dataReceived(IAC + WILL + NAWS)
|
||||||
self.proto.dataReceived([IAC, SB, NAWS, '', 'x', '', 'd', IAC, SE])
|
self.proto.dataReceived(b"".join([IAC, SB, NAWS, b'', b'x', b'', b'd', IAC, SE]))
|
||||||
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'][0], 120)
|
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'][0], 78)
|
||||||
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'][0], 100)
|
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'][0], 45)
|
||||||
self.assertEqual(self.proto.handshakes, 6)
|
self.assertEqual(self.proto.handshakes, 6)
|
||||||
# test ttype
|
# test ttype
|
||||||
self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"])
|
self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"])
|
||||||
self.assertFalse(self.proto.protocol_flags["TTYPE"])
|
self.assertFalse(self.proto.protocol_flags["TTYPE"])
|
||||||
self.assertTrue(self.proto.protocol_flags["ANSI"])
|
self.assertTrue(self.proto.protocol_flags["ANSI"])
|
||||||
self.proto.dataReceived(IAC + WILL + TTYPE)
|
self.proto.dataReceived(IAC + WILL + TTYPE)
|
||||||
self.proto.dataReceived([IAC, SB, TTYPE, IS, "MUDLET", IAC, SE])
|
self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MUDLET", IAC, SE]))
|
||||||
self.assertTrue(self.proto.protocol_flags["XTERM256"])
|
self.assertTrue(self.proto.protocol_flags["XTERM256"])
|
||||||
self.assertEqual(self.proto.protocol_flags["CLIENTNAME"], "MUDLET")
|
self.assertEqual(self.proto.protocol_flags["CLIENTNAME"], "MUDLET")
|
||||||
self.proto.dataReceived([IAC, SB, TTYPE, IS, "XTERM", IAC, SE])
|
self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"XTERM", IAC, SE]))
|
||||||
self.proto.dataReceived([IAC, SB, TTYPE, IS, "MTTS 137", IAC, SE])
|
self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MTTS 137", IAC, SE]))
|
||||||
self.assertEqual(self.proto.handshakes, 5)
|
self.assertEqual(self.proto.handshakes, 5)
|
||||||
# test mccp
|
# test mccp
|
||||||
self.proto.dataReceived(IAC + DONT + MCCP)
|
self.proto.dataReceived(IAC + DONT + MCCP)
|
||||||
|
|
@ -138,7 +138,7 @@ class TestTelnet(TwistedTestCase):
|
||||||
self.assertEqual(self.proto.handshakes, 3)
|
self.assertEqual(self.proto.handshakes, 3)
|
||||||
# test oob
|
# test oob
|
||||||
self.proto.dataReceived(IAC + DO + MSDP)
|
self.proto.dataReceived(IAC + DO + MSDP)
|
||||||
self.proto.dataReceived([IAC, SB, MSDP, MSDP_VAR, "LIST", MSDP_VAL, "COMMANDS", IAC, SE])
|
self.proto.dataReceived(b"".join([IAC, SB, MSDP, MSDP_VAR, b"LIST", MSDP_VAL, b"COMMANDS", IAC, SE]))
|
||||||
self.assertTrue(self.proto.protocol_flags['OOB'])
|
self.assertTrue(self.proto.protocol_flags['OOB'])
|
||||||
self.assertEqual(self.proto.handshakes, 2)
|
self.assertEqual(self.proto.handshakes, 2)
|
||||||
# test mxp
|
# test mxp
|
||||||
|
|
|
||||||
|
|
@ -817,7 +817,7 @@ class TypedObject(SharedMemoryModel):
|
||||||
kwargs={'pk': self.pk, 'slug': slugify(self.name)})
|
kwargs={'pk': self.pk, 'slug': slugify(self.name)})
|
||||||
except:
|
except:
|
||||||
return '#'
|
return '#'
|
||||||
|
|
||||||
def web_get_puppet_url(self):
|
def web_get_puppet_url(self):
|
||||||
"""
|
"""
|
||||||
Returns the URI path for a View that allows users to puppet a specific
|
Returns the URI path for a View that allows users to puppet a specific
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,25 @@ from evennia.utils.test_resources import EvenniaTest
|
||||||
# ------------------------------------------------------------
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
class TestAttributes(EvenniaTest):
|
||||||
|
def test_attrhandler(self):
|
||||||
|
key = 'testattr'
|
||||||
|
value = 'test attr value '
|
||||||
|
self.obj1.attributes.add(key, value)
|
||||||
|
self.assertEqual(self.obj1.attributes.get(key), value)
|
||||||
|
self.obj1.db.testattr = value
|
||||||
|
self.assertEqual(self.obj1.db.testattr, value)
|
||||||
|
|
||||||
|
def test_weird_text_save(self):
|
||||||
|
"test 'weird' text type (different in py2 vs py3)"
|
||||||
|
from django.utils.safestring import SafeText
|
||||||
|
key = 'test attr 2'
|
||||||
|
value = SafeText('test attr value 2')
|
||||||
|
self.obj1.attributes.add(key, value)
|
||||||
|
self.assertEqual(self.obj1.attributes.get(key), value)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TestTypedObjectManager(EvenniaTest):
|
class TestTypedObjectManager(EvenniaTest):
|
||||||
def _manager(self, methodname, *args, **kwargs):
|
def _manager(self, methodname, *args, **kwargs):
|
||||||
return list(getattr(self.obj1.__class__.objects, methodname)(*args, **kwargs))
|
return list(getattr(self.obj1.__class__.objects, methodname)(*args, **kwargs))
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ except ImportError:
|
||||||
from pickle import dumps, loads
|
from pickle import dumps, loads
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.utils.safestring import SafeString, SafeBytes
|
||||||
from evennia.utils.utils import to_str, uses_database, is_iter
|
from evennia.utils.utils import to_str, uses_database, is_iter
|
||||||
from evennia.utils import logger
|
from evennia.utils import logger
|
||||||
|
|
||||||
|
|
@ -521,7 +522,7 @@ def to_pickle(data):
|
||||||
def process_item(item):
|
def process_item(item):
|
||||||
"""Recursive processor and identification of data"""
|
"""Recursive processor and identification of data"""
|
||||||
dtype = type(item)
|
dtype = type(item)
|
||||||
if dtype in (str, int, float, bool):
|
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||||
return item
|
return item
|
||||||
elif dtype == tuple:
|
elif dtype == tuple:
|
||||||
return tuple(process_item(val) for val in item)
|
return tuple(process_item(val) for val in item)
|
||||||
|
|
@ -573,7 +574,7 @@ def from_pickle(data, db_obj=None):
|
||||||
def process_item(item):
|
def process_item(item):
|
||||||
"""Recursive processor and identification of data"""
|
"""Recursive processor and identification of data"""
|
||||||
dtype = type(item)
|
dtype = type(item)
|
||||||
if dtype in (str, int, float, bool):
|
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||||
return item
|
return item
|
||||||
elif _IS_PACKED_DBOBJ(item):
|
elif _IS_PACKED_DBOBJ(item):
|
||||||
# this must be checked before tuple
|
# this must be checked before tuple
|
||||||
|
|
@ -602,7 +603,7 @@ def from_pickle(data, db_obj=None):
|
||||||
def process_tree(item, parent):
|
def process_tree(item, parent):
|
||||||
"""Recursive processor, building a parent-tree from iterable data"""
|
"""Recursive processor, building a parent-tree from iterable data"""
|
||||||
dtype = type(item)
|
dtype = type(item)
|
||||||
if dtype in (str, int, float, bool):
|
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
|
||||||
return item
|
return item
|
||||||
elif _IS_PACKED_DBOBJ(item):
|
elif _IS_PACKED_DBOBJ(item):
|
||||||
# this must be checked before tuple
|
# this must be checked before tuple
|
||||||
|
|
|
||||||
|
|
@ -10,13 +10,10 @@ class TestEvForm(TestCase):
|
||||||
def test_form(self):
|
def test_form(self):
|
||||||
self.maxDiff = None
|
self.maxDiff = None
|
||||||
form1 = evform._test()
|
form1 = evform._test()
|
||||||
print("len(form1): {}".format(len(form1)))
|
|
||||||
form2 = evform._test()
|
form2 = evform._test()
|
||||||
print("len(form2): {}".format(len(form2)))
|
|
||||||
|
|
||||||
self.assertEqual(form1, form2)
|
self.assertEqual(form1, form2)
|
||||||
|
|
||||||
# self.assertEqual(form, "")
|
# self.assertEqual(form1, "")
|
||||||
# '.------------------------------------------------.\n'
|
# '.------------------------------------------------.\n'
|
||||||
# '| |\n'
|
# '| |\n'
|
||||||
# '| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b'
|
# '| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b'
|
||||||
|
|
|
||||||
|
|
@ -10,24 +10,24 @@ class EvenniaForm(forms.Form):
|
||||||
This is a stock Django form, but modified so that all values provided
|
This is a stock Django form, but modified so that all values provided
|
||||||
through it are escaped (sanitized). Validation is performed by the fields
|
through it are escaped (sanitized). Validation is performed by the fields
|
||||||
you define in the form.
|
you define in the form.
|
||||||
|
|
||||||
This has little to do with Evennia itself and is more general web security-
|
This has little to do with Evennia itself and is more general web security-
|
||||||
related.
|
related.
|
||||||
|
|
||||||
https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#Goals_of_Input_Validation
|
https://www.owasp.org/index.php/Input_Validation_Cheat_Sheet#Goals_of_Input_Validation
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def clean(self):
|
def clean(self):
|
||||||
"""
|
"""
|
||||||
Django hook. Performed on form submission.
|
Django hook. Performed on form submission.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
cleaned (dict): Dictionary of key:value pairs submitted on the form.
|
cleaned (dict): Dictionary of key:value pairs submitted on the form.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Call parent function
|
# Call parent function
|
||||||
cleaned = super(EvenniaForm, self).clean()
|
cleaned = super(EvenniaForm, self).clean()
|
||||||
|
|
||||||
# Escape all values provided by user
|
# Escape all values provided by user
|
||||||
cleaned = {k:escape(v) for k,v in cleaned.items()}
|
cleaned = {k:escape(v) for k,v in cleaned.items()}
|
||||||
return cleaned
|
return cleaned
|
||||||
|
|
@ -35,123 +35,125 @@ class EvenniaForm(forms.Form):
|
||||||
class AccountForm(UserCreationForm):
|
class AccountForm(UserCreationForm):
|
||||||
"""
|
"""
|
||||||
This is a generic Django form tailored to the Account model.
|
This is a generic Django form tailored to the Account model.
|
||||||
|
|
||||||
In this incarnation it does not allow getting/setting of attributes, only
|
In this incarnation it does not allow getting/setting of attributes, only
|
||||||
core User model fields (username, email, password).
|
core User model fields (username, email, password).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
"""
|
"""
|
||||||
This is a Django construct that provides additional configuration to
|
This is a Django construct that provides additional configuration to
|
||||||
the form.
|
the form.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# The model/typeclass this form creates
|
# The model/typeclass this form creates
|
||||||
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||||
|
|
||||||
# The fields to display on the form, in the given order
|
# The fields to display on the form, in the given order
|
||||||
fields = ("username", "email")
|
fields = ("username", "email")
|
||||||
|
|
||||||
# Any overrides of field classes
|
# Any overrides of field classes
|
||||||
field_classes = {'username': UsernameField}
|
field_classes = {'username': UsernameField}
|
||||||
|
|
||||||
# Username is collected as part of the core UserCreationForm, so we just need
|
# Username is collected as part of the core UserCreationForm, so we just need
|
||||||
# to add a field to (optionally) capture email.
|
# to add a field to (optionally) capture email.
|
||||||
email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False)
|
email = forms.EmailField(help_text="A valid email address. Optional; used for password resets.", required=False)
|
||||||
|
|
||||||
class ObjectForm(EvenniaForm, ModelForm):
|
class ObjectForm(EvenniaForm, ModelForm):
|
||||||
"""
|
"""
|
||||||
This is a Django form for generic Evennia Objects that allows modification
|
This is a Django form for generic Evennia Objects that allows modification
|
||||||
of attributes when called from a descendent of ObjectUpdate or ObjectCreate
|
of attributes when called from a descendent of ObjectUpdate or ObjectCreate
|
||||||
views.
|
views.
|
||||||
|
|
||||||
It defines no fields by default; you have to do that by extending this class
|
It defines no fields by default; you have to do that by extending this class
|
||||||
and defining what fields you want to be recorded. See the CharacterForm for
|
and defining what fields you want to be recorded. See the CharacterForm for
|
||||||
a simple example of how to do this.
|
a simple example of how to do this.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
"""
|
"""
|
||||||
This is a Django construct that provides additional configuration to
|
This is a Django construct that provides additional configuration to
|
||||||
the form.
|
the form.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# The model/typeclass this form creates
|
# The model/typeclass this form creates
|
||||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
|
||||||
|
|
||||||
# The fields to display on the form, in the given order
|
# The fields to display on the form, in the given order
|
||||||
fields = ("db_key",)
|
fields = ("db_key",)
|
||||||
|
|
||||||
# This lets us rename ugly db-specific keys to something more human
|
# This lets us rename ugly db-specific keys to something more human
|
||||||
labels = {
|
labels = {
|
||||||
'db_key': 'Name',
|
'db_key': 'Name',
|
||||||
}
|
}
|
||||||
|
|
||||||
class CharacterForm(ObjectForm):
|
class CharacterForm(ObjectForm):
|
||||||
"""
|
"""
|
||||||
This is a Django form for Evennia Character objects.
|
This is a Django form for Evennia Character objects.
|
||||||
|
|
||||||
Since Evennia characters only have one attribute by default, this form only
|
Since Evennia characters only have one attribute by default, this form only
|
||||||
defines a field for that single attribute. The names of fields you define should
|
defines a field for that single attribute. The names of fields you define should
|
||||||
correspond to their names as stored in the dbhandler; you can display
|
correspond to their names as stored in the dbhandler; you can display
|
||||||
'prettier' versions of the fieldname on the form using the 'label' kwarg.
|
'prettier' versions of the fieldname on the form using the 'label' kwarg.
|
||||||
|
|
||||||
The basic field types are CharFields and IntegerFields, which let you enter
|
The basic field types are CharFields and IntegerFields, which let you enter
|
||||||
text and numbers respectively. IntegerFields have some neat validation tricks
|
text and numbers respectively. IntegerFields have some neat validation tricks
|
||||||
they can do, like mandating values fall within a certain range.
|
they can do, like mandating values fall within a certain range.
|
||||||
|
|
||||||
For example, a complete "age" field (which stores its value to
|
For example, a complete "age" field (which stores its value to
|
||||||
`character.db.age` might look like:
|
`character.db.age` might look like:
|
||||||
|
|
||||||
age = forms.IntegerField(
|
age = forms.IntegerField(
|
||||||
label="Your Age",
|
label="Your Age",
|
||||||
min_value=18, max_value=9000,
|
min_value=18, max_value=9000,
|
||||||
help_text="Years since your birth.")
|
help_text="Years since your birth.")
|
||||||
|
|
||||||
Default input fields are generic single-line text boxes. You can control what
|
Default input fields are generic single-line text boxes. You can control what
|
||||||
sort of input field users will see by specifying a "widget." An example of
|
sort of input field users will see by specifying a "widget." An example of
|
||||||
this is used for the 'desc' field to show a Textarea box instead of a Textbox.
|
this is used for the 'desc' field to show a Textarea box instead of a Textbox.
|
||||||
|
|
||||||
For help in building out your form, please see:
|
For help in building out your form, please see:
|
||||||
https://docs.djangoproject.com/en/1.11/topics/forms/#building-a-form-in-django
|
https://docs.djangoproject.com/en/1.11/topics/forms/#building-a-form-in-django
|
||||||
|
|
||||||
For more information on fields and their capabilities, see:
|
For more information on fields and their capabilities, see:
|
||||||
https://docs.djangoproject.com/en/1.11/ref/forms/fields/
|
https://docs.djangoproject.com/en/1.11/ref/forms/fields/
|
||||||
|
|
||||||
For more on widgets, see:
|
For more on widgets, see:
|
||||||
https://docs.djangoproject.com/en/1.11/ref/forms/widgets/
|
https://docs.djangoproject.com/en/1.11/ref/forms/widgets/
|
||||||
|
|
||||||
"""
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
"""
|
"""
|
||||||
This is a Django construct that provides additional configuration to
|
This is a Django construct that provides additional configuration to
|
||||||
the form.
|
the form.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Get the correct object model
|
# Get the correct object model
|
||||||
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
||||||
|
|
||||||
# Allow entry of the 'key' field
|
# Allow entry of the 'key' field
|
||||||
fields = ("db_key",)
|
fields = ("db_key",)
|
||||||
|
|
||||||
# Rename 'key' to something more intelligible
|
# Rename 'key' to something more intelligible
|
||||||
labels = {
|
labels = {
|
||||||
'db_key': 'Name',
|
'db_key': 'Name',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fields pertaining to configurable attributes on the Character object.
|
# Fields pertaining to configurable attributes on the Character object.
|
||||||
desc = forms.CharField(label='Description', max_length=2048, required=False,
|
desc = forms.CharField(
|
||||||
|
label='Description', max_length=2048, required=False,
|
||||||
widget=forms.Textarea(attrs={'rows': 3}),
|
widget=forms.Textarea(attrs={'rows': 3}),
|
||||||
help_text="A brief description of your character.")
|
help_text="A brief description of your character.")
|
||||||
|
|
||||||
|
|
||||||
class CharacterUpdateForm(CharacterForm):
|
class CharacterUpdateForm(CharacterForm):
|
||||||
"""
|
"""
|
||||||
This is a Django form for updating Evennia Character objects.
|
This is a Django form for updating Evennia Character objects.
|
||||||
|
|
||||||
By default it is the same as the CharacterForm, but if there are circumstances
|
By default it is the same as the CharacterForm, but if there are circumstances
|
||||||
in which you don't want to let players edit all the same attributes they had
|
in which you don't want to let players edit all the same attributes they had
|
||||||
access to during creation, you can redefine this form with those fields you do
|
access to during creation, you can redefine this form with those fields you do
|
||||||
wish to allow.
|
wish to allow.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,22 @@
|
||||||
|
"""
|
||||||
|
This file contains the generic, assorted views that don't fall under one of the other applications.
|
||||||
|
Views are django's way of processing e.g. html templates on the fly.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This file contains the generic, assorted views that don't fall under one of
|
|
||||||
the other applications. Views are django's way of processing e.g. html
|
|
||||||
templates on the fly.
|
|
||||||
|
|
||||||
"""
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.contrib.admin.sites import site
|
from django.contrib.admin.sites import site
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib import messages
|
from django.contrib import messages
|
||||||
from django.contrib.auth import authenticate
|
|
||||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||||
from django.contrib.admin.views.decorators import staff_member_required
|
from django.contrib.admin.views.decorators import staff_member_required
|
||||||
from django.core.exceptions import PermissionDenied
|
from django.core.exceptions import PermissionDenied
|
||||||
from django.db.models.functions import Lower
|
from django.db.models.functions import Lower
|
||||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect, Http404
|
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.urls import reverse, reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.views.generic import View, TemplateView, ListView, DetailView, FormView
|
from django.views.generic import TemplateView, ListView, DetailView
|
||||||
from django.views.generic.base import RedirectView
|
from django.views.generic.base import RedirectView
|
||||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||||
|
|
||||||
|
|
@ -26,15 +24,15 @@ from evennia import SESSION_HANDLER
|
||||||
from evennia.help.models import HelpEntry
|
from evennia.help.models import HelpEntry
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.accounts.models import AccountDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.utils import class_from_module, logger
|
from evennia.utils import class_from_module
|
||||||
from evennia.utils.logger import tail_log_file
|
from evennia.utils.logger import tail_log_file
|
||||||
from evennia.web.website.forms import *
|
from evennia.web.website import forms as website_forms
|
||||||
|
|
||||||
from django.contrib.auth import login
|
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
|
|
||||||
_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
|
_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
|
||||||
|
|
||||||
|
|
||||||
def _gamestats():
|
def _gamestats():
|
||||||
# Some misc. configurable stuff.
|
# Some misc. configurable stuff.
|
||||||
# TODO: Move this to either SQL or settings.py based configuration.
|
# TODO: Move this to either SQL or settings.py based configuration.
|
||||||
|
|
@ -49,8 +47,10 @@ def _gamestats():
|
||||||
# nsess = len(AccountDB.objects.get_connected_accounts()) or "no one"
|
# nsess = len(AccountDB.objects.get_connected_accounts()) or "no one"
|
||||||
|
|
||||||
nobjs = ObjectDB.objects.all().count()
|
nobjs = ObjectDB.objects.all().count()
|
||||||
nrooms = ObjectDB.objects.filter(db_location__isnull=True).exclude(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()
|
nrooms = ObjectDB.objects.filter(
|
||||||
nexits = ObjectDB.objects.filter(db_location__isnull=False, db_destination__isnull=False).count()
|
db_location__isnull=True).exclude(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()
|
||||||
|
nexits = ObjectDB.objects.filter(
|
||||||
|
db_location__isnull=False, db_destination__isnull=False).count()
|
||||||
nchars = ObjectDB.objects.filter(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()
|
nchars = ObjectDB.objects.filter(db_typeclass_path=_BASE_CHAR_TYPECLASS).count()
|
||||||
nothers = nobjs - nrooms - nchars - nexits
|
nothers = nobjs - nrooms - nchars - nexits
|
||||||
|
|
||||||
|
|
@ -99,6 +99,7 @@ def admin_wrapper(request):
|
||||||
"""
|
"""
|
||||||
return staff_member_required(site.index)(request)
|
return staff_member_required(site.index)(request)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Class-based views
|
# Class-based views
|
||||||
#
|
#
|
||||||
|
|
@ -237,6 +238,7 @@ class EvenniaDeleteView(DeleteView, TypeclassMixin):
|
||||||
# Makes sure the page has a sensible title.
|
# Makes sure the page has a sensible title.
|
||||||
return 'Delete %s' % self.typeclass._meta.verbose_name.title()
|
return 'Delete %s' % self.typeclass._meta.verbose_name.title()
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Object views
|
# Object views
|
||||||
#
|
#
|
||||||
|
|
@ -336,8 +338,9 @@ class ObjectDetailView(EvenniaDetailView):
|
||||||
|
|
||||||
# Check if this object was requested in a valid manner
|
# Check if this object was requested in a valid manner
|
||||||
if slugify(obj.name) != self.kwargs.get(self.slug_url_kwarg):
|
if slugify(obj.name) != self.kwargs.get(self.slug_url_kwarg):
|
||||||
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
|
raise HttpResponseBadRequest(
|
||||||
{'verbose_name': queryset.model._meta.verbose_name})
|
u"No %(verbose_name)s found matching the query" %
|
||||||
|
{'verbose_name': queryset.model._meta.verbose_name})
|
||||||
|
|
||||||
# Check if the requestor account has permissions to access object
|
# Check if the requestor account has permissions to access object
|
||||||
account = self.request.user
|
account = self.request.user
|
||||||
|
|
@ -430,7 +433,8 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
||||||
object detail page so the user can see their changes reflected.
|
object detail page so the user can see their changes reflected.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if self.success_url: return self.success_url
|
if self.success_url:
|
||||||
|
return self.success_url
|
||||||
return self.object.web_get_detail_url()
|
return self.object.web_get_detail_url()
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
|
|
@ -448,10 +452,10 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
|
||||||
# Get attributes
|
# Get attributes
|
||||||
data = {k:getattr(obj.db, k, '') for k in self.form_class.base_fields}
|
data = {k: getattr(obj.db, k, '') for k in self.form_class.base_fields}
|
||||||
|
|
||||||
# Get model fields
|
# Get model fields
|
||||||
data.update({k:getattr(obj, k, '') for k in self.form_class.Meta.fields})
|
data.update({k: getattr(obj, k, '') for k in self.form_class.Meta.fields})
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
@ -471,17 +475,18 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# Get the attributes after they've been cleaned and validated
|
# Get the attributes after they've been cleaned and validated
|
||||||
data = {k:v for k,v in form.cleaned_data.items() if k not in self.form_class.Meta.fields}
|
data = {k: v for k, v in form.cleaned_data.items() if k not in self.form_class.Meta.fields}
|
||||||
|
|
||||||
# Update the object attributes
|
# Update the object attributes
|
||||||
for key, value in data.items():
|
for key, value in data.items():
|
||||||
setattr(self.object.db, key, value)
|
self.object.attributes.add(key, value)
|
||||||
messages.success(self.request, "Successfully updated '%s' for %s." % (key, self.object))
|
messages.success(self.request, "Successfully updated '%s' for %s." % (key, self.object))
|
||||||
|
|
||||||
# Do not return super().form_valid; we don't want to update the model
|
# Do not return super().form_valid; we don't want to update the model
|
||||||
# instance, just its attributes.
|
# instance, just its attributes.
|
||||||
return HttpResponseRedirect(self.get_success_url())
|
return HttpResponseRedirect(self.get_success_url())
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Account views
|
# Account views
|
||||||
#
|
#
|
||||||
|
|
@ -496,7 +501,7 @@ class AccountMixin(TypeclassMixin):
|
||||||
"""
|
"""
|
||||||
# -- Django constructs --
|
# -- Django constructs --
|
||||||
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
|
||||||
form_class = AccountForm
|
form_class = website_forms.AccountForm
|
||||||
|
|
||||||
|
|
||||||
class AccountCreateView(AccountMixin, EvenniaCreateView):
|
class AccountCreateView(AccountMixin, EvenniaCreateView):
|
||||||
|
|
@ -537,11 +542,13 @@ class AccountCreateView(AccountMixin, EvenniaCreateView):
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
# Inform user of success
|
# Inform user of success
|
||||||
messages.success(self.request, "Your account '%s' was successfully created! You may log in using it now." % account.name)
|
messages.success(self.request, "Your account '%s' was successfully created! "
|
||||||
|
"You may log in using it now." % account.name)
|
||||||
|
|
||||||
# Redirect the user to the login page
|
# Redirect the user to the login page
|
||||||
return HttpResponseRedirect(self.success_url)
|
return HttpResponseRedirect(self.success_url)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Character views
|
# Character views
|
||||||
#
|
#
|
||||||
|
|
@ -556,7 +563,7 @@ class CharacterMixin(TypeclassMixin):
|
||||||
"""
|
"""
|
||||||
# -- Django constructs --
|
# -- Django constructs --
|
||||||
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
|
||||||
form_class = CharacterForm
|
form_class = website_forms.CharacterForm
|
||||||
success_url = reverse_lazy('character-manage')
|
success_url = reverse_lazy('character-manage')
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
|
|
@ -608,7 +615,8 @@ class CharacterListView(LoginRequiredMixin, CharacterMixin, ListView):
|
||||||
|
|
||||||
# Return a queryset consisting of characters the user is allowed to
|
# Return a queryset consisting of characters the user is allowed to
|
||||||
# see.
|
# see.
|
||||||
ids = [obj.id for obj in self.typeclass.objects.all() if obj.access(account, self.access_type)]
|
ids = [obj.id for obj in self.typeclass.objects.all()
|
||||||
|
if obj.access(account, self.access_type)]
|
||||||
|
|
||||||
return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
|
return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
|
||||||
|
|
||||||
|
|
@ -637,7 +645,7 @@ class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, Obje
|
||||||
char = self.get_object()
|
char = self.get_object()
|
||||||
|
|
||||||
# Get the page the user came from
|
# Get the page the user came from
|
||||||
next = self.request.GET.get('next', self.success_url)
|
next_page = self.request.GET.get('next', self.success_url)
|
||||||
|
|
||||||
if char:
|
if char:
|
||||||
# If the account owns the char, store the ID of the char in the
|
# If the account owns the char, store the ID of the char in the
|
||||||
|
|
@ -650,7 +658,7 @@ class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, Obje
|
||||||
self.request.session['puppet'] = None
|
self.request.session['puppet'] = None
|
||||||
messages.error(self.request, "You cannot become '%s'." % char)
|
messages.error(self.request, "You cannot become '%s'." % char)
|
||||||
|
|
||||||
return next
|
return next_page
|
||||||
|
|
||||||
|
|
||||||
class CharacterManageView(LoginRequiredMixin, CharacterMixin, ListView):
|
class CharacterManageView(LoginRequiredMixin, CharacterMixin, ListView):
|
||||||
|
|
@ -674,7 +682,7 @@ class CharacterUpdateView(CharacterMixin, ObjectUpdateView):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
# -- Django constructs --
|
# -- Django constructs --
|
||||||
form_class = CharacterUpdateForm
|
form_class = website_forms.CharacterUpdateForm
|
||||||
template_name = 'website/character_form.html'
|
template_name = 'website/character_form.html'
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -705,7 +713,8 @@ class CharacterDetailView(CharacterMixin, ObjectDetailView):
|
||||||
|
|
||||||
# Return a queryset consisting of characters the user is allowed to
|
# Return a queryset consisting of characters the user is allowed to
|
||||||
# see.
|
# see.
|
||||||
ids = [obj.id for obj in self.typeclass.objects.all() if obj.access(account, self.access_type)]
|
ids = [obj.id for obj in self.typeclass.objects.all()
|
||||||
|
if obj.access(account, self.access_type)]
|
||||||
|
|
||||||
return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
|
return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
|
||||||
|
|
||||||
|
|
@ -746,7 +755,6 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
||||||
self.attributes = {k: form.cleaned_data[k] for k in form.cleaned_data.keys()}
|
self.attributes = {k: form.cleaned_data[k] for k in form.cleaned_data.keys()}
|
||||||
charname = self.attributes.pop('db_key')
|
charname = self.attributes.pop('db_key')
|
||||||
description = self.attributes.pop('desc')
|
description = self.attributes.pop('desc')
|
||||||
|
|
||||||
# Create a character
|
# Create a character
|
||||||
character, errors = self.typeclass.create(charname, account, description=description)
|
character, errors = self.typeclass.create(charname, account, description=description)
|
||||||
|
|
||||||
|
|
@ -756,7 +764,7 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
||||||
|
|
||||||
if character:
|
if character:
|
||||||
# Assign attributes from form
|
# Assign attributes from form
|
||||||
for key,value in self.attributes.items():
|
for key, value in self.attributes.items():
|
||||||
setattr(character.db, key, value)
|
setattr(character.db, key, value)
|
||||||
|
|
||||||
# Return the user to the character management page, unless overridden
|
# Return the user to the character management page, unless overridden
|
||||||
|
|
@ -768,6 +776,7 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
||||||
messages.error(self.request, "Your character could not be created.")
|
messages.error(self.request, "Your character could not be created.")
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Channel views
|
# Channel views
|
||||||
#
|
#
|
||||||
|
|
@ -881,12 +890,14 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
|
||||||
context = super(ChannelDetailView, self).get_context_data(**kwargs)
|
context = super(ChannelDetailView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
# Get the filename this Channel is recording to
|
# Get the filename this Channel is recording to
|
||||||
filename = self.object.attributes.get("log_file", default="channel_%s.log" % self.object.key)
|
filename = self.object.attributes.get(
|
||||||
|
"log_file", default="channel_%s.log" % self.object.key)
|
||||||
|
|
||||||
# Split log entries so we can filter by time
|
# Split log entries so we can filter by time
|
||||||
bucket = []
|
bucket = []
|
||||||
for log in (x.strip() for x in tail_log_file(filename, 0, self.max_num_lines)):
|
for log in (x.strip() for x in tail_log_file(filename, 0, self.max_num_lines)):
|
||||||
if not log: continue
|
if not log:
|
||||||
|
continue
|
||||||
time, msg = log.split(' [-] ')
|
time, msg = log.split(' [-] ')
|
||||||
time_key = time.split(':')[0]
|
time_key = time.split(':')[0]
|
||||||
|
|
||||||
|
|
@ -904,7 +915,6 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
|
||||||
|
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
def get_object(self, queryset=None):
|
def get_object(self, queryset=None):
|
||||||
"""
|
"""
|
||||||
Override of Django hook that retrieves an object by slugified channel
|
Override of Django hook that retrieves an object by slugified channel
|
||||||
|
|
@ -924,8 +934,9 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
|
||||||
|
|
||||||
# Check if this object was requested in a valid manner
|
# Check if this object was requested in a valid manner
|
||||||
if not obj:
|
if not obj:
|
||||||
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
|
raise HttpResponseBadRequest(
|
||||||
{'verbose_name': queryset.model._meta.verbose_name})
|
u"No %(verbose_name)s found matching the query" %
|
||||||
|
{'verbose_name': queryset.model._meta.verbose_name})
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
|
|
@ -976,6 +987,7 @@ class HelpMixin(TypeclassMixin):
|
||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
class HelpListView(HelpMixin, ListView):
|
class HelpListView(HelpMixin, ListView):
|
||||||
"""
|
"""
|
||||||
Returns a list of help entries that can be viewed by a user, authenticated
|
Returns a list of help entries that can be viewed by a user, authenticated
|
||||||
|
|
@ -989,6 +1001,7 @@ class HelpListView(HelpMixin, ListView):
|
||||||
# -- Evennia constructs --
|
# -- Evennia constructs --
|
||||||
page_title = "Help Index"
|
page_title = "Help Index"
|
||||||
|
|
||||||
|
|
||||||
class HelpDetailView(HelpMixin, EvenniaDetailView):
|
class HelpDetailView(HelpMixin, EvenniaDetailView):
|
||||||
"""
|
"""
|
||||||
Returns the detail page for a given help entry.
|
Returns the detail page for a given help entry.
|
||||||
|
|
@ -1012,7 +1025,8 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
|
||||||
obj = self.get_object()
|
obj = self.get_object()
|
||||||
|
|
||||||
# Get queryset and filter out non-related categories
|
# Get queryset and filter out non-related categories
|
||||||
queryset = self.get_queryset().filter(db_help_category=obj.db_help_category).order_by(Lower('db_key'))
|
queryset = self.get_queryset().filter(
|
||||||
|
db_help_category=obj.db_help_category).order_by(Lower('db_key'))
|
||||||
context['topic_list'] = queryset
|
context['topic_list'] = queryset
|
||||||
|
|
||||||
# Find the index position of the given obj in the queryset
|
# Find the index position of the given obj in the queryset
|
||||||
|
|
@ -1025,12 +1039,14 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
|
||||||
try:
|
try:
|
||||||
assert i+1 <= len(objs) and objs[i+1] is not obj
|
assert i+1 <= len(objs) and objs[i+1] is not obj
|
||||||
context['topic_next'] = objs[i+1]
|
context['topic_next'] = objs[i+1]
|
||||||
except: context['topic_next'] = None
|
except:
|
||||||
|
context['topic_next'] = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
assert i-1 >= 0 and objs[i-1] is not obj
|
assert i-1 >= 0 and objs[i-1] is not obj
|
||||||
context['topic_previous'] = objs[i-1]
|
context['topic_previous'] = objs[i-1]
|
||||||
except: context['topic_previous'] = None
|
except:
|
||||||
|
context['topic_previous'] = None
|
||||||
|
|
||||||
# Format the help entry using HTML instead of newlines
|
# Format the help entry using HTML instead of newlines
|
||||||
text = obj.db_entrytext
|
text = obj.db_entrytext
|
||||||
|
|
@ -1057,11 +1073,14 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
|
||||||
# Find the object in the queryset
|
# Find the object in the queryset
|
||||||
category = slugify(self.kwargs.get('category', ''))
|
category = slugify(self.kwargs.get('category', ''))
|
||||||
topic = slugify(self.kwargs.get('topic', ''))
|
topic = slugify(self.kwargs.get('topic', ''))
|
||||||
obj = next((x for x in queryset if slugify(x.db_help_category)==category and slugify(x.db_key)==topic), None)
|
obj = next((x for x in queryset
|
||||||
|
if slugify(x.db_help_category) == category and
|
||||||
|
slugify(x.db_key) == topic), None)
|
||||||
|
|
||||||
# Check if this object was requested in a valid manner
|
# Check if this object was requested in a valid manner
|
||||||
if not obj:
|
if not obj:
|
||||||
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
|
raise HttpResponseBadRequest(
|
||||||
{'verbose_name': queryset.model._meta.verbose_name})
|
u"No %(verbose_name)s found matching the query" %
|
||||||
|
{'verbose_name': queryset.model._meta.verbose_name})
|
||||||
|
|
||||||
return obj
|
return obj
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue