Resolve django 1.11 migration errors.

This commit is contained in:
Griatch 2017-06-17 22:15:00 +02:00
commit 0ff1718437
54 changed files with 6444 additions and 574 deletions

View file

@ -64,8 +64,8 @@ ENFORCED_SETTING = False
# requirements
PYTHON_MIN = '2.7'
TWISTED_MIN = '16.0.0'
DJANGO_MIN = '1.8'
DJANGO_REC = '1.9'
DJANGO_MIN = '1.11'
DJANGO_REC = '1.11'
sys.path[1] = EVENNIA_ROOT
@ -344,10 +344,13 @@ ERROR_DJANGO_MIN = \
ERROR: Django {dversion} found. Evennia requires version {django_min}
or higher.
Install it with for example `pip install --upgrade django`
If you are using a virtualenv, use the command `pip install --upgrade -e evennia` where
`evennia` is the folder to where you cloned the Evennia library. If not
in a virtualenv you can install django with for example `pip install --upgrade django`
or with `pip install django=={django_min}` to get a specific version.
It's also a good idea to run `evennia migrate` after this upgrade.
It's also a good idea to run `evennia migrate` after this upgrade. Ignore
any warnings and don't run `makemigrate` even if told to.
"""
NOTE_DJANGO_MIN = \
@ -506,7 +509,7 @@ def create_secret_key():
return secret_key
def create_settings_file(init=True):
def create_settings_file(init=True, secret_settings=False):
"""
Uses the template settings file to build a working settings file.
@ -514,18 +517,27 @@ def create_settings_file(init=True):
init (bool): This is part of the normal evennia --init
operation. If false, this function will copy a fresh
template file in (asking if it already exists).
secret_settings (bool, optional): If False, create settings.py, otherwise
create the secret_settings.py file.
"""
settings_path = os.path.join(GAMEDIR, "server", "conf", "settings.py")
if secret_settings:
settings_path = os.path.join(GAMEDIR, "server", "conf", "secret_settings.py")
setting_dict = {"secret_key": "\'%s\'" % create_secret_key()}
else:
settings_path = os.path.join(GAMEDIR, "server", "conf", "settings.py")
setting_dict = {
"settings_default": os.path.join(EVENNIA_LIB, "settings_default.py"),
"servername": "\"%s\"" % GAMEDIR.rsplit(os.path.sep, 1)[1].capitalize(),
"secret_key": "\'%s\'" % create_secret_key()}
if not init:
# if not --init mode, settings file may already exist from before
if os.path.exists(settings_path):
inp = input("server/conf/settings.py already exists. "
"Do you want to reset it? y/[N]> ")
inp = input("%s already exists. Do you want to reset it? y/[N]> " % settings_path)
if not inp.lower() == 'y':
print ("Aborted.")
sys.exit()
return
else:
print ("Reset the settings file.")
@ -535,12 +547,6 @@ def create_settings_file(init=True):
with open(settings_path, 'r') as f:
settings_string = f.read()
# tweak the settings
setting_dict = {
"settings_default": os.path.join(EVENNIA_LIB, "settings_default.py"),
"servername": "\"%s\"" % GAMEDIR.rsplit(os.path.sep, 1)[1].capitalize(),
"secret_key": "\'%s\'" % create_secret_key()}
settings_string = settings_string.format(**setting_dict)
with open(settings_path, 'w') as f:
@ -564,8 +570,13 @@ def create_game_directory(dirname):
sys.exit()
# copy template directory
shutil.copytree(EVENNIA_TEMPLATE, GAMEDIR)
# rename gitignore to .gitignore
os.rename(os.path.join(GAMEDIR, 'gitignore'),
os.path.join(GAMEDIR, '.gitignore'))
# pre-build settings file in the new GAMEDIR
create_settings_file()
create_settings_file(secret_settings=True)
def create_superuser():

View file

@ -193,7 +193,8 @@ def client_options(session, *args, **kwargs):
"UTF-8", "SCREENREADER", "ENCODING",
"MCCP", "SCREENHEIGHT",
"SCREENWIDTH", "INPUTDEBUG",
"RAW", "NOCOLOR"))
"RAW", "NOCOLOR",
"NOGOAHEAD"))
session.msg(client_options=options)
return
@ -244,6 +245,8 @@ def client_options(session, *args, **kwargs):
flags["NOCOLOR"] = validate_bool(value)
elif key == "raw":
flags["RAW"] = validate_bool(value)
elif key == "nogoahead":
flags["NOGOAHEAD"] = validate_bool(value)
elif key in ('Char 1', 'Char.Skills 1', 'Char.Items 1',
'Room 1', 'IRE.Rift 1', 'IRE.Composer 1'):
# ignore mudlet's default send (aimed at IRE games)

View file

@ -206,7 +206,7 @@ class IRCBot(irc.IRCClient, Session):
reason (str): Motivation for the disconnect.
"""
self.sessionhandler.disconnect(self, reason=reason)
self.sessionhandler.disconnect(self)
self.stopping = True
self.transport.loseConnection()

View file

@ -294,6 +294,7 @@ if SSH_ENABLED:
if WEBSERVER_ENABLED:
from evennia.server.webserver import Website
# Start a reverse proxy to relay data to the Server-side webserver
@ -337,7 +338,7 @@ if WEBSERVER_ENABLED:
websocket_started = True
webclientstr = "\n + webclient%s" % pstring
web_root = server.Site(web_root, logPath=settings.HTTP_LOG_FILE)
web_root = Website(web_root, logPath=settings.HTTP_LOG_FILE)
proxy_service = internet.TCPServer(proxyport,
web_root,
interface=interface)

View file

@ -283,7 +283,7 @@ class SshProtocol(Manhole, session.Session):
else:
# we need to make sure to kill the color at the end in order
# to match the webclient output.
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"),
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("||n" if text.endswith("|") else "|n"),
strip_ansi=nocolor, xterm256=xterm256, mxp=False)
self.sendLine(linetosend)

View file

@ -0,0 +1,65 @@
"""
SUPPRESS-GO-AHEAD
This supports suppressing or activating Evennia
the GO-AHEAD telnet operation after every server reply.
If the client sends no explicit DONT SUPRESS GO-AHEAD,
Evennia will default to supressing it since many clients
will fail to use it and has no knowledge of this standard.
It is set as the NOGOAHEAD protocol_flag option.
http://www.faqs.org/rfcs/rfc858.html
"""
from builtins import object
SUPPRESS_GA = chr(3)
# default taken from telnet specification
# try to get the customized mssp info, if it exists.
class SuppressGA(object):
"""
Implements the SUPRESS-GO-AHEAD protocol. Add this to a variable on the telnet
protocol to set it up.
"""
def __init__(self, protocol):
"""
Initialize suppression of GO-AHEADs.
Args:
protocol (Protocol): The active protocol instance.
"""
self.protocol = protocol
self.protocol.protocol_flags["NOGOAHEAD"] = True
# tell the client that we prefer to suppress GA ...
self.protocol.will(SUPPRESS_GA).addCallbacks(self.do_suppress_ga, self.dont_suppress_ga)
# ... but also accept if the client really wants not to.
self.protocol.do(SUPPRESS_GA).addCallbacks(self.do_suppress_ga, self.dont_suppress_ga)
def dont_suppress_ga(self, option):
"""
Called when client requests to not suppress GA.
Args:
option (Option): Not used.
"""
self.protocol.protocol_flags["NOGOAHEAD"] = False
self.protocol.handshake_done()
def do_suppress_ga(self, option):
"""
Client wants to suppress GA
Args:
option (Option): Not used.
"""
self.protocol.protocol_flags["NOGOAHEAD"] = True
self.protocol.handshake_done()

View file

@ -13,7 +13,7 @@ from twisted.conch.telnet import Telnet, StatefulTelnetProtocol
from twisted.conch.telnet import IAC, NOP, LINEMODE, GA, WILL, WONT, ECHO, NULL
from django.conf import settings
from evennia.server.session import Session
from evennia.server.portal import ttype, mssp, telnet_oob, naws
from evennia.server.portal import ttype, mssp, telnet_oob, naws, suppress_ga
from evennia.server.portal.mccp import Mccp, mccp_compress, MCCP
from evennia.server.portal.mxp import Mxp, mxp_parse
from evennia.utils import ansi
@ -47,9 +47,11 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
client_address = client_address[0] if client_address else None
# this number is counted down for every handshake that completes.
# when it reaches 0 the portal/server syncs their data
self.handshakes = 7 # naws, ttype, mccp, mssp, msdp, gmcp, mxp
self.handshakes = 8 # suppress-go-ahead, naws, ttype, mccp, mssp, msdp, gmcp, mxp
self.init_session(self.protocol_name, client_address, self.factory.sessionhandler)
# suppress go-ahead
self.sga = suppress_ga.SuppressGA(self)
# negotiate client size
self.naws = naws.Naws(self)
# negotiate ttype (client info)
@ -128,7 +130,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
option == ttype.TTYPE or
option == naws.NAWS or
option == MCCP or
option == mssp.MSSP)
option == mssp.MSSP or
option == suppress_ga.SUPPRESS_GA)
def enableLocal(self, option):
"""
@ -141,7 +144,9 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
enable (bool): If this option should be enabled.
"""
return option == MCCP or option == ECHO
return (option == MCCP or
option == ECHO or
option == suppress_ga.SUPPRESS_GA)
def disableLocal(self, option):
"""
@ -221,6 +226,8 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
# escape IAC in line mode, and correctly add \r\n
line += self.delimiter
line = line.replace(IAC, IAC + IAC).replace('\n', '\r\n')
if not self.protocol_flags.get("NOGOAHEAD", True):
line += IAC + GA
return self.transport.write(mccp_compress(self, line))
# Session hooks
@ -309,7 +316,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
prompt = text
if not raw:
# processing
prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + ("|n" if prompt[-1] != "|" else "||n"),
prompt = ansi.parse_ansi(_RE_N.sub("", prompt) + ("||n" if prompt.endswith("|") else "|n"),
strip_ansi=nocolor, xterm256=xterm256)
if mxp:
prompt = mxp_parse(prompt)
@ -337,7 +344,7 @@ class TelnetProtocol(Telnet, StatefulTelnetProtocol, Session):
else:
# we need to make sure to kill the color at the end in order
# to match the webclient output.
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("|n" if text[-1] != "|" else "||n"),
linetosend = ansi.parse_ansi(_RE_N.sub("", text) + ("||n" if text.endswith("|") else "|n"),
strip_ansi=nocolor, xterm256=xterm256, mxp=mxp)
if mxp:
linetosend = mxp_parse(linetosend)

View file

@ -47,7 +47,11 @@ IAC = chr(255)
SB = chr(250)
SE = chr(240)
force_str = lambda inp: to_str(inp, force_string=True)
def force_str(inp):
"""Helper to shorten code"""
return to_str(inp, force_string=True)
# pre-compiled regexes
# returns 2-tuple

View file

@ -16,7 +16,7 @@ import os
from twisted.web import static
from twisted.application import internet, service
from twisted.internet import reactor, defer
from twisted.internet.task import LoopingCall
from twisted.internet.task import LoopingCall, deferLater
import django
django.setup()
@ -151,7 +151,8 @@ class Evennia(object):
sys.path.insert(1, '.')
# create a store of services
self.services = service.IServiceCollection(application)
self.services = service.MultiService()
self.services.setServiceParent(application)
self.amp_protocol = None # set by amp factory
self.sessions = SESSIONS
self.sessions.server = self
@ -167,10 +168,21 @@ class Evennia(object):
# initialize channelhandler
channelhandler.CHANNELHANDLER.update()
# set a callback if the server is killed abruptly,
# by Ctrl-C, reboot etc.
reactor.addSystemEventTrigger('before', 'shutdown',
self.shutdown, _reactor_stopping=True)
# wrap the SIGINT handler to make sure we empty the threadpool
# even when we reload and we have long-running requests in queue.
# this is necessary over using Twisted's signal handler.
# (see https://github.com/evennia/evennia/issues/1128)
def _wrap_sigint_handler(*args):
from twisted.internet.defer import Deferred
if hasattr(self, "web_root"):
d = self.web_root.empty_threadpool()
d.addCallback(lambda _: self.shutdown(_reactor_stopping=True))
else:
d = Deferred(lambda _: self.shutdown(_reactor_stopping=True))
d.addCallback(lambda _: reactor.stop())
reactor.callLater(1, d.callback, None)
reactor.sigInt = _wrap_sigint_handler
self.game_running = True
# track the server time
@ -183,7 +195,7 @@ class Evennia(object):
Optimize some SQLite stuff at startup since we
can't save it to the database.
"""
if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASE_ENGINE == "sqlite3")
if ((".".join(str(i) for i in django.VERSION) < "1.2" and settings.DATABASES.get('default', {}).get('ENGINE') == "sqlite3")
or (hasattr(settings, 'DATABASES')
and settings.DATABASES.get("default", {}).get('ENGINE', None)
== 'django.db.backends.sqlite3')):
@ -383,16 +395,16 @@ class Evennia(object):
# always called, also for a reload
self.at_server_stop()
# if _reactor_stopping is true, reactor does not need to
# be stopped again.
if os.name == 'nt' and os.path.exists(SERVER_PIDFILE):
# for Windows we need to remove pid files manually
os.remove(SERVER_PIDFILE)
if hasattr(self, "web_root"): # not set very first start
yield self.web_root.empty_threadpool()
if not _reactor_stopping:
# this will also send a reactor.stop signal, so we set a
# flag to avoid loops.
self.shutdown_complete = True
# kill the server
self.shutdown_complete = True
reactor.callLater(1, reactor.stop)
# we make sure the proper gametime is saved as late as possible
@ -526,18 +538,20 @@ if WEBSERVER_ENABLED:
# Start a django-compatible webserver.
from twisted.python import threadpool
from evennia.server.webserver import DjangoWebRoot, WSGIWebServer, Website
#from twisted.python import threadpool
from evennia.server.webserver import DjangoWebRoot, WSGIWebServer, Website, LockableThreadPool
# start a thread pool and define the root url (/) as a wsgi resource
# recognized by Django
threads = threadpool.ThreadPool(minthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[0]),
threads = LockableThreadPool(minthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[0]),
maxthreads=max(1, settings.WEBSERVER_THREADPOOL_LIMITS[1]))
web_root = DjangoWebRoot(threads)
# point our media resources to url /media
web_root.putChild("media", static.File(settings.MEDIA_ROOT))
# point our static resources to url /static
web_root.putChild("static", static.File(settings.STATIC_ROOT))
EVENNIA.web_root = web_root
if WEB_PLUGINS_MODULE:
# custom overloads

