Ran black on sources
This commit is contained in:
parent
cc5aa91be1
commit
21d62e651a
19 changed files with 362 additions and 322 deletions
|
|
@ -90,7 +90,7 @@ def _init_command(cls, **kwargs):
|
||||||
"aliases": " ".join(cls.aliases),
|
"aliases": " ".join(cls.aliases),
|
||||||
"category": cls.help_category,
|
"category": cls.help_category,
|
||||||
"text": cls.__doc__,
|
"text": cls.__doc__,
|
||||||
"tags": ""
|
"tags": "",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,7 @@ class HelpCategory:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def search_index_entry(self):
|
def search_index_entry(self):
|
||||||
return {
|
return {"key": str(self), "aliases": "", "category": self.key, "tags": "", "text": ""}
|
||||||
"key": str(self),
|
|
||||||
"aliases": "",
|
|
||||||
"category": self.key,
|
|
||||||
"tags": "",
|
|
||||||
"text": ""
|
|
||||||
}
|
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"Category: {self.key}"
|
return f"Category: {self.key}"
|
||||||
|
|
@ -59,28 +53,13 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5):
|
||||||
search_index = lunr(
|
search_index = lunr(
|
||||||
ref="key",
|
ref="key",
|
||||||
fields=[
|
fields=[
|
||||||
{
|
{"field_name": "key", "boost": 10,},
|
||||||
"field_name": "key",
|
{"field_name": "aliases", "boost": 9,},
|
||||||
"boost": 10,
|
{"field_name": "category", "boost": 8,},
|
||||||
},
|
{"field_name": "tags", "boost": 5},
|
||||||
{
|
{"field_name": "text", "boost": 1,},
|
||||||
"field_name": "aliases",
|
|
||||||
"boost": 9,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field_name": "category",
|
|
||||||
"boost": 8,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field_name": "tags",
|
|
||||||
"boost": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"field_name": "text",
|
|
||||||
"boost": 1,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
documents=indx
|
documents=indx,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
matches = search_index.search(query)[:suggestion_maxnum]
|
matches = search_index.search(query)[:suggestion_maxnum]
|
||||||
|
|
@ -89,9 +68,10 @@ def help_search_with_index(query, candidate_entries, suggestion_maxnum=5):
|
||||||
matches = []
|
matches = []
|
||||||
|
|
||||||
# matches (objs), suggestions (strs)
|
# matches (objs), suggestions (strs)
|
||||||
return ([mapping[match["ref"]] for match in matches],
|
return (
|
||||||
[str(match["ref"]) # + f" (score {match['score']})") # good debug
|
[mapping[match["ref"]] for match in matches],
|
||||||
for match in matches])
|
[str(match["ref"]) for match in matches], # + f" (score {match['score']})") # good debug
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CmdHelp(Command):
|
class CmdHelp(Command):
|
||||||
|
|
@ -195,11 +175,11 @@ class CmdHelp(Command):
|
||||||
for category in sorted(set(list(hdict_cmds.keys()) + list(hdict_db.keys()))):
|
for category in sorted(set(list(hdict_cmds.keys()) + list(hdict_db.keys()))):
|
||||||
|
|
||||||
category_str = f"-- {category.title()} "
|
category_str = f"-- {category.title()} "
|
||||||
grid.append(ANSIString(
|
grid.append(
|
||||||
category_clr
|
ANSIString(
|
||||||
+ category_str
|
category_clr + category_str + "-" * (width - len(category_str)) + topic_clr
|
||||||
+ "-" * (width - len(category_str))
|
)
|
||||||
+ topic_clr))
|
)
|
||||||
verbatim_elements.append(len(grid) - 1)
|
verbatim_elements.append(len(grid) - 1)
|
||||||
|
|
||||||
entries = sorted(set(hdict_cmds.get(category, []) + hdict_db.get(category, [])))
|
entries = sorted(set(hdict_cmds.get(category, []) + hdict_db.get(category, [])))
|
||||||
|
|
@ -281,8 +261,8 @@ class CmdHelp(Command):
|
||||||
]
|
]
|
||||||
all_categories = list(
|
all_categories = list(
|
||||||
set(
|
set(
|
||||||
[HelpCategory(cmd.help_category) for cmd in all_cmds] +
|
[HelpCategory(cmd.help_category) for cmd in all_cmds]
|
||||||
[HelpCategory(topic.help_category) for topic in all_topics]
|
+ [HelpCategory(topic.help_category) for topic in all_topics]
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -302,38 +282,47 @@ class CmdHelp(Command):
|
||||||
return
|
return
|
||||||
|
|
||||||
# Try to access a particular help entry or category
|
# Try to access a particular help entry or category
|
||||||
entries = ([cmd for cmd in all_cmds if cmd] +
|
entries = [cmd for cmd in all_cmds if cmd] + list(HelpEntry.objects.all()) + all_categories
|
||||||
list(HelpEntry.objects.all()) +
|
|
||||||
all_categories)
|
|
||||||
|
|
||||||
for match_query in [f"{query}~1", f"{query}*"]:
|
for match_query in [f"{query}~1", f"{query}*"]:
|
||||||
# We first do an exact word-match followed by a start-by query
|
# We first do an exact word-match followed by a start-by query
|
||||||
|
|
||||||
matches, suggestions = help_search_with_index(
|
matches, suggestions = help_search_with_index(
|
||||||
match_query, entries, suggestion_maxnum=self.suggestion_maxnum)
|
match_query, entries, suggestion_maxnum=self.suggestion_maxnum
|
||||||
|
)
|
||||||
|
|
||||||
if matches:
|
if matches:
|
||||||
match = matches[0]
|
match = matches[0]
|
||||||
if isinstance(match, HelpCategory):
|
if isinstance(match, HelpCategory):
|
||||||
formatted = self.format_help_list(
|
formatted = self.format_help_list(
|
||||||
{match.key: [cmd.key for cmd in all_cmds
|
{
|
||||||
if match.key.lower() == cmd.help_category]},
|
match.key: [
|
||||||
{match.key: [topic.key for topic in all_topics
|
cmd.key
|
||||||
if match.key.lower() == topic.help_category]}
|
for cmd in all_cmds
|
||||||
|
if match.key.lower() == cmd.help_category
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
match.key: [
|
||||||
|
topic.key
|
||||||
|
for topic in all_topics
|
||||||
|
if match.key.lower() == topic.help_category
|
||||||
|
]
|
||||||
|
},
|
||||||
)
|
)
|
||||||
elif inherits_from(match, "evennia.commands.command.Command"):
|
elif inherits_from(match, "evennia.commands.command.Command"):
|
||||||
formatted = self.format_help_entry(
|
formatted = self.format_help_entry(
|
||||||
match.key,
|
match.key,
|
||||||
match.get_help(caller, cmdset),
|
match.get_help(caller, cmdset),
|
||||||
aliases=match.aliases,
|
aliases=match.aliases,
|
||||||
suggested=suggestions[1:]
|
suggested=suggestions[1:],
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
formatted = self.format_help_entry(
|
formatted = self.format_help_entry(
|
||||||
match.key,
|
match.key,
|
||||||
match.entrytext,
|
match.entrytext,
|
||||||
aliases=match.aliases.all(),
|
aliases=match.aliases.all(),
|
||||||
suggested=suggestions[1:]
|
suggested=suggestions[1:],
|
||||||
)
|
)
|
||||||
|
|
||||||
self.msg_help(formatted)
|
self.msg_help(formatted)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ class S3Boto3TestCase(TestCase):
|
||||||
|
|
||||||
|
|
||||||
class S3Boto3StorageTests(S3Boto3TestCase):
|
class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
|
|
||||||
def test_clean_name(self):
|
def test_clean_name(self):
|
||||||
"""
|
"""
|
||||||
Test the base case of _clean_name
|
Test the base case of _clean_name
|
||||||
|
|
@ -94,87 +93,75 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
"""
|
"""
|
||||||
Test URL generation.
|
Test URL generation.
|
||||||
"""
|
"""
|
||||||
self.storage.custom_domain = 'example.com'
|
self.storage.custom_domain = "example.com"
|
||||||
|
|
||||||
# We expect no leading slashes in the path,
|
# We expect no leading slashes in the path,
|
||||||
# and trailing slashes should be preserved.
|
# and trailing slashes should be preserved.
|
||||||
self.assertEqual(self.storage.url(''), 'https://example.com/')
|
self.assertEqual(self.storage.url(""), "https://example.com/")
|
||||||
self.assertEqual(self.storage.url('path'), 'https://example.com/path')
|
self.assertEqual(self.storage.url("path"), "https://example.com/path")
|
||||||
self.assertEqual(self.storage.url('path/'), 'https://example.com/path/')
|
self.assertEqual(self.storage.url("path/"), "https://example.com/path/")
|
||||||
self.assertEqual(self.storage.url('path/1'), 'https://example.com/path/1')
|
self.assertEqual(self.storage.url("path/1"), "https://example.com/path/1")
|
||||||
self.assertEqual(self.storage.url('path/1/'), 'https://example.com/path/1/')
|
self.assertEqual(self.storage.url("path/1/"), "https://example.com/path/1/")
|
||||||
|
|
||||||
def test_storage_save(self):
|
def test_storage_save(self):
|
||||||
"""
|
"""
|
||||||
Test saving a file
|
Test saving a file
|
||||||
"""
|
"""
|
||||||
name = 'test_storage_save.txt'
|
name = "test_storage_save.txt"
|
||||||
content = ContentFile('new content')
|
content = ContentFile("new content")
|
||||||
self.storage.save(name, content)
|
self.storage.save(name, content)
|
||||||
self.storage.bucket.Object.assert_called_once_with(name)
|
self.storage.bucket.Object.assert_called_once_with(name)
|
||||||
|
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
obj.upload_fileobj.assert_called_with(
|
||||||
content,
|
content, ExtraArgs={"ContentType": "text/plain", "ACL": self.storage.default_acl,}
|
||||||
ExtraArgs={
|
|
||||||
'ContentType': 'text/plain',
|
|
||||||
'ACL': self.storage.default_acl,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_storage_save_with_acl(self):
|
def test_storage_save_with_acl(self):
|
||||||
"""
|
"""
|
||||||
Test saving a file with user defined ACL.
|
Test saving a file with user defined ACL.
|
||||||
"""
|
"""
|
||||||
name = 'test_storage_save.txt'
|
name = "test_storage_save.txt"
|
||||||
content = ContentFile('new content')
|
content = ContentFile("new content")
|
||||||
self.storage.default_acl = 'private'
|
self.storage.default_acl = "private"
|
||||||
self.storage.save(name, content)
|
self.storage.save(name, content)
|
||||||
self.storage.bucket.Object.assert_called_once_with(name)
|
self.storage.bucket.Object.assert_called_once_with(name)
|
||||||
|
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
obj.upload_fileobj.assert_called_with(
|
||||||
content,
|
content, ExtraArgs={"ContentType": "text/plain", "ACL": "private",}
|
||||||
ExtraArgs={
|
|
||||||
'ContentType': 'text/plain',
|
|
||||||
'ACL': 'private',
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_content_type(self):
|
def test_content_type(self):
|
||||||
"""
|
"""
|
||||||
Test saving a file with a None content type.
|
Test saving a file with a None content type.
|
||||||
"""
|
"""
|
||||||
name = 'test_image.jpg'
|
name = "test_image.jpg"
|
||||||
content = ContentFile('data')
|
content = ContentFile("data")
|
||||||
content.content_type = None
|
content.content_type = None
|
||||||
self.storage.save(name, content)
|
self.storage.save(name, content)
|
||||||
self.storage.bucket.Object.assert_called_once_with(name)
|
self.storage.bucket.Object.assert_called_once_with(name)
|
||||||
|
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
obj.upload_fileobj.assert_called_with(
|
||||||
content,
|
content, ExtraArgs={"ContentType": "image/jpeg", "ACL": self.storage.default_acl,}
|
||||||
ExtraArgs={
|
|
||||||
'ContentType': 'image/jpeg',
|
|
||||||
'ACL': self.storage.default_acl,
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_storage_save_gzipped(self):
|
def test_storage_save_gzipped(self):
|
||||||
"""
|
"""
|
||||||
Test saving a gzipped file
|
Test saving a gzipped file
|
||||||
"""
|
"""
|
||||||
name = 'test_storage_save.gz'
|
name = "test_storage_save.gz"
|
||||||
content = ContentFile("I am gzip'd")
|
content = ContentFile("I am gzip'd")
|
||||||
self.storage.save(name, content)
|
self.storage.save(name, content)
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
obj.upload_fileobj.assert_called_with(
|
||||||
content,
|
content,
|
||||||
ExtraArgs={
|
ExtraArgs={
|
||||||
'ContentType': 'application/octet-stream',
|
"ContentType": "application/octet-stream",
|
||||||
'ContentEncoding': 'gzip',
|
"ContentEncoding": "gzip",
|
||||||
'ACL': self.storage.default_acl,
|
"ACL": self.storage.default_acl,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_storage_save_gzip(self):
|
def test_storage_save_gzip(self):
|
||||||
|
|
@ -182,21 +169,21 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
Test saving a file with gzip enabled.
|
Test saving a file with gzip enabled.
|
||||||
"""
|
"""
|
||||||
self.storage.gzip = True
|
self.storage.gzip = True
|
||||||
name = 'test_storage_save.css'
|
name = "test_storage_save.css"
|
||||||
content = ContentFile("I should be gzip'd")
|
content = ContentFile("I should be gzip'd")
|
||||||
self.storage.save(name, content)
|
self.storage.save(name, content)
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
obj.upload_fileobj.assert_called_with(
|
||||||
mock.ANY,
|
mock.ANY,
|
||||||
ExtraArgs={
|
ExtraArgs={
|
||||||
'ContentType': 'text/css',
|
"ContentType": "text/css",
|
||||||
'ContentEncoding': 'gzip',
|
"ContentEncoding": "gzip",
|
||||||
'ACL': self.storage.default_acl,
|
"ACL": self.storage.default_acl,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
args, kwargs = obj.upload_fileobj.call_args
|
args, kwargs = obj.upload_fileobj.call_args
|
||||||
content = args[0]
|
content = args[0]
|
||||||
zfile = gzip.GzipFile(mode='rb', fileobj=content)
|
zfile = gzip.GzipFile(mode="rb", fileobj=content)
|
||||||
self.assertEqual(zfile.read(), b"I should be gzip'd")
|
self.assertEqual(zfile.read(), b"I should be gzip'd")
|
||||||
|
|
||||||
def test_storage_save_gzip_twice(self):
|
def test_storage_save_gzip_twice(self):
|
||||||
|
|
@ -205,26 +192,26 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
"""
|
"""
|
||||||
# Given
|
# Given
|
||||||
self.storage.gzip = True
|
self.storage.gzip = True
|
||||||
name = 'test_storage_save.css'
|
name = "test_storage_save.css"
|
||||||
content = ContentFile("I should be gzip'd")
|
content = ContentFile("I should be gzip'd")
|
||||||
|
|
||||||
# When
|
# When
|
||||||
self.storage.save(name, content)
|
self.storage.save(name, content)
|
||||||
self.storage.save('test_storage_save_2.css', content)
|
self.storage.save("test_storage_save_2.css", content)
|
||||||
|
|
||||||
# Then
|
# Then
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.upload_fileobj.assert_called_with(
|
obj.upload_fileobj.assert_called_with(
|
||||||
mock.ANY,
|
mock.ANY,
|
||||||
ExtraArgs={
|
ExtraArgs={
|
||||||
'ContentType': 'text/css',
|
"ContentType": "text/css",
|
||||||
'ContentEncoding': 'gzip',
|
"ContentEncoding": "gzip",
|
||||||
'ACL': self.storage.default_acl,
|
"ACL": self.storage.default_acl,
|
||||||
}
|
},
|
||||||
)
|
)
|
||||||
args, kwargs = obj.upload_fileobj.call_args
|
args, kwargs = obj.upload_fileobj.call_args
|
||||||
content = args[0]
|
content = args[0]
|
||||||
zfile = gzip.GzipFile(mode='rb', fileobj=content)
|
zfile = gzip.GzipFile(mode="rb", fileobj=content)
|
||||||
self.assertEqual(zfile.read(), b"I should be gzip'd")
|
self.assertEqual(zfile.read(), b"I should be gzip'd")
|
||||||
|
|
||||||
def test_compress_content_len(self):
|
def test_compress_content_len(self):
|
||||||
|
|
@ -240,15 +227,15 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
"""
|
"""
|
||||||
Test opening a file in write mode
|
Test opening a file in write mode
|
||||||
"""
|
"""
|
||||||
name = 'test_open_for_writïng.txt'
|
name = "test_open_for_writïng.txt"
|
||||||
content = 'new content'
|
content = "new content"
|
||||||
|
|
||||||
# Set the encryption flag used for multipart uploads
|
# Set the encryption flag used for multipart uploads
|
||||||
self.storage.encryption = True
|
self.storage.encryption = True
|
||||||
self.storage.reduced_redundancy = True
|
self.storage.reduced_redundancy = True
|
||||||
self.storage.default_acl = 'public-read'
|
self.storage.default_acl = "public-read"
|
||||||
|
|
||||||
file = self.storage.open(name, 'w')
|
file = self.storage.open(name, "w")
|
||||||
self.storage.bucket.Object.assert_called_with(name)
|
self.storage.bucket.Object.assert_called_with(name)
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
# Set the name of the mock object
|
# Set the name of the mock object
|
||||||
|
|
@ -256,21 +243,22 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
|
|
||||||
file.write(content)
|
file.write(content)
|
||||||
obj.initiate_multipart_upload.assert_called_with(
|
obj.initiate_multipart_upload.assert_called_with(
|
||||||
ACL='public-read',
|
ACL="public-read",
|
||||||
ContentType='text/plain',
|
ContentType="text/plain",
|
||||||
ServerSideEncryption='AES256',
|
ServerSideEncryption="AES256",
|
||||||
StorageClass='REDUCED_REDUNDANCY'
|
StorageClass="REDUCED_REDUNDANCY",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Save the internal file before closing
|
# Save the internal file before closing
|
||||||
multipart = obj.initiate_multipart_upload.return_value
|
multipart = obj.initiate_multipart_upload.return_value
|
||||||
multipart.parts.all.return_value = [mock.MagicMock(e_tag='123', part_number=1)]
|
multipart.parts.all.return_value = [mock.MagicMock(e_tag="123", part_number=1)]
|
||||||
file.close()
|
file.close()
|
||||||
multipart.Part.assert_called_with(1)
|
multipart.Part.assert_called_with(1)
|
||||||
part = multipart.Part.return_value
|
part = multipart.Part.return_value
|
||||||
part.upload.assert_called_with(Body=content.encode('utf-8'))
|
part.upload.assert_called_with(Body=content.encode("utf-8"))
|
||||||
multipart.complete.assert_called_once_with(
|
multipart.complete.assert_called_once_with(
|
||||||
MultipartUpload={'Parts': [{'ETag': '123', 'PartNumber': 1}]})
|
MultipartUpload={"Parts": [{"ETag": "123", "PartNumber": 1}]}
|
||||||
|
)
|
||||||
|
|
||||||
def test_storage_open_no_write(self):
|
def test_storage_open_no_write(self):
|
||||||
"""
|
"""
|
||||||
|
|
@ -278,19 +266,19 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
|
|
||||||
A file should be created as by obj.put(...).
|
A file should be created as by obj.put(...).
|
||||||
"""
|
"""
|
||||||
name = 'test_open_no_write.txt'
|
name = "test_open_no_write.txt"
|
||||||
|
|
||||||
# Set the encryption flag used for puts
|
# Set the encryption flag used for puts
|
||||||
self.storage.encryption = True
|
self.storage.encryption = True
|
||||||
self.storage.reduced_redundancy = True
|
self.storage.reduced_redundancy = True
|
||||||
self.storage.default_acl = 'public-read'
|
self.storage.default_acl = "public-read"
|
||||||
|
|
||||||
file = self.storage.open(name, 'w')
|
file = self.storage.open(name, "w")
|
||||||
self.storage.bucket.Object.assert_called_with(name)
|
self.storage.bucket.Object.assert_called_with(name)
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.load.side_effect = ClientError({'Error': {},
|
obj.load.side_effect = ClientError(
|
||||||
'ResponseMetadata': {'HTTPStatusCode': 404}},
|
{"Error": {}, "ResponseMetadata": {"HTTPStatusCode": 404}}, "head_bucket"
|
||||||
'head_bucket')
|
)
|
||||||
|
|
||||||
# Set the name of the mock object
|
# Set the name of the mock object
|
||||||
obj.key = name
|
obj.key = name
|
||||||
|
|
@ -300,25 +288,25 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
|
|
||||||
obj.load.assert_called_once_with()
|
obj.load.assert_called_once_with()
|
||||||
obj.put.assert_called_once_with(
|
obj.put.assert_called_once_with(
|
||||||
ACL='public-read',
|
ACL="public-read",
|
||||||
Body=b"",
|
Body=b"",
|
||||||
ContentType='text/plain',
|
ContentType="text/plain",
|
||||||
ServerSideEncryption='AES256',
|
ServerSideEncryption="AES256",
|
||||||
StorageClass='REDUCED_REDUNDANCY'
|
StorageClass="REDUCED_REDUNDANCY",
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_storage_open_no_overwrite_existing(self):
|
def test_storage_open_no_overwrite_existing(self):
|
||||||
"""
|
"""
|
||||||
Test opening an existing file in write mode and closing without writing.
|
Test opening an existing file in write mode and closing without writing.
|
||||||
"""
|
"""
|
||||||
name = 'test_open_no_overwrite_existing.txt'
|
name = "test_open_no_overwrite_existing.txt"
|
||||||
|
|
||||||
# Set the encryption flag used for puts
|
# Set the encryption flag used for puts
|
||||||
self.storage.encryption = True
|
self.storage.encryption = True
|
||||||
self.storage.reduced_redundancy = True
|
self.storage.reduced_redundancy = True
|
||||||
self.storage.default_acl = 'public-read'
|
self.storage.default_acl = "public-read"
|
||||||
|
|
||||||
file = self.storage.open(name, 'w')
|
file = self.storage.open(name, "w")
|
||||||
self.storage.bucket.Object.assert_called_with(name)
|
self.storage.bucket.Object.assert_called_with(name)
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
|
|
||||||
|
|
@ -335,125 +323,111 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
"""
|
"""
|
||||||
Test writing content that exceeds the buffer size
|
Test writing content that exceeds the buffer size
|
||||||
"""
|
"""
|
||||||
name = 'test_open_for_writïng_beyond_buffer_size.txt'
|
name = "test_open_for_writïng_beyond_buffer_size.txt"
|
||||||
|
|
||||||
# Set the encryption flag used for multipart uploads
|
# Set the encryption flag used for multipart uploads
|
||||||
self.storage.encryption = True
|
self.storage.encryption = True
|
||||||
self.storage.reduced_redundancy = True
|
self.storage.reduced_redundancy = True
|
||||||
self.storage.default_acl = 'public-read'
|
self.storage.default_acl = "public-read"
|
||||||
|
|
||||||
file = self.storage.open(name, 'w')
|
file = self.storage.open(name, "w")
|
||||||
self.storage.bucket.Object.assert_called_with(name)
|
self.storage.bucket.Object.assert_called_with(name)
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
# Set the name of the mock object
|
# Set the name of the mock object
|
||||||
obj.key = name
|
obj.key = name
|
||||||
|
|
||||||
# Initiate the multipart upload
|
# Initiate the multipart upload
|
||||||
file.write('')
|
file.write("")
|
||||||
obj.initiate_multipart_upload.assert_called_with(
|
obj.initiate_multipart_upload.assert_called_with(
|
||||||
ACL='public-read',
|
ACL="public-read",
|
||||||
ContentType='text/plain',
|
ContentType="text/plain",
|
||||||
ServerSideEncryption='AES256',
|
ServerSideEncryption="AES256",
|
||||||
StorageClass='REDUCED_REDUNDANCY'
|
StorageClass="REDUCED_REDUNDANCY",
|
||||||
)
|
)
|
||||||
multipart = obj.initiate_multipart_upload.return_value
|
multipart = obj.initiate_multipart_upload.return_value
|
||||||
|
|
||||||
# Write content at least twice as long as the buffer size
|
# Write content at least twice as long as the buffer size
|
||||||
written_content = ''
|
written_content = ""
|
||||||
counter = 1
|
counter = 1
|
||||||
while len(written_content) < 2 * file.buffer_size:
|
while len(written_content) < 2 * file.buffer_size:
|
||||||
content = 'hello, aws {counter}\n'.format(counter=counter)
|
content = "hello, aws {counter}\n".format(counter=counter)
|
||||||
# Write more than just a few bytes in each iteration to keep the
|
# Write more than just a few bytes in each iteration to keep the
|
||||||
# test reasonably fast
|
# test reasonably fast
|
||||||
content += '*' * int(file.buffer_size / 10)
|
content += "*" * int(file.buffer_size / 10)
|
||||||
file.write(content)
|
file.write(content)
|
||||||
written_content += content
|
written_content += content
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
# Save the internal file before closing
|
# Save the internal file before closing
|
||||||
multipart.parts.all.return_value = [
|
multipart.parts.all.return_value = [
|
||||||
mock.MagicMock(e_tag='123', part_number=1),
|
mock.MagicMock(e_tag="123", part_number=1),
|
||||||
mock.MagicMock(e_tag='456', part_number=2)
|
mock.MagicMock(e_tag="456", part_number=2),
|
||||||
]
|
]
|
||||||
file.close()
|
file.close()
|
||||||
self.assertListEqual(
|
self.assertListEqual(multipart.Part.call_args_list, [mock.call(1), mock.call(2)])
|
||||||
multipart.Part.call_args_list,
|
|
||||||
[mock.call(1), mock.call(2)]
|
|
||||||
)
|
|
||||||
part = multipart.Part.return_value
|
part = multipart.Part.return_value
|
||||||
uploaded_content = ''.join(
|
uploaded_content = "".join(
|
||||||
args_list[1]['Body'].decode('utf-8')
|
args_list[1]["Body"].decode("utf-8") for args_list in part.upload.call_args_list
|
||||||
for args_list in part.upload.call_args_list
|
|
||||||
)
|
)
|
||||||
self.assertEqual(uploaded_content, written_content)
|
self.assertEqual(uploaded_content, written_content)
|
||||||
multipart.complete.assert_called_once_with(
|
multipart.complete.assert_called_once_with(
|
||||||
MultipartUpload={'Parts': [
|
MultipartUpload={
|
||||||
{'ETag': '123', 'PartNumber': 1},
|
"Parts": [{"ETag": "123", "PartNumber": 1}, {"ETag": "456", "PartNumber": 2},]
|
||||||
{'ETag': '456', 'PartNumber': 2},
|
}
|
||||||
]}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_auto_creating_bucket(self):
|
def test_auto_creating_bucket(self):
|
||||||
self.storage.auto_create_bucket = True
|
self.storage.auto_create_bucket = True
|
||||||
Bucket = mock.MagicMock()
|
Bucket = mock.MagicMock()
|
||||||
self.storage._connections.connection.Bucket.return_value = Bucket
|
self.storage._connections.connection.Bucket.return_value = Bucket
|
||||||
self.storage._connections.connection.meta.client.meta.region_name = 'sa-east-1'
|
self.storage._connections.connection.meta.client.meta.region_name = "sa-east-1"
|
||||||
|
|
||||||
Bucket.meta.client.head_bucket.side_effect = ClientError({'Error': {},
|
Bucket.meta.client.head_bucket.side_effect = ClientError(
|
||||||
'ResponseMetadata': {'HTTPStatusCode': 404}},
|
{"Error": {}, "ResponseMetadata": {"HTTPStatusCode": 404}}, "head_bucket"
|
||||||
'head_bucket')
|
)
|
||||||
self.storage._get_or_create_bucket('testbucketname')
|
self.storage._get_or_create_bucket("testbucketname")
|
||||||
Bucket.create.assert_called_once_with(
|
Bucket.create.assert_called_once_with(
|
||||||
ACL='public-read',
|
ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",}
|
||||||
CreateBucketConfiguration={
|
|
||||||
'LocationConstraint': 'sa-east-1',
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_auto_creating_bucket_with_acl(self):
|
def test_auto_creating_bucket_with_acl(self):
|
||||||
self.storage.auto_create_bucket = True
|
self.storage.auto_create_bucket = True
|
||||||
self.storage.bucket_acl = 'public-read'
|
self.storage.bucket_acl = "public-read"
|
||||||
Bucket = mock.MagicMock()
|
Bucket = mock.MagicMock()
|
||||||
self.storage._connections.connection.Bucket.return_value = Bucket
|
self.storage._connections.connection.Bucket.return_value = Bucket
|
||||||
self.storage._connections.connection.meta.client.meta.region_name = 'sa-east-1'
|
self.storage._connections.connection.meta.client.meta.region_name = "sa-east-1"
|
||||||
|
|
||||||
Bucket.meta.client.head_bucket.side_effect = ClientError({'Error': {},
|
Bucket.meta.client.head_bucket.side_effect = ClientError(
|
||||||
'ResponseMetadata': {'HTTPStatusCode': 404}},
|
{"Error": {}, "ResponseMetadata": {"HTTPStatusCode": 404}}, "head_bucket"
|
||||||
'head_bucket')
|
)
|
||||||
self.storage._get_or_create_bucket('testbucketname')
|
self.storage._get_or_create_bucket("testbucketname")
|
||||||
Bucket.create.assert_called_once_with(
|
Bucket.create.assert_called_once_with(
|
||||||
ACL='public-read',
|
ACL="public-read", CreateBucketConfiguration={"LocationConstraint": "sa-east-1",}
|
||||||
CreateBucketConfiguration={
|
|
||||||
'LocationConstraint': 'sa-east-1',
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_storage_exists(self):
|
def test_storage_exists(self):
|
||||||
self.assertTrue(self.storage.exists("file.txt"))
|
self.assertTrue(self.storage.exists("file.txt"))
|
||||||
self.storage.connection.meta.client.head_object.assert_called_with(
|
self.storage.connection.meta.client.head_object.assert_called_with(
|
||||||
Bucket=self.storage.bucket_name,
|
Bucket=self.storage.bucket_name, Key="file.txt",
|
||||||
Key="file.txt",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_storage_exists_false(self):
|
def test_storage_exists_false(self):
|
||||||
self.storage.connection.meta.client.head_object.side_effect = ClientError(
|
self.storage.connection.meta.client.head_object.side_effect = ClientError(
|
||||||
{'Error': {'Code': '404', 'Message': 'Not Found'}},
|
{"Error": {"Code": "404", "Message": "Not Found"}}, "HeadObject",
|
||||||
'HeadObject',
|
|
||||||
)
|
)
|
||||||
self.assertFalse(self.storage.exists("file.txt"))
|
self.assertFalse(self.storage.exists("file.txt"))
|
||||||
self.storage.connection.meta.client.head_object.assert_called_with(
|
self.storage.connection.meta.client.head_object.assert_called_with(
|
||||||
Bucket=self.storage.bucket_name,
|
Bucket=self.storage.bucket_name, Key="file.txt",
|
||||||
Key='file.txt',
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_storage_exists_doesnt_create_bucket(self):
|
def test_storage_exists_doesnt_create_bucket(self):
|
||||||
with mock.patch.object(self.storage, '_get_or_create_bucket') as method:
|
with mock.patch.object(self.storage, "_get_or_create_bucket") as method:
|
||||||
self.storage.exists('file.txt')
|
self.storage.exists("file.txt")
|
||||||
self.assertFalse(method.called)
|
self.assertFalse(method.called)
|
||||||
|
|
||||||
def test_storage_delete(self):
|
def test_storage_delete(self):
|
||||||
self.storage.delete("path/to/file.txt")
|
self.storage.delete("path/to/file.txt")
|
||||||
self.storage.bucket.Object.assert_called_with('path/to/file.txt')
|
self.storage.bucket.Object.assert_called_with("path/to/file.txt")
|
||||||
self.storage.bucket.Object.return_value.delete.assert_called_with()
|
self.storage.bucket.Object.return_value.delete.assert_called_with()
|
||||||
|
|
||||||
def test_storage_listdir_base(self):
|
def test_storage_listdir_base(self):
|
||||||
|
|
@ -464,14 +438,8 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
# 4.txt
|
# 4.txt
|
||||||
pages = [
|
pages = [
|
||||||
{
|
{
|
||||||
'CommonPrefixes': [
|
"CommonPrefixes": [{"Prefix": "some"}, {"Prefix": "other"},],
|
||||||
{'Prefix': 'some'},
|
"Contents": [{"Key": "2.txt"}, {"Key": "4.txt"},],
|
||||||
{'Prefix': 'other'},
|
|
||||||
],
|
|
||||||
'Contents': [
|
|
||||||
{'Key': '2.txt'},
|
|
||||||
{'Key': '4.txt'},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -479,42 +447,35 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
paginator.paginate.return_value = pages
|
paginator.paginate.return_value = pages
|
||||||
self.storage._connections.connection.meta.client.get_paginator.return_value = paginator
|
self.storage._connections.connection.meta.client.get_paginator.return_value = paginator
|
||||||
|
|
||||||
dirs, files = self.storage.listdir('')
|
dirs, files = self.storage.listdir("")
|
||||||
paginator.paginate.assert_called_with(Bucket=None, Delimiter='/', Prefix='')
|
paginator.paginate.assert_called_with(Bucket=None, Delimiter="/", Prefix="")
|
||||||
|
|
||||||
self.assertEqual(dirs, ['some', 'other'])
|
self.assertEqual(dirs, ["some", "other"])
|
||||||
self.assertEqual(files, ['2.txt', '4.txt'])
|
self.assertEqual(files, ["2.txt", "4.txt"])
|
||||||
|
|
||||||
def test_storage_listdir_subdir(self):
|
def test_storage_listdir_subdir(self):
|
||||||
# Files:
|
# Files:
|
||||||
# some/path/1.txt
|
# some/path/1.txt
|
||||||
# some/2.txt
|
# some/2.txt
|
||||||
pages = [
|
pages = [
|
||||||
{
|
{"CommonPrefixes": [{"Prefix": "some/path"},], "Contents": [{"Key": "some/2.txt"},],},
|
||||||
'CommonPrefixes': [
|
|
||||||
{'Prefix': 'some/path'},
|
|
||||||
],
|
|
||||||
'Contents': [
|
|
||||||
{'Key': 'some/2.txt'},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
paginator = mock.MagicMock()
|
paginator = mock.MagicMock()
|
||||||
paginator.paginate.return_value = pages
|
paginator.paginate.return_value = pages
|
||||||
self.storage._connections.connection.meta.client.get_paginator.return_value = paginator
|
self.storage._connections.connection.meta.client.get_paginator.return_value = paginator
|
||||||
|
|
||||||
dirs, files = self.storage.listdir('some/')
|
dirs, files = self.storage.listdir("some/")
|
||||||
paginator.paginate.assert_called_with(Bucket=None, Delimiter='/', Prefix='some/')
|
paginator.paginate.assert_called_with(Bucket=None, Delimiter="/", Prefix="some/")
|
||||||
|
|
||||||
self.assertEqual(dirs, ['path'])
|
self.assertEqual(dirs, ["path"])
|
||||||
self.assertEqual(files, ['2.txt'])
|
self.assertEqual(files, ["2.txt"])
|
||||||
|
|
||||||
def test_storage_size(self):
|
def test_storage_size(self):
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.content_length = 4098
|
obj.content_length = 4098
|
||||||
|
|
||||||
name = 'file.txt'
|
name = "file.txt"
|
||||||
self.assertEqual(self.storage.size(name), obj.content_length)
|
self.assertEqual(self.storage.size(name), obj.content_length)
|
||||||
|
|
||||||
def test_storage_mtime(self):
|
def test_storage_mtime(self):
|
||||||
|
|
@ -527,30 +488,29 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
obj = self.storage.bucket.Object.return_value
|
obj = self.storage.bucket.Object.return_value
|
||||||
obj.last_modified = datetime.datetime.now(utc)
|
obj.last_modified = datetime.datetime.now(utc)
|
||||||
|
|
||||||
name = 'file.txt'
|
name = "file.txt"
|
||||||
self.assertFalse(
|
self.assertFalse(
|
||||||
is_aware(self.storage.modified_time(name)),
|
is_aware(self.storage.modified_time(name)),
|
||||||
'Naive datetime object expected from modified_time()'
|
"Naive datetime object expected from modified_time()",
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertIs(
|
self.assertIs(
|
||||||
settings.USE_TZ,
|
settings.USE_TZ,
|
||||||
is_aware(self.storage.get_modified_time(name)),
|
is_aware(self.storage.get_modified_time(name)),
|
||||||
'{} datetime object expected from get_modified_time() when USE_TZ={}'.format(
|
"{} datetime object expected from get_modified_time() when USE_TZ={}".format(
|
||||||
('Naive', 'Aware')[settings.USE_TZ],
|
("Naive", "Aware")[settings.USE_TZ], settings.USE_TZ
|
||||||
settings.USE_TZ
|
),
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_storage_url(self):
|
def test_storage_url(self):
|
||||||
name = 'test_storage_size.txt'
|
name = "test_storage_size.txt"
|
||||||
url = 'http://aws.amazon.com/%s' % name
|
url = "http://aws.amazon.com/%s" % name
|
||||||
self.storage.bucket.meta.client.generate_presigned_url.return_value = url
|
self.storage.bucket.meta.client.generate_presigned_url.return_value = url
|
||||||
self.storage.bucket.name = 'bucket'
|
self.storage.bucket.name = "bucket"
|
||||||
self.assertEqual(self.storage.url(name), url)
|
self.assertEqual(self.storage.url(name), url)
|
||||||
self.storage.bucket.meta.client.generate_presigned_url.assert_called_with(
|
self.storage.bucket.meta.client.generate_presigned_url.assert_called_with(
|
||||||
'get_object',
|
"get_object",
|
||||||
Params={'Bucket': self.storage.bucket.name, 'Key': name},
|
Params={"Bucket": self.storage.bucket.name, "Key": name},
|
||||||
ExpiresIn=self.storage.querystring_expire,
|
ExpiresIn=self.storage.querystring_expire,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -558,26 +518,24 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
|
|
||||||
self.assertEqual(self.storage.url(name, expire=custom_expire), url)
|
self.assertEqual(self.storage.url(name, expire=custom_expire), url)
|
||||||
self.storage.bucket.meta.client.generate_presigned_url.assert_called_with(
|
self.storage.bucket.meta.client.generate_presigned_url.assert_called_with(
|
||||||
'get_object',
|
"get_object",
|
||||||
Params={'Bucket': self.storage.bucket.name, 'Key': name},
|
Params={"Bucket": self.storage.bucket.name, "Key": name},
|
||||||
ExpiresIn=custom_expire,
|
ExpiresIn=custom_expire,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_generated_url_is_encoded(self):
|
def test_generated_url_is_encoded(self):
|
||||||
self.storage.custom_domain = "mock.cloudfront.net"
|
self.storage.custom_domain = "mock.cloudfront.net"
|
||||||
filename = "whacky & filename.mp4"
|
filename = "whacky & filename.mp4"
|
||||||
url = self.storage.url(filename)
|
url = self.storage.url(filename)
|
||||||
parsed_url = urlparse.urlparse(url)
|
parsed_url = urlparse.urlparse(url)
|
||||||
self.assertEqual(parsed_url.path,
|
self.assertEqual(parsed_url.path, "/whacky%20%26%20filename.mp4")
|
||||||
"/whacky%20%26%20filename.mp4")
|
|
||||||
self.assertFalse(self.storage.bucket.meta.client.generate_presigned_url.called)
|
self.assertFalse(self.storage.bucket.meta.client.generate_presigned_url.called)
|
||||||
|
|
||||||
def test_special_characters(self):
|
def test_special_characters(self):
|
||||||
self.storage.custom_domain = "mock.cloudfront.net"
|
self.storage.custom_domain = "mock.cloudfront.net"
|
||||||
|
|
||||||
name = "ãlöhâ.jpg"
|
name = "ãlöhâ.jpg"
|
||||||
content = ContentFile('new content')
|
content = ContentFile("new content")
|
||||||
self.storage.save(name, content)
|
self.storage.save(name, content)
|
||||||
self.storage.bucket.Object.assert_called_once_with(name)
|
self.storage.bucket.Object.assert_called_once_with(name)
|
||||||
|
|
||||||
|
|
@ -586,13 +544,21 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
self.assertEqual(parsed_url.path, "/%C3%A3l%C3%B6h%C3%A2.jpg")
|
self.assertEqual(parsed_url.path, "/%C3%A3l%C3%B6h%C3%A2.jpg")
|
||||||
|
|
||||||
def test_strip_signing_parameters(self):
|
def test_strip_signing_parameters(self):
|
||||||
expected = 'http://bucket.s3-aws-region.amazonaws.com/foo/bar'
|
expected = "http://bucket.s3-aws-region.amazonaws.com/foo/bar"
|
||||||
self.assertEqual(self.storage._strip_signing_parameters(
|
self.assertEqual(
|
||||||
'%s?X-Amz-Date=12345678&X-Amz-Signature=Signature' % expected), expected)
|
self.storage._strip_signing_parameters(
|
||||||
self.assertEqual(self.storage._strip_signing_parameters(
|
"%s?X-Amz-Date=12345678&X-Amz-Signature=Signature" % expected
|
||||||
'%s?expires=12345678&signature=Signature' % expected), expected)
|
),
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
self.storage._strip_signing_parameters(
|
||||||
|
"%s?expires=12345678&signature=Signature" % expected
|
||||||
|
),
|
||||||
|
expected,
|
||||||
|
)
|
||||||
|
|
||||||
@skipIf(threading is None, 'Test requires threading')
|
@skipIf(threading is None, "Test requires threading")
|
||||||
def test_connection_threading(self):
|
def test_connection_threading(self):
|
||||||
connections = []
|
connections = []
|
||||||
|
|
||||||
|
|
@ -613,23 +579,23 @@ class S3Boto3StorageTests(S3Boto3TestCase):
|
||||||
"Found '/'. Use '' instead."
|
"Found '/'. Use '' instead."
|
||||||
)
|
)
|
||||||
with self.assertRaises(ImproperlyConfigured, msg=msg):
|
with self.assertRaises(ImproperlyConfigured, msg=msg):
|
||||||
s3boto3.S3Boto3Storage(location='/')
|
s3boto3.S3Boto3Storage(location="/")
|
||||||
|
|
||||||
def test_override_class_variable(self):
|
def test_override_class_variable(self):
|
||||||
class MyStorage1(s3boto3.S3Boto3Storage):
|
class MyStorage1(s3boto3.S3Boto3Storage):
|
||||||
location = 'foo1'
|
location = "foo1"
|
||||||
|
|
||||||
storage = MyStorage1()
|
storage = MyStorage1()
|
||||||
self.assertEqual(storage.location, 'foo1')
|
self.assertEqual(storage.location, "foo1")
|
||||||
|
|
||||||
class MyStorage2(s3boto3.S3Boto3Storage):
|
class MyStorage2(s3boto3.S3Boto3Storage):
|
||||||
location = 'foo2'
|
location = "foo2"
|
||||||
|
|
||||||
storage = MyStorage2()
|
storage = MyStorage2()
|
||||||
self.assertEqual(storage.location, 'foo2')
|
self.assertEqual(storage.location, "foo2")
|
||||||
|
|
||||||
def test_override_init_argument(self):
|
def test_override_init_argument(self):
|
||||||
storage = s3boto3.S3Boto3Storage(location='foo1')
|
storage = s3boto3.S3Boto3Storage(location="foo1")
|
||||||
self.assertEqual(storage.location, 'foo1')
|
self.assertEqual(storage.location, "foo1")
|
||||||
storage = s3boto3.S3Boto3Storage(location='foo2')
|
storage = s3boto3.S3Boto3Storage(location="foo2")
|
||||||
self.assertEqual(storage.location, 'foo2')
|
self.assertEqual(storage.location, "foo2")
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ class HelpEntry(SharedMemoryModel):
|
||||||
"aliases": " ".join(self.aliases.all()),
|
"aliases": " ".join(self.aliases.all()),
|
||||||
"category": self.db_help_category,
|
"category": self.db_help_category,
|
||||||
"text": self.db_entrytext,
|
"text": self.db_entrytext,
|
||||||
"tags": " ".join(str(tag) for tag in self.tags.all())
|
"tags": " ".join(str(tag) for tag in self.tags.all()),
|
||||||
}
|
}
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|
|
||||||
|
|
@ -2134,6 +2134,7 @@ class DefaultCharacter(DefaultObject):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from evennia.utils.utils import latinify
|
from evennia.utils.utils import latinify
|
||||||
|
|
||||||
latin_name = latinify(name, default="X")
|
latin_name = latinify(name, default="X")
|
||||||
return latin_name
|
return latin_name
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,9 @@ class DefaultObjectTest(EvenniaTest):
|
||||||
self.assertEqual(obj.db_home, self.room1)
|
self.assertEqual(obj.db_home, self.room1)
|
||||||
|
|
||||||
def test_character_create_weirdname(self):
|
def test_character_create_weirdname(self):
|
||||||
obj, errors = DefaultCharacter.create("SigurðurÞórarinsson", self.account, home=self.room1.dbref)
|
obj, errors = DefaultCharacter.create(
|
||||||
|
"SigurðurÞórarinsson", self.account, home=self.room1.dbref
|
||||||
|
)
|
||||||
self.assertTrue(obj, errors)
|
self.assertTrue(obj, errors)
|
||||||
self.assertFalse(errors, errors)
|
self.assertFalse(errors, errors)
|
||||||
self.assertEqual(obj.name, "SigurXurXorarinsson")
|
self.assertEqual(obj.name, "SigurXurXorarinsson")
|
||||||
|
|
|
||||||
|
|
@ -255,9 +255,7 @@ IN_GAME_ERRORS = True
|
||||||
DATABASES = {
|
DATABASES = {
|
||||||
"default": {
|
"default": {
|
||||||
"ENGINE": "django.db.backends.sqlite3",
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
"NAME": os.getenv(
|
"NAME": os.getenv("TEST_DB_PATH", os.path.join(GAME_DIR, "server", "evennia.db3")),
|
||||||
"TEST_DB_PATH", os.path.join(GAME_DIR, "server", "evennia.db3")
|
|
||||||
),
|
|
||||||
"USER": "",
|
"USER": "",
|
||||||
"PASSWORD": "",
|
"PASSWORD": "",
|
||||||
"HOST": "",
|
"HOST": "",
|
||||||
|
|
@ -872,9 +870,7 @@ TEMPLATES = [
|
||||||
os.path.join(GAME_DIR, "web", "template_overrides"),
|
os.path.join(GAME_DIR, "web", "template_overrides"),
|
||||||
os.path.join(EVENNIA_DIR, "web", "website", "templates", WEBSITE_TEMPLATE),
|
os.path.join(EVENNIA_DIR, "web", "website", "templates", WEBSITE_TEMPLATE),
|
||||||
os.path.join(EVENNIA_DIR, "web", "website", "templates"),
|
os.path.join(EVENNIA_DIR, "web", "website", "templates"),
|
||||||
os.path.join(
|
os.path.join(EVENNIA_DIR, "web", "webclient", "templates", WEBCLIENT_TEMPLATE),
|
||||||
EVENNIA_DIR, "web", "webclient", "templates", WEBCLIENT_TEMPLATE
|
|
||||||
),
|
|
||||||
os.path.join(EVENNIA_DIR, "web", "webclient", "templates"),
|
os.path.join(EVENNIA_DIR, "web", "webclient", "templates"),
|
||||||
],
|
],
|
||||||
"APP_DIRS": True,
|
"APP_DIRS": True,
|
||||||
|
|
@ -946,9 +942,7 @@ AUTH_USER_MODEL = "accounts.AccountDB"
|
||||||
# Password validation plugins
|
# Password validation plugins
|
||||||
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
{
|
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
|
||||||
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
"OPTIONS": {"min_length": 8},
|
"OPTIONS": {"min_length": 8},
|
||||||
|
|
@ -961,14 +955,8 @@ AUTH_PASSWORD_VALIDATORS = [
|
||||||
# Username validation plugins
|
# Username validation plugins
|
||||||
AUTH_USERNAME_VALIDATORS = [
|
AUTH_USERNAME_VALIDATORS = [
|
||||||
{"NAME": "django.contrib.auth.validators.ASCIIUsernameValidator"},
|
{"NAME": "django.contrib.auth.validators.ASCIIUsernameValidator"},
|
||||||
{
|
{"NAME": "django.core.validators.MinLengthValidator", "OPTIONS": {"limit_value": 3},},
|
||||||
"NAME": "django.core.validators.MinLengthValidator",
|
{"NAME": "django.core.validators.MaxLengthValidator", "OPTIONS": {"limit_value": 30},},
|
||||||
"OPTIONS": {"limit_value": 3},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"NAME": "django.core.validators.MaxLengthValidator",
|
|
||||||
"OPTIONS": {"limit_value": 30},
|
|
||||||
},
|
|
||||||
{"NAME": "evennia.server.validators.EvenniaUsernameAvailabilityValidator"},
|
{"NAME": "evennia.server.validators.EvenniaUsernameAvailabilityValidator"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -982,22 +970,18 @@ MESSAGE_TAGS = {messages.ERROR: "danger"}
|
||||||
# Django REST Framework settings
|
# Django REST Framework settings
|
||||||
REST_FRAMEWORK = {
|
REST_FRAMEWORK = {
|
||||||
# django_filters allows you to specify search fields for models in an API View
|
# django_filters allows you to specify search fields for models in an API View
|
||||||
'DEFAULT_FILTER_BACKENDS': (
|
"DEFAULT_FILTER_BACKENDS": ("django_filters.rest_framework.DjangoFilterBackend",),
|
||||||
'django_filters.rest_framework.DjangoFilterBackend',
|
|
||||||
),
|
|
||||||
# whether to paginate results and how many per page
|
# whether to paginate results and how many per page
|
||||||
"DEFAULT_PAGINATION_CLASS": 'rest_framework.pagination.LimitOffsetPagination',
|
"DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination",
|
||||||
'PAGE_SIZE': 25,
|
"PAGE_SIZE": 25,
|
||||||
# require logged in users to call API so that access checks can work on them
|
# require logged in users to call API so that access checks can work on them
|
||||||
'DEFAULT_PERMISSION_CLASSES': [
|
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated",],
|
||||||
'rest_framework.permissions.IsAuthenticated',
|
|
||||||
],
|
|
||||||
# These are the different ways people can authenticate for API requests - via
|
# These are the different ways people can authenticate for API requests - via
|
||||||
# session or with user/password. Other ways are possible, such as via tokens
|
# session or with user/password. Other ways are possible, such as via tokens
|
||||||
# or oauth, but require additional dependencies.
|
# or oauth, but require additional dependencies.
|
||||||
'DEFAULT_AUTHENTICATION_CLASSES': [
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||||
'rest_framework.authentication.BasicAuthentication',
|
"rest_framework.authentication.BasicAuthentication",
|
||||||
'rest_framework.authentication.SessionAuthentication',
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
],
|
],
|
||||||
# default permission checks used by the EvenniaPermission class
|
# default permission checks used by the EvenniaPermission class
|
||||||
"DEFAULT_CREATE_PERMISSION": "builder",
|
"DEFAULT_CREATE_PERMISSION": "builder",
|
||||||
|
|
|
||||||
|
|
@ -335,8 +335,9 @@ class EvMore(object):
|
||||||
# no justification. Simple division by line
|
# no justification. Simple division by line
|
||||||
lines = text.split("\n")
|
lines = text.split("\n")
|
||||||
|
|
||||||
self._data = [_LBR.join(lines[i: i + self.height])
|
self._data = [
|
||||||
for i in range(0, len(lines), self.height)]
|
_LBR.join(lines[i : i + self.height]) for i in range(0, len(lines), self.height)
|
||||||
|
]
|
||||||
self._npages = len(self._data)
|
self._npages = len(self._data)
|
||||||
self._paginator = self.paginator_index
|
self._paginator = self.paginator_index
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,7 @@ class LatinifyTest(TestCase):
|
||||||
class TestFormatGrid(TestCase):
|
class TestFormatGrid(TestCase):
|
||||||
|
|
||||||
maxDiff = None
|
maxDiff = None
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
# make the random only semi-random with a fixed seed
|
# make the random only semi-random with a fixed seed
|
||||||
random.seed(1)
|
random.seed(1)
|
||||||
|
|
@ -283,9 +284,21 @@ class TestFormatGrid(TestCase):
|
||||||
|
|
||||||
def test_overlap(self):
|
def test_overlap(self):
|
||||||
"""Grid with elements overlapping into the next slot"""
|
"""Grid with elements overlapping into the next slot"""
|
||||||
elements = ("alias", "batchcode", "batchcommands", "cmdsets",
|
elements = (
|
||||||
"copy", "cpattr", "desc", "destroy", "dig",
|
"alias",
|
||||||
"examine", "find", "force", "lock")
|
"batchcode",
|
||||||
|
"batchcommands",
|
||||||
|
"cmdsets",
|
||||||
|
"copy",
|
||||||
|
"cpattr",
|
||||||
|
"desc",
|
||||||
|
"destroy",
|
||||||
|
"dig",
|
||||||
|
"examine",
|
||||||
|
"find",
|
||||||
|
"force",
|
||||||
|
"lock",
|
||||||
|
)
|
||||||
rows = utils.format_grid(elements, width=78)
|
rows = utils.format_grid(elements, width=78)
|
||||||
self.assertEqual(len(rows), 2)
|
self.assertEqual(len(rows), 2)
|
||||||
for element in elements:
|
for element in elements:
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ class TagTypeFilter(CharFilter):
|
||||||
"""
|
"""
|
||||||
This class lets you create different filters for tags of a specified db_tagtype.
|
This class lets you create different filters for tags of a specified db_tagtype.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tag_type = None
|
tag_type = None
|
||||||
|
|
||||||
def filter(self, qs, value):
|
def filter(self, qs, value):
|
||||||
|
|
@ -45,11 +46,13 @@ class TagTypeFilter(CharFilter):
|
||||||
|
|
||||||
class AliasFilter(TagTypeFilter):
|
class AliasFilter(TagTypeFilter):
|
||||||
"""A filter for objects by their aliases (tags with a tagtype of 'alias'"""
|
"""A filter for objects by their aliases (tags with a tagtype of 'alias'"""
|
||||||
|
|
||||||
tag_type = "alias"
|
tag_type = "alias"
|
||||||
|
|
||||||
|
|
||||||
class PermissionFilter(TagTypeFilter):
|
class PermissionFilter(TagTypeFilter):
|
||||||
"""A filter for objects by their permissions (tags with a tagtype of 'permission'"""
|
"""A filter for objects by their permissions (tags with a tagtype of 'permission'"""
|
||||||
|
|
||||||
tag_type = "permission"
|
tag_type = "permission"
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -58,6 +61,7 @@ SHARED_FIELDS = ["db_key", "db_typeclass_path", "db_tags__db_key", "db_tags__db_
|
||||||
|
|
||||||
class BaseTypeclassFilterSet(FilterSet):
|
class BaseTypeclassFilterSet(FilterSet):
|
||||||
"""A parent class with filters for aliases and permissions"""
|
"""A parent class with filters for aliases and permissions"""
|
||||||
|
|
||||||
alias = AliasFilter(lookup_expr="iexact")
|
alias = AliasFilter(lookup_expr="iexact")
|
||||||
permission = PermissionFilter(lookup_expr="iexact")
|
permission = PermissionFilter(lookup_expr="iexact")
|
||||||
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_key")
|
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="db_key")
|
||||||
|
|
@ -81,14 +85,20 @@ class BaseTypeclassFilterSet(FilterSet):
|
||||||
|
|
||||||
class ObjectDBFilterSet(BaseTypeclassFilterSet):
|
class ObjectDBFilterSet(BaseTypeclassFilterSet):
|
||||||
"""This adds filters for ObjectDB instances - characters, rooms, exits, etc"""
|
"""This adds filters for ObjectDB instances - characters, rooms, exits, etc"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ObjectDB
|
model = ObjectDB
|
||||||
fields = SHARED_FIELDS + ["db_location__db_key", "db_home__db_key", "db_location__id",
|
fields = SHARED_FIELDS + [
|
||||||
"db_home__id"]
|
"db_location__db_key",
|
||||||
|
"db_home__db_key",
|
||||||
|
"db_location__id",
|
||||||
|
"db_home__id",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
class AccountDBFilterSet(BaseTypeclassFilterSet):
|
class AccountDBFilterSet(BaseTypeclassFilterSet):
|
||||||
"""This adds filters for Account objects"""
|
"""This adds filters for Account objects"""
|
||||||
|
|
||||||
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="username")
|
name = CharFilter(lookup_expr="iexact", method="filter_name", field_name="username")
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
|
@ -98,7 +108,16 @@ class AccountDBFilterSet(BaseTypeclassFilterSet):
|
||||||
|
|
||||||
class ScriptDBFilterSet(BaseTypeclassFilterSet):
|
class ScriptDBFilterSet(BaseTypeclassFilterSet):
|
||||||
"""This adds filters for Script objects"""
|
"""This adds filters for Script objects"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ScriptDB
|
model = ScriptDB
|
||||||
fields = SHARED_FIELDS + ["db_desc", "db_obj__db_key", "db_obj__id", "db_account__id",
|
fields = SHARED_FIELDS + [
|
||||||
"db_account__username", "db_is_active", "db_persistent", "db_interval"]
|
"db_desc",
|
||||||
|
"db_obj__db_key",
|
||||||
|
"db_obj__id",
|
||||||
|
"db_account__id",
|
||||||
|
"db_account__username",
|
||||||
|
"db_is_active",
|
||||||
|
"db_persistent",
|
||||||
|
"db_interval",
|
||||||
|
]
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ class EvenniaPermission(permissions.BasePermission):
|
||||||
Evennia's permission structure. Based on the action in a given
|
Evennia's permission structure. Based on the action in a given
|
||||||
view, we'll check a corresponding Evennia access/lock check.
|
view, we'll check a corresponding Evennia access/lock check.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# subclass this to change these permissions
|
# subclass this to change these permissions
|
||||||
MINIMUM_LIST_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_LIST_PERMISSION", "builder")
|
MINIMUM_LIST_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_LIST_PERMISSION", "builder")
|
||||||
MINIMUM_CREATE_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_CREATE_PERMISSION", "builder")
|
MINIMUM_CREATE_PERMISSION = settings.REST_FRAMEWORK.get("DEFAULT_CREATE_PERMISSION", "builder")
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,15 @@ class TypeclassSerializerMixin(object):
|
||||||
not have them render PK-related fields.
|
not have them render PK-related fields.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
shared_fields = ["id", "db_key", "attributes", "db_typeclass_path", "aliases", "tags", "permissions"]
|
shared_fields = [
|
||||||
|
"id",
|
||||||
|
"db_key",
|
||||||
|
"attributes",
|
||||||
|
"db_typeclass_path",
|
||||||
|
"aliases",
|
||||||
|
"tags",
|
||||||
|
"permissions",
|
||||||
|
]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_tags(obj):
|
def get_tags(obj):
|
||||||
|
|
@ -98,7 +106,9 @@ class TypeclassSerializerMixin(object):
|
||||||
Returns:
|
Returns:
|
||||||
List of TagSerializer data
|
List of TagSerializer data
|
||||||
"""
|
"""
|
||||||
return TagSerializer(obj.permissions.get(return_tagobj=True, return_list=True), many=True).data
|
return TagSerializer(
|
||||||
|
obj.permissions.get(return_tagobj=True, return_list=True), many=True
|
||||||
|
).data
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_attributes(obj):
|
def get_attributes(obj):
|
||||||
|
|
@ -136,7 +146,13 @@ class ObjectDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = DefaultObject
|
model = DefaultObject
|
||||||
fields = ["db_location", "db_home", "contents", "exits", "nicks"] + TypeclassSerializerMixin.shared_fields
|
fields = [
|
||||||
|
"db_location",
|
||||||
|
"db_home",
|
||||||
|
"contents",
|
||||||
|
"exits",
|
||||||
|
"nicks",
|
||||||
|
] + TypeclassSerializerMixin.shared_fields
|
||||||
read_only_fields = ["id"]
|
read_only_fields = ["id"]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -168,6 +184,7 @@ class ObjectDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
|
||||||
|
|
||||||
class AccountSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
|
class AccountSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
|
||||||
"""This uses the DefaultAccount object to have access to the sessions property"""
|
"""This uses the DefaultAccount object to have access to the sessions property"""
|
||||||
|
|
||||||
attributes = serializers.SerializerMethodField()
|
attributes = serializers.SerializerMethodField()
|
||||||
nicks = serializers.SerializerMethodField()
|
nicks = serializers.SerializerMethodField()
|
||||||
db_key = serializers.CharField(required=False)
|
db_key = serializers.CharField(required=False)
|
||||||
|
|
@ -202,6 +219,11 @@ class ScriptDBSerializer(TypeclassSerializerMixin, serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = ScriptDB
|
model = ScriptDB
|
||||||
fields = ["db_interval", "db_persistent", "db_start_delay",
|
fields = [
|
||||||
"db_is_active", "db_repeats"] + TypeclassSerializerMixin.shared_fields
|
"db_interval",
|
||||||
|
"db_persistent",
|
||||||
|
"db_start_delay",
|
||||||
|
"db_is_active",
|
||||||
|
"db_repeats",
|
||||||
|
] + TypeclassSerializerMixin.shared_fields
|
||||||
read_only_fields = ["id"]
|
read_only_fields = ["id"]
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,7 @@ urlpatterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(REST_API_ENABLED=True, ROOT_URLCONF=__name__, AUTH_USERNAME_VALIDATORS=[])
|
||||||
REST_API_ENABLED=True, ROOT_URLCONF=__name__, AUTH_USERNAME_VALIDATORS=[]
|
|
||||||
)
|
|
||||||
class TestEvenniaRESTApi(EvenniaTest):
|
class TestEvenniaRESTApi(EvenniaTest):
|
||||||
client_class = APIClient
|
client_class = APIClient
|
||||||
maxDiff = None
|
maxDiff = None
|
||||||
|
|
@ -37,27 +35,58 @@ class TestEvenniaRESTApi(EvenniaTest):
|
||||||
|
|
||||||
def get_view_details(self, action):
|
def get_view_details(self, action):
|
||||||
"""Helper function for generating list of named tuples"""
|
"""Helper function for generating list of named tuples"""
|
||||||
View = namedtuple("View", ["view_name", "obj", "list", "serializer", "create_data", "retrieve_data"])
|
View = namedtuple(
|
||||||
|
"View", ["view_name", "obj", "list", "serializer", "create_data", "retrieve_data"]
|
||||||
|
)
|
||||||
views = [
|
views = [
|
||||||
View("object-%s" % action, self.obj1, [self.obj1, self.char1, self.exit, self.room1, self.room2, self.obj2,
|
View(
|
||||||
self.char2], serializers.ObjectDBSerializer,
|
"object-%s" % action,
|
||||||
|
self.obj1,
|
||||||
|
[self.obj1, self.char1, self.exit, self.room1, self.room2, self.obj2, self.char2],
|
||||||
|
serializers.ObjectDBSerializer,
|
||||||
{"db_key": "object-create-test-name"},
|
{"db_key": "object-create-test-name"},
|
||||||
serializers.ObjectDBSerializer(self.obj1).data),
|
serializers.ObjectDBSerializer(self.obj1).data,
|
||||||
View("character-%s" % action, self.char1, [self.char1, self.char2], serializers.ObjectDBSerializer,
|
),
|
||||||
|
View(
|
||||||
|
"character-%s" % action,
|
||||||
|
self.char1,
|
||||||
|
[self.char1, self.char2],
|
||||||
|
serializers.ObjectDBSerializer,
|
||||||
{"db_key": "character-create-test-name"},
|
{"db_key": "character-create-test-name"},
|
||||||
serializers.ObjectDBSerializer(self.char1).data),
|
serializers.ObjectDBSerializer(self.char1).data,
|
||||||
View("exit-%s" % action, self.exit, [self.exit], serializers.ObjectDBSerializer,
|
),
|
||||||
|
View(
|
||||||
|
"exit-%s" % action,
|
||||||
|
self.exit,
|
||||||
|
[self.exit],
|
||||||
|
serializers.ObjectDBSerializer,
|
||||||
{"db_key": "exit-create-test-name"},
|
{"db_key": "exit-create-test-name"},
|
||||||
serializers.ObjectDBSerializer(self.exit).data),
|
serializers.ObjectDBSerializer(self.exit).data,
|
||||||
View("room-%s" % action, self.room1, [self.room1, self.room2], serializers.ObjectDBSerializer,
|
),
|
||||||
|
View(
|
||||||
|
"room-%s" % action,
|
||||||
|
self.room1,
|
||||||
|
[self.room1, self.room2],
|
||||||
|
serializers.ObjectDBSerializer,
|
||||||
{"db_key": "room-create-test-name"},
|
{"db_key": "room-create-test-name"},
|
||||||
serializers.ObjectDBSerializer(self.room1).data),
|
serializers.ObjectDBSerializer(self.room1).data,
|
||||||
View("script-%s" % action, self.script, [self.script], serializers.ScriptDBSerializer,
|
),
|
||||||
|
View(
|
||||||
|
"script-%s" % action,
|
||||||
|
self.script,
|
||||||
|
[self.script],
|
||||||
|
serializers.ScriptDBSerializer,
|
||||||
{"db_key": "script-create-test-name"},
|
{"db_key": "script-create-test-name"},
|
||||||
serializers.ScriptDBSerializer(self.script).data),
|
serializers.ScriptDBSerializer(self.script).data,
|
||||||
View("account-%s" % action, self.account2, [self.account, self.account2], serializers.AccountSerializer,
|
),
|
||||||
|
View(
|
||||||
|
"account-%s" % action,
|
||||||
|
self.account2,
|
||||||
|
[self.account, self.account2],
|
||||||
|
serializers.AccountSerializer,
|
||||||
{"username": "account-create-test-name"},
|
{"username": "account-create-test-name"},
|
||||||
serializers.AccountSerializer(self.account2).data),
|
serializers.AccountSerializer(self.account2).data,
|
||||||
|
),
|
||||||
]
|
]
|
||||||
return views
|
return views
|
||||||
|
|
||||||
|
|
@ -65,9 +94,7 @@ class TestEvenniaRESTApi(EvenniaTest):
|
||||||
views = self.get_view_details("detail")
|
views = self.get_view_details("detail")
|
||||||
for view in views:
|
for view in views:
|
||||||
with self.subTest(msg="Testing {} retrieve".format(view.view_name)):
|
with self.subTest(msg="Testing {} retrieve".format(view.view_name)):
|
||||||
view_url = reverse(
|
view_url = reverse("api:{}".format(view.view_name), kwargs={"pk": view.obj.pk})
|
||||||
"api:{}".format(view.view_name), kwargs={"pk": view.obj.pk}
|
|
||||||
)
|
|
||||||
response = self.client.get(view_url)
|
response = self.client.get(view_url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertDictEqual(response.data, view.retrieve_data)
|
self.assertDictEqual(response.data, view.retrieve_data)
|
||||||
|
|
@ -76,9 +103,7 @@ class TestEvenniaRESTApi(EvenniaTest):
|
||||||
views = self.get_view_details("detail")
|
views = self.get_view_details("detail")
|
||||||
for view in views:
|
for view in views:
|
||||||
with self.subTest(msg="Testing {} update".format(view.view_name)):
|
with self.subTest(msg="Testing {} update".format(view.view_name)):
|
||||||
view_url = reverse(
|
view_url = reverse("api:{}".format(view.view_name), kwargs={"pk": view.obj.pk})
|
||||||
"api:{}".format(view.view_name), kwargs={"pk": view.obj.pk}
|
|
||||||
)
|
|
||||||
# test both PUT (update) and PATCH (partial update) here
|
# test both PUT (update) and PATCH (partial update) here
|
||||||
for new_key, method in (("foobar", "put"), ("fizzbuzz", "patch")):
|
for new_key, method in (("foobar", "put"), ("fizzbuzz", "patch")):
|
||||||
field = "username" if "account" in view.view_name else "db_key"
|
field = "username" if "account" in view.view_name else "db_key"
|
||||||
|
|
@ -93,9 +118,7 @@ class TestEvenniaRESTApi(EvenniaTest):
|
||||||
views = self.get_view_details("detail")
|
views = self.get_view_details("detail")
|
||||||
for view in views:
|
for view in views:
|
||||||
with self.subTest(msg="Testing {} delete".format(view.view_name)):
|
with self.subTest(msg="Testing {} delete".format(view.view_name)):
|
||||||
view_url = reverse(
|
view_url = reverse("api:{}".format(view.view_name), kwargs={"pk": view.obj.pk})
|
||||||
"api:{}".format(view.view_name), kwargs={"pk": view.obj.pk}
|
|
||||||
)
|
|
||||||
response = self.client.delete(view_url)
|
response = self.client.delete(view_url)
|
||||||
self.assertEqual(response.status_code, 204)
|
self.assertEqual(response.status_code, 204)
|
||||||
with self.assertRaises(ObjectDoesNotExist):
|
with self.assertRaises(ObjectDoesNotExist):
|
||||||
|
|
@ -108,7 +131,9 @@ class TestEvenniaRESTApi(EvenniaTest):
|
||||||
view_url = reverse(f"api:{view.view_name}")
|
view_url = reverse(f"api:{view.view_name}")
|
||||||
response = self.client.get(view_url)
|
response = self.client.get(view_url)
|
||||||
self.assertEqual(response.status_code, 200)
|
self.assertEqual(response.status_code, 200)
|
||||||
self.assertCountEqual(response.data['results'], [view.serializer(obj).data for obj in view.list])
|
self.assertCountEqual(
|
||||||
|
response.data["results"], [view.serializer(obj).data for obj in view.list]
|
||||||
|
)
|
||||||
|
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
views = self.get_view_details("list")
|
views = self.get_view_details("list")
|
||||||
|
|
|
||||||
|
|
@ -22,18 +22,18 @@ from evennia.web.api.views import (
|
||||||
CharacterViewSet,
|
CharacterViewSet,
|
||||||
ExitViewSet,
|
ExitViewSet,
|
||||||
RoomViewSet,
|
RoomViewSet,
|
||||||
ScriptDBViewSet
|
ScriptDBViewSet,
|
||||||
)
|
)
|
||||||
|
|
||||||
app_name = "api"
|
app_name = "api"
|
||||||
|
|
||||||
router = routers.DefaultRouter()
|
router = routers.DefaultRouter()
|
||||||
router.trailing_slash = "/?"
|
router.trailing_slash = "/?"
|
||||||
router.register(r'accounts', AccountDBViewSet, basename="account")
|
router.register(r"accounts", AccountDBViewSet, basename="account")
|
||||||
router.register(r'objects', ObjectDBViewSet, basename="object")
|
router.register(r"objects", ObjectDBViewSet, basename="object")
|
||||||
router.register(r'characters', CharacterViewSet, basename="character")
|
router.register(r"characters", CharacterViewSet, basename="character")
|
||||||
router.register(r'exits', ExitViewSet, basename="exit")
|
router.register(r"exits", ExitViewSet, basename="exit")
|
||||||
router.register(r'rooms', RoomViewSet, basename="room")
|
router.register(r"rooms", RoomViewSet, basename="room")
|
||||||
router.register(r'scripts', ScriptDBViewSet, basename="script")
|
router.register(r"scripts", ScriptDBViewSet, basename="script")
|
||||||
|
|
||||||
urlpatterns = router.urls
|
urlpatterns = router.urls
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,12 @@ from evennia.objects.models import ObjectDB
|
||||||
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultRoom
|
from evennia.objects.objects import DefaultCharacter, DefaultExit, DefaultRoom
|
||||||
from evennia.accounts.models import AccountDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.scripts.models import ScriptDB
|
from evennia.scripts.models import ScriptDB
|
||||||
from evennia.web.api.serializers import ObjectDBSerializer, AccountSerializer, ScriptDBSerializer, AttributeSerializer
|
from evennia.web.api.serializers import (
|
||||||
|
ObjectDBSerializer,
|
||||||
|
AccountSerializer,
|
||||||
|
ScriptDBSerializer,
|
||||||
|
AttributeSerializer,
|
||||||
|
)
|
||||||
from evennia.web.api.filters import ObjectDBFilterSet, AccountDBFilterSet, ScriptDBFilterSet
|
from evennia.web.api.filters import ObjectDBFilterSet, AccountDBFilterSet, ScriptDBFilterSet
|
||||||
from evennia.web.api.permissions import EvenniaPermission
|
from evennia.web.api.permissions import EvenniaPermission
|
||||||
|
|
||||||
|
|
@ -24,6 +29,7 @@ class TypeclassViewSetMixin(object):
|
||||||
This mixin adds some shared functionality to each viewset of a typeclass. They all use the same
|
This mixin adds some shared functionality to each viewset of a typeclass. They all use the same
|
||||||
permission classes and filter backend. You can override any of these in your own viewsets.
|
permission classes and filter backend. You can override any of these in your own viewsets.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# permission classes determine who is authorized to call the view
|
# permission classes determine who is authorized to call the view
|
||||||
permission_classes = [EvenniaPermission]
|
permission_classes = [EvenniaPermission]
|
||||||
# the filter backend allows for retrieval views to have filter arguments passed to it,
|
# the filter backend allows for retrieval views to have filter arguments passed to it,
|
||||||
|
|
@ -58,7 +64,10 @@ class TypeclassViewSetMixin(object):
|
||||||
handler.add(key=key, value=value, category=category)
|
handler.add(key=key, value=value, category=category)
|
||||||
else:
|
else:
|
||||||
handler.remove(key=key, category=category)
|
handler.remove(key=key, category=category)
|
||||||
return Response(AttributeSerializer(obj.db_attributes.all(), many=True).data, status=status.HTTP_200_OK)
|
return Response(
|
||||||
|
AttributeSerializer(obj.db_attributes.all(), many=True).data,
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
)
|
||||||
return Response(attr.errors, status=status.HTTP_400_BAD_REQUEST)
|
return Response(attr.errors, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -69,6 +78,7 @@ class ObjectDBViewSet(TypeclassViewSetMixin, ModelViewSet):
|
||||||
instances. Serializers are similar to django forms, used for the
|
instances. Serializers are similar to django forms, used for the
|
||||||
transmitting of data (typically json).
|
transmitting of data (typically json).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_class = ObjectDBSerializer
|
serializer_class = ObjectDBSerializer
|
||||||
queryset = ObjectDB.objects.all()
|
queryset = ObjectDB.objects.all()
|
||||||
filterset_class = ObjectDBFilterSet
|
filterset_class = ObjectDBFilterSet
|
||||||
|
|
@ -79,21 +89,27 @@ class CharacterViewSet(ObjectDBViewSet):
|
||||||
This overrides the queryset to only retrieve Character objects
|
This overrides the queryset to only retrieve Character objects
|
||||||
based on your DefaultCharacter typeclass path.
|
based on your DefaultCharacter typeclass path.
|
||||||
"""
|
"""
|
||||||
queryset = DefaultCharacter.objects.typeclass_search(DefaultCharacter.path, include_children=True)
|
|
||||||
|
queryset = DefaultCharacter.objects.typeclass_search(
|
||||||
|
DefaultCharacter.path, include_children=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RoomViewSet(ObjectDBViewSet):
|
class RoomViewSet(ObjectDBViewSet):
|
||||||
"""Viewset for Room objects"""
|
"""Viewset for Room objects"""
|
||||||
|
|
||||||
queryset = DefaultRoom.objects.typeclass_search(DefaultRoom.path, include_children=True)
|
queryset = DefaultRoom.objects.typeclass_search(DefaultRoom.path, include_children=True)
|
||||||
|
|
||||||
|
|
||||||
class ExitViewSet(ObjectDBViewSet):
|
class ExitViewSet(ObjectDBViewSet):
|
||||||
"""Viewset for Exit objects"""
|
"""Viewset for Exit objects"""
|
||||||
|
|
||||||
queryset = DefaultExit.objects.typeclass_search(DefaultExit.path, include_children=True)
|
queryset = DefaultExit.objects.typeclass_search(DefaultExit.path, include_children=True)
|
||||||
|
|
||||||
|
|
||||||
class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
|
class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
|
||||||
"""Viewset for Account objects"""
|
"""Viewset for Account objects"""
|
||||||
|
|
||||||
serializer_class = AccountSerializer
|
serializer_class = AccountSerializer
|
||||||
queryset = AccountDB.objects.all()
|
queryset = AccountDB.objects.all()
|
||||||
filterset_class = AccountDBFilterSet
|
filterset_class = AccountDBFilterSet
|
||||||
|
|
@ -101,6 +117,7 @@ class AccountDBViewSet(TypeclassViewSetMixin, ModelViewSet):
|
||||||
|
|
||||||
class ScriptDBViewSet(TypeclassViewSetMixin, ModelViewSet):
|
class ScriptDBViewSet(TypeclassViewSetMixin, ModelViewSet):
|
||||||
"""Viewset for Script objects"""
|
"""Viewset for Script objects"""
|
||||||
|
|
||||||
serializer_class = ScriptDBSerializer
|
serializer_class = ScriptDBSerializer
|
||||||
queryset = ScriptDB.objects.all()
|
queryset = ScriptDB.objects.all()
|
||||||
filterset_class = ScriptDBFilterSet
|
filterset_class = ScriptDBFilterSet
|
||||||
|
|
|
||||||
|
|
@ -24,4 +24,4 @@ urlpatterns = [
|
||||||
]
|
]
|
||||||
|
|
||||||
if settings.REST_API_ENABLED:
|
if settings.REST_API_ENABLED:
|
||||||
urlpatterns += [url(r'^api/', include("evennia.web.api.urls", namespace="api"))]
|
urlpatterns += [url(r"^api/", include("evennia.web.api.urls", namespace="api"))]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue