mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Move and split views
This commit is contained in:
parent
dac2be3074
commit
273cc31146
25 changed files with 1299 additions and 1196 deletions
|
|
@ -99,6 +99,11 @@ class FileHelpEntry:
|
|||
"text": self.entrytext,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return self.key
|
||||
|
||||
def __repr__(self):
|
||||
return f"<FileHelpEntry {self.key}>"
|
||||
|
||||
|
||||
class FileHelpStorageHandler:
|
||||
|
|
|
|||
|
|
@ -108,6 +108,8 @@ class HelpEntry(SharedMemoryModel):
|
|||
# HelpEntry main class methods
|
||||
#
|
||||
#
|
||||
def __str__(self):
|
||||
return str(self.key)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<HelpEntry {self.key}>"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
"""
|
||||
This sub-package holds the web presence of Evennia, using normal
|
||||
Django to relate the database contents to web pages. Also the basic
|
||||
webclient and the website are defined in here (the webserver itself is
|
||||
found under the `server` package).
|
||||
This sub-package holds the web presence of Evennia, using normal Django to
|
||||
relate the database contents to web pages.
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -27,9 +27,9 @@ folder and edit it to add/remove links to the menu.
|
|||
</li>
|
||||
<!-- evennia documentation -->
|
||||
<li>
|
||||
<a class="nav-link" href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a>
|
||||
<a class="nav-link" href="https://www.evennia.com/docs/latest/Evennia-Introduction.html">About</a>
|
||||
</li>
|
||||
<li><a class="nav-link" href="https://github.com/evennia/evennia/wiki">Documentation</a></li>
|
||||
<li><a class="nav-link" href="https://www.evennia.com/docs/latest">Documentation</a></li>
|
||||
<!-- end evennia documentation -->
|
||||
|
||||
<!-- game views -->
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@
|
|||
<footer class="footer">
|
||||
{% block footer %}
|
||||
<div class="container">
|
||||
<span class="text-white">Powered by <a class="text-white font-weight-bold" href="http://evennia.com">Evennia.</a></span>
|
||||
<span class="text-white">Powered by <a class="text-white font-weight-bold" href="https://evennia.com">Evennia.</a></span>
|
||||
</div>
|
||||
{% endblock %}
|
||||
</footer>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
<h1 class="card-title">{{ view.page_title }} ({{ object|title }})</h1>
|
||||
<hr />
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}">Compendium</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}">Help Index</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}#{{ object.db_help_category }}">{{ object.db_help_category|title }}</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ object.db_key|title }}</li>
|
||||
</ol>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}">Compendium</a></li>
|
||||
<li class="breadcrumb-item"><a href="{% url 'help' %}">Help Index</a></li>
|
||||
</ol>
|
||||
<hr />
|
||||
<div class="row">
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<hr />
|
||||
|
||||
<p class="lead">
|
||||
Welcome to your new installation of <a href="http://evennia.com">Evennia</a>, your friendly
|
||||
Welcome to your new installation of <a href="https://evennia.com">Evennia</a>, your friendly
|
||||
neighborhood next-generation MUD development system and server.
|
||||
</p>
|
||||
<p>
|
||||
|
|
@ -32,12 +32,15 @@
|
|||
{% endif %}
|
||||
<p>
|
||||
For more info, take your time to
|
||||
peruse our extensive online <a href="https://github.com/evennia/evennia/wiki">documentation</a>.
|
||||
peruse our extensive online <a href="https://evennia.com/docs/latest">documentation</a>.
|
||||
</p>
|
||||
<p>
|
||||
Should you have any questions, concerns, bug reports, or
|
||||
if you want to help out, don't hesitate to join the Evennia community to make your voice heard! Drop a mail to the
|
||||
<a href="https://groups.google.com/forum/#!forum/evennia">mailing list</a> or to come say hi in the <a href="http://webchat.freenode.net/?channels=evennia">developer chatroom</a>.
|
||||
if you want to help out, don't hesitate to join the Evennia community
|
||||
to make your voice heard! Drop a mail to the <a
|
||||
href="https://github.com/evennia/evennia/discussions">mailing
|
||||
list</a> or to come say hi in the <a
|
||||
href="http://webchat.freenode.net/?channels=evennia">developer chatroom</a>.
|
||||
</p>
|
||||
<p>
|
||||
If you find bugs, please report them to our <a href="https://github.com/evennia/evennia/issues">Issue tracker</a>.
|
||||
|
|
@ -100,7 +103,7 @@
|
|||
<h4 class="card-header text-center">Evennia</h4>
|
||||
|
||||
<div class="card-body">
|
||||
<p><a href="http://evennia.com">Evennia</a> is an open-source MUD server built in
|
||||
<p><a href="https://evennia.com">Evennia</a> is an open-source MUD server built in
|
||||
<a href="http://python.org">Python</a>, on top of the
|
||||
<a href="http://twistedmatrix.com">Twisted</a> and
|
||||
<a href="http://djangoproject.com">Django</a> frameworks. This
|
||||
|
|
|
|||
10
evennia/web/templatetags/README.md
Normal file
10
evennia/web/templatetags/README.md
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
# Template tags
|
||||
|
||||
Template-tags are python code safely callable from inside the html template.
|
||||
|
||||
In a template they are used as
|
||||
|
||||
{% mytagname args %s}
|
||||
|
||||
See the Django documentation on template tags and a lot of tags already coming
|
||||
with Django. Placing code here allows for custom tags.
|
||||
|
|
@ -28,7 +28,7 @@ class EvenniaForm(forms.Form):
|
|||
|
||||
"""
|
||||
# Call parent function
|
||||
cleaned = super(EvenniaForm, self).clean()
|
||||
cleaned = super().clean()
|
||||
|
||||
# Escape all values provided by user
|
||||
cleaned = {k: escape(v) for k, v in cleaned.items()}
|
||||
|
|
|
|||
|
|
@ -6,69 +6,57 @@ from django.conf import settings
|
|||
from django.contrib import admin
|
||||
from django.conf.urls import url, include
|
||||
from django import views as django_views
|
||||
from . import views
|
||||
from .views import (index, errors, accounts, help as helpviews, channels,
|
||||
characters, admin as adminviews)
|
||||
|
||||
urlpatterns = [
|
||||
url(r"^$", views.EvenniaIndexView.as_view(), name="index"),
|
||||
url(r"^tbi/", views.to_be_implemented, name="to_be_implemented"),
|
||||
# website front page
|
||||
url(r"^$", index.EvenniaIndexView.as_view(), name="index"),
|
||||
|
||||
# errors
|
||||
url(r"^tbi/", errors.to_be_implemented, name="to_be_implemented"),
|
||||
|
||||
# User Authentication (makes login/logout url names available)
|
||||
url(r"^auth/register", views.AccountCreateView.as_view(), name="register"),
|
||||
url(r"^auth/register", accounts.AccountCreateView.as_view(), name="register"),
|
||||
url(r"^auth/", include("django.contrib.auth.urls")),
|
||||
|
||||
# Help Topics
|
||||
url(r"^help/$", views.HelpListView.as_view(), name="help"),
|
||||
url(
|
||||
r"^help/(?P<category>[\w\d\-]+)/(?P<topic>[\w\d\-]+)/$",
|
||||
views.HelpDetailView.as_view(),
|
||||
name="help-entry-detail",
|
||||
),
|
||||
url(r"^help/$", helpviews.HelpListView.as_view(), name="help"),
|
||||
url(r"^help/(?P<category>[\w\d\-]+)/(?P<topic>[\w\d\-]+)/$",
|
||||
helpviews.HelpDetailView.as_view(),
|
||||
name="help-entry-detail"),
|
||||
|
||||
# Channels
|
||||
url(r"^channels/$", views.ChannelListView.as_view(), name="channels"),
|
||||
url(
|
||||
r"^channels/(?P<slug>[\w\d\-]+)/$",
|
||||
views.ChannelDetailView.as_view(),
|
||||
name="channel-detail",
|
||||
),
|
||||
url(r"^channels/$", channels.ChannelListView.as_view(), name="channels"),
|
||||
url(r"^channels/(?P<slug>[\w\d\-]+)/$",
|
||||
channels.ChannelDetailView.as_view(),
|
||||
name="channel-detail"),
|
||||
|
||||
# Character management
|
||||
url(r"^characters/$", views.CharacterListView.as_view(), name="characters"),
|
||||
url(
|
||||
r"^characters/create/$",
|
||||
views.CharacterCreateView.as_view(),
|
||||
name="character-create",
|
||||
),
|
||||
url(
|
||||
r"^characters/manage/$",
|
||||
views.CharacterManageView.as_view(),
|
||||
name="character-manage",
|
||||
),
|
||||
url(
|
||||
r"^characters/detail/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$",
|
||||
views.CharacterDetailView.as_view(),
|
||||
name="character-detail",
|
||||
),
|
||||
url(
|
||||
r"^characters/puppet/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$",
|
||||
views.CharacterPuppetView.as_view(),
|
||||
name="character-puppet",
|
||||
),
|
||||
url(
|
||||
r"^characters/update/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$",
|
||||
views.CharacterUpdateView.as_view(),
|
||||
name="character-update",
|
||||
),
|
||||
url(
|
||||
r"^characters/delete/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$",
|
||||
views.CharacterDeleteView.as_view(),
|
||||
name="character-delete",
|
||||
),
|
||||
url(r"^characters/$", characters.CharacterListView.as_view(), name="characters"),
|
||||
url(r"^characters/create/$",
|
||||
characters.CharacterCreateView.as_view(),
|
||||
name="character-create"),
|
||||
url(r"^characters/manage/$",
|
||||
characters.CharacterManageView.as_view(),
|
||||
name="character-manage"),
|
||||
url(r"^characters/detail/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$",
|
||||
characters.CharacterDetailView.as_view(),
|
||||
name="character-detail"),
|
||||
url(r"^characters/puppet/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$",
|
||||
characters.CharacterPuppetView.as_view(),
|
||||
name="character-puppet"),
|
||||
url(r"^characters/update/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$",
|
||||
characters.CharacterUpdateView.as_view(),
|
||||
name="character-update"),
|
||||
url(r"^characters/delete/(?P<slug>[\w\d\-]+)/(?P<pk>[0-9]+)/$",
|
||||
characters.CharacterDeleteView.as_view(),
|
||||
name="character-delete"),
|
||||
|
||||
# Django original admin page. Make this URL is always available, whether
|
||||
# we've chosen to use Evennia's custom admin or not.
|
||||
|
||||
url(r"django_admin/", views.admin_wrapper, name="django_admin"),
|
||||
url(r"django_admin/", adminviews.admin_wrapper, name="django_admin"),
|
||||
|
||||
# Admin docs
|
||||
url(r"^admin/doc/", include("django.contrib.admindocs.urls")),
|
||||
|
|
@ -77,7 +65,7 @@ urlpatterns = [
|
|||
if settings.EVENNIA_ADMIN:
|
||||
urlpatterns += [
|
||||
# Our override for the admin.
|
||||
url("^admin/$", views.evennia_admin, name="evennia_admin"),
|
||||
url("^admin/$", adminviews.evennia_admin, name="evennia_admin"),
|
||||
# Makes sure that other admin pages get loaded.
|
||||
url(r"^admin/", admin.site.urls),
|
||||
]
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
4
evennia/web/website/views/__init__.py
Normal file
4
evennia/web/website/views/__init__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
"""
|
||||
Website views.
|
||||
|
||||
"""
|
||||
76
evennia/web/website/views/accounts.py
Normal file
76
evennia/web/website/views/accounts.py
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
"""
|
||||
Views for managing accounts.
|
||||
|
||||
"""
|
||||
|
||||
|
||||
from evennia.utils import class_from_module
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.urls import reverse_lazy
|
||||
from django.http import HttpResponseRedirect
|
||||
|
||||
from .mixins import EvenniaCreateView, TypeclassMixin
|
||||
from evennia.web.website import forms
|
||||
|
||||
|
||||
class AccountMixin(TypeclassMixin):
|
||||
"""
|
||||
This is used to grant abilities to classes it is added to.
|
||||
|
||||
Any view class with this in its inheritance list will be modified to work
|
||||
with Account objects instead of generic Objects or otherwise.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS,
|
||||
fallback=settings.FALLBACK_ACCOUNT_TYPECLASS)
|
||||
form_class = forms.AccountForm
|
||||
|
||||
|
||||
class AccountCreateView(AccountMixin, EvenniaCreateView):
|
||||
"""
|
||||
Account creation view.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
template_name = "website/registration/register.html"
|
||||
success_url = reverse_lazy("login")
|
||||
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Django hook, modified for Evennia.
|
||||
|
||||
This hook is called after a valid form is submitted.
|
||||
|
||||
When an account creation form is submitted and the data is deemed valid,
|
||||
proceeds with creating the Account object.
|
||||
|
||||
"""
|
||||
# Get values provided
|
||||
username = form.cleaned_data["username"]
|
||||
password = form.cleaned_data["password1"]
|
||||
email = form.cleaned_data.get("email", "")
|
||||
|
||||
# Create account
|
||||
account, errs = self.typeclass.create(username=username, password=password, email=email)
|
||||
|
||||
# If unsuccessful, display error messages to user
|
||||
if not account:
|
||||
[messages.error(self.request, err) for err in errs]
|
||||
|
||||
# Call the Django "form failure" hook
|
||||
return self.form_invalid(form)
|
||||
|
||||
# Inform user of success
|
||||
messages.success(
|
||||
self.request,
|
||||
"Your account '%s' was successfully created! "
|
||||
"You may log in using it now." % account.name,
|
||||
)
|
||||
|
||||
# Redirect the user to the login page
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
24
evennia/web/website/views/admin.py
Normal file
24
evennia/web/website/views/admin.py
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
"""
|
||||
Admin views.
|
||||
|
||||
"""
|
||||
|
||||
from django.contrib.admin.sites import site
|
||||
from evennia.accounts.models import AccountDB
|
||||
from django.shortcuts import render
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
|
||||
|
||||
@staff_member_required
|
||||
def evennia_admin(request):
|
||||
"""
|
||||
Helpful Evennia-specific admin page.
|
||||
"""
|
||||
return render(request, "evennia_admin.html", {"accountdb": AccountDB})
|
||||
|
||||
|
||||
def admin_wrapper(request):
|
||||
"""
|
||||
Wrapper that allows us to properly use the base Django admin site, if needed.
|
||||
"""
|
||||
return staff_member_required(site.index)(request)
|
||||
175
evennia/web/website/views/channels.py
Normal file
175
evennia/web/website/views/channels.py
Normal file
|
|
@ -0,0 +1,175 @@
|
|||
"""
|
||||
Views for managing channels.
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.views.generic import ListView
|
||||
from django.utils.text import slugify
|
||||
from django.db.models.functions import Lower
|
||||
from django.http import HttpResponseBadRequest
|
||||
|
||||
from evennia.utils.logger import tail_log_file
|
||||
from evennia.utils import class_from_module
|
||||
from .mixins import TypeclassMixin
|
||||
from .objects import ObjectDetailView
|
||||
|
||||
|
||||
class ChannelMixin(TypeclassMixin):
|
||||
"""
|
||||
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,
|
||||
fallback=settings.FALLBACK_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.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.typeclass.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().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().get_context_data(**kwargs)
|
||||
channel = self.object
|
||||
|
||||
# Get the filename this Channel is recording to
|
||||
filename = channel.get_log_filename()
|
||||
|
||||
# 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(
|
||||
"No %(verbose_name)s found matching the query"
|
||||
% {"verbose_name": queryset.model._meta.verbose_name}
|
||||
)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
254
evennia/web/website/views/characters.py
Normal file
254
evennia/web/website/views/characters.py
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
"""
|
||||
Views for manipulating Characters (children of Objects often used for
|
||||
puppeting).
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.urls import reverse_lazy
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.db.models.functions import Lower
|
||||
from django.views.generic.base import RedirectView
|
||||
from django.views.generic import ListView
|
||||
from evennia.utils import class_from_module
|
||||
from .mixins import TypeclassMixin
|
||||
from .objects import ObjectDetailView, ObjectDeleteView, ObjectUpdateView, ObjectCreateView
|
||||
from evennia.web.website import forms
|
||||
|
||||
|
||||
class CharacterMixin(TypeclassMixin):
|
||||
"""
|
||||
This is a "mixin", a modifier of sorts.
|
||||
|
||||
Any view class with this in its inheritance list will be modified to work
|
||||
with Character objects instead of generic Objects or otherwise.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
model = class_from_module(settings.BASE_CHARACTER_TYPECLASS,
|
||||
fallback=settings.FALLBACK_CHARACTER_TYPECLASS)
|
||||
form_class = forms.CharacterForm
|
||||
success_url = reverse_lazy("character-manage")
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
This method will override the Django get_queryset method to only
|
||||
return a list of characters associated with the current authenticated
|
||||
user.
|
||||
|
||||
Returns:
|
||||
queryset (QuerySet): Django queryset for use in the given view.
|
||||
|
||||
"""
|
||||
# Get IDs of characters owned by account
|
||||
account = self.request.user
|
||||
ids = [getattr(x, "id") for x in account.characters if x]
|
||||
|
||||
# Return a queryset consisting of those characters
|
||||
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
|
||||
of their characters within the context of the website.
|
||||
|
||||
It also ensures that any user attempting to puppet something is logged in,
|
||||
and that their intended puppet is one that they own.
|
||||
|
||||
"""
|
||||
|
||||
def get_redirect_url(self, *args, **kwargs):
|
||||
"""
|
||||
Django hook.
|
||||
|
||||
This view returns the URL to which the user should be redirected after
|
||||
a passed or failed puppet attempt.
|
||||
|
||||
Returns:
|
||||
url (str): Path to post-puppet destination.
|
||||
|
||||
"""
|
||||
# Get the requested character, if it belongs to the authenticated user
|
||||
char = self.get_object()
|
||||
|
||||
# Get the page the user came from
|
||||
next_page = self.request.GET.get("next", self.success_url)
|
||||
|
||||
if char:
|
||||
# If the account owns the char, store the ID of the char in the
|
||||
# Django request's session (different from Evennia session!).
|
||||
# We do this because characters don't serialize well.
|
||||
self.request.session["puppet"] = int(char.pk)
|
||||
messages.success(self.request, "You become '%s'!" % char)
|
||||
else:
|
||||
# If the puppeting failed, clear out the cached puppet value
|
||||
self.request.session["puppet"] = None
|
||||
messages.error(self.request, "You cannot become '%s'." % char)
|
||||
|
||||
return next_page
|
||||
|
||||
|
||||
class CharacterManageView(LoginRequiredMixin, CharacterMixin, ListView):
|
||||
"""
|
||||
This view provides a mechanism by which a logged-in player can browse,
|
||||
edit, or delete their own characters.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
paginate_by = 10
|
||||
template_name = "website/character_manage_list.html"
|
||||
|
||||
# -- Evennia constructs --
|
||||
page_title = "Manage Characters"
|
||||
|
||||
|
||||
class CharacterUpdateView(CharacterMixin, ObjectUpdateView):
|
||||
"""
|
||||
This view provides a mechanism by which a logged-in player (enforced by
|
||||
ObjectUpdateView) can edit the attributes of a character they own.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
form_class = forms.CharacterUpdateForm
|
||||
template_name = "website/character_form.html"
|
||||
|
||||
|
||||
class CharacterDetailView(CharacterMixin, ObjectDetailView):
|
||||
"""
|
||||
This view provides a mechanism by which a user can view the attributes of
|
||||
a character, owned by them or not.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
template_name = "website/object_detail.html"
|
||||
|
||||
# -- 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):
|
||||
"""
|
||||
This view provides a mechanism by which a logged-in player (enforced by
|
||||
ObjectDeleteView) can delete a character they own.
|
||||
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
||||
"""
|
||||
This view provides a mechanism by which a logged-in player (enforced by
|
||||
ObjectCreateView) can create a new character.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
template_name = "website/character_form.html"
|
||||
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Django hook, modified for Evennia.
|
||||
|
||||
This hook is called after a valid form is submitted.
|
||||
|
||||
When an character creation form is submitted and the data is deemed valid,
|
||||
proceeds with creating the Character object.
|
||||
|
||||
"""
|
||||
# Get account object creating the character
|
||||
account = self.request.user
|
||||
character = None
|
||||
|
||||
# Get attributes from the form
|
||||
self.attributes = {k: form.cleaned_data[k] for k in form.cleaned_data.keys()}
|
||||
charname = self.attributes.pop("db_key")
|
||||
description = self.attributes.pop("desc")
|
||||
# Create a character
|
||||
character, errors = self.typeclass.create(charname, account, description=description)
|
||||
|
||||
if errors:
|
||||
# Echo error messages to the user
|
||||
[messages.error(self.request, x) for x in errors]
|
||||
|
||||
if character:
|
||||
# Assign attributes from form
|
||||
for key, value in self.attributes.items():
|
||||
setattr(character.db, key, value)
|
||||
|
||||
# Return the user to the character management page, unless overridden
|
||||
messages.success(self.request, "Your character '%s' was created!" % character.name)
|
||||
return HttpResponseRedirect(self.success_url)
|
||||
|
||||
else:
|
||||
# Call the Django "form failed" hook
|
||||
messages.error(self.request, "Your character could not be created.")
|
||||
return self.form_invalid(form)
|
||||
|
||||
16
evennia/web/website/views/errors.py
Normal file
16
evennia/web/website/views/errors.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
"""
|
||||
Error views.
|
||||
|
||||
"""
|
||||
|
||||
from django.shortcuts import render
|
||||
|
||||
def to_be_implemented(request):
|
||||
"""
|
||||
A notice letting the user know that this particular feature hasn't been
|
||||
implemented yet.
|
||||
"""
|
||||
|
||||
pagevars = {"page_title": "To Be Implemented..."}
|
||||
|
||||
return render(request, "tbi.html", pagevars)
|
||||
162
evennia/web/website/views/help.py
Normal file
162
evennia/web/website/views/help.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
"""
|
||||
Views to manipulate help entries.
|
||||
|
||||
"""
|
||||
|
||||
from django.utils.text import slugify
|
||||
from django.views.generic import ListView
|
||||
from django.http import HttpResponseBadRequest
|
||||
from django.db.models.functions import Lower
|
||||
from evennia.help.models import HelpEntry
|
||||
from .mixins import TypeclassMixin, EvenniaDetailView
|
||||
|
||||
|
||||
class HelpMixin(TypeclassMixin):
|
||||
"""
|
||||
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 = HelpEntry
|
||||
|
||||
# -- Evennia constructs --
|
||||
page_title = "Help"
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Django hook; here we want to return a list of only those HelpEntries
|
||||
and other documentation that the current user is allowed to see.
|
||||
|
||||
Returns:
|
||||
queryset (QuerySet): List of Help entries available to the user.
|
||||
|
||||
"""
|
||||
account = self.request.user
|
||||
|
||||
# Get list of all HelpEntries
|
||||
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.typeclass.objects.filter(id__in=bucket)
|
||||
.order_by(Lower("db_key"))
|
||||
.order_by(Lower("db_help_category"))
|
||||
)
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
class HelpListView(HelpMixin, ListView):
|
||||
"""
|
||||
Returns a list of help entries that can be viewed by a user, authenticated
|
||||
or not.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
paginate_by = 500
|
||||
template_name = "website/help_list.html"
|
||||
|
||||
# -- Evennia constructs --
|
||||
page_title = "Help Index"
|
||||
|
||||
|
||||
class HelpDetailView(HelpMixin, EvenniaDetailView):
|
||||
"""
|
||||
Returns the detail page for a given help entry.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
template_name = "website/help_detail.html"
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Adds navigational data to the template to let browsers go to the next
|
||||
or previous entry in the help list.
|
||||
|
||||
Returns:
|
||||
context (dict): Django context object
|
||||
|
||||
"""
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
# Get the object in question
|
||||
obj = self.get_object()
|
||||
|
||||
# Get queryset and filter out non-related categories
|
||||
queryset = (
|
||||
self.get_queryset()
|
||||
.filter(db_help_category=obj.db_help_category)
|
||||
.order_by(Lower("db_key"))
|
||||
)
|
||||
context["topic_list"] = queryset
|
||||
|
||||
# Find the index position of the given obj in the queryset
|
||||
objs = list(queryset)
|
||||
for i, x in enumerate(objs):
|
||||
if obj is x:
|
||||
break
|
||||
|
||||
# Find the previous and next topics, if either exist
|
||||
try:
|
||||
assert i + 1 <= len(objs) and objs[i + 1] is not obj
|
||||
context["topic_next"] = objs[i + 1]
|
||||
except:
|
||||
context["topic_next"] = None
|
||||
|
||||
try:
|
||||
assert i - 1 >= 0 and objs[i - 1] is not obj
|
||||
context["topic_previous"] = objs[i - 1]
|
||||
except:
|
||||
context["topic_previous"] = None
|
||||
|
||||
# Format the help entry using HTML instead of newlines
|
||||
text = obj.db_entrytext
|
||||
text = text.replace("\r\n\r\n", "\n\n")
|
||||
text = text.replace("\r\n", "\n")
|
||||
text = text.replace("\n", "<br />")
|
||||
context["entry_text"] = text
|
||||
|
||||
return context
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
"""
|
||||
Override of Django hook that retrieves an object by category and topic
|
||||
instead of pk and slug.
|
||||
|
||||
Returns:
|
||||
entry (HelpEntry): HelpEntry 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
|
||||
category = slugify(self.kwargs.get("category", ""))
|
||||
topic = slugify(self.kwargs.get("topic", ""))
|
||||
obj = next(
|
||||
(
|
||||
x
|
||||
for x in queryset
|
||||
if slugify(x.db_help_category) == category and slugify(x.db_key) == topic
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
# Check if this object was requested in a valid manner
|
||||
if not obj:
|
||||
return HttpResponseBadRequest(
|
||||
"No %(verbose_name)s found matching the query"
|
||||
% {"verbose_name": queryset.model._meta.verbose_name}
|
||||
)
|
||||
|
||||
return obj
|
||||
116
evennia/web/website/views/index.py
Normal file
116
evennia/web/website/views/index.py
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
"""
|
||||
The main index page, including the game stats
|
||||
|
||||
"""
|
||||
|
||||
from django.conf import settings
|
||||
from django.views.generic import TemplateView
|
||||
from evennia import SESSION_HANDLER
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.utils import class_from_module
|
||||
|
||||
|
||||
def _gamestats():
|
||||
"""
|
||||
Generate a the gamestat context for the main index page
|
||||
"""
|
||||
|
||||
|
||||
# Some misc. configurable stuff.
|
||||
# TODO: Move this to either SQL or settings.py based configuration.
|
||||
fpage_account_limit = 4
|
||||
|
||||
# A QuerySet of the most recently connected accounts.
|
||||
recent_users = AccountDB.objects.get_recently_connected_accounts()[:fpage_account_limit]
|
||||
nplyrs_conn_recent = len(recent_users) or "none"
|
||||
nplyrs = AccountDB.objects.num_total_accounts() or "none"
|
||||
nplyrs_reg_recent = len(AccountDB.objects.get_recently_created_accounts()) or "none"
|
||||
nsess = SESSION_HANDLER.account_count()
|
||||
# nsess = len(AccountDB.objects.get_connected_accounts()) or "no one"
|
||||
|
||||
nobjs = ObjectDB.objects.count()
|
||||
nobjs = nobjs or 1 # fix zero-div error with empty database
|
||||
Character = class_from_module(settings.BASE_CHARACTER_TYPECLASS,
|
||||
fallback=settings.FALLBACK_CHARACTER_TYPECLASS)
|
||||
nchars = Character.objects.all_family().count()
|
||||
Room = class_from_module(settings.BASE_ROOM_TYPECLASS,
|
||||
fallback=settings.FALLBACK_ROOM_TYPECLASS)
|
||||
nrooms = Room.objects.all_family().count()
|
||||
Exit = class_from_module(settings.BASE_EXIT_TYPECLASS,
|
||||
fallback=settings.FALLBACK_EXIT_TYPECLASS)
|
||||
nexits = Exit.objects.all_family().count()
|
||||
nothers = nobjs - nchars - nrooms - nexits
|
||||
|
||||
pagevars = {
|
||||
"page_title": "Front Page",
|
||||
"accounts_connected_recent": recent_users,
|
||||
"num_accounts_connected": nsess or "no one",
|
||||
"num_accounts_registered": nplyrs or "no",
|
||||
"num_accounts_connected_recent": nplyrs_conn_recent or "no",
|
||||
"num_accounts_registered_recent": nplyrs_reg_recent or "no one",
|
||||
"num_rooms": nrooms or "none",
|
||||
"num_exits": nexits or "no",
|
||||
"num_objects": nobjs or "none",
|
||||
"num_characters": nchars or "no",
|
||||
"num_others": nothers or "no",
|
||||
}
|
||||
return pagevars
|
||||
|
||||
|
||||
class EvenniaIndexView(TemplateView):
|
||||
"""
|
||||
This is a basic example of a Django class-based view, which are functionally
|
||||
very similar to Evennia Commands but differ in structure. Commands are used
|
||||
to interface with users using a terminal client. Views are used to interface
|
||||
with users using a web browser.
|
||||
|
||||
To use a class-based view, you need to have written a template in HTML, and
|
||||
then you write a view like this to tell Django what values to display on it.
|
||||
|
||||
While there are simpler ways of writing views using plain functions (and
|
||||
Evennia currently contains a few examples of them), just like Commands,
|
||||
writing views as classes provides you with more flexibility-- you can extend
|
||||
classes and change things to suit your needs rather than having to copy and
|
||||
paste entire code blocks over and over. Django also comes with many default
|
||||
views for displaying things, all of them implemented as classes.
|
||||
|
||||
This particular example displays the index page.
|
||||
|
||||
"""
|
||||
|
||||
# Tell the view what HTML template to use for the page
|
||||
template_name = "website/index.html"
|
||||
|
||||
# This method tells the view what data should be displayed on the template.
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
This is a common Django method. Think of this as the website
|
||||
equivalent of the Evennia Command.func() method.
|
||||
|
||||
If you just want to display a static page with no customization, you
|
||||
don't need to define this method-- just create a view, define
|
||||
template_name and you're done.
|
||||
|
||||
The only catch here is that if you extend or overwrite this method,
|
||||
you'll always want to make sure you call the parent method to get a
|
||||
context object. It's just a dict, but it comes prepopulated with all
|
||||
sorts of background data intended for display on the page.
|
||||
|
||||
You can do whatever you want to it, but it must be returned at the end
|
||||
of this method.
|
||||
|
||||
Keyword Args:
|
||||
any (any): Passed through.
|
||||
|
||||
Returns:
|
||||
context (dict): Dictionary of data you want to display on the page.
|
||||
|
||||
"""
|
||||
# Always call the base implementation first to get a context object
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
# Add game statistics and other pagevars
|
||||
context.update(_gamestats())
|
||||
|
||||
return context
|
||||
91
evennia/web/website/views/mixins.py
Normal file
91
evennia/web/website/views/mixins.py
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
"""
|
||||
These are mixins for class-based views, granting functionality.
|
||||
|
||||
"""
|
||||
from django.views.generic import DetailView
|
||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||
|
||||
|
||||
class TypeclassMixin:
|
||||
"""
|
||||
This is a "mixin", a modifier of sorts.
|
||||
|
||||
Django views typically work with classes called "models." Evennia objects
|
||||
are an enhancement upon these Django models and are called "typeclasses."
|
||||
But Django itself has no idea what a "typeclass" is.
|
||||
|
||||
For the sake of mitigating confusion, any view class with this in its
|
||||
inheritance list will be modified to work with Evennia Typeclass objects or
|
||||
Django models interchangeably.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def typeclass(self):
|
||||
return self.model
|
||||
|
||||
@typeclass.setter
|
||||
def typeclass(self, value):
|
||||
self.model = value
|
||||
|
||||
|
||||
class EvenniaCreateView(CreateView, TypeclassMixin):
|
||||
"""
|
||||
This view extends Django's default CreateView.
|
||||
|
||||
CreateView is used for creating new objects, be they Accounts, Characters or
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def page_title(self):
|
||||
# Makes sure the page has a sensible title.
|
||||
return "Create %s" % self.typeclass._meta.verbose_name.title()
|
||||
|
||||
|
||||
class EvenniaDetailView(DetailView, TypeclassMixin):
|
||||
"""
|
||||
This view extends Django's default DetailView.
|
||||
|
||||
DetailView is used for displaying objects, be they Accounts, Characters or
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def page_title(self):
|
||||
# Makes sure the page has a sensible title.
|
||||
return "%s Detail" % self.typeclass._meta.verbose_name.title()
|
||||
|
||||
|
||||
class EvenniaUpdateView(UpdateView, TypeclassMixin):
|
||||
"""
|
||||
This view extends Django's default UpdateView.
|
||||
|
||||
UpdateView is used for updating objects, be they Accounts, Characters or
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def page_title(self):
|
||||
# Makes sure the page has a sensible title.
|
||||
return "Update %s" % self.typeclass._meta.verbose_name.title()
|
||||
|
||||
|
||||
class EvenniaDeleteView(DeleteView, TypeclassMixin):
|
||||
"""
|
||||
This view extends Django's default DeleteView.
|
||||
|
||||
DeleteView is used for deleting objects, be they Accounts, Characters or
|
||||
otherwise.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def page_title(self):
|
||||
# Makes sure the page has a sensible title.
|
||||
return "Delete %s" % self.typeclass._meta.verbose_name.title()
|
||||
|
||||
|
||||
268
evennia/web/website/views/objects.py
Normal file
268
evennia/web/website/views/objects.py
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
"""
|
||||
Views for managing a specific object)
|
||||
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.contrib import messages
|
||||
from evennia.utils import class_from_module
|
||||
from django.utils.text import slugify
|
||||
from .mixins import (
|
||||
EvenniaCreateView, EvenniaDeleteView, EvenniaUpdateView, EvenniaDetailView)
|
||||
|
||||
|
||||
class ObjectDetailView(EvenniaDetailView):
|
||||
"""
|
||||
This is an important view.
|
||||
|
||||
Any view you write that deals with displaying, updating or deleting a
|
||||
specific object will want to inherit from this. It provides the mechanisms
|
||||
by which to retrieve the object and make sure the user requesting it has
|
||||
permissions to actually *do* things to it.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
#
|
||||
# Choose what class of object this view will display. Note that this should
|
||||
# be an actual Python class (i.e. do `from typeclasses.characters import
|
||||
# Character`, then put `Character`), not an Evennia typeclass path
|
||||
# (i.e. `typeclasses.characters.Character`).
|
||||
#
|
||||
# So when you extend it, this line should look simple, like:
|
||||
# model = Object
|
||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS,
|
||||
fallback=settings.FALLBACK_OBJECT_TYPECLASS)
|
||||
|
||||
# What HTML template you wish to use to display this page.
|
||||
template_name = "website/object_detail.html"
|
||||
|
||||
# -- Evennia constructs --
|
||||
#
|
||||
# What lock type to check for the requesting user, authenticated or not.
|
||||
# https://github.com/evennia/evennia/wiki/Locks#valid-access_types
|
||||
access_type = "view"
|
||||
|
||||
# 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", "desc"]
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
"""
|
||||
Adds an 'attributes' list to the request context consisting of the
|
||||
attributes specified at the class level, and in the order provided.
|
||||
|
||||
Django views do not provide a way to reference dynamic attributes, so
|
||||
we have to grab them all before we render the template.
|
||||
|
||||
Returns:
|
||||
context (dict): Django context object
|
||||
|
||||
"""
|
||||
# Get the base Django context object
|
||||
context = super().get_context_data(**kwargs)
|
||||
|
||||
# Get the object in question
|
||||
obj = self.get_object()
|
||||
|
||||
# Create an ordered dictionary to contain the attribute map
|
||||
attribute_list = OrderedDict()
|
||||
|
||||
for attribute in self.attributes:
|
||||
# Check if the attribute is a core fieldname (name, desc)
|
||||
if attribute in self.typeclass._meta._property_names:
|
||||
attribute_list[attribute.title()] = getattr(obj, attribute, "")
|
||||
|
||||
# Check if the attribute is a db attribute (char1.db.favorite_color)
|
||||
else:
|
||||
attribute_list[attribute.title()] = getattr(obj.db, attribute, "")
|
||||
|
||||
# Add our attribute map to the Django request context, so it gets
|
||||
# displayed on the template
|
||||
context["attribute_list"] = attribute_list
|
||||
|
||||
# Return the comprehensive context object
|
||||
return context
|
||||
|
||||
def get_object(self, queryset=None):
|
||||
"""
|
||||
Override of Django hook that provides some important Evennia-specific
|
||||
functionality.
|
||||
|
||||
Evennia does not natively store slugs, so where a slug is provided,
|
||||
calculate the same for the object and make sure it matches.
|
||||
|
||||
This also checks to make sure the user has access to view/edit/delete
|
||||
this object!
|
||||
|
||||
"""
|
||||
# A queryset can be provided to pre-emptively limit what objects can
|
||||
# possibly be returned. For example, you can supply a queryset that
|
||||
# only returns objects whose name begins with "a".
|
||||
if not queryset:
|
||||
queryset = self.get_queryset()
|
||||
|
||||
# Get the object, ignoring all checks and filters for now
|
||||
obj = self.typeclass.objects.get(pk=self.kwargs.get("pk"))
|
||||
|
||||
# Check if this object was requested in a valid manner
|
||||
if slugify(obj.name) != self.kwargs.get(self.slug_url_kwarg):
|
||||
raise HttpResponseBadRequest(
|
||||
"No %(verbose_name)s found matching the query"
|
||||
% {"verbose_name": queryset.model._meta.verbose_name}
|
||||
)
|
||||
|
||||
# Check if the requestor account has permissions to access object
|
||||
account = self.request.user
|
||||
if not obj.access(account, self.access_type):
|
||||
raise PermissionDenied("You are not authorized to %s this object." % self.access_type)
|
||||
|
||||
# Get the object, if it is in the specified queryset
|
||||
obj = super(ObjectDetailView, self).get_object(queryset)
|
||||
|
||||
return obj
|
||||
|
||||
|
||||
class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView):
|
||||
"""
|
||||
This is an important view.
|
||||
|
||||
Any view you write that deals with creating a specific object will want to
|
||||
inherit from this. It provides the mechanisms by which to make sure the user
|
||||
requesting creation of an object is authenticated, and provides a sane
|
||||
default title for the page.
|
||||
|
||||
"""
|
||||
|
||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS,
|
||||
fallback=settings.FALLBACK_OBJECT_TYPECLASS)
|
||||
|
||||
|
||||
class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView, EvenniaDeleteView):
|
||||
"""
|
||||
This is an important view for obvious reasons!
|
||||
|
||||
Any view you write that deals with deleting a specific object will want to
|
||||
inherit from this. It provides the mechanisms by which to make sure the user
|
||||
requesting deletion of an object is authenticated, and that they have
|
||||
permissions to delete the requested object.
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS,
|
||||
fallback=settings.FALLBACK_OBJECT_TYPECLASS)
|
||||
template_name = "website/object_confirm_delete.html"
|
||||
|
||||
# -- Evennia constructs --
|
||||
access_type = "delete"
|
||||
|
||||
def delete(self, request, *args, **kwargs):
|
||||
"""
|
||||
Calls the delete() method on the fetched object and then
|
||||
redirects to the success URL.
|
||||
|
||||
We extend this so we can capture the name for the sake of confirmation.
|
||||
|
||||
"""
|
||||
# Get the object in question. ObjectDetailView.get_object() will also
|
||||
# check to make sure the current user (authenticated or not) has
|
||||
# permission to delete it!
|
||||
obj = str(self.get_object())
|
||||
|
||||
# Perform the actual deletion (the parent class handles this, which will
|
||||
# in turn call the delete() method on the object)
|
||||
response = super().delete(request, *args, **kwargs)
|
||||
|
||||
# Notify the user of the deletion
|
||||
messages.success(request, "Successfully deleted '%s'." % obj)
|
||||
return response
|
||||
|
||||
|
||||
class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView, EvenniaUpdateView):
|
||||
"""
|
||||
This is an important view.
|
||||
|
||||
Any view you write that deals with updating a specific object will want to
|
||||
inherit from this. It provides the mechanisms by which to make sure the user
|
||||
requesting editing of an object is authenticated, and that they have
|
||||
permissions to edit the requested object.
|
||||
|
||||
This functions slightly different from default Django UpdateViews in that
|
||||
it does not update core model fields, *only* object attributes!
|
||||
|
||||
"""
|
||||
|
||||
# -- Django constructs --
|
||||
model = class_from_module(settings.BASE_OBJECT_TYPECLASS,
|
||||
fallback=settings.FALLBACK_OBJECT_TYPECLASS)
|
||||
|
||||
# -- Evennia constructs --
|
||||
access_type = "edit"
|
||||
|
||||
def get_success_url(self):
|
||||
"""
|
||||
Django hook.
|
||||
|
||||
Can be overridden to return any URL you want to redirect the user to
|
||||
after the object is successfully updated, but by default it goes to the
|
||||
object detail page so the user can see their changes reflected.
|
||||
|
||||
"""
|
||||
if self.success_url:
|
||||
return self.success_url
|
||||
return self.object.web_get_detail_url()
|
||||
|
||||
def get_initial(self):
|
||||
"""
|
||||
Django hook, modified for Evennia.
|
||||
|
||||
Prepopulates the update form field values based on object db attributes.
|
||||
|
||||
Returns:
|
||||
data (dict): Dictionary of key:value pairs containing initial form
|
||||
data.
|
||||
|
||||
"""
|
||||
# Get the object we want to update
|
||||
obj = self.get_object()
|
||||
|
||||
# Get attributes
|
||||
data = {k: getattr(obj.db, k, "") for k in self.form_class.base_fields}
|
||||
|
||||
# Get model fields
|
||||
data.update({k: getattr(obj, k, "") for k in self.form_class.Meta.fields})
|
||||
|
||||
return data
|
||||
|
||||
def form_valid(self, form):
|
||||
"""
|
||||
Override of Django hook.
|
||||
|
||||
Updates object attributes based on values submitted.
|
||||
|
||||
This is run when the form is submitted and the data on it is deemed
|
||||
valid-- all values are within expected ranges, all strings contain
|
||||
valid characters and lengths, etc.
|
||||
|
||||
This method is only called if all values for the fields submitted
|
||||
passed form validation, so at this point we can assume the data is
|
||||
validated and sanitized.
|
||||
|
||||
"""
|
||||
# Get the attributes after they've been cleaned and validated
|
||||
data = {k: v for k, v in form.cleaned_data.items() if k not in self.form_class.Meta.fields}
|
||||
|
||||
# Update the object attributes
|
||||
for key, value in data.items():
|
||||
self.object.attributes.add(key, value)
|
||||
messages.success(self.request, "Successfully updated '%s' for %s." % (key, self.object))
|
||||
|
||||
# Do not return super().form_valid; we don't want to update the model
|
||||
# instance, just its attributes.
|
||||
return HttpResponseRedirect(self.get_success_url())
|
||||
42
evennia/web/website/views/views.py
Normal file
42
evennia/web/website/views/views.py
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
"""
|
||||
This file contains the generic, assorted views that don't fall under one of the other applications.
|
||||
Views are django's way of processing e.g. html templates on the fly.
|
||||
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.contrib.admin.sites import site
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.contrib.admin.views.decorators import staff_member_required
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models.functions import Lower
|
||||
from django.http import HttpResponseBadRequest, HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse_lazy
|
||||
from django.views.generic import TemplateView, ListView, DetailView
|
||||
from django.views.generic.base import RedirectView
|
||||
from django.views.generic.edit import CreateView, UpdateView, DeleteView
|
||||
|
||||
from evennia import SESSION_HANDLER
|
||||
from evennia.help.models import HelpEntry
|
||||
from evennia.objects.models import ObjectDB
|
||||
from evennia.accounts.models import AccountDB
|
||||
from evennia.utils import class_from_module
|
||||
from evennia.utils.logger import tail_log_file
|
||||
from . import forms
|
||||
|
||||
from django.utils.text import slugify
|
||||
|
||||
#
|
||||
# Channel views
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# Help views
|
||||
#
|
||||
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue