diff --git a/evennia/web/website/templates/website/_menu.html b/evennia/web/website/templates/website/_menu.html
index 9f95ebb67f..888a63bf3f 100644
--- a/evennia/web/website/templates/website/_menu.html
+++ b/evennia/web/website/templates/website/_menu.html
@@ -25,15 +25,23 @@ folder and edit it to add/remove links to the menu.
Home
+
About
Documentation
+
+
+
+ Characters
Channels
Help
+
+
{% if webclient_enabled %}
Play Online
{% endif %}
+
{% if user.is_staff %}
Admin
{% endif %}
diff --git a/evennia/web/website/templates/website/character_list.html b/evennia/web/website/templates/website/character_list.html
new file mode 100644
index 0000000000..4e7601d49a
--- /dev/null
+++ b/evennia/web/website/templates/website/character_list.html
@@ -0,0 +1,27 @@
+{% extends "base.html" %}
+
+{% block titleblock %}
+{{ view.page_title }}
+{% endblock %}
+
+{% block content %}
+
+{% load addclass %}
+
+
+
+
+
{{ view.page_title }}
+
+
+
+ {% for object in object_list %}
+ - {{ object }}
+ {% endfor %}
+
+
+
+
+
+
+{% endblock %}
diff --git a/evennia/web/website/tests.py b/evennia/web/website/tests.py
index 4175dc22fc..ac77c43607 100644
--- a/evennia/web/website/tests.py
+++ b/evennia/web/website/tests.py
@@ -191,6 +191,10 @@ class CharacterPuppetView(EvenniaWebTest):
response = self.client.get(reverse(self.url_name, kwargs=kwargs), follow=True)
self.assertTrue(response.status_code >= 400, "Invalid access should return a 4xx code-- either obj not found or permission denied! (Returned %s)" % response.status_code)
+class CharacterListView(EvenniaWebTest):
+ url_name = 'characters'
+ unauthenticated_response = 302
+
class CharacterManageView(EvenniaWebTest):
url_name = 'character-manage'
unauthenticated_response = 302
diff --git a/evennia/web/website/urls.py b/evennia/web/website/urls.py
index 6acff85abe..c89b7d48aa 100644
--- a/evennia/web/website/urls.py
+++ b/evennia/web/website/urls.py
@@ -25,6 +25,7 @@ urlpatterns = [
url(r'^channels/(?P[\w\d\-]+)/$', website_views.ChannelDetailView.as_view(), name="channel-detail"),
# Character management
+ 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/manage/$', website_views.CharacterManageView.as_view(), name="character-manage"),
url(r'^characters/detail/(?P[\w\d\-]+)/(?P[0-9]+)/$', website_views.CharacterDetailView.as_view(), name="character-detail"),
diff --git a/evennia/web/website/views.py b/evennia/web/website/views.py
index 4f21638003..4382793256 100644
--- a/evennia/web/website/views.py
+++ b/evennia/web/website/views.py
@@ -577,6 +577,42 @@ class CharacterMixin(TypeclassMixin):
return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
+class CharacterListView(LoginRequiredMixin, CharacterMixin, ListView):
+ """
+ This view provides a mechanism by which a logged-in player can view a list
+ of all other characters.
+
+ This view requires authentication by default as a nominal effort to prevent
+ human stalkers and automated bots/scrapers from harvesting data on your users.
+
+ """
+ # -- Django constructs --
+ template_name = 'website/character_list.html'
+ paginate_by = 100
+
+ # -- Evennia constructs --
+ page_title = 'Character List'
+ access_type = 'view'
+
+ def get_queryset(self):
+ """
+ This method will override the Django get_queryset method to return a
+ list of all characters (filtered/sorted) instead of just those limited
+ to the account.
+
+ Returns:
+ queryset (QuerySet): Django queryset for use in the given view.
+
+ """
+ account = self.request.user
+
+ # Return a queryset consisting of characters the user is allowed to
+ # see.
+ ids = [obj.id for obj in self.typeclass.objects.all() if obj.access(account, self.access_type)]
+
+ return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
+
+
class CharacterPuppetView(LoginRequiredMixin, CharacterMixin, RedirectView, ObjectDetailView):
"""
This view provides a mechanism by which a logged-in player can "puppet" one
@@ -654,6 +690,24 @@ class CharacterDetailView(CharacterMixin, ObjectDetailView):
# -- Evennia constructs --
# What attributes to display for this object
attributes = ['name', 'desc']
+ access_type = 'view'
+
+ def get_queryset(self):
+ """
+ This method will override the Django get_queryset method to return a
+ list of all characters the user may access.
+
+ Returns:
+ queryset (QuerySet): Django queryset for use in the given view.
+
+ """
+ account = self.request.user
+
+ # Return a queryset consisting of characters the user is allowed to
+ # see.
+ ids = [obj.id for obj in self.typeclass.objects.all() if obj.access(account, self.access_type)]
+
+ return self.typeclass.objects.filter(id__in=ids).order_by(Lower('db_key'))
class CharacterDeleteView(CharacterMixin, ObjectDeleteView):
@@ -718,7 +772,7 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
# Channel views
#
-class ChannelMixin(object):
+class ChannelMixin(TypeclassMixin):
"""
This is a "mixin", a modifier of sorts.
@@ -748,13 +802,13 @@ class ChannelMixin(object):
account = self.request.user
# Get list of all Channels
- channels = self.model.objects.all().iterator()
+ channels = self.typeclass.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(
+ filtered = self.typeclass.objects.filter(
id__in=bucket
).order_by(
Lower('db_key')
@@ -880,7 +934,7 @@ class ChannelDetailView(ChannelMixin, ObjectDetailView):
# Help views
#
-class HelpMixin(object):
+class HelpMixin(TypeclassMixin):
"""
This is a "mixin", a modifier of sorts.
@@ -906,13 +960,13 @@ class HelpMixin(object):
account = self.request.user
# Get list of all HelpEntries
- entries = self.model.objects.all().iterator()
+ entries = self.typeclass.objects.all().iterator()
# Now figure out which ones the current user is allowed to see
bucket = [entry.id for entry in entries if entry.access(account, 'view')]
# Re-query and set a sorted list
- filtered = self.model.objects.filter(
+ filtered = self.typeclass.objects.filter(
id__in=bucket
).order_by(
Lower('db_key')