mirror of
https://github.com/evennia/evennia.git
synced 2026-03-16 21:06:30 +01:00
Merge pull request #1722 from strikaco/channelsurfing
Adds ChannelViews
This commit is contained in:
commit
cd3af403a7
10 changed files with 529 additions and 7 deletions
|
|
@ -2,6 +2,10 @@
|
|||
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.comms.models import TempMsg, ChannelDB
|
||||
from evennia.comms.managers import ChannelManager
|
||||
|
|
@ -622,3 +626,151 @@ class DefaultChannel(with_metaclass(TypeclassBase, ChannelDB)):
|
|||
|
||||
"""
|
||||
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,24 +23,26 @@ folder and edit it to add/remove links to the menu.
|
|||
<ul class="navbar-nav">
|
||||
{% block nabvar_left %}
|
||||
<li>
|
||||
<a class="nav-link" href="/">Home</a>
|
||||
<a class="nav-link" href="{% url 'index' %}">Home</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="nav-link" href="https://github.com/evennia/evennia/wiki/Evennia-Introduction/">About</a>
|
||||
</li>
|
||||
<li><a class="nav-link" href="https://github.com/evennia/evennia/wiki">Documentation</a></li>
|
||||
{% if user.is_staff %}
|
||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% endif %}
|
||||
<li><a class="nav-link" href="{% url 'channels' %}">Channels</a></li>
|
||||
<li><a class="nav-link" href="{% url 'help' %}">Help</a></li>
|
||||
{% if webclient_enabled %}
|
||||
<li><a class="nav-link" href="{% url 'webclient:index' %}">Play Online</a></li>
|
||||
{% endif %}
|
||||
{% if user.is_staff %}
|
||||
<li><a class="nav-link" href="{% url 'admin:index' %}">Admin</a></li>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
</ul>
|
||||
<ul class="nav navbar-nav ml-auto w-120 justify-content-end">
|
||||
{% block navbar_right %}
|
||||
{% endblock %}
|
||||
|
||||
{% block navbar_user %}
|
||||
{% if account %}
|
||||
<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 %}
|
||||
<!-- 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 -->
|
||||
<hr />
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
<div class="col-lg-3 col-sm-12">
|
||||
{% if user.is_staff %}
|
||||
<!-- 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 -->
|
||||
<hr />
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
{% load addclass %}
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="card mt-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h1 class="card-title">{{ view.page_title }}</h1>
|
||||
<hr />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ from django.conf import settings
|
|||
from django.utils.text import slugify
|
||||
from django.test import Client, override_settings
|
||||
from django.urls import reverse
|
||||
from evennia.utils import class_from_module
|
||||
from evennia.utils.test_resources import EvenniaTest
|
||||
|
||||
class EvenniaWebTest(EvenniaTest):
|
||||
|
|
@ -13,6 +14,7 @@ class EvenniaWebTest(EvenniaTest):
|
|||
exit_typeclass = settings.BASE_EXIT_TYPECLASS
|
||||
room_typeclass = settings.BASE_ROOM_TYPECLASS
|
||||
script_typeclass = settings.BASE_SCRIPT_TYPECLASS
|
||||
channel_typeclass = settings.BASE_CHANNEL_TYPECLASS
|
||||
|
||||
# Default named url
|
||||
url_name = 'index'
|
||||
|
|
@ -92,6 +94,25 @@ class PasswordResetTest(EvenniaWebTest):
|
|||
class WebclientTest(EvenniaWebTest):
|
||||
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):
|
||||
url_name = 'character-create'
|
||||
unauthenticated_response = 302
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ urlpatterns = [
|
|||
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"),
|
||||
|
||||
# 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
|
||||
url(r'^characters/create/$', website_views.CharacterCreateView.as_view(), name="character-create"),
|
||||
url(r'^characters/manage/$', website_views.CharacterManageView.as_view(), name="character-manage"),
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ 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, logger
|
||||
from evennia.utils.logger import tail_log_file
|
||||
from evennia.web.website.forms import *
|
||||
|
||||
from django.contrib.auth import login
|
||||
|
|
@ -713,6 +714,168 @@ class CharacterCreateView(CharacterMixin, ObjectCreateView):
|
|||
messages.error(self.request, "Your character could not be created.")
|
||||
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
|
||||
#
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue