Reshuffling the Evennia package into the new template paradigm.
This commit is contained in:
parent
2846e64833
commit
2b3a32e447
371 changed files with 17250 additions and 304 deletions
14
lib/comms/__init__.py
Normal file
14
lib/comms/__init__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
Makes it easier to import by grouping all relevant things already at this
|
||||
level.
|
||||
|
||||
You can henceforth import most things directly from src.comms
|
||||
Also, the initiated object manager is available as src.comms.msgmanager and
|
||||
src.comms.channelmanager.
|
||||
|
||||
"""
|
||||
|
||||
#from src.comms.models import *
|
||||
|
||||
#msgmanager = Msg.objects
|
||||
#channelmanager = ChannelDB.objects
|
||||
49
lib/comms/admin.py
Normal file
49
lib/comms/admin.py
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
#
|
||||
# This sets up how models are displayed
|
||||
# in the web admin interface.
|
||||
#
|
||||
|
||||
from django.contrib import admin
|
||||
from src.comms.models import ChannelDB
|
||||
from src.typeclasses.admin import AttributeInline, TagInline
|
||||
|
||||
|
||||
class ChannelAttributeInline(AttributeInline):
|
||||
model = ChannelDB.db_attributes.through
|
||||
|
||||
|
||||
class ChannelTagInline(TagInline):
|
||||
model = ChannelDB.db_tags.through
|
||||
|
||||
|
||||
class MsgAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'db_date_sent', 'db_sender', 'db_receivers',
|
||||
'db_channels', 'db_message', 'db_lock_storage')
|
||||
list_display_links = ("id",)
|
||||
ordering = ["db_date_sent", 'db_sender', 'db_receivers', 'db_channels']
|
||||
#readonly_fields = ['db_message', 'db_sender', 'db_receivers', 'db_channels']
|
||||
search_fields = ['id', '^db_date_sent', '^db_message']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
#admin.site.register(Msg, MsgAdmin)
|
||||
|
||||
|
||||
class ChannelAdmin(admin.ModelAdmin):
|
||||
inlines = [ChannelTagInline, ChannelAttributeInline]
|
||||
list_display = ('id', 'db_key', 'db_lock_storage', "subscriptions")
|
||||
list_display_links = ("id", 'db_key')
|
||||
ordering = ["db_key"]
|
||||
search_fields = ['id', 'db_key', 'db_aliases']
|
||||
save_as = True
|
||||
save_on_top = True
|
||||
list_select_related = True
|
||||
fieldsets = (
|
||||
(None, {'fields': (('db_key',), 'db_lock_storage', 'db_subscriptions')}),
|
||||
)
|
||||
|
||||
def subscriptions(self, obj):
|
||||
"Helper method to get subs from a channel"
|
||||
return ", ".join([str(sub) for sub in obj.db_subscriptions.all()])
|
||||
|
||||
admin.site.register(ChannelDB, ChannelAdmin)
|
||||
161
lib/comms/channelhandler.py
Normal file
161
lib/comms/channelhandler.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
"""
|
||||
The channel handler handles the stored set of channels
|
||||
and how they are represented against the cmdhandler.
|
||||
|
||||
If there is a channel named 'newbie', we want to be able
|
||||
to just write
|
||||
|
||||
> newbie Hello!
|
||||
|
||||
For this to work, 'newbie', the name of the channel, must
|
||||
be identified by the cmdhandler as a command name. The
|
||||
channelhandler stores all channels as custom 'commands'
|
||||
that the cmdhandler can import and look through.
|
||||
|
||||
Warning - channel names take precedence over command names,
|
||||
so make sure to not pick clashing channel names.
|
||||
|
||||
Unless deleting a channel you normally don't need to bother about
|
||||
the channelhandler at all - the create_channel method handles the update.
|
||||
|
||||
To delete a channel cleanly, delete the channel object, then call
|
||||
update() on the channelhandler. Or use Channel.objects.delete() which
|
||||
does this for you.
|
||||
|
||||
"""
|
||||
from src.comms.models import ChannelDB
|
||||
from src.commands import cmdset, command
|
||||
|
||||
|
||||
class ChannelCommand(command.Command):
|
||||
"""
|
||||
Channel
|
||||
|
||||
Usage:
|
||||
<channel name or alias> <message>
|
||||
|
||||
This is a channel. If you have subscribed to it, you can send to
|
||||
it by entering its name or alias, followed by the text you want to
|
||||
send.
|
||||
"""
|
||||
# this flag is what identifies this cmd as a channel cmd
|
||||
# and branches off to the system send-to-channel command
|
||||
# (which is customizable by admin)
|
||||
is_channel = True
|
||||
key = "general"
|
||||
help_category = "Channel Names"
|
||||
obj = None
|
||||
|
||||
def parse(self):
|
||||
"""
|
||||
Simple parser
|
||||
"""
|
||||
# cmdhandler sends channame:msg here.
|
||||
channelname, msg = self.args.split(":", 1)
|
||||
self.args = (channelname.strip(), msg.strip())
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Create a new message and send it to channel, using
|
||||
the already formatted input.
|
||||
"""
|
||||
channelkey, msg = self.args
|
||||
caller = self.caller
|
||||
if not msg:
|
||||
self.msg("Say what?")
|
||||
return
|
||||
channel = ChannelDB.objects.get_channel(channelkey)
|
||||
|
||||
if not channel:
|
||||
self.msg("Channel '%s' not found." % channelkey)
|
||||
return
|
||||
if not channel.has_connection(caller):
|
||||
string = "You are not connected to channel '%s'."
|
||||
self.msg(string % channelkey)
|
||||
return
|
||||
if not channel.access(caller, 'send'):
|
||||
string = "You are not permitted to send to channel '%s'."
|
||||
self.msg(string % channelkey)
|
||||
return
|
||||
channel.msg(msg, senders=self.caller, online=True)
|
||||
|
||||
|
||||
class ChannelHandler(object):
|
||||
"""
|
||||
Handles the set of commands related to channels.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.cached_channel_cmds = []
|
||||
self.cached_cmdsets = {}
|
||||
|
||||
def __str__(self):
|
||||
return ", ".join(str(cmd) for cmd in self.cached_channel_cmds)
|
||||
|
||||
def clear(self):
|
||||
"""
|
||||
Reset the cache storage.
|
||||
"""
|
||||
self.cached_channel_cmds = []
|
||||
|
||||
def _format_help(self, channel):
|
||||
"builds a doc string"
|
||||
key = channel.key
|
||||
aliases = channel.aliases.all()
|
||||
ustring = "%s <message>" % key.lower() + "".join(["\n %s <message>" % alias.lower() for alias in aliases])
|
||||
desc = channel.db.desc
|
||||
string = \
|
||||
"""
|
||||
Channel '%s'
|
||||
|
||||
Usage (not including your personal aliases):
|
||||
%s
|
||||
|
||||
%s
|
||||
""" % (key, ustring, desc)
|
||||
return string
|
||||
|
||||
def add_channel(self, channel):
|
||||
"""
|
||||
Add an individual channel to the handler. This should be
|
||||
called whenever a new channel is created. To
|
||||
remove a channel, simply delete the channel object
|
||||
and run self.update on the handler.
|
||||
"""
|
||||
# map the channel to a searchable command
|
||||
cmd = ChannelCommand(key=channel.key.strip().lower(),
|
||||
aliases=channel.aliases.all(),
|
||||
locks="cmd:all();%s" % channel.locks,
|
||||
help_category="Channel names",
|
||||
obj=channel,
|
||||
arg_regex=r"\s.*?",
|
||||
is_channel=True)
|
||||
self.cached_channel_cmds.append(cmd)
|
||||
self.cached_cmdsets = {}
|
||||
|
||||
def update(self):
|
||||
"Updates the handler completely."
|
||||
self.cached_channel_cmds = []
|
||||
self.cached_cmdsets = {}
|
||||
for channel in ChannelDB.objects.get_all_channels():
|
||||
self.add_channel(channel)
|
||||
|
||||
def get_cmdset(self, source_object):
|
||||
"""
|
||||
Retrieve cmdset for channels this source_object has
|
||||
access to send to.
|
||||
"""
|
||||
if source_object in self.cached_cmdsets:
|
||||
return self.cached_cmdsets[source_object]
|
||||
else:
|
||||
# create a new cmdset holding all channels
|
||||
chan_cmdset = cmdset.CmdSet()
|
||||
chan_cmdset.key = '_channelset'
|
||||
chan_cmdset.priority = 120
|
||||
chan_cmdset.duplicates = True
|
||||
for cmd in [cmd for cmd in self.cached_channel_cmds
|
||||
if cmd.access(source_object, 'send')]:
|
||||
chan_cmdset.add(cmd)
|
||||
self.cached_cmdsets[source_object] = chan_cmdset
|
||||
return chan_cmdset
|
||||
|
||||
CHANNELHANDLER = ChannelHandler()
|
||||
330
lib/comms/comms.py
Normal file
330
lib/comms/comms.py
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
"""
|
||||
Default Typeclass for Comms.
|
||||
|
||||
See objects.objects for more information on Typeclassing.
|
||||
"""
|
||||
from src.typeclasses.models import TypeclassBase
|
||||
from src.comms.models import Msg, TempMsg, ChannelDB
|
||||
from src.comms.managers import ChannelManager
|
||||
from src.utils import logger
|
||||
from src.utils.utils import make_iter
|
||||
|
||||
|
||||
class Channel(ChannelDB):
|
||||
"""
|
||||
This is the base class for all Comms. Inherit from this to create different
|
||||
types of communication channels.
|
||||
"""
|
||||
__metaclass__ = TypeclassBase
|
||||
objects = ChannelManager()
|
||||
|
||||
def at_first_save(self):
|
||||
"""
|
||||
Called by the typeclass system the very first time the channel
|
||||
is saved to the database. Generally, don't overload this but
|
||||
the hooks called by this method.
|
||||
"""
|
||||
self.at_channel_creation()
|
||||
|
||||
if hasattr(self, "_createdict"):
|
||||
# this is only set if the channel was created
|
||||
# with the utils.create.create_channel function.
|
||||
cdict = self._createdict
|
||||
if not cdict.get("key"):
|
||||
if not self.db_key:
|
||||
self.db_key = "#i" % self.dbid
|
||||
elif cdict["key"] and self.key != cdict["key"]:
|
||||
self.key = cdict["key"]
|
||||
if cdict.get("keep_log"):
|
||||
self.db_keep_log = cdict["keep_log"]
|
||||
if cdict.get("aliases"):
|
||||
self.aliases.add(cdict["aliases"])
|
||||
if cdict.get("locks"):
|
||||
self.locks.add(cdict["locks"])
|
||||
if cdict.get("keep_log"):
|
||||
self.attributes.add("keep_log", cdict["keep_log"])
|
||||
if cdict.get("desc"):
|
||||
self.attributes.add("desc", cdict["desc"])
|
||||
|
||||
def at_channel_creation(self):
|
||||
"""
|
||||
Called once, when the channel is first created.
|
||||
"""
|
||||
pass
|
||||
|
||||
# helper methods, for easy overloading
|
||||
|
||||
def has_connection(self, player):
|
||||
"""
|
||||
Checks so this player is actually listening
|
||||
to this channel.
|
||||
"""
|
||||
if hasattr(player, "player"):
|
||||
player = player.player
|
||||
return player in self.db_subscriptions.all()
|
||||
|
||||
def connect(self, player):
|
||||
"Connect the user to this channel. This checks access."
|
||||
if hasattr(player, "player"):
|
||||
player = player.player
|
||||
# check access
|
||||
if not self.access(player, 'listen'):
|
||||
return False
|
||||
# pre-join hook
|
||||
connect = self.pre_join_channel(player)
|
||||
if not connect:
|
||||
return False
|
||||
# subscribe
|
||||
self.db_subscriptions.add(player)
|
||||
# post-join hook
|
||||
self.post_join_channel(player)
|
||||
return True
|
||||
|
||||
def disconnect(self, player):
|
||||
"Disconnect user from this channel."
|
||||
if hasattr(player, "player"):
|
||||
player = player.player
|
||||
# pre-disconnect hook
|
||||
disconnect = self.pre_leave_channel(player)
|
||||
if not disconnect:
|
||||
return False
|
||||
# disconnect
|
||||
self.db_subscriptions.remove(player)
|
||||
# post-disconnect hook
|
||||
self.post_leave_channel(player)
|
||||
return True
|
||||
|
||||
def access(self, accessing_obj, access_type='listen', default=False):
|
||||
"""
|
||||
Determines if another object has permission to access.
|
||||
accessing_obj - object trying to access this one
|
||||
access_type - type of access sought
|
||||
default - what to return if no lock of access_type was found
|
||||
"""
|
||||
return self.locks.check(accessing_obj, access_type=access_type, default=default)
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
Deletes channel while also cleaning up channelhandler
|
||||
"""
|
||||
self.attributes.clear()
|
||||
self.aliases.clear()
|
||||
super(Channel, self).delete()
|
||||
from src.comms.channelhandler import CHANNELHANDLER
|
||||
CHANNELHANDLER.update()
|
||||
|
||||
def channel_prefix(self, msg=None, emit=False):
|
||||
"""
|
||||
How the channel should prefix itself for users. Return a string.
|
||||
"""
|
||||
return '[%s] ' % self.key
|
||||
|
||||
def format_senders(self, senders=None):
|
||||
"""
|
||||
Function used to format a list of sender names.
|
||||
|
||||
This function exists separately so that external sources can use
|
||||
it to format source names in the same manner as normal object/player
|
||||
names.
|
||||
"""
|
||||
if not senders:
|
||||
return ''
|
||||
return ', '.join(senders)
|
||||
|
||||
def pose_transform(self, msg, sender_string):
|
||||
"""
|
||||
Detects if the sender is posing, and modifies the message accordingly.
|
||||
"""
|
||||
pose = False
|
||||
message = msg.message
|
||||
message_start = message.lstrip()
|
||||
if message_start.startswith((':', ';')):
|
||||
pose = True
|
||||
message = message[1:]
|
||||
if not message.startswith((':', "'", ',')):
|
||||
if not message.startswith(' '):
|
||||
message = ' ' + message
|
||||
if pose:
|
||||
return '%s%s' % (sender_string, message)
|
||||
else:
|
||||
return '%s: %s' % (sender_string, message)
|
||||
|
||||
def format_external(self, msg, senders, emit=False):
|
||||
"""
|
||||
Used for formatting external messages. This is needed as a separate
|
||||
operation because the senders of external messages may not be in-game
|
||||
objects/players, and so cannot have things like custom user
|
||||
preferences.
|
||||
|
||||
senders should be a list of strings, each containing a sender.
|
||||
msg should contain the body of the message to be sent.
|
||||
"""
|
||||
if not senders:
|
||||
emit = True
|
||||
if emit:
|
||||
return msg.message
|
||||
senders = ', '.join(senders)
|
||||
return self.pose_transform(msg, senders)
|
||||
|
||||
def format_message(self, msg, emit=False):
|
||||
"""
|
||||
Formats a message body for display.
|
||||
|
||||
If emit is True, it means the message is intended to be posted detached
|
||||
from an identity.
|
||||
"""
|
||||
# We don't want to count things like external sources as senders for
|
||||
# the purpose of constructing the message string.
|
||||
senders = [sender for sender in msg.senders if hasattr(sender, 'key')]
|
||||
if not senders:
|
||||
emit = True
|
||||
if emit:
|
||||
return msg.message
|
||||
else:
|
||||
senders = [sender.key for sender in msg.senders]
|
||||
senders = ', '.join(senders)
|
||||
return self.pose_transform(msg, senders)
|
||||
|
||||
def message_transform(self, msg, emit=False, prefix=True,
|
||||
sender_strings=None, external=False):
|
||||
"""
|
||||
Generates the formatted string sent to listeners on a channel.
|
||||
"""
|
||||
if sender_strings or external:
|
||||
body = self.format_external(msg, sender_strings, emit=emit)
|
||||
else:
|
||||
body = self.format_message(msg, emit=emit)
|
||||
if prefix:
|
||||
body = "%s%s" % (self.channel_prefix(msg, emit=emit), body)
|
||||
msg.message = body
|
||||
return msg
|
||||
|
||||
def pre_join_channel(self, joiner):
|
||||
"""
|
||||
Run right before a channel is joined. If this returns a false value,
|
||||
channel joining is aborted.
|
||||
"""
|
||||
return True
|
||||
|
||||
def post_join_channel(self, joiner):
|
||||
"""
|
||||
Run right after an object or player joins a channel.
|
||||
"""
|
||||
return True
|
||||
|
||||
def pre_leave_channel(self, leaver):
|
||||
"""
|
||||
Run right before a user leaves a channel. If this returns a false
|
||||
value, leaving the channel will be aborted.
|
||||
"""
|
||||
return True
|
||||
|
||||
def post_leave_channel(self, leaver):
|
||||
"""
|
||||
Run right after an object or player leaves a channel.
|
||||
"""
|
||||
pass
|
||||
|
||||
def pre_send_message(self, msg):
|
||||
"""
|
||||
Run before a message is sent to the channel.
|
||||
|
||||
This should return the message object, after any transformations.
|
||||
If the message is to be discarded, return a false value.
|
||||
"""
|
||||
return msg
|
||||
|
||||
def post_send_message(self, msg):
|
||||
"""
|
||||
Run after a message is sent to the channel.
|
||||
"""
|
||||
pass
|
||||
|
||||
def at_init(self):
|
||||
"""
|
||||
This is always called whenever this channel is initiated --
|
||||
that is, whenever it its typeclass is cached from memory. This
|
||||
happens on-demand first time the channel is used or activated
|
||||
in some way after being created but also after each server
|
||||
restart or reload.
|
||||
"""
|
||||
pass
|
||||
|
||||
def distribute_message(self, msg, online=False):
|
||||
"""
|
||||
Method for grabbing all listeners that a message should be sent to on
|
||||
this channel, and sending them a message.
|
||||
"""
|
||||
# get all players connected to this channel and send to them
|
||||
for player in self.db_subscriptions.all():
|
||||
try:
|
||||
# note our addition of the from_channel keyword here. This could be checked
|
||||
# by a custom player.msg() to treat channel-receives differently.
|
||||
player.msg(msg.message, from_obj=msg.senders, from_channel=self.id)
|
||||
except AttributeError, e:
|
||||
logger.log_trace("%s\nCannot send msg to player '%s'." % (e, player))
|
||||
|
||||
def msg(self, msgobj, header=None, senders=None, sender_strings=None,
|
||||
persistent=False, online=False, emit=False, external=False):
|
||||
"""
|
||||
Send the given message to all players connected to channel. Note that
|
||||
no permission-checking is done here; it is assumed to have been
|
||||
done before calling this method. The optional keywords are not used if
|
||||
persistent is False.
|
||||
|
||||
msgobj - a Msg/TempMsg instance or a message string. If one of the
|
||||
former, the remaining keywords will be ignored. If a string,
|
||||
this will either be sent as-is (if persistent=False) or it
|
||||
will be used together with header and senders keywords to
|
||||
create a Msg instance on the fly.
|
||||
senders - an object, player or a list of objects or players.
|
||||
Optional if persistent=False.
|
||||
sender_strings - Name strings of senders. Used for external
|
||||
connections where the sender is not a player or object. When
|
||||
this is defined, external will be assumed.
|
||||
external - Treat this message agnostic of its sender.
|
||||
persistent (default False) - ignored if msgobj is a Msg or TempMsg.
|
||||
If True, a Msg will be created, using header and senders
|
||||
keywords. If False, other keywords will be ignored.
|
||||
online (bool) - If this is set true, only messages people who are
|
||||
online. Otherwise, messages all players connected. This can
|
||||
make things faster, but may not trigger listeners on players
|
||||
that are offline.
|
||||
emit (bool) - Signals to the message formatter that this message is
|
||||
not to be directly associated with a name.
|
||||
"""
|
||||
if senders:
|
||||
senders = make_iter(senders)
|
||||
else:
|
||||
senders = []
|
||||
if isinstance(msgobj, basestring):
|
||||
# given msgobj is a string
|
||||
msg = msgobj
|
||||
if persistent and self.db.keep_log:
|
||||
msgobj = Msg()
|
||||
msgobj.save()
|
||||
else:
|
||||
# Use TempMsg, so this message is not stored.
|
||||
msgobj = TempMsg()
|
||||
msgobj.header = header
|
||||
msgobj.message = msg
|
||||
msgobj.channels = [self] # add this channel
|
||||
|
||||
if not msgobj.senders:
|
||||
msgobj.senders = senders
|
||||
msgobj = self.pre_send_message(msgobj)
|
||||
if not msgobj:
|
||||
return False
|
||||
msgobj = self.message_transform(msgobj, emit=emit,
|
||||
sender_strings=sender_strings,
|
||||
external=external)
|
||||
self.distribute_message(msgobj, online=online)
|
||||
self.post_send_message(msgobj)
|
||||
return True
|
||||
|
||||
def tempmsg(self, message, header=None, senders=None):
|
||||
"""
|
||||
A wrapper for sending non-persistent messages.
|
||||
"""
|
||||
self.msg(message, senders=senders, header=header, persistent=False)
|
||||
|
||||
332
lib/comms/managers.py
Normal file
332
lib/comms/managers.py
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
"""
|
||||
These managers handles the
|
||||
"""
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
from src.typeclasses.managers import (TypedObjectManager, TypeclassManager,
|
||||
returns_typeclass_list, returns_typeclass)
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_PlayerDB = None
|
||||
_ObjectDB = None
|
||||
_ChannelDB = None
|
||||
_SESSIONS = None
|
||||
|
||||
# error class
|
||||
|
||||
|
||||
class CommError(Exception):
|
||||
"Raise by comm system, to allow feedback to player when caught."
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
# helper functions
|
||||
#
|
||||
|
||||
def dbref(dbref, reqhash=True):
|
||||
"""
|
||||
Valid forms of dbref (database reference number)
|
||||
are either a string '#N' or an integer N.
|
||||
Output is the integer part.
|
||||
"""
|
||||
if reqhash and not (isinstance(dbref, basestring) and dbref.startswith("#")):
|
||||
return None
|
||||
if isinstance(dbref, basestring):
|
||||
dbref = dbref.lstrip('#')
|
||||
try:
|
||||
if int(dbref) < 0:
|
||||
return None
|
||||
except Exception:
|
||||
return None
|
||||
return dbref
|
||||
|
||||
|
||||
def identify_object(inp):
|
||||
"identify if an object is a player or an object; return its database model"
|
||||
# load global stores
|
||||
global _PlayerDB, _ObjectDB, _ChannelDB
|
||||
if not _PlayerDB:
|
||||
from src.players.models import PlayerDB as _PlayerDB
|
||||
if not _ObjectDB:
|
||||
from src.objects.models import ObjectDB as _ObjectDB
|
||||
if not _ChannelDB:
|
||||
from src.comms.models import ChannelDB as _ChannelDB
|
||||
|
||||
if not inp:
|
||||
return inp, None
|
||||
if isinstance(inp, basestring):
|
||||
return inp, "string"
|
||||
elif inp.is_typeclass(_PlayerDB, exact=False):
|
||||
return inp, "player"
|
||||
elif inp.is_typeclass(_ObjectDB, exact=False):
|
||||
return inp, "object"
|
||||
elif inp.is_typeclass(_ChannelDB, exact=False):
|
||||
return inp, "channel"
|
||||
elif dbref(inp):
|
||||
return dbref(inp), "dbref"
|
||||
return inp, None # something else
|
||||
|
||||
|
||||
def to_object(inp, objtype='player'):
|
||||
"""
|
||||
Locates the object related to the given
|
||||
playername or channel key. If input was already
|
||||
the correct object, return it.
|
||||
inp - the input object/string
|
||||
objtype - 'player' or 'channel'
|
||||
"""
|
||||
obj, typ = identify_object(inp)
|
||||
if typ == objtype:
|
||||
return obj
|
||||
if objtype == 'player':
|
||||
if typ == 'object':
|
||||
return obj.player
|
||||
if typ == 'string':
|
||||
return _PlayerDB.objects.get(user_username__iexact=obj)
|
||||
if typ == 'dbref':
|
||||
return _PlayerDB.objects.get(id=obj)
|
||||
print objtype, inp, obj, typ, type(inp)
|
||||
raise CommError()
|
||||
elif objtype == 'object':
|
||||
if typ == 'player':
|
||||
return obj.obj
|
||||
if typ == 'string':
|
||||
return _ObjectDB.objects.get(db_key__iexact=obj)
|
||||
if typ == 'dbref':
|
||||
return _ObjectDB.objects.get(id=obj)
|
||||
print objtype, inp, obj, typ, type(inp)
|
||||
raise CommError()
|
||||
elif objtype == 'channel':
|
||||
if typ == 'string':
|
||||
return _ChannelDB.objects.get(db_key__iexact=obj)
|
||||
if typ == 'dbref':
|
||||
return _ChannelDB.objects.get(id=obj)
|
||||
print objtype, inp, obj, typ, type(inp)
|
||||
raise CommError()
|
||||
|
||||
#
|
||||
# Msg manager
|
||||
#
|
||||
|
||||
class MsgManager(models.Manager):
|
||||
"""
|
||||
This MsgManager implements methods for searching
|
||||
and manipulating Messages directly from the database.
|
||||
|
||||
These methods will all return database objects
|
||||
(or QuerySets) directly.
|
||||
|
||||
A Message represents one unit of communication, be it over a
|
||||
Channel or via some form of in-game mail system. Like an e-mail,
|
||||
it always has a sender and can have any number of receivers (some
|
||||
of which may be Channels).
|
||||
|
||||
Evennia-specific:
|
||||
get_message_by_id
|
||||
get_messages_by_sender
|
||||
get_messages_by_receiver
|
||||
get_messages_by_channel
|
||||
text_search
|
||||
message_search (equivalent to ev.search_messages)
|
||||
"""
|
||||
|
||||
def identify_object(self, obj):
|
||||
"method version for easy access"
|
||||
return identify_object(obj)
|
||||
|
||||
def get_message_by_id(self, idnum):
|
||||
"Retrieve message by its id."
|
||||
try:
|
||||
return self.get(id=self.dbref(idnum, reqhash=False))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def get_messages_by_sender(self, obj, exclude_channel_messages=False):
|
||||
"""
|
||||
Get all messages sent by one entity - this could be either a
|
||||
player or an object
|
||||
|
||||
only_non_channel: only return messages -not- aimed at a channel
|
||||
(e.g. private tells)
|
||||
"""
|
||||
obj, typ = identify_object(obj)
|
||||
if exclude_channel_messages:
|
||||
# explicitly exclude channel recipients
|
||||
if typ == 'player':
|
||||
return list(self.filter(db_sender_players=obj,
|
||||
db_receivers_channels__isnull=True).exclude(db_hide_from_players=obj))
|
||||
elif typ == 'object':
|
||||
return list(self.filter(db_sender_objects=obj,
|
||||
db_receivers_channels__isnull=True).exclude(db_hide_from_objects=obj))
|
||||
else:
|
||||
raise CommError
|
||||
else:
|
||||
# get everything, channel or not
|
||||
if typ == 'player':
|
||||
return list(self.filter(db_sender_players=obj).exclude(db_hide_from_players=obj))
|
||||
elif typ == 'object':
|
||||
return list(self.filter(db_sender_objects=obj).exclude(db_hide_from_objects=obj))
|
||||
else:
|
||||
raise CommError
|
||||
|
||||
def get_messages_by_receiver(self, obj):
|
||||
"""
|
||||
Get all messages sent to one give recipient
|
||||
"""
|
||||
obj, typ = identify_object(obj)
|
||||
if typ == 'player':
|
||||
return list(self.filter(db_receivers_players=obj).exclude(db_hide_from_players=obj))
|
||||
elif typ == 'object':
|
||||
return list(self.filter(db_receivers_objects=obj).exclude(db_hide_from_objects=obj))
|
||||
elif typ == 'channel':
|
||||
return list(self.filter(db_receivers_channels=obj).exclude(db_hide_from_channels=obj))
|
||||
else:
|
||||
raise CommError
|
||||
|
||||
def get_messages_by_channel(self, channel):
|
||||
"""
|
||||
Get all messages sent to one channel
|
||||
"""
|
||||
return self.filter(db_receivers_channels=channel).exclude(db_hide_from_channels=channel)
|
||||
|
||||
def message_search(self, sender=None, receiver=None, freetext=None, dbref=None):
|
||||
"""
|
||||
Search the message database for particular messages. At least one
|
||||
of the arguments must be given to do a search.
|
||||
|
||||
sender - get messages sent by a particular player or object
|
||||
receiver - get messages received by a certain player,object or channel
|
||||
freetext - Search for a text string in a message.
|
||||
NOTE: This can potentially be slow, so make sure to supply
|
||||
one of the other arguments to limit the search.
|
||||
dbref - (int) the exact database id of the message. This will override
|
||||
all other search criteria since it's unique and
|
||||
always gives a list with only one match.
|
||||
"""
|
||||
# unique msg id
|
||||
if dbref:
|
||||
msg = self.objects.filter(id=dbref)
|
||||
if msg:
|
||||
return msg[0]
|
||||
|
||||
# We use Q objects to gradually build up the query - this way we only
|
||||
# need to do one database lookup at the end rather than gradually
|
||||
# refining with multiple filter:s. Django Note: Q objects can be
|
||||
# combined with & and | (=AND,OR). ~ negates the queryset
|
||||
|
||||
# filter by sender
|
||||
sender, styp = identify_object(sender)
|
||||
if styp == 'player':
|
||||
sender_restrict = Q(db_sender_players=sender) & ~Q(db_hide_from_players=sender)
|
||||
elif styp == 'object':
|
||||
sender_restrict = Q(db_sender_objects=sender) & ~Q(db_hide_from_objects=sender)
|
||||
else:
|
||||
sender_restrict = Q()
|
||||
# filter by receiver
|
||||
receiver, rtyp = identify_object(receiver)
|
||||
if rtyp == 'player':
|
||||
receiver_restrict = Q(db_receivers_players=receiver) & ~Q(db_hide_from_players=receiver)
|
||||
elif rtyp == 'object':
|
||||
receiver_restrict = Q(db_receivers_objects=receiver) & ~Q(db_hide_from_objects=receiver)
|
||||
elif rtyp == 'channel':
|
||||
receiver_restrict = Q(db_receivers_channels=receiver) & ~Q(db_hide_from_channels=receiver)
|
||||
else:
|
||||
receiver_restrict = Q()
|
||||
# filter by full text
|
||||
if freetext:
|
||||
fulltext_restrict = Q(db_header__icontains=freetext) | Q(db_message__icontains=freetext)
|
||||
else:
|
||||
fulltext_restrict = Q()
|
||||
# execute the query
|
||||
return list(self.filter(sender_restrict & receiver_restrict & fulltext_restrict))
|
||||
|
||||
|
||||
#
|
||||
# Channel manager
|
||||
#
|
||||
|
||||
class ChannelDBManager(TypedObjectManager):
|
||||
"""
|
||||
This ChannelManager implements methods for searching
|
||||
and manipulating Channels directly from the database.
|
||||
|
||||
These methods will all return database objects
|
||||
(or QuerySets) directly.
|
||||
|
||||
A Channel is an in-game venue for communication. It's
|
||||
essentially representation of a re-sender: Users sends
|
||||
Messages to the Channel, and the Channel re-sends those
|
||||
messages to all users subscribed to the Channel.
|
||||
|
||||
Evennia-specific:
|
||||
get_all_channels
|
||||
get_channel(channel)
|
||||
get_subscriptions(player)
|
||||
channel_search (equivalent to ev.search_channel)
|
||||
|
||||
"""
|
||||
@returns_typeclass_list
|
||||
def get_all_channels(self):
|
||||
"""
|
||||
Returns all channels in game.
|
||||
"""
|
||||
return self.all()
|
||||
|
||||
@returns_typeclass
|
||||
def get_channel(self, channelkey):
|
||||
"""
|
||||
Return the channel object if given its key.
|
||||
Also searches its aliases.
|
||||
"""
|
||||
# first check the channel key
|
||||
channels = self.filter(db_key__iexact=channelkey)
|
||||
if not channels:
|
||||
# also check aliases
|
||||
channels = [channel for channel in self.all()
|
||||
if channelkey in channel.aliases.all()]
|
||||
if channels:
|
||||
return channels[0]
|
||||
return None
|
||||
|
||||
@returns_typeclass_list
|
||||
def get_subscriptions(self, player):
|
||||
"""
|
||||
Return all channels a given player is subscribed to
|
||||
"""
|
||||
return player.subscription_set.all()
|
||||
|
||||
@returns_typeclass_list
|
||||
def channel_search(self, ostring, exact=True):
|
||||
"""
|
||||
Search the channel database for a particular channel.
|
||||
|
||||
ostring - the key or database id of the channel.
|
||||
exact - require an exact key match (still not case sensitive)
|
||||
"""
|
||||
channels = []
|
||||
if not ostring: return channels
|
||||
try:
|
||||
# try an id match first
|
||||
dbref = int(ostring.strip('#'))
|
||||
channels = self.filter(id=dbref)
|
||||
except Exception:
|
||||
pass
|
||||
if not channels:
|
||||
# no id match. Search on the key.
|
||||
if exact:
|
||||
channels = self.filter(db_key__iexact=ostring)
|
||||
else:
|
||||
channels = self.filter(db_key__icontains=ostring)
|
||||
if not channels:
|
||||
# still no match. Search by alias.
|
||||
channels = [channel for channel in self.all()
|
||||
if ostring.lower() in [a.lower
|
||||
for a in channel.aliases.all()]]
|
||||
return channels
|
||||
|
||||
class ChannelManager(ChannelDBManager, TypeclassManager):
|
||||
pass
|
||||
|
||||
|
||||
44
lib/comms/migrations/0001_initial.py
Normal file
44
lib/comms/migrations/0001_initial.py
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='ChannelDB',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('db_key', models.CharField(max_length=255, verbose_name=b'key', db_index=True)),
|
||||
('db_typeclass_path', models.CharField(help_text=b"this defines what 'type' of entity this is. This variable holds a Python path to a module with a valid Evennia Typeclass.", max_length=255, null=True, verbose_name=b'typeclass')),
|
||||
('db_date_created', models.DateTimeField(auto_now_add=True, verbose_name=b'creation date')),
|
||||
('db_lock_storage', models.TextField(help_text=b"locks limit access to an entity. A lock is defined as a 'lock string' on the form 'type:lockfunctions', defining what functionality is locked and how to determine access. Not defining a lock means no access is granted.", verbose_name=b'locks', blank=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Channel',
|
||||
'verbose_name_plural': 'Channels',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Msg',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('db_sender_external', models.CharField(help_text=b"identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).", max_length=255, null=True, verbose_name=b'external sender', db_index=True)),
|
||||
('db_header', models.TextField(null=True, verbose_name=b'header', blank=True)),
|
||||
('db_message', models.TextField(verbose_name=b'messsage')),
|
||||
('db_date_sent', models.DateTimeField(auto_now_add=True, verbose_name=b'date sent', db_index=True)),
|
||||
('db_lock_storage', models.TextField(help_text=b'access locks on this message.', verbose_name=b'locks', blank=True)),
|
||||
('db_hide_from_channels', models.ManyToManyField(related_name=b'hide_from_channels_set', null=True, to='comms.ChannelDB')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Message',
|
||||
},
|
||||
bases=(models.Model,),
|
||||
),
|
||||
]
|
||||
21
lib/comms/migrations/0002_msg_db_hide_from_objects.py
Normal file
21
lib/comms/migrations/0002_msg_db_hide_from_objects.py
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('objects', '0001_initial'),
|
||||
('comms', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='msg',
|
||||
name='db_hide_from_objects',
|
||||
field=models.ManyToManyField(related_name=b'hide_from_objects_set', null=True, to='objects.ObjectDB'),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
72
lib/comms/migrations/0003_auto_20140917_0756.py
Normal file
72
lib/comms/migrations/0003_auto_20140917_0756.py
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import models, migrations
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('objects', '0001_initial'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('typeclasses', '0001_initial'),
|
||||
('comms', '0002_msg_db_hide_from_objects'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='msg',
|
||||
name='db_hide_from_players',
|
||||
field=models.ManyToManyField(related_name=b'hide_from_players_set', null=True, to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='msg',
|
||||
name='db_receivers_channels',
|
||||
field=models.ManyToManyField(help_text=b'channel recievers', related_name=b'channel_set', null=True, to='comms.ChannelDB'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='msg',
|
||||
name='db_receivers_objects',
|
||||
field=models.ManyToManyField(help_text=b'object receivers', related_name=b'receiver_object_set', null=True, to='objects.ObjectDB'),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='msg',
|
||||
name='db_receivers_players',
|
||||
field=models.ManyToManyField(help_text=b'player receivers', related_name=b'receiver_player_set', null=True, to=settings.AUTH_USER_MODEL),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='msg',
|
||||
name='db_sender_objects',
|
||||
field=models.ManyToManyField(related_name=b'sender_object_set', null=True, verbose_name=b'sender(object)', to='objects.ObjectDB', db_index=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='msg',
|
||||
name='db_sender_players',
|
||||
field=models.ManyToManyField(related_name=b'sender_player_set', null=True, verbose_name=b'sender(player)', to=settings.AUTH_USER_MODEL, db_index=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='channeldb',
|
||||
name='db_attributes',
|
||||
field=models.ManyToManyField(help_text=b'attributes on this object. An attribute can hold any pickle-able python object (see docs for special cases).', to='typeclasses.Attribute', null=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='channeldb',
|
||||
name='db_subscriptions',
|
||||
field=models.ManyToManyField(related_name=b'subscription_set', null=True, verbose_name=b'subscriptions', to=settings.AUTH_USER_MODEL, db_index=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='channeldb',
|
||||
name='db_tags',
|
||||
field=models.ManyToManyField(help_text=b'tags on this object. Tags are simple string markers to identify, group and alias objects.', to='typeclasses.Tag', null=True),
|
||||
preserve_default=True,
|
||||
),
|
||||
]
|
||||
1
lib/comms/migrations/__init__.py
Normal file
1
lib/comms/migrations/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
368
lib/comms/models.py
Normal file
368
lib/comms/models.py
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
"""
|
||||
Models for the comsystem. The Commsystem is intended to be
|
||||
used by Players (thematic IC communication is probably
|
||||
best handled by custom commands instead).
|
||||
|
||||
The comm system could take the form of channels, but can also
|
||||
be adopted for storing tells or in-game mail.
|
||||
|
||||
The comsystem's main component is the Message (Msg), which
|
||||
carries the actual information between two parties.
|
||||
Msgs are stored in the database and usually not
|
||||
deleted.
|
||||
A Msg always have one sender (a user), but can have
|
||||
any number targets, both users and channels.
|
||||
|
||||
Channels are central objects that act as targets for
|
||||
Msgs. Players can connect to channels by use of a
|
||||
ChannelConnect object (this object is necessary to easily
|
||||
be able to delete connections on the fly).
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from django.conf import settings
|
||||
from django.db import models
|
||||
from src.typeclasses.models import TypedObject
|
||||
from src.utils.idmapper.models import SharedMemoryModel
|
||||
from src.comms import managers
|
||||
from src.comms.managers import identify_object
|
||||
from src.locks.lockhandler import LockHandler
|
||||
from src.utils.utils import crop, make_iter, lazy_property
|
||||
|
||||
__all__ = ("Msg", "TempMsg", "ChannelDB")
|
||||
|
||||
|
||||
_GA = object.__getattribute__
|
||||
_SA = object.__setattr__
|
||||
_DA = object.__delattr__
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Msg
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class Msg(SharedMemoryModel):
|
||||
"""
|
||||
A single message. This model describes all ooc messages
|
||||
sent in-game, both to channels and between players.
|
||||
|
||||
The Msg class defines the following properties:
|
||||
sender - sender of message
|
||||
receivers - list of target objects for message
|
||||
channels - list of channels message was sent to
|
||||
message - the text being sent
|
||||
date_sent - time message was sent
|
||||
hide_from_sender - bool if message should be hidden from sender
|
||||
hide_from_receivers - list of receiver objects to hide message from
|
||||
hide_from_channels - list of channels objects to hide message from
|
||||
permissions - perm strings
|
||||
|
||||
"""
|
||||
#
|
||||
# Msg database model setup
|
||||
#
|
||||
#
|
||||
# These databse fields are all set using their corresponding properties,
|
||||
# named same as the field, but withtout the db_* prefix.
|
||||
|
||||
# Sender is either a player, an object or an external sender, like
|
||||
# an IRC channel; normally there is only one, but if co-modification of
|
||||
# a message is allowed, there may be more than one "author"
|
||||
db_sender_players = models.ManyToManyField("players.PlayerDB", related_name='sender_player_set', null=True, verbose_name='sender(player)', db_index=True)
|
||||
db_sender_objects = models.ManyToManyField("objects.ObjectDB", related_name='sender_object_set', null=True, verbose_name='sender(object)', db_index=True)
|
||||
db_sender_external = models.CharField('external sender', max_length=255, null=True, db_index=True,
|
||||
help_text="identifier for external sender, for example a sender over an IRC connection (i.e. someone who doesn't have an exixtence in-game).")
|
||||
# The destination objects of this message. Stored as a
|
||||
# comma-separated string of object dbrefs. Can be defined along
|
||||
# with channels below.
|
||||
db_receivers_players = models.ManyToManyField('players.PlayerDB', related_name='receiver_player_set', null=True, help_text="player receivers")
|
||||
db_receivers_objects = models.ManyToManyField('objects.ObjectDB', related_name='receiver_object_set', null=True, help_text="object receivers")
|
||||
db_receivers_channels = models.ManyToManyField("ChannelDB", related_name='channel_set', null=True, help_text="channel recievers")
|
||||
|
||||
# header could be used for meta-info about the message if your system needs
|
||||
# it, or as a separate store for the mail subject line maybe.
|
||||
db_header = models.TextField('header', null=True, blank=True)
|
||||
# the message body itself
|
||||
db_message = models.TextField('messsage')
|
||||
# send date
|
||||
db_date_sent = models.DateTimeField('date sent', editable=False, auto_now_add=True, db_index=True)
|
||||
# lock storage
|
||||
db_lock_storage = models.TextField('locks', blank=True,
|
||||
help_text='access locks on this message.')
|
||||
|
||||
# these can be used to filter/hide a given message from supplied objects/players/channels
|
||||
db_hide_from_players = models.ManyToManyField("players.PlayerDB", related_name='hide_from_players_set', null=True)
|
||||
db_hide_from_objects = models.ManyToManyField("objects.ObjectDB", related_name='hide_from_objects_set', null=True)
|
||||
db_hide_from_channels = models.ManyToManyField("ChannelDB", related_name='hide_from_channels_set', null=True)
|
||||
|
||||
# Database manager
|
||||
objects = managers.MsgManager()
|
||||
_is_deleted = False
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
SharedMemoryModel.__init__(self, *args, **kwargs)
|
||||
self.extra_senders = []
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Message"
|
||||
|
||||
# Wrapper properties to easily set database fields. These are
|
||||
# @property decorators that allows to access these fields using
|
||||
# normal python operations (without having to remember to save()
|
||||
# etc). So e.g. a property 'attr' has a get/set/del decorator
|
||||
# defined that allows the user to do self.attr = value,
|
||||
# value = self.attr and del self.attr respectively (where self
|
||||
# is the object in question).
|
||||
|
||||
# sender property (wraps db_sender_*)
|
||||
#@property
|
||||
def __senders_get(self):
|
||||
"Getter. Allows for value = self.sender"
|
||||
return list(self.db_sender_players.all()) + \
|
||||
list(self.db_sender_objects.all()) + \
|
||||
self.extra_senders
|
||||
|
||||
#@sender.setter
|
||||
def __senders_set(self, value):
|
||||
"Setter. Allows for self.sender = value"
|
||||
for val in (v for v in make_iter(value) if v):
|
||||
obj, typ = identify_object(val)
|
||||
if typ == 'player':
|
||||
self.db_sender_players.add(obj)
|
||||
elif typ == 'object':
|
||||
self.db_sender_objects.add(obj)
|
||||
elif isinstance(typ, basestring):
|
||||
self.db_sender_external = obj
|
||||
elif not obj:
|
||||
return
|
||||
else:
|
||||
raise ValueError(obj)
|
||||
self.save()
|
||||
|
||||
#@sender.deleter
|
||||
def __senders_del(self):
|
||||
"Deleter. Clears all senders"
|
||||
self.db_sender_players.clear()
|
||||
self.db_sender_objects.clear()
|
||||
self.db_sender_external = ""
|
||||
self.extra_senders = []
|
||||
self.save()
|
||||
senders = property(__senders_get, __senders_set, __senders_del)
|
||||
|
||||
def remove_sender(self, value):
|
||||
"Remove a single sender or a list of senders"
|
||||
for val in make_iter(value):
|
||||
obj, typ = identify_object(val)
|
||||
if typ == 'player':
|
||||
self.db_sender_players.remove(obj)
|
||||
elif typ == 'object':
|
||||
self.db_sender_objects.remove(obj)
|
||||
elif isinstance(obj, basestring):
|
||||
self.db_sender_external = obj
|
||||
else:
|
||||
raise ValueError(obj)
|
||||
self.save()
|
||||
|
||||
# receivers property
|
||||
#@property
|
||||
def __receivers_get(self):
|
||||
"""
|
||||
Getter. Allows for value = self.receivers.
|
||||
Returns three lists of receivers: players, objects and channels.
|
||||
"""
|
||||
return list(self.db_receivers_players.all()) + list(self.db_receivers_objects.all())
|
||||
|
||||
#@receivers.setter
|
||||
def __receivers_set(self, value):
|
||||
"""
|
||||
Setter. Allows for self.receivers = value.
|
||||
This appends a new receiver to the message.
|
||||
"""
|
||||
for val in (v for v in make_iter(value) if v):
|
||||
obj, typ = identify_object(val)
|
||||
if typ == 'player':
|
||||
self.db_receivers_players.add(obj)
|
||||
elif typ == 'object':
|
||||
self.db_receivers_objects.add(obj)
|
||||
elif not obj:
|
||||
return
|
||||
else:
|
||||
raise ValueError
|
||||
self.save()
|
||||
|
||||
#@receivers.deleter
|
||||
def __receivers_del(self):
|
||||
"Deleter. Clears all receivers"
|
||||
self.db_receivers_players.clear()
|
||||
self.db_receivers_objects.clear()
|
||||
self.extra_senders = []
|
||||
self.save()
|
||||
receivers = property(__receivers_get, __receivers_set, __receivers_del)
|
||||
|
||||
def remove_receiver(self, obj):
|
||||
"Remove a single recevier"
|
||||
obj, typ = identify_object(obj)
|
||||
if typ == 'player':
|
||||
self.db_receivers_players.remove(obj)
|
||||
elif typ == 'object':
|
||||
self.db_receivers_objects.remove(obj)
|
||||
else:
|
||||
raise ValueError
|
||||
self.save()
|
||||
|
||||
# channels property
|
||||
#@property
|
||||
def __channels_get(self):
|
||||
"Getter. Allows for value = self.channels. Returns a list of channels."
|
||||
return self.db_receivers_channels.all()
|
||||
|
||||
#@channels.setter
|
||||
def __channels_set(self, value):
|
||||
"""
|
||||
Setter. Allows for self.channels = value.
|
||||
Requires a channel to be added.
|
||||
"""
|
||||
for val in (v for v in make_iter(value) if v):
|
||||
self.db_receivers_channels.add(val)
|
||||
|
||||
#@channels.deleter
|
||||
def __channels_del(self):
|
||||
"Deleter. Allows for del self.channels"
|
||||
self.db_receivers_channels.clear()
|
||||
self.save()
|
||||
channels = property(__channels_get, __channels_set, __channels_del)
|
||||
|
||||
def __hide_from_get(self):
|
||||
"""
|
||||
Getter. Allows for value = self.hide_from.
|
||||
Returns 3 lists of players, objects and channels
|
||||
"""
|
||||
return self.db_hide_from_players.all(), self.db_hide_from_objects.all(), self.db_hide_from_channels.all()
|
||||
|
||||
#@hide_from_sender.setter
|
||||
def __hide_from_set(self, value):
|
||||
"Setter. Allows for self.hide_from = value. Will append to hiders"
|
||||
obj, typ = identify_object(value)
|
||||
if typ == "player":
|
||||
self.db_hide_from_players.add(obj)
|
||||
elif typ == "object":
|
||||
self.db_hide_from_objects.add(obj)
|
||||
elif typ == "channel":
|
||||
self.db_hide_from_channels.add(obj)
|
||||
else:
|
||||
raise ValueError
|
||||
self.save()
|
||||
|
||||
#@hide_from_sender.deleter
|
||||
def __hide_from_del(self):
|
||||
"Deleter. Allows for del self.hide_from_senders"
|
||||
self.db_hide_from_players.clear()
|
||||
self.db_hide_from_objects.clear()
|
||||
self.db_hide_from_channels.clear()
|
||||
self.save()
|
||||
hide_from = property(__hide_from_get, __hide_from_set, __hide_from_del)
|
||||
|
||||
#
|
||||
# Msg class methods
|
||||
#
|
||||
|
||||
def __str__(self):
|
||||
"This handles what is shown when e.g. printing the message"
|
||||
senders = ",".join(obj.key for obj in self.senders)
|
||||
receivers = ",".join(["[%s]" % obj.key for obj in self.channels] + [obj.key for obj in self.receivers])
|
||||
return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40))
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# TempMsg
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class TempMsg(object):
|
||||
"""
|
||||
This is a non-persistent object for sending
|
||||
temporary messages that will not be stored.
|
||||
It mimics the "real" Msg object, but don't require
|
||||
sender to be given.
|
||||
"""
|
||||
def __init__(self, senders=None, receivers=None, channels=None, message="", header="", type="", lockstring="", hide_from=None):
|
||||
self.senders = senders and make_iter(senders) or []
|
||||
self.receivers = receivers and make_iter(receivers) or []
|
||||
self.channels = channels and make_iter(channels) or []
|
||||
self.type = type
|
||||
self.header = header
|
||||
self.message = message
|
||||
self.lock_storage = lockstring
|
||||
self.hide_from = hide_from and make_iter(hide_from) or []
|
||||
self.date_sent = datetime.now()
|
||||
|
||||
@lazy_property
|
||||
def locks(self):
|
||||
return LockHandler(self)
|
||||
|
||||
def __str__(self):
|
||||
"This handles what is shown when e.g. printing the message"
|
||||
senders = ",".join(obj.key for obj in self.senders)
|
||||
receivers = ",".join(["[%s]" % obj.key for obj in self.channels] + [obj.key for obj in self.receivers])
|
||||
return "%s->%s: %s" % (senders, receivers, crop(self.message, width=40))
|
||||
|
||||
def remove_sender(self, obj):
|
||||
"Remove a sender or a list of senders"
|
||||
for o in make_iter(obj):
|
||||
try:
|
||||
self.senders.remove(o)
|
||||
except ValueError:
|
||||
pass # nothing to remove
|
||||
|
||||
def remove_receiver(self, obj):
|
||||
"Remove a sender or a list of senders"
|
||||
for o in make_iter(obj):
|
||||
try:
|
||||
self.senders.remove(o)
|
||||
except ValueError:
|
||||
pass # nothing to remove
|
||||
|
||||
def access(self, accessing_obj, access_type='read', default=False):
|
||||
"checks lock access"
|
||||
return self.locks.check(accessing_obj,
|
||||
access_type=access_type, default=default)
|
||||
|
||||
|
||||
#------------------------------------------------------------
|
||||
#
|
||||
# Channel
|
||||
#
|
||||
#------------------------------------------------------------
|
||||
|
||||
class ChannelDB(TypedObject):
|
||||
"""
|
||||
This is the basis of a comm channel, only implementing
|
||||
the very basics of distributing messages.
|
||||
|
||||
The Channel class defines the following properties:
|
||||
key - main name for channel
|
||||
desc - optional description of channel
|
||||
aliases - alternative names for the channel
|
||||
permissions - perm strings
|
||||
|
||||
"""
|
||||
db_subscriptions = models.ManyToManyField("players.PlayerDB",
|
||||
related_name="subscription_set", null=True, verbose_name='subscriptions', db_index=True)
|
||||
|
||||
# Database manager
|
||||
objects = managers.ChannelDBManager()
|
||||
|
||||
_typeclass_paths = settings.CHANNEL_TYPECLASS_PATHS
|
||||
_default_typeclass_path = settings.BASE_CHANNEL_TYPECLASS or "src.comms.comms.Channel"
|
||||
|
||||
class Meta:
|
||||
"Define Django meta options"
|
||||
verbose_name = "Channel"
|
||||
verbose_name_plural = "Channels"
|
||||
|
||||
def __str__(self):
|
||||
return "Channel '%s' (%s)" % (self.key, self.db.desc)
|
||||
Loading…
Add table
Add a link
Reference in a new issue