Made all unit tests pass

This commit is contained in:
Griatch 2019-01-09 22:08:48 +01:00
parent 844b04adbb
commit aa48593a40
10 changed files with 148 additions and 110 deletions

View file

@ -258,12 +258,12 @@ def prototype_from_object(obj):
aliases = obj.aliases.get(return_list=True)
if aliases:
prot['aliases'] = aliases
tags = [(tag.db_key, tag.db_category, tag.db_data)
for tag in obj.tags.all(return_objs=True)]
tags = sorted([(tag.db_key, tag.db_category, tag.db_data)
for tag in obj.tags.all(return_objs=True)])
if tags:
prot['tags'] = tags
attrs = [(attr.key, attr.value, attr.category, ';'.join(attr.locks.all()))
for attr in obj.attributes.all()]
attrs = sorted([(attr.key, attr.value, attr.category, ';'.join(attr.locks.all()))
for attr in obj.attributes.all()])
if attrs:
prot['attrs'] = attrs

View file

@ -122,9 +122,9 @@ class TestUtils(EvenniaTest):
self.assertEqual(obj_prototype,
{'aliases': ['foo'],
'attrs': [('oldtest', 'to_keep', None, ''),
('test', 'testval', None, ''),
('desc', 'changed desc', None, '')],
'attrs': [('desc', 'changed desc', None, ''),
('oldtest', 'to_keep', None, ''),
('test', 'testval', None, '')],
'key': 'Obj',
'home': '#1',
'location': '#1',
@ -213,9 +213,9 @@ class TestUtils(EvenniaTest):
self.assertEqual(count, 1)
new_prot = spawner.prototype_from_object(self.obj1)
self.assertEqual({'attrs': [('oldtest', 'to_keep', None, ''),
('fooattr', 'fooattrval', None, ''),
self.assertEqual({'attrs': [('fooattr', 'fooattrval', None, ''),
('new', 'new_val', None, ''),
('oldtest', 'to_keep', None, ''),
('test', 'testval_changed', None, '')],
'home': Something,
'key': 'Obj',

View file

@ -1401,7 +1401,7 @@ def create_superuser():
"""
print(
"\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)

View file

@ -114,20 +114,20 @@ class TestTelnet(TwistedTestCase):
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'], {0: DEFAULT_WIDTH})
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'], {0: DEFAULT_HEIGHT})
self.proto.dataReceived(IAC + WILL + NAWS)
self.proto.dataReceived([IAC, SB, NAWS, '', 'x', '', 'd', IAC, SE])
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'][0], 120)
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'][0], 100)
self.proto.dataReceived(b"".join([IAC, SB, NAWS, b'', b'x', b'', b'd', IAC, SE]))
self.assertEqual(self.proto.protocol_flags['SCREENWIDTH'][0], 78)
self.assertEqual(self.proto.protocol_flags['SCREENHEIGHT'][0], 45)
self.assertEqual(self.proto.handshakes, 6)
# test ttype
self.assertTrue(self.proto.protocol_flags["FORCEDENDLINE"])
self.assertFalse(self.proto.protocol_flags["TTYPE"])
self.assertTrue(self.proto.protocol_flags["ANSI"])
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.assertEqual(self.proto.protocol_flags["CLIENTNAME"], "MUDLET")
self.proto.dataReceived([IAC, SB, TTYPE, IS, "XTERM", IAC, SE])
self.proto.dataReceived([IAC, SB, TTYPE, IS, "MTTS 137", IAC, SE])
self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"XTERM", IAC, SE]))
self.proto.dataReceived(b"".join([IAC, SB, TTYPE, IS, b"MTTS 137", IAC, SE]))
self.assertEqual(self.proto.handshakes, 5)
# test mccp
self.proto.dataReceived(IAC + DONT + MCCP)
@ -138,7 +138,7 @@ class TestTelnet(TwistedTestCase):
self.assertEqual(self.proto.handshakes, 3)
# test oob
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.assertEqual(self.proto.handshakes, 2)
# test mxp

View file

@ -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):
def _manager(self, methodname, *args, **kwargs):
return list(getattr(self.obj1.__class__.objects, methodname)(*args, **kwargs))

View file

@ -29,6 +29,7 @@ except ImportError:
from pickle import dumps, loads
from django.core.exceptions import ObjectDoesNotExist
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 import logger
@ -521,7 +522,7 @@ def to_pickle(data):
def process_item(item):
"""Recursive processor and identification of data"""
dtype = type(item)
if dtype in (str, int, float, bool):
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
return item
elif dtype == tuple:
return tuple(process_item(val) for val in item)
@ -573,7 +574,7 @@ def from_pickle(data, db_obj=None):
def process_item(item):
"""Recursive processor and identification of data"""
dtype = type(item)
if dtype in (str, int, float, bool):
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
return item
elif _IS_PACKED_DBOBJ(item):
# this must be checked before tuple
@ -602,7 +603,7 @@ def from_pickle(data, db_obj=None):
def process_tree(item, parent):
"""Recursive processor, building a parent-tree from iterable data"""
dtype = type(item)
if dtype in (str, int, float, bool):
if dtype in (str, int, float, bool, bytes, SafeString, SafeBytes):
return item
elif _IS_PACKED_DBOBJ(item):
# this must be checked before tuple

View file

@ -10,13 +10,10 @@ class TestEvForm(TestCase):
def test_form(self):
self.maxDiff = None
form1 = evform._test()
print("len(form1): {}".format(len(form1)))
form2 = evform._test()
print("len(form2): {}".format(len(form2)))
self.assertEqual(form1, form2)
# self.assertEqual(form, "")
# self.assertEqual(form1, "")
# '.------------------------------------------------.\n'
# '| |\n'
# '| Name: \x1b[0m\x1b[1m\x1b[32mTom\x1b[1m\x1b[32m \x1b'

View file

@ -140,10 +140,12 @@ class CharacterForm(ObjectForm):
}
# 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}),
help_text="A brief description of your character.")
class CharacterUpdateForm(CharacterForm):
"""
This is a Django form for updating Evennia Character objects.

View file

@ -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 django.contrib.admin.sites import site
from django.conf import settings
from django.contrib import messages
from django.contrib.auth import authenticate
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.admin.views.decorators import staff_member_required
from django.core.exceptions import PermissionDenied
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.urls import reverse, reverse_lazy
from django.views.generic import View, TemplateView, ListView, DetailView, FormView
from django.urls import reverse_lazy
from django.views.generic import TemplateView, ListView, DetailView
from django.views.generic.base import RedirectView
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.objects.models import ObjectDB
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.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
_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS
def _gamestats():
# Some misc. configurable stuff.
# 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"
nobjs = ObjectDB.objects.all().count()
nrooms = ObjectDB.objects.filter(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()
nrooms = ObjectDB.objects.filter(
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()
nothers = nobjs - nrooms - nchars - nexits
@ -99,6 +99,7 @@ def admin_wrapper(request):
"""
return staff_member_required(site.index)(request)
#
# Class-based views
#
@ -237,6 +238,7 @@ class EvenniaDeleteView(DeleteView, TypeclassMixin):
# Makes sure the page has a sensible title.
return 'Delete %s' % self.typeclass._meta.verbose_name.title()
#
# Object views
#
@ -336,7 +338,8 @@ class ObjectDetailView(EvenniaDetailView):
# Check if this object was requested in a valid manner
if slugify(obj.name) != self.kwargs.get(self.slug_url_kwarg):
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
raise HttpResponseBadRequest(
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
@ -430,7 +433,8 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
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()
def get_initial(self):
@ -475,13 +479,14 @@ class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
# Update the object attributes
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))
# Do not return super().form_valid; we don't want to update the model
# instance, just its attributes.
return HttpResponseRedirect(self.get_success_url())
#
# Account views
#
@ -496,7 +501,7 @@ class AccountMixin(TypeclassMixin):
"""
# -- Django constructs --
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
form_class = AccountForm
form_class = website_forms.AccountForm
class AccountCreateView(AccountMixin, EvenniaCreateView):
@ -537,11 +542,13 @@ class AccountCreateView(AccountMixin, EvenniaCreateView):
return self.form_invalid(form)
# 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
return HttpResponseRedirect(self.success_url)
#
# Character views
#
@ -556,7 +563,7 @@ class CharacterMixin(TypeclassMixin):
"""
# -- Django constructs --
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
form_class = CharacterForm
form_class = website_forms.CharacterForm
success_url = reverse_lazy('character-manage')
def get_queryset(self):
@ -608,7 +615,8 @@ class CharacterListView(LoginRequiredMixin, CharacterMixin, ListView):
# Return a queryset consisting of characters the user is allowed to
# 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'))
@ -637,7 +645,7 @@ class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, Obje
char = self.get_object()
# 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 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
messages.error(self.request, "You cannot become '%s'." % char)
return next
return next_page
class CharacterManageView(LoginRequiredMixin, CharacterMixin, ListView):
@ -674,7 +682,7 @@ class CharacterUpdateView(CharacterMixin, ObjectUpdateView):
"""
# -- Django constructs --
form_class = CharacterUpdateForm
form_class = website_forms.CharacterUpdateForm
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
# 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'))
@ -746,7 +755,6 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
self.attributes = {k: form.cleaned_data[k] for k in form.cleaned_data.keys()}
charname = self.attributes.pop('db_key')
description = self.attributes.pop('desc')
# Create a character
character, errors = self.typeclass.create(charname, account, description=description)
@ -768,6 +776,7 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
messages.error(self.request, "Your character could not be created.")
return self.form_invalid(form)
#
# Channel views
#
@ -881,12 +890,14 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
context = super(ChannelDetailView, self).get_context_data(**kwargs)
# 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
bucket = []
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_key = time.split(':')[0]
@ -904,7 +915,6 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
return context
def get_object(self, queryset=None):
"""
Override of Django hook that retrieves an object by slugified channel
@ -924,7 +934,8 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
# Check if this object was requested in a valid manner
if not obj:
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
raise HttpResponseBadRequest(
u"No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
return obj
@ -976,6 +987,7 @@ class HelpMixin(TypeclassMixin):
return filtered
class HelpListView(HelpMixin, ListView):
"""
Returns a list of help entries that can be viewed by a user, authenticated
@ -989,6 +1001,7 @@ class HelpListView(HelpMixin, ListView):
# -- Evennia constructs --
page_title = "Help Index"
class HelpDetailView(HelpMixin, EvenniaDetailView):
"""
Returns the detail page for a given help entry.
@ -1012,7 +1025,8 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
obj = self.get_object()
# 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
# Find the index position of the given obj in the queryset
@ -1025,12 +1039,14 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
try:
assert i+1 <= len(objs) and objs[i+1] is not obj
context['topic_next'] = objs[i+1]
except: context['topic_next'] = None
except:
context['topic_next'] = None
try:
assert i-1 >= 0 and objs[i-1] is not obj
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
text = obj.db_entrytext
@ -1057,11 +1073,14 @@ class HelpDetailView(HelpMixin, EvenniaDetailView):
# Find the object in the queryset
category = slugify(self.kwargs.get('category', ''))
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
if not obj:
raise HttpResponseBadRequest(u"No %(verbose_name)s found matching the query" %
raise HttpResponseBadRequest(
u"No %(verbose_name)s found matching the query" %
{'verbose_name': queryset.model._meta.verbose_name})
return obj