View file

@ -18,6 +18,8 @@ from twisted.internet import reactor
from twisted.application import internet
from twisted.web.proxy import ReverseProxyResource
from twisted.web.server import NOT_DONE_YET
from twisted.python import threadpool
from twisted.internet import defer
from twisted.web.wsgi import WSGIResource
from django.conf import settings
@ -28,6 +30,27 @@ from evennia.utils import logger
_UPSTREAM_IPS = settings.UPSTREAM_IPS
_DEBUG = settings.DEBUG
class LockableThreadPool(threadpool.ThreadPool):
"""
Threadpool that can be locked from accepting new requests.
"""
def __init__(self, *args, **kwargs):
self._accept_new = True
threadpool.ThreadPool.__init__(self, *args, **kwargs)
def lock(self):
self._accept_new = False
def callInThread(self, func, *args, **kwargs):
"""
called in the main reactor thread. Makes sure the pool
is not locked before continuing.
"""
if self._accept_new:
threadpool.ThreadPool.callInThread(self, func, *args, **kwargs)
#
# X-Forwarded-For Handler
#
@ -102,7 +125,7 @@ class EvenniaReverseProxyResource(ReverseProxyResource):
clientFactory.noisy = False
self.reactor.connectTCP(self.host, self.port, clientFactory)
# don't trigger traceback if connection is lost before request finish.
request.notifyFinish().addErrback(lambda f: f.cancel())
request.notifyFinish().addErrback(lambda f: logger.log_trace("%s\nCaught errback in webserver.py:75." % f))
return NOT_DONE_YET
@ -115,8 +138,9 @@ class DjangoWebRoot(resource.Resource):
"""
This creates a web root (/) that Django
understands by tweaking the way
child instancee ars recognized.
child instances are recognized.
"""
def __init__(self, pool):
"""
Setup the django+twisted resource.
@ -125,9 +149,30 @@ class DjangoWebRoot(resource.Resource):
pool (ThreadPool): The twisted threadpool.
"""
self.pool = pool
self._echo_log = True
self._pending_requests = {}
resource.Resource.__init__(self)
self.wsgi_resource = WSGIResource(reactor, pool, WSGIHandler())
def empty_threadpool(self):
"""
Converts our _pending_requests list of deferreds into a DeferredList
Returns:
deflist (DeferredList): Contains all deferreds of pending requests.
"""
self.pool.lock()
if self._pending_requests and self._echo_log:
self._echo_log = False # just to avoid multiple echoes
msg = "Webserver waiting for %i requests ... "
logger.log_info(msg % len(self._pending_requests))
return defer.DeferredList(self._pending_requests, consumeErrors=True)
def _decrement_requests(self, *args, **kwargs):
self._pending_requests.pop(kwargs.get('deferred', None), None)
def getChild(self, path, request):
"""
To make things work we nudge the url tree to make this the
@ -137,11 +182,22 @@ class DjangoWebRoot(resource.Resource):
path (str): Url path.
request (Request object): Incoming request.
Notes:
We make sure to save the request queue so
that we can safely kill the threadpool
on a server reload.
"""
path0 = request.prepath.pop(0)
request.postpath.insert(0, path0)
deferred = request.notifyFinish()
self._pending_requests[deferred] = deferred
deferred.addBoth(self._decrement_requests, deferred=deferred)
return self.wsgi_resource
#
# Site with deactivateable logging
#
@ -151,11 +207,13 @@ class Website(server.Site):
This class will only log http requests if settings.DEBUG is True.
"""
noisy = False
def log(self, request):
"Conditional logging"
"""Conditional logging"""
if _DEBUG:
server.Site.log(self, request)
#
# Threaded Webserver
#