"""
Views for manipulating Characters (children of Objects often used for
puppeting).
"""
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.db.models.functions import Lower
from django.http import HttpResponseRedirect
from django.urls import reverse_lazy
from django.utils.encoding import iri_to_uri
from django.utils.http import url_has_allowed_host_and_scheme
from django.views.generic import ListView
from django.views.generic.base import RedirectView
from evennia.utils import class_from_module
from evennia.web.website import forms
from .mixins import TypeclassMixin
from .objects import (
ObjectCreateView,
ObjectDeleteView,
ObjectDetailView,
ObjectUpdateView,
)
[docs]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")
[docs] 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"))
[docs]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"
[docs] 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"))
[docs]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.
"""
[docs] 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)
# since next_page is untrusted input from the user, we need to check it's safe to
next_page = iri_to_uri(next_page)
if not url_has_allowed_host_and_scheme(
url=next_page,
allowed_hosts={self.request.get_host()},
require_https=self.request.is_secure(),
):
next_page = 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
[docs]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"
[docs]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"
[docs]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"
[docs] 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"))
[docs]class CharacterDeleteView(CharacterMixin, ObjectDeleteView):
"""
This view provides a mechanism by which a logged-in player (enforced by
ObjectDeleteView) can delete a character they own.
"""
# using the character form fails there
form_class = forms.EvenniaForm
[docs]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"