Browse Source

changed hardcoded urls to reverse resolved ones, finally allowing m-d to run in a shared apache vhost.

Natenom/support-murmur-13-1446181288462
Michael Ziegler 16 years ago
parent
commit
bc85e76ed1
  1. 9
      pyweb/mumble/templatetags/mumble_extras.py
  2. 21
      pyweb/mumble/views.py
  3. 5
      pyweb/settings.py
  4. 9
      pyweb/urls.py
  5. 7
      pyweb/views.py
  6. 31
      template/index.htm
  7. 6
      template/mumble/channel.htm
  8. 2
      template/mumble/list.htm
  9. 28
      template/mumble/mumble.htm
  10. 14
      template/mumble/player.htm
  11. 2
      template/mumble/server.htm

9
pyweb/mumble/templatetags/mumble_extras.py

@ -16,9 +16,12 @@
from django import template from django import template
from django.template.loader import render_to_string from django.template.loader import render_to_string
from django.conf import settings
register = template.Library(); register = template.Library();
### FILTER: trunc -- converts "a very very extaordinary long text" to "a very very extra..." ### FILTER: trunc -- converts "a very very extaordinary long text" to "a very very extra..."
def trunc( string, maxlen = 50 ): def trunc( string, maxlen = 50 ):
if len(string) < maxlen: if len(string) < maxlen:
@ -31,11 +34,11 @@ register.filter( 'trunc', trunc );
### FILTER: chanview -- renders an mmChannel / mmPlayer object with the correct template. ### FILTER: chanview -- renders an mmChannel / mmPlayer object with the correct template.
def chanview( obj, user = None ): def chanview( obj, user = None ):
if obj.is_server: if obj.is_server:
return render_to_string( 'mumble/server.htm', { 'Server': obj, 'MumbleAccount': user } );
return render_to_string( 'mumble/server.htm', { 'Server': obj, 'MumbleAccount': user, 'media_url': settings.MEDIA_URL } );
elif obj.is_channel: elif obj.is_channel:
return render_to_string( 'mumble/channel.htm', { 'Channel': obj, 'MumbleAccount': user } );
return render_to_string( 'mumble/channel.htm', { 'Channel': obj, 'MumbleAccount': user, 'media_url': settings.MEDIA_URL } );
elif obj.is_player: elif obj.is_player:
return render_to_string( 'mumble/player.htm', { 'Player': obj, 'MumbleAccount': user } );
return render_to_string( 'mumble/player.htm', { 'Player': obj, 'MumbleAccount': user, 'media_url': settings.MEDIA_URL } );
register.filter( 'chanview', chanview ); register.filter( 'chanview', chanview );

21
pyweb/mumble/views.py

@ -16,6 +16,7 @@
import simplejson import simplejson
from StringIO import StringIO from StringIO import StringIO
from os.path import join
from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404 from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404
from django.template import RequestContext from django.template import RequestContext
@ -23,24 +24,30 @@ from django.http import Http404, HttpResponse, HttpResponseRedirect
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings from django.conf import settings
from django.core.urlresolvers import reverse
from django.contrib.auth import views as auth_views
from models import Mumble, MumbleUser from models import Mumble, MumbleUser
from forms import * from forms import *
from mmobjects import * from mmobjects import *
def redir( request ):
return HttpResponseRedirect( reverse( mumbles ) );
def mumbles( request ): def mumbles( request ):
"""Display a list of all configured Mumble servers, or redirects if only one configured.""" """Display a list of all configured Mumble servers, or redirects if only one configured."""
mumbles = get_list_or_404( Mumble ); mumbles = get_list_or_404( Mumble );
if len(mumbles) == 1: if len(mumbles) == 1:
return HttpResponseRedirect( '/mumble/%d' % mumbles[0].id );
return HttpResponseRedirect( reverse( show, kwargs={ 'server': mumbles[0].id, } ) );
return render_to_response( return render_to_response(
'mumble/list.htm', 'mumble/list.htm',
{ 'MumbleObjects': mumbles, { 'MumbleObjects': mumbles,
'MumbleActive': True, 'MumbleActive': True,
'media_url': settings.MEDIA_URL,
}, },
context_instance = RequestContext(request) context_instance = RequestContext(request)
); );
@ -65,7 +72,7 @@ def show( request, server ):
adminform = MumbleForm( request.POST, instance=srv ); adminform = MumbleForm( request.POST, instance=srv );
if adminform.is_valid(): if adminform.is_valid():
adminform.save(); adminform.save();
return HttpResponseRedirect( '/mumble/%d' % int(server) );
return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) );
else: else:
displayTab = 2; displayTab = 2;
else: else:
@ -95,14 +102,14 @@ def show( request, server ):
model.server = srv; model.server = srv;
model.owner = request.user; model.owner = request.user;
model.save(); model.save();
return HttpResponseRedirect( '/mumble/%d' % int(server) );
return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) );
else: else:
displayTab = 1; displayTab = 1;
else: else:
regform = MumbleUserForm( request.POST, instance=user ); regform = MumbleUserForm( request.POST, instance=user );
if regform.is_valid(): if regform.is_valid():
regform.save(); regform.save();
return HttpResponseRedirect( '/mumble/%d' % int(server) );
return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) );
else: else:
displayTab = 1; displayTab = 1;
else: else:
@ -118,7 +125,7 @@ def show( request, server ):
textureform = MumbleTextureForm( request.POST, request.FILES ); textureform = MumbleTextureForm( request.POST, request.FILES );
if textureform.is_valid(): if textureform.is_valid():
user.setTexture( request.FILES['texturefile'] ); user.setTexture( request.FILES['texturefile'] );
return HttpResponseRedirect( '/mumble/%d' % int(server) );
return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) );
else: else:
textureform = MumbleTextureForm(); textureform = MumbleTextureForm();
@ -134,10 +141,14 @@ def show( request, server ):
for id in srv.players: for id in srv.players:
channelTable.append( srv.players[id] ); channelTable.append( srv.players[id] );
show_url = reverse( show, kwargs={ 'server': srv.id } );
login_url = reverse( auth_views.login );
return render_to_response( return render_to_response(
'mumble/mumble.htm', 'mumble/mumble.htm',
{ {
'media_url': settings.MEDIA_URL,
'login_url': "%s?next=%s" % ( login_url, show_url ),
'DBaseObject': srv, 'DBaseObject': srv,
'ChannelTable': channelTable, 'ChannelTable': channelTable,
'CurrentUserIsAdmin': isAdmin, 'CurrentUserIsAdmin': isAdmin,

5
pyweb/settings.py

@ -36,6 +36,7 @@ MUMBLE_DJANGO_ROOT = None; ##
################################################################# #################################################################
from django.core.urlresolvers import get_script_prefix
from os.path import join, dirname, abspath, exists from os.path import join, dirname, abspath, exists
if not MUMBLE_DJANGO_ROOT or not exists( MUMBLE_DJANGO_ROOT ): if not MUMBLE_DJANGO_ROOT or not exists( MUMBLE_DJANGO_ROOT ):
MUMBLE_DJANGO_ROOT = dirname(dirname(abspath(__file__))); MUMBLE_DJANGO_ROOT = dirname(dirname(abspath(__file__)));
@ -119,12 +120,12 @@ MEDIA_ROOT = join( MUMBLE_DJANGO_ROOT, 'htdocs' )
# URL that handles the media served from MEDIA_ROOT. Make sure to use a # URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash if there is a path component (optional in other cases). # trailing slash if there is a path component (optional in other cases).
# Examples: "http://media.lawrence.com", "http://example.com/media/" # Examples: "http://media.lawrence.com", "http://example.com/media/"
MEDIA_URL = ''
MEDIA_URL = join( get_script_prefix(), 'static' );
# URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
# trailing slash. # trailing slash.
# Examples: "http://foo.com/media/", "/media/". # Examples: "http://foo.com/media/", "/media/".
ADMIN_MEDIA_PREFIX = '/media/'
ADMIN_MEDIA_PREFIX = join( get_script_prefix(), 'media' );
# Make this unique, and don't share it with anybody. # Make this unique, and don't share it with anybody.
SECRET_KEY = 'u-mp185msk#z4%s(do2^5405)y5d!9adbn92)apu_p^qvqh10v' SECRET_KEY = 'u-mp185msk#z4%s(do2^5405)y5d!9adbn92)apu_p^qvqh10v'

9
pyweb/urls.py

@ -22,8 +22,9 @@ admin.autodiscover()
from django.conf import settings from django.conf import settings
urlpatterns = patterns('', urlpatterns = patterns('',
(r'^/?$', 'django.views.generic.simple.redirect_to', { 'url': '/mumble/' } ),
(r'^/?$', 'mumble.views.redir' ),
# Uncomment the admin/doc line below and add 'django.contrib.admindocs' # Uncomment the admin/doc line below and add 'django.contrib.admindocs'
# to INSTALLED_APPS to enable admin documentation: # to INSTALLED_APPS to enable admin documentation:
@ -36,12 +37,14 @@ urlpatterns = patterns('',
(r'^mumble/', include('mumble.urls')), (r'^mumble/', include('mumble.urls')),
# Uncomment the next line to enable the admin: # Uncomment the next line to enable the admin:
(r'^admin/(.*)', admin.site.root),
(r'^admin/', admin.site.urls),
) )
# Development stuff # Development stuff
if settings.DEBUG: if settings.DEBUG:
urlpatterns += patterns('', urlpatterns += patterns('',
(r'^static/(?P<path>.*)$', 'django.views.static.serve', {'document_root': settings.MEDIA_ROOT, 'show_indexes': True} ),
(r'^%s/(?P<path>.*)$' % settings.MEDIA_URL[1:],
'django.views.static.serve',
{'document_root': settings.MEDIA_ROOT, 'show_indexes': True} ),
) )

7
pyweb/views.py

@ -20,6 +20,7 @@ from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings
from mumble.models import Mumble, MumbleUser from mumble.models import Mumble, MumbleUser
#from guestbook.models import Entry, Comment #from guestbook.models import Entry, Comment
@ -29,6 +30,7 @@ from mumble.models import Mumble, MumbleUser
def profile( request ): def profile( request ):
userdata = { userdata = {
"ProfileActive": True, "ProfileActive": True,
'media_url': settings.MEDIA_URL,
"mumbleaccs": MumbleUser.objects.filter( owner = request.user ), "mumbleaccs": MumbleUser.objects.filter( owner = request.user ),
# "gbposts": Entry.objects.filter( author = request.user ).count(), # "gbposts": Entry.objects.filter( author = request.user ).count(),
# "gbcomments": Comment.objects.filter( author = request.user ).count(), # "gbcomments": Comment.objects.filter( author = request.user ).count(),
@ -43,4 +45,7 @@ def profile( request ):
def imprint( request ): def imprint( request ):
return render_to_response( 'registration/imprint.html', {}, context_instance = RequestContext(request) );
return render_to_response(
'registration/imprint.html',
{ 'media_url': settings.MEDIA_URL, },
context_instance = RequestContext(request) );

31
template/index.htm

@ -3,15 +3,15 @@
<title>Mumble Administration</title> <title>Mumble Administration</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<link rel="stylesheet" type="text/css" href="/static/ext-2.2/resources/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="/static/style.css" />
<link rel="stylesheet" type="text/css" href="/static/templatestyle.css" />
<link rel="stylesheet" type="text/css" href="/static/mumble/style.css" />
<link rel="shortcut icon" type="image/png" href="/static/mumble/mumble.16x16.png" />
<link rel="stylesheet" type="text/css" href="{{ media_url }}/ext-2.2/resources/css/ext-all.css" />
<link rel="stylesheet" type="text/css" href="{{ media_url }}/style.css" />
<link rel="stylesheet" type="text/css" href="{{ media_url }}/templatestyle.css" />
<link rel="stylesheet" type="text/css" href="{{ media_url }}/mumble/style.css" />
<link rel="shortcut icon" type="image/png" href="{{ media_url }}/mumble/mumble.16x16.png" />
<script type="text/javascript" src="/static/ext-2.2/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="/static/ext-2.2/ext-all.js"></script>
<script type="text/javascript" src="/static/checkcolumn.js"></script>
<script type="text/javascript" src="{{ media_url }}/ext-2.2/adapter/ext/ext-base.js"></script>
<script type="text/javascript" src="{{ media_url }}/ext-2.2/ext-all.js"></script>
<script type="text/javascript" src="{{ media_url }}/checkcolumn.js"></script>
{% block HeadTag %} {% block HeadTag %}
{% endblock %} {% endblock %}
@ -22,28 +22,28 @@
<div id="headpanel"> <div id="headpanel">
<div id="headlinks"> <div id="headlinks">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<a href="/accounts/logout">Logout</a> |
<a href="{% url django.contrib.auth.views.logout %}">Logout</a> |
{% else %} {% else %}
<a href="/accounts/login">Login</a> |
<a href="{% url django.contrib.auth.views.login %}">Login</a> |
{% endif %} {% endif %}
<a href="/admin" target="_blank">Admin</a> |
<a href="/accounts/imprint">Imprint</a>
<a href="{% url admin:index %}" target="_blank">Admin</a> |
<a href="{% url views.imprint %}">Imprint</a>
</div> </div>
<h2>{% block Headline %}{% endblock %}</h2> <h2>{% block Headline %}{% endblock %}</h2>
</div> </div>
<!-- navi --> <!-- navi -->
<div id="navipanel"> <div id="navipanel">
<ul id="navilinks"> <ul id="navilinks">
<li><a href="/">Home</a></li>
<li><a href="{% url mumble.views.redir %}">Home</a></li>
{% if MumbleActive %} {% if MumbleActive %}
<li><b>Mumble</b></li> <li><b>Mumble</b></li>
{% else %} {% else %}
<li><a href="/mumble">Mumble</a></li>
<li><a href="{% url mumble.views.mumbles %}">Mumble</a></li>
{% endif %} {% endif %}
{% if ProfileActive %} {% if ProfileActive %}
<li><b>Profile</b></li> <li><b>Profile</b></li>
{% else %} {% else %}
<li><a href="/accounts/profile">Profile</a></li>
<li><a href="{% url views.profile %}">Profile</a></li>
{% endif %} {% endif %}
</ul> </ul>
</div> </div>
@ -66,4 +66,3 @@
</div> </div>
</body> </body>
</html> </html>

6
template/mumble/channel.htm

@ -1,10 +1,10 @@
{% load mumble_extras %} {% load mumble_extras %}
<div class="mumble"> <div class="mumble">
<img src="/static/mumble/knoten_v.png" />
<img src="{{ media_url }}/mumble/knoten_v.png" />
{% if Channel.linked %} {% if Channel.linked %}
<img src="/static/mumble/channel_linked.png" alt="linked channel" />
<img src="{{ media_url }}/mumble/channel_linked.png" alt="linked channel" />
{% else %} {% else %}
<img src="/static/mumble/channel.png" alt="channel" />
<img src="{{ media_url }}/mumble/channel.png" alt="channel" />
{% endif %} {% endif %}
<a href="{{ Channel|chanurl:MumbleAccount }}" class="mumble" id="link_{{ Channel.id }}" title="{{ Channel.name }}"> <a href="{{ Channel|chanurl:MumbleAccount }}" class="mumble" id="link_{{ Channel.id }}" title="{{ Channel.name }}">
{{ Channel.name|trunc:30 }} {{ Channel.name|trunc:30 }}

2
template/mumble/list.htm

@ -7,7 +7,7 @@ Configured Mumble Servers
<div class="rahmen"> <div class="rahmen">
<ul> <ul>
{% for mumble in MumbleObjects %} {% for mumble in MumbleObjects %}
<li><a href="/mumble/{{mumble.id}}/">{{mumble.name}}</a></li>
<li><a href="{% url mumble.views.show mumble.id %}">{{mumble.name}}</a></li>
{% endfor %} {% endfor %}
</ul> </ul>
</div> </div>

28
template/mumble/mumble.htm

@ -10,12 +10,12 @@
{% block Content %} {% block Content %}
<noscript> <noscript>
<p> <p>
{% blocktrans %}
{% blocktrans %}
<b>Hint:</b><br /> <b>Hint:</b><br />
This area is used to display additional information for each channel and player, but requires JavaScript to be This area is used to display additional information for each channel and player, but requires JavaScript to be
displayed correctly. You will not see the detail pages, but you can use all links and forms displayed correctly. You will not see the detail pages, but you can use all links and forms
that are displayed. that are displayed.
{% endblocktrans %}
{% endblocktrans %}
</p> </p>
</noscript> </noscript>
<div id="mumble_ext_container"></div> <div id="mumble_ext_container"></div>
@ -35,7 +35,7 @@
<div id="mumble_registration" class="mumble-ext"> <div id="mumble_registration" class="mumble-ext">
{% if user.is_authenticated %} {% if user.is_authenticated %}
<h2>{% trans "Server registration" %}</h2> <h2>{% trans "Server registration" %}</h2>
<form action="" method="post">
<form action="{% url mumble.views.show DBaseObject.id %}" method="post">
{% if Registered %} {% if Registered %}
{% trans "You are registered on this server" %}.<br /> {% trans "You are registered on this server" %}.<br />
{% else %} {% else %}
@ -48,9 +48,9 @@
<input type="submit" /> <input type="submit" />
</form> </form>
{% else %} {% else %}
{% blocktrans with DBaseObject.id as serverid %}
<p>You need to be <a href="/accounts/login?next=%2Fmumble%2F{{ serverid }}">logged in</a> to be able to register an account on this Mumble server.</p>
{% endblocktrans %}
{% blocktrans %}
<p>You need to be <a href="{{ login_url }}">logged in</a> to be able to register an account on this Mumble server.</p>
{% endblocktrans %}
{% endif %} {% endif %}
</div> </div>
@ -58,15 +58,17 @@
<div id="mumble_texture" class="mumble-ext"> <div id="mumble_texture" class="mumble-ext">
<h2>{% trans "User Texture" %}</h2> <h2>{% trans "User Texture" %}</h2>
<p> <p>
{% blocktrans with DBaseObject.id as serverid %}
{% blocktrans with DBaseObject.id as serverid %}
You can upload an image that you would like to use as your user texture here.<br /> You can upload an image that you would like to use as your user texture here.<br />
Your current texture is:<br /> Your current texture is:<br />
<img src="/mumble/{{serverid}}/texture.png" alt="user texture" /><br />
{% endblocktrans %}
<img src="{% url mumble.views.showTexture DBaseObject.id MumbleAccount.id %}" alt="user texture" /><br />
{% blocktrans with DBaseObject.id as serverid %}
Hint: The texture image <b>needs</b> to be 600x60 in size. If you upload an image with Hint: The texture image <b>needs</b> to be 600x60 in size. If you upload an image with
a different size, it will be resized accordingly.<br /> a different size, it will be resized accordingly.<br />
{% endblocktrans %}
{% endblocktrans %}
</p> </p>
<form action="" method="post" enctype="multipart/form-data">
<form action="{% url mumble.views.show DBaseObject.id %}" method="post" enctype="multipart/form-data">
<table> <table>
{{ TextureForm }} {{ TextureForm }}
</table> </table>
@ -79,7 +81,7 @@
{% if CurrentUserIsAdmin %} {% if CurrentUserIsAdmin %}
<div id="mumble_admin" class="mumble-ext"> <div id="mumble_admin" class="mumble-ext">
<h2>{% trans "Server administration" %}</h2> <h2>{% trans "Server administration" %}</h2>
<form action="" method="post">
<form action="{% url mumble.views.show DBaseObject.id %}" method="post">
<table> <table>
{{ AdminForm }} {{ AdminForm }}
</table> </table>
@ -113,7 +115,7 @@
</ul> </ul>
{% endif %} {% endif %}
<h2>{% trans "User Texture" %}</h2> <h2>{% trans "User Texture" %}</h2>
<img src="/mumble/{{ DBaseObject.id }}/{{ item.mumbleuser.id }}/texture.png" alt="user texture" />
<img src="{% url mumble.views.showTexture DBaseObject.id MumbleAccount.id %}" alt="user texture" />
</div> </div>
{% else %} {% else %}
<div id="mumble_{{ item.id }}" class="mumble-ext x-hide-display"> <div id="mumble_{{ item.id }}" class="mumble-ext x-hide-display">
@ -184,7 +186,7 @@
valueField: 'uid', valueField: 'uid',
displayField: 'uname', displayField: 'uname',
store: new Ext.data.Store({ store: new Ext.data.Store({
url: '/mumble/djangousers',
url: '{% url mumble.views.users DBaseObject.id %}',
reader: new Ext.data.JsonReader({ reader: new Ext.data.JsonReader({
fields: [ 'uid', 'uname' ], fields: [ 'uid', 'uname' ],
root: 'objects', root: 'objects',

14
template/mumble/player.htm

@ -2,24 +2,24 @@
<div class="mumble"> <div class="mumble">
<span class="mumble"> <span class="mumble">
{% if Player.isAuthed %} {% if Player.isAuthed %}
<img src="/static/mumble/authenticated.png" alt="authed" title="Authenticated" />
<img src="{{ media_url }}/mumble/authenticated.png" alt="authed" title="Authenticated" />
{% endif %} {% endif %}
{% if Player.muted or Player.suppressed %} {% if Player.muted or Player.suppressed %}
<img src="/static/mumble/muted_server.png" alt="muted" title="Muted by server" />
<img src="{{ media_url }}/mumble/muted_server.png" alt="muted" title="Muted by server" />
{% endif %} {% endif %}
{% if Player.deafened %} {% if Player.deafened %}
<img src="/static/mumble/deafened_server.png" alt="deafened" title="Deafened by server" />
<img src="{{ media_url }}/mumble/deafened_server.png" alt="deafened" title="Deafened by server" />
{% endif %} {% endif %}
{% if Player.selfmuted %} {% if Player.selfmuted %}
<img src="/static/mumble/muted_self.png" alt="self-muted" title="Muted by themselves" />
<img src="{{ media_url }}/mumble/muted_self.png" alt="self-muted" title="Muted by themselves" />
{% endif %} {% endif %}
{% if Player.selfdeafened %} {% if Player.selfdeafened %}
<img src="/static/mumble/deafened_self.png" alt="self-deafened" title="Deafened by themselves" />
<img src="{{ media_url }}/mumble/deafened_self.png" alt="self-deafened" title="Deafened by themselves" />
{% endif %} {% endif %}
</span> </span>
<span> <span>
<img src="/static/mumble/knoten_v.png" />
<img src="/static/mumble/talking_off.png" alt="Player" />
<img src="{{ media_url }}/mumble/knoten_v.png" />
<img src="{{ media_url }}/mumble/talking_off.png" alt="Player" />
<a id="link_{{ Player.id }}" class="mumble" href="#" title="{{ Player.name }}">{{ Player.name|trunc:30 }}</a> <a id="link_{{ Player.id }}" class="mumble" href="#" title="{{ Player.name }}">{{ Player.name|trunc:30 }}</a>
</span> </span>
</div> </div>

2
template/mumble/server.htm

@ -1,6 +1,6 @@
{% load mumble_extras %} {% load mumble_extras %}
<div style="margin-left: 20px"> <div style="margin-left: 20px">
<img src="/static/mumble/mumble.16x16.png" alt="server" />
<img src="{{ media_url }}/mumble/mumble.16x16.png" alt="server" />
<a class="mumble" id="link_server" href="{{ Server|chanurl:MumbleAccount }}">{{ Server.name|trunc:30 }}</a> <a class="mumble" id="link_server" href="{{ Server|chanurl:MumbleAccount }}">{{ Server.name|trunc:30 }}</a>
</div> </div>
{% for sub in Server.rootchan.subchans %} {% for sub in Server.rootchan.subchans %}

Loading…
Cancel
Save