Merges develop.
This commit is contained in:
commit
0459d0412d
10 changed files with 536 additions and 7 deletions
|
|
@ -2,6 +2,10 @@
|
||||||
Base typeclass for in-game Channels.
|
Base typeclass for in-game Channels.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
from django.contrib.contenttypes.models import ContentType
|
||||||
|
from django.urls import reverse
|
||||||
|
from django.utils.text import slugify
|
||||||
|
|
||||||
from evennia.typeclasses.models import TypeclassBase
|
from evennia.typeclasses.models import TypeclassBase
|
||||||
from evennia.comms.models import TempMsg, ChannelDB
|
from evennia.comms.models import TempMsg, ChannelDB
|
||||||
from evennia.comms.managers import ChannelManager
|
from evennia.comms.managers import ChannelManager
|
||||||
|
|
@ -622,3 +626,151 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
#
|
||||||
|
# Web/Django methods
|
||||||
|
#
|
||||||
|
|
||||||
|
def web_get_admin_url(self):
|
||||||
|
"""
|
||||||
|
Returns the URI path for the Django Admin page for this object.
|
||||||
|
|
||||||
|
ex. Account#1 = '/admin/accounts/accountdb/1/change/'
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
path (str): URI path to Django Admin page for object.
|
||||||
|
|
||||||
|
"""
|
||||||
|
content_type = ContentType.objects.get_for_model(self.__class__)
|
||||||
|
return reverse("admin:%s_%s_change" % (content_type.app_label,
|
||||||
|
content_type.model), args=(self.id,))
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def web_get_create_url(cls):
|
||||||
|
"""
|
||||||
|
Returns the URI path for a View that allows users to create new
|
||||||
|
instances of this object.
|
||||||
|
|
||||||
|
ex. Chargen = '/characters/create/'
|
||||||
|
|
||||||
|
For this to work, the developer must have defined a named view somewhere
|
||||||
|
in urls.py that follows the format 'modelname-action', so in this case
|
||||||
|
a named view of 'channel-create' would be referenced by this method.
|
||||||
|
|
||||||
|
ex.
|
||||||
|
url(r'channels/create/', ChannelCreateView.as_view(), name='channel-create')
|
||||||
|
|
||||||
|
If no View has been created and defined in urls.py, returns an
|
||||||
|
HTML anchor.
|
||||||
|
|
||||||
|
This method is naive and simply returns a path. Securing access to
|
||||||
|
the actual view and limiting who can create new objects is the
|
||||||
|
developer's responsibility.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
path (str): URI path to object creation page, if defined.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return reverse('%s-create' % slugify(cls._meta.verbose_name))
|
||||||
|
except:
|
||||||
|
return '#'
|
||||||
|
|
||||||
|
def web_get_detail_url(self):
|
||||||
|
"""
|
||||||
|
Returns the URI path for a View that allows users to view details for
|
||||||
|
this object.
|
||||||
|
|
||||||
|
ex. Oscar (Character) = '/characters/oscar/1/'
|
||||||
|
|
||||||
|
For this to work, the developer must have defined a named view somewhere
|
||||||
|
in urls.py that follows the format 'modelname-action', so in this case
|
||||||
|
a named view of 'channel-detail' would be referenced by this method.
|
||||||
|
|
||||||
|
ex.
|
||||||
|
url(r'channels/(?P<slug>[\w\d\-]+)/$',
|
||||||
|
ChannelDetailView.as_view(), name='channel-detail')
|
||||||
|
|
||||||
|
If no View has been created and defined in urls.py, returns an
|
||||||
|
HTML anchor.
|
||||||
|
|
||||||
|
This method is naive and simply returns a path. Securing access to
|
||||||
|
the actual view and limiting who can view this object is the developer's
|
||||||
|
responsibility.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
path (str): URI path to object detail page, if defined.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return reverse('%s-detail' % slugify(self._meta.verbose_name),
|
||||||
|
kwargs={'slug': slugify(self.db_key)})
|
||||||
|
except:
|
||||||
|
return '#'
|
||||||
|
|
||||||
|
|
||||||
|
def web_get_update_url(self):
|
||||||
|
"""
|
||||||
|
Returns the URI path for a View that allows users to update this
|
||||||
|
object.
|
||||||
|
|
||||||
|
ex. Oscar (Character) = '/characters/oscar/1/change/'
|
||||||
|
|
||||||
|
For this to work, the developer must have defined a named view somewhere
|
||||||
|
in urls.py that follows the format 'modelname-action', so in this case
|
||||||
|
a named view of 'channel-update' would be referenced by this method.
|
||||||
|
|
||||||
|
ex.
|
||||||
|
url(r'channels/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/change/$',
|
||||||
|
ChannelUpdateView.as_view(), name='channel-update')
|
||||||
|
|
||||||
|
If no View has been created and defined in urls.py, returns an
|
||||||
|
HTML anchor.
|
||||||
|
|
||||||
|
This method is naive and simply returns a path. Securing access to
|
||||||
|
the actual view and limiting who can modify objects is the developer's
|
||||||
|
responsibility.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
path (str): URI path to object update page, if defined.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return reverse('%s-update' % slugify(self._meta.verbose_name),
|
||||||
|
kwargs={'slug': slugify(self.db_key)})
|
||||||
|
except:
|
||||||
|
return '#'
|
||||||
|
|
||||||
|
def web_get_delete_url(self):
|
||||||
|
"""
|
||||||
|
Returns the URI path for a View that allows users to delete this object.
|
||||||
|
|
||||||
|
ex. Oscar (Character) = '/characters/oscar/1/delete/'
|
||||||
|
|
||||||
|
For this to work, the developer must have defined a named view somewhere
|
||||||
|
in urls.py that follows the format 'modelname-action', so in this case
|
||||||
|
a named view of 'channel-delete' would be referenced by this method.
|
||||||
|
|
||||||
|
ex.
|
||||||
|
url(r'channels/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/delete/$',
|
||||||
|
ChannelDeleteView.as_view(), name='channel-delete')
|
||||||
|
|
||||||
|
If no View has been created and defined in urls.py, returns an
|
||||||
|
HTML anchor.
|
||||||
|
|
||||||
|
This method is naive and simply returns a path. Securing access to
|
||||||
|
the actual view and limiting who can delete this object is the developer's
|
||||||
|
responsibility.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
path (str): URI path to object deletion page, if defined.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return reverse('%s-delete' % slugify(self._meta.verbose_name),
|
||||||
|
kwargs={'slug': slugify(self.db_key)})
|
||||||
|
except:
|
||||||
|
return '#'
|
||||||
|
|
||||||
|
# Used by Django Sites/Admin
|
||||||
|
get_absolute_url = web_get_detail_url
|
||||||
|
|
@ -23,25 +23,34 @@ folder and edit it to add/remove links to the menu.
|
||||||
<ul class="navbar-nav">
|
<ul class="navbar-nav">
|
||||||
{% block nabvar_left %}
|
{% block nabvar_left %}
|
||||||
<li>
|
<li>
|
||||||
<a class="nav-link" href="/">Home</a>
|
<a class="nav-link" href="{% url 'index' %}">Home</a>
|
||||||
</li>
|
</li>
|
||||||
|
<!-- evennia documentation -->
|
||||||
<li>
|
<li>
|
||||||
<a class="nav-link" href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a>
|
<a class="nav-link" href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a>
|
||||||
</li>
|
</li>
|
||||||
<li><a class="nav-link" href="https://github.com/evennia/evennia/wiki">Documentation</a></li>
|
<li><a class="nav-link" href="https://github.com/evennia/evennia/wiki">Documentation</a></li>
|
||||||
|
<!-- end evennia documentation -->
|
||||||
|
|
||||||
|
<!-- game views -->
|
||||||
<li><a class="nav-link" href="{% url 'characters' %}">Characters</a></li>
|
<li><a class="nav-link" href="{% url 'characters' %}">Characters</a></li>
|
||||||
{% if user.is_staff %}
|
<li><a class="nav-link" href="{% url 'channels' %}">Channels</a></li>
|
||||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
|
||||||
{% endif %}
|
|
||||||
<li><a class="nav-link" href="{% url 'help' %}">Help</a></li>
|
<li><a class="nav-link" href="{% url 'help' %}">Help</a></li>
|
||||||
|
<!-- end game views -->
|
||||||
|
|
||||||
{% if webclient_enabled %}
|
{% if webclient_enabled %}
|
||||||
<li><a class="nav-link" href="{% url 'webclient:index' %}">Play Online</a></li>
|
<li><a class="nav-link" href="{% url 'webclient:index' %}">Play Online</a></li>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.is_staff %}
|
||||||
|
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
||||||
|
{% endif %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav ml-auto w-120 justify-content-end">
|
<ul class="nav navbar-nav ml-auto w-120 justify-content-end">
|
||||||
{% block navbar_right %}
|
{% block navbar_right %}
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block navbar_user %}
|
{% block navbar_user %}
|
||||||
{% if account %}
|
{% if account %}
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
|
|
|
||||||
94
evennia/web/website/templates/website/channel_detail.html
Normal file
94
evennia/web/website/templates/website/channel_detail.html
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block titleblock %}
|
||||||
|
{{ view.page_title }} ({{ object }})
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% load addclass %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title">{{ view.page_title }} ({{ object }})</h1>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'channels' %}">Channels</a></li>
|
||||||
|
<li class="breadcrumb-item active" aria-current="page">{{ object.db_key|title }}</li>
|
||||||
|
</ol>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- left column -->
|
||||||
|
<div class="col-lg-9 col-sm-12">
|
||||||
|
|
||||||
|
<!-- heading -->
|
||||||
|
<div class="card border-light">
|
||||||
|
<div class="card-body">
|
||||||
|
{% if object.db.desc and object.db.desc != None %}{{ object.db.desc }}{% else %}No description provided.{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<!-- end heading -->
|
||||||
|
|
||||||
|
{% if object_list %}
|
||||||
|
<pre>{% for log in object_list %}
|
||||||
|
<a id="{{ log.key }}"></a>{{ log.timestamp }}: {{ log.message }}{% endfor %}</pre>
|
||||||
|
{% else %}
|
||||||
|
<pre>No recent log entries have been recorded for this channel.</pre>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<!-- end left column -->
|
||||||
|
|
||||||
|
<!-- right column -->
|
||||||
|
<div class="col-lg-3 col-sm-12">
|
||||||
|
|
||||||
|
{% if request.user.is_staff %}
|
||||||
|
<!-- admin button -->
|
||||||
|
<a class="btn btn-info btn-block mb-3" href="{{ object.web_get_admin_url }}">Admin</a>
|
||||||
|
<!-- end admin button -->
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-body">
|
||||||
|
<dl>
|
||||||
|
{% for attribute, value in attribute_list.items %}
|
||||||
|
<dt>{{ attribute }}</dt>
|
||||||
|
<dd>{{ value }}</dd>
|
||||||
|
{% endfor %}
|
||||||
|
</dl>
|
||||||
|
<dl>
|
||||||
|
<dt>Subscriptions</dt>
|
||||||
|
<dd>{{ object.subscriptions.all|length }}</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if object_filters %}
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">Date Index</div>
|
||||||
|
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
{% for filter in object_filters %}
|
||||||
|
<a href="#{{ filter }}" class="list-group-item">{{ filter }}</a>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- end right column -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
86
evennia/web/website/templates/website/channel_list.html
Normal file
86
evennia/web/website/templates/website/channel_list.html
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block titleblock %}
|
||||||
|
{{ view.page_title }}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
{% load addclass %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'channels' %}">Channels</a></li>
|
||||||
|
</ol>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<!-- left column -->
|
||||||
|
<div class="col-lg-9 col-sm-12">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-sm">
|
||||||
|
<thead class="thead-light">
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Channel</th>
|
||||||
|
<th scope="col">Description</th>
|
||||||
|
<th scope="col">Subscriptions</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for object in object_list %}
|
||||||
|
<tr>
|
||||||
|
<td><a href="{{ object.web_get_detail_url }}">{{ object.name }}</a></td>
|
||||||
|
<td>{{ object.db.desc }}</td>
|
||||||
|
<td>{{ object.subscriptions.all|length }}</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="3">No channels were found!</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- end left column -->
|
||||||
|
|
||||||
|
<!-- right column -->
|
||||||
|
<div class="col-lg-3 col-sm-12">
|
||||||
|
|
||||||
|
{% if request.user.is_staff %}
|
||||||
|
<!-- admin button -->
|
||||||
|
<a class="btn btn-info btn-block mb-3" href="/admin/comms/channeldb/">Admin</a>
|
||||||
|
<!-- end admin button -->
|
||||||
|
<hr />
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if most_popular %}
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">Most Popular</div>
|
||||||
|
|
||||||
|
<ul class="list-group list-group-flush">
|
||||||
|
{% for top in most_popular %}
|
||||||
|
<a href="{{ top.web_get_detail_url }}" class="list-group-item">{{ top }} <span class="badge badge-light float-right">{{ top.subscriptions.all|length }}</span></a>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- end right column -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
@ -56,7 +56,7 @@
|
||||||
|
|
||||||
{% if request.user.is_staff %}
|
{% if request.user.is_staff %}
|
||||||
<!-- admin button -->
|
<!-- admin button -->
|
||||||
<a class="btn btn-info btn-block mb-3" href="{{ object.web_get_admin_url }}">Edit</a>
|
<a class="btn btn-info btn-block mb-3" href="{{ object.web_get_admin_url }}">Admin</a>
|
||||||
<!-- end admin button -->
|
<!-- end admin button -->
|
||||||
<hr />
|
<hr />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
<div class="col-lg-3 col-sm-12">
|
<div class="col-lg-3 col-sm-12">
|
||||||
{% if user.is_staff %}
|
{% if user.is_staff %}
|
||||||
<!-- admin button -->
|
<!-- admin button -->
|
||||||
<a class="btn btn-info btn-block mb-3" href="/admin/help/helpentry/add/">Create New</a>
|
<a class="btn btn-info btn-block mb-3" href="/admin/help/helpentry/add/">Admin</a>
|
||||||
<!-- end admin button -->
|
<!-- end admin button -->
|
||||||
<hr />
|
<hr />
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
{% load addclass %}
|
{% load addclass %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card mt-3">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||||
<hr />
|
<hr />
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ from django.conf import settings
|
||||||
from django.utils.text import slugify
|
from django.utils.text import slugify
|
||||||
from django.test import Client, override_settings
|
from django.test import Client, override_settings
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from evennia.utils import class_from_module
|
||||||
from evennia.utils.test_resources import EvenniaTest
|
from evennia.utils.test_resources import EvenniaTest
|
||||||
|
|
||||||
class EvenniaWebTest(EvenniaTest):
|
class EvenniaWebTest(EvenniaTest):
|
||||||
|
|
@ -13,6 +14,7 @@ class EvenniaWebTest(EvenniaTest):
|
||||||
exit_typeclass = settings.BASE_EXIT_TYPECLASS
|
exit_typeclass = settings.BASE_EXIT_TYPECLASS
|
||||||
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
||||||
script_typeclass = settings.BASE_SCRIPT_TYPECLASS
|
script_typeclass = settings.BASE_SCRIPT_TYPECLASS
|
||||||
|
channel_typeclass = settings.BASE_CHANNEL_TYPECLASS
|
||||||
|
|
||||||
# Default named url
|
# Default named url
|
||||||
url_name = 'index'
|
url_name = 'index'
|
||||||
|
|
@ -92,6 +94,25 @@ class PasswordResetTest(EvenniaWebTest):
|
||||||
class WebclientTest(EvenniaWebTest):
|
class WebclientTest(EvenniaWebTest):
|
||||||
url_name = 'webclient:index'
|
url_name = 'webclient:index'
|
||||||
|
|
||||||
|
class ChannelListTest(EvenniaWebTest):
|
||||||
|
url_name = 'channels'
|
||||||
|
|
||||||
|
class ChannelDetailTest(EvenniaWebTest):
|
||||||
|
url_name = 'channel-detail'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ChannelDetailTest, self).setUp()
|
||||||
|
|
||||||
|
klass = class_from_module(self.channel_typeclass)
|
||||||
|
|
||||||
|
# Create a channel
|
||||||
|
klass.create('demo')
|
||||||
|
|
||||||
|
def get_kwargs(self):
|
||||||
|
return {
|
||||||
|
'slug': slugify('demo')
|
||||||
|
}
|
||||||
|
|
||||||
class CharacterCreateView(EvenniaWebTest):
|
class CharacterCreateView(EvenniaWebTest):
|
||||||
url_name = 'character-create'
|
url_name = 'character-create'
|
||||||
unauthenticated_response = 302
|
unauthenticated_response = 302
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ urlpatterns = [
|
||||||
url(r'^help/$', website_views.HelpListView.as_view(), name="help"),
|
url(r'^help/$', website_views.HelpListView.as_view(), name="help"),
|
||||||
url(r'^help/(?P<category>[\w\d\-]+)/(?P<topic>[\w\d\-]+)/$', website_views.HelpDetailView.as_view(), name="help-entry-detail"),
|
url(r'^help/(?P<category>[\w\d\-]+)/(?P<topic>[\w\d\-]+)/$', website_views.HelpDetailView.as_view(), name="help-entry-detail"),
|
||||||
|
|
||||||
|
# Channels
|
||||||
|
url(r'^channels/$', website_views.ChannelListView.as_view(), name="channels"),
|
||||||
|
url(r'^channels/(?P<slug>[\w\d\-]+)/$', website_views.ChannelDetailView.as_view(), name="channel-detail"),
|
||||||
|
|
||||||
# Character management
|
# Character management
|
||||||
url(r'^characters/$', website_views.CharacterListView.as_view(), name="characters"),
|
url(r'^characters/$', website_views.CharacterListView.as_view(), name="characters"),
|
||||||
url(r'^characters/create/$', website_views.CharacterCreateView.as_view(), name="character-create"),
|
url(r'^characters/create/$', website_views.CharacterCreateView.as_view(), name="character-create"),
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ from evennia.help.models import HelpEntry
|
||||||
from evennia.objects.models import ObjectDB
|
from evennia.objects.models import ObjectDB
|
||||||
from evennia.accounts.models import AccountDB
|
from evennia.accounts.models import AccountDB
|
||||||
from evennia.utils import class_from_module, logger
|
from evennia.utils import class_from_module, logger
|
||||||
|
from evennia.utils.logger import tail_log_file
|
||||||
from evennia.web.website.forms import *
|
from evennia.web.website.forms import *
|
||||||
|
|
||||||
from django.contrib.auth import login
|
from django.contrib.auth import login
|
||||||
|
|
@ -767,6 +768,168 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
||||||
messages.error(self.request, "Your character could not be created.")
|
messages.error(self.request, "Your character could not be created.")
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Channel views
|
||||||
|
#
|
||||||
|
|
||||||
|
class ChannelMixin(object):
|
||||||
|
"""
|
||||||
|
This is a "mixin", a modifier of sorts.
|
||||||
|
|
||||||
|
Any view class with this in its inheritance list will be modified to work
|
||||||
|
with HelpEntry objects instead of generic Objects or otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
|
model = class_from_module(settings.BASE_CHANNEL_TYPECLASS)
|
||||||
|
|
||||||
|
# -- Evennia constructs --
|
||||||
|
page_title = 'Channels'
|
||||||
|
|
||||||
|
# What lock type to check for the requesting user, authenticated or not.
|
||||||
|
# https://github.com/evennia/evennia/wiki/Locks#valid-access_types
|
||||||
|
access_type = 'listen'
|
||||||
|
|
||||||
|
def get_queryset(self):
|
||||||
|
"""
|
||||||
|
Django hook; here we want to return a list of only those Channels
|
||||||
|
and other documentation that the current user is allowed to see.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
queryset (QuerySet): List of Channels available to the user.
|
||||||
|
|
||||||
|
"""
|
||||||
|
account = self.request.user
|
||||||
|
|
||||||
|
# Get list of all Channels
|
||||||
|
channels = self.model.objects.all().iterator()
|
||||||
|
|
||||||
|
# Now figure out which ones the current user is allowed to see
|
||||||
|
bucket = [channel.id for channel in channels if channel.access(account, 'listen')]
|
||||||
|
|
||||||
|
# Re-query and set a sorted list
|
||||||
|
filtered = self.model.objects.filter(
|
||||||
|
id__in=bucket
|
||||||
|
).order_by(
|
||||||
|
Lower('db_key')
|
||||||
|
)
|
||||||
|
|
||||||
|
return filtered
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelListView(ChannelMixin, ListView):
|
||||||
|
"""
|
||||||
|
Returns a list of channels that can be viewed by a user, authenticated
|
||||||
|
or not.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
|
paginate_by = 100
|
||||||
|
template_name = 'website/channel_list.html'
|
||||||
|
|
||||||
|
# -- Evennia constructs --
|
||||||
|
page_title = "Channel Index"
|
||||||
|
|
||||||
|
max_popular = 10
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Django hook; we override it to calculate the most popular channels.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
context (dict): Django context object
|
||||||
|
|
||||||
|
"""
|
||||||
|
context = super(ChannelListView, self).get_context_data(**kwargs)
|
||||||
|
|
||||||
|
# Calculate which channels are most popular
|
||||||
|
context['most_popular'] = sorted(
|
||||||
|
list(self.get_queryset()),
|
||||||
|
key=lambda channel: len(channel.subscriptions.all()),
|
||||||
|
reverse=True)[:self.max_popular]
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelDetailView(ChannelMixin, ObjectDetailView):
|
||||||
|
"""
|
||||||
|
Returns the log entries for a given channel.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# -- Django constructs --
|
||||||
|
template_name = 'website/channel_detail.html'
|
||||||
|
|
||||||
|
# -- Evennia constructs --
|
||||||
|
# What attributes of the object you wish to display on the page. Model-level
|
||||||
|
# attributes will take precedence over identically-named db.attributes!
|
||||||
|
# The order you specify here will be followed.
|
||||||
|
attributes = ['name']
|
||||||
|
|
||||||
|
# How many log entries to read and display.
|
||||||
|
max_num_lines = 10000
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Django hook; before we can display the channel logs, we need to recall
|
||||||
|
the logfile and read its lines.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
context (dict): Django context object
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get the parent context object, necessary first step
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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
|
||||||
|
time, msg = log.split(' [-] ')
|
||||||
|
time_key = time.split(':')[0]
|
||||||
|
|
||||||
|
bucket.append({
|
||||||
|
'key': time_key,
|
||||||
|
'timestamp': time,
|
||||||
|
'message': msg
|
||||||
|
})
|
||||||
|
|
||||||
|
# Add the processed entries to the context
|
||||||
|
context['object_list'] = bucket
|
||||||
|
|
||||||
|
# Get a list of unique timestamps by hour and sort them
|
||||||
|
context['object_filters'] = sorted(set([x['key'] for x in bucket]))
|
||||||
|
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
def get_object(self, queryset=None):
|
||||||
|
"""
|
||||||
|
Override of Django hook that retrieves an object by slugified channel
|
||||||
|
name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
channel (Channel): Channel requested in the URL.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Get the queryset for the help entries the user can access
|
||||||
|
if not queryset:
|
||||||
|
queryset = self.get_queryset()
|
||||||
|
|
||||||
|
# Find the object in the queryset
|
||||||
|
channel = slugify(self.kwargs.get('slug', ''))
|
||||||
|
obj = next((x for x in queryset if slugify(x.db_key) == channel), 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" %
|
||||||
|
{'verbose_name': queryset.model._meta.verbose_name})
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Help views
|
# Help views
|
||||||
#
|
#
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